From 6c79c790a98f702af90d7b871a964db05f653743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Tue, 18 Apr 2023 10:56:15 +0100 Subject: [PATCH] feat: implement demo guide steps logic (#3528) https://linear.app/unleash/issue/2-915/implement-guide-steps-logic https://user-images.githubusercontent.com/14320932/232099388-a8138b29-8256-4ed2-b8f4-f7607cf3ab9c.mp4 See discussion for context: https://unleash-internal.slack.com/archives/C046LV85N3C/p1681723816687779?thread_ts=1681488537.345059&cid=C046LV85N3C Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: #3537 --- frontend/package.json | 9 +- frontend/src/component/App.tsx | 8 + frontend/src/component/demo/Demo.tsx | 236 ++++++++++++++++++ .../component/demo/DemoSteps/DemoSteps.tsx | 232 +++++++++++++++++ .../component/demo/DemoTopics/DemoTopics.tsx | 210 ++++++++++++++++ frontend/src/index.tsx | 2 + frontend/yarn.lock | 80 ++++++ 7 files changed, 773 insertions(+), 4 deletions(-) create mode 100644 frontend/src/component/demo/Demo.tsx create mode 100644 frontend/src/component/demo/DemoSteps/DemoSteps.tsx create mode 100644 frontend/src/component/demo/DemoTopics/DemoTopics.tsx diff --git a/frontend/package.json b/frontend/package.json index 48a5fe3673..503a47fb47 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,8 +66,10 @@ "chartjs-adapter-date-fns": "3.0.0", "classnames": "2.3.2", "copy-to-clipboard": "3.3.3", + "countries-and-timezones": "^3.4.0", "cypress": "9.7.0", "date-fns": "2.29.3", + "date-fns-tz": "^2.0.0", "debounce": "1.2.1", "deep-diff": "1.0.2", "dequal": "2.0.3", @@ -92,7 +94,9 @@ "react-dropzone": "14.2.3", "react-error-boundary": "3.1.4", "react-hooks-global-state": "2.1.0", + "react-joyride": "^2.5.3", "react-markdown": "^8.0.4", + "react-linkify": "^1.0.0-alpha", "react-router-dom": "6.8.1", "react-table": "7.8.0", "react-test-renderer": "17.0.2", @@ -107,10 +111,7 @@ "vite-plugin-svgr": "2.4.0", "vite-tsconfig-paths": "4.0.5", "vitest": "0.28.5", - "whatwg-fetch": "3.6.2", - "countries-and-timezones": "^3.4.0", - "date-fns-tz": "^2.0.0", - "react-linkify": "^1.0.0-alpha" + "whatwg-fetch": "3.6.2" }, "optionalDependencies": { "orval": "^6.10.3" diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx index 65a50202c2..ad70203d09 100644 --- a/frontend/src/component/App.tsx +++ b/frontend/src/component/App.tsx @@ -20,6 +20,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import MaintenanceBanner from './maintenance/MaintenanceBanner'; import { styled } from '@mui/material'; import { InitialRedirect } from './InitialRedirect'; +import { Demo } from './demo/Demo'; const StyledContainer = styled('div')(() => ({ '& ul': { @@ -100,6 +101,13 @@ export const App = () => { + } + /> + diff --git a/frontend/src/component/demo/Demo.tsx b/frontend/src/component/demo/Demo.tsx new file mode 100644 index 0000000000..31ceb5396d --- /dev/null +++ b/frontend/src/component/demo/Demo.tsx @@ -0,0 +1,236 @@ +import { Typography } from '@mui/material'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import { Badge } from 'component/common/Badge/Badge'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { useEffect, useState } from 'react'; +import { Step } from 'react-joyride'; +import { DemoTopics } from './DemoTopics/DemoTopics'; +import { DemoSteps } from './DemoSteps/DemoSteps'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { createLocalStorage } from 'utils/createLocalStorage'; + +export interface ITutorialTopic { + title: string; + steps: Step[]; +} + +const defaultProgress = { + expanded: true, + run: false, + topic: 0, + steps: [0], +}; + +const { value: storedProgress, setValue: setStoredProgress } = + createLocalStorage('Tutorial:v1', defaultProgress); + +const TOPICS: ITutorialTopic[] = [ + { + title: 'Import', + steps: [ + { + target: 'button[data-testid="IMPORT_BUTTON"]', + title: ( + + Import toggle configuration + + ), + content: ( + <> + + This is a cool feature that enables you to import + your toggle configuration. This is just an example + and not part of the final guide. + + + ), + disableBeacon: true, + }, + ], + }, + { + title: 'New feature toggle', + steps: [ + { + target: 'button[data-testid="NAVIGATE_TO_CREATE_FEATURE"]', + title: ( + + Add a new feature toggle + + ), + content: ( + <> + + You can use this button to add a new feature toggle. + This is just an example and not part of the final + guide. + + + ), + disableBeacon: true, + }, + ], + }, + { + title: 'Enable/disable a feature toggle', + steps: [ + { + target: '.MuiSwitch-sizeMedium', + title: ( + + Enable/disable a feature toggle + + ), + content: ( + <> + + The simplest way to use a feature toggle is to + enable or disable it for everyone (on/off). + + } + > + Look at the demo page when toggling! + + + ), + disableBeacon: true, + }, + ], + }, + { + title: 'Community', + steps: [ + { + target: 'a[href="https://twitter.com/getunleash"]', + title: ( + + Follow us on Twitter! + + ), + content: ( + <> + + Following us on Twitter is one of the easiest ways + of keeping up with us. This is just an example and + not part of the final guide. + + + ), + disableBeacon: true, + }, + { + target: 'a[href="https://www.linkedin.com/company/getunleash"]', + title: ( + + Follow us on LinkedIn! + + ), + content: ( + <> + + You can also follow us LinkedIn. This is just an + example and not part of the final guide. + + + ), + disableBeacon: true, + }, + { + target: 'a[href="https://github.com/Unleash/unleash"]', + title: ( + + Check out Unleash on GitHub! + + ), + content: ( + <> + + Unleash is open-source, check out the project on + GitHub. This is just an example and not part of the + final guide. + + + ), + disableBeacon: true, + }, + { + target: 'a[href="https://slack.unleash.run"]', + title: ( + Join us on Slack! + ), + content: ( + <> + + Join our community in Slack. This is just an example + and not part of the final guide. + + + ), + disableBeacon: true, + }, + ], + }, +]; + +export const Demo = () => { + const { uiConfig } = useUiConfig(); + const [loaded, setLoaded] = useState(false); + const [expanded, setExpanded] = useState(storedProgress.expanded ?? true); + const [run, setRun] = useState(storedProgress.run ?? false); + const [topic, setTopic] = useState(storedProgress.topic ?? 0); + const [steps, setSteps] = useState(storedProgress.steps ?? [0]); + + useEffect(() => { + setTimeout(() => { + setLoaded(true); + }, 1000); + }, []); + + useEffect(() => { + setStoredProgress({ + expanded, + run, + topic, + steps, + }); + }, [expanded, run, topic, steps]); + + if (!uiConfig.flags.demo) return null; + + return ( + <> + { + setTopic(topic); + setSteps(steps => { + const newSteps = [...steps]; + newSteps[topic] = 0; + return newSteps; + }); + }} + topics={TOPICS} + /> + + } + /> + + ); +}; diff --git a/frontend/src/component/demo/DemoSteps/DemoSteps.tsx b/frontend/src/component/demo/DemoSteps/DemoSteps.tsx new file mode 100644 index 0000000000..7bef1012e6 --- /dev/null +++ b/frontend/src/component/demo/DemoSteps/DemoSteps.tsx @@ -0,0 +1,232 @@ +import Joyride, { + ACTIONS, + CallBackProps, + EVENTS, + STATUS, + TooltipRenderProps, +} from 'react-joyride'; +import { Button, Typography, styled, useTheme } from '@mui/material'; +import { ITutorialTopic } from '../Demo'; +import { useEffect } from 'react'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; + +const StyledTooltip = styled('div')(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.primary, + borderRadius: theme.shape.borderRadiusMedium, + width: '100%', + maxWidth: theme.spacing(45), + padding: theme.spacing(3), +})); + +const StyledTooltipTitle = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), + marginBottom: theme.spacing(1), + flexWrap: 'wrap', +})); + +const StyledTooltipActions = styled('div')(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + marginTop: theme.spacing(3), + '&&& button': { + '&:first-of-type': { + marginLeft: theme.spacing(-2), + }, + fontSize: theme.fontSizes.smallBody, + }, +})); + +const StyledTooltipPrimaryActions = styled('div')(({ theme }) => ({ + display: 'flex', + gap: theme.spacing(1), +})); + +interface IDemoStepsProps { + run: boolean; + setRun: React.Dispatch>; + setExpanded: React.Dispatch>; + steps: number[]; + setSteps: React.Dispatch>; + topic: number; + setTopic: React.Dispatch>; + topics: ITutorialTopic[]; +} + +export const DemoSteps = ({ + run, + setRun, + setExpanded, + steps, + setSteps, + topic, + setTopic, + topics, +}: IDemoStepsProps) => { + const theme = useTheme(); + + const skip = () => { + setRun(false); + setTopic(-1); + setExpanded(false); + }; + + const back = () => { + if (steps[topic] === 0) { + setRun(false); + const newTopic = topic - 1; + setTopic(newTopic); + setSteps(steps => { + const newSteps = [...steps]; + newSteps[newTopic] = topics[newTopic].steps.length - 1; + return newSteps; + }); + } else { + setSteps(steps => { + const newSteps = [...steps]; + newSteps[topic] = steps[topic] - 1; + return newSteps; + }); + } + }; + + const joyrideCallback = (data: CallBackProps) => { + const { action, index, status, type, step } = data; + + if (action === ACTIONS.UPDATE) { + const el = document.querySelector(step.target as string); + if (el) { + el.scrollIntoView({ + block: 'center', + }); + } + } + + if ( + ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND] as string[]).includes( + type + ) + ) { + const newStep = index + (action === ACTIONS.PREV ? -1 : 1); + setSteps(steps => { + const newSteps = [...steps]; + newSteps[topic] = newStep; + return newSteps; + }); + } else if ( + ([STATUS.FINISHED, STATUS.SKIPPED] as string[]).includes(status) + ) { + setRun(false); + if (topic === topics.length - 1) { + setTopic(-1); + setExpanded(false); + } else { + const newTopic = topic + 1; + setTopic(newTopic); + setSteps(steps => { + const newSteps = [...steps]; + newSteps[newTopic] = 0; + return newSteps; + }); + } + } + }; + + useEffect(() => { + setRun(true); + }, [topic, steps]); + + if (topic === -1) return null; + + return ( + { + const { onClick } = primaryProps; + + return ( + + + {step.title} + 1} + show={ + + (step {steps[topic] + 1} of{' '} + {topics[topic].steps.length}) + + } + /> + + {step.content} + + + + 0 || steps[topic] > 0} + show={ + + } + /> + + + + + ); + }} + /> + ); +}; diff --git a/frontend/src/component/demo/DemoTopics/DemoTopics.tsx b/frontend/src/component/demo/DemoTopics/DemoTopics.tsx new file mode 100644 index 0000000000..2105e08d71 --- /dev/null +++ b/frontend/src/component/demo/DemoTopics/DemoTopics.tsx @@ -0,0 +1,210 @@ +import { + Accordion, + AccordionDetails, + AccordionSummary, + Button, + LinearProgress, + Typography, + linearProgressClasses, + styled, +} from '@mui/material'; +import { CheckCircle, CircleOutlined, ExpandMore } from '@mui/icons-material'; +import { ITutorialTopic } from '../Demo'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + position: 'fixed', + bottom: 0, + left: 0, + width: '100%', + maxWidth: theme.spacing(30), + zIndex: theme.zIndex.fab, + '&&&': { + borderRadius: 0, + borderTopLeftRadius: theme.shape.borderRadiusLarge, + borderTopRightRadius: theme.shape.borderRadiusLarge, + }, + '&:before': { + display: 'none', + }, + '& .expand-icon': { + position: 'absolute', + right: theme.spacing(2), + fontSize: theme.fontSizes.mainHeader, + transition: 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + }, + '&.Mui-expanded .expand-icon': { + transform: 'rotate(180deg)', + }, +})); + +const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ + '& .MuiAccordionSummary-content': { + flexDirection: 'column', + alignItems: 'center', + }, + backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText, + borderTopLeftRadius: theme.shape.borderRadiusLarge, + borderTopRightRadius: theme.shape.borderRadiusLarge, +})); + +const StyledExpandMoreIcon = styled(ExpandMore)(({ theme }) => ({ + color: theme.palette.primary.contrastText, +})); + +const StyledTitle = styled('div')({ + display: 'flex', + alignItems: 'center', +}); + +const StyledSubtitle = styled(Typography)(({ theme }) => ({ + fontSize: theme.fontSizes.smallerBody, + marginTop: theme.spacing(0.5), + marginBottom: theme.spacing(0.5), +})); + +const StyledProgress = styled('div')(({ theme }) => ({ + width: '100%', + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1.5), +})); + +const StyledLinearProgress = styled(LinearProgress)(({ theme }) => ({ + width: '100%', + height: theme.spacing(1), + borderRadius: theme.shape.borderRadius, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.primary.dark, + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: theme.shape.borderRadius, + backgroundColor: theme.palette.primary.contrastText, + }, +})); + +const StyledStep = styled('li', { + shouldForwardProp: prop => prop !== 'selected' && prop !== 'completed', +})<{ selected?: boolean; completed?: boolean }>( + ({ theme, selected, completed }) => ({ + padding: theme.spacing(1), + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + marginTop: theme.spacing(1), + borderRadius: theme.shape.borderRadius, + gap: theme.spacing(1), + backgroundColor: theme.palette.background.elevation2, + ...(selected && { + backgroundColor: theme.palette.secondary.light, + fontWeight: theme.typography.fontWeightBold, + border: `1px solid ${theme.palette.primary.main}`, + }), + ...(completed && { + backgroundColor: theme.palette.background.elevation1, + textDecoration: 'line-through', + }), + }) +); + +const StyledCheckCircle = styled(CheckCircle)(({ theme }) => ({ + color: theme.palette.primary.main, + fontSize: theme.fontSizes.bodySize, +})); + +const StyledCircleOutlined = styled(CircleOutlined)(({ theme }) => ({ + color: theme.palette.neutral.main, + fontSize: theme.fontSizes.bodySize, +})); + +const StyledStepIcon = styled(ExpandMore)(({ theme }) => ({ + transform: 'rotate(-90deg)', + fontSize: theme.fontSizes.bodySize, +})); + +const StyledButton = styled(Button)(({ theme }) => ({ + width: '100%', + marginTop: theme.spacing(2), + '&&&': { + fontSize: theme.fontSizes.smallBody, + }, +})); + +interface IDemoTopicsProps { + expanded: boolean; + setExpanded: React.Dispatch>; + steps: number[]; + currentTopic: number; + setCurrentTopic: (topic: number) => void; + topics: ITutorialTopic[]; +} + +export const DemoTopics = ({ + expanded, + setExpanded, + steps, + currentTopic, + setCurrentTopic, + topics, +}: IDemoTopicsProps) => { + const completedSteps = steps.reduce((acc, step) => acc + (step || 0), 0); + const totalSteps = topics.flatMap(({ steps }) => steps).length; + const percentage = (completedSteps / totalSteps) * 100; + + return ( + setExpanded(expanded => !expanded)} + > + + + Unleash tutorial + + + + Complete all steps to finish tutorial + + + + {percentage.toFixed()}% + + + + + + + The steps will guide you + + {topics.map((topic, index) => { + const selected = currentTopic === index; + const completed = steps[index] === topic.steps.length; + return ( + setCurrentTopic(index)} + selected={selected} + completed={completed} + > + } + elseShow={} + /> + + {topic.title} + + + + ); + })} + + View demo link again + + + + ); +}; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 0b800d72b0..4354f6a52e 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -15,6 +15,8 @@ import { InstanceStatus } from 'component/common/InstanceStatus/InstanceStatus'; import { UIProviderContainer } from 'component/providers/UIProvider/UIProviderContainer'; import { MessageBanner } from 'component/common/MessageBanner/MessageBanner'; +window.global ||= window; + ReactDOM.render( diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a98b40eaca..906a93ecad 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1469,6 +1469,11 @@ resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.0.0-rc.9.tgz#56b9c6df627190f2dcda15f81f25d68826d9be4d" integrity sha512-dGGHpb61hLwifAu7sotuHFDBw6GTdpG8aKC0fsK17EuTzMRvUrH7lEAr6LTJ+sx3AZYed9yZ77rltVDHyg2hRg== +"@gilbarbara/deep-equal@^0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz#1a106721368dba5e7e9fb7e9a3a6f9efbd8df36d" + integrity sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA== + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" @@ -4142,6 +4147,11 @@ deepmerge@^2.2.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + defaults@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" @@ -4921,6 +4931,11 @@ executable@^4.1.1: dependencies: pify "^2.2.0" +exenv@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== + expect@^29.0.0: version "29.3.1" resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" @@ -5793,6 +5808,16 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lite@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/is-lite/-/is-lite-0.8.2.tgz#26ab98b32aae8cc8b226593b9a641d2bf4bd3b6a" + integrity sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw== + +is-lite@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/is-lite/-/is-lite-0.9.2.tgz#4b19e9a26b7c99ed50f748bcf088db57893d0730" + integrity sha512-qZuxbaEiKLOKhX4sbHLfhFN9iA3YciuZLb37/DfXCpWnz8p7qNL2lwkpxYMXfjlS8eEEjpULPZxAUI8N6FYvYQ== + is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" @@ -7313,6 +7338,11 @@ pony-cause@1.1.1, pony-cause@^1.0.0: resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-1.1.1.tgz#f795524f83bebbf1878bd3587b45f69143cbf3f9" integrity sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g== +popper.js@^1.16.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + postcss@^8.4.20, postcss@^8.4.21: version "8.4.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" @@ -7493,6 +7523,19 @@ react-error-boundary@3.1.4, react-error-boundary@^3.1.0: dependencies: "@babel/runtime" "^7.12.5" +react-floater@^0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/react-floater/-/react-floater-0.7.6.tgz#a98ee90e3d51200c6e6a564ff33496f3c0d7cfee" + integrity sha512-tt/15k/HpaShbtvWCwsQYLR+ebfUuYbl+oAUJ3DcEDkgYKeUcSkDey2PdAIERdVwzdFZANz47HbwoET2/Rduxg== + dependencies: + deepmerge "^4.2.2" + exenv "^1.2.2" + is-lite "^0.8.2" + popper.js "^1.16.0" + prop-types "^15.8.1" + react-proptype-conditional-require "^1.0.4" + tree-changes "^0.9.1" + react-hooks-global-state@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-hooks-global-state/-/react-hooks-global-state-2.1.0.tgz#91d1d85c6c920f2e8681579d71d552207c5fe4ac" @@ -7522,6 +7565,20 @@ react-linkify@^1.0.0-alpha: dependencies: linkify-it "^2.0.3" tlds "^1.199.0" +react-joyride@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/react-joyride/-/react-joyride-2.5.3.tgz#3e753f80502a74abcc956babec4873d204345911" + integrity sha512-DKKvb/JAAsHm0x/RWO3WI6NOtTMHDso5v8MTauxTSz2dFs7Tu1rWg1BDBWmEMj6pUCvem7hblFbCiDAcvhs8tQ== + dependencies: + deepmerge "^4.2.2" + exenv "^1.2.2" + is-lite "^0.9.2" + prop-types "^15.8.1" + react-floater "^0.7.6" + react-is "^16.13.1" + scroll "^3.0.1" + scrollparent "^2.0.1" + tree-changes "^0.9.2" react-markdown@^8.0.4: version "8.0.4" @@ -7544,6 +7601,11 @@ react-markdown@^8.0.4: unist-util-visit "^4.0.0" vfile "^5.0.0" +react-proptype-conditional-require@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz#69c2d5741e6df5e08f230f36bbc2944ee1222555" + integrity sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw== + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -7920,6 +7982,16 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" +scroll@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scroll/-/scroll-3.0.1.tgz#d5afb59fb3592ee3df31c89743e78b39e4cd8a26" + integrity sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg== + +scrollparent@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/scrollparent/-/scrollparent-2.0.1.tgz#715d5b9cc57760fb22bdccc3befb5bfe06b1a317" + integrity sha512-HSdN78VMvFCSGCkh0oYX/tY4R3P1DW61f8+TeZZ4j2VLgfwvw0bpRSOv4PCVKisktIwbzHCfZsx+rLbbDBqIBA== + semver@7.3.8, semver@^7.3.2, semver@^7.3.7: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" @@ -8477,6 +8549,14 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-changes@^0.9.1, tree-changes@^0.9.2: + version "0.9.3" + resolved "https://registry.yarnpkg.com/tree-changes/-/tree-changes-0.9.3.tgz#89433ab3b4250c2910d386be1f83912b7144efcc" + integrity sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ== + dependencies: + "@gilbarbara/deep-equal" "^0.1.1" + is-lite "^0.8.2" + trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"