Skip to content

Commit

Permalink
feat(Stepper): Additions to the Stepper component for accessibility c…
Browse files Browse the repository at this point in the history
…ompliance

Co-authored-by: Chris Cedrone <[email protected]>
Co-authored-by: Laura Silva <[email protected]>
  • Loading branch information
3 people authored Jul 25, 2024
1 parent d67bff7 commit af3afe7
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 180 deletions.
5 changes: 5 additions & 0 deletions .changeset/feat-stepper-a11y.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-magma-dom': minor
---

feat(Stepper): Additions to the Stepper component for accessibility compliance. The 'aria-current' attribute now reads 'step' when active, the DOM structure has been changed to a UL LI layout, and when labels are hidden, the screenreader sees the step state for each step label.
54 changes: 40 additions & 14 deletions packages/react-magma-dom/src/components/Stepper/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ThemeInterface } from '../../theme/magma';
import { useIsInverse } from '../../inverse';
import { transparentize } from 'polished';
import { HiddenStyles } from '../../utils/UtilityStyles';
import { StepperLayout } from './Stepper';

export interface StepProps extends React.HTMLAttributes<HTMLDivElement> {
/**
Expand All @@ -18,7 +19,7 @@ export interface StepProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* @internal
*/
areLabelsHidden?: boolean;
layout?: StepperLayout;
/**
* Label beneath each step.
*/
Expand All @@ -31,10 +32,18 @@ export interface StepProps extends React.HTMLAttributes<HTMLDivElement> {
* @internal
*/
stepStatus?: StepStatus;
/**
* @internal
*/
index?: number;
/**
* @internal
*/
isInverse?: boolean;
/**
* @internal
*/
stepLabel?: string;
/**
* @internal
*/
Expand All @@ -47,7 +56,7 @@ export interface StepProps extends React.HTMLAttributes<HTMLDivElement> {

export enum StepStatus {
active = 'active',
complete = 'complete',
completed = 'completed',
incomplete = 'incomplete',
}

Expand All @@ -74,13 +83,13 @@ function buildStepCircleOutlineColors(props) {
function buildStepCircleBackgroundColors(props) {
const { isInverse, stepStatus, hasError, theme } = props;
if (isInverse) {
if (stepStatus === StepStatus.complete && !hasError) {
if (stepStatus === StepStatus.completed && !hasError) {
return theme.colors.tertiary500;
} else if (hasError) {
return theme.colors.danger500;
}
} else {
if (stepStatus === StepStatus.complete && !hasError) {
if (stepStatus === StepStatus.completed && !hasError) {
return theme.colors.primary500;
} else if (hasError) {
return theme.colors.danger500;
Expand Down Expand Up @@ -131,7 +140,6 @@ const StyledStep = typedStyled.div`
text-align: center;
align-self: self-start;
align-items: center;
`;

const StyledStepIndicator = typedStyled.span<{
Expand Down Expand Up @@ -160,7 +168,7 @@ const StyledStepIndicator = typedStyled.span<{
}
`;

const StyledStepTextWrapper = typedStyled.div`
const StyledStepTextWrapper = typedStyled.span`
flex: 1;
display: flex;
align-self: center;
Expand Down Expand Up @@ -203,9 +211,11 @@ export const Step = React.forwardRef<HTMLDivElement, StepProps>(
(props, ref) => {
const {
hasError,
areLabelsHidden,
index,
label,
layout,
secondaryLabel,
stepLabel,
testId,
isInverse: isInverseProp,
stepStatus,
Expand All @@ -222,15 +232,25 @@ export const Step = React.forwardRef<HTMLDivElement, StepProps>(
stepStatus={stepStatus}
theme={theme}
>
{stepStatus === StepStatus.complete && !hasError && (
{stepStatus === StepStatus.completed && !hasError && (
<CheckIcon aria-hidden="true" />
)}
{hasError && <CrossIcon aria-hidden="true" />}
</StyledStepIndicator>

<StyledStepTextWrapper>
{!areLabelsHidden ? (
{layout !== StepperLayout.hideLabels &&
layout !== StepperLayout.summaryView ? (
<>
{layout === StepperLayout.showLabels && (
<HiddenLabelText>
{`${
stepStatus === StepStatus.completed
? `${stepLabel} ${stepStatus}, `
: ''
}`}
</HiddenLabelText>
)}
{label && (
<StyledLabel
label={label}
Expand All @@ -253,11 +273,17 @@ export const Step = React.forwardRef<HTMLDivElement, StepProps>(
)}
</>
) : (
<HiddenLabelText>
{label && secondaryLabel
? `${label} ${secondaryLabel}`
: label || secondaryLabel}
</HiddenLabelText>
layout !== StepperLayout.summaryView && (
<HiddenLabelText>
{`${
stepStatus === StepStatus.completed
? `${stepLabel} ${stepStatus}, `
: ''
}${label || ''}${secondaryLabel ? ' ' : ''}${
secondaryLabel || ''
}`}
</HiddenLabelText>
)
)}
</StyledStepTextWrapper>
</StyledStep>
Expand Down
147 changes: 112 additions & 35 deletions packages/react-magma-dom/src/components/Stepper/Stepper.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,38 @@ export default {
],
argTypes: {
breakpoint: {
control: {
type: 'number',
},
control: 'number',
},
breakpointLayout: {
control: {
type: 'select',
options: StepperLayout,
defaultValue: StepperLayout.hideLabels,
},
},
layout: {
control: {
type: 'select',
options: StepperLayout,
defaultValue: StepperLayout.showLabels,
},
},
completionLabel: {
control: {
type: 'text',
},
control: 'text',
},
stepLabel: {
control: {
type: 'text',
},
control: 'text',
},
isInverse: {
control: {
type: 'boolean',
},
control: 'boolean',
defaultValue: false,
},
testId: {
control: {
type: 'text',
},
control: 'text',
},
ariaLabel: {
control: {
type: 'text',
},
control: 'text',
defaultValue: 'progress',
},
},
} as Meta;
Expand Down Expand Up @@ -99,16 +91,16 @@ const Template: Story<StepperProps> = args => {
return (
<Step
key={i}
testId={`Step ${i}`}
label={`Step ${i}`}
testId={`Item ${i}`}
label={`Item ${i}`}
secondaryLabel={`Description area in secondaryLabel component ${i}`}
/>
);
});

return (
<>
<Stepper ariaLabel="progress" currentStep={currentStep} {...args}>
<Stepper currentStep={currentStep} {...args}>
{step}
</Stepper>

Expand All @@ -122,8 +114,8 @@ const Template: Story<StepperProps> = args => {
>
<div>
{currentStep < numberOfSteps
? `Step Content ${currentStep + 1}`
: `Steps Completed`}
? `Item Content ${currentStep + 1}`
: `Items Completed`}
</div>
</Container>

Expand Down Expand Up @@ -163,21 +155,103 @@ const Template: Story<StepperProps> = args => {
);
};

const RealisticLabels: Story<StepperProps> = args => {
const [currentStep, setCurrentStep] = React.useState(0);

const handleOnNext = () => {
if (currentStep !== 4) {
setCurrentStep(currentStep + 1);
}
};
const handleOnPrevious = () => {
if (currentStep !== 0) {
setCurrentStep(currentStep - 1);
}
};
const handleFinish = () => {
if (currentStep >= 4) {
setCurrentStep(0);
}
};

return (
<>
<Stepper currentStep={currentStep} {...args}>
<Step
key={0}
label="Fenway seating"
secondaryLabel="Select an area in the ball park"
testId="fenway0"
/>
<Step
key={1}
label="Guest information"
secondaryLabel="Please fill out the registration form for your party"
testId="fenway1"
/>
<Step
key={2}
label="Yankees fans?"
secondaryLabel="An additional surcharge may be applicable"
testId="fenway2"
/>
<Step
key={3}
label="MBTA and parking information"
secondaryLabel="Suggested methods of transportation"
testId="fenway3"
/>
</Stepper>

<Container
style={{
background: '#F5F5F5',
borderRadius: '6px',
margin: '20px 0 0',
padding: '20px',
}}
>
{currentStep === 0 && <div>Fenway seating Content</div>}
{currentStep === 1 && <div>Guest information Content</div>}
{currentStep === 2 && <div>Yankees fans? Content</div>}
{currentStep === 3 && <div>MBTA and parking information Content</div>}
{currentStep === 4 && <div>Steps completed</div>}
</Container>

<Container style={{ padding: '20px 0' }}>
<ButtonGroup>
<Button disabled={currentStep === 0} onClick={handleOnPrevious}>
Previous
</Button>
<Button onClick={currentStep >= 4 ? handleFinish : handleOnNext}>
{currentStep >= 4 ? 'Finish' : 'Next'}
</Button>
</ButtonGroup>
</Container>
</>
);
};

const ErrorTemplate: Story<StepperProps> = args => {
return (
<>
<Stepper ariaLabel="progress" currentStep={2} {...args}>
<Step label="First Step" secondaryLabel="Description One">
Step Content One
<Stepper currentStep={2} {...args}>
<Step key={0} label="First Item" secondaryLabel="Description One">
Item Content One
</Step>
<Step label="Second Step" secondaryLabel="Description Two">
Step Content Two
<Step key={1} label="Second Item" secondaryLabel="Description Two">
Item Content Two
</Step>
<Step label="Third Step" hasError secondaryLabel="Description Three">
Step Content Three
<Step
key={2}
label="Third Item"
hasError
secondaryLabel="Description Three"
>
Item Content Three
</Step>
<Step label="Fourth Step" secondaryLabel="Description Four">
Step Content Four
<Step key={3} label="Fourth Item" secondaryLabel="Description Four">
Item Content Four
</Step>
</Stepper>
<Container
Expand All @@ -188,7 +262,7 @@ const ErrorTemplate: Story<StepperProps> = args => {
padding: '20px',
}}
>
<div>Step Content Three</div>
<div>Item Content Three</div>
</Container>

<Container style={{ padding: '20px 0' }}>
Expand All @@ -202,8 +276,11 @@ const ErrorTemplate: Story<StepperProps> = args => {
};

export const Default = Template.bind({});
Default.args = {
ariaLabel: 'progress',
Default.args = {};

export const RealWorldExample = RealisticLabels.bind({});
RealisticLabels.args = {
stepLabel: 'Module',
};

export const WithError = ErrorTemplate.bind({});
Expand Down
Loading

2 comments on commit af3afe7

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

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

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

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

Please sign in to comment.