From 54b5ebb2b8d612a5c490efbd2956dbccaee7c3c7 Mon Sep 17 00:00:00 2001 From: Georgii Lobko Date: Mon, 14 Jul 2025 12:24:55 +0200 Subject: [PATCH 01/24] feat: Init Tela drawer --- pages/app-layout/runtime-drawers.page.tsx | 26 ++++++++++++++++ src/app-layout/runtime-drawer/index.tsx | 7 ++++- src/app-layout/utils/use-drawers.ts | 26 ++++++++++++++-- .../visual-refresh-toolbar/index.tsx | 9 ++++++ .../visual-refresh-toolbar/interfaces.ts | 3 ++ .../visual-refresh-toolbar/multi-layout.ts | 8 +++++ .../visual-refresh-toolbar/toolbar/index.tsx | 30 +++++++++++++++++++ .../toolbar/styles.scss | 13 +++++--- src/internal/plugins/controllers/drawers.ts | 2 +- 9 files changed, 115 insertions(+), 9 deletions(-) diff --git a/pages/app-layout/runtime-drawers.page.tsx b/pages/app-layout/runtime-drawers.page.tsx index 68706db78c..dc813d9e28 100644 --- a/pages/app-layout/runtime-drawers.page.tsx +++ b/pages/app-layout/runtime-drawers.page.tsx @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React, { useContext, useRef, useState } from 'react'; +import ReactDOM, { unmountComponentAtNode } from 'react-dom'; import { AppLayout, @@ -31,6 +32,31 @@ type DemoContext = React.Context< }> >; +awsuiPlugins.appLayout.registerDrawer({ + id: 'amazon-q', + type: 'global-ai', + resizable: true, + + ariaLabels: { + closeButton: 'Close button', + content: 'Content', + triggerButton: 'Trigger button', + resizeHandle: 'Resize handle', + }, + + trigger: { + iconSvg: ` + + + `, + }, + + mountContent: container => { + ReactDOM.render(
Tela
, container); + }, + unmountContent: container => unmountComponentAtNode(container), +}); + export default function WithDrawers() { const [activeDrawerId, setActiveDrawerId] = useState(null); const [helpPathSlug, setHelpPathSlug] = useState('default'); diff --git a/src/app-layout/runtime-drawer/index.tsx b/src/app-layout/runtime-drawer/index.tsx index 14004148a6..a1657a88c0 100644 --- a/src/app-layout/runtime-drawer/index.tsx +++ b/src/app-layout/runtime-drawer/index.tsx @@ -21,6 +21,7 @@ export interface DrawersLayout { global: Array; localBefore: Array; localAfter: Array; + aiDrawer?: RuntimeDrawer; } type VisibilityCallback = (isVisible: boolean) => void; @@ -93,7 +94,8 @@ const mapRuntimeConfigToDrawer = ( export function convertRuntimeDrawers( localDrawers: Array, - globalDrawers: Array + globalDrawers: Array, + aiDrawer?: RuntimeDrawerConfig ): DrawersLayout { const converted = localDrawers.map(mapRuntimeConfigToDrawer); const sorted = sortByPriority(converted); @@ -101,5 +103,8 @@ export function convertRuntimeDrawers( global: sortByPriority(globalDrawers.map(mapRuntimeConfigToDrawer)), localBefore: sorted.filter(item => (item.orderPriority ?? 0) > 0), localAfter: sorted.filter(item => (item.orderPriority ?? 0) <= 0), + ...(aiDrawer && { + aiDrawer: mapRuntimeConfigToDrawer(aiDrawer), + }), }; } diff --git a/src/app-layout/utils/use-drawers.ts b/src/app-layout/utils/use-drawers.ts index d5201cf6a8..3cbe22b453 100644 --- a/src/app-layout/utils/use-drawers.ts +++ b/src/app-layout/utils/use-drawers.ts @@ -79,9 +79,10 @@ function useRuntimeDrawers( return; } const unsubscribe = awsuiPluginsInternal.appLayout.onDrawersRegistered(drawers => { - const localDrawers = drawers.filter(drawer => drawer.type !== 'global'); + const localDrawers = drawers.filter(drawer => !drawer.type?.includes('global')); const globalDrawers = drawers.filter(drawer => drawer.type === 'global'); - setRuntimeDrawers(convertRuntimeDrawers(localDrawers, globalDrawers)); + const aiDrawer = drawers.find(drawer => drawer.type === 'global-ai'); + setRuntimeDrawers(convertRuntimeDrawers(localDrawers, globalDrawers, aiDrawer)); if (!localDrawerWasOpenRef.current) { const defaultActiveLocalDrawer = sortByPriority(localDrawers).find(drawer => drawer.defaultActive); if (defaultActiveLocalDrawer) { @@ -198,6 +199,7 @@ export function useDrawers( changeHandler: 'onChange', }); const [activeGlobalDrawersIds, setActiveGlobalDrawersIds] = useState>([]); + const [activeAiDrawer, setActiveAiDrawer] = useState(null); const [drawerSizes, setDrawerSizes] = useState>({}); const [expandedDrawerId, setExpandedDrawerId] = useState(null); // FIFO queue that keeps track of open drawers, where the first element is the most recently opened drawer @@ -210,6 +212,21 @@ export function useDrawers( fireNonCancelableEvent(activeGlobalDrawer?.onResize, { id, size }); } + function onActiveAiDrawerChange( + newDrawerId: string | null, + { initiatedByUserAction }: OnChangeParams = DEFAULT_ON_CHANGE_PARAMS + ) { + setActiveAiDrawer(newDrawerId); + + if (newDrawerId) { + fireNonCancelableEvent(runtimeDrawers?.aiDrawer?.onToggle, { isOpen: true, initiatedByUserAction }); + } + + if (activeDrawerId) { + fireNonCancelableEvent(runtimeDrawers?.aiDrawer?.onToggle, { isOpen: false, initiatedByUserAction }); + } + } + function onActiveDrawerChange( newDrawerId: string | null, { initiatedByUserAction }: OnChangeParams = DEFAULT_ON_CHANGE_PARAMS @@ -278,7 +295,7 @@ export function useDrawers( activeGlobalDrawersIds, onActiveGlobalDrawersChange ); - const { localBefore, localAfter, global: runtimeGlobalDrawers } = runtimeDrawers; + const { localBefore, localAfter, global: runtimeGlobalDrawers, aiDrawer } = runtimeDrawers; const combinedLocalDrawers = drawers ? [...localBefore, ...drawers, ...localAfter] : applyToolsDrawer(toolsProps, runtimeDrawers); @@ -341,5 +358,8 @@ export function useDrawers( onActiveGlobalDrawersChange, expandedDrawerId, setExpandedDrawerId, + aiDrawer, + onActiveAiDrawerChange, + activeAiDrawer, }; } diff --git a/src/app-layout/visual-refresh-toolbar/index.tsx b/src/app-layout/visual-refresh-toolbar/index.tsx index b78bfa65b0..b6d7038b69 100644 --- a/src/app-layout/visual-refresh-toolbar/index.tsx +++ b/src/app-layout/visual-refresh-toolbar/index.tsx @@ -151,6 +151,9 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef void; + aiDrawer?: InternalDrawer; + onActiveAiDrawerChange: (value: string | null) => void; + activeAiDrawer: string | null; } diff --git a/src/app-layout/visual-refresh-toolbar/multi-layout.ts b/src/app-layout/visual-refresh-toolbar/multi-layout.ts index 53e125765b..7e05133e33 100644 --- a/src/app-layout/visual-refresh-toolbar/multi-layout.ts +++ b/src/app-layout/visual-refresh-toolbar/multi-layout.ts @@ -34,6 +34,9 @@ export interface SharedProps { onSplitPanelToggle: () => void; expandedDrawerId?: string | null; setExpandedDrawerId: (value: string | null) => void; + aiDrawer: AppLayoutProps.Drawer | undefined; + onActiveAiDrawerChange: (value: string | null) => void; + activeAiDrawer?: string | null; } function checkAlreadyExists(value: boolean, propName: string) { @@ -70,6 +73,11 @@ export function mergeProps( toolbar.activeGlobalDrawersIds = props.activeGlobalDrawersIds; toolbar.onActiveGlobalDrawersChange = props.onActiveGlobalDrawersChange; } + if (props.aiDrawer && !checkAlreadyExists(!!toolbar.aiDrawer, 'aiDrawer')) { + toolbar.aiDrawer = props.aiDrawer; + toolbar.onActiveAiDrawerChange = props.onActiveAiDrawerChange; + toolbar.activeAiDrawer = props.activeAiDrawer; + } if (props.navigation && !checkAlreadyExists(!!toolbar.hasNavigation, 'navigation')) { toolbar.hasNavigation = true; toolbar.navigationOpen = props.navigationOpen; diff --git a/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx b/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx index c8dc199153..b4b8207982 100644 --- a/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx +++ b/src/app-layout/visual-refresh-toolbar/toolbar/index.tsx @@ -50,6 +50,10 @@ export interface ToolbarProps { expandedDrawerId?: string | null; setExpandedDrawerId?: (value: string | null) => void; + + aiDrawer?: AppLayoutProps.Drawer; + onActiveAiDrawerChange?: (value: string | null) => void; + activeAiDrawer?: string | null; } export interface AppLayoutToolbarImplementationProps { @@ -84,6 +88,9 @@ export function AppLayoutToolbarImplementation({ onSplitPanelToggle, expandedDrawerId, setExpandedDrawerId, + aiDrawer, + activeAiDrawer, + onActiveAiDrawerChange, } = toolbarProps; const drawerExpandedMode = !!expandedDrawerId; const ref = useRef(null); @@ -123,6 +130,29 @@ export function AppLayoutToolbarImplementation({ }} >
+ {aiDrawer && !activeAiDrawer && ( +
+ { + if (setExpandedDrawerId) { + setExpandedDrawerId(null); + } + if (navigationOpen && activeAiDrawer) { + return; + } + onActiveAiDrawerChange?.(aiDrawer?.id); + }} + // ref={navigationFocusRef} + selected={!drawerExpandedMode && !!activeAiDrawer} + disabled={anyPanelOpenInMobile} + /> +
+
+ )} {hasNavigation && (