Description
The step indicator (progress indicator) is a visual representation of a user's progress through a set of steps or series of actions. Their purpose is to both guide the user through the process and to help them create a mental model of the amount of time and effort that is required to fulfill the process.
If the user should be able to navigate back and forth, use the mode="loose"
property. More about the modes further down.
The current active step is set with the current_step
property or within the data with the is_current
object property.
NB: Ensure, when ever possible, to bind the current_step
to the browsers path location. See the example below or the example on CodeSandbox.
Sidebar
The StepIndicator has two layouts. One for larger screens (above medium) and one for more narrow screens (below medium).
Depending on the screen width, the component will hide the Sidebar responsively, and in return show a button to access all the steps inside a Drawer. You may have a look at an demo.
Why a sidebar?
In order to provide a user experience, where the number of steps are almost unlimited, and at the same time, make these steps as accessible as possible, the solution was to provide the steps side-by-side the content, as long as there is enough available room for it to show properly.
How to use the sidebar?
The step indicator has an optional Sidebar "sister" component you can place in your layout if the steps should be shown side-by-side to the content. Use the property sidebar_id
to bind these two together:
<main><StepIndicator sidebar_id="unique-id" mode="strict" data={[...]} /></main><aside><StepIndicator.Sidebar sidebar_id="unique-id" /></aside>
Sidebar and SSR
NB: Keep in mind, if you SSR render the Sidebar before the main component, it has no access to the data
and will show a basic skeleton to ensure the sidebar gets its width. This helps to lower the impact of unwanted layout shifts.
If you are able to provide the data
to the Sidebar as well, it will use it during SSR render. You also can provide the data with a wrapper Eufemia Provider (You can nest Eufemia Providers):
<Provider StepIndicator={{ data: [...] }}><main><StepIndicator sidebar_id="unique-id" mode="strict" /></main><aside><StepIndicator.Sidebar sidebar_id="unique-id" /></aside></Provider>
Modes
The mode property is mandatory. It tells the component how it should behave.
Strict mode
Use strict
for a chronological step order.
The user can navigate between the visited steps and the current step. The component keeps track of these reached steps.
Loose mode
Use loose
if the user should be able to navigate freely between all steps. Also, those which are not visited before.
Static mode
Use static
for non-interactive steps.
Modify a step
You can easily modify a step – e.g. should one step not be interactive, you can use the inactive
property on that step:
const steps = [{ title: 'Active' },{ title: 'Not active', inactive: true },]
More details about modifying steps in the properties panel.
How wide should the sidebar be?
The sidebar comes with these styles:
.dnb-step-indicator__sidebar {max-width: 20rem;margin-right: var(--spacing-x-large);}.dnb-step-indicator__sidebar .dnb-step-indicator__item {min-width: 320px;}
API changes after version 9.8
The API has been simplified and some properties have to be changed by the next major version release. The changes are made backward compatible. But in order to use the new UX look, you have to make a few changes:
- Add a property called
mode="strict"
(choose your mode) - Add a property called
sidebar_id
with an unique ID. - Optional, place
<StepIndicator.Sidebar sidebar_id="unique-step-indicator" />
in your layout.
Demos
StepIndicator in loose mode
Every step can be clicked.
You want to place <StepIndicator.Sidebar sidebar_id="unique-id-loose" />
somewhere in your layout.
const InteractiveDemo = () => { const [step, setStep] = React.useState(1) return ( <div style={{ display: 'flex', }} > <StepIndicator.Sidebar sidebar_id="unique-id-loose" /> <Space stretch> <StepIndicator sidebar_id="unique-id-loose" mode="loose" current_step={step} on_change={({ current_step }) => { setStep(current_step) }} data={[ 'Cum odio si bolig bla et ta', 'Auctor tortor vestibulum placerat bibendum sociis aliquam nunc sed venenatis massa eget duis', 'Bibendum sociis', ]} bottom /> <Button variant="secondary" on_click={() => { setStep((step) => { if (step >= 2) { step = -1 } return step + 1 }) }} > Next step </Button> </Space> </div> ) } render(<InteractiveDemo />)
SyntaxError: Unexpected token (1:3)
StepIndicator in strict mode
Every visited step can be clicked, including the current step.
You want to place <StepIndicator.Sidebar sidebar_id="unique-id-strict" />
somewhere in your layout.
<StepIndicator.Sidebar sidebar_id="unique-id-strict" /> <StepIndicator sidebar_id="unique-id-strict" mode="strict" current_step={1} on_change={({ current_step }) => { console.log('on_change', current_step) }} data={[ { title: 'Velg mottaker', }, { title: 'Bestill eller erstatt', on_click: ({ current_step }) => console.log('current_step:', current_step), status: 'Du må velge bestill nytt kort eller erstatt kort for å kunne fullføre bestillingen din.', }, { title: 'Oppsummering', }, ]} />
StepIndicator in static mode
None of the steps are clickable.
You want to place <StepIndicator.Sidebar sidebar_id="unique-id-static" />
somewhere in your layout.
<StepIndicator sidebar_id="unique-id-static" mode="static" current_step={1} on_change={({ current_step }) => { console.log('on_change', current_step) }} data={[ { title: 'Om din nye bolig', }, { title: 'Ditt lån og egenkapital', on_click: ({ current_step }) => console.log(current_step), }, { title: 'Oppsummering', }, ]} />
StepIndicator with sidebar
<StepIndicator style={{ display: 'none', }} sidebar_id="unique-id-sidebar" mode="loose" data={[ { title: 'Om din nye bolig', }, { title: 'Ditt lån og egenkapital', }, { title: 'Oppsummering', is_current: true, }, ]} /> <StepIndicator.Sidebar sidebar_id="unique-id-sidebar" top="large" />
StepIndicator with a router
const StepIndicatorWithRouter = () => { const [currentStep, setCurrentStep] = React.useState(1) const [history, setInstance] = React.useState(null) React.useEffect(() => { const history = createBrowserHistory() const step = parseFloat(history.location.search?.replace(/[?]/, '')) || 1 setCurrentStep(step) setInstance(history) }, []) return ( <> <StepIndicator sidebar_id="step-indicator-router" mode="loose" current_step={currentStep - 1} on_change={({ current_step }) => { const step = current_step + 1 setCurrentStep(step) history.push('?' + step) }} data={[ { title: 'Om din nye bolig', }, { title: 'Ditt lån og egenkapital', }, { title: 'Oppsummering', }, ]} space /> <StepIndicator.Sidebar sidebar_id="step-indicator-router" space /> </> ) } render(<StepIndicatorWithRouter />)
SyntaxError: Unexpected token (1:3)
StepIndicator customized
Completely customized step indicator.
function CustomStepIndicator({ children, data, ...props }) { const [step, setStep] = React.useState(0) return ( <> <StepIndicator sidebar_id="unique-id-customized" mode="loose" data={data} current_step={step} on_change={({ current_step }) => setStep(current_step)} {...props} /> <Section style_type="lavender" spacing> {children(step)} </Section> </> ) } render( <CustomStepIndicator data={[ { title: 'First', is_current: true, }, { title: 'Second', }, { title: 'Last', }, ]} > {(step) => { switch (step) { case 0: return <>Step One</> case 1: return <>Step Two</> default: return <>Fallback</> } }} </CustomStepIndicator>, )
SyntaxError: Unexpected token (19:1)
StepIndicator with text only
<StepIndicator sidebar_id="unique-id-text" mode="static" current_step={1} data={['Om din nye bolig', 'Ditt lån og egenkapital', 'Oppsummering']} />
StepIndicator with a custom renderer.
<StepIndicator sidebar_id="unique-id-renderer" mode="strict" current_step={1} on_change={({ current_step }) => { console.log('on_change', current_step) }} on_item_render={({ StepItem }) => { return <StepItem onClick={(e) => console.log(e)} /> }} data={[ { title: 'Om din nye bolig', }, { title: 'Ditt lån og egenkapital', on_click: ({ current_step }) => console.log(current_step), on_render: ({ StepItem }) => ( <StepItem onClick={(e) => console.log(e)} /> ), }, { title: 'Oppsummering', /** * We can also overwrite the states * inactive: true * is_current: true */ }, ]} />