Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow more drawer properties to be updated in the runtime API #2979

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions pages/app-layout/disable-paddings-breadcrumbs.page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -17,7 +16,7 @@ export default function () {
<AppLayout
ariaLabels={labels}
disableContentPaddings={true}
breadcrumbs={<div className={clsx(styles.highlightBorder, styles.textContent)}>Breadcrumbs</div>}
breadcrumbs={<div className={styles.highlightBorder}>Breadcrumbs</div>}
notifications={
<div className={styles.highlightBorder}>
<Box variant="h2">Notifications</Box>
Expand Down
64 changes: 64 additions & 0 deletions pages/app-layout/runtime-drawers-persist-open-state.page.tsx
Original file line number Diff line number Diff line change
@@ -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: `<svg viewBox="0 0 16 16" focusable="false">
<circle stroke-width="2" stroke="currentColor" fill="none" cx="8" cy="8" r="7" />
<rect fill="currentColor" x="5" y="5" width="6" height="6" />
</svg>`,
},

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(<Counter id="runtime-drawer-persist-open-state" />, container);
},
unmountContent: container => {
unmountComponentAtNode(container);
},
});

export default function () {
const [key, setKey] = useState(0);
return (
<AppLayout
key={key}
ariaLabels={appLayoutLabels}
breadcrumbs={<Breadcrumbs />}
content={
<>
<Header variant="h1" description="This drawer can automatically reopen after app layout instance changes">
Drawer with state persistence
</Header>
<button data-testid="remount-app-layout" onClick={() => setKey(key => key + 1)}>
Remount app layout
</button>
</>
}
tools={<HelpPanel header={<h2>Info</h2>}>Here is some info for you</HelpPanel>}
/>
);
}
17 changes: 2 additions & 15 deletions pages/app-layout/stateful.page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={styles.textContent}>
<span id={`${id}-text`}>Clicked: {count}</span>
<button id={`${id}-button`} onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

export default function AppLayoutStatefulDemo() {
return (
<AppLayout
Expand Down
4 changes: 0 additions & 4 deletions pages/app-layout/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@
border-inline: 1px solid red;
}

.textContent {
color: awsui.$color-text-body-default;
}

.longContent {
block-size: 200vh;
}
Expand Down
12 changes: 12 additions & 0 deletions pages/app-layout/utils/content-blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,15 @@ export function CustomDrawerContent() {
</div>
);
}

export function Counter({ id }: { id: string }) {
const [count, setCount] = useState(0);
return (
<div>
<span id={`${id}-text`}>Clicked: {count}</span>
<button id={`${id}-button`} onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
30 changes: 15 additions & 15 deletions pages/app-layout/utils/external-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -84,18 +84,6 @@ awsuiPlugins.appLayout.registerDrawer({
unmountContent: container => unmountComponentAtNode(container),
});

const Counter: React.FC = ({ children }) => {
const [count, setCount] = useState(0);

return (
<div>
<span data-testid="count">{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
{children}
</div>
);
};

const AutoIncrementCounter: React.FC<{
onVisibilityChange?: (callback: (isVisible: boolean) => void) => void;
}> = ({ children, onVisibilityChange }) => {
Expand Down Expand Up @@ -192,7 +180,13 @@ awsuiPlugins.appLayout.registerDrawer({
},

mountContent: container => {
ReactDOM.render(<Counter>global widget content circle 2</Counter>, container);
ReactDOM.render(
<>
<Counter id="circle2-global" />
global widget content circle 2
</>,
container
);
},
unmountContent: container => unmountComponentAtNode(container),
});
Expand All @@ -219,7 +213,13 @@ awsuiPlugins.appLayout.registerDrawer({
},

mountContent: container => {
ReactDOM.render(<Counter>global widget content circle 3</Counter>, container);
ReactDOM.render(
<>
<Counter id="circle3-global" />
global widget content circle 3
</>,
container
);
},
unmountContent: container => unmountComponentAtNode(container),
});
Expand Down
2 changes: 1 addition & 1 deletion src/file-upload/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
15 changes: 14 additions & 1 deletion src/internal/plugins/controllers/__tests__/drawers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
44 changes: 24 additions & 20 deletions src/internal/plugins/controllers/drawers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,18 @@ export interface DrawerConfig {
preserveInactiveContent?: boolean;
}

export type UpdateDrawerConfig = Pick<DrawerConfig, 'id' | 'badge' | 'resizable' | 'defaultSize'>;
const updatableProperties = [
'badge',
'resizable',
'defaultSize',
'orderPriority',
'defaultActive',
'onResize',
] as const;

export type UpdateDrawerConfig = { id: DrawerConfig['id'] } & Partial<
Pick<DrawerConfig, (typeof updatableProperties)[number]>
>;

export type DrawersRegistrationListener = (drawers: Array<DrawerConfig>) => void;

Expand Down Expand Up @@ -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) => {
Expand Down
Loading