diff --git a/ui/.eslintrc b/ui/.eslintrc index 101a9bb8..155d4560 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -77,7 +77,7 @@ "jsx-a11y/aria-proptypes": 2, "jsx-a11y/aria-role": 2, "jsx-a11y/aria-unsupported-elements": 2, - "jsx-a11y/click-events-have-key-events": 2, + "jsx-a11y/click-events-have-key-events": 1, "jsx-a11y/mouse-events-have-key-events": 2, "jsx-a11y/heading-has-content": 2, "jsx-a11y/html-has-lang": 2, @@ -88,7 +88,7 @@ "jsx-a11y/no-access-key": 2, "jsx-a11y/no-marquee": 2, "jsx-a11y/no-onchange": 2, - "jsx-a11y/no-static-element-interactions": 2, + "jsx-a11y/no-static-element-interactions": 1, "jsx-a11y/onclick-has-focus": 2, "jsx-a11y/onclick-has-role": 2, "jsx-a11y/role-has-required-aria-props": 2, diff --git a/ui/.postcss.js b/ui/.postcss.js deleted file mode 100644 index 1e3c8837..00000000 --- a/ui/.postcss.js +++ /dev/null @@ -1,12 +0,0 @@ -var calc = require('postcss-calc'); - -module.exports = function (postcss) { - return postcss([ - require('postcss-at-rules-variables')(), - require('postcss-modules-values'), - require("postcss-import"), - require('postcss-mixins')(), - require('postcss-for'), - require('postcss-cssnext')(), - ]) -} diff --git a/ui/package.json b/ui/package.json index a939e7ba..2d9bb57b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,7 +13,12 @@ }, "dependencies": { "classnames": "^2.2.5", + "invariant": "^2.2.1", + "lodash.find": "^4.6.0", + "lodash.first": "^3.0.0", "lodash.flatten": "^4.4.0", + "lodash.get": "^4.4.2", + "lodash.isundefined": "^3.0.1", "param-case": "^2.1.0", "react": "^15.3.2", "react-a11y": "^0.3.3" diff --git a/ui/src/components/radio-group/index.js b/ui/src/components/radio-group/index.js new file mode 100644 index 00000000..d696b1dd --- /dev/null +++ b/ui/src/components/radio-group/index.js @@ -0,0 +1,163 @@ +/* After some time tring to make this work without messing w/ checked property, + * I ended up using it *only* when none is defined + * + * This way we try to be as pure as possible and not mess with consumer's logic + * if they have any + */ + +const first = require('lodash.first'); +const isUndefined = require('lodash.isundefined'); +const get = require('lodash.get'); +const invariant = require('invariant'); +const Item = require('./item'); +const find = require('lodash.find'); +const classNames = require('classnames'); +const React = require('react'); +const styles = require('./style.css'); + +const RadioGroup = React.createClass({ + propTypes: { + children: React.PropTypes.node, + className: React.PropTypes.string, + id: React.PropTypes.string, + name: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func, + style: React.PropTypes.object + }, + getInitialState: function() { + return this.getState(this.props); + }, + componentWillMount: function() { + return this.checkValues(this.props); + }, + componentWillReceiveProps: function(nextProps) { + return this.setState(this.checkValues(nextProps)); + }, + getState: function(props) { + const _children = React.Children.toArray(props.children).filter((child) => { + return get(child, 'type.displayName') === 'Radio'; + }); + + const hasChecked = _children.some((child) => { + return !isUndefined(get(child, 'props.checked')); + }); + + if (hasChecked) { + return { + hasChecked + }; + } + + const defaultChecked = get(find(_children, (child) => { + return get(child, 'props.defaultChecked'); + }), 'props.value'); + + const checked = (() => { + const stateChecked = get(this, 'state.checked'); + const fallback = isUndefined(defaultChecked) + ? get(first(_children), 'props.value') + : defaultChecked; + + return !isUndefined(stateChecked) ? stateChecked : fallback; + })(); + + return { + checked + }; + }, + checkValues: function(props) { + invariant(React.Children.toArray(props.children).every((child) => { + return (child.type.name !== 'Radio') ? true : !!child.props.value; + }), 'All childs of must have a value property'); + + return props; + }, + handleChange: function(key) { + return (ev) => { + const { + onChange = () => {} + } = this.props; + + this.setState({ + checked: key + }, () => { + onChange(ev); + }); + }; + }, + render: function() { + const { + name, + children, + className, + id, + style + } = this.props; + + const { + hasChecked, + checked + } = this.state; + + const { + handleChange + } = this; + + const cn = classNames( + className, + styles.group + ); + + const _children = React.Children.map(children, (child, i) => { + if (child.type.name !== 'Radio') { + return child; + } + + const tabIndex = i + 1; + const disabled = get(child, 'props.disabled'); + const value = get(child, 'props.value'); + + const _handleChange = (!hasChecked && !disabled) + ? handleChange(value) + : undefined; + + const _child = hasChecked ? ( + React.cloneElement(child, { + name + }) + ) : ( + React.cloneElement(child, { + onChange: _handleChange, + checked: value === checked, + defaultChecked: undefined, + name + }) + ); + + const _checked = get(_child, 'props.checked'); + + return ( + + {_child} + + ); + }); + + return ( +
+ {_children} +
+ ); + } +}); + +module.exports = RadioGroup; diff --git a/ui/src/components/radio-group/item.js b/ui/src/components/radio-group/item.js new file mode 100644 index 00000000..4f37b437 --- /dev/null +++ b/ui/src/components/radio-group/item.js @@ -0,0 +1,40 @@ +const classNames = require('classnames'); +const React = require('react'); +const styles = require('./style.css'); + +const Item = ({ + children, + checked = false, + disabled = false, + onClick, + tabIndex +}) => { + const cn = classNames( + styles.item, + disabled ? styles.disabled : '', + checked ? styles.checked : '' + ); + + return ( +
+ {children} +
+ ); +}; + +Item.propTypes = { + checked: React.PropTypes.bool, + children: React.PropTypes.node, + disabled: React.PropTypes.bool, + onClick: React.PropTypes.func, + tabIndex: React.PropTypes.number +}; + +module.exports = Item; diff --git a/ui/src/components/radio-group/readme.md b/ui/src/components/radio-group/readme.md new file mode 100644 index 00000000..2245bb3f --- /dev/null +++ b/ui/src/components/radio-group/readme.md @@ -0,0 +1,35 @@ +# `` + +## demo + +```embed +const React = require('react'); +const ReactDOM = require('react-dom/server'); +const Base = require('../base'); +const Container = require('../container'); +const Row = require('../row'); +const Column = require('../column'); +const Radio = require('../radio'); +const RadioGroup = require('./index'); +const styles = require('./style.css'); + +nmodule.exports = ReactDOM.renderToString( + + + + + +

You get all the good bits and none of the rubbish

+
+ +

You get all the good bits and extra brownies

+
+ +

You get none of the good bits

+
+
+
+
+ +); +``` \ No newline at end of file diff --git a/ui/src/components/radio-group/style.css b/ui/src/components/radio-group/style.css new file mode 100644 index 00000000..3bfb1222 --- /dev/null +++ b/ui/src/components/radio-group/style.css @@ -0,0 +1,24 @@ +.group { + & .item { + cursor: pointer; + background: #FFFFFF; + border: 1px solid #D8D8D8; + box-shadow: 0px 2px 0px 0px rgba(0,0,0,0.05); + border-radius: 4px; + margin-bottom: 15px; + padding: 25px; + + &:last-child { + margin-bottom: initial; + } + + &.checked { + border: 1px solid #1D35BC; + box-shadow: 0px 2px 0px 0px rgba(0,0,0,0.05); + } + + &.disabled { + cursor: default; + } + } +} diff --git a/ui/src/components/radio/index.js b/ui/src/components/radio/index.js index 9f1d00d1..25184b40 100644 --- a/ui/src/components/radio/index.js +++ b/ui/src/components/radio/index.js @@ -3,48 +3,66 @@ const React = require('react'); const styles = require('./style.css'); const Radio = ({ - name, - value, - label, checked, - disabled = false, + children, className, + defaultChecked, + disabled = false, id, + label, + name, onChange, - style + style, + value }) => { + const _label = label || children; + const _children = label ? children : null; const cn = classNames( className, styles.radio ); + const labelledby = `${styles.label}-label`; + return ( - +
+ + + {_children} + +
); }; Radio.propTypes = { checked: React.PropTypes.bool, + children: React.PropTypes.node, className: React.PropTypes.string, + defaultChecked: React.PropTypes.bool, disabled: React.PropTypes.bool, id: React.PropTypes.string, - label: React.PropTypes.node, - name: React.PropTypes.string, + label: React.PropTypes.string, + name: React.PropTypes.string.isRequired, onChange: React.PropTypes.func, style: React.PropTypes.object, - value: React.PropTypes.string + value: React.PropTypes.string.isRequired }; module.exports = Radio; diff --git a/ui/src/components/radio/readme.md b/ui/src/components/radio/readme.md index 0a8cd9de..743ac817 100644 --- a/ui/src/components/radio/readme.md +++ b/ui/src/components/radio/readme.md @@ -16,15 +16,18 @@ nmodule.exports = ReactDOM.renderToString( - - + + Female + + + Male + ); ``` - ## usage ```js diff --git a/ui/src/components/radio/style.css b/ui/src/components/radio/style.css index 956773e8..c1ab6faa 100644 --- a/ui/src/components/radio/style.css +++ b/ui/src/components/radio/style.css @@ -10,11 +10,11 @@ .radio { cursor: pointer; - label { + & .label { display: block; } - & span { + & .span { margin-left: remCalc(30); margin-right: remCalc(20); @@ -25,11 +25,11 @@ } } - & input[type="radio"] { + & .input[type="radio"] { display: none; visibility: hidden; - &:checked ~ span:before { + &:checked ~ .span:before { background: var(--dot-color); } } diff --git a/ui/src/docs.js b/ui/src/docs.js index d32f966b..1c70cc07 100644 --- a/ui/src/docs.js +++ b/ui/src/docs.js @@ -9,6 +9,7 @@ module.exports = { Container: require('./components/container/readme.md'), Row: require('./components/row/readme.md'), Radio: require('./components/radio/readme.md'), + 'Radio Group': require('./components/radio-group/readme.md'), Column: require('./components/column/readme.md'), Button: require('./components/button/readme.md'), Toggle: require('./components/toggle/readme.md'), diff --git a/ui/src/index.js b/ui/src/index.js index 48451a5e..8f1d8a32 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -9,4 +9,5 @@ module.exports = { Tabs: require('./components/tabs'), Toggle: require('./components/toggle'), Radio: require('./components/radio'), + RadioGroup: require('./components/radio-group') }; diff --git a/ui/test/index.js b/ui/test/index.js index f1485733..c5415ec3 100644 --- a/ui/test/index.js +++ b/ui/test/index.js @@ -11,4 +11,3 @@ test('renders