Skip to content

Commit

Permalink
Merge pull request #86 from devrnt/feat/wizard-wrapper
Browse files Browse the repository at this point in the history
Feat/wizard wrapper
  • Loading branch information
devrnt authored Apr 15, 2022
2 parents b5abeb7 + f435bce commit 7b343d7
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 68 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
}
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Example: pass a footer component that contains a "previous" and "next" button to
| startIndex | number | Indicate the wizard to start at the given step || 0 |
| header | React.ReactNode | Header that is shown above the active step || |
| footer | React.ReactNode | Footer that is shown below the active stepstep || |
| wrapper | React.React.ReactElement | Optional wrapper that is exclusively wrapped around the active step component. It is not wrapped around the `header` and `footer` || |
| children | React.ReactNode | Each child component will be treated as an individual step | ✔️ |

#### Example
Expand All @@ -95,9 +96,17 @@ const Header = () => <p>I am the header component</p>;
// Example: show the "previous" and "next" buttons in the footer
const Footer = () => <p>I am the footer component</p>;

// Example: Wrap steps in an `<AnimatePresence` from framer-motion
const Wrapper = () => <AnimatePresence exitBeforeEnter />;

const App = () => {
return (
<Wizard startIndex={0} header={<Header />} footer={<Footer />}>
<Wizard
startIndex={0}
header={<Header />}
footer={<Footer />}
wrapper={<Wrapper />}
>
<Step1 />
<Step2 />
<Step3 />
Expand Down Expand Up @@ -236,7 +245,7 @@ If an async function is attached to `handleStep` the `isLoading` property will i

Since `react-use-wizard` is focused to manage the logic of a wizard it doesn't mean you can't add some animation by your own. Add any animation library that you like. I highly suggest [framer-motion](https://www.framer.com/motion/) to add your animations.

Checkout this [example](/~https://github.com/devrnt/react-use-wizard/blob/main/playground/components/animatedStep.tsx) to see how a step can be animated with framer motion.
Checkout this [example](/~https://github.com/devrnt/react-use-wizard/tree/feat/wizard-wrapper/playground/modules/wizard/animated) to see how a step can be animated with framer motion.

## IE11

Expand Down
58 changes: 36 additions & 22 deletions playground/components/animatedStep.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { motion } from 'framer-motion';
import { motion, Variants } from 'framer-motion';
import * as React from 'react';

import { useWizard } from '../../dist';

const variants = {
const variants: Variants = {
enter: (direction: number) => {
return {
x: direction < 0 ? 1000 : -1000,
x: direction > 0 ? 800 : -800,
opacity: 0,
};
},
Expand All @@ -15,32 +15,46 @@ const variants = {
x: 0,
opacity: 1,
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 800 : -800,
opacity: 0,
};
},
};

type Props = {
previousStep: React.MutableRefObject<number>;
};

const AnimatedStep: React.FC<Props> = ({
children,
previousStep: previousStepIndex,
}) => {
const { activeStep } = useWizard();
const AnimatedStep: React.FC<Props> = React.memo(
({ children, previousStep: previousStepIndex }) => {
const { activeStep } = useWizard();

React.useEffect(() => {
previousStepIndex.current = activeStep;
}, [activeStep, previousStepIndex]);
React.useEffect(() => {
return () => {
previousStepIndex.current = activeStep;
};
}, [activeStep, previousStepIndex]);

return (
<motion.div
custom={activeStep - previousStepIndex.current}
variants={variants}
initial="enter"
animate="center"
>
{children}
</motion.div>
);
};
return (
<motion.div
custom={activeStep - previousStepIndex.current}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
type: 'spring',
stiffness: 300,
damping: 30,
}}
>
{children}
</motion.div>
);
},
);

export default AnimatedStep;
27 changes: 14 additions & 13 deletions playground/modules/wizard/animated/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ const AnimatedSection: React.FC = () => {

return (
<Section title="Animated wizard" description="animation by framer motion">
<AnimatePresence>
<Wizard footer={<Footer />}>
{Array(4)
.fill(null)
.map((_, index) => {
return (
<AnimatedStep key={index} previousStep={previousStep}>
<Step number={index + 1} withCallback={false}></Step>
</AnimatedStep>
);
})}
</Wizard>
</AnimatePresence>
<Wizard
footer={<Footer />}
wrapper={<AnimatePresence initial={false} exitBeforeEnter />}
>
{Array(4)
.fill(null)
.map((_, index) => {
return (
<AnimatedStep key={index} previousStep={previousStep}>
<Step number={index + 1} withCallback={false}></Step>
</AnimatedStep>
);
})}
</Wizard>
</Section>
);
};
Expand Down
2 changes: 1 addition & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build": "parcel build index.html --public-url /react-use-wizard/"
},
"dependencies": {
"framer-motion": "^4.0.0",
"framer-motion": "^4.1.17",
"goober": "^2.0.35",
"react-app-polyfill": "^2.0.0",
"react-query": "^3.13.0"
Expand Down
53 changes: 25 additions & 28 deletions playground/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3270,23 +3270,25 @@ format@^0.2.0:
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=

framer-motion@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.0.0.tgz#3eab6d79e9a662ed8dfcfd05e680ba3a6b841672"
integrity sha512-j7lPnXEZdpkAelLRU8FhaZC3UbqnZwSTT/W9rSRwb3cKX4eWrekakK86cVkUE79v1HRE9IPzdPVNP9/e8CPRag==
framer-motion@^4.1.17:
version "4.1.17"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.17.tgz#4029469252a62ea599902e5a92b537120cc89721"
integrity sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==
dependencies:
framesync "5.2.0"
framesync "5.3.0"
hey-listen "^1.0.8"
popmotion "9.3.1"
style-value-types "4.1.1"
popmotion "9.3.6"
style-value-types "4.1.4"
tslib "^2.1.0"
optionalDependencies:
"@emotion/is-prop-valid" "^0.8.2"

framesync@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.2.0.tgz#f14480654cd05a6af4c72c9890cad93556841643"
integrity sha512-dcl92w5SHc0o6pRK3//VBVNvu6WkYkiXmHG6ZIXrVzmgh0aDYMDAaoA3p3LH71JIdN5qmhDcfONFA4Lmq22tNA==
framesync@5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
dependencies:
tslib "^2.1.0"

fs.realpath@^1.0.0:
version "1.0.0"
Expand Down Expand Up @@ -4554,15 +4556,15 @@ pn@^1.1.0:
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==

popmotion@9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.1.tgz#134319ed4b9b8e3ec506c99064f7b2f14bc05781"
integrity sha512-Qozvg8rz2OGeZwWuIjqlSXqqgWto/+QL24ll8sAAc0n71KY/wvN1W4sAASxTuHv8YWdDnk9u9IdadyPo2DGvDA==
popmotion@9.3.6:
version "9.3.6"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1"
integrity sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==
dependencies:
framesync "5.2.0"
framesync "5.3.0"
hey-listen "^1.0.8"
style-value-types "4.1.1"
tslib "^1.10.0"
style-value-types "4.1.4"
tslib "^2.1.0"

postcss-calc@^8.0.0:
version "8.0.0"
Expand Down Expand Up @@ -5512,13 +5514,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"

style-value-types@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.1.tgz#15d08bd9bb17ac3f5d4a863bf9d5e4eb8e4f0686"
integrity sha512-cNLrl6jk+I1T18ZI2KIp/fcqKVuykcNELDrOz7y+TYZR97xmNdN0ewupURvVFnQxVrRJv98TMBq92VMsggq3kw==
style-value-types@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
dependencies:
hey-listen "^1.0.8"
tslib "^1.10.0"
tslib "^2.1.0"

stylehacks@^5.0.1:
version "5.0.1"
Expand Down Expand Up @@ -5648,11 +5650,6 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"

tslib@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

tslib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ export type WizardProps = {
footer?: React.ReactNode;
/** Optional start index @default 0 */
startIndex?: number;
/**
* Optional wrapper that is exclusively wrapped around the active step component. It is not wrapped around the `header` and `footer`
*
* @example With `framer-motion` - `<AnimatePresence />`
* ```jsx
* <Wizard wrapper={<AnimatePresence exitBeforeEnter />}>
* ...
* </Wizard>
* ```
*/
wrapper?: React.ReactElement;
};

export type WizardValues = {
Expand Down
12 changes: 10 additions & 2 deletions src/wizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Handler, WizardProps } from './types';
import WizardContext from './wizardContext';

const Wizard: React.FC<React.PropsWithChildren<WizardProps>> = React.memo(
({ header, footer, children, startIndex = 0 }) => {
({ header, footer, children, wrapper: Wrapper, startIndex = 0 }) => {
const [activeStep, setActiveStep] = React.useState(startIndex);
const [isLoading, setIsLoading] = React.useState(false);
const hasNextStep = React.useRef(true);
Expand Down Expand Up @@ -111,10 +111,18 @@ const Wizard: React.FC<React.PropsWithChildren<WizardProps>> = React.memo(
return reactChildren[activeStep];
}, [activeStep, children, header, footer]);

const enhancedActiveStepContent = React.useMemo(
() =>
Wrapper
? React.cloneElement(Wrapper, { children: activeStepContent })
: activeStepContent,
[Wrapper, activeStepContent],
);

return (
<WizardContext.Provider value={wizardValue}>
{header}
{activeStepContent}
{enhancedActiveStepContent}
{footer}
</WizardContext.Provider>
);
Expand Down
13 changes: 13 additions & 0 deletions test/wizard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ const ComponentWithFooter = (
</Wizard>
);

const ComponentWithWrapper = (
<Wizard wrapper={<main />}>
<p>step 1</p>
<p>step 2</p>
</Wizard>
);

describe('Wizard', () => {
test('should render first step', () => {
const { queryByText } = render(Component);
Expand Down Expand Up @@ -60,4 +67,10 @@ describe('Wizard', () => {

expect(queryByText('footer')).toBeInTheDocument();
});

test('should render wrapper', () => {
const { queryByRole } = render(ComponentWithWrapper);

expect(queryByRole('main')).toBeInTheDocument();
});
});

0 comments on commit 7b343d7

Please sign in to comment.