Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions pages/app/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import React from 'react';

import { Box, SpaceBetween } from '~components';
import { Box, Divider, SpaceBetween } from '~components';
import I18nProvider, { I18nProviderProps } from '~components/i18n';
import messages from '~components/i18n/messages/all.all';

Expand Down Expand Up @@ -32,7 +32,9 @@ export function SimplePage({ title, subtitle, settings, children, screenshotArea
{settings ? (
<SpaceBetween size="s">
<div>{settings}</div>
<hr />
<Box margin={{ vertical: 's' }}>
<Divider />
</Box>
</SpaceBetween>
) : null}

Expand Down
107 changes: 107 additions & 0 deletions pages/side-navigation/collapsed-panel-layout.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React, { useState } from 'react';

import { Box, Button, Icon, PanelLayout, SpaceBetween } from '~components';
import SideNavigation, { SideNavigationProps } from '~components/side-navigation';

const items: SideNavigationProps.Item[] = [
{ type: 'link', text: 'Home', href: '#/home', icon: <Icon name="gen-ai" /> },
{ type: 'link', text: 'Projects', href: '#/projects', icon: <Icon name="folder" /> },
{ type: 'link', text: 'Settings', href: '#/settings', icon: <Icon name="settings" /> },
{
type: 'section',
text: 'Account',
items: [
{ type: 'link', text: 'Profile', href: '#/profile', icon: <Icon name="user-profile" /> },
{ type: 'link', text: 'Billing', href: '#/billing', icon: <Icon name="file" /> },
],
},
];

const COLLAPSED_SIZE = 65;
const EXPANDED_SIZE = 220;
const MAX_SIZE = 400;
const COLLAPSE_THRESHOLD = 155;
const SNAP_BUFFER = 30;

export default function SideNavigationWithPanelLayoutPage() {
const [activeHref, setActiveHref] = useState('#/home');
const [panelSize, setPanelSize] = useState(EXPANDED_SIZE);

const collapsed = panelSize < COLLAPSE_THRESHOLD;

function handleResize({ detail }: { detail: { panelSize: number } }) {
const size = detail.panelSize;
if (collapsed && size > COLLAPSED_SIZE + SNAP_BUFFER) {
setPanelSize(Math.max(size, EXPANDED_SIZE));
} else if (!collapsed && size < COLLAPSE_THRESHOLD) {
setPanelSize(COLLAPSED_SIZE);
} else {
setPanelSize(size);
}
}

function toggleCollapse() {
setPanelSize(prev => (prev < COLLAPSE_THRESHOLD ? EXPANDED_SIZE : COLLAPSED_SIZE));
}

const nav = (
<div style={{ display: 'flex', flexDirection: 'column', minBlockSize: '100%', padding: '4px' }}>
<div
style={{
display: 'flex',
justifyContent: collapsed ? 'center' : 'flex-end',
alignSelf: 'stretch',
}}
>
<Button
iconName={collapsed ? 'angle-right' : 'angle-left'}
variant="icon"
onClick={toggleCollapse}
ariaLabel={collapsed ? 'Expand navigation' : 'Collapse navigation'}
/>
</div>
<SideNavigation
activeHref={activeHref}
items={items}
collapsed={collapsed}
variant="highlighted"
onFollow={e => {
e.preventDefault();
setActiveHref(e.detail.href);
}}
/>
</div>
);

const content = (
<div style={{ padding: '24px' }}>
<SpaceBetween size="m">
<Box variant="h1">SideNavigation with PanelLayout</Box>
<Box>
Panel size: {panelSize}px — Collapsed: {String(collapsed)}
</Box>
<Box color="text-status-inactive">
Drag the resize handle to resize the panel. Dragging below {COLLAPSE_THRESHOLD}px snaps to collapsed state.
</Box>
</SpaceBetween>
</div>
);

return (
<PanelLayout
panelSize={panelSize}
onPanelResize={handleResize}
minPanelSize={COLLAPSED_SIZE}
maxPanelSize={MAX_SIZE}
panelPosition="side-start"
resizable={true}
panelContent={nav}
mainContent={content}
panelFocusable={{ ariaLabel: 'Navigation panel' }}
mainFocusable={{ ariaLabel: 'Main content' }}
/>
);
}
145 changes: 145 additions & 0 deletions pages/side-navigation/collapsed-permutations.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React, { useState } from 'react';

import { Box, Button, Icon, SpaceBetween, Toggle } from '~components';
import SideNavigation, { SideNavigationProps } from '~components/side-navigation';

// Items covering all types to verify collapsed behavior
const allTypesItems: SideNavigationProps.Item[] = [
// Links with and without icons
{ type: 'link', text: 'Link with icon', href: '#/icon', icon: <Icon name="gen-ai" /> },
{ type: 'link', text: 'Link without icon (hidden collapsed)', href: '#/no-icon' },
{ type: 'divider' },
// Link group
{
type: 'link-group',
text: 'Link group (with icon)',
href: '#/group',
icon: <Icon name="folder" />,
items: [
{ type: 'link', text: 'Child 1', href: '#/child1' },
{ type: 'link', text: 'Child 2', href: '#/child2' },
],
},
{
type: 'link-group',
text: 'Link group (no icon, hidden)',
href: '#/group-no-icon',
items: [{ type: 'link', text: 'Child', href: '#/child' }],
},
{ type: 'divider' },
// Expandable link group
{
type: 'expandable-link-group',
text: 'Expandable group (with icon)',
href: '#/expandable',
icon: <Icon name="settings" />,
items: [
{ type: 'link', text: 'Sub 1', href: '#/sub1' },
{ type: 'link', text: 'Sub 2', href: '#/sub2' },
],
},
{
type: 'expandable-link-group',
text: 'Expandable group (no icon, hidden)',
href: '#/expandable-no-icon',
items: [{ type: 'link', text: 'Sub', href: '#/sub' }],
},
{ type: 'divider' },
// Section
{
type: 'section',
text: 'Section (header hidden in collapsed)',
items: [
{ type: 'link', text: 'Section child with icon', href: '#/s1', icon: <Icon name="file" /> },
{ type: 'link', text: 'Section child without icon (hidden)', href: '#/s2' },
{ type: 'link', text: 'Another with icon', href: '#/s3', icon: <Icon name="share" /> },
],
},
{ type: 'divider' },
// Section group
{
type: 'section-group',
title: 'Section group',
items: [
{
type: 'section',
text: 'Nested section',
items: [{ type: 'link', text: 'Nested item with icon', href: '#/n1', icon: <Icon name="user-profile" /> }],
},
],
},
];

export default function SideNavigationCollapsedPermutationsPage() {
const [activeHref, setActiveHref] = useState('#/icon');
const [collapsed, setCollapsed] = useState(false);
const [highlighted, setHighlighted] = useState(true);

return (
<div style={{ display: 'flex', blockSize: '100vh', overflow: 'hidden' }}>
<div
style={{
inlineSize: collapsed ? '52px' : '260px',
flexShrink: 0,
transition: 'inline-size 250ms cubic-bezier(0, 0, 0, 1)',
overflow: 'auto',
borderInlineEnd: '1px solid #e9ebed',
display: 'flex',
flexDirection: 'column',
}}
>
<div
style={{
padding: '8px',
display: 'flex',
justifyContent: collapsed ? 'center' : 'flex-end',
alignSelf: 'stretch',
}}
>
<Button
iconName={collapsed ? 'angle-right' : 'angle-left'}
variant="icon"
onClick={() => setCollapsed(c => !c)}
ariaLabel={collapsed ? 'Expand' : 'Collapse'}
/>
</div>
<SideNavigation
activeHref={activeHref}
items={allTypesItems}
collapsed={collapsed}
variant={highlighted ? 'highlighted' : 'default'}
onFollow={e => {
e.preventDefault();
setActiveHref(e.detail.href);
}}
/>
</div>

<main style={{ flex: 1, padding: '24px', overflow: 'auto' }}>
<SpaceBetween size="m">
<Box variant="h1">Collapsed state — all item types</Box>
<SpaceBetween size="s" direction="horizontal">
<Toggle checked={collapsed} onChange={({ detail }) => setCollapsed(detail.checked)}>
Collapsed
</Toggle>
<Toggle checked={highlighted} onChange={({ detail }) => setHighlighted(detail.checked)}>
Highlighted variant
</Toggle>
</SpaceBetween>
<Box variant="h3">In collapsed mode:</Box>
<Box>✓ Links with icons → shown as icon only</Box>
<Box>✗ Links without icons → hidden</Box>
<Box>✓ Link groups with icons → shown as icon only</Box>
<Box>✗ Link groups without icons → hidden</Box>
<Box>✓ Expandable groups with icons → shown as icon, non-expandable</Box>
<Box>✗ Expandable groups without icons → hidden</Box>
<Box>✓ Sections → header hidden, children with icons shown as flat list</Box>
<Box>✓ Dividers → deduplicated</Box>
</SpaceBetween>
</main>
</div>
);
}
Loading
Loading