Skip to content

Commit

Permalink
Allowed passing customized icon for completed steps in Stepper (#2345)
Browse files Browse the repository at this point in the history
  • Loading branch information
smmr-dn authored Nov 19, 2024
1 parent fd0bfe1 commit d869473
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/curly-bikes-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@itwin/itwinui-react': minor
---

`Stepper` now allows passing custom icon or content in each step circle to indicate step completion. This can be done by using a `stepContent` property in each item of the `steps` array.
19 changes: 19 additions & 0 deletions apps/react-workshop/src/Stepper.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Stepper, type StepperLocalization } from '@itwin/itwinui-react';
import { SvgCheckmarkSmall } from '@itwin/itwinui-icons-react';

export default {
title: 'Stepper',
Expand All @@ -27,6 +28,24 @@ export const Basic = () => {
);
};

export const CustomIcon = () => {
const onStepClick = (index: number) => {
console.log(`Clicked index: ${index}`);
};
return (
<Stepper
currentStep={2}
steps={[
{ name: 'First Step', stepContent: () => <SvgCheckmarkSmall /> },
{ name: 'Second Step', stepContent: () => <SvgCheckmarkSmall /> },
{ name: 'Third Step' },
{ name: 'Last Step' },
]}
onStepClick={onStepClick}
/>
);
};

export const Long = () => {
const onStepClick = (index: number) => {
console.log(`Clicked index: ${index}`);
Expand Down
10 changes: 10 additions & 0 deletions apps/website/src/content/docs/stepper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ The ‘Step X of X’ label that appears in the default long stepper can be repl
<AllExamples.StepperLocalizationExample client:load />
</LiveExample>

### Custom Icon

By default, each step circle shows the step number. However, users can customize the icon or content displayed for each step in the `Stepper` component by providing the `stepContent` property in each item of the `steps` array.

This property is a function that returns custom content to represent the status of each step. A common use case for this customization is to indicate step completion.

<LiveExample src='Stepper.customIcon.jsx'>
<AllExamples.StepperCustomIconExample client:load />
</LiveExample>

### Layout

We recommend the following layout:
Expand Down
15 changes: 15 additions & 0 deletions examples/Stepper.customIcon.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.demo-container {
min-width: min(100%, 400px);
display: flex;
flex-direction: column;
gap: var(--iui-size-m);
}

.demo-stepper {
align-self: stretch;
}

.demo-button-container {
display: flex;
gap: var(--iui-size-xs);
}
56 changes: 56 additions & 0 deletions examples/Stepper.customIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import { Button, Stepper } from '@itwin/itwinui-react';
import { SvgCheckmarkSmall } from '@itwin/itwinui-icons-react';

export default () => {
const [currentStep, setCurrentStep] = React.useState(0);

const steps = Array(4)
.fill()
.map((_, index) => ({
name: `Step ${index + 1}`,
stepContent: () => (index < currentStep ? <SvgCheckmarkSmall /> : null),
}));

return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
/>
</div>

<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) {
setCurrentStep(currentStep - 1);
}
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
}
}}
>
Next
</Button>
</div>
</div>
);
};
4 changes: 4 additions & 0 deletions examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,10 @@ import { default as StepperMainExampleRaw } from './Stepper.main';
const StepperMainExample = withThemeProvider(StepperMainExampleRaw);
export { StepperMainExample };

import { default as StepperCustomIconExampleRaw } from './Stepper.customIcon';
const StepperCustomIconExample = withThemeProvider(StepperCustomIconExampleRaw);
export { StepperCustomIconExample };

import { default as StepperShortExampleRaw } from './Stepper.short';
const StepperShortExample = withThemeProvider(StepperShortExampleRaw);
export { StepperShortExample };
Expand Down
8 changes: 8 additions & 0 deletions packages/itwinui-css/src/stepper/stepper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
--_iui-stepper-step-background-color: var(--iui-color-background);
--_iui-stepper-step-border-color: var(--iui-color-border-positive);
--_iui-stepper-step-number-color: var(--iui-color-text-positive);
--_iui-stepper-step-icon-color: var(--iui-color-text-positive);
--_iui-stepper-step-text-color: var(--iui-color-text-positive);
--_iui-stepper-step-track-before-color: var(--iui-color-border-positive);
--_iui-stepper-step-track-after-color: var(--iui-color-border-positive);
Expand All @@ -37,6 +38,7 @@
--_iui-stepper-step-background-color: var(--iui-color-background-positive);
--_iui-stepper-step-border-color: var(--iui-color-background-positive);
--_iui-stepper-step-number-color: var(--iui-color-white);
--_iui-stepper-step-icon-color: var(--iui-color-white);
--_iui-stepper-step-track-after-color: var(--iui-color-border);

font-weight: var(--iui-font-weight-semibold);
Expand All @@ -47,6 +49,7 @@
--_iui-stepper-step-background-color: var(--iui-color-background);
--_iui-stepper-step-border-color: var(--iui-color-border);
--_iui-stepper-step-number-color: var(--iui-color-text-muted);
--_iui-stepper-step-icon-color: var(--iui-color-text-muted);
--_iui-stepper-step-text-color: var(--iui-color-text-muted);
--_iui-stepper-step-track-before-color: var(--iui-color-border);

Expand Down Expand Up @@ -82,6 +85,10 @@
background-color: var(--_iui-stepper-step-background-color);
color: var(--_iui-stepper-step-number-color);

:is(svg) {
fill: var(--_iui-stepper-step-icon-color);
}

.iui-clickable & {
cursor: pointer;
transition:
Expand All @@ -93,6 +100,7 @@
--_iui-stepper-step-background-color: var(--iui-color-background-positive);
--_iui-stepper-step-border-color: var(--iui-color-background-positive);
--_iui-stepper-step-number-color: var(--iui-color-white);
--_iui-stepper-step-icon-color: var(--iui-color-white);
}
}

Expand Down
39 changes: 39 additions & 0 deletions packages/itwinui-react/src/core/Stepper/Stepper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { screen, render, fireEvent, act } from '@testing-library/react';
import { Stepper } from './Stepper.js';
import { SvgCheckmarkSmall } from '../../utils/index.js';

it('should render all step names and numbers in default stepper', () => {
const stepper = (
Expand Down Expand Up @@ -69,6 +70,44 @@ it('should add custom props to Stepper', () => {
).toBeTruthy();
});

it('should pass custom icon for completed steps', () => {
const stepper = (
<Stepper
currentStep={3}
steps={[
{
name: 'Step One',
stepContent: () => (
<span className={`test-icon`}>
<SvgCheckmarkSmall />
</span>
),
},
{
name: 'Step Two',
stepContent: () => (
<span className={`test-icon`}>
<SvgCheckmarkSmall />
</span>
),
},
{
name: 'Step Three',
stepContent: () => (
<span className={`test-icon`}>
<SvgCheckmarkSmall />
</span>
),
},
]}
/>
);

const { container } = render(stepper);
const completedSteps = container.querySelectorAll('.test-icon');
expect(completedSteps).toHaveLength(3);
});

it('should set the active step to the step provided and raises onClick event on completed steps', () => {
const mockedOnClick = vi.fn();
const stepper = (
Expand Down
5 changes: 5 additions & 0 deletions packages/itwinui-react/src/core/Stepper/Stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type StepProperties = {
* A tooltip giving detailed description to this step.
*/
description?: string;
/**
* Custom content displayed in the step's circle.
*/
stepContent?: () => React.ReactNode;
} & React.ComponentProps<'li'>;

export type StepperProps = {
Expand Down Expand Up @@ -119,6 +123,7 @@ export const Stepper = React.forwardRef((props, ref) => {
type={type}
onClick={onStepClick}
description={s.description}
stepContent={s.stepContent}
/>
);
})}
Expand Down
7 changes: 6 additions & 1 deletion packages/itwinui-react/src/core/Stepper/StepperStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export type StepperStepProps = {
* A tooltip giving detailed description to this step.
*/
description?: string;
/**
* Custom content displayed in the step's circle.
*/
stepContent?: () => React.ReactNode;
/**
* Allows props to be passed for stepper step.
*/
Expand Down Expand Up @@ -70,6 +74,7 @@ export const StepperStep = React.forwardRef((props, forwardedRef) => {
trackContentProps,
circleProps,
nameProps,
stepContent,
...rest
} = props;

Expand Down Expand Up @@ -132,7 +137,7 @@ export const StepperStep = React.forwardRef((props, forwardedRef) => {
{...circleProps}
className={cx('iui-stepper-circle', circleProps?.className)}
>
{index + 1}
{stepContent ? stepContent() : index + 1}
</Box>
</Box>

Expand Down

0 comments on commit d869473

Please sign in to comment.