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

fix(stepper): improves AT Users experience with screen readers #7691

Merged
merged 13 commits into from
Sep 19, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"complete": "Completed step"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"complete": "Completed step"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const CSS = {
container: "container",
stepperItemContent: "stepper-item-content",
stepperItemDescription: "stepper-item-description",
stepperItemHeader: "stepper-item-header",
stepperItemHeading: "stepper-item-heading",
stepperItemHeaderText: "stepper-item-header-text",
stepperItemNumber: "stepper-item-number",
visuallyHidden: "visually-hidden",
};
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,8 @@
}
}

.visually-hidden {
@apply sr-only;
}

@include base-component();
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
VNode,
Watch,
} from "@stencil/core";
import { toAriaBoolean } from "../../utils/dom";
import { Layout, Scale } from "../interfaces";
import {
connectInteractive,
Expand All @@ -38,6 +37,7 @@ import {
LoadableComponent,
componentFocusable,
} from "../../utils/loadable";
import { CSS } from "./resources";

/**
* @slot - A slot for adding custom content.
Expand Down Expand Up @@ -210,13 +210,18 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo
render(): VNode {
return (
<Host
aria-expanded={toAriaBoolean(this.selected)}
aria-current={this.selected ? "step" : "false"}
onClick={this.handleItemClick}
onKeyDown={this.keyDownHandler}
>
<div class="container">
<div class={CSS.container}>
{this.complete && (
<span aria-live="polite" class={CSS.visuallyHidden}>
{"Completed step"}
Copy link
Member

Choose a reason for hiding this comment

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

should reference message here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without T9nComponent interface implemented, we cant test the message bundles for all supported locales. Once the translations are back this will be replaced.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added #7759.

Copy link
Member

Choose a reason for hiding this comment

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

This should reference the messages now right?

</span>
)}
<div
class="stepper-item-header"
class={CSS.stepperItemHeader}
tabIndex={
/* additional tab index logic needed because of display: contents */
this.layout === "horizontal" && !this.disabled ? 0 : null
Expand All @@ -225,13 +230,15 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo
ref={(el) => (this.headerEl = el)}
>
{this.icon ? this.renderIcon() : null}
{this.numbered ? <div class="stepper-item-number">{this.renderNumbers()}.</div> : null}
<div class="stepper-item-header-text">
<span class="stepper-item-heading">{this.heading}</span>
<span class="stepper-item-description">{this.description}</span>
{this.numbered ? (
<div class={CSS.stepperItemNumber}>{this.renderNumbers()}.</div>
) : null}
<div class={CSS.stepperItemHeaderText}>
<span class={CSS.stepperItemHeading}>{this.heading}</span>
<span class={CSS.stepperItemDescription}>{this.description}</span>
</div>
</div>
<div class="stepper-item-content">
<div class={CSS.stepperItemContent}>
<slot />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label": "Progress steps"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label": "Progress steps"
}
33 changes: 28 additions & 5 deletions packages/calcite-components/src/components/stepper/stepper.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { defaults, hidden, reflects, renders } from "../../tests/commonTests";
import { html } from "../../../support/formatting";
import { NumberStringFormatOptions } from "../../utils/locale";

// we use browser-context function to click on items to workaround `E2EElement#click` error
async function itemClicker(item: HTMLCalciteStepperItemElement) {
item.click();
}

// todo test the automatic setting of first item to selected
describe("calcite-stepper", () => {
describe("defaults", () => {
Expand Down Expand Up @@ -482,11 +487,6 @@ describe("calcite-stepper", () => {
await page.waitForChanges();
expect(eventSpy).toHaveReceivedEventTimes(expectedEvents);

// we use browser-context function to click on items to workaround `E2EElement#click` error
async function itemClicker(item: HTMLCalciteStepperItemElement) {
item.click();
}

await page.$eval("#step-2", itemClicker);
expect(eventSpy).toHaveReceivedEventTimes(++expectedEvents);
expect(await getSelectedItemId()).toBe("step-2");
Expand Down Expand Up @@ -640,4 +640,27 @@ describe("calcite-stepper", () => {
);
expect(stepper2Number.textContent).toBe(`${thaiNumeral1}.`);
});

it("should have correct ARIA attributes", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-stepper>
<calcite-stepper-item heading="Step 1" id="step-1">
<div>Step 1 content</div>
</calcite-stepper-item>
<calcite-stepper-item heading="Step 2" id="step-2">
<div>Step 2 content</div>
</calcite-stepper-item>
</calcite-stepper>`);

const stepper = await page.find("calcite-stepper");
const [stepperItem1, stepperItem2] = await page.findAll("calcite-stepper-item");
const messages = await import(`./assets/stepper/t9n/messages.json`);

expect(stepper.getAttribute("aria-label")).toEqual(messages.label);
expect(stepperItem1.getAttribute("aria-current")).toEqual("step");

await page.$eval("#step-2", itemClicker);
await page.waitForChanges();
expect(stepperItem2.getAttribute("aria-current")).toEqual("step");
});
});
26 changes: 14 additions & 12 deletions packages/calcite-components/src/components/stepper/stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import {
Event,
EventEmitter,
h,
Host,
Listen,
Method,
Prop,
VNode,
Watch,
} from "@stencil/core";

import { focusElementInGroup } from "../../utils/dom";
import { focusElementInGroup, slotChangeGetAssignedElements } from "../../utils/dom";
import { NumberingSystem } from "../../utils/locale";
import { Layout, Scale } from "../interfaces";
import { StepperItemChangeEventDetail, StepperItemKeyEventDetail } from "./interfaces";
Expand Down Expand Up @@ -112,17 +113,9 @@ export class Stepper {

render(): VNode {
return (
<slot
onSlotchange={(event: Event) => {
const items = (event.currentTarget as HTMLSlotElement)
.assignedElements()
.filter((el) => el?.tagName === "CALCITE-STEPPER-ITEM");
const spacing = Array(items.length).fill("1fr").join(" ");
this.el.style.gridTemplateAreas = spacing;
this.el.style.gridTemplateColumns = spacing;
this.setStepperItemNumberingSystem();
}}
/>
<Host aria-label={"Progress steps"} role="region">
Copy link
Member

Choose a reason for hiding this comment

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

aria-label should reference message

Copy link
Member

Choose a reason for hiding this comment

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

This should reference the messages now right?

<slot onSlotchange={this.handleDefaultSlotChange} />
</Host>
);
}

Expand Down Expand Up @@ -331,4 +324,13 @@ export class Stepper {
item.numberingSystem = this.numberingSystem;
});
}

private handleDefaultSlotChange = (event: Event): void => {
const slottedItems = slotChangeGetAssignedElements(event);
const items = slottedItems.filter((el) => el?.tagName === "CALCITE-STEPPER-ITEM");
const spacing = Array(items.length).fill("1fr").join(" ");
this.el.style.gridTemplateAreas = spacing;
this.el.style.gridTemplateColumns = spacing;
this.setStepperItemNumberingSystem();
};
}
2 changes: 2 additions & 0 deletions t9nmanifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ packages\calcite-components\src\components\popover\assets\popover\t9n
packages\calcite-components\src\components\rating\assets\rating\t9n
packages\calcite-components\src\components\scrim\assets\scrim\t9n
packages\calcite-components\src\components\shell-panel\assets\shell-panel\t9n
packages\calcite-components\src\components\stepper\assets\stepper\t9n
packages\calcite-components\src\components\stepper-item\assets\stepper-item\t9n
packages\calcite-components\src\components\tab-title\assets\tab-title\t9n
packages\calcite-components\src\components\table\assets\table\t9n
packages\calcite-components\src\components\table-cell\assets\table-cell\t9n
Expand Down