diff --git a/package-lock.json b/package-lock.json index d663110273..66823ea2d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@cloudscape-design/components", "version": "3.0.0", + "hasInstallScript": true, "dependencies": { "@cloudscape-design/collection-hooks": "^1.0.0", "@cloudscape-design/component-toolkit": "^1.0.0-beta", diff --git a/pages/app-layout/with-error-boundaries.page.tsx b/pages/app-layout/with-error-boundaries.page.tsx new file mode 100644 index 0000000000..c3e11fa21b --- /dev/null +++ b/pages/app-layout/with-error-boundaries.page.tsx @@ -0,0 +1,372 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { useContext, useRef, useState } from 'react'; + +import { AppLayout, Button, Container, ContentLayout, Header, HelpPanel, SpaceBetween, SplitPanel } from '~components'; +import { AppLayoutProps } from '~components/app-layout'; +import BreadcrumbGroup from '~components/breadcrumb-group'; +import ErrorBoundary from '~components/error-boundary'; +import I18nProvider from '~components/i18n'; +import messages from '~components/i18n/messages/all.en'; +import awsuiPlugins from '~components/internal/plugins'; +import { registerBottomDrawer, registerLeftDrawer } from '~components/internal/plugins/widget'; +import { mount, unmount } from '~mount'; + +import './utils/external-widget'; +import AppContext, { AppContextType } from '../app/app-context'; +import { CustomDrawerContent } from './utils/content-blocks'; +import { drawerLabels } from './utils/drawers'; +import appLayoutLabels from './utils/labels'; +import { splitPaneli18nStrings } from './utils/strings'; + +type DemoContext = React.Context< + AppContextType<{ + hasTools: boolean | undefined; + hasDrawers: boolean | undefined; + splitPanelPosition: AppLayoutProps.SplitPanelPreferences['position']; + }> +>; + +export default function WithErrorBoundariesPage() { + const [activeDrawerId, setActiveDrawerId] = useState(null); + const { urlParams, setUrlParams } = useContext(AppContext as DemoContext); + const [isToolsOpen, setIsToolsOpen] = useState(false); + const [isBrokenNavigation, setIsBrokenNavigation] = useState(false); + const appLayoutRef = useRef(null); + const [breadcrumbsItems, setBreadcrumbsItems] = useState([ + { text: 'Home', href: '#' }, + { text: 'Service', href: '#' }, + ]); + const drawersProps: Pick | null = { + activeDrawerId: activeDrawerId, + drawers: [ + { + ariaLabels: { + closeButton: 'ProHelp close button', + drawerName: 'ProHelp drawer content', + triggerButton: 'ProHelp trigger button', + resizeHandle: 'ProHelp resize handle', + }, + content: , + id: 'pro-help', + trigger: { + iconName: 'contact', + }, + }, + ], + onDrawerChange: event => { + setActiveDrawerId(event.detail.activeDrawerId); + }, + }; + + return ( + + console.log('parent level: ', error)}> + +
+ +
Error boundaries in app layout slots
+ + } + > + + Toolbar}> + + + + + + Panels}> + + + + + + + + + +
+
+
+ } + splitPanel={ + + This is the Split Panel! + + } + splitPanelPreferences={{ + position: urlParams.splitPanelPosition, + }} + onSplitPanelPreferencesChange={event => { + const { position } = event.detail; + setUrlParams({ splitPanelPosition: position === 'side' ? position : undefined }); + }} + onToolsChange={event => { + setIsToolsOpen(event.detail.open); + }} + tools={} + toolsOpen={isToolsOpen} + navigation={isBrokenNavigation ? ({} as any) :
navigation
} + {...drawersProps} + /> + +
+ ); +} + +function Info({ helpPathSlug }: { helpPathSlug: string }) { + return Info}>Here is some info for you: {helpPathSlug}; +} diff --git a/src/app-layout/visual-refresh-toolbar/drawer/global-ai-drawer.tsx b/src/app-layout/visual-refresh-toolbar/drawer/global-ai-drawer.tsx index c86ea70a08..f3da7b3a19 100644 --- a/src/app-layout/visual-refresh-toolbar/drawer/global-ai-drawer.tsx +++ b/src/app-layout/visual-refresh-toolbar/drawer/global-ai-drawer.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import { InternalItemOrGroup } from '../../../button-group/interfaces'; import ButtonGroup from '../../../button-group/internal'; +import { InternalErrorBoundary } from '../../../error-boundary/internal'; import PanelResizeHandle from '../../../internal/components/panel-resize-handle'; import customCssProps from '../../../internal/generated/custom-css-properties'; import { usePrevious } from '../../../internal/hooks/use-previous'; @@ -14,6 +15,7 @@ import { AppLayoutProps } from '../../interfaces'; import { FocusControlState } from '../../utils/use-focus-control'; import { AppLayoutInternals, InternalDrawer } from '../interfaces'; import { OnChangeParams } from '../state/use-ai-drawer'; +import { TriggerButtonErrorBoundary } from '../toolbar/drawer-triggers'; import { useResize } from './use-resize'; import sharedStyles from '../../resize/styles.css.js'; @@ -188,26 +190,34 @@ export function AppLayoutGlobalAiDrawerImplementation({
- {activeAiDrawer?.header ??
} + + {activeAiDrawer?.header ??
} +
- { - switch (event.detail.id) { - case 'close': - onActiveAiDrawerChange?.(null, { initiatedByUserAction: true }); - break; - case 'expand': - setExpandedDrawerId(isExpanded ? null : activeDrawerId!); - break; - default: - activeAiDrawer?.onHeaderActionClick?.(event); - } - }} - ariaLabel="Left panel actions" - items={drawerActions} - /> + console.log('Error boundary for the local drawer: ', error)} + suppressNested={false} + suppressible={true} + > + { + switch (event.detail.id) { + case 'close': + onActiveAiDrawerChange?.(null, { initiatedByUserAction: true }); + break; + case 'expand': + setExpandedDrawerId(isExpanded ? null : activeDrawerId!); + break; + default: + activeAiDrawer?.onHeaderActionClick?.(event); + } + }} + ariaLabel="Left panel actions" + items={drawerActions} + /> +
{!isMobile && isExpanded && activeAiDrawer?.ariaLabels?.exitExpandedModeButton && ( @@ -242,7 +252,15 @@ export function AppLayoutGlobalAiDrawerImplementation({
)}
-
{activeAiDrawer?.content}
+
+ console.log('Error boundary for the local drawer: ', error)} + suppressNested={false} + suppressible={true} + > + {activeAiDrawer?.content} + +
diff --git a/src/app-layout/visual-refresh-toolbar/drawer/global-bottom-drawer.tsx b/src/app-layout/visual-refresh-toolbar/drawer/global-bottom-drawer.tsx index 7fdabd08d2..a2aa427e7a 100644 --- a/src/app-layout/visual-refresh-toolbar/drawer/global-bottom-drawer.tsx +++ b/src/app-layout/visual-refresh-toolbar/drawer/global-bottom-drawer.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import { InternalItemOrGroup } from '../../../button-group/interfaces'; import ButtonGroup from '../../../button-group/internal'; +import { InternalErrorBoundary } from '../../../error-boundary/internal'; import PanelResizeHandle from '../../../internal/components/panel-resize-handle'; import customCssProps from '../../../internal/generated/custom-css-properties'; import { usePrevious } from '../../../internal/hooks/use-previous'; @@ -241,74 +242,81 @@ function AppLayoutGlobalBottomDrawerImplementation({ }} data-testid={`awsui-app-layout-drawer-${activeDrawerId}`} > -
- {!isMobile && !isExpanded &&
} - {!isMobile && activeDrawer?.resizable && !isExpanded && ( - // Prevents receiving focus in Firefox -
- -
- )} -
-
- {activeDrawer?.header ??
} -
- { - switch (event.detail.id) { - case 'close': - onActiveGlobalBottomDrawerChange(null, { initiatedByUserAction: true }); - break; - case 'expand': - setExpandedDrawerId(isExpanded ? null : activeDrawerId); - break; - default: - activeDrawer?.onHeaderActionClick?.(event); - } - }} - ariaLabel="Global panel actions" - items={drawerActions} - __internalRootRef={(root: HTMLElement) => { - if (!root) { - return; - } - refs.close = { - current: root.querySelector('[data-itemid="close"]') as unknown as Focusable, - }; - }} + console.log('Error boundary for the local drawer: ', error)} + suppressNested={false} + suppressible={true} + > +
+ {!isMobile && !isExpanded &&
} + {!isMobile && activeDrawer?.resizable && !isExpanded && ( + // Prevents receiving focus in Firefox +
+
-
-
-
+ )} +
+
+ {activeDrawer?.header ??
} +
+ { + switch (event.detail.id) { + case 'close': + onActiveGlobalBottomDrawerChange(null, { initiatedByUserAction: true }); + break; + case 'expand': + setExpandedDrawerId(isExpanded ? null : activeDrawerId); + break; + default: + activeDrawer?.onHeaderActionClick?.(event); + } + }} + ariaLabel="Global panel actions" + items={drawerActions} + __internalRootRef={(root: HTMLElement) => { + if (!root) { + return; + } + refs.close = { + current: root.querySelector('[data-itemid="close"]') as unknown as Focusable, + }; + }} + /> +
+
+
- {activeDrawer?.content} +
+ {activeDrawer?.content} +
-
+ ); }} diff --git a/src/app-layout/visual-refresh-toolbar/drawer/global-drawer.tsx b/src/app-layout/visual-refresh-toolbar/drawer/global-drawer.tsx index 27ed111208..abd102d6be 100644 --- a/src/app-layout/visual-refresh-toolbar/drawer/global-drawer.tsx +++ b/src/app-layout/visual-refresh-toolbar/drawer/global-drawer.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import { InternalItemOrGroup } from '../../../button-group/interfaces'; import ButtonGroup from '../../../button-group/internal'; +import { InternalErrorBoundary } from '../../../error-boundary/internal'; import PanelResizeHandle from '../../../internal/components/panel-resize-handle'; import customCssProps from '../../../internal/generated/custom-css-properties'; import { usePrevious } from '../../../internal/hooks/use-previous'; @@ -179,33 +180,45 @@ function AppLayoutGlobalDrawerImplementation({ data-testid={`awsui-app-layout-drawer-content-${activeDrawerId}`} >
- { - switch (event.detail.id) { - case 'close': - onActiveGlobalDrawersChange(activeDrawerId, { initiatedByUserAction: true }); - break; - case 'expand': - setExpandedDrawerId(isExpanded ? null : activeDrawerId); - break; - default: - activeGlobalDrawer?.onHeaderActionClick?.(event); - } - }} - ariaLabel="Global panel actions" - items={drawerActions} - __internalRootRef={(root: HTMLElement) => { - if (!root) { - return; - } - refs.close = { current: root.querySelector('[data-itemid="close"]') as unknown as Focusable }; - }} - /> + console.log('Error boundary for the trigger button: ', error)} + suppressNested={false} + suppressible={true} + > + { + switch (event.detail.id) { + case 'close': + onActiveGlobalDrawersChange(activeDrawerId, { initiatedByUserAction: true }); + break; + case 'expand': + setExpandedDrawerId(isExpanded ? null : activeDrawerId); + break; + default: + activeGlobalDrawer?.onHeaderActionClick?.(event); + } + }} + ariaLabel="Global panel actions" + items={drawerActions} + __internalRootRef={(root: HTMLElement) => { + if (!root) { + return; + } + refs.close = { current: root.querySelector('[data-itemid="close"]') as unknown as Focusable }; + }} + /> +
- {activeGlobalDrawer?.content} + console.log('Error boundary for the trigger button: ', error)} + suppressNested={false} + suppressible={true} + > + {activeGlobalDrawer?.content} +
diff --git a/src/app-layout/visual-refresh-toolbar/drawer/local-drawer.tsx b/src/app-layout/visual-refresh-toolbar/drawer/local-drawer.tsx index 1e988cb9ac..26ba5b1a2f 100644 --- a/src/app-layout/visual-refresh-toolbar/drawer/local-drawer.tsx +++ b/src/app-layout/visual-refresh-toolbar/drawer/local-drawer.tsx @@ -5,6 +5,7 @@ import { Transition } from 'react-transition-group'; import clsx from 'clsx'; import { InternalButton } from '../../../button/internal'; +import { BuiltInErrorBoundary } from '../../../error-boundary/internal'; import PanelResizeHandle from '../../../internal/components/panel-resize-handle'; import customCssProps from '../../../internal/generated/custom-css-properties'; import { getLimitedValue } from '../../../split-panel/utils/size-utils'; @@ -143,11 +144,11 @@ export function AppLayoutDrawerImplementation({ )} style={{ blockSize: drawerHeight }} > - {toolsContent} + {toolsContent} {activeDrawerId !== TOOLS_DRAWER_ID && (
- {activeDrawer?.content} + {activeDrawer?.content}
)} diff --git a/src/app-layout/visual-refresh-toolbar/drawer/styles.scss b/src/app-layout/visual-refresh-toolbar/drawer/styles.scss index d1879fa43d..e7a2148d45 100644 --- a/src/app-layout/visual-refresh-toolbar/drawer/styles.scss +++ b/src/app-layout/visual-refresh-toolbar/drawer/styles.scss @@ -25,6 +25,46 @@ $global-drawer-expanded-mode-motion: #{awsui.$motion-duration-refresh-only-slow} $drawer-resize-handle-size: awsui.$space-m; $ai-drawer-heider-height: 42px; +.ai-drawer-error-boundary { + position: sticky; + z-index: constants.$drawer-z-index; + background-color: awsui.$color-background-container-content; + display: grid; + inline-size: var(#{custom-props.$drawerSize}); + + block-size: 100%; + overflow: hidden; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features */ + overscroll-behavior-y: contain; + pointer-events: auto; + word-wrap: break-word; + border-start-end-radius: awsui.$space-xxs; + min-inline-size: 300px; + + @include mobile-only { + margin-block-start: 42px; + inline-size: 100%; + } +} + +.drawer-error-boundary { + @include mobile-only { + position: sticky; + z-index: constants.$drawer-z-index; + background-color: awsui.$color-background-container-content; + display: grid; + inline-size: var(#{custom-props.$drawerSize}); + margin-block-start: 42px; + + block-size: 100%; + overflow: hidden; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features */ + overscroll-behavior-y: contain; + pointer-events: auto; + word-wrap: break-word; + } +} + .drawer { position: sticky; z-index: constants.$drawer-z-index; diff --git a/src/app-layout/visual-refresh-toolbar/toolbar/drawer-triggers.tsx b/src/app-layout/visual-refresh-toolbar/toolbar/drawer-triggers.tsx index 4df4cf7aab..a098a7293f 100644 --- a/src/app-layout/visual-refresh-toolbar/toolbar/drawer-triggers.tsx +++ b/src/app-layout/visual-refresh-toolbar/toolbar/drawer-triggers.tsx @@ -5,7 +5,11 @@ import clsx from 'clsx'; import { useContainerQuery } from '@cloudscape-design/component-toolkit'; +import Box from '../../../box/internal'; +import { InternalErrorBoundary } from '../../../error-boundary/internal'; import { useMobile } from '../../../internal/hooks/use-mobile'; +import Popover from '../../../popover/internal'; +import StatusIndicator from '../../../status-indicator/internal'; import { splitItems } from '../../drawer/drawers-helpers'; import OverflowMenu from '../../drawer/overflow-menu'; import { AppLayoutProps, AppLayoutPropsWithDefaults } from '../../interfaces'; @@ -52,6 +56,36 @@ interface DrawerTriggersProps { disabled: boolean; } +export const TriggerButtonErrorBoundary: React.FC<{ id: string }> = ({ id, children }) => { + return ( + console.log('Error boundary for the trigger button: ', error)} + suppressNested={false} + suppressible={true} + renderFallback={props => { + return ( + + {props.description} + {props.action} + + } + > + + + ); + }} + > + {children} + + ); +}; + export function DrawerTriggers({ ariaLabels, activeDrawerId, @@ -150,67 +184,71 @@ export function DrawerTriggers({ > {splitPanelToggleProps && ( <> - { - exitExpandedMode(); - if (!!expandedDrawerId && splitPanelToggleProps.active) { - return; - } - onSplitPanelToggle?.(); - }} - selected={!expandedDrawerId && splitPanelToggleProps.active} - ref={splitPanelResolvedPosition === 'side' ? splitPanelFocusRef : undefined} - hasTooltip={true} - isMobile={isMobile} - isForSplitPanel={true} - disabled={disabled} - /> - {hasMultipleTriggers ?
: null} + + { + exitExpandedMode(); + if (!!expandedDrawerId && splitPanelToggleProps.active) { + return; + } + onSplitPanelToggle?.(); + }} + selected={!expandedDrawerId && splitPanelToggleProps.active} + ref={splitPanelResolvedPosition === 'side' ? splitPanelFocusRef : undefined} + hasTooltip={true} + isMobile={isMobile} + isForSplitPanel={true} + disabled={disabled} + /> + {hasMultipleTriggers ?
: null} +
)} {visibleItems.slice(0, globalDrawersStartIndex).map(item => { const isForPreviousActiveDrawer = previousActiveLocalDrawerId?.current === item.id; const selected = !expandedDrawerId && item.id === activeDrawerId; return ( - { - exitExpandedMode(); - if (!!expandedDrawerId && activeDrawerId === item.id) { - return; - } - onActiveDrawerChange?.(activeDrawerId !== item.id ? item.id : null, { initiatedByUserAction: true }); - }} - ref={item.id === previousActiveLocalDrawerId.current ? drawersFocusRef : undefined} - selected={selected} - badge={item.badge} - testId={`awsui-app-layout-trigger-${item.id}`} - hasTooltip={true} - hasOpenDrawer={hasOpenDrawer} - tooltipText={item.ariaLabels?.drawerName} - isForPreviousActiveDrawer={isForPreviousActiveDrawer} - isMobile={isMobile} - disabled={disabled} - /> + + { + exitExpandedMode(); + if (!!expandedDrawerId && activeDrawerId === item.id) { + return; + } + onActiveDrawerChange?.(activeDrawerId !== item.id ? item.id : null, { initiatedByUserAction: true }); + }} + ref={item.id === previousActiveLocalDrawerId.current ? drawersFocusRef : undefined} + selected={selected} + badge={item.badge} + testId={`awsui-app-layout-trigger-${item.id}`} + hasTooltip={true} + hasOpenDrawer={hasOpenDrawer} + tooltipText={item.ariaLabels?.drawerName} + isForPreviousActiveDrawer={isForPreviousActiveDrawer} + isMobile={isMobile} + disabled={disabled} + /> + ); })} {globalDrawersStartIndex > 0 && visibleItems.length > globalDrawersStartIndex && ( @@ -227,98 +265,103 @@ export function DrawerTriggers({ } return ( - { - exitExpandedMode(); - if (!!expandedDrawerId && item.id !== expandedDrawerId && activeGlobalDrawersIds.includes(item.id)) { - return; + + { + exitExpandedMode(); + if (!!expandedDrawerId && item.id !== expandedDrawerId && activeGlobalDrawersIds.includes(item.id)) { + return; + } + if (isBottom) { + onActiveGlobalBottomDrawerChange?.(selected ? null : item.id, { initiatedByUserAction: true }); + return; + } + onActiveGlobalDrawersChange?.(item.id, { initiatedByUserAction: true }); + }} + ref={isBottom ? bottomDrawersFocusRef : globalDrawersFocusControl?.refs[item.id]?.toggle} + selected={selected} + badge={item.badge} + testId={`awsui-app-layout-trigger-${item.id}`} + hasTooltip={true} + hasOpenDrawer={hasOpenDrawer} + tooltipText={item.ariaLabels?.drawerName} + isForPreviousActiveDrawer={isForPreviousActiveDrawer} + isMobile={isMobile} + disabled={disabled} + /> + + ); + })} + {overflowItems.length > 0 && ( + + { + const isBottom = item?.position === 'bottom'; + let active = + activeGlobalDrawersIds.includes(item.id) && (!expandedDrawerId || item.id === expandedDrawerId); + if (isBottom) { + active = + item.id === activeGlobalBottomDrawerId && (!expandedDrawerId || item.id === expandedDrawerId); } + return { + ...item, + active, + }; + })} + ariaLabel={overflowMenuHasBadge ? ariaLabels?.drawersOverflowWithBadge : ariaLabels?.drawersOverflow} + customTriggerBuilder={({ onClick, triggerRef, ariaLabel, ariaExpanded, testUtilsClass }) => { + return ( + + ); + }} + onItemClick={event => { + const id = event.detail.id; + exitExpandedMode(); + const item = overflowItems.find(item => item.id === id); + const isBottom = item?.position === 'bottom'; if (isBottom) { + const selected = + item.id === activeGlobalBottomDrawerId && (!expandedDrawerId || item.id === expandedDrawerId); onActiveGlobalBottomDrawerChange?.(selected ? null : item.id, { initiatedByUserAction: true }); return; } - onActiveGlobalDrawersChange?.(item.id, { initiatedByUserAction: true }); + if (globalDrawers.find(drawer => drawer.id === id)) { + if (!!expandedDrawerId && id !== expandedDrawerId && activeGlobalDrawersIds.includes(id)) { + return; + } + onActiveGlobalDrawersChange?.(id, { initiatedByUserAction: true }); + } else { + onActiveDrawerChange?.(event.detail.id, { initiatedByUserAction: true }); + } }} - ref={isBottom ? bottomDrawersFocusRef : globalDrawersFocusControl?.refs[item.id]?.toggle} - selected={selected} - badge={item.badge} - testId={`awsui-app-layout-trigger-${item.id}`} - hasTooltip={true} - hasOpenDrawer={hasOpenDrawer} - tooltipText={item.ariaLabels?.drawerName} - isForPreviousActiveDrawer={isForPreviousActiveDrawer} - isMobile={isMobile} - disabled={disabled} + globalDrawersStartIndex={globalDrawersStartIndex - indexOfOverflowItem} /> - ); - })} - {overflowItems.length > 0 && ( - { - const isBottom = item?.position === 'bottom'; - let active = - activeGlobalDrawersIds.includes(item.id) && (!expandedDrawerId || item.id === expandedDrawerId); - if (isBottom) { - active = item.id === activeGlobalBottomDrawerId && (!expandedDrawerId || item.id === expandedDrawerId); - } - return { - ...item, - active, - }; - })} - ariaLabel={overflowMenuHasBadge ? ariaLabels?.drawersOverflowWithBadge : ariaLabels?.drawersOverflow} - customTriggerBuilder={({ onClick, triggerRef, ariaLabel, ariaExpanded, testUtilsClass }) => { - return ( - - ); - }} - onItemClick={event => { - const id = event.detail.id; - exitExpandedMode(); - const item = overflowItems.find(item => item.id === id); - const isBottom = item?.position === 'bottom'; - if (isBottom) { - const selected = - item.id === activeGlobalBottomDrawerId && (!expandedDrawerId || item.id === expandedDrawerId); - onActiveGlobalBottomDrawerChange?.(selected ? null : item.id, { initiatedByUserAction: true }); - return; - } - if (globalDrawers.find(drawer => drawer.id === id)) { - if (!!expandedDrawerId && id !== expandedDrawerId && activeGlobalDrawersIds.includes(id)) { - return; - } - onActiveGlobalDrawersChange?.(id, { initiatedByUserAction: true }); - } else { - onActiveDrawerChange?.(event.detail.id, { initiatedByUserAction: true }); - } - }} - globalDrawersStartIndex={globalDrawersStartIndex - indexOfOverflowItem} - /> + )} diff --git a/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx b/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx index a0b6414524..269af7c924 100644 --- a/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx +++ b/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx @@ -6,7 +6,11 @@ import clsx from 'clsx'; import { useResizeObserver } from '@cloudscape-design/component-toolkit/internal'; +import Box from '../../../box/internal'; +import { InternalErrorBoundary } from '../../../error-boundary/internal'; import { createWidgetizedComponent } from '../../../internal/widgets'; +import Popover from '../../../popover/internal'; +import StatusIndicator from '../../../status-indicator/internal'; import { AppLayoutProps } from '../../interfaces'; import { OnChangeParams } from '../../utils/use-drawers'; import { Focusable, FocusControlMultipleStates } from '../../utils/use-focus-control'; @@ -68,6 +72,65 @@ export interface AppLayoutToolbarImplementationProps { toolbarProps: ToolbarProps; } +export const ToolbarSectionErrorBoundary: React.FC<{ id: string }> = ({ id, children }) => { + return ( + console.log('Error boundary for the trigger button: ', error)} + suppressNested={false} + suppressible={true} + renderFallback={props => { + return ( + + {props.description} + {props.action} + + } + > + + + ); + }} + > + {children} + + ); +}; + +export const LeftTriggerErrorBoundary: React.FC<{ id: string }> = ({ id, children }) => { + return ( + console.log('Error boundary for the trigger button: ', error)} + suppressNested={false} + suppressible={true} + renderFallback={props => { + return ( + + {props.description} + {props.action} + + } + > + + + ); + }} + > + {children} + + ); +}; + export function AppLayoutToolbarImplementation({ appLayoutInternals, // the value could be undefined if this component is loaded as a widget by a different app layout version @@ -171,26 +234,28 @@ export function AppLayoutToolbarImplementation({ opacity: ['entering', 'exiting'].includes(state) ? 0 : 1, }} > - { - if (setExpandedDrawerId) { - setExpandedDrawerId(null); - } - onActiveAiDrawerChange?.(aiDrawer?.id ?? null, { initiatedByUserAction: true }); - }} - ref={aiDrawerFocusRef} - selected={!drawerExpandedMode && !!activeAiDrawerId} - disabled={anyPanelOpenInMobile} - variant={aiDrawer?.trigger?.customIcon ? 'custom' : 'circle'} - testId={`awsui-app-layout-trigger-${aiDrawer?.id}`} - isForPreviousActiveDrawer={true} - /> + + { + if (setExpandedDrawerId) { + setExpandedDrawerId(null); + } + onActiveAiDrawerChange?.(aiDrawer?.id ?? null, { initiatedByUserAction: true }); + }} + ref={aiDrawerFocusRef} + selected={!drawerExpandedMode && !!activeAiDrawerId} + disabled={anyPanelOpenInMobile} + variant={aiDrawer?.trigger?.customIcon ? 'custom' : 'circle'} + testId={`awsui-app-layout-trigger-${aiDrawer?.id}`} + isForPreviousActiveDrawer={true} + /> + )} @@ -229,27 +294,29 @@ export function AppLayoutToolbarImplementation({ bottomDrawers?.length || (hasSplitPanel && splitPanelToggleProps?.displayed)) && (
- !!item.trigger) ?? []} - drawersFocusRef={drawersFocusRef} - onActiveDrawerChange={onActiveDrawerChange} - splitPanelToggleProps={splitPanelToggleProps?.displayed ? splitPanelToggleProps : undefined} - splitPanelFocusRef={splitPanelFocusRef} - onSplitPanelToggle={onSplitPanelToggle} - disabled={anyPanelOpenInMobile} - globalDrawersFocusControl={globalDrawersFocusControl} - bottomDrawersFocusRef={bottomDrawersFocusRef} - globalDrawers={globalDrawers?.filter(item => !!item.trigger) ?? []} - activeGlobalDrawersIds={activeGlobalDrawersIds ?? []} - onActiveGlobalDrawersChange={onActiveGlobalDrawersChange} - expandedDrawerId={expandedDrawerId} - setExpandedDrawerId={setExpandedDrawerId!} - bottomDrawers={bottomDrawers} - onActiveGlobalBottomDrawerChange={onActiveGlobalBottomDrawerChange} - activeGlobalBottomDrawerId={activeGlobalBottomDrawerId} - /> + + !!item.trigger) ?? []} + drawersFocusRef={drawersFocusRef} + onActiveDrawerChange={onActiveDrawerChange} + splitPanelToggleProps={splitPanelToggleProps?.displayed ? splitPanelToggleProps : undefined} + splitPanelFocusRef={splitPanelFocusRef} + onSplitPanelToggle={onSplitPanelToggle} + disabled={anyPanelOpenInMobile} + globalDrawersFocusControl={globalDrawersFocusControl} + bottomDrawersFocusRef={bottomDrawersFocusRef} + globalDrawers={globalDrawers?.filter(item => !!item.trigger) ?? []} + activeGlobalDrawersIds={activeGlobalDrawersIds ?? []} + onActiveGlobalDrawersChange={onActiveGlobalDrawersChange} + expandedDrawerId={expandedDrawerId} + setExpandedDrawerId={setExpandedDrawerId!} + bottomDrawers={bottomDrawers} + onActiveGlobalBottomDrawerChange={onActiveGlobalBottomDrawerChange} + activeGlobalBottomDrawerId={activeGlobalBottomDrawerId} + /> +
)} diff --git a/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss b/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss index 37ce2d8f90..182bc07312 100644 --- a/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss +++ b/src/app-layout/visual-refresh-toolbar/toolbar/styles.scss @@ -11,6 +11,13 @@ $toolbar-height: 42px; +.ai-trigger-fallback { + display: flex; + align-items: center; + block-size: 100%; + padding-inline-start: awsui.$space-m; +} + .universal-toolbar { background-color: awsui.$color-background-layout-panel-content; box-sizing: border-box; diff --git a/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx b/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx index b8325b7fcf..70c67f1535 100644 --- a/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx +++ b/src/app-layout/visual-refresh-toolbar/widget-areas/before-main-slot.tsx @@ -3,6 +3,7 @@ import React, { useRef } from 'react'; import clsx from 'clsx'; +import { InternalErrorBoundary } from '../../../error-boundary/internal'; import { createWidgetizedComponent } from '../../../internal/widgets'; import { ActiveDrawersContext } from '../../utils/visibility-context'; import { AppLayoutGlobalAiDrawerImplementation } from '../drawer/global-ai-drawer'; @@ -103,10 +104,16 @@ export const BeforeMainSlotImplementation = ({ toolbarProps, appLayoutState, app (drawerExpandedMode || drawerExpandedModeInChildLayout) && styles.hidden )} > - + console.log('Error boundary for the nav panel: ', error)} + suppressNested={false} + suppressible={true} + > + + )}