Skip to content

Commit 3a4a23a

Browse files
committed
WIP
1 parent 73d25ee commit 3a4a23a

File tree

10 files changed

+519
-42
lines changed

10 files changed

+519
-42
lines changed

packages/drawer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"devDependencies": {
4949
"@faker-js/faker": "^8.0.2",
5050
"@storybook/test": "8.5.3",
51+
"@leafygreen-ui/guide-cue": "workspace:^",
5152
"@lg-tools/build": "workspace:^"
5253
},
5354
"peerDependencies": {

packages/drawer/src/DrawerToolbarLayout/DrawerToolbarLayout/DrawerToolbarLayout.spec.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,38 @@ describe('packages/DrawerToolbarLayout', () => {
259259

260260
expect(isOpen()).toBe(false);
261261
});
262+
263+
test('passes ref correctly to ToolbarIconButton instances', () => {
264+
const codeButtonRef = React.createRef<HTMLButtonElement>();
265+
const code2ButtonRef = React.createRef<HTMLButtonElement>();
266+
267+
const dataWithRefs: DrawerLayoutProps['toolbarData'] = [
268+
{
269+
id: 'Code',
270+
label: 'Code',
271+
content: 'Drawer Content',
272+
title: `Drawer Title`,
273+
glyph: 'Code',
274+
ref: codeButtonRef,
275+
},
276+
{
277+
id: 'Code2',
278+
label: 'Code2',
279+
content: 'Drawer Content2',
280+
title: `Drawer Title2`,
281+
glyph: 'Code',
282+
ref: code2ButtonRef,
283+
},
284+
];
285+
286+
render(<Component data={dataWithRefs} />);
287+
288+
// Verify that refs are properly assigned to DOM elements
289+
expect(codeButtonRef.current).toBeInstanceOf(HTMLButtonElement);
290+
expect(code2ButtonRef.current).toBeInstanceOf(HTMLButtonElement);
291+
292+
// Verify the elements have the correct labels
293+
expect(codeButtonRef.current).toHaveAttribute('aria-label', 'Code');
294+
expect(code2ButtonRef.current).toHaveAttribute('aria-label', 'Code2');
295+
});
262296
});

packages/drawer/src/DrawerToolbarLayout/DrawerToolbarLayout/DrawerToolbarLayout.stories.tsx

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useMemo, useRef, useState } from 'react';
22
import {
33
storybookArgTypes,
44
storybookExcludedControlParams,
@@ -8,6 +8,8 @@ import { StoryFn, StoryObj } from '@storybook/react';
88

99
import Button from '@leafygreen-ui/button';
1010
import { css } from '@leafygreen-ui/emotion';
11+
import { GuideCue } from '@leafygreen-ui/guide-cue';
12+
import { usePrevious } from '@leafygreen-ui/hooks';
1113
import { palette } from '@leafygreen-ui/palette';
1214
import { spacing } from '@leafygreen-ui/tokens';
1315

@@ -444,3 +446,131 @@ export const EmbeddedDynamic: StoryObj<DrawerLayoutProps> = {
444446
},
445447
},
446448
};
449+
450+
interface MainContentProps {
451+
setGuideCueKey: React.Dispatch<React.SetStateAction<number>>;
452+
setGuideCueOpen: (open: boolean) => void;
453+
}
454+
455+
const MainContent: React.FC<MainContentProps> = ({
456+
setGuideCueKey,
457+
setGuideCueOpen,
458+
}) => {
459+
const { isDrawerOpen } = useDrawerToolbarContext();
460+
const prevIsDrawerOpen = usePrevious(isDrawerOpen);
461+
462+
// Force GuideCue rerender when drawer state changes for immediate repositioning
463+
useEffect(() => {
464+
if (prevIsDrawerOpen !== undefined && prevIsDrawerOpen !== isDrawerOpen) {
465+
setGuideCueKey((prev: number) => prev + 1);
466+
}
467+
}, [isDrawerOpen, prevIsDrawerOpen, setGuideCueKey]);
468+
469+
return (
470+
<main
471+
className={css`
472+
padding: ${spacing[400]}px;
473+
`}
474+
>
475+
<div
476+
className={css`
477+
display: flex;
478+
flex-direction: column;
479+
align-items: flex-start;
480+
gap: ${spacing[200]}px;
481+
`}
482+
>
483+
<Button onClick={() => setGuideCueOpen(true)}>
484+
Show GuideCue on Dashboard Button
485+
</Button>
486+
<p>
487+
This example demonstrates how to use refs in toolbarData to attach a
488+
GuideCue to a toolbar icon button. The button tooltip is
489+
automatically disabled while the GuideCue is visible to prevent
490+
conflicts between the two overlays.
491+
</p>
492+
</div>
493+
<LongContent />
494+
</main>
495+
);
496+
};
497+
498+
const WithGuideCueComponent: StoryFn<DrawerLayoutProps> = ({
499+
displayMode,
500+
}: DrawerLayoutProps) => {
501+
const dashboardButtonRef = useRef<HTMLButtonElement>(null);
502+
const [guideCueOpen, setGuideCueOpen] = useState(false);
503+
const [guideCueKey, setGuideCueKey] = useState(0);
504+
505+
// Use useMemo to make toolbar data reactive to guideCueOpen state changes
506+
const DRAWER_TOOLBAR_DATA: DrawerLayoutProps['toolbarData'] = useMemo(
507+
() => [
508+
{
509+
id: 'Code',
510+
label: 'Code',
511+
content: <LongContent />,
512+
title: 'Code',
513+
glyph: 'Code',
514+
},
515+
{
516+
id: 'Dashboard',
517+
label: 'Dashboard',
518+
content: <LongContent />,
519+
title: 'Dashboard',
520+
glyph: 'Dashboard',
521+
ref: dashboardButtonRef, // This ref is passed to the ToolbarIconButton
522+
tooltipEnabled: !guideCueOpen, // Disable tooltip when guide cue is open
523+
},
524+
{
525+
id: 'Apps',
526+
label: 'Apps',
527+
content: <LongContent />,
528+
title: 'Apps',
529+
glyph: 'Apps',
530+
},
531+
],
532+
[guideCueOpen],
533+
); // Re-compute when guideCueOpen changes
534+
535+
return (
536+
<div
537+
className={css`
538+
height: 90vh;
539+
width: 100%;
540+
`}
541+
>
542+
<DrawerLayout displayMode={displayMode} toolbarData={DRAWER_TOOLBAR_DATA}>
543+
<MainContent
544+
setGuideCueKey={setGuideCueKey}
545+
setGuideCueOpen={setGuideCueOpen}
546+
/>
547+
</DrawerLayout>
548+
549+
<GuideCue
550+
key={`guide-cue-${guideCueKey}`} // Force rerender on drawer state changes
551+
open={guideCueOpen} // Always show when toggled
552+
setOpen={setGuideCueOpen}
553+
title="Dashboard Feature"
554+
refEl={dashboardButtonRef}
555+
numberOfSteps={1}
556+
onPrimaryButtonClick={() => setGuideCueOpen(false)}
557+
tooltipAlign="left"
558+
tooltipJustify="start"
559+
>
560+
Click here to access your dashboard with analytics and insights!
561+
</GuideCue>
562+
</div>
563+
);
564+
};
565+
566+
export const WithGuideCue: StoryObj<DrawerLayoutProps> = {
567+
render: WithGuideCueComponent,
568+
args: {
569+
displayMode: DisplayMode.Overlay,
570+
},
571+
parameters: {
572+
controls: {
573+
exclude: toolbarExcludedControls,
574+
},
575+
},
576+
};

packages/drawer/src/DrawerToolbarLayout/DrawerToolbarLayout/DrawerToolbarLayout.types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ToolbarIconButtonProps } from '@leafygreen-ui/toolbar';
55

66
type PickedRequiredToolbarIconButtonProps = Pick<
77
ToolbarIconButtonProps,
8-
'glyph' | 'label' | 'onClick' | 'disabled'
8+
'glyph' | 'label' | 'onClick' | 'disabled' | 'tooltipEnabled'
99
>;
1010

1111
interface LayoutBase extends PickedRequiredToolbarIconButtonProps {
@@ -19,6 +19,11 @@ interface LayoutBase extends PickedRequiredToolbarIconButtonProps {
1919
* @defaultValue true
2020
*/
2121
visible?: boolean;
22+
23+
/**
24+
* Optional ref to be passed to the ToolbarIconButton instance.
25+
*/
26+
ref?: React.RefObject<HTMLButtonElement>;
2227
}
2328

2429
interface LayoutWithContent extends LayoutBase {

packages/drawer/src/DrawerToolbarLayout/DrawerToolbarLayout/DrawerToolbarLayoutContent.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ export const DrawerToolbarLayoutContent = forwardRef<
120120
}}
121121
active={toolbarItem.id === id}
122122
disabled={toolbarItem.disabled}
123+
ref={toolbarItem.ref}
124+
tooltipEnabled={toolbarItem.tooltipEnabled}
123125
/>
124126
))}
125127
</Toolbar>

packages/drawer/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
{
2424
"path": "../emotion"
2525
},
26+
{
27+
"path": "../guide-cue"
28+
},
2629
{
2730
"path": "../hooks"
2831
},

packages/toolbar/src/ToolbarIconButton/ToolbarIconButton.spec.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { createRef } from 'react';
22
import { render } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
34
import { axe } from 'jest-axe';
45

56
import { Toolbar } from '../Toolbar';
@@ -35,6 +36,48 @@ describe('packages/toolbar-icon-button', () => {
3536
expect(ref.current).toBeDefined();
3637
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
3738
});
39+
40+
test('shows tooltip on hover when tooltipEnabled is true', async () => {
41+
const { getByRole, findByRole } = render(
42+
<Toolbar>
43+
<ToolbarIconButton
44+
glyph="Code"
45+
label="Code Tooltip"
46+
tooltipEnabled={true}
47+
/>
48+
</Toolbar>,
49+
);
50+
51+
const button = getByRole('button');
52+
userEvent.hover(button);
53+
54+
// Tooltip should appear
55+
const tooltip = await findByRole('tooltip');
56+
expect(tooltip).toBeInTheDocument();
57+
expect(tooltip).toHaveTextContent('Code Tooltip');
58+
});
59+
60+
test('does not show tooltip on hover when tooltipEnabled is false', async () => {
61+
const { getByRole, queryByRole } = render(
62+
<Toolbar>
63+
<ToolbarIconButton
64+
glyph="Code"
65+
label="Code Tooltip"
66+
tooltipEnabled={false}
67+
/>
68+
</Toolbar>,
69+
);
70+
71+
const button = getByRole('button');
72+
userEvent.hover(button);
73+
74+
// Wait a bit to ensure tooltip would have appeared if enabled
75+
await new Promise(resolve => setTimeout(resolve, 100));
76+
77+
// Tooltip should not appear
78+
const tooltip = queryByRole('tooltip');
79+
expect(tooltip).not.toBeInTheDocument();
80+
});
3881
});
3982

4083
/* eslint-disable jest/no-disabled-tests */

packages/toolbar/src/ToolbarIconButton/ToolbarIconButton.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const ToolbarIconButton = React.forwardRef<
2424
glyph,
2525
disabled = false,
2626
active = false,
27+
tooltipEnabled = true,
2728
'aria-label': ariaLabel,
2829
...rest
2930
}: ToolbarIconButtonProps,
@@ -60,6 +61,7 @@ export const ToolbarIconButton = React.forwardRef<
6061
data-testid={`${lgIds.iconButtonTooltip}-${index}`}
6162
data-lgid={`${lgIds.iconButtonTooltip}-${index}`}
6263
align={Align.Left}
64+
enabled={tooltipEnabled}
6365
trigger={
6466
<div className={triggerStyles}>
6567
<IconButton

packages/toolbar/src/ToolbarIconButton/ToolbarIconButton.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,11 @@ export interface ToolbarIconButtonProps extends ButtonProps {
2828
* Callback fired when the ToolbarIconButton is clicked
2929
*/
3030
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
31+
32+
/**
33+
* Enables the tooltip to trigger based on hover events.
34+
* When false, the tooltip will not show on hover.
35+
* @default true
36+
*/
37+
tooltipEnabled?: boolean;
3138
}

0 commit comments

Comments
 (0)