@khanacademy/wonder-blocks-clickable 2.3.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/es/index.js +4 -4
- package/dist/index.js +9 -9
- package/package.json +1 -1
- package/src/components/__docs__/clickable-behavior.argtypes.js +9 -0
- package/src/components/__docs__/clickable-behavior.stories.js +87 -1
- package/src/components/__tests__/clickable-behavior.test.js +55 -5
- package/src/components/clickable-behavior.js +15 -8
package/CHANGELOG.md
CHANGED
package/dist/es/index.js
CHANGED
|
@@ -46,8 +46,7 @@ const disabledHandlers = {
|
|
|
46
46
|
onTouchEnd: () => void 0,
|
|
47
47
|
onTouchCancel: () => void 0,
|
|
48
48
|
onKeyDown: () => void 0,
|
|
49
|
-
onKeyUp: () => void 0
|
|
50
|
-
tabIndex: 0
|
|
49
|
+
onKeyUp: () => void 0
|
|
51
50
|
};
|
|
52
51
|
const keyCodes = {
|
|
53
52
|
enter: 13,
|
|
@@ -351,7 +350,8 @@ class ClickableBehavior extends React.Component {
|
|
|
351
350
|
render() {
|
|
352
351
|
const childrenProps = this.props.disabled ? _extends({}, disabledHandlers, {
|
|
353
352
|
onFocus: this.handleFocus,
|
|
354
|
-
onBlur: this.handleBlur
|
|
353
|
+
onBlur: this.handleBlur,
|
|
354
|
+
tabIndex: this.props.tabIndex
|
|
355
355
|
}) : {
|
|
356
356
|
onClick: this.handleClick,
|
|
357
357
|
onMouseEnter: this.handleMouseEnter,
|
|
@@ -366,7 +366,7 @@ class ClickableBehavior extends React.Component {
|
|
|
366
366
|
onKeyUp: this.handleKeyUp,
|
|
367
367
|
onFocus: this.handleFocus,
|
|
368
368
|
onBlur: this.handleBlur,
|
|
369
|
-
tabIndex:
|
|
369
|
+
tabIndex: this.props.tabIndex
|
|
370
370
|
};
|
|
371
371
|
childrenProps.rel = this.props.rel || (this.props.target === "_blank" ? "noopener noreferrer" : undefined);
|
|
372
372
|
const {
|
package/dist/index.js
CHANGED
|
@@ -163,10 +163,7 @@ const disabledHandlers = {
|
|
|
163
163
|
onTouchEnd: () => void 0,
|
|
164
164
|
onTouchCancel: () => void 0,
|
|
165
165
|
onKeyDown: () => void 0,
|
|
166
|
-
onKeyUp: () => void 0
|
|
167
|
-
// Clickable components should still be tabbable so they can
|
|
168
|
-
// be used as anchors.
|
|
169
|
-
tabIndex: 0
|
|
166
|
+
onKeyUp: () => void 0
|
|
170
167
|
};
|
|
171
168
|
const keyCodes = {
|
|
172
169
|
enter: 13,
|
|
@@ -225,7 +222,11 @@ const startState = {
|
|
|
225
222
|
* const ClickableBehavior = getClickableBehavior();
|
|
226
223
|
*
|
|
227
224
|
* return (
|
|
228
|
-
* <ClickableBehavior
|
|
225
|
+
* <ClickableBehavior
|
|
226
|
+
* disabled={props.disabled}
|
|
227
|
+
* onClick={props.onClick}
|
|
228
|
+
* tabIndex={0}
|
|
229
|
+
* >
|
|
229
230
|
* {({hovered}, childrenProps) => (
|
|
230
231
|
* <RoundRect
|
|
231
232
|
* textcolor="white"
|
|
@@ -580,7 +581,8 @@ class ClickableBehavior extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
580
581
|
const childrenProps = this.props.disabled ? { ...disabledHandlers,
|
|
581
582
|
// Keep these handlers for keyboard accessibility.
|
|
582
583
|
onFocus: this.handleFocus,
|
|
583
|
-
onBlur: this.handleBlur
|
|
584
|
+
onBlur: this.handleBlur,
|
|
585
|
+
tabIndex: this.props.tabIndex
|
|
584
586
|
} : {
|
|
585
587
|
onClick: this.handleClick,
|
|
586
588
|
onMouseEnter: this.handleMouseEnter,
|
|
@@ -595,9 +597,7 @@ class ClickableBehavior extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
595
597
|
onKeyUp: this.handleKeyUp,
|
|
596
598
|
onFocus: this.handleFocus,
|
|
597
599
|
onBlur: this.handleBlur,
|
|
598
|
-
|
|
599
|
-
// things that aren't buttons or anchors.
|
|
600
|
-
tabIndex: 0
|
|
600
|
+
tabIndex: this.props.tabIndex
|
|
601
601
|
}; // When the link is set to open in a new window, we want to set some
|
|
602
602
|
// `rel` attributes. This is to ensure that the links we're sending folks
|
|
603
603
|
// to can't hijack the existing page. These defaults can be overriden
|
package/package.json
CHANGED
|
@@ -14,6 +14,15 @@ export default {
|
|
|
14
14
|
},
|
|
15
15
|
},
|
|
16
16
|
},
|
|
17
|
+
tabIndex: {
|
|
18
|
+
control: {type: "number"},
|
|
19
|
+
description: `Used to indicate the tab order of an element.
|
|
20
|
+
Use 0 to make an element focusable, and use -1 to make an
|
|
21
|
+
element non-focusable via keyboard navigation.`,
|
|
22
|
+
table: {
|
|
23
|
+
type: {summary: "number"},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
17
26
|
/**
|
|
18
27
|
* States
|
|
19
28
|
*/
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import {StyleSheet} from "aphrodite";
|
|
4
4
|
|
|
5
5
|
import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
6
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import {View, addStyle} from "@khanacademy/wonder-blocks-core";
|
|
7
7
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
8
8
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
9
9
|
|
|
@@ -72,12 +72,98 @@ Default.parameters = {
|
|
|
72
72
|
},
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
+
export const WrappingButton: StoryComponentType = (args) => {
|
|
76
|
+
const ClickableBehavior = getClickableBehavior();
|
|
77
|
+
const StyledButton = addStyle("button");
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<ClickableBehavior {...args}>
|
|
81
|
+
{(state, childrenProps) => {
|
|
82
|
+
const {pressed, hovered, focused} = state;
|
|
83
|
+
return (
|
|
84
|
+
<StyledButton
|
|
85
|
+
style={[
|
|
86
|
+
styles.clickable,
|
|
87
|
+
styles.newButton,
|
|
88
|
+
hovered && styles.hovered,
|
|
89
|
+
focused && styles.focused,
|
|
90
|
+
pressed && styles.pressed,
|
|
91
|
+
]}
|
|
92
|
+
{...childrenProps}
|
|
93
|
+
>
|
|
94
|
+
This is an element wrapped with ClickableBehavior
|
|
95
|
+
</StyledButton>
|
|
96
|
+
);
|
|
97
|
+
}}
|
|
98
|
+
</ClickableBehavior>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
WrappingButton.parameters = {
|
|
103
|
+
chromatic: {
|
|
104
|
+
// we don't need screenshots because this story only displays the
|
|
105
|
+
// resting/default state.
|
|
106
|
+
disableSnapshot: true,
|
|
107
|
+
},
|
|
108
|
+
docs: {
|
|
109
|
+
storyDescription: `This is an example of a \`<ClickableBehavior>\`
|
|
110
|
+
wrapping a button. Since buttons have a built in tabIndex,
|
|
111
|
+
a tabIndex does not need to be added to \`<ClickableBehavior>\`
|
|
112
|
+
here.`,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const WithTabIndex: StoryComponentType = () => {
|
|
117
|
+
const ClickableBehavior = getClickableBehavior();
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ClickableBehavior role="button" tabIndex={0}>
|
|
121
|
+
{(state, childrenProps) => {
|
|
122
|
+
const {pressed, hovered, focused} = state;
|
|
123
|
+
return (
|
|
124
|
+
<View
|
|
125
|
+
style={[
|
|
126
|
+
styles.clickable,
|
|
127
|
+
hovered && styles.hovered,
|
|
128
|
+
focused && styles.focused,
|
|
129
|
+
pressed && styles.pressed,
|
|
130
|
+
]}
|
|
131
|
+
{...childrenProps}
|
|
132
|
+
>
|
|
133
|
+
This is an element wrapped with ClickableBehavior
|
|
134
|
+
</View>
|
|
135
|
+
);
|
|
136
|
+
}}
|
|
137
|
+
</ClickableBehavior>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
WithTabIndex.parameters = {
|
|
142
|
+
chromatic: {
|
|
143
|
+
// we don't need screenshots because this story only displays the
|
|
144
|
+
// resting/default state.
|
|
145
|
+
disableSnapshot: true,
|
|
146
|
+
},
|
|
147
|
+
docs: {
|
|
148
|
+
storyDescription: `A \`<ClickableBehavior>\` element does not have
|
|
149
|
+
a tabIndex by default, as many elements it could wrap may have
|
|
150
|
+
their own built in tabIndex attribute, such as buttons. If this
|
|
151
|
+
is not the case, a tabIndex should be passed in using the
|
|
152
|
+
\`tabIndex\` prop.`,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
75
156
|
const styles = StyleSheet.create({
|
|
76
157
|
clickable: {
|
|
77
158
|
cursor: "pointer",
|
|
78
159
|
padding: Spacing.medium_16,
|
|
79
160
|
textAlign: "center",
|
|
80
161
|
},
|
|
162
|
+
newButton: {
|
|
163
|
+
border: "none",
|
|
164
|
+
backgroundColor: Color.white,
|
|
165
|
+
width: "100%",
|
|
166
|
+
},
|
|
81
167
|
hovered: {
|
|
82
168
|
textDecoration: "underline",
|
|
83
169
|
backgroundColor: Color.blue,
|
|
@@ -282,7 +282,7 @@ describe("ClickableBehavior", () => {
|
|
|
282
282
|
expect(button).not.toHaveTextContent("focused");
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
-
test("tabIndex
|
|
285
|
+
test("should not have a tabIndex if one is not passed in", () => {
|
|
286
286
|
// Arrange
|
|
287
287
|
// Act
|
|
288
288
|
render(
|
|
@@ -299,14 +299,18 @@ describe("ClickableBehavior", () => {
|
|
|
299
299
|
|
|
300
300
|
// Assert
|
|
301
301
|
const button = screen.getByTestId("test-button-1");
|
|
302
|
-
expect(button).toHaveAttribute("tabIndex"
|
|
302
|
+
expect(button).not.toHaveAttribute("tabIndex");
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
test("
|
|
305
|
+
test("should have the tabIndex that is passed in", () => {
|
|
306
306
|
// Arrange
|
|
307
307
|
// Act
|
|
308
308
|
render(
|
|
309
|
-
<ClickableBehavior
|
|
309
|
+
<ClickableBehavior
|
|
310
|
+
disabled={false}
|
|
311
|
+
onClick={(e) => {}}
|
|
312
|
+
tabIndex={1}
|
|
313
|
+
>
|
|
310
314
|
{(state, childrenProps) => {
|
|
311
315
|
return (
|
|
312
316
|
<button data-test-id="test-button-2" {...childrenProps}>
|
|
@@ -319,7 +323,53 @@ describe("ClickableBehavior", () => {
|
|
|
319
323
|
|
|
320
324
|
// Assert
|
|
321
325
|
const button = screen.getByTestId("test-button-2");
|
|
322
|
-
expect(button).toHaveAttribute("tabIndex", "
|
|
326
|
+
expect(button).toHaveAttribute("tabIndex", "1");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("should have the tabIndex that is passed in even if disabled", () => {
|
|
330
|
+
// Arrange
|
|
331
|
+
// Act
|
|
332
|
+
render(
|
|
333
|
+
<ClickableBehavior disabled={true} onClick={(e) => {}} tabIndex={1}>
|
|
334
|
+
{(state, childrenProps) => {
|
|
335
|
+
return (
|
|
336
|
+
<button data-test-id="test-button-3" {...childrenProps}>
|
|
337
|
+
Label
|
|
338
|
+
</button>
|
|
339
|
+
);
|
|
340
|
+
}}
|
|
341
|
+
</ClickableBehavior>,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// Assert
|
|
345
|
+
const button = screen.getByTestId("test-button-3");
|
|
346
|
+
expect(button).toHaveAttribute("tabIndex", "1");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("should make non-interactive children keyboard focusable if tabIndex 0 is passed", () => {
|
|
350
|
+
// Arrange
|
|
351
|
+
render(
|
|
352
|
+
<ClickableBehavior
|
|
353
|
+
disabled={false}
|
|
354
|
+
onClick={(e) => {}}
|
|
355
|
+
tabIndex={1}
|
|
356
|
+
>
|
|
357
|
+
{(state, childrenProps) => {
|
|
358
|
+
return (
|
|
359
|
+
<div data-test-id="test-div-1" {...childrenProps}>
|
|
360
|
+
Label
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}}
|
|
364
|
+
</ClickableBehavior>,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Act
|
|
368
|
+
const button = screen.getByTestId("test-div-1");
|
|
369
|
+
userEvent.tab();
|
|
370
|
+
|
|
371
|
+
// Assert
|
|
372
|
+
expect(button).toHaveFocus();
|
|
323
373
|
});
|
|
324
374
|
|
|
325
375
|
it("does not change state if disabled", () => {
|
|
@@ -88,6 +88,13 @@ type CommonProps = {|
|
|
|
88
88
|
|
|
89
89
|
skipClientNav?: boolean,
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Used to indicate the tab order of an element.
|
|
93
|
+
* Use 0 to make an element focusable, and use -1 to make an
|
|
94
|
+
* element non-focusable via keyboard navigation.
|
|
95
|
+
*/
|
|
96
|
+
tabIndex?: number,
|
|
97
|
+
|
|
91
98
|
/**
|
|
92
99
|
* A function to be executed `onclick`.
|
|
93
100
|
*/
|
|
@@ -206,7 +213,7 @@ export type ChildrenProps = {|
|
|
|
206
213
|
onKeyUp: (e: SyntheticKeyboardEvent<>) => mixed,
|
|
207
214
|
onFocus: (e: SyntheticFocusEvent<>) => mixed,
|
|
208
215
|
onBlur: (e: SyntheticFocusEvent<>) => mixed,
|
|
209
|
-
tabIndex
|
|
216
|
+
tabIndex?: number,
|
|
210
217
|
rel?: string,
|
|
211
218
|
|};
|
|
212
219
|
|
|
@@ -222,9 +229,6 @@ const disabledHandlers = {
|
|
|
222
229
|
onTouchCancel: () => void 0,
|
|
223
230
|
onKeyDown: () => void 0,
|
|
224
231
|
onKeyUp: () => void 0,
|
|
225
|
-
// Clickable components should still be tabbable so they can
|
|
226
|
-
// be used as anchors.
|
|
227
|
-
tabIndex: 0,
|
|
228
232
|
};
|
|
229
233
|
|
|
230
234
|
const keyCodes = {
|
|
@@ -286,7 +290,11 @@ const startState: ClickableState = {
|
|
|
286
290
|
* const ClickableBehavior = getClickableBehavior();
|
|
287
291
|
*
|
|
288
292
|
* return (
|
|
289
|
-
* <ClickableBehavior
|
|
293
|
+
* <ClickableBehavior
|
|
294
|
+
* disabled={props.disabled}
|
|
295
|
+
* onClick={props.onClick}
|
|
296
|
+
* tabIndex={0}
|
|
297
|
+
* >
|
|
290
298
|
* {({hovered}, childrenProps) => (
|
|
291
299
|
* <RoundRect
|
|
292
300
|
* textcolor="white"
|
|
@@ -614,6 +622,7 @@ export default class ClickableBehavior extends React.Component<
|
|
|
614
622
|
// Keep these handlers for keyboard accessibility.
|
|
615
623
|
onFocus: this.handleFocus,
|
|
616
624
|
onBlur: this.handleBlur,
|
|
625
|
+
tabIndex: this.props.tabIndex,
|
|
617
626
|
}
|
|
618
627
|
: {
|
|
619
628
|
onClick: this.handleClick,
|
|
@@ -629,9 +638,7 @@ export default class ClickableBehavior extends React.Component<
|
|
|
629
638
|
onKeyUp: this.handleKeyUp,
|
|
630
639
|
onFocus: this.handleFocus,
|
|
631
640
|
onBlur: this.handleBlur,
|
|
632
|
-
|
|
633
|
-
// things that aren't buttons or anchors.
|
|
634
|
-
tabIndex: 0,
|
|
641
|
+
tabIndex: this.props.tabIndex,
|
|
635
642
|
};
|
|
636
643
|
|
|
637
644
|
// When the link is set to open in a new window, we want to set some
|