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 8 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
21 changes: 21 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,26 @@ export const Basic = () => {
);
};

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

export const Long = () => {
const onStepClick = (index: number) => {
console.log(`Clicked index: ${index}`);
Expand Down
8 changes: 8 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,14 @@ 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 displays the step number. However, end users can customize the icon or content shown for completed steps in the `Stepper` component by using the `stepCircleRenderer` prop. This prop accepts a function that takes the step index and returns the custom content to represent 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);
}
53 changes: 53 additions & 0 deletions examples/Stepper.customIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* 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';

const steps = [
{ name: 'First Step' },
{ name: 'Second Step' },
{ name: 'Third Step' },
{ name: 'Last Step' },
];

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

return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
stepCircleRenderer={() => <SvgCheckmarkSmall />}
/>
</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
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, img) {
mayank99 marked this conversation as resolved.
Show resolved Hide resolved
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
29 changes: 29 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,34 @@ it('should add custom props to Stepper', () => {
).toBeTruthy();
});

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

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

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
6 changes: 6 additions & 0 deletions packages/itwinui-react/src/core/Stepper/Stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export type StepperProps = {
* Option to provide localized strings.
*/
localization?: StepperLocalization;
/**
* Custom content passed for completed steps.
*/
stepCircleRenderer?: (completedIndex: number) => React.ReactNode;
mayank99 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Click handler on completed step.
*/
Expand Down Expand Up @@ -82,6 +86,7 @@ export const Stepper = React.forwardRef((props, ref) => {
steps,
type = 'default',
localization = defaultStepperLocalization,
stepCircleRenderer,
onStepClick,
stepProps,
trackContentProps,
Expand Down Expand Up @@ -119,6 +124,7 @@ export const Stepper = React.forwardRef((props, ref) => {
type={type}
onClick={onStepClick}
description={s.description}
stepCircleRenderer={stepCircleRenderer}
/>
);
})}
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
*/
stepCircleRenderer?: (index: number) => 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,
stepCircleRenderer,
...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}
{stepCircleRenderer && isPast ? stepCircleRenderer(index) : index + 1}
</Box>
</Box>

Expand Down
Loading