Skip to content
Open
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
2 changes: 1 addition & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const parameters = {
['Select', ['Guideline', '*']],
],
['Feedback', [['Modal', ['Guideline', '*']]]],
'Progress & loading',
['Progress & loading', [['Stepper', ['Guideline', '*']]]],
'Styling',
'Deprecated',
],
Expand Down
60 changes: 60 additions & 0 deletions stories/stepper.guideline.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Meta, Canvas } from '@storybook/addon-docs/blocks';
import * as StepperStories from './stepper.stories';

<Meta of={StepperStories} name="Guideline" />

<style>{`
.sbdocs-content h2, .sbdocs-content h3 { margin-top: 3rem; }
.sbdocs-content .sb-story { margin-bottom: 0.5rem; }
.sbdocs-content .sbdocs-preview { margin-bottom: 0.5rem; }
`}</style>

# Stepper

A stepper breaks a process into ordered phases that must be completed in sequence.
Use it when each phase has a distinct nature and the next phase cannot begin before the previous one is done.

<Canvas of={StepperStories.SimpleStepper} sourceState="none" />

## When to use

- The task has 3 to 5 distinct phases with a clear sequence.
- Each phase requires its own decisions or inputs before moving on.
- Users benefit from knowing how far along they are.

## When not to use

- Steps can be completed in any order: use a checklist instead.
- The task is non-linear or revisitable at any time.

## Typical pattern

Steps do not need to be of the same nature. A common structure is:

1. **Form:** the user configures the operation (inputs, selects, options).
2. **Execution:** the system performs actions one by one, shown as a table of tasks with their status.
3. **Summary:** results are displayed and the user can confirm or exit.

Whether the user can navigate back to a previous step depends on the process. In a sequence of forms, going back is safe and expected. When a step involves system actions (API calls, data mutations), going back may not be possible or meaningful.

## Step states

Each step can carry one of the following states:

- **Pending:** not yet reached, shown with a numbered circle.
- **Active:** the current step, shown with a filled circle.
- **Completed:** passed successfully, shown with a green checkmark.
- **In progress:** the active step is waiting on an async operation, shown with a spinner.
- **Error:** the step failed, shown with a red circle.

**Completed steps** (steps 1 and 2 done, step 3 active):

<Canvas of={StepperStories.StateCompleted} sourceState="none" />

**In progress** (step 2 waiting on an async operation):

<Canvas of={StepperStories.StateInProgress} sourceState="none" />

**Error** (step 2 failed):

<Canvas of={StepperStories.StateError} sourceState="none" />
139 changes: 84 additions & 55 deletions stories/stepper.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {
Stepper,
useStepper,
} from '../src/lib/components/steppers/Stepper.component';
import { Steppers } from '../src/lib/components/steppers/Steppers.component';
import styled from 'styled-components';
import { Box } from '../src/lib/components/box/Box';
import { Stack } from '../src/lib/spacing';
import { Button } from '../src/lib/components/buttonv2/Buttonv2.component';
import { Text } from '../src/lib/components/text/Text.component';
import { Wrapper as StoryWrapper } from './common';
Expand All @@ -16,26 +15,39 @@ const Wrapper = styled.div`
flex-direction: column;
align-items: stretch;
height: 100%;
min-width: 16rem;
border: 1px solid rgba(128, 128, 128, 0.2);
border-radius: 6px;
padding: 16px;
`;

const StepBody = styled.div`
flex: 1;
padding: 8px 0;
`;

const StepActions = styled.div`
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
`;

const Hidden = styled.span`
visibility: hidden;
`;

const FirstStepComponent = (props: Record<string, never>) => {
const { next } = useStepper(StepIndexes.Step1, STEPS);
return (
<Wrapper>
<Box
display="flex"
flexDirection={'row'}
alignItems="baseline"
gap={'2rem'}
></Box>
<Stack>
<StepBody>
<Text>First Step</Text>
<Button
label="Next"
variant="secondary"
onClick={() => next({ name: 'something' })}
/>
</Stack>
</StepBody>
<StepActions>
<Hidden><Button label="Back" variant="secondary" onClick={() => {}} /></Hidden>
<Button label="Next" variant="primary" onClick={() => next({ name: 'something' })} />
</StepActions>
</Wrapper>
);
};
Expand All @@ -44,21 +56,13 @@ const SecondStepComponent = ({ name }: { name: string }) => {
const { next, prev } = useStepper(StepIndexes.Step2, STEPS);
return (
<Wrapper>
<Box
display="flex"
flexDirection={'row'}
alignItems="baseline"
gap={'2rem'}
></Box>
<Stack>
<Text>Second Step : {name}</Text>
<StepBody>
<Text>Second Step: {name}</Text>
</StepBody>
<StepActions>
<Button label="Back" variant="secondary" onClick={() => prev({})} />
<Button
label="Next"
variant="secondary"
onClick={() => next({ type: 'anything' })}
/>
</Stack>
<Button label="Next" variant="primary" onClick={() => next({ type: 'anything' })} />
</StepActions>
</Wrapper>
);
};
Expand All @@ -67,37 +71,21 @@ const ThirdStepComponent = ({ type }: { type: string }) => {
const { prev } = useStepper(StepIndexes.Step3, STEPS);
return (
<Wrapper>
<Box
display="flex"
flexDirection={'row'}
alignItems="baseline"
gap={'2rem'}
></Box>
<Stack>
<Text>Third Step : {type}</Text>
<Button
label="Back"
variant="secondary"
onClick={() => prev({ name: 'something' })}
/>
</Stack>
<StepBody>
<Text>Third Step: {type}</Text>
</StepBody>
<StepActions>
<Button label="Back" variant="secondary" onClick={() => prev({ name: 'something' })} />
<Hidden><Button label="Next" variant="primary" onClick={() => {}} /></Hidden>
</StepActions>
</Wrapper>
);
};

const STEPS = [
{
label: 'Step 1',
Component: FirstStepComponent,
},
{
label: 'Step 2',
Component: SecondStepComponent,
},
{
label: 'Step 3',
Component: ThirdStepComponent,
},
{ label: 'Step 1', Component: FirstStepComponent },
{ label: 'Step 2', Component: SecondStepComponent },
{ label: 'Step 3', Component: ThirdStepComponent },
] as const;

enum StepIndexes {
Expand All @@ -122,3 +110,44 @@ export const SimpleStepper: Story = {
</StoryWrapper>
),
};

const STATE_STEPS = [
{ title: 'Configure' },
{ title: 'Schedule' },
{ title: 'Confirm' },
];

export const StateCompleted = {
tags: ['!dev'],
render: () => (
<Steppers steps={STATE_STEPS} activeStep={2} />
),
};

export const StateInProgress = {
tags: ['!dev'],
render: () => (
<Steppers
steps={[
{ title: 'Configure' },
{ title: 'Schedule', inProgress: true },
{ title: 'Confirm' },
]}
activeStep={1}
/>
),
};

export const StateError = {
tags: ['!dev'],
render: () => (
<Steppers
steps={[
{ title: 'Configure' },
{ title: 'Schedule', error: true },
{ title: 'Confirm' },
]}
activeStep={1}
/>
),
};
Loading