Skip to content

Commit 5de4833

Browse files
[Expression Remediation] Create arrow key navigation for TabBar component. (#1384)
## Summary: Updating TabBar and TabbarItem component to support arrow key navigation. Included in this PR - The creation of arrow key navigation for Tabbar and specifically the TabItems used for changing the presenting panel. - Implementation of functionality for tab bar items, specifically the focus property and ability to change the role type. Issue: https://khanacademy.atlassian.net/browse/LEMS-2130 ## Test plan: Storybook - http://localhost:6006/?path=/story/math-input-full-keypad--everything-minus-navigation-pad - Confirm that tabbing goes through only the selected tabitem, and to select another tab a user must use their left and right arrow keys. Author: catandthemachines Reviewers: catandthemachines, handeyeco, mark-fitzgerald, anakaren-rojas Required Reviewers: Approved By: mark-fitzgerald Checks: ⌛ Upload Coverage (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ gerald Pull Request URL: #1384
1 parent 4b56e10 commit 5de4833

File tree

12 files changed

+231
-86
lines changed

12 files changed

+231
-86
lines changed

.changeset/polite-snails-know.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/math-input": minor
3+
---
4+
5+
Updating TabBar experience in to use arrow-key navigation to access the other TabItems. This will ensure the Expression Widget in perseus has proper keyboard navigation for users.

packages/math-input/src/components/keypad/__tests__/__snapshots__/keypad.test.tsx.snap

+10-4
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@ exports[`keypad should snapshot expanded: first render 1`] = `
1010
>
1111
<div
1212
class="default_xu2jcg-o_O-tabbar_721koc"
13-
role="tablist"
1413
>
1514
<div
1615
class="default_xu2jcg-o_O-pages_cjo0m2"
16+
role="tablist"
1717
>
1818
<button
1919
aria-disabled="false"
2020
aria-label="Numbers"
2121
aria-selected="true"
2222
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
2323
role="tab"
24+
tabindex="0"
2425
type="button"
2526
>
2627
<div
@@ -53,6 +54,7 @@ exports[`keypad should snapshot expanded: first render 1`] = `
5354
aria-selected="false"
5455
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
5556
role="tab"
57+
tabindex="-1"
5658
type="button"
5759
>
5860
<div
@@ -84,6 +86,7 @@ exports[`keypad should snapshot expanded: first render 1`] = `
8486
aria-selected="false"
8587
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
8688
role="tab"
89+
tabindex="-1"
8790
type="button"
8891
>
8992
<div
@@ -115,6 +118,7 @@ exports[`keypad should snapshot expanded: first render 1`] = `
115118
aria-selected="false"
116119
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
117120
role="tab"
121+
tabindex="-1"
118122
type="button"
119123
>
120124
<div
@@ -170,7 +174,6 @@ exports[`keypad should snapshot expanded: first render 1`] = `
170174
<div
171175
aria-label="Keypad"
172176
class="default_xu2jcg-o_O-keypadGrid_ztxlrb-o_O-expressionGrid_1fuqhx9"
173-
tabindex="0"
174177
>
175178
<div
176179
class="default_xu2jcg-o_O-inlineStyles_1c2q4k1"
@@ -1063,17 +1066,18 @@ exports[`keypad should snapshot unexpanded: first render 1`] = `
10631066
>
10641067
<div
10651068
class="default_xu2jcg-o_O-tabbar_721koc"
1066-
role="tablist"
10671069
>
10681070
<div
10691071
class="default_xu2jcg-o_O-pages_cjo0m2"
1072+
role="tablist"
10701073
>
10711074
<button
10721075
aria-disabled="false"
10731076
aria-label="Numbers"
10741077
aria-selected="true"
10751078
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
10761079
role="tab"
1080+
tabindex="0"
10771081
type="button"
10781082
>
10791083
<div
@@ -1106,6 +1110,7 @@ exports[`keypad should snapshot unexpanded: first render 1`] = `
11061110
aria-selected="false"
11071111
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
11081112
role="tab"
1113+
tabindex="-1"
11091114
type="button"
11101115
>
11111116
<div
@@ -1137,6 +1142,7 @@ exports[`keypad should snapshot unexpanded: first render 1`] = `
11371142
aria-selected="false"
11381143
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
11391144
role="tab"
1145+
tabindex="-1"
11401146
type="button"
11411147
>
11421148
<div
@@ -1168,6 +1174,7 @@ exports[`keypad should snapshot unexpanded: first render 1`] = `
11681174
aria-selected="false"
11691175
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
11701176
role="tab"
1177+
tabindex="-1"
11711178
type="button"
11721179
>
11731180
<div
@@ -1223,7 +1230,6 @@ exports[`keypad should snapshot unexpanded: first render 1`] = `
12231230
<div
12241231
aria-label="Keypad"
12251232
class="default_xu2jcg-o_O-keypadGrid_ztxlrb-o_O-expressionGrid_1fuqhx9"
1226-
tabindex="0"
12271233
>
12281234
<div
12291235
class="default_xu2jcg-o_O-inlineStyles_1c2q4k1"

packages/math-input/src/components/keypad/__tests__/__snapshots__/mobile-keypad.test.tsx.snap

+2-6
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ exports[`mobile keypad should render keypad when active 1`] = `
2121
>
2222
<div
2323
class="default_xu2jcg-o_O-tabbar_721koc"
24-
role="tablist"
2524
>
26-
<div
27-
class="default_xu2jcg-o_O-pages_cjo0m2"
28-
/>
2925
<div
3026
class="default_xu2jcg"
3127
>
@@ -34,7 +30,8 @@ exports[`mobile keypad should render keypad when active 1`] = `
3430
aria-label="Dismiss"
3531
aria-selected="false"
3632
class="button_vr44p2-o_O-reset_152ygtm-o_O-link_13xlah4-o_O-clickable_1ncqa8p"
37-
role="tab"
33+
role="button"
34+
tabindex="0"
3835
type="button"
3936
>
4037
<div
@@ -68,7 +65,6 @@ exports[`mobile keypad should render keypad when active 1`] = `
6865
<div
6966
aria-label="Keypad"
7067
class="default_xu2jcg-o_O-keypadGrid_ztxlrb-o_O-fractionsGrid_1aelnry"
71-
tabindex="0"
7268
>
7369
<div
7470
class="default_xu2jcg-o_O-inlineStyles_1c2q4k1"

packages/math-input/src/components/keypad/__tests__/keypad.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe("keypad", () => {
110110

111111
// Assert
112112
expect(
113-
screen.getByRole("tab", {
113+
screen.getByRole("button", {
114114
name: "Dismiss",
115115
}),
116116
).toBeInTheDocument();
@@ -125,7 +125,7 @@ describe("keypad", () => {
125125

126126
// Assert
127127
expect(
128-
screen.queryByRole("tab", {
128+
screen.queryByRole("button", {
129129
name: "Dismiss",
130130
}),
131131
).not.toBeInTheDocument();

packages/math-input/src/components/keypad/__tests__/mobile-keypad.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe("mobile keypad", () => {
5656
);
5757

5858
// Assert
59-
expect(screen.queryAllByRole("tab")).not.toHaveLength(0);
59+
expect(screen.queryAllByRole("button")).not.toHaveLength(0);
6060
});
6161

6262
it("should fire an 'opened' event when activated", () => {

packages/math-input/src/components/keypad/keypad.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ export default function Keypad(props: Props) {
151151
<View style={styles.keypadInnerContainer}>
152152
<View
153153
style={[styles.keypadGrid, gridStyle]}
154-
tabIndex={0}
155154
aria-label="Keypad"
156155
>
157156
{selectedPage === "Fractions" && (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {render, screen} from "@testing-library/react";
2+
import {userEvent as userEventLib} from "@testing-library/user-event";
3+
import * as React from "react";
4+
5+
import TabbarItem from "../item";
6+
7+
describe("<TabbarItem />", () => {
8+
let userEvent;
9+
beforeEach(() => {
10+
userEvent = userEventLib.setup({
11+
advanceTimers: jest.advanceTimersByTime,
12+
});
13+
});
14+
15+
it("renders tab item", async () => {
16+
// Arrange
17+
render(
18+
<TabbarItem
19+
itemType="Numbers"
20+
itemState="active"
21+
role="tab"
22+
onClick={() => {}}
23+
/>,
24+
);
25+
26+
// Assert
27+
expect(screen.getByRole("tab", {name: "Numbers"})).toBeInTheDocument();
28+
expect(screen.getByRole("tab", {name: "Numbers"})).not.toHaveFocus();
29+
});
30+
31+
it("renders tab item with role button", async () => {
32+
// Arrange
33+
render(
34+
<TabbarItem
35+
itemType="Numbers"
36+
itemState="active"
37+
role="button"
38+
onClick={() => {}}
39+
/>,
40+
);
41+
42+
// Assert
43+
expect(
44+
screen.getByRole("button", {name: "Numbers"}),
45+
).toBeInTheDocument();
46+
});
47+
48+
it("can set focus when focus is enabled", async () => {
49+
// Arrange
50+
jest.useFakeTimers();
51+
render(
52+
<TabbarItem
53+
itemType="Numbers"
54+
itemState="active"
55+
focus={true}
56+
role="tab"
57+
onClick={() => {}}
58+
/>,
59+
);
60+
jest.runAllTimers();
61+
62+
// Assert
63+
expect(screen.getByRole("tab", {name: "Numbers"})).toHaveFocus();
64+
});
65+
66+
it("handles onClick callback", async () => {
67+
// Arrange
68+
const mockClick = jest.fn();
69+
render(
70+
<TabbarItem
71+
itemType="Numbers"
72+
itemState="active"
73+
role="tab"
74+
onClick={mockClick}
75+
/>,
76+
);
77+
78+
// Assert
79+
await userEvent.click(screen.getByRole("tab", {name: "Numbers"}));
80+
expect(mockClick).toHaveBeenCalled();
81+
});
82+
});

packages/math-input/src/components/tabbar/__tests__/tabbar.test.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ describe("<Tabbar />", () => {
7373
);
7474

7575
// Assert
76-
expect(screen.getByRole("tab", {name: "Dismiss"})).toBeInTheDocument();
76+
expect(
77+
screen.getByRole("button", {name: "Dismiss"}),
78+
).toBeInTheDocument();
7779
});
7880

7981
it("does not show dismiss button without onClickClose callback", async () => {
@@ -88,7 +90,7 @@ describe("<Tabbar />", () => {
8890

8991
// Assert
9092
expect(
91-
screen.queryByRole("tab", {name: "Dismiss"}),
93+
screen.queryByRole("button", {name: "Dismiss"}),
9294
).not.toBeInTheDocument();
9395
});
9496

@@ -105,7 +107,7 @@ describe("<Tabbar />", () => {
105107
);
106108

107109
// Assert
108-
await userEvent.click(screen.getByRole("tab", {name: "Dismiss"}));
110+
await userEvent.click(screen.getByRole("button", {name: "Dismiss"}));
109111
expect(mockClickCloseCallback).toHaveBeenCalled();
110112
});
111113
});

0 commit comments

Comments
 (0)