mirror of
https://github.com/strapi/strapi.git
synced 2025-08-10 17:58:07 +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 { useIntl } from 'react-intl';
|
||||
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 { Typography } from '@strapi/design-system/Typography';
|
||||
import { LinkButton } from '@strapi/design-system/LinkButton';
|
||||
import ArrowRight from '@strapi/icons/ArrowRight';
|
||||
import StepperHomepage from '../Stepper/Homepage/StepperHomepage';
|
||||
import layout from '../layout';
|
||||
|
||||
const GuidedTourHomepage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
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 => ({
|
||||
isDone: Object.entries(guidedTourState[section.key]).every(([, value]) => value),
|
||||
...section,
|
||||
}));
|
||||
|
||||
const activeSection = enrichedSections.find(section => !section.isDone).key;
|
||||
const activeSection = enrichedSections.find(section => !section.isDone)?.key;
|
||||
|
||||
return (
|
||||
<Stack size={5}>
|
||||
{enrichedSections.map(section => (
|
||||
<Box hasRadius shadow="tableShadow" background="neutral0" padding={5} key={section.key}>
|
||||
<Typography>{formatMessage(section.title)}</Typography>
|
||||
{section.key === activeSection && (
|
||||
<LinkButton to={section.cta.target}>{formatMessage(section.cta.title)}</LinkButton>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
<Stack size={6} hasRadius shadow="tableShadow" padding={7} background="neutral0">
|
||||
<Typography variant="beta" as="h2">
|
||||
Guided tour
|
||||
</Typography>
|
||||
<StepperHomepage sections={sections} currentSectionKey={activeSection} />
|
||||
</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 { 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', () => ({
|
||||
useModels: jest.fn(),
|
||||
}));
|
||||
|
Loading…
x
Reference in New Issue
Block a user