From 0d0e72ea74ce212c366c5be57b3633bbccfa318a Mon Sep 17 00:00:00 2001 From: Anurag Hazra Date: Tue, 28 Nov 2023 17:04:03 +0530 Subject: [PATCH] fix: SpotlightPopoverTour bugs (#1826) --- .changeset/three-glasses-complain.md | 9 + packages/blade/.storybook/react/global.css | 5 + packages/blade/docs/guides/Usage.stories.mdx | 14 + .../BottomSheet/BottomSheet.stories.tsx | 60 ++++ .../SpotlightPopoverTour/Tour.web.tsx | 63 ++-- .../SpotlightPopoverTour/TourPopover.web.tsx | 4 +- ...tchenSink.SpotlightPopoverTour.stories.tsx | 8 +- .../docs/Tour.stories.mdx | 6 + .../{ => docs}/Tour.stories.tsx | 120 +++---- .../docs/TourDocs.stories.tsx | 293 ++++++++++++++++++ .../SpotlightPopoverTour/docs/examples.ts | 129 ++++++++ .../components/SpotlightPopoverTour/utils.ts | 16 +- .../blade/src/utils/storybook/ScrollLink.tsx | 12 +- .../src/utils/storybook/StoryPageWrapper.tsx | 23 +- 14 files changed, 651 insertions(+), 111 deletions(-) create mode 100644 .changeset/three-glasses-complain.md create mode 100644 packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.mdx rename packages/blade/src/components/SpotlightPopoverTour/{ => docs}/Tour.stories.tsx (92%) create mode 100644 packages/blade/src/components/SpotlightPopoverTour/docs/TourDocs.stories.tsx create mode 100644 packages/blade/src/components/SpotlightPopoverTour/docs/examples.ts diff --git a/.changeset/three-glasses-complain.md b/.changeset/three-glasses-complain.md new file mode 100644 index 00000000000..547a3c2cf4f --- /dev/null +++ b/.changeset/three-glasses-complain.md @@ -0,0 +1,9 @@ +--- +"@razorpay/blade": patch +--- + +fix(blade): fixed SpotlightPopoverTour bugs + +- Safari body-scroll-lock causing the page to get clipped because storybook doesn't set width/height on body - fixed by setting width/height +- Initial delay of opening the mask - fixed by immediately updating the mask size on initial render +- Delay of transitioning between steps which occurs because we need to wait for the animation to finish before scrolling otherwise the scroll gets interrupted - fixed by reduced this to 100ms diff --git a/packages/blade/.storybook/react/global.css b/packages/blade/.storybook/react/global.css index 756dda27b3a..238ccbd78bf 100644 --- a/packages/blade/.storybook/react/global.css +++ b/packages/blade/.storybook/react/global.css @@ -2,3 +2,8 @@ /* Need this to ensure mdx stories don't break visually */ box-sizing: border-box; } + +body { + width: 100%; + height: 100%; +} diff --git a/packages/blade/docs/guides/Usage.stories.mdx b/packages/blade/docs/guides/Usage.stories.mdx index f92822d1323..5f13ce6fbb5 100644 --- a/packages/blade/docs/guides/Usage.stories.mdx +++ b/packages/blade/docs/guides/Usage.stories.mdx @@ -45,6 +45,20 @@ function AppWrapper(): JSX.Element { export default AppWrapper; ``` +## iOS Safari Specific Setup + +When using BottomSheet or SpotlightPopoverTour, +Make sure to set a width/height to the `body` otherwise when they open, the page will get clipped. + +This happens due to a bug in iOS safari where it won't compute the height of the body correctly. + +```css +body { + width: 100%; + height: 100%; +} +``` + ## Mapping Components from Figma to Blade in your code Blade is built with **"What you see in Figma is what you get on Code" ** philosophy. diff --git a/packages/blade/src/components/BottomSheet/BottomSheet.stories.tsx b/packages/blade/src/components/BottomSheet/BottomSheet.stories.tsx index bad10b02d8a..a3f58e46482 100644 --- a/packages/blade/src/components/BottomSheet/BottomSheet.stories.tsx +++ b/packages/blade/src/components/BottomSheet/BottomSheet.stories.tsx @@ -47,6 +47,7 @@ import { Link } from '~components/Link'; import { Sandbox } from '~utils/storybook/Sandbox'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { isReactNative } from '~utils'; +import { SandboxHighlighter } from '~utils/storybook/Sandbox/SandpackEditor'; const Page = (): React.ReactElement => { return ( @@ -134,6 +135,20 @@ const Page = (): React.ReactElement => { export default App; `} + iOS Safari Specific Setup + + When using BottomSheet or SpotlightPopoverTour, Make sure to set a width/height to the + `body` otherwise when they open, the page will get clipped. This happens due to a bug in iOS + safari where it won't compute the height of the body correctly. + + + {` + body { + width: 100%; + height: 100%; + } + `} + ); }; @@ -225,6 +240,51 @@ const BottomSheetTemplate: ComponentStory = ({ ...a return ( + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has + been the industry's standard dummy text ever since the 1500s, when an unknown printer took a + galley of type and scrambled it to make a type specimen book. It has survived not only five + centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum + passages, and more recently with desktop publishing software like Aldus PageMaker including + versions of Lorem Ipsum. + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has + been the industry's standard dummy text ever since the 1500s, when an unknown printer took a + galley of type and scrambled it to make a type specimen book. It has survived not only five + centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum + passages, and more recently with desktop publishing software like Aldus PageMaker including + versions of Lorem Ipsum. + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has + been the industry's standard dummy text ever since the 1500s, when an unknown printer took a + galley of type and scrambled it to make a type specimen book. It has survived not only five + centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum + passages, and more recently with desktop publishing software like Aldus PageMaker including + versions of Lorem Ipsum. + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has + been the industry's standard dummy text ever since the 1500s, when an unknown printer took a + galley of type and scrambled it to make a type specimen book. It has survived not only five + centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum + passages, and more recently with desktop publishing software like Aldus PageMaker including + versions of Lorem Ipsum. + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has + been the industry's standard dummy text ever since the 1500s, when an unknown printer took a + galley of type and scrambled it to make a type specimen book. It has survived not only five + centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum + passages, and more recently with desktop publishing software like Aldus PageMaker including + versions of Lorem Ipsum. + { - const ref = refIdMap.get(steps[activeStep]?.name); - if (!ref?.current) return; + const updateMaskSize = useCallback( + (shouldSkipDelay = false) => { + const ref = refIdMap.get(steps[activeStep]?.name); + if (!ref?.current) return; - const rect = ref.current.getBoundingClientRect(); - setSize({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - }); - }, [activeStep, refIdMap, steps]); + const rect = ref.current.getBoundingClientRect(); + setSize({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + }); + if (shouldSkipDelay) { + setDelayedSize({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + }); + } + }, + [activeStep, refIdMap, setDelayedSize, steps], + ); const scrollToStep = useCallback(() => { const ref = refIdMap.get(steps[delayedActiveStep]?.name); @@ -116,39 +128,44 @@ const SpotlightPopoverTour = ({ // If the element is already in view, don't scroll if (intersection?.isIntersecting) return; + setIsScrolling(true); smoothScroll(ref.current, { behavior: 'smooth', block: 'center', inline: 'center', }) .then(() => { - updateMaskSize(); + // wait for the scroll to finish before updating the mask size + // We also don't want to delay the size update since its already delayed by the scroll + updateMaskSize(true); }) .finally(() => { - // do nothing + setIsScrolling(false); }); }, [delayedActiveStep, refIdMap, steps, updateMaskSize, intersection?.isIntersecting]); // Update the size of the mask when the active step changes useIsomorphicLayoutEffect(() => { updateMaskSize(); - }, [isOpen, activeStep, refIdMap, steps, updateMaskSize]); + }, [activeStep, updateMaskSize]); // Scroll into view when the active step changes useIsomorphicLayoutEffect(() => { + // We need to wait for the transition to finish before scrolling + // Otherwise the browser sometimes interrupts the scroll + const scrollDelay = 100; setTimeout(() => { if (!isOpen) return; if (isTransitioning) return; scrollToStep(); - }, transitionDelay); + }, scrollDelay); }, [isOpen, scrollToStep, isTransitioning]); - useLockBodyScroll(isOpen); - // reset the mask size when the tour is closed - React.useEffect(() => { + useIsomorphicLayoutEffect(() => { if (isOpen) { - updateMaskSize(); + // on initial mount, we don't want to delay the size update + updateMaskSize(true); onOpenChange?.({ isOpen }); } if (!isOpen) { @@ -162,6 +179,8 @@ const SpotlightPopoverTour = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); + useLockBodyScroll(isOpen); + const contextValue = useMemo(() => { return { attachStep, removeStep }; }, [attachStep, removeStep]); @@ -171,7 +190,7 @@ const SpotlightPopoverTour = ({ {isOpen ? ( diff --git a/packages/blade/src/components/SpotlightPopoverTour/TourPopover.web.tsx b/packages/blade/src/components/SpotlightPopoverTour/TourPopover.web.tsx index 8059261eef3..7de5df4334e 100644 --- a/packages/blade/src/components/SpotlightPopoverTour/TourPopover.web.tsx +++ b/packages/blade/src/components/SpotlightPopoverTour/TourPopover.web.tsx @@ -12,7 +12,6 @@ import { useTransitionStyles, autoUpdate, useClick, - useDismiss, FloatingFocusManager, } from '@floating-ui/react'; import React from 'react'; @@ -108,10 +107,9 @@ const TourPopover = ({ // remove click handler if popover is controlled const isControlled = isOpen !== undefined; const click = useClick(context, { enabled: !isControlled }); - const dismiss = useDismiss(context); const role = useRole(context); - const { getFloatingProps } = useInteractions([click, dismiss, role]); + const { getFloatingProps } = useInteractions([click, role]); const contextValue = React.useMemo(() => { return { diff --git a/packages/blade/src/components/SpotlightPopoverTour/_KitchenSink.SpotlightPopoverTour.stories.tsx b/packages/blade/src/components/SpotlightPopoverTour/_KitchenSink.SpotlightPopoverTour.stories.tsx index dba644d354a..9f432ce503d 100644 --- a/packages/blade/src/components/SpotlightPopoverTour/_KitchenSink.SpotlightPopoverTour.stories.tsx +++ b/packages/blade/src/components/SpotlightPopoverTour/_KitchenSink.SpotlightPopoverTour.stories.tsx @@ -1,11 +1,11 @@ import { composeStories } from '@storybook/react'; -import * as tourStories from './Tour.stories'; +import * as tourStories from './docs/Tour.stories'; import { Box } from '~components/Box'; import { Heading } from '~components/Typography'; const allStories = Object.values(composeStories(tourStories)); -export const Tour = (): JSX.Element => { +export const SpotlightPopoverTour = (): JSX.Element => { return ( {allStories.map((Story) => { @@ -21,8 +21,8 @@ export const Tour = (): JSX.Element => { }; export default { - title: 'Components/KitchenSink/Tour', - component: Tour, + title: 'Components/KitchenSink/SpotlightPopoverTour', + component: SpotlightPopoverTour, parameters: { // enable Chromatic's snapshotting only for kitchensink chromatic: { disableSnapshot: false }, diff --git a/packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.mdx b/packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.mdx new file mode 100644 index 00000000000..3af7bf49af5 --- /dev/null +++ b/packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.mdx @@ -0,0 +1,6 @@ +import { Meta } from '@storybook/addon-docs'; +import { TourDocs } from './TourDocs.stories'; + + + + diff --git a/packages/blade/src/components/SpotlightPopoverTour/Tour.stories.tsx b/packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.tsx similarity index 92% rename from packages/blade/src/components/SpotlightPopoverTour/Tour.stories.tsx rename to packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.tsx index 2d90580da9c..46f05fde4b9 100644 --- a/packages/blade/src/components/SpotlightPopoverTour/Tour.stories.tsx +++ b/packages/blade/src/components/SpotlightPopoverTour/docs/Tour.stories.tsx @@ -3,14 +3,15 @@ import type { Meta, ComponentStory } from '@storybook/react'; import React from 'react'; import { Title } from '@storybook/addon-docs'; import isChromatic from 'chromatic'; +import { SpotlightPopoverTourStep } from '../TourStep'; +import { SpotlightPopoverTourFooter } from '../TourFooter'; +import { SpotlightPopoverTour } from '../..'; import type { SpotlightPopoverStepRenderProps, SpotlightPopoverTourProps, SpotlightPopoverTourSteps, -} from './types'; -import { SpotlightPopoverTourStep } from './TourStep'; -import { SpotlightPopoverTourFooter } from './TourFooter'; -import { SpotlightPopoverTour } from '.'; +} from '../types'; +import { BasicExample } from './examples'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { Code, Text } from '~components/Typography'; @@ -20,10 +21,12 @@ import { Sandbox } from '~utils/storybook/Sandbox'; import { Card, CardBody } from '~components/Card'; import { Amount } from '~components/Amount'; import { Link } from '~components/Link'; +import { SandboxHighlighter } from '~utils/storybook/Sandbox/SandpackEditor'; const Page = (): React.ReactElement => { return ( { }} > Usage - + {BasicExample} + iOS Safari Specific Setup + + When using BottomSheet or SpotlightPopoverTour, Make sure to set a width/height to the + `body` otherwise when they open, the page will get clipped. This happens due to a bug in iOS + safari where it won't compute the height of the body correctly. + + {` - import React from 'react'; - import { - SpotlightPopoverTour, - SpotlightPopoverTourStep, - SpotlightPopoverTourFooter, - Box, - Text, - Button - } from '@razorpay/blade/components'; - - function App(): React.ReactElement { - const [activeStep, setActiveStep] = React.useState(0); - const [isOpen, setIsOpen] = React.useState(false); - - return ( - - - { - console.log('finished'); - setActiveStep(0); - setIsOpen(false); - }} - onOpenChange={({ isOpen }) => { - console.log('open change', isOpen); - setIsOpen(isOpen); - }} - onStepChange={(step) => { - console.log('step change', step); - setActiveStep(step); - }} - > - - - - Step 1 - - - - - Step 2 - - - - - - ); - } - - export default App; - `} - + body { + width: 100%; + height: 100%; + } + `} + + Examples + + To see examples properly, switch to the{' '} + + story view + + ); }; @@ -222,6 +183,11 @@ export default { }, }, parameters: { + options: { + storySort: { + order: ['Docs', '*'], + }, + }, docs: { page: Page, }, @@ -410,8 +376,12 @@ const TourTemplate: ComponentStory<(props: StoryControlProps) => React.ReactElem ); }; -export const Default = TourTemplate.bind({}); -Default.storyName = 'Default'; +export const Basic = TourTemplate.bind({}); +Basic.storyName = 'Basic'; +Basic.parameters = { + docs: { disable: false }, + viewMode: 'story', +}; export const CustomPlacement = () => { const [activeStep, setActiveStep] = React.useState(0); @@ -533,6 +503,10 @@ export const CustomPlacement = () => { ); }; CustomPlacement.storyName = 'Custom Placement'; +CustomPlacement.parameters = { + docs: { disable: true }, + viewMode: 'story', +}; export const WithScrollablePage = () => { const [activeStep, setActiveStep] = React.useState(0); @@ -739,6 +713,10 @@ export const WithScrollablePage = () => { ); }; WithScrollablePage.storyName = 'With Scrollable Page'; +WithScrollablePage.parameters = { + docs: { disable: true }, + viewMode: 'story', +}; const InterruptibleTourFooter = ({ activeStep, @@ -952,3 +930,7 @@ export const InterruptibleTour = () => { ); }; InterruptibleTour.storyName = 'Product Usecase: Interruptible Tour'; +InterruptibleTour.parameters = { + docs: { disable: true }, + viewMode: 'story', +}; diff --git a/packages/blade/src/components/SpotlightPopoverTour/docs/TourDocs.stories.tsx b/packages/blade/src/components/SpotlightPopoverTour/docs/TourDocs.stories.tsx new file mode 100644 index 00000000000..43db6621233 --- /dev/null +++ b/packages/blade/src/components/SpotlightPopoverTour/docs/TourDocs.stories.tsx @@ -0,0 +1,293 @@ +import React from 'react'; +import { BasicExample } from './examples'; +import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; +import { Code, Heading, Text, Title } from '~components/Typography'; +import { Box } from '~components/Box'; +import type { TableData } from '~components/Table'; +import { + Table, + TableBody, + TableCell, + TableHeader, + TableHeaderCell, + TableHeaderRow, + TableRow, +} from '~components/Table'; +import { ScrollLink } from '~utils/storybook/ScrollLink'; +import { InfoIcon } from '~components/Icons'; +import { Popover, PopoverInteractiveWrapper } from '~components/Popover'; +import { SandboxHighlighter } from '~utils/storybook/Sandbox/SandpackEditor'; +import { Sandbox } from '~utils/storybook/Sandbox/StackblitzEditor/Sandbox'; + +type Item = { + id: string; + prop: string; + type: string; + description: string; + default?: string; + required?: boolean; + typeLink?: string; + typeHint?: React.ReactElement; +}; + +const tourProps: TableData = { + nodes: [ + { + id: '1', + prop: 'steps', + type: 'Step[]', + typeLink: '#step-props', + description: + 'Array of steps to be rendered, The order of the steps will be the order in which they are rendered depending on the activeStep prop', + required: true, + }, + { + id: '2', + prop: 'isOpen', + type: 'boolean', + description: 'Whether the tour is open or not', + required: true, + default: 'false', + }, + { + id: '6', + prop: 'activeStep', + type: 'number', + description: 'Active step to be rendered', + required: true, + }, + { + id: '3', + prop: 'onOpenChange', + type: '({ isOpen: boolean }) => void', + description: 'Callback when the tour is opened or closed', + }, + { + id: '4', + prop: 'onFinish', + type: '() => void', + description: 'Callback which fires when the stopTour method is called from the steps array', + }, + { + id: '5', + prop: 'onStepChange', + type: '(step: number) => void', + description: 'Callback which fires when the step changes', + }, + { + id: '7', + prop: 'children', + type: 'React.ReactElement', + description: '', + }, + ], +}; + +const SpotlightPopoverStepRenderPropsCode = ( + + {` + type SpotlightPopoverStepRenderProps = { + /** + * Go to a specific step + */ + goToStep: (step: number) => void; + /** + * Go to the next step + */ + goToNext: () => void; + /** + * Go to the previous step + */ + goToPrevious: () => void; + /** + * Stop the tour + * + * This will call the \`onFinish\` callback + */ + stopTour: () => void; + /** + * Current active step (zero based index) + */ + activeStep: number; + /** + * Total number of steps + */ + totalSteps: number; + }; + `} + +); + +const tourStepProps: TableData = { + nodes: [ + { + id: '5', + prop: 'name', + type: 'string', + description: 'Unique identifier for the tour step', + required: true, + }, + { + id: '3', + prop: 'content', + type: '(props: SpotlightPopoverStepRenderProps) => React.ReactElement', + typeHint: SpotlightPopoverStepRenderPropsCode, + description: 'Content of the Popover', + required: true, + }, + { + id: '1', + prop: 'title', + type: 'string', + description: 'Popover Title', + }, + { + id: '2', + prop: 'titleLeading', + type: 'React.ReactNode', + description: 'Leading content placed before the title', + }, + { + id: '4', + prop: 'footer', + type: '(props: SpotlightPopoverStepRenderProps) => React.ReactNode', + typeHint: SpotlightPopoverStepRenderPropsCode, + description: 'Footer content', + }, + { + id: '6', + prop: 'placement', + type: + '"top" | "right" | "bottom" | "left" | "top-end" | "top-start" | "right-end" | "right-start" | "bottom-end" | "bottom-start" | "left-end" | "left-start"', + description: 'Placement of Popover', + default: '"top"', + }, + ], +}; + +const BladeArgTable = ({ data }: { data: TableData }): React.ReactElement => { + return ( + + {(tableData) => ( + <> + + + prop + type + description + default + + + + {tableData.map((tableItem, index) => ( + + + + {tableItem.prop}{' '} + {tableItem.required && ( + + * + + )} + + + + + {tableItem.typeHint ? ( + + + + + + ) : null} + {tableItem.typeLink ? ( + + {tableItem.type} + + ) : ( + {tableItem.type} + )} + + + {tableItem.description || '-'} + {tableItem.default || '-'} + + ))} + + + )} +
+ ); +}; + +const TourDocs = (): React.ReactElement => { + return ( + + Usage + {BasicExample} + iOS Safari Specific Setup + + When using BottomSheet or SpotlightPopoverTour, Make sure to set a width/height to the + `body` otherwise when they open, the page will get clipped. This happens due to a bug in iOS + safari where it won't compute the height of the body correctly. + + + {` + body { + width: 100%; + height: 100%; + } + `} + + Examples + + To see examples properly, switch to the{' '} + + story view + + + + API + + + + SpotlightPopoverTour props + + + + + Step type + + Step type defines each step of the tour, and passed to the{' '} + steps prop in the SpotlightPopoverTour component. + + + + + + ); +}; + +export { TourDocs }; diff --git a/packages/blade/src/components/SpotlightPopoverTour/docs/examples.ts b/packages/blade/src/components/SpotlightPopoverTour/docs/examples.ts new file mode 100644 index 00000000000..73076680c98 --- /dev/null +++ b/packages/blade/src/components/SpotlightPopoverTour/docs/examples.ts @@ -0,0 +1,129 @@ +export const BasicExample = ` + import React from 'react'; + import { + SpotlightPopoverTour, + SpotlightPopoverTourStep, + SpotlightPopoverTourFooter, + Box, + Text, + Button + } from '@razorpay/blade/components'; + import type { + SpotlightPopoverStepRenderProps, + SpotlightPopoverTourSteps, + } from '@razorpay/blade/components'; + + const CustomTourFooter = ({ + activeStep, + totalSteps, + goToNext, + goToPrevious, + stopTour, + }: SpotlightPopoverStepRenderProps) => { + const isLast = activeStep === totalSteps - 1; + const isFirst = activeStep === 0; + return ( + + ); + }; + + const steps: SpotlightPopoverTourSteps = [ + { + name: 'step-1', + title: 'Step 1', + content: () => { + return ( + + This is step 1 + + ); + }, + placement: 'top', + footer: CustomTourFooter, + }, + { + name: 'step-2', + title: 'Step 2', + content: () => { + return ( + + This is step 2 + + ); + }, + placement: 'bottom', + footer: CustomTourFooter, + }, + ]; + + function App(): React.ReactElement { + const [activeStep, setActiveStep] = React.useState(0); + const [isOpen, setIsOpen] = React.useState(false); + + return ( + + + { + console.log('finished'); + setActiveStep(0); + setIsOpen(false); + }} + onOpenChange={({ isOpen }) => { + console.log('open change', isOpen); + setIsOpen(isOpen); + }} + onStepChange={(step) => { + console.log('step change', step); + setActiveStep(step); + }} + > + + + + Step 1 + + + + + Step 2 + + + + + + ); + } + + export default App; +`; diff --git a/packages/blade/src/components/SpotlightPopoverTour/utils.ts b/packages/blade/src/components/SpotlightPopoverTour/utils.ts index f142446663b..e26cca83b67 100644 --- a/packages/blade/src/components/SpotlightPopoverTour/utils.ts +++ b/packages/blade/src/components/SpotlightPopoverTour/utils.ts @@ -14,13 +14,16 @@ import { useScrollLock } from '~utils/useScrollLock'; * This is used to delay the active step change to allow for transitions to finish * This prevents the popover's footer from changing it's JSX while it's transitioning */ -function useDelayedState(initialState: T, delay: number): T { - const [delayedState, setDelayedState] = React.useState(initialState); +function useDelayedState( + initialState: T, + delay: number, +): [T, React.Dispatch>] { + const [delayedState, _setDelayedState] = React.useState(initialState); const timeoutRef = React.useRef(undefined); React.useEffect(() => { timeoutRef.current = window.setTimeout(() => { - setDelayedState(initialState); + _setDelayedState(initialState); }, delay); return () => { @@ -28,7 +31,12 @@ function useDelayedState(initialState: T, delay: number): T { }; }, [delay, initialState]); - return delayedState; + const setDelayedState = React.useCallback((newState: React.SetStateAction) => { + _setDelayedState(newState); + window.clearTimeout(timeoutRef.current); + }, []); + + return [delayedState, setDelayedState]; } /** diff --git a/packages/blade/src/utils/storybook/ScrollLink.tsx b/packages/blade/src/utils/storybook/ScrollLink.tsx index 80630d70c2f..3a22d00faa8 100644 --- a/packages/blade/src/utils/storybook/ScrollLink.tsx +++ b/packages/blade/src/utils/storybook/ScrollLink.tsx @@ -1,8 +1,16 @@ import { Link } from '~components/Link'; -const ScrollLink = ({ children, href }: { children: string; href: string }): JSX.Element => ( +const ScrollLink = ({ + children, + href, + size = 'small', +}: { + children: string; + href: string; + size?: 'small' | 'medium'; +}): JSX.Element => ( { document.querySelector(href)?.scrollIntoView({ diff --git a/packages/blade/src/utils/storybook/StoryPageWrapper.tsx b/packages/blade/src/utils/storybook/StoryPageWrapper.tsx index a8c7e69d688..14ed08bd599 100644 --- a/packages/blade/src/utils/storybook/StoryPageWrapper.tsx +++ b/packages/blade/src/utils/storybook/StoryPageWrapper.tsx @@ -25,12 +25,14 @@ type StoryPageWrapperTypes = { paymentTheme: string; bankingTheme: string; }; + argTableComponent?: unknown; componentDescription: string; propsDescription?: string; componentName: string; children?: React.ReactNode; note?: React.ReactChild; showStorybookControls?: boolean; + showDefaultExample?: boolean; showArgsTable?: boolean; /** * Use this to override default API decision link generated from componentName @@ -96,7 +98,7 @@ const StoryPageWrapper = (props: StoryPageWrapperTypes): React.ReactElement => { (componentInfo) => componentInfo.name === props.componentName, ); - const { showStorybookControls = true, showArgsTable = true } = props; + const { showStorybookControls = true, showArgsTable = true, showDefaultExample = true } = props; return ( @@ -149,11 +151,15 @@ const StoryPageWrapper = (props: StoryPageWrapperTypes): React.ReactElement => { )} {showStorybookControls ? ( <> - Example - - {`This is the default ${props.componentName}. You can change the properties using the controls below.`} - - + {showDefaultExample ? ( + <> + Example + + {`This is the default ${props.componentName}. You can change the properties using the controls below.`} + + + + ) : null} {showArgsTable ? ( <> @@ -184,7 +190,10 @@ const StoryPageWrapper = (props: StoryPageWrapperTypes): React.ReactElement => {
) : null}
- + ) : null}