mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
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
This commit is contained in:
parent
35d354d2b6
commit
6c79c790a9
@ -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"
|
||||
|
@ -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 = () => {
|
||||
|
||||
<FeedbackNPS openUrl="http://feedback.unleash.run" />
|
||||
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
uiConfig.flags.demo
|
||||
)}
|
||||
show={<Demo />}
|
||||
/>
|
||||
|
||||
<SplashPageRedirect />
|
||||
</StyledContainer>
|
||||
</>
|
||||
|
236
frontend/src/component/demo/Demo.tsx
Normal file
236
frontend/src/component/demo/Demo.tsx
Normal file
@ -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: (
|
||||
<Typography fontWeight="bold">
|
||||
Import toggle configuration
|
||||
</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
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.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'New feature toggle',
|
||||
steps: [
|
||||
{
|
||||
target: 'button[data-testid="NAVIGATE_TO_CREATE_FEATURE"]',
|
||||
title: (
|
||||
<Typography fontWeight="bold">
|
||||
Add a new feature toggle
|
||||
</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
You can use this button to add a new feature toggle.
|
||||
This is just an example and not part of the final
|
||||
guide.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Enable/disable a feature toggle',
|
||||
steps: [
|
||||
{
|
||||
target: '.MuiSwitch-sizeMedium',
|
||||
title: (
|
||||
<Typography fontWeight="bold">
|
||||
Enable/disable a feature toggle
|
||||
</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
The simplest way to use a feature toggle is to
|
||||
enable or disable it for everyone (on/off).
|
||||
</Typography>
|
||||
<Badge
|
||||
sx={{ marginTop: 2 }}
|
||||
icon={<InfoOutlinedIcon />}
|
||||
>
|
||||
Look at the demo page when toggling!
|
||||
</Badge>
|
||||
</>
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
steps: [
|
||||
{
|
||||
target: 'a[href="https://twitter.com/getunleash"]',
|
||||
title: (
|
||||
<Typography fontWeight="bold">
|
||||
Follow us on Twitter!
|
||||
</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
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.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
{
|
||||
target: 'a[href="https://www.linkedin.com/company/getunleash"]',
|
||||
title: (
|
||||
<Typography fontWeight="bold">
|
||||
Follow us on LinkedIn!
|
||||
</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
You can also follow us LinkedIn. This is just an
|
||||
example and not part of the final guide.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
{
|
||||
target: 'a[href="https://github.com/Unleash/unleash"]',
|
||||
title: (
|
||||
<Typography fontWeight="bold">
|
||||
Check out Unleash on GitHub!
|
||||
</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Unleash is open-source, check out the project on
|
||||
GitHub. This is just an example and not part of the
|
||||
final guide.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
{
|
||||
target: 'a[href="https://slack.unleash.run"]',
|
||||
title: (
|
||||
<Typography fontWeight="bold">Join us on Slack!</Typography>
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Join our community in Slack. This is just an example
|
||||
and not part of the final guide.
|
||||
</Typography>
|
||||
</>
|
||||
),
|
||||
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 (
|
||||
<>
|
||||
<DemoTopics
|
||||
expanded={expanded}
|
||||
setExpanded={setExpanded}
|
||||
steps={steps}
|
||||
currentTopic={topic}
|
||||
setCurrentTopic={(topic: number) => {
|
||||
setTopic(topic);
|
||||
setSteps(steps => {
|
||||
const newSteps = [...steps];
|
||||
newSteps[topic] = 0;
|
||||
return newSteps;
|
||||
});
|
||||
}}
|
||||
topics={TOPICS}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={loaded}
|
||||
show={
|
||||
<DemoSteps
|
||||
run={run}
|
||||
setRun={setRun}
|
||||
setExpanded={setExpanded}
|
||||
steps={steps}
|
||||
setSteps={setSteps}
|
||||
topic={topic}
|
||||
setTopic={setTopic}
|
||||
topics={TOPICS}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
232
frontend/src/component/demo/DemoSteps/DemoSteps.tsx
Normal file
232
frontend/src/component/demo/DemoSteps/DemoSteps.tsx
Normal file
@ -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<React.SetStateAction<boolean>>;
|
||||
setExpanded: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
steps: number[];
|
||||
setSteps: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
topic: number;
|
||||
setTopic: React.Dispatch<React.SetStateAction<number>>;
|
||||
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 (
|
||||
<Joyride
|
||||
run={run}
|
||||
stepIndex={steps[topic]}
|
||||
callback={joyrideCallback}
|
||||
steps={topics[topic].steps}
|
||||
disableScrolling
|
||||
disableOverlayClose
|
||||
spotlightClicks
|
||||
floaterProps={{
|
||||
disableAnimation: true,
|
||||
styles: {
|
||||
floater: {
|
||||
filter: `drop-shadow(${theme.palette.primary.main} 0px 0px 3px)`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
styles={{
|
||||
options: {
|
||||
arrowColor: theme.palette.background.paper,
|
||||
zIndex: theme.zIndex.snackbar,
|
||||
},
|
||||
spotlight: {
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
border: `2px solid ${theme.palette.primary.main}`,
|
||||
outline: `2px solid ${theme.palette.secondary.border}`,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
overlay: {
|
||||
backgroundColor: 'transparent',
|
||||
mixBlendMode: 'unset',
|
||||
},
|
||||
}}
|
||||
tooltipComponent={({
|
||||
step,
|
||||
primaryProps,
|
||||
tooltipProps,
|
||||
}: TooltipRenderProps) => {
|
||||
const { onClick } = primaryProps;
|
||||
|
||||
return (
|
||||
<StyledTooltip {...tooltipProps}>
|
||||
<StyledTooltipTitle>
|
||||
{step.title}
|
||||
<ConditionallyRender
|
||||
condition={topics[topic].steps.length > 1}
|
||||
show={
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
flexShrink={0}
|
||||
>
|
||||
(step {steps[topic] + 1} of{' '}
|
||||
{topics[topic].steps.length})
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</StyledTooltipTitle>
|
||||
{step.content}
|
||||
<StyledTooltipActions>
|
||||
<Button variant="text" onClick={skip}>
|
||||
Skip
|
||||
</Button>
|
||||
<StyledTooltipPrimaryActions>
|
||||
<ConditionallyRender
|
||||
condition={topic > 0 || steps[topic] > 0}
|
||||
show={
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={back}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Button onClick={onClick} variant="contained">
|
||||
{topic === topics.length - 1 &&
|
||||
steps[topic] ===
|
||||
topics[topic].steps.length - 1
|
||||
? 'Finish'
|
||||
: 'Next'}
|
||||
</Button>
|
||||
</StyledTooltipPrimaryActions>
|
||||
</StyledTooltipActions>
|
||||
</StyledTooltip>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
210
frontend/src/component/demo/DemoTopics/DemoTopics.tsx
Normal file
210
frontend/src/component/demo/DemoTopics/DemoTopics.tsx
Normal file
@ -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<React.SetStateAction<boolean>>;
|
||||
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 (
|
||||
<StyledAccordion
|
||||
expanded={expanded}
|
||||
onChange={() => setExpanded(expanded => !expanded)}
|
||||
>
|
||||
<StyledAccordionSummary>
|
||||
<StyledTitle>
|
||||
<Typography fontWeight="bold">Unleash tutorial</Typography>
|
||||
<StyledExpandMoreIcon className="expand-icon" />
|
||||
</StyledTitle>
|
||||
<StyledSubtitle>
|
||||
Complete all steps to finish tutorial
|
||||
</StyledSubtitle>
|
||||
<StyledProgress>
|
||||
<Typography variant="body2">
|
||||
{percentage.toFixed()}%
|
||||
</Typography>
|
||||
<StyledLinearProgress
|
||||
variant="determinate"
|
||||
value={percentage}
|
||||
/>
|
||||
</StyledProgress>
|
||||
</StyledAccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography variant="body2" paddingTop={1}>
|
||||
The steps will guide you
|
||||
</Typography>
|
||||
{topics.map((topic, index) => {
|
||||
const selected = currentTopic === index;
|
||||
const completed = steps[index] === topic.steps.length;
|
||||
return (
|
||||
<StyledStep
|
||||
key={topic.title}
|
||||
onClick={() => setCurrentTopic(index)}
|
||||
selected={selected}
|
||||
completed={completed}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={completed}
|
||||
show={<StyledCheckCircle />}
|
||||
elseShow={<StyledCircleOutlined />}
|
||||
/>
|
||||
<Typography variant="body2" sx={{ flex: 1 }}>
|
||||
{topic.title}
|
||||
</Typography>
|
||||
<StyledStepIcon />
|
||||
</StyledStep>
|
||||
);
|
||||
})}
|
||||
<StyledButton variant="outlined">
|
||||
View demo link again
|
||||
</StyledButton>
|
||||
</AccordionDetails>
|
||||
</StyledAccordion>
|
||||
);
|
||||
};
|
@ -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(
|
||||
<UIProviderContainer>
|
||||
<AccessProvider>
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user