Skip to content

Commit c1b1ef0

Browse files
committed
Revert "Remove headerBefore and ariaLabel"
This reverts commit fca330f.
1 parent 3445801 commit c1b1ef0

File tree

9 files changed

+151
-16
lines changed

9 files changed

+151
-16
lines changed

pages/app-layout/split-panel-with-custom-header.page.tsx

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import React, { useContext, useEffect, useState } from 'react';
3+
import React, { useContext, useEffect, useRef, useState } from 'react';
44

55
import AppLayout, { AppLayoutProps } from '~components/app-layout';
6+
import Badge from '~components/badge';
7+
import Box from '~components/box';
68
import Button from '~components/button';
79
import FormField from '~components/form-field';
810
import Header from '~components/header';
@@ -21,22 +23,68 @@ import * as toolsContent from './utils/tools-content';
2123

2224
type SplitPanelDemoContext = React.Context<
2325
AppContextType<{
26+
ariaLabel?: string;
2427
description?: string;
28+
editableHeader: boolean;
2529
headerText?: string;
2630
renderActions: boolean;
31+
renderBadge: boolean;
2732
renderInfoLink: boolean;
2833
splitPanelOpen: boolean;
2934
splitPanelPosition: AppLayoutProps.SplitPanelPreferences['position'];
3035
}>
3136
>;
3237

38+
function EditableHeader({ onChange, value }: { onChange: (text: string) => void; value: string }) {
39+
const [internalValue, setInternalValue] = useState(value);
40+
const [editing, setEditing] = useState(false);
41+
const inputRef = useRef<HTMLInputElement>(null);
42+
useEffect(() => {
43+
if (editing) {
44+
inputRef.current?.focus();
45+
}
46+
}, [editing]);
47+
48+
return (
49+
<Box display="inline-block">
50+
<SpaceBetween direction="horizontal" size="xxs" alignItems="center">
51+
{editing ? (
52+
<>
53+
<Input value={internalValue} onChange={({ detail }) => setInternalValue(detail.value)} ref={inputRef} />
54+
<Button
55+
variant="icon"
56+
iconName="check"
57+
onClick={() => {
58+
onChange(internalValue);
59+
setEditing(false);
60+
}}
61+
/>
62+
<Button variant="icon" iconName="close" onClick={() => setEditing(false)} />
63+
</>
64+
) : (
65+
<>
66+
<Box variant="h3" tagOverride="h2" display="inline" margin={{ vertical: 'n' }} padding={{ vertical: 'n' }}>
67+
{value}
68+
</Box>
69+
<Button variant="icon" iconName="edit" onClick={() => setEditing(true)}></Button>
70+
</>
71+
)}
72+
</SpaceBetween>
73+
</Box>
74+
);
75+
}
76+
3377
export default function () {
3478
const { urlParams, setUrlParams } = useContext(AppContext as SplitPanelDemoContext);
3579
const [toolsOpen, setToolsOpen] = useState(false);
3680

3781
// Initialize the header to a default value if not set.
38-
// eslint-disable-next-line react-hooks/exhaustive-deps
39-
useEffect(() => setUrlParams({ ...urlParams, headerText: urlParams.headerText || 'Header text' }), []);
82+
useEffect(() => {
83+
if (!urlParams.editableHeader) {
84+
setUrlParams({ ...urlParams, headerText: urlParams.headerText || 'Header text' });
85+
}
86+
// eslint-disable-next-line react-hooks/exhaustive-deps
87+
}, []);
4088

4189
return (
4290
<ScreenshotArea gutters={false}>
@@ -58,7 +106,7 @@ export default function () {
58106
onToolsChange={({ detail }) => setToolsOpen(detail.open)}
59107
splitPanel={
60108
<SplitPanel
61-
header={urlParams.headerText || ''}
109+
header={(!urlParams.editableHeader && urlParams.headerText) || ''}
62110
i18nStrings={splitPaneli18nStrings}
63111
headerActions={
64112
urlParams.renderActions && (
@@ -68,6 +116,21 @@ export default function () {
68116
</SpaceBetween>
69117
)
70118
}
119+
headerBefore={
120+
(urlParams.renderBadge || urlParams.editableHeader) && (
121+
<>
122+
{urlParams.renderBadge && <Badge>Badge</Badge>}
123+
{urlParams.editableHeader && (
124+
<Box display="inline-block" margin={{ left: urlParams.renderBadge ? 'xs' : 'n' }}>
125+
<EditableHeader
126+
value={urlParams.headerText || ''}
127+
onChange={value => setUrlParams({ ...urlParams, headerText: value })}
128+
/>
129+
</Box>
130+
)}
131+
</>
132+
)
133+
}
71134
headerDescription={urlParams.description}
72135
headerInfo={
73136
urlParams.renderInfoLink && (
@@ -76,6 +139,7 @@ export default function () {
76139
</Link>
77140
)
78141
}
142+
ariaLabel={urlParams.ariaLabel}
79143
>
80144
<ScrollableDrawerContent />
81145
</SplitPanel>
@@ -89,6 +153,18 @@ export default function () {
89153
</div>
90154
<SpaceBetween size="l">
91155
<SpaceBetween direction="horizontal" size="xl">
156+
<Toggle
157+
checked={urlParams.renderBadge}
158+
onChange={({ detail }) => setUrlParams({ ...urlParams, renderBadge: detail.checked })}
159+
>
160+
With badge
161+
</Toggle>
162+
<Toggle
163+
checked={urlParams.editableHeader}
164+
onChange={({ detail }) => setUrlParams({ ...urlParams, editableHeader: detail.checked })}
165+
>
166+
Editable header text
167+
</Toggle>
92168
<Toggle
93169
checked={urlParams.renderInfoLink}
94170
onChange={({ detail }) => setUrlParams({ ...urlParams, renderInfoLink: detail.checked })}
@@ -114,6 +190,12 @@ export default function () {
114190
onChange={({ detail }) => setUrlParams({ ...urlParams, description: detail.value })}
115191
/>
116192
</FormField>
193+
<FormField label="ARIA label">
194+
<Input
195+
value={urlParams.ariaLabel || ''}
196+
onChange={({ detail }) => setUrlParams({ ...urlParams, ariaLabel: detail.value })}
197+
/>
198+
</FormField>
117199
<Containers />
118200
</SpaceBetween>
119201
</>

src/split-panel/__tests__/header.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ describe('Split panel: Header slots', () => {
1515
expect(wrapper!.findHeaderActions()!.getElement()).toHaveTextContent('Action');
1616
});
1717

18+
test('renders headerBefore', () => {
19+
const { wrapper } = renderSplitPanel({
20+
props: { headerBefore: <span>Before</span> },
21+
});
22+
expect(wrapper!.findHeaderBefore()).not.toBeNull();
23+
expect(wrapper!.findHeaderBefore()!.getElement()).toHaveTextContent('Before');
24+
});
25+
1826
test('renders headerDescription', () => {
1927
const { wrapper } = renderSplitPanel({
2028
props: { headerDescription: 'Header description' },
@@ -34,6 +42,7 @@ describe('Split panel: Header slots', () => {
3442
test('does not render header properties when not provided', () => {
3543
const { wrapper } = renderSplitPanel();
3644
expect(wrapper!.findHeaderActions()).toBeNull();
45+
expect(wrapper!.findHeaderBefore()).toBeNull();
3746
expect(wrapper!.findHeaderDescription()).toBeNull();
3847
expect(wrapper!.findHeaderInfo()).toBeNull();
3948
});

src/split-panel/bottom.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function SplitPanelContentBottom({
3333
children,
3434
appLayoutMaxWidth,
3535
panelHeaderId,
36+
ariaLabel,
3637
onToggle,
3738
hasCustomElements,
3839
}: SplitPanelContentBottomProps) {
@@ -101,7 +102,12 @@ export function SplitPanelContentBottom({
101102
{closeBehavior === 'hide' && !isOpen ? null : (
102103
<>
103104
{isOpen && <div className={styles['slider-wrapper-bottom']}>{resizeHandle}</div>}
104-
<div className={styles['drawer-content-bottom']} aria-labelledby={panelHeaderId} role="region">
105+
<div
106+
className={styles['drawer-content-bottom']}
107+
aria-labelledby={panelHeaderId}
108+
aria-label={ariaLabel}
109+
role="region"
110+
>
105111
<div className={clsx(styles['pane-header-wrapper-bottom'], centeredMaxWidthClasses)} ref={headerRef}>
106112
{header}
107113
</div>

src/split-panel/implementation.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export function SplitPanelImplementation({
3636
hidePreferencesButton,
3737
closeBehavior,
3838
i18nStrings = {},
39+
ariaLabel,
3940
headerActions,
41+
headerBefore,
4042
headerDescription,
4143
headerInfo,
4244
...restProps
@@ -92,19 +94,26 @@ export function SplitPanelImplementation({
9294
};
9395

9496
const panelHeaderUniqueId = useUniqueId('split-panel-header');
95-
const panelHeaderId = panelHeaderUniqueId;
97+
const panelHeaderId = ariaLabel ? undefined : panelHeaderUniqueId;
9698

97-
const hasCustomElements = !!headerActions || !!headerInfo;
99+
const hasCustomElements = !!headerActions || !!headerBefore || !!headerInfo;
98100

99101
const showDescription = headerDescription && isOpen;
100102

101103
const wrappedHeader = (
102104
<div className={clsx(styles.header, isToolbar && styles['with-toolbar'])} style={appLayoutMaxWidth}>
103105
<div className={styles['header-content']}>
104106
<div className={styles['header-main-row']}>
105-
<div>
106-
<h2 className={clsx(styles['header-text'], testUtilStyles['header-text'])} id={panelHeaderId}>
107-
{header}
107+
<div className={styles['header-text-and-info']}>
108+
<h2 className={styles['header-tag']}>
109+
{headerBefore && (
110+
<span className={clsx(styles['header-before'], testUtilStyles['header-before'])}>{headerBefore}</span>
111+
)}
112+
{header && (
113+
<div className={clsx(styles['header-text'], testUtilStyles['header-text'])} id={panelHeaderId}>
114+
{header}
115+
</div>
116+
)}
108117
</h2>
109118
{headerInfo && (
110119
<span className={clsx(styles['header-info'], testUtilStyles['header-info'])}>{headerInfo}</span>
@@ -240,6 +249,7 @@ export function SplitPanelImplementation({
240249
toggleRef={refs.toggle}
241250
header={wrappedHeader}
242251
panelHeaderId={panelHeaderId}
252+
ariaLabel={ariaLabel}
243253
closeBehavior={closeBehavior}
244254
>
245255
{children}
@@ -258,6 +268,7 @@ export function SplitPanelImplementation({
258268
header={wrappedHeader}
259269
panelHeaderId={panelHeaderId}
260270
appLayoutMaxWidth={appLayoutMaxWidth}
271+
ariaLabel={ariaLabel}
261272
closeBehavior={closeBehavior}
262273
hasCustomElements={hasCustomElements}
263274
>

src/split-panel/interfaces.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ export interface SplitPanelProps extends BaseComponentProps {
3434
*/
3535
i18nStrings?: SplitPanelProps.I18nStrings;
3636

37+
/**
38+
* ARIA label for the panel. Use this if the value passed in the `header` property is not descriptive as a label for the panel.
39+
*/
40+
ariaLabel?: string;
41+
3742
/**
3843
* Actions for the header. Available only if you specify the `header` property.
3944
*/
@@ -48,6 +53,11 @@ export interface SplitPanelProps extends BaseComponentProps {
4853
* The area next to the heading, used to display an Info link.
4954
*/
5055
headerInfo?: React.ReactNode;
56+
57+
/**
58+
* Content displayed before the header text.
59+
*/
60+
headerBefore?: React.ReactNode;
5161
}
5262

5363
export namespace SplitPanelProps {
@@ -74,6 +84,7 @@ export interface SplitPanelContentProps {
7484
splitPanelRef?: React.Ref<any>;
7585
cappedSize: number;
7686
panelHeaderId?: string;
87+
ariaLabel?: string;
7788
resizeHandle: React.ReactNode;
7889
header: React.ReactNode;
7990
children: React.ReactNode;

src/split-panel/side.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function SplitPanelContentSide({
3232
cappedSize,
3333
openButtonAriaLabel,
3434
panelHeaderId,
35+
ariaLabel,
3536
onToggle,
3637
closeBehavior,
3738
}: SplitPanelContentSideProps) {
@@ -64,6 +65,7 @@ export function SplitPanelContentSide({
6465
}}
6566
onClick={() => !isOpen && onToggle()}
6667
aria-labelledby={panelHeaderId}
68+
aria-label={ariaLabel}
6769
role="region"
6870
>
6971
{isOpen ? (

src/split-panel/styles.scss

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,20 @@ $app-layout-drawer-width: calc(#{awsui.$space-layout-toggle-diameter} + 2 * #{aw
254254
align-items: flex-start;
255255
}
256256

257+
&-tag {
258+
flex-grow: 1;
259+
margin-block: 0;
260+
}
261+
262+
&-tag,
263+
&-info {
264+
display: inline;
265+
}
266+
257267
&-text {
258268
$vertical-margin: calc(#{awsui.$space-scaled-xxs} + 1px);
259-
@include styles.font-panel-header;
260-
flex-grow: 1;
261269
display: inline-block;
270+
@include styles.font-panel-header;
262271
padding-block: 0;
263272
padding-inline: 0;
264273
margin-inline: 0;
@@ -267,8 +276,8 @@ $app-layout-drawer-width: calc(#{awsui.$space-layout-toggle-diameter} + 2 * #{aw
267276
min-block-size: calc(#{awsui.$font-panel-header-line-height} + #{$vertical-margin});
268277
}
269278

270-
&-info {
271-
display: inline;
279+
&-before + &-text,
280+
&-tag + &-info {
272281
margin-inline-start: awsui.$space-scaled-xs;
273282
}
274283

src/split-panel/test-classes/styles.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
.root,
77
.header-actions,
8+
.header-before,
89
.header-description,
910
.header-info,
1011
.header-text,

src/test-utils/dom/split-panel/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ export default class SplitPanelWrapper extends ComponentWrapper {
3131
}
3232

3333
findHeaderActions() {
34-
return this.findByClassName(testUtilStyles['header-actions-slot']);
34+
return this.findByClassName(testUtilStyles['header-actions']);
35+
}
36+
37+
findHeaderBefore() {
38+
return this.findByClassName(testUtilStyles['header-before']);
3539
}
3640

3741
findHeaderDescription() {
3842
return this.findByClassName(testUtilStyles['header-description']);
3943
}
4044

4145
findHeaderInfo() {
42-
return this.findByClassName(testUtilStyles['header-info-slot']);
46+
return this.findByClassName(testUtilStyles['header-info']);
4347
}
4448

4549
/**

0 commit comments

Comments
 (0)