Skip to content

Commit 9f373a4

Browse files
authored
feat: support hiding content and jumping ahead on stepper (#1279)
* feat(stepper): support hiding content and jumping ahead * refactor(stepper): align stories/tests and substep navigation * test(stepper-provider): complete coverage * fix: linting * fix(stepper): make story variants respect args
1 parent 4399c60 commit 9f373a4

8 files changed

Lines changed: 677 additions & 43 deletions

File tree

src/molecules/Stepper/Step/Step.tsx

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, useMemo } from 'react';
1+
import { FC, KeyboardEvent, useMemo } from 'react';
22

33
import { Typography } from '@equinor/eds-core-react';
44
import { TypographyVariants } from '@equinor/eds-core-react/dist/types/components/Typography/Typography.tokens';
@@ -43,44 +43,70 @@ interface StepProps {
4343
index: number;
4444
onlyShowCurrentStepLabel?: boolean;
4545
children?: string;
46+
allowJumpingAhead?: boolean;
4647
}
4748

4849
export const Step: FC<StepProps> = ({
4950
index,
5051
onlyShowCurrentStepLabel = false,
5152
children,
53+
allowJumpingAhead = false,
5254
}) => {
5355
const { currentStep, setCurrentStep, isStepAtIndexDisabled } = useStepper();
5456

5557
const isDisabled = isStepAtIndexDisabled(index);
5658

5759
const textVariant = useMemo((): TypographyVariants => {
5860
if (index < currentStep) return 'body_short';
61+
if (allowJumpingAhead && index > currentStep) return 'body_short_bold';
5962
return 'body_short_bold';
60-
}, [currentStep, index]);
63+
}, [currentStep, index, allowJumpingAhead]);
6164

6265
const textColor = useMemo((): string | undefined => {
63-
if (index > currentStep || isDisabled)
66+
if (isDisabled) return colors.interactive.disabled__text.rgba;
67+
if (index > currentStep && !allowJumpingAhead)
6468
return colors.interactive.disabled__text.rgba;
6569
return colors.text.static_icons__default.rgba;
66-
}, [currentStep, index, isDisabled]);
70+
}, [currentStep, index, isDisabled, allowJumpingAhead]);
71+
72+
const isClickable = useMemo((): boolean => {
73+
if (isDisabled) return false;
74+
if (index < currentStep) return true;
75+
if (allowJumpingAhead && index > currentStep) return true;
76+
return false;
77+
}, [index, currentStep, isDisabled, allowJumpingAhead]);
6778

6879
const handleOnClick = () => {
69-
if (index < currentStep && !isDisabled) {
80+
if (isClickable) {
81+
setCurrentStep(index);
82+
}
83+
};
84+
85+
const handleOnKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
86+
if (!isClickable) return;
87+
88+
if (event.key === 'Enter' || event.key === ' ') {
89+
event.preventDefault();
7090
setCurrentStep(index);
7191
}
7292
};
7393

7494
return (
7595
<Container
7696
data-testid="step"
77-
$clickable={index < currentStep}
97+
$clickable={isClickable}
7898
onClick={handleOnClick}
99+
onKeyDown={handleOnKeyDown}
79100
$disabled={isDisabled}
80-
aria-disabled={isDisabled}
101+
aria-disabled={!isClickable}
81102
role="button"
103+
tabIndex={isClickable ? 0 : -1}
82104
>
83-
<StepIcon index={index} disabled={isDisabled} />
105+
<StepIcon
106+
index={index}
107+
disabled={isDisabled}
108+
allowJumpingAhead={allowJumpingAhead}
109+
/>
84110
{(!onlyShowCurrentStepLabel || currentStep === index) && (
85111
<Typography variant={textVariant} color={textColor}>
86112
{children}

src/molecules/Stepper/Step/StepIcon.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ const IconWrapper = styled.span<IconWrapperProps>`
3030
> p {
3131
// Ensure text icons are not squished
3232
padding: 8px;
33-
color: ${(props) =>
34-
props.$filled
33+
color: ${({ $filled, $outlined }) =>
34+
$filled
3535
? colors.text.static_icons__primary_white.rgba
36-
: colors.interactive.disabled__text.rgba};
36+
: $outlined
37+
? colors.interactive.primary__resting.rgba
38+
: colors.interactive.disabled__text.rgba};
3739
}
3840
> svg {
3941
transform: scale(0.9);
@@ -43,15 +45,28 @@ const IconWrapper = styled.span<IconWrapperProps>`
4345
interface StepIconProps {
4446
index: number;
4547
disabled?: boolean;
48+
allowJumpingAhead?: boolean;
4649
}
4750

48-
export const StepIcon: FC<StepIconProps> = ({ index, disabled }) => {
51+
export const StepIcon: FC<StepIconProps> = ({
52+
index,
53+
disabled,
54+
allowJumpingAhead,
55+
}) => {
4956
const { currentStep } = useStepper();
5057

5158
if (disabled) {
5259
return <Icon data={lock} color={colors.interactive.disabled__text.rgba} />;
5360
}
5461

62+
if (index > currentStep && allowJumpingAhead) {
63+
return (
64+
<IconWrapper $outlined>
65+
<Typography variant="caption">{index + 1}</Typography>
66+
</IconWrapper>
67+
);
68+
}
69+
5570
if (index >= currentStep) {
5671
return (
5772
<IconWrapper $filled={index === currentStep}>

src/molecules/Stepper/Stepper.jsdom.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ function fakeSteps(): StepperProviderProps['steps'] {
2020
{
2121
label: faker.string.uuid(),
2222
},
23+
{
24+
label: faker.string.uuid(),
25+
},
2326
];
2427
let i = 0;
2528
const stepAmount = faker.number.int({ min: 0, max: 30 });
@@ -98,3 +101,30 @@ test('maxWidth works as expected', async () => {
98101
`max-width: ${maxWidth}`
99102
);
100103
});
104+
105+
test('Future step icon is outlined and primary when allowJumpingAhead is true', async () => {
106+
const steps: StepperProviderProps['steps'] = [
107+
{ label: 'Step 1' },
108+
{ label: 'Step 2' },
109+
{ label: 'Step 3' },
110+
];
111+
112+
await renderWithRouter(
113+
<StepperProvider steps={steps}>
114+
<Stepper allowJumpingAhead />
115+
</StepperProvider>,
116+
{
117+
routes: ['/'],
118+
initialEntries: ['/'],
119+
}
120+
);
121+
122+
const secondStepButton = screen
123+
.getByText('Step 2')
124+
.closest('[data-testid="step"]');
125+
const secondStepNumber = secondStepButton?.querySelector('p');
126+
127+
expect(secondStepNumber).toHaveStyle(
128+
`color: ${colors.interactive.primary__resting.rgba}`
129+
);
130+
});

0 commit comments

Comments
 (0)