Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 31 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@

## Features

- Hooks
- Simplicity
- Focused on logic
- No UI restrictions
- Written in TypeScript
- Documented, self explaining methods

## Installation

Expand Down Expand Up @@ -72,25 +71,26 @@ const Step1 = () => {

### Wizard

`Wizard` is used to wrap your steps. Each child component will be treated as an individual step. You can pass a shared `footer` and `header` component that should always be in your steps.
`Wizard` is used to wrap your steps. Each child component will be treated as an individual step. You can pass a shared `footer` and `header` component that should always be in your steps.

Example: pass a footer component that contains a "previous" and "next" button to the wizard.

#### Props

| name | type | description | required | default |
| ---------- | --------------- | ------------------------------------------------------------- | -------- | ------- |
| 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 | ❌ | |
| children | React.ReactNode | Each child component will be treated as an individual step | ✔️ |
| name | type | description | required | default |
| ---------- | --------------- | ---------------------------------------------------------- | -------- | ------- |
| 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 | ❌ | |
| children | React.ReactNode | Each child component will be treated as an individual step | ✔️ |

#### Example

```javascript
// Example: show the active step in this component
// Example: show the active step in the header
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>;

const App = () => {
Expand All @@ -106,22 +106,24 @@ const App = () => {

### useWizard

Used to retrieve all methods and properties related to your wizard. Make sure `Wizard` is wrapped around your component when calling `useWizard`.
Used to retrieve all methods and properties related to your wizard. Make sure `Wizard` is wrapped around your component when calling `useWizard`.

`handleStep` is used to attach a handler to the step, can either be `async` or a `sync` function. This function will be invoked when calling `nextStep`.

**Remark** - You can't use `useWizard` in the same component where `Wizard` is used.

#### Methods

| name | type | description |
| ----------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------- |
| nextStep | () => Promise<void> | Go to the next step |
| previousStep | () => void | Go to the previous step |
| handleStep | (handler: Handler) => void | Attach a callback that will be called when calling `nextStep`. `handler` can be either sync or async |
| isLoading | boolean | \* Will reflect the handler promise state: will be `true` if the handler promise is pending and `false` when the handler is either fulfilled or rejected |
| activeStep | number | The current active step of the wizard |
| isFirstStep | boolean | Indicate if the current step is the first step (aka no previous step) |
| isLastStep | boolean | Indicate if the current step is the last step (aka no next step) |
| |
| name | type | description |
| ------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| nextStep | () => Promise<void> | Go to the next step |
| previousStep | () => void | Go to the previous step |
| handleStep | (handler: Handler) => void | Attach a callback that will be called when calling `nextStep`. `handler` can be either sync or async |
| isLoading | boolean | \* Will reflect the handler promise state: will be `true` if the handler promise is pending and `false` when the handler is either fulfilled or rejected |
| activeStep | number | The current active step of the wizard |
| isFirstStep | boolean | Indicate if the current step is the first step (aka no previous step) |
| isLastStep | boolean | Indicate if the current step is the last step (aka no next step) |
| |

#### Example

Expand Down Expand Up @@ -149,6 +151,7 @@ const Step1 = () => {
handleStep,
} = useWizard();

// This handler is optional
handleStep(() => {
alert('Going to step 2');
});
Expand All @@ -173,6 +176,7 @@ const Step1 = () => {
It's recommended to pass the shared components to the `header` or `footer` in the `Wizard` to avoid duplication.

## Examples

Go to [examples](https://github.com/devrnt/react-use-wiard/tree/master/examples) to check see some examples

## Async
Expand Down Expand Up @@ -200,14 +204,17 @@ const Step1 = () => {
```

### Errors

If no errors are thrown then the wizard will go to the next step, so no need to call `nextStep` by yourself.

If an error is thrown in the conencted function the wizard will just stay at the same step and will rethrow the error. (So you can try-catch in your attached function).
If an error is thrown in the attached function the wizard will just stay at the same step and will rethrow the error. (So you can try-catch in your attached function).

### IsLoading

If an async function is attached to `handleStep` the `isLoading` property will indicate the loading state of the function. In general `isLoading` will reflect the handler promise state: will be `true` if the handler promise is pending and `false` when the handler is either fulfilled or rejected.

## Animation
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/docs/readme/example/components/animatedStep.tsx) to see how a step can be animated with framer motion.
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/docs/readme/example/components/animatedStep.tsx) to see how a step can be animated with framer motion.
2 changes: 1 addition & 1 deletion playground/components/asyncStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Container = styled('div')`
`;

const P = styled('p')`
color: white;
color: var(--text);
`;

const AsyncStep: React.FC<Props> = React.memo(({ number }) => {
Expand Down
20 changes: 17 additions & 3 deletions playground/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,31 @@ import { useWizard } from '../../dist';
import { Button } from '../modules/common';

const Actions = styled('div')`
display: grid;
display: flex;
justify-content: center;
margin: 1rem 0;
grid-template-columns: min-content min-content;
gap: 1rem;
flex-direction: row;
`;

const Info = styled('div')`
display: flex;
justify-content: center;
gap: 1rem;
flex-direction: column;
gap: 0;

& > p {
margin: 0.25rem 0;
}

@media screen and (min-width: 600px) {
flex-direction: row;
gap: 1rem;

& > p {
margin: initial;
}
}
`;

const Footer: React.FC = React.memo(() => {
Expand Down
2 changes: 1 addition & 1 deletion playground/components/step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Container = styled('div')`
`;

const P = styled('p')`
color: white;
color: var(--text);
`;

const Step: React.FC<Props> = React.memo(({ number, withCallback = true }) => {
Expand Down
3 changes: 2 additions & 1 deletion playground/modules/common/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Container = styled('button')`
border: 1px solid var(--purple);
padding: 0.7rem 1.75rem;
border-radius: 6px;
color: white;
color: var(--text);
font-size: 1.1rem;
font-weight: 700;
background-color: var(--dark);
Expand All @@ -27,6 +27,7 @@ const Container = styled('button')`
&:disabled {
opacity: 0.4;
background-image: initial;
cursor: initial;
}
`;

Expand Down
30 changes: 23 additions & 7 deletions playground/modules/common/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Container = styled('main')`
display: flex;
justify-content: center;
background: var(--dark);
overflow: hidden;
`;

const Wrapper = styled('div')`
Expand All @@ -27,29 +28,35 @@ const Body = styled('div')`
const Description = styled('div')`
font-size: 1.1rem;
font-weight: 200;
line-height: 1.5rem;
`;

const Divider = styled('div')`
height: 2px;
width: 7%;
width: 15%;
background-image: linear-gradient(48.66deg, var(--purple), var(--blue));
margin: 2.5rem 0;
margin: 2.5rem auto;
display: flex;

@media screen and (min-width: 800px) {
max-width: 7%;
}
`;

const TopBar = styled('header')`
position: sticky;
top: 0;
left: 0;
right: 0;
padding: 1.5rem 1.75rem;
padding: 1.75rem 0;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4rem;
margin-bottom: 5rem;
background-color: var(--nav);
backdrop-filter: blur(20px);
z-index: 1000;
width: 100%;
`;

const Logo = styled('img')`
Expand All @@ -62,11 +69,19 @@ const GithubLogo = styled('img')`
`;

const MaxWidth = styled('div')`
max-width: 53rem;
width: 100%;
padding: 0 2rem;
margin: 0 auto;
justify-content: space-between;
width: 100%;

@media screen and (min-width: 800px) {
max-width: 53rem;
padding: 0 2rem;
}
`;

const H1 = styled('h1')`
font-size: 2.25rem;
`;

const Page = ({ title, description, children }: Props) => {
Expand All @@ -77,6 +92,7 @@ const Page = ({ title, description, children }: Props) => {
<MaxWidth
style={{
display: 'flex',
alignItems: 'center',
}}
>
<Logo src={logoPath} />
Expand All @@ -90,7 +106,7 @@ const Page = ({ title, description, children }: Props) => {
</MaxWidth>
</TopBar>
<MaxWidth>
<h1>{title}</h1>
<H1>{title}</H1>
<Description>{description}</Description>
<Divider />
<Body>{children}</Body>
Expand Down
9 changes: 7 additions & 2 deletions playground/modules/common/style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ const GlobalStyle = createGlobalStyles`
--blue: #08D8F4;
--code:#260949;
--step:#170231;
--text: #cccccc;
}

* {
box-sizing: border-box;
}

html {
Expand All @@ -31,7 +36,7 @@ const GlobalStyle = createGlobalStyles`
body {
font-family: 'Inter', Helvetica, sans-serif;
text-rendering: optimizeLegibility;
color: white;
color: var(--text);
}

i {
Expand All @@ -44,7 +49,7 @@ const GlobalStyle = createGlobalStyles`
border-radius: 2px;
font-family: 'Fira Code', monospace;
font-size: 0.95rem;
padding: 0.75rem 0.35rem;
padding: 0.75rem 1rem;
}
`;

Expand Down
46 changes: 32 additions & 14 deletions playground/modules/wizard/wizard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { styled } from 'goober';
import { css, styled } from 'goober';
import * as React from 'react';

import { Wizard } from '../../../dist';
import { AnimatedStep, AsyncStep, Footer, Step } from '../../components';

const Grid = styled('section')`
display: grid;
grid-template-columns: repeat(1, 1fr);
const Container = styled('section')`
display: flex;
flex-direction: column;
width: 100%;
`;

Expand All @@ -21,27 +21,45 @@ const Title = styled('h2')`
}
`;

const Item = styled('div')`
display: grid;
grid-template-rows: min-content;
const Item = styled('div')<{ showDivider: boolean }>`
display: flex;
flex-direction: column;

&::after {
content: '';
margin: 3rem 0 2rem;
content: '';
background-image: linear-gradient(48.66deg, var(--purple), var(--blue));
width: 100%;
position: relative;
height: 1px;
height: ${({ showDivider }) => (showDivider ? '1px' : 0)};
}

${({ showDivider }) =>
showDivider
? `
&::after {
margin: 3rem 0 2rem;
content: '';
background-image: linear-gradient(
48.66deg,
var(--purple),
var(--blue)
);
width: 100%;
position: relative;
height: 1px;
}
`
: ''}
`;

const WizardModule = () => {
return (
<Grid>
<Container>
<Title>
Simple wizard <span>mix of async and sync steps</span>
</Title>
<Item>
<Item showDivider>
<Wizard footer={<Footer />}>
<AsyncStep number={1} />
<Step number={2} />
Expand All @@ -51,9 +69,9 @@ const WizardModule = () => {
</Item>

<Title>
Animated wizard <span>animations by framer motion</span>
Animated wizard <span>animation by framer motion</span>
</Title>
<Item>
<Item showDivider={false}>
<Wizard footer={<Footer />}>
<AnimatedStep>
<Step number={1} withCallback={false} />
Expand All @@ -69,7 +87,7 @@ const WizardModule = () => {
</AnimatedStep>
</Wizard>
</Item>
</Grid>
</Container>
);
};

Expand Down