Skip to content

Commit

Permalink
feat(feature tour): FF for hero (#1508)
Browse files Browse the repository at this point in the history
* fix(homepagepreview): add default if no variant

* build(inView): allows us to know if elem is in view

* chore(heroBannerToolTipImage): add in required image

* feat(hero feature tour): add in storybook for feature tour

* feat(hero): add in functionality within hero body

* fix(feature tour homepage): no need beacon

* fix(homepage preview): unintended rebase commit

* fix(feature tour): fix rebase issues

* fix(hero-body): fix bugs from rebase

* fix(feature tour): styling fixes)

* fix(feature tour): fix feature tour render when component is loading

* fix(feature tour): fix tip badge being shown

* fix(edithomepage): fix padding issue

* fix(featureTour): attempt ui bug w useEffect

* fix(hero body): add deps

---------

Co-authored-by: seaerchin <[email protected]>
  • Loading branch information
kishore03109 and seaerchin authored Sep 27, 2023
1 parent 83cd575 commit fe243e9
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 63 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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",
Expand Down
36 changes: 36 additions & 0 deletions src/assets/images/HeroBannerToolTipImage.tsx

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/assets/images/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from "./SiteLaunchPendingImage"
export * from "./NotFoundSubmarineImage"
export * from "./HomepageAnnouncementsSampleImage"
export * from "./HomepageTextCardsSampleImage"
export * from "./HeroBannerToolTipImage"
1 change: 1 addition & 0 deletions src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
29 changes: 29 additions & 0 deletions src/features/FeatureTour/FeatureTourContent.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Badge
mt="2rem"
variant="subtle"
display="inline-flex"
columnGap="0.5rem"
alignItems="center"
colorScheme="brand.secondary"
>
<Icon as={BiCheck} h="1rem" w="1rem" />
<Text textStyle="caption-1">New feature</Text>
</Badge>
<Text textStyle="subhead-1" mt="1rem" color="base.content.default">
{" "}
Now you can customise your hero banner with various layouts!{" "}
</Text>
<Text textStyle="body-2" mt="0.5rem" color="base.content.default">
{`We've added some variations to how you can display your hero section.
Try them out here.`}
</Text>
</>
)
}
16 changes: 16 additions & 0 deletions src/features/FeatureTour/FeatureTourSequence.ts
Original file line number Diff line number Diff line change
@@ -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<Step> = [
{
target: "#isomer-dashboard-feature-tour-step-1",
Expand Down Expand Up @@ -62,3 +67,14 @@ export const STORYBOOK_FEATURE_STEPS: Array<Step> = [
placement: "top-end",
},
]

export const HERO_OPTIONS_FEATURE_STEPS: Array<Step> = [
{
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,
},
]
32 changes: 23 additions & 9 deletions src/features/FeatureTour/FeatureTourTooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,26 +22,27 @@ export default {
},
} as Meta

const Template: Story<FeatureTourTooltipProps> = (args) => {
const { index, isLastStep } = args
const Template: StoryFn<FeatureTourTooltipProps & { steps: Step[] }> = (
args
) => {
const { index, isLastStep, steps = DASHBOARD_FEATURE_STEPS } = args
const [featureStep, setFeatureStep] = useState<number>(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,
}
Expand All @@ -53,6 +58,7 @@ const Template: Story<FeatureTourTooltipProps> = (args) => {
primaryProps={mockPrimaryProps}
isLastStep={isThisLastStep}
index={featureStep}
size={steps.length}
/>
</FeatureTourContext.Provider>
)
Expand All @@ -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,
}
81 changes: 54 additions & 27 deletions src/features/FeatureTour/FeatureTourTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = (
<Text textStyle="subhead-1" color="base.content.dark" marginTop="1.25rem">
{step.title}
</Text>
)
} else {
titleComponent = step.title
isTipBadgeShown = false
}

let contentComponent: React.ReactNode
if (typeof step.content === "string") {
contentComponent = (
<Text textStyle="body-2" color="base.content.default" marginTop="0.5rem">
{step.content}
</Text>
)
} else {
contentComponent = step.content
}

return (
<Box
padding="1.5rem"
alignItems="center"
maxW="100%"
w="18rem"
w="20rem"
color="secondary.500"
bg="background.action.defaultInverse"
borderRadius="4px"
{...tooltipProps}
position="relative"
>
<Badge
colorScheme="success"
variant="solid"
display="flex"
width="fit-content"
backgroundColor="background.action.success"
>
<Icon as={BiBulb} mr="0.25rem" fontSize="1rem" color="text.inverse" />
<Text textStyle="caption-1" color="text.inverse">
Tip
</Text>
</Badge>
<Text textStyle="subhead-1" color="base.content.dark" marginTop="1.25rem">
{step.title}
</Text>
<Text textStyle="body-2" color="base.content.default" marginTop="0.5rem">
{step.content}
</Text>
{isTipBadgeShown && (
<Badge
colorScheme="success"
variant="solid"
display="flex"
width="fit-content"
backgroundColor="background.action.success"
>
<Icon as={BiBulb} mr="0.25rem" fontSize="1rem" color="text.inverse" />
<Text textStyle="caption-1" color="text.inverse">
Tip
</Text>
</Badge>
)}

{titleComponent}
{contentComponent}

<Flex
flexDirection="row"
marginTop="2.5rem"
marginTop="2rem"
alignItems="center"
justifyContent="space-between"
>
<ProgressIndicator
numIndicators={size}
currActiveIdx={index}
onClick={paginationCallback}
/>
{showProgressIndicator && (
<ProgressIndicator
numIndicators={size}
currActiveIdx={index}
onClick={paginationCallback}
/>
)}
{isLastStep ? (
<Button {...primaryProps} title="Done">
Done
Got it
</Button>
) : (
<Button
Expand Down
85 changes: 58 additions & 27 deletions src/layouts/components/Homepage/HeroBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@ import {
Tooltip,
} from "@opengovsg/design-system-react"
import _ from "lodash"
import { useState } from "react"
import { useEffect, useState } from "react"
import { BiInfoCircle } from "react-icons/bi"
import { useInView } from "react-intersection-observer"

import { Editable } from "components/Editable"
import { FormContext, FormError, FormTitle } from "components/Form"
import FormFieldMedia from "components/FormFieldMedia"

import { FEATURE_FLAGS } from "constants/featureFlags"
import { HERO_LAYOUTS } from "constants/homepage"
import { LOCAL_STORAGE_KEYS } from "constants/localStorage"

import { useEditableContext } from "contexts/EditableContext"

import { BxGrayTranslucent } from "assets"
import { FeatureTourHandler } from "features/FeatureTour/FeatureTour"
import { HERO_OPTIONS_FEATURE_STEPS } from "features/FeatureTour/FeatureTourSequence"
import {
SectionSize,
SectionAlignment,
Expand Down Expand Up @@ -293,36 +297,63 @@ const HeroLayoutForm = ({
const { onChange } = useEditableContext()
const showNewLayouts = useFeatureIsOn(FEATURE_FLAGS.HOMEPAGE_TEMPLATES)

// We only want to show the feature tour when the
// DOM elements are in view. Else, the feature tour
// does not work properly
const { ref, inView } = useInView({
threshold: 0,
// we want the animation to fully load before rendering the feature tour
delay: 100,
})

const [showFeatureTour, setShowFeatureTour] = useState(false)
useEffect(() => {
// attempt to fix spotlight offset issue via useEffect
setShowFeatureTour(showNewLayouts && inView)
}, [inView, showNewLayouts])

return (
<VStack spacing="1rem" align="flex-start" w="100%">
<Text textStyle="h5">{`Customise ${
showNewLayouts ? "Layout" : "Hero"
}`}</Text>
{showNewLayouts && (
<FormControl isRequired>
<FormLabel textStyle="subhead-1">Layout</FormLabel>
<SingleSelect
isClearable={false}
name="hero layout options"
value={variant}
items={_.values(HERO_LAYOUTS)}
// NOTE: Safe cast - the possible values are given by `HERO_LAYOUTS`
onChange={(val) => {
onChange({
target: {
// NOTE: Format is field type, index, section type, field
id: "section-0-hero-variant",
value: val as HeroBannerLayouts,
},
})
}}
<Box>
{showFeatureTour && (
<Box>
<FeatureTourHandler
localStorageKey={LOCAL_STORAGE_KEYS.HeroOptionsFeatureTour}
steps={HERO_OPTIONS_FEATURE_STEPS}
/>
</FormControl>
</Box>
)}
<VStack spacing="1rem" w="100%">
{children({ currentSelectedOption: variant })}
<VStack spacing="1rem" align="flex-start" w="100%" ref={ref}>
<Text textStyle="h5">{`Customise ${
showNewLayouts ? "Layout" : "Hero"
}`}</Text>
{showNewLayouts && (
<FormControl isRequired>
<Box id="isomer-hero-feature-tour-step-1">
<FormLabel textStyle="subhead-1">Layout</FormLabel>
<SingleSelect
isClearable={false}
name="hero layout options"
value={variant}
items={_.values(HERO_LAYOUTS)}
// NOTE: Safe cast - the possible values are given by `HERO_LAYOUTS`
onChange={(val) => {
onChange({
target: {
// NOTE: Format is field type, index, section type, field
id: "section-0-hero-variant",
value: val as HeroBannerLayouts,
},
})
}}
/>
</Box>
</FormControl>
)}
<VStack spacing="1rem" w="100%">
{children({ currentSelectedOption: variant })}
</VStack>
</VStack>
</VStack>
</Box>
)
}

Expand Down

0 comments on commit fe243e9

Please sign in to comment.