diff --git a/src/app-layout/__tests__/__snapshots__/widget-contract-old.test.tsx.snap b/src/app-layout/__tests__/__snapshots__/widget-contract-old.test.tsx.snap
index 776ae9c761..a0b1a5fdf2 100644
--- a/src/app-layout/__tests__/__snapshots__/widget-contract-old.test.tsx.snap
+++ b/src/app-layout/__tests__/__snapshots__/widget-contract-old.test.tsx.snap
@@ -1365,6 +1365,8 @@ Map {
"children":
notifications
,
+ "flashbarProps": null,
+ "setFlashbarProps": [Function],
},
"AppLayoutSplitPanelDrawerBottomImplementation" => {
"appLayoutInternals": {
diff --git a/src/app-layout/visual-refresh-toolbar/interfaces.ts b/src/app-layout/visual-refresh-toolbar/interfaces.ts
index 968a690e95..6204f7b225 100644
--- a/src/app-layout/visual-refresh-toolbar/interfaces.ts
+++ b/src/app-layout/visual-refresh-toolbar/interfaces.ts
@@ -5,6 +5,7 @@ import React from 'react';
import { BreadcrumbGroupProps } from '../../breadcrumb-group/interfaces';
import { ButtonGroupProps } from '../../button-group/interfaces';
+import { FlashbarProps } from '../../flashbar/interfaces';
import { SplitPanelSideToggleProps } from '../../internal/context/split-panel-context';
import { NonCancelableEventHandler } from '../../internal/events';
import { SomeOptional } from '../../internal/types';
@@ -90,6 +91,8 @@ interface AppLayoutWidgetizedState extends AppLayoutInternals {
verticalOffsets: VerticalLayoutOutput;
navigationAnimationDisabled: boolean;
aiDrawerExpandedMode: boolean;
+ flashbarProps: FlashbarProps | null;
+ setFlashbarProps: (props: FlashbarProps | null) => void;
splitPanelOffsets: {
stickyVerticalBottomOffset: number;
mainContentPaddingBlockEnd: number | undefined;
diff --git a/src/app-layout/visual-refresh-toolbar/notifications/index.tsx b/src/app-layout/visual-refresh-toolbar/notifications/index.tsx
index b9d7e2d601..152a206b22 100644
--- a/src/app-layout/visual-refresh-toolbar/notifications/index.tsx
+++ b/src/app-layout/visual-refresh-toolbar/notifications/index.tsx
@@ -5,20 +5,26 @@ import clsx from 'clsx';
import { useResizeObserver } from '@cloudscape-design/component-toolkit/internal';
+import { FlashbarImplementation } from '../../../flashbar/implementation';
import { highContrastHeaderClassName } from '../../../internal/utils/content-header-utils';
-import { AppLayoutInternals } from '../interfaces';
+import { AppLayoutInternals, AppLayoutState } from '../interfaces';
import { NotificationsSlot } from '../skeleton/slots';
+import { FlashbarPropsSetter } from '../state/runtime-notifications';
import testutilStyles from '../../test-classes/styles.css.js';
import styles from './styles.css.js';
export interface AppLayoutNotificationsImplementationProps {
appLayoutInternals: AppLayoutInternals;
+ flashbarProps?: AppLayoutState['widgetizedState']['flashbarProps'];
+ setFlashbarProps?: AppLayoutState['widgetizedState']['setFlashbarProps'];
children: React.ReactNode;
}
export function AppLayoutNotificationsImplementation({
appLayoutInternals,
+ flashbarProps,
+ setFlashbarProps,
children,
}: AppLayoutNotificationsImplementationProps) {
const { ariaLabels, stickyNotifications, setNotificationsHeight, verticalOffsets } = appLayoutInternals;
@@ -51,7 +57,8 @@ export function AppLayoutNotificationsImplementation({
}}
>
- {children}
+ {children}
+ {flashbarProps && }
);
diff --git a/src/app-layout/visual-refresh-toolbar/state/runtime-notifications.ts b/src/app-layout/visual-refresh-toolbar/state/runtime-notifications.ts
new file mode 100644
index 0000000000..7e6b2535ac
--- /dev/null
+++ b/src/app-layout/visual-refresh-toolbar/state/runtime-notifications.ts
@@ -0,0 +1,31 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import { createContext, useState } from 'react';
+
+import { FlashbarProps } from '../../../flashbar/interfaces';
+import { WidgetMessage } from '../../../internal/plugins/widget/interfaces';
+
+export const FlashbarPropsSetter = createContext<((props: FlashbarProps | null) => void) | null>(null);
+
+export function useRuntimeNotifications() {
+ const [flashbarProps, setFlashbarProps] = useState(null);
+ const [notifications, setNotifications] = useState>([]);
+
+ function notificationsMessageHandler(message: WidgetMessage) {
+ if (message.type === 'emitNotification') {
+ setNotifications(notifications => [...notifications, message.payload]);
+ }
+ }
+
+ return {
+ flashbarProps:
+ flashbarProps || notifications.length > 0
+ ? {
+ ...flashbarProps,
+ items: [...(flashbarProps?.items ?? []), ...notifications],
+ }
+ : null,
+ setFlashbarProps,
+ notificationsMessageHandler,
+ };
+}
diff --git a/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx b/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx
index e1e47549d0..499e35ea4c 100644
--- a/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx
+++ b/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx
@@ -26,6 +26,7 @@ import {
} from '../compute-layout';
import { AppLayoutState } from '../interfaces';
import { AppLayoutInternalProps, AppLayoutInternals } from '../interfaces';
+import { useRuntimeNotifications } from './runtime-notifications';
import { useAiDrawer } from './use-ai-drawer';
import { useWidgetMessages } from './use-widget-messages';
@@ -158,7 +159,11 @@ export const useAppLayout = (
expandedDrawerId,
setExpandedDrawerId,
});
- useWidgetMessages(hasToolbar, message => aiDrawerMessageHandler(message));
+ const { flashbarProps, setFlashbarProps, notificationsMessageHandler } = useRuntimeNotifications();
+ useWidgetMessages(hasToolbar, message => {
+ aiDrawerMessageHandler(message);
+ notificationsMessageHandler(message);
+ });
const aiDrawerFocusControl = useAsyncFocusControl(!!activeAiDrawer?.id, true, activeAiDrawer?.id);
const onActiveDrawerChangeHandler = (
@@ -469,6 +474,8 @@ export const useAppLayout = (
widgetizedState: {
...appLayoutInternals,
aiDrawerExpandedMode: expandedDrawerId === activeAiDrawer?.id,
+ flashbarProps,
+ setFlashbarProps,
isNested,
navigationAnimationDisabled,
verticalOffsets,
diff --git a/src/app-layout/visual-refresh-toolbar/widget-areas/top-content-slot.tsx b/src/app-layout/visual-refresh-toolbar/widget-areas/top-content-slot.tsx
index 5265418cab..8d8cf4a4f0 100644
--- a/src/app-layout/visual-refresh-toolbar/widget-areas/top-content-slot.tsx
+++ b/src/app-layout/visual-refresh-toolbar/widget-areas/top-content-slot.tsx
@@ -26,7 +26,11 @@ export const TopContentSlotImplementation = ({ appLayoutProps, appLayoutState }:
>
)}
{appLayoutProps.notifications && (
-
+
{appLayoutProps.notifications}
)}
diff --git a/src/flashbar/implementation.tsx b/src/flashbar/implementation.tsx
index 9bf77cc624..cc43acecf7 100644
--- a/src/flashbar/implementation.tsx
+++ b/src/flashbar/implementation.tsx
@@ -1,13 +1,34 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import React from 'react';
+import React, { useContext, useEffect } from 'react';
+import { FlashbarPropsSetter } from '../app-layout/visual-refresh-toolbar/state/runtime-notifications';
import { createWidgetizedComponent } from '../internal/widgets';
import CollapsibleFlashbar from './collapsible-flashbar';
-import { InternalFlashbarProps } from './interfaces';
+import { FlashbarProps, InternalFlashbarProps } from './interfaces';
import NonCollapsibleFlashbar from './non-collapsible-flashbar';
+function FlashbarPropagator({
+ props,
+ setFlashbarProps,
+}: {
+ props: FlashbarProps;
+ setFlashbarProps: (props: FlashbarProps | null) => void;
+}) {
+ useEffect(() => {
+ setFlashbarProps(props);
+ return () => setFlashbarProps(null);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return null;
+}
+
export function FlashbarImplementation(props: InternalFlashbarProps) {
+ const setFlashbarProps = useContext(FlashbarPropsSetter);
+ if (setFlashbarProps) {
+ return ;
+ }
if (props.stackItems) {
return ;
} else {
diff --git a/src/internal/plugins/widget.ts b/src/internal/plugins/widget.ts
index 45d6fa4508..b75719511d 100644
--- a/src/internal/plugins/widget.ts
+++ b/src/internal/plugins/widget.ts
@@ -2,4 +2,4 @@
// SPDX-License-Identifier: Apache-2.0
export * from './widget/interfaces';
export { isAppLayoutReady, whenAppLayoutReady } from './widget/core';
-export { registerLeftDrawer, updateDrawer } from './widget/index';
+export { registerLeftDrawer, updateDrawer, emitNotification } from './widget/index';
diff --git a/src/internal/plugins/widget/index.ts b/src/internal/plugins/widget/index.ts
index 1a0ef52991..7b63b41110 100644
--- a/src/internal/plugins/widget/index.ts
+++ b/src/internal/plugins/widget/index.ts
@@ -2,7 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
import { getAppLayoutMessageHandler, pushInitialMessage } from './core';
-import { AppLayoutUpdateMessage, DrawerPayload, RegisterDrawerMessage } from './interfaces';
+import {
+ AppLayoutUpdateMessage,
+ DrawerPayload,
+ EmitNotificationMessage,
+ EmitNotificationPayload,
+ RegisterDrawerMessage,
+} from './interfaces';
/**
* Registers a new runtime drawer to app layout
@@ -21,3 +27,13 @@ export function registerLeftDrawer(drawer: DrawerPayload) {
export function updateDrawer(message: AppLayoutUpdateMessage) {
getAppLayoutMessageHandler()?.(message);
}
+
+/**
+ * Emit a notification to the app layout
+ * @param payload
+ */
+export function emitNotification(payload: EmitNotificationPayload) {
+ const message: EmitNotificationMessage = { type: 'emitNotification', payload };
+ pushInitialMessage(message);
+ getAppLayoutMessageHandler()?.(message);
+}
diff --git a/src/internal/plugins/widget/interfaces.ts b/src/internal/plugins/widget/interfaces.ts
index d74ed6233b..a024ba069c 100644
--- a/src/internal/plugins/widget/interfaces.ts
+++ b/src/internal/plugins/widget/interfaces.ts
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ButtonGroupProps } from '../../../button-group/interfaces';
+import { FlashbarProps } from '../../../flashbar/interfaces';
import { NonCancelableEventHandler } from '../../events';
interface Message {
@@ -70,6 +71,14 @@ export type AppLayoutUpdateMessage =
| ExpandDrawerMessage
| ExitExpandedModeMessage;
-export type InitialMessage = RegisterDrawerMessage;
+export interface EmitNotificationPayload {
+ type: FlashbarProps.Type;
+ header: string;
+ content: string;
+}
+
+export type EmitNotificationMessage = Message<'emitNotification', EmitNotificationPayload>;
+
+export type InitialMessage = EmitNotificationMessage | RegisterDrawerMessage;
export type WidgetMessage = InitialMessage | AppLayoutUpdateMessage;