From 9a172a280fd5cee146deba051119ea99ce1c23a6 Mon Sep 17 00:00:00 2001 From: Boris Serdiuk Date: Mon, 4 Nov 2024 14:50:44 +0100 Subject: [PATCH 1/3] feat: Allow more drawer properties to be updated in the runtime API --- .../controllers/__tests__/drawers.test.ts | 13 ++++++ src/internal/plugins/controllers/drawers.ts | 44 ++++++++++--------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/internal/plugins/controllers/__tests__/drawers.test.ts b/src/internal/plugins/controllers/__tests__/drawers.test.ts index 2ac9631596..dc83c199d3 100644 --- a/src/internal/plugins/controllers/__tests__/drawers.test.ts +++ b/src/internal/plugins/controllers/__tests__/drawers.test.ts @@ -72,6 +72,19 @@ describe('update drawer', () => { expect(onDrawersRegistered).toHaveBeenLastCalledWith([updatedDrawer, drawerB]); }); + test('does not update an unknown property', async () => { + const onDrawersRegistered = jest.fn(); + const drawers = new DrawersController(); + drawers.onDrawersRegistered(onDrawersRegistered); + drawers.registerDrawer(drawerA); + await delay(); + expect(onDrawersRegistered).toHaveBeenCalledWith([drawerA]); + const updatedDrawer = { ...drawerA, type: 'global' }; + drawers.updateDrawer(updatedDrawer); + await delay(); + expect(onDrawersRegistered).toHaveBeenLastCalledWith([drawerA]); + }); + test('throw error if the update drawer is not registered', () => { const drawers = new DrawersController(); expect(() => drawers.updateDrawer({ id: 'test-drawer' } as UpdateDrawerConfig)).toThrowError( diff --git a/src/internal/plugins/controllers/drawers.ts b/src/internal/plugins/controllers/drawers.ts index b63268c802..c7ff534d57 100644 --- a/src/internal/plugins/controllers/drawers.ts +++ b/src/internal/plugins/controllers/drawers.ts @@ -32,7 +32,18 @@ export interface DrawerConfig { preserveInactiveContent?: boolean; } -export type UpdateDrawerConfig = Pick; +const updatableProperties = [ + 'badge', + 'resizable', + 'defaultSize', + 'orderPriority', + 'defaultActive', + 'onResize', +] as const; + +export type UpdateDrawerConfig = { id: DrawerConfig['id'] } & Partial< + Pick +>; export type DrawersRegistrationListener = (drawers: Array) => void; @@ -67,29 +78,22 @@ export class DrawersController { this.scheduleUpdate(); }; - updateDrawer = (config: UpdateDrawerConfig) => { - const { id: drawerId, resizable, badge, defaultSize } = config; + updateDrawer = ({ id: drawerId, ...rest }: UpdateDrawerConfig) => { const drawerIndex = this.drawers.findIndex(({ id }) => id === drawerId); const oldDrawerConfig = this.drawers?.[drawerIndex]; - if (drawerIndex >= 0 && oldDrawerConfig) { - const drawers = this.drawers.slice(); - const drawerConfig = { ...oldDrawerConfig }; - if (typeof resizable === 'boolean') { - drawerConfig.resizable = resizable; - } - if (typeof badge === 'boolean') { - drawerConfig.badge = badge; - } - if (typeof defaultSize === 'number') { - drawerConfig.defaultSize = defaultSize; - } - - drawers[drawerIndex] = drawerConfig; - this.drawers = drawers; - this.scheduleUpdate(); - } else { + if (!oldDrawerConfig) { throw new Error(`[AwsUi] [runtime drawers] drawer with id ${drawerId} not found`); } + const drawers = this.drawers.slice(); + const updatedDrawer = { ...oldDrawerConfig }; + for (const key of updatableProperties) { + if (key in rest) { + updatedDrawer[key] = (rest as any)[key]; + } + } + drawers[drawerIndex] = updatedDrawer; + this.drawers = drawers; + this.scheduleUpdate(); }; onDrawersRegistered = (listener: DrawersRegistrationListener) => { From 8eb6d5f68268913b928af01c0812882f64b3c78f Mon Sep 17 00:00:00 2001 From: Boris Serdiuk Date: Mon, 4 Nov 2024 17:01:13 +0100 Subject: [PATCH 2/3] demo dev page --- .../disable-paddings-breadcrumbs.page.tsx | 3 +- ...untime-drawers-persist-open-state.page.tsx | 64 +++++++++++++++++++ pages/app-layout/stateful.page.tsx | 17 +---- pages/app-layout/styles.scss | 4 -- pages/app-layout/utils/content-blocks.tsx | 12 ++++ pages/app-layout/utils/external-widget.tsx | 30 +++++---- 6 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 pages/app-layout/runtime-drawers-persist-open-state.page.tsx diff --git a/pages/app-layout/disable-paddings-breadcrumbs.page.tsx b/pages/app-layout/disable-paddings-breadcrumbs.page.tsx index e9d78921b7..aa1a396897 100644 --- a/pages/app-layout/disable-paddings-breadcrumbs.page.tsx +++ b/pages/app-layout/disable-paddings-breadcrumbs.page.tsx @@ -1,7 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import clsx from 'clsx'; import AppLayout from '~components/app-layout'; import Box from '~components/box'; @@ -17,7 +16,7 @@ export default function () { Breadcrumbs} + breadcrumbs={
Breadcrumbs
} notifications={
Notifications diff --git a/pages/app-layout/runtime-drawers-persist-open-state.page.tsx b/pages/app-layout/runtime-drawers-persist-open-state.page.tsx new file mode 100644 index 0000000000..d23fdcb162 --- /dev/null +++ b/pages/app-layout/runtime-drawers-persist-open-state.page.tsx @@ -0,0 +1,64 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { useState } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import AppLayout from '~components/app-layout'; +import Header from '~components/header'; +import HelpPanel from '~components/help-panel'; +import awsuiPlugins from '~components/internal/plugins'; + +import { Breadcrumbs, Counter } from './utils/content-blocks'; +import appLayoutLabels from './utils/labels'; + +awsuiPlugins.appLayout.registerDrawer({ + id: 'runtime-drawer-persist-open-state', + type: 'global', + + trigger: { + iconSvg: ` + + + `, + }, + + ariaLabels: { + closeButton: 'Close button', + content: 'Content', + triggerButton: 'Trigger button', + resizeHandle: 'Resize handle', + }, + + mountContent: (container, { onVisibilityChange }) => { + awsuiPlugins.appLayout.updateDrawer({ id: 'runtime-drawer-persist-open-state', defaultActive: true }); + onVisibilityChange(isVisible => { + awsuiPlugins.appLayout.updateDrawer({ id: 'runtime-drawer-persist-open-state', defaultActive: isVisible }); + }); + render(, container); + }, + unmountContent: container => { + unmountComponentAtNode(container); + }, +}); + +export default function () { + const [key, setKey] = useState(0); + return ( + } + content={ + <> +
+ Drawer with state persistence +
+ + + } + tools={Info}>Here is some info for you} + /> + ); +} diff --git a/pages/app-layout/stateful.page.tsx b/pages/app-layout/stateful.page.tsx index 3f34f9abc3..c9c3d00aa2 100644 --- a/pages/app-layout/stateful.page.tsx +++ b/pages/app-layout/stateful.page.tsx @@ -1,26 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { useState } from 'react'; +import React from 'react'; import AppLayout from '~components/app-layout'; import Header from '~components/header'; +import { Counter } from './utils/content-blocks'; import labels from './utils/labels'; -import styles from './styles.scss'; - -function Counter({ id }: { id: string }) { - const [count, setCount] = useState(0); - return ( -
- Clicked: {count} - -
- ); -} - export default function AppLayoutStatefulDemo() { return ( ); } + +export function Counter({ id }: { id: string }) { + const [count, setCount] = useState(0); + return ( +
+ Clicked: {count} + +
+ ); +} diff --git a/pages/app-layout/utils/external-widget.tsx b/pages/app-layout/utils/external-widget.tsx index dad5c3ffc8..77cc02bd18 100644 --- a/pages/app-layout/utils/external-widget.tsx +++ b/pages/app-layout/utils/external-widget.tsx @@ -6,6 +6,8 @@ import ReactDOM, { unmountComponentAtNode } from 'react-dom'; import Drawer from '~components/drawer'; import awsuiPlugins from '~components/internal/plugins'; +import { Counter } from './content-blocks'; + const searchParams = new URL(location.hash.substring(1), location.href).searchParams; const Content = React.forwardRef((props, ref) => { @@ -82,18 +84,6 @@ awsuiPlugins.appLayout.registerDrawer({ unmountContent: container => unmountComponentAtNode(container), }); -const Counter: React.FC = ({ children }) => { - const [count, setCount] = useState(0); - - return ( -
- {count} - - {children} -
- ); -}; - const AutoIncrementCounter: React.FC<{ onVisibilityChange?: (callback: (isVisible: boolean) => void) => void; }> = ({ children, onVisibilityChange }) => { @@ -186,7 +176,13 @@ awsuiPlugins.appLayout.registerDrawer({ }, mountContent: container => { - ReactDOM.render(global widget content circle 2, container); + ReactDOM.render( + <> + + global widget content circle 2 + , + container + ); }, unmountContent: container => unmountComponentAtNode(container), }); @@ -213,7 +209,13 @@ awsuiPlugins.appLayout.registerDrawer({ }, mountContent: container => { - ReactDOM.render(global widget content circle 3, container); + ReactDOM.render( + <> + + global widget content circle 3 + , + container + ); }, unmountContent: container => unmountComponentAtNode(container), }); From b03c46c01a2247f8759e17c88b27b79995ce3c8e Mon Sep 17 00:00:00 2001 From: Georgii Lobko Date: Thu, 14 Nov 2024 17:05:20 +0100 Subject: [PATCH 3/3] chore: Add defaultActive to unit test data --- src/internal/plugins/controllers/__tests__/drawers.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/plugins/controllers/__tests__/drawers.test.ts b/src/internal/plugins/controllers/__tests__/drawers.test.ts index dc83c199d3..21160fa7a8 100644 --- a/src/internal/plugins/controllers/__tests__/drawers.test.ts +++ b/src/internal/plugins/controllers/__tests__/drawers.test.ts @@ -66,7 +66,7 @@ describe('update drawer', () => { expect(onDrawersRegistered).not.toHaveBeenCalled(); await delay(); expect(onDrawersRegistered).toHaveBeenCalledWith([drawerA, drawerB]); - const updatedDrawer = { ...drawerA, badge: true }; + const updatedDrawer = { ...drawerA, badge: true, defaultActive: true }; drawers.updateDrawer(updatedDrawer); await delay(); expect(onDrawersRegistered).toHaveBeenLastCalledWith([updatedDrawer, drawerB]);