Skip to content

Commit 8d7be36

Browse files
author
Kubit
committed
Add new props to readers on StepperNumber component
1 parent 2cdedf4 commit 8d7be36

File tree

6 files changed

+153
-32
lines changed

6 files changed

+153
-32
lines changed

src/components/stepperNumber/__test__/stepperNumber.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,45 @@ describe('StepperNumber component', () => {
123123
const firstStep = screen.getAllByText(StepperNumberSteps[0]);
124124
expect(firstStep.length).not.toBe(0);
125125
});
126+
127+
it('screenReaderTitle can be used as hidden title section of the stepper number', () => {
128+
const screenReaderTitle = {
129+
content: 'Stepper number title',
130+
};
131+
132+
renderProvider(
133+
<StepperNumber
134+
{...mockProps}
135+
orientation={StepperNumberOrientationType.VERTICAL}
136+
screenReaderTextBuilder={undefined}
137+
screenReaderTitle={screenReaderTitle}
138+
/>
139+
);
140+
141+
const section = screen.getByLabelText(screenReaderTitle.content);
142+
expect(section).toBeInTheDocument();
143+
});
144+
145+
it('screenReaderCompletedStep, when vertical and no screenReaderTextBuilder, is used as screen reader text for the completed steps', () => {
146+
const screenReaderTitle = {
147+
content: 'Stepper number title',
148+
};
149+
const screenReaderCompletedStep = {
150+
content: 'Completed step',
151+
};
152+
153+
renderProvider(
154+
<StepperNumber
155+
{...mockProps}
156+
currentStep={3}
157+
orientation={StepperNumberOrientationType.VERTICAL}
158+
screenReaderCompletedStep={screenReaderCompletedStep}
159+
screenReaderTextBuilder={undefined}
160+
screenReaderTitle={screenReaderTitle}
161+
/>
162+
);
163+
164+
const completeSteps = screen.getAllByText(screenReaderCompletedStep.content);
165+
expect(completeSteps.length).toBe(3);
166+
});
126167
});
Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import styled, { css } from 'styled-components';
22

3+
import { srOnlyMixin } from '@/styles';
34
import { getStyles } from '@/utils';
45

56
import {
@@ -15,11 +16,11 @@ export const StepsContainerStyled = styled.ol<{ styles?: StepperNumberStateStyle
1516
`;
1617

1718
export const BuilStepContainerStyled = styled.li<{
18-
orientation: StepperNumberOrientationType;
19+
$orientation: StepperNumberOrientationType;
1920
horizontalOrientationWidth?: string;
2021
}>`
21-
${({ orientation, horizontalOrientationWidth }) =>
22-
orientation === StepperNumberOrientationType.VERTICAL
22+
${({ $orientation, horizontalOrientationWidth }) =>
23+
$orientation === StepperNumberOrientationType.VERTICAL
2324
? css`
2425
display: flex;
2526
`
@@ -28,36 +29,38 @@ export const BuilStepContainerStyled = styled.li<{
2829
`}
2930
`;
3031

31-
export const StepContainerStyled = styled.div<{
32+
export const StepContainerStyled = styled.span<{
3233
styles?: StepperNumberStateStylesType;
3334
state: StepperNumberStateType;
3435
}>`
3536
${({ styles, state }) => getStyles(styles?.[state]?.stepContainer)};
3637
`;
3738

38-
export const StepCircleBarWrapperStyled = styled.div<{ orientation: StepperNumberOrientationType }>`
39+
export const StepCircleBarWrapperStyled = styled.span<{
40+
$orientation: StepperNumberOrientationType;
41+
}>`
3942
display: flex;
40-
flex-direction: ${({ orientation }) =>
41-
orientation === StepperNumberOrientationType.VERTICAL ? 'column' : 'row'};
43+
flex-direction: ${({ $orientation }) =>
44+
$orientation === StepperNumberOrientationType.VERTICAL ? 'column' : 'row'};
4245
align-items: center;
4346
flex: 1;
4447
`;
4548

46-
export const StepCircleStyled = styled.div<{
49+
export const StepCircleStyled = styled.span<{
4750
styles?: StepperNumberStateStylesType;
4851
state: StepperNumberStateType;
4952
}>`
5053
${({ styles, state }) => getStyles(styles?.[state]?.stepCircle)};
5154
`;
5255

53-
export const StepBarStyled = styled.div<{
56+
export const StepBarStyled = styled.span<{
5457
styles?: StepperNumberStateStylesType;
5558
state: StepperNumberStateType;
5659
}>`
5760
${({ styles, state }) => getStyles(styles?.[state]?.stepBar)};
5861
`;
5962

60-
export const StepperNumberContainerVerticalStep = styled.div<{
63+
export const StepperNumberContainerVerticalStep = styled.span<{
6164
styles?: StepperNumberStateStylesType;
6265
state: StepperNumberStateType;
6366
isLastStep: boolean;
@@ -66,3 +69,11 @@ export const StepperNumberContainerVerticalStep = styled.div<{
6669
${({ styles, state, isLastStep }) =>
6770
isLastStep && getStyles(styles?.[state]?.stepNameContainer?.isLast)};
6871
`;
72+
73+
export const ScreenReaderTitle = styled.h2`
74+
${srOnlyMixin}
75+
`;
76+
77+
export const ScreenReaderCompletedStep = styled.span`
78+
${srOnlyMixin}
79+
`;

src/components/stepperNumber/stepperNumberStandAlone.tsx

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import * as React from 'react';
33
import { ScreenReaderOnly } from '@/components/screenReaderOnly';
44
import { Text } from '@/components/text/text';
55
import { TextComponentType } from '@/components/text/types/component';
6+
import { useId } from '@/hooks';
67

78
import { ElementOrIcon } from '../elementOrIcon';
89
import { buildAriaCurrent, buildScreenReaderText, mapToStepState } from './helpers';
910
import {
1011
BuilStepContainerStyled,
12+
ScreenReaderCompletedStep,
13+
ScreenReaderTitle,
1114
StepBarStyled,
1215
StepCircleBarWrapperStyled,
1316
StepCircleStyled,
@@ -28,45 +31,69 @@ const StepperNumberStandAloneComponent = (
2831
{ currentStep = defaultStep, ...props }: IStepperNumberStandAlone,
2932
ref: React.ForwardedRef<HTMLElement> | undefined | null
3033
): JSX.Element => {
34+
const id = useId('stepperNumber');
35+
const screenReaderTitleId = `${id}ScreenReaderTitle`;
36+
const ariaLabelledBy = props.screenReaderTitle?.content ? screenReaderTitleId : undefined;
3137
const steps = mapToStepState(props.steps, currentStep);
3238
const isVertical = props.orientation === StepperNumberOrientationType.VERTICAL;
3339

40+
const usingScreenReaderTextBuilder = Boolean(props.screenReaderTextBuilder);
41+
3442
return (
3543
<StepperNumberContainerStyled
3644
ref={ref}
3745
aria-hidden={!isVertical}
3846
aria-label={props['aria-label']}
47+
aria-labelledby={ariaLabelledBy}
3948
data-testid={`${props.dataTestId}StepsSection`}
4049
>
50+
{props.screenReaderTitle?.content && (
51+
<ScreenReaderTitle
52+
as={props.screenReaderTitle.component}
53+
data-testid={props.screenReaderTitle.dataTestId ?? `${props.dataTestId}ScreenReaderTitle`}
54+
id={screenReaderTitleId}
55+
>
56+
{props.screenReaderTitle?.content}
57+
</ScreenReaderTitle>
58+
)}
4159
<StepsContainerStyled data-testid={`${props.dataTestId}StepsContainer`} styles={props.styles}>
4260
{steps.map((step, index) => {
4361
const isLastStep = index === steps.length - 1;
4462
return (
4563
<BuilStepContainerStyled
4664
key={'stepContainer' + index}
65+
$orientation={props.orientation}
4766
aria-current={buildAriaCurrent(currentStep, index, props.orientation)}
4867
data-testid={`${props.dataTestId}Li${index}`}
4968
horizontalOrientationWidth={isLastStep ? 'auto' : props.horizontalOrientationWidth}
50-
orientation={props.orientation}
5169
>
52-
<ScreenReaderOnly dataTestId={`${props.dataTestId}ScreenReaderText${index}`}>
53-
{buildScreenReaderText(
54-
index,
55-
currentStep,
56-
steps.length,
57-
props.screenReaderTextBuilder,
58-
step.name,
59-
isVertical
60-
)}
61-
</ScreenReaderOnly>
62-
<StepContainerStyled aria-hidden={true} state={step.state} styles={props.styles}>
63-
<StepCircleBarWrapperStyled orientation={props.orientation}>
70+
{usingScreenReaderTextBuilder && (
71+
<ScreenReaderOnly dataTestId={`${props.dataTestId}ScreenReaderText${index}`}>
72+
{buildScreenReaderText(
73+
index,
74+
currentStep,
75+
steps.length,
76+
props.screenReaderTextBuilder,
77+
step.name,
78+
isVertical
79+
)}
80+
</ScreenReaderOnly>
81+
)}
82+
<StepContainerStyled
83+
aria-hidden={!isVertical || usingScreenReaderTextBuilder ? true : undefined}
84+
state={step.state}
85+
styles={props.styles}
86+
>
87+
<StepCircleBarWrapperStyled $orientation={props.orientation}>
6488
<StepCircleStyled state={step.state} styles={props.styles}>
6589
{step.state === StepperNumberStateType.COMPLETED ? (
66-
<ElementOrIcon
67-
customIconStyles={props.styles?.[step.state]?.iconSelected}
68-
{...props.completedStepIcon}
69-
/>
90+
<React.Fragment>
91+
<ElementOrIcon
92+
customIconStyles={props.styles?.[step.state]?.iconSelected}
93+
{...props.completedStepIcon}
94+
/>
95+
<ScreenReaderOnly>{index + 1}</ScreenReaderOnly>
96+
</React.Fragment>
7097
) : (
7198
<Text
7299
component={TextComponentType.SPAN}
@@ -82,7 +109,7 @@ const StepperNumberStandAloneComponent = (
82109
</StepCircleBarWrapperStyled>
83110
{isVertical && (
84111
<StepperNumberContainerVerticalStep
85-
aria-hidden={true}
112+
aria-hidden={usingScreenReaderTextBuilder ? true : undefined}
86113
isLastStep={isLastStep}
87114
state={step.state}
88115
styles={props.styles}
@@ -94,6 +121,16 @@ const StepperNumberStandAloneComponent = (
94121
>
95122
{step.name}
96123
</Text>
124+
{!usingScreenReaderTextBuilder &&
125+
step.state === StepperNumberStateType.COMPLETED &&
126+
props.screenReaderCompletedStep?.content && (
127+
<ScreenReaderCompletedStep
128+
as={props.screenReaderCompletedStep.component}
129+
data-testid={props.screenReaderCompletedStep.dataTestId}
130+
>
131+
&nbsp;{props.screenReaderCompletedStep.content}
132+
</ScreenReaderCompletedStep>
133+
)}
97134
</StepperNumberContainerVerticalStep>
98135
)}
99136
</StepContainerStyled>

src/components/stepperNumber/stories/argtypes.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,28 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
108108
category: CATEGORY_CONTROL.ACCESIBILITY,
109109
},
110110
},
111-
111+
screenReaderTitle: {
112+
description: 'Stepper number section title',
113+
control: { type: 'object' },
114+
type: { name: 'object' },
115+
table: {
116+
type: {
117+
summary: 'StepperNumberScreenReaderTextType',
118+
},
119+
category: CATEGORY_CONTROL.ACCESIBILITY,
120+
},
121+
},
122+
screenReaderCompletedStep: {
123+
description: 'Screen reader text for completed step',
124+
control: { type: 'object' },
125+
type: { name: 'object' },
126+
table: {
127+
type: {
128+
summary: 'StepperNumberScreenReaderTextType',
129+
},
130+
category: CATEGORY_CONTROL.ACCESIBILITY,
131+
},
132+
},
112133
dataTestId: {
113134
description: 'String used for testing',
114135
control: { type: 'text' },

src/components/stepperNumber/stories/stepperNumber.stories.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ type Story = StoryObj<typeof meta> & { args: { themeArgs?: object } };
2929

3030
const commonArgs: IStepperNumber = {
3131
variant: Object.values(variantsObject[themeSelected].StepperNumberVariantType || {})[0] as string,
32-
orientation: StepperNumberOrientationType.HORIZONTAL,
32+
orientation: StepperNumberOrientationType.VERTICAL,
3333
horizontalOrientationWidth: '5.75rem',
3434
completedStepIcon: { icon: ICONS.ICON_PLACEHOLDER },
35-
steps: ['Step 1', 'Step 2', 'Step 3'],
35+
steps: ['User Registration', 'Role Validation', 'Tutorial'],
3636
currentStep: 0,
37-
['aria-label']: 'ariaLabel',
37+
screenReaderTitle: { content: 'Screen reader title' },
38+
screenReaderCompletedStep: { content: 'COMPLETED' },
3839
};
3940

4041
export const StepperNumber: Story = {

src/components/stepperNumber/types/stepperNumber.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IElementOrIcon } from '@/components/elementOrIcon';
2+
import { IText } from '@/components/text';
23
import { CustomTokenTypes } from '@/types';
34

45
import { StepperNumberOrientationType } from './orientation';
@@ -14,6 +15,10 @@ export interface StepStateType {
1415
state: StepperNumberStateType;
1516
}
1617

18+
export type StepperNumberScreenReaderTextType = Pick<IText<string>, 'component' | 'dataTestId'> & {
19+
content?: string;
20+
};
21+
1722
export interface IStepperNumberStandAlone {
1823
styles?: StepperNumberStateStylesType;
1924
orientation: StepperNumberOrientationType;
@@ -22,7 +27,12 @@ export interface IStepperNumberStandAlone {
2227
steps?: string[];
2328
currentStep?: number;
2429
['aria-label']?: string;
30+
/**
31+
* @deprecated use screenReaderTitle and screenReaderCompletedStep instead
32+
*/
2533
screenReaderTextBuilder?: StepperNumberprefixSuffixType;
34+
screenReaderTitle?: StepperNumberScreenReaderTextType;
35+
screenReaderCompletedStep?: StepperNumberScreenReaderTextType;
2636
dataTestId?: string;
2737
}
2838

0 commit comments

Comments
 (0)