diff --git a/package-lock.json b/package-lock.json index 008d28053..657ec2709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "react-hook-form": "^7.32.0", "react-icons": "^4.4.0", "react-input-mask": "^2.0.4", + "react-intersection-observer": "^9.5.2", "react-joyride": "^2.5.3", "react-markdown": "^8.0.7", "react-query": "^3.34.16", @@ -30756,6 +30757,14 @@ "react": "^16.8.4 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-intersection-observer": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz", + "integrity": "sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "license": "MIT" diff --git a/package.json b/package.json index 0ebd40ef1..6c741c390 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-hook-form": "^7.32.0", "react-icons": "^4.4.0", "react-input-mask": "^2.0.4", + "react-intersection-observer": "^9.5.2", "react-joyride": "^2.5.3", "react-markdown": "^8.0.7", "react-query": "^3.34.16", diff --git a/src/assets/images/HeroBannerToolTipImage.tsx b/src/assets/images/HeroBannerToolTipImage.tsx new file mode 100644 index 000000000..2032ebe9f --- /dev/null +++ b/src/assets/images/HeroBannerToolTipImage.tsx @@ -0,0 +1,36 @@ +export const HeroBannerToolTipImage = ( + props: React.SVGProps +): JSX.Element => { + return ( + + + + + + + + + + ) +} diff --git a/src/assets/images/index.ts b/src/assets/images/index.ts index 00aa40dce..cecd9ea7b 100644 --- a/src/assets/images/index.ts +++ b/src/assets/images/index.ts @@ -19,3 +19,4 @@ export * from "./SiteLaunchPendingImage" export * from "./NotFoundSubmarineImage" export * from "./HomepageAnnouncementsSampleImage" export * from "./HomepageTextCardsSampleImage" +export * from "./HeroBannerToolTipImage" diff --git a/src/constants/localStorage.ts b/src/constants/localStorage.ts index 3a099f12a..41db10956 100644 --- a/src/constants/localStorage.ts +++ b/src/constants/localStorage.ts @@ -5,4 +5,5 @@ export enum LOCAL_STORAGE_KEYS { DashboardFeatureTour = "dashboard-identity-feature-tour-v1", WorkspaceFeatureTour = "workspace-identity-feature-tour-v1", Feedback = "feedback", + HeroOptionsFeatureTour = "hero-options-feature-tour-v1", } diff --git a/src/features/FeatureTour/FeatureTourContent.tsx b/src/features/FeatureTour/FeatureTourContent.tsx new file mode 100644 index 000000000..771f06c65 --- /dev/null +++ b/src/features/FeatureTour/FeatureTourContent.tsx @@ -0,0 +1,29 @@ +import { Icon, Text } from "@chakra-ui/react" +import { Badge } from "@opengovsg/design-system-react" +import { BiCheck } from "react-icons/bi" + +export const HeroOptionsFeatureTourContent = (): JSX.Element => { + return ( + <> + + + New feature + + + {" "} + Now you can customise your hero banner with various layouts!{" "} + + + {`We've added some variations to how you can display your hero section. + Try them out here.`} + + + ) +} diff --git a/src/features/FeatureTour/FeatureTourSequence.ts b/src/features/FeatureTour/FeatureTourSequence.ts index c2a43e37f..91ddb3d7d 100644 --- a/src/features/FeatureTour/FeatureTourSequence.ts +++ b/src/features/FeatureTour/FeatureTourSequence.ts @@ -1,5 +1,10 @@ +import React from "react" import { Step } from "react-joyride" +import { HeroBannerToolTipImage } from "assets/images" + +import { HeroOptionsFeatureTourContent } from "./FeatureTourContent" + export const DASHBOARD_FEATURE_STEPS: Array = [ { target: "#isomer-dashboard-feature-tour-step-1", @@ -62,3 +67,14 @@ export const STORYBOOK_FEATURE_STEPS: Array = [ placement: "top-end", }, ] + +export const HERO_OPTIONS_FEATURE_STEPS: Array = [ + { + target: "#isomer-hero-feature-tour-step-1", + content: React.createElement("div", {}, HeroOptionsFeatureTourContent()), + floaterProps: { placement: "right-end" }, + placement: "right-end", + title: React.createElement("div", {}, HeroBannerToolTipImage({})), + disableBeacon: true, + }, +] diff --git a/src/features/FeatureTour/FeatureTourTooltip.stories.tsx b/src/features/FeatureTour/FeatureTourTooltip.stories.tsx index f1429ec84..d0dc35798 100644 --- a/src/features/FeatureTour/FeatureTourTooltip.stories.tsx +++ b/src/features/FeatureTour/FeatureTourTooltip.stories.tsx @@ -1,9 +1,13 @@ import { ButtonProps } from "@opengovsg/design-system-react" -import { Meta, Story } from "@storybook/react" +import { Meta, StoryFn } from "@storybook/react" import { useState } from "react" +import { Step } from "react-joyride" import { FeatureTourContext } from "./FeatureTourContext" -import { DASHBOARD_FEATURE_STEPS } from "./FeatureTourSequence" +import { + DASHBOARD_FEATURE_STEPS, + HERO_OPTIONS_FEATURE_STEPS, +} from "./FeatureTourSequence" import { FeatureTourStep, FeatureTourTooltip, @@ -18,26 +22,27 @@ export default { }, } as Meta -const Template: Story = (args) => { - const { index, isLastStep } = args +const Template: StoryFn = ( + args +) => { + const { index, isLastStep, steps = DASHBOARD_FEATURE_STEPS } = args const [featureStep, setFeatureStep] = useState(index ?? 0) const handleNextClick = () => { - featureStep === DASHBOARD_FEATURE_STEPS.length - 1 + featureStep === steps.length - 1 ? setFeatureStep(featureStep) : setFeatureStep(featureStep + 1) } const getFeatureTourTooltipContent = (step: number): FeatureTourStep => { return { - title: DASHBOARD_FEATURE_STEPS[step].title, - content: DASHBOARD_FEATURE_STEPS[step].content, + title: steps[step].title, + content: steps[step].content, } } const featureTourTooltipContent = getFeatureTourTooltipContent(featureStep) - const isThisLastStep = - isLastStep ?? featureStep === DASHBOARD_FEATURE_STEPS.length - 1 + const isThisLastStep = isLastStep ?? featureStep === steps.length - 1 const mockPrimaryProps: ButtonProps = { onClick: handleNextClick, } @@ -53,6 +58,7 @@ const Template: Story = (args) => { primaryProps={mockPrimaryProps} isLastStep={isThisLastStep} index={featureStep} + size={steps.length} /> ) @@ -65,3 +71,11 @@ LastFeatureStep.args = { index: DASHBOARD_FEATURE_STEPS.length - 1, isLastStep: true, } + +export const HeroBannerStep = Template.bind({}) + +HeroBannerStep.args = { + index: 0, + isLastStep: true, + steps: HERO_OPTIONS_FEATURE_STEPS, +} diff --git a/src/features/FeatureTour/FeatureTourTooltip.tsx b/src/features/FeatureTour/FeatureTourTooltip.tsx index de99e8a42..ed4d62015 100644 --- a/src/features/FeatureTour/FeatureTourTooltip.tsx +++ b/src/features/FeatureTour/FeatureTourTooltip.tsx @@ -5,7 +5,6 @@ import { BiBulb, BiRightArrowAlt } from "react-icons/bi" import { ProgressIndicator } from "components/ProgressIndicator/ProgressIndicator" import { useFeatureTourContext } from "./FeatureTourContext" -import { DASHBOARD_FEATURE_STEPS } from "./FeatureTourSequence" export interface FeatureTourStep { content: React.ReactNode @@ -32,50 +31,78 @@ export const FeatureTourTooltip = ({ index, }: FeatureTourTooltipProps): JSX.Element => { const { paginationCallback } = useFeatureTourContext() + const showProgressIndicator = size > 1 + + let isTipBadgeShown = true + let titleComponent: React.ReactNode + if (typeof step.title === "string") { + titleComponent = ( + + {step.title} + + ) + } else { + titleComponent = step.title + isTipBadgeShown = false + } + + let contentComponent: React.ReactNode + if (typeof step.content === "string") { + contentComponent = ( + + {step.content} + + ) + } else { + contentComponent = step.content + } + return ( - - - - Tip - - - - {step.title} - - - {step.content} - + {isTipBadgeShown && ( + + + + Tip + + + )} + + {titleComponent} + {contentComponent} + - + {showProgressIndicator && ( + + )} {isLastStep ? ( ) : (