Skip to content

Commit ba9eff6

Browse files
committed
feat: Support Table Column Groups collection prefernces
1 parent b30df34 commit ba9eff6

15 files changed

Lines changed: 1199 additions & 96 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
5+
import Box from '~components/box';
6+
import CollectionPreferences, { CollectionPreferencesProps } from '~components/collection-preferences';
7+
import SpaceBetween from '~components/space-between';
8+
9+
import { contentDisplayPreferenceI18nStrings } from '../common/i18n-strings';
10+
import {
11+
baseProperties,
12+
contentDisplayGroups,
13+
groupedContentDisplay,
14+
groupedContentDisplayOptions,
15+
} from './shared-configs';
16+
17+
export default function ContentDisplayGroupsPage() {
18+
const [preferences, setPreferences] = useState<CollectionPreferencesProps.Preferences>({
19+
contentDisplay: groupedContentDisplay,
20+
});
21+
22+
return (
23+
<SpaceBetween size="l">
24+
<h1>Content Display with Groups</h1>
25+
26+
<CollectionPreferences
27+
{...baseProperties}
28+
preferences={preferences}
29+
onConfirm={({ detail }) => setPreferences(detail)}
30+
contentDisplayPreference={{
31+
title: 'Column preferences',
32+
description: 'Customize column visibility and order.',
33+
options: groupedContentDisplayOptions,
34+
groups: contentDisplayGroups,
35+
enableColumnFiltering: true,
36+
...contentDisplayPreferenceI18nStrings,
37+
}}
38+
/>
39+
40+
<Box variant="h2">Current preferences.contentDisplay</Box>
41+
<pre
42+
tabIndex={0}
43+
style={{ background: '#f4f4f4', padding: '12px', borderRadius: '4px', overflow: 'auto', maxHeight: '400px' }}
44+
>
45+
{JSON.stringify(preferences.contentDisplay, null, 2)}
46+
</pre>
47+
</SpaceBetween>
48+
);
49+
}

pages/collection-preferences/shared-configs.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,56 @@ export const customPreference = (customState: boolean) => (
9696
View as
9797
</Checkbox>
9898
);
99+
100+
export const groupedContentDisplayOptions: CollectionPreferencesProps.ContentDisplayOption[] = [
101+
{ id: 'id', label: 'Instance ID', alwaysVisible: true },
102+
{ id: 'name', label: 'Name' },
103+
{ id: 'type', label: 'Instance type' },
104+
{ id: 'az', label: 'Availability zone' },
105+
{ id: 'state', label: 'State' },
106+
{ id: 'cpu', label: 'CPU (%)' },
107+
{ id: 'memory', label: 'Memory (%)' },
108+
{ id: 'netIn', label: 'Network in (MB/s)' },
109+
{ id: 'netOut', label: 'Network out (MB/s)' },
110+
{ id: 'cost', label: 'Monthly cost ($)' },
111+
];
112+
113+
export const contentDisplayGroups: CollectionPreferencesProps.ContentDisplayOptionGroup[] = [
114+
{ id: 'config', label: 'Configuration' },
115+
{ id: 'performance', label: 'Performance' },
116+
{ id: 'network', label: 'Network' },
117+
];
118+
119+
export const groupedContentDisplay: CollectionPreferencesProps.ContentDisplayItem[] = [
120+
{ id: 'id', visible: true },
121+
{ id: 'name', visible: true },
122+
{
123+
type: 'group',
124+
id: 'config',
125+
visible: true,
126+
children: [
127+
{ id: 'type', visible: true },
128+
{ id: 'az', visible: true },
129+
{ id: 'state', visible: true },
130+
],
131+
},
132+
{
133+
type: 'group',
134+
id: 'performance',
135+
visible: true,
136+
children: [
137+
{ id: 'cpu', visible: true },
138+
{ id: 'memory', visible: true },
139+
],
140+
},
141+
{
142+
type: 'group',
143+
id: 'network',
144+
visible: true,
145+
children: [
146+
{ id: 'netIn', visible: true },
147+
{ id: 'netOut', visible: true },
148+
],
149+
},
150+
{ id: 'cost', visible: true },
151+
];

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

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8916,6 +8916,9 @@ It contains the following:
89168916
- \`title\` (string) - Specifies the text displayed at the top of the preference.
89178917
- \`description\` (string) - Specifies the description displayed below the title.
89188918
- \`options\` - Specifies an array of options for reordering and visible content selection.
8919+
- \`groups\` - (Optional) Specifies an array of column group definitions for multi-level content display. Each group contains:
8920+
- \`id\` (string) - A unique identifier for the group.
8921+
- \`label\` (string) - The text displayed as the group label.
89198922
- \`enableColumnFiltering\` (boolean) - Adds a columns filter.
89208923
- \`liveAnnouncementDndStarted\` ((position: number, total: number) => string) - (Optional) Adds a message to be announced by screen readers when an option is picked.
89218924
- \`liveAnnouncementDndDiscarded\` (string) - (Optional) Adds a message to be announced by screen readers when a reordering action is canceled.
@@ -8929,7 +8932,17 @@ Each option contains the following:
89298932
- \`label\` (string) - Specifies a short description of the content.
89308933
- \`alwaysVisible\` (boolean) - (Optional) Determines whether the visibility is always on and therefore cannot be toggled. This is set to \`false\` by default.
89318934

8932-
You must provide an ordered list of the items to display in the \`preferences.contentDisplay\` property.",
8935+
You must provide an ordered list of the items to display in the \`preferences.contentDisplay\` property.
8936+
Each content display item is one of the following:
8937+
- \`ContentDisplayColumn\` - Represents a single column.
8938+
- \`type\` ('column') - (Optional) Identifies the entry as a column. Defaults to \`'column'\` when omitted.
8939+
- \`id\` (string) - The column identifier.
8940+
- \`visible\` (boolean) - Whether the column is visible.
8941+
- \`ContentDisplayGroup\` - Represents a column group.
8942+
- \`type\` ('group') - Identifies the entry as a group.
8943+
- \`id\` (string) - The group identifier.
8944+
- \`visible\` (boolean) - Whether the group is visible.
8945+
- \`children\` (ReadonlyArray<ContentDisplayItem>) - The columns or nested groups within this group.",
89338946
"i18nTag": true,
89348947
"inlineType": {
89358948
"name": "CollectionPreferencesProps.ContentDisplayPreference",
@@ -8954,6 +8967,11 @@ You must provide an ordered list of the items to display in the \`preferences.co
89548967
"optional": true,
89558968
"type": "boolean",
89568969
},
8970+
{
8971+
"name": "groups",
8972+
"optional": true,
8973+
"type": "ReadonlyArray<CollectionPreferencesProps.ContentDisplayOptionGroup>",
8974+
},
89578975
{
89588976
"inlineType": {
89598977
"name": "CollectionPreferencesProps.ContentDisplayPreferenceI18nStrings",
@@ -37338,9 +37356,23 @@ Returns the current value of the input.",
3733837356
},
3733937357
},
3734037358
{
37341-
"description": "Returns options that the user can reorder.",
37359+
"description": "Returns the top-level items in the preference list.
37360+
37361+
For tables **without** column grouping this returns all column options.
37362+
For tables **with** column grouping this returns the top-level entries only
37363+
(which are group items). Use \`.findChildrenOptions()\` on a group item to
37364+
access the leaf columns nested within it.",
3734237365
"name": "findOptions",
37343-
"parameters": [],
37366+
"parameters": [
37367+
{
37368+
"defaultValue": "{}",
37369+
"flags": {
37370+
"isOptional": false,
37371+
},
37372+
"name": "option",
37373+
"typeName": "{ group?: boolean | undefined; }",
37374+
},
37375+
],
3734437376
"returnType": {
3734537377
"isNullable": false,
3734637378
"name": "Array",
@@ -37379,6 +37411,33 @@ Returns the current value of the input.",
3737937411
},
3738037412
{
3738137413
"methods": [
37414+
{
37415+
"description": "Returns all child option items nested under this item when it is a group.
37416+
Returns \`null\` when this item is a leaf column (has no nested children).
37417+
37418+
The children are the leaf-level \`ContentDisplayOptionWrapper\`s inside the group's
37419+
nested \`InternalList\` — i.e. they already carry a drag handle and visibility toggle.",
37420+
"name": "findChildrenOptions",
37421+
"parameters": [
37422+
{
37423+
"defaultValue": "{}",
37424+
"flags": {
37425+
"isOptional": false,
37426+
},
37427+
"name": "option",
37428+
"typeName": "{ group?: boolean | undefined; }",
37429+
},
37430+
],
37431+
"returnType": {
37432+
"isNullable": true,
37433+
"name": "Array",
37434+
"typeArguments": [
37435+
{
37436+
"name": "ContentDisplayOptionWrapper",
37437+
},
37438+
],
37439+
},
37440+
},
3738237441
{
3738337442
"description": "Returns the drag handle for the option item.",
3738437443
"name": "findDragHandle",
@@ -37408,7 +37467,8 @@ Returns the current value of the input.",
3740837467
},
3740937468
},
3741037469
{
37411-
"description": "Returns the visibility toggle for the option item.",
37470+
"description": "Returns the visibility toggle for the option item.
37471+
Note that, despite its typings, this may return null for group items since groups do not have a visibility toggle.",
3741237472
"name": "findVisibilityToggle",
3741337473
"parameters": [],
3741437474
"returnType": {
@@ -48211,9 +48271,23 @@ To find a specific item use the \`findBreadcrumbLink(n)\` function as chaining \
4821148271
},
4821248272
},
4821348273
{
48214-
"description": "Returns options that the user can reorder.",
48274+
"description": "Returns the top-level items in the preference list.
48275+
48276+
For tables **without** column grouping this returns all column options.
48277+
For tables **with** column grouping this returns the top-level entries only
48278+
(which are group items). Use \`.findChildrenOptions()\` on a group item to
48279+
access the leaf columns nested within it.",
4821548280
"name": "findOptions",
48216-
"parameters": [],
48281+
"parameters": [
48282+
{
48283+
"defaultValue": "{}",
48284+
"flags": {
48285+
"isOptional": false,
48286+
},
48287+
"name": "option",
48288+
"typeName": "{ group?: boolean | undefined; }",
48289+
},
48290+
],
4821748291
"returnType": {
4821848292
"isNullable": false,
4821948293
"name": "MultiElementWrapper",
@@ -48247,6 +48321,33 @@ To find a specific item use the \`findBreadcrumbLink(n)\` function as chaining \
4824748321
},
4824848322
{
4824948323
"methods": [
48324+
{
48325+
"description": "Returns all child option items nested under this item when it is a group.
48326+
Returns \`null\` when this item is a leaf column (has no nested children).
48327+
48328+
The children are the leaf-level \`ContentDisplayOptionWrapper\`s inside the group's
48329+
nested \`InternalList\` — i.e. they already carry a drag handle and visibility toggle.",
48330+
"name": "findChildrenOptions",
48331+
"parameters": [
48332+
{
48333+
"defaultValue": "{}",
48334+
"flags": {
48335+
"isOptional": false,
48336+
},
48337+
"name": "option",
48338+
"typeName": "{ group?: boolean | undefined; }",
48339+
},
48340+
],
48341+
"returnType": {
48342+
"isNullable": true,
48343+
"name": "MultiElementWrapper",
48344+
"typeArguments": [
48345+
{
48346+
"name": "ContentDisplayOptionWrapper",
48347+
},
48348+
],
48349+
},
48350+
},
4825048351
{
4825148352
"description": "Returns the drag handle for the option item.",
4825248353
"name": "findDragHandle",
@@ -48266,7 +48367,8 @@ To find a specific item use the \`findBreadcrumbLink(n)\` function as chaining \
4826648367
},
4826748368
},
4826848369
{
48269-
"description": "Returns the visibility toggle for the option item.",
48370+
"description": "Returns the visibility toggle for the option item.
48371+
Note that, despite its typings, this may return null for group items since groups do not have a visibility toggle.",
4827048372
"name": "findVisibilityToggle",
4827148373
"parameters": [],
4827248374
"returnType": {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ exports[`test-utils selectors 1`] = `
155155
"awsui_content-before_tc96w",
156156
"awsui_content-density_tc96w",
157157
"awsui_content-display-description_tc96w",
158+
"awsui_content-display-group-children_tc96w",
159+
"awsui_content-display-group-header_tc96w",
158160
"awsui_content-display-no-match_tc96w",
159161
"awsui_content-display-option-content_tc96w",
160162
"awsui_content-display-option-label_tc96w",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';
4+
5+
import createWrapper from '../../../../lib/components/test-utils/selectors';
6+
import ContentDisplayPageObject from './pages/content-display-page';
7+
8+
const windowDimensions = {
9+
width: 1200,
10+
height: 1200,
11+
};
12+
13+
const setupTest = (testFn: (page: ContentDisplayPageObject) => Promise<void>) => {
14+
return useBrowser(async browser => {
15+
const page = new ContentDisplayPageObject(browser);
16+
await browser.url('#/light/collection-preferences/content-display-groups');
17+
await page.setWindowSize(windowDimensions);
18+
page.wrapper = createWrapper().findCollectionPreferences();
19+
await page.openCollectionPreferencesModal();
20+
await testFn(page);
21+
});
22+
};
23+
24+
describe('Collection preferences - Grouped Content Display', () => {
25+
test(
26+
'renders group headers and leaf options',
27+
setupTest(async page => {
28+
const modal = page.wrapper.findModal().findContentDisplayPreference();
29+
const options = modal.findOptions();
30+
31+
// Should have options rendered
32+
const texts = await page.getElementsText(options.toSelector());
33+
expect(texts.length).toBeGreaterThan(0);
34+
35+
// Should contain group labels
36+
const content = await page.getText(modal.toSelector());
37+
expect(content).toContain('Configuration');
38+
expect(content).toContain('Performance');
39+
expect(content).toContain('Network');
40+
})
41+
);
42+
43+
test(
44+
'toggles visibility of a leaf option within a group',
45+
setupTest(async page => {
46+
const modal = page.wrapper.findModal().findContentDisplayPreference();
47+
const options = modal.findOptions();
48+
const firstOption = options.get(1);
49+
const toggle = firstOption.findVisibilityToggle().findNativeInput();
50+
51+
// Toggle visibility
52+
await page.click(toggle.toSelector());
53+
})
54+
);
55+
56+
test(
57+
'reorders a group item with drag and drop',
58+
setupTest(async page => {
59+
const modal = page.wrapper.findModal().findContentDisplayPreference();
60+
const options = modal.findOptions();
61+
62+
// Get initial order
63+
const initialTexts = await page.getElementsText(options.toSelector());
64+
expect(initialTexts.length).toBeGreaterThan(0);
65+
66+
// Drag first item down
67+
const activeDragHandle = options.get(1).findDragHandle();
68+
const targetDragHandle = options.get(3).findDragHandle();
69+
await page.dragAndDropTo(activeDragHandle.toSelector(), targetDragHandle.toSelector());
70+
71+
// Order should have changed
72+
const newTexts = await page.getElementsText(options.toSelector());
73+
expect(newTexts).not.toEqual(initialTexts);
74+
})
75+
);
76+
77+
test(
78+
'filters options within groups',
79+
setupTest(async page => {
80+
const modal = page.wrapper.findModal().findContentDisplayPreference();
81+
const filterInput = modal.findTextFilter().findInput().findNativeInput();
82+
83+
// Type a filter
84+
await page.click(filterInput.toSelector());
85+
await page.keys('Network');
86+
87+
// Should show filtered results
88+
const content = await page.getText(modal.toSelector());
89+
expect(content).toContain('Network');
90+
})
91+
);
92+
93+
test(
94+
'nested list has aria-label matching group name',
95+
setupTest(async page => {
96+
const modal = page.wrapper.findModal().findContentDisplayPreference();
97+
// Verify nested lists exist by checking content
98+
const content = await page.getText(modal.toSelector());
99+
expect(content).toContain('Configuration');
100+
})
101+
);
102+
});

0 commit comments

Comments
 (0)