Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
0047299
feat: initial commit
cansuaa May 6, 2025
5f8ee38
fix a11y test findings
cansuaa May 7, 2025
f8148f4
update test utils snapshots
cansuaa May 7, 2025
a52e05f
test fixes
cansuaa May 7, 2025
1863bbe
test fix
cansuaa May 7, 2025
25a97ab
updates
cansuaa May 15, 2025
e8d8042
Merge branch 'main' into cansua/treeview
cansuaa May 15, 2025
debfb65
updates
cansuaa May 22, 2025
e0336ce
Merge branch 'main' into cansua/treeview
cansuaa May 22, 2025
b66fdcd
update snapshot
cansuaa May 22, 2025
887fc01
updates to interface, dev pages and added tests
cansuaa May 30, 2025
7cb3e78
change name from Treeview to TreeView
cansuaa Jun 3, 2025
60f1d79
Merge branch 'main' into cansua/treeview
cansuaa Jun 3, 2025
ea2bb74
change page folder name and add a11y tests
cansuaa Jun 3, 2025
03e7bd1
display grid and structured item transition
cansuaa Jun 10, 2025
93bf261
test utils update
cansuaa Jun 10, 2025
7dad45f
renderItemToggleIcon
cansuaa Jun 12, 2025
93a709e
Merge branch 'main' into cansua/treeview
cansuaa Jun 12, 2025
5aeac36
test util update
cansuaa Jun 12, 2025
b721cca
remove connector lines
cansuaa Jun 12, 2025
d63cc11
adjust focus offset of toggle
cansuaa Jun 12, 2025
20520ce
use internal ExpandToggle in table and update snapshots
cansuaa Jun 12, 2025
4faa0de
Merge branch 'main' into cansua/treeview
cansuaa Jun 12, 2025
457bef8
cleanup
cansuaa Jun 12, 2025
68ce238
remove role group from ul
cansuaa Jun 12, 2025
01e8b39
rename dev page
cansuaa Jun 12, 2025
e21a09d
update API and test util descriptions
cansuaa Jun 23, 2025
45f6025
addressed comments
cansuaa Jun 23, 2025
13fd9a0
address comments and add tree-view to the required props list
cansuaa Jun 23, 2025
d17ad91
i18n translations
cansuaa Jun 24, 2025
4414810
move dev page items to a different folder
cansuaa Jun 25, 2025
61f01ca
add a permutations dev page
cansuaa Jun 25, 2025
3d956e3
update structured item
cansuaa Jun 25, 2025
d7d054f
update treeitem styles and move them to a separate file
cansuaa Jun 25, 2025
f150c78
enable renderItemToggleIcon for both core and console
cansuaa Jun 25, 2025
36b320a
make texts longer in permutation page
cansuaa Jun 25, 2025
6abdd5b
change icon for the custom icon with slow animation toggle icon type
cansuaa Jun 25, 2025
d16d67a
update heading in permutations dev page
cansuaa Jun 25, 2025
6ed1222
Merge branch 'main' into cansua/treeview
cansuaa Jun 25, 2025
aa44c4c
add integ test
cansuaa Jun 25, 2025
b4a7192
remove with-motion mixin from dev page styles and move rule to only a…
cansuaa Jun 25, 2025
cefe3b9
Revert "i18n translations" to add them to its own PR
cansuaa Jun 25, 2025
60c2773
updates in dev pages
cansuaa Jun 26, 2025
432e32c
Merge branch 'main' into cansua/treeview
cansuaa Jun 26, 2025
d68c485
remove duplicate tree-view entry in messages-types
cansuaa Jun 26, 2025
a4981e3
chore: Adjust toggle position
pan-kot Jun 26, 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
4 changes: 2 additions & 2 deletions .stylelintrc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
]
}
],
"@cloudscape-design/no-motion-outside-of-mixin": [true],
"@cloudscape-design/z-index-value-constraint": [true],
"plugin/no-unsupported-browser-features": [
true,
Expand All @@ -58,7 +57,8 @@
],
"rules": {
"property-disallowed-list": ["border", "border-radius", "border-style", "margin", "padding"],
"csstools/use-logical": "always"
"csstools/use-logical": "always",
"@cloudscape-design/no-motion-outside-of-mixin": [true]
}
}
],
Expand Down
1 change: 1 addition & 0 deletions build-tools/utils/pluralize.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const pluralizationMap = {
ToggleButton: 'ToggleButtons',
TokenGroup: 'TokenGroups',
TopNavigation: 'TopNavigations',
TreeView: 'TreeViews',
TutorialPanel: 'TutorialPanels',
Wizard: 'Wizards',
};
Expand Down
109 changes: 109 additions & 0 deletions pages/tree-view/basic.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';
import clsx from 'clsx';

import Box from '~components/box';
import Container from '~components/container';
import FormField from '~components/form-field';
import Grid from '~components/grid';
import Icon from '~components/icon';
import Select, { SelectProps } from '~components/select';
import TreeView, { TreeViewProps } from '~components/tree-view';

import ScreenshotArea from '../utils/screenshot-area';
import { Actions } from './common';
import { items } from './items/basic-page-items';

import styles from './styles.scss';

export default function BasicTreeView() {
const [expandedItems, setExpandedItems] = useState<Array<string>>(['1', '4.1']);
const [toggleIconType, setToggleIconType] = useState<SelectProps.Option>({
label: 'Default',
value: 'default',
});

const renderItemToggleIcon = ({ expanded }: TreeViewProps.ItemToggleRenderIconData) => {
if (toggleIconType.value === 'custom') {
return <Icon size="small" name={expanded ? 'treeview-collapse' : 'treeview-expand'} ariaLabel="Toggle" />;
}

if (toggleIconType.value === 'custom-with-slow-animation') {
return (
<Icon
size="small"
name="angle-down"
className={clsx(styles.animation, expanded && styles['animation-expanded'])}
/>
);
}
};

return (
<ScreenshotArea>
<h1>Basic tree view</h1>

<Grid gridDefinition={[{ colspan: { m: 7, xs: 12 } }]}>
<div>
<FormField label="Toggle icon" stretch={true}>
<Select
selectedOption={toggleIconType}
onChange={({ detail }) => setToggleIconType(detail.selectedOption)}
options={[
{
label: 'Default',
value: 'default',
},
{
label: 'Custom',
value: 'custom',
},
{
label: 'Custom with slow animation',
value: 'custom-with-slow-animation',
},
]}
/>
</FormField>

<br />

<Container>
<TreeView
ariaLabel="Basic tree view"
items={items}
renderItem={item => {
return {
icon: item.hideIcon ? undefined : (
<Icon name={expandedItems.includes(item.id) ? 'folder-open' : 'folder'} ariaLabel="folder" />
),
content: item.content,
secondaryContent: item.details && <Box color="text-status-inactive">{item.details}</Box>,
actions: item.hasActions ? <Actions actionType="inline-button-dropdown" /> : undefined,
};
}}
getItemId={item => item.id}
getItemChildren={item => item.children}
onItemToggle={({ detail }: any) => {
if (detail.expanded) {
return setExpandedItems(prev => [...prev, detail.item.id]);
} else {
return setExpandedItems(prev => prev.filter(id => id !== detail.item.id));
}
}}
expandedItems={expandedItems}
i18nStrings={{
expandButtonLabel: () => 'Expand item',
collapseButtonLabel: () => 'Collapse item',
}}
renderItemToggleIcon={renderItemToggleIcon}
/>
</Container>
</div>
</Grid>

<div style={{ marginTop: '10px' }}>Expanded items: {expandedItems.map(id => `Item ${id}`).join(', ')}</div>
</ScreenshotArea>
);
}
121 changes: 121 additions & 0 deletions pages/tree-view/common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';

import { SpaceBetween } from '~components';
import Box from '~components/box';
import Button from '~components/button';
import ButtonDropdown from '~components/button-dropdown';
import ButtonGroup from '~components/button-group';
import StatusIndicator from '~components/status-indicator/internal';

import { Item } from './items/dynamic-items';

export function Content(item: Item) {
if (item.status) {
return <StatusIndicator type={item.status}>{item.name}</StatusIndicator>;
}

return (
<div style={{ display: 'flex', gap: '10px' }}>
{item.name}

{item.errorCount && item.errorCount > 0 && <StatusIndicator type="error">{item.errorCount}</StatusIndicator>}
{item.warningCount && item.warningCount > 0 && (
<StatusIndicator type="warning">{item.warningCount}</StatusIndicator>
)}
{item.successCount && item.successCount > 0 && (
<StatusIndicator type="success">{item.successCount}</StatusIndicator>
)}
</div>
);
}

export function Actions(
{
actionType,
}: {
actionType?: 'button-group' | 'button-dropdown' | 'inline-button-dropdown' | 'text' | 'custom-inline-button-group';
} = {
actionType: 'inline-button-dropdown',
}
) {
const [markedAsFavorite, setMarkedAsFavorite] = useState(false);

if (actionType === 'text') {
return <Box color="text-status-inactive">Some metadata</Box>;
}

if (actionType === 'button-group') {
return (
<ButtonGroup
variant="icon"
items={[
{
id: 'settings',
iconName: 'settings',
type: 'icon-button',
text: 'Settings',
},
{
type: 'icon-toggle-button',
id: 'favorite',
text: 'Favorite',
pressed: markedAsFavorite,
iconName: 'star',
pressedIconName: 'star-filled',
},
{
id: 'menu',
type: 'menu-dropdown',
text: 'Menu',
items: [
{ id: 'start', text: 'Start' },
{ id: 'stop', text: 'Stop', disabled: true },
{
id: 'hibernate',
text: 'Hibernate',
disabled: true,
},
{ id: 'reboot', text: 'Reboot', disabled: true },
{ id: 'terminate', text: 'Terminate' },
],
},
]}
onItemClick={({ detail }) => {
if (detail.id === 'favorite') {
setMarkedAsFavorite(!markedAsFavorite);
}
}}
/>
);
}

const buttonDropdownItems = [
{ id: 'start', text: 'Start' },
{ id: 'stop', text: 'Stop', disabled: true },
{
id: 'hibernate',
text: 'Hibernate',
disabled: true,
},
{ id: 'reboot', text: 'Reboot', disabled: true },
{ id: 'terminate', text: 'Terminate' },
];

if (actionType === 'custom-inline-button-group') {
return (
<SpaceBetween direction="horizontal" size="s">
<Button variant="inline-icon" iconName="settings" />
<Button variant="inline-icon" iconName="star" />
<ButtonDropdown items={buttonDropdownItems} ariaLabel="Control instance" variant="inline-icon" />
</SpaceBetween>
);
}

if (actionType === 'inline-button-dropdown') {
return <ButtonDropdown items={buttonDropdownItems} ariaLabel="Control instance" variant="inline-icon" />;
}

return <ButtonDropdown items={buttonDropdownItems} ariaLabel="Control instance" variant="icon" />;
}
87 changes: 87 additions & 0 deletions pages/tree-view/dynamic-items.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';

import { Button, SpaceBetween } from '~components';
import Box from '~components/box';
import Icon from '~components/icon';
import TreeView from '~components/tree-view';

import { Actions, Content } from './common';
import { allItems, items } from './items/dynamic-items';

const allExpandableItemIds = allItems.filter(item => item.children && item.children.length > 0).map(item => item.id);

export default function DynamicItemsPage() {
const [expandedItems, setExpandedItems] = useState<Array<string>>([]);

return (
<>
<h1>Dynamic items page</h1>

<SpaceBetween size="s">
<Button
onClick={() => {
setExpandedItems(allExpandableItemIds);
console.time('expand-all');
requestAnimationFrame(() => console.timeEnd('expand-all'));
}}
>
Expand all
</Button>

<Button
onClick={() => {
setExpandedItems([]);
console.time('collapse-all');
requestAnimationFrame(() => console.timeEnd('collapse-all'));
}}
>
Collapse all
</Button>
</SpaceBetween>

<Box padding="xl">
<TreeView
items={items}
renderItem={item => {
const isExpanded = expandedItems.includes(item.id);
return {
icon: <Icon name={isExpanded ? 'folder-open' : 'folder'} />,
content: <Content {...item} />,
actions: item.hasActions ? <Actions actionType="button-group" /> : undefined,
secondaryContent: item.tagName ? (
<Box color="text-status-inactive">
<SpaceBetween size="xxs" direction="horizontal">
<Icon name="ticket" />
<span>{item.tagName}</span>
</SpaceBetween>
</Box>
) : undefined,
};
}}
getItemId={item => item.id}
getItemChildren={item => item.children}
onItemToggle={({ detail }) => {
if (detail.expanded) {
const logName = `expand-item-${detail.item.name}`;
console.time(logName);
requestAnimationFrame(() => console.timeEnd(logName));
return setExpandedItems(prev => [...prev, detail.id]);
} else {
const logName = `collapse-item-${detail.item.name}`;
console.time(logName);
requestAnimationFrame(() => console.timeEnd(logName));
return setExpandedItems(prev => prev.filter(id => id !== detail.id));
}
}}
expandedItems={expandedItems}
i18nStrings={{
expandButtonLabel: () => 'Expand item',
collapseButtonLabel: () => 'Collapse item',
}}
/>
</Box>
</>
);
}
Loading