@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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @khanacademy/wonder-blocks-clickable
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ceb111df: ClickableBehavior no longer has tabIndex 0 by default. It must be passed in.
8
+
3
9
  ## 2.3.3
4
10
 
5
11
  ### Patch Changes
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: 0
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 disabled={props.disabled} onClick={props.onClick}>
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
- // We set tabIndex to 0 so that users can tab to clickable
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-clickable",
3
- "version": "2.3.3",
3
+ "version": "2.4.0",
4
4
  "design": "v1",
5
5
  "description": "Clickable component for Wonder-Blocks.",
6
6
  "main": "dist/index.js",
@@ -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 should be 0", () => {
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", "0");
302
+ expect(button).not.toHaveAttribute("tabIndex");
303
303
  });
304
304
 
305
- test("tabIndex should be 0 even for disabled components", () => {
305
+ test("should have the tabIndex that is passed in", () => {
306
306
  // Arrange
307
307
  // Act
308
308
  render(
309
- <ClickableBehavior disabled={true} onClick={(e) => {}}>
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", "0");
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: number,
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 disabled={props.disabled} onClick={props.onClick}>
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
- // We set tabIndex to 0 so that users can tab to clickable
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