Skip to content

Commit 28197a7

Browse files
committed
chore: Implement flashbar runtime API
1 parent 46bf2fc commit 28197a7

File tree

14 files changed

+291
-191
lines changed

14 files changed

+291
-191
lines changed

src/app-layout/utils/use-ai-drawer.ts

Lines changed: 0 additions & 153 deletions
This file was deleted.

src/app-layout/visual-refresh-toolbar/drawer/global-ai-drawer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import customCssProps from '../../../internal/generated/custom-css-properties';
1111
import { usePrevious } from '../../../internal/hooks/use-previous';
1212
import { getLimitedValue } from '../../../split-panel/utils/size-utils';
1313
import { AppLayoutProps } from '../../interfaces';
14-
import { OnChangeParams } from '../../utils/use-ai-drawer';
1514
import { FocusControlState } from '../../utils/use-focus-control';
1615
import { AppLayoutInternals, InternalDrawer } from '../interfaces';
16+
import { OnChangeParams } from '../state/use-ai-drawer';
1717
import { useResize } from './use-resize';
1818

1919
import sharedStyles from '../../resize/styles.css.js';

src/app-layout/visual-refresh-toolbar/interfaces.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React from 'react';
55

66
import { BreadcrumbGroupProps } from '../../breadcrumb-group/interfaces';
77
import { ButtonGroupProps } from '../../button-group/interfaces';
8+
import { FlashbarProps } from '../../flashbar/interfaces';
89
import { SplitPanelSideToggleProps } from '../../internal/context/split-panel-context';
910
import { NonCancelableEventHandler } from '../../internal/events';
1011
import { SomeOptional } from '../../internal/types';
@@ -90,6 +91,8 @@ interface AppLayoutWidgetizedState extends AppLayoutInternals {
9091
verticalOffsets: VerticalLayoutOutput;
9192
navigationAnimationDisabled: boolean;
9293
aiDrawerExpandedMode: boolean;
94+
flashbarProps: FlashbarProps | null;
95+
setFlashbarProps: (props: FlashbarProps | null) => void;
9396
splitPanelOffsets: {
9497
stickyVerticalBottomOffset: number;
9598
mainContentPaddingBlockEnd: number | undefined;

src/app-layout/visual-refresh-toolbar/notifications/index.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,26 @@ import clsx from 'clsx';
55

66
import { useResizeObserver } from '@cloudscape-design/component-toolkit/internal';
77

8+
import { FlashbarImplementation } from '../../../flashbar/implementation';
89
import { highContrastHeaderClassName } from '../../../internal/utils/content-header-utils';
9-
import { AppLayoutInternals } from '../interfaces';
10+
import { AppLayoutInternals, AppLayoutState } from '../interfaces';
1011
import { NotificationsSlot } from '../skeleton/slots';
12+
import { FlashbarPropsSetter } from '../state/runtime-notifications';
1113

1214
import testutilStyles from '../../test-classes/styles.css.js';
1315
import styles from './styles.css.js';
1416

1517
export interface AppLayoutNotificationsImplementationProps {
1618
appLayoutInternals: AppLayoutInternals;
19+
flashbarProps: AppLayoutState['widgetizedState']['flashbarProps'];
20+
setFlashbarProps: AppLayoutState['widgetizedState']['setFlashbarProps'];
1721
children: React.ReactNode;
1822
}
1923

2024
export function AppLayoutNotificationsImplementation({
2125
appLayoutInternals,
26+
flashbarProps,
27+
setFlashbarProps,
2228
children,
2329
}: AppLayoutNotificationsImplementationProps) {
2430
const { ariaLabels, stickyNotifications, setNotificationsHeight, verticalOffsets } = appLayoutInternals;
@@ -51,7 +57,8 @@ export function AppLayoutNotificationsImplementation({
5157
}}
5258
>
5359
<div className={testutilStyles.notifications} role="region" aria-label={ariaLabels?.notifications}>
54-
{children}
60+
<FlashbarPropsSetter.Provider value={setFlashbarProps}>{children}</FlashbarPropsSetter.Provider>
61+
{flashbarProps && <FlashbarImplementation {...flashbarProps} />}
5562
</div>
5663
</NotificationsSlot>
5764
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { createContext, useState } from 'react';
4+
5+
import { FlashbarProps } from '../../../flashbar/interfaces';
6+
import { WidgetMessage } from '../../../internal/plugins/widget/interfaces';
7+
8+
export const FlashbarPropsSetter = createContext<((props: FlashbarProps | null) => void) | null>(null);
9+
10+
export function useRuntimeNotifications() {
11+
const [flashbarProps, setFlashbarProps] = useState<FlashbarProps | null>(null);
12+
const [notifications, setNotifications] = useState<Array<FlashbarProps.MessageDefinition>>([]);
13+
14+
function notificationsMessageHandler(message: WidgetMessage) {
15+
if (message.type === 'emitNotification') {
16+
setNotifications(notifications => [...notifications, message.payload]);
17+
}
18+
}
19+
20+
return {
21+
flashbarProps: flashbarProps && { ...flashbarProps, items: [...flashbarProps.items, ...notifications] },
22+
setFlashbarProps,
23+
notificationsMessageHandler,
24+
};
25+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { useRef, useState } from 'react';
4+
5+
import { fireNonCancelableEvent } from '../../../internal/events';
6+
import { metrics } from '../../../internal/metrics';
7+
import { DrawerPayload as RuntimeAiDrawerConfig, WidgetMessage } from '../../../internal/plugins/widget/interfaces';
8+
import { mapRuntimeConfigToAiDrawer } from '../../runtime-drawer';
9+
10+
export interface OnChangeParams {
11+
initiatedByUserAction: boolean;
12+
}
13+
14+
const DEFAULT_ON_CHANGE_PARAMS = { initiatedByUserAction: true };
15+
16+
const MIN_DRAWER_SIZE = 400;
17+
18+
interface UseDrawersProps {
19+
onAiDrawerFocus: () => void;
20+
expandedDrawerId: string | null;
21+
setExpandedDrawerId: (value: string | null) => void;
22+
}
23+
24+
export function useAiDrawer({ onAiDrawerFocus, expandedDrawerId, setExpandedDrawerId }: UseDrawersProps) {
25+
const [runtimeDrawer, setRuntimeDrawer] = useState<RuntimeAiDrawerConfig | null>(null);
26+
const [activeAiDrawerId, setActiveAiDrawerId] = useState<string | null>(null);
27+
const [size, setSize] = useState<number | null>(null);
28+
const aiDrawerWasOpenRef = useRef(false);
29+
aiDrawerWasOpenRef.current = aiDrawerWasOpenRef.current || !!activeAiDrawerId;
30+
31+
function onActiveAiDrawerResize(size: number) {
32+
if (!activeAiDrawerId) {
33+
return;
34+
}
35+
setSize(size);
36+
fireNonCancelableEvent(activeAiDrawer?.onResize, { id: activeAiDrawerId, size });
37+
}
38+
39+
function onActiveAiDrawerChange(
40+
newDrawerId: string | null,
41+
{ initiatedByUserAction }: OnChangeParams = DEFAULT_ON_CHANGE_PARAMS
42+
) {
43+
setActiveAiDrawerId(newDrawerId);
44+
45+
if (newDrawerId) {
46+
fireNonCancelableEvent(runtimeDrawer?.onToggle, { isOpen: true, initiatedByUserAction });
47+
}
48+
49+
if (activeAiDrawerId) {
50+
fireNonCancelableEvent(runtimeDrawer?.onToggle, { isOpen: false, initiatedByUserAction });
51+
52+
if (activeAiDrawerId === expandedDrawerId) {
53+
setExpandedDrawerId?.(null);
54+
}
55+
}
56+
57+
onAiDrawerFocus?.();
58+
}
59+
60+
function checkId(newId: string) {
61+
if (runtimeDrawer && runtimeDrawer.id !== newId) {
62+
metrics.sendOpsMetricObject('awsui-widget-drawer-incorrect-id', { oldId: runtimeDrawer.id, newId });
63+
}
64+
}
65+
66+
function aiDrawerMessageHandler(event: WidgetMessage) {
67+
if (event.type === 'registerLeftDrawer') {
68+
setRuntimeDrawer(event.payload);
69+
if (!aiDrawerWasOpenRef.current && event.payload.defaultActive) {
70+
onActiveAiDrawerChange(event.payload.id, { initiatedByUserAction: false });
71+
}
72+
return;
73+
}
74+
75+
switch (event.type) {
76+
case 'updateDrawerConfig':
77+
checkId(event.payload.id);
78+
setRuntimeDrawer(current => (current ? { ...current, ...event.payload } : current));
79+
break;
80+
case 'openDrawer':
81+
checkId(event.payload.id);
82+
onActiveAiDrawerChange(event.payload.id, { initiatedByUserAction: false });
83+
break;
84+
case 'closeDrawer':
85+
checkId(event.payload.id);
86+
onActiveAiDrawerChange(null, { initiatedByUserAction: false });
87+
break;
88+
case 'resizeDrawer':
89+
checkId(event.payload.id);
90+
onActiveAiDrawerResize(event.payload.size);
91+
break;
92+
case 'expandDrawer':
93+
checkId(event.payload.id);
94+
setExpandedDrawerId(event.payload.id);
95+
break;
96+
case 'exitExpandedMode':
97+
setExpandedDrawerId(null);
98+
break;
99+
}
100+
}
101+
102+
const aiDrawer = runtimeDrawer && mapRuntimeConfigToAiDrawer(runtimeDrawer);
103+
const activeAiDrawer = activeAiDrawerId && activeAiDrawerId === aiDrawer?.id ? aiDrawer : null;
104+
const activeAiDrawerSize = activeAiDrawerId ? (size ?? activeAiDrawer?.defaultSize ?? MIN_DRAWER_SIZE) : 0;
105+
const minAiDrawerSize = Math.min(activeAiDrawer?.defaultSize ?? MIN_DRAWER_SIZE, MIN_DRAWER_SIZE);
106+
107+
return {
108+
aiDrawer,
109+
aiDrawerMessageHandler,
110+
onActiveAiDrawerChange,
111+
activeAiDrawer,
112+
activeAiDrawerId,
113+
activeAiDrawerSize,
114+
minAiDrawerSize,
115+
onActiveAiDrawerResize,
116+
};
117+
}

0 commit comments

Comments
 (0)