Skip to content

Commit f45dbc2

Browse files
feat: Add list component (#3481)
1 parent f384632 commit f45dbc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1446
-155
lines changed

build-tools/utils/pluralize.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const pluralizationMap = {
4545
KeyValuePairs: 'KeyValuePairs',
4646
LineChart: 'LineCharts',
4747
Link: 'Links',
48+
List: 'Lists',
4849
LiveRegion: 'LiveRegions',
4950
MixedLineBarChart: 'MixedLineBarCharts',
5051
Modal: 'Modals',

pages/list/permutations.page.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
5+
import { Box, ButtonDropdown, Icon, SpaceBetween } from '~components';
6+
import List, { ListProps } from '~components/list';
7+
8+
import createPermutations from '../utils/permutations';
9+
import PermutationsView from '../utils/permutations-view';
10+
import ScreenshotArea from '../utils/screenshot-area';
11+
12+
interface Item {
13+
content: string;
14+
description?: string;
15+
timestamp?: string;
16+
}
17+
const items: Item[] = [
18+
{ content: 'Item 1', timestamp: '3:23 pm' },
19+
{ content: 'Item 2 has a longer title', timestamp: 'Two hours ago' },
20+
{
21+
content: 'Item 3 with more text',
22+
description: 'Item 3 has a long description that probably spans onto multiple lines on smaller viewports.',
23+
timestamp: 'Yesterday',
24+
},
25+
{ content: 'Item 4', description: 'Description', timestamp: 'January 1 2025' },
26+
];
27+
28+
/* eslint-disable react/jsx-key */
29+
const permutations = createPermutations<
30+
ListProps<Item> & { viewportWidth: number; _disablePaddings: boolean | 'item' }
31+
>([
32+
{
33+
viewportWidth: [200, 400],
34+
items: [items],
35+
_disablePaddings: [false, true, 'item'],
36+
renderItem: [
37+
({ content }) => ({ content, id: content }),
38+
({ content }) => ({ content, id: content, secondaryContent: <Box variant="small">Description</Box> }),
39+
({ content }) => ({
40+
content,
41+
id: content,
42+
icon: <Icon name="anchor-link" ariaLabel="Icon" />,
43+
secondaryContent: <Box variant="small">Description</Box>,
44+
}),
45+
({ content, description }) => ({
46+
id: content,
47+
content,
48+
secondaryContent: description && <Box variant="small">{description}</Box>,
49+
actions: <ButtonDropdown variant="icon" items={[{ id: 'item', text: 'item' }]} ariaLabel="Actions" />,
50+
}),
51+
({ content, description, timestamp }) => ({
52+
id: content,
53+
content,
54+
secondaryContent: description && <Box variant="small">{description}</Box>,
55+
actions: (
56+
<SpaceBetween size="xs" direction="horizontal" alignItems="center">
57+
<Box variant="small">{timestamp}</Box>
58+
<ButtonDropdown variant="icon" items={[{ id: 'item', text: 'item' }]} ariaLabel="Actions" />
59+
</SpaceBetween>
60+
),
61+
}),
62+
],
63+
},
64+
]);
65+
/* eslint-enable react/jsx-key */
66+
67+
export default function ListItemPermutations() {
68+
return (
69+
<>
70+
<h1>List permutations</h1>
71+
<ScreenshotArea>
72+
<PermutationsView
73+
permutations={permutations}
74+
render={({ viewportWidth, _disablePaddings, ...permutation }) => (
75+
<div style={{ width: viewportWidth, borderRight: '1px solid red', padding: '4px', overflow: 'hidden' }}>
76+
<List
77+
{...permutation}
78+
disablePaddings={_disablePaddings === true}
79+
disableItemPaddings={_disablePaddings === 'item'}
80+
/>
81+
</div>
82+
)}
83+
/>
84+
</ScreenshotArea>
85+
</>
86+
);
87+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
5+
import { Box, ButtonDropdown, SpaceBetween } from '~components';
6+
import List, { ListProps } from '~components/list';
7+
8+
import createPermutations from '../utils/permutations';
9+
import PermutationsView from '../utils/permutations-view';
10+
import ScreenshotArea from '../utils/screenshot-area';
11+
12+
interface Item {
13+
content: string;
14+
description?: string;
15+
timestamp?: string;
16+
}
17+
const items: Item[] = [
18+
{ content: 'Item 1', timestamp: '3:23 pm' },
19+
{ content: 'Item 2 has a longer title', timestamp: 'Two hours ago' },
20+
{
21+
content: 'Item 3 with more text',
22+
description: 'Item 3 has a long description that probably spans onto multiple lines on smaller viewports.',
23+
timestamp: 'Yesterday',
24+
},
25+
{ content: 'Item 4', description: 'Description', timestamp: 'January 1 2025' },
26+
];
27+
28+
/* eslint-disable react/jsx-key */
29+
const permutations = createPermutations<ListProps<Item> & { viewportWidth: number; _sortable: boolean | 'disabled' }>([
30+
{
31+
viewportWidth: [200, 400],
32+
items: [items],
33+
_sortable: [true, false, 'disabled'],
34+
disableItemPaddings: [false, true],
35+
renderItem: [
36+
({ content }) => ({ content, id: content }),
37+
({ content, description, timestamp }) => ({
38+
id: content,
39+
content,
40+
secondaryContent: description && <Box variant="small">{description}</Box>,
41+
actions: (
42+
<SpaceBetween size="xs" direction="horizontal" alignItems="center">
43+
<Box variant="small">{timestamp}</Box>
44+
<ButtonDropdown variant="icon" items={[{ id: 'item', text: 'item' }]} ariaLabel="Actions" />
45+
</SpaceBetween>
46+
),
47+
}),
48+
],
49+
},
50+
]);
51+
/* eslint-enable react/jsx-key */
52+
53+
export default function ListItemPermutations() {
54+
return (
55+
<>
56+
<h1>List permutations</h1>
57+
<ScreenshotArea>
58+
<PermutationsView
59+
permutations={permutations}
60+
render={({ viewportWidth, _sortable, ...permutation }) => (
61+
<div style={{ width: viewportWidth, borderRight: '1px solid red', padding: '4px', overflow: 'hidden' }}>
62+
<List {...permutation} sortable={!!_sortable} sortDisabled={_sortable === 'disabled'} />
63+
</div>
64+
)}
65+
/>
66+
</ScreenshotArea>
67+
</>
68+
);
69+
}

src/__a11y__/run-a11y-tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function urlFormatter(inputUrl: string, theme: Theme, mode: Mode) {
2222
return `#/${mode}/${inputUrl}?visualRefresh=${theme === 'visual-refresh' ? 'true' : 'false'}`;
2323
}
2424

25-
const vrOnlyComponents = ['app-layout-toolbar'];
25+
const vrOnlyComponents = ['app-layout-toolbar', 'list'];
2626

2727
export default function runA11yTests(theme: Theme, mode: Mode, skip: string[] = []) {
2828
describe(`A11y checks for ${mode} ${theme}`, () => {

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

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11524,6 +11524,173 @@ The default is \`secondary\`, except inside the following components where it de
1152411524
}
1152511525
`;
1152611526

11527+
exports[`Documenter definition for list matches the snapshot: list 1`] = `
11528+
{
11529+
"dashCaseName": "list",
11530+
"events": [
11531+
{
11532+
"cancelable": false,
11533+
"description": "Called when items are reordered in a sortable list.",
11534+
"detailInlineType": {
11535+
"name": "ListProps.SortingState<T>",
11536+
"properties": [
11537+
{
11538+
"name": "items",
11539+
"optional": false,
11540+
"type": "ReadonlyArray<T>",
11541+
},
11542+
],
11543+
"type": "object",
11544+
},
11545+
"detailType": "ListProps.SortingState<T>",
11546+
"name": "onSortingChange",
11547+
},
11548+
],
11549+
"functions": [],
11550+
"name": "List",
11551+
"properties": [
11552+
{
11553+
"description": "Adds an aria-describedby to the list.",
11554+
"name": "ariaDescribedby",
11555+
"optional": true,
11556+
"type": "string",
11557+
},
11558+
{
11559+
"description": "Adds an aria-label to the list.",
11560+
"name": "ariaLabel",
11561+
"optional": true,
11562+
"type": "string",
11563+
},
11564+
{
11565+
"description": "Adds an aria-labelledby to the list.",
11566+
"name": "ariaLabelledby",
11567+
"optional": true,
11568+
"type": "string",
11569+
},
11570+
{
11571+
"description": "Removes padding around and inside list items.",
11572+
"name": "disableItemPaddings",
11573+
"optional": true,
11574+
"type": "boolean",
11575+
},
11576+
{
11577+
"description": "Removes top and bottom padding around the list. Does not apply for sortable lists.",
11578+
"name": "disablePaddings",
11579+
"optional": true,
11580+
"type": "boolean",
11581+
},
11582+
{
11583+
"description": "An object containing all the localized strings required by the component.
11584+
11585+
- \`liveAnnouncementDndStarted\` ((position: number, total: number) => string) - (Optional) Adds a message to be announced by screen readers when an item is picked for reordering.
11586+
- \`liveAnnouncementDndDiscarded\` (string) - (Optional) Adds a message to be announced by screen readers when a reordering action is canceled.
11587+
- \`liveAnnouncementDndItemReordered\` ((initialPosition: number, currentPosition: number, total: number) => string) - (Optional) Adds a message to be announced by screen readers when an item is being moved.
11588+
- \`liveAnnouncementDndItemCommitted\` ((initialPosition: number, finalPosition: number, total: number) => string) - (Optional) Adds a message to be announced by screen readers when a reordering action is committed.
11589+
- \`dragHandleAriaDescription\` (string) - (Optional) Adds an ARIA description for the drag handle.
11590+
- \`dragHandleAriaLabel\` (string) - (Optional) Adds an ARIA label for the drag handle.",
11591+
"i18nTag": true,
11592+
"inlineType": {
11593+
"name": "SortableAreaProps.DndAreaI18nStrings",
11594+
"properties": [
11595+
{
11596+
"name": "dragHandleAriaDescription",
11597+
"optional": true,
11598+
"type": "string",
11599+
},
11600+
{
11601+
"name": "dragHandleAriaLabel",
11602+
"optional": true,
11603+
"type": "string",
11604+
},
11605+
{
11606+
"name": "liveAnnouncementDndDiscarded",
11607+
"optional": true,
11608+
"type": "string",
11609+
},
11610+
{
11611+
"name": "liveAnnouncementDndItemCommitted",
11612+
"optional": true,
11613+
"type": "((initialPosition: number, finalPosition: number, total: number) => string)",
11614+
},
11615+
{
11616+
"name": "liveAnnouncementDndItemReordered",
11617+
"optional": true,
11618+
"type": "((initialPosition: number, currentPosition: number, total: number) => string)",
11619+
},
11620+
{
11621+
"name": "liveAnnouncementDndStarted",
11622+
"optional": true,
11623+
"type": "((position: number, total: number) => string)",
11624+
},
11625+
],
11626+
"type": "object",
11627+
},
11628+
"name": "i18nStrings",
11629+
"optional": true,
11630+
"type": "SortableAreaProps.DndAreaI18nStrings",
11631+
},
11632+
{
11633+
"description": "The items to display in the list.",
11634+
"name": "items",
11635+
"optional": false,
11636+
"type": "ReadonlyArray<T>",
11637+
},
11638+
{
11639+
"description": "Render an item. The function should return an object with the following keys:
11640+
* \`id\` (string) - A unique identifier for the item.
11641+
* \`content\` (React.ReactNode) - The content of the item.
11642+
* \`secondaryContent\` (React.ReactNode) - (Optional) Secondary content, for example item description.
11643+
* \`icon\` (React.ReactNode) - (Optional) An icon, displayed at the start.
11644+
* \`action\` (React.ReactNode) - (Optional) Action button(s).
11645+
* \`announcementLabel\` (string) - (Optional) An announcement label for the item, used when sorting.
11646+
By default, the \`content\` is used: a custom label should be provided if \`content\` is not a string.",
11647+
"inlineType": {
11648+
"name": "(item: T) => { id: string; content: React.ReactNode; secondaryContent?: React.ReactNode; icon?: React.ReactNode; action?: React.ReactNode; announcementLabel?: string | undefined; }",
11649+
"parameters": [
11650+
{
11651+
"name": "item",
11652+
"type": "T",
11653+
},
11654+
],
11655+
"returnType": "{ id: string; content: React.ReactNode; secondaryContent?: React.ReactNode; icon?: React.ReactNode; action?: React.ReactNode; announcementLabel?: string | undefined; }",
11656+
"type": "function",
11657+
},
11658+
"name": "renderItem",
11659+
"optional": false,
11660+
"type": "(item: T) => { id: string; content: React.ReactNode; secondaryContent?: React.ReactNode; icon?: React.ReactNode; action?: React.ReactNode; announcementLabel?: string | undefined; }",
11661+
},
11662+
{
11663+
"description": "Makes the list sortable by enabling drag and drop functionality.",
11664+
"name": "sortable",
11665+
"optional": true,
11666+
"type": "boolean",
11667+
},
11668+
{
11669+
"description": "Disables sorting drag handles. Use this to temporarily prevent users from reordering the list.",
11670+
"name": "sortDisabled",
11671+
"optional": true,
11672+
"type": "boolean",
11673+
},
11674+
{
11675+
"description": "The HTML tag to render. By default \`ul\` is used for standard lists and \`ol\` for sortable lists.",
11676+
"inlineType": {
11677+
"name": ""ol" | "ul"",
11678+
"type": "union",
11679+
"values": [
11680+
"ol",
11681+
"ul",
11682+
],
11683+
},
11684+
"name": "tagOverride",
11685+
"optional": true,
11686+
"type": "string",
11687+
},
11688+
],
11689+
"regions": [],
11690+
"releaseStatus": "stable",
11691+
}
11692+
`;
11693+
1152711694
exports[`Documenter definition for live-region matches the snapshot: live-region 1`] = `
1152811695
{
1152911696
"dashCaseName": "live-region",

0 commit comments

Comments
 (0)