Update dependencies and linter (#1180)

old
Javi Velasco 2017-01-26 18:05:32 +01:00 committed by GitHub
parent bc18e56065
commit 9d6ec1eedd
169 changed files with 3590 additions and 3758 deletions

View File

@ -1,3 +1,10 @@
**/node_modules/ **/node_modules/
**/build/ **/build/
docs/
spec/
lib lib
karma.conf.js
gulpfile.js
tests.webpack*
server.js
webpack*

239
.eslintrc
View File

@ -1,227 +1,30 @@
{ {
"parser": "babel-eslint",
"extends": "airbnb",
"env": { "env": {
"browser": true, "browser": true,
"node": true, "node": true,
"mocha": true, "jest": true,
"es6": true "es6": true
}, },
"ecmaFeatures": {
"jsx": true,
"templateStrings": true,
"superInFunctions": false,
"classes": true,
"modules": true
},
"parser": "babel-eslint",
"plugins": [
"babel",
"react"
],
"rules": { "rules": {
"block-scoped-var": [0], "func-names": "off",
"brace-style": [2, "1tbs", { "global-require": "off",
"allowSingleLine": true "no-use-before-define": 0,
}], "no-underscore-dangle": 0,
"camelcase": [0], "react/sort-prop-types": 2,
"comma-dangle": [2, "never"], "react/jsx-no-bind": 2,
"comma-spacing": [2], "react/require-default-props": 0,
"comma-style": [2, "last"], "react/no-find-dom-node": 0,
"complexity": [0, 11], "react/jsx-filename-extension": 0,
"constructor-super": [2], "import/prefer-default-export": 0,
"consistent-return": [0], "jsx-a11y/no-static-element-interactions": 0,
"consistent-this": [0, "that"], "import/no-extraneous-dependencies": [
"curly": [2, "multi-line"], "error", {
"default-case": [2], "devDependencies": true,
"dot-notation": [2, { "optionalDependencies": false,
"allowKeywords": true "peerDependencies": false
}], }
"eol-last": [2], ]
"eqeqeq": [2],
"func-names": [0],
"func-style": [0, "declaration"],
"generator-star-spacing": [2, "after"],
"guard-for-in": [0],
"handle-callback-err": [0],
"key-spacing": [2, {
"beforeColon": false,
"afterColon": true
}],
"quotes": [2, "single", "avoid-escape"],
"max-depth": [0, 4],
"max-len": [0, 80, 4],
"max-nested-callbacks": [0, 2],
"max-params": [0, 3],
"max-statements": [0, 10],
"new-parens": [2],
"new-cap": [0],
"newline-after-var": [0],
"no-alert": [2],
"no-array-constructor": [2],
"no-bitwise": [0],
"no-caller": [2],
"no-catch-shadow": [2],
"no-cond-assign": [2],
"no-console": [0],
"no-constant-condition": [1],
"no-continue": [2],
"no-control-regex": [2],
"no-debugger": [2],
"no-delete-var": [2],
"no-div-regex": [0],
"no-dupe-args": [2],
"no-dupe-keys": [2],
"no-duplicate-case": [2],
"no-else-return": [0],
"no-empty": [2],
"no-empty-character-class": [2],
"no-eq-null": [0],
"no-eval": [2],
"no-ex-assign": [2],
"no-extend-native": [1],
"no-extra-bind": [2],
"no-extra-boolean-cast": [2],
"no-extra-parens": [0],
"no-extra-semi": [1],
"no-fallthrough": [2],
"no-floating-decimal": [2],
"no-func-assign": [2],
"no-implied-eval": [2],
"no-inline-comments": [0],
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": [2],
"no-irregular-whitespace": [2],
"no-iterator": [2],
"no-label-var": [2],
"no-labels": [2],
"no-lone-blocks": [2],
"no-lonely-if": [2],
"no-loop-func": [2],
"no-mixed-requires": [0, false],
"no-mixed-spaces-and-tabs": [2, false],
"no-multi-spaces": [2],
"no-multi-str": [2],
"no-multiple-empty-lines": [2, {
"max": 2
}],
"no-native-reassign": [1],
"no-negated-in-lhs": [2],
"no-nested-ternary": [0],
"no-new": [2],
"no-new-func": [2],
"no-new-object": [2],
"no-new-require": [0],
"no-new-wrappers": [2],
"no-obj-calls": [2],
"no-octal": [2],
"no-octal-escape": [2],
"no-path-concat": [0],
"no-param-reassign": [2],
"no-plusplus": [0],
"no-process-env": [0],
"no-process-exit": [2],
"no-proto": [2],
"no-redeclare": [2],
"no-regex-spaces": [2],
"no-reserved-keys": [0],
"no-restricted-modules": [0],
"no-return-assign": [2],
"no-script-url": [2],
"no-self-compare": [0],
"no-sequences": [2],
"no-shadow": [2],
"no-shadow-restricted-names": [2],
"semi-spacing": [2],
"no-spaced-func": [2],
"no-sparse-arrays": [2],
"no-sync": [0],
"no-ternary": [0],
"no-this-before-super": [2],
"no-throw-literal": [2],
"no-trailing-spaces": [2],
"no-undef": [2],
"no-undef-init": [2],
"no-undefined": [0],
"no-underscore-dangle": [0],
"no-unreachable": [2],
"no-unused-expressions": [2, {
"allowShortCircuit": true
}],
"no-unused-vars": [1, {
"vars": "all",
"args": "after-used"
}],
"no-use-before-define": [2, "nofunc"],
"no-var": [2],
"no-void": [0],
"no-warning-comments": [0, {
"terms": ["todo", "fixme", "xxx"],
"location": "start"
}],
"no-with": [2],
"object-shorthand": [2],
"one-var": [0],
"operator-assignment": [0, "always"],
"operator-linebreak": [2, "before"],
"padded-blocks": [0],
"prefer-const": [2],
"prefer-spread": [2],
"quote-props": [0],
"radix": [0],
"semi": [2],
"sort-vars": [0],
"keyword-spacing": [2, {"after": true}],
"space-before-function-paren": [2, { "anonymous": "always", "named": "always" }],
"space-before-blocks": [0, "always"],
"space-in-brackets": [0, "never", {
"singleValue": true,
"arraysInArrays": false,
"arraysInObjects": false,
"objectsInArrays": true,
"objectsInObjects": true,
"propertyName": false
}],
"space-in-parens": [2, "never"],
"space-infix-ops": [2],
"space-unary-ops": [2, {
"words": true,
"nonwords": false
}],
"spaced-line-comment": [0, "always"],
"strict": [1],
"use-isnan": [2],
"valid-jsdoc": [0],
"valid-typeof": [2],
"vars-on-top": [0],
"wrap-iife": [2],
"wrap-regex": [2],
"yoda": [2, "never", {
"exceptRange": true
}],
"react/display-name": 0,
"react/jsx-boolean-value": 1,
"react/jsx-closing-bracket-location": 0,
"react/jsx-curly-spacing": 1,
"react/jsx-max-props-per-line": 0,
"react/jsx-indent-props": 0,
"react/jsx-no-duplicate-props": 1,
"react/jsx-no-undef": 1,
"react/jsx-pascal-case": 1,
"react/sort-prop-types": 1,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 1,
"react/no-danger": 0,
"react/no-did-mount-set-state": 0,
"react/no-did-update-set-state": 1,
"react/no-multi-comp": 0,
"react/no-unknown-property": 1,
"react/prop-types": [2, {"ignore": ["onMouseDown", "onTouchStart"]}],
"react/react-in-jsx-scope": 1,
"react/self-closing-comp": 1,
"react/sort-comp": 1
} }
} }

View File

@ -1,8 +1,8 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { APP_BAR } from '../identifiers.js'; import { APP_BAR } from '../identifiers';
import InjectIconButton from '../button/IconButton.js'; import InjectIconButton from '../button/IconButton';
const factory = (IconButton) => { const factory = (IconButton) => {
class AppBar extends React.Component { class AppBar extends React.Component {
@ -13,13 +13,13 @@ const factory = (IconButton) => {
flat: PropTypes.bool, flat: PropTypes.bool,
leftIcon: PropTypes.oneOfType([ leftIcon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
onLeftIconClick: PropTypes.func, onLeftIconClick: PropTypes.func,
onRightIconClick: PropTypes.func, onRightIconClick: PropTypes.func,
rightIcon: PropTypes.oneOfType([ rightIcon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
scrollHide: PropTypes.bool, scrollHide: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
@ -30,27 +30,27 @@ const factory = (IconButton) => {
leftIcon: PropTypes.string, leftIcon: PropTypes.string,
rightIcon: PropTypes.string, rightIcon: PropTypes.string,
scrollHide: PropTypes.string, scrollHide: PropTypes.string,
title: PropTypes.string title: PropTypes.string,
}), }),
title: PropTypes.node title: PropTypes.node,
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
fixed: false, fixed: false,
flat: false, flat: false,
scrollHide: false scrollHide: false,
}; };
state = {hidden: false, height: 0}; state = { hidden: false, height: 0 };
componentDidMount () { componentDidMount() {
if (this.props.scrollHide) { if (this.props.scrollHide) {
this.initializeScroll(); this.initializeScroll();
} }
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (!this.props.scrollHide && nextProps.scrollHide) { if (!this.props.scrollHide && nextProps.scrollHide) {
this.initializeScroll(); this.initializeScroll();
} }
@ -60,7 +60,7 @@ const factory = (IconButton) => {
} }
} }
componentWillUnmount () { componentWillUnmount() {
if (this.props.scrollHide) { if (this.props.scrollHide) {
this.endScroll(); this.endScroll();
} }
@ -70,7 +70,7 @@ const factory = (IconButton) => {
window.addEventListener('scroll', this.handleScroll); window.addEventListener('scroll', this.handleScroll);
const { height } = this.rootNode.getBoundingClientRect(); const { height } = this.rootNode.getBoundingClientRect();
this.curScroll = window.scrollY; this.curScroll = window.scrollY;
this.setState({height}); this.setState({ height });
}; };
endScroll = () => { endScroll = () => {
@ -79,31 +79,42 @@ const factory = (IconButton) => {
handleScroll = () => { handleScroll = () => {
const scrollDiff = this.curScroll - window.scrollY; const scrollDiff = this.curScroll - window.scrollY;
const hidden = scrollDiff < 0 && window.scrollY !== undefined && window.scrollY > this.state.height; const hidden = scrollDiff < 0
this.setState({hidden}); && window.scrollY !== undefined
&& window.scrollY > this.state.height;
this.setState({ hidden });
this.curScroll = window.scrollY; this.curScroll = window.scrollY;
}; };
render () { render() {
const { children, leftIcon, onLeftIconClick, onRightIconClick, rightIcon, theme, title } = this.props; const {
children,
leftIcon,
onLeftIconClick,
onRightIconClick,
rightIcon,
theme,
title,
} = this.props;
const className = classnames(theme.appBar, { const className = classnames(theme.appBar, {
[theme.fixed]: this.props.fixed, [theme.fixed]: this.props.fixed,
[theme.flat]: this.props.flat, [theme.flat]: this.props.flat,
[theme.scrollHide]: this.state.hidden [theme.scrollHide]: this.state.hidden,
}, this.props.className); }, this.props.className);
return ( return (
<header <header
className={className} className={className}
data-react-toolbox='app-bar' data-react-toolbox="app-bar"
ref={node => {this.rootNode = node;}} ref={(node) => { this.rootNode = node; }}
> >
<div className={theme.inner}> <div className={theme.inner}>
{leftIcon && <IconButton {leftIcon && <IconButton
inverse inverse
className={classnames(theme.leftIcon)} className={classnames(theme.leftIcon)}
onClick={onLeftIconClick} onClick={onLeftIconClick}
icon={leftIcon} /> icon={leftIcon}
/>
} }
{title && <h1 className={classnames(theme.title)}>{title}</h1>} {title && <h1 className={classnames(theme.title)}>{title}</h1>}
{children} {children}
@ -111,7 +122,8 @@ const factory = (IconButton) => {
inverse inverse
className={classnames(theme.rightIcon)} className={classnames(theme.rightIcon)}
onClick={onRightIconClick} onClick={onRightIconClick}
icon={rightIcon} /> icon={rightIcon}
/>
} }
</div> </div>
</header> </header>

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { APP_BAR } from '../identifiers.js'; import { APP_BAR } from '../identifiers';
import { appBarFactory } from './AppBar.js'; import { appBarFactory } from './AppBar';
import { IconButton } from '../button'; import { IconButton } from '../button';
import theme from './theme.css'; import theme from './theme.css';

View File

@ -1,3 +1,4 @@
/* eslint-disable */
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classnames from 'classnames'; import classnames from 'classnames';
@ -10,192 +11,191 @@ import events from '../utils/events.js';
const POSITION = { const POSITION = {
AUTO: 'auto', AUTO: 'auto',
DOWN: 'down', DOWN: 'down',
UP: 'up' UP: 'up',
}; };
const factory = (Chip, Input) => { const factory = (Chip, Input) => {
class Autocomplete extends Component { class Autocomplete extends Component {
static propTypes = { static propTypes = {
allowCreate: PropTypes.bool, allowCreate: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
direction: PropTypes.oneOf(['auto', 'up', 'down']), direction: PropTypes.oneOf(['auto', 'up', 'down']),
disabled: PropTypes.bool, disabled: PropTypes.bool,
error: React.PropTypes.oneOfType([ error: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.node React.PropTypes.node,
]), ]),
keepFocusOnChange: PropTypes.bool, keepFocusOnChange: PropTypes.bool,
label: React.PropTypes.oneOfType([ label: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.node React.PropTypes.node,
]), ]),
multiple: PropTypes.bool, multiple: PropTypes.bool,
onBlur: PropTypes.func, onBlur: PropTypes.func,
onChange: PropTypes.func, onChange: PropTypes.func,
onFocus: PropTypes.func, onFocus: PropTypes.func,
onQueryChange: PropTypes.func, onQueryChange: PropTypes.func,
query: PropTypes.string, query: PropTypes.string,
selectedPosition: PropTypes.oneOf(['above', 'below', 'none']), selectedPosition: PropTypes.oneOf(['above', 'below', 'none']),
showSelectedWhenNotInSource: PropTypes.bool, showSelectedWhenNotInSource: PropTypes.bool,
showSuggestionsWhenValueIsSet: PropTypes.bool, showSuggestionsWhenValueIsSet: PropTypes.bool,
source: PropTypes.any, source: PropTypes.any,
suggestionMatch: PropTypes.oneOf(['disabled', 'start', 'anywhere', 'word']), suggestionMatch: PropTypes.oneOf(['disabled', 'start', 'anywhere', 'word']),
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
autocomplete: PropTypes.string, autocomplete: PropTypes.string,
focus: PropTypes.string, focus: PropTypes.string,
input: PropTypes.string, input: PropTypes.string,
suggestion: PropTypes.string, suggestion: PropTypes.string,
suggestions: PropTypes.string, suggestions: PropTypes.string,
up: PropTypes.string, up: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
values: PropTypes.string values: PropTypes.string,
}), }),
value: PropTypes.any value: PropTypes.any,
}; };
static defaultProps = { static defaultProps = {
allowCreate: false, allowCreate: false,
className: '', className: '',
direction: 'auto', direction: 'auto',
keepFocusOnChange: false, keepFocusOnChange: false,
multiple: true, multiple: true,
selectedPosition: 'above', selectedPosition: 'above',
showSelectedWhenNotInSource: false, showSelectedWhenNotInSource: false,
showSuggestionsWhenValueIsSet: false, showSuggestionsWhenValueIsSet: false,
source: {}, source: {},
suggestionMatch: 'start' suggestionMatch: 'start',
}; };
state = { state = {
direction: this.props.direction, direction: this.props.direction,
focus: false, focus: false,
showAllSuggestions: this.props.showSuggestionsWhenValueIsSet, showAllSuggestions: this.props.showSuggestionsWhenValueIsSet,
query: this.props.query ? this.props.query : this.query(this.props.value), query: this.props.query ? this.props.query : this.query(this.props.value),
isValueAnObject: false isValueAnObject: false,
}; };
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (!this.props.multiple) { if (!this.props.multiple) {
const query = nextProps.query ? nextProps.query : this.query(nextProps.value); const query = nextProps.query ? nextProps.query : this.query(nextProps.value);
this.updateQuery(query, false); this.updateQuery(query, false);
} }
} }
shouldComponentUpdate (nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if (!this.state.focus && nextState.focus && this.props.direction === POSITION.AUTO) { if (!this.state.focus && nextState.focus && this.props.direction === POSITION.AUTO) {
const direction = this.calculateDirection(); const direction = this.calculateDirection();
if (this.state.direction !== direction) { if (this.state.direction !== direction) {
this.setState({ direction }); this.setState({ direction });
} }
} }
return true; return true;
} }
handleChange = (values, event) => { handleChange = (values, event) => {
const value = this.props.multiple ? values : values[0]; const value = this.props.multiple ? values : values[0];
const { showSuggestionsWhenValueIsSet: showAllSuggestions } = this.props; const { showSuggestionsWhenValueIsSet: showAllSuggestions } = this.props;
const query = this.query(value); const query = this.query(value);
if (this.props.onChange) this.props.onChange(value, event); if (this.props.onChange) this.props.onChange(value, event);
if (this.props.keepFocusOnChange) { if (this.props.keepFocusOnChange) {
this.setState({ query, showAllSuggestions }); this.setState({ query, showAllSuggestions });
} else { } else {
this.setState({ focus: false, query, showAllSuggestions }, () => { this.setState({ focus: false, query, showAllSuggestions }, () => {
ReactDOM.findDOMNode(this).querySelector('input').blur(); ReactDOM.findDOMNode(this).querySelector('input').blur();
}); });
} }
this.updateQuery(query, this.props.query); this.updateQuery(query, this.props.query);
}; };
handleMouseDown = (event) => { handleMouseDown = (event) => {
this.selectOrCreateActiveItem(event); this.selectOrCreateActiveItem(event);
} }
handleQueryBlur = (event) => { handleQueryBlur = (event) => {
if (this.state.focus) this.setState({focus: false}); if (this.state.focus) this.setState({ focus: false });
if (this.props.onBlur) this.props.onBlur(event, this.state.active); if (this.props.onBlur) this.props.onBlur(event, this.state.active);
}; };
updateQuery = (query, notify) => { updateQuery = (query, notify) => {
if (notify && this.props.onQueryChange) this.props.onQueryChange(query); if (notify && this.props.onQueryChange) this.props.onQueryChange(query);
this.setState({ query }); this.setState({ query });
} }
handleQueryChange = (value) => { handleQueryChange = (value) => {
const query = this.clearQuery ? '' : value; const query = this.clearQuery ? '' : value;
this.clearQuery = false; this.clearQuery = false;
this.updateQuery(query, true); this.updateQuery(query, true);
this.setState({showAllSuggestions: false, active: null}); this.setState({ showAllSuggestions: false, active: null });
}; };
handleQueryFocus = (event) => { handleQueryFocus = (event) => {
this.suggestionsNode.scrollTop = 0; this.suggestionsNode.scrollTop = 0;
this.setState({active: '', focus: true}); this.setState({ active: '', focus: true });
if (this.props.onFocus) this.props.onFocus(event); if (this.props.onFocus) this.props.onFocus(event);
}; };
handleQueryKeyDown = (event) => { handleQueryKeyDown = (event) => {
// Mark query for clearing in handleQueryChange when pressing backspace and showing all suggestions. // Mark query for clearing in handleQueryChange when pressing backspace and showing all suggestions.
this.clearQuery = ( this.clearQuery = (
event.which === 8 event.which === 8
&& this.props.showSuggestionsWhenValueIsSet && this.props.showSuggestionsWhenValueIsSet
&& this.state.showAllSuggestions && this.state.showAllSuggestions
); );
if (event.which === 13) { if (event.which === 13) {
this.selectOrCreateActiveItem(event); this.selectOrCreateActiveItem(event);
} }
}; };
handleQueryKeyUp = (event) => { handleQueryKeyUp = (event) => {
if (event.which === 27) ReactDOM.findDOMNode(this).querySelector('input').blur(); if (event.which === 27) ReactDOM.findDOMNode(this).querySelector('input').blur();
if ([40, 38].indexOf(event.which) !== -1) { if ([40, 38].indexOf(event.which) !== -1) {
const suggestionsKeys = [...this.suggestions().keys()]; const suggestionsKeys = [...this.suggestions().keys()];
let index = suggestionsKeys.indexOf(this.state.active) + (event.which === 40 ? +1 : -1); let index = suggestionsKeys.indexOf(this.state.active) + (event.which === 40 ? +1 : -1);
if (index < 0) index = suggestionsKeys.length - 1; if (index < 0) index = suggestionsKeys.length - 1;
if (index >= suggestionsKeys.length) index = 0; if (index >= suggestionsKeys.length) index = 0;
this.setState({active: suggestionsKeys[index]}); this.setState({ active: suggestionsKeys[index] });
} }
}; };
handleSuggestionHover = (event) => { handleSuggestionHover = (event) => {
this.setState({active: event.target.id}); this.setState({ active: event.target.id });
}; };
calculateDirection () { calculateDirection() {
if (this.props.direction === 'auto') { if (this.props.direction === 'auto') {
const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect(); const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect();
const screen_height = window.innerHeight || document.documentElement.offsetHeight; const screen_height = window.innerHeight || document.documentElement.offsetHeight;
const up = client.top > ((screen_height / 2) + client.height); const up = client.top > ((screen_height / 2) + client.height);
return up ? 'up' : 'down'; return up ? 'up' : 'down';
} else { }
return this.props.direction; return this.props.direction;
} }
}
query (key) { query(key) {
let query_value = ''; let query_value = '';
if (!this.props.multiple && key) { if (!this.props.multiple && key) {
const source_value = this.source().get(`${key}`); const source_value = this.source().get(`${key}`);
query_value = source_value ? source_value : key; query_value = source_value || key;
} }
return query_value; return query_value;
} }
selectOrCreateActiveItem (event) { selectOrCreateActiveItem(event) {
let target = this.state.active; let target = this.state.active;
if (!target) { if (!target) {
target = this.props.allowCreate target = this.props.allowCreate
? this.state.query ? this.state.query
: [...this.suggestions().keys()][0]; : [...this.suggestions().keys()][0];
this.setState({active: target}); this.setState({ active: target });
} }
this.select(event, target); this.select(event, target);
} }
normalise (value) { normalise(value) {
const sdiak = 'áâäąáâäąččććççĉĉďđďđééěëēėęéěëēėęĝĝğğġġģģĥĥħħíîíîĩĩīīĭĭįįi̇ıĵĵķķĸĺĺļļŀŀłłĺľĺľňńņŋŋņňńʼnóöôőøōōóöőôøřřŕŕŗŗššśśŝŝşşţţťťŧŧũũūūŭŭůůűűúüúüűųųŵŵýyŷŷýyžžźźżżß'; const sdiak = 'áâäąáâäąččććççĉĉďđďđééěëēėęéěëēėęĝĝğğġġģģĥĥħħíîíîĩĩīīĭĭįįi̇ıĵĵķķĸĺĺļļŀŀłłĺľĺľňńņŋŋņňńʼnóöôőøōōóöőôøřřŕŕŗŗššśśŝŝşşţţťťŧŧũũūūŭŭůůűűúüúüűųųŵŵýyŷŷýyžžźźżżß';
const bdiak = 'AAAAAAAACCCCCCCCDDDDEEEEEEEEEEEEEGGGGGGGGHHHHIIIIIIIIIIIIIIJJKKKLLLLLLLLLLLLNNNNNNNNNOOOOOOOOOOOORRRRRRSSSSSSSSTTTTTTUUUUUUUUUUUUUUUUUWWYYYYYYZZZZZZS'; const bdiak = 'AAAAAAAACCCCCCCCDDDDEEEEEEEEEEEEEGGGGGGGGHHHHIIIIIIIIIIIIIIJJKKKLLLLLLLLLLLLNNNNNNNNNOOOOOOOOOOOORRRRRRSSSSSSSSTTTTTTUUUUUUUUUUUUUUUUUWWYYYYYYZZZZZZS';
@ -209,209 +209,206 @@ const factory = (Chip, Input) => {
} }
return normalised.toLowerCase().trim(); return normalised.toLowerCase().trim();
} }
suggestions () { suggestions() {
let suggest = new Map(); let suggest = new Map();
const rawQuery = this.state.query || (this.props.multiple ? '' : this.props.value); const rawQuery = this.state.query || (this.props.multiple ? '' : this.props.value);
const query = this.normalise((`${rawQuery}`)); const query = this.normalise((`${rawQuery}`));
const values = this.values(); const values = this.values();
const source = this.source(); const source = this.source();
// Suggest any non-set value which matches the query // Suggest any non-set value which matches the query
if (this.props.multiple) { if (this.props.multiple) {
for (const [key, value] of source) { for (const [key, value] of source) {
if (!values.has(key) && this.matches(this.normalise(value), query)) { if (!values.has(key) && this.matches(this.normalise(value), query)) {
suggest.set(key, value); suggest.set(key, value);
} }
} }
// When multiple is false, suggest any value which matches the query if showAllSuggestions is false // When multiple is false, suggest any value which matches the query if showAllSuggestions is false
} else if (query && !this.state.showAllSuggestions) { } else if (query && !this.state.showAllSuggestions) {
for (const [key, value] of source) { for (const [key, value] of source) {
if (this.matches(this.normalise(value), query)) { if (this.matches(this.normalise(value), query)) {
suggest.set(key, value); suggest.set(key, value);
} }
} }
// When multiple is false, suggest all values when showAllSuggestions is true // When multiple is false, suggest all values when showAllSuggestions is true
} else { } else {
suggest = source; suggest = source;
} }
return suggest; return suggest;
} }
matches (value, query) { matches(value, query) {
const { suggestionMatch } = this.props; const { suggestionMatch } = this.props;
if (suggestionMatch === 'disabled') { if (suggestionMatch === 'disabled') {
return true; return true;
} else if (suggestionMatch === 'start') { } else if (suggestionMatch === 'start') {
return value.startsWith(query); return value.startsWith(query);
} else if (suggestionMatch === 'anywhere') { } else if (suggestionMatch === 'anywhere') {
return value.includes(query); return value.includes(query);
} else if (suggestionMatch === 'word') { } else if (suggestionMatch === 'word') {
const re = new RegExp(`\\b${query}`, 'g'); const re = new RegExp(`\\b${query}`, 'g');
return re.test(value); return re.test(value);
} }
return false; return false;
} }
source () { source() {
const { source: src } = this.props; const { source: src } = this.props;
if (src.hasOwnProperty('length')) { if (src.hasOwnProperty('length')) {
return new Map(src.map((item) => Array.isArray(item) ? [...item] : [item, item])); return new Map(src.map(item => Array.isArray(item) ? [...item] : [item, item]));
} else { }
return new Map(Object.keys(src).map((key) => [`${key}`, src[key]])); return new Map(Object.keys(src).map(key => [`${key}`, src[key]]));
} }
}
values () { values() {
let vals = this.props.multiple ? this.props.value : [this.props.value]; let vals = this.props.multiple ? this.props.value : [this.props.value];
if (!vals) vals = []; if (!vals) vals = [];
if (this.props.showSelectedWhenNotInSource && this.isValueAnObject()) { if (this.props.showSelectedWhenNotInSource && this.isValueAnObject()) {
return new Map(Object.entries(vals)); return new Map(Object.entries(vals));
} }
const valueMap = new Map(); const valueMap = new Map();
const stringVals = vals.map(v => `${v}`); const stringVals = vals.map(v => `${v}`);
for (const [k, v] of this.source()) { for (const [k, v] of this.source()) {
if (stringVals.indexOf(k) !== -1) valueMap.set(k, v); if (stringVals.indexOf(k) !== -1) valueMap.set(k, v);
} }
return valueMap; return valueMap;
} }
select = (event, target) => { select = (event, target) => {
events.pauseEvent(event); events.pauseEvent(event);
const values = this.values(this.props.value); const values = this.values(this.props.value);
const source = this.source(); const source = this.source();
const newValue = target === void 0 ? event.target.id : target; const newValue = target === void 0 ? event.target.id : target;
if (this.isValueAnObject()) { if (this.isValueAnObject()) {
const newItem = Array.from(source).reduce((obj, [k, value]) => { const newItem = Array.from(source).reduce((obj, [k, value]) => {
if (k === newValue) { if (k === newValue) {
obj[k] = value; obj[k] = value;
} }
return obj; return obj;
}, {}); }, {});
return this.handleChange(Object.assign(this.mapToObject(values), newItem), event); return this.handleChange(Object.assign(this.mapToObject(values), newItem), event);
} }
this.handleChange([newValue, ...values.keys()], event); this.handleChange([newValue, ...values.keys()], event);
}; };
unselect (key, event) { unselect(key, event) {
if (!this.props.disabled) { if (!this.props.disabled) {
const values = this.values(this.props.value); const values = this.values(this.props.value);
values.delete(key); values.delete(key);
if (this.isValueAnObject()) { if (this.isValueAnObject()) {
return this.handleChange(this.mapToObject(values), event); return this.handleChange(this.mapToObject(values), event);
} }
this.handleChange([...values.keys()], event); this.handleChange([...values.keys()], event);
} }
} }
isValueAnObject () { isValueAnObject() {
return !Array.isArray(this.props.value) && typeof this.props.value === 'object'; return !Array.isArray(this.props.value) && typeof this.props.value === 'object';
} }
mapToObject (map) { mapToObject(map) {
return Array.from(map).reduce((obj, [k, value]) => { return Array.from(map).reduce((obj, [k, value]) => {
obj[k] = value; obj[k] = value;
return obj; return obj;
}, {}); }, {});
} }
renderSelected () { renderSelected() {
if (this.props.multiple) { if (this.props.multiple) {
const selectedItems = [...this.values()].map(([key, value]) => { const selectedItems = [...this.values()].map(([key, value]) => (
return ( <Chip
<Chip key={key}
key={key} className={this.props.theme.value}
className={this.props.theme.value} deletable
deletable onDeleteClick={this.unselect.bind(this, key)}
onDeleteClick={this.unselect.bind(this, key)} >
> {value}
{value} </Chip>
</Chip> ));
);
});
return <ul className={this.props.theme.values}>{selectedItems}</ul>; return <ul className={this.props.theme.values}>{selectedItems}</ul>;
} }
} }
renderSuggestions () { renderSuggestions() {
const { theme } = this.props; const { theme } = this.props;
const suggestions = [...this.suggestions()].map(([key, value]) => { const suggestions = [...this.suggestions()].map(([key, value]) => {
const className = classnames(theme.suggestion, {[theme.active]: this.state.active === key}); const className = classnames(theme.suggestion, { [theme.active]: this.state.active === key });
return ( return (
<li <li
id={key} id={key}
key={key} key={key}
className={className} className={className}
onMouseDown={this.handleMouseDown} onMouseDown={this.handleMouseDown}
onMouseOver={this.handleSuggestionHover} onMouseOver={this.handleSuggestionHover}
> >
{value} {value}
</li> </li>
); );
}); });
return ( return (
<ul <ul
className={classnames(theme.suggestions, {[theme.up]: this.state.direction === 'up'})} className={classnames(theme.suggestions, { [theme.up]: this.state.direction === 'up' })}
ref={node => { this.suggestionsNode = node; }} ref={(node) => { this.suggestionsNode = node; }}
> >
{suggestions} {suggestions}
</ul> </ul>
); );
} }
render () { render() {
const { const {
allowCreate, error, label, source, suggestionMatch, query, //eslint-disable-line no-unused-vars allowCreate, error, label, source, suggestionMatch, query, // eslint-disable-line no-unused-vars
selectedPosition, keepFocusOnChange, showSuggestionsWhenValueIsSet, showSelectedWhenNotInSource, onQueryChange, //eslint-disable-line no-unused-vars selectedPosition, keepFocusOnChange, showSuggestionsWhenValueIsSet, showSelectedWhenNotInSource, onQueryChange, // eslint-disable-line no-unused-vars
theme, ...other theme, ...other
} = this.props; } = this.props;
const className = classnames(theme.autocomplete, { const className = classnames(theme.autocomplete, {
[theme.focus]: this.state.focus [theme.focus]: this.state.focus,
}, this.props.className); }, this.props.className);
return ( return (
<div data-react-toolbox='autocomplete' className={className}> <div data-react-toolbox="autocomplete" className={className}>
{this.props.selectedPosition === 'above' ? this.renderSelected() : null} {this.props.selectedPosition === 'above' ? this.renderSelected() : null}
<Input <Input
{...other} {...other}
ref={node => { this.inputNode = node; }} ref={(node) => { this.inputNode = node; }}
autoComplete="off" autoComplete="off"
className={theme.input} className={theme.input}
error={error} error={error}
label={label} label={label}
onBlur={this.handleQueryBlur} onBlur={this.handleQueryBlur}
onChange={this.handleQueryChange} onChange={this.handleQueryChange}
onFocus={this.handleQueryFocus} onFocus={this.handleQueryFocus}
onKeyDown={this.handleQueryKeyDown} onKeyDown={this.handleQueryKeyDown}
onKeyUp={this.handleQueryKeyUp} onKeyUp={this.handleQueryKeyUp}
theme={theme} theme={theme}
themeNamespace="input" themeNamespace="input"
value={this.state.query} value={this.state.query}
/> />
{this.renderSuggestions()} {this.renderSuggestions()}
{this.props.selectedPosition === 'below' ? this.renderSelected() : null} {this.props.selectedPosition === 'below' ? this.renderSelected() : null}
</div> </div>
); );
} }
} }
return Autocomplete; return Autocomplete;

View File

@ -1,8 +1,8 @@
import { AUTOCOMPLETE } from '../identifiers.js';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { autocompleteFactory } from './Autocomplete.js'; import { AUTOCOMPLETE } from '../identifiers';
import Chip from '../chip'; import { autocompleteFactory } from './Autocomplete';
import Input from '../input'; import { Chip } from '../chip';
import { Input } from '../input';
import theme from './theme.css'; import theme from './theme.css';
const Autocomplete = autocompleteFactory(Chip, Input); const Autocomplete = autocompleteFactory(Chip, Input);

View File

@ -1,14 +1,14 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { AVATAR } from '../identifiers.js'; import { AVATAR } from '../identifiers';
import InjectFontIcon from '../font_icon/FontIcon.js'; import InjectFontIcon from '../font_icon/FontIcon';
const factory = (FontIcon) => { const factory = (FontIcon) => {
const Avatar = ({alt, children, className, cover, icon, image, theme, title, ...other}) => ( const Avatar = ({ alt, children, className, cover, icon, image, theme, title, ...other }) => (
<div data-react-toolbox='avatar' className={classnames(theme.avatar, className)} {...other}> <div data-react-toolbox="avatar" className={classnames(theme.avatar, className)} {...other}>
{children} {children}
{cover && typeof image === 'string' && <span aria-label={alt} className={theme.image} style={{backgroundImage: `url(${image})`}} />} {cover && typeof image === 'string' && <span aria-label={alt} className={theme.image} style={{ backgroundImage: `url(${image})` }} />}
{!cover && (typeof image === 'string' ? <img alt={alt} className={theme.image} src={image} /> : image)} {!cover && (typeof image === 'string' ? <img alt={alt} className={theme.image} src={image} /> : image)}
{typeof icon === 'string' ? <FontIcon className={theme.letter} value={icon} alt={alt} /> : icon} {typeof icon === 'string' ? <FontIcon className={theme.letter} value={icon} alt={alt} /> : icon}
{title ? <span className={theme.letter}>{title[0]}</span> : null} {title ? <span className={theme.letter}>{title[0]}</span> : null}
@ -25,14 +25,14 @@ const factory = (FontIcon) => {
theme: PropTypes.shape({ theme: PropTypes.shape({
avatar: PropTypes.string, avatar: PropTypes.string,
image: PropTypes.string, image: PropTypes.string,
letter: PropTypes.string letter: PropTypes.string,
}), }),
title: PropTypes.string title: PropTypes.string,
}; };
Avatar.defaultProps = { Avatar.defaultProps = {
alt: '', alt: '',
cover: false cover: false,
}; };
return Avatar; return Avatar;

View File

@ -1,7 +1,7 @@
import { AVATAR } from '../identifiers.js';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { avatarFactory } from './Avatar.js'; import { AVATAR } from '../identifiers';
import FontIcon from '../font_icon/FontIcon.js'; import { avatarFactory } from './Avatar';
import { FontIcon } from '../font_icon/FontIcon';
import theme from './theme.css'; import theme from './theme.css';
const Avatar = avatarFactory(FontIcon); const Avatar = avatarFactory(FontIcon);

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { BUTTON } from '../identifiers.js'; import { BUTTON } from '../identifiers';
import InjectFontIcon from '../font_icon/FontIcon.js'; import InjectFontIcon from '../font_icon/FontIcon';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
const factory = (ripple, FontIcon) => { const factory = (ripple, FontIcon) => {
class SimpleBrowseButton extends Component { class SimpleBrowseButton extends Component {
@ -16,7 +16,7 @@ const factory = (ripple, FontIcon) => {
floating: PropTypes.bool, floating: PropTypes.bool,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
inverse: PropTypes.bool, inverse: PropTypes.bool,
label: PropTypes.string, label: PropTypes.string,
@ -39,9 +39,9 @@ const factory = (ripple, FontIcon) => {
primary: PropTypes.string, primary: PropTypes.string,
raised: PropTypes.string, raised: PropTypes.string,
rippleWrapper: PropTypes.string, rippleWrapper: PropTypes.string,
toggle: PropTypes.string toggle: PropTypes.string,
}), }),
type: PropTypes.string type: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
@ -52,16 +52,28 @@ const factory = (ripple, FontIcon) => {
mini: false, mini: false,
neutral: true, neutral: true,
primary: false, primary: false,
raised: false raised: false,
}; };
getLevel = () => {
if (this.props.primary) return 'primary';
if (this.props.accent) return 'accent';
return 'neutral';
}
getShape = () => {
if (this.props.raised) return 'raised';
if (this.props.floating) return 'floating';
return 'flat';
}
handleMouseUp = (event) => { handleMouseUp = (event) => {
this.refs.label.blur(); this.labelNode.blur();
if (this.props.onMouseUp) this.props.onMouseUp(event); if (this.props.onMouseUp) this.props.onMouseUp(event);
}; };
handleMouseLeave = (event) => { handleMouseLeave = (event) => {
this.refs.label.blur(); this.labelNode.blur();
if (this.props.onMouseLeave) this.props.onMouseLeave(event); if (this.props.onMouseLeave) this.props.onMouseLeave(event);
}; };
@ -69,34 +81,48 @@ const factory = (ripple, FontIcon) => {
if (this.props.onChange) this.props.onChange(event); if (this.props.onChange) this.props.onChange(event);
}; };
render () { render() {
const { accent, children, className, flat, floating, icon, const {
inverse, label, mini, neutral, primary, theme, raised, ...others} = this.props; accent, // eslint-disable-line
const element = 'label'; children,
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; className,
const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat'; flat, // eslint-disable-line
floating, // eslint-disable-line
icon,
inverse,
label,
mini,
neutral,
primary, // eslint-disable-line
raised, // eslint-disable-line
theme,
...others
} = this.props;
const element = 'label';
const level = this.getLevel();
const shape = this.getShape();
const classes = classnames(theme.button, [theme[shape]], { const classes = classnames(theme.button, [theme[shape]], {
[theme[level]]: neutral, [theme[level]]: neutral,
[theme.mini]: mini, [theme.mini]: mini,
[theme.inverse]: inverse [theme.inverse]: inverse,
}, className); }, className);
const props = { const props = {
...others, ...others,
ref: 'label', ref: (node) => { this.labelNode = node; },
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseUp: this.handleMouseUp, onMouseUp: this.handleMouseUp,
onMouseLeave: this.handleMouseLeave, onMouseLeave: this.handleMouseLeave,
'data-react-toolbox': 'label' 'data-react-toolbox': 'label',
}; };
return React.createElement(element, props, return React.createElement(element, props,
icon ? <FontIcon className={theme.icon} value={icon}/> : null, icon ? <FontIcon className={theme.icon} value={icon} /> : null,
<span>{label}</span>, <span>{label}</span>,
<input className={classes} type="file" onChange={this.handleFileChange}/>, <input className={classes} type="file" onChange={this.handleFileChange} />,
children children,
); );
} }
} }

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { BUTTON } from '../identifiers.js'; import { BUTTON } from '../identifiers';
import InjectFontIcon from '../font_icon/FontIcon.js'; import InjectFontIcon from '../font_icon/FontIcon';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
const factory = (ripple, FontIcon) => { const factory = (ripple, FontIcon) => {
class Button extends Component { class Button extends Component {
@ -17,7 +17,7 @@ const factory = (ripple, FontIcon) => {
href: PropTypes.string, href: PropTypes.string,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
inverse: PropTypes.bool, inverse: PropTypes.bool,
label: PropTypes.string, label: PropTypes.string,
@ -39,9 +39,9 @@ const factory = (ripple, FontIcon) => {
primary: PropTypes.string, primary: PropTypes.string,
raised: PropTypes.string, raised: PropTypes.string,
rippleWrapper: PropTypes.string, rippleWrapper: PropTypes.string,
toggle: PropTypes.string toggle: PropTypes.string,
}), }),
type: PropTypes.string type: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
@ -53,48 +53,76 @@ const factory = (ripple, FontIcon) => {
neutral: true, neutral: true,
primary: false, primary: false,
raised: false, raised: false,
type: 'button' type: 'button',
}; };
getLevel = () => {
if (this.props.primary) return 'primary';
if (this.props.accent) return 'accent';
return 'neutral';
}
getShape = () => {
if (this.props.raised) return 'raised';
if (this.props.floating) return 'floating';
return 'flat';
}
handleMouseUp = (event) => { handleMouseUp = (event) => {
this.refs.button.blur(); this.buttonNode.blur();
if (this.props.onMouseUp) this.props.onMouseUp(event); if (this.props.onMouseUp) this.props.onMouseUp(event);
}; };
handleMouseLeave = (event) => { handleMouseLeave = (event) => {
this.refs.button.blur(); this.buttonNode.blur();
if (this.props.onMouseLeave) this.props.onMouseLeave(event); if (this.props.onMouseLeave) this.props.onMouseLeave(event);
}; };
render () { render() {
const { accent, children, className, flat, floating, href, icon, const {
inverse, label, mini, neutral, primary, theme, type, raised, ...others} = this.props; accent, // eslint-disable-line
children,
className,
flat, // eslint-disable-line
floating, // eslint-disable-line
href,
icon,
inverse,
label,
mini,
neutral,
primary, // eslint-disable-line
raised, // eslint-disable-line
theme,
type,
...others
} = this.props;
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = this.getLevel();
const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat'; const shape = this.getShape();
const classes = classnames(theme.button, [theme[shape]], { const classes = classnames(theme.button, [theme[shape]], {
[theme[level]]: neutral, [theme[level]]: neutral,
[theme.mini]: mini, [theme.mini]: mini,
[theme.inverse]: inverse [theme.inverse]: inverse,
}, className); }, className);
const props = { const props = {
...others, ...others,
href, href,
ref: 'button', ref: (node) => { this.buttonNode = node; },
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseUp: this.handleMouseUp, onMouseUp: this.handleMouseUp,
onMouseLeave: this.handleMouseLeave, onMouseLeave: this.handleMouseLeave,
type: !href ? type : null, type: !href ? type : null,
'data-react-toolbox': 'button' 'data-react-toolbox': 'button',
}; };
return React.createElement(element, props, return React.createElement(element, props,
icon ? <FontIcon className={theme.icon} value={icon}/> : null, icon ? <FontIcon className={theme.icon} value={icon} /> : null,
label, label,
children children,
); );
} }
} }

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { BUTTON } from '../identifiers.js'; import { BUTTON } from '../identifiers';
import InjectFontIcon from '../font_icon/FontIcon.js'; import InjectFontIcon from '../font_icon/FontIcon';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
const factory = (ripple, FontIcon) => { const factory = (ripple, FontIcon) => {
class IconButton extends Component { class IconButton extends Component {
@ -15,15 +15,28 @@ const factory = (ripple, FontIcon) => {
href: PropTypes.string, href: PropTypes.string,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
inverse: PropTypes.bool, inverse: PropTypes.bool,
neutral: PropTypes.bool, neutral: PropTypes.bool,
onMouseLeave: PropTypes.func, onMouseLeave: PropTypes.func,
onMouseUp: PropTypes.func, onMouseUp: PropTypes.func,
primary: PropTypes.bool, primary: PropTypes.bool,
theme: PropTypes.object, theme: PropTypes.shape({
type: PropTypes.string accent: PropTypes.string,
button: PropTypes.string,
flat: PropTypes.string,
floating: PropTypes.string,
icon: PropTypes.string,
inverse: PropTypes.string,
mini: PropTypes.string,
neutral: PropTypes.string,
primary: PropTypes.string,
raised: PropTypes.string,
rippleWrapper: PropTypes.string,
toggle: PropTypes.string,
}),
type: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
@ -31,44 +44,65 @@ const factory = (ripple, FontIcon) => {
className: '', className: '',
neutral: true, neutral: true,
primary: false, primary: false,
type: 'button' type: 'button',
}; };
getLevel = () => {
if (this.props.primary) return 'primary';
if (this.props.accent) return 'accent';
return 'neutral';
}
handleMouseUp = (event) => { handleMouseUp = (event) => {
this.refs.button.blur(); this.buttonNode.blur();
if (this.props.onMouseUp) this.props.onMouseUp(event); if (this.props.onMouseUp) this.props.onMouseUp(event);
}; };
handleMouseLeave = (event) => { handleMouseLeave = (event) => {
this.refs.button.blur(); this.buttonNode.blur();
if (this.props.onMouseLeave) this.props.onMouseLeave(event); if (this.props.onMouseLeave) this.props.onMouseLeave(event);
}; };
render () { render() {
const {accent, children, className, href, icon, inverse, neutral, const {
primary, theme, type, ...others} = this.props; accent, // eslint-disable-line
children,
className,
href,
icon,
inverse,
neutral,
primary, // eslint-disable-line
theme,
type,
...others
} = this.props;
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = this.getLevel();
const classes = classnames([theme.toggle], { const classes = classnames([theme.toggle], {
[theme[level]]: neutral, [theme[level]]: neutral,
[theme.inverse]: inverse [theme.inverse]: inverse,
}, className); }, className);
const props = { const props = {
...others, ...others,
href, href,
ref: 'button', ref: (node) => { this.buttonNode = node; },
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseUp: this.handleMouseUp, onMouseUp: this.handleMouseUp,
onMouseLeave: this.handleMouseLeave, onMouseLeave: this.handleMouseLeave,
type: !href ? type : null, type: !href ? type : null,
'data-react-toolbox': 'button' 'data-react-toolbox': 'button',
}; };
const iconElement = typeof icon === 'string'
? <FontIcon className={theme.icon} value={icon} />
: icon;
return React.createElement(element, props, return React.createElement(element, props,
icon ? typeof icon === 'string' ? <FontIcon className={theme.icon} value={icon} /> : icon : null, icon && iconElement,
children children,
); );
} }
} }
@ -76,7 +110,7 @@ const factory = (ripple, FontIcon) => {
return ripple(IconButton); return ripple(IconButton);
}; };
const IconButton = factory(rippleFactory({centered: true}), InjectFontIcon); const IconButton = factory(rippleFactory({ centered: true }), InjectFontIcon);
export default themr(BUTTON)(IconButton); export default themr(BUTTON)(IconButton);
export { factory as iconButtonFactory }; export { factory as iconButtonFactory };
export { IconButton }; export { IconButton };

View File

@ -1,3 +1,4 @@
/* eslint-disable */
import expect from 'expect'; import expect from 'expect';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
@ -10,23 +11,23 @@ const getRenderedClassName = (tree, Component) => {
return ReactDOM.findDOMNode(rendered).getAttribute('class'); return ReactDOM.findDOMNode(rendered).getAttribute('class');
}; };
describe('Button', function () { describe('Button', () => {
describe('#render', function () { describe('#render', () => {
it('uses flat and neutral styles by default', function () { it('uses flat and neutral styles by default', () => {
const tree = TestUtils.renderIntoDocument(<Button theme={theme} />); const tree = TestUtils.renderIntoDocument(<Button theme={theme} />);
const className = getRenderedClassName(tree, RawButton); const className = getRenderedClassName(tree, RawButton);
expect(className).toContain(theme.flat); expect(className).toContain(theme.flat);
expect(className).toContain(theme.neutral); expect(className).toContain(theme.neutral);
}); });
it('renders accent button with accent style', function () { it('renders accent button with accent style', () => {
const tree = TestUtils.renderIntoDocument(<Button accent theme={theme} />); const tree = TestUtils.renderIntoDocument(<Button accent theme={theme} />);
const className = getRenderedClassName(tree, RawButton); const className = getRenderedClassName(tree, RawButton);
expect(className).toContain(theme.flat); expect(className).toContain(theme.flat);
expect(className).toContain(theme.accent); expect(className).toContain(theme.accent);
}); });
it('renders mini button with mini style', function () { it('renders mini button with mini style', () => {
const tree = TestUtils.renderIntoDocument(<Button floating mini theme={theme} />); const tree = TestUtils.renderIntoDocument(<Button floating mini theme={theme} />);
const className = getRenderedClassName(tree, RawButton); const className = getRenderedClassName(tree, RawButton);
expect(className).toContain(theme.floating); expect(className).toContain(theme.floating);
@ -34,7 +35,7 @@ describe('Button', function () {
expect(className).toContain(theme.mini); expect(className).toContain(theme.mini);
}); });
it('renders mini accented button with both styles', function () { it('renders mini accented button with both styles', () => {
const tree = TestUtils.renderIntoDocument(<Button accent mini theme={theme} />); const tree = TestUtils.renderIntoDocument(<Button accent mini theme={theme} />);
const className = getRenderedClassName(tree, RawButton); const className = getRenderedClassName(tree, RawButton);
expect(className).toContain(theme.flat); expect(className).toContain(theme.flat);

View File

@ -1,14 +1,14 @@
import { BUTTON } from '../identifiers.js';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { buttonFactory } from './Button.js'; import { BUTTON } from '../identifiers';
import { browseButtonFactory } from './BrowseButton.js'; import { buttonFactory } from './Button';
import { iconButtonFactory } from './IconButton.js'; import { browseButtonFactory } from './BrowseButton';
import FontIcon from '../font_icon/FontIcon.js'; import { iconButtonFactory } from './IconButton';
import { FontIcon } from '../font_icon/FontIcon';
import themedRippleFactory from '../ripple'; import themedRippleFactory from '../ripple';
import theme from './theme.css'; import theme from './theme.css';
const Button = buttonFactory(themedRippleFactory({ centered: false }), FontIcon); const Button = buttonFactory(themedRippleFactory({ centered: false }), FontIcon);
const IconButton = iconButtonFactory(themedRippleFactory({centered: true}), FontIcon); const IconButton = iconButtonFactory(themedRippleFactory({ centered: true }), FontIcon);
const BrowseButton = browseButtonFactory(themedRippleFactory({ centered: false }), FontIcon); const BrowseButton = browseButtonFactory(themedRippleFactory({ centered: false }), FontIcon);
const ThemedButton = themr(BUTTON, theme)(Button); const ThemedButton = themr(BUTTON, theme)(Button);
const ThemedIconButton = themr(BUTTON, theme)(IconButton); const ThemedIconButton = themr(BUTTON, theme)(IconButton);

View File

@ -1,28 +1,28 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import classnames from 'classnames'; import classnames from 'classnames';
import { CARD } from '../identifiers.js'; import { CARD } from '../identifiers';
const Card = ({children, className, raised, theme, ...other}) => { const Card = ({ children, className, raised, theme, ...other }) => {
const classes = classnames(theme.card, { const classes = classnames(theme.card, {
[theme.raised]: raised [theme.raised]: raised,
}, className); }, className);
return ( return (
<div data-react-toolbox='card' className={classes} {...other}> <div data-react-toolbox="card" className={classes} {...other}>
{children} {children}
</div> </div>
); );
}; };
Card.propTypes = { Card.propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
raised: PropTypes.bool, raised: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
card: PropTypes.string, card: PropTypes.string,
raised: PropTypes.string raised: PropTypes.string,
}) }),
}; };
export default themr(CARD)(Card); export default themr(CARD)(Card);

View File

@ -1,7 +1,7 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import classnames from 'classnames'; import classnames from 'classnames';
import { CARD } from '../identifiers.js'; import { CARD } from '../identifiers';
const CardActions = ({ children, className, theme, ...other }) => ( const CardActions = ({ children, className, theme, ...other }) => (
<div className={classnames(theme.cardActions, className)} {...other}> <div className={classnames(theme.cardActions, className)} {...other}>
@ -10,11 +10,11 @@ const CardActions = ({ children, className, theme, ...other }) => (
); );
CardActions.propTypes = { CardActions.propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
theme: PropTypes.shape({ theme: PropTypes.shape({
cardActions: PropTypes.string cardActions: PropTypes.string,
}) }),
}; };
export default themr(CARD)(CardActions); export default themr(CARD)(CardActions);

View File

@ -1,20 +1,29 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import classnames from 'classnames'; import classnames from 'classnames';
import { CARD } from '../identifiers.js'; import { CARD } from '../identifiers';
const CardMedia = ({ aspectRatio, children, className, color, contentOverlay, image, theme, ...other }) => { const CardMedia = ({
aspectRatio,
children,
className,
color,
contentOverlay,
image,
theme,
...other
}) => {
const classes = classnames(theme.cardMedia, { const classes = classnames(theme.cardMedia, {
[theme[aspectRatio]]: aspectRatio [theme[aspectRatio]]: aspectRatio,
}, className); }, className);
const innerClasses = classnames(theme.content, { const innerClasses = classnames(theme.content, {
[theme.contentOverlay]: contentOverlay [theme.contentOverlay]: contentOverlay,
}); });
const bgStyle = { const bgStyle = {
backgroundColor: color ? color : undefined, backgroundColor: color || undefined,
backgroundImage: typeof image === 'string' ? `url('${image}')` : undefined backgroundImage: typeof image === 'string' ? `url('${image}')` : undefined,
}; };
return ( return (
@ -27,22 +36,22 @@ const CardMedia = ({ aspectRatio, children, className, color, contentOverlay, im
}; };
CardMedia.propTypes = { CardMedia.propTypes = {
aspectRatio: PropTypes.oneOf([ 'wide', 'square' ]), aspectRatio: PropTypes.oneOf(['wide', 'square']),
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
color: PropTypes.string, color: PropTypes.string,
contentOverlay: PropTypes.bool, contentOverlay: PropTypes.bool,
image: PropTypes.oneOfType([ image: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
theme: PropTypes.shape({ theme: PropTypes.shape({
cardMedia: PropTypes.string, cardMedia: PropTypes.string,
content: PropTypes.string, content: PropTypes.string,
contentOverlay: PropTypes.string, contentOverlay: PropTypes.string,
square: PropTypes.string, square: PropTypes.string,
wide: PropTypes.string wide: PropTypes.string,
}) }),
}; };
export default themr(CARD)(CardMedia); export default themr(CARD)(CardMedia);

View File

@ -1,7 +1,7 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import classnames from 'classnames'; import classnames from 'classnames';
import { CARD } from '../identifiers.js'; import { CARD } from '../identifiers';
const CardText = ({ children, className, theme, ...other }) => ( const CardText = ({ children, className, theme, ...other }) => (
<div className={classnames(theme.cardText, className)} {...other}> <div className={classnames(theme.cardText, className)} {...other}>
@ -10,11 +10,11 @@ const CardText = ({ children, className, theme, ...other }) => (
); );
CardText.propTypes = { CardText.propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
theme: PropTypes.shape({ theme: PropTypes.shape({
cardText: PropTypes.string cardText: PropTypes.string,
}) }),
}; };
export default themr(CARD)(CardText); export default themr(CARD)(CardText);

View File

@ -1,14 +1,14 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { CARD } from '../identifiers.js'; import { CARD } from '../identifiers';
import InjectAvatar from '../avatar/Avatar.js'; import InjectAvatar from '../avatar/Avatar';
const factory = (Avatar) => { const factory = (Avatar) => {
const CardTitle = ({avatar, children, className, subtitle, theme, title, ...other}) => { const CardTitle = ({ avatar, children, className, subtitle, theme, title, ...other }) => {
const classes = classnames(theme.cardTitle, { const classes = classnames(theme.cardTitle, {
[theme.small]: avatar, [theme.small]: avatar,
[theme.large]: !avatar [theme.large]: !avatar,
}, className); }, className);
return ( return (
@ -29,28 +29,28 @@ const factory = (Avatar) => {
CardTitle.propTypes = { CardTitle.propTypes = {
avatar: PropTypes.oneOfType([ avatar: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element, PropTypes.element,
PropTypes.array PropTypes.array,
]), ]),
className: PropTypes.string, className: PropTypes.string,
subtitle: PropTypes.oneOfType([ subtitle: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
theme: PropTypes.shape({ theme: PropTypes.shape({
large: PropTypes.string, large: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
small: PropTypes.string, small: PropTypes.string,
subtitle: PropTypes.string subtitle: PropTypes.string,
}), }),
title: PropTypes.oneOfType([ title: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]) ]),
}; };
return CardTitle; return CardTitle;

View File

@ -1,11 +1,11 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { CARD } from '../identifiers.js'; import { CARD } from '../identifiers';
import { Card } from './Card.js'; import { Card } from './Card';
import { CardActions } from './CardActions.js'; import { CardActions } from './CardActions';
import { CardMedia } from './CardMedia.js'; import { CardMedia } from './CardMedia';
import { CardText } from './CardText.js'; import { CardText } from './CardText';
import { cardTitleFactory } from './CardTitle.js'; import { cardTitleFactory } from './CardTitle';
import Avatar from '../avatar'; import { Avatar } from '../avatar';
import theme from './theme.css'; import theme from './theme.css';
const CardTitle = cardTitleFactory(Avatar); const CardTitle = cardTitleFactory(Avatar);

View File

@ -1,10 +1,11 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import styleShape from 'react-style-proptype';
const factory = (ripple) => { const factory = (ripple) => {
const Check = ({checked, children, onMouseDown, theme, style}) => ( const Check = ({ checked, children, onMouseDown, theme, style }) => (
<div <div
data-react-toolbox='check' data-react-toolbox="check"
className={classnames(theme.check, { [theme.checked]: checked })} className={classnames(theme.check, { [theme.checked]: checked })}
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
style={style} style={style}
@ -15,13 +16,13 @@ const factory = (ripple) => {
Check.propTypes = { Check.propTypes = {
checked: PropTypes.bool, checked: PropTypes.bool,
children: PropTypes.any, children: PropTypes.node,
onMouseDown: PropTypes.func, onMouseDown: PropTypes.func,
style: PropTypes.object, style: styleShape,
theme: PropTypes.shape({ theme: PropTypes.shape({
check: PropTypes.string, check: PropTypes.string,
checked: PropTypes.string checked: PropTypes.string,
}) }),
}; };
return ripple(Check); return ripple(Check);

View File

@ -1,9 +1,10 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import styleShape from 'react-style-proptype';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { CHECKBOX } from '../identifiers.js'; import { CHECKBOX } from '../identifiers';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
import checkFactory from './Check.js'; import checkFactory from './Check';
const factory = (Check) => { const factory = (Check) => {
class Checkbox extends Component { class Checkbox extends Component {
@ -14,25 +15,25 @@ const factory = (Check) => {
disabled: PropTypes.bool, disabled: PropTypes.bool,
label: PropTypes.oneOfType([ label: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.node PropTypes.node,
]), ]),
name: PropTypes.string, name: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
onMouseEnter: PropTypes.func, onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func, onMouseLeave: PropTypes.func,
style: PropTypes.object, style: styleShape,
theme: PropTypes.shape({ theme: PropTypes.shape({
disabled: PropTypes.string, disabled: PropTypes.string,
field: PropTypes.string, field: PropTypes.string,
input: PropTypes.string, input: PropTypes.string,
ripple: PropTypes.string ripple: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
checked: false, checked: false,
className: '', className: '',
disabled: false disabled: false,
}; };
handleToggle = (event) => { handleToggle = (event) => {
@ -42,24 +43,29 @@ const factory = (Check) => {
} }
}; };
blur () { blur() {
this.inputNode && this.inputNode.blur(); if (this.inputNode) {
this.inputNode.blur();
}
} }
focus () { focus() {
this.inputNode && this.inputNode.focus(); if (this.inputNode) {
this.inputNode.focus();
}
} }
render () { render() {
const { checked, children, disabled, label, name, style, onChange, // eslint-disable-line const { checked, children, disabled, label, name, style, onChange, // eslint-disable-line
onMouseEnter, onMouseLeave, theme, ...others } = this.props; onMouseEnter, onMouseLeave, theme, ...others } = this.props;
const className = classnames(theme.field, { const className = classnames(theme.field, {
[theme.disabled]: this.props.disabled [theme.disabled]: this.props.disabled,
}, this.props.className); }, this.props.className);
return ( return (
<label <label
data-react-toolbox='checkbox' data-react-toolbox="checkbox"
htmlFor={name}
className={className} className={className}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
@ -72,8 +78,8 @@ const factory = (Check) => {
name={name} name={name}
onChange={() => {}} onChange={() => {}}
onClick={this.handleToggle} onClick={this.handleToggle}
ref={node => { this.inputNode = node; }} ref={(node) => { this.inputNode = node; }}
type='checkbox' type="checkbox"
/> />
<Check <Check
checked={checked} checked={checked}
@ -82,7 +88,7 @@ const factory = (Check) => {
style={style} style={style}
theme={theme} theme={theme}
/> />
{label ? <span data-react-toolbox='label' className={theme.text}>{label}</span> : null} {label ? <span data-react-toolbox="label" className={theme.text}>{label}</span> : null}
{children} {children}
</label> </label>
); );
@ -92,7 +98,7 @@ const factory = (Check) => {
return Checkbox; return Checkbox;
}; };
const Check = checkFactory(rippleFactory({ centered: true, spread: 2.6})); const Check = checkFactory(rippleFactory({ centered: true, spread: 2.6 }));
const Checkbox = factory(Check); const Checkbox = factory(Check);
export default themr(CHECKBOX)(Checkbox); export default themr(CHECKBOX)(Checkbox);
export { factory as checkboxFactory }; export { factory as checkboxFactory };

View File

@ -1,11 +1,11 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { CHECKBOX } from '../identifiers.js'; import { CHECKBOX } from '../identifiers';
import themedRippleFactory from '../ripple'; import themedRippleFactory from '../ripple';
import { checkboxFactory } from './Checkbox.js'; import { checkboxFactory } from './Checkbox';
import checkFactory from './Check.js'; import checkFactory from './Check';
import theme from './theme.css'; import theme from './theme.css';
const ThemedCheck = checkFactory(themedRippleFactory({ centered: true, spread: 2.6})); const ThemedCheck = checkFactory(themedRippleFactory({ centered: true, spread: 2.6 }));
const ThemedCheckbox = themr(CHECKBOX, theme)(checkboxFactory(ThemedCheck)); const ThemedCheckbox = themr(CHECKBOX, theme)(checkboxFactory(ThemedCheck));
export default ThemedCheckbox; export default ThemedCheckbox;

View File

@ -1,11 +1,11 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { CHIP } from '../identifiers.js'; import { CHIP } from '../identifiers';
import InjectAvatar from '../avatar/Avatar.js'; import InjectAvatar from '../avatar/Avatar';
const factory = (Avatar) => { const factory = (Avatar) => {
const Chip = ({children, className, deletable, onDeleteClick, theme, ...other}) => { const Chip = ({ children, className, deletable, onDeleteClick, theme, ...other }) => {
let hasAvatar = false; let hasAvatar = false;
if (React.Children.count(children)) { if (React.Children.count(children)) {
const flatChildren = React.Children.toArray(children); const flatChildren = React.Children.toArray(children);
@ -14,12 +14,12 @@ const factory = (Avatar) => {
} }
const classes = classnames(theme.chip, { const classes = classnames(theme.chip, {
[theme.deletable]: !!deletable, [theme.deletable]: !!deletable,
[theme.avatar]: !!hasAvatar [theme.avatar]: !!hasAvatar,
}, className); }, className);
return ( return (
<div data-react-toolbox='chip' className={classes} {...other}> <div data-react-toolbox="chip" className={classes} {...other}>
{typeof children === 'string' ? <span>{children}</span> : children} {typeof children === 'string' ? <span>{children}</span> : children}
{ {
deletable ? ( deletable ? (
@ -45,13 +45,13 @@ const factory = (Avatar) => {
deletable: PropTypes.string, deletable: PropTypes.string,
delete: PropTypes.string, delete: PropTypes.string,
deleteIcon: PropTypes.string, deleteIcon: PropTypes.string,
deleteX: PropTypes.string deleteX: PropTypes.string,
}) }),
}; };
Chip.defaultProps = { Chip.defaultProps = {
className: '', className: '',
deletable: false deletable: false,
}; };
return Chip; return Chip;

View File

@ -1,3 +1,4 @@
/* eslint-disable */
import expect from 'expect'; import expect from 'expect';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
@ -7,30 +8,30 @@ import { CHIP } from '../../identifiers.js';
import { chipFactory } from '../Chip'; import { chipFactory } from '../Chip';
import { tooltipFactory } from '../../tooltip'; import { tooltipFactory } from '../../tooltip';
const Avatar = ({title}) => <span>{title}</span>; // eslint-disable-line react/prop-types const Avatar = ({ title }) => <span>{title}</span>; // eslint-disable-line react/prop-types
const Chip = themr(CHIP)(chipFactory(Avatar)); const Chip = themr(CHIP)(chipFactory(Avatar));
describe('Chip', function () { describe('Chip', () => {
describe('with avatar', function () { describe('with avatar', () => {
it('adds the avatar class to the element', function () { it('adds the avatar class to the element', () => {
const tree = ReactTestUtils.renderIntoDocument( const tree = ReactTestUtils.renderIntoDocument(
<Chip theme={{avatar: 'avatar-class'}}> <Chip theme={{ avatar: 'avatar-class' }}>
<Avatar title='Test'/> <Avatar title="Test" />
<span>Test</span> <span>Test</span>
</Chip> </Chip>,
); );
const chip = ReactTestUtils.findRenderedComponentWithType(tree, Chip); const chip = ReactTestUtils.findRenderedComponentWithType(tree, Chip);
const chipNode = ReactDOM.findDOMNode(chip); const chipNode = ReactDOM.findDOMNode(chip);
expect(chipNode.className).toMatch(/\bavatar-class\b/); expect(chipNode.className).toMatch(/\bavatar-class\b/);
}); });
it('works with non-flat children', function () { it('works with non-flat children', () => {
const TooltippedChip = tooltipFactory()(Chip); const TooltippedChip = tooltipFactory()(Chip);
const tree = ReactTestUtils.renderIntoDocument( const tree = ReactTestUtils.renderIntoDocument(
<TooltippedChip theme={{avatar: 'avatar-class'}} tooltip='Test tooltip'> <TooltippedChip theme={{ avatar: 'avatar-class' }} tooltip="Test tooltip">
<Avatar title='Test'/> <Avatar title="Test" />
<span>Test</span> <span>Test</span>
</TooltippedChip> </TooltippedChip>,
); );
const chip = ReactTestUtils.findRenderedComponentWithType(tree, Chip); const chip = ReactTestUtils.findRenderedComponentWithType(tree, Chip);
const chipNode = ReactDOM.findDOMNode(chip); const chipNode = ReactDOM.findDOMNode(chip);
@ -38,12 +39,12 @@ describe('Chip', function () {
}); });
}); });
describe('without avatar', function () { describe('without avatar', () => {
it('does not add avatar class to the element', function () { it('does not add avatar class to the element', () => {
const tree = ReactTestUtils.renderIntoDocument( const tree = ReactTestUtils.renderIntoDocument(
<Chip theme={{avatar: 'avatar-class'}}> <Chip theme={{ avatar: 'avatar-class' }}>
<span>Test</span> <span>Test</span>
</Chip> </Chip>,
); );
const chip = ReactTestUtils.findRenderedComponentWithType(tree, Chip); const chip = ReactTestUtils.findRenderedComponentWithType(tree, Chip);
const chipNode = ReactDOM.findDOMNode(chip); const chipNode = ReactDOM.findDOMNode(chip);

View File

@ -1,7 +1,7 @@
import { CHIP } from '../identifiers.js';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { chipFactory } from './Chip.js'; import { CHIP } from '../identifiers';
import Avatar from '../avatar'; import { chipFactory } from './Chip';
import { Avatar } from '../avatar';
import theme from './theme.css'; import theme from './theme.css';
const Chip = chipFactory(Avatar); const Chip = chipFactory(Avatar);

View File

@ -1,64 +1,62 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import CssTransitionGroup from 'react-addons-css-transition-group'; import CssTransitionGroup from 'react-addons-css-transition-group';
import { range, getAnimationModule } from '../utils/utils'; import { range, getAnimationModule } from '../utils/utils';
import time from '../utils/time.js'; import time from '../utils/time';
import CalendarMonth from './CalendarMonth.js'; import CalendarMonth from './CalendarMonth';
const DIRECTION_STEPS = { left: -1, right: 1 }; const DIRECTION_STEPS = { left: -1, right: 1 };
const factory = (IconButton) => { const factory = (IconButton) => {
class Calendar extends Component { class Calendar extends Component {
static propTypes = { static propTypes = {
disabledDates: React.PropTypes.array, disabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
display: PropTypes.oneOf(['months', 'years']), display: PropTypes.oneOf(['months', 'years']),
enabledDates: React.PropTypes.array, enabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
handleSelect: PropTypes.func, handleSelect: PropTypes.func,
locale: React.PropTypes.oneOfType([ locale: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.object React.PropTypes.object,
]), ]),
maxDate: PropTypes.object, maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.object, minDate: PropTypes.instanceOf(Date),
onChange: PropTypes.func, onChange: PropTypes.func,
selectedDate: PropTypes.object, selectedDate: PropTypes.instanceOf(Date),
sundayFirstDayOfWeek: React.PropTypes.bool, sundayFirstDayOfWeek: React.PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
calendar: PropTypes.string, calendar: PropTypes.string,
next: PropTypes.string, next: PropTypes.string,
prev: PropTypes.string, prev: PropTypes.string,
years: PropTypes.string years: PropTypes.string,
}), }),
viewDate: PropTypes.object
}; };
static defaultProps = { static defaultProps = {
display: 'months', display: 'months',
selectedDate: new Date() selectedDate: new Date(),
}; };
state = { state = {
viewDate: this.props.selectedDate viewDate: this.props.selectedDate,
}; };
componentWillMount () { componentWillMount() {
document.body.addEventListener('keydown', this.handleKeys); document.body.addEventListener('keydown', this.handleKeys);
} }
componentDidUpdate () { componentDidUpdate() {
if (this.refs.activeYear) { if (this.activeYearNode) {
this.scrollToActive(); this.scrollToActive();
} }
} }
componentWillUnmount () { componentWillUnmount() {
document.body.removeEventListener('keydown', this.handleKeys); document.body.removeEventListener('keydown', this.handleKeys);
} }
scrollToActive () { scrollToActive() {
this.refs.years.scrollTop = this.refs.activeYear.offsetTop const offset = (this.yearsNode.offsetHeight / 2) + (this.activeYearNode.offsetHeight / 2);
- this.refs.years.offsetHeight / 2 this.yearsNode.scrollTop = this.activeYearNode.offsetTop - offset;
+ this.refs.activeYear.offsetHeight / 2;
} }
handleDayClick = (day) => { handleDayClick = (day) => {
@ -66,16 +64,18 @@ const factory = (IconButton) => {
}; };
handleYearClick = (event) => { handleYearClick = (event) => {
const year = parseInt(event.currentTarget.id); const year = parseInt(event.currentTarget.id, 10);
const viewDate = time.setYear(this.props.selectedDate, year); const viewDate = time.setYear(this.props.selectedDate, year);
this.setState({viewDate}); this.setState({ viewDate });
this.props.onChange(viewDate, false); this.props.onChange(viewDate, false);
}; };
handleKeys = (e) => { handleKeys = (e) => {
const { selectedDate } = this.props; const { selectedDate } = this.props;
if (e.which === 37 || e.which === 38 || e.which === 39 || e.which === 40 || e.which === 13) e.preventDefault(); if (e.which === 37 || e.which === 38 || e.which === 39 || e.which === 40 || e.which === 13) {
e.preventDefault();
}
switch (e.which) { switch (e.which) {
case 13: this.props.handleSelect(); break; // enter case 13: this.props.handleSelect(); break; // enter
@ -96,35 +96,44 @@ const factory = (IconButton) => {
const direction = event.currentTarget.id; const direction = event.currentTarget.id;
this.setState({ this.setState({
direction, direction,
viewDate: time.addMonths(this.state.viewDate, DIRECTION_STEPS[direction]) viewDate: time.addMonths(this.state.viewDate, DIRECTION_STEPS[direction]),
}); });
}; };
renderYears () { renderYears() {
return ( return (
<ul data-react-toolbox='years' ref="years" className={this.props.theme.years}> <ul
data-react-toolbox="years"
className={this.props.theme.years}
ref={(node) => { this.yearsNode = node; }}
>
{range(1900, 2100).map(year => ( {range(1900, 2100).map(year => (
<li <li
children={year}
className={year === this.state.viewDate.getFullYear() ? this.props.theme.active : ''} className={year === this.state.viewDate.getFullYear() ? this.props.theme.active : ''}
id={year} id={year}
key={year} key={year}
onClick={this.handleYearClick} onClick={this.handleYearClick}
ref={year === this.state.viewDate.getFullYear() ? 'activeYear' : undefined} ref={(node) => {
/> if (year === this.state.viewDate.getFullYear()) {
this.activeYearNode = node;
}
}}
>
{year}
</li>
))} ))}
</ul> </ul>
); );
} }
renderMonths () { renderMonths() {
const { theme } = this.props; const { theme } = this.props;
const animation = this.state.direction === 'left' ? 'slideLeft' : 'slideRight'; const animation = this.state.direction === 'left' ? 'slideLeft' : 'slideRight';
const animationModule = getAnimationModule(animation, theme); const animationModule = getAnimationModule(animation, theme);
return ( return (
<div data-react-toolbox='calendar'> <div data-react-toolbox="calendar">
<IconButton id='left' className={theme.prev} icon='chevron_left' onClick={this.changeViewMonth} /> <IconButton id="left" className={theme.prev} icon="chevron_left" onClick={this.changeViewMonth} />
<IconButton id='right' className={theme.next} icon='chevron_right' onClick={this.changeViewMonth} /> <IconButton id="right" className={theme.next} icon="chevron_right" onClick={this.changeViewMonth} />
<CssTransitionGroup <CssTransitionGroup
transitionName={animationModule} transitionName={animationModule}
transitionEnterTimeout={350} transitionEnterTimeout={350}
@ -148,7 +157,7 @@ const factory = (IconButton) => {
); );
} }
render () { render() {
return ( return (
<div className={this.props.theme.calendar}> <div className={this.props.theme.calendar}>
{this.props.display === 'months' ? this.renderMonths() : this.renderYears()} {this.props.display === 'months' ? this.renderMonths() : this.renderYears()}

View File

@ -1,33 +1,34 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import time from '../utils/time.js'; import time from '../utils/time';
class Day extends Component { class Day extends Component {
static propTypes = { static propTypes = {
day: PropTypes.number, day: PropTypes.number,
disabled: PropTypes.bool, disabled: PropTypes.bool,
onClick: PropTypes.func, onClick: PropTypes.func,
selectedDate: PropTypes.object, selectedDate: PropTypes.instanceOf(Date),
sundayFirstDayOfWeek: PropTypes.bool, sundayFirstDayOfWeek: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
day: PropTypes.string, day: PropTypes.string,
disabled: PropTypes.string disabled: PropTypes.string,
}), }),
viewDate: PropTypes.object viewDate: PropTypes.instanceOf(Date),
}; };
dayStyle () { dayStyle() {
if (this.props.day === 1) { if (this.props.day === 1) {
const e = (this.props.sundayFirstDayOfWeek) ? 0 : 1; const e = (this.props.sundayFirstDayOfWeek) ? 0 : 1;
const firstDay = time.getFirstWeekDay(this.props.viewDate) - e; const firstDay = time.getFirstWeekDay(this.props.viewDate) - e;
return { return {
marginLeft: `${ (firstDay >= 0 ? firstDay : 6) * 100 / 7 }%` marginLeft: `${(firstDay >= 0 ? firstDay : 6) * (100 / 7)}%`,
}; };
} }
return undefined;
} }
isSelected () { isSelected() {
const sameYear = this.props.viewDate.getFullYear() === this.props.selectedDate.getFullYear(); const sameYear = this.props.viewDate.getFullYear() === this.props.selectedDate.getFullYear();
const sameMonth = this.props.viewDate.getMonth() === this.props.selectedDate.getMonth(); const sameMonth = this.props.viewDate.getMonth() === this.props.selectedDate.getMonth();
const sameDay = this.props.day === this.props.selectedDate.getDate(); const sameDay = this.props.day === this.props.selectedDate.getDate();
@ -40,14 +41,14 @@ class Day extends Component {
} }
}; };
render () { render() {
const className = classnames(this.props.theme.day, { const className = classnames(this.props.theme.day, {
[this.props.theme.active]: this.isSelected(), [this.props.theme.active]: this.isSelected(),
[this.props.theme.disabled]: this.props.disabled [this.props.theme.disabled]: this.props.disabled,
}); });
return ( return (
<div data-react-toolbox='day' className={className} style={this.dayStyle()}> <div data-react-toolbox="day" className={className} style={this.dayStyle()}>
<span onClick={this.handleClick}> <span onClick={this.handleClick}>
{this.props.day} {this.props.day}
</span> </span>

View File

@ -1,41 +1,41 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { range } from '../utils/utils'; import { range } from '../utils/utils';
import time from '../utils/time.js'; import time from '../utils/time';
import CalendarDay from './CalendarDay.js'; import CalendarDay from './CalendarDay';
class Month extends Component { class Month extends Component {
static propTypes = { static propTypes = {
disabledDates: React.PropTypes.array, disabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
enabledDates: React.PropTypes.array, enabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
locale: React.PropTypes.oneOfType([ locale: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.object React.PropTypes.object,
]), ]),
maxDate: PropTypes.object, maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.object, minDate: PropTypes.instanceOf(Date),
onDayClick: PropTypes.func, onDayClick: PropTypes.func,
selectedDate: PropTypes.object, selectedDate: PropTypes.instanceOf(Date),
sundayFirstDayOfWeek: React.PropTypes.bool, sundayFirstDayOfWeek: React.PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
days: PropTypes.string, days: PropTypes.string,
month: PropTypes.string, month: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
week: PropTypes.string week: PropTypes.string,
}), }),
viewDate: PropTypes.object viewDate: PropTypes.instanceOf(Date),
}; };
static defaultProps = { static defaultProps = {
disabledDates: [], disabledDates: [],
enabledDates: [] enabledDates: [],
}; };
handleDayClick = (day) => { handleDayClick = (day) => {
if (this.props.onDayClick) this.props.onDayClick(day); if (this.props.onDayClick) this.props.onDayClick(day);
}; };
isDayDisabled (date) { isDayDisabled(date) {
const {minDate, maxDate, enabledDates, disabledDates} = this.props; const { minDate, maxDate, enabledDates, disabledDates } = this.props;
const compareDate = compDate => date.getTime() === compDate.getTime(); const compareDate = compDate => date.getTime() === compDate.getTime();
const dateInDisabled = disabledDates.filter(compareDate).length > 0; const dateInDisabled = disabledDates.filter(compareDate).length > 0;
const dateInEnabled = enabledDates.filter(compareDate).length > 0; const dateInEnabled = enabledDates.filter(compareDate).length > 0;
@ -44,14 +44,14 @@ class Month extends Component {
|| dateInDisabled; || dateInDisabled;
} }
renderWeeks () { renderWeeks() {
const days = range(0, 7).map(d => time.getDayOfWeekLetter(d, this.props.locale)); const days = range(0, 7).map(d => time.getDayOfWeekLetter(d, this.props.locale));
const source = (this.props.sundayFirstDayOfWeek) ? days : [...days.slice(1), days[0]]; const source = (this.props.sundayFirstDayOfWeek) ? days : [...days.slice(1), days[0]];
return source.map((d, i) => (<span key={i}>{d}</span>)); return source.map((day, i) => (<span key={i}>{day}</span>)); // eslint-disable-line
} }
renderDays () { renderDays() {
return range(1, time.getDaysInMonth(this.props.viewDate) + 1).map(i => { return range(1, time.getDaysInMonth(this.props.viewDate) + 1).map((i) => {
const date = new Date(this.props.viewDate.getFullYear(), this.props.viewDate.getMonth(), i); const date = new Date(this.props.viewDate.getFullYear(), this.props.viewDate.getMonth(), i);
return ( return (
<CalendarDay <CalendarDay
@ -68,11 +68,13 @@ class Month extends Component {
}); });
} }
render () { render() {
const fullMonth = time.getFullMonth(this.props.viewDate, this.props.locale);
const fullYear = this.props.viewDate.getFullYear();
return ( return (
<div data-react-toolbox='month' className={this.props.theme.month}> <div data-react-toolbox="month" className={this.props.theme.month}>
<span className={this.props.theme.title}> <span className={this.props.theme.title}>
{time.getFullMonth(this.props.viewDate, this.props.locale)} {this.props.viewDate.getFullYear()} {fullMonth} {fullYear}
</span> </span>
<div className={this.props.theme.week}>{this.renderWeeks()}</div> <div className={this.props.theme.week}>{this.renderWeeks()}</div>
<div className={this.props.theme.days}>{this.renderDays()}</div> <div className={this.props.theme.days}>{this.renderDays()}</div>

View File

@ -1,15 +1,15 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { DATE_PICKER } from '../identifiers.js'; import { DATE_PICKER } from '../identifiers';
import events from '../utils/events.js'; import events from '../utils/events';
import time from '../utils/time.js'; import time from '../utils/time';
import InjectIconButton from '../button/IconButton.js'; import InjectIconButton from '../button/IconButton';
import InjectInput from '../input/Input.js'; import InjectInput from '../input/Input';
import InjectDialog from '../dialog/Dialog.js'; import InjectDialog from '../dialog/Dialog';
import calendarFactory from './Calendar.js'; import calendarFactory from './Calendar';
import datePickerDialogFactory from './DatePickerDialog.js'; import datePickerDialogFactory from './DatePickerDialog';
const factory = (Input, DatePickerDialog) => { const factory = (Input, DatePickerDialog) => {
class DatePicker extends Component { class DatePicker extends Component {
@ -18,22 +18,22 @@ const factory = (Input, DatePickerDialog) => {
autoOk: PropTypes.bool, autoOk: PropTypes.bool,
cancelLabel: PropTypes.string, cancelLabel: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
disabledDates: React.PropTypes.array, disabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
enabledDates: React.PropTypes.array, enabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
error: PropTypes.string, error: PropTypes.string,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
inputClassName: PropTypes.string, inputClassName: PropTypes.string,
inputFormat: PropTypes.func, inputFormat: PropTypes.func,
label: PropTypes.string, label: PropTypes.string,
locale: React.PropTypes.oneOfType([ locale: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.object React.PropTypes.object,
]), ]),
maxDate: PropTypes.object, maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.object, minDate: PropTypes.instanceOf(Date),
name: PropTypes.string, name: PropTypes.string,
okLabel: PropTypes.string, okLabel: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
@ -46,32 +46,32 @@ const factory = (Input, DatePickerDialog) => {
sundayFirstDayOfWeek: React.PropTypes.bool, sundayFirstDayOfWeek: React.PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
container: PropTypes.string, container: PropTypes.string,
input: PropTypes.string input: PropTypes.string,
}), }),
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([
PropTypes.instanceOf(Date), PropTypes.instanceOf(Date),
PropTypes.string PropTypes.string,
]) ]),
}; };
static defaultProps = { static defaultProps = {
active: false, active: false,
locale: 'en', locale: 'en',
sundayFirstDayOfWeek: false sundayFirstDayOfWeek: false,
}; };
state = { state = {
active: this.props.active active: this.props.active,
}; };
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.active !== this.props.active && this.state.active !== nextProps.active) { if (nextProps.active !== this.props.active && this.state.active !== nextProps.active) {
this.setState({ active: nextProps.active }); this.setState({ active: nextProps.active });
} }
} }
handleDismiss = () => { handleDismiss = () => {
this.setState({active: false}); this.setState({ active: false });
if (this.props.onDismiss) { if (this.props.onDismiss) {
this.props.onDismiss(); this.props.onDismiss();
} }
@ -79,34 +79,34 @@ const factory = (Input, DatePickerDialog) => {
handleInputFocus = (event) => { handleInputFocus = (event) => {
events.pauseEvent(event); events.pauseEvent(event);
this.setState({active: true}); this.setState({ active: true });
}; };
handleInputBlur = (event) => { handleInputBlur = (event) => {
events.pauseEvent(event); events.pauseEvent(event);
this.setState({active: false}); this.setState({ active: false });
}; };
handleInputClick = (event) => { handleInputClick = (event) => {
events.pauseEvent(event); events.pauseEvent(event);
this.setState({active: true}); this.setState({ active: true });
if (this.props.onClick) this.props.onClick(event); if (this.props.onClick) this.props.onClick(event);
}; };
handleInputKeyPress = (event) => { handleInputKeyPress = (event) => {
if (event.charCode === 13) { if (event.charCode === 13) {
events.pauseEvent(event); events.pauseEvent(event);
this.setState({active: true}); this.setState({ active: true });
} }
if (this.props.onKeyPress) this.props.onKeyPress(event); if (this.props.onKeyPress) this.props.onKeyPress(event);
}; };
handleSelect = (value, event) => { handleSelect = (value, event) => {
if (this.props.onChange) this.props.onChange(value, event); if (this.props.onChange) this.props.onChange(value, event);
this.setState({active: false}); this.setState({ active: false });
}; };
render () { render() {
const { active, onDismiss,// eslint-disable-line const { active, onDismiss,// eslint-disable-line
autoOk, cancelLabel, enabledDates, disabledDates, inputClassName, inputFormat, autoOk, cancelLabel, enabledDates, disabledDates, inputClassName, inputFormat,
locale, maxDate, minDate, okLabel, onEscKeyDown, onOverlayClick, readonly, locale, maxDate, minDate, okLabel, onEscKeyDown, onOverlayClick, readonly,
@ -116,10 +116,10 @@ const factory = (Input, DatePickerDialog) => {
const formattedDate = date === undefined ? '' : finalInputFormat(value, locale); const formattedDate = date === undefined ? '' : finalInputFormat(value, locale);
return ( return (
<div data-react-toolbox='date-picker' className={this.props.theme.container}> <div data-react-toolbox="date-picker" className={this.props.theme.container}>
<Input <Input
{...others} {...others}
className={classnames(this.props.theme.input, {[inputClassName]: inputClassName })} className={classnames(this.props.theme.input, { [inputClassName]: inputClassName })}
disabled={readonly} disabled={readonly}
error={this.props.error} error={this.props.error}
icon={this.props.icon} icon={this.props.icon}
@ -129,7 +129,7 @@ const factory = (Input, DatePickerDialog) => {
onKeyPress={this.handleInputKeyPress} onKeyPress={this.handleInputKeyPress}
onClick={this.handleInputClick} onClick={this.handleInputClick}
readOnly readOnly
type='text' type="text"
value={formattedDate} value={formattedDate}
/> />
<DatePickerDialog <DatePickerDialog
@ -166,7 +166,7 @@ const DatePicker = factory(InjectInput, DatePickerDialog);
export default themr(DATE_PICKER)(DatePicker); export default themr(DATE_PICKER)(DatePicker);
export { export {
DatePickerDialog as DatePickerDialog, DatePickerDialog,
factory as datePickerFactory factory as datePickerFactory,
}; };
export { DatePicker }; export { DatePicker };

View File

@ -1,6 +1,6 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import time from '../utils/time.js'; import time from '../utils/time';
const factory = (Dialog, Calendar) => { const factory = (Dialog, Calendar) => {
class CalendarDialog extends Component { class CalendarDialog extends Component {
@ -9,14 +9,14 @@ const factory = (Dialog, Calendar) => {
autoOk: PropTypes.bool, autoOk: PropTypes.bool,
cancelLabel: PropTypes.string, cancelLabel: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
disabledDates: PropTypes.array, disabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
enabledDates: PropTypes.array, enabledDates: React.PropTypes.arrayOf(PropTypes.instanceOf(Date)),
locale: React.PropTypes.oneOfType([ locale: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.object React.PropTypes.object,
]), ]),
maxDate: PropTypes.object, maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.object, minDate: PropTypes.instanceOf(Date),
name: PropTypes.string, name: PropTypes.string,
okLabel: PropTypes.string, okLabel: PropTypes.string,
onDismiss: PropTypes.func, onDismiss: PropTypes.func,
@ -32,9 +32,9 @@ const factory = (Dialog, Calendar) => {
header: PropTypes.string, header: PropTypes.string,
monthsDisplay: PropTypes.string, monthsDisplay: PropTypes.string,
year: PropTypes.string, year: PropTypes.string,
yearsDisplay: PropTypes.string yearsDisplay: PropTypes.string,
}), }),
value: PropTypes.object value: PropTypes.instanceOf(Date),
}; };
static defaultProps = { static defaultProps = {
@ -42,24 +42,24 @@ const factory = (Dialog, Calendar) => {
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
className: '', className: '',
okLabel: 'Ok', okLabel: 'Ok',
value: new Date() value: new Date(),
}; };
state = { state = {
display: 'months', display: 'months',
date: this.props.value date: this.props.value,
}; };
componentWillMount () { componentWillMount() {
this.updateStateDate(this.props.value); this.updateStateDate(this.props.value);
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
this.updateStateDate(nextProps.value); this.updateStateDate(nextProps.value);
} }
handleNewDate = (value, dayClick) => { handleNewDate = (value, dayClick) => {
const state = {display: 'months', date: value}; const state = { display: 'months', date: value };
if (time.dateOutOfRange(value, this.props.minDate, this.props.maxDate)) { if (time.dateOutOfRange(value, this.props.minDate, this.props.maxDate)) {
if (this.props.maxDate && this.props.minDate) { if (this.props.maxDate && this.props.minDate) {
state.date = time.closestDate(value, this.props.maxDate, this.props.minDate); state.date = time.closestDate(value, this.props.maxDate, this.props.minDate);
@ -87,12 +87,18 @@ const factory = (Dialog, Calendar) => {
} }
}; };
actions = [ actions = [{
{ label: this.props.cancelLabel, className: this.props.theme.button, onClick: this.props.onDismiss }, label: this.props.cancelLabel,
{ label: this.props.okLabel, className: this.props.theme.button, name: this.props.name, onClick: this.handleSelect } className: this.props.theme.button,
]; onClick: this.props.onDismiss,
}, {
label: this.props.okLabel,
className: this.props.theme.button,
name: this.props.name,
onClick: this.handleSelect,
}];
render () { render() {
const { theme } = this.props; const { theme } = this.props;
const display = `${this.state.display}Display`; const display = `${this.state.display}Display`;
const className = classnames(theme.dialog, this.props.className); const className = classnames(theme.dialog, this.props.className);
@ -111,10 +117,10 @@ const factory = (Dialog, Calendar) => {
type="custom" type="custom"
> >
<header className={headerClassName}> <header className={headerClassName}>
<span id='years' className={theme.year} onClick={this.handleSwitchDisplay}> <span id="years" className={theme.year} onClick={this.handleSwitchDisplay}>
{this.state.date.getFullYear()} {this.state.date.getFullYear()}
</span> </span>
<h3 id='months' className={theme.date} onClick={this.handleSwitchDisplay}> <h3 id="months" className={theme.date} onClick={this.handleSwitchDisplay}>
{shortDayOfWeek}, {shortMonth} {date} {shortDayOfWeek}, {shortMonth} {date}
</h3> </h3>
</header> </header>

View File

@ -1,82 +1,83 @@
/* eslint-disable */
import expect from 'expect'; import expect from 'expect';
import theme from '../theme.css'; import theme from '../theme.css';
import { DatePickerDialog } from '../DatePicker'; import { DatePickerDialog } from '../DatePicker';
import utils from '../../utils/testing'; import utils from '../../utils/testing';
describe('DatePickerDialog', function () { describe('DatePickerDialog', () => {
describe('#on mount', function () { describe('#on mount', () => {
it('passes value through to calendar if no maxDate/minDate specified', function () { it('passes value through to calendar if no maxDate/minDate specified', () => {
const value = new Date(2016, 1, 1); const value = new Date(2016, 1, 1);
const wrapper = utils.shallowRenderComponent(DatePickerDialog, {theme, value}); const wrapper = utils.shallowRenderComponent(DatePickerDialog, { theme, value });
expect(getDatePassedToCalendar(wrapper)).toBe(value); expect(getDatePassedToCalendar(wrapper)).toBe(value);
}); });
describe('when minDate but not maxDate specified', function () { describe('when minDate but not maxDate specified', () => {
const minDate = new Date(2016, 1, 2); const minDate = new Date(2016, 1, 2);
it('passes through a value after minDate', function () { it('passes through a value after minDate', () => {
const value = new Date(2016, 1, 3); const value = new Date(2016, 1, 3);
const wrapper = utils.shallowRenderComponent(DatePickerDialog, {theme, value, minDate}); const wrapper = utils.shallowRenderComponent(DatePickerDialog, { theme, value, minDate });
expect(getDatePassedToCalendar(wrapper)).toBe(value); expect(getDatePassedToCalendar(wrapper)).toBe(value);
}); });
it('sanitises a value before minDate to minDate', function () { it('sanitises a value before minDate to minDate', () => {
const wrapper = utils.shallowRenderComponent(DatePickerDialog, { const wrapper = utils.shallowRenderComponent(DatePickerDialog, {
theme, value: new Date(2016, 1, 1), minDate theme, value: new Date(2016, 1, 1), minDate,
}); });
expect(getDatePassedToCalendar(wrapper)).toBe(minDate); expect(getDatePassedToCalendar(wrapper)).toBe(minDate);
}); });
}); });
describe('when maxDate but not minDate specified', function () { describe('when maxDate but not minDate specified', () => {
const maxDate = new Date(2016, 1, 2); const maxDate = new Date(2016, 1, 2);
it('passes through a value before maxDate', function () { it('passes through a value before maxDate', () => {
const value = new Date(2016, 1, 1); const value = new Date(2016, 1, 1);
const wrapper = utils.shallowRenderComponent(DatePickerDialog, {theme, value, maxDate}); const wrapper = utils.shallowRenderComponent(DatePickerDialog, { theme, value, maxDate });
expect(getDatePassedToCalendar(wrapper)).toBe(value); expect(getDatePassedToCalendar(wrapper)).toBe(value);
}); });
it('sanitises a value after maxDate to maxDate', function () { it('sanitises a value after maxDate to maxDate', () => {
const wrapper = utils.shallowRenderComponent(DatePickerDialog, { const wrapper = utils.shallowRenderComponent(DatePickerDialog, {
theme, value: new Date(2016, 1, 3), maxDate theme, value: new Date(2016, 1, 3), maxDate,
}); });
expect(getDatePassedToCalendar(wrapper)).toBe(maxDate); expect(getDatePassedToCalendar(wrapper)).toBe(maxDate);
}); });
}); });
describe('if both minDate and maxDate are set', function () { describe('if both minDate and maxDate are set', () => {
const minDate = new Date(2016, 1, 2); const minDate = new Date(2016, 1, 2);
const maxDate = new Date(2016, 1, 4); const maxDate = new Date(2016, 1, 4);
it('sanitises value to minDate if value is before minDate', function () { it('sanitises value to minDate if value is before minDate', () => {
const wrapper = utils.shallowRenderComponent(DatePickerDialog, { const wrapper = utils.shallowRenderComponent(DatePickerDialog, {
theme, theme,
value: new Date(2016, 1, 1), value: new Date(2016, 1, 1),
minDate, minDate,
maxDate maxDate,
}); });
expect(getDatePassedToCalendar(wrapper)).toBe(minDate); expect(getDatePassedToCalendar(wrapper)).toBe(minDate);
}); });
it('sanitises value to maxDate if value is after maxDate', function () { it('sanitises value to maxDate if value is after maxDate', () => {
const wrapper = utils.shallowRenderComponent(DatePickerDialog, { const wrapper = utils.shallowRenderComponent(DatePickerDialog, {
theme, theme,
value: new Date(2016, 1, 5), value: new Date(2016, 1, 5),
minDate, minDate,
maxDate maxDate,
}); });
expect(getDatePassedToCalendar(wrapper)).toBe(maxDate); expect(getDatePassedToCalendar(wrapper)).toBe(maxDate);
}); });
it('doesn\'t sanitise when value is between maxDate/minDate', function () { it('doesn\'t sanitise when value is between maxDate/minDate', () => {
const value = new Date(2016, 1, 3); const value = new Date(2016, 1, 3);
const wrapper = utils.shallowRenderComponent(DatePickerDialog, {theme, value, minDate, maxDate}); const wrapper = utils.shallowRenderComponent(DatePickerDialog, { theme, value, minDate, maxDate });
expect(getDatePassedToCalendar(wrapper)).toBe(value); expect(getDatePassedToCalendar(wrapper)).toBe(value);
}); });
}); });
function getDatePassedToCalendar (wrapper) { function getDatePassedToCalendar(wrapper) {
return wrapper.props.children[1].props.children.props.selectedDate; return wrapper.props.children[1].props.children.props.selectedDate;
} }
}); });

View File

@ -1,12 +1,12 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { DATE_PICKER, DIALOG } from '../identifiers.js'; import { DATE_PICKER, DIALOG } from '../identifiers';
import { datePickerFactory } from './DatePicker.js'; import { datePickerFactory } from './DatePicker';
import datePickerDialogFactory from './DatePickerDialog.js'; import datePickerDialogFactory from './DatePickerDialog';
import calendarFactory from './Calendar.js'; import calendarFactory from './Calendar';
import { IconButton } from '../button'; import { IconButton } from '../button';
import Input from '../input'; import { Input } from '../input';
import Dialog from '../dialog'; import { Dialog } from '../dialog';
import theme from './theme.css'; import theme from './theme.css';
const Calendar = calendarFactory(IconButton); const Calendar = calendarFactory(IconButton);

View File

@ -1,21 +1,22 @@
/* eslint-disable jsx-a11y/aria-role */
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import classnames from 'classnames'; import classnames from 'classnames';
import { DIALOG } from '../identifiers.js'; import { DIALOG } from '../identifiers';
import Portal from '../hoc/Portal.js'; import Portal from '../hoc/Portal';
import ActivableRenderer from '../hoc/ActivableRenderer.js'; import ActivableRenderer from '../hoc/ActivableRenderer';
import InjectButton from '../button/Button.js'; import InjectButton from '../button/Button';
import InjectOverlay from '../overlay/Overlay.js'; import InjectOverlay from '../overlay/Overlay';
const factory = (Overlay, Button) => { const factory = (Overlay, Button) => {
const Dialog = (props) => { const Dialog = (props) => {
const actions = props.actions.map((action, idx) => { const actions = props.actions.map((action, idx) => {
const className = classnames(props.theme.button, {[action.className]: action.className}); const className = classnames(props.theme.button, { [action.className]: action.className });
return <Button key={idx} {...action} className={className} />; return <Button key={idx} {...action} className={className} />; // eslint-disable-line
}); });
const className = classnames([props.theme.dialog, props.theme[props.type]], { const className = classnames([props.theme.dialog, props.theme[props.type]], {
[props.theme.active]: props.active [props.theme.active]: props.active,
}, props.className); }, props.className);
return ( return (
@ -31,15 +32,15 @@ const factory = (Overlay, Button) => {
theme={props.theme} theme={props.theme}
themeNamespace="overlay" themeNamespace="overlay"
/> />
<div data-react-toolbox='dialog' className={className}> <div data-react-toolbox="dialog" className={className}>
<section role='body' className={props.theme.body}> <section role="body" className={props.theme.body}>
{props.title ? <h6 className={props.theme.title}>{props.title}</h6> : null} {props.title ? <h6 className={props.theme.title}>{props.title}</h6> : null}
{props.children} {props.children}
</section> </section>
{actions.length {actions.length
? <nav role='navigation' className={props.theme.navigation}> ? <nav role="navigation" className={props.theme.navigation}>
{actions} {actions}
</nav> </nav>
: null : null
} }
</div> </div>
@ -48,7 +49,11 @@ const factory = (Overlay, Button) => {
}; };
Dialog.propTypes = { Dialog.propTypes = {
actions: PropTypes.array, actions: PropTypes.arrayOf(PropTypes.shape({
className: PropTypes.string,
label: PropTypes.string,
children: PropTypes.node,
})),
active: PropTypes.bool, active: PropTypes.bool,
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
@ -65,16 +70,16 @@ const factory = (Overlay, Button) => {
navigation: PropTypes.string, navigation: PropTypes.string,
overlay: PropTypes.string, overlay: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
wrapper: PropTypes.string wrapper: PropTypes.string,
}), }),
title: PropTypes.string, title: PropTypes.string,
type: PropTypes.string type: PropTypes.string,
}; };
Dialog.defaultProps = { Dialog.defaultProps = {
actions: [], actions: [],
active: false, active: false,
type: 'normal' type: 'normal',
}; };
return ActivableRenderer()(Dialog); return ActivableRenderer()(Dialog);

View File

@ -1,8 +1,8 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { DIALOG } from '../identifiers.js'; import { DIALOG } from '../identifiers';
import { dialogFactory } from './Dialog.js'; import { dialogFactory } from './Dialog';
import Overlay from '../overlay'; import { Overlay } from '../overlay';
import Button from '../button'; import { Button } from '../button';
import theme from './theme.css'; import theme from './theme.css';
const Dialog = dialogFactory(Overlay, Button); const Dialog = dialogFactory(Overlay, Button);

View File

@ -1,10 +1,10 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import classnames from 'classnames'; import classnames from 'classnames';
import Portal from '../hoc/Portal.js'; import Portal from '../hoc/Portal';
import { DRAWER } from '../identifiers.js'; import { DRAWER } from '../identifiers';
import ActivableRenderer from '../hoc/ActivableRenderer.js'; import ActivableRenderer from '../hoc/ActivableRenderer';
import InjectOverlay from '../overlay/Overlay.js'; import InjectOverlay from '../overlay/Overlay';
const factory = (Overlay) => { const factory = (Overlay) => {
const Drawer = ({ const Drawer = ({
@ -16,35 +16,32 @@ const factory = (Overlay) => {
onEscKeyDown, onEscKeyDown,
theme, theme,
type, type,
withOverlay withOverlay,
}) => { }) => {
const _className = classnames([theme.drawer, theme[type]], { const _className = classnames([theme.drawer, theme[type]], {
[theme.active]: active [theme.active]: active,
}, className); }, className);
const content = ( const content = (
<aside <aside data-react-toolbox="drawer" className={_className}>
data-react-toolbox='drawer' {children}
className={_className} </aside>
children={children}
/>
); );
return React.createElement(insideTree ? 'div' : Portal, { return React.createElement(
className: theme.wrapper, insideTree ? 'div' : Portal,
children: [ { className: theme.wrapper },
withOverlay && ( withOverlay && (
<Overlay <Overlay
active={active} active={active}
onClick={onOverlayClick} onClick={onOverlayClick}
onEscKeyDown={onEscKeyDown} onEscKeyDown={onEscKeyDown}
theme={theme} theme={theme}
themeNamespace="overlay" themeNamespace="overlay"
/> />
), ),
content content,
] );
});
}; };
Drawer.propTypes = { Drawer.propTypes = {
@ -52,18 +49,18 @@ const factory = (Overlay) => {
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
insideTree: PropTypes.bool, insideTree: PropTypes.bool,
onOverlayClick: PropTypes.func,
onEscKeyDown: PropTypes.func, onEscKeyDown: PropTypes.func,
onOverlayClick: PropTypes.func,
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
drawer: PropTypes.string, drawer: PropTypes.string,
left: PropTypes.string, left: PropTypes.string,
right: PropTypes.string right: PropTypes.string,
}), }),
type: PropTypes.oneOf([ type: PropTypes.oneOf([
'left', 'right' 'left', 'right',
]), ]),
withOverlay: PropTypes.bool withOverlay: PropTypes.bool,
}; };
Drawer.defaultProps = { Drawer.defaultProps = {
@ -71,7 +68,7 @@ const factory = (Overlay) => {
className: '', className: '',
insideTree: false, insideTree: false,
type: 'left', type: 'left',
withOverlay: true withOverlay: true,
}; };
return ActivableRenderer()(Drawer); return ActivableRenderer()(Drawer);

View File

@ -1,7 +1,7 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { DRAWER } from '../identifiers.js'; import { DRAWER } from '../identifiers';
import { Overlay } from '../overlay'; import { Overlay } from '../overlay';
import { drawerFactory } from './Drawer.js'; import { drawerFactory } from './Drawer';
import theme from './theme.css'; import theme from './theme.css';
const Drawer = drawerFactory(Overlay); const Drawer = drawerFactory(Overlay);

View File

@ -1,10 +1,11 @@
/* eslint-disable */
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { DROPDOWN } from '../identifiers.js'; import { DROPDOWN } from '../identifiers';
import InjectInput from '../input/Input.js'; import InjectInput from '../input/Input';
import events from '../utils/events.js'; import events from '../utils/events';
const factory = (Input) => { const factory = (Input) => {
class Dropdown extends Component { class Dropdown extends Component {
@ -21,7 +22,10 @@ const factory = (Input) => {
onClick: PropTypes.func, onClick: PropTypes.func,
onFocus: PropTypes.func, onFocus: PropTypes.func,
required: PropTypes.bool, required: PropTypes.bool,
source: PropTypes.array.isRequired, source: PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
])).isRequired,
template: PropTypes.func, template: PropTypes.func,
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
@ -36,12 +40,12 @@ const factory = (Input) => {
templateValue: PropTypes.string, templateValue: PropTypes.string,
up: PropTypes.string, up: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
values: PropTypes.string values: PropTypes.string,
}), }),
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.number PropTypes.number,
]) ]),
}; };
static defaultProps = { static defaultProps = {
@ -49,27 +53,27 @@ const factory = (Input) => {
className: '', className: '',
allowBlank: true, allowBlank: true,
disabled: false, disabled: false,
required: false required: false,
}; };
state = { state = {
active: false, active: false,
up: false up: false,
}; };
componentWillUpdate (nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
if (!this.state.active && nextState.active) { if (!this.state.active && nextState.active) {
events.addEventsToDocument(this.getDocumentEvents()); events.addEventsToDocument(this.getDocumentEvents());
} }
} }
componentDidUpdate (prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevState.active && !this.state.active) { if (prevState.active && !this.state.active) {
events.removeEventsFromDocument(this.getDocumentEvents()); events.removeEventsFromDocument(this.getDocumentEvents());
} }
} }
componentWillUnmount () { componentWillUnmount() {
if (this.state.active) { if (this.state.active) {
events.removeEventsFromDocument(this.getDocumentEvents()); events.removeEventsFromDocument(this.getDocumentEvents());
} }
@ -77,26 +81,24 @@ const factory = (Input) => {
getDocumentEvents = () => ({ getDocumentEvents = () => ({
click: this.handleDocumentClick, click: this.handleDocumentClick,
touchend: this.handleDocumentClick touchend: this.handleDocumentClick,
}); });
open = (event) => { getSelectedItem = () => {
const client = event.target.getBoundingClientRect(); for (const item of this.props.source) {
const screenHeight = window.innerHeight || document.documentElement.offsetHeight; if (item.value === this.props.value) return item;
const up = this.props.auto ? client.top > ((screenHeight / 2) + client.height) : false; }
if (this.inputNode) this.inputNode.blur(); return !this.props.allowBlank
this.setState({active: true, up}); ? this.props.source[0]
: undefined;
}; };
close = () => { handleSelect = (item, event) => {
if (this.state.active) { if (this.props.onBlur) this.props.onBlur(event);
this.setState({active: false}); if (!this.props.disabled && this.props.onChange) {
} if (this.props.name) event.target.name = this.props.name;
} this.props.onChange(item, event);
this.close();
handleDocumentClick = (event) => {
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
this.setState({active: false});
} }
}; };
@ -106,32 +108,44 @@ const factory = (Input) => {
if (this.props.onClick) this.props.onClick(event); if (this.props.onClick) this.props.onClick(event);
}; };
handleSelect = (item, event) => { handleDocumentClick = (event) => {
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
this.setState({ active: false });
}
};
close = () => {
if (this.state.active) {
this.setState({ active: false });
}
}
open = (event) => {
const client = event.target.getBoundingClientRect();
const screenHeight = window.innerHeight || document.documentElement.offsetHeight;
const up = this.props.auto ? client.top > ((screenHeight / 2) + client.height) : false;
if (this.inputNode) this.inputNode.blur();
this.setState({ active: true, up });
};
handleFocus = (event) => {
event.stopPropagation();
if (!this.props.disabled) this.open(event);
if (this.props.onFocus) this.props.onFocus(event);
};
handleBlur = (event) => {
event.stopPropagation();
if (this.state.active) this.close();
if (this.props.onBlur) this.props.onBlur(event); if (this.props.onBlur) this.props.onBlur(event);
if (!this.props.disabled && this.props.onChange) { }
if (this.props.name) {
event.target.name = this.props.name;
}
this.props.onChange(item, event);
this.close();
}
};
getSelectedItem = () => { renderTemplateValue(selected) {
for (const item of this.props.source) {
if (item.value === this.props.value) return item;
}
if (!this.props.allowBlank) {
return this.props.source[0];
}
};
renderTemplateValue (selected) {
const { theme } = this.props; const { theme } = this.props;
const className = classnames(theme.field, { const className = classnames(theme.field, {
[theme.errored]: this.props.error, [theme.errored]: this.props.error,
[theme.disabled]: this.props.disabled, [theme.disabled]: this.props.disabled,
[theme.required]: this.props.required [theme.required]: this.props.required,
}); });
return ( return (
@ -140,11 +154,12 @@ const factory = (Input) => {
{this.props.template(selected)} {this.props.template(selected)}
</div> </div>
{this.props.label {this.props.label
? <label className={theme.label}> ? (
<label htmlFor={this.props.name} className={theme.label}>
{this.props.label} {this.props.label}
{this.props.required ? <span className={theme.required}> * </span> : null} {this.props.required ? <span className={theme.required}> * </span> : null}
</label> </label>
: null} ) : null}
{this.props.error ? <span className={theme.error}>{this.props.error}</span> : null} {this.props.error ? <span className={theme.error}>{this.props.error}</span> : null}
</div> </div>
); );
@ -154,30 +169,22 @@ const factory = (Input) => {
const { theme } = this.props; const { theme } = this.props;
const className = classnames({ const className = classnames({
[theme.selected]: item.value === this.props.value, [theme.selected]: item.value === this.props.value,
[theme.disabled]: item.disabled [theme.disabled]: item.disabled,
}); });
return ( return (
<li key={idx} className={className} onClick={!item.disabled ? this.handleSelect.bind(this, item.value) : null}> <li
key={idx}
className={className}
onClick={!item.disabled && this.handleSelect.bind(this, item.value)}
>
{this.props.template ? this.props.template(item) : item.label} {this.props.template ? this.props.template(item) : item.label}
</li> </li>
); );
}; };
handleFocus = event => { render() {
event.stopPropagation();
if (!this.props.disabled) this.open(event);
if (this.props.onFocus) this.props.onFocus(event);
};
handleBlur = event => {
event.stopPropagation();
if (this.state.active) this.close();
if (this.props.onBlur) this.props.onBlur(event);
}
render () {
const { const {
allowBlank, auto, required, onChange, onFocus, onBlur, //eslint-disable-line no-unused-vars allowBlank, auto, required, onChange, onFocus, onBlur, // eslint-disable-line no-unused-vars
source, template, theme, ...others source, template, theme, ...others
} = this.props; } = this.props;
const selected = this.getSelectedItem(); const selected = this.getSelectedItem();
@ -185,13 +192,13 @@ const factory = (Input) => {
[theme.up]: this.state.up, [theme.up]: this.state.up,
[theme.active]: this.state.active, [theme.active]: this.state.active,
[theme.disabled]: this.props.disabled, [theme.disabled]: this.props.disabled,
[theme.required]: this.props.required [theme.required]: this.props.required,
}, this.props.className); }, this.props.className);
return ( return (
<div <div
className={className} className={className}
data-react-toolbox='dropdown' data-react-toolbox="dropdown"
onBlur={this.handleBlur} onBlur={this.handleBlur}
onFocus={this.handleFocus} onFocus={this.handleFocus}
> >
@ -202,14 +209,14 @@ const factory = (Input) => {
onClick={this.handleClick} onClick={this.handleClick}
required={this.props.required} required={this.props.required}
readOnly readOnly
ref={node => { this.inputNode = node && node.getWrappedInstance(); }} ref={(node) => { this.inputNode = node && node.getWrappedInstance(); }}
type={template && selected ? 'hidden' : null} type={template && selected ? 'hidden' : null}
theme={theme} theme={theme}
themeNamespace="input" themeNamespace="input"
value={selected && selected.label ? selected.label : ''} value={selected && selected.label ? selected.label : ''}
/> />
{template && selected ? this.renderTemplateValue(selected) : null} {template && selected ? this.renderTemplateValue(selected) : null}
<ul className={theme.values} ref='values'> <ul className={theme.values}>
{source.map(this.renderValue)} {source.map(this.renderValue)}
</ul> </ul>
</div> </div>

View File

@ -1,29 +1,30 @@
/* eslint-disable */
import expect from 'expect'; import expect from 'expect';
import React from 'react'; import React from 'react';
import { import {
renderIntoDocument, renderIntoDocument,
scryRenderedDOMComponentsWithClass, scryRenderedDOMComponentsWithClass,
Simulate Simulate,
} from 'react-addons-test-utils'; } from 'react-addons-test-utils';
import sinon from 'sinon'; import sinon from 'sinon';
import theme from '../theme.css'; import theme from '../theme.css';
import Dropdown from '../Dropdown'; import Dropdown from '../Dropdown';
describe('Dropdown', function () { describe('Dropdown', () => {
describe('#renderValue', function () { describe('#renderValue', () => {
const source = [ const source = [
{ value: 'EN-gb', label: 'England' }, { value: 'EN-gb', label: 'England' },
{ value: 'ES-es', label: 'Spain', disabled: true }, { value: 'ES-es', label: 'Spain', disabled: true },
{ value: 'TH-th', label: 'Thailand', disabled: true }, { value: 'TH-th', label: 'Thailand', disabled: true },
{ value: 'EN-en', label: 'USA'} { value: 'EN-en', label: 'USA' },
]; ];
it('renders dropdown item with disabled style', function () { it('renders dropdown item with disabled style', () => {
const tree = renderIntoDocument(<Dropdown theme={theme} source={source} />); const tree = renderIntoDocument(<Dropdown theme={theme} source={source} />);
const disabled = scryRenderedDOMComponentsWithClass(tree, theme.disabled); const disabled = scryRenderedDOMComponentsWithClass(tree, theme.disabled);
expect(disabled.length).toEqual(2); expect(disabled.length).toEqual(2);
}); });
it('does not call onChange callback when disabled dorpdown item is clicked', function () { it('does not call onChange callback when disabled dorpdown item is clicked', () => {
const spy = sinon.spy(); const spy = sinon.spy();
const tree = renderIntoDocument(<Dropdown const tree = renderIntoDocument(<Dropdown
theme={theme} theme={theme}

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { DROPDOWN } from '../identifiers.js'; import { DROPDOWN } from '../identifiers';
import { dropdownFactory } from './Dropdown.js'; import { dropdownFactory } from './Dropdown';
import { Input } from '../input'; import { Input } from '../input';
import theme from './theme.css'; import theme from './theme.css';

View File

@ -3,9 +3,9 @@ import classnames from 'classnames';
const FontIcon = ({ alt, children, className, theme, value, ...other}) => ( // eslint-disable-line const FontIcon = ({ alt, children, className, theme, value, ...other}) => ( // eslint-disable-line
<span <span
data-react-toolbox='font-icon' data-react-toolbox="font-icon"
aria-label={alt} aria-label={alt}
className={classnames({'material-icons': typeof value === 'string' || typeof children === 'string'}, className)} className={classnames({ 'material-icons': typeof value === 'string' || typeof children === 'string' }, className)}
{...other} {...other}
> >
<span aria-hidden="true">{value}</span> <span aria-hidden="true">{value}</span>
@ -15,18 +15,18 @@ const FontIcon = ({ alt, children, className, theme, value, ...other}) => ( // e
FontIcon.propTypes = { FontIcon.propTypes = {
alt: PropTypes.string, alt: PropTypes.string,
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
theme: PropTypes.object, theme: PropTypes.object, // eslint-disable-line
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]) ]),
}; };
FontIcon.defaultProps = { FontIcon.defaultProps = {
alt: '', alt: '',
className: '' className: '',
}; };
export default FontIcon; export default FontIcon;

View File

@ -1,4 +1,4 @@
import FontIcon from './FontIcon.js'; import { FontIcon } from './FontIcon';
export default FontIcon; export default FontIcon;
export { FontIcon }; export { FontIcon };

View File

@ -1,86 +0,0 @@
import React, { Component, PropTypes } from 'react';
import InjectAutocomplete from '../autocomplete/Autocomplete.js';
import InjectButton from '../button/Button.js';
import InjectCheckbox from '../checkbox/Checkbox.js';
import InjectDatePicker from '../date_picker/DatePicker.js';
import InjectDropdown from '../dropdown/Dropdown.js';
import InjectInput from '../input/Input.js';
import InjectRadioGroup from '../radio/RadioGroup.js';
import InjectSlider from '../slider/Slider.js';
import InjectSwitch from '../switch/Switch.js';
import InjectTimePicker from '../time_picker/TimePicker.js';
const factory = (
Autocomplete, Button, Checkbox, DatePicker, Dropdown,
Input, RadioGroup, Slider, Switch, TimePicker
) => {
const COMPONENTS = {
'autocomplete': Autocomplete,
'button': Button,
'checkbox': Checkbox,
'datepicker': DatePicker,
'dropdown': Dropdown,
'input': Input,
'radioGroup': RadioGroup,
'slider': Slider,
'switch': Switch,
'timepicker': TimePicker
};
class Form extends Component {
static propTypes = {
attributes: PropTypes.array,
children: PropTypes.node,
className: PropTypes.string,
model: PropTypes.object,
onChange: PropTypes.func,
onError: PropTypes.func,
onSubmit: PropTypes.func,
onValid: PropTypes.func,
storage: PropTypes.string
};
static defaultProps = {
attributes: [],
className: ''
};
onSubmit = (event) => {
event.preventDefault();
if (this.props.onSubmit) this.props.onSubmit(event);
};
onChange = (field, value, event) => {
if (this.props.onChange) this.props.onChange(field, value, event);
};
renderFields () {
return Object.keys(this.props.model).map((field, index) => {
const properties = this.props.model[field];
const Field = COMPONENTS[properties.kind.toLowerCase()];
return <Field key={index} {...properties} onChange={this.onChange.bind(this, field)} />;
});
}
render () {
return (
<form data-react-toolbox='form' className={this.props.className} onSubmit={this.onSubmit}>
{this.renderFields()}
{this.props.children}
</form>
);
}
}
return Form;
};
const Form = factory(
InjectAutocomplete, InjectButton, InjectCheckbox, InjectDatePicker, InjectDropdown,
InjectInput, InjectRadioGroup, InjectSlider, InjectSwitch, InjectTimePicker
);
export default Form;
export { factory as formFactory };
export { Form };

View File

@ -1,19 +0,0 @@
import { formFactory } from './Form.js';
import Autocomplete from '../autocomplete';
import Button from '../button';
import Checkbox from '../checkbox';
import DatePicker from '../date_picker';
import Dropdown from '../dropdown';
import Input from '../input';
import RadioGroup from '../radio';
import Slider from '../slider';
import Switch from '../switch';
import TimePicker from '../time_picker';
const ThemedForm = formFactory(
Autocomplete, Button, Checkbox, DatePicker, Dropdown,
Input, RadioGroup, Slider, Switch, TimePicker
);
export default ThemedForm;
export { ThemedForm as Form };

View File

@ -1,24 +0,0 @@
# Form
```
var Form = require('react-toolbox/components/form');
var fields : [
{ref: "name", label: "Your Name", required: true, storage: true},
{ref: "description", multiline: true, label: "Description", value: "Doer"},
{ref: "birthdate", type: "date", label: "Birthdate"}
]
<Form attributes={fields} storage="my_toolbox_form" />
```
## Properties
| Name | Type | Default | Description|
|:- |:-: | :- |:-|
| **attributes** | array | | Array of fields you want hold, fields can be instances of <Autocomplete>, <Button/>, <Dropdown>, <Input/> or <Switch/> |
| **className** | String | | Set the class-styles of the Component.|
| **onChange** | Function | | Dispatch callback when values of the component changes.|
| **onError** | Function | | Dispatch callback when a required field is null or has incorrect type.|
| **onSubmit** | Function | | Dispatch callback when user clicks on submit <Button/> |
| **onValid** | Function | | Dispatch callback when all required fields are full-filled.|
| **Storage** | String | | Sets a localStorage key for save all current field values.|

View File

@ -1,40 +1,40 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
const ActivableRendererFactory = (options = {delay: 500}) => const ActivableRendererFactory = (options = { delay: 500 }) =>
ActivableComponent => class ActivableRenderer extends Component { ActivableComponent => class ActivableRenderer extends Component {
static propTypes = { static propTypes = {
active: PropTypes.bool.isRequired, active: PropTypes.bool.isRequired,
children: PropTypes.any, children: PropTypes.node,
delay: PropTypes.number delay: PropTypes.number,
}; };
static defaultProps = { static defaultProps = {
delay: options.delay delay: options.delay,
} }
state = { state = {
active: this.props.active, active: this.props.active,
rendered: this.props.active rendered: this.props.active,
}; };
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.active && !this.props.active) this.renderAndActivate(); if (nextProps.active && !this.props.active) this.renderAndActivate();
if (!nextProps.active && this.props.active) this.deactivateAndUnrender(); if (!nextProps.active && this.props.active) this.deactivateAndUnrender();
} }
componentWillUnmount () { componentWillUnmount() {
clearTimeout(this.activateTimeout); clearTimeout(this.activateTimeout);
clearTimeout(this.unrenderTimeout); clearTimeout(this.unrenderTimeout);
} }
renderAndActivate () { renderAndActivate() {
if (this.unrenderTimeout) clearTimeout(this.unrenderTimeout); if (this.unrenderTimeout) clearTimeout(this.unrenderTimeout);
this.setState({ rendered: true, active: false }, () => { this.setState({ rendered: true, active: false }, () => {
this.activateTimeout = setTimeout(() => this.setState({ active: true }), 20); this.activateTimeout = setTimeout(() => this.setState({ active: true }), 20);
}); });
} }
deactivateAndUnrender () { deactivateAndUnrender() {
this.setState({ rendered: true, active: false }, () => { this.setState({ rendered: true, active: false }, () => {
this.unrenderTimeout = setTimeout(() => { this.unrenderTimeout = setTimeout(() => {
this.setState({ rendered: false }); this.setState({ rendered: false });
@ -43,7 +43,7 @@ const ActivableRendererFactory = (options = {delay: 500}) =>
}); });
} }
render () { render() {
const { delay, ...others } = this.props; // eslint-disable-line no-unused-vars const { delay, ...others } = this.props; // eslint-disable-line no-unused-vars
const { active, rendered } = this.state; const { active, rendered } = this.state;
return rendered return rendered

View File

@ -3,22 +3,20 @@ import ReactDOM from 'react-dom';
class Portal extends Component { class Portal extends Component {
static propTypes = { static propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
container: PropTypes.any, container: PropTypes.node,
lockBody: PropTypes.bool
} }
static defaultProps = { static defaultProps = {
className: '', className: '',
lockBody: true
} }
componentDidMount () { componentDidMount() {
this._renderOverlay(); this._renderOverlay();
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (this._overlayTarget && nextProps.container !== this.props.container) { if (this._overlayTarget && nextProps.container !== this.props.container) {
this._portalContainerNode.removeChild(this._overlayTarget); this._portalContainerNode.removeChild(this._overlayTarget);
this._portalContainerNode = getContainer(nextProps.container); this._portalContainerNode = getContainer(nextProps.container);
@ -26,42 +24,45 @@ class Portal extends Component {
} }
} }
componentDidUpdate () { componentDidUpdate() {
this._renderOverlay(); this._renderOverlay();
} }
componentWillUnmount () { componentWillUnmount() {
this._unrenderOverlay(); this._unrenderOverlay();
this._unmountOverlayTarget(); this._unmountOverlayTarget();
} }
_mountOverlayTarget () { getMountNode() {
if (!this._overlayTarget) { return this._overlayTarget;
this._overlayTarget = document.createElement('div');
this._portalContainerNode = getContainer(this.props.container);
this._portalContainerNode.appendChild(this._overlayTarget);
}
} }
_unmountOverlayTarget () { getOverlayDOMNode() {
if (this._overlayTarget) { if (!this.isMounted()) { // eslint-disable-line
this._portalContainerNode.removeChild(this._overlayTarget); throw new Error('getOverlayDOMNode(): A component must be mounted to have a DOM node.');
this._overlayTarget = null;
} }
this._portalContainerNode = null;
if (this._overlayInstance) {
if (this._overlayInstance.getWrappedDOMNode) {
return this._overlayInstance.getWrappedDOMNode();
}
return ReactDOM.findDOMNode(this._overlayInstance);
}
return null;
} }
_getOverlay () { _getOverlay() {
if (!this.props.children) return null; if (!this.props.children) return null;
return <div className={this.props.className}>{this.props.children}</div>; return <div className={this.props.className}>{this.props.children}</div>;
} }
_renderOverlay () { _renderOverlay() {
const overlay = this._getOverlay(); const overlay = this._getOverlay();
if (overlay !== null) { if (overlay !== null) {
this._mountOverlayTarget(); this._mountOverlayTarget();
this._overlayInstance = ReactDOM.unstable_renderSubtreeIntoContainer( this._overlayInstance = ReactDOM.unstable_renderSubtreeIntoContainer(
this, overlay, this._overlayTarget this, overlay, this._overlayTarget,
); );
} else { } else {
this._unrenderOverlay(); this._unrenderOverlay();
@ -69,40 +70,36 @@ class Portal extends Component {
} }
} }
_unrenderOverlay () { _unrenderOverlay() {
if (this._overlayTarget) { if (this._overlayTarget) {
ReactDOM.unmountComponentAtNode(this._overlayTarget); ReactDOM.unmountComponentAtNode(this._overlayTarget);
this._overlayInstance = null; this._overlayInstance = null;
} }
} }
getMountNode () { _mountOverlayTarget() {
return this._overlayTarget; if (!this._overlayTarget) {
this._overlayTarget = document.createElement('div');
this._portalContainerNode = getContainer(this.props.container);
this._portalContainerNode.appendChild(this._overlayTarget);
}
} }
getOverlayDOMNode () { _unmountOverlayTarget() {
if (!this.isMounted()) { if (this._overlayTarget) {
throw new Error('getOverlayDOMNode(): A component must be mounted to have a DOM node.'); this._portalContainerNode.removeChild(this._overlayTarget);
this._overlayTarget = null;
} }
this._portalContainerNode = null;
if (this._overlayInstance) {
if (this._overlayInstance.getWrappedDOMNode) {
return this._overlayInstance.getWrappedDOMNode();
} else {
return ReactDOM.findDOMNode(this._overlayInstance);
}
}
return null;
} }
render () { render() {
return null; return null;
} }
} }
function getContainer (container) { function getContainer(container) {
const _container = typeof container === 'function' ? container() : container; const _container = typeof container === 'function' ? container() : container;
return ReactDOM.findDOMNode(_container) || document.body; return ReactDOM.findDOMNode(_container) || document.body;
} }

View File

@ -1,31 +1,31 @@
import './utils/polyfills'; // Import polyfills for IE11 import './utils/polyfills'; // Import polyfills for IE11
export { overrideComponentTypeChecker } from './utils/is-component-of-type'; export { overrideComponentTypeChecker } from './utils/is-component-of-type';
export AppBar from './app_bar'; export { default as AppBar } from './app_bar';
export Autocomplete from './autocomplete'; export { default as Autocomplete } from './autocomplete';
export Avatar from './avatar'; export { default as Avatar } from './avatar';
export * from './button'; export * from './button';
export * from './card'; export * from './card';
export Chip from './chip'; export { default as Chip } from './chip';
export Checkbox from './checkbox'; export { default as Checkbox } from './checkbox';
export DatePicker from './date_picker'; export { default as DatePicker } from './date_picker';
export Dialog from './dialog'; export { default as Dialog } from './dialog';
export Drawer from './drawer'; export { default as Drawer } from './drawer';
export Dropdown from './dropdown'; export { default as Dropdown } from './dropdown';
export FontIcon from './font_icon'; export { default as FontIcon } from './font_icon';
export Form from './form'; export { default as Input } from './input';
export Input from './input';
export * from './layout'; export * from './layout';
export Link from './link'; export { default as Link } from './link';
export * from './list'; export * from './list';
export * from './menu'; export * from './menu';
export Navigation from './navigation'; export { default as Navigation } from './navigation';
export ProgressBar from './progress_bar'; export { default as ProgressBar } from './progress_bar';
export * from './radio'; export * from './radio';
export Ripple from './ripple'; export Ripple from './ripple';
export Slider from './slider'; export { default as Slider } from './slider';
export Snackbar from './snackbar'; export { default as Snackbar } from './snackbar';
export Switch from './switch'; export { default as Switch } from './switch';
export Table from './table'; export { default as Table } from './table';
export * from './tabs'; export * from './tabs';
export Tooltip from './tooltip'; export Tooltip from './tooltip';
export TimePicker from './time_picker'; export { default as TimePicker } from './time_picker';

View File

@ -1,57 +1,60 @@
import React from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { INPUT } from '../identifiers.js'; import { INPUT } from '../identifiers';
import InjectedFontIcon from '../font_icon/FontIcon.js'; import InjectedFontIcon from '../font_icon/FontIcon';
const factory = (FontIcon) => { const factory = (FontIcon) => {
class Input extends React.Component { class Input extends React.Component {
static propTypes = { static propTypes = {
children: React.PropTypes.any, children: PropTypes.node,
className: React.PropTypes.string, className: PropTypes.string,
disabled: React.PropTypes.bool, disabled: PropTypes.bool,
error: React.PropTypes.oneOfType([ error: PropTypes.oneOfType([
React.PropTypes.string, PropTypes.string,
React.PropTypes.node PropTypes.node,
]), ]),
floating: React.PropTypes.bool, floating: PropTypes.bool,
hint: React.PropTypes.oneOfType([ hint: PropTypes.oneOfType([
React.PropTypes.string, PropTypes.string,
React.PropTypes.node PropTypes.node,
]), ]),
icon: React.PropTypes.oneOfType([ icon: PropTypes.oneOfType([
React.PropTypes.string, PropTypes.string,
React.PropTypes.element PropTypes.element,
]), ]),
label: React.PropTypes.oneOfType([ label: PropTypes.oneOfType([
React.PropTypes.string, PropTypes.string,
React.PropTypes.node PropTypes.node,
]), ]),
maxLength: React.PropTypes.number, maxLength: PropTypes.number,
multiline: React.PropTypes.bool, multiline: PropTypes.bool,
name: React.PropTypes.string, name: PropTypes.string,
onBlur: React.PropTypes.func, onBlur: PropTypes.func,
onChange: React.PropTypes.func, onChange: PropTypes.func,
onFocus: React.PropTypes.func, onFocus: PropTypes.func,
onKeyPress: React.PropTypes.func, onKeyPress: PropTypes.func,
required: React.PropTypes.bool, required: PropTypes.bool,
rows: React.PropTypes.number, rows: PropTypes.number,
theme: React.PropTypes.shape({ theme: PropTypes.shape({
bar: React.PropTypes.string, bar: PropTypes.string,
counter: React.PropTypes.string, counter: PropTypes.string,
disabled: React.PropTypes.string, disabled: PropTypes.string,
error: React.PropTypes.string, error: PropTypes.string,
errored: React.PropTypes.string, errored: PropTypes.string,
hidden: React.PropTypes.string, hidden: PropTypes.string,
hint: React.PropTypes.string, hint: PropTypes.string,
icon: React.PropTypes.string, icon: PropTypes.string,
input: React.PropTypes.string, input: PropTypes.string,
inputElement: React.PropTypes.string, inputElement: PropTypes.string,
required: React.PropTypes.string, required: PropTypes.string,
withIcon: React.PropTypes.string withIcon: PropTypes.string,
}), }),
type: React.PropTypes.string, type: PropTypes.string,
value: React.PropTypes.any value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
]),
}; };
static defaultProps = { static defaultProps = {
@ -61,17 +64,17 @@ const factory = (FontIcon) => {
floating: true, floating: true,
multiline: false, multiline: false,
required: false, required: false,
type: 'text' type: 'text',
}; };
componentDidMount () { componentDidMount() {
if (this.props.multiline) { if (this.props.multiline) {
window.addEventListener('resize', this.handleAutoresize); window.addEventListener('resize', this.handleAutoresize);
this.handleAutoresize(); this.handleAutoresize();
} }
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (!this.props.multiline && nextProps.multiline) { if (!this.props.multiline && nextProps.multiline) {
window.addEventListener('resize', this.handleAutoresize); window.addEventListener('resize', this.handleAutoresize);
} else if (this.props.multiline && !nextProps.multiline) { } else if (this.props.multiline && !nextProps.multiline) {
@ -79,12 +82,12 @@ const factory = (FontIcon) => {
} }
} }
componentDidUpdate () { componentDidUpdate() {
// resize the textarea, if nessesary // resize the textarea, if nessesary
if (this.props.multiline) this.handleAutoresize(); if (this.props.multiline) this.handleAutoresize();
} }
componentWillUnmount () { componentWillUnmount() {
if (this.props.multiline) window.removeEventListener('resize', this.handleAutoresize); if (this.props.multiline) window.removeEventListener('resize', this.handleAutoresize);
} }
@ -103,7 +106,7 @@ const factory = (FontIcon) => {
}; };
handleAutoresize = () => { handleAutoresize = () => {
const element = this.refs.input; const element = this.inputNode;
const rows = this.props.rows; const rows = this.props.rows;
if (typeof rows === 'number' && !isNaN(rows)) { if (typeof rows === 'number' && !isNaN(rows)) {
@ -135,51 +138,52 @@ const factory = (FontIcon) => {
if (!isReplacing && value.length === maxLength) { if (!isReplacing && value.length === maxLength) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
return; return undefined;
} }
} }
if (onKeyPress) onKeyPress(event); if (onKeyPress) onKeyPress(event);
return undefined;
}; };
blur () { blur() {
this.refs.input.blur(); this.inputNode.blur();
} }
focus () { focus() {
this.refs.input.focus(); this.inputNode.focus();
} }
render () { render() {
const { children, disabled, error, floating, hint, icon, const { children, disabled, error, floating, hint, icon,
name, label: labelText, maxLength, multiline, required, name, label: labelText, maxLength, multiline, required,
theme, type, value, onKeyPress, rows = 1, ...others} = this.props; theme, type, value, onKeyPress, rows = 1, ...others } = this.props;
const length = maxLength && value ? value.length : 0; const length = maxLength && value ? value.length : 0;
const labelClassName = classnames(theme.label, {[theme.fixed]: !floating}); const labelClassName = classnames(theme.label, { [theme.fixed]: !floating });
const className = classnames(theme.input, { const className = classnames(theme.input, {
[theme.disabled]: disabled, [theme.disabled]: disabled,
[theme.errored]: error, [theme.errored]: error,
[theme.hidden]: type === 'hidden', [theme.hidden]: type === 'hidden',
[theme.withIcon]: icon [theme.withIcon]: icon,
}, this.props.className); }, this.props.className);
const valuePresent = value !== null const valuePresent = value !== null
&& value !== undefined && value !== undefined
&& value !== '' && value !== ''
&& !(typeof value === Number && isNaN(value)); && !(typeof value === Number && isNaN(value)); // eslint-disable-line
const inputElementProps = { const inputElementProps = {
...others, ...others,
className: classnames(theme.inputElement, {[theme.filled]: valuePresent}), className: classnames(theme.inputElement, { [theme.filled]: valuePresent }),
onChange: this.handleChange, onChange: this.handleChange,
ref: 'input', ref: (node) => { this.inputNode = node; },
role: 'input', role: 'input',
name, name,
disabled, disabled,
required, required,
type, type,
value value,
}; };
if (!multiline) { if (!multiline) {
inputElementProps.maxLength = maxLength; inputElementProps.maxLength = maxLength;
@ -190,15 +194,15 @@ const factory = (FontIcon) => {
} }
return ( return (
<div data-react-toolbox='input' className={className}> <div data-react-toolbox="input" className={className}>
{React.createElement(multiline ? 'textarea' : 'input', inputElementProps)} {React.createElement(multiline ? 'textarea' : 'input', inputElementProps)}
{icon ? <FontIcon className={theme.icon} value={icon} /> : null} {icon ? <FontIcon className={theme.icon} value={icon} /> : null}
<span className={theme.bar} /> <span className={theme.bar} />
{labelText {labelText
? <label className={labelClassName}> ? <label className={labelClassName} htmlFor={name}>
{labelText} {labelText}
{required ? <span className={theme.required}> * </span> : null} {required ? <span className={theme.required}> * </span> : null}
</label> </label>
: null} : null}
{hint ? <span hidden={labelText} className={theme.hint}>{hint}</span> : null} {hint ? <span hidden={labelText} className={theme.hint}>{hint}</span> : null}
{error ? <span className={theme.error}>{error}</span> : null} {error ? <span className={theme.error}>{error}</span> : null}

View File

@ -1,7 +1,7 @@
import { INPUT } from '../identifiers.js';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { inputFactory } from './Input.js'; import { INPUT } from '../identifiers';
import FontIcon from '../font_icon/FontIcon.js'; import { inputFactory } from './Input';
import { FontIcon } from '../font_icon/FontIcon';
import theme from './theme.css'; import theme from './theme.css';
const Input = inputFactory(FontIcon); const Input = inputFactory(FontIcon);

View File

@ -2,8 +2,8 @@ import React, { cloneElement, Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { getViewport } from '../utils/utils'; import { getViewport } from '../utils/utils';
import filterReactChildren from '../utils/filter-react-children.js'; import filterReactChildren from '../utils/filter-react-children';
import isComponentOfType from '../utils/is-component-of-type.js'; import isComponentOfType from '../utils/is-component-of-type';
import InjectAppBar from '../app_bar/AppBar'; import InjectAppBar from '../app_bar/AppBar';
import InjectNavDrawer from './NavDrawer'; import InjectNavDrawer from './NavDrawer';
import InjectSidebar from './Sidebar'; import InjectSidebar from './Sidebar';
@ -21,23 +21,31 @@ const factory = (AppBar, NavDrawer, Sidebar) => {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
theme: PropTypes.object theme: PropTypes.shape({
appbarFixed: PropTypes.string,
layout: PropTypes.string,
layoutInner: PropTypes.string,
navDrawerClipped: PropTypes.string,
navDrawerPinned: PropTypes.string,
sidebarClipped: PropTypes.string,
sidebarPinned: PropTypes.string,
}),
}; };
static defaultProps = { static defaultProps = {
className: '' className: '',
}; };
state = { state = {
width: isBrowser() && getViewport().width width: isBrowser() && getViewport().width,
}; };
componentDidMount () { componentDidMount() {
if (!this.state.width) this.handleResize(); if (!this.state.width) this.handleResize();
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
} }
componentWillUnmount () { componentWillUnmount() {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
} }
@ -45,7 +53,7 @@ const factory = (AppBar, NavDrawer, Sidebar) => {
this.setState({ width: getViewport().width }); this.setState({ width: getViewport().width });
} }
isPinned = sideNav => { isPinned = (sideNav) => {
if (sideNav) { if (sideNav) {
const { permanentAt, pinned } = sideNav.props; const { permanentAt, pinned } = sideNav.props;
const { width } = this.state; const { width } = this.state;
@ -54,7 +62,7 @@ const factory = (AppBar, NavDrawer, Sidebar) => {
return undefined; return undefined;
} }
render () { render() {
const { children, className, theme, ...rest } = this.props; const { children, className, theme, ...rest } = this.props;
const appBar = filterReactChildren(children, isAppBar)[0]; const appBar = filterReactChildren(children, isAppBar)[0];
const navDrawer = filterReactChildren(children, isNavDrawer)[0]; const navDrawer = filterReactChildren(children, isNavDrawer)[0];
@ -69,17 +77,17 @@ const factory = (AppBar, NavDrawer, Sidebar) => {
const clonedAppBar = appBar && cloneElement(appBar, { const clonedAppBar = appBar && cloneElement(appBar, {
theme, theme,
themeNamespace: 'appbar' themeNamespace: 'appbar',
}); });
const clonedLeftSideNav = navDrawer && cloneElement(navDrawer, { const clonedLeftSideNav = navDrawer && cloneElement(navDrawer, {
clipped: navDrawerClipped, clipped: navDrawerClipped,
pinned: navDrawerPinned pinned: navDrawerPinned,
}); });
const clonedRightSideNav = sidebar && cloneElement(sidebar, { const clonedRightSideNav = sidebar && cloneElement(sidebar, {
clipped: sidebarClipped, clipped: sidebarClipped,
pinned: sidebarPinned pinned: sidebarPinned,
}); });
const _className = classnames(theme.layout, const _className = classnames(theme.layout,
@ -88,8 +96,8 @@ const factory = (AppBar, NavDrawer, Sidebar) => {
[theme.navDrawerClipped]: navDrawerClipped, [theme.navDrawerClipped]: navDrawerClipped,
[theme.sidebarPinned]: sidebarPinned, [theme.sidebarPinned]: sidebarPinned,
[theme.sidebarClipped]: sidebarClipped, [theme.sidebarClipped]: sidebarClipped,
[theme.appbarFixed]: appBarFixed [theme.appbarFixed]: appBarFixed,
}, className); }, className);
return ( return (
<div {...rest} className={_className}> <div {...rest} className={_className}>

View File

@ -4,7 +4,7 @@ import { themr } from 'react-css-themr';
import InjectDrawer from '../drawer/Drawer'; import InjectDrawer from '../drawer/Drawer';
import { LAYOUT } from '../identifiers'; import { LAYOUT } from '../identifiers';
const factory = Drawer => { const factory = (Drawer) => {
const NavDrawer = ({ const NavDrawer = ({
active, active,
className, className,
@ -16,7 +16,7 @@ const factory = Drawer => {
}) => { }) => {
const _className = classnames({ const _className = classnames({
[theme.pinned]: pinned, [theme.pinned]: pinned,
[theme.clipped]: clipped [theme.clipped]: clipped,
}, className); }, className);
return ( return (
@ -40,12 +40,15 @@ const factory = Drawer => {
permanentAt: PropTypes.oneOf(['sm', 'smTablet', 'md', 'lg', 'lgTablet', 'xl', 'xxl', 'xxxl']), permanentAt: PropTypes.oneOf(['sm', 'smTablet', 'md', 'lg', 'lgTablet', 'xl', 'xxl', 'xxxl']),
pinned: PropTypes.bool, pinned: PropTypes.bool,
right: PropTypes.bool, right: PropTypes.bool,
theme: PropTypes.object theme: PropTypes.shape({
clipped: PropTypes.string,
pinned: PropTypes.string,
}),
}; };
NavDrawer.defaultProps = { NavDrawer.defaultProps = {
className: '', className: '',
pinned: false pinned: false,
}; };
return NavDrawer; return NavDrawer;

View File

@ -1,31 +1,29 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import cn from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LAYOUT } from '../identifiers.js'; import { LAYOUT } from '../identifiers';
const Panel = ({ bodyScroll, children, className, theme, ...other }) => ( const Panel = ({ bodyScroll, children, className, theme, ...other }) => {
<div const _className = cn(theme.panel, { [theme.bodyScroll]: bodyScroll }, className);
{...other} return (
data-react-toolbox='panel' <div {...other} data-react-toolbox="panel" className={_className}>
className={classnames(theme.panel, { {children}
[theme.bodyScroll]: bodyScroll </div>
}, className)} );
children={children} };
/>
);
Panel.propTypes = { Panel.propTypes = {
bodyScroll: PropTypes.bool, bodyScroll: PropTypes.bool,
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
theme: PropTypes.shape({ theme: PropTypes.shape({
panel: PropTypes.string panel: PropTypes.string,
}) }),
}; };
Panel.defaultProps = { Panel.defaultProps = {
bodyScroll: true, bodyScroll: true,
className: '' className: '',
}; };
export default themr(LAYOUT)(Panel); export default themr(LAYOUT)(Panel);

View File

@ -4,7 +4,7 @@ import { themr } from 'react-css-themr';
import InjectDrawer from '../drawer/Drawer'; import InjectDrawer from '../drawer/Drawer';
import { LAYOUT } from '../identifiers'; import { LAYOUT } from '../identifiers';
const factory = Drawer => { const factory = (Drawer) => {
const Sidebar = ({ const Sidebar = ({
active, active,
className, className,
@ -16,7 +16,7 @@ const factory = Drawer => {
}) => { }) => {
const _className = classnames({ const _className = classnames({
[theme.pinned]: pinned, [theme.pinned]: pinned,
[theme.clipped]: clipped [theme.clipped]: clipped,
}, className); }, className);
return ( return (
@ -40,14 +40,17 @@ const factory = Drawer => {
clipped: PropTypes.bool, clipped: PropTypes.bool,
permanentAt: PropTypes.oneOf(['sm', 'smTablet', 'md', 'lg', 'lgTablet', 'xl', 'xxl', 'xxxl']), permanentAt: PropTypes.oneOf(['sm', 'smTablet', 'md', 'lg', 'lgTablet', 'xl', 'xxl', 'xxxl']),
pinned: PropTypes.bool, pinned: PropTypes.bool,
theme: PropTypes.object, theme: PropTypes.shape({
width: PropTypes.oneOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 25, 33, 50, 66, 75, 100]) clipped: PropTypes.string,
pinned: PropTypes.string,
}),
width: PropTypes.oneOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 25, 33, 50, 66, 75, 100]),
}; };
Sidebar.defaultProps = { Sidebar.defaultProps = {
className: '', className: '',
pinned: false, pinned: false,
right: false right: false,
}; };
return Sidebar; return Sidebar;

View File

@ -3,9 +3,9 @@ import { LAYOUT } from '../identifiers';
import { layoutFactory } from './Layout'; import { layoutFactory } from './Layout';
import { sidebarFactory } from './Sidebar'; import { sidebarFactory } from './Sidebar';
import { navDrawerFactory } from './NavDrawer'; import { navDrawerFactory } from './NavDrawer';
import Panel from './Panel'; import { Panel } from './Panel';
import AppBar from '../app_bar'; import { AppBar } from '../app_bar';
import Drawer from '../drawer'; import { Drawer } from '../drawer';
import theme from './theme.css'; import theme from './theme.css';
const injectTheme = component => themr(LAYOUT, theme)(component); const injectTheme = component => themr(LAYOUT, theme)(component);

View File

@ -1,19 +1,19 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LINK } from '../identifiers.js'; import { LINK } from '../identifiers';
import FontIcon from '../font_icon/FontIcon.js'; import { FontIcon } from '../font_icon/FontIcon';
const Link = ({active, children, className, count, icon, label, theme, ...others}) => { const Link = ({ active, children, className, count, icon, label, theme, ...others }) => {
const _className = classnames(theme.link, { const _className = classnames(theme.link, {
[theme.active]: active [theme.active]: active,
}, className); }, className);
return ( return (
<a data-react-toolbox='link' className={_className} {...others}> <a data-react-toolbox="link" className={_className} {...others}>
{icon ? <FontIcon className={theme.icon} value={icon} /> : null} {icon ? <FontIcon className={theme.icon} value={icon} /> : null}
{label ? <abbr>{label}</abbr> : null} {label ? <abbr>{label}</abbr> : null}
{count && parseInt(count) !== 0 ? <small>{count}</small> : null} {count && parseInt(count, 10) !== 0 ? <small>{count}</small> : null}
{children} {children}
</a> </a>
); );
@ -26,19 +26,19 @@ Link.propTypes = {
count: PropTypes.number, count: PropTypes.number,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
label: PropTypes.string, label: PropTypes.string,
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
icon: PropTypes.string, icon: PropTypes.string,
link: PropTypes.string link: PropTypes.string,
}) }),
}; };
Link.defaultProps = { Link.defaultProps = {
active: false, active: false,
className: '' className: '',
}; };
export default themr(LINK)(Link); export default themr(LINK)(Link);

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LINK } from '../identifiers.js'; import { LINK } from '../identifiers';
import { Link } from './Link.js'; import { Link } from './Link';
import theme from './theme.css'; import theme from './theme.css';
const ThemedLink = themr(LINK, theme)(Link); const ThemedLink = themr(LINK, theme)(Link);

View File

@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import InjectListItem from './ListItem.js'; import InjectListItem from './ListItem';
const mergeProp = (propName, child, parent) => ( const mergeProp = (propName, child, parent) => (
child[propName] !== undefined child[propName] !== undefined
@ -15,20 +15,18 @@ const factory = (ListItem) => {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
ripple: PropTypes.bool,
selectable: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
list: PropTypes.string list: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
ripple: false, ripple: false,
selectable: false selectable: false,
}; };
renderItems () { renderItems() {
return React.Children.map(this.props.children, (item) => { return React.Children.map(this.props.children, (item) => {
if (item === null || item === undefined) { if (item === null || item === undefined) {
return item; return item;
@ -36,15 +34,14 @@ const factory = (ListItem) => {
const selectable = mergeProp('selectable', item.props, this.props); const selectable = mergeProp('selectable', item.props, this.props);
const ripple = mergeProp('ripple', item.props, this.props); const ripple = mergeProp('ripple', item.props, this.props);
return React.cloneElement(item, { selectable, ripple }); return React.cloneElement(item, { selectable, ripple });
} else {
return React.cloneElement(item);
} }
return React.cloneElement(item);
}); });
} }
render () { render() {
return ( return (
<ul data-react-toolbox='list' className={classnames(this.props.theme.list, this.props.className)}> <ul data-react-toolbox="list" className={classnames(this.props.theme.list, this.props.className)}>
{this.renderItems()} {this.renderItems()}
</ul> </ul>
); );

View File

@ -1,14 +1,25 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import InjectCheckbox from '../checkbox/Checkbox.js'; import InjectCheckbox from '../checkbox/Checkbox';
import InjectListItemContent from './ListItemContent.js'; import InjectListItemContent from './ListItemContent';
const factory = (Checkbox, ListItemContent) => { const factory = (Checkbox, ListItemContent) => {
const ListCheckbox = ({ caption, checked, className, disabled, legend, name, onBlur, onChange, onFocus, theme }) => { const ListCheckbox = ({
caption,
checked,
className,
disabled,
legend,
name,
onBlur,
onChange,
onFocus,
theme,
}) => {
const _className = classnames(theme.item, theme.checkboxItem, { const _className = classnames(theme.item, theme.checkboxItem, {
[theme.disabled]: disabled [theme.disabled]: disabled,
}, className); }, className);
return ( return (
@ -41,13 +52,13 @@ const factory = (Checkbox, ListItemContent) => {
checkbox: PropTypes.string, checkbox: PropTypes.string,
checkboxItem: PropTypes.string, checkboxItem: PropTypes.string,
disabled: PropTypes.string, disabled: PropTypes.string,
item: PropTypes.string item: PropTypes.string,
}) }),
}; };
ListCheckbox.defaultProps = { ListCheckbox.defaultProps = {
checked: false, checked: false,
disabled: false disabled: false,
}; };
return ListCheckbox; return ListCheckbox;

View File

@ -1,8 +1,8 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
const ListDivider = ({inset, theme}) => ( const ListDivider = ({ inset, theme }) => (
<hr className={inset ? `${theme.divider} ${theme.inset}` : theme.divider} /> <hr className={inset ? `${theme.divider} ${theme.inset}` : theme.divider} />
); );
@ -10,12 +10,12 @@ ListDivider.propTypes = {
inset: PropTypes.bool, inset: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
divider: PropTypes.string, divider: PropTypes.string,
inset: PropTypes.string inset: PropTypes.string,
}) }),
}; };
ListDivider.defaultProps = { ListDivider.defaultProps = {
inset: false inset: false,
}; };
export default themr(LIST)(ListDivider); export default themr(LIST)(ListDivider);

View File

@ -1,28 +1,31 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import InjectListItemContent from './ListItemContent.js'; import InjectListItemContent from './ListItemContent';
import InjectListItemLayout from './ListItemLayout.js'; import InjectListItemLayout from './ListItemLayout';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
const factory = (ripple, ListItemLayout, ListItemContent) => { const factory = (ripple, ListItemLayout, ListItemContent) => {
class ListItem extends Component { class ListItem extends Component {
static propTypes = { static propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
hasRipple: PropTypes.bool,
onClick: PropTypes.func, onClick: PropTypes.func,
onMouseDown: PropTypes.func,
onTouchStart: PropTypes.func,
ripple: PropTypes.bool, ripple: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
listItem: PropTypes.string listItem: PropTypes.string,
}), }),
to: PropTypes.string to: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
disabled: false, disabled: false,
ripple: false ripple: false,
}; };
handleClick = (event) => { handleClick = (event) => {
@ -31,16 +34,16 @@ const factory = (ripple, ListItemLayout, ListItemContent) => {
} }
}; };
groupChildren () { groupChildren() {
const children = { const children = {
leftActions: [], leftActions: [],
rightActions: [], rightActions: [],
ignored: [] ignored: [],
}; };
React.Children.forEach(this.props.children, (child, i) => { React.Children.forEach(this.props.children, (child, i) => {
if (!React.isValidElement(child)) { if (!React.isValidElement(child)) {
return; return undefined;
} }
const { listItemIgnore, ...rest } = child.props; const { listItemIgnore, ...rest } = child.props;
@ -48,23 +51,34 @@ const factory = (ripple, ListItemLayout, ListItemContent) => {
if (listItemIgnore) { if (listItemIgnore) {
children.ignored.push(strippedChild); children.ignored.push(strippedChild);
return; return undefined;
} }
if (child.type === ListItemContent) { if (child.type === ListItemContent) {
children.itemContent = strippedChild; children.itemContent = strippedChild;
return; return undefined;
} }
const bucket = children.itemContent ? 'rightActions' : 'leftActions'; const bucket = children.itemContent ? 'rightActions' : 'leftActions';
children[bucket].push({...strippedChild, key: i}); children[bucket].push({ ...strippedChild, key: i });
return undefined;
}); });
return children; return children;
} }
render () { render() {
const {className, onMouseDown, onTouchStart, to, onClick, ripple: hasRipple, theme, ...other} = this.props; //eslint-disable-line no-unused-vars const {
className,
hasRipple, // eslint-disable-line no-unused-vars
onClick, // eslint-disable-line no-unused-vars
onMouseDown, // eslint-disable-line no-unused-vars
onTouchStart, // eslint-disable-line no-unused-vars
ripple:
theme,
to,
...other
} = this.props;
const children = this.groupChildren(); const children = this.groupChildren();
const content = <ListItemLayout theme={theme} {...children} {...other}/>; const content = <ListItemLayout theme={theme} {...children} {...other} />;
return ( return (
<li className={`${theme.listItem} ${className}`} onClick={this.handleClick} onMouseDown={onMouseDown} onTouchStart={onTouchStart}> <li className={`${theme.listItem} ${className}`} onClick={this.handleClick} onMouseDown={onMouseDown} onTouchStart={onTouchStart}>
{to ? <a href={this.props.to}>{content}</a> : content} {to ? <a href={this.props.to}>{content}</a> : content}

View File

@ -1,9 +1,9 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
const ListItemAction = ({action, theme}) => { const ListItemAction = ({ action, theme }) => {
const {onClick, onMouseDown} = action.props; const { onClick, onMouseDown } = action.props;
const stopRipple = onClick && !onMouseDown; const stopRipple = onClick && !onMouseDown;
const stop = e => e.stopPropagation(); const stop = e => e.stopPropagation();
return ( return (
@ -14,10 +14,10 @@ const ListItemAction = ({action, theme}) => {
}; };
ListItemAction.propTypes = { ListItemAction.propTypes = {
action: PropTypes.object, action: PropTypes.node,
theme: PropTypes.shape({ theme: PropTypes.shape({
itemAction: PropTypes.string itemAction: PropTypes.string,
}) }),
}; };
export default themr(LIST)(ListItemAction); export default themr(LIST)(ListItemAction);

View File

@ -1,28 +1,30 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import InjectListItemAction from './ListItemAction.js'; import InjectListItemAction from './ListItemAction';
const factory = (ListItemAction) => { const factory = (ListItemAction) => {
const ListItemActions = ({type, children, theme}) => { const ListItemActions = ({ type, children, theme }) => {
const validChildren = React.Children.toArray(children).filter(c => ( const validChildren = React.Children.toArray(children).filter(c => (
React.isValidElement(c) React.isValidElement(c)
)); ));
return ( return (
<span className={theme[type]}> <span className={theme[type]}>
{validChildren.map((action, i) => <ListItemAction key={i} theme={theme} action={action} />)} {validChildren.map((action, i) => (
<ListItemAction key={i} theme={theme} action={action} /> // eslint-disable-line
))}
</span> </span>
); );
}; };
ListItemActions.propTypes = { ListItemActions.propTypes = {
children: PropTypes.any, children: PropTypes.node,
theme: PropTypes.shape({ theme: PropTypes.shape({
left: PropTypes.string, left: PropTypes.string,
right: PropTypes.string right: PropTypes.string,
}), }),
type: PropTypes.oneOf(['left', 'right']) type: PropTypes.oneOf(['left', 'right']),
}; };
return ListItemActions; return ListItemActions;

View File

@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import InjectListItemText from './ListItemText.js'; import InjectListItemText from './ListItemText';
const types = ['auto', 'normal', 'large']; const types = ['auto', 'normal', 'large'];
@ -11,34 +11,34 @@ const factory = (ListItemText) => {
static propTypes = { static propTypes = {
caption: PropTypes.oneOfType([ caption: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.node PropTypes.node,
]), ]),
children: PropTypes.any, children: PropTypes.node,
legend: PropTypes.string, legend: PropTypes.string,
theme: PropTypes.shape({ theme: PropTypes.shape({
auto: PropTypes.string, auto: PropTypes.string,
itemContentRoot: PropTypes.string, itemContentRoot: PropTypes.string,
large: PropTypes.string, large: PropTypes.string,
normal: PropTypes.string normal: PropTypes.string,
}), }),
type: PropTypes.oneOf(types) type: PropTypes.oneOf(types),
}; };
getType () { getType() {
const {type, children, caption, legend} = this.props; const { type, children, caption, legend } = this.props;
let count = React.Children.count(children); let count = React.Children.count(children);
[caption, legend].forEach(s => { count += s ? 1 : 0; }); [caption, legend].forEach((s) => { count += s ? 1 : 0; });
const typeIndex = Math.min(count, types.length); const typeIndex = Math.min(count, types.length);
return type || types[typeIndex]; return type || types[typeIndex];
} }
render () { render() {
const {children, caption, legend, theme} = this.props; const { children, caption, legend, theme } = this.props;
const contentType = this.getType(); const contentType = this.getType();
const className = classnames(theme.itemContentRoot, { const className = classnames(theme.itemContentRoot, {
[theme[contentType]]: theme[contentType] [theme[contentType]]: theme[contentType],
}); });
return ( return (

View File

@ -1,36 +1,38 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import FontIcon from '../font_icon/FontIcon.js'; import { FontIcon } from '../font_icon/FontIcon';
import InjectAvatar from '../avatar/Avatar.js'; import InjectAvatar from '../avatar/Avatar';
import InjectListItemContent from './ListItemContent.js'; import InjectListItemContent from './ListItemContent';
import InjectListItemActions from './ListItemActions.js'; import InjectListItemActions from './ListItemActions';
const factory = (Avatar, ListItemContent, ListItemActions) => { const factory = (Avatar, ListItemContent, ListItemActions) => {
const ListItemLayout = (props) => { const ListItemLayout = (props) => {
const className = classnames(props.theme.item, { const className = classnames(props.theme.item, {
[props.theme.disabled]: props.disabled, [props.theme.disabled]: props.disabled,
[props.theme.selectable]: props.selectable [props.theme.selectable]: props.selectable,
}, props.className); }, props.className);
const leftActions = [ const leftActions = [
props.leftIcon && <FontIcon value={props.leftIcon} key='leftIcon'/>, props.leftIcon && <FontIcon value={props.leftIcon} key="leftIcon" />,
props.avatar && <Avatar image={props.avatar} key='avatar'/>, props.avatar && <Avatar image={props.avatar} key="avatar" />,
...props.leftActions ...props.leftActions,
]; ];
const rightActions = [ const rightActions = [
props.rightIcon && <FontIcon value={props.rightIcon} key='rightIcon'/>, props.rightIcon && <FontIcon value={props.rightIcon} key="rightIcon" />,
...props.rightActions ...props.rightActions,
]; ];
const content = props.itemContent || <ListItemContent theme={props.theme} caption={props.caption} legend={props.legend} />; const emptyActions = item => !item[0] && !item[1] && !item[2];
const emptyActions = (item) => !item[0] && !item[1] && !item[2]; const content = props.itemContent || (
<ListItemContent theme={props.theme} caption={props.caption} legend={props.legend} />
);
return ( return (
<span className={className}> <span className={className}>
{!emptyActions(leftActions) > 0 && <ListItemActions type='left' theme={props.theme}>{leftActions}</ListItemActions>} {!emptyActions(leftActions) > 0 && <ListItemActions type="left" theme={props.theme}>{leftActions}</ListItemActions>}
{content} {content}
{!emptyActions(rightActions) > 0 && <ListItemActions type='right' theme={props.theme}>{rightActions}</ListItemActions>} {!emptyActions(rightActions) > 0 && <ListItemActions type="right" theme={props.theme}>{rightActions}</ListItemActions>}
</span> </span>
); );
}; };
@ -38,36 +40,34 @@ const factory = (Avatar, ListItemContent, ListItemActions) => {
ListItemLayout.propTypes = { ListItemLayout.propTypes = {
avatar: PropTypes.oneOfType([ avatar: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
caption: PropTypes.string, caption: PropTypes.string,
children: PropTypes.any,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
itemContent: PropTypes.element, itemContent: PropTypes.element,
leftActions: PropTypes.array, leftActions: PropTypes.arrayOf(PropTypes.node),
leftIcon: PropTypes.oneOfType([ leftIcon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
legend: PropTypes.string, legend: PropTypes.string,
rightActions: PropTypes.array, rightActions: PropTypes.arrayOf(PropTypes.node),
rightIcon: PropTypes.oneOfType([ rightIcon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
selectable: PropTypes.bool, selectable: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
disabled: PropTypes.string, disabled: PropTypes.string,
item: PropTypes.string, item: PropTypes.string,
selectable: PropTypes.string selectable: PropTypes.string,
}), }),
to: PropTypes.string
}; };
ListItemLayout.defaultProps = { ListItemLayout.defaultProps = {
disabled: false, disabled: false,
selectable: false selectable: false,
}; };
return ListItemLayout; return ListItemLayout;

View File

@ -1,10 +1,10 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
const ListItemText = ({className, primary, children, theme, ...other}) => { const ListItemText = ({ className, primary, children, theme, ...other }) => {
const _className = classnames(theme.itemText, {[theme.primary]: primary}, className); const _className = classnames(theme.itemText, { [theme.primary]: primary }, className);
return ( return (
<span data-react-toolbox="list-item-text" className={_className} {...other}> <span data-react-toolbox="list-item-text" className={_className} {...other}>
{children} {children}
@ -13,17 +13,17 @@ const ListItemText = ({className, primary, children, theme, ...other}) => {
}; };
ListItemText.propTypes = { ListItemText.propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
primary: PropTypes.bool, primary: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
itemText: PropTypes.string, itemText: PropTypes.string,
primary: PropTypes.string primary: PropTypes.string,
}) }),
}; };
ListItemText.defaultProps = { ListItemText.defaultProps = {
primary: false primary: false,
}; };
export default themr(LIST)(ListItemText); export default themr(LIST)(ListItemText);

View File

@ -1,7 +1,7 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
const ListSubHeader = ({ caption, className, theme }) => ( const ListSubHeader = ({ caption, className, theme }) => (
<h5 className={classnames(theme.subheader, className)}>{caption}</h5> <h5 className={classnames(theme.subheader, className)}>{caption}</h5>
@ -10,11 +10,11 @@ const ListSubHeader = ({ caption, className, theme }) => (
ListSubHeader.propTypes = { ListSubHeader.propTypes = {
caption: PropTypes.string, caption: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
theme: PropTypes.object theme: PropTypes.object, // eslint-disable-line
}; };
ListSubHeader.defaultProps = { ListSubHeader.defaultProps = {
className: '' className: '',
}; };
export default themr(LIST)(ListSubHeader); export default themr(LIST)(ListSubHeader);

View File

@ -1,21 +1,21 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { LIST } from '../identifiers.js'; import { LIST } from '../identifiers';
import { Avatar } from '../avatar'; import { Avatar } from '../avatar';
import { Checkbox } from '../checkbox'; import { Checkbox } from '../checkbox';
import { ListItemText } from './ListItemText.js'; import { ListItemText } from './ListItemText';
import { ListItemAction } from './ListItemAction.js'; import { ListItemAction } from './ListItemAction';
import { ListSubHeader } from './ListSubHeader.js'; import { ListSubHeader } from './ListSubHeader';
import { ListDivider } from './ListDivider.js'; import { ListDivider } from './ListDivider';
import { listFactory } from './List.js'; import { listFactory } from './List';
import { listItemFactory } from './ListItem.js'; import { listItemFactory } from './ListItem';
import { listCheckboxFactory } from './ListCheckbox.js'; import { listCheckboxFactory } from './ListCheckbox';
import { listItemActionsFactory } from './ListItemActions.js'; import { listItemActionsFactory } from './ListItemActions';
import { listItemContentFactory } from './ListItemContent.js'; import { listItemContentFactory } from './ListItemContent';
import { listItemLayoutFactory } from './ListItemLayout.js'; import { listItemLayoutFactory } from './ListItemLayout';
import themedRippleFactory from '../ripple'; import themedRippleFactory from '../ripple';
import theme from './theme.css'; import theme from './theme.css';
const applyTheme = (Component) => themr(LIST, theme)(Component); const applyTheme = Component => themr(LIST, theme)(Component);
const ripple = themedRippleFactory({ centered: false, listItemIgnore: true }); const ripple = themedRippleFactory({ centered: false, listItemIgnore: true });
const ThemedListItemAction = applyTheme(ListItemAction); const ThemedListItemAction = applyTheme(ListItemAction);
const ThemedListSubHeader = applyTheme(ListSubHeader); const ThemedListSubHeader = applyTheme(ListSubHeader);
@ -23,9 +23,13 @@ const ThemedListItemText = applyTheme(ListItemText);
const ThemedListDivider = applyTheme(ListDivider); const ThemedListDivider = applyTheme(ListDivider);
const ThemedListItemContent = applyTheme(listItemContentFactory(ThemedListItemText)); const ThemedListItemContent = applyTheme(listItemContentFactory(ThemedListItemText));
const ThemedListItemActions = applyTheme(listItemActionsFactory(ThemedListItemAction)); const ThemedListItemActions = applyTheme(listItemActionsFactory(ThemedListItemAction));
const ThemedListItemLayout = applyTheme(listItemLayoutFactory(Avatar, ThemedListItemContent, ThemedListItemActions)); const ThemedListItemLayout = applyTheme(
listItemLayoutFactory(Avatar, ThemedListItemContent, ThemedListItemActions),
);
const ThemedListCheckbox = applyTheme(listCheckboxFactory(Checkbox, ThemedListItemContent)); const ThemedListCheckbox = applyTheme(listCheckboxFactory(Checkbox, ThemedListItemContent));
const ThemedListItem = applyTheme(listItemFactory(ripple, ThemedListItemLayout, ThemedListItemContent)); const ThemedListItem = applyTheme(
listItemFactory(ripple, ThemedListItemLayout, ThemedListItemContent),
);
const ThemedList = applyTheme(listFactory(ThemedListItem)); const ThemedList = applyTheme(listFactory(ThemedListItem));
export { ThemedListItemActions as ListItemActions }; export { ThemedListItemActions as ListItemActions };

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { MENU } from '../identifiers.js'; import { MENU } from '../identifiers';
import InjectIconButton from '../button/IconButton.js'; import InjectIconButton from '../button/IconButton';
import InjectMenu from './Menu.js'; import InjectMenu from './Menu';
const factory = (IconButton, Menu) => { const factory = (IconButton, Menu) => {
class IconMenu extends Component { class IconMenu extends Component {
@ -12,7 +12,7 @@ const factory = (IconButton, Menu) => {
className: PropTypes.string, className: PropTypes.string,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
iconRipple: PropTypes.bool, iconRipple: PropTypes.bool,
menuRipple: PropTypes.bool, menuRipple: PropTypes.bool,
@ -22,11 +22,11 @@ const factory = (IconButton, Menu) => {
onShow: PropTypes.func, onShow: PropTypes.func,
position: PropTypes.string, position: PropTypes.string,
selectable: PropTypes.bool, selectable: PropTypes.bool,
selected: PropTypes.any, selected: PropTypes.node,
theme: PropTypes.shape({ theme: PropTypes.shape({
icon: PropTypes.string, icon: PropTypes.string,
iconMenu: PropTypes.string iconMenu: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
@ -35,11 +35,11 @@ const factory = (IconButton, Menu) => {
iconRipple: true, iconRipple: true,
menuRipple: true, menuRipple: true,
position: 'auto', position: 'auto',
selectable: false selectable: false,
}; };
state = { state = {
active: false active: false,
} }
handleButtonClick = (event) => { handleButtonClick = (event) => {
@ -52,7 +52,7 @@ const factory = (IconButton, Menu) => {
if (this.props.onHide) this.props.onHide(); if (this.props.onHide) this.props.onHide();
} }
render () { render() {
const { const {
children, className, icon, iconRipple, inverse, menuRipple, onHide, // eslint-disable-line children, className, icon, iconRipple, inverse, menuRipple, onHide, // eslint-disable-line
onSelect, onShow, position, selectable, selected, theme, ...other onSelect, onShow, position, selectable, selected, theme, ...other

View File

@ -2,10 +2,10 @@ import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { MENU } from '../identifiers.js'; import { MENU } from '../identifiers';
import { events } from '../utils'; import { events } from '../utils';
import { getViewport } from '../utils/utils'; import { getViewport } from '../utils/utils';
import InjectMenuItem from './MenuItem.js'; import InjectMenuItem from './MenuItem';
const POSITION = { const POSITION = {
AUTO: 'auto', AUTO: 'auto',
@ -13,7 +13,7 @@ const POSITION = {
TOP_LEFT: 'topLeft', TOP_LEFT: 'topLeft',
TOP_RIGHT: 'topRight', TOP_RIGHT: 'topRight',
BOTTOM_LEFT: 'bottomLeft', BOTTOM_LEFT: 'bottomLeft',
BOTTOM_RIGHT: 'bottomRight' BOTTOM_RIGHT: 'bottomRight',
}; };
const factory = (MenuItem) => { const factory = (MenuItem) => {
@ -29,7 +29,7 @@ const factory = (MenuItem) => {
position: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])), position: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])),
ripple: PropTypes.bool, ripple: PropTypes.bool,
selectable: PropTypes.bool, selectable: PropTypes.bool,
selected: PropTypes.any, selected: PropTypes.node,
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
bottomLeft: PropTypes.string, bottomLeft: PropTypes.string,
@ -40,8 +40,8 @@ const factory = (MenuItem) => {
rippled: PropTypes.string, rippled: PropTypes.string,
static: PropTypes.string, static: PropTypes.string,
topLeft: PropTypes.string, topLeft: PropTypes.string,
topRight: PropTypes.string topRight: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
@ -49,17 +49,17 @@ const factory = (MenuItem) => {
outline: true, outline: true,
position: POSITION.STATIC, position: POSITION.STATIC,
ripple: true, ripple: true,
selectable: true selectable: true,
}; };
state = { state = {
active: this.props.active, active: this.props.active,
rippled: false rippled: false,
}; };
componentDidMount () { componentDidMount() {
this.positionTimeoutHandle = setTimeout(() => { this.positionTimeoutHandle = setTimeout(() => {
const { width, height } = this.refs.menu.getBoundingClientRect(); const { width, height } = this.menuNode.getBoundingClientRect();
const position = this.props.position === POSITION.AUTO const position = this.props.position === POSITION.AUTO
? this.calculatePosition() ? this.calculatePosition()
: this.props.position; : this.props.position;
@ -67,7 +67,7 @@ const factory = (MenuItem) => {
}); });
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.position !== nextProps.position) { if (this.props.position !== nextProps.position) {
const position = nextProps.position === POSITION.AUTO const position = nextProps.position === POSITION.AUTO
? this.calculatePosition() ? this.calculatePosition()
@ -104,64 +104,39 @@ const factory = (MenuItem) => {
} }
} }
componentWillUpdate (nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
if (!this.state.active && nextState.active) { if (!this.state.active && nextState.active) {
events.addEventsToDocument({ events.addEventsToDocument({
click: this.handleDocumentClick, click: this.handleDocumentClick,
touchstart: this.handleDocumentClick touchstart: this.handleDocumentClick,
}); });
} }
} }
componentDidUpdate (prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevState.active && !this.state.active) { if (prevState.active && !this.state.active) {
if (this.props.onHide) this.props.onHide(); if (this.props.onHide) this.props.onHide();
events.removeEventsFromDocument({ events.removeEventsFromDocument({
click: this.handleDocumentClick, click: this.handleDocumentClick,
touchstart: this.handleDocumentClick touchstart: this.handleDocumentClick,
}); });
} else if (!prevState.active && this.state.active && this.props.onShow) { } else if (!prevState.active && this.state.active && this.props.onShow) {
this.props.onShow(); this.props.onShow();
} }
} }
componentWillUnmount () { componentWillUnmount() {
if (this.state.active) { if (this.state.active) {
events.removeEventsFromDocument({ events.removeEventsFromDocument({
click: this.handleDocumentClick, click: this.handleDocumentClick,
touchstart: this.handleDocumentClick touchstart: this.handleDocumentClick,
}); });
} }
clearTimeout(this.positionTimeoutHandle); clearTimeout(this.positionTimeoutHandle);
clearTimeout(this.activateTimeoutHandle); clearTimeout(this.activateTimeoutHandle);
} }
handleDocumentClick = (event) => { getMenuStyle() {
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
this.setState({active: false, rippled: false});
}
};
handleSelect = (item, event) => {
const { value, onClick } = item.props;
if (onClick) event.persist();
this.setState({ active: false, rippled: this.props.ripple }, () => {
if (onClick) onClick(event);
if (this.props.onSelect) this.props.onSelect(value);
});
};
calculatePosition () {
const parentNode = ReactDOM.findDOMNode(this).parentNode;
if (!parentNode) return;
const {top, left, height, width} = parentNode.getBoundingClientRect();
const {height: wh, width: ww} = getViewport();
const toTop = top < ((wh / 2) - height / 2);
const toLeft = left < ((ww / 2) - width / 2);
return `${toTop ? 'top' : 'bottom'}${toLeft ? 'Left' : 'Right'}`;
}
getMenuStyle () {
const { width, height, position } = this.state; const { width, height, position } = this.state;
if (position !== POSITION.STATIC) { if (position !== POSITION.STATIC) {
if (this.state.active) { if (this.state.active) {
@ -176,50 +151,82 @@ const factory = (MenuItem) => {
return { clip: 'rect(0 0 0 0)' }; return { clip: 'rect(0 0 0 0)' };
} }
} }
return undefined;
} }
getRootStyle () { getRootStyle() {
if (this.state.position !== POSITION.STATIC) { return this.state.position !== POSITION.STATIC
return { width: this.state.width, height: this.state.height }; ? { width: this.state.width, height: this.state.height }
: undefined;
}
calculatePosition() {
const parentNode = ReactDOM.findDOMNode(this).parentNode;
if (!parentNode) return undefined;
const { top, left, height, width } = parentNode.getBoundingClientRect();
const { height: wh, width: ww } = getViewport();
const toTop = top < ((wh / 2) - (height / 2));
const toLeft = left < ((ww / 2) - (width / 2));
return `${toTop ? 'top' : 'bottom'}${toLeft ? 'Left' : 'Right'}`;
}
handleDocumentClick = (event) => {
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
this.setState({ active: false, rippled: false });
} }
};
handleSelect = (item, event) => {
const { value, onClick } = item.props;
if (onClick) event.persist();
this.setState({ active: false, rippled: this.props.ripple }, () => {
if (onClick) onClick(event);
if (this.props.onSelect) this.props.onSelect(value);
});
};
show() {
const { width, height } = this.menuNode.getBoundingClientRect();
this.setState({ active: true, width, height });
} }
renderItems () { hide() {
this.setState({ active: false });
}
renderItems() {
return React.Children.map(this.props.children, (item) => { return React.Children.map(this.props.children, (item) => {
if (!item) return item; if (!item) return item;
if (item.type === MenuItem) { if (item.type === MenuItem) {
return React.cloneElement(item, { return React.cloneElement(item, {
ripple: item.props.ripple || this.props.ripple, ripple: item.props.ripple || this.props.ripple,
selected: typeof item.props.value !== 'undefined' && this.props.selectable && item.props.value === this.props.selected, selected: typeof item.props.value !== 'undefined'
onClick: this.handleSelect.bind(this, item) && this.props.selectable
&& item.props.value === this.props.selected,
onClick: this.handleSelect.bind(this, item),
}); });
} else {
return React.cloneElement(item);
} }
return React.cloneElement(item);
}); });
} }
show () { render() {
const { width, height } = this.refs.menu.getBoundingClientRect();
this.setState({active: true, width, height});
}
hide () {
this.setState({active: false});
}
render () {
const { theme } = this.props; const { theme } = this.props;
const outlineStyle = { width: this.state.width, height: this.state.height }; const outlineStyle = { width: this.state.width, height: this.state.height };
const className = classnames([theme.menu, theme[this.state.position]], { const className = classnames([theme.menu, theme[this.state.position]], {
[theme.active]: this.state.active, [theme.active]: this.state.active,
[theme.rippled]: this.state.rippled [theme.rippled]: this.state.rippled,
}, this.props.className); }, this.props.className);
return ( return (
<div data-react-toolbox='menu' className={className} style={this.getRootStyle()}> <div data-react-toolbox="menu" className={className} style={this.getRootStyle()}>
{this.props.outline ? <div className={theme.outline} style={outlineStyle} /> : null} {this.props.outline ? <div className={theme.outline} style={outlineStyle} /> : null}
<ul ref='menu' className={theme.menuInner} style={this.getMenuStyle()}> <ul
ref={(node) => { this.menuNode = node; }}
className={theme.menuInner}
style={this.getMenuStyle()}
>
{this.renderItems()} {this.renderItems()}
</ul> </ul>
</div> </div>

View File

@ -1,15 +1,15 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { MENU } from '../identifiers.js'; import { MENU } from '../identifiers';
const MenuDivider = ({ theme }) => ( const MenuDivider = ({ theme }) => (
<hr data-react-toolbox='menu-divider' className={theme.menuDivider}/> <hr data-react-toolbox="menu-divider" className={theme.menuDivider} />
); );
MenuDivider.propTypes = { MenuDivider.propTypes = {
theme: PropTypes.shape({ theme: PropTypes.shape({
menuDivider: PropTypes.string menuDivider: PropTypes.string,
}) }),
}; };
export default themr(MENU)(MenuDivider); export default themr(MENU)(MenuDivider);

View File

@ -1,20 +1,20 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { MENU } from '../identifiers.js'; import { MENU } from '../identifiers';
import FontIcon from '../font_icon/FontIcon.js'; import { FontIcon } from '../font_icon/FontIcon';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
const factory = (ripple) => { const factory = (ripple) => {
class MenuItem extends Component { class MenuItem extends Component {
static propTypes = { static propTypes = {
caption: PropTypes.string, caption: PropTypes.string,
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
icon: PropTypes.oneOfType([ icon: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
onClick: PropTypes.func, onClick: PropTypes.func,
selected: PropTypes.bool, selected: PropTypes.bool,
@ -25,14 +25,14 @@ const factory = (ripple) => {
icon: PropTypes.string, icon: PropTypes.string,
menuItem: PropTypes.string, menuItem: PropTypes.string,
selected: PropTypes.string, selected: PropTypes.string,
shortcut: PropTypes.string shortcut: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
disabled: false, disabled: false,
selected: false selected: false,
}; };
handleClick = (event) => { handleClick = (event) => {
@ -41,16 +41,25 @@ const factory = (ripple) => {
} }
}; };
render () { render() {
const {icon, caption, children, shortcut, selected, disabled, theme, ...others} = this.props; const {
caption,
children,
disabled,
icon,
selected,
shortcut,
theme,
...others
} = this.props;
const className = classnames(theme.menuItem, { const className = classnames(theme.menuItem, {
[theme.selected]: selected, [theme.selected]: selected,
[theme.disabled]: disabled [theme.disabled]: disabled,
}, this.props.className); }, this.props.className);
return ( return (
<li {...others} data-react-toolbox='menu-item' className={className} onClick={this.handleClick}> <li {...others} data-react-toolbox="menu-item" className={className} onClick={this.handleClick}>
{icon ? <FontIcon value={icon} className={theme.icon}/> : null} {icon ? <FontIcon value={icon} className={theme.icon} /> : null}
<span className={theme.caption}>{caption}</span> <span className={theme.caption}>{caption}</span>
{shortcut ? <small className={theme.shortcut}>{shortcut}</small> : null} {shortcut ? <small className={theme.shortcut}>{shortcut}</small> : null}
{children} {children}

View File

@ -1,13 +1,14 @@
/* eslint-disable */
import expect from 'expect'; import expect from 'expect';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import ReactTestUtils from 'react-addons-test-utils'; import ReactTestUtils from 'react-addons-test-utils';
import Menu from '../Menu'; import Menu from '../Menu';
import MenuItem, {MenuItem as RawMenuItem} from '../MenuItem'; import MenuItem, { MenuItem as RawMenuItem } from '../MenuItem';
describe('MenuItem', function () { describe('MenuItem', () => {
describe('#onClick', function () { describe('#onClick', () => {
it('passes to listener the event', function () { it('passes to listener the event', () => {
let listenerCalled = false; let listenerCalled = false;
const handleClick = function (event) { const handleClick = function (event) {
listenerCalled = true; listenerCalled = true;
@ -17,7 +18,7 @@ describe('MenuItem', function () {
const tree = ReactTestUtils.renderIntoDocument( const tree = ReactTestUtils.renderIntoDocument(
<Menu> <Menu>
<MenuItem key="1" onClick={handleClick}/> <MenuItem key="1" onClick={handleClick} />
</Menu>); </Menu>);
const menuItem = ReactTestUtils.findRenderedComponentWithType(tree, RawMenuItem); const menuItem = ReactTestUtils.findRenderedComponentWithType(tree, RawMenuItem);

View File

@ -1,14 +1,14 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { MENU } from '../identifiers.js'; import { MENU } from '../identifiers';
import { IconButton } from '../button'; import { IconButton } from '../button';
import { MenuDivider } from './MenuDivider.js'; import { MenuDivider } from './MenuDivider';
import { menuItemFactory } from './MenuItem.js'; import { menuItemFactory } from './MenuItem';
import { menuFactory } from './Menu.js'; import { menuFactory } from './Menu';
import { iconMenuFactory } from './IconMenu.js'; import { iconMenuFactory } from './IconMenu';
import themedRippleFactory from '../ripple'; import themedRippleFactory from '../ripple';
import theme from './theme.css'; import theme from './theme.css';
const applyTheme = (Component) => themr(MENU, theme)(Component); const applyTheme = Component => themr(MENU, theme)(Component);
const ThemedMenuDivider = applyTheme(MenuDivider); const ThemedMenuDivider = applyTheme(MenuDivider);
const ThemedMenuItem = applyTheme(menuItemFactory(themedRippleFactory({}))); const ThemedMenuItem = applyTheme(menuItemFactory(themedRippleFactory({})));
const ThemedMenu = applyTheme(menuFactory(ThemedMenuItem)); const ThemedMenu = applyTheme(menuFactory(ThemedMenuItem));

View File

@ -1,23 +1,23 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { NAVIGATION } from '../identifiers.js'; import { NAVIGATION } from '../identifiers';
import InjectButton from '../button/Button.js'; import InjectButton from '../button/Button';
import InjectLink from '../link/Link.js'; import InjectLink from '../link/Link';
const factory = (Button, Link) => { const factory = (Button, Link) => {
const Navigation = ({ actions, children, className, routes, theme, type }) => { const Navigation = ({ actions, children, className, routes, theme, type }) => {
const _className = classnames(theme[type], className); const _className = classnames(theme[type], className);
const buttons = actions.map((action, index) => { const buttons = actions.map((action, index) => (
return <Button className={theme.button} key={index} {...action} />; <Button className={theme.button} key={index} {...action} /> // eslint-disable-line
}); ));
const links = routes.map((route, index) => { const links = routes.map((route, index) => (
return <Link className={theme.link} key={index} {...route} />; <Link className={theme.link} key={index} {...route} /> // eslint-disable-line
}); ));
return ( return (
<nav data-react-toolbox='navigation' className={_className}> <nav data-react-toolbox="navigation" className={_className}>
{links} {links}
{buttons} {buttons}
{children} {children}
@ -26,24 +26,24 @@ const factory = (Button, Link) => {
}; };
Navigation.propTypes = { Navigation.propTypes = {
actions: PropTypes.array, actions: PropTypes.array, // eslint-disable-line
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
routes: PropTypes.array, routes: PropTypes.array, // eslint-disable-line
theme: PropTypes.shape({ theme: PropTypes.shape({
button: PropTypes.string, button: PropTypes.string,
horizontal: PropTypes.string, horizontal: PropTypes.string,
link: PropTypes.string, link: PropTypes.string,
vertical: PropTypes.string vertical: PropTypes.string,
}), }),
type: PropTypes.oneOf(['vertical', 'horizontal']) type: PropTypes.oneOf(['vertical', 'horizontal']),
}; };
Navigation.defaultProps = { Navigation.defaultProps = {
actions: [], actions: [],
className: '', className: '',
type: 'horizontal', type: 'horizontal',
routes: [] routes: [],
}; };
return Navigation; return Navigation;

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { NAVIGATION } from '../identifiers.js'; import { NAVIGATION } from '../identifiers';
import { navigationFactory } from './Navigation.js'; import { navigationFactory } from './Navigation';
import { Button } from '../button'; import { Button } from '../button';
import { Link } from '../link'; import { Link } from '../link';
import theme from './theme.css'; import theme from './theme.css';

View File

@ -1,7 +1,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { OVERLAY } from '../identifiers.js'; import { OVERLAY } from '../identifiers';
class Overlay extends Component { class Overlay extends Component {
static propTypes = { static propTypes = {
@ -14,21 +14,21 @@ class Overlay extends Component {
theme: PropTypes.shape({ theme: PropTypes.shape({
active: PropTypes.string, active: PropTypes.string,
backdrop: PropTypes.string, backdrop: PropTypes.string,
overlay: PropTypes.string overlay: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
lockScroll: true lockScroll: true,
}; };
componentDidMount () { componentDidMount() {
const { active, lockScroll, onEscKeyDown } = this.props; const { active, lockScroll, onEscKeyDown } = this.props;
if (onEscKeyDown) document.body.addEventListener('keydown', this.handleEscKey.bind(this)); if (onEscKeyDown) document.body.addEventListener('keydown', this.handleEscKey.bind(this));
if (active && lockScroll) document.body.style.overflow = 'hidden'; if (active && lockScroll) document.body.style.overflow = 'hidden';
} }
componentWillUpdate (nextProps) { componentWillUpdate(nextProps) {
if (this.props.lockScroll) { if (this.props.lockScroll) {
const becomingActive = nextProps.active && !this.props.active; const becomingActive = nextProps.active && !this.props.active;
const becomingUnactive = !nextProps.active && this.props.active; const becomingUnactive = !nextProps.active && this.props.active;
@ -43,13 +43,13 @@ class Overlay extends Component {
} }
} }
componentDidUpdate (prevProps) { componentDidUpdate(prevProps) {
if (this.props.active && !prevProps.active && this.props.onEscKeyDown) { if (this.props.active && !prevProps.active && this.props.onEscKeyDown) {
document.body.addEventListener('keydown', this.handleEscKey.bind(this)); document.body.addEventListener('keydown', this.handleEscKey.bind(this));
} }
} }
componentWillUnmount () { componentWillUnmount() {
if (this.props.active && this.props.lockScroll) { if (this.props.active && this.props.lockScroll) {
if (!document.querySelectorAll('[data-react-toolbox="overlay"]')[1]) { if (!document.querySelectorAll('[data-react-toolbox="overlay"]')[1]) {
document.body.style.overflow = ''; document.body.style.overflow = '';
@ -67,7 +67,7 @@ class Overlay extends Component {
} }
} }
handleClick = event => { handleClick = (event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (this.props.onClick) { if (this.props.onClick) {
@ -75,14 +75,14 @@ class Overlay extends Component {
} }
} }
render () { render() {
const { active, className, lockScroll, theme, onEscKeyDown, ...other } = this.props; // eslint-disable-line const { active, className, lockScroll, theme, onEscKeyDown, ...other } = this.props; // eslint-disable-line
return ( return (
<div <div
{...other} {...other}
onClick={this.handleClick} onClick={this.handleClick}
className={classnames(theme.overlay, { className={classnames(theme.overlay, {
[theme.active]: active [theme.active]: active,
}, className)} }, className)}
/> />
); );

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { OVERLAY } from '../identifiers.js'; import { OVERLAY } from '../identifiers';
import { Overlay } from './Overlay.js'; import { Overlay } from './Overlay';
import theme from './theme.css'; import theme from './theme.css';
const ThemedOverlay = themr(OVERLAY, theme)(Overlay); const ThemedOverlay = themr(OVERLAY, theme)(Overlay);

View File

@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { PROGRESS_BAR } from '../identifiers.js'; import { PROGRESS_BAR } from '../identifiers';
import prefixer from '../utils/prefixer.js'; import prefixer from '../utils/prefixer';
class ProgressBar extends Component { class ProgressBar extends Component {
static propTypes = { static propTypes = {
@ -21,10 +21,10 @@ class ProgressBar extends Component {
linear: PropTypes.string, linear: PropTypes.string,
multicolor: PropTypes.string, multicolor: PropTypes.string,
path: PropTypes.string, path: PropTypes.string,
value: PropTypes.string value: PropTypes.string,
}), }),
type: PropTypes.oneOf(['linear', 'circular']), type: PropTypes.oneOf(['linear', 'circular']),
value: PropTypes.number value: PropTypes.number,
}; };
static defaultProps = { static defaultProps = {
@ -35,61 +35,60 @@ class ProgressBar extends Component {
mode: 'indeterminate', mode: 'indeterminate',
multicolor: false, multicolor: false,
type: 'linear', type: 'linear',
value: 0 value: 0,
}; };
calculateRatio (value) { calculateRatio(value) {
if (value < this.props.min) return 0; if (value < this.props.min) return 0;
if (value > this.props.max) return 1; if (value > this.props.max) return 1;
return (value - this.props.min) / (this.props.max - this.props.min); return (value - this.props.min) / (this.props.max - this.props.min);
} }
circularStyle () { circularStyle() {
if (this.props.mode !== 'indeterminate') { return this.props.mode !== 'indeterminate'
return {strokeDasharray: `${2 * Math.PI * 25 * this.calculateRatio(this.props.value)}, 400`}; ? { strokeDasharray: `${2 * Math.PI * 25 * this.calculateRatio(this.props.value)}, 400` }
} : undefined;
} }
linearStyle () { linearStyle() {
if (this.props.mode !== 'indeterminate') { if (this.props.mode !== 'indeterminate') {
return { return {
buffer: prefixer({transform: `scaleX(${this.calculateRatio(this.props.buffer)})`}), buffer: prefixer({ transform: `scaleX(${this.calculateRatio(this.props.buffer)})` }),
value: prefixer({transform: `scaleX(${this.calculateRatio(this.props.value)})`}) value: prefixer({ transform: `scaleX(${this.calculateRatio(this.props.value)})` }),
}; };
} else {
return {};
} }
return {};
} }
renderCircular () { renderCircular() {
return ( return (
<svg className={this.props.theme.circle} viewBox="0 0 60 60"> <svg className={this.props.theme.circle} viewBox="0 0 60 60">
<circle className={this.props.theme.path} style={this.circularStyle()} cx='30' cy='30' r='25' /> <circle className={this.props.theme.path} style={this.circularStyle()} cx="30" cy="30" r="25" />
</svg> </svg>
); );
} }
renderLinear () { renderLinear() {
const {buffer, value} = this.linearStyle(); const { buffer, value } = this.linearStyle();
return ( return (
<div> <div>
<span ref='buffer' data-ref='buffer' className={this.props.theme.buffer} style={buffer}/> <span data-ref="buffer" className={this.props.theme.buffer} style={buffer} />
<span ref='value' data-ref='value' className={this.props.theme.value} style={value}/> <span data-ref="value" className={this.props.theme.value} style={value} />
</div> </div>
); );
} }
render () { render() {
const { className, disabled, max, min, mode, multicolor, type, theme, value } = this.props; const { className, disabled, max, min, mode, multicolor, type, theme, value } = this.props;
const _className = classnames(theme[type], { const _className = classnames(theme[type], {
[theme[mode]]: mode, [theme[mode]]: mode,
[theme.multicolor]: multicolor [theme.multicolor]: multicolor,
}, className); }, className);
return ( return (
<div <div
disabled={disabled} disabled={disabled}
data-react-toolbox='progress-bar' data-react-toolbox="progress-bar"
aria-valuenow={value} aria-valuenow={value}
aria-valuemin={min} aria-valuemin={min}
aria-valuemax={max} aria-valuemax={max}

View File

@ -1,3 +1,4 @@
/* eslint-disable */
import React from 'react'; import React from 'react';
import expect from 'expect'; import expect from 'expect';
import TestUtils from 'react-addons-test-utils'; import TestUtils from 'react-addons-test-utils';
@ -5,61 +6,65 @@ import ProgressBar, { ProgressBar as RawProgressBar } from '../ProgressBar';
import theme from '../theme.css'; import theme from '../theme.css';
import utils from '../../utils/testing'; import utils from '../../utils/testing';
describe('ProgressBar', function () { describe('ProgressBar', () => {
let progressBar; let progressBar;
describe('#calculateRatio', function () { describe('#calculateRatio', () => {
before(function () { before(() => {
const tree = TestUtils.renderIntoDocument(<ProgressBar min={100} max={300} theme={theme} />); const tree = TestUtils.renderIntoDocument(<ProgressBar min={100} max={300} theme={theme} />);
progressBar = TestUtils.findRenderedComponentWithType(tree, RawProgressBar); progressBar = TestUtils.findRenderedComponentWithType(tree, RawProgressBar);
}); });
it('calculates the right ratio', function () { it('calculates the right ratio', () => {
expect(progressBar.calculateRatio(150)).toEqual(0.25); expect(progressBar.calculateRatio(150)).toEqual(0.25);
}); });
it('gets 0 when value is less than min', function () { it('gets 0 when value is less than min', () => {
expect(progressBar.calculateRatio(10)).toEqual(0); expect(progressBar.calculateRatio(10)).toEqual(0);
}); });
it('gets 1 when value is more than max', function () { it('gets 1 when value is more than max', () => {
expect(progressBar.calculateRatio(400)).toEqual(1); expect(progressBar.calculateRatio(400)).toEqual(1);
}); });
}); });
describe('#render', function () { describe('#render', () => {
let buffer, value, wrapper, circle, strokeLength; let buffer,
value,
wrapper,
circle,
strokeLength;
it('renders the value and buffer bars when it is linear', function () { it('renders the value and buffer bars when it is linear', () => {
wrapper = utils.shallowRenderComponent(RawProgressBar, {theme}).props.children; wrapper = utils.shallowRenderComponent(RawProgressBar, { theme }).props.children;
expect(wrapper.props.children.length).toEqual(2); expect(wrapper.props.children.length).toEqual(2);
expect(wrapper.props.children[0].ref).toEqual('buffer'); expect(wrapper.props.children[0].ref).toEqual('buffer');
expect(wrapper.props.children[1].ref).toEqual('value'); expect(wrapper.props.children[1].ref).toEqual('value');
}); });
it('renders the value and buffer bars when it is linear', function () { it('renders the value and buffer bars when it is linear', () => {
progressBar = utils.shallowRenderComponent(RawProgressBar, {mode: 'determinate', value: 30, buffer: 60, theme}); progressBar = utils.shallowRenderComponent(RawProgressBar, { mode: 'determinate', value: 30, buffer: 60, theme });
buffer = (progressBar.props.children.props.children[0]); buffer = (progressBar.props.children.props.children[0]);
value = (progressBar.props.children.props.children[1]); value = (progressBar.props.children.props.children[1]);
expect(buffer.props.style.transform).toEqual(`scaleX(${0.6})`); expect(buffer.props.style.transform).toEqual(`scaleX(${0.6})`);
expect(value.props.style.transform).toEqual(`scaleX(${0.3})`); expect(value.props.style.transform).toEqual(`scaleX(${0.3})`);
}); });
it('renders the svg circle when it is circular', function () { it('renders the svg circle when it is circular', () => {
progressBar = utils.shallowRenderComponent(RawProgressBar, {type: 'circular', theme}); progressBar = utils.shallowRenderComponent(RawProgressBar, { type: 'circular', theme });
expect(progressBar.props.children.type).toEqual('svg'); expect(progressBar.props.children.type).toEqual('svg');
expect(progressBar.props.children.props.children.type).toEqual('circle'); expect(progressBar.props.children.props.children.type).toEqual('circle');
}); });
it('renders the proper circle length style when it is circular and determinate', function () { it('renders the proper circle length style when it is circular and determinate', () => {
progressBar = utils.shallowRenderComponent(RawProgressBar, {type: 'circular', mode: 'determinate', value: 30, theme}); progressBar = utils.shallowRenderComponent(RawProgressBar, { type: 'circular', mode: 'determinate', value: 30, theme });
circle = progressBar.props.children.props.children; circle = progressBar.props.children.props.children;
strokeLength = 2 * Math.PI * circle.props.r * 0.3; strokeLength = 2 * Math.PI * circle.props.r * 0.3;
expect(circle.props.style.strokeDasharray).toEqual(`${strokeLength}, 400`); expect(circle.props.style.strokeDasharray).toEqual(`${strokeLength}, 400`);
}); });
it('contains mode and className in its className', function () { it('contains mode and className in its className', () => {
progressBar = utils.shallowRenderComponent(RawProgressBar, {mode: 'determinate', className: 'tight', theme}); progressBar = utils.shallowRenderComponent(RawProgressBar, { mode: 'determinate', className: 'tight', theme });
expect(progressBar.props.className).toContain(theme.determinate); expect(progressBar.props.className).toContain(theme.determinate);
expect(progressBar.props.className).toContain(theme.tight); expect(progressBar.props.className).toContain(theme.tight);
}); });

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { PROGRESS_BAR } from '../identifiers.js'; import { PROGRESS_BAR } from '../identifiers';
import { ProgressBar } from './ProgressBar.js'; import { ProgressBar } from './ProgressBar';
import theme from './theme.css'; import theme from './theme.css';
const ThemedProgressBar = themr(PROGRESS_BAR, theme)(ProgressBar); const ThemedProgressBar = themr(PROGRESS_BAR, theme)(ProgressBar);

View File

@ -1,9 +1,9 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
const factory = (ripple) => { const factory = (ripple) => {
const Radio = ({checked, onMouseDown, theme, ...other}) => ( const Radio = ({ checked, onMouseDown, theme, ...other }) => (
<div <div
data-react-toolbox='radio' data-react-toolbox="radio"
className={theme[checked ? 'radioChecked' : 'radio']} className={theme[checked ? 'radioChecked' : 'radio']}
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
{...other} {...other}
@ -12,13 +12,13 @@ const factory = (ripple) => {
Radio.propTypes = { Radio.propTypes = {
checked: PropTypes.bool, checked: PropTypes.bool,
children: PropTypes.any, children: PropTypes.node,
onMouseDown: PropTypes.func, onMouseDown: PropTypes.func,
theme: PropTypes.shape({ theme: PropTypes.shape({
radio: PropTypes.string, radio: PropTypes.string,
radioChecked: PropTypes.string, radioChecked: PropTypes.string,
ripple: PropTypes.string ripple: PropTypes.string,
}) }),
}; };
return ripple(Radio); return ripple(Radio);

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { RADIO } from '../identifiers.js'; import { RADIO } from '../identifiers';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
import radioFactory from './Radio.js'; import radioFactory from './Radio';
const factory = (Radio) => { const factory = (Radio) => {
class RadioButton extends Component { class RadioButton extends Component {
@ -14,7 +14,7 @@ const factory = (Radio) => {
disabled: PropTypes.bool, disabled: PropTypes.bool,
label: PropTypes.oneOfType([ label: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.node PropTypes.node,
]), ]),
name: PropTypes.string, name: PropTypes.string,
onBlur: PropTypes.func, onBlur: PropTypes.func,
@ -26,38 +26,54 @@ const factory = (Radio) => {
disabled: PropTypes.string, disabled: PropTypes.string,
field: PropTypes.string, field: PropTypes.string,
input: PropTypes.string, input: PropTypes.string,
text: PropTypes.string text: PropTypes.string,
}), }),
value: PropTypes.any value: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
checked: false, checked: false,
className: '', className: '',
disabled: false disabled: false,
}; };
handleClick = (event) => { handleClick = (event) => {
const {checked, disabled, onChange} = this.props; const { checked, disabled, onChange } = this.props;
if (event.pageX !== 0 && event.pageY !== 0) this.blur(); if (event.pageX !== 0 && event.pageY !== 0) this.blur();
if (!disabled && !checked && onChange) onChange(event, this); if (!disabled && !checked && onChange) onChange(event, this);
}; };
blur () { blur() {
this.inputNode && this.inputNode.blur(); if (this.inputNode) {
this.inputNode.blur();
}
} }
focus () { focus() {
this.inputNode && this.inputNode.focus(); if (this.inputNode) {
this.inputNode.focus();
}
} }
render () { render() {
const { checked, children, className, disabled, label, name, onChange, // eslint-disable-line const {
onMouseEnter, onMouseLeave, theme, ...others } = this.props; checked,
children,
className,
disabled,
label,
name,
onChange, // eslint-disable-line
onMouseEnter,
onMouseLeave,
theme,
...others
} = this.props;
const _className = classnames(theme[this.props.disabled ? 'disabled' : 'field'], className); const _className = classnames(theme[this.props.disabled ? 'disabled' : 'field'], className);
return ( return (
<label <label
data-react-toolbox='radio-button' htmlFor={name}
data-react-toolbox="radio-button"
className={_className} className={_className}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
@ -70,8 +86,8 @@ const factory = (Radio) => {
name={name} name={name}
onChange={() => {}} onChange={() => {}}
onClick={this.handleClick} onClick={this.handleClick}
ref={node => { this.inputNode = node; }} ref={(node) => { this.inputNode = node; }}
type='radio' type="radio"
/> />
<Radio checked={checked} disabled={disabled} theme={theme} /> <Radio checked={checked} disabled={disabled} theme={theme} />
{label ? <span className={theme.text}>{label}</span> : null} {label ? <span className={theme.text}>{label}</span> : null}

View File

@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { RADIO } from '../identifiers.js'; import { RADIO } from '../identifiers';
import InjectRadioButton from './RadioButton.js'; import InjectRadioButton from './RadioButton';
import { isComponentOfType } from '../utils/react.js'; import { isComponentOfType } from '../utils/react';
const factory = (RadioButton) => { const factory = (RadioButton) => {
class RadioGroup extends Component { class RadioGroup extends Component {
@ -10,35 +10,34 @@ const factory = (RadioButton) => {
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
name: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
value: PropTypes.any value: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
disabled: false disabled: false,
}; };
handleChange = (value) => { handleChange = (value) => {
if (this.props.onChange) this.props.onChange(value); if (this.props.onChange) this.props.onChange(value);
}; };
renderRadioButtons () { renderRadioButtons() {
return React.Children.map(this.props.children, child => ( return React.Children.map(this.props.children, child => (
!isComponentOfType(RadioButton, child) !isComponentOfType(RadioButton, child)
? child ? child
: React.cloneElement(child, { : React.cloneElement(child, {
checked: child.props.value === this.props.value, checked: child.props.value === this.props.value,
disabled: this.props.disabled || child.props.disabled, disabled: this.props.disabled || child.props.disabled,
onChange: this.handleChange.bind(this, child.props.value) onChange: this.handleChange.bind(this, child.props.value),
}) })
)); ));
} }
render () { render() {
return ( return (
<div data-react-toolbox='radio-group' className={this.props.className}> <div data-react-toolbox="radio-group" className={this.props.className}>
{this.renderRadioButtons()} {this.renderRadioButtons()}
</div> </div>
); );

View File

@ -1,12 +1,12 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { RADIO } from '../identifiers.js'; import { RADIO } from '../identifiers';
import themedRippleFactory from '../ripple'; import themedRippleFactory from '../ripple';
import radioFactory from './Radio.js'; import radioFactory from './Radio';
import { radioButtonFactory } from './RadioButton.js'; import { radioButtonFactory } from './RadioButton';
import { radioGroupFactory } from './RadioGroup.js'; import { radioGroupFactory } from './RadioGroup';
import theme from './theme.css'; import theme from './theme.css';
const ThemedRadio = radioFactory(themedRippleFactory({ centered: true, spread: 2.6})); const ThemedRadio = radioFactory(themedRippleFactory({ centered: true, spread: 2.6 }));
const ThemedRadioButton = themr(RADIO, theme)(radioButtonFactory(ThemedRadio)); const ThemedRadioButton = themr(RADIO, theme)(radioButtonFactory(ThemedRadio));
const ThemedRadioGroup = themr(RADIO, theme)(radioGroupFactory(ThemedRadioButton)); const ThemedRadioGroup = themr(RADIO, theme)(radioGroupFactory(ThemedRadioButton));

View File

@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import dissoc from 'ramda/src/dissoc'; import dissoc from 'ramda/src/dissoc';
import { RIPPLE } from '../identifiers.js'; import { RIPPLE } from '../identifiers';
import events from '../utils/events'; import events from '../utils/events';
import prefixer from '../utils/prefixer'; import prefixer from '../utils/prefixer';
@ -13,7 +13,7 @@ const defaults = {
multiple: true, multiple: true,
passthrough: true, passthrough: true,
spread: 2, spread: 2,
theme: {} theme: {},
}; };
const rippleFactory = (options = {}) => { const rippleFactory = (options = {}) => {
@ -25,14 +25,16 @@ const rippleFactory = (options = {}) => {
spread: defaultSpread, spread: defaultSpread,
theme: defaultTheme, theme: defaultTheme,
...props ...props
} = {...defaults, ...options}; } = { ...defaults, ...options };
return ComposedComponent => { return (ComposedComponent) => {
class RippledComponent extends Component { class RippledComponent extends Component {
static propTypes = { static propTypes = {
children: PropTypes.any, children: PropTypes.node,
disabled: PropTypes.bool, disabled: PropTypes.bool,
onMouseDown: PropTypes.func,
onRippleEnded: PropTypes.func, onRippleEnded: PropTypes.func,
onTouchStart: PropTypes.func,
ripple: PropTypes.bool, ripple: PropTypes.bool,
rippleCentered: PropTypes.bool, rippleCentered: PropTypes.bool,
rippleClassName: PropTypes.string, rippleClassName: PropTypes.string,
@ -42,8 +44,8 @@ const rippleFactory = (options = {}) => {
ripple: PropTypes.string, ripple: PropTypes.string,
rippleActive: PropTypes.string, rippleActive: PropTypes.string,
rippleRestarting: PropTypes.string, rippleRestarting: PropTypes.string,
rippleWrapper: PropTypes.string rippleWrapper: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
@ -52,43 +54,83 @@ const rippleFactory = (options = {}) => {
rippleCentered: defaultCentered, rippleCentered: defaultCentered,
rippleClassName: defaultClassName, rippleClassName: defaultClassName,
rippleMultiple: defaultMultiple, rippleMultiple: defaultMultiple,
rippleSpread: defaultSpread rippleSpread: defaultSpread,
}; };
state = { state = {
ripples: {} ripples: {},
}; };
componentDidUpdate (prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
// If a new ripple was just added, add a remove event listener to its animation // If a new ripple was just added, add a remove event listener to its animation
if (Object.keys(prevState.ripples).length < Object.keys(this.state.ripples).length) { if (Object.keys(prevState.ripples).length < Object.keys(this.state.ripples).length) {
this.addRippleRemoveEventListener(this.getLastKey()); this.addRippleRemoveEventListener(this.getLastKey());
} }
} }
componentWillUnmount () { componentWillUnmount() {
// Remove document event listeners for ripple if they still exists // Remove document event listeners for ripple if they still exists
Object.keys(this.state.ripples).forEach(key => { Object.keys(this.state.ripples).forEach((key) => {
this.state.ripples[key].endRipple(); this.state.ripples[key].endRipple();
}); });
} }
/** /**
* Add an event listener to the reference with given key so when the animation transition * Find out a descriptor object for the ripple element being created depending on
* ends we can be sure that it finished and it can be safely removed from the state. * the position where the it was triggered and the component's dimensions.
* This function is called whenever a new ripple is added to the component.
* *
* @param {String} rippleKey Is the key of the ripple to add the event. * @param {Number} x Coordinate x in the viewport where ripple was triggered
* @param {Number} y Coordinate y in the viewport where ripple was triggered
* @return {Object} Descriptor element including position and size of the element
*/ */
addRippleRemoveEventListener (rippleKey) { getDescriptor(x, y) {
const self = this; const { left, top, height, width } = ReactDOM.findDOMNode(this).getBoundingClientRect();
events.addEventListenerOnTransitionEnded(this.refs[rippleKey], function onOpacityEnd (e) { const { rippleCentered: centered, rippleSpread: spread } = this.props;
if (e.propertyName === 'opacity') { return {
if (self.props.onRippleEnded) self.props.onRippleEnded(e); left: centered ? 0 : x - left - (width / 2),
events.removeEventListenerOnTransitionEnded(self.refs[rippleKey], onOpacityEnd); top: centered ? 0 : y - top - (height / 2),
self.setState({ ripples: dissoc(rippleKey, self.state.ripples) }); width: width * spread,
} };
}); }
/**
* Increments and internal counter and returns the next value as a string. It
* is used to assign key references to new ripple elements.
*
* @return {String} Key to be assigned to a ripple.
*/
getNextKey() {
this.currentCount = this.currentCount ? this.currentCount + 1 : 1;
return `ripple${this.currentCount}`;
}
/**
* Return the last generated key for a ripple element. When there is only one ripple
* and to get the reference when a ripple was just created.
*
* @return {String} The last generated ripple key.
*/
getLastKey() {
return `ripple${this.currentCount}`;
}
/**
* Variable to store the ripple references
*/
rippleNodes = {};
/**
* Determine if a ripple should start depending if its a touch event. For mobile both
* touchStart and mouseDown are launched so in case is touch we should always trigger
* but if its not we should check if a touch was already triggered to decide.
*
* @param {Boolean} isTouch True in case a touch event triggered the ripple false otherwise.
* @return {Boolean} True in case the ripple should trigger or false if it shouldn't.
*/
rippleShouldTrigger(isTouch) {
const shouldStart = isTouch ? true : !this.touchCache;
this.touchCache = isTouch;
return shouldStart;
} }
/** /**
@ -102,76 +144,46 @@ const rippleFactory = (options = {}) => {
* @param {Number} y Coordinate Y on the screen where animation should start * @param {Number} y Coordinate Y on the screen where animation should start
* @param {Boolean} isTouch Use events from touch or mouse. * @param {Boolean} isTouch Use events from touch or mouse.
*/ */
animateRipple (x, y, isTouch) { animateRipple(x, y, isTouch) {
if (this.rippleShouldTrigger(isTouch)) { if (this.rippleShouldTrigger(isTouch)) {
const { top, left, width } = this.getDescriptor(x, y); const { top, left, width } = this.getDescriptor(x, y);
const noRipplesActive = Object.keys(this.state.ripples).length === 0; const noRipplesActive = Object.keys(this.state.ripples).length === 0;
const key = this.props.rippleMultiple || noRipplesActive ? this.getNextKey() : this.getLastKey(); const key = (this.props.rippleMultiple || noRipplesActive)
? this.getNextKey()
: this.getLastKey();
const endRipple = this.addRippleDeactivateEventListener(isTouch, key); const endRipple = this.addRippleDeactivateEventListener(isTouch, key);
const initialState = { active: false, restarting: true, top, left, width, endRipple }; const initialState = { active: false, restarting: true, top, left, width, endRipple };
const runningState = { active: true, restarting: false }; const runningState = { active: true, restarting: false };
const ripples = {...this.state.ripples, [key]: initialState }; const ripples = { ...this.state.ripples, [key]: initialState };
this.setState({ ripples }, () => { this.setState({ ripples }, () => {
if (this.refs[key]) this.refs[key].offsetWidth; //eslint-disable-line no-unused-expressions if (this.rippleNodes[key]) this.rippleNodes[key].offsetWidth; // eslint-disable-line
this.setState({ ripples: { this.setState({ ripples: {
...this.state.ripples, ...this.state.ripples,
[key]: Object.assign({}, this.state.ripples[key], runningState) [key]: Object.assign({}, this.state.ripples[key], runningState),
} }); } });
}); });
} }
} }
/** /**
* Determine if a ripple should start depending if its a touch event. For mobile both * Add an event listener to the reference with given key so when the animation transition
* touchStart and mouseDown are launched so in case is touch we should always trigger * ends we can be sure that it finished and it can be safely removed from the state.
* but if its not we should check if a touch was already triggered to decide. * This function is called whenever a new ripple is added to the component.
* *
* @param {Boolean} isTouch True in case a touch event triggered the ripple false otherwise. * @param {String} rippleKey Is the key of the ripple to add the event.
* @return {Boolean} True in case the ripple should trigger or false if it shouldn't.
*/ */
rippleShouldTrigger (isTouch) { addRippleRemoveEventListener(rippleKey) {
const shouldStart = isTouch ? true : !this.touchCache; const self = this;
this.touchCache = isTouch; const rippleNode = this.rippleNodes[rippleKey];
return shouldStart; events.addEventListenerOnTransitionEnded(rippleNode, function onOpacityEnd(e) {
} if (e.propertyName === 'opacity') {
if (self.props.onRippleEnded) self.props.onRippleEnded(e);
/** events.removeEventListenerOnTransitionEnded(self.rippleNodes[rippleKey], onOpacityEnd);
* Find out a descriptor object for the ripple element being created depending on // self.rippleNodes = dissoc(rippleKey, self.rippleNodes);
* the position where the it was triggered and the component's dimensions. delete self.rippleNodes[rippleKey];
* self.setState({ ripples: dissoc(rippleKey, self.state.ripples) });
* @param {Number} x Coordinate x in the viewport where ripple was triggered }
* @param {Number} y Coordinate y in the viewport where ripple was triggered });
* @return {Object} Descriptor element including position and size of the element
*/
getDescriptor (x, y) {
const { left, top, height, width } = ReactDOM.findDOMNode(this).getBoundingClientRect();
const { rippleCentered: centered, rippleSpread: spread } = this.props;
return {
left: centered ? 0 : x - left - width / 2,
top: centered ? 0 : y - top - height / 2,
width: width * spread
};
}
/**
* Increments and internal counter and returns the next value as a string. It
* is used to assign key references to new ripple elements.
*
* @return {String} Key to be assigned to a ripple.
*/
getNextKey () {
this.currentCount = this.currentCount ? this.currentCount + 1 : 1;
return `ripple${this.currentCount}`;
}
/**
* Return the last generated key for a ripple element. When there is only one ripple
* and to get the reference when a ripple was just created.
*
* @return {String} The last generated ripple key.
*/
getLastKey () {
return `ripple${this.currentCount}`;
} }
/** /**
@ -181,9 +193,9 @@ const rippleFactory = (options = {}) => {
* *
* @param {Boolean} isTouch True in case the trigger was a touch event false otherwise. * @param {Boolean} isTouch True in case the trigger was a touch event false otherwise.
* @param {String} rippleKey It's a key to identify the ripple that should be deactivated. * @param {String} rippleKey It's a key to identify the ripple that should be deactivated.
* @return {Function} Callback function that deactivates the ripple and removes the event listener * @return {Function} Callback function that deactivates the ripple and removes the listener
*/ */
addRippleDeactivateEventListener (isTouch, rippleKey) { addRippleDeactivateEventListener(isTouch, rippleKey) {
const eventType = isTouch ? 'touchend' : 'mouseup'; const eventType = isTouch ? 'touchend' : 'mouseup';
const endRipple = this.createRippleDeactivateCallback(eventType, rippleKey); const endRipple = this.createRippleDeactivateCallback(eventType, rippleKey);
document.addEventListener(eventType, endRipple); document.addEventListener(eventType, endRipple);
@ -191,7 +203,7 @@ const rippleFactory = (options = {}) => {
} }
/** /**
* Generates a function that can be called to deactivate a given ripple and remove its finishing * Generates a function that can be called to deactivate a ripple and remove its finishing
* event listener. If is generated because we need to store it to be called on unmount in case * event listener. If is generated because we need to store it to be called on unmount in case
* the ripple is still running. * the ripple is still running.
* *
@ -199,13 +211,13 @@ const rippleFactory = (options = {}) => {
* @param {String} rippleKey Is the key representing the ripple * @param {String} rippleKey Is the key representing the ripple
* @return {Function} Callback function that deactivates the ripple and removes the listener * @return {Function} Callback function that deactivates the ripple and removes the listener
*/ */
createRippleDeactivateCallback (eventType, rippleKey) { createRippleDeactivateCallback(eventType, rippleKey) {
const self = this; const self = this;
return function endRipple () { return function endRipple() {
document.removeEventListener(eventType, endRipple); document.removeEventListener(eventType, endRipple);
self.setState({ ripples: { self.setState({ ripples: {
...self.state.ripples, ...self.state.ripples,
[rippleKey]: Object.assign({}, self.state.ripples[rippleKey], { active: false }) [rippleKey]: Object.assign({}, self.state.ripples[rippleKey], { active: false }),
} }); } });
}; };
} }
@ -226,26 +238,25 @@ const rippleFactory = (options = {}) => {
} }
}; };
renderRipple (key, className, { active, left, restarting, top, width }) { renderRipple(key, className, { active, left, restarting, top, width }) {
const scale = restarting ? 0 : 1; const scale = restarting ? 0 : 1;
const transform = `translate3d(${-width / 2 + left}px, ${-width / 2 + top}px, 0) scale(${scale})`; const transform = `translate3d(${(-width / 2) + left}px, ${(-width / 2) + top}px, 0) scale(${scale})`;
const _className = classnames(this.props.theme.ripple, { const _className = classnames(this.props.theme.ripple, {
[this.props.theme.rippleActive]: active, [this.props.theme.rippleActive]: active,
[this.props.theme.rippleRestarting]: restarting [this.props.theme.rippleRestarting]: restarting,
}, className); }, className);
return ( return (
<span key={key} data-react-toolbox='ripple' className={this.props.theme.rippleWrapper} {...props}> <span key={key} data-react-toolbox="ripple" className={this.props.theme.rippleWrapper} {...props}>
<span <span
role='ripple'
ref={key}
className={_className} className={_className}
style={prefixer({ transform }, {width, height: width})} ref={(node) => { if (node) this.rippleNodes[key] = node; }}
style={prefixer({ transform }, { width, height: width })}
/> />
</span> </span>
); );
} }
render () { render() {
const { const {
children, children,
disabled, disabled,
@ -259,9 +270,17 @@ const rippleFactory = (options = {}) => {
...other ...other
} = this.props; } = this.props;
const { ripples } = this.state; const { ripples } = this.state;
const childRipples = Object.keys(ripples).map(key => this.renderRipple(key, rippleClassName, ripples[key])); const childRipples = Object.keys(ripples).map(key =>
const childProps = { onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart, ...other }; this.renderRipple(key, rippleClassName, ripples[key]),
const finalProps = defaultPassthrough ? { ...childProps, theme, disabled } : childProps; );
const childProps = {
onMouseDown: this.handleMouseDown,
onTouchStart: this.handleTouchStart,
...other,
};
const finalProps = defaultPassthrough
? { ...childProps, theme, disabled }
: childProps;
return !ripple return !ripple
? React.createElement(ComposedComponent, finalProps, children) ? React.createElement(ComposedComponent, finalProps, children)

View File

@ -1,4 +1,4 @@
import rippleFactory from './Ripple.js'; import rippleFactory from './Ripple';
import theme from './theme.css'; import theme from './theme.css';
export default (options) => rippleFactory({ ...options, theme }); export default options => rippleFactory({ ...options, theme });

View File

@ -1,12 +1,13 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classnames from 'classnames'; import classnames from 'classnames';
import styleShape from 'react-style-proptype';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { round, range } from '../utils/utils'; import { round, range } from '../utils/utils';
import { SLIDER } from '../identifiers.js'; import { SLIDER } from '../identifiers';
import events from '../utils/events.js'; import events from '../utils/events';
import InjectProgressBar from '../progress_bar/ProgressBar.js'; import InjectProgressBar from '../progress_bar/ProgressBar';
import InjectInput from '../input/Input.js'; import InjectInput from '../input/Input';
const factory = (ProgressBar, Input) => { const factory = (ProgressBar, Input) => {
class Slider extends Component { class Slider extends Component {
@ -20,7 +21,7 @@ const factory = (ProgressBar, Input) => {
pinned: PropTypes.bool, pinned: PropTypes.bool,
snaps: PropTypes.bool, snaps: PropTypes.bool,
step: PropTypes.number, step: PropTypes.number,
style: PropTypes.object, style: styleShape,
theme: PropTypes.shape({ theme: PropTypes.shape({
container: PropTypes.string, container: PropTypes.string,
editable: PropTypes.string, editable: PropTypes.string,
@ -34,9 +35,9 @@ const factory = (ProgressBar, Input) => {
ring: PropTypes.string, ring: PropTypes.string,
slider: PropTypes.string, slider: PropTypes.string,
snap: PropTypes.string, snap: PropTypes.string,
snaps: PropTypes.string snaps: PropTypes.string,
}), }),
value: PropTypes.number value: PropTypes.number,
}; };
static defaultProps = { static defaultProps = {
@ -47,52 +48,84 @@ const factory = (ProgressBar, Input) => {
pinned: false, pinned: false,
snaps: false, snaps: false,
step: 0.01, step: 0.01,
value: 0 value: 0,
}; };
state = { state = {
inputFocused: false, inputFocused: false,
inputValue: null, inputValue: null,
sliderLength: 0, sliderLength: 0,
sliderStart: 0 sliderStart: 0,
}; };
componentDidMount () { componentDidMount() {
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
this.handleResize(); this.handleResize();
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (this.state.inputFocused && this.props.value !== nextProps.value) { if (this.state.inputFocused && this.props.value !== nextProps.value) {
this.setState({inputValue: this.valueForInput(nextProps.value)}); this.setState({ inputValue: this.valueForInput(nextProps.value) });
} }
} }
shouldComponentUpdate (nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.state.inputFocused || !nextState.inputFocused; return this.state.inputFocused || !nextState.inputFocused;
} }
componentWillUnmount () { componentWillUnmount() {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
events.removeEventsFromDocument(this.getMouseEventMap()); events.removeEventsFromDocument(this.getMouseEventMap());
events.removeEventsFromDocument(this.getTouchEventMap()); events.removeEventsFromDocument(this.getTouchEventMap());
events.removeEventsFromDocument(this.getKeyboardEvents()); events.removeEventsFromDocument(this.getKeyboardEvents());
} }
getInput() {
return this.inputNode && this.inputNode.getWrappedInstance
? this.inputNode.getWrappedInstance()
: this.inputNode;
}
getKeyboardEvents() {
return {
keydown: this.handleKeyDown,
};
}
getMouseEventMap() {
return {
mousemove: this.handleMouseMove,
mouseup: this.handleMouseUp,
};
}
getTouchEventMap() {
return {
touchmove: this.handleTouchMove,
touchend: this.handleTouchEnd,
};
}
addToValue(increment) {
let value = this.state.inputFocused ? parseFloat(this.state.inputValue) : this.props.value;
value = this.trimValue(value + increment);
if (value !== this.props.value) this.props.onChange(value);
}
handleInputFocus = () => { handleInputFocus = () => {
this.setState({ this.setState({
inputFocused: true, inputFocused: true,
inputValue: this.valueForInput(this.props.value) inputValue: this.valueForInput(this.props.value),
}); });
}; };
handleInputChange = (value) => { handleInputChange = (value) => {
this.setState({inputValue: value}); this.setState({ inputValue: value });
}; };
handleInputBlur = (event) => { handleInputBlur = (event) => {
const value = this.state.inputValue || 0; const value = this.state.inputValue || 0;
this.setState({inputFocused: false, inputValue: null}, () => { this.setState({ inputFocused: false, inputValue: null }, () => {
this.props.onChange(this.trimValue(value), event); this.props.onChange(this.trimValue(value), event);
}); });
}; };
@ -120,9 +153,9 @@ const factory = (ProgressBar, Input) => {
}; };
handleResize = (event, callback) => { handleResize = (event, callback) => {
const {left, right} = ReactDOM.findDOMNode(this.refs.progressbar).getBoundingClientRect(); const { left, right } = ReactDOM.findDOMNode(this.progressbarNode).getBoundingClientRect();
const cb = (callback) || (() => {}); const cb = (callback) || (() => {});
this.setState({sliderStart: left, sliderLength: right - left}, cb); this.setState({ sliderStart: left, sliderLength: right - left }, cb);
}; };
handleSliderBlur = () => { handleSliderBlur = () => {
@ -148,159 +181,126 @@ const factory = (ProgressBar, Input) => {
events.pauseEvent(event); events.pauseEvent(event);
}; };
addToValue (increment) { end(revents) {
let value = this.state.inputFocused ? parseFloat(this.state.inputValue) : this.props.value;
value = this.trimValue(value + increment);
if (value !== this.props.value) this.props.onChange(value);
}
getInput () {
return this.refs.input && this.refs.input.getWrappedInstance
? this.refs.input.getWrappedInstance()
: this.refs.input;
}
getKeyboardEvents () {
return {
keydown: this.handleKeyDown
};
}
getMouseEventMap () {
return {
mousemove: this.handleMouseMove,
mouseup: this.handleMouseUp
};
}
getTouchEventMap () {
return {
touchmove: this.handleTouchMove,
touchend: this.handleTouchEnd
};
}
end (revents) {
events.removeEventsFromDocument(revents); events.removeEventsFromDocument(revents);
this.setState({ pressed: false }); this.setState({ pressed: false });
} }
knobOffset () { knobOffset() {
const { max, min } = this.props; const { max, min } = this.props;
const translated = this.state.sliderLength * (this.props.value - min) / (max - min); const translated = this.state.sliderLength * ((this.props.value - min) / (max - min));
return translated * 100 / this.state.sliderLength; return (translated * 100) / this.state.sliderLength;
} }
move (position) { move(position) {
const newValue = this.positionToValue(position); const newValue = this.positionToValue(position);
if (newValue !== this.props.value) this.props.onChange(newValue); if (newValue !== this.props.value) this.props.onChange(newValue);
} }
positionToValue (position) { positionToValue(position) {
const { sliderStart: start, sliderLength: length } = this.state; const { sliderStart: start, sliderLength: length } = this.state;
const { max, min, step } = this.props; const { max, min, step } = this.props;
const pos = (position.x - start) / length * (max - min); const pos = ((position.x - start) / length) * (max - min);
return this.trimValue(Math.round(pos / step) * step + min); return this.trimValue((Math.round(pos / step) * step) + min);
} }
start (position) { start(position) {
this.handleResize(null, () => { this.handleResize(null, () => {
this.setState({pressed: true}); this.setState({ pressed: true });
this.props.onChange(this.positionToValue(position)); this.props.onChange(this.positionToValue(position));
}); });
} }
stepDecimals () { stepDecimals() {
return (this.props.step.toString().split('.')[1] || []).length; return (this.props.step.toString().split('.')[1] || []).length;
} }
trimValue (value) { trimValue(value) {
if (value < this.props.min) return this.props.min; if (value < this.props.min) return this.props.min;
if (value > this.props.max) return this.props.max; if (value > this.props.max) return this.props.max;
return round(value, this.stepDecimals()); return round(value, this.stepDecimals());
} }
valueForInput (value) { valueForInput(value) {
const decimals = this.stepDecimals(); const decimals = this.stepDecimals();
return decimals > 0 ? value.toFixed(decimals) : value.toString(); return decimals > 0 ? value.toFixed(decimals) : value.toString();
} }
renderSnaps () { renderSnaps() {
if (this.props.snaps) { if (!this.props.snaps) return undefined;
return ( return (
<div ref='snaps' className={this.props.theme.snaps}> <div className={this.props.theme.snaps}>
{range(0, (this.props.max - this.props.min) / this.props.step).map(i => { {range(0, (this.props.max - this.props.min) / this.props.step).map(i =>
return <div key={`span-${i}`} className={this.props.theme.snap} />; <div key={`span-${i}`} className={this.props.theme.snap} />,
})} )}
</div> </div>
); );
}
} }
renderInput () { renderInput() {
if (this.props.editable) { if (!this.props.editable) return undefined;
const value = this.state.inputFocused ? this.state.inputValue : this.valueForInput(this.props.value); return (
return ( <Input
<Input ref={(node) => { this.inputNode = node; }}
ref='input' className={this.props.theme.input}
className={this.props.theme.input} disabled={this.props.disabled}
disabled={this.props.disabled} onFocus={this.handleInputFocus}
onFocus={this.handleInputFocus} onChange={this.handleInputChange}
onChange={this.handleInputChange} onBlur={this.handleInputBlur}
onBlur={this.handleInputBlur} value={this.state.inputFocused
value={value} ? this.state.inputValue
/> : this.valueForInput(this.props.value)}
); />
} );
} }
render () { render() {
const { theme } = this.props; const { theme } = this.props;
const knobStyles = {left: `${this.knobOffset()}%`}; const knobStyles = { left: `${this.knobOffset()}%` };
const className = classnames(theme.slider, { const className = classnames(theme.slider, {
[theme.editable]: this.props.editable, [theme.editable]: this.props.editable,
[theme.disabled]: this.props.disabled, [theme.disabled]: this.props.disabled,
[theme.pinned]: this.props.pinned, [theme.pinned]: this.props.pinned,
[theme.pressed]: this.state.pressed, [theme.pressed]: this.state.pressed,
[theme.ring]: this.props.value === this.props.min [theme.ring]: this.props.value === this.props.min,
}, this.props.className); }, this.props.className);
return ( return (
<div <div
className={className} className={className}
disabled={this.props.disabled} disabled={this.props.disabled}
data-react-toolbox='slider' data-react-toolbox="slider"
onBlur={this.handleSliderBlur} onBlur={this.handleSliderBlur}
onFocus={this.handleSliderFocus} onFocus={this.handleSliderFocus}
style={this.props.style} style={this.props.style}
tabIndex='0' tabIndex="0"
> >
<div <div
ref='slider' ref={(node) => { this.sliderNode = node; }}
className={theme.container} className={theme.container}
onMouseDown={this.handleMouseDown} onMouseDown={this.handleMouseDown}
onTouchStart={this.handleTouchStart} onTouchStart={this.handleTouchStart}
> >
<div <div
ref='knob' ref={(node) => { this.knobNode = node; }}
className={theme.knob} className={theme.knob}
onMouseDown={this.handleMouseDown} onMouseDown={this.handleMouseDown}
onTouchStart={this.handleTouchStart} onTouchStart={this.handleTouchStart}
style={knobStyles} style={knobStyles}
> >
<div className={theme.innerknob} data-value={parseInt(this.props.value)}/> <div className={theme.innerknob} data-value={parseInt(this.props.value, 10)} />
</div> </div>
<div className={theme.progress}> <div className={theme.progress}>
<ProgressBar <ProgressBar
disabled={this.props.disabled} disabled={this.props.disabled}
ref='progressbar' ref={(node) => { this.progressbarNode = node; }}
className={theme.innerprogress} className={theme.innerprogress}
max={this.props.max} max={this.props.max}
min={this.props.min} min={this.props.min}
mode='determinate' mode="determinate"
value={this.props.value} value={this.props.value}
/> />
{this.renderSnaps()} {this.renderSnaps()}
</div> </div>
</div> </div>

View File

@ -1,73 +1,77 @@
/* eslint-disable */
import React from 'react'; import React from 'react';
import TestUtils from 'react-addons-test-utils'; import TestUtils from 'react-addons-test-utils';
import sinon from 'sinon'; import sinon from 'sinon';
import expect from 'expect'; import expect from 'expect';
import { ProgressBar } from '../../progress_bar/ProgressBar.js'; import { ProgressBar } from '../../progress_bar/ProgressBar';
import Input, { Input as RawInput } from '../../input/Input.js'; import Input, { Input as RawInput } from '../../input/Input';
import Slider, { Slider as RawSlider } from '../Slider.js'; import Slider, { Slider as RawSlider } from '../Slider';
import utils from '../../utils/testing'; import utils from '../../utils/testing';
import theme from '../theme.css'; import theme from '../theme.css';
describe('Slider', function () { describe('Slider', () => {
let slider, progress, input, onChange; let slider,
progress,
input,
onChange;
describe('#positionToValue', function () { describe('#positionToValue', () => {
before(function () { before(() => {
const tree = TestUtils.renderIntoDocument(<Slider min={-500} max={500} />); const tree = TestUtils.renderIntoDocument(<Slider min={-500} max={500} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
slider.setState({ sliderStart: 500, sliderLength: 100 }); slider.setState({ sliderStart: 500, sliderLength: 100 });
}); });
it('returns min when position is less than origin', function () { it('returns min when position is less than origin', () => {
expect(slider.positionToValue({x: 400})).toEqual(-500); expect(slider.positionToValue({ x: 400 })).toEqual(-500);
}); });
it('returns max when position is more and origin plus length', function () { it('returns max when position is more and origin plus length', () => {
expect(slider.positionToValue({x: 900})).toEqual(500); expect(slider.positionToValue({ x: 900 })).toEqual(500);
}); });
it('returns the proper position when the position is inside slider', function () { it('returns the proper position when the position is inside slider', () => {
expect(slider.positionToValue({x: 520})).toEqual(-300); expect(slider.positionToValue({ x: 520 })).toEqual(-300);
}); });
}); });
describe('#trimValue', function () { describe('#trimValue', () => {
before(function () { before(() => {
const tree = TestUtils.renderIntoDocument(<Slider min={0} max={100} step={0.1} />); const tree = TestUtils.renderIntoDocument(<Slider min={0} max={100} step={0.1} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
}); });
it('rounds to the proper number', function () { it('rounds to the proper number', () => {
expect(slider.trimValue(57.16)).toEqual(57.2); expect(slider.trimValue(57.16)).toEqual(57.2);
expect(slider.trimValue(57.12)).toEqual(57.10); expect(slider.trimValue(57.12)).toEqual(57.10);
}); });
it('returns min if number is less than min', function () { it('returns min if number is less than min', () => {
expect(slider.trimValue(-57.16)).toEqual(0); expect(slider.trimValue(-57.16)).toEqual(0);
}); });
it('returns max if number is more than max', function () { it('returns max if number is more than max', () => {
expect(slider.trimValue(257.16)).toEqual(100); expect(slider.trimValue(257.16)).toEqual(100);
}); });
}); });
describe('#valueForInput', function () { describe('#valueForInput', () => {
before(function () { before(() => {
const tree = TestUtils.renderIntoDocument(<Slider min={0} max={100} step={0.01} />); const tree = TestUtils.renderIntoDocument(<Slider min={0} max={100} step={0.01} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
}); });
it('returns a fixed number when an integer is given', function () { it('returns a fixed number when an integer is given', () => {
expect(slider.valueForInput(4)).toEqual('4.00'); expect(slider.valueForInput(4)).toEqual('4.00');
}); });
it('returns a fixed number when a float is given', function () { it('returns a fixed number when a float is given', () => {
expect(slider.valueForInput(4.06)).toEqual('4.06'); expect(slider.valueForInput(4.06)).toEqual('4.06');
}); });
}); });
describe('#knobOffset', function () { describe('#knobOffset', () => {
it('returns the corresponding offset for a given value and slider length/start', function () { it('returns the corresponding offset for a given value and slider length/start', () => {
const tree = TestUtils.renderIntoDocument(<Slider min={-500} max={500} value={-250} />); const tree = TestUtils.renderIntoDocument(<Slider min={-500} max={500} value={-250} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
slider.setState({ sliderStart: 500, sliderLength: 100 }); slider.setState({ sliderStart: 500, sliderLength: 100 });
@ -75,8 +79,8 @@ describe('Slider', function () {
}); });
}); });
describe('#render', function () { describe('#render', () => {
it('contains a linear progress bar with proper properties', function () { it('contains a linear progress bar with proper properties', () => {
const tree = TestUtils.renderIntoDocument(<Slider min={100} max={1000} value={140} />); const tree = TestUtils.renderIntoDocument(<Slider min={100} max={1000} value={140} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar); progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar);
@ -87,24 +91,24 @@ describe('Slider', function () {
expect(progress.props.max).toEqual(1000); expect(progress.props.max).toEqual(1000);
}); });
it('contains an input component if its editable', function () { it('contains an input component if its editable', () => {
const tree = TestUtils.renderIntoDocument(<Slider editable value={130} />); const tree = TestUtils.renderIntoDocument(<Slider editable value={130} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
input = TestUtils.findRenderedComponentWithType(slider, Input); input = TestUtils.findRenderedComponentWithType(slider, Input);
expect(parseInt(input.props.value)).toEqual(slider.props.value); expect(parseInt(input.props.value)).toEqual(slider.props.value);
}); });
it('contains the proper number of snaps when snapped', function () { it('contains the proper number of snaps when snapped', () => {
slider = utils.shallowRenderComponent(RawSlider, {editable: true, pinned: true, theme}); slider = utils.shallowRenderComponent(RawSlider, { editable: true, pinned: true, theme });
expect(slider.props.className).toContain(theme.ring); expect(slider.props.className).toContain(theme.ring);
expect(slider.props.className).toContain(theme.pinned); expect(slider.props.className).toContain(theme.pinned);
slider = utils.shallowRenderComponent(RawSlider, {editable: true, value: 50, theme}); slider = utils.shallowRenderComponent(RawSlider, { editable: true, value: 50, theme });
expect(slider.props.className).toNotContain(theme.ring); expect(slider.props.className).toNotContain(theme.ring);
}); });
}); });
describe('#events', function () { describe('#events', () => {
beforeEach(function () { beforeEach(() => {
onChange = sinon.spy(); onChange = sinon.spy();
const tree = TestUtils.renderIntoDocument(<Slider min={-500} max={500} onChange={onChange} />); const tree = TestUtils.renderIntoDocument(<Slider min={-500} max={500} onChange={onChange} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
@ -112,32 +116,32 @@ describe('Slider', function () {
slider.handleResize = (event, callback) => { callback(); }; slider.handleResize = (event, callback) => { callback(); };
}); });
it('sets pressed state when knob is clicked', function () { it('sets pressed state when knob is clicked', () => {
TestUtils.Simulate.mouseDown(slider.refs.knob); TestUtils.Simulate.mouseDown(slider.refs.knob);
expect(slider.state.pressed).toEqual(true); expect(slider.state.pressed).toEqual(true);
}); });
it('sets pressed state when knob is touched', function () { it('sets pressed state when knob is touched', () => {
TestUtils.Simulate.touchStart(slider.refs.knob, {touches: [{pageX: 200}]}); TestUtils.Simulate.touchStart(slider.refs.knob, { touches: [{ pageX: 200 }] });
expect(slider.state.pressed).toEqual(true); expect(slider.state.pressed).toEqual(true);
}); });
it('sets a proper value when the slider is clicked', function () { it('sets a proper value when the slider is clicked', () => {
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 200 }); TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 200 });
expect(onChange.called).toEqual(true); expect(onChange.called).toEqual(true);
expect(onChange.getCall(0).args[0]).toEqual(-300); expect(onChange.getCall(0).args[0]).toEqual(-300);
}); });
it('sets a proper value when the slider is touched', function () { it('sets a proper value when the slider is touched', () => {
TestUtils.Simulate.touchStart(slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]}); TestUtils.Simulate.touchStart(slider.refs.slider, { touches: [{ pageX: 200, pageY: 0 }] });
expect(onChange.called).toEqual(true); expect(onChange.called).toEqual(true);
expect(onChange.getCall(0).args[0]).toEqual(-300); expect(onChange.getCall(0).args[0]).toEqual(-300);
}); });
it('changes input value when slider changes', function () { it('changes input value when slider changes', () => {
const tree = TestUtils.renderIntoDocument(<Slider editable onChange={onChange} />); const tree = TestUtils.renderIntoDocument(<Slider editable onChange={onChange} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
slider.setState({sliderStart: 0, sliderLength: 1000}); slider.setState({ sliderStart: 0, sliderLength: 1000 });
slider.handleResize = (event, callback) => { callback(); }; slider.handleResize = (event, callback) => { callback(); };
input = TestUtils.findRenderedComponentWithType(slider, Input); input = TestUtils.findRenderedComponentWithType(slider, Input);
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 }); TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 });
@ -145,21 +149,21 @@ describe('Slider', function () {
expect(onChange.getCall(0).args[0]).toEqual(90); expect(onChange.getCall(0).args[0]).toEqual(90);
}); });
it('changes its value when input is blurred', function () { it('changes its value when input is blurred', () => {
const tree = TestUtils.renderIntoDocument(<Slider editable value={50} onChange={onChange} />); const tree = TestUtils.renderIntoDocument(<Slider editable value={50} onChange={onChange} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
input = TestUtils.findRenderedComponentWithType(slider, RawInput); input = TestUtils.findRenderedComponentWithType(slider, RawInput);
TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}}); TestUtils.Simulate.change(input.refs.input, { target: { value: '80' } });
TestUtils.Simulate.blur(input.refs.input); TestUtils.Simulate.blur(input.refs.input);
expect(onChange.called).toEqual(true); expect(onChange.called).toEqual(true);
expect(onChange.getCall(0).args[0]).toEqual(80); expect(onChange.getCall(0).args[0]).toEqual(80);
}); });
it('calls onChange callback when the value is changed', function () { it('calls onChange callback when the value is changed', () => {
const onChangeSpy = sinon.spy(); const onChangeSpy = sinon.spy();
const tree = TestUtils.renderIntoDocument(<Slider onChange={onChangeSpy} />); const tree = TestUtils.renderIntoDocument(<Slider onChange={onChangeSpy} />);
slider = TestUtils.findRenderedComponentWithType(tree, RawSlider); slider = TestUtils.findRenderedComponentWithType(tree, RawSlider);
slider.setState({sliderStart: 0, sliderLength: 1000}); slider.setState({ sliderStart: 0, sliderLength: 1000 });
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 }); TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 });
expect(onChangeSpy.called).toEqual(true); expect(onChangeSpy.called).toEqual(true);
}); });

View File

@ -1,8 +1,8 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { SLIDER } from '../identifiers.js'; import { SLIDER } from '../identifiers';
import { ProgressBar } from '../progress_bar'; import { ProgressBar } from '../progress_bar';
import { Input} from '../input'; import { Input } from '../input';
import { sliderFactory } from './Slider.js'; import { sliderFactory } from './Slider';
import theme from './theme.css'; import theme from './theme.css';
const ThemedSlider = themr(SLIDER, theme)(sliderFactory(ProgressBar, Input)); const ThemedSlider = themr(SLIDER, theme)(sliderFactory(ProgressBar, Input));

View File

@ -1,10 +1,10 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { SNACKBAR } from '../identifiers.js'; import { SNACKBAR } from '../identifiers';
import ActivableRenderer from '../hoc/ActivableRenderer.js'; import ActivableRenderer from '../hoc/ActivableRenderer';
import InjectButton from '../button/Button.js'; import InjectButton from '../button/Button';
import Portal from '../hoc/Portal.js'; import Portal from '../hoc/Portal';
const factory = (Button) => { const factory = (Button) => {
class Snackbar extends Component { class Snackbar extends Component {
@ -15,7 +15,7 @@ const factory = (Button) => {
className: PropTypes.string, className: PropTypes.string,
label: PropTypes.oneOfType([ label: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.element PropTypes.element,
]), ]),
onClick: PropTypes.func, onClick: PropTypes.func,
onTimeout: PropTypes.func, onTimeout: PropTypes.func,
@ -26,29 +26,29 @@ const factory = (Button) => {
cancel: PropTypes.string, cancel: PropTypes.string,
label: PropTypes.string, label: PropTypes.string,
snackbar: PropTypes.string, snackbar: PropTypes.string,
warning: PropTypes.string warning: PropTypes.string,
}), }),
timeout: PropTypes.number, timeout: PropTypes.number,
type: PropTypes.oneOf([ 'accept', 'cancel', 'warning' ]) type: PropTypes.oneOf(['accept', 'cancel', 'warning']),
}; };
componentDidMount () { componentDidMount() {
if (this.props.active && this.props.timeout) { if (this.props.active && this.props.timeout) {
this.scheduleTimeout(this.props); this.scheduleTimeout(this.props);
} }
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.active && nextProps.timeout) { if (nextProps.active && nextProps.timeout) {
this.scheduleTimeout(nextProps); this.scheduleTimeout(nextProps);
} }
} }
componentWillUnmount () { componentWillUnmount() {
clearTimeout(this.curTimeout); clearTimeout(this.curTimeout);
} }
scheduleTimeout = props => { scheduleTimeout = (props) => {
const { onTimeout, timeout } = props; const { onTimeout, timeout } = props;
if (this.curTimeout) clearTimeout(this.curTimeout); if (this.curTimeout) clearTimeout(this.curTimeout);
this.curTimeout = setTimeout(() => { this.curTimeout = setTimeout(() => {
@ -57,20 +57,20 @@ const factory = (Button) => {
}, timeout); }, timeout);
} }
render () { render() {
const {action, active, children, label, onClick, theme, type } = this.props; const { action, active, children, label, onClick, theme, type } = this.props;
const className = classnames([theme.snackbar, theme[type]], { const className = classnames([theme.snackbar, theme[type]], {
[theme.active]: active [theme.active]: active,
}, this.props.className); }, this.props.className);
return ( return (
<Portal className={theme.portal}> <Portal className={theme.portal}>
<div data-react-toolbox='snackbar' className={className}> <div data-react-toolbox="snackbar" className={className}>
<span className={theme.label}> <span className={theme.label}>
{label} {label}
{children} {children}
</span> </span>
{action ? <Button className={theme.button} label={action} onClick={onClick}/> : null} {action ? <Button className={theme.button} label={action} onClick={onClick} /> : null}
</div> </div>
</Portal> </Portal>
); );

View File

@ -1,6 +1,6 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { SNACKBAR } from '../identifiers.js'; import { SNACKBAR } from '../identifiers';
import { snackbarFactory } from './Snackbar.js'; import { snackbarFactory } from './Snackbar';
import { Button } from '../button'; import { Button } from '../button';
import theme from './theme.css'; import theme from './theme.css';

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { SWITCH } from '../identifiers.js'; import { SWITCH } from '../identifiers';
import rippleFactory from '../ripple/Ripple.js'; import rippleFactory from '../ripple/Ripple';
import thumbFactory from './Thumb.js'; import thumbFactory from './Thumb';
const factory = (Thumb) => { const factory = (Thumb) => {
class Switch extends Component { class Switch extends Component {
@ -25,14 +25,14 @@ const factory = (Thumb) => {
on: PropTypes.string, on: PropTypes.string,
ripple: PropTypes.string, ripple: PropTypes.string,
text: PropTypes.string, text: PropTypes.string,
thumb: PropTypes.string thumb: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
checked: false, checked: false,
className: '', className: '',
disabled: false disabled: false,
}; };
handleToggle = (event) => { handleToggle = (event) => {
@ -42,27 +42,35 @@ const factory = (Thumb) => {
} }
}; };
blur () { blur() {
this.refs.input.blur(); this.inputNode.blur();
} }
focus () { focus() {
this.refs.input.focus(); this.inputNode.focus();
} }
render () { render() {
const { className, checked, ripple, disabled, onChange, theme, ...others } = this.props; //eslint-disable-line no-unused-vars const {
checked,
className,
disabled,
onChange, // eslint-disable-line no-unused-vars
ripple,
theme,
...others
} = this.props;
const _className = classnames(theme[disabled ? 'disabled' : 'field'], className); const _className = classnames(theme[disabled ? 'disabled' : 'field'], className);
return ( return (
<label data-react-toolbox='switch' className={_className}> <label data-react-toolbox="switch" className={_className} htmlFor={this.props.name}>
<input <input
{...others} {...others}
checked={this.props.checked} checked={this.props.checked}
className={theme.input} className={theme.input}
onClick={this.handleToggle} onClick={this.handleToggle}
readOnly readOnly
ref='input' ref={(node) => { this.inputNode = node; }}
type='checkbox' type="checkbox"
/> />
<span className={theme[checked ? 'on' : 'off']}> <span className={theme[checked ? 'on' : 'off']}>
<Thumb disabled={this.props.disabled} theme={theme} ripple={ripple} /> <Thumb disabled={this.props.disabled} theme={theme} ripple={ripple} />

View File

@ -1,16 +1,17 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
const factory = (ripple) => { const factory = (ripple) => {
const Thumb = ({onMouseDown, theme, ...other}) => ( const Thumb = ({ onMouseDown, theme, ...other }) => (
<span role='thumb' className={theme.thumb} onMouseDown={onMouseDown} {...other} /> <span className={theme.thumb} onMouseDown={onMouseDown} {...other} />
); );
Thumb.propTypes = { Thumb.propTypes = {
children: PropTypes.any, children: PropTypes.node,
onMouseDown: PropTypes.func,
theme: PropTypes.shape({ theme: PropTypes.shape({
ripple: PropTypes.string, ripple: PropTypes.string,
thumb: PropTypes.string thumb: PropTypes.string,
}) }),
}; };
return ripple(Thumb); return ripple(Thumb);

View File

@ -1,11 +1,11 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { switchFactory } from './Switch.js'; import { switchFactory } from './Switch';
import { SWITCH } from '../identifiers.js'; import { SWITCH } from '../identifiers';
import thumbFactory from './Thumb.js'; import thumbFactory from './Thumb';
import themedRippleFactory from '../ripple'; import themedRippleFactory from '../ripple';
import theme from './theme.css'; import theme from './theme.css';
const applyTheme = (Component) => themr(SWITCH, theme)(Component); const applyTheme = Component => themr(SWITCH, theme)(Component);
const ripple = themedRippleFactory({ centered: true, spread: 2.6 }); const ripple = themedRippleFactory({ centered: true, spread: 2.6 });
const ThemedThumb = applyTheme(thumbFactory(ripple)); const ThemedThumb = applyTheme(thumbFactory(ripple));
const ThemedSwitch = applyTheme(switchFactory(ThemedThumb)); const ThemedSwitch = applyTheme(switchFactory(ThemedThumb));

View File

@ -1,11 +1,11 @@
import React, { PropTypes, Component, cloneElement } from 'react'; import React, { PropTypes, Component, cloneElement } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import filterReactChildren from '../utils/filter-react-children.js'; import filterReactChildren from '../utils/filter-react-children';
import isComponentOfType from '../utils/is-component-of-type.js'; import isComponentOfType from '../utils/is-component-of-type';
import { TABLE } from '../identifiers.js'; import { TABLE } from '../identifiers';
import InjectTableHead from './TableHead.js'; import InjectTableHead from './TableHead';
import InjectTableRow from './TableRow.js'; import InjectTableRow from './TableRow';
const factory = (TableHead, TableRow) => { const factory = (TableHead, TableRow) => {
const isTableHead = child => isComponentOfType(TableHead, child); const isTableHead = child => isComponentOfType(TableHead, child);
@ -20,21 +20,21 @@ const factory = (TableHead, TableRow) => {
selectable: PropTypes.bool, selectable: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
head: PropTypes.string, head: PropTypes.string,
table: PropTypes.string table: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
multiSelectable: false, multiSelectable: false,
selectable: true selectable: true,
}; };
getRowTuples = () => React.Children getRowTuples = () => React.Children
.toArray(filterReactChildren(this.props.children, isTableRow)) .toArray(filterReactChildren(this.props.children, isTableRow))
.map((child, index) => [index, Boolean(child.props.selected)]); .map((child, index) => [index, Boolean(child.props.selected)]);
handleHeadSelect = value => { handleHeadSelect = (value) => {
if (this.props.onRowSelect) { if (this.props.onRowSelect) {
this.props.onRowSelect(value this.props.onRowSelect(value
? this.getRowTuples().map(item => item[0]) ? this.getRowTuples().map(item => item[0])
@ -42,7 +42,7 @@ const factory = (TableHead, TableRow) => {
} }
}; };
handleRowSelect = idx => { handleRowSelect = (idx) => {
if (this.props.onRowSelect) { if (this.props.onRowSelect) {
if (this.props.multiSelectable) { if (this.props.multiSelectable) {
const current = this.getRowTuples().filter(item => item[1]).map(item => item[0]); const current = this.getRowTuples().filter(item => item[1]).map(item => item[0]);
@ -66,8 +66,8 @@ const factory = (TableHead, TableRow) => {
selected, selected,
multiSelectable: this.props.multiSelectable, multiSelectable: this.props.multiSelectable,
onSelect: this.handleHeadSelect, onSelect: this.handleHeadSelect,
selectable: this.props.selectable selectable: this.props.selectable,
}) }),
); );
}; };
@ -76,11 +76,11 @@ const factory = (TableHead, TableRow) => {
(child, idx) => cloneElement(child, { (child, idx) => cloneElement(child, {
idx, idx,
onSelect: this.handleRowSelect, onSelect: this.handleRowSelect,
selectable: this.props.selectable selectable: this.props.selectable,
}) }),
); );
render () { render() {
const { const {
className, className,
multiSelectable, // eslint-disable-line multiSelectable, // eslint-disable-line

View File

@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { TABLE } from '../identifiers.js'; import { TABLE } from '../identifiers';
import InjectFontIcon from '../font_icon/FontIcon.js'; import InjectFontIcon from '../font_icon/FontIcon';
const ASC = 'asc'; const ASC = 'asc';
const DESC = 'desc'; const DESC = 'desc';
@ -10,7 +10,7 @@ const DESC = 'desc';
const factory = (FontIcon) => { const factory = (FontIcon) => {
class TableCell extends Component { class TableCell extends Component {
static propTypes = { static propTypes = {
children: PropTypes.any, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
column: PropTypes.number, column: PropTypes.number,
numeric: PropTypes.bool, numeric: PropTypes.bool,
@ -25,23 +25,23 @@ const factory = (FontIcon) => {
rowCell: PropTypes.string, rowCell: PropTypes.string,
sorted: PropTypes.string, sorted: PropTypes.string,
sortIcon: PropTypes.string, sortIcon: PropTypes.string,
tableCell: PropTypes.string tableCell: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
children: PropTypes.node, children: PropTypes.node,
className: '', className: '',
numeric: false, numeric: false,
tagName: 'td' tagName: 'td',
}; };
handleClick = event => { handleClick = (event) => {
const { onClick, row, column } = this.props; const { onClick, row, column } = this.props;
if (onClick) onClick(event, column, row); if (onClick) onClick(event, column, row);
} }
render () { render() {
const { const {
children, children,
className, className,
@ -58,23 +58,23 @@ const factory = (FontIcon) => {
[theme.headCell]: tagName === 'th', [theme.headCell]: tagName === 'th',
[theme.rowCell]: tagName === 'td', [theme.rowCell]: tagName === 'td',
[theme.sorted]: sorted, [theme.sorted]: sorted,
[theme.numeric]: numeric [theme.numeric]: numeric,
}, className); }, className);
const props = { const props = {
...other, ...other,
className: _className, className: _className,
onClick: this.handleClick onClick: this.handleClick,
}; };
return ( return (
React.createElement(tagName, props, [ React.createElement(tagName, props, [
sorted && <FontIcon sorted && <FontIcon
className={classnames(theme.sortIcon, { [theme.asc]: sorted === ASC })} className={classnames(theme.sortIcon, { [theme.asc]: sorted === ASC })}
key='icon' key="icon"
value="arrow_downward" value="arrow_downward"
/>, />,
children children,
]) ])
); );
} }

View File

@ -1,8 +1,8 @@
import React, { Component, PropTypes, cloneElement } from 'react'; import React, { Component, PropTypes, cloneElement } from 'react';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { TABLE } from '../identifiers.js'; import { TABLE } from '../identifiers';
import InjectCheckbox from '../checkbox/Checkbox.js'; import InjectCheckbox from '../checkbox/Checkbox';
import InjectTableCell from './TableCell.js'; import InjectTableCell from './TableCell';
const factory = (Checkbox, TableCell) => { const factory = (Checkbox, TableCell) => {
class TableHead extends Component { class TableHead extends Component {
@ -15,19 +15,19 @@ const factory = (Checkbox, TableCell) => {
selectable: PropTypes.bool, selectable: PropTypes.bool,
selected: PropTypes.bool, selected: PropTypes.bool,
theme: PropTypes.shape({ theme: PropTypes.shape({
checkboxCell: PropTypes.string checkboxCell: PropTypes.string,
}) }),
} }
static defaultProps = { static defaultProps = {
displaySelect: true displaySelect: true,
} }
handleSelect = (value, event) => { handleSelect = (value, event) => {
this.props.onSelect(value, event); this.props.onSelect(value, event);
}; };
render () { render() {
const { const {
children, children,
displaySelect, displaySelect,
@ -49,7 +49,7 @@ const factory = (Checkbox, TableCell) => {
</TableCell>} </TableCell>}
{React.Children.map(children, (child, index) => cloneElement(child, { {React.Children.map(children, (child, index) => cloneElement(child, {
column: index, column: index,
tagName: 'th' tagName: 'th',
}))} }))}
</tr> </tr>
); );

View File

@ -1,9 +1,9 @@
import React, { cloneElement, Component, PropTypes } from 'react'; import React, { cloneElement, Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { TABLE } from '../identifiers.js'; import { TABLE } from '../identifiers';
import InjectCheckbox from '../checkbox/Checkbox.js'; import InjectCheckbox from '../checkbox/Checkbox';
import InjectTableCell from './TableCell.js'; import InjectTableCell from './TableCell';
const factory = (Checkbox, TableCell) => { const factory = (Checkbox, TableCell) => {
class TableRow extends Component { class TableRow extends Component {
@ -17,19 +17,19 @@ const factory = (Checkbox, TableCell) => {
theme: PropTypes.shape({ theme: PropTypes.shape({
checkboxCell: PropTypes.string, checkboxCell: PropTypes.string,
row: PropTypes.string, row: PropTypes.string,
selected: PropTypes.string selected: PropTypes.string,
}) }),
}; };
handleSelect = value => { handleSelect = (value) => {
const { idx, onSelect } = this.props; const { idx, onSelect } = this.props;
if (onSelect) onSelect(idx, value); if (onSelect) onSelect(idx, value);
}; };
render () { render() {
const { children, className, selectable, idx, selected, theme, ...other } = this.props; // eslint-disable-line const { children, className, selectable, idx, selected, theme, ...other } = this.props; // eslint-disable-line
const _className = classnames(theme.row, { const _className = classnames(theme.row, {
[theme.selected]: selectable && selected [theme.selected]: selectable && selected,
}, className); }, className);
return ( return (
<tr {...other} className={_className}> <tr {...other} className={_className}>
@ -38,7 +38,7 @@ const factory = (Checkbox, TableCell) => {
</TableCell>} </TableCell>}
{React.Children.map(children, (child, index) => cloneElement(child, { {React.Children.map(children, (child, index) => cloneElement(child, {
column: index, column: index,
tagName: 'td' tagName: 'td',
}))} }))}
</tr> </tr>
); );

View File

@ -1,15 +1,15 @@
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { TABLE } from '../identifiers.js'; import { TABLE } from '../identifiers';
import { Checkbox } from '../checkbox'; import { Checkbox } from '../checkbox';
import { FontIcon } from '../font_icon'; import { FontIcon } from '../font_icon';
import { tableFactory } from './Table.js'; import { tableFactory } from './Table';
import { tableHeadFactory } from './TableHead.js'; import { tableHeadFactory } from './TableHead';
import { tableRowFactory } from './TableRow.js'; import { tableRowFactory } from './TableRow';
import { tableCellFactory } from './TableCell.js'; import { tableCellFactory } from './TableCell';
import theme from './theme.css'; import theme from './theme.css';
const applyTheme = (Component) => themr(TABLE, theme)(Component); const applyTheme = Component => themr(TABLE, theme)(Component);
const ThemedTableCell = applyTheme(tableCellFactory(FontIcon)); const ThemedTableCell = applyTheme(tableCellFactory(FontIcon));
const ThemedTableHead = applyTheme(tableHeadFactory(Checkbox, ThemedTableCell)); const ThemedTableHead = applyTheme(tableHeadFactory(Checkbox, ThemedTableCell));
const ThemedTableRow = applyTheme(tableRowFactory(Checkbox, ThemedTableCell)); const ThemedTableRow = applyTheme(tableRowFactory(Checkbox, ThemedTableCell));

View File

@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { FontIcon } from '../font_icon';
import { themr } from 'react-css-themr'; import { themr } from 'react-css-themr';
import { TABS } from '../identifiers.js'; import { FontIcon } from '../font_icon';
import rippleFactory from '../ripple/Ripple.js'; import { TABS } from '../identifiers';
import rippleFactory from '../ripple/Ripple';
const factory = (ripple) => { const factory = (ripple) => {
class Tab extends Component { class Tab extends Component {
@ -26,18 +26,18 @@ const factory = (ripple) => {
label: PropTypes.string, label: PropTypes.string,
rippleWrapper: PropTypes.string, rippleWrapper: PropTypes.string,
withIcon: PropTypes.string, withIcon: PropTypes.string,
withText: PropTypes.string withText: PropTypes.string,
}) }),
}; };
static defaultProps = { static defaultProps = {
active: false, active: false,
className: '', className: '',
disabled: false, disabled: false,
hidden: false hidden: false,
}; };
componentDidUpdate (prevProps) { componentDidUpdate(prevProps) {
if (!prevProps.active && this.props.active && this.props.onActive) { if (!prevProps.active && this.props.active && this.props.onActive) {
this.props.onActive(); this.props.onActive();
} }
@ -49,7 +49,7 @@ const factory = (ripple) => {
} }
}; };
render () { render() {
const { const {
index, onActive, // eslint-disable-line index, onActive, // eslint-disable-line
active, activeClassName, children, className, disabled, hidden, label, icon, theme, ...other active, activeClassName, children, className, disabled, hidden, label, icon, theme, ...other
@ -60,15 +60,15 @@ const factory = (ripple) => {
[theme.withText]: label, [theme.withText]: label,
[theme.withIcon]: icon, [theme.withIcon]: icon,
[theme.disabled]: disabled, [theme.disabled]: disabled,
[activeClassName]: active [activeClassName]: active,
}, className); }, className);
return ( return (
<label {...other} data-react-toolbox='tab' className={_className} onClick={this.handleClick}> <div {...other} data-react-toolbox="tab" className={_className} onClick={this.handleClick}>
{icon && <FontIcon className={theme.icon} value={icon}/>} {icon && <FontIcon className={theme.icon} value={icon} />}
{label} {label}
{children} {children}
</label> </div>
); );
} }
} }

Some files were not shown because too many files have changed in this diff Show More