@khanacademy/wonder-blocks-clickable 2.4.5 → 2.4.6

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/clickable-behavior.d.ts +248 -0
  3. package/dist/components/clickable-behavior.js.flow +296 -0
  4. package/dist/components/clickable.d.ts +150 -0
  5. package/dist/components/clickable.js.flow +176 -0
  6. package/dist/es/index.js +147 -147
  7. package/dist/index.d.ts +7 -0
  8. package/dist/index.js +169 -171
  9. package/dist/index.js.flow +18 -2
  10. package/dist/util/get-clickable-behavior.d.ts +25 -0
  11. package/dist/util/get-clickable-behavior.js.flow +19 -0
  12. package/dist/util/is-client-side-url.d.ts +7 -0
  13. package/dist/util/is-client-side-url.js.flow +14 -0
  14. package/package.json +5 -5
  15. package/src/components/__tests__/{clickable-behavior.test.js → clickable-behavior.test.tsx} +138 -82
  16. package/src/components/__tests__/{clickable.test.js → clickable.test.tsx} +27 -26
  17. package/src/components/{clickable-behavior.js → clickable-behavior.ts} +63 -78
  18. package/src/components/{clickable.js → clickable.tsx} +107 -124
  19. package/src/{index.js → index.ts} +0 -1
  20. package/src/util/__tests__/{get-clickable-behavior.test.js → get-clickable-behavior.test.tsx} +0 -1
  21. package/src/util/__tests__/{is-client-side-url.js.test.js → is-client-side-url.js.test.ts} +2 -3
  22. package/src/util/{get-clickable-behavior.js → get-clickable-behavior.ts} +9 -3
  23. package/src/util/{is-client-side-url.js → is-client-side-url.ts} +0 -1
  24. package/tsconfig.json +12 -0
  25. package/tsconfig.tsbuildinfo +1 -0
  26. package/src/components/__docs__/accessibility.stories.mdx +0 -152
  27. package/src/components/__docs__/clickable-behavior.argtypes.js +0 -64
  28. package/src/components/__docs__/clickable-behavior.stories.js +0 -178
  29. package/src/components/__docs__/clickable.argtypes.js +0 -237
  30. package/src/components/__docs__/clickable.stories.js +0 -307
@@ -1,152 +0,0 @@
1
- import {Meta, Story, Canvas} from "@storybook/addon-docs";
2
- import {StyleSheet} from "aphrodite";
3
-
4
- import Clickable from "@khanacademy/wonder-blocks-clickable";
5
- import Color from "@khanacademy/wonder-blocks-color";
6
- import {View} from "@khanacademy/wonder-blocks-core";
7
- import Icon, {icons} from "@khanacademy/wonder-blocks-icon";
8
- import Spacing from "@khanacademy/wonder-blocks-spacing";
9
-
10
- <Meta
11
- title="Clickable / Clickable / Accessibility"
12
- component={Clickable}
13
- parameters={{
14
- previewTabs: {
15
- canvas: {hidden: true},
16
- },
17
- viewMode: "docs",
18
- chromatic: {
19
- // Disables chromatic testing for these stories.
20
- disableSnapshot: true,
21
- },
22
- }}
23
- />
24
-
25
- # Accessibility
26
-
27
- ## Keyboard interactions
28
-
29
- | Key | Action |
30
- | --- | --- |
31
- | Enter or Space | Activates the clickable element |
32
-
33
-
34
- ## Roles
35
-
36
- | Component | Role | Usage |
37
- | --- | --- | --- |
38
- | `<Clickable onClick={} />` | button | A clickable button element |
39
- | `<Clickable href="/math" skipClientNav={true} />` | link | A clickable anchor element |
40
- | `<Clickable href="/math" />` | link | A clickable anchor element (using `react-router`'s Link) |
41
-
42
- ## Attributes
43
- | Attribute | Usage |
44
- | --- | --- |
45
- | tabindex="0" | Includes the clickable element in the tab sequence. |
46
- | aria-disabled="true" | Indicates that the element is perceivable but disabled. |
47
- | aria-label="value" | Defines a string value that labels the clickable element. Use it in case the clickable element doesn't include any descriptive text (e.g. Icons, images) |
48
-
49
- ## Examples
50
-
51
- ### Labeling
52
-
53
- `Clickable` has an `ariaLabel` prop that sets the component's accessible name.
54
- ariaLabel should be passed when using graphical elements to let screen reader
55
- users know the purpose of the clickable element.
56
-
57
- _NOTE:_ If the clickable element is not graphical, it's best to avoid using `ariaLabel` as the text content of the element itself, which is read by default, should ideally be descriptive enough to not need to manually pass in the label.
58
-
59
- This is an example of a component with an accessible label:
60
-
61
- <Canvas>
62
- <Story name="Labeling">
63
- <View>
64
- <Clickable onClick={()=>{}} aria-label="More information about this subject">
65
- {({hovered, focused, pressed}) => (
66
- <Icon icon={icons.info} />
67
- )}
68
- </Clickable>
69
- </View>
70
- </Story>
71
- </Canvas>
72
-
73
- ### Disabled state
74
-
75
- Clickable does not need an `aria-disabled` attribute, if it also has a
76
- `disabled` component prop. We internally take care of defining the behavior so
77
- users can use these type of controls (including Screen Readers). By defining the
78
- internal behavior we can ensure that the component is accessible via Keyboard
79
- but not interactable/operatable.
80
-
81
- <Canvas>
82
- <Story name="Disabled state">
83
- <Clickable
84
- onClick={(e) => console.log("Hello, world!")}
85
- disabled={true}
86
- >
87
- {({hovered, focused, pressed}) => (
88
- "This is a disabled clickable element"
89
- )}
90
- </Clickable>
91
- </Story>
92
- </Canvas>
93
-
94
- ### Keyboard navigation
95
-
96
- Clickable adds support to keyboard navigation and setting ARIA attributes. This
97
- way, your components are accessible and emulate better the browser's behavior.
98
-
99
- **NOTE:** If you want to navigate to an external URL and/or reload the window,
100
- make sure to use `href` and `skipClientNav={true}.
101
-
102
- <Canvas>
103
- <Story name="Keyboard navigation">
104
- <View>
105
- <Clickable role="tab" aria-controls="panel-1" id="tab-1">
106
- {({hovered, focused, pressed}) => (
107
- <View
108
- style={[
109
- styles.resting,
110
- hovered && styles.hovered,
111
- focused && styles.focused,
112
- pressed && styles.pressed,
113
- ]}
114
- >
115
- <Body>Open School Info</Body>
116
- </View>
117
- )}
118
- </Clickable>
119
- <View
120
- id="panel-1"
121
- role="tabpanel"
122
- tabindex="0"
123
- aria-labelledby="tab-1"
124
- style={styles.panel}
125
- >
126
- This is the information for the school.
127
- </View>
128
- </View>
129
- </Story>
130
- </Canvas>
131
-
132
- export const styles = StyleSheet.create({
133
- resting: {
134
- boxShadow: `inset 0px 0px 1px 1px ${Color.lightBlue}`,
135
- padding: Spacing.xSmall_8,
136
- },
137
- hovered: {
138
- textDecoration: "underline",
139
- backgroundColor: Color.blue,
140
- color: Color.white,
141
- },
142
- pressed: {
143
- color: Color.darkBlue,
144
- },
145
- focused: {
146
- outline: `solid 4px ${Color.lightBlue}`,
147
- },
148
- panel: {
149
- padding: Spacing.medium_16,
150
- boxShadow: `inset 0px 0px 0 1px ${Color.offBlack8}`,
151
- }
152
- });
@@ -1,64 +0,0 @@
1
- import clickableArgtypes from "./clickable.argtypes";
2
-
3
- export default {
4
- children: {
5
- description:
6
- "A function that returns the a React `Element`. The function is passed an object with three boolean properties: hovered, focused, and pressed, and a `childrenProps` argument that contains all the event handlers that should be passed to the React `Element` itself.",
7
- type: {
8
- required: true,
9
- },
10
- table: {
11
- type: {
12
- summary:
13
- "(state: ClickableState, childrenProps: ChildrenProps) => React.Node",
14
- },
15
- },
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
- },
26
- /**
27
- * States
28
- */
29
- disabled: {
30
- ...clickableArgtypes.disabled,
31
- description:
32
- "Whether the component is disabled.\n\n" +
33
- "If the component is disabled, this component will return handlers that do nothing.",
34
- },
35
- /**
36
- * Events
37
- */
38
- onClick: {
39
- ...clickableArgtypes.onClick,
40
- description:
41
- "An onClick function which ClickableBehavior can execute when clicked.",
42
- },
43
- onkeyDown: clickableArgtypes.onkeyDown,
44
- onKeyUp: clickableArgtypes.onKeyUp,
45
- /**
46
- * Navigation
47
- */
48
- skipClientNav: clickableArgtypes.skipClientNav,
49
- rel: clickableArgtypes.rel,
50
- target: clickableArgtypes.target,
51
- href: {
52
- ...clickableArgtypes.href,
53
- description:
54
- "Optional `href` which `ClickableBehavior` should direct to, uses client-side routing by default if react-router is present.\n\n" +
55
- "For keyboard navigation, the default is that both an enter and space press would also navigate to this location. See the triggerOnEnter and triggerOnSpace props for more details",
56
- },
57
- beforeNav: clickableArgtypes.beforeNav,
58
- safeWithNav: clickableArgtypes.safeWithNav,
59
-
60
- /**
61
- * Accessibility
62
- */
63
- role: clickableArgtypes.role,
64
- };
@@ -1,178 +0,0 @@
1
- // @flow
2
- import * as React from "react";
3
- import {StyleSheet} from "aphrodite";
4
-
5
- import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
6
- import {View, addStyle} from "@khanacademy/wonder-blocks-core";
7
- import Color from "@khanacademy/wonder-blocks-color";
8
- import Spacing from "@khanacademy/wonder-blocks-spacing";
9
-
10
- import type {StoryComponentType} from "@storybook/react";
11
- import ComponentInfo from "../../../../../.storybook/components/component-info";
12
- import {name, version} from "../../../package.json";
13
- import argTypes from "./clickable-behavior.argtypes";
14
-
15
- const ClickableBehavior: React.ComponentType<
16
- React.ElementConfig<typeof ClickableBehavior>,
17
- > = getClickableBehavior();
18
-
19
- export default {
20
- title: "Clickable / ClickableBehavior",
21
- component: ClickableBehavior,
22
- argTypes: argTypes,
23
- args: {
24
- disabled: false,
25
- },
26
- parameters: {
27
- componentSubtitle: ((
28
- <ComponentInfo name={name} version={version} />
29
- ): any),
30
- docs: {
31
- description: {
32
- component: null,
33
- },
34
- source: {
35
- // See https://github.com/storybookjs/storybook/issues/12596
36
- excludeDecorators: true,
37
- },
38
- },
39
- },
40
- };
41
-
42
- export const Default: StoryComponentType = (args) => {
43
- const ClickableBehavior = getClickableBehavior();
44
-
45
- return (
46
- <ClickableBehavior role="button" {...args}>
47
- {(state, childrenProps) => {
48
- const {pressed, hovered, focused} = state;
49
- return (
50
- <View
51
- style={[
52
- styles.clickable,
53
- hovered && styles.hovered,
54
- focused && styles.focused,
55
- pressed && styles.pressed,
56
- ]}
57
- {...childrenProps}
58
- >
59
- This is an element wrapped with ClickableBehavior
60
- </View>
61
- );
62
- }}
63
- </ClickableBehavior>
64
- );
65
- };
66
-
67
- Default.parameters = {
68
- chromatic: {
69
- // we don't need screenshots because this story only displays the
70
- // resting/default state.
71
- disableSnapshot: true,
72
- },
73
- };
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
-
156
- const styles = StyleSheet.create({
157
- clickable: {
158
- cursor: "pointer",
159
- padding: Spacing.medium_16,
160
- textAlign: "center",
161
- },
162
- newButton: {
163
- border: "none",
164
- backgroundColor: Color.white,
165
- width: "100%",
166
- },
167
- hovered: {
168
- textDecoration: "underline",
169
- backgroundColor: Color.blue,
170
- color: Color.white,
171
- },
172
- pressed: {
173
- backgroundColor: Color.darkBlue,
174
- },
175
- focused: {
176
- outline: `solid 4px ${Color.lightBlue}`,
177
- },
178
- });
@@ -1,237 +0,0 @@
1
- export default {
2
- children: {
3
- description:
4
- "The child of Clickable must be a function which returns the component which should be made Clickable. The function is passed an object with three boolean properties: hovered, focused, and pressed.",
5
- control: {
6
- type: "text",
7
- },
8
- type: {
9
- required: true,
10
- },
11
- table: {
12
- type: {
13
- summary: "(ClickableState) => React.Node",
14
- },
15
- },
16
- },
17
- id: {
18
- description: "An optional id attribute.",
19
- table: {
20
- type: {
21
- summary: "string",
22
- },
23
- },
24
- control: {
25
- type: "text",
26
- },
27
- },
28
- /**
29
- * States
30
- */
31
- light: {
32
- description:
33
- "Whether the Clickable is on a dark colored background. Sets the default focus ring color to white, instead of blue. Defaults to false.",
34
- defaultValue: false,
35
- type: {
36
- required: true,
37
- },
38
- table: {
39
- category: "States",
40
- type: {
41
- summary: "boolean",
42
- },
43
- },
44
- },
45
- disabled: {
46
- description: "Disables or enables the child; defaults to false",
47
- defaultValue: false,
48
- type: {
49
- required: true,
50
- },
51
- table: {
52
- category: "States",
53
- type: {
54
- summary: "boolean",
55
- },
56
- },
57
- },
58
- hideDefaultFocusRing: {
59
- description:
60
- "Don't show the default focus ring. This should be used when implementing a custom focus ring within your own component that uses Clickable.",
61
- table: {
62
- category: "States",
63
- type: {
64
- summary: "boolean",
65
- },
66
- },
67
- },
68
- /**
69
- * Styling
70
- */
71
- className: {
72
- description: "Adds CSS classes to the Clickable.",
73
- control: {
74
- type: "text",
75
- },
76
- table: {
77
- category: "Styling",
78
- },
79
- type: {
80
- summary: "string",
81
- },
82
- },
83
- style: {
84
- description: "Optional custom styles.",
85
- table: {
86
- category: "Styling",
87
- type: {
88
- summary: "StyleType",
89
- },
90
- },
91
- },
92
- /**
93
- * Events
94
- */
95
- onClick: {
96
- description:
97
- "An onClick function which Clickable can execute when clicked.",
98
- table: {
99
- category: "Events",
100
- type: {
101
- summary: "(e: SyntheticEvent<>) => mixed",
102
- detail: "`onClick` is optional if `href` is present, but must be defined if `href` is not",
103
- },
104
- },
105
- action: "clicked",
106
- },
107
- onkeyDown: {
108
- description: "Respond to raw `keydown` event.",
109
- table: {
110
- category: "Events",
111
- type: {
112
- summary: "(e: SyntheticKeyboardEvent<>) => mixed",
113
- },
114
- },
115
- },
116
- onKeyUp: {
117
- description: "Respond to raw `keyup` event.",
118
- table: {
119
- category: "Events",
120
- type: {
121
- summary: "(e: SyntheticKeyboardEvent<>) => mixed",
122
- },
123
- },
124
- },
125
- /**
126
- * Navigation
127
- */
128
- skipClientNav: {
129
- description:
130
- "Avoids client-side routing in the presence of the `href` prop",
131
- defaultValue: false,
132
- control: {
133
- type: "boolean",
134
- },
135
- table: {
136
- category: "Navigation",
137
- type: {
138
- summary: "boolean",
139
- },
140
- },
141
- },
142
- rel: {
143
- description:
144
- 'Specifies the type of relationship between the current document and the linked document. Should only be used when `href` is specified. This defaults to `noopener noreferrer` when `target="_blank"`, but can be overridden by setting this prop to something else.',
145
- control: {type: "text"},
146
- table: {
147
- category: "Navigation",
148
- type: {
149
- summary: "string",
150
- },
151
- },
152
- },
153
- target: {
154
- description:
155
- "A target destination window for a link to open in. Should only be used when `href` is specified.",
156
- control: {type: "text"},
157
- table: {
158
- category: "Navigation",
159
- type: {
160
- summary: "string",
161
- },
162
- },
163
- },
164
- href: {
165
- description:
166
- "Optional `href` which `Clickable` should direct to, uses client-side routing by default if react-router is present",
167
- control: {type: "text"},
168
- table: {
169
- category: "Navigation",
170
- type: {
171
- summary: "string",
172
- detail: "URL is required when we use `safeWithNav`",
173
- },
174
- },
175
- },
176
- beforeNav: {
177
- description:
178
- "Run async code before navigating. If the promise returned rejects then navigation will not occur. If both `safeWithNav` and `beforeNav` are provided, `beforeNav` will be run first and `safeWithNav` will only be run if `beforeNav` does not reject.",
179
- table: {
180
- category: "Navigation",
181
- type: {
182
- summary: "() => Promise<mixed>",
183
- },
184
- },
185
- },
186
- safeWithNav: {
187
- description: `Run async code in the background while client-side navigating. If the browser does a full page load navigation, the callback promise must be settled before the navigation will occur. Errors are ignored so that navigation is guaranteed to succeed.`,
188
- table: {
189
- category: "Navigation",
190
- type: {
191
- summary: "() => Promise<mixed>",
192
- },
193
- },
194
- },
195
-
196
- /**
197
- * Accessibility
198
- */
199
- "aria-label": {
200
- description:
201
- "A label for the clickable element read by a screen reader.",
202
- control: {
203
- type: "text",
204
- },
205
- table: {
206
- category: "Accessibility",
207
- type: {
208
- summary: "string",
209
- detail: `aria-label should be used when using
210
- graphical elements to let people using screen readers the purpose of the
211
- clickable element.`,
212
- },
213
- },
214
- },
215
- role: {
216
- description:
217
- "The role of the component, can be a role of type `ClickableRole`",
218
- control: {type: "select"},
219
- options: [
220
- "button",
221
- "checkbox",
222
- "link",
223
- "listbox",
224
- "menu",
225
- "menuitem",
226
- "radio",
227
- "tab",
228
- ],
229
- table: {
230
- category: "Accessibility",
231
- type: {
232
- summary: "ClickableRole",
233
- detail: `"button" | "link" | "checkbox" | "radio" | "listbox" | "option" | "menuitem" | "menu" | "tab"`,
234
- },
235
- },
236
- },
237
- };