Skip to content

Commit 4323c25

Browse files
katiegeorgeKatie George
and
Katie George
authored
feat: Adds Striped Rows to Table and CollectionPreferences (#550)
Co-authored-by: Katie George <[email protected]>
1 parent 9404812 commit 4323c25

File tree

19 files changed

+356
-3
lines changed

19 files changed

+356
-3
lines changed

pages/table/striped-rows.page.tsx

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useState } from 'react';
4+
import ScreenshotArea from '../utils/screenshot-area';
5+
import { useCollection } from '@cloudscape-design/collection-hooks';
6+
import Button from '~components/button';
7+
import CollectionPreferences, { CollectionPreferencesProps } from '~components/collection-preferences';
8+
import Header from '~components/header';
9+
import Pagination from '~components/pagination';
10+
import Table, { TableProps } from '~components/table';
11+
import TextFilter from '~components/text-filter';
12+
import { Instance, generateItems } from './generate-data';
13+
import {
14+
columnsConfig,
15+
EmptyState,
16+
getMatchesCountText,
17+
paginationLabels,
18+
pageSizeOptions,
19+
visibleContentOptions,
20+
} from './shared-configs';
21+
22+
const allItems = generateItems();
23+
const ariaLabels: TableProps<Instance>['ariaLabels'] = {
24+
selectionGroupLabel: 'group label',
25+
allItemsSelectionLabel: ({ selectedItems }) => `${selectedItems.length} item selected`,
26+
itemSelectionLabel: ({ selectedItems }, item) =>
27+
`${item.id} is ${selectedItems.indexOf(item) < 0 ? 'not ' : ''}selected`,
28+
};
29+
30+
export default function App() {
31+
const [preferences, setPreferences] = useState<CollectionPreferencesProps.Preferences>({
32+
pageSize: 20,
33+
visibleContent: ['id', 'type', 'dnsName', 'state'],
34+
wrapLines: false,
35+
36+
// set to true for default striped rows.
37+
stripedRows: true,
38+
});
39+
const [selectedItems, setSelectedItems] = React.useState<any>([]);
40+
41+
const { items, actions, filteredItemsCount, collectionProps, filterProps, paginationProps } = useCollection(
42+
allItems,
43+
{
44+
filtering: {
45+
empty: (
46+
<EmptyState
47+
title="No resources"
48+
subtitle="No resources to display."
49+
action={<Button>Create resource</Button>}
50+
/>
51+
),
52+
noMatch: (
53+
<EmptyState
54+
title="No matches"
55+
subtitle="We can’t find a match."
56+
action={<Button onClick={() => actions.setFiltering('')}>Clear filter</Button>}
57+
/>
58+
),
59+
},
60+
pagination: { pageSize: preferences.pageSize },
61+
sorting: {},
62+
}
63+
);
64+
65+
return (
66+
<ScreenshotArea>
67+
<Table<Instance>
68+
{...collectionProps}
69+
header={
70+
<Header headingTagOverride="h1" counter={`(${allItems.length})`}>
71+
Instances
72+
</Header>
73+
}
74+
ariaLabels={ariaLabels}
75+
selectionType="multi"
76+
onSelectionChange={({ detail }) => setSelectedItems(detail.selectedItems)}
77+
selectedItems={selectedItems}
78+
stripedRows={preferences.stripedRows}
79+
wrapLines={preferences.wrapLines}
80+
columnDefinitions={columnsConfig}
81+
items={items}
82+
pagination={<Pagination {...paginationProps} ariaLabels={paginationLabels} />}
83+
filter={
84+
<TextFilter
85+
{...filterProps!}
86+
countText={getMatchesCountText(filteredItemsCount!)}
87+
filteringAriaLabel="Filter instances"
88+
/>
89+
}
90+
visibleColumns={preferences.visibleContent}
91+
preferences={
92+
<CollectionPreferences
93+
title="Preferences"
94+
confirmLabel="Confirm"
95+
cancelLabel="Cancel"
96+
onConfirm={({ detail }) => setPreferences(detail)}
97+
preferences={preferences}
98+
pageSizePreference={{
99+
title: 'Select page size',
100+
options: pageSizeOptions,
101+
}}
102+
visibleContentPreference={{
103+
title: 'Select visible columns',
104+
options: visibleContentOptions,
105+
}}
106+
wrapLinesPreference={{
107+
label: 'Wrap lines',
108+
description: 'Wrap lines description',
109+
}}
110+
stripedRowsPreference={{
111+
label: 'Striped rows',
112+
description: 'Striped rows description',
113+
}}
114+
/>
115+
}
116+
/>
117+
</ScreenshotArea>
118+
);
119+
}

src/__integ__/__snapshots__/themes.test.ts.snap

+7
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Object {
5050
"color-background-button-primary-disabled": "#ffffff",
5151
"color-background-button-primary-hover": "#0a4a74",
5252
"color-background-calendar-today": "#f2f3f3",
53+
"color-background-cell-shaded": "#f2f3f3",
5354
"color-background-code-editor-gutter-active-line-default": "#687078",
5455
"color-background-code-editor-gutter-active-line-error": "#d13212",
5556
"color-background-code-editor-gutter-default": "#f2f3f3",
@@ -627,6 +628,7 @@ Object {
627628
"color-background-button-primary-disabled": "#2a2e33",
628629
"color-background-button-primary-hover": "#00a1c9",
629630
"color-background-calendar-today": "#16191f",
631+
"color-background-cell-shaded": "#16191f",
630632
"color-background-code-editor-gutter-active-line-default": "#879596",
631633
"color-background-code-editor-gutter-active-line-error": "#ff5d64",
632634
"color-background-code-editor-gutter-default": "#21252c",
@@ -1204,6 +1206,7 @@ Object {
12041206
"color-background-button-primary-disabled": "#ffffff",
12051207
"color-background-button-primary-hover": "#0a4a74",
12061208
"color-background-calendar-today": "#f2f3f3",
1209+
"color-background-cell-shaded": "#f2f3f3",
12071210
"color-background-code-editor-gutter-active-line-default": "#687078",
12081211
"color-background-code-editor-gutter-active-line-error": "#d13212",
12091212
"color-background-code-editor-gutter-default": "#f2f3f3",
@@ -1781,6 +1784,7 @@ Object {
17811784
"color-background-button-primary-disabled": "#ffffff",
17821785
"color-background-button-primary-hover": "#0a4a74",
17831786
"color-background-calendar-today": "#f2f3f3",
1787+
"color-background-cell-shaded": "#f2f3f3",
17841788
"color-background-code-editor-gutter-active-line-default": "#687078",
17851789
"color-background-code-editor-gutter-active-line-error": "#d13212",
17861790
"color-background-code-editor-gutter-default": "#f2f3f3",
@@ -2358,6 +2362,7 @@ Object {
23582362
"color-background-button-primary-disabled": "#e9ebed",
23592363
"color-background-button-primary-hover": "#033160",
23602364
"color-background-calendar-today": "#f4f4f4",
2365+
"color-background-cell-shaded": "#f8f8f8",
23612366
"color-background-code-editor-gutter-active-line-default": "#5f6b7a",
23622367
"color-background-code-editor-gutter-active-line-error": "#d91515",
23632368
"color-background-code-editor-gutter-default": "#f4f4f4",
@@ -2935,6 +2940,7 @@ Object {
29352940
"color-background-button-primary-disabled": "#354150",
29362941
"color-background-button-primary-hover": "#89bdee",
29372942
"color-background-calendar-today": "#354150",
2943+
"color-background-cell-shaded": "#232f3e",
29382944
"color-background-code-editor-gutter-active-line-default": "#7d8998",
29392945
"color-background-code-editor-gutter-active-line-error": "#eb6f6f",
29402946
"color-background-code-editor-gutter-default": "#192534",
@@ -3512,6 +3518,7 @@ Object {
35123518
"color-background-button-primary-disabled": "#354150",
35133519
"color-background-button-primary-hover": "#89bdee",
35143520
"color-background-calendar-today": "#354150",
3521+
"color-background-cell-shaded": "#232f3e",
35153522
"color-background-code-editor-gutter-active-line-default": "#7d8998",
35163523
"color-background-code-editor-gutter-active-line-error": "#eb6f6f",
35173524
"color-background-code-editor-gutter-default": "#192534",

src/__tests__/__snapshots__/documenter.test.ts.snap

+46
Original file line numberDiff line numberDiff line change
@@ -3929,6 +3929,11 @@ The values for all configured preferences are present even if the user didn't ch
39293929
"optional": true,
39303930
"type": "number",
39313931
},
3932+
Object {
3933+
"name": "stripedRows",
3934+
"optional": true,
3935+
"type": "false | true",
3936+
},
39323937
Object {
39333938
"name": "visibleContent",
39343939
"optional": true,
@@ -4057,6 +4062,11 @@ It contains the following:
40574062
"optional": true,
40584063
"type": "number",
40594064
},
4065+
Object {
4066+
"name": "stripedRows",
4067+
"optional": true,
4068+
"type": "false | true",
4069+
},
40604070
Object {
40614071
"name": "visibleContent",
40624072
"optional": true,
@@ -4074,6 +4084,36 @@ It contains the following:
40744084
"optional": true,
40754085
"type": "CollectionPreferencesProps.Preferences<CustomPreferenceType>",
40764086
},
4087+
Object {
4088+
"description": "Configures the built-in \\"striped rows\\" preference.
4089+
If you set it, the component displays this preference in the modal.
4090+
4091+
It contains the following:
4092+
- \`label\` (string) - Specifies the label for the option checkbox.
4093+
- \`description\` (string) - Specifies the text displayed below the checkbox label.
4094+
4095+
You must set the current value in the \`preferences.stripedRows\` property.
4096+
",
4097+
"inlineType": Object {
4098+
"name": "CollectionPreferencesProps.StripedRowsPreference",
4099+
"properties": Array [
4100+
Object {
4101+
"name": "description",
4102+
"optional": false,
4103+
"type": "string",
4104+
},
4105+
Object {
4106+
"name": "label",
4107+
"optional": false,
4108+
"type": "string",
4109+
},
4110+
],
4111+
"type": "object",
4112+
},
4113+
"name": "stripedRowsPreference",
4114+
"optional": true,
4115+
"type": "CollectionPreferencesProps.StripedRowsPreference",
4116+
},
40774117
Object {
40784118
"description": "Specifies the title of the preferences modal dialog. It is also used as an \`aria-label\` for the trigger button.",
40794119
"name": "title",
@@ -11100,6 +11140,12 @@ need to position the sticky header below other fixed position elements on the pa
1110011140
"optional": true,
1110111141
"type": "number",
1110211142
},
11143+
Object {
11144+
"description": "Specifies if table rows alternate being shaded and unshaded. If set to \`true\`, every other row will be shaded.",
11145+
"name": "stripedRows",
11146+
"optional": true,
11147+
"type": "boolean",
11148+
},
1110311149
Object {
1110411150
"description": "Use this property to inform screen readers how many items there are in a table.
1110511151
It specifies the total count of all items in a table.

src/collection-preferences/__tests__/collection-preferences.test.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
visibleContentPreference,
77
pageSizePreference,
88
wrapLinesPreference,
9+
stripedRowsPreference,
910
} from './shared';
1011

1112
const expectVisibleModal = (wrapper: CollectionPreferencesWrapper, visible = true) => {
@@ -122,18 +123,21 @@ describe('Collection preferences - Preferences display', () => {
122123
expect(wrapper.findModal()!.findPageSizePreference()).toBeNull();
123124
expect(wrapper.findModal()!.findVisibleContentPreference()).toBeNull();
124125
expect(wrapper.findModal()!.findWrapLinesPreference()).toBeNull();
126+
expect(wrapper.findModal()!.findStripedRowsPreference()).toBeNull();
125127
expect(wrapper.findModal()!.findCustomPreference()).toBeNull();
126128
});
127129
test('displays predefined preferences', () => {
128130
const wrapper = renderCollectionPreferences({
129131
pageSizePreference,
130132
visibleContentPreference,
131133
wrapLinesPreference,
134+
stripedRowsPreference,
132135
});
133136
wrapper.findTriggerButton().click();
134137
expect(wrapper.findModal()!.findPageSizePreference()).not.toBeNull();
135138
expect(wrapper.findModal()!.findVisibleContentPreference()).not.toBeNull();
136139
expect(wrapper.findModal()!.findWrapLinesPreference()).not.toBeNull();
140+
expect(wrapper.findModal()!.findStripedRowsPreference()).not.toBeNull();
137141
});
138142
test('displays custom preference when no predefined preference is specified', () => {
139143
const wrapper = renderCollectionPreferences({ customPreference: () => 'CustomPref' });
@@ -146,6 +150,7 @@ describe('Collection preferences - Preferences display', () => {
146150
pageSizePreference,
147151
visibleContentPreference,
148152
wrapLinesPreference,
153+
stripedRowsPreference,
149154
});
150155
wrapper.findTriggerButton().click();
151156
expect(wrapper.findModal()!.findCustomPreference()!.getElement()).toHaveTextContent('CustomPref');

src/collection-preferences/__tests__/shared.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,8 @@ export const wrapLinesPreference: CollectionPreferencesProps.WrapLinesPreference
4747
label: 'Wrap lines label',
4848
description: 'Wrap lines description',
4949
};
50+
51+
export const stripedRowsPreference: CollectionPreferencesProps.StripedRowsPreference = {
52+
label: 'Striped rows label',
53+
description: 'Striped rows description',
54+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { CollectionPreferencesProps } from '../../../lib/components/collection-preferences';
4+
import { CollectionPreferencesWrapper } from '../../../lib/components/test-utils/dom';
5+
import { renderCollectionPreferences, stripedRowsPreference } from './shared';
6+
7+
function renderWithStripedRows(props: Partial<CollectionPreferencesProps>): CollectionPreferencesWrapper {
8+
return renderCollectionPreferences({ stripedRowsPreference, ...props });
9+
}
10+
11+
const isChecked = (wrapper: CollectionPreferencesWrapper, checked = true) => {
12+
if (checked) {
13+
expect(wrapper.findModal()!.findStripedRowsPreference()!.findNativeInput().getElement()).toBeChecked();
14+
} else {
15+
expect(wrapper.findModal()!.findStripedRowsPreference()!.findNativeInput().getElement()).not.toBeChecked();
16+
}
17+
};
18+
19+
describe('Striped rows', () => {
20+
test('correctly displays label and description', () => {
21+
const wrapper = renderWithStripedRows({});
22+
wrapper.findTriggerButton().click();
23+
expect(wrapper.findModal()!.findStripedRowsPreference()!.findLabel()!.getElement()).toHaveTextContent(
24+
'Striped rows label'
25+
);
26+
expect(wrapper.findModal()!.findStripedRowsPreference()!.findDescription()!.getElement()).toHaveTextContent(
27+
'Striped rows description'
28+
);
29+
});
30+
test('displays as checked when value specified in preferences property is true', () => {
31+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: true }, onConfirm: () => {} });
32+
wrapper.findTriggerButton().click();
33+
isChecked(wrapper);
34+
});
35+
test('displays as checked when value specified in preferences property is true', () => {
36+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: false }, onConfirm: () => {} });
37+
wrapper.findTriggerButton().click();
38+
isChecked(wrapper, false);
39+
});
40+
test('changes temporary value upon click', () => {
41+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: true }, onConfirm: () => {} });
42+
wrapper.findTriggerButton().click();
43+
wrapper.findModal()!.findStripedRowsPreference()!.findNativeInput().click();
44+
isChecked(wrapper, false);
45+
});
46+
test('restores previous value on dismiss', () => {
47+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: true }, onConfirm: () => {} });
48+
wrapper.findTriggerButton().click();
49+
wrapper.findModal()!.findStripedRowsPreference()!.findNativeInput().click();
50+
isChecked(wrapper, false);
51+
wrapper.findModal()!.findDismissButton()!.click();
52+
wrapper.findTriggerButton().click();
53+
isChecked(wrapper);
54+
});
55+
test('restores previous value on cancel', () => {
56+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: true }, onConfirm: () => {} });
57+
wrapper.findTriggerButton().click();
58+
wrapper.findModal()!.findStripedRowsPreference()!.findNativeInput().click();
59+
isChecked(wrapper, false);
60+
wrapper.findModal()!.findCancelButton()!.click();
61+
wrapper.findTriggerButton().click();
62+
isChecked(wrapper);
63+
});
64+
test('decorates onConfim event details correctly upon change', () => {
65+
const onConfirmSpy = jest.fn();
66+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: true }, onConfirm: onConfirmSpy });
67+
wrapper.findTriggerButton().click();
68+
wrapper.findModal()!.findStripedRowsPreference()!.findNativeInput().click();
69+
wrapper.findModal()!.findConfirmButton()!.click();
70+
expect(onConfirmSpy).toHaveBeenCalledTimes(1);
71+
expect(onConfirmSpy).toHaveBeenCalledWith(expect.objectContaining({ detail: { stripedRows: false } }));
72+
});
73+
test('decorates onConfim event details correctly even without change', () => {
74+
const onConfirmSpy = jest.fn();
75+
const wrapper = renderWithStripedRows({ preferences: { stripedRows: true }, onConfirm: onConfirmSpy });
76+
wrapper.findTriggerButton().click();
77+
wrapper.findModal()!.findConfirmButton()!.click();
78+
expect(onConfirmSpy).toHaveBeenCalledTimes(1);
79+
expect(onConfirmSpy).toHaveBeenCalledWith(expect.objectContaining({ detail: { stripedRows: true } }));
80+
});
81+
});

0 commit comments

Comments
 (0)