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 0cf2090b05..5ea723dfda 100644 --- a/pages/app-layout/utils/external-widget.tsx +++ b/pages/app-layout/utils/external-widget.tsx @@ -6,7 +6,7 @@ import ReactDOM, { unmountComponentAtNode } from 'react-dom'; import Drawer from '~components/drawer'; import awsuiPlugins from '~components/internal/plugins'; -import { CustomDrawerContent } from './content-blocks'; +import { Counter, CustomDrawerContent } from './content-blocks'; const searchParams = new URL(location.hash.substring(1), location.href).searchParams; @@ -84,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 }) => { @@ -192,7 +180,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), }); @@ -219,7 +213,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), }); diff --git a/src/file-upload/internal.tsx b/src/file-upload/internal.tsx index c6bd6760fa..460f9d029e 100644 --- a/src/file-upload/internal.tsx +++ b/src/file-upload/internal.tsx @@ -9,9 +9,9 @@ import { warnOnce } from '@cloudscape-design/component-toolkit/internal'; import InternalBox from '../box/internal'; import { ButtonProps } from '../button/interfaces'; import { useFormFieldContext } from '../contexts/form-field'; -import InternalFileInput from '../file-input/internal'; import InternalFileDropzone from '../file-dropzone/internal'; import { useFilesDragging } from '../file-dropzone/use-files-dragging'; +import InternalFileInput from '../file-input/internal'; import InternalFileTokenGroup from '../file-token-group/internal'; import { ConstraintText, FormFieldError, FormFieldWarning } from '../form-field/internal'; import { getBaseProps } from '../internal/base-component'; diff --git a/src/internal/plugins/controllers/__tests__/drawers.test.ts b/src/internal/plugins/controllers/__tests__/drawers.test.ts index 2ac9631596..21160fa7a8 100644 --- a/src/internal/plugins/controllers/__tests__/drawers.test.ts +++ b/src/internal/plugins/controllers/__tests__/drawers.test.ts @@ -66,12 +66,25 @@ 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]); }); + 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) => {