|
1 |
| -import React from 'react'; |
| 1 | +import React, { useEffect, useMemo, useRef, useState } from 'react'; |
2 | 2 | import { storybookExcludedControlParams } from '@lg-tools/storybook-utils';
|
3 | 3 | import { StoryFn, StoryObj } from '@storybook/react';
|
4 | 4 | import { expect, userEvent, waitFor, within } from '@storybook/test';
|
5 | 5 |
|
6 | 6 | import Button from '@leafygreen-ui/button';
|
7 | 7 | import { css } from '@leafygreen-ui/emotion';
|
| 8 | +import { GuideCue } from '@leafygreen-ui/guide-cue'; |
| 9 | +import { usePrevious } from '@leafygreen-ui/hooks'; |
8 | 10 | import { palette } from '@leafygreen-ui/palette';
|
9 | 11 | import { spacing } from '@leafygreen-ui/tokens';
|
10 | 12 |
|
11 | 13 | import { DisplayMode, Drawer } from '../../Drawer';
|
12 |
| -import { DrawerLayoutProvider } from '../../DrawerLayout'; |
| 14 | +import { |
| 15 | + DrawerLayout, |
| 16 | + DrawerLayoutProps, |
| 17 | + DrawerLayoutProvider, |
| 18 | +} from '../../DrawerLayout'; |
13 | 19 | import { getTestUtils } from '../../testing';
|
14 | 20 | import { useDrawerToolbarContext } from '../DrawerToolbarContext/DrawerToolbarContext';
|
15 | 21 |
|
@@ -535,3 +541,139 @@ export const EmbeddedClosesDrawerWhenActiveItemIsRemovedFromToolbarData: StoryOb
|
535 | 541 | },
|
536 | 542 | play: playClosesDrawerWhenActiveItemIsRemovedFromToolbarData,
|
537 | 543 | };
|
| 544 | + |
| 545 | +interface MainContentProps { |
| 546 | + dashboardButtonRef: React.RefObject<HTMLButtonElement>; |
| 547 | + guideCueOpen: boolean; |
| 548 | + setGuideCueOpen: React.Dispatch<React.SetStateAction<boolean>>; |
| 549 | +} |
| 550 | + |
| 551 | +const MainContent: React.FC<MainContentProps> = ({ |
| 552 | + dashboardButtonRef, |
| 553 | + guideCueOpen, |
| 554 | + setGuideCueOpen, |
| 555 | +}) => { |
| 556 | + const { isDrawerOpen } = useDrawerToolbarContext(); |
| 557 | + const prevIsDrawerOpen = usePrevious(isDrawerOpen); |
| 558 | + |
| 559 | + // Close GuideCue immediately when drawer begins transitioning (state change) |
| 560 | + useEffect(() => { |
| 561 | + if (prevIsDrawerOpen !== undefined && prevIsDrawerOpen !== isDrawerOpen) { |
| 562 | + // Close guide cue immediately when drawer transition begins |
| 563 | + if (guideCueOpen) { |
| 564 | + setGuideCueOpen(false); |
| 565 | + } |
| 566 | + } |
| 567 | + }, [isDrawerOpen, prevIsDrawerOpen, guideCueOpen, setGuideCueOpen]); |
| 568 | + |
| 569 | + return ( |
| 570 | + <main |
| 571 | + className={css` |
| 572 | + padding: ${spacing[400]}px; |
| 573 | + `} |
| 574 | + > |
| 575 | + <div |
| 576 | + className={css` |
| 577 | + display: flex; |
| 578 | + flex-direction: column; |
| 579 | + align-items: flex-start; |
| 580 | + gap: ${spacing[200]}px; |
| 581 | + `} |
| 582 | + > |
| 583 | + <Button onClick={() => setGuideCueOpen(true)}>Show GuideCue</Button> |
| 584 | + <p> |
| 585 | + This example demonstrates how to use refs in toolbarData to attach a |
| 586 | + GuideCue to a toolbar icon button. The button tooltip is automatically |
| 587 | + disabled while the GuideCue is visible to prevent conflicts between |
| 588 | + the two overlays. |
| 589 | + </p> |
| 590 | + </div> |
| 591 | + <LongContent /> |
| 592 | + <GuideCue |
| 593 | + open={guideCueOpen} |
| 594 | + setOpen={setGuideCueOpen} |
| 595 | + title="Dashboard Feature" |
| 596 | + refEl={dashboardButtonRef} |
| 597 | + numberOfSteps={1} |
| 598 | + onPrimaryButtonClick={() => setGuideCueOpen(false)} |
| 599 | + tooltipAlign="left" |
| 600 | + tooltipJustify="start" |
| 601 | + > |
| 602 | + Click here to access your dashboard with analytics and insights! |
| 603 | + </GuideCue> |
| 604 | + </main> |
| 605 | + ); |
| 606 | +}; |
| 607 | + |
| 608 | +const WithGuideCueComponent: StoryFn<DrawerLayoutProps> = ({ |
| 609 | + displayMode, |
| 610 | +}: DrawerLayoutProps) => { |
| 611 | + const dashboardButtonRef = useRef<HTMLButtonElement>(null); |
| 612 | + const [guideCueOpen, setGuideCueOpen] = useState(false); |
| 613 | + |
| 614 | + // Use useMemo to make toolbar data reactive to guideCueOpen state changes |
| 615 | + const DRAWER_TOOLBAR_DATA: DrawerLayoutProps['toolbarData'] = useMemo( |
| 616 | + () => [ |
| 617 | + { |
| 618 | + id: 'Code', |
| 619 | + label: 'Code', |
| 620 | + content: <LongContent />, |
| 621 | + title: 'Code', |
| 622 | + glyph: 'Code', |
| 623 | + }, |
| 624 | + { |
| 625 | + id: 'Dashboard', |
| 626 | + label: 'Dashboard', |
| 627 | + content: <LongContent />, |
| 628 | + title: 'Dashboard', |
| 629 | + glyph: 'Dashboard', |
| 630 | + ref: dashboardButtonRef, // This ref is passed to the ToolbarIconButton |
| 631 | + isTooltipEnabled: !guideCueOpen, // Disable tooltip when guide cue is open |
| 632 | + }, |
| 633 | + { |
| 634 | + id: 'Apps', |
| 635 | + label: 'Apps', |
| 636 | + content: <LongContent />, |
| 637 | + title: 'Apps', |
| 638 | + glyph: 'Apps', |
| 639 | + }, |
| 640 | + ], |
| 641 | + [guideCueOpen], |
| 642 | + ); |
| 643 | + |
| 644 | + return ( |
| 645 | + <div |
| 646 | + className={css` |
| 647 | + height: 90vh; |
| 648 | + width: 100%; |
| 649 | + `} |
| 650 | + > |
| 651 | + <DrawerLayout displayMode={displayMode} toolbarData={DRAWER_TOOLBAR_DATA}> |
| 652 | + <MainContent |
| 653 | + dashboardButtonRef={dashboardButtonRef} |
| 654 | + guideCueOpen={guideCueOpen} |
| 655 | + setGuideCueOpen={setGuideCueOpen} |
| 656 | + /> |
| 657 | + </DrawerLayout> |
| 658 | + </div> |
| 659 | + ); |
| 660 | +}; |
| 661 | + |
| 662 | +export const WithGuideCue: StoryObj<DrawerLayoutProps> = { |
| 663 | + render: WithGuideCueComponent, |
| 664 | + args: { |
| 665 | + displayMode: DisplayMode.Overlay, |
| 666 | + }, |
| 667 | + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { |
| 668 | + const canvas = within(canvasElement); |
| 669 | + const guideCueButton = await canvas.getByRole('button', { |
| 670 | + name: 'Show GuideCue', |
| 671 | + }); |
| 672 | + await userEvent.click(guideCueButton); |
| 673 | + }, |
| 674 | + parameters: { |
| 675 | + chromatic: { |
| 676 | + delay: 300, |
| 677 | + }, |
| 678 | + }, |
| 679 | +}; |
0 commit comments