Skip to content

Commit 955ec7c

Browse files
authored
feat: Drawer header actions (#3462)
1 parent 2e71a9b commit 955ec7c

File tree

8 files changed

+68
-8
lines changed

8 files changed

+68
-8
lines changed

pages/app-layout/utils/external-widget.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';
44
import ReactDOM, { unmountComponentAtNode } from 'react-dom';
55

6+
import ButtonDropdown from '~components/button-dropdown';
67
import Drawer from '~components/drawer';
78
import awsuiPlugins from '~components/internal/plugins';
89

@@ -153,13 +154,20 @@ awsuiPlugins.appLayout.registerDrawer({
153154

154155
mountContent: (container, mountContext) => {
155156
ReactDOM.render(
156-
<AutoIncrementCounter onVisibilityChange={mountContext?.onVisibilityChange}>
157-
global widget content circle 1
158-
{new Array(100).fill(null).map((_, index) => (
159-
<div key={index}>{index}</div>
160-
))}
161-
<div data-testid="circle-global-bottom-content">circle-global bottom content</div>
162-
</AutoIncrementCounter>,
157+
<Drawer
158+
header={<h2>Global drawer</h2>}
159+
headerActions={
160+
<ButtonDropdown items={[{ id: 'settings', text: 'Settings' }]} ariaLabel="Control drawer" variant="icon" />
161+
}
162+
>
163+
<AutoIncrementCounter onVisibilityChange={mountContext?.onVisibilityChange}>
164+
global widget content circle 1
165+
{new Array(100).fill(null).map((_, index) => (
166+
<div key={index}>{index}</div>
167+
))}
168+
<div data-testid="circle-global-bottom-content">circle-global bottom content</div>
169+
</AutoIncrementCounter>
170+
</Drawer>,
163171
container
164172
);
165173
},

src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8451,6 +8451,11 @@ It should contain the only \`h2\` used in the drawer.",
84518451
"isDefault": false,
84528452
"name": "header",
84538453
},
8454+
{
8455+
"description": "Actions for the header. Available only if you specify the \`header\` property.",
8456+
"isDefault": false,
8457+
"name": "headerActions",
8458+
},
84548459
],
84558460
"releaseStatus": "stable",
84568461
}

src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ exports[`test-utils selectors 1`] = `
246246
],
247247
"drawer": [
248248
"awsui_drawer_1sxt8",
249+
"awsui_header-actions_1sxt8",
249250
"awsui_header_1sxt8",
250251
"awsui_test-utils-drawer-content_1sxt8",
251252
],

src/drawer/__tests__/drawer.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ test('renders header if it is provided', () => {
2525
expect(wrapper.findContent()!.getElement()).toHaveTextContent('there is a header above');
2626
});
2727

28+
test('renders header actions if it is provided', () => {
29+
const wrapper = renderDrawer(
30+
<Drawer header="Bla bla" headerActions={<div>Header actions</div>}>
31+
there is a header above
32+
</Drawer>
33+
);
34+
expect(wrapper.findHeader()!.getElement()).toHaveTextContent('Bla bla');
35+
expect(wrapper.findHeaderActions()!.getElement()).toHaveTextContent('Header actions');
36+
expect(wrapper.findContent()!.getElement()).toHaveTextContent('there is a header above');
37+
});
38+
39+
test('does not render header actions if header is not provided', () => {
40+
const wrapper = renderDrawer(<Drawer headerActions={<div>Header actions</div>}>there is a header above</Drawer>);
41+
expect(wrapper.findHeaderActions()).toBeFalsy();
42+
});
43+
2844
test('renders loading state', () => {
2945
const { container } = render(<Drawer loading={true} i18nStrings={{ loadingText: 'Loading content' }} />);
3046
expect(createWrapper(container).findStatusIndicator()!.getElement()).toHaveTextContent('Loading content');

src/drawer/implementation.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export function DrawerImplementation({
2323
i18nStrings,
2424
disableContentPaddings,
2525
__internalRootRef,
26+
headerActions,
2627
...restProps
2728
}: DrawerInternalProps) {
2829
const baseProps = getBaseProps(restProps);
@@ -32,6 +33,7 @@ export function DrawerImplementation({
3233
...baseProps,
3334
className: clsx(baseProps.className, styles.drawer, isToolbar && styles['with-toolbar']),
3435
};
36+
3537
return loading ? (
3638
<div
3739
{...containerProps}
@@ -46,7 +48,12 @@ export function DrawerImplementation({
4648
</div>
4749
) : (
4850
<div {...containerProps} ref={__internalRootRef}>
49-
{header && <div className={styles.header}>{header}</div>}
51+
{header && (
52+
<div className={styles.header}>
53+
{header}
54+
{headerActions && <div className={styles['header-actions']}>{headerActions}</div>}
55+
</div>
56+
)}
5057
<div
5158
className={clsx(
5259
styles['test-utils-drawer-content'],

src/drawer/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export interface DrawerProps extends BaseComponentProps {
3333
* @i18n
3434
*/
3535
i18nStrings?: I18nStrings;
36+
37+
/**
38+
* Actions for the header. Available only if you specify the `header` property.
39+
*/
40+
headerActions?: React.ReactNode;
3641
}
3742

3843
interface I18nStrings {

src/drawer/styles.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313

1414
.header {
1515
@include styles.font-panel-header;
16+
display: flex;
17+
justify-content: space-between;
1618
color: awsui.$color-text-heading-default;
1719
padding-block: awsui.$space-panel-header-vertical;
1820
padding-inline: awsui.$space-panel-side-left calc(#{awsui.$space-xl} + #{awsui.$space-scaled-xxl});
1921
// padding to make sure the header doesn't overlap with the close icon
2022
border-block-end: awsui.$border-divider-section-width solid awsui.$color-border-panel-header;
23+
2124
.with-toolbar > & {
2225
border-color: transparent;
2326
margin-block-end: 0px;
@@ -36,6 +39,17 @@
3639
/* stylelint-enable @cloudscape-design/no-implicit-descendant, selector-max-type */
3740
}
3841

42+
.header-actions {
43+
display: inline-flex;
44+
align-items: start;
45+
z-index: 1;
46+
/*
47+
Compensate for the difference between the runtime drawer's and the drawer component's heading
48+
to ensure the header actions are vertically aligned
49+
*/
50+
margin-block-start: -6px;
51+
}
52+
3953
.content-with-paddings:not(:empty) {
4054
padding-block-start: awsui.$space-panel-content-top;
4155
padding-inline-start: awsui.$space-panel-side-left;

src/test-utils/dom/drawer/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export default class DrawerWrapper extends ComponentWrapper {
1111
return this.findByClassName(styles.header);
1212
}
1313

14+
findHeaderActions(): ElementWrapper | null {
15+
return this.findByClassName(styles['header-actions']);
16+
}
17+
1418
findContent(): ElementWrapper | null {
1519
return this.findByClassName(styles['test-utils-drawer-content']);
1620
}

0 commit comments

Comments
 (0)