Skip to content

Commit 1369fb7

Browse files
feat: Add panel-layout component (#4114)
1 parent 94344c6 commit 1369fb7

File tree

34 files changed

+2213
-13
lines changed

34 files changed

+2213
-13
lines changed

build-tools/utils/pluralize.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const pluralizationMap = {
5555
NavigableGroup: 'NavigableGroups',
5656
Pagination: 'Paginations',
5757
AppLayoutToolbar: 'AppLayoutToolbars',
58+
PanelLayout: 'PanelLayouts',
5859
PieChart: 'PieCharts',
5960
Popover: 'Popovers',
6061
ProgressBar: 'ProgressBars',

pages/app-layout/styles.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,10 @@
123123
.ai-panel-logo {
124124
font-weight: 600;
125125
}
126+
127+
.ai-artifact-panel {
128+
block-size: 100%;
129+
overflow: auto;
130+
background-color: awsui.$color-background-layout-main;
131+
border-inline-start: 1px solid awsui.$color-border-divider-default;
132+
}

pages/app-layout/utils/external-global-left-panel-widget.tsx

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import React from 'react';
3+
import React, { useState } from 'react';
44

5-
import { Box, Button } from '~components';
5+
import { Box, Button, PanelLayout } from '~components';
66
import { registerLeftDrawer, updateDrawer } from '~components/internal/plugins/widget';
77
import { mount, unmount } from '~mount';
88

99
import styles from '../styles.scss';
1010

11+
const DEFAULT_SIZE = 360;
12+
const CHAT_SIZE = 280;
13+
const MIN_CHAT_SIZE = 150;
14+
const MIN_ARTIFACT_SIZE = 360;
15+
1116
const AIDrawer = () => {
12-
return (
17+
const [hasArtifact, setHasArtifact] = useState(false);
18+
const [artifactLoaded, setArtifactLoaded] = useState(false);
19+
const [chatSize, setChatSize] = useState(CHAT_SIZE);
20+
const [maxPanelSize, setMaxPanelSize] = useState(Number.MAX_SAFE_INTEGER);
21+
const constrainedChatSize = Math.min(chatSize, maxPanelSize);
22+
const collapsed = constrainedChatSize < MIN_CHAT_SIZE;
23+
24+
const chatContent = (
1325
<Box padding="m">
1426
<Box variant="h2" padding={{ bottom: 'm' }}>
1527
Chat demo
1628
</Box>
1729
<Button
1830
onClick={() => {
31+
setHasArtifact(true);
1932
updateDrawer({ type: 'expandDrawer', payload: { id: 'ai-panel' } });
33+
updateDrawer({
34+
type: 'updateDrawerConfig',
35+
payload: {
36+
id: 'ai-panel',
37+
defaultSize: DEFAULT_SIZE + CHAT_SIZE,
38+
minSize: DEFAULT_SIZE + CHAT_SIZE,
39+
} as any,
40+
});
2041
}}
2142
>
22-
expand programmatically
43+
Open artifact
2344
</Button>
2445
<Button
2546
onClick={() => {
@@ -37,17 +58,51 @@ const AIDrawer = () => {
3758
resize to window.innerWidth + 1000
3859
</Button>
3960
{new Array(100).fill(null).map((_, index) => (
40-
<div key={index}>Tela content</div>
61+
<div key={index}>Tela chat content</div>
4162
))}
4263
</Box>
4364
);
65+
const artifactContent = (
66+
<Box padding="m">
67+
<Box variant="h2" padding={{ bottom: 'm' }}>
68+
Artifact
69+
</Box>
70+
<Button
71+
onClick={() => {
72+
setHasArtifact(false);
73+
updateDrawer({ type: 'exitExpandedMode' });
74+
updateDrawer({
75+
type: 'updateDrawerConfig',
76+
payload: { id: 'ai-panel', defaultSize: DEFAULT_SIZE, minSize: DEFAULT_SIZE } as any,
77+
});
78+
}}
79+
>
80+
Close artifact panel
81+
</Button>
82+
<Button onClick={() => setArtifactLoaded(true)}>Load artifact details</Button>
83+
{artifactLoaded && new Array(100).fill(null).map((_, index) => <div key={index}>Tela content</div>)}
84+
</Box>
85+
);
86+
return (
87+
<PanelLayout
88+
resizable={true}
89+
panelSize={constrainedChatSize}
90+
maxPanelSize={maxPanelSize}
91+
minPanelSize={MIN_CHAT_SIZE}
92+
onPanelResize={({ detail }) => setChatSize(detail.panelSize)}
93+
onLayoutChange={({ detail }) => setMaxPanelSize(detail.totalSize - MIN_ARTIFACT_SIZE)}
94+
panelContent={chatContent}
95+
mainContent={<div className={styles['ai-artifact-panel']}>{artifactContent}</div>}
96+
display={hasArtifact ? (collapsed ? 'main-only' : 'all') : 'panel-only'}
97+
/>
98+
);
4499
};
45100

46101
registerLeftDrawer({
47102
id: 'ai-panel',
48103
resizable: true,
49104
isExpandable: true,
50-
defaultSize: 420,
105+
defaultSize: DEFAULT_SIZE,
51106
preserveInactiveContent: true,
52107

53108
ariaLabels: {
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useContext, useState } from 'react';
4+
5+
import { Box, Checkbox, Drawer, FormField, Header, Input, SegmentedControl, SpaceBetween } from '~components';
6+
import AppLayout from '~components/app-layout';
7+
import Button from '~components/button';
8+
import { I18nProvider } from '~components/i18n';
9+
import messages from '~components/i18n/messages/all.en';
10+
import PanelLayout, { PanelLayoutProps } from '~components/panel-layout';
11+
12+
import AppContext, { AppContextType } from '../app/app-context';
13+
import labels from '../app-layout/utils/labels';
14+
import ScreenshotArea from '../utils/screenshot-area';
15+
16+
interface PanelLayoutContentProps {
17+
longPanelContent: boolean;
18+
longMainContent: boolean;
19+
buttons: boolean;
20+
minPanelSize: number;
21+
maxPanelSize: number;
22+
minContentSize: number;
23+
display: PanelLayoutProps.Display;
24+
panelPosition: PanelLayoutProps.PanelPosition;
25+
}
26+
type PageContext = React.Context<AppContextType<Partial<PanelLayoutContentProps>>>;
27+
28+
const PanelLayoutContent = ({
29+
longPanelContent,
30+
longMainContent,
31+
buttons,
32+
minContentSize,
33+
minPanelSize,
34+
maxPanelSize,
35+
display,
36+
panelPosition,
37+
}: PanelLayoutContentProps) => {
38+
const [size, setSize] = useState(Math.max(200, minPanelSize));
39+
const [actualMaxPanelSize, setActualMaxPanelSize] = useState(size);
40+
41+
const actualSize = Math.min(size, actualMaxPanelSize);
42+
const collapsed = actualMaxPanelSize < minPanelSize;
43+
44+
return (
45+
<PanelLayout
46+
panelSize={actualSize}
47+
minPanelSize={minPanelSize}
48+
maxPanelSize={actualMaxPanelSize}
49+
resizable={true}
50+
onPanelResize={({ detail }) => setSize(detail.panelSize)}
51+
onLayoutChange={({ detail }) => setActualMaxPanelSize(Math.min(detail.totalSize - minContentSize, maxPanelSize))}
52+
display={display === 'all' && collapsed ? 'main-only' : display}
53+
panelPosition={panelPosition}
54+
mainFocusable={longMainContent && !buttons ? { ariaLabel: 'Main content' } : undefined}
55+
panelFocusable={longPanelContent && !buttons ? { ariaLabel: 'Panel content' } : undefined}
56+
panelContent={
57+
<Box padding="m">
58+
<Header>Panel content</Header>
59+
{new Array(longPanelContent ? 20 : 1)
60+
.fill('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
61+
.map((t, i) => (
62+
<div key={i}>{t}</div>
63+
))}
64+
{buttons && <Button>Button</Button>}
65+
</Box>
66+
}
67+
mainContent={
68+
<Box padding="m">
69+
<Header>Main content{collapsed && ' [collapsed]'}</Header>
70+
{new Array(longMainContent ? 200 : 1)
71+
.fill('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
72+
.map((t, i) => (
73+
<div key={i}>{t}</div>
74+
))}
75+
{buttons && <Button>button</Button>}
76+
</Box>
77+
}
78+
/>
79+
);
80+
};
81+
82+
export default function PanelLayoutPage() {
83+
const {
84+
urlParams: {
85+
longPanelContent = false,
86+
longMainContent = false,
87+
buttons = true,
88+
minPanelSize = 200,
89+
maxPanelSize = 600,
90+
minContentSize = 600,
91+
display = 'all',
92+
panelPosition = 'side-start',
93+
},
94+
setUrlParams,
95+
} = useContext(AppContext as PageContext);
96+
97+
return (
98+
<I18nProvider messages={[messages]} locale="en">
99+
<ScreenshotArea disableAnimations={true} gutters={false}>
100+
<AppLayout
101+
ariaLabels={labels}
102+
navigation={
103+
<Drawer header="Settings">
104+
<SpaceBetween direction="vertical" size="m">
105+
<Checkbox
106+
checked={longMainContent}
107+
onChange={({ detail }) => setUrlParams({ longMainContent: detail.checked })}
108+
>
109+
Long main content
110+
</Checkbox>
111+
<Checkbox
112+
checked={longPanelContent}
113+
onChange={({ detail }) => setUrlParams({ longPanelContent: detail.checked })}
114+
>
115+
Long panel content
116+
</Checkbox>
117+
<Checkbox checked={buttons} onChange={({ detail }) => setUrlParams({ buttons: detail.checked })}>
118+
Include interactive content
119+
</Checkbox>
120+
<FormField label="Minimum panel size">
121+
<Input
122+
type="number"
123+
value={minPanelSize.toString()}
124+
onChange={({ detail }) => setUrlParams({ minPanelSize: detail.value ? parseInt(detail.value) : 0 })}
125+
/>
126+
</FormField>
127+
<FormField label="Maximum panel size">
128+
<Input
129+
type="number"
130+
value={maxPanelSize.toString()}
131+
onChange={({ detail }) =>
132+
setUrlParams({ maxPanelSize: detail.value ? parseInt(detail.value) : Number.MAX_SAFE_INTEGER })
133+
}
134+
/>
135+
</FormField>
136+
<FormField label="Minimum content size">
137+
<Input
138+
type="number"
139+
value={minContentSize.toString()}
140+
onChange={({ detail }) =>
141+
setUrlParams({ minContentSize: detail.value ? parseInt(detail.value) : 0 })
142+
}
143+
/>
144+
</FormField>
145+
<FormField label="Panel position">
146+
<SegmentedControl
147+
options={[
148+
{ id: 'side-start', text: 'side-start' },
149+
{ id: 'side-end', text: 'side-end' },
150+
]}
151+
selectedId={panelPosition}
152+
onChange={({ detail }) => setUrlParams({ panelPosition: detail.selectedId as any })}
153+
/>
154+
</FormField>
155+
<FormField label="Display">
156+
<SegmentedControl
157+
options={[
158+
{ id: 'all', text: 'all' },
159+
{ id: 'panel-only', text: 'panel-only' },
160+
{ id: 'main-only', text: 'main-only' },
161+
]}
162+
selectedId={display}
163+
onChange={({ detail }) => setUrlParams({ display: detail.selectedId as any })}
164+
/>
165+
</FormField>
166+
</SpaceBetween>
167+
</Drawer>
168+
}
169+
content={<Header variant="h1">Panel layout in drawer demo</Header>}
170+
drawers={[
171+
{
172+
id: 'panel',
173+
content: (
174+
<PanelLayoutContent
175+
longMainContent={longMainContent}
176+
longPanelContent={longPanelContent}
177+
buttons={buttons}
178+
minPanelSize={minPanelSize}
179+
maxPanelSize={maxPanelSize}
180+
minContentSize={minContentSize}
181+
display={display}
182+
panelPosition={panelPosition}
183+
/>
184+
),
185+
resizable: true,
186+
defaultSize: 1000,
187+
ariaLabels: {
188+
drawerName: 'Panel',
189+
triggerButton: 'Open panel',
190+
closeButton: 'Close panel',
191+
resizeHandle: 'Resize drawer',
192+
},
193+
trigger: { iconName: 'contact' },
194+
},
195+
]}
196+
/>
197+
</ScreenshotArea>
198+
</I18nProvider>
199+
);
200+
}

0 commit comments

Comments
 (0)