Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowed passing customized icon for completed steps in Stepper #2345

Merged
merged 23 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 17 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
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 users to pass in custom icon or content in each step circle to indicate step completion.
mayank99 marked this conversation as resolved.
Show resolved Hide resolved
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 takes the step index and returns custom content to represent the status of each step. A common use case for this customization is to indicate step completion.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it no longer takes the step index.

Suggested change
This property is a function that takes the step index and returns custom content to represent the status of each step. A common use case for this customization is to indicate step completion.
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the change.


<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);
}
68 changes: 68 additions & 0 deletions examples/Stepper.customIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* 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 [completedSteps, setCompletedSteps] = React.useState(new Set());

const steps = Array(4)
.fill()
.map((_, index) => ({
name: `Step ${index + 1}`,
stepContent: () =>
completedSteps.has(index) ? <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);
setCompletedSteps((prevCompletedSteps) => {
const newCompletedSteps = new Set(prevCompletedSteps);
newCompletedSteps.delete(currentStep - 1);
return newCompletedSteps;
});
}
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
setCompletedSteps((prevCompletedSteps) => {
const newCompletedSteps = new Set(prevCompletedSteps);
newCompletedSteps.add(currentStep);
return newCompletedSteps;
});
}
}}
>
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
6 changes: 6 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 Down Expand Up @@ -82,6 +83,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 +98,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;
/**
* A custom content displayed in each step's circle.
r100-stack marked this conversation as resolved.
Show resolved Hide resolved
*/
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 passed for completed step.
r100-stack marked this conversation as resolved.
Show resolved Hide resolved
*/
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
Loading