Skip to content
Merged
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
1 change: 1 addition & 0 deletions build-tools/utils/pluralize.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const pluralizationMap = {
NavigableGroup: 'NavigableGroups',
Pagination: 'Paginations',
AppLayoutToolbar: 'AppLayoutToolbars',
PanelLayout: 'PanelLayouts',
PieChart: 'PieCharts',
Popover: 'Popovers',
ProgressBar: 'ProgressBars',
Expand Down
7 changes: 7 additions & 0 deletions pages/app-layout/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,10 @@
.ai-panel-logo {
font-weight: 600;
}

.ai-artifact-panel {
block-size: 100%;
overflow: auto;
background-color: awsui.$color-background-layout-main;
border-inline-start: 1px solid awsui.$color-border-divider-default;
}
67 changes: 61 additions & 6 deletions pages/app-layout/utils/external-global-left-panel-widget.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import React, { useState } from 'react';

import { Box, Button } from '~components';
import { Box, Button, PanelLayout } from '~components';
import { registerLeftDrawer, updateDrawer } from '~components/internal/plugins/widget';
import { mount, unmount } from '~mount';

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

const DEFAULT_SIZE = 360;
const CHAT_SIZE = 280;
const MIN_CHAT_SIZE = 150;
const MIN_ARTIFACT_SIZE = 360;

const AIDrawer = () => {
return (
const [hasArtifact, setHasArtifact] = useState(false);
const [artifactLoaded, setArtifactLoaded] = useState(false);
const [chatSize, setChatSize] = useState(CHAT_SIZE);
const [maxPanelSize, setMaxPanelSize] = useState(Number.MAX_SAFE_INTEGER);
const constrainedChatSize = Math.min(chatSize, maxPanelSize);
const collapsed = constrainedChatSize < MIN_CHAT_SIZE;

const chatContent = (
<Box padding="m">
<Box variant="h2" padding={{ bottom: 'm' }}>
Chat demo
</Box>
<Button
onClick={() => {
setHasArtifact(true);
updateDrawer({ type: 'expandDrawer', payload: { id: 'ai-panel' } });
updateDrawer({
type: 'updateDrawerConfig',
payload: {
id: 'ai-panel',
defaultSize: DEFAULT_SIZE + CHAT_SIZE,
minSize: DEFAULT_SIZE + CHAT_SIZE,
} as any,
});
}}
>
expand programmatically
Open artifact
</Button>
<Button
onClick={() => {
Expand All @@ -37,17 +58,51 @@ const AIDrawer = () => {
resize to window.innerWidth + 1000
</Button>
{new Array(100).fill(null).map((_, index) => (
<div key={index}>Tela content</div>
<div key={index}>Tela chat content</div>
))}
</Box>
);
const artifactContent = (
<Box padding="m">
<Box variant="h2" padding={{ bottom: 'm' }}>
Artifact
</Box>
<Button
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: There is no margins between these buttons.

onClick={() => {
setHasArtifact(false);
updateDrawer({ type: 'exitExpandedMode' });
updateDrawer({
type: 'updateDrawerConfig',
payload: { id: 'ai-panel', defaultSize: DEFAULT_SIZE, minSize: DEFAULT_SIZE } as any,
});
}}
>
Close artifact panel
</Button>
<Button onClick={() => setArtifactLoaded(true)}>Load artifact details</Button>
{artifactLoaded && new Array(100).fill(null).map((_, index) => <div key={index}>Tela content</div>)}
</Box>
);
return (
<PanelLayout
resizable={true}
panelSize={constrainedChatSize}
maxPanelSize={maxPanelSize}
minPanelSize={MIN_CHAT_SIZE}
onPanelResize={({ detail }) => setChatSize(detail.panelSize)}
onLayoutChange={({ detail }) => setMaxPanelSize(detail.totalSize - MIN_ARTIFACT_SIZE)}
panelContent={chatContent}
mainContent={<div className={styles['ai-artifact-panel']}>{artifactContent}</div>}
display={hasArtifact ? (collapsed ? 'main-only' : 'all') : 'panel-only'}
/>
);
};

registerLeftDrawer({
id: 'ai-panel',
resizable: true,
isExpandable: true,
defaultSize: 420,
defaultSize: DEFAULT_SIZE,
preserveInactiveContent: true,

ariaLabels: {
Expand Down
200 changes: 200 additions & 0 deletions pages/panel-layout/app-layout-panel.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useState } from 'react';

import { Box, Checkbox, Drawer, FormField, Header, Input, SegmentedControl, SpaceBetween } from '~components';
import AppLayout from '~components/app-layout';
import Button from '~components/button';
import { I18nProvider } from '~components/i18n';
import messages from '~components/i18n/messages/all.en';
import PanelLayout, { PanelLayoutProps } from '~components/panel-layout';

import AppContext, { AppContextType } from '../app/app-context';
import labels from '../app-layout/utils/labels';
import ScreenshotArea from '../utils/screenshot-area';

interface PanelLayoutContentProps {
longPanelContent: boolean;
longMainContent: boolean;
buttons: boolean;
minPanelSize: number;
maxPanelSize: number;
minContentSize: number;
display: PanelLayoutProps.Display;
panelPosition: PanelLayoutProps.PanelPosition;
}
type PageContext = React.Context<AppContextType<Partial<PanelLayoutContentProps>>>;

const PanelLayoutContent = ({
longPanelContent,
longMainContent,
buttons,
minContentSize,
minPanelSize,
maxPanelSize,
display,
panelPosition,
}: PanelLayoutContentProps) => {
const [size, setSize] = useState(Math.max(200, minPanelSize));
const [actualMaxPanelSize, setActualMaxPanelSize] = useState(size);

const actualSize = Math.min(size, actualMaxPanelSize);
const collapsed = actualMaxPanelSize < minPanelSize;

return (
<PanelLayout
panelSize={actualSize}
minPanelSize={minPanelSize}
maxPanelSize={actualMaxPanelSize}
resizable={true}
onPanelResize={({ detail }) => setSize(detail.panelSize)}
onLayoutChange={({ detail }) => setActualMaxPanelSize(Math.min(detail.totalSize - minContentSize, maxPanelSize))}
display={display === 'all' && collapsed ? 'main-only' : display}
panelPosition={panelPosition}
mainFocusable={longMainContent && !buttons ? { ariaLabel: 'Main content' } : undefined}
panelFocusable={longPanelContent && !buttons ? { ariaLabel: 'Panel content' } : undefined}
panelContent={
<Box padding="m">
<Header>Panel content</Header>
{new Array(longPanelContent ? 20 : 1)
.fill('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
.map((t, i) => (
<div key={i}>{t}</div>
))}
{buttons && <Button>Button</Button>}
</Box>
}
mainContent={
<Box padding="m">
<Header>Main content{collapsed && ' [collapsed]'}</Header>
{new Array(longMainContent ? 200 : 1)
.fill('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
.map((t, i) => (
<div key={i}>{t}</div>
))}
{buttons && <Button>button</Button>}
</Box>
}
/>
);
};

export default function PanelLayoutPage() {
const {
urlParams: {
longPanelContent = false,
longMainContent = false,
buttons = true,
minPanelSize = 200,
maxPanelSize = 600,
minContentSize = 600,
display = 'all',
panelPosition = 'side-start',
},
setUrlParams,
} = useContext(AppContext as PageContext);

return (
<I18nProvider messages={[messages]} locale="en">
<ScreenshotArea disableAnimations={true} gutters={false}>
<AppLayout
ariaLabels={labels}
navigation={
<Drawer header="Settings">
<SpaceBetween direction="vertical" size="m">
<Checkbox
checked={longMainContent}
onChange={({ detail }) => setUrlParams({ longMainContent: detail.checked })}
>
Long main content
</Checkbox>
<Checkbox
checked={longPanelContent}
onChange={({ detail }) => setUrlParams({ longPanelContent: detail.checked })}
>
Long panel content
</Checkbox>
<Checkbox checked={buttons} onChange={({ detail }) => setUrlParams({ buttons: detail.checked })}>
Include interactive content
</Checkbox>
<FormField label="Minimum panel size">
<Input
type="number"
value={minPanelSize.toString()}
onChange={({ detail }) => setUrlParams({ minPanelSize: detail.value ? parseInt(detail.value) : 0 })}
/>
</FormField>
<FormField label="Maximum panel size">
<Input
type="number"
value={maxPanelSize.toString()}
onChange={({ detail }) =>
setUrlParams({ maxPanelSize: detail.value ? parseInt(detail.value) : Number.MAX_SAFE_INTEGER })
}
/>
</FormField>
<FormField label="Minimum content size">
<Input
type="number"
value={minContentSize.toString()}
onChange={({ detail }) =>
setUrlParams({ minContentSize: detail.value ? parseInt(detail.value) : 0 })
}
/>
</FormField>
<FormField label="Panel position">
<SegmentedControl
options={[
{ id: 'side-start', text: 'side-start' },
{ id: 'side-end', text: 'side-end' },
]}
selectedId={panelPosition}
onChange={({ detail }) => setUrlParams({ panelPosition: detail.selectedId as any })}
/>
</FormField>
<FormField label="Display">
<SegmentedControl
options={[
{ id: 'all', text: 'all' },
{ id: 'panel-only', text: 'panel-only' },
{ id: 'main-only', text: 'main-only' },
]}
selectedId={display}
onChange={({ detail }) => setUrlParams({ display: detail.selectedId as any })}
/>
</FormField>
</SpaceBetween>
</Drawer>
}
content={<Header variant="h1">Panel layout in drawer demo</Header>}
drawers={[
{
id: 'panel',
content: (
<PanelLayoutContent
longMainContent={longMainContent}
longPanelContent={longPanelContent}
buttons={buttons}
minPanelSize={minPanelSize}
maxPanelSize={maxPanelSize}
minContentSize={minContentSize}
display={display}
panelPosition={panelPosition}
/>
),
resizable: true,
defaultSize: 1000,
ariaLabels: {
drawerName: 'Panel',
triggerButton: 'Open panel',
closeButton: 'Close panel',
resizeHandle: 'Resize drawer',
},
trigger: { iconName: 'contact' },
},
]}
/>
</ScreenshotArea>
</I18nProvider>
);
}
Loading
Loading