Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5c34e00
Add new props
jperals Aug 6, 2025
a7b499a
Add dev page
jperals Aug 6, 2025
e26e94b
Use provided ARIA label
jperals Aug 6, 2025
fbb9a89
Implementation
jperals Aug 6, 2025
c130feb
Implementation
jperals Aug 6, 2025
65c5993
Adjust spacings
jperals Aug 6, 2025
96a0320
Reduce clickable area when custom elements are present
jperals Aug 6, 2025
fde0da5
Fix hover effect
jperals Aug 6, 2025
f61c515
Fix spacings
jperals Aug 6, 2025
6262e06
Fine tune spacings
jperals Aug 6, 2025
52816b8
Place description below buttons
jperals Aug 6, 2025
6cadee4
Do not render header if not passed in
jperals Aug 6, 2025
b29465f
Spacing adjustments
jperals Aug 7, 2025
da88035
Initialize the header to a default value if not set
jperals Aug 7, 2025
4a3963c
Place headerBefore slot inside heading tag
jperals Aug 7, 2025
559b0f1
Persist split panel open state
jperals Aug 8, 2025
34777dd
Editable header text
jperals Aug 8, 2025
6366475
Adjust spacings
jperals Aug 8, 2025
380e607
Add test utils
jperals Aug 8, 2025
d5170d0
Add unit test coverage
jperals Aug 8, 2025
5f054b6
Remove unnecessary CSS rules
jperals Aug 8, 2025
fca330f
Remove headerBefore and ariaLabel
jperals Aug 8, 2025
f55adbe
Refactor class names
jperals Aug 8, 2025
080f4aa
Remove leftover metadata prop
jperals Aug 8, 2025
70722c7
Update snapshots
jperals Aug 8, 2025
8a84c9b
Adjust spacings
jperals Aug 8, 2025
794370d
Adjust line height
jperals Aug 9, 2025
e20b3fe
Adjust header height with vertical-align
jperals Aug 9, 2025
3c3eaa8
Revert "Adjust header height with vertical-align"
jperals Aug 9, 2025
15088fa
Line height fix
jperals Aug 9, 2025
ca7ae9b
Spacing adjustments
jperals Aug 9, 2025
68589b5
Remove line-height workaround
jperals Aug 11, 2025
ea54e0b
Add actionsAsLinks prop to dev page
jperals Aug 11, 2025
8533bf0
Spacing adjustments
jperals Aug 11, 2025
32c2970
Update test utils selectors snapshots
jperals Aug 8, 2025
a6e875b
Update test util class names
jperals Aug 8, 2025
26f1707
Update documenter snapshots
jperals Aug 8, 2025
ff11b27
Kepp panelHeaderId mandatory
jperals Aug 11, 2025
60b566e
Refactor class names
jperals Aug 11, 2025
0f04442
Add integ coverage
jperals Aug 11, 2025
3ba701b
line-height workaround
jperals Aug 11, 2025
45cb617
Add comment
jperals Aug 11, 2025
afc5826
Move line-height workaround to child node
jperals Aug 11, 2025
a1bbb7f
Merge branch 'main' into feat/split-panel-header-extra-slots
jperals Aug 11, 2025
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
136 changes: 136 additions & 0 deletions pages/app-layout/split-panel-with-custom-header.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useEffect, useState } from 'react';

import AppLayout, { AppLayoutProps } from '~components/app-layout';
import Button from '~components/button';
import FormField from '~components/form-field';
import Header from '~components/header';
import Input from '~components/input';
import Link from '~components/link';
import SpaceBetween from '~components/space-between';
import SplitPanel from '~components/split-panel';
import Toggle from '~components/toggle';

import AppContext, { AppContextType } from '../app/app-context';
import ScreenshotArea from '../utils/screenshot-area';
import { Breadcrumbs, Containers, Navigation, ScrollableDrawerContent, Tools } from './utils/content-blocks';
import labels from './utils/labels';
import { splitPaneli18nStrings } from './utils/strings';
import * as toolsContent from './utils/tools-content';

type SplitPanelDemoContext = React.Context<
AppContextType<{
actionsAsLinks: boolean;
description?: string;
headerText?: string;
renderActions: boolean;
renderInfoLink: boolean;
splitPanelOpen: boolean;
splitPanelPosition: AppLayoutProps.SplitPanelPreferences['position'];
}>
>;

export default function () {
const { urlParams, setUrlParams } = useContext(AppContext as SplitPanelDemoContext);
const [toolsOpen, setToolsOpen] = useState(false);

// Initialize the header to a default value if not set.
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => setUrlParams({ ...urlParams, headerText: urlParams.headerText || 'Header text' }), []);

return (
<ScreenshotArea gutters={false}>
<AppLayout
ariaLabels={labels}
breadcrumbs={<Breadcrumbs />}
navigation={<Navigation />}
tools={<Tools>{toolsContent.long}</Tools>}
toolsOpen={toolsOpen}
splitPanelOpen={urlParams.splitPanelOpen}
onSplitPanelToggle={({ detail }) => setUrlParams({ ...urlParams, splitPanelOpen: detail.open })}
splitPanelPreferences={{
position: urlParams.splitPanelPosition,
}}
onSplitPanelPreferencesChange={event => {
const { position } = event.detail;
setUrlParams({ splitPanelPosition: position === 'side' ? position : undefined });
}}
onToolsChange={({ detail }) => setToolsOpen(detail.open)}
splitPanel={
<SplitPanel
header={urlParams.headerText || ''}
i18nStrings={splitPaneli18nStrings}
headerActions={
urlParams.renderActions &&
(urlParams.actionsAsLinks ? (
<Link>Action</Link>
) : (
<SpaceBetween direction="horizontal" size="xs">
<Button>Button</Button>
<Button>Button</Button>
</SpaceBetween>
))
}
headerDescription={urlParams.description}
headerInfo={
urlParams.renderInfoLink && (
<Link variant="info" onFollow={() => setToolsOpen(true)}>
Info
</Link>
)
}
>
<ScrollableDrawerContent />
</SplitPanel>
}
content={
<>
<div style={{ marginBottom: '1rem' }}>
<Header variant="h1" description="Basic demo with split panel">
Demo page
</Header>
</div>
<SpaceBetween size="l">
<Toggle
checked={urlParams.renderInfoLink}
onChange={({ detail }) => setUrlParams({ ...urlParams, renderInfoLink: detail.checked })}
>
With info link
</Toggle>
<SpaceBetween direction="horizontal" size="xl">
<Toggle
checked={urlParams.renderActions}
onChange={({ detail }) => setUrlParams({ ...urlParams, renderActions: detail.checked })}
>
With action buttons
</Toggle>
{urlParams.renderActions && (
<Toggle
checked={urlParams.actionsAsLinks}
onChange={({ detail }) => setUrlParams({ ...urlParams, actionsAsLinks: detail.checked })}
>
As links
</Toggle>
)}
</SpaceBetween>
<FormField label="Header text">
<Input
value={urlParams.headerText || ''}
onChange={({ detail }) => setUrlParams({ ...urlParams, headerText: detail.value })}
/>
</FormField>
<FormField label="Description">
<Input
value={urlParams.description || ''}
onChange={({ detail }) => setUrlParams({ ...urlParams, description: detail.value })}
/>
</FormField>
<Containers />
</SpaceBetween>
</>
}
/>
</ScreenshotArea>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21583,6 +21583,21 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"isDefault": true,
"name": "children",
},
{
"description": "Actions for the header. Available only if you specify the \`header\` property.",
"isDefault": false,
"name": "headerActions",
},
{
"description": "Supplementary text below the heading.",
"isDefault": false,
"name": "headerDescription",
},
{
"description": "The area next to the heading, used to display an Info link.",
"isDefault": false,
"name": "headerInfo",
},
],
"releaseStatus": "stable",
}
Expand Down Expand Up @@ -83176,6 +83191,30 @@ Component's wrapper class",
"type": "reference",
},
},
{
"name": "findHeaderActions",
"parameters": [],
"returnType": {
"name": "null | ElementWrapper",
"type": "union",
},
},
{
"name": "findHeaderDescription",
"parameters": [],
"returnType": {
"name": "null | ElementWrapper",
"type": "union",
},
},
{
"name": "findHeaderInfo",
"parameters": [],
"returnType": {
"name": "null | ElementWrapper",
"type": "union",
},
},
{
"name": "findOpenButton",
"parameters": [],
Expand Down Expand Up @@ -138429,6 +138468,33 @@ Component's wrapper class",
"typeArguments": [],
},
},
{
"name": "findHeaderActions",
"parameters": [],
"returnType": {
"name": "ElementWrapper",
"type": "reference",
"typeArguments": [],
},
},
{
"name": "findHeaderDescription",
"parameters": [],
"returnType": {
"name": "ElementWrapper",
"type": "reference",
"typeArguments": [],
},
},
{
"name": "findHeaderInfo",
"parameters": [],
"returnType": {
"name": "ElementWrapper",
"type": "reference",
"typeArguments": [],
},
},
{
"name": "findOpenButton",
"parameters": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ exports[`test-utils selectors 1`] = `
],
"split-panel": [
"awsui_close-button_rjqu5",
"awsui_header-actions_rjqu5",
"awsui_header-description_rjqu5",
"awsui_header-info_rjqu5",
"awsui_header-text_rjqu5",
"awsui_open-button_rjqu5",
"awsui_open-position-bottom_rjqu5",
Expand Down
2 changes: 1 addition & 1 deletion src/app-layout/__integ__/app-layout-split-panel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ describe.each(['classic', 'refresh', 'refresh-toolbar'] as const)('%s', theme =>
);

test(
'switches to bottom position when when tools panel opens and available space is too small',
'switches to bottom position when tools panel opens and available space is too small',
setupTest(async page => {
await page.setWindowSize({ ...viewports.desktop, width: 1100 });
await page.openPanel();
Expand Down
63 changes: 63 additions & 0 deletions src/split-panel/__integ__/header.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { BasePageObject } from '@cloudscape-design/browser-test-tools/page-objects';
import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';

import createWrapper from '../../../lib/components/test-utils/selectors';
import { viewports } from '../../app-layout/__integ__/constants';
import { getUrlParams } from '../../app-layout/__integ__/utils';

const wrapper = createWrapper().findAppLayout();
const url = '#/light/app-layout/split-panel-with-custom-header';

describe.each(['refresh', 'refresh-toolbar'] as const)('%s', theme => {
describe.each(['desktop', 'mobile'] as const)('%s', viewport => {
function setupTest(testFn: (page: BasePageObject) => Promise<void>, params = {}) {
return useBrowser(async browser => {
const page = new BasePageObject(browser);
await page.setWindowSize(viewports[viewport]);
await browser.url(`${url}?${getUrlParams(theme, params)}`);
await page.waitForVisible(wrapper.findContentRegion().toSelector());
await testFn(page);
});
}

describe('only the expand button is clickable in collapsed bottom split panel when certain header slots are populated', () => {
const cases = [
{ slotName: 'headerActions', devPageParam: 'renderActions' },
{ slotName: 'headerInfo', devPageParam: 'renderInfoLink' },
];
test.each(cases)('$slotName', ({ devPageParam }) =>
setupTest(
async page => {
await page.click(wrapper.findSplitPanel().findHeader().toSelector()); // Click on the header text
await expect(
page.isDisplayedInViewport(wrapper.findSplitPanel().findOpenPanelBottom().toSelector())
).resolves.toBe(false);
await page.click(wrapper.findSplitPanelOpenButton().toSelector());
await expect(
page.isDisplayedInViewport(wrapper.findSplitPanel().findOpenPanelBottom().toSelector())
).resolves.toBe(true);
},
{ [devPageParam]: true }
)()
);
});

test(
'the entire header is clickable in collapsed bottom split panel even if headerDescription slot is populated',
setupTest(
async page => {
await page.click(wrapper.findSplitPanel().findHeader().toSelector()); // Click on the header text
await expect(
page.isDisplayedInViewport(wrapper.findSplitPanel().findOpenPanelBottom().toSelector())
).resolves.toBe(true);
await expect(
page.isDisplayedInViewport(wrapper.findSplitPanel().findOpenPanelBottom().toSelector())
).resolves.toBe(true);
},
{ headerDescription: true }
)
);
});
});
55 changes: 55 additions & 0 deletions src/split-panel/__tests__/common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { render } from '@testing-library/react';

import TestI18nProvider from '../../../lib/components/i18n/testing';
import {
SplitPanelContextProps,
SplitPanelContextProvider,
} from '../../../lib/components/internal/context/split-panel-context';
import SplitPanel, { SplitPanelProps } from '../../../lib/components/split-panel';
import createWrapper from '../../../lib/components/test-utils/dom';
import { defaultSplitPanelContextProps } from './helpers';

const i18nStrings = {
closeButtonAriaLabel: 'closeButtonAriaLabel',
openButtonAriaLabel: 'openButtonAriaLabel',
preferencesTitle: 'preferencesTitle',
preferencesPositionLabel: 'preferencesPositionLabel',
preferencesPositionDescription: 'preferencesPositionDescription',
preferencesPositionSide: 'preferencesPositionSide',
preferencesPositionBottom: 'preferencesPositionBottom',
preferencesConfirm: 'preferencesConfirm',
preferencesCancel: 'preferencesCancel',
resizeHandleAriaLabel: 'resizeHandleAriaLabel',
};

export const defaultProps: SplitPanelProps = {
header: 'Split panel header',
children: <p>Split panel content</p>,
hidePreferencesButton: undefined,
i18nStrings,
};

export function renderSplitPanel({
props,
contextProps,
messages = {},
modalMessages = {},
}: {
props?: Partial<SplitPanelProps>;
contextProps?: Partial<SplitPanelContextProps>;
messages?: Record<string, string>;
modalMessages?: Record<string, string>;
} = {}) {
const { container } = render(
<TestI18nProvider messages={{ 'split-panel': messages, modal: modalMessages }}>
<SplitPanelContextProvider value={{ ...defaultSplitPanelContextProps, ...contextProps }}>
<SplitPanel {...defaultProps} {...props} />
</SplitPanelContextProvider>
</TestI18nProvider>
);
const wrapper = createWrapper(container).findSplitPanel();
return { wrapper };
}
Loading
Loading