mirror of
https://github.com/strapi/strapi.git
synced 2025-08-14 03:34:53 +00:00
Guided tour/stepper (#12082)
* feat: added guided tour context, hook and provider to helper-plugin * feat(guided-tour): using helpers in the admin + init reducer Co-authored-by: ronronscelestes <ronronscelestes@users.noreply.github.com> * feedback fixes * created data structure for guided tour state and content Co-authored-by: Vincent <vincentbpro@users.noreply.github.com> * removed closed leaf key from initialState * init guided tour home component * feedback fix fontWeight prop * Stepper, StepNumber, StepLine, Step * wip adding Stepper to homepage * WIP: homepage stepper Co-authored-by: ronronscelestes <ronronscelestes@users.noreply.github.com> * fixed grid aligment StepHomepage * feat: added homepage stepper Co-authored-by: ronronscelestes <ronronscelestes@users.noreply.github.com> * changed StepLine color if isNotDone + added useGuidedTour mock and updated snapshort for homepage tests * removed guided tour component from homepage until we add local storage and user role detection in the next PR * removed changed from homepage snapshots Co-authored-by: ronronscelestes <ronronscelestes@users.noreply.github.com> Co-authored-by: ronronscelestes <julie.plantey@gmail.com> Co-authored-by: Vincent <vincentbpro@users.noreply.github.com>
This commit is contained in:
parent
ccd92c08ad
commit
81e1fb2de9
@ -1,35 +1,40 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { useGuidedTour } from '@strapi/helper-plugin';
|
import { useGuidedTour } from '@strapi/helper-plugin';
|
||||||
import { Box } from '@strapi/design-system/Box';
|
import { useIntl } from 'react-intl';
|
||||||
import { Stack } from '@strapi/design-system/Stack';
|
import { Stack } from '@strapi/design-system/Stack';
|
||||||
import { Typography } from '@strapi/design-system/Typography';
|
import { Typography } from '@strapi/design-system/Typography';
|
||||||
import { LinkButton } from '@strapi/design-system/LinkButton';
|
import { LinkButton } from '@strapi/design-system/LinkButton';
|
||||||
|
import ArrowRight from '@strapi/icons/ArrowRight';
|
||||||
|
import StepperHomepage from '../Stepper/Homepage/StepperHomepage';
|
||||||
import layout from '../layout';
|
import layout from '../layout';
|
||||||
|
|
||||||
const GuidedTourHomepage = () => {
|
const GuidedTourHomepage = () => {
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { guidedTourState } = useGuidedTour();
|
const { guidedTourState } = useGuidedTour();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const sections = Object.entries(layout).map(([key, val]) => ({ key, ...val.home }));
|
const sections = Object.entries(layout).map(([key, val]) => ({
|
||||||
|
key,
|
||||||
|
title: val.home.title,
|
||||||
|
content: (
|
||||||
|
<LinkButton to={val.home.cta.target} endIcon={<ArrowRight />}>
|
||||||
|
{formatMessage(val.home.cta.title)}
|
||||||
|
</LinkButton>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
const enrichedSections = sections.map(section => ({
|
const enrichedSections = sections.map(section => ({
|
||||||
isDone: Object.entries(guidedTourState[section.key]).every(([, value]) => value),
|
isDone: Object.entries(guidedTourState[section.key]).every(([, value]) => value),
|
||||||
...section,
|
...section,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const activeSection = enrichedSections.find(section => !section.isDone).key;
|
const activeSection = enrichedSections.find(section => !section.isDone)?.key;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack size={5}>
|
<Stack size={6} hasRadius shadow="tableShadow" padding={7} background="neutral0">
|
||||||
{enrichedSections.map(section => (
|
<Typography variant="beta" as="h2">
|
||||||
<Box hasRadius shadow="tableShadow" background="neutral0" padding={5} key={section.key}>
|
Guided tour
|
||||||
<Typography>{formatMessage(section.title)}</Typography>
|
</Typography>
|
||||||
{section.key === activeSection && (
|
<StepperHomepage sections={sections} currentSectionKey={activeSection} />
|
||||||
<LinkButton to={section.cta.target}>{formatMessage(section.cta.title)}</LinkButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { pxToRem } from '@strapi/helper-plugin';
|
||||||
|
import { Typography } from '@strapi/design-system/Typography';
|
||||||
|
import { Box } from '@strapi/design-system/Box';
|
||||||
|
import StepNumber from '../StepNumber';
|
||||||
|
import StepLine from '../StepLine';
|
||||||
|
|
||||||
|
const GridItemAlignCenter = styled(Box)`
|
||||||
|
align-self: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const GridItemJustifyCenter = styled(Box)`
|
||||||
|
justify-self: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StepHomepage = ({ type, title, number, content, hasLine }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box>
|
||||||
|
<StepNumber type={type} number={number} />
|
||||||
|
</Box>
|
||||||
|
<GridItemAlignCenter>
|
||||||
|
<Typography variant="delta" as="h3">
|
||||||
|
{formatMessage(title)}
|
||||||
|
</Typography>
|
||||||
|
</GridItemAlignCenter>
|
||||||
|
<GridItemJustifyCenter height="100%">
|
||||||
|
{hasLine && <StepLine type={type} minHeight={pxToRem(64)} />}
|
||||||
|
</GridItemJustifyCenter>
|
||||||
|
<Box>{type === 'isActive' && content}</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StepHomepage.defaultProps = {
|
||||||
|
content: undefined,
|
||||||
|
number: undefined,
|
||||||
|
type: 'isNotDone',
|
||||||
|
hasLine: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
StepHomepage.propTypes = {
|
||||||
|
content: PropTypes.node,
|
||||||
|
number: PropTypes.number,
|
||||||
|
title: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
defaultMessage: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
type: PropTypes.oneOf(['isActive', 'isDone', 'isNotDone']),
|
||||||
|
hasLine: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepHomepage;
|
@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { pxToRem } from '@strapi/helper-plugin';
|
||||||
|
import { Grid } from '@strapi/design-system/Grid';
|
||||||
|
import StepHomepage from './StepHomepage';
|
||||||
|
|
||||||
|
const GridCustom = styled(Grid)`
|
||||||
|
gap: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[4]}`};
|
||||||
|
grid-template-columns: ${pxToRem(30)} 1fr;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const getType = (activeSectionIndex, index) => {
|
||||||
|
if (activeSectionIndex === -1) {
|
||||||
|
return 'isDone';
|
||||||
|
}
|
||||||
|
if (index < activeSectionIndex) {
|
||||||
|
return 'isDone';
|
||||||
|
}
|
||||||
|
if (index > activeSectionIndex) {
|
||||||
|
return 'isNotDone';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'isActive';
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepperHomepage = ({ sections, currentSectionKey }) => {
|
||||||
|
const activeSectionIndex = sections.findIndex(section => section.key === currentSectionKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridCustom>
|
||||||
|
{sections.map((section, index) => (
|
||||||
|
<StepHomepage
|
||||||
|
key={section.key}
|
||||||
|
title={section.title}
|
||||||
|
content={section.content}
|
||||||
|
number={index + 1}
|
||||||
|
type={getType(activeSectionIndex, index)}
|
||||||
|
hasLine={index !== sections.length - 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</GridCustom>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StepperHomepage.defaultProps = {
|
||||||
|
currentSectionKey: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
StepperHomepage.propTypes = {
|
||||||
|
sections: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
key: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
defaultMessage: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
content: PropTypes.node,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
currentSectionKey: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepperHomepage;
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { pxToRem } from '@strapi/helper-plugin';
|
||||||
|
import { Box } from '@strapi/design-system/Box';
|
||||||
|
|
||||||
|
const StepLine = ({ type, ...props }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
width={pxToRem(2)}
|
||||||
|
height="100%"
|
||||||
|
background={type === 'isNotDone' ? 'neutral300' : 'primary500'}
|
||||||
|
hasRadius
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StepLine.defaultProps = {
|
||||||
|
type: 'isNotDone',
|
||||||
|
};
|
||||||
|
|
||||||
|
StepLine.propTypes = {
|
||||||
|
type: PropTypes.oneOf(['isActive', 'isDone', 'isNotDone']),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepLine;
|
@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { pxToRem } from '@strapi/helper-plugin';
|
||||||
|
import { Flex } from '@strapi/design-system/Flex';
|
||||||
|
import { Typography } from '@strapi/design-system/Typography';
|
||||||
|
import { Icon } from '@strapi/design-system/Icon';
|
||||||
|
import Check from '@strapi/icons/Check';
|
||||||
|
|
||||||
|
const StepNumber = ({ type, number }) => {
|
||||||
|
if (type === 'isDone') {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
background="primary600"
|
||||||
|
padding={2}
|
||||||
|
borderRadius="50%"
|
||||||
|
width={pxToRem(30)}
|
||||||
|
height={pxToRem(30)}
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Icon as={Check} aria-hidden width="16px" color="neutral0" />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'isActive') {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
background="primary600"
|
||||||
|
padding={2}
|
||||||
|
borderRadius="50%"
|
||||||
|
width={pxToRem(30)}
|
||||||
|
height={pxToRem(30)}
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Typography fontWeight="semiBold" textColor="neutral0">
|
||||||
|
{number}
|
||||||
|
</Typography>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
borderColor="neutral500"
|
||||||
|
borderWidth="1px"
|
||||||
|
borderStyle="solid"
|
||||||
|
padding={2}
|
||||||
|
borderRadius="50%"
|
||||||
|
width={pxToRem(30)}
|
||||||
|
height={pxToRem(30)}
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Typography fontWeight="semiBold" textColor="neutral600">
|
||||||
|
{number}
|
||||||
|
</Typography>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StepNumber.defaultProps = {
|
||||||
|
number: undefined,
|
||||||
|
type: 'isNotDone',
|
||||||
|
};
|
||||||
|
|
||||||
|
StepNumber.propTypes = {
|
||||||
|
number: PropTypes.number,
|
||||||
|
type: PropTypes.oneOf(['isActive', 'isDone', 'isNotDone']),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepNumber;
|
@ -7,6 +7,26 @@ import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
|||||||
import HomePage from '../index';
|
import HomePage from '../index';
|
||||||
import { useModels } from '../../../hooks';
|
import { useModels } from '../../../hooks';
|
||||||
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
|
useGuidedTour: jest.fn(() => ({
|
||||||
|
guidedTourState: {
|
||||||
|
apiTokens: {
|
||||||
|
create: false,
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
contentManager: {
|
||||||
|
create: false,
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
contentTypeBuilder: {
|
||||||
|
create: false,
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('../../../hooks', () => ({
|
jest.mock('../../../hooks', () => ({
|
||||||
useModels: jest.fn(),
|
useModels: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user