Skip to content

Commit

Permalink
Merge branch 'refs/heads/feat/public-layout-component' into dev-v3-gl…
Browse files Browse the repository at this point in the history
…obko
  • Loading branch information
georgylobko committed Feb 18, 2025
2 parents 4fccce3 + 5b0b82f commit 8bde7f2
Show file tree
Hide file tree
Showing 29 changed files with 1,609 additions and 24 deletions.
1 change: 1 addition & 0 deletions build-tools/utils/pluralize.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const pluralizationMap = {
Modal: 'Modals',
Multiselect: 'Multiselects',
Pagination: 'Paginations',
AppLayoutToolbar: 'AppLayoutToolbars',
PieChart: 'PieCharts',
Popover: 'Popovers',
ProgressBar: 'ProgressBars',
Expand Down
213 changes: 213 additions & 0 deletions pages/app-layout-toolbar/default.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useRef, useState } from 'react';

import {
AppLayoutToolbar,
Button,
ContentLayout,
Header,
HelpPanel,
Link,
SpaceBetween,
SplitPanel,
Toggle,
} from '~components';
import { AppLayoutToolbarProps } from '~components/app-layout-toolbar';

import AppContext, { AppContextType } from '../app/app-context';
import { Breadcrumbs, Containers, CustomDrawerContent, Navigation } from '../app-layout/utils/content-blocks';
import { drawerLabels } from '../app-layout/utils/drawers';
import appLayoutLabels from '../app-layout/utils/labels';

type DemoContext = React.Context<
AppContextType<{
navigationTriggerHide: boolean | undefined;
drawerTriggerHide: boolean | undefined;
splitPanelTriggerHide: boolean | undefined;
breadcrumbsHide: boolean | undefined;
splitPanelPosition: AppLayoutToolbarProps.SplitPanelPreferences['position'];
}>
>;

export default function WithDrawers() {
const [activeDrawerId, setActiveDrawerId] = useState<string | null>(null);
const [helpPathSlug, setHelpPathSlug] = useState<string>('default');
const { urlParams, setUrlParams } = useContext(AppContext as DemoContext);
const navigationTriggerHide = urlParams.navigationTriggerHide ?? false;
const drawerTriggerHide = urlParams.drawerTriggerHide ?? false;
const splitPanelTriggerHide = urlParams.splitPanelTriggerHide ?? false;
const breadcrumbsHide = urlParams.breadcrumbsHide ?? false;
const [isToolsOpen, setIsToolsOpen] = useState(false);
const [isNavigationOpen, setIsNavigationOpen] = useState(true);
const [splitPanelOpen, setSplitPanelOpen] = useState(false);
const pageLayoutRef = useRef<AppLayoutToolbarProps.Ref>(null);

const drawersProps: Pick<AppLayoutToolbarProps, 'activeDrawerId' | 'onDrawerChange' | 'drawers'> | null = {
activeDrawerId: activeDrawerId,
drawers: [
{
ariaLabels: {
closeButton: 'ProHelp close button',
drawerName: 'ProHelp drawer content',
triggerButton: 'ProHelp trigger button',
resizeHandle: 'ProHelp resize handle',
},
content: <CustomDrawerContent />,
id: 'pro-help',
trigger: drawerTriggerHide
? undefined
: {
iconName: 'contact',
},
},
],
onDrawerChange: event => {
setActiveDrawerId(event.detail.activeDrawerId);
},
};

return (
<AppLayoutToolbar
ariaLabels={{ ...appLayoutLabels, ...drawerLabels }}
breadcrumbs={breadcrumbsHide ? undefined : <Breadcrumbs />}
ref={pageLayoutRef}
content={
<ContentLayout
disableOverlap={true}
header={
<SpaceBetween size="m">
<Header
variant="h1"
description="Sometimes you need custom triggers for drawers and navigation to get the job done."
info={
<Link
data-testid="info-link-header"
variant="info"
onFollow={() => {
setHelpPathSlug('header');
setIsToolsOpen(true);
pageLayoutRef.current?.focusToolsClose();
}}
>
Info
</Link>
}
>
Page layout
</Header>

<SpaceBetween size="xs">
<Toggle
checked={navigationTriggerHide}
onChange={({ detail }) => setUrlParams({ navigationTriggerHide: detail.checked })}
>
Hide navigation trigger
</Toggle>
<Toggle
checked={drawerTriggerHide}
onChange={({ detail }) => setUrlParams({ drawerTriggerHide: detail.checked })}
>
Hide drawer trigger
</Toggle>
<Toggle
checked={splitPanelTriggerHide}
onChange={({ detail }) => setUrlParams({ splitPanelTriggerHide: detail.checked })}
>
Hide split panel trigger
</Toggle>
<Toggle
checked={breadcrumbsHide}
onChange={({ detail }) => setUrlParams({ breadcrumbsHide: detail.checked })}
>
Hide breadcrumbs
</Toggle>

<Button
onClick={() => {
setIsNavigationOpen(current => !current);
pageLayoutRef.current?.focusNavigation();
}}
>
Toggle navigation
</Button>

<Button
onClick={() => {
setActiveDrawerId('pro-help');
pageLayoutRef.current?.focusActiveDrawer();
}}
>
Open a drawer without trigger
</Button>
<Button onClick={() => setActiveDrawerId(null)}>Close a drawer without trigger</Button>
<Button onClick={() => setSplitPanelOpen(true)}>Open split panel</Button>
</SpaceBetween>
</SpaceBetween>
}
>
<Header
info={
<Link
data-testid="info-link-content"
variant="info"
onFollow={() => {
setHelpPathSlug('content');
setIsToolsOpen(true);
}}
>
Info
</Link>
}
>
Content
</Header>
<Containers />
</ContentLayout>
}
splitPanel={
<SplitPanel
closeBehavior={splitPanelTriggerHide ? 'hide' : undefined}
header="Split panel header"
i18nStrings={{
preferencesTitle: 'Preferences',
preferencesPositionLabel: 'Split panel position',
preferencesPositionDescription: 'Choose the default split panel position for the service.',
preferencesPositionSide: 'Side',
preferencesPositionBottom: 'Bottom',
preferencesConfirm: 'Confirm',
preferencesCancel: 'Cancel',
closeButtonAriaLabel: 'Close panel',
openButtonAriaLabel: 'Open panel',
resizeHandleAriaLabel: 'Slider',
}}
>
This is the Split Panel!
</SplitPanel>
}
splitPanelOpen={splitPanelOpen}
splitPanelPreferences={{
position: urlParams.splitPanelPosition,
}}
onSplitPanelToggle={event => setSplitPanelOpen(event.detail.open)}
onSplitPanelPreferencesChange={event => {
const { position } = event.detail;
setUrlParams({ splitPanelPosition: position === 'side' ? position : undefined });
}}
onToolsChange={event => {
setIsToolsOpen(event.detail.open);
}}
tools={<Info helpPathSlug={helpPathSlug} />}
toolsOpen={isToolsOpen}
navigationOpen={isNavigationOpen}
navigation={<Navigation />}
onNavigationChange={event => setIsNavigationOpen(event.detail.open)}
navigationTriggerHide={navigationTriggerHide}
{...drawersProps}
/>
);
}

function Info({ helpPathSlug }: { helpPathSlug: string }) {
return <HelpPanel header={<h2>Info</h2>}>Here is some info for you: {helpPathSlug}</HelpPanel>;
}
104 changes: 104 additions & 0 deletions pages/app-layout-toolbar/multi-layout-with-hidden-instances.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useRef, useState } from 'react';

import AppLayoutToolbar from '~components/app-layout-toolbar';
import BreadcrumbGroup from '~components/breadcrumb-group';
import Header from '~components/header';
import ScreenreaderOnly from '~components/internal/components/screenreader-only';
import Link from '~components/link';
import SideNavigation, { SideNavigationProps } from '~components/side-navigation';
import SpaceBetween from '~components/space-between';

import { Tools } from '../app-layout/utils/content-blocks';
import labels from '../app-layout/utils/labels';
import { IframeWrapper } from '../utils/iframe-wrapper';
import ScreenshotArea from '../utils/screenshot-area';

function createView(name: string) {
return function View() {
return (
<AppLayoutToolbar
data-testid="secondary-layout"
ariaLabels={labels}
breadcrumbs={
name !== 'page2' && (
<BreadcrumbGroup
onFollow={event => event.preventDefault()}
items={[
{ text: 'Home', href: '#' },
{ text: name, href: `#${name}` },
]}
/>
)
}
navigationHide={true}
content={
<SpaceBetween size="s">
<Header variant="h1" description="This page contains nested app layout instances">
Multiple page layouts
</Header>

<Link external={true} href="#">
External link
</Link>

<div>Page content: {name}</div>
</SpaceBetween>
}
tools={<Tools>Tools content: {name}</Tools>}
/>
);
};
}

const ROUTES: Array<{ navLink: SideNavigationProps.Link; View: React.ComponentType }> = [
{ navLink: { type: 'link', text: 'Page 1', href: 'page1' }, View: createView('page1') },
{ navLink: { type: 'link', text: 'Page 2', href: 'page2' }, View: createView('page2') },
{ navLink: { type: 'link', text: 'Page 3', href: 'page3' }, View: createView('page3') },
];

export default function () {
const [activeHref, setActiveHref] = useState('page1');
const openPagesHistory = useRef<Set<string>>(new Set([activeHref]));

return (
<ScreenshotArea gutters={false}>
<AppLayoutToolbar
{...{ __disableRuntimeDrawers: true }}
data-testid="main-layout"
ariaLabels={labels}
navigation={
<SideNavigation
activeHref={activeHref}
header={{ href: '#/', text: 'Service name' }}
onFollow={event => {
if (!event.detail.external) {
event.preventDefault();
openPagesHistory.current.add(event.detail.href);
setActiveHref(event.detail.href);
}
}}
items={ROUTES.map(route => route.navLink)}
/>
}
toolsHide={true}
disableContentPaddings={true}
content={
<>
<ScreenreaderOnly>
<h1>Multiple app layouts with iframe</h1>
</ScreenreaderOnly>
{ROUTES.filter(
item => item.navLink.href === activeHref || openPagesHistory.current.has(item.navLink.href)
).map(item => (
<div key={item.navLink.href} style={{ display: item.navLink.href !== activeHref ? 'none' : '' }}>
<IframeWrapper id={item.navLink.href} AppComponent={item.View} />
</div>
))}
</>
}
/>
</ScreenshotArea>
);
}
Loading

0 comments on commit 8bde7f2

Please sign in to comment.