@khanacademy/wonder-blocks-clickable 2.2.6 → 2.2.7
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 +8 -0
- package/dist/es/index.js +14 -2
- package/dist/index.js +128 -108
- package/package.json +2 -2
- package/src/components/__docs__/accessibility.stories.mdx +152 -0
- package/src/components/__docs__/clickable-behavior.argtypes.js +55 -0
- package/src/components/__docs__/clickable-behavior.stories.js +92 -0
- package/src/components/__docs__/clickable.argtypes.js +237 -0
- package/src/components/__docs__/clickable.stories.js +300 -0
- package/src/components/clickable-behavior.js +33 -34
- package/src/components/clickable.js +29 -7
- package/src/components/clickable.stories.js +0 -127
package/CHANGELOG.md
CHANGED
package/dist/es/index.js
CHANGED
|
@@ -453,7 +453,7 @@ class Clickable extends React.Component {
|
|
|
453
453
|
|
|
454
454
|
const ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
|
|
455
455
|
|
|
456
|
-
const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), style];
|
|
456
|
+
const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), disabled && styles.disabled, style];
|
|
457
457
|
|
|
458
458
|
if (beforeNav) {
|
|
459
459
|
return React.createElement(ClickableBehavior, {
|
|
@@ -517,10 +517,22 @@ const styles = StyleSheet.create({
|
|
|
517
517
|
cursor: "pointer"
|
|
518
518
|
},
|
|
519
519
|
focused: {
|
|
520
|
-
|
|
520
|
+
":focus": {
|
|
521
|
+
outline: `solid 2px ${Color.blue}`
|
|
522
|
+
}
|
|
521
523
|
},
|
|
522
524
|
focusedLight: {
|
|
523
525
|
outline: `solid 2px ${Color.white}`
|
|
526
|
+
},
|
|
527
|
+
disabled: {
|
|
528
|
+
color: Color.offBlack32,
|
|
529
|
+
cursor: "not-allowed",
|
|
530
|
+
":focus": {
|
|
531
|
+
outline: "none"
|
|
532
|
+
},
|
|
533
|
+
":focus-visible": {
|
|
534
|
+
outline: `solid 2px ${Color.blue}`
|
|
535
|
+
}
|
|
524
536
|
}
|
|
525
537
|
});
|
|
526
538
|
|
package/dist/index.js
CHANGED
|
@@ -200,18 +200,19 @@ const startState = {
|
|
|
200
200
|
* 3. Keyup (spacebar/enter) -> focus state
|
|
201
201
|
*
|
|
202
202
|
* Warning: The event handlers returned (onClick, onMouseEnter, onMouseLeave,
|
|
203
|
-
* onMouseDown, onMouseUp, onDragStart, onTouchStart, onTouchEnd, onTouchCancel,
|
|
204
|
-
* onKeyUp, onFocus, onBlur, tabIndex) should be passed on to the
|
|
205
|
-
* that has the ClickableBehavior. You cannot override these handlers
|
|
206
|
-
* potentially breaking the functionality of ClickableBehavior.
|
|
203
|
+
* onMouseDown, onMouseUp, onDragStart, onTouchStart, onTouchEnd, onTouchCancel,
|
|
204
|
+
* onKeyDown, onKeyUp, onFocus, onBlur, tabIndex) should be passed on to the
|
|
205
|
+
* component that has the ClickableBehavior. You cannot override these handlers
|
|
206
|
+
* without potentially breaking the functionality of ClickableBehavior.
|
|
207
207
|
*
|
|
208
|
-
* There are internal props triggerOnEnter and triggerOnSpace that can be set
|
|
209
|
-
*
|
|
210
|
-
*
|
|
208
|
+
* There are internal props triggerOnEnter and triggerOnSpace that can be set to
|
|
209
|
+
* false if one of those keys shouldn't count as a click on this component. Be
|
|
210
|
+
* careful about setting those to false -- make certain that the component
|
|
211
211
|
* shouldn't process that key.
|
|
212
212
|
*
|
|
213
|
-
* See [this
|
|
214
|
-
|
|
213
|
+
* See [this
|
|
214
|
+
document](https://docs.google.com/document/d/1DG5Rg2f0cawIL5R8UqnPQpd7pbdObk8OyjO5ryYQmBM/edit#)
|
|
215
|
+
for a more thorough explanation of expected behaviors and potential cavaets.
|
|
215
216
|
*
|
|
216
217
|
* `ClickableBehavior` accepts a function as `children` which is passed state
|
|
217
218
|
* and an object containing event handlers and some other props. The `children`
|
|
@@ -219,32 +220,30 @@ const startState = {
|
|
|
219
220
|
*
|
|
220
221
|
* Example:
|
|
221
222
|
*
|
|
222
|
-
* ```
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* </ClickableBehavior>
|
|
240
|
-
* }
|
|
223
|
+
* ```jsx
|
|
224
|
+
* function MyClickableComponent(props: Props) {
|
|
225
|
+
* const ClickableBehavior = getClickableBehavior();
|
|
226
|
+
*
|
|
227
|
+
* return (
|
|
228
|
+
* <ClickableBehavior disabled={props.disabled} onClick={props.onClick}>
|
|
229
|
+
* {({hovered}, childrenProps) => (
|
|
230
|
+
* <RoundRect
|
|
231
|
+
* textcolor="white"
|
|
232
|
+
* backgroundColor={hovered ? "red" : "blue"}
|
|
233
|
+
* {...childrenProps}
|
|
234
|
+
* >
|
|
235
|
+
* {props.children}
|
|
236
|
+
* </RoundRect>
|
|
237
|
+
* )}
|
|
238
|
+
* </ClickableBehavior>
|
|
239
|
+
* );
|
|
241
240
|
* }
|
|
242
241
|
* ```
|
|
243
242
|
*
|
|
244
|
-
* This follows a pattern called [Function as Child
|
|
245
|
-
* (https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9).
|
|
243
|
+
* This follows a pattern called [Function as Child
|
|
244
|
+
* Components](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9).
|
|
246
245
|
*
|
|
247
|
-
* WARNING
|
|
246
|
+
* **WARNING:** Do not use this component directly, use getClickableBehavior
|
|
248
247
|
* instead. getClickableBehavior takes three arguments (href, directtNav, and
|
|
249
248
|
* router) and returns either the default ClickableBehavior or a react-router
|
|
250
249
|
* aware version.
|
|
@@ -252,9 +251,9 @@ const startState = {
|
|
|
252
251
|
* The react-router aware version is returned if `router` is a react-router-dom
|
|
253
252
|
* router, `skipClientNav` is not `true`, and `href` is an internal URL.
|
|
254
253
|
*
|
|
255
|
-
* The `router` can be accessed via __RouterContext (imported from
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
* The `router` can be accessed via __RouterContext (imported from
|
|
255
|
+
'react-router') from a component rendered as a descendant of a BrowserRouter.
|
|
256
|
+
See https://reacttraining.com/react-router/web/guides/basic-components.
|
|
258
257
|
*/
|
|
259
258
|
|
|
260
259
|
class ClickableBehavior extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
@@ -619,69 +618,6 @@ ClickableBehavior.defaultProps = {
|
|
|
619
618
|
|
|
620
619
|
/***/ }),
|
|
621
620
|
/* 3 */
|
|
622
|
-
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
623
|
-
|
|
624
|
-
"use strict";
|
|
625
|
-
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getClickableBehavior; });
|
|
626
|
-
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
627
|
-
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
628
|
-
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
|
|
629
|
-
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_router_dom__WEBPACK_IMPORTED_MODULE_1__);
|
|
630
|
-
/* harmony import */ var _components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2);
|
|
631
|
-
/* harmony import */ var _is_client_side_url_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1);
|
|
632
|
-
/**
|
|
633
|
-
* Returns either the default ClickableBehavior or a react-router aware version.
|
|
634
|
-
*
|
|
635
|
-
* The react-router aware version is returned if `router` is a react-router-dom
|
|
636
|
-
* router, `skipClientNav` is not `true`, and `href` is an internal URL.
|
|
637
|
-
*
|
|
638
|
-
* The `router` can be accessed via __RouterContext (imported from 'react-router')
|
|
639
|
-
* from a component rendered as a descendant of a BrowserRouter.
|
|
640
|
-
* See https://reacttraining.com/react-router/web/guides/basic-components.
|
|
641
|
-
*/
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const ClickableBehaviorWithRouter = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_1__["withRouter"])(_components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ "a"]);
|
|
647
|
-
function getClickableBehavior(
|
|
648
|
-
/**
|
|
649
|
-
* The URL to navigate to.
|
|
650
|
-
*/
|
|
651
|
-
href,
|
|
652
|
-
/**
|
|
653
|
-
* Should we skip using the react router and go to the page directly.
|
|
654
|
-
*/
|
|
655
|
-
skipClientNav,
|
|
656
|
-
/**
|
|
657
|
-
* router object added to the React context object by react-router-dom.
|
|
658
|
-
*/
|
|
659
|
-
router) {
|
|
660
|
-
if (router && skipClientNav !== true && href && Object(_is_client_side_url_js__WEBPACK_IMPORTED_MODULE_3__[/* isClientSideUrl */ "a"])(href)) {
|
|
661
|
-
// We cast to `any` here since the type of ClickableBehaviorWithRouter
|
|
662
|
-
// is slightly different from the return type of this function.
|
|
663
|
-
// TODO(WB-1037): Always return the wrapped version once all routes have
|
|
664
|
-
// been ported to the app-shell in webapp.
|
|
665
|
-
return ClickableBehaviorWithRouter;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
return _components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ "a"];
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
/***/ }),
|
|
672
|
-
/* 4 */
|
|
673
|
-
/***/ (function(module, exports) {
|
|
674
|
-
|
|
675
|
-
module.exports = require("react-router-dom");
|
|
676
|
-
|
|
677
|
-
/***/ }),
|
|
678
|
-
/* 5 */
|
|
679
|
-
/***/ (function(module, exports) {
|
|
680
|
-
|
|
681
|
-
module.exports = require("@khanacademy/wonder-blocks-core");
|
|
682
|
-
|
|
683
|
-
/***/ }),
|
|
684
|
-
/* 6 */
|
|
685
621
|
/***/ (function(module, exports) {
|
|
686
622
|
|
|
687
623
|
module.exports =
|
|
@@ -1135,6 +1071,69 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1135
1071
|
/******/
|
|
1136
1072
|
]);
|
|
1137
1073
|
|
|
1074
|
+
/***/ }),
|
|
1075
|
+
/* 4 */
|
|
1076
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
1077
|
+
|
|
1078
|
+
"use strict";
|
|
1079
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getClickableBehavior; });
|
|
1080
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
1081
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
1082
|
+
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
|
|
1083
|
+
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_router_dom__WEBPACK_IMPORTED_MODULE_1__);
|
|
1084
|
+
/* harmony import */ var _components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2);
|
|
1085
|
+
/* harmony import */ var _is_client_side_url_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1);
|
|
1086
|
+
/**
|
|
1087
|
+
* Returns either the default ClickableBehavior or a react-router aware version.
|
|
1088
|
+
*
|
|
1089
|
+
* The react-router aware version is returned if `router` is a react-router-dom
|
|
1090
|
+
* router, `skipClientNav` is not `true`, and `href` is an internal URL.
|
|
1091
|
+
*
|
|
1092
|
+
* The `router` can be accessed via __RouterContext (imported from 'react-router')
|
|
1093
|
+
* from a component rendered as a descendant of a BrowserRouter.
|
|
1094
|
+
* See https://reacttraining.com/react-router/web/guides/basic-components.
|
|
1095
|
+
*/
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
const ClickableBehaviorWithRouter = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_1__["withRouter"])(_components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ "a"]);
|
|
1101
|
+
function getClickableBehavior(
|
|
1102
|
+
/**
|
|
1103
|
+
* The URL to navigate to.
|
|
1104
|
+
*/
|
|
1105
|
+
href,
|
|
1106
|
+
/**
|
|
1107
|
+
* Should we skip using the react router and go to the page directly.
|
|
1108
|
+
*/
|
|
1109
|
+
skipClientNav,
|
|
1110
|
+
/**
|
|
1111
|
+
* router object added to the React context object by react-router-dom.
|
|
1112
|
+
*/
|
|
1113
|
+
router) {
|
|
1114
|
+
if (router && skipClientNav !== true && href && Object(_is_client_side_url_js__WEBPACK_IMPORTED_MODULE_3__[/* isClientSideUrl */ "a"])(href)) {
|
|
1115
|
+
// We cast to `any` here since the type of ClickableBehaviorWithRouter
|
|
1116
|
+
// is slightly different from the return type of this function.
|
|
1117
|
+
// TODO(WB-1037): Always return the wrapped version once all routes have
|
|
1118
|
+
// been ported to the app-shell in webapp.
|
|
1119
|
+
return ClickableBehaviorWithRouter;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
return _components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ "a"];
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/***/ }),
|
|
1126
|
+
/* 5 */
|
|
1127
|
+
/***/ (function(module, exports) {
|
|
1128
|
+
|
|
1129
|
+
module.exports = require("react-router-dom");
|
|
1130
|
+
|
|
1131
|
+
/***/ }),
|
|
1132
|
+
/* 6 */
|
|
1133
|
+
/***/ (function(module, exports) {
|
|
1134
|
+
|
|
1135
|
+
module.exports = require("@khanacademy/wonder-blocks-core");
|
|
1136
|
+
|
|
1138
1137
|
/***/ }),
|
|
1139
1138
|
/* 7 */
|
|
1140
1139
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
@@ -1145,15 +1144,15 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1145
1144
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
1146
1145
|
/* harmony import */ var aphrodite__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8);
|
|
1147
1146
|
/* harmony import */ var aphrodite__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(aphrodite__WEBPACK_IMPORTED_MODULE_1__);
|
|
1148
|
-
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
1147
|
+
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
|
|
1149
1148
|
/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_router_dom__WEBPACK_IMPORTED_MODULE_2__);
|
|
1150
1149
|
/* harmony import */ var react_router__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
|
|
1151
1150
|
/* harmony import */ var react_router__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react_router__WEBPACK_IMPORTED_MODULE_3__);
|
|
1152
|
-
/* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(
|
|
1151
|
+
/* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6);
|
|
1153
1152
|
/* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__);
|
|
1154
|
-
/* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(
|
|
1153
|
+
/* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3);
|
|
1155
1154
|
/* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__);
|
|
1156
|
-
/* harmony import */ var _util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(
|
|
1155
|
+
/* harmony import */ var _util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4);
|
|
1157
1156
|
/* harmony import */ var _util_is_client_side_url_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(1);
|
|
1158
1157
|
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
1159
1158
|
|
|
@@ -1171,11 +1170,20 @@ const StyledLink = Object(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODU
|
|
|
1171
1170
|
/**
|
|
1172
1171
|
* A component to turn any custom component into a clickable one.
|
|
1173
1172
|
*
|
|
1174
|
-
* Works by wrapping ClickableBehavior around the child element and styling
|
|
1175
|
-
* child appropriately and encapsulates routing logic which can be
|
|
1176
|
-
* Expects a function which returns an element as
|
|
1173
|
+
* Works by wrapping `ClickableBehavior` around the child element and styling
|
|
1174
|
+
* the child appropriately and encapsulates routing logic which can be
|
|
1175
|
+
* customized. Expects a function which returns an element as its child.
|
|
1176
|
+
*
|
|
1177
|
+
* Clickable allows your components to:
|
|
1178
|
+
*
|
|
1179
|
+
* - Handle mouse / touch / keyboard events
|
|
1180
|
+
* - Match the standard behavior of the given role
|
|
1181
|
+
* - Apply custom styles based on pressed / focused / hovered state
|
|
1182
|
+
* - Perform Client Side Navigation when href is passed and the component is a
|
|
1183
|
+
* descendent of a react-router Router.
|
|
1184
|
+
*
|
|
1185
|
+
* ### Usage
|
|
1177
1186
|
*
|
|
1178
|
-
* Example usage:
|
|
1179
1187
|
* ```jsx
|
|
1180
1188
|
* <Clickable onClick={() => alert("You clicked me!")}>
|
|
1181
1189
|
* {({hovered, focused, pressed}) =>
|
|
@@ -1244,7 +1252,7 @@ class Clickable extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
1244
1252
|
} = this.props;
|
|
1245
1253
|
const ClickableBehavior = Object(_util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_6__[/* default */ "a"])(href, skipClientNav, router);
|
|
1246
1254
|
|
|
1247
|
-
const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), style];
|
|
1255
|
+
const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), disabled && styles.disabled, style];
|
|
1248
1256
|
|
|
1249
1257
|
if (beforeNav) {
|
|
1250
1258
|
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](ClickableBehavior, {
|
|
@@ -1321,10 +1329,22 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_1__["StyleSheet"].create({
|
|
|
1321
1329
|
cursor: "pointer"
|
|
1322
1330
|
},
|
|
1323
1331
|
focused: {
|
|
1324
|
-
|
|
1332
|
+
":focus": {
|
|
1333
|
+
outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default.a.blue}`
|
|
1334
|
+
}
|
|
1325
1335
|
},
|
|
1326
1336
|
focusedLight: {
|
|
1327
1337
|
outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default.a.white}`
|
|
1338
|
+
},
|
|
1339
|
+
disabled: {
|
|
1340
|
+
color: _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default.a.offBlack32,
|
|
1341
|
+
cursor: "not-allowed",
|
|
1342
|
+
":focus": {
|
|
1343
|
+
outline: "none"
|
|
1344
|
+
},
|
|
1345
|
+
":focus-visible": {
|
|
1346
|
+
outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default.a.blue}`
|
|
1347
|
+
}
|
|
1328
1348
|
}
|
|
1329
1349
|
});
|
|
1330
1350
|
|
|
@@ -1352,7 +1372,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1352
1372
|
/* harmony import */ var _components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
|
|
1353
1373
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ClickableBehavior", function() { return _components_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_1__["a"]; });
|
|
1354
1374
|
|
|
1355
|
-
/* harmony import */ var _util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
1375
|
+
/* harmony import */ var _util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
|
|
1356
1376
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getClickableBehavior", function() { return _util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_2__["a"]; });
|
|
1357
1377
|
|
|
1358
1378
|
/* harmony import */ var _util_is_client_side_url_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-clickable",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Clickable component for Wonder-Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.16.3",
|
|
19
|
-
"@khanacademy/wonder-blocks-core": "^4.3.
|
|
19
|
+
"@khanacademy/wonder-blocks-core": "^4.3.2"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"aphrodite": "^1.2.5",
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import clickableArgtypes from "./clickable.argtypes.js";
|
|
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
|
+
/**
|
|
18
|
+
* States
|
|
19
|
+
*/
|
|
20
|
+
disabled: {
|
|
21
|
+
...clickableArgtypes.disabled,
|
|
22
|
+
description:
|
|
23
|
+
"Whether the component is disabled.\n\n" +
|
|
24
|
+
"If the component is disabled, this component will return handlers that do nothing.",
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* Events
|
|
28
|
+
*/
|
|
29
|
+
onClick: {
|
|
30
|
+
...clickableArgtypes.onClick,
|
|
31
|
+
description:
|
|
32
|
+
"An onClick function which ClickableBehavior can execute when clicked.",
|
|
33
|
+
},
|
|
34
|
+
onkeyDown: clickableArgtypes.onkeyDown,
|
|
35
|
+
onKeyUp: clickableArgtypes.onKeyUp,
|
|
36
|
+
/**
|
|
37
|
+
* Navigation
|
|
38
|
+
*/
|
|
39
|
+
skipClientNav: clickableArgtypes.skipClientNav,
|
|
40
|
+
rel: clickableArgtypes.rel,
|
|
41
|
+
target: clickableArgtypes.target,
|
|
42
|
+
href: {
|
|
43
|
+
...clickableArgtypes.href,
|
|
44
|
+
description:
|
|
45
|
+
"Optional `href` which `ClickableBehavior` should direct to, uses client-side routing by default if react-router is present.\n\n" +
|
|
46
|
+
"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",
|
|
47
|
+
},
|
|
48
|
+
beforeNav: clickableArgtypes.beforeNav,
|
|
49
|
+
safeWithNav: clickableArgtypes.safeWithNav,
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Accessibility
|
|
53
|
+
*/
|
|
54
|
+
role: clickableArgtypes.role,
|
|
55
|
+
};
|