@khanacademy/wonder-blocks-button
Advanced tools
Comparing version 2.0.1 to 2.1.0
// @flow | ||
import React from "react"; | ||
import * as React from "react"; | ||
import {StyleSheet} from "aphrodite"; | ||
@@ -14,2 +14,4 @@ import {Link} from "react-router-dom"; | ||
import {addStyle} from "@khanacademy/wonder-blocks-core"; | ||
import {CircularSpinner} from "@khanacademy/wonder-blocks-progress-spinner"; | ||
import type {ClickableHandlers} from "@khanacademy/wonder-blocks-core"; | ||
@@ -33,8 +35,2 @@ import type {SharedProps} from "./button.js"; | ||
handleClick = (e: SyntheticEvent<>) => { | ||
if (this.props.disabled) { | ||
e.preventDefault(); | ||
} | ||
}; | ||
render() { | ||
@@ -45,3 +41,3 @@ const { | ||
color, | ||
disabled, | ||
disabled: disabledProp, | ||
focused, | ||
@@ -56,2 +52,4 @@ hovered, | ||
testId, | ||
spinner, | ||
"aria-label": ariaLabel, | ||
...handlers | ||
@@ -68,2 +66,4 @@ } = this.props; | ||
const disabled = spinner || disabledProp; | ||
const defaultStyle = [ | ||
@@ -83,3 +83,5 @@ sharedStyles.shared, | ||
"aria-disabled": disabled ? "true" : undefined, | ||
"aria-label": ariaLabel, | ||
"data-test-id": testId, | ||
role: "button", | ||
style: [defaultStyle, style], | ||
@@ -91,19 +93,17 @@ ...handlers, | ||
const label = <Label style={sharedStyles.text}>{children}</Label>; | ||
const label = ( | ||
<Label | ||
style={[sharedStyles.text, spinner && sharedStyles.hiddenText]} | ||
> | ||
{children} | ||
</Label> | ||
); | ||
if (href) { | ||
return router && !skipClientNav ? ( | ||
<StyledLink | ||
{...commonProps} | ||
onClick={this.handleClick} | ||
to={href} | ||
> | ||
<StyledLink {...commonProps} to={href}> | ||
{label} | ||
</StyledLink> | ||
) : ( | ||
<StyledAnchor | ||
{...commonProps} | ||
onClick={this.handleClick} | ||
href={href} | ||
> | ||
<StyledAnchor {...commonProps} href={href}> | ||
{label} | ||
@@ -114,4 +114,15 @@ </StyledAnchor> | ||
return ( | ||
<StyledButton {...commonProps} disabled={disabled}> | ||
<StyledButton | ||
type="button" | ||
{...commonProps} | ||
disabled={disabled} | ||
> | ||
{label} | ||
{spinner && ( | ||
<CircularSpinner | ||
style={sharedStyles.spinner} | ||
size={{medium: "small", small: "xsmall"}[size]} | ||
light={kind === "primary"} | ||
/> | ||
)} | ||
</StyledButton> | ||
@@ -149,4 +160,14 @@ ); | ||
fontWeight: "bold", | ||
userSelect: "none", | ||
whiteSpace: "nowrap", | ||
overflow: "hidden", | ||
textOverflow: "ellipsis", | ||
pointerEvents: "none", // fix Safari bug where the browser was eating mouse events | ||
}, | ||
hiddenText: { | ||
visibility: "hidden", | ||
}, | ||
spinner: { | ||
position: "absolute", | ||
}, | ||
}); | ||
@@ -153,0 +174,0 @@ |
@@ -6,2 +6,3 @@ // @flow | ||
import {getClickableBehavior} from "@khanacademy/wonder-blocks-core"; | ||
import type {StyleType} from "@khanacademy/wonder-blocks-core"; | ||
import ButtonCore from "./button-core.js"; | ||
@@ -23,7 +24,17 @@ | ||
* If true, replaces the contents with a spinner. | ||
* | ||
* Note: setting this prop to `true` will disable the button. | ||
* | ||
* TODO(kevinb): support spinner + light once we have designs | ||
*/ | ||
// TODO(yejia): Implement once spinner is implemented. | ||
// spinner: boolean, | ||
spinner: boolean, | ||
/** | ||
* This should be use when `spinner={true}` to let people using screen | ||
* readers that the action taken by clicking the button will take some | ||
* time to complete. | ||
*/ | ||
"aria-label": string, | ||
/** | ||
* The color of the button, either blue or red. | ||
@@ -89,5 +100,5 @@ */ | ||
/** | ||
* The content of the modal, appearing between the titlebar and footer. | ||
* Optional custom styles. | ||
*/ | ||
style?: any, | ||
style?: StyleType, | ||
// TODO(yejia): use this if ADR #47 has been implemented | ||
@@ -147,2 +158,4 @@ /* | ||
disabled: false, | ||
spinner: false, | ||
"aria-label": "", | ||
}; | ||
@@ -158,2 +171,4 @@ | ||
skipClientNav, | ||
spinner, | ||
disabled, | ||
...sharedProps | ||
@@ -170,5 +185,6 @@ } = this.props; | ||
<ClickableBehavior | ||
disabled={sharedProps.disabled} | ||
disabled={spinner || disabled} | ||
href={href} | ||
onClick={onClick} | ||
href={href} | ||
role="button" | ||
> | ||
@@ -181,2 +197,4 @@ {(state, handlers) => { | ||
{...handlers} | ||
disabled={disabled} | ||
spinner={spinner} | ||
skipClientNav={skipClientNav} | ||
@@ -183,0 +201,0 @@ href={href} |
@@ -311,4 +311,32 @@ There are three `kind`s of buttons: `"primary"` (default), `"secondary"`, and | ||
Buttons can show a `spinner`. This is useful when indicating to a user that | ||
their input has been recognized but that the operation will take some time. | ||
While the `spinner` property is set to `true` the button is disabled. | ||
```jsx | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
const {StyleSheet} = require("aphrodite"); | ||
const styles = StyleSheet.create({ | ||
row: { | ||
flexDirection: "row", | ||
alignItems: "center", | ||
}, | ||
button: { | ||
marginRight: 10, | ||
} | ||
}); | ||
<View style={styles.row}> | ||
<Button spinner={true} aria-label="loading" style={styles.button}> | ||
Click me! | ||
</Button> | ||
<Button spinner={true} aria-label="loading" size="small" style={styles.button}> | ||
Click me! | ||
</Button> | ||
</View> | ||
``` | ||
Buttons can have a `style` props which supports width, position, margin, | ||
and flex styles: | ||
and flex styles. | ||
@@ -367,9 +395,10 @@ ### Best Practices | ||
Layouts often specify a specific width of button. When implementing such | ||
designs use `minWidth` instead of `width`. `minWidth` allows the button | ||
to resize to fit the content whereas `width` does not. This is important | ||
Layouts often specify a specific width of button. When implementing such | ||
designs use `minWidth` instead of `width`. `minWidth` allows the button | ||
to resize to fit the content whereas `width` does not. This is important | ||
for international sites since sometimes strings for UI elements can be much | ||
longer in other languages. Both of the buttons below have a "natural" width | ||
of 144px. The one on the right is wider but it accommodates the full string | ||
instead of wrapping or truncating it. | ||
longer in other languages. Both of the buttons below have a "natural" width | ||
of 144px. The one on the right is wider but it accommodates the full string | ||
instead of wrapping it. Note that if the parent container of the button doesn't | ||
have enough room to accommodate the width of the button, the text will truncate. | ||
```jsx | ||
@@ -437,1 +466,51 @@ const {View} = require("@khanacademy/wonder-blocks-core"); | ||
``` | ||
When an action is going to take a while, show a spinner during that time. | ||
```jsx | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
const {StyleSheet} = require("aphrodite"); | ||
const styles = StyleSheet.create({ | ||
row: { | ||
flexDirection: "row", | ||
}, | ||
button: { | ||
marginRight: 10, | ||
}, | ||
}); | ||
class Example extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
waiting: false, | ||
} | ||
} | ||
componentWillUnmount() { | ||
this.timeout.clear(); | ||
} | ||
handleClick() { | ||
this.setState({waiting: true}); | ||
this.timeout = setTimeout(() => { | ||
this.setState({waiting: false}); | ||
}, 2000); | ||
} | ||
render() { | ||
return <View style={styles.row}> | ||
<Button | ||
spinner={this.state.waiting} | ||
aria-label={this.state.waiting ? "waiting" : ""} | ||
onClick={() => this.handleClick()} | ||
> | ||
Click me! | ||
</Button> | ||
</View> | ||
} | ||
} | ||
<Example /> | ||
``` |
@@ -51,2 +51,4 @@ // @flow | ||
tabIndex={disabled ? -1 : 0} | ||
spinner={false} | ||
aria-label={""} | ||
{...stateProps} | ||
@@ -66,2 +68,34 @@ {...defaultHandlers} | ||
} | ||
for (const kind of ["primary", "secondary", "tertiary"]) { | ||
for (const size of ["medium", "small"]) { | ||
test(`kind:${kind} size:${size} spinner:true`, () => { | ||
const spinner = true; | ||
const disabled = spinner; | ||
const stateProps = { | ||
disabled, | ||
focused: false, | ||
hovered: false, | ||
pressed: false, | ||
}; | ||
const tree = renderer | ||
.create( | ||
<ButtonCore | ||
kind={kind} | ||
size={size} | ||
color="default" | ||
light={false} | ||
tabIndex={disabled ? -1 : 0} | ||
spinner={spinner} | ||
aria-label={"loading"} | ||
{...stateProps} | ||
{...defaultHandlers} | ||
> | ||
Click me | ||
</ButtonCore>, | ||
) | ||
.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
} | ||
} | ||
}); |
@@ -85,3 +85,3 @@ module.exports = | ||
/******/ // Load entry module and return exports | ||
/******/ return __webpack_require__(__webpack_require__.s = 9); | ||
/******/ return __webpack_require__(__webpack_require__.s = 10); | ||
/******/ }) | ||
@@ -111,3 +111,3 @@ /************************************************************************/ | ||
module.exports = require("@khanacademy/wonder-blocks-color"); | ||
module.exports = require("@khanacademy/wonder-blocks-progress-spinner"); | ||
@@ -118,3 +118,3 @@ /***/ }), | ||
module.exports = require("@khanacademy/wonder-blocks-typography"); | ||
module.exports = require("@khanacademy/wonder-blocks-color"); | ||
@@ -125,3 +125,3 @@ /***/ }), | ||
module.exports = require("react-router-dom"); | ||
module.exports = require("@khanacademy/wonder-blocks-typography"); | ||
@@ -132,6 +132,12 @@ /***/ }), | ||
module.exports = require("react-router-dom"); | ||
/***/ }), | ||
/* 7 */ | ||
/***/ (function(module, exports) { | ||
module.exports = require("aphrodite"); | ||
/***/ }), | ||
/* 7 */ | ||
/* 8 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -152,7 +158,7 @@ | ||
var _react2 = _interopRequireDefault(_react); | ||
var React = _interopRequireWildcard(_react); | ||
var _aphrodite = __webpack_require__(6); | ||
var _aphrodite = __webpack_require__(7); | ||
var _reactRouterDom = __webpack_require__(5); | ||
var _reactRouterDom = __webpack_require__(6); | ||
@@ -163,5 +169,5 @@ var _propTypes = __webpack_require__(1); | ||
var _wonderBlocksTypography = __webpack_require__(4); | ||
var _wonderBlocksTypography = __webpack_require__(5); | ||
var _wonderBlocksColor = __webpack_require__(3); | ||
var _wonderBlocksColor = __webpack_require__(4); | ||
@@ -172,4 +178,8 @@ var _wonderBlocksColor2 = _interopRequireDefault(_wonderBlocksColor); | ||
var _wonderBlocksProgressSpinner = __webpack_require__(3); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } | ||
@@ -191,17 +201,5 @@ | ||
function ButtonCore() { | ||
var _ref; | ||
var _temp, _this, _ret; | ||
_classCallCheck(this, ButtonCore); | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = ButtonCore.__proto__ || Object.getPrototypeOf(ButtonCore)).call.apply(_ref, [this].concat(args))), _this), _this.handleClick = function (e) { | ||
if (_this.props.disabled) { | ||
e.preventDefault(); | ||
} | ||
}, _temp), _possibleConstructorReturn(_this, _ret); | ||
return _possibleConstructorReturn(this, (ButtonCore.__proto__ || Object.getPrototypeOf(ButtonCore)).apply(this, arguments)); | ||
} | ||
@@ -216,3 +214,3 @@ | ||
color = _props.color, | ||
disabled = _props.disabled, | ||
disabledProp = _props.disabled, | ||
focused = _props.focused, | ||
@@ -227,3 +225,5 @@ hovered = _props.hovered, | ||
testId = _props.testId, | ||
handlers = _objectWithoutProperties(_props, ["children", "skipClientNav", "color", "disabled", "focused", "hovered", "href", "kind", "light", "pressed", "size", "style", "testId"]); | ||
spinner = _props.spinner, | ||
ariaLabel = _props["aria-label"], | ||
handlers = _objectWithoutProperties(_props, ["children", "skipClientNav", "color", "disabled", "focused", "hovered", "href", "kind", "light", "pressed", "size", "style", "testId", "spinner", "aria-label"]); | ||
@@ -237,2 +237,4 @@ var router = this.context.router; | ||
var disabled = spinner || disabledProp; | ||
var defaultStyle = [sharedStyles.shared, disabled && sharedStyles.disabled, buttonStyles.default, disabled && buttonStyles.disabled, !disabled && (pressed ? buttonStyles.active : (hovered || focused) && buttonStyles.focus), size === "small" && sharedStyles.small]; | ||
@@ -242,3 +244,5 @@ | ||
"aria-disabled": disabled ? "true" : undefined, | ||
"aria-label": ariaLabel, | ||
"data-test-id": testId, | ||
role: "button", | ||
style: [defaultStyle, style] | ||
@@ -249,5 +253,7 @@ }, handlers); | ||
var label = _react2.default.createElement( | ||
var label = React.createElement( | ||
Label, | ||
{ style: sharedStyles.text }, | ||
{ | ||
style: [sharedStyles.text, spinner && sharedStyles.hiddenText] | ||
}, | ||
children | ||
@@ -257,22 +263,25 @@ ); | ||
if (href) { | ||
return router && !skipClientNav ? _react2.default.createElement( | ||
return router && !skipClientNav ? React.createElement( | ||
StyledLink, | ||
_extends({}, commonProps, { | ||
onClick: this.handleClick, | ||
to: href | ||
}), | ||
_extends({}, commonProps, { to: href }), | ||
label | ||
) : _react2.default.createElement( | ||
) : React.createElement( | ||
StyledAnchor, | ||
_extends({}, commonProps, { | ||
onClick: this.handleClick, | ||
href: href | ||
}), | ||
_extends({}, commonProps, { href: href }), | ||
label | ||
); | ||
} else { | ||
return _react2.default.createElement( | ||
return React.createElement( | ||
StyledButton, | ||
_extends({}, commonProps, { disabled: disabled }), | ||
label | ||
_extends({ | ||
type: "button" | ||
}, commonProps, { | ||
disabled: disabled | ||
}), | ||
label, | ||
spinner && React.createElement(_wonderBlocksProgressSpinner.CircularSpinner, { | ||
style: sharedStyles.spinner, | ||
size: { medium: "small", small: "xsmall" }[size], | ||
light: kind === "primary" | ||
}) | ||
); | ||
@@ -284,3 +293,3 @@ } | ||
return ButtonCore; | ||
}(_react2.default.Component); | ||
}(React.Component); | ||
@@ -317,3 +326,13 @@ ButtonCore.contextTypes = { router: _propTypes2.default.any }; | ||
fontWeight: "bold", | ||
userSelect: "none", | ||
whiteSpace: "nowrap", | ||
overflow: "hidden", | ||
textOverflow: "ellipsis", | ||
pointerEvents: "none" // fix Safari bug where the browser was eating mouse events | ||
}, | ||
hiddenText: { | ||
visibility: "hidden" | ||
}, | ||
spinner: { | ||
position: "absolute" | ||
} | ||
@@ -439,3 +458,3 @@ }); | ||
/***/ }), | ||
/* 8 */ | ||
/* 9 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -464,3 +483,3 @@ | ||
var _buttonCore = __webpack_require__(7); | ||
var _buttonCore = __webpack_require__(8); | ||
@@ -515,3 +534,5 @@ var _buttonCore2 = _interopRequireDefault(_buttonCore); | ||
skipClientNav = _props.skipClientNav, | ||
sharedProps = _objectWithoutProperties(_props, ["onClick", "href", "children", "skipClientNav"]); | ||
spinner = _props.spinner, | ||
disabled = _props.disabled, | ||
sharedProps = _objectWithoutProperties(_props, ["onClick", "href", "children", "skipClientNav", "spinner", "disabled"]); | ||
@@ -523,5 +544,6 @@ var ClickableBehavior = (0, _wonderBlocksCore.getClickableBehavior)(href, skipClientNav, this.context.router); | ||
{ | ||
disabled: sharedProps.disabled, | ||
disabled: spinner || disabled, | ||
href: href, | ||
onClick: onClick, | ||
href: href | ||
role: "button" | ||
}, | ||
@@ -532,2 +554,4 @@ function (state, handlers) { | ||
_extends({}, sharedProps, state, handlers, { | ||
disabled: disabled, | ||
spinner: spinner, | ||
skipClientNav: skipClientNav, | ||
@@ -551,3 +575,5 @@ href: href | ||
size: "medium", | ||
disabled: false | ||
disabled: false, | ||
spinner: false, | ||
"aria-label": "" | ||
}; | ||
@@ -558,3 +584,3 @@ Button.contextTypes = { router: _propTypes2.default.any }; | ||
/***/ }), | ||
/* 9 */ | ||
/* 10 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -570,3 +596,3 @@ | ||
var _button = __webpack_require__(8); | ||
var _button = __webpack_require__(9); | ||
@@ -573,0 +599,0 @@ var _button2 = _interopRequireDefault(_button); |
@@ -331,4 +331,40 @@ // This file is auto-generated by gen-snapshot-tests.js | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
const {StyleSheet} = require("aphrodite"); | ||
const styles = StyleSheet.create({ | ||
row: { | ||
flexDirection: "row", | ||
alignItems: "center", | ||
}, | ||
button: { | ||
marginRight: 10, | ||
}, | ||
}); | ||
const example = ( | ||
<View style={styles.row}> | ||
<Button | ||
spinner={true} | ||
aria-label="loading" | ||
style={styles.button} | ||
> | ||
Click me! | ||
</Button> | ||
<Button | ||
spinner={true} | ||
aria-label="loading" | ||
size="small" | ||
style={styles.button} | ||
> | ||
Click me! | ||
</Button> | ||
</View> | ||
); | ||
const tree = renderer.create(example).toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
it("example 9", () => { | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
const example = ( | ||
<View> | ||
@@ -341,3 +377,3 @@ <Button>Label</Button> | ||
}); | ||
it("example 9", () => { | ||
it("example 10", () => { | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
@@ -375,3 +411,3 @@ const {StyleSheet} = require("aphrodite"); | ||
}); | ||
it("example 10", () => { | ||
it("example 11", () => { | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
@@ -406,3 +442,3 @@ const {StyleSheet} = require("aphrodite"); | ||
}); | ||
it("example 11", () => { | ||
it("example 12", () => { | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
@@ -433,2 +469,53 @@ const {StyleSheet} = require("aphrodite"); | ||
}); | ||
it("example 13", () => { | ||
const {View} = require("@khanacademy/wonder-blocks-core"); | ||
const {StyleSheet} = require("aphrodite"); | ||
const styles = StyleSheet.create({ | ||
row: { | ||
flexDirection: "row", | ||
}, | ||
button: { | ||
marginRight: 10, | ||
}, | ||
}); | ||
class Example extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
waiting: false, | ||
}; | ||
} | ||
componentWillUnmount() { | ||
this.timeout.clear(); | ||
} | ||
handleClick() { | ||
this.setState({waiting: true}); | ||
this.timeout = setTimeout(() => { | ||
this.setState({waiting: false}); | ||
}, 2000); | ||
} | ||
render() { | ||
return ( | ||
<View style={styles.row}> | ||
<Button | ||
spinner={this.state.waiting} | ||
aria-label={this.state.waiting ? "waiting" : ""} | ||
onClick={() => this.handleClick()} | ||
> | ||
Click me! | ||
</Button> | ||
</View> | ||
); | ||
} | ||
} | ||
const example = <Example />; | ||
const tree = renderer.create(example).toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
{ | ||
"name": "@khanacademy/wonder-blocks-button", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"design": "v1", | ||
@@ -10,2 +10,3 @@ "publishConfig": { | ||
"main": "dist/index.js", | ||
"module": "dist/es/index.js", | ||
"source": "index.js", | ||
@@ -18,5 +19,6 @@ "scripts": { | ||
"dependencies": { | ||
"@khanacademy/wonder-blocks-color": "^1.0.6", | ||
"@khanacademy/wonder-blocks-core": "^1.1.1", | ||
"@khanacademy/wonder-blocks-typography": "^1.0.6" | ||
"@khanacademy/wonder-blocks-color": "^1.0.7", | ||
"@khanacademy/wonder-blocks-core": "^1.2.0", | ||
"@khanacademy/wonder-blocks-progress-spinner": "^1.0.4", | ||
"@khanacademy/wonder-blocks-typography": "^1.0.7" | ||
}, | ||
@@ -23,0 +25,0 @@ "peerDependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
333463
15
1891
8
12
12
2
190