Skip to content

feat: Widgetize app layout skeleton event base #3497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
{
"path": "lib/components/internal/widget-exports.js",
"brotli": false,
"limit": "810 kB",
"limit": "830 kB",
"ignore": "react-dom"
}
],
Expand Down
157 changes: 86 additions & 71 deletions pages/app-layout/runtime-drawers.page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useRef, useState } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';

import {
AppLayout,
Expand Down Expand Up @@ -31,6 +31,18 @@ type DemoContext = React.Context<
}>
>;

const CustomContent = () => {
useEffect(() => {
console.log('mount');

return () => {
console.log('unmount');
};
}, []);

return <div>Custom content</div>;
};

export default function WithDrawers() {
const [activeDrawerId, setActiveDrawerId] = useState<string | null>(null);
const [helpPathSlug, setHelpPathSlug] = useState<string>('default');
Expand Down Expand Up @@ -70,83 +82,86 @@ export default function WithDrawers() {
breadcrumbs={<Breadcrumbs />}
ref={appLayoutRef}
content={
<ContentLayout
disableOverlap={true}
header={
<SpaceBetween size="m">
<Header
variant="h1"
description="Sometimes you need custom drawers to get the job done."
info={
<Link
data-testid="info-link-header"
variant="info"
onFollow={() => {
setHelpPathSlug('header');
setIsToolsOpen(true);
appLayoutRef.current?.focusToolsClose();
}}
>
Info
</Link>
}
>
Testing Custom Drawers!
</Header>
<div data-testid="app-layout-content-area">
<ContentLayout
disableOverlap={true}
header={
<SpaceBetween size="m">
<Header
variant="h1"
description="Sometimes you need custom drawers to get the job done."
info={
<Link
data-testid="info-link-header"
variant="info"
onFollow={() => {
setHelpPathSlug('header');
setIsToolsOpen(true);
appLayoutRef.current?.focusToolsClose();
}}
>
Info
</Link>
}
>
Testing Custom Drawers!
</Header>

<SpaceBetween size="xs">
<Toggle checked={hasTools} onChange={({ detail }) => setUrlParams({ hasTools: detail.checked })}>
Use Tools
</Toggle>
<SpaceBetween size="xs">
<Toggle checked={hasTools} onChange={({ detail }) => setUrlParams({ hasTools: detail.checked })}>
Use Tools
</Toggle>

<Toggle checked={hasDrawers} onChange={({ detail }) => setUrlParams({ hasDrawers: detail.checked })}>
Use Drawers
</Toggle>
<Toggle checked={hasDrawers} onChange={({ detail }) => setUrlParams({ hasDrawers: detail.checked })}>
Use Drawers
</Toggle>

<Button
onClick={() => awsuiPlugins.appLayout.openDrawer('circle4-global')}
data-testid="open-drawer-button"
>
Open a drawer without a trigger
</Button>
<Button onClick={() => awsuiPlugins.appLayout.closeDrawer('circle4-global')}>
Close a drawer without a trigger
</Button>
<Button
onClick={() => awsuiPlugins.appLayout.openDrawer('circle4-global')}
data-testid="open-drawer-button"
>
Open a drawer without a trigger
</Button>
<Button onClick={() => awsuiPlugins.appLayout.closeDrawer('circle4-global')}>
Close a drawer without a trigger
</Button>

<Button
onClick={() => awsuiPlugins.appLayout.resizeDrawer('circle-global', 400)}
data-testid="button-circle-global-resize"
>
Resize circle-global drawer to 400px
</Button>
<Button
onClick={() => awsuiPlugins.appLayout.resizeDrawer('circle3-global', 500)}
data-testid="button-circle3-global-resize"
>
Resize circle3-global drawer to 500px
</Button>
<Button
onClick={() => awsuiPlugins.appLayout.resizeDrawer('circle-global', 400)}
data-testid="button-circle-global-resize"
>
Resize circle-global drawer to 400px
</Button>
<Button
onClick={() => awsuiPlugins.appLayout.resizeDrawer('circle3-global', 500)}
data-testid="button-circle3-global-resize"
>
Resize circle3-global drawer to 500px
</Button>
</SpaceBetween>
</SpaceBetween>
</SpaceBetween>
}
>
<Header
info={
<Link
data-testid="info-link-content"
variant="info"
onFollow={() => {
setHelpPathSlug('content');
setIsToolsOpen(true);
}}
>
Info
</Link>
}
>
Content
</Header>
<Containers />
</ContentLayout>
<Header
info={
<Link
data-testid="info-link-content"
variant="info"
onFollow={() => {
setHelpPathSlug('content');
setIsToolsOpen(true);
}}
>
Info
</Link>
}
>
Content
</Header>
<CustomContent />
<Containers />
</ContentLayout>
</div>
}
splitPanel={
<SplitPanel header="Split panel header" i18nStrings={splitPaneli18nStrings}>
Expand Down
14 changes: 13 additions & 1 deletion pages/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ interface GlobalFlags {
appLayoutWidget?: boolean;
appLayoutToolbar?: boolean;
}
// used for local dev / testing
interface CustomFlags {
appLayoutDelayedWidget?: boolean;
}
const awsuiVisualRefreshFlag = Symbol.for('awsui-visual-refresh-flag');
const awsuiGlobalFlagsSymbol = Symbol.for('awsui-global-flags');
const awsuiCustomFlagsSymbol = Symbol.for('awsui-custom-flags');

interface ExtendedWindow extends Window {
[awsuiVisualRefreshFlag]?: () => boolean;
[awsuiGlobalFlagsSymbol]?: GlobalFlags;
[awsuiCustomFlagsSymbol]?: CustomFlags;
}
declare const window: ExtendedWindow;

Expand Down Expand Up @@ -86,15 +92,21 @@ function App() {
}

const history = createHashHistory();
const { direction, visualRefresh, appLayoutWidget, appLayoutToolbar } = parseQuery(history.location.search);
const { direction, visualRefresh, appLayoutWidget, appLayoutToolbar, appLayoutDelayedWidget } = parseQuery(
history.location.search
);

// The VR class needs to be set before any React rendering occurs.
window[awsuiVisualRefreshFlag] = () => visualRefresh;
if (!window[awsuiGlobalFlagsSymbol]) {
window[awsuiGlobalFlagsSymbol] = {};
}
if (!window[awsuiCustomFlagsSymbol]) {
window[awsuiCustomFlagsSymbol] = {};
}
window[awsuiGlobalFlagsSymbol].appLayoutWidget = appLayoutWidget;
window[awsuiGlobalFlagsSymbol].appLayoutToolbar = appLayoutToolbar;
window[awsuiCustomFlagsSymbol].appLayoutDelayedWidget = appLayoutDelayedWidget;

// Apply the direction value to the HTML element dir attribute
document.documentElement.setAttribute('dir', direction);
Expand Down
56 changes: 29 additions & 27 deletions pages/utils/iframe-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,37 @@ export function IframeWrapper({ id, AppComponent }: { id: string; AppComponent:
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
const container = ref.current;
if (!container) {
return;
}
const iframeEl = container.ownerDocument.createElement('iframe');
iframeEl.className = styles['full-screen'];
iframeEl.id = id;
iframeEl.title = id;
container.appendChild(iframeEl);
setTimeout(() => {
const container = ref.current;
if (!container) {
return;
}
const iframeEl = container.ownerDocument.createElement('iframe');
iframeEl.className = styles['full-screen'];
iframeEl.id = id;
iframeEl.title = id;
container.appendChild(iframeEl);

const iframeDocument = iframeEl.contentDocument!;
// Prevent iframe document instance from reload
// https://bugzilla.mozilla.org/show_bug.cgi?id=543435
iframeDocument.open();
// set html5 doctype
iframeDocument.writeln('<!DOCTYPE html>');
iframeDocument.close();
const iframeDocument = iframeEl.contentDocument!;
// Prevent iframe document instance from reload
// https://bugzilla.mozilla.org/show_bug.cgi?id=543435
iframeDocument.open();
// set html5 doctype
iframeDocument.writeln('<!DOCTYPE html>');
iframeDocument.close();

const innerAppRoot = iframeDocument.createElement('div');
iframeDocument.body.appendChild(innerAppRoot);
copyStyles(document, iframeDocument);
iframeDocument.dir = document.dir;
const syncClassesCleanup = syncClasses(document.body, iframeDocument.body);
ReactDOM.render(<AppComponent />, innerAppRoot);
return () => {
syncClassesCleanup();
ReactDOM.unmountComponentAtNode(innerAppRoot);
container.removeChild(iframeEl);
};
const innerAppRoot = iframeDocument.createElement('div');
iframeDocument.body.appendChild(innerAppRoot);
copyStyles(document, iframeDocument);
iframeDocument.dir = document.dir;
const syncClassesCleanup = syncClasses(document.body, iframeDocument.body);
ReactDOM.render(<AppComponent />, innerAppRoot);
return () => {
syncClassesCleanup();
ReactDOM.unmountComponentAtNode(innerAppRoot);
Copy link
Member

Choose a reason for hiding this comment

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

This cleanup does not work because it is inside a timeout now

container.removeChild(iframeEl);
};
}, 50);
}, [id, AppComponent]);

return <div ref={ref}></div>;
Expand Down
Loading
Loading