Skip to content

Commit cde680f

Browse files
authored
chore: Custom button for AI panel's exit expanded mode (#3940)
1 parent e06d3c4 commit cde680f

File tree

7 files changed

+112
-46
lines changed

7 files changed

+112
-46
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
{
177177
"path": "lib/components/internal/widget-exports.js",
178178
"brotli": false,
179-
"limit": "963 kB",
179+
"limit": "964 kB",
180180
"ignore": "react-dom"
181181
}
182182
],

pages/app-layout/utils/external-global-left-panel-widget.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ registerLeftDrawer({
7676
`,
7777
},
7878

79+
exitExpandedModeTrigger: {
80+
customIcon: `
81+
<svg width="94" height="24" viewBox="0 0 94 24" fill="none" focusable="false" aria-hidden="true">
82+
<rect width="94" height="24" rx="4" fill="url(#paint0_linear_145_32649)"/>
83+
<defs>
84+
<linearGradient id="paint0_linear_145_32649" x1="135.919" y1="21" x2="108.351" y2="74.1863" gradientUnits="userSpaceOnUse">
85+
<stop stop-color="#B8E7FF"/>
86+
<stop offset="0.255" stop-color="#0099FF"/>
87+
<stop offset="0.514134" stop-color="#5C7FFF"/>
88+
<stop offset="0.732534" stop-color="#8575FF"/>
89+
<stop offset="1" stop-color="#962EFF"/>
90+
</linearGradient>
91+
</defs>
92+
</svg>
93+
`,
94+
},
95+
7996
onResize: event => {
8097
console.log('resize', event.detail);
8198
},

src/app-layout/__tests__/runtime-drawers-widgetized.test.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -191,27 +191,40 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, ({ size }) => {
191191
}
192192
});
193193

194-
test('should exit focus mode by clicking on a custom exit button in the AI global drawer', () => {
195-
awsuiWidgetPlugins.registerLeftDrawer({
196-
...drawerDefaults,
197-
ariaLabels: {
198-
exitExpandedModeButton: 'exitExpandedModeButton',
199-
},
200-
isExpandable: true,
201-
});
202-
const { globalDrawersWrapper } = renderComponent(<AppLayout />);
203-
204-
globalDrawersWrapper.findAiDrawerTrigger()!.click();
205-
if (size === 'mobile') {
206-
expect(globalDrawersWrapper.findExpandedModeButtonByActiveDrawerId(drawerDefaults.id)).toBeFalsy();
207-
} else {
208-
createWrapper().findButtonGroup()!.findButtonById('expand')!.click();
209-
expect(globalDrawersWrapper.findDrawerById(drawerDefaults.id)!.isDrawerInExpandedMode()).toBe(true);
210-
expect(globalDrawersWrapper.isLayoutInDrawerExpandedMode()).toBe(true);
211-
globalDrawersWrapper.findLeaveExpandedModeButtonInAIDrawer()!.click();
212-
expect(globalDrawersWrapper.isLayoutInDrawerExpandedMode()).toBe(false);
194+
test.each(['standard', 'custom', 'custom-invalid'] as const)(
195+
'should exit focus mode by clicking on a %s exit button in the AI global drawer',
196+
type => {
197+
awsuiWidgetPlugins.registerLeftDrawer({
198+
...drawerDefaults,
199+
ariaLabels: {
200+
exitExpandedModeButton: 'exitExpandedModeButton',
201+
},
202+
isExpandable: true,
203+
...(type === 'custom' && {
204+
exitExpandedModeTrigger: {
205+
customIcon: `
206+
<svg width="94" height="24" viewBox="0 0 94 24" fill="none" focusable="false" aria-hidden="true"></svg>
207+
`,
208+
},
209+
}),
210+
...(type === 'custom-invalid' && {
211+
exitExpandedModeTrigger: {},
212+
}),
213+
});
214+
const { globalDrawersWrapper } = renderComponent(<AppLayout />);
215+
216+
globalDrawersWrapper.findAiDrawerTrigger()!.click();
217+
if (size === 'mobile') {
218+
expect(globalDrawersWrapper.findExpandedModeButtonByActiveDrawerId(drawerDefaults.id)).toBeFalsy();
219+
} else {
220+
createWrapper().findButtonGroup()!.findButtonById('expand')!.click();
221+
expect(globalDrawersWrapper.findDrawerById(drawerDefaults.id)!.isDrawerInExpandedMode()).toBe(true);
222+
expect(globalDrawersWrapper.isLayoutInDrawerExpandedMode()).toBe(true);
223+
globalDrawersWrapper.findLeaveExpandedModeButtonInAIDrawer()!.click();
224+
expect(globalDrawersWrapper.isLayoutInDrawerExpandedMode()).toBe(false);
225+
}
213226
}
214-
});
227+
);
215228

216229
describe('metrics', () => {
217230
let sendPanoramaMetricSpy: jest.SpyInstance;

src/app-layout/runtime-drawer/index.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ function checkForUnsupportedProps(headerActions: ReadonlyArray<ButtonGroupProps.
9999
return headerActions;
100100
}
101101

102+
const convertRuntimeTriggerToReactNode = (runtimeTrigger?: string) => {
103+
if (!runtimeTrigger) {
104+
return undefined;
105+
}
106+
// eslint-disable-next-line react/no-danger
107+
return <span style={{ lineHeight: 0 }} dangerouslySetInnerHTML={{ __html: runtimeTrigger }} />;
108+
};
109+
102110
export const mapRuntimeConfigToDrawer = (
103111
runtimeConfig: RuntimeDrawerConfig
104112
): AppLayoutProps.Drawer & {
@@ -114,10 +122,7 @@ export const mapRuntimeConfigToDrawer = (
114122
trigger: trigger
115123
? {
116124
...(trigger.iconSvg && {
117-
iconSvg: (
118-
// eslint-disable-next-line react/no-danger
119-
<span dangerouslySetInnerHTML={{ __html: trigger.iconSvg }} />
120-
),
125+
iconSvg: convertRuntimeTriggerToReactNode(trigger.iconSvg),
121126
}),
122127
}
123128
: undefined,
@@ -142,22 +147,22 @@ export const mapRuntimeConfigToAiDrawer = (
142147
orderPriority?: number;
143148
onToggle?: NonCancelableEventHandler<DrawerStateChangeParams>;
144149
headerActions?: ReadonlyArray<ButtonGroupProps.Item>;
150+
exitExpandedModeTrigger?: React.ReactNode;
145151
} => {
146-
const { mountContent, unmountContent, trigger, ...runtimeDrawer } = runtimeConfig;
152+
const { mountContent, unmountContent, trigger, exitExpandedModeTrigger, ...runtimeDrawer } = runtimeConfig;
147153

148154
return {
149155
...runtimeDrawer,
150156
ariaLabels: { drawerName: runtimeDrawer.ariaLabels.content ?? '', ...runtimeDrawer.ariaLabels },
151-
trigger: trigger
157+
...(trigger && {
158+
trigger: {
159+
customIcon: convertRuntimeTriggerToReactNode(trigger?.customIcon),
160+
iconSvg: convertRuntimeTriggerToReactNode(trigger?.iconSvg),
161+
},
162+
}),
163+
exitExpandedModeTrigger: exitExpandedModeTrigger
152164
? {
153-
customIcon: trigger?.customIcon ? (
154-
// eslint-disable-next-line react/no-danger
155-
<span style={{ lineHeight: 0 }} dangerouslySetInnerHTML={{ __html: trigger.customIcon }} />
156-
) : undefined,
157-
iconSvg: trigger.iconSvg ? (
158-
// eslint-disable-next-line react/no-danger
159-
<span dangerouslySetInnerHTML={{ __html: trigger.iconSvg }} />
160-
) : undefined,
165+
customIcon: convertRuntimeTriggerToReactNode(exitExpandedModeTrigger?.customIcon),
161166
}
162167
: undefined,
163168
content: (

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

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ interface AIDrawerProps {
3838
interface AppLayoutGlobalAiDrawerImplementationProps {
3939
appLayoutInternals: AppLayoutInternals;
4040
show: boolean;
41-
activeAiDrawer: InternalDrawer | null;
41+
activeAiDrawer:
42+
| (InternalDrawer & {
43+
exitExpandedModeTrigger?: {
44+
customIcon?: React.ReactNode;
45+
};
46+
})
47+
| null;
4248
aiDrawerProps: AIDrawerProps;
4349
}
4450

@@ -207,16 +213,31 @@ export function AppLayoutGlobalAiDrawerImplementation({
207213
{!isMobile && isExpanded && activeAiDrawer?.ariaLabels?.exitExpandedModeButton && (
208214
<div className={styles['drawer-back-to-console-slot']}>
209215
<div className={styles['drawer-back-to-console-button-wrapper']}>
210-
<button
211-
className={clsx(
212-
testutilStyles['active-ai-drawer-leave-expanded-mode-custom-button'],
213-
styles['drawer-back-to-console-button']
214-
)}
215-
formAction="none"
216-
onClick={() => setExpandedDrawerId(null)}
217-
>
218-
{activeAiDrawer?.ariaLabels?.exitExpandedModeButton}
219-
</button>
216+
{activeAiDrawer?.exitExpandedModeTrigger?.customIcon ? (
217+
<button
218+
className={clsx(
219+
testutilStyles['active-ai-drawer-leave-expanded-mode-custom-button'],
220+
styles['drawer-back-to-console-custom-button']
221+
)}
222+
formAction="none"
223+
onClick={() => setExpandedDrawerId(null)}
224+
aria-label={activeAiDrawer?.ariaLabels?.exitExpandedModeButton}
225+
>
226+
{activeAiDrawer?.exitExpandedModeTrigger?.customIcon}
227+
</button>
228+
) : (
229+
<button
230+
className={clsx(
231+
testutilStyles['active-ai-drawer-leave-expanded-mode-custom-button'],
232+
styles['drawer-back-to-console-button']
233+
)}
234+
formAction="none"
235+
onClick={() => setExpandedDrawerId(null)}
236+
aria-label={activeAiDrawer?.ariaLabels?.exitExpandedModeButton}
237+
>
238+
{activeAiDrawer?.ariaLabels?.exitExpandedModeButton}
239+
</button>
240+
)}
220241
</div>
221242
</div>
222243
)}

src/app-layout/visual-refresh-toolbar/drawer/styles.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,13 @@ $ai-drawer-heider-height: 41px;
381381
outline: none;
382382
}
383383
}
384+
385+
> .drawer-back-to-console-custom-button {
386+
all: initial;
387+
display: flex;
388+
cursor: pointer;
389+
text-align: center;
390+
}
384391
}
385392
}
386393
}

src/internal/plugins/widget/interfaces.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export interface DrawerPayload {
3939
iconSvg?: string;
4040
customIcon?: string;
4141
};
42+
exitExpandedModeTrigger?: {
43+
customIcon?: string;
44+
};
4245
mountContent: (container: HTMLElement, mountContext: MountContentContext) => void;
4346
unmountContent: (container: HTMLElement) => void;
4447
preserveInactiveContent?: boolean;

0 commit comments

Comments
 (0)