commit
44d96682d9
|
@ -0,0 +1 @@
|
|||
!node_modules
|
|
@ -0,0 +1,206 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"templateStrings": true,
|
||||
"superInFunctions": false,
|
||||
"classes": false,
|
||||
"modules": [2]
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"block-scoped-var": [0],
|
||||
"brace-style": [2, "1tbs", {
|
||||
"allowSingleLine": true
|
||||
}],
|
||||
"camelcase": [0],
|
||||
"comma-dangle": [2, "never"],
|
||||
"comma-spacing": [2],
|
||||
"comma-style": [2, "last"],
|
||||
"complexity": [0, 11],
|
||||
"consistent-return": [2],
|
||||
"consistent-this": [0, "that"],
|
||||
"curly": [2, "multi-line"],
|
||||
"default-case": [2],
|
||||
"dot-notation": [2, {
|
||||
"allowKeywords": true
|
||||
}],
|
||||
"eol-last": [2],
|
||||
"eqeqeq": [2],
|
||||
"func-names": [0],
|
||||
"func-style": [0, "declaration"],
|
||||
"generator-star-spacing": [2, "after"],
|
||||
"strict": [2, "always"],
|
||||
"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": [2, {
|
||||
"capIsNewExceptions": ["ToInteger", "ToObject", "ToPrimitive", "ToUint32"]
|
||||
}],
|
||||
"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-empty-label": [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-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],
|
||||
"no-unused-vars": [1, {
|
||||
"vars": "all",
|
||||
"args": "after-used"
|
||||
}],
|
||||
"no-use-before-define": [2],
|
||||
"no-void": [0],
|
||||
"no-warning-comments": [0, {
|
||||
"terms": ["todo", "fixme", "xxx"],
|
||||
"location": "start"
|
||||
}],
|
||||
"no-with": [2],
|
||||
"one-var": [0],
|
||||
"operator-assignment": [0, "always"],
|
||||
"operator-linebreak": [2, "after"],
|
||||
"padded-blocks": [0],
|
||||
"quote-props": [0],
|
||||
"radix": [0],
|
||||
"semi": [2],
|
||||
"semi-spacing": [2, {
|
||||
"before": false,
|
||||
"after": true
|
||||
}],
|
||||
"sort-vars": [0],
|
||||
"space-after-keywords": [2, "always"],
|
||||
"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-return-throw-case": [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
|
||||
}]
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
localCSS = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
active : React.PropTypes.bool
|
||||
className : React.PropTypes.string
|
||||
hideable : React.PropTypes.bool
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
type : 'left'
|
||||
|
||||
getInitialState: ->
|
||||
active : @props.active
|
||||
|
||||
# -- Events
|
||||
onClick: (event) ->
|
||||
@setState active: false if event.target is @getDOMNode()
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
className += ' hideable' if @props.hideable
|
||||
className += ' active' if @state.active
|
||||
<div data-react-toolbox='aside' className={className} onClick={@onClick}>
|
||||
<aside className={localCSS.container}>
|
||||
{ @props.children }
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
show: ->
|
||||
@setState active: true
|
||||
|
||||
hide: ->
|
||||
@setState active: false
|
|
@ -1,48 +0,0 @@
|
|||
@import '../constants'
|
||||
|
||||
WIDTH = (4 * UNIT)
|
||||
|
||||
:local(.container)
|
||||
position : absolute
|
||||
top : 0
|
||||
height : 100%
|
||||
width : WIDTH
|
||||
background-color : WHITE
|
||||
transition-property : transform
|
||||
transition-duration : ANIMATION_DURATION
|
||||
transition-timing-function : ANIMATION_EASE
|
||||
|
||||
:local(.root)
|
||||
z-index : 2
|
||||
position : fixed
|
||||
top : 0
|
||||
width : 100vw
|
||||
height : 100vh
|
||||
pointer-events : none
|
||||
transition-property : background-color
|
||||
transition-duration : ANIMATION_DURATION
|
||||
transition-timing-function : ANIMATION_EASE
|
||||
|
||||
// -- Overrides
|
||||
&:not(.hideable)
|
||||
z-index : 2
|
||||
width : WIDTH
|
||||
&.hideable
|
||||
z-index : 3
|
||||
&.active
|
||||
background-color : rgba(0,0,0,0.5)
|
||||
&.left
|
||||
left : 0
|
||||
&:not(.active) > :local(.container)
|
||||
left : 0
|
||||
transform : translateX(-100%)
|
||||
&.right
|
||||
right : 0
|
||||
&:not(.active) > :local(.container)
|
||||
right : 0
|
||||
transform : translateX(100%)
|
||||
&.active
|
||||
pointer-events : all
|
||||
> :local(.container)
|
||||
box-shadow : ZDEPTH_SHADOW_1
|
||||
transform : translateX(0%)
|
|
@ -19,11 +19,9 @@ var data = [
|
|||
| Name | Type | Default | Description|
|
||||
|:- |:-: | :- |:-|
|
||||
| **className** | String | | Sets the class-styles of the Component.|
|
||||
| **colors** | Object | | JSON data representing all colors per key in the dropdown.||
|
||||
| **dataSource** | Object | | JSON data representing all items in the component.|
|
||||
| **disabled** | Boolean | | If true, component will be disabled.|
|
||||
| **error** | String | | Sets the error string.|
|
||||
| **exact** | Bool | true | If true, component only accepts values from dataSource property..|
|
||||
| **label** | String | | The text string to use for the floating label element.|
|
||||
| **multiple** | Bool | true | If true, component can hold multiple values.|
|
||||
| **onChange** | Function | | Callback function that is fired when the components's value changes.|
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Input = require '../input'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
colors : React.PropTypes.object
|
||||
dataSource : React.PropTypes.any
|
||||
disabled : React.PropTypes.bool
|
||||
error : React.PropTypes.string
|
||||
exact : React.PropTypes.bool
|
||||
label : React.PropTypes.string
|
||||
multiple : React.PropTypes.bool
|
||||
onChange : React.PropTypes.func
|
||||
required : React.PropTypes.bool
|
||||
type : React.PropTypes.string
|
||||
value : React.PropTypes.any
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
colors : {}
|
||||
dataSource : {}
|
||||
exact : true
|
||||
multiple : true
|
||||
type : 'text'
|
||||
|
||||
getInitialState: ->
|
||||
focus : false
|
||||
dataSource : _index @props.dataSource
|
||||
suggestions : {}
|
||||
values : {}
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@setValue @props.value if @props.value
|
||||
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
@setState dataSource: _index next_props.dataSource if next_props.dataSource
|
||||
|
||||
# -- Events
|
||||
onFocus: ->
|
||||
@refs.suggestions.getDOMNode().scrollTop = 0
|
||||
@setState focus: true, suggestions: @_getSuggestions()
|
||||
|
||||
onChange: ->
|
||||
suggestions = {}
|
||||
value = @refs.input.getValue().toLowerCase().trim()
|
||||
if value.length > 0
|
||||
@setState focus: true, suggestions: suggestions = @_getSuggestions value
|
||||
@setState focus: false if Object.keys(suggestions).length is 0
|
||||
|
||||
onKeyPress: (event) ->
|
||||
key_ascii = event.which
|
||||
query = @refs.input.getValue().trim()
|
||||
|
||||
if @state.focus
|
||||
children = @refs.suggestions.getDOMNode().children
|
||||
for child, index in children when child.classList.contains 'active'
|
||||
child.classList.remove 'active'
|
||||
query = child.getAttribute 'id'
|
||||
break
|
||||
|
||||
if key_ascii is 13 and query isnt ''
|
||||
for key, label of @state.suggestions when query.toLowerCase() is label.toLowerCase()
|
||||
suggestion = {"#{key}": label}
|
||||
break
|
||||
unless @props.exact
|
||||
@_addValue suggestion or {"#{query}": query}
|
||||
else if suggestion
|
||||
@_addValue suggestion
|
||||
|
||||
else if @state.focus and key_ascii in [40, 38]
|
||||
if key_ascii is 40
|
||||
index = if index >= children.length - 1 then 0 else index + 1
|
||||
else
|
||||
index = if index is 0 then children.length - 1 else index - 1
|
||||
children[index].classList.add 'active'
|
||||
|
||||
onBlur: (event) ->
|
||||
setTimeout (=> @setState focus: false, suggestions: {}), 300
|
||||
|
||||
onSelect: (event) ->
|
||||
key = event.target.getAttribute 'id'
|
||||
@_addValue {"#{key}": @state.suggestions[key]}
|
||||
|
||||
onRefreshSelection: (event) ->
|
||||
children = @refs.suggestions.getDOMNode().children
|
||||
for child, index in children when child.classList.contains 'active'
|
||||
child.classList.remove 'active'
|
||||
break
|
||||
|
||||
onDelete: (event) ->
|
||||
delete @state.values[event.target.getAttribute 'id']
|
||||
@setState focus: false, values: @state.values
|
||||
@props.onChange? @
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
className += ' focus' if @state.focus
|
||||
<div data-react-toolbox='autocomplete' className={className}>
|
||||
{ <label>{@props.label}</label> if @props.label }
|
||||
{
|
||||
if @props.multiple
|
||||
<ul className={localCSS.values} data-flex='horizontal wrap'
|
||||
onClick={@onDelete}>
|
||||
{
|
||||
for key, label of @state.values
|
||||
<li key={key} id={key} style={backgroundColor: @props.colors[key]}>{label}</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
<Input {...@props} ref='input' value='' label=''
|
||||
onBlur={@onBlur}
|
||||
onChange={@onChange}
|
||||
onFocus={@onFocus}
|
||||
onKeyDown={@onKeyPress}
|
||||
/>
|
||||
<ul ref='suggestions' className={localCSS.suggestions}
|
||||
onClick={@onSelect} onMouseEnter={@onRefreshSelection}>
|
||||
{<li key={key} id={key}>{label}</li> for key, label of @state.suggestions}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
getValue: ->
|
||||
if @props.multiple
|
||||
(key for key of @state.values)
|
||||
else
|
||||
Object.keys(@state.values)?[0]
|
||||
|
||||
setValue: (data = []) ->
|
||||
values = {}
|
||||
data = [data] if typeof data is 'string'
|
||||
values[key] = label for key, label of @state?.dataSource when key in data
|
||||
@state.values = values
|
||||
@setState values: values
|
||||
@refs.input.setValue values[Object.keys(values)?[0]] unless @props.multiple
|
||||
|
||||
setError: (data) ->
|
||||
@refs.input.setError data
|
||||
|
||||
# -- Internal methods
|
||||
_addValue: (value) ->
|
||||
key = Object.keys(value)[0]
|
||||
if @props.multiple
|
||||
values = @state.values
|
||||
values[key] = value[key]
|
||||
@props.onChange? @
|
||||
else
|
||||
values = value
|
||||
setTimeout (=> @props.onChange? @), 10
|
||||
@setState focus: false, values: values
|
||||
@refs.input.setValue if @props.multiple then '' else value[key]
|
||||
|
||||
_getSuggestions: (query) ->
|
||||
suggestions = {}
|
||||
for key, label of @state.dataSource when not @state.values[key]
|
||||
if not query or label.toLowerCase().trim().indexOf(query) is 0
|
||||
suggestions[key] = label
|
||||
suggestions
|
||||
|
||||
# -- Private methods
|
||||
_index = (data = {}) ->
|
||||
indexed = data
|
||||
if data.length?
|
||||
indexed = {}
|
||||
indexed[item] = item for item in data
|
||||
indexed
|
|
@ -0,0 +1,203 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import utils from '../utils';
|
||||
import Input from '../input';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Autocomplete',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
dataSource: React.PropTypes.any,
|
||||
disabled: React.PropTypes.bool,
|
||||
error: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
multiple: React.PropTypes.bool,
|
||||
onChange: React.PropTypes.func,
|
||||
required: React.PropTypes.bool,
|
||||
type: React.PropTypes.string,
|
||||
value: React.PropTypes.any
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
dataSource: {},
|
||||
multiple: true,
|
||||
type: 'text'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
dataSource: this._indexDataSource(this.props.dataSource),
|
||||
focus: false,
|
||||
query: '',
|
||||
values: new Map()
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
if (this.props.value) this.setValue(this.props.value);
|
||||
},
|
||||
|
||||
componentWillReceiveProps (props) {
|
||||
if (props.dataSource) {
|
||||
this.setState({dataSource: this._indexDataSource(props.dataSource)});
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUpdate (props, state) {
|
||||
this.refs.input.setValue(state.query);
|
||||
},
|
||||
|
||||
handleQueryChange () {
|
||||
const query = this.refs.input.getValue();
|
||||
if (this.state.query !== query) {
|
||||
this.setState({query: query});
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyPress (event) {
|
||||
if (event.which === 13 && this.state.active) {
|
||||
this._selectOption(this.state.active);
|
||||
}
|
||||
|
||||
if ([40, 38].indexOf(event.which) !== -1) {
|
||||
const suggestionsKeys = [...this._getSuggestions().keys()];
|
||||
let index = suggestionsKeys.indexOf(this.state.active) + (event.which === 40 ? +1 : -1);
|
||||
if (index < 0) index = suggestionsKeys.length - 1;
|
||||
if (index >= suggestionsKeys.length) index = 0;
|
||||
this.setState({active: suggestionsKeys[index]});
|
||||
}
|
||||
},
|
||||
|
||||
handleFocus () {
|
||||
this.refs.suggestions.getDOMNode().scrollTop = 0;
|
||||
this.setState({active: '', focus: true});
|
||||
},
|
||||
|
||||
handleBlur () {
|
||||
if (this.state.focus) this.setState({focus: false});
|
||||
},
|
||||
|
||||
handleHover (event) {
|
||||
this.setState({active: event.target.getAttribute('id')});
|
||||
},
|
||||
|
||||
handleSelect (event) {
|
||||
utils.events.pauseEvent(event);
|
||||
this._selectOption(event.target.getAttribute('id'));
|
||||
},
|
||||
|
||||
handleUnselect (event) {
|
||||
this._unselectOption(event.target.getAttribute('id'));
|
||||
},
|
||||
|
||||
_indexDataSource (data = {}) {
|
||||
if (data.length) {
|
||||
return new Map(data.map((item) => [item, item]));
|
||||
} else {
|
||||
return new Map(Object.keys(data).map((key) => [key, data[key]]));
|
||||
}
|
||||
},
|
||||
|
||||
_getSuggestions () {
|
||||
let query = this.state.query.toLowerCase().trim() || '';
|
||||
let suggestions = new Map();
|
||||
for (let [key, value] of this.state.dataSource) {
|
||||
if (!this.state.values.has(key) && value.toLowerCase().trim().startsWith(query)) {
|
||||
suggestions.set(key, value);
|
||||
}
|
||||
}
|
||||
return suggestions;
|
||||
},
|
||||
|
||||
_selectOption (key) {
|
||||
let { values, dataSource } = this.state;
|
||||
let query = !this.props.multiple ? dataSource.get(key) : '';
|
||||
values = new Map(values);
|
||||
|
||||
if (!this.props.multiple) values.clear();
|
||||
values.set(key, dataSource.get(key));
|
||||
|
||||
this.setState({focus: false, query: query, values: values}, () => {
|
||||
this.refs.input.blur();
|
||||
if (this.props.onChange) this.props.onChange(this);
|
||||
});
|
||||
},
|
||||
|
||||
_unselectOption (key) {
|
||||
if (key) {
|
||||
let values = new Map(this.state.values);
|
||||
values.delete(key);
|
||||
this.setState({focus: false, values: values}, () => {
|
||||
if (this.props.onChange) this.props.onChange(this);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getValue () {
|
||||
let values = [...this.state.values.keys()];
|
||||
return this.props.multiple ? values : (values.length > 0 ? values[0] : null);
|
||||
},
|
||||
|
||||
setValue (dataParam = []) {
|
||||
let values = new Map();
|
||||
let data = (typeof dataParam === 'string') ? [dataParam] : dataParam;
|
||||
for (let [key, value] of this.state.dataSource) {
|
||||
if (data.indexOf(key) !== -1) values.set(key, value);
|
||||
}
|
||||
this.setState({values: values, query: this.props.multiple ? '' : values.get(data[0])});
|
||||
},
|
||||
|
||||
setError (data) {
|
||||
this.input.setError(data);
|
||||
},
|
||||
|
||||
renderSelected () {
|
||||
if (this.props.multiple) {
|
||||
return (
|
||||
<ul className={css.values} data-flex='horizontal wrap' onClick={this.handleUnselect}>
|
||||
{[...this.state.values].map(([key, value]) => {
|
||||
return (<li key={key} id={key}>{value}</li>);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${css.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
if (this.state.focus) className += ' focus';
|
||||
|
||||
return (
|
||||
<div data-react-toolbox='autocomplete' className={className}>
|
||||
{this.props.label ? (<label>{this.props.label}</label>) : ''}
|
||||
{this.renderSelected()}
|
||||
<Input {...this.props} ref='input' label='' value=''
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleQueryChange}
|
||||
onFocus={this.handleFocus}
|
||||
onKeyUp={this.handleKeyPress} />
|
||||
<ul ref='suggestions'
|
||||
className={css.suggestions}
|
||||
onMouseDown={this.handleSelect}
|
||||
onMouseOver={this.handleHover}>
|
||||
{[...this._getSuggestions()].map(([key, value]) => {
|
||||
return (
|
||||
<li id={key} key={key} className={this.state.active === key ? 'active' : ''}>
|
||||
{value}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -29,7 +29,7 @@
|
|||
> *
|
||||
cursor : pointer
|
||||
padding : (SPACE / 2)
|
||||
&:hover, &.active
|
||||
&.active
|
||||
color : WHITE
|
||||
background-color : PRIMARY_LIGHT
|
||||
|
||||
|
|
|
@ -2,28 +2,24 @@
|
|||
|
||||
```
|
||||
var Button = require('react-toolbox/components/button');
|
||||
|
||||
<Button caption="Login" />
|
||||
<Button caption="Primary" className="primary" icon="access_alarm" />
|
||||
<Button caption="Secondary" className="accent" />
|
||||
<Button caption="Disabled" disabled />
|
||||
|
||||
<Button type="circle" icon="access_alarm" />
|
||||
<Button type="circle" icon="explore" className="primary" />
|
||||
<Button type="circle" icon="zoom_in" className="accent" />
|
||||
<Button type="circle" icon="input" disabled={true} />
|
||||
<Button className="accent" label="Flat button" />
|
||||
<Button className="primary" type="raised" label="Raised" />
|
||||
<Button className="accent" type="raised" label="Raised" icon="assignment_turned_in" />
|
||||
<Button className="primary" type="floating" icon="add" />
|
||||
<Button className="accent mini" type="floating" icon="add" />
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Name | Type | Default | Description|
|
||||
|:- |:-: | :- |:-|
|
||||
| **caption** | String | | The text string to use for the floating label element.|
|
||||
| **className** | String | | Set the class-styles of the Component.|
|
||||
| **disabled** | Boolean | | If true, component will be disabled.|
|
||||
| **icon** | String | | Default value using JSON data.|
|
||||
| **label** | String | | The text string to use for the floating label element.|
|
||||
| **loading** | Boolean | | If true, component will be disabled and show a loading animation.|
|
||||
| **type** | String | "text" | Type of the component, overwrite this property if you need set a different stylesheet.|
|
||||
| **ripple** | Boolean | | If true, component will have a ripple effect on click.|
|
||||
| **type** | String | "flat" | Type of the component, overwrite this property if you need set a different stylesheet.|
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
localCSS = require './style'
|
||||
FontIcon = require '../font_icon'
|
||||
Ripple = require '../ripple'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Button'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
caption : React.PropTypes.string
|
||||
className : React.PropTypes.string
|
||||
disabled : React.PropTypes.bool
|
||||
icon : React.PropTypes.string
|
||||
loading : React.PropTypes.bool
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
type : 'raised'
|
||||
|
||||
getInitialState: ->
|
||||
loading : @props.loading
|
||||
focused : false
|
||||
ripple : undefined
|
||||
|
||||
# -- Lifecycle
|
||||
componentWillReceiveProps: ->
|
||||
@setState ripple: undefined
|
||||
|
||||
# -- Events
|
||||
onClick: (event) ->
|
||||
event.preventDefault()
|
||||
client = event.target.getBoundingClientRect?()
|
||||
@setState
|
||||
focused: true
|
||||
ripple:
|
||||
left : event.pageX - client?.left
|
||||
top : event.pageY - client?.top
|
||||
width : (client?.width * 2.5)
|
||||
@props.onClick? event, @
|
||||
setTimeout (=> @setState focused: false), 450
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = @props.className
|
||||
className += " #{@props.type}" if @props.type
|
||||
className += ' focused' if @state.focused
|
||||
|
||||
<button data-react-toolbox='button'
|
||||
onClick={@onClick}
|
||||
className={localCSS.root + ' ' + className}
|
||||
disabled={@props.disabled or @state.loading}
|
||||
data-flex='horizontal center'>
|
||||
{ <FontIcon value={@props.icon} /> if @props.icon }
|
||||
{ <abbr>{@props.caption}</abbr> if @props.caption }
|
||||
<Ripple origin={@state.ripple} loading={@state.loading} />
|
||||
</button>
|
||||
|
||||
# -- Extends
|
||||
loading: (value) ->
|
||||
@setState loading: value
|
|
@ -0,0 +1,62 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import FontIcon from '../font_icon';
|
||||
import Ripple from '../ripple';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Button',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
disabled: React.PropTypes.bool,
|
||||
icon: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
loading: React.PropTypes.bool,
|
||||
ripple: React.PropTypes.bool,
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
ripple: true,
|
||||
type: 'flat'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return { loading: this.props.loading };
|
||||
},
|
||||
|
||||
handleClick (event) {
|
||||
if (this.props.onClick) this.props.onClick(event, this);
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = this.props.className;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
if (this.state.focused) className += ' focused';
|
||||
|
||||
return (
|
||||
<button
|
||||
className={css.root + ' ' + className}
|
||||
data-flex='horizontal center'
|
||||
data-react-toolbox='button'
|
||||
disabled={this.props.disabled || this.state.loading}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{ this.props.icon ? <FontIcon value={this.props.icon}/> : null }
|
||||
{ this.props.label ? <abbr>{this.props.label}</abbr> : null }
|
||||
{ this.props.ripple ? <Ripple loading={this.props.loading}/> : null }
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
loading (value) {
|
||||
this.setState({loading: value});
|
||||
}
|
||||
});
|
|
@ -51,10 +51,17 @@
|
|||
> [data-react-toolbox='icon']
|
||||
line-height : BUTTON_CIRCLE_HEIGHT
|
||||
|
||||
&.mini
|
||||
width : BUTTON_CIRCLE_MINI_HEIGHT
|
||||
height : BUTTON_CIRCLE_MINI_HEIGHT
|
||||
> [data-react-toolbox='icon']
|
||||
line-height : BUTTON_CIRCLE_MINI_HEIGHT
|
||||
|
||||
// Overrides
|
||||
&[disabled]
|
||||
color : darken(DIVIDER, 25%)
|
||||
background-color : DIVIDER
|
||||
pointer-events : none
|
||||
|
||||
&:not([disabled])
|
||||
cursor : pointer
|
||||
|
@ -65,7 +72,7 @@
|
|||
&:not(.primary):not(.accent)
|
||||
color : TEXT
|
||||
background-color : WHITE
|
||||
&.focused
|
||||
&:active
|
||||
box-shadow : ZDEPTH_SHADOW_2, inset 0 0 0 UNIT alpha(WHITE, 10%)
|
||||
&.primary, &.accent
|
||||
color : WHITE
|
||||
|
@ -76,6 +83,3 @@
|
|||
|
||||
&:not(.primary):not(.accent) > [data-react-toolbox='ripple']
|
||||
background-color : DIVIDER
|
||||
|
||||
> *
|
||||
pointer-events: none
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
css = require './style'
|
||||
dateUtils = require '../date_utils'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName: 'Day',
|
||||
|
||||
propTypes:
|
||||
day : React.PropTypes.number
|
||||
onClick : React.PropTypes.func
|
||||
selectedDate : React.PropTypes.object
|
||||
viewDate : React.PropTypes.object
|
||||
|
||||
_dayStyle: ->
|
||||
marginLeft: "#{dateUtils.firstWeekDay(@props.viewDate) * 100/7}%"
|
||||
|
||||
_isSelected: () ->
|
||||
isSameYear = @props.viewDate.getFullYear() == @props.selectedDate.getFullYear()
|
||||
isSameMonth = @props.viewDate.getMonth() == @props.selectedDate.getMonth()
|
||||
isSameDay = @props.day == @props.selectedDate.getDate()
|
||||
isSameYear && isSameMonth && isSameDay
|
||||
|
||||
render: ->
|
||||
className = " #{css.day}"
|
||||
className += " active" if @_isSelected()
|
||||
dayStyle = @_dayStyle() if @props.day == 1
|
||||
|
||||
<div className={className} style={dayStyle}>
|
||||
<span onClick={@props.onClick}>{ @props.day }</span>
|
||||
</div>
|
|
@ -0,0 +1,41 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import time from '../utils/time';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Day',
|
||||
|
||||
propTypes: {
|
||||
day: React.PropTypes.number,
|
||||
onClick: React.PropTypes.func,
|
||||
selectedDate: React.PropTypes.object,
|
||||
viewDate: React.PropTypes.object
|
||||
},
|
||||
|
||||
dayStyle () {
|
||||
if (this.props.day === 1) {
|
||||
return {
|
||||
marginLeft: `${time.getFirstWeekDay(this.props.viewDate) * 100 / 7}%`
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
isSelected () {
|
||||
const sameYear = this.props.viewDate.getFullYear() === this.props.selectedDate.getFullYear();
|
||||
const sameMonth = this.props.viewDate.getMonth() === this.props.selectedDate.getMonth();
|
||||
const sameDay = this.props.day === this.props.selectedDate.getDate();
|
||||
return sameYear && sameMonth && sameDay;
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={this.isSelected() ? `${css.day} active` : css.day} style={this.dayStyle()}>
|
||||
<span onClick={this.props.onClick}>{this.props.day}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,91 +0,0 @@
|
|||
CTG = React.addons.CSSTransitionGroup
|
||||
css = require './style'
|
||||
dateUtils = require '../date_utils'
|
||||
FontIcon = require '../font_icon'
|
||||
Month = require './month'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName: 'Calendar',
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
display : React.PropTypes.oneOf(['months', 'years'])
|
||||
onChange : React.PropTypes.func
|
||||
selectedDate : React.PropTypes.object
|
||||
viewDate : React.PropTypes.object
|
||||
|
||||
getDefaultProps: ->
|
||||
display : 'months'
|
||||
selectedDate : new Date()
|
||||
|
||||
getInitialState: ->
|
||||
selectedDate : @props.selectedDate
|
||||
viewDate : @props.selectedDate
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
@_scrollToActive() if @refs.activeYear
|
||||
if prevState.selectedDate.getTime() != @state.selectedDate.getTime() && @props.onChange
|
||||
@props.onChange? @
|
||||
|
||||
# -- Events
|
||||
onDayClick: (event) ->
|
||||
@setState
|
||||
selectedDate: dateUtils.setDay(@state.viewDate, parseInt(event.target.textContent))
|
||||
|
||||
onYearClick: (event) ->
|
||||
newDate = dateUtils.setYear(@state.viewDate, parseInt(event.target.textContent))
|
||||
@setState
|
||||
selectedDate: newDate
|
||||
viewDate: newDate
|
||||
|
||||
# -- Private methods
|
||||
_scrollToActive: ->
|
||||
@refs.years.getDOMNode().scrollTop =
|
||||
@refs.activeYear.getDOMNode().offsetTop -
|
||||
@refs.years.getDOMNode().offsetHeight/2 +
|
||||
@refs.activeYear.getDOMNode().offsetHeight/2
|
||||
|
||||
# -- Public methods
|
||||
getValue: ->
|
||||
@state.selectedDate
|
||||
|
||||
incrementViewMonth: ->
|
||||
@setState
|
||||
direction: 'right'
|
||||
viewDate: dateUtils.addMonths(@state.viewDate, 1)
|
||||
|
||||
decrementViewMonth: ->
|
||||
@setState
|
||||
direction: 'left'
|
||||
viewDate: dateUtils.addMonths(@state.viewDate, -1)
|
||||
|
||||
# -- Render
|
||||
renderYear: (year) ->
|
||||
props =
|
||||
className : if year == @state.viewDate.getFullYear() then 'active' else ''
|
||||
key : "year-#{year}"
|
||||
onClick : @onYearClick
|
||||
props.ref = 'activeYear' if year == @state.viewDate.getFullYear()
|
||||
return <li {...props}>{ year }</li>
|
||||
|
||||
render: ->
|
||||
<div className={css.root}>
|
||||
{ if @props.display == 'months'
|
||||
<div className={@state.direction}>
|
||||
<FontIcon className={css.prev} value='chevron_left' onClick={@decrementViewMonth} />
|
||||
<FontIcon className={css.next} value='chevron_right' onClick={@incrementViewMonth} />
|
||||
<CTG transitionName='slide-horizontal'>
|
||||
<Month
|
||||
key={@state.viewDate.getMonth()}
|
||||
viewDate={@state.viewDate}
|
||||
selectedDate={@state.selectedDate}
|
||||
onDayClick={@onDayClick} />
|
||||
</CTG>
|
||||
</div>
|
||||
else if @props.display == 'years'
|
||||
<ul ref="years" className={css.years}>
|
||||
{ @renderYear(i) for i in [1900..2100] }
|
||||
</ul>
|
||||
}
|
||||
</div>
|
|
@ -0,0 +1,121 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import utils from '../utils';
|
||||
import FontIcon from '../font_icon';
|
||||
import Month from './month';
|
||||
|
||||
const { CSSTransitionGroup: CTG } = React.addons;
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Calendar',
|
||||
|
||||
propTypes: {
|
||||
display: React.PropTypes.oneOf(['months', 'years']),
|
||||
onChange: React.PropTypes.func,
|
||||
selectedDate: React.PropTypes.object,
|
||||
viewDate: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
display: 'months',
|
||||
selectedDate: new Date()
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
selectedDate: this.props.selectedDate,
|
||||
viewDate: this.props.selectedDate
|
||||
};
|
||||
},
|
||||
|
||||
componentDidUpdate () {
|
||||
if (this.refs.activeYear) {
|
||||
this.scrollToActive();
|
||||
}
|
||||
},
|
||||
|
||||
onDayClick (event) {
|
||||
let newDate = utils.time.setDay(this.state.viewDate, parseInt(event.target.textContent));
|
||||
this.setState({selectedDate: newDate});
|
||||
if (this.props.onChange) this.props.onChange(newDate);
|
||||
},
|
||||
|
||||
onYearClick (event) {
|
||||
let newDate = utils.time.setYear(this.state.selectedDate, parseInt(event.target.textContent));
|
||||
this.setState({selectedDate: newDate, viewDate: newDate});
|
||||
if (this.props.onChange) this.props.onChange(newDate);
|
||||
},
|
||||
|
||||
scrollToActive () {
|
||||
this.refs.years.getDOMNode().scrollTop =
|
||||
this.refs.activeYear.getDOMNode().offsetTop -
|
||||
this.refs.years.getDOMNode().offsetHeight / 2 +
|
||||
this.refs.activeYear.getDOMNode().offsetHeight / 2;
|
||||
},
|
||||
|
||||
incrementViewMonth () {
|
||||
this.setState({
|
||||
direction: 'right',
|
||||
viewDate: utils.time.addMonths(this.state.viewDate, 1)
|
||||
});
|
||||
},
|
||||
|
||||
decrementViewMonth () {
|
||||
this.setState({
|
||||
direction: 'left',
|
||||
viewDate: utils.time.addMonths(this.state.viewDate, -1)
|
||||
});
|
||||
},
|
||||
|
||||
renderYear (year) {
|
||||
let props = {
|
||||
className: year === this.state.viewDate.getFullYear() ? 'active' : '',
|
||||
key: `year-${year}`,
|
||||
onClick: this.onYearClick
|
||||
};
|
||||
|
||||
if (year === this.state.viewDate.getFullYear()) {
|
||||
props.ref = 'activeYear';
|
||||
}
|
||||
|
||||
return (<li {...props}>{ year }</li>);
|
||||
},
|
||||
|
||||
renderYears () {
|
||||
return (
|
||||
<ul ref="years" className={css.years}>
|
||||
{ utils.range(1900, 2100).map(i => { return this.renderYear(i); })}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
|
||||
renderMonths () {
|
||||
return (
|
||||
<div className={this.state.direction}>
|
||||
<FontIcon className={css.prev} value='chevron_left' onClick={this.decrementViewMonth}/>
|
||||
<FontIcon className={css.next} value='chevron_right' onClick={this.incrementViewMonth}/>
|
||||
<CTG transitionName='slide-horizontal'>
|
||||
<Month
|
||||
key={this.state.viewDate.getMonth()}
|
||||
viewDate={this.state.viewDate}
|
||||
selectedDate={this.state.selectedDate}
|
||||
onDayClick={this.onDayClick} />
|
||||
</CTG>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={css.root}>
|
||||
{ this.props.display === 'months' ? this.renderMonths() : this.renderYears() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
css = require './style'
|
||||
Day = require './day'
|
||||
util = require '../date_utils'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName: 'Month',
|
||||
|
||||
propTypes:
|
||||
onDayClick : React.PropTypes.func
|
||||
selectedDate : React.PropTypes.object
|
||||
viewDate : React.PropTypes.object
|
||||
|
||||
render: ->
|
||||
<div>
|
||||
<span className={css.title}>
|
||||
{ util.monthInWords(@props.viewDate)} {@props.viewDate.getFullYear() }
|
||||
</span>
|
||||
<div className={css.week}>
|
||||
{ <span key={"dw#{i}"}>{ util.weekDayInWords(i).charAt(0) }</span> for i in [0..6] }
|
||||
</div>
|
||||
<div className={css.days}>
|
||||
{ for i in [1..util.daysInMonth(@props.viewDate)]
|
||||
<Day key={"d#{i}"}
|
||||
day={i}
|
||||
onClick={@props.onDayClick}
|
||||
selectedDate={@props.selectedDate}
|
||||
viewDate={@props.viewDate} /> }
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,52 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import utils from '../utils';
|
||||
import Day from './day';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Month',
|
||||
|
||||
propTypes: {
|
||||
onDayClick: React.PropTypes.func,
|
||||
selectedDate: React.PropTypes.object,
|
||||
viewDate: React.PropTypes.object
|
||||
},
|
||||
|
||||
renderWeeks () {
|
||||
return utils.range(0, 7).map(i => {
|
||||
return (
|
||||
<span key={`dw${i}`}>
|
||||
{ utils.time.getFullDayOfWeek(i).charAt(0) }
|
||||
</span>
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
renderDays () {
|
||||
return utils.range(1, utils.time.getDaysInMonth(this.props.viewDate) + 1).map(i => {
|
||||
return (
|
||||
<Day key={`d${i}`}
|
||||
day={i}
|
||||
onClick={this.props.onDayClick}
|
||||
selectedDate={this.props.selectedDate}
|
||||
viewDate={this.props.viewDate} />
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<span className={css.title}>
|
||||
{ utils.time.getFullMonth(this.props.viewDate)} {this.props.viewDate.getFullYear() }
|
||||
</span>
|
||||
<div className={css.week}>{ this.renderWeeks() }</div>
|
||||
<div className={css.days}>{ this.renderDays() }</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,72 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Navigation = require '../navigation'
|
||||
Ripple = require '../ripple'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
color : React.PropTypes.string
|
||||
image : React.PropTypes.string
|
||||
text : React.PropTypes.string
|
||||
legend : React.PropTypes.string
|
||||
loading : React.PropTypes.bool
|
||||
onClick : React.PropTypes.func
|
||||
title : React.PropTypes.string
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
loading : false
|
||||
type : 'default'
|
||||
|
||||
getInitialState: ->
|
||||
loading : @props.loading
|
||||
ripple : undefined
|
||||
|
||||
# -- Lifecycle
|
||||
componentWillReceiveProps: ->
|
||||
@setState ripple: undefined
|
||||
|
||||
# -- Events
|
||||
onClick: (event) ->
|
||||
event.preventDefault() if @props.onClick?
|
||||
client = event.target.getBoundingClientRect?()
|
||||
@setState ripple:
|
||||
left : event.pageX - client?.left
|
||||
top : event.pageY - client?.top
|
||||
width : (client?.width * 2.5)
|
||||
@props.onClick? event, @
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
className += ' touch' if @props.onClick?
|
||||
className += ' image' if @props.image?
|
||||
className += ' color' if @props.color?
|
||||
className += ' loading' if @state.loading
|
||||
style = {}
|
||||
style.backgroundImage = "url(#{@props.image})" if @props.image?
|
||||
style.backgroundColor = @props.color if @props.color
|
||||
|
||||
<div data-react-toolbox='card' className={className} onClick={@onClick}>
|
||||
{
|
||||
if @props.title or @props.image
|
||||
<figure className={localCSS.figure} style={style}>
|
||||
{ <small>{@props.subtitle}</small> if @props.subtitle }
|
||||
{ <h2>{@props.title}</h2> if @props.title }
|
||||
</figure>
|
||||
}
|
||||
{ <p>{@props.text}</p> if @props.text }
|
||||
{ <small>{@props.legend}</small> if @props.legend }
|
||||
{ <Navigation className={localCSS.navigation} actions={@props.actions} /> if @props.actions }
|
||||
{ <Ripple className={localCSS.ripple} origin={@state.ripple} loading={@state.loading} /> }
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
loading: (value) ->
|
||||
attributes = loading: value
|
||||
attributes.ripple = undefined unless value
|
||||
@setState attributes
|
|
@ -0,0 +1,82 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
import Navigation from '../navigation';
|
||||
import Ripple from '../ripple';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Card',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
color: React.PropTypes.string,
|
||||
image: React.PropTypes.string,
|
||||
text: React.PropTypes.string,
|
||||
legend: React.PropTypes.string,
|
||||
loading: React.PropTypes.bool,
|
||||
onClick: React.PropTypes.func,
|
||||
title: React.PropTypes.string,
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
loading: false,
|
||||
type: 'default'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
loading: this.props.loading
|
||||
};
|
||||
},
|
||||
|
||||
onClick (event) {
|
||||
if (this.props.onClick) {
|
||||
event.preventDefault();
|
||||
this.props.onClick(event, this);
|
||||
}
|
||||
},
|
||||
|
||||
renderHeading () {
|
||||
let headingStyle = {};
|
||||
if (this.props.image) headingStyle.backgroundImage = `url(${this.props.image})`;
|
||||
if (this.props.color) headingStyle.backgroundColor = this.props.color;
|
||||
if (this.props.title || this.props.image) {
|
||||
return (
|
||||
<figure className={style.figure} style={headingStyle}>
|
||||
{ this.props.subtitle ? <small>{this.props.subtitle}</small> : null }
|
||||
{ this.props.title ? <h2>{this.props.title}</h2> : null }
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
if (this.props.onClick) className += ' touch';
|
||||
if (this.props.image) className += ' image';
|
||||
if (this.props.color) className += ' color';
|
||||
if (this.state.loading) className += ' loading';
|
||||
|
||||
return (
|
||||
<div data-react-toolbox='card' className={className} onMouseDown={this.onClick}>
|
||||
{ this.renderHeading() }
|
||||
{ this.props.text ? <p>{this.props.text}</p> : null }
|
||||
{ this.props.legend ? <small>{this.props.legend}</small> : null}
|
||||
{ this.props.actions ? <Navigation className={style.navigation} actions={this.props.actions} /> : null }
|
||||
{ <Ripple ref="ripple" className={style.ripple} loading={this.state.loading} /> }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
loading (value) {
|
||||
this.setState({loading: value});
|
||||
}
|
||||
});
|
|
@ -6,7 +6,6 @@ OFFSET = (SPACE / 1.25)
|
|||
:local(.navigation)
|
||||
padding : OFFSET = (OFFSET / 2)
|
||||
> *
|
||||
pointer-events : all
|
||||
padding-left : OFFSET
|
||||
padding-right : OFFSET
|
||||
box-shadow : none !important
|
||||
|
@ -33,17 +32,14 @@ OFFSET = (SPACE / 1.25)
|
|||
background : WHITE
|
||||
|
||||
// -- Children
|
||||
*:not(button)
|
||||
pointer-events : none
|
||||
> *:not(:local(.ripple)):not(:local(.navigation))
|
||||
padding : OFFSET
|
||||
&:not(.color):not(.image) > :local(.ripple)
|
||||
background-color : DIVIDER
|
||||
background-color : red
|
||||
&:not(.color) > *:not(figure), > *:not(:last-child)
|
||||
box-shadow : 0 1px darken(BACKGROUND, 5%)
|
||||
> :local(.figure)
|
||||
|
||||
|
||||
// -- Overrides
|
||||
&.touch
|
||||
cursor : pointer
|
||||
|
@ -56,10 +52,11 @@ OFFSET = (SPACE / 1.25)
|
|||
color : WHITE
|
||||
&.loading
|
||||
cursor : none
|
||||
pointer-events : none
|
||||
-webkit-filter : grayscale(100%)
|
||||
&, &:hover
|
||||
box-shadow : 0 0 0 1px DIVIDER
|
||||
> :local(.ripple)
|
||||
:local(.ripple)
|
||||
width : SIZE = (SIZE * 2)
|
||||
height : SIZE
|
||||
&.small > :local(.figure)
|
||||
|
@ -68,3 +65,6 @@ OFFSET = (SPACE / 1.25)
|
|||
height : SIZE
|
||||
&.wide
|
||||
width : (SIZE * 2)
|
||||
|
||||
:local(.ripple)
|
||||
background-color: #888
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
css = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Face'
|
||||
|
||||
# -- States & Properties
|
||||
getDefaultProps: ->
|
||||
active : null
|
||||
numbers : []
|
||||
radius : 0
|
||||
|
||||
# -- Internal methods
|
||||
_numberStyle: (radius, num) ->
|
||||
position : 'absolute'
|
||||
left : (radius + radius * Math.sin(360 * (Math.PI/180) / 12 * (num - 1)) + @props.spacing)
|
||||
top : (radius - radius * Math.cos(360 * (Math.PI/180) / 12 * (num - 1)) + @props.spacing)
|
||||
|
||||
_faceStyle: ->
|
||||
height : @props.radius * 2
|
||||
width : @props.radius * 2
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
<div ref="root"
|
||||
className={css.face}
|
||||
onTouchStart={@props.onTouchStart}
|
||||
onMouseDown={@props.onMouseDown}
|
||||
style={@_faceStyle()}>
|
||||
{ for i, k in @props.numbers
|
||||
<span className={css.number + (if parseInt(i) == @props.active then ' active' else '')}
|
||||
style={@_numberStyle(@props.radius - @props.spacing, k + 1)}
|
||||
key={i} >
|
||||
{i}
|
||||
</span> }
|
||||
</div>
|
|
@ -0,0 +1,56 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Face',
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
active: null,
|
||||
numbers: [],
|
||||
radius: 0,
|
||||
twoDigits: false
|
||||
};
|
||||
},
|
||||
|
||||
numberStyle (rad, num) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: (rad + rad * Math.sin(360 * (Math.PI / 180) / 12 * (num - 1)) + this.props.spacing),
|
||||
top: (rad - rad * Math.cos(360 * (Math.PI / 180) / 12 * (num - 1)) + this.props.spacing)
|
||||
};
|
||||
},
|
||||
|
||||
faceStyle () {
|
||||
return {
|
||||
height: this.props.radius * 2,
|
||||
width: this.props.radius * 2
|
||||
};
|
||||
},
|
||||
|
||||
renderNumber (number, idx) {
|
||||
return (
|
||||
<span className={css.number + (number === this.props.active ? ' active' : '')}
|
||||
style={this.numberStyle(this.props.radius - this.props.spacing, idx + 1)}
|
||||
key={number}>
|
||||
{ this.props.twoDigits ? ('0' + number).slice(-2) : number }
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div ref="root"
|
||||
className={css.face}
|
||||
onTouchStart={this.props.onTouchStart}
|
||||
onMouseDown={this.props.onMouseDown}
|
||||
style={this.faceStyle()}>
|
||||
{ this.props.numbers.map(this.renderNumber)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,125 +0,0 @@
|
|||
css = require './style'
|
||||
prefixer = require "../prefixer"
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Hand'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
initialAngle : React.PropTypes.number
|
||||
onHandChange : React.PropTypes.func
|
||||
onHandMoved : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
initialAngle : 0
|
||||
length : 0
|
||||
origin : {}
|
||||
|
||||
getInitialState: ->
|
||||
angle : @props.initialAngle
|
||||
knobWidth : 0
|
||||
radius : 0
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@setState knobWidth: @refs.knob.getDOMNode().offsetWidth
|
||||
|
||||
componentWillUpdate: (nextProps, nextState) ->
|
||||
if nextState.angle != @state.angle ||
|
||||
nextProps.length != @props.length &&
|
||||
@props.length != 0
|
||||
@props.onHandChange(nextState.angle)
|
||||
|
||||
# -- Event handlers
|
||||
_getMouseEventMap: ->
|
||||
mousemove : @onMouseMove
|
||||
mouseup : @onMouseUp
|
||||
|
||||
_getTouchEventMap: ->
|
||||
touchmove: @onTouchMove
|
||||
touchend: @onTouchEnd
|
||||
|
||||
onMouseMove: (event) ->
|
||||
@_move(_getMousePosition(event))
|
||||
|
||||
onTouchMove: (event) ->
|
||||
@_move(_getTouchPosition(event))
|
||||
|
||||
onMouseUp: ->
|
||||
@_end(@_getMouseEventMap())
|
||||
|
||||
onTouchEnd: ->
|
||||
@_end(@_getTouchEventMap())
|
||||
|
||||
# -- Public API
|
||||
mouseStart: (event) ->
|
||||
_addEventsToDocument(@_getMouseEventMap())
|
||||
@_move(_getMousePosition(event))
|
||||
|
||||
touchStart: (event) ->
|
||||
_addEventsToDocument(@_getTouchEventMap())
|
||||
@_move(_getTouchPosition(event))
|
||||
_pauseEvent(event)
|
||||
|
||||
# -- Internal methods
|
||||
_move: (position) ->
|
||||
@props.onHandMouseMove(@_getPositionRadius(position)) if @props.onHandMouseMove
|
||||
newDegrees = @_trimAngleToValue(@_positionToAngle(position))
|
||||
newDegrees = if newDegrees == 360 then 0 else newDegrees
|
||||
@setState(angle: newDegrees) if @state.angle != newDegrees
|
||||
|
||||
_getPositionRadius: (position) ->
|
||||
x = @props.origin.x - position.x
|
||||
y = @props.origin.y - position.y
|
||||
Math.sqrt(x * x + y * y)
|
||||
|
||||
_trimAngleToValue: (angle) ->
|
||||
@props.step * Math.round(angle/@props.step)
|
||||
|
||||
_positionToAngle: (position) ->
|
||||
_angle360(@props.origin.x, @props.origin.y, position.x, position.y)
|
||||
|
||||
_end: (events) ->
|
||||
@props.onHandMoved() if @props.onHandMoved
|
||||
_removeEventsFromDocument(events)
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
style = prefixer.transform("rotate(#{@state.angle}deg)")
|
||||
style.height = @props.length - @state.knobWidth/2
|
||||
|
||||
<div className={css.hand + ' ' + @props.className} style={style}>
|
||||
<div ref='knob' className={css.knob}></div>
|
||||
</div>
|
||||
|
||||
# -- Static Helper functions
|
||||
_addEventsToDocument = (events) ->
|
||||
document.addEventListener(key, events[key], false) for key of events
|
||||
|
||||
_removeEventsFromDocument = (events) ->
|
||||
document.removeEventListener(key, events[key], false) for key of events
|
||||
|
||||
_pauseEvent = (event) ->
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
event.returnValue = false
|
||||
event.cancelBubble = true
|
||||
return null
|
||||
|
||||
_getMousePosition = (event) ->
|
||||
x: event.pageX
|
||||
y: event.pageY
|
||||
|
||||
_getTouchPosition = (event) ->
|
||||
x: event.touches[0]['pageX']
|
||||
y: event.touches[0]['pageY']
|
||||
|
||||
_angle360 = (cx, cy, ex, ey) ->
|
||||
theta = _angle(cx, cy, ex, ey)
|
||||
if (theta < 0) then 360 + theta else theta
|
||||
|
||||
_angle = (cx, cy, ex, ey) ->
|
||||
theta = Math.atan2(ey - cy, ex - cx) + Math.PI/2
|
||||
theta * 180 / Math.PI
|
|
@ -0,0 +1,114 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import utils from '../utils';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Hand',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
angle: React.PropTypes.number,
|
||||
onMove: React.PropTypes.func,
|
||||
onMoved: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
angle: 0,
|
||||
length: 0,
|
||||
origin: {}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return { knobWidth: 0 };
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({knobWidth: this.refs.knob.getDOMNode().offsetWidth});
|
||||
},
|
||||
|
||||
getMouseEventMap () {
|
||||
return {
|
||||
mousemove: this.onMouseMove,
|
||||
mouseup: this.onMouseUp
|
||||
};
|
||||
},
|
||||
|
||||
getTouchEventMap () {
|
||||
return {
|
||||
touchmove: this.onTouchMove,
|
||||
touchend: this.onTouchEnd
|
||||
};
|
||||
},
|
||||
|
||||
onMouseMove (event) {
|
||||
this.move(utils.events.getMousePosition(event));
|
||||
},
|
||||
|
||||
onTouchMove (event) {
|
||||
this.move(utils.events.getTouchPosition(event));
|
||||
},
|
||||
|
||||
onMouseUp () {
|
||||
this.end(this.getMouseEventMap());
|
||||
},
|
||||
|
||||
onTouchEnd () {
|
||||
this.end(this.getTouchEventMap());
|
||||
},
|
||||
|
||||
mouseStart (event) {
|
||||
utils.events.addEventsToDocument(this.getMouseEventMap());
|
||||
this.move(utils.events.getMousePosition(event));
|
||||
},
|
||||
|
||||
touchStart (event) {
|
||||
utils.events.addEventsToDocument(this.getTouchEventMap());
|
||||
this.move(utils.events.getTouchPosition(event));
|
||||
utils.events.pauseEvent(event);
|
||||
},
|
||||
|
||||
getPositionRadius (position) {
|
||||
let x = this.props.origin.x - position.x;
|
||||
let y = this.props.origin.y - position.y;
|
||||
return Math.sqrt(x * x + y * y);
|
||||
},
|
||||
|
||||
trimAngleToValue (angle) {
|
||||
return this.props.step * Math.round(angle / this.props.step);
|
||||
},
|
||||
|
||||
positionToAngle (position) {
|
||||
return utils.angle360FromPositions(this.props.origin.x, this.props.origin.y, position.x, position.y);
|
||||
},
|
||||
|
||||
end (events) {
|
||||
if (this.props.onMoved) this.props.onMoved();
|
||||
utils.events.removeEventsFromDocument(events);
|
||||
},
|
||||
|
||||
move (position) {
|
||||
let degrees = this.trimAngleToValue(this.positionToAngle(position));
|
||||
let radius = this.getPositionRadius(position);
|
||||
if (this.props.onMove) this.props.onMove(degrees === 360 ? 0 : degrees, radius);
|
||||
},
|
||||
|
||||
render () {
|
||||
let style = utils.prefixer({
|
||||
height: this.props.length - this.state.knobWidth / 2,
|
||||
transform: `rotate(${this.props.angle}deg)`
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={css.hand + ' ' + this.props.className} style={style}>
|
||||
<div ref='knob' className={css.knob}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
Face = require './face'
|
||||
Hand = require './hand'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Hours'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
format : React.PropTypes.oneOf(['24hr', 'ampm'])
|
||||
onChange : React.PropTypes.func
|
||||
onHandMoved : React.PropTypes.func
|
||||
selected : React.PropTypes.number
|
||||
|
||||
getInitialState: ->
|
||||
innerNumber : @props.format == '24hr' && 0 < @props.selected <= 12
|
||||
|
||||
# -- Events
|
||||
_onHandMouseMove: (radius) ->
|
||||
if @props.format == '24hr'
|
||||
currentInner = radius < @props.radius - @props.spacing * 2
|
||||
@setState innerNumber: currentInner if @state.innerNumber != currentInner
|
||||
|
||||
_onHandChange: (degrees) ->
|
||||
@props.onChange(@_valueFromDegrees(degrees))
|
||||
|
||||
_onMouseDown: (event)->
|
||||
@refs.hand.mouseStart(event)
|
||||
|
||||
_onTouchStart: (event) ->
|
||||
@refs.hand.touchStart(event)
|
||||
|
||||
# -- Internal Methods
|
||||
_valueFromDegrees: (degrees) ->
|
||||
if @props.format == 'ampm' || @props.format == '24hr' && @state.innerNumber
|
||||
parseInt(INNER_NUMBERS[degrees/STEP])
|
||||
else
|
||||
parseInt(OUTER_NUMBERS[degrees/STEP])
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
innerRadius = @props.radius - @props.spacing * 2
|
||||
handRadius = if @state.innerNumber then innerRadius else @props.radius
|
||||
handLength = handRadius - @props.spacing
|
||||
ampmActive = if @props.format == '24hr' then @props.selected else @props.selected % 12 || 12
|
||||
|
||||
<div>
|
||||
<Face
|
||||
onTouchStart={@_onTouchStart}
|
||||
onMouseDown={@_onMouseDown}
|
||||
numbers={if @props.format == '24hr' then OUTER_NUMBERS else INNER_NUMBERS}
|
||||
spacing={@props.spacing}
|
||||
radius={@props.radius}
|
||||
active={ampmActive} />
|
||||
{
|
||||
if @props.format == '24hr'
|
||||
<Face
|
||||
onMouseDown={@_onMouseDown}
|
||||
numbers={INNER_NUMBERS}
|
||||
spacing={@props.spacing}
|
||||
radius={innerRadius}
|
||||
active={@props.selected} />
|
||||
}
|
||||
<Hand ref='hand'
|
||||
degrees={@state.degrees}
|
||||
initialAngle={@props.selected * STEP}
|
||||
length={handLength}
|
||||
onHandMouseMove={@_onHandMouseMove}
|
||||
onHandMoved={@props.onHandMoved}
|
||||
onHandChange={@_onHandChange}
|
||||
origin={@props.center}
|
||||
step={STEP} />
|
||||
</div>
|
||||
|
||||
# -- Private constants
|
||||
INNER_NUMBERS = [12].concat([1..11])
|
||||
OUTER_NUMBERS = ['00'].concat([13..23])
|
||||
STEP = 360/12
|
|
@ -0,0 +1,93 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import utils from '../utils';
|
||||
import Face from './face';
|
||||
import Hand from './hand';
|
||||
|
||||
const outerNumbers = [0, ...utils.range(13, 24)];
|
||||
const innerNumbers = [12, ...utils.range(1, 12)];
|
||||
const step = 360 / 12;
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Hours',
|
||||
|
||||
propTypes: {
|
||||
format: React.PropTypes.oneOf(['24hr', 'ampm']),
|
||||
onChange: React.PropTypes.func,
|
||||
onHandMoved: React.PropTypes.func,
|
||||
selected: React.PropTypes.number
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
inner: this.props.format === '24hr' && this.props.selected > 0 && this.props.selected <= 12
|
||||
};
|
||||
},
|
||||
|
||||
onHandMove (degrees, radius) {
|
||||
let currentInner = radius < this.props.radius - this.props.spacing * 2;
|
||||
this.props.onChange(this.valueFromDegrees(degrees));
|
||||
if (this.props.format === '24hr' && this.state.inner !== currentInner) {
|
||||
this.setState({inner: currentInner});
|
||||
}
|
||||
},
|
||||
|
||||
onMouseDown (event) {
|
||||
this.refs.hand.mouseStart(event);
|
||||
},
|
||||
|
||||
onTouchStart (event) {
|
||||
this.refs.hand.touchStart(event);
|
||||
},
|
||||
|
||||
valueFromDegrees (degrees) {
|
||||
if (this.props.format === 'ampm' || this.props.format === '24hr' && this.state.inner) {
|
||||
return innerNumbers[degrees / step];
|
||||
} else {
|
||||
return outerNumbers[degrees / step];
|
||||
}
|
||||
},
|
||||
|
||||
renderInnerFace (innerRadius) {
|
||||
if (this.props.format === '24hr') {
|
||||
return (
|
||||
<Face
|
||||
onTouchStart={this.onTouchStart}
|
||||
onMouseDown={this.onMouseDown}
|
||||
numbers={innerNumbers}
|
||||
spacing={this.props.spacing}
|
||||
radius={innerRadius}
|
||||
active={this.props.selected} />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
const { format, selected, radius, spacing, center, onHandMoved } = this.props;
|
||||
const is24hr = format === '24hr';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Face
|
||||
onTouchStart={this.onTouchStart}
|
||||
onMouseDown={this.onMouseDown}
|
||||
numbers={is24hr ? outerNumbers : innerNumbers}
|
||||
spacing={spacing}
|
||||
radius={radius}
|
||||
twoDigits={is24hr}
|
||||
active={is24hr ? selected : (selected % 12 || 12)} />
|
||||
{ this.renderInnerFace(radius - spacing * 2) }
|
||||
<Hand ref='hand'
|
||||
angle={selected * step}
|
||||
length={(this.state.inner ? radius - spacing * 2 : radius) - spacing}
|
||||
onMove={this.onHandMove}
|
||||
onMoved={onHandMoved}
|
||||
origin={center}
|
||||
step={step} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,100 +0,0 @@
|
|||
css = require './style'
|
||||
dateUtils = require '../date_utils'
|
||||
Hours = require './hours'
|
||||
Minutes = require './minutes'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Clock'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
display : React.PropTypes.oneOf(['hours', 'minutes'])
|
||||
format : React.PropTypes.oneOf(['24hr', 'ampm'])
|
||||
initialTime : React.PropTypes.object
|
||||
onChange : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
display : 'hours'
|
||||
format : '24hr'
|
||||
initialTime : new Date()
|
||||
|
||||
getInitialState: ->
|
||||
radius : 0
|
||||
time : @props.initialTime
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
window.addEventListener('resize', @handleResize)
|
||||
@setState radius: @_getRadius()
|
||||
|
||||
componentWillUpdate: (props, state) ->
|
||||
center = @_getCenter()
|
||||
if state.time.getTime() != @state.time.getTime() && @props.onChange
|
||||
@props.onChange(state.time)
|
||||
if @state.center?.x != center.x && @state.center?.y != center.y
|
||||
@setState center: center
|
||||
|
||||
componentWillUnmount: ->
|
||||
window.removeEventListener('resize', @handleResize)
|
||||
|
||||
# -- Events handlers
|
||||
onHourChange: (hours) ->
|
||||
@setState time: dateUtils.setHours(@state.time, @_adaptHourToFormat(hours))
|
||||
|
||||
onMinuteChange: (minutes) ->
|
||||
@setState time: dateUtils.setMinutes(@state.time, minutes)
|
||||
|
||||
# -- Helper methods
|
||||
_getRadius: ->
|
||||
@refs.wrapper.getDOMNode().getBoundingClientRect().width/2
|
||||
|
||||
_adaptHourToFormat: (hour) ->
|
||||
if @props.format == 'ampm'
|
||||
if dateUtils.timeMode(@state.time) == 'pm'
|
||||
if hour < 12 then hour + 12 else hour
|
||||
else
|
||||
if hour == 12 then 0 else hour
|
||||
else
|
||||
hour
|
||||
|
||||
handleResize: ->
|
||||
@setState
|
||||
center: @_getCenter()
|
||||
radius: @_getRadius()
|
||||
|
||||
_getCenter: ->
|
||||
bounds = @getDOMNode().getBoundingClientRect()
|
||||
{
|
||||
x: bounds.left + (bounds.right - bounds.left)/2
|
||||
y: bounds.top + (bounds.bottom - bounds.top) /2
|
||||
}
|
||||
|
||||
# -- Public methods
|
||||
toggleTimeMode: ->
|
||||
@setState time: dateUtils.toggleTimeMode(@state.time)
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
<div className={css.root}>
|
||||
<div ref="wrapper" className={css.wrapper} style={height: @state.radius * 2} >
|
||||
{
|
||||
if @props.display == 'minutes'
|
||||
<Minutes
|
||||
center={@state.center}
|
||||
onChange={@onMinuteChange}
|
||||
radius={@state.radius}
|
||||
selected={@state.time.getMinutes()}
|
||||
spacing={@state.radius * 0.16} />
|
||||
else if @props.display == 'hours'
|
||||
<Hours
|
||||
center={@state.center}
|
||||
format={@props.format}
|
||||
onChange={@onHourChange}
|
||||
radius={@state.radius}
|
||||
selected={@state.time.getHours()}
|
||||
spacing={@state.radius * 0.16} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,123 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import time from '../utils/time';
|
||||
import Hours from './hours';
|
||||
import Minutes from './minutes';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Clock',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
display: React.PropTypes.oneOf(['hours', 'minutes']),
|
||||
format: React.PropTypes.oneOf(['24hr', 'ampm']),
|
||||
initialTime: React.PropTypes.object,
|
||||
onChange: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
display: 'hours',
|
||||
format: '24hr',
|
||||
initialTime: new Date()
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
center: {x: null, y: null},
|
||||
radius: 0,
|
||||
time: this.props.initialTime
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('resize', this.calculateShape);
|
||||
this.calculateShape();
|
||||
},
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('resize', this.calculateShape);
|
||||
},
|
||||
|
||||
onHourChange (hours) {
|
||||
if (this.state.time.getHours() !== hours) {
|
||||
const newTime = time.setHours(this.state.time, this.adaptHourToFormat(hours));
|
||||
this.setState({time: newTime});
|
||||
if (this.props.onChange) this.props.onChange(newTime);
|
||||
}
|
||||
},
|
||||
|
||||
onMinuteChange (minutes) {
|
||||
if (this.state.time.getMinutes() !== minutes) {
|
||||
const newTime = time.setMinutes(this.state.time, minutes);
|
||||
this.setState({time: newTime});
|
||||
if (this.props.onChange) this.props.onChange(newTime);
|
||||
}
|
||||
},
|
||||
|
||||
toggleTimeMode () {
|
||||
const newTime = time.toggleTimeMode(this.state.time);
|
||||
this.setState({time: newTime});
|
||||
if (this.props.onChange) this.props.onChange(newTime);
|
||||
},
|
||||
|
||||
adaptHourToFormat (hour) {
|
||||
if (this.props.format === 'ampm') {
|
||||
if (time.getTimeMode(this.state.time) === 'pm') {
|
||||
return hour < 12 ? hour + 12 : hour;
|
||||
} else {
|
||||
return hour === 12 ? 0 : hour;
|
||||
}
|
||||
} else {
|
||||
return hour;
|
||||
}
|
||||
},
|
||||
|
||||
calculateShape () {
|
||||
let { top, left, width } = this.refs.wrapper.getDOMNode().getBoundingClientRect();
|
||||
this.setState({
|
||||
center: { x: left + width / 2, y: top + width / 2 },
|
||||
radius: width / 2
|
||||
});
|
||||
},
|
||||
|
||||
renderHours () {
|
||||
return (
|
||||
<Hours
|
||||
center={this.state.center}
|
||||
format={this.props.format}
|
||||
onChange={this.onHourChange}
|
||||
radius={this.state.radius}
|
||||
selected={this.state.time.getHours()}
|
||||
spacing={this.state.radius * 0.16} />
|
||||
);
|
||||
},
|
||||
|
||||
renderMinutes () {
|
||||
return (
|
||||
<Minutes
|
||||
center={this.state.center}
|
||||
onChange={this.onMinuteChange}
|
||||
radius={this.state.radius}
|
||||
selected={this.state.time.getMinutes()}
|
||||
spacing={this.state.radius * 0.16} />
|
||||
);
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={css.root}>
|
||||
<div ref="wrapper" className={css.wrapper} style={{height: this.state.radius * 2}}>
|
||||
{ this.props.display === 'hours' ? this.renderHours() : '' }
|
||||
{ this.props.display === 'minutes' ? this.renderMinutes() : '' }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
Face = require './face'
|
||||
Hand = require './hand'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName: 'Minutes'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
selected : React.PropTypes.number
|
||||
onChange : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
selected : 0
|
||||
onChange : null
|
||||
|
||||
# -- Events
|
||||
_onHandChange: (degrees) ->
|
||||
@props.onChange(degrees/STEP)
|
||||
|
||||
_onMouseDown: (event)->
|
||||
@refs.hand.mouseStart(event)
|
||||
|
||||
_onTouchStart: (event) ->
|
||||
@refs.hand.touchStart(event)
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
handClass = if MINUTES.indexOf(('0' + @props.selected).slice(-2)) == -1 then 'smallKnob' else ''
|
||||
|
||||
<div>
|
||||
<Face
|
||||
onTouchStart={@_onTouchStart}
|
||||
onMouseDown={@_onMouseDown}
|
||||
numbers={MINUTES}
|
||||
spacing={@props.spacing}
|
||||
radius={@props.radius}
|
||||
active={@props.selected} />
|
||||
<Hand ref='hand'
|
||||
className={handClass}
|
||||
initialAngle={@props.selected * STEP}
|
||||
length={@props.radius - @props.spacing}
|
||||
onHandChange={@_onHandChange}
|
||||
origin={@props.center}
|
||||
step={STEP} />
|
||||
</div>
|
||||
|
||||
# -- Private constants
|
||||
MINUTES = (('0' + i).slice(-2) for i in [0..55] by 5)
|
||||
STEP = 360/60
|
|
@ -0,0 +1,61 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import utils from '../utils';
|
||||
import Face from './face';
|
||||
import Hand from './hand';
|
||||
|
||||
const minutes = utils.range(0, 60, 5);
|
||||
const step = 360 / 60;
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Minutes',
|
||||
|
||||
propTypes: {
|
||||
selected: React.PropTypes.number,
|
||||
onChange: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
selected: 0,
|
||||
onChange: null
|
||||
};
|
||||
},
|
||||
|
||||
onHandMove (degrees) {
|
||||
this.props.onChange(degrees / step);
|
||||
},
|
||||
|
||||
onMouseDown (event) {
|
||||
this.refs.hand.mouseStart(event);
|
||||
},
|
||||
|
||||
onTouchStart (event) {
|
||||
this.refs.hand.touchStart(event);
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Face
|
||||
onTouchStart={this.onTouchStart}
|
||||
onMouseDown={this.onMouseDown}
|
||||
numbers={minutes}
|
||||
spacing={this.props.spacing}
|
||||
radius={this.props.radius}
|
||||
twoDigits={true}
|
||||
active={this.props.selected} />
|
||||
<Hand ref='hand'
|
||||
className={minutes.indexOf(this.props.selected) === -1 ? 'smallKnob' : ''}
|
||||
angle={this.props.selected * step}
|
||||
length={this.props.radius - this.props.spacing}
|
||||
onMove={this.onHandMove}
|
||||
origin={this.props.center}
|
||||
step={step} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -36,6 +36,7 @@ HEADER_HEIGHT = (1.65 * UNIT)
|
|||
INPUT_HEIGHT = (2 * SPACE)
|
||||
BUTTON_HEIGHT = (2.5 * SPACE)
|
||||
BUTTON_CIRCLE_HEIGHT = (2.75 * SPACE)
|
||||
BUTTON_CIRCLE_MINI_HEIGHT = (2 * SPACE)
|
||||
LOADING_HEIGHT = (1.5 * UNIT)
|
||||
PROGRESS_BAR_HEIGHT = (SPACE / 4)
|
||||
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
css = require './style'
|
||||
dateUtils = require '../date_utils'
|
||||
Dialog = require '../dialog'
|
||||
Calendar = require '../calendar'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName: 'CalendarDialog'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
initialDate : React.PropTypes.object
|
||||
onDateSelected : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
initialDate : new Date()
|
||||
|
||||
getInitialState: ->
|
||||
date : @props.initialDate
|
||||
display : 'months'
|
||||
actions : [
|
||||
{ caption: "Cancel", type: "flat accent", onClick: @onDateCancel },
|
||||
{ caption: "Ok", type: "flat accent", onClick: @onDateSelected }
|
||||
]
|
||||
|
||||
# -- Events
|
||||
onCalendarChange: (calendar) ->
|
||||
@setState
|
||||
date: dateUtils.cloneDatetime(calendar.getValue())
|
||||
display: 'months'
|
||||
|
||||
onDateCancel: (ref, method) ->
|
||||
@refs.dialog.hide()
|
||||
|
||||
onDateSelected: ->
|
||||
@props.onDateSelected(@state.date) if @props.onDateSelected
|
||||
@refs.dialog.hide()
|
||||
|
||||
# -- Public methods
|
||||
show: ->
|
||||
@refs.dialog.show()
|
||||
|
||||
displayMonths: ->
|
||||
@setState display: 'months'
|
||||
|
||||
displayYears: ->
|
||||
@setState display: 'years'
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "display-#{@state.display}"
|
||||
<Dialog ref="dialog" type={css.dialog} className={className} actions={@state.actions}>
|
||||
<header className={css.header}>
|
||||
<span className={css.headerWeekday}>{dateUtils.weekDayInWords(@state.date.getDay())}</span>
|
||||
<div onClick={@displayMonths}>
|
||||
<span className={css.headerMonth}>{dateUtils.monthInShortWords(@state.date)}</span>
|
||||
<span className={css.headerDay}>{@state.date.getDate()}</span>
|
||||
</div>
|
||||
<span className={css.headerYear} onClick={@displayYears}>
|
||||
{@state.date.getFullYear()}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div className={css.calendarWrapper}>
|
||||
<Calendar
|
||||
ref="calendar"
|
||||
display={@state.display}
|
||||
onChange={@onCalendarChange}
|
||||
selectedDate={@state.date} />
|
||||
</div>
|
||||
</Dialog>
|
|
@ -0,0 +1,88 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import time from '../utils/time';
|
||||
import Calendar from '../calendar';
|
||||
import Dialog from '../dialog';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'CalendarDialog',
|
||||
|
||||
propTypes: {
|
||||
initialDate: React.PropTypes.object,
|
||||
onDateSelected: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
initialDate: new Date()
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
date: this.props.initialDate,
|
||||
display: 'months',
|
||||
actions: [
|
||||
{ label: 'Cancel', type: 'flat accent', onClick: this.onDateCancel },
|
||||
{ label: 'Ok', type: 'flat accent', onClick: this.onDateSelected }
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
onCalendarChange (date) {
|
||||
this.setState({date: date, display: 'months'});
|
||||
},
|
||||
|
||||
onDateCancel () {
|
||||
this.refs.dialog.hide();
|
||||
},
|
||||
|
||||
onDateSelected () {
|
||||
if (this.props.onDateSelected) this.props.onDateSelected(this.state.date);
|
||||
this.refs.dialog.hide();
|
||||
},
|
||||
|
||||
show () {
|
||||
this.refs.dialog.show();
|
||||
},
|
||||
|
||||
displayMonths () {
|
||||
this.setState({display: 'months'});
|
||||
},
|
||||
|
||||
displayYears () {
|
||||
this.setState({display: 'years'});
|
||||
},
|
||||
|
||||
render () {
|
||||
const className = `display-${this.state.display}`;
|
||||
return (
|
||||
<Dialog ref="dialog" className={className} type={css.dialog} actions={this.state.actions}>
|
||||
<header className={css.header}>
|
||||
<span className={css.headerWeekday}>
|
||||
{time.getFullDayOfWeek(this.state.date.getDay())}
|
||||
</span>
|
||||
<div onClick={this.displayMonths}>
|
||||
<span className={css.headerMonth}>{time.getShortMonth(this.state.date)}</span>
|
||||
<span className={css.headerDay}>{this.state.date.getDate()}</span>
|
||||
</div>
|
||||
<span className={css.headerYear} onClick={this.displayYears}>
|
||||
{this.state.date.getFullYear()}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div className={css.calendarWrapper}>
|
||||
<Calendar
|
||||
ref="calendar"
|
||||
display={this.state.display}
|
||||
onChange={this.onCalendarChange}
|
||||
selectedDate={this.props.date} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,52 +0,0 @@
|
|||
css = require './style'
|
||||
dateUtils = require '../date_utils'
|
||||
Input = require '../input'
|
||||
CalendarDialog = require './dialog'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'DatePicker'
|
||||
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
value : React.PropTypes.object
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
|
||||
getInitialState: ->
|
||||
value : @props.value
|
||||
|
||||
# -- Events
|
||||
openCalendarDialog: ->
|
||||
@refs.dialog.show()
|
||||
|
||||
onDateSelected: (value) ->
|
||||
@refs.input.setValue(@formatDate(value))
|
||||
@setState value: value
|
||||
|
||||
# -- Private methods
|
||||
formatDate: (date) ->
|
||||
day = date.getDate()
|
||||
month = dateUtils.monthInWords(date)
|
||||
year = date.getFullYear()
|
||||
"#{day} #{month} #{year}"
|
||||
|
||||
# -- Public methods
|
||||
getValue: ->
|
||||
@state.value
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
<div>
|
||||
<Input
|
||||
ref="input"
|
||||
type="text"
|
||||
disabled={true}
|
||||
onClick={@openCalendarDialog}
|
||||
placeholder="Pick up date"
|
||||
value={@formatDate(@state.value) if @state.value} />
|
||||
<CalendarDialog
|
||||
ref="dialog"
|
||||
initialDate={@state.value}
|
||||
onDateSelected={@onDateSelected} />
|
||||
</div>
|
|
@ -0,0 +1,64 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import time from '../utils/time';
|
||||
import CalendarDialog from './dialog';
|
||||
import Input from '../input';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'DatePicker',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
value: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: ''
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
value: this.props.value
|
||||
};
|
||||
},
|
||||
|
||||
openCalendarDialog () {
|
||||
this.refs.dialog.show();
|
||||
},
|
||||
|
||||
onDateSelected (value) {
|
||||
this.refs.input.setValue(this.formatDate(value));
|
||||
this.setState({value: value});
|
||||
},
|
||||
|
||||
formatDate (date) {
|
||||
return `${date.getDate()} ${time.getFullMonth(date)} ${date.getFullYear()}`;
|
||||
},
|
||||
|
||||
getValue () {
|
||||
return this.state.value;
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
ref="input"
|
||||
type="text"
|
||||
disabled={true}
|
||||
onClick={this.openCalendarDialog}
|
||||
placeholder="Pick up date"
|
||||
value={this.state.value ? this.formatDate(this.state.value) : null} />
|
||||
<CalendarDialog
|
||||
ref="dialog"
|
||||
initialDate={this.state.value}
|
||||
onDateSelected={this.onDateSelected} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,103 +0,0 @@
|
|||
module.exports =
|
||||
daysInMonth: (date) ->
|
||||
(new Date(date.getFullYear(), date.getMonth() + 1, 0)).getDate()
|
||||
|
||||
firstWeekDay: (date) ->
|
||||
(new Date(date.getFullYear(), date.getMonth(), 1)).getDay()
|
||||
|
||||
monthInWords: (date) ->
|
||||
switch (date.getMonth())
|
||||
when 0 then 'January'
|
||||
when 1 then 'February'
|
||||
when 2 then 'March'
|
||||
when 3 then 'April'
|
||||
when 4 then 'May'
|
||||
when 5 then 'June'
|
||||
when 6 then 'July'
|
||||
when 7 then 'August'
|
||||
when 8 then 'September'
|
||||
when 9 then 'October'
|
||||
when 10 then 'November'
|
||||
when 11 then 'December'
|
||||
|
||||
monthInShortWords: (date) ->
|
||||
switch (date.getMonth())
|
||||
when 0 then 'Jan'
|
||||
when 1 then 'Feb'
|
||||
when 2 then 'Mar'
|
||||
when 3 then 'Apr'
|
||||
when 4 then 'May'
|
||||
when 5 then 'Jun'
|
||||
when 6 then 'Jul'
|
||||
when 7 then 'Aug'
|
||||
when 8 then 'Sep'
|
||||
when 9 then 'Oct'
|
||||
when 10 then 'Nov'
|
||||
when 11 then 'Dec'
|
||||
|
||||
weekDayInWords: (day) ->
|
||||
switch (day)
|
||||
when 0 then 'Sunday'
|
||||
when 1 then 'Monday'
|
||||
when 2 then 'Tuesday'
|
||||
when 3 then 'Wednesday'
|
||||
when 4 then 'Thursday'
|
||||
when 5 then 'Friday'
|
||||
when 6 then 'Saturday'
|
||||
|
||||
weekDayInShortWords: (day) ->
|
||||
switch (day)
|
||||
when 0 then 'Sun'
|
||||
when 1 then 'Mon'
|
||||
when 2 then 'Tue'
|
||||
when 3 then 'Wed'
|
||||
when 4 then 'Thu'
|
||||
when 5 then 'Fri'
|
||||
when 6 then 'Sat'
|
||||
|
||||
addDays: (date, days) ->
|
||||
newDate = @cloneDatetime(date)
|
||||
newDate.setDate(date.getDate() + days)
|
||||
newDate
|
||||
|
||||
addMonths: (date, months) ->
|
||||
newDate = @cloneDatetime(date)
|
||||
newDate.setMonth(date.getMonth() + months)
|
||||
newDate
|
||||
|
||||
addYears: (date, years) ->
|
||||
newDate = @cloneDatetime(date)
|
||||
newDate.setFullYear(date.getFullYear() + years)
|
||||
newDate
|
||||
|
||||
setDay: (date, day) ->
|
||||
newDate = @cloneDatetime(date)
|
||||
newDate.setDate(day)
|
||||
newDate
|
||||
|
||||
setYear: (date, year) ->
|
||||
newDate = @cloneDatetime(date)
|
||||
newDate.setFullYear(year)
|
||||
newDate
|
||||
|
||||
cloneDatetime: (date) ->
|
||||
new Date(date.getTime())
|
||||
|
||||
timeMode: (datetime) ->
|
||||
if datetime.getHours() >= 12 then 'pm' else 'am'
|
||||
|
||||
toggleTimeMode: (datetime) ->
|
||||
newDatetime = @cloneDatetime(datetime)
|
||||
hours = datetime.getHours()
|
||||
if hours > 12 then newDatetime.setHours(hours - 12) else newDatetime.setHours(hours + 12)
|
||||
newDatetime
|
||||
|
||||
setHours: (datetime, hours) ->
|
||||
newDatetime = @cloneDatetime(datetime)
|
||||
newDatetime.setHours(hours)
|
||||
newDatetime
|
||||
|
||||
setMinutes: (datetime, minutes) ->
|
||||
newDatetime = @cloneDatetime(datetime)
|
||||
newDatetime.setMinutes(minutes)
|
||||
newDatetime
|
|
@ -1,43 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Button = require '../button'
|
||||
Navigation = require '../navigation'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Dialog'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
actions : React.PropTypes.array
|
||||
active : React.PropTypes.bool
|
||||
className : React.PropTypes.string
|
||||
title : React.PropTypes.string
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
actions : []
|
||||
className : 'normal'
|
||||
|
||||
getInitialState: ->
|
||||
active : @props.active
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
rootClass = localCSS.root
|
||||
rootClass += ' active' if @state.active
|
||||
containerClass = "#{localCSS.container} #{@props.className}"
|
||||
containerClass += " #{@props.type}" if @props.type
|
||||
|
||||
<div data-react-toolbox='dialog' data-flex='vertical center' className={rootClass}>
|
||||
<div className={containerClass}>
|
||||
{<h1>{@props.title}</h1> if @props.title}
|
||||
{@props.children}
|
||||
{<Navigation actions={@props.actions}/> if @props.actions.length > 0}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
show: ->
|
||||
@setState active: true
|
||||
|
||||
hide: ->
|
||||
@setState active: false
|
|
@ -0,0 +1,52 @@
|
|||
/* global React */
|
||||
|
||||
import style from './style';
|
||||
import Navigation from '../navigation';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'Dialog',
|
||||
|
||||
propTypes: {
|
||||
actions: React.PropTypes.array,
|
||||
active: React.PropTypes.bool,
|
||||
className: React.PropTypes.string,
|
||||
title: React.PropTypes.string,
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
actions: [],
|
||||
className: 'normal'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return { active: this.props.active };
|
||||
},
|
||||
|
||||
render () {
|
||||
let rootClass = style.root;
|
||||
let containerClass = `${style.container} ${this.props.className}`;
|
||||
if (this.state.active) rootClass += ' active';
|
||||
if (this.props.type) containerClass += ` ${this.props.type}`;
|
||||
|
||||
return (
|
||||
<div data-react-toolbox='dialog' data-flex='vertical center' className={rootClass}>
|
||||
<div className={containerClass}>
|
||||
{ this.props.title ? <h1>{this.props.title}</h1> : null }
|
||||
{ this.props.children }
|
||||
{ this.props.actions.length > 0 ? <Navigation actions={this.props.actions}/> : null }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
show () {
|
||||
this.setState({active: true});
|
||||
},
|
||||
|
||||
hide () {
|
||||
this.setState({active: false});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Drawer',
|
||||
|
||||
propTypes: {
|
||||
active: React.PropTypes.bool,
|
||||
className: React.PropTypes.string,
|
||||
hideable: React.PropTypes.bool,
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
type: 'left'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return { active: this.props.active };
|
||||
},
|
||||
|
||||
handleOverlayClick () {
|
||||
if (this.props.hideable) {
|
||||
this.setState({active: false});
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${css.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
if (this.props.hideable) className += ' hideable';
|
||||
if (this.state.active) className += ' active';
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={css.overlay} onClick={this.handleOverlayClick}></div>
|
||||
<aside className={css.container}>
|
||||
{ this.props.children }
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
show () {
|
||||
this.setState({active: true});
|
||||
},
|
||||
|
||||
hide () {
|
||||
this.setState({active: false});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
@import '../constants'
|
||||
|
||||
WIDTH = (4 * UNIT)
|
||||
|
||||
:local(.root)
|
||||
height : 100vh
|
||||
pointer-events : none
|
||||
position : fixed
|
||||
top : 0
|
||||
left : 0
|
||||
right : 0
|
||||
width : 100vw
|
||||
z-index : 2
|
||||
|
||||
:local(.overlay)
|
||||
background-color : black
|
||||
height : 100%
|
||||
opacity : 0
|
||||
transition-duration : .5 * ANIMATION_DURATION
|
||||
transition-property : opacity
|
||||
transition-timing-function : ANIMATION_EASE
|
||||
width : 100%
|
||||
|
||||
:local(.container)
|
||||
background-color : WHITE
|
||||
box-shadow : ZDEPTH_SHADOW_1
|
||||
display : block
|
||||
height : 100%
|
||||
overflow-y : scroll
|
||||
position : absolute
|
||||
top : 0
|
||||
transform-style : preserve-3d
|
||||
transition-delay : 0s
|
||||
transition-duration : .5 * ANIMATION_DURATION
|
||||
transition-property : transform
|
||||
transition-timing-function : ANIMATION_EASE
|
||||
width : WIDTH
|
||||
will-change : transform
|
||||
|
||||
:local(.root)
|
||||
&.left
|
||||
> :local(.container)
|
||||
left : 0
|
||||
&:not(.active) > :local(.container)
|
||||
transform : translateX(- WIDTH)
|
||||
|
||||
&.right
|
||||
> :local(.container)
|
||||
right : 0
|
||||
&:not(.active) > :local(.container)
|
||||
transform : translateX(WIDTH)
|
||||
|
||||
&.active
|
||||
pointer-events : all
|
||||
> :local(.container)
|
||||
transition-delay : ANIMATION_DELAY
|
||||
transform : translateX(0)
|
||||
|
||||
> :local(.overlay)
|
||||
opacity : .5
|
|
@ -1,102 +0,0 @@
|
|||
###
|
||||
v2
|
||||
- can set a icon like dispatcher
|
||||
###
|
||||
|
||||
localCSS = require './style'
|
||||
Ripple = require '../ripple'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
dataSource : React.PropTypes.array
|
||||
disabled : React.PropTypes.bool
|
||||
label : React.PropTypes.string
|
||||
onChange : React.PropTypes.funca
|
||||
template : React.PropTypes.func
|
||||
type : React.PropTypes.string
|
||||
value : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
dataSource : []
|
||||
type : 'normal'
|
||||
|
||||
getInitialState: ->
|
||||
active : false
|
||||
ripple : undefined
|
||||
selected : _selectValue @props.value, @props.dataSource
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@setState
|
||||
height: @refs.values.getDOMNode().firstElementChild.getBoundingClientRect().height
|
||||
|
||||
componentDidUpdate: (prev_props, prev_state) ->
|
||||
@props.onChange? @ if prev_state.selected isnt @state.selected and prev_state.active
|
||||
|
||||
# -- Events
|
||||
onSelect: (event) ->
|
||||
@setState active: true, ripple: undefined unless @props.disabled
|
||||
|
||||
onItem: (event) ->
|
||||
unless @props.disabled
|
||||
client = event.target.getBoundingClientRect?()
|
||||
value = event.target.getAttribute('id').toString()
|
||||
for item in @props.dataSource when item.value.toString() is value
|
||||
@setState
|
||||
active : false
|
||||
selected : item
|
||||
ripple :
|
||||
left : event.pageX - client?.left
|
||||
top : event.pageY - client?.top
|
||||
width : (client?.width * 2)
|
||||
break
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
className += ' disabled' if @props.disabled
|
||||
if @state.active is true
|
||||
className += ' active'
|
||||
stylesheet = height: @state.height * @props.dataSource.length
|
||||
|
||||
<div data-react-toolbox='dropdown' className={className}>
|
||||
{ <label>{@props.label}</label> if @props.label }
|
||||
<ul ref='values' className={localCSS.values} style={stylesheet} onClick={@onItem}>
|
||||
{
|
||||
for item, index in @props.dataSource
|
||||
<li id={item.value} key={index} className={'selected' if item.value is @state.selected.value}>
|
||||
{ if @props.template then @props.template item else item.label }
|
||||
{ <Ripple className={localCSS.ripple} origin={@state.ripple}/> if item.value is @state.selected.value }
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div ref='value' className={localCSS.value} onClick={@onSelect}>
|
||||
{
|
||||
if @props.template
|
||||
@props.template @state.selected
|
||||
else
|
||||
<span>{@state.selected.label}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
getValue: ->
|
||||
@state.selected.value
|
||||
|
||||
setValue: (data) ->
|
||||
@setState selected: data
|
||||
|
||||
# -- Internal methods
|
||||
_selectValue = (value, dataSource) ->
|
||||
if value
|
||||
for item in dataSource when item.value.toString() is value.toString()
|
||||
return item
|
||||
break
|
||||
else
|
||||
dataSource[0]
|
|
@ -0,0 +1,126 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
import Ripple from '../ripple';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Dropdown',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
dataSource: React.PropTypes.array,
|
||||
disabled: React.PropTypes.bool,
|
||||
label: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func,
|
||||
template: React.PropTypes.func,
|
||||
type: React.PropTypes.string,
|
||||
value: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
dataSource: [],
|
||||
type: 'normal'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
active: false,
|
||||
selected: _selectValue(this.props.value, this.props.dataSource)
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({
|
||||
height: this.refs.values.getDOMNode().firstElementChild.getBoundingClientRect().height
|
||||
});
|
||||
},
|
||||
|
||||
componentDidUpdate (prev_props, prev_state) {
|
||||
if (this.props.onChange && prev_state.selected !== this.state.selected && prev_state.active) {
|
||||
this.props.onChange(this);
|
||||
}
|
||||
},
|
||||
|
||||
onSelect () {
|
||||
if (!this.props.disabled) {
|
||||
this.setState({active: true});
|
||||
}
|
||||
},
|
||||
|
||||
onItem (id) {
|
||||
if (!this.props.disabled) {
|
||||
let value = id.toString();
|
||||
for (let item of this.props.dataSource) {
|
||||
if (item.value.toString() === value) {
|
||||
this.setState({
|
||||
active: false,
|
||||
selected: item
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let stylesheet;
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
if (this.props.disabled) className += ' disabled';
|
||||
if (this.state.active === true) {
|
||||
className += ' active';
|
||||
stylesheet = { height: this.state.height * this.props.dataSource.length };
|
||||
}
|
||||
|
||||
const items = this.props.dataSource.map((item, index) => {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
id={item.value}
|
||||
onClick={this.onItem.bind(this, item.value)}
|
||||
style={{position: 'relative'}}
|
||||
className={ item.value === this.state.selected.value ? 'selected' : null}
|
||||
>
|
||||
{ this.props.template ? this.props.template(item) : item.label }
|
||||
<Ripple className={style.ripple}/>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-react-toolbox='dropdown' className={className}>
|
||||
{this.props.label ? <label>{this.props.label}</label> : null}
|
||||
<ul ref='values' className={style.values} style={stylesheet}>{ items }</ul>
|
||||
<div ref='value' className={style.value} onClick={this.onSelect}>
|
||||
{ this.props.template ? this.props.template(this.state.selected) : <span>{this.state.selected.label}</span> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
getValue () {
|
||||
return this.state.selected.value;
|
||||
},
|
||||
|
||||
setValue (data) {
|
||||
this.setState({selected: data});
|
||||
}
|
||||
});
|
||||
|
||||
function _selectValue (value, dataSource) {
|
||||
let item;
|
||||
if (value) {
|
||||
for (item of dataSource) {
|
||||
if (item.value.toString() === value.toString()) break;
|
||||
}
|
||||
return item;
|
||||
} else {
|
||||
return dataSource[0];
|
||||
}
|
||||
}
|
|
@ -26,10 +26,6 @@
|
|||
cursor : pointer
|
||||
&.selected
|
||||
color : PRIMARY
|
||||
> *
|
||||
pointer-events : none
|
||||
> :local(.ripple)
|
||||
background-color : DIVIDER
|
||||
|
||||
:local(.value)
|
||||
display : block
|
||||
|
@ -74,8 +70,6 @@
|
|||
opacity : 1
|
||||
transition : opacity ANIMATION_DURATION ANIMATION_EASE
|
||||
&:not(.active)
|
||||
> ul
|
||||
pointer-events : none
|
||||
> *, > * > li
|
||||
transition-delay : ANIMATION_DURATION
|
||||
|
||||
|
@ -99,3 +93,8 @@
|
|||
bottom : -(SPACE / 4)
|
||||
font-size : FONT_SIZE_TINY
|
||||
color : TEXT_SECONDARY
|
||||
|
||||
:local(.ripple)
|
||||
background-color : DIVIDER
|
||||
opacity : 1
|
||||
z-index : Z_INDEX_LOW
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
###
|
||||
@todo
|
||||
###
|
||||
|
||||
# -- Components
|
||||
Autocomplete = require './autocomplete'
|
||||
Dropdown = require './dropdown'
|
||||
Input = require './input'
|
||||
Switch = require './switch'
|
||||
# -- Constants
|
||||
TYPE =
|
||||
AUTOCOMPLETE: 'autocomplete'
|
||||
CHECKBOX : 'checkbox'
|
||||
DROPDOWN : 'dropdown'
|
||||
LABEL : 'label'
|
||||
RADIO : 'radio'
|
||||
SWITCH : 'switch'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
attributes : React.PropTypes.array
|
||||
className : React.PropTypes.string
|
||||
label : React.PropTypes.string
|
||||
value : React.PropTypes.any
|
||||
onChange : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
attributes : []
|
||||
className : ''
|
||||
|
||||
getInitialState: ->
|
||||
attributes : @props.attributes
|
||||
type : _determineType @props.attributes
|
||||
|
||||
# -- Lifecycle
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
if attributes = next_props.attributes
|
||||
@setState attributes: attributes, type: _determineType attributes
|
||||
@setValue next_props.value or @props.value
|
||||
|
||||
componentDidUpdate: ->
|
||||
if @state.type is TYPE.RADIO
|
||||
no_active = true
|
||||
no_active = false for key, ref of @refs when ref.getValue?() is true
|
||||
@refs[default].setValue true if no_active and default = Object.keys(@refs)?[0]
|
||||
|
||||
# -- Events
|
||||
onChange: (event) ->
|
||||
if @state.type is TYPE.RADIO
|
||||
for ref, el of @refs when el.refs.input.getDOMNode() isnt event.target
|
||||
el.setValue false
|
||||
|
||||
is_valid = true
|
||||
value = @getValue()
|
||||
for attr in @state.attributes when attr.required and value[attr.ref]?.trim() is ''
|
||||
is_valid = false
|
||||
@refs[attr.ref].setError? 'Required field'
|
||||
break
|
||||
setTimeout (=> @props.onChange event, @), 10 if @props.onChange?
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
<div data-react-toolbox='fieldset'
|
||||
className={@props.className} onChange={@onChange}>
|
||||
{ <label>{@props.label}</label> if @props.label }
|
||||
{
|
||||
for attribute, index in @state.attributes
|
||||
if attribute.type is TYPE.LABEL
|
||||
<label>{attribute.caption}</label>
|
||||
else if attribute.type is TYPE.AUTOCOMPLETE
|
||||
<Autocomplete key={index} {...attribute} onChange={@onChange}/>
|
||||
else if attribute.type is TYPE.DROPDOWN
|
||||
<Dropdown key={index} {...attribute} onChange={@onChange}/>
|
||||
else if attribute.type is TYPE.SWITCH
|
||||
<Switch key={index} {...attribute} onChange={@onChange}/>
|
||||
else
|
||||
<Input key={index} {...attribute} />
|
||||
}
|
||||
{ @props.children }
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
clean: ->
|
||||
instance.setValue? undefined for key, instance of @refs
|
||||
|
||||
getValue: ->
|
||||
value = {}
|
||||
if @state.type isnt TYPE.RADIO
|
||||
value[ref] = input.getValue() for ref, input of @refs when input.getValue?
|
||||
else
|
||||
value = ref for ref, input of @refs when input.getValue?() is true
|
||||
value
|
||||
|
||||
setValue: (data = {}) ->
|
||||
if data instanceof Object
|
||||
@refs[key]?.setValue? value for key, value of data
|
||||
else
|
||||
@refs[key].setValue? key is data for key of @refs
|
||||
|
||||
# -- Internal methods
|
||||
_determineType = (attributes) ->
|
||||
type = ''
|
||||
group_radio = true
|
||||
group_checkbox = true
|
||||
for attribute in attributes when attribute.type isnt TYPE.LABEL
|
||||
group_radio = false if attribute.type isnt TYPE.RADIO
|
||||
group_checkbox = false if attribute.type isnt TYPE.CHECKBOX
|
||||
type = TYPE.RADIO if group_radio
|
||||
type = TYPE.CHECKBOX if group_checkbox
|
||||
type
|
|
@ -1,20 +0,0 @@
|
|||
localCSS = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName: 'FontIcon',
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
value : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
|
||||
onClick: (event) ->
|
||||
@props.onClick? @props.onClick(event)
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className} #{@props.value}"
|
||||
<span data-react-toolbox='icon' className={className} onClick={@onClick} />
|
|
@ -0,0 +1,37 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'FontIcon',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
value: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: ''
|
||||
};
|
||||
},
|
||||
|
||||
onClick (event) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(event);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<span
|
||||
data-react-toolbox='icon'
|
||||
className={`${style.root} ${this.props.className} ${this.props.value}`}
|
||||
onClick={this.props.onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,105 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Autocomplete = require '../autocomplete'
|
||||
Dropdown = require '../dropdown'
|
||||
Button = require '../button'
|
||||
Input = require '../input'
|
||||
Switch = require '../switch'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
attributes : React.PropTypes.array
|
||||
className : React.PropTypes.string
|
||||
onChange : React.PropTypes.func
|
||||
onError : React.PropTypes.func
|
||||
onSubmit : React.PropTypes.func
|
||||
onValid : React.PropTypes.func
|
||||
storage : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
attributes : []
|
||||
className : ''
|
||||
|
||||
getInitialState: ->
|
||||
attributes : @storage @props
|
||||
|
||||
# -- Lifecycle
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
if next_props.attributes
|
||||
attributes = @storage next_props
|
||||
@setState attributes: attributes
|
||||
@setValue (item for item in attributes)
|
||||
|
||||
# -- Events
|
||||
onSubmit: (event) ->
|
||||
event.preventDefault()
|
||||
@props.onSubmit? event, @
|
||||
|
||||
onChange: (event) ->
|
||||
is_valid = true
|
||||
value = @getValue()
|
||||
for attr in @state.attributes when attr.required and value[attr.ref]?.trim() is ""
|
||||
is_valid = false
|
||||
@refs[attr.ref].setError? 'Required field'
|
||||
break
|
||||
|
||||
@props.onChange? event, @
|
||||
@storage @props, value if @props.storage
|
||||
if is_valid
|
||||
@refs.submit?.getDOMNode().removeAttribute 'disabled'
|
||||
@props.onValid? event, @
|
||||
else
|
||||
@refs.submit?.getDOMNode().setAttribute 'disabled', true
|
||||
@props.onError? event, @
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
<form data-react-toolbox='form' className={className}
|
||||
onChange={@onChange} onSubmit={@onSubmit}>
|
||||
{
|
||||
for attribute, index in @state.attributes
|
||||
if attribute.type is 'submit'
|
||||
<Button key={index} {...attribute} type='square' ref='submit' onClick={@onSubmit}/>
|
||||
else if attribute.type is 'autocomplete'
|
||||
<Autocomplete key={index} {...attribute} onChange={@onChange}/>
|
||||
else if attribute.type is 'dropdown'
|
||||
<Dropdown key={index} {...attribute} onChange={@onChange}/>
|
||||
else if attribute.type is 'switch'
|
||||
<Switch key={index} {...attribute} onChange={@onChange}/>
|
||||
else
|
||||
<Input key={index} {...attribute} />
|
||||
}
|
||||
{ @props.children }
|
||||
</form>
|
||||
|
||||
# -- Extends
|
||||
storage: (props, value) ->
|
||||
key = "react-toolbox-form-#{props.storage}"
|
||||
if value
|
||||
store = {}
|
||||
store[attr.ref] = value[attr.ref] for attr in props.attributes when attr.storage
|
||||
window.localStorage.setItem key, JSON.stringify store
|
||||
else if props.storage
|
||||
store = JSON.parse window.localStorage.getItem key or {}
|
||||
input.value = store?[input.ref] or input.value for input in props.attributes
|
||||
props.attributes
|
||||
|
||||
getValue: ->
|
||||
value = {}
|
||||
for ref, el of @refs when el.getValue?
|
||||
if ref.indexOf('.') is -1
|
||||
value[ref] = el.getValue()
|
||||
else
|
||||
parent = value
|
||||
for attr, index in hierarchy = ref.split('.')
|
||||
if index is hierarchy.length - 1
|
||||
parent[attr] = el.getValue()
|
||||
else
|
||||
parent[attr] = parent[attr] or {}
|
||||
parent = parent[attr]
|
||||
value
|
||||
|
||||
setValue: (data = {}) ->
|
||||
@refs[field.ref].setValue? field.value for field in data
|
|
@ -0,0 +1,159 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
import Autocomplete from '../autocomplete';
|
||||
import Dropdown from '../dropdown';
|
||||
import Button from '../button';
|
||||
import Input from '../input';
|
||||
import Switch from '../switch';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Form',
|
||||
|
||||
propTypes: {
|
||||
attributes: React.PropTypes.array,
|
||||
className: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func,
|
||||
onError: React.PropTypes.func,
|
||||
onSubmit: React.PropTypes.func,
|
||||
onValid: React.PropTypes.func,
|
||||
storage: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
attributes: [],
|
||||
className: ''
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
attributes: this.storage(this.props)
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps (next_props) {
|
||||
if (next_props.attributes) {
|
||||
let attributes = this.storage(next_props);
|
||||
this.setState({attributes: attributes});
|
||||
this.setValue(attributes.map((item) => { return item; }));
|
||||
}
|
||||
},
|
||||
|
||||
onSubmit (event) {
|
||||
event.preventDefault();
|
||||
if (this.props.onSubmit) {
|
||||
this.props.onSubmit(event, this);
|
||||
}
|
||||
},
|
||||
|
||||
onChange (event) {
|
||||
let is_valid = true;
|
||||
let value = this.getValue();
|
||||
for (let attr of this.state.attributes) {
|
||||
if (attr.required && value[attr.ref] !== undefined && value[attr.ref].trim() === '') {
|
||||
is_valid = false;
|
||||
console.log('NOT VALUD');
|
||||
if (this.refs[attr.ref].setError) this.refs[attr.ref].setError('Requited field');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.onChange) this.props.onChange(event, this);
|
||||
if (this.props.storage) this.storage(this.props, value);
|
||||
|
||||
if (is_valid) {
|
||||
if (this.refs.submit) this.refs.submit.getDOMNode().removeAttribute('disabled');
|
||||
if (this.props.onValid) this.props.onValid(event, this);
|
||||
} else {
|
||||
if (this.refs.submit) this.refs.submit.getDOMNode().setAttribute('disabled', true);
|
||||
if (this.props.onError) this.props.onError(event, this);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
const attributes = this.state.attributes.map((attribute, index) => {
|
||||
if (attribute.type === 'submit') {
|
||||
return <Button key={index} {...attribute} type='square' ref='submit' onClick={this.onSubmit}/>;
|
||||
} else if (attribute.type === 'autocomplete') {
|
||||
return <Autocomplete key={index} {...attribute} onChange={this.onChange}/>;
|
||||
} else if (attribute.type === 'dropdown') {
|
||||
return <Dropdown key={index} {...attribute} onChange={this.onChange}/>;
|
||||
} else if (attribute.type === 'switch') {
|
||||
return <Switch key={index} {...attribute} onChange={this.onChange}/>;
|
||||
} else {
|
||||
return <Input key={index} {...attribute} />;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
data-react-toolbox='form'
|
||||
className={className}
|
||||
onChange={this.onChange}
|
||||
onSubmit={this.onSubmit}
|
||||
>
|
||||
{ attributes }
|
||||
{ this.props.children }
|
||||
</form>
|
||||
);
|
||||
},
|
||||
|
||||
storage (props, value) {
|
||||
let key = `react-toolbox-form-${props.storage}`;
|
||||
if (value) {
|
||||
let store = {};
|
||||
for (let attr of props.attributes) {
|
||||
if (attr.storage) store[attr.ref] = value[attr.ref];
|
||||
}
|
||||
window.localStorage.setItem(key, JSON.stringify(store));
|
||||
} else if (props.storage) {
|
||||
let store = JSON.parse(window.localStorage.getItem(key) || {});
|
||||
for (let input of props.attributes) {
|
||||
if (store && store[input.ref]) {
|
||||
input.value = store[input.ref];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return props.attributes;
|
||||
},
|
||||
|
||||
getValue () {
|
||||
let value = {};
|
||||
for (let ref of Object.keys(this.refs)) {
|
||||
let el = this.refs[ref];
|
||||
if (el.getValue) {
|
||||
if (ref.indexOf('.') === -1) {
|
||||
value[ref] = el.getValue();
|
||||
} else {
|
||||
let parent = value;
|
||||
let hierarchy = ref.split('.');
|
||||
hierarchy.forEach((attr, index) => {
|
||||
if (index === hierarchy.length - 1) {
|
||||
parent[attr] = el.getValue();
|
||||
} else {
|
||||
parent[attr] = parent[attr] || {};
|
||||
parent = parent[attr];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
setValue (data = {}) {
|
||||
for (let field of data) {
|
||||
if (this.refs[field.ref].setValue) {
|
||||
this.refs[field.ref].setValue(field.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
module.exports =
|
||||
# -- Components
|
||||
Aside : require "./aside"
|
||||
Autocomplete : require "./autocomplete"
|
||||
Button : require "./button"
|
||||
Card : require "./card"
|
||||
Dialog : require "./dialog"
|
||||
Dropdown : require "./dropdown"
|
||||
Fieldset : require "./fieldset"
|
||||
FontIcon : require "./font_icon"
|
||||
Form : require "./form"
|
||||
Input : require "./input"
|
||||
Link : require "./link"
|
||||
List : require "./list"
|
||||
Loading : require "./loading"
|
||||
Navigation : require "./navigation"
|
||||
ProgressBar : require "./progress_bar"
|
||||
Ripple : require "./ripple"
|
||||
Slider : require "./slider"
|
||||
Switch : require "./switch"
|
||||
Tab : require "./tabs/tab"
|
||||
Tabs : require "./tabs/tabs"
|
||||
# -- Tools
|
|
@ -0,0 +1,22 @@
|
|||
module.exports = {
|
||||
Aside: require('./aside'),
|
||||
Autocomplete: require('./autocomplete'),
|
||||
Button: require('./button'),
|
||||
Card: require('./card'),
|
||||
Dialog: require('./dialog'),
|
||||
Dropdown: require('./dropdown'),
|
||||
Fieldset: require('./fieldset'),
|
||||
FontIcon: require('./font_icon'),
|
||||
Form: require('./form'),
|
||||
Input: require('./input'),
|
||||
Link: require('./link'),
|
||||
List: require('./list'),
|
||||
Loading: require('./loading'),
|
||||
Navigation: require('./navigation'),
|
||||
ProgressBar: require('./progress_bar'),
|
||||
Ripple: require('./ripple'),
|
||||
Slider: require('./slider'),
|
||||
Switch: require('./switch'),
|
||||
Tab: require('./tabs/tab'),
|
||||
Tabs: require('./tabs/tabs')
|
||||
};
|
|
@ -1,119 +0,0 @@
|
|||
localCSS = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Input'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
disabled : React.PropTypes.bool
|
||||
error : React.PropTypes.string
|
||||
label : React.PropTypes.string
|
||||
multiline : React.PropTypes.bool
|
||||
onBlur : React.PropTypes.func
|
||||
onChange : React.PropTypes.func
|
||||
onKeyPress : React.PropTypes.func
|
||||
onFocus : React.PropTypes.func
|
||||
onBlur : React.PropTypes.func
|
||||
required : React.PropTypes.bool
|
||||
type : React.PropTypes.string
|
||||
value : React.PropTypes.any
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
disabled : false
|
||||
multiline : false
|
||||
required : false
|
||||
type : 'text'
|
||||
|
||||
getInitialState: ->
|
||||
checked : @props.value
|
||||
error : @props.error
|
||||
touch : @props.type in ['checkbox', 'radio']
|
||||
value : @props.value
|
||||
focus : false
|
||||
valid : false
|
||||
|
||||
# -- Events
|
||||
onBlur: (event) ->
|
||||
@setState focus: false
|
||||
@props.onBlur? event, @
|
||||
|
||||
onChange: (event) ->
|
||||
if @state.touch
|
||||
@setState checked: event.target.checked, error: undefined
|
||||
else
|
||||
@setState value: event.target.value, error: undefined
|
||||
@props.onChange? event, @
|
||||
|
||||
onFocus: (event) ->
|
||||
@setState focus: true
|
||||
@props.onFocus? event, @
|
||||
|
||||
onKeyPress: (event) ->
|
||||
@setState focus: true
|
||||
@props.onKeyPress? event, @
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
className += ' checked' if @state.checked
|
||||
className += ' disabled' if @props.disabled
|
||||
className += ' error' if @state.error
|
||||
className += ' focus' if @state.focus
|
||||
className += ' hidden' if @props.type is 'hidden'
|
||||
className += ' touch' if @state.touch
|
||||
className += ' radio' if @props.type is 'radio'
|
||||
className += ' valid' if @state.value? and @state.value.length > 0
|
||||
|
||||
<div data-react-toolbox='input' className={className}>
|
||||
{
|
||||
if @props.multiline
|
||||
<textarea ref='input' {...@props}
|
||||
value={@state.value}
|
||||
onChange={@onChange}
|
||||
onKeyPress={@onKeyPress}
|
||||
onFocus={@onFocus}
|
||||
onBlur={@onBlur}
|
||||
value={@state.value} />
|
||||
|
||||
else if @props.type is 'file'
|
||||
delete @props.value
|
||||
<input ref="input" {...@props} onChange={@onChange} />
|
||||
|
||||
else
|
||||
<input ref='input' {...@props}
|
||||
value={@state.value}
|
||||
checked={@state.checked}
|
||||
onBlur={@onBlur}
|
||||
onChange={@onChange}
|
||||
onFocus={@onFocus}
|
||||
onKeyPress={@onKeyPress} />
|
||||
}
|
||||
<span className='bar'></span>
|
||||
{ <label>{@props.label}</label> if @props.label }
|
||||
{ <span className='error'>{@state.error}</span> if @state.error }
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
blur: ->
|
||||
@refs.input.blur?()
|
||||
|
||||
focus: ->
|
||||
@refs.input.focus?()
|
||||
|
||||
getValue: ->
|
||||
if @props.type is 'file'
|
||||
@state.value
|
||||
else
|
||||
@refs.input?.getDOMNode()[if @state.touch then 'checked' else 'value']
|
||||
|
||||
setError: (data = 'Unknown error') ->
|
||||
@setState error: @props.error or data
|
||||
|
||||
setValue: (data) ->
|
||||
data = false if @state.touch and data is undefined
|
||||
attributes = value: data
|
||||
attributes.checked = data if @state.touch and data?
|
||||
@setState attributes
|
|
@ -0,0 +1,154 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Input',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
disabled: React.PropTypes.bool,
|
||||
error: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
multiline: React.PropTypes.bool,
|
||||
onBlur: React.PropTypes.func,
|
||||
onChange: React.PropTypes.func,
|
||||
onFocus: React.PropTypes.func,
|
||||
onKeyPress: React.PropTypes.func,
|
||||
required: React.PropTypes.bool,
|
||||
type: React.PropTypes.string,
|
||||
value: React.PropTypes.any
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
disabled: false,
|
||||
multiline: false,
|
||||
required: false,
|
||||
type: 'text'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
checked: this.props.value,
|
||||
error: this.props.error,
|
||||
touch: ['checkbox', 'radio'].indexOf(this.props.type) !== -1,
|
||||
value: this.props.value,
|
||||
focus: false,
|
||||
valid: false
|
||||
};
|
||||
},
|
||||
|
||||
onBlur (event) {
|
||||
this.setState({focus: false});
|
||||
if (this.props.onBlur) this.props.onBlur(event, this);
|
||||
},
|
||||
|
||||
onChange (event) {
|
||||
if (this.state.touch) {
|
||||
this.setState({checked: event.target.checked, error: undefined});
|
||||
} else {
|
||||
this.setState({value: event.target.value, error: undefined});
|
||||
}
|
||||
if (this.props.onChange) this.props.onChange(event, this);
|
||||
},
|
||||
|
||||
onFocus (event) {
|
||||
this.setState({focus: true});
|
||||
if (this.props.onFocus) this.props.onFocus(event, this);
|
||||
},
|
||||
|
||||
onKeyPress (event) {
|
||||
this.setState({focus: true});
|
||||
if (this.props.onKeyPress) this.props.onKeyPress(event, this);
|
||||
},
|
||||
|
||||
renderInput () {
|
||||
if (this.props.multiline) {
|
||||
return (
|
||||
<textarea
|
||||
ref='input'
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
onKeyPress={this.onKeyPress}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
value={this.state.value} />
|
||||
);
|
||||
} else if (this.props.type === 'file') {
|
||||
return (
|
||||
<input
|
||||
ref='input'
|
||||
{...this.props}
|
||||
value={undefined}
|
||||
onChange={this.onChange} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<input
|
||||
ref='input'
|
||||
{...this.props}
|
||||
value={this.state.value}
|
||||
checked={this.state.checked}
|
||||
onBlur={this.onBlur}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onKeyPress={this.onKeyPress} />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
if (this.state.checked) className += ' checked';
|
||||
if (this.props.disabled) className += ' disabled';
|
||||
if (this.state.error) className += ' error';
|
||||
if (this.state.focus) className += ' focus';
|
||||
if (this.props.type === 'hidden') className += ' hidden';
|
||||
if (this.state.touch) className += ' touch';
|
||||
if (this.props.type === 'radio') className += ' radio';
|
||||
if (this.state.value && this.state.value.length > 0) className += ' valid';
|
||||
|
||||
return (
|
||||
<div data-react-toolbox='input' className={className}>
|
||||
{ this.renderInput() }
|
||||
<span className='bar'></span>
|
||||
{ this.props.label ? <label>{this.props.label}</label> : null }
|
||||
{ this.state.error ? <span className='error'>{this.state.error}</span> : null }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
blur () {
|
||||
this.refs.input.getDOMNode().blur();
|
||||
},
|
||||
|
||||
focus () {
|
||||
this.refs.input.getDOMNode().focus();
|
||||
},
|
||||
|
||||
getValue () {
|
||||
if (this.props.type === 'file') {
|
||||
return this.state.value;
|
||||
} else if (this.refs.input) {
|
||||
return this.refs.input.getDOMNode()[this.state.touch ? 'checked' : 'value'];
|
||||
}
|
||||
},
|
||||
|
||||
setError (data = 'Unknown error') {
|
||||
this.setState({error: this.props.error || data});
|
||||
},
|
||||
|
||||
setValue (argData) {
|
||||
let data = this.state.touch && argData === undefined ? false : argData;
|
||||
let attributes = { value: data };
|
||||
if (this.state.touch && data) attributes.checked = data;
|
||||
this.setState(attributes);
|
||||
}
|
||||
});
|
|
@ -1,31 +0,0 @@
|
|||
localCSS = require './style'
|
||||
FontIcon = require '../font_icon'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
caption : React.PropTypes.string
|
||||
className : React.PropTypes.string
|
||||
count : React.PropTypes.number
|
||||
icon : React.PropTypes.string
|
||||
onClick : React.PropTypes.func
|
||||
route : React.PropTypes.array
|
||||
|
||||
getDefaultProps: ->
|
||||
attributes : ''
|
||||
className : ''
|
||||
|
||||
# -- Events
|
||||
onClick: (event) ->
|
||||
@props.onClick? event, @
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
<a data-react-toolbox='link' href={"##{@props.route}"} className={className}
|
||||
onClick={@onClick} data-flex='horizontal center'>
|
||||
{ <FontIcon className={localCSS.icon} value={@props.icon} /> if @props.icon }
|
||||
{ <abbr>{@props.caption}</abbr> if @props.caption }
|
||||
{ <small>{@props.count}</small> if @props.count and parseInt(@props.count) isnt 0}
|
||||
</a>
|
|
@ -0,0 +1,49 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
import FontIcon from '../font_icon';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Link',
|
||||
|
||||
propTypes: {
|
||||
label: React.PropTypes.string,
|
||||
className: React.PropTypes.string,
|
||||
count: React.PropTypes.number,
|
||||
icon: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
route: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
attributes: '',
|
||||
className: ''
|
||||
};
|
||||
},
|
||||
|
||||
onClick (event) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(event, this);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<a
|
||||
data-react-toolbox='link'
|
||||
data-flex='horizontal center'
|
||||
href={`${this.props.route}`}
|
||||
className={`${style.root} ${this.props.className}`}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{ this.props.icon ? <FontIcon className={style.icon} value={this.props.icon} /> : null }
|
||||
{ this.props.label ? <abbr>{this.props.label}</abbr> : null }
|
||||
{ this.props.count && parseInt(this.props.count) !== 0 ? <small>{this.props.count}</small> : null}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -3,7 +3,7 @@
|
|||
```
|
||||
var Link = require('react-toolbox/components/link');
|
||||
|
||||
<Link route='http://google.com' caption='Go to Google.com' />
|
||||
<Link route='http://google.com' label='Go to Google.com' />
|
||||
<Link route='/profile/soyjavi' icon='user' />
|
||||
```
|
||||
|
||||
|
@ -11,9 +11,9 @@ var Link = require('react-toolbox/components/link');
|
|||
|
||||
| Name | Type | Default | Description|
|
||||
|:- |:-: | :- |:-|
|
||||
| **caption** | String | "normal" | he text string to use for the floating label element.|
|
||||
| **label** | String | "normal" | he text string to use for the floating label element.|
|
||||
| **className** | String | | Sets the class-styles of the Component.|
|
||||
| **count** | Number | | Sets a count number behind caption property.|
|
||||
| **count** | Number | | Sets a count number behind label property.|
|
||||
| **icon** | String | | Sets a <FontIcon/> sub-component.|
|
||||
| **onClick** | Function | | Dispatch event when user clicks on component.|
|
||||
| **route** | String | | URL String|
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
localCSS = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
dataSource : React.PropTypes.array
|
||||
ItemFactory : React.PropTypes.func
|
||||
onClick : React.PropTypes.func
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
attributes : ''
|
||||
className : ''
|
||||
dataSource : []
|
||||
type : 'default'
|
||||
|
||||
# -- Events
|
||||
onClick: (event, data, index) ->
|
||||
@props.onClick? event, item, (@refs[index] if @refs[index]?)
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
|
||||
<ul data-react-toolbox='list' className={className}>
|
||||
{
|
||||
for data, index in @props.dataSource
|
||||
<li key={index} onClick={@onClick.bind null, data, index}>
|
||||
{@props.itemFactory data, index}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
|
@ -0,0 +1,52 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'List',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
dataSource: React.PropTypes.array,
|
||||
ItemFactory: React.PropTypes.func,
|
||||
onClick: React.PropTypes.func,
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
attributes: '',
|
||||
className: '',
|
||||
dataSource: [],
|
||||
type: 'default'
|
||||
};
|
||||
},
|
||||
|
||||
onClick (event, data, index) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(event, data, (this.refs[index] ? this.refs[index] : null));
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
|
||||
const items = this.props.dataSource.map((data, index) => {
|
||||
return (
|
||||
<li key={index} onClick={this.onClick.bind(null, data, index)}>
|
||||
{this.props.itemFactory(data, index)}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<ul data-react-toolbox='list' className={className}>
|
||||
{ items }
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
###
|
||||
@todo
|
||||
###
|
||||
|
||||
require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
type : "normal"
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
<div data-component-loading={@props.type} data-flex="vertical center">
|
||||
<div></div><div></div><div></div>
|
||||
</div>
|
|
@ -0,0 +1,28 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
require('./style');
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Loading',
|
||||
|
||||
propTypes: {
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
type: 'normal'
|
||||
};
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div data-component-loading={this.props.type} data-flex="vertical center">
|
||||
<div></div><div></div><div></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Button = require '../button'
|
||||
Link = require '../link'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'Navigation'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
actions : React.PropTypes.array
|
||||
className : React.PropTypes.string
|
||||
routes : React.PropTypes.array
|
||||
type : React.PropTypes.string
|
||||
|
||||
getDefaultProps: ->
|
||||
actions : []
|
||||
className : ''
|
||||
type : 'default'
|
||||
routes : []
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += " #{@props.type}" if @props.type
|
||||
|
||||
<nav data-react-toolbox='navigation' className={className}>
|
||||
{ <Link key={index} {...route} /> for route, index in @props.routes }
|
||||
{ <Button key={index} {...action} /> for action, index in @props.actions }
|
||||
{ @props.children }
|
||||
</nav>
|
|
@ -0,0 +1,49 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
import Button from '../button';
|
||||
import Link from '../link';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Navigation',
|
||||
|
||||
propTypes: {
|
||||
actions: React.PropTypes.array,
|
||||
className: React.PropTypes.string,
|
||||
routes: React.PropTypes.array,
|
||||
type: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
actions: [],
|
||||
className: '',
|
||||
type: 'default',
|
||||
routes: []
|
||||
};
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
if (this.props.type) className += ` ${this.props.type}`;
|
||||
|
||||
const buttons = this.props.actions.map((action, index) => {
|
||||
return <Button key={index} {...action} />;
|
||||
});
|
||||
|
||||
const links = this.props.routes.map((route, index) => {
|
||||
return <Link key={index} {...route} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<nav data-react-toolbox='navigation' className={className}>
|
||||
{ links }
|
||||
{ buttons }
|
||||
{ this.props.children }
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
capitalized = (prop) ->
|
||||
prop[0].toUpperCase() + prop[1..-1].toLowerCase()
|
||||
|
||||
vendorNoMoz = (prop, args) ->
|
||||
"Webkit#{capitalized(prop)}": args
|
||||
"Ms#{capitalized(prop)}": args
|
||||
"#{prop}": args
|
||||
|
||||
module.exports =
|
||||
transform: (value) -> vendorNoMoz('transform', value)
|
|
@ -1,48 +0,0 @@
|
|||
TestUtils = React.addons.TestUtils
|
||||
expect = require('expect')
|
||||
utils = require('../../test_utils')
|
||||
ProgressBar = require('../index')
|
||||
|
||||
describe 'ProgressBar', ->
|
||||
describe '#calculateRatio', ->
|
||||
before ->
|
||||
@progressBar = utils.renderComponent(ProgressBar, { min: 100, max: 300 })
|
||||
|
||||
it 'calculates the right ratio', ->
|
||||
expect(@progressBar.calculateRatio(150)).toEqual(0.25)
|
||||
|
||||
it 'gets 0 when value is less than min', ->
|
||||
expect(@progressBar.calculateRatio(10)).toEqual(0)
|
||||
|
||||
it 'gets 1 when value is more than max', ->
|
||||
expect(@progressBar.calculateRatio(400)).toEqual(1)
|
||||
|
||||
describe '#render', ->
|
||||
it 'renders the value and buffer bars when it is linear', ->
|
||||
progressBarWrapper = utils.shallowRenderComponent(ProgressBar).props.children
|
||||
expect(progressBarWrapper.props.children.length).toEqual(2)
|
||||
expect(progressBarWrapper.props.children[0].ref).toEqual('buffer')
|
||||
expect(progressBarWrapper.props.children[1].ref).toEqual('value')
|
||||
|
||||
it 'renders the proper scaleX for buffer and value when its linear and determinate', ->
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {mode: 'determinate', value: 30, buffer: 60})
|
||||
buffer = (progressBar.props.children.props.children[0])
|
||||
value = (progressBar.props.children.props.children[1])
|
||||
expect(buffer.props.style.transform).toEqual("scaleX(#{0.6})")
|
||||
expect(value.props.style.transform).toEqual("scaleX(#{0.3})")
|
||||
|
||||
it 'renders the svg circle when it is circular', ->
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {type: 'circular'})
|
||||
expect(progressBar.props.children.type).toEqual('svg')
|
||||
expect(progressBar.props.children.props.children.type).toEqual('circle')
|
||||
|
||||
it 'renders the proper circle length style when it is circular and determinate', ->
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {type: 'circular', mode: 'determinate', value: 30})
|
||||
circle = progressBar.props.children.props.children
|
||||
strokeLength = 2 * Math.PI * circle.props.r * 0.3
|
||||
expect(circle.props.style.strokeDasharray).toEqual("#{strokeLength}, 400")
|
||||
|
||||
it 'contains mode and className in its className', ->
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {mode: 'determinate', className: 'tight'})
|
||||
expect(progressBar.props.className).toContain('determinate')
|
||||
expect(progressBar.props.className).toContain('tight')
|
|
@ -0,0 +1,63 @@
|
|||
import expect from 'expect';
|
||||
import utils from '../../utils/testing';
|
||||
import ProgressBar from '../index';
|
||||
|
||||
describe('ProgressBar', function () {
|
||||
let progressBar;
|
||||
|
||||
describe('#calculateRatio', function () {
|
||||
before(function () {
|
||||
progressBar = utils.renderComponent(ProgressBar, {min: 100, max: 300});
|
||||
});
|
||||
|
||||
it('calculates the right ratio', function () {
|
||||
expect(progressBar.calculateRatio(150)).toEqual(0.25);
|
||||
});
|
||||
|
||||
it('gets 0 when value is less than min', function () {
|
||||
expect(progressBar.calculateRatio(10)).toEqual(0);
|
||||
});
|
||||
|
||||
it('gets 1 when value is more than max', function () {
|
||||
expect(progressBar.calculateRatio(400)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#render', function () {
|
||||
let buffer, value, wrapper, circle, strokeLength;
|
||||
|
||||
it('renders the value and buffer bars when it is linear', function () {
|
||||
wrapper = utils.shallowRenderComponent(ProgressBar).props.children;
|
||||
expect(wrapper.props.children.length).toEqual(2);
|
||||
expect(wrapper.props.children[0].ref).toEqual('buffer');
|
||||
expect(wrapper.props.children[1].ref).toEqual('value');
|
||||
});
|
||||
|
||||
it('renders the value and buffer bars when it is linear', function () {
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {mode: 'determinate', value: 30, buffer: 60});
|
||||
buffer = (progressBar.props.children.props.children[0]);
|
||||
value = (progressBar.props.children.props.children[1]);
|
||||
expect(buffer.props.style.transform).toEqual(`scaleX(${0.6})`);
|
||||
expect(value.props.style.transform).toEqual(`scaleX(${0.3})`);
|
||||
});
|
||||
|
||||
it('renders the svg circle when it is circular', function () {
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {type: 'circular'});
|
||||
expect(progressBar.props.children.type).toEqual('svg');
|
||||
expect(progressBar.props.children.props.children.type).toEqual('circle');
|
||||
});
|
||||
|
||||
it('renders the proper circle length style when it is circular and determinate', function () {
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {type: 'circular', mode: 'determinate', value: 30});
|
||||
circle = progressBar.props.children.props.children;
|
||||
strokeLength = 2 * Math.PI * circle.props.r * 0.3;
|
||||
expect(circle.props.style.strokeDasharray).toEqual(`${strokeLength}, 400`);
|
||||
});
|
||||
|
||||
it('contains mode and className in its className', function () {
|
||||
progressBar = utils.shallowRenderComponent(ProgressBar, {mode: 'determinate', className: 'tight'});
|
||||
expect(progressBar.props.className).toContain('determinate');
|
||||
expect(progressBar.props.className).toContain('tight');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,68 +0,0 @@
|
|||
localCSS = require './style'
|
||||
prefixer = require '../prefixer'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- Properties
|
||||
propTypes:
|
||||
buffer : React.PropTypes.number
|
||||
className : React.PropTypes.string
|
||||
max : React.PropTypes.number
|
||||
min : React.PropTypes.number
|
||||
mode : React.PropTypes.string
|
||||
multicolor : React.PropTypes.bool
|
||||
type : React.PropTypes.string
|
||||
value : React.PropTypes.number
|
||||
|
||||
getDefaultProps: ->
|
||||
buffer : 0
|
||||
className : ''
|
||||
max : 100
|
||||
min : 0
|
||||
mode : 'indeterminate'
|
||||
multicolor : false
|
||||
type : 'linear'
|
||||
value : 0
|
||||
|
||||
# -- Helper methods
|
||||
calculateRatio: (value) ->
|
||||
return 0 if value < @props.min
|
||||
return 1 if value > @props.max
|
||||
return (value - @props.min) / (@props.max - @props.min)
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = if @props.type == 'linear' then localCSS.linearBar else localCSS.circularBar
|
||||
className += " #{@props.className}" if @props.className
|
||||
className += " #{@props.mode}" if @props.mode
|
||||
className += " multicolor" if @props.multicolor
|
||||
|
||||
<div className={className} role="progressbar"
|
||||
aria-valuenow={@props.value}
|
||||
aria-valuemin={@props.min}
|
||||
aria-valuemax={@props.max}>
|
||||
{ if @props.type == 'circular' then @renderCircular() else @renderLinear() }
|
||||
</div>
|
||||
|
||||
renderCircular: ->
|
||||
<svg className={localCSS.circle}>
|
||||
<circle className={localCSS.circlePath} style={@circularStyle()} cx="30" cy="30" r="25"/>
|
||||
</svg>
|
||||
|
||||
circularStyle: ->
|
||||
_transformDasharray(@calculateRatio(@props.value)) unless @props.mode == 'indeterminate'
|
||||
|
||||
renderLinear: ->
|
||||
<div>
|
||||
<span ref="buffer" data-ref="buffer" className={localCSS.bufferBar} style={@linearStyles()?.buffer}></span>
|
||||
<span ref="value" data-ref="value" className={localCSS.valueBar} style={@linearStyles()?.value}></span>
|
||||
</div>
|
||||
|
||||
linearStyles: ->
|
||||
unless @props.mode == 'indeterminate'
|
||||
buffer: prefixer.transform("scaleX(#{@calculateRatio(@props.buffer)})")
|
||||
value: prefixer.transform("scaleX(#{@calculateRatio(@props.value)})")
|
||||
|
||||
# -- Private methods
|
||||
_transformDasharray = (ratio) ->
|
||||
strokeDasharray: "#{2 * Math.PI * 25 * ratio}, 400"
|
|
@ -0,0 +1,92 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import prefixer from '../utils/prefixer';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
buffer: React.PropTypes.number,
|
||||
className: React.PropTypes.string,
|
||||
max: React.PropTypes.number,
|
||||
min: React.PropTypes.number,
|
||||
mode: React.PropTypes.string,
|
||||
multicolor: React.PropTypes.bool,
|
||||
type: React.PropTypes.string,
|
||||
value: React.PropTypes.number
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
buffer: 0,
|
||||
className: '',
|
||||
max: 100,
|
||||
min: 0,
|
||||
mode: 'indeterminate',
|
||||
multicolor: false,
|
||||
type: 'linear',
|
||||
value: 0
|
||||
};
|
||||
},
|
||||
|
||||
calculateRatio (value) {
|
||||
if (value < this.props.min) return 0;
|
||||
if (value > this.props.max) return 1;
|
||||
return (value - this.props.min) / (this.props.max - this.props.min);
|
||||
},
|
||||
|
||||
circularStyle () {
|
||||
if (this.props.mode !== 'indeterminate') {
|
||||
return {strokeDasharray: `${2 * Math.PI * 25 * this.calculateRatio(this.props.value)}, 400`};
|
||||
}
|
||||
},
|
||||
|
||||
renderCircular () {
|
||||
return (
|
||||
<svg className={css.circle}>
|
||||
<circle className={css.circlePath} style={this.circularStyle()} cx="30" cy="30" r="25" />
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
|
||||
linearStyle () {
|
||||
if (this.props.mode !== 'indeterminate') {
|
||||
return {
|
||||
buffer: prefixer({transform: `scaleX(${this.calculateRatio(this.props.buffer)})`}),
|
||||
value: prefixer({transform: `scaleX(${this.calculateRatio(this.props.value)})`})
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
renderLinear () {
|
||||
const {buffer, value} = this.linearStyle();
|
||||
return (
|
||||
<div>
|
||||
<span ref="buffer" data-ref="buffer" className={css.bufferBar} style={buffer}></span>
|
||||
<span ref="value" data-ref="value" className={css.valueBar} style={value}></span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = this.props.type === 'linear' ? css.linearBar : css.circularBar;
|
||||
if (this.props.className) className += ` ${this.props.className}`;
|
||||
if (this.props.mode) className += ` ${this.props.mode}`;
|
||||
if (this.props.multicolor) className += ` multicolor`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
role="progressbar"
|
||||
aria-valuenow={this.props.value}
|
||||
aria-valuemin={this.props.min}
|
||||
aria-valuemax={this.props.max}>
|
||||
{ this.props.type === 'circular' ? this.renderCircular() : this.renderLinear() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
localCSS = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
loading : React.PropTypes.bool
|
||||
origin : React.PropTypes.object
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
loading : false
|
||||
|
||||
getInitialState: ->
|
||||
className : undefined
|
||||
|
||||
# -- Lifecycle
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
@setState className: "active" if next_props.origin?
|
||||
|
||||
componentDidMount: ->
|
||||
el = @getDOMNode()
|
||||
for key in ['animationend', 'webkitAnimationEnd', 'oAnimationEnd', 'MSAnimationEnd']
|
||||
el.addEventListener key, (=> @setState className: undefined), false
|
||||
@setState className: 'active' if @props.origin?
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className} #{@state.className}"
|
||||
className += ' loading' if @props.loading
|
||||
<div data-react-toolbox='ripple' className={className}
|
||||
style={
|
||||
left : @props.origin?.left,
|
||||
top : @props.origin?.top,
|
||||
width : @props.origin?.width,
|
||||
height: @props.origin?.width} />
|
|
@ -0,0 +1,77 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Ripple',
|
||||
|
||||
propTypes: {
|
||||
auto: React.PropTypes.bool,
|
||||
className: React.PropTypes.string,
|
||||
loading: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
auto: true,
|
||||
className: '',
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
active: false,
|
||||
restarting: false,
|
||||
top: null,
|
||||
left: null,
|
||||
width: null
|
||||
};
|
||||
},
|
||||
|
||||
start ({ pageX, pageY }) {
|
||||
const {top, left, width} = this._getDescriptor(pageX, pageY);
|
||||
this.setState({active: false, restarting: true, width: 0}, () => {
|
||||
this.refs.ripple.getDOMNode().offsetWidth; //eslint-disable-line no-unused-expressions
|
||||
this.setState({active: true, restarting: false, top: top, left: left, width: width});
|
||||
});
|
||||
},
|
||||
|
||||
end () {
|
||||
this.setState({active: false});
|
||||
},
|
||||
|
||||
_getDescriptor (pageX, pageY) {
|
||||
let { left, top, width } = this.getDOMNode().getBoundingClientRect();
|
||||
return {
|
||||
left: pageX - left,
|
||||
top: pageY - top,
|
||||
width: width * 2.5
|
||||
};
|
||||
},
|
||||
|
||||
render () {
|
||||
let { left, top, width } = this.state;
|
||||
let className = `${style.ripple} ${this.props.className}`;
|
||||
if (this.state.active) className += ' active';
|
||||
if (this.state.restarting) className += ' restarting';
|
||||
if (this.props.loading) className += ' loading';
|
||||
|
||||
return (
|
||||
<span
|
||||
className={style.root}
|
||||
onMouseDown={this.props.auto ? this.start : null}
|
||||
onMouseUp={this.end}>
|
||||
|
||||
<span
|
||||
ref="ripple"
|
||||
className={className}
|
||||
style={{left: left, top: top, width: width, height: width}}>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,30 +1,47 @@
|
|||
@import '../constants'
|
||||
|
||||
FINAL_OPACITY = .4
|
||||
|
||||
:local(.root)
|
||||
position : absolute
|
||||
background-color : alpha(WHITE, 0.65)
|
||||
transform translateX(-50%) translateY(-50%)
|
||||
top : 0
|
||||
left : 0
|
||||
right : 0
|
||||
bottom : 0
|
||||
|
||||
:local(.ripple)
|
||||
position : absolute
|
||||
background-color : currentColor
|
||||
transform : translateX(-50%) translateY(-50%)
|
||||
border-radius : 50%
|
||||
width : SIZE = (4 * UNIT)
|
||||
height : SIZE
|
||||
pointer-events : none
|
||||
opacity : 0
|
||||
// -- Classes
|
||||
&.active, &.loading
|
||||
animation-name ripple
|
||||
animation-timing-function ANIMATION_EASE
|
||||
animation-duration (2 * ANIMATION_DURATION)
|
||||
&.active
|
||||
animation-iteration-count 1
|
||||
width : 0
|
||||
height : 0
|
||||
opacity : FINAL_OPACITY
|
||||
transition-duration : 1.4 * ANIMATION_DURATION
|
||||
transition-property : height, width
|
||||
|
||||
&:not(.active).restarting
|
||||
transition-property : none
|
||||
|
||||
&:not(.active)
|
||||
opacity : 0
|
||||
transition-property : opacity, height, width
|
||||
|
||||
&.loading
|
||||
animation-name : ripple
|
||||
animation-iteration-count : infinite
|
||||
animation-timing-function : ANIMATION_EASE
|
||||
animation-duration : (2 * ANIMATION_DURATION)
|
||||
height : (4 * UNIT)
|
||||
width : (4 * UNIT)
|
||||
left : 50%
|
||||
opacity : 0
|
||||
top : 50%
|
||||
animation-iteration-count infinite
|
||||
|
||||
@keyframes ripple
|
||||
0%
|
||||
width : 0
|
||||
height : 0
|
||||
opacity : 1
|
||||
opacity : FINAL_OPACITY
|
||||
100%
|
||||
opacity : 0
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
TestUtils = React.addons.TestUtils
|
||||
expect = require('expect')
|
||||
sinon = require('sinon')
|
||||
utils = require('../../test_utils')
|
||||
ProgressBar = require('../../progress_bar')
|
||||
Input = require('../../input')
|
||||
Slider = require('../index')
|
||||
|
||||
describe 'Slider', ->
|
||||
describe '#positionToValue', ->
|
||||
before ->
|
||||
props = { min: -500, max: 500 }
|
||||
state = { sliderStart: 500, sliderLength: 100 }
|
||||
@slider = utils.renderComponent(Slider, props, state)
|
||||
|
||||
it 'returns min when position is less than origin', ->
|
||||
expect(@slider.positionToValue({x: 400})).toEqual(-500)
|
||||
|
||||
it 'returns max when position is more and origin plus length', ->
|
||||
expect(@slider.positionToValue({x: 900})).toEqual(500)
|
||||
|
||||
it 'returns the proper position when the position is inside slider', ->
|
||||
expect(@slider.positionToValue({x: 520})).toEqual(-300)
|
||||
|
||||
describe '#endPositionToValue', ->
|
||||
before ->
|
||||
props = { min: -500, max: 500 }
|
||||
state = { sliderStart: 500, sliderLength: 100, startPosition: 520, startValue: -300 }
|
||||
@slider = utils.renderComponent(Slider, props, state)
|
||||
|
||||
it 'returns the proper value when is moved left', ->
|
||||
expect(@slider.endPositionToValue({x: 510})).toEqual(-400)
|
||||
|
||||
it 'returns the proper value when is moved right', ->
|
||||
expect(@slider.endPositionToValue({x: 570})).toEqual(200)
|
||||
|
||||
it 'returns the proper value when is not moved', ->
|
||||
expect(@slider.endPositionToValue({x: 520})).toEqual(-300)
|
||||
|
||||
describe '#trimValue', ->
|
||||
before ->
|
||||
props = { min: 0, max: 100, step: 0.1 }
|
||||
@slider = utils.renderComponent(Slider, props)
|
||||
|
||||
it 'rounds to the proper number', ->
|
||||
expect(@slider.trimValue(57.16)).toEqual(57.2)
|
||||
expect(@slider.trimValue(57.12)).toEqual(57.10)
|
||||
|
||||
it 'returns min if number is less than min', ->
|
||||
expect(@slider.trimValue(-57.16)).toEqual(0)
|
||||
|
||||
it 'returns max if number is more than max', ->
|
||||
expect(@slider.trimValue(257.16)).toEqual(100)
|
||||
|
||||
describe '#valueForInput', ->
|
||||
before ->
|
||||
props = { min: 0, max: 100, step: 0.01 }
|
||||
@slider = utils.renderComponent(Slider, props)
|
||||
|
||||
it 'returns a fixed number when an integer is given', ->
|
||||
expect(@slider.valueForInput(4)).toEqual('4.00')
|
||||
|
||||
it 'returns a fixed number when a float is given', ->
|
||||
expect(@slider.valueForInput(4.06)).toEqual('4.06')
|
||||
|
||||
describe '#calculateKnobOffset', ->
|
||||
it 'returns the corresponding offset for a given value and slider length/start', ->
|
||||
props = { min: -500, max: 500, value: -250 }
|
||||
state = { sliderStart: 500, sliderLength: 100 }
|
||||
slider = utils.renderComponent(Slider, props, state)
|
||||
expect(slider.calculateKnobOffset()).toEqual(25)
|
||||
|
||||
describe '#getValue', ->
|
||||
it 'retrieves the current value', ->
|
||||
slider = utils.renderComponent(Slider, {value: 10})
|
||||
expect(slider.getValue()).toEqual(slider.state.value)
|
||||
|
||||
describe '#setValue', ->
|
||||
it 'set the current value', ->
|
||||
slider = utils.renderComponent(Slider, {value: 10})
|
||||
slider.setValue(50)
|
||||
expect(slider.state.value).toEqual(50)
|
||||
|
||||
describe '#render', ->
|
||||
it "contains a linear progress bar with proper properties", ->
|
||||
slider = utils.renderComponent(Slider, {min: 100, max: 1000, value: 140})
|
||||
progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar)
|
||||
expect(progress.props.mode).toEqual('determinate')
|
||||
expect(progress.props.type).toEqual('linear')
|
||||
expect(progress.props.value).toEqual(140)
|
||||
expect(progress.props.min).toEqual(100)
|
||||
expect(progress.props.max).toEqual(1000)
|
||||
|
||||
it "contains an input component if its editable", ->
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 130})
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input)
|
||||
expect(input.props.value).toEqual(slider.props.value)
|
||||
|
||||
it "contains the proper number of snaps when snapped", ->
|
||||
slider = utils.renderComponent(Slider, {snaps: true, step: 10})
|
||||
snaps = slider.refs.snaps
|
||||
expect(snaps.props.children.length).toEqual(10)
|
||||
|
||||
it "has the proper classes for pinned, editable and ring", ->
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, pinned: true})
|
||||
expect(slider.props.className).toContain("ring")
|
||||
expect(slider.props.className).toContain("pinned")
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, value: 50})
|
||||
expect(slider.props.className).toNotContain("ring")
|
||||
|
||||
describe 'events', ->
|
||||
before ->
|
||||
props = { min: -500, max: 500 }
|
||||
state = { sliderStart: 0, sliderLength: 1000 }
|
||||
@slider = utils.renderComponent(Slider, props, state)
|
||||
|
||||
it "sets pressed state when knob is clicked", ->
|
||||
TestUtils.Simulate.mouseDown(@slider.refs.knob)
|
||||
expect(@slider.state.pressed).toEqual(true)
|
||||
|
||||
it "sets pressed state when knob is touched", ->
|
||||
TestUtils.Simulate.touchStart(@slider.refs.knob, {touches: [{pageX: 200}]})
|
||||
expect(@slider.state.pressed).toEqual(true)
|
||||
|
||||
it "sets a proper value when the slider is clicked", ->
|
||||
TestUtils.Simulate.mouseDown(@slider.refs.slider, { pageX: 200 })
|
||||
expect(@slider.state.value).toEqual(-300)
|
||||
|
||||
it "sets a proper value when the slider is touched", ->
|
||||
TestUtils.Simulate.touchStart(@slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]})
|
||||
expect(@slider.state.value).toEqual(-300)
|
||||
|
||||
it "changes its value when input changes", ->
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 50})
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input)
|
||||
TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}})
|
||||
expect(slider.state.value).toEqual(80)
|
||||
|
||||
it "changes input value when slider changes", ->
|
||||
slider = utils.renderComponent(Slider, {editable: true}, {sliderStart: 0, sliderLength: 1000})
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input)
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 })
|
||||
expect(input.state.value).toEqual(90)
|
||||
|
||||
it "calls onChange callback when the value is changed", ->
|
||||
onChangeSpy = sinon.spy()
|
||||
slider = utils.renderComponent(Slider, {onChange: onChangeSpy}, {sliderStart: 0, sliderLength: 1000})
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 })
|
||||
expect(onChangeSpy.called).toEqual(true)
|
|
@ -0,0 +1,167 @@
|
|||
/* global React */
|
||||
|
||||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
import utils from '../../utils/testing';
|
||||
import ProgressBar from '../../progress_bar';
|
||||
import Input from '../../input';
|
||||
import Slider from '../index';
|
||||
|
||||
describe('Slider', function () {
|
||||
const TestUtils = React.addons.TestUtils;
|
||||
let props, state, slider, progress, input;
|
||||
|
||||
describe('#positionToValue', function () {
|
||||
before(function () {
|
||||
props = { min: -500, max: 500 };
|
||||
state = { sliderStart: 500, sliderLength: 100 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
});
|
||||
|
||||
it('returns min when position is less than origin', function () {
|
||||
expect(slider.positionToValue({x: 400})).toEqual(-500);
|
||||
});
|
||||
|
||||
it('returns max when position is more and origin plus length', function () {
|
||||
expect(slider.positionToValue({x: 900})).toEqual(500);
|
||||
});
|
||||
|
||||
it('returns the proper position when the position is inside slider', function () {
|
||||
expect(slider.positionToValue({x: 520})).toEqual(-300);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#trimValue', function () {
|
||||
before(function () {
|
||||
props = { min: 0, max: 100, step: 0.1 };
|
||||
slider = utils.renderComponent(Slider, props);
|
||||
});
|
||||
|
||||
it('rounds to the proper number', function () {
|
||||
expect(slider.trimValue(57.16)).toEqual(57.2);
|
||||
expect(slider.trimValue(57.12)).toEqual(57.10);
|
||||
});
|
||||
|
||||
it('returns min if number is less than min', function () {
|
||||
expect(slider.trimValue(-57.16)).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns max if number is more than max', function () {
|
||||
expect(slider.trimValue(257.16)).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#valueForInput', function () {
|
||||
before(function () {
|
||||
props = { min: 0, max: 100, step: 0.01 };
|
||||
slider = utils.renderComponent(Slider, props);
|
||||
});
|
||||
|
||||
it('returns a fixed number when an integer is given', function () {
|
||||
expect(slider.valueForInput(4)).toEqual('4.00');
|
||||
});
|
||||
|
||||
it('returns a fixed number when a float is given', function () {
|
||||
expect(slider.valueForInput(4.06)).toEqual('4.06');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#knobOffset', function () {
|
||||
it('returns the corresponding offset for a given value and slider length/start', function () {
|
||||
props = { min: -500, max: 500, value: -250 };
|
||||
state = { sliderStart: 500, sliderLength: 100 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
expect(slider.knobOffset()).toEqual(25);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getValue', function () {
|
||||
it('retrieves the current value', function () {
|
||||
slider = utils.renderComponent(Slider, {value: 10});
|
||||
expect(slider.getValue()).toEqual(slider.state.value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setValue', function () {
|
||||
it('set the current value', function () {
|
||||
slider = utils.renderComponent(Slider, {value: 10});
|
||||
slider.setValue(50);
|
||||
expect(slider.state.value).toEqual(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#render', function () {
|
||||
it('contains a linear progress bar with proper properties', function () {
|
||||
slider = utils.renderComponent(Slider, {min: 100, max: 1000, value: 140});
|
||||
progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar);
|
||||
expect(progress.props.mode).toEqual('determinate');
|
||||
expect(progress.props.type).toEqual('linear');
|
||||
expect(progress.props.value).toEqual(140);
|
||||
expect(progress.props.min).toEqual(100);
|
||||
expect(progress.props.max).toEqual(1000);
|
||||
});
|
||||
|
||||
it('contains an input component if its editable', function () {
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 130});
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input);
|
||||
expect(input.props.value).toEqual(slider.props.value);
|
||||
});
|
||||
|
||||
it('contains the proper number of snaps when snapped', function () {
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, pinned: true});
|
||||
expect(slider.props.className).toContain('ring');
|
||||
expect(slider.props.className).toContain('pinned');
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, value: 50});
|
||||
expect(slider.props.className).toNotContain('ring');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#events', function () {
|
||||
before(function () {
|
||||
props = { min: -500, max: 500 };
|
||||
state = { sliderStart: 0, sliderLength: 1000 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
});
|
||||
|
||||
it('sets pressed state when knob is clicked', function () {
|
||||
TestUtils.Simulate.mouseDown(slider.refs.knob);
|
||||
expect(slider.state.pressed).toEqual(true);
|
||||
});
|
||||
|
||||
it('sets pressed state when knob is touched', function () {
|
||||
TestUtils.Simulate.touchStart(slider.refs.knob, {touches: [{pageX: 200}]});
|
||||
expect(slider.state.pressed).toEqual(true);
|
||||
});
|
||||
|
||||
it('sets a proper value when the slider is clicked', function () {
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 200 });
|
||||
expect(slider.state.value).toEqual(-300);
|
||||
});
|
||||
|
||||
it('sets a proper value when the slider is touched', function () {
|
||||
TestUtils.Simulate.touchStart(slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]});
|
||||
expect(slider.state.value).toEqual(-300);
|
||||
});
|
||||
|
||||
it('changes its value when input changes', function () {
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 50});
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input);
|
||||
TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}});
|
||||
expect(slider.state.value).toEqual(80);
|
||||
});
|
||||
|
||||
it('changes input value when slider changes', function () {
|
||||
slider = utils.renderComponent(Slider, {editable: true}, {sliderStart: 0, sliderLength: 1000});
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input);
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 });
|
||||
expect(input.state.value).toEqual(90);
|
||||
});
|
||||
|
||||
it('calls onChange callback when the value is changed', function () {
|
||||
let onChangeSpy = sinon.spy();
|
||||
slider = utils.renderComponent(Slider, {onChange: onChangeSpy}, {sliderStart: 0, sliderLength: 1000});
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 });
|
||||
expect(onChangeSpy.called).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,245 +0,0 @@
|
|||
prefixer = require "../prefixer"
|
||||
localCSS = require './style'
|
||||
ProgressBar = require "../progress_bar"
|
||||
Input = require "../input"
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
editable : React.PropTypes.bool
|
||||
max : React.PropTypes.number
|
||||
min : React.PropTypes.number
|
||||
onChange : React.PropTypes.func
|
||||
pinned : React.PropTypes.bool
|
||||
snaps : React.PropTypes.bool
|
||||
step : React.PropTypes.number
|
||||
value : React.PropTypes.number
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ""
|
||||
editable : false
|
||||
max : 100
|
||||
min : 0
|
||||
pinned : false
|
||||
snaps : false
|
||||
step : 0.01
|
||||
value : 0
|
||||
|
||||
getInitialState: ->
|
||||
sliderStart : 0
|
||||
sliderLength : 0
|
||||
value : @props.value
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@onResize()
|
||||
window.addEventListener('resize', @onResize)
|
||||
|
||||
componentWillUnmount: ->
|
||||
window.removeEventListener('resize', @onResize)
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
if prevState.value != @state.value
|
||||
@props.onChange? @
|
||||
if @state.value != parseFloat(@refs.input?.getValue())
|
||||
@refs.input?.setValue(@valueForInput(@state.value))
|
||||
|
||||
# -- Events
|
||||
onResize: (event) ->
|
||||
sliderBounds = @refs.progressbar.getDOMNode().getBoundingClientRect()
|
||||
@setState
|
||||
sliderStart: sliderBounds['left'],
|
||||
sliderLength: (sliderBounds['right'] - sliderBounds['left'])
|
||||
|
||||
onSliderMouseDown: (event) ->
|
||||
position = _getMousePosition(event)
|
||||
value = @positionToValue(position)
|
||||
@setState value: value, =>
|
||||
@start(position)
|
||||
_addEventsToDocument(@getMouseEventMap())
|
||||
_pauseEvent(event)
|
||||
|
||||
onSliderTouchStart: (event) ->
|
||||
position = _getTouchPosition(event)
|
||||
value = @positionToValue(position)
|
||||
@setState value: value, =>
|
||||
@start(position)
|
||||
_addEventsToDocument(@getTouchEventMap())
|
||||
_pauseEvent(event)
|
||||
|
||||
onSliderFocus: (event) ->
|
||||
_addEventsToDocument(@getKeyboardEvents())
|
||||
|
||||
onSliderBlur: (event) ->
|
||||
_removeEventsFromDocument(@getKeyboardEvents())
|
||||
|
||||
onInputChange: (event) ->
|
||||
@setState value: @trimValue(event.target.value)
|
||||
|
||||
onKeyDown: (event) ->
|
||||
event.stopPropagation()
|
||||
@getDOMNode().blur() if event.keyCode in [13, 27]
|
||||
@addToValue(@props.step) if event.keyCode == 38
|
||||
@addToValue(-@props.step) if event.keyCode == 40
|
||||
|
||||
onMouseDown: (event) ->
|
||||
@start(_getMousePosition(event))
|
||||
_addEventsToDocument(@getMouseEventMap())
|
||||
|
||||
onTouchStart: (event) ->
|
||||
event.stopPropagation()
|
||||
@start(_getTouchPosition(event))
|
||||
_addEventsToDocument(@getTouchEventMap())
|
||||
|
||||
onMouseMove: (event) ->
|
||||
_pauseEvent(event)
|
||||
@move(_getMousePosition(event))
|
||||
|
||||
onTouchMove: (event) ->
|
||||
@move(_getTouchPosition(event))
|
||||
|
||||
onMouseUp: ->
|
||||
@end(@getMouseEventMap())
|
||||
|
||||
onTouchEnd: ->
|
||||
@end(@getTouchEventMap())
|
||||
|
||||
# -- Internal methods
|
||||
getMouseEventMap: ->
|
||||
mousemove: @onMouseMove
|
||||
mouseup: @onMouseUp
|
||||
|
||||
getTouchEventMap: ->
|
||||
touchmove: @onTouchMove
|
||||
touchend: @onTouchEnd
|
||||
|
||||
getKeyboardEvents: ->
|
||||
keydown: @onKeyDown
|
||||
|
||||
positionToValue: (position) ->
|
||||
offset = position.x - @state.sliderStart
|
||||
@trimValue(offset / @state.sliderLength * (@props.max - @props.min) + @props.min)
|
||||
|
||||
start: (position) ->
|
||||
@setState
|
||||
pressed: true
|
||||
startPosition: position.x
|
||||
startValue: @state.value
|
||||
|
||||
move: (position) ->
|
||||
value = @endPositionToValue(position)
|
||||
@setState value: value
|
||||
|
||||
end: (events) ->
|
||||
_removeEventsFromDocument(events)
|
||||
@setState pressed: false
|
||||
|
||||
endPositionToValue: (position) ->
|
||||
offset = position.x - @state.startPosition
|
||||
diffValue = offset / @state.sliderLength * (@props.max - @props.min)
|
||||
@trimValue(diffValue + @state.startValue)
|
||||
|
||||
trimValue: (value) ->
|
||||
value = @props.min if (value < @props.min)
|
||||
value = @props.max if (value > @props.max)
|
||||
_round(value, @stepDecimals())
|
||||
|
||||
stepDecimals: ->
|
||||
(@props.step.toString().split('.')[1] || []).length
|
||||
|
||||
addToValue: (value) ->
|
||||
@setState value: @trimValue(@state.value + value)
|
||||
|
||||
valueForInput: (value) ->
|
||||
decimals = @stepDecimals()
|
||||
if decimals > 0 then value.toFixed(decimals) else value.toString()
|
||||
|
||||
calculateKnobOffset: ->
|
||||
@state.sliderLength * (@state.value - @props.min) / (@props.max - @props.min)
|
||||
|
||||
render: ->
|
||||
className = @props.className
|
||||
className += " editable" if @props.editable
|
||||
className += " pinned" if @props.pinned
|
||||
className += " pressed" if @state.pressed
|
||||
className += " ring" if @state.value == @props.min
|
||||
knobStyles = prefixer.transform("translateX(#{@calculateKnobOffset()}px)")
|
||||
|
||||
<div className={localCSS.root + className}
|
||||
tabIndex="0"
|
||||
onFocus={@onSliderFocus}
|
||||
onBlur={@onSliderBlur} >
|
||||
|
||||
<div ref="slider"
|
||||
className={localCSS.container}
|
||||
onTouchStart={@onSliderTouchStart}
|
||||
onMouseDown={@onSliderMouseDown} >
|
||||
|
||||
<div ref="knob" className={localCSS.knob} style={knobStyles}
|
||||
onMouseDown={@onMouseDown}
|
||||
onTouchStart={@onTouchStart} >
|
||||
<div className={localCSS.knobInner} data-value={parseInt(@state.value)}></div>
|
||||
</div>
|
||||
|
||||
<div className={localCSS.progress} >
|
||||
<ProgressBar ref="progressbar" mode="determinate"
|
||||
className={localCSS.progressInner}
|
||||
value={@state.value}
|
||||
max={@props.max}
|
||||
min={@props.min}/>
|
||||
{
|
||||
if @props.snaps
|
||||
<div ref="snaps" className={localCSS.snaps}>
|
||||
{
|
||||
for i in [1..((@props.max - @props.min) / @props.step)]
|
||||
<div key="span-#{i}" className={localCSS.snap}></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
if @props.editable
|
||||
<Input ref="input" className={localCSS.input}
|
||||
onChange={@onInputChange}
|
||||
value={@valueForInput(@state.value)} />
|
||||
}
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
getValue: ->
|
||||
@state.value
|
||||
|
||||
setValue: (value) ->
|
||||
@setState value: value
|
||||
|
||||
# -- Private methods
|
||||
_pauseEvent = (event) ->
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
event.returnValue = false
|
||||
event.cancelBubble = true
|
||||
return null
|
||||
|
||||
_getMousePosition = (event) ->
|
||||
x: event.pageX
|
||||
y: event.pageY
|
||||
|
||||
_getTouchPosition = (event) ->
|
||||
x: event.touches[0]['pageX']
|
||||
y: event.touches[0]['pageY']
|
||||
|
||||
_addEventsToDocument = (events) ->
|
||||
document.addEventListener(key, events[key], false) for key of events
|
||||
|
||||
_removeEventsFromDocument = (events) ->
|
||||
document.removeEventListener(key, events[key], false) for key of events
|
||||
|
||||
_round = (n, decimals) ->
|
||||
if (!isNaN(parseFloat(n)) && isFinite(n))
|
||||
decimalPower = Math.pow(10, decimals)
|
||||
return Math.round(parseFloat(n) * decimalPower) / decimalPower
|
||||
return NaN
|
|
@ -0,0 +1,256 @@
|
|||
/* global React */
|
||||
|
||||
import css from './style';
|
||||
import utils from '../utils';
|
||||
import ProgressBar from '../progress_bar';
|
||||
import Input from '../input';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'Slider',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
editable: React.PropTypes.bool,
|
||||
max: React.PropTypes.number,
|
||||
min: React.PropTypes.number,
|
||||
onChange: React.PropTypes.func,
|
||||
pinned: React.PropTypes.bool,
|
||||
snaps: React.PropTypes.bool,
|
||||
step: React.PropTypes.number,
|
||||
value: React.PropTypes.number
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
editable: false,
|
||||
max: 100,
|
||||
min: 0,
|
||||
pinned: false,
|
||||
snaps: false,
|
||||
step: 0.01,
|
||||
value: 0
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
sliderStart: 0,
|
||||
sliderLength: 0,
|
||||
value: this.props.value
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
},
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
},
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (prevState.value !== this.state.value) {
|
||||
if (this.props.onChange) this.props.onChange(this);
|
||||
if (this.refs.input) this.refs.input.setValue(this.valueForInput(this.state.value));
|
||||
}
|
||||
},
|
||||
|
||||
onResize () {
|
||||
const {left, right} = this.refs.progressbar.getDOMNode().getBoundingClientRect();
|
||||
this.setState({sliderStart: left, sliderLength: right - left});
|
||||
},
|
||||
|
||||
onSliderFocus () {
|
||||
utils.events.addEventsToDocument(this.getKeyboardEvents());
|
||||
},
|
||||
|
||||
onSliderBlur () {
|
||||
utils.events.removeEventsFromDocument(this.getKeyboardEvents());
|
||||
},
|
||||
|
||||
onInputChange (event) {
|
||||
this.setState({value: this.trimValue(event.target.value) });
|
||||
},
|
||||
|
||||
onKeyDown (event) {
|
||||
event.stopPropagation();
|
||||
if (event.keyCode in [13, 27]) this.getDOMNode().blur();
|
||||
if (event.keyCode === 38) this.addToValue(this.props.step);
|
||||
if (event.keyCode === 40) this.addToValue(-this.props.step);
|
||||
},
|
||||
|
||||
onMouseDown (event) {
|
||||
utils.events.addEventsToDocument(this.getMouseEventMap());
|
||||
this.start(utils.events.getMousePosition(event));
|
||||
utils.events.pauseEvent(event);
|
||||
},
|
||||
|
||||
onTouchStart (event) {
|
||||
this.start(utils.events.getTouchPosition(event));
|
||||
utils.events.addEventsToDocument(this.getTouchEventMap());
|
||||
utils.events.pauseEvent(event);
|
||||
},
|
||||
|
||||
onMouseMove (event) {
|
||||
utils.events.pauseEvent(event);
|
||||
this.move(utils.events.getMousePosition(event));
|
||||
},
|
||||
|
||||
onTouchMove (event) {
|
||||
this.move(utils.events.getTouchPosition(event));
|
||||
},
|
||||
|
||||
onMouseUp () {
|
||||
this.end(this.getMouseEventMap());
|
||||
},
|
||||
|
||||
onTouchEnd () {
|
||||
this.end(this.getTouchEventMap());
|
||||
},
|
||||
|
||||
getMouseEventMap () {
|
||||
return {
|
||||
mousemove: this.onMouseMove,
|
||||
mouseup: this.onMouseUp
|
||||
};
|
||||
},
|
||||
|
||||
getTouchEventMap () {
|
||||
return {
|
||||
touchmove: this.onTouchMove,
|
||||
touchend: this.onTouchEnd
|
||||
};
|
||||
},
|
||||
|
||||
getKeyboardEvents () {
|
||||
return {
|
||||
keydown: this.onKeyDown
|
||||
};
|
||||
},
|
||||
|
||||
start (position) {
|
||||
this.setState({pressed: true, value: this.positionToValue(position)});
|
||||
},
|
||||
|
||||
move (position) {
|
||||
this.setState({value: this.positionToValue(position)});
|
||||
},
|
||||
|
||||
end (revents) {
|
||||
utils.events.removeEventsFromDocument(revents);
|
||||
this.setState({pressed: false});
|
||||
},
|
||||
|
||||
positionToValue (position) {
|
||||
let { sliderStart: start, sliderLength: length } = this.state;
|
||||
let { max, min } = this.props;
|
||||
return this.trimValue((position.x - start) / length * (max - min) + min);
|
||||
},
|
||||
|
||||
trimValue (value) {
|
||||
if (value < this.props.min) return this.props.min;
|
||||
if (value > this.props.max) return this.props.max;
|
||||
return utils.round(value, this.stepDecimals());
|
||||
},
|
||||
|
||||
stepDecimals () {
|
||||
return (this.props.step.toString().split('.')[1] || []).length;
|
||||
},
|
||||
|
||||
addToValue (value) {
|
||||
this.setState({value: this.trimValue(this.state.value + value)});
|
||||
},
|
||||
|
||||
valueForInput (value) {
|
||||
const decimals = this.stepDecimals();
|
||||
return decimals > 0 ? value.toFixed(decimals) : value.toString();
|
||||
},
|
||||
|
||||
knobOffset () {
|
||||
let { max, min } = this.props;
|
||||
return this.state.sliderLength * (this.state.value - min) / (max - min);
|
||||
},
|
||||
|
||||
renderSnaps () {
|
||||
if (this.props.snaps) {
|
||||
return (
|
||||
<div ref='snaps' className={css.snaps}>
|
||||
{
|
||||
utils.range(0, (this.props.max - this.props.min) / this.props.step).map(i => {
|
||||
return (<div key={`span-${i}`} className={css.snap}></div>);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderInput () {
|
||||
if (this.props.editable) {
|
||||
return (
|
||||
<Input
|
||||
ref='input'
|
||||
className={css.input}
|
||||
onChange={this.onInputChange}
|
||||
value={this.valueForInput(this.state.value)} />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let knobStyles = utils.prefixer({transform: `translateX(${this.knobOffset()}px)`});
|
||||
let className = this.props.className;
|
||||
if (this.props.editable) className += ' editable';
|
||||
if (this.props.pinned) className += ' pinned';
|
||||
if (this.state.pressed) className += ' pressed';
|
||||
if (this.state.value === this.props.min) className += ' ring';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css.root + className}
|
||||
tabIndex='0'
|
||||
onFocus={this.onSliderFocus}
|
||||
onBlur={this.onSliderBlur} >
|
||||
|
||||
<div
|
||||
ref='slider'
|
||||
className={css.container}
|
||||
onTouchStart={this.onTouchStart}
|
||||
onMouseDown={this.onMouseDown} >
|
||||
|
||||
<div
|
||||
ref='knob'
|
||||
className={css.knob}
|
||||
style={knobStyles}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onTouchStart={this.onTouchStart} >
|
||||
<div className={css.knobInner} data-value={parseInt(this.state.value)}></div>
|
||||
</div>
|
||||
|
||||
<div className={css.progress}>
|
||||
<ProgressBar
|
||||
ref='progressbar'
|
||||
mode='determinate'
|
||||
className={css.progressInner}
|
||||
value={this.state.value}
|
||||
max={this.props.max}
|
||||
min={this.props.min}/>
|
||||
{ this.renderSnaps() }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ this.renderInput() }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
getValue () {
|
||||
return this.state.value;
|
||||
},
|
||||
|
||||
setValue (value) {
|
||||
this.setState({value: value});
|
||||
}
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Ripple = require '../ripple'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
disabled : React.PropTypes.bool
|
||||
label : React.PropTypes.string
|
||||
onChange : React.PropTypes.func
|
||||
value : React.PropTypes.bool
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
|
||||
getInitialState: ->
|
||||
value : @props.value
|
||||
ripple : undefined
|
||||
|
||||
# -- Lifecycle
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
@setState value: next_props.value if next_props.value
|
||||
|
||||
# -- Events
|
||||
onClick: (event) ->
|
||||
unless @props.disabled
|
||||
@setState
|
||||
value : not @state.value
|
||||
ripple: change: true
|
||||
setTimeout (=> @props.onChange? event, @), 10
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = "#{localCSS.root} #{@props.className}"
|
||||
className += ' checked' if @state.value
|
||||
className += ' disabled' if @props.disabled
|
||||
<div data-react-toolbox='switch' className={className} onClick={@onClick}>
|
||||
<span></span>
|
||||
{ <label>{@props.label}</label> if @props.label }
|
||||
<Ripple className={localCSS.ripple} origin={@state.ripple} />
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
getValue: ->
|
||||
@state.value
|
||||
|
||||
setValue: (data) ->
|
||||
@setState value: data
|
|
@ -0,0 +1,70 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Switch',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
disabled: React.PropTypes.bool,
|
||||
label: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func,
|
||||
value: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: ''
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
value: this.props.value
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps (next_props) {
|
||||
if (next_props.value) {
|
||||
this.setState({value: next_props.value});
|
||||
}
|
||||
},
|
||||
|
||||
onClick (event) {
|
||||
if (!this.props.disabled) {
|
||||
this.setState({value: !this.state.value});
|
||||
setTimeout(() => {
|
||||
if (this.props.onChange) this.props.onChange(event, this);
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.root} ${this.props.className}`;
|
||||
if (this.state.value) className += ' checked';
|
||||
if (this.props.disabled) className += ' disabled';
|
||||
|
||||
return (
|
||||
<div
|
||||
data-react-toolbox='switch'
|
||||
className={className}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<span></span>
|
||||
{ this.props.label ? <label>{this.props.label}</label> : null }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
getValue () {
|
||||
return this.state.value;
|
||||
},
|
||||
|
||||
setValue (data) {
|
||||
this.setState({value: data});
|
||||
}
|
||||
});
|
|
@ -1,16 +1,6 @@
|
|||
@import '../constants'
|
||||
|
||||
SIZE = (SPACE / 1.25)
|
||||
:local(.ripple)
|
||||
z-index : -1
|
||||
overflow : hidden
|
||||
max-width : (SIZE * 2.7)
|
||||
max-height : (SIZE * 2.7)
|
||||
top : (SIZE / 2)
|
||||
left : (SIZE * 2)
|
||||
background-color : alpha(TEXT, 10%)
|
||||
opacity : 0
|
||||
animation-duration : (1.0 * ANIMATION_DURATION)
|
||||
|
||||
:local(.root)
|
||||
position : relative
|
||||
|
@ -55,6 +45,3 @@ SIZE = (SPACE / 1.25)
|
|||
background-color : PRIMARY
|
||||
right : 0
|
||||
box-shadow : ZDEPTH_SHADOW_2
|
||||
> :local(.ripple)
|
||||
left : 0
|
||||
background-color : alpha(PRIMARY, 15%)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
###
|
||||
@todo
|
||||
###
|
||||
|
||||
module.exports =
|
||||
Tab : require './tab'
|
||||
Tabs : require './tabs'
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
Tab: require('./tab'),
|
||||
Tabs: require('./tabs')
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
localCSS = require './style'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
active : React.PropTypes.boolean
|
||||
className : React.PropTypes.string
|
||||
disabled : React.PropTypes.boolean
|
||||
hidden : React.PropTypes.boolean
|
||||
label : React.PropTypes.string.required
|
||||
onActive : React.PropTypes.func
|
||||
tabIndex : React.PropTypes.number
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ""
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@active @props.active
|
||||
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
@active next_props.active if next_props.active
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = @props.className
|
||||
className += ' active' if @props.active
|
||||
className += ' disabled' if @props.disabled
|
||||
className += ' hidden' if @props.hidden
|
||||
<section data-react-toolbox='tab'
|
||||
className={localCSS.tab + ' ' + className}
|
||||
data-flex='vertical'
|
||||
tabIndex={@props.tabIndex}>
|
||||
{ @props.children }
|
||||
</section>
|
||||
|
||||
# -- Extends
|
||||
active: (value) ->
|
||||
@setState active: value
|
||||
@props.onActive? @ if value
|
|
@ -0,0 +1,57 @@
|
|||
/* global React */
|
||||
|
||||
import style from './style';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'Tab',
|
||||
|
||||
propTypes: {
|
||||
active: React.PropTypes.bool,
|
||||
className: React.PropTypes.string,
|
||||
disabled: React.PropTypes.bool,
|
||||
hidden: React.PropTypes.bool,
|
||||
label: React.PropTypes.string.isRequired,
|
||||
onActive: React.PropTypes.func,
|
||||
tabIndex: React.PropTypes.number
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: ''
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
this.active(this.props.active);
|
||||
},
|
||||
|
||||
componentWillReceiveProps (next_props) {
|
||||
if (next_props.active) this.active(next_props.active);
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = `${style.tab} ${this.props.className}`;
|
||||
if (this.props.active) className += ' active';
|
||||
if (this.props.disabled) className += ' disabled';
|
||||
if (this.props.hidden) className += ' hidden';
|
||||
|
||||
return (
|
||||
<section
|
||||
data-react-toolbox='tab'
|
||||
className={className}
|
||||
data-flex='vertical'
|
||||
tabIndex={this.props.tabIndex}
|
||||
>
|
||||
{ this.props.children }
|
||||
</section>
|
||||
);
|
||||
},
|
||||
|
||||
active (value) {
|
||||
this.setState({active: value});
|
||||
if (this.props.onActive && value) {
|
||||
this.props.onActive(this);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,74 +0,0 @@
|
|||
localCSS = require './style'
|
||||
Tab = require './tab'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
index : React.PropTypes.number.required
|
||||
onChange : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ""
|
||||
index : 0
|
||||
|
||||
getInitialState: ->
|
||||
index : @props.index
|
||||
pointer : {}
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@setState pointer: _pointerPosition @state.index, @refs.navigation.getDOMNode()
|
||||
|
||||
componentWillReceiveProps: (next_props) ->
|
||||
index = next_props.index or @state.index
|
||||
@setState
|
||||
index : index
|
||||
pointer : _pointerPosition index, @refs.navigation.getDOMNode()
|
||||
|
||||
# -- Events
|
||||
onClick: (index, event, ref) ->
|
||||
@setState
|
||||
index : index
|
||||
pointer : _pointerPosition index, @refs.navigation.getDOMNode()
|
||||
@props.onChange? @
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
labels = []
|
||||
tabs = React.Children.map @props.children, (tab, index) =>
|
||||
active = @state.index is index
|
||||
|
||||
className = tab.props.className
|
||||
className += ' active' if active
|
||||
className += ' disabled' if tab.props.disabled
|
||||
className += ' hidden' if tab.props.hidden
|
||||
labels.push
|
||||
className : className
|
||||
label : tab.props.label
|
||||
key : index
|
||||
onClick : (@onClick.bind null, index unless tab.props.disabled)
|
||||
|
||||
React.addons.cloneWithProps tab,
|
||||
active : active
|
||||
key : index
|
||||
tabIndex : index
|
||||
|
||||
<div data-react-toolbox='tabs'
|
||||
className={localCSS.root + ' ' + @props.className}
|
||||
data-flex='vertical'>
|
||||
<nav ref='navigation' data-flex='horizontal'>
|
||||
{ <label {...props}>{props.label}</label> for props in labels }
|
||||
</nav>
|
||||
<span className={localCSS.pointer} style={@state.pointer}></span>
|
||||
{ tabs }
|
||||
</div>
|
||||
|
||||
# -- Private methods
|
||||
_pointerPosition = (index = 0, navigation) ->
|
||||
label = navigation.children[index].getBoundingClientRect()
|
||||
style =
|
||||
top : "#{navigation.getBoundingClientRect().height}px"
|
||||
left : "#{label.left}px"
|
||||
width : "#{label.width}px"
|
|
@ -0,0 +1,103 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import style from './style';
|
||||
|
||||
function _pointerPosition (index = 0, navigation) {
|
||||
const label = navigation.children[index].getBoundingClientRect();
|
||||
return {
|
||||
top: `${navigation.getBoundingClientRect().height}px`,
|
||||
left: `${label.left}px`,
|
||||
width: `${label.width}px`
|
||||
};
|
||||
}
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'Tabs',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
index: React.PropTypes.number.isRequired,
|
||||
onChange: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
index: 0
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
index: this.props.index,
|
||||
pointer: {}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({
|
||||
pointer: _pointerPosition(this.state.index, this.refs.navigation.getDOMNode())
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps (next_props) {
|
||||
const index = next_props.index || this.state.index;
|
||||
this.setState({
|
||||
index: index,
|
||||
pointer: _pointerPosition(index, this.refs.navigation.getDOMNode())
|
||||
});
|
||||
},
|
||||
|
||||
onClick (index) {
|
||||
this.setState({
|
||||
index: index,
|
||||
pointer: _pointerPosition(index, this.refs.navigation.getDOMNode())
|
||||
});
|
||||
if (this.props.onChange) this.props.onChange(this);
|
||||
},
|
||||
|
||||
render () {
|
||||
let labels = [];
|
||||
const tabs = this.props.children.map((tab, index) => {
|
||||
let active = this.state.index === index;
|
||||
|
||||
let className = tab.props.className;
|
||||
if (active) className += ' active';
|
||||
if (tab.props.disabled) className += ' disabled';
|
||||
if (tab.props.hidden) className += ' hidden';
|
||||
|
||||
labels.push({
|
||||
className: className,
|
||||
label: tab.props.label,
|
||||
key: index,
|
||||
onClick: !tab.props.disabled ? this.onClick.bind(null, index) : null
|
||||
});
|
||||
|
||||
return React.cloneElement(tab, {active: active, key: index, tabIndex: index });
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
data-react-toolbox='tabs'
|
||||
className={style.root + ' ' + this.props.className}
|
||||
data-flex='vertical'
|
||||
>
|
||||
<nav ref='navigation' data-flex='horizontal'>
|
||||
{ labels.map((props) => { return <label {...props}>{props.label}</label>; }) }
|
||||
</nav>
|
||||
<span className={style.pointer} style={this.state.pointer}></span>
|
||||
{ tabs }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
active (value) {
|
||||
this.setState({active: value});
|
||||
if (this.props.onActive && value) {
|
||||
this.props.onActive(this);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
TestUtils = React.addons.TestUtils
|
||||
|
||||
module.exports =
|
||||
renderComponent: (Component, props={}, state={}) ->
|
||||
component = TestUtils.renderIntoDocument(<Component {...props}/>)
|
||||
component.setState(state) unless state == {}
|
||||
component
|
||||
|
||||
shallowRenderComponent: (component, props, children...) ->
|
||||
shallowRenderer = TestUtils.createRenderer()
|
||||
shallowRenderer.render(React.createElement(component, props,
|
||||
children.length > 1 ? children : children[0]))
|
||||
shallowRenderer.getRenderOutput()
|
|
@ -1,92 +0,0 @@
|
|||
css = require './style'
|
||||
dateUtils = require '../date_utils'
|
||||
|
||||
Button = require '../button'
|
||||
Clock = require '../clock'
|
||||
Dialog = require '../dialog'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'TimePickerDialog'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
initialTime : React.PropTypes.object
|
||||
format : React.PropTypes.oneOf(['24hr', 'ampm'])
|
||||
onTimeSelected : React.PropTypes.func
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ''
|
||||
initialTime : new Date()
|
||||
format : '24hr'
|
||||
|
||||
getInitialState: ->
|
||||
display : 'hours'
|
||||
time : @props.initialTime
|
||||
actions: [
|
||||
{ caption: "Cancel", type: "flat accent", onClick: @onTimeCancel },
|
||||
{ caption: "Ok", type: "flat accent", onClick: @onTimeSelected }
|
||||
]
|
||||
|
||||
# -- Events
|
||||
onClockChange: (time) ->
|
||||
@setState time: time
|
||||
|
||||
onTimeCancel: (ref, method) ->
|
||||
@refs.dialog.hide()
|
||||
|
||||
onTimeSelected: ->
|
||||
@props.onTimeSelected(@state.time) if @props.onTimeSelected
|
||||
@refs.dialog.hide()
|
||||
|
||||
# -- Public methods
|
||||
displayMinutes: ->
|
||||
@setState display: 'minutes'
|
||||
|
||||
displayHours: ->
|
||||
@setState display: 'hours'
|
||||
|
||||
toggleTimeMode: ->
|
||||
@refs.clock.toggleTimeMode()
|
||||
|
||||
show: ->
|
||||
@refs.dialog.show()
|
||||
setTimeout @refs.clock.handleResize, 500
|
||||
|
||||
# -- Private helpers
|
||||
_formatHours: ->
|
||||
if @props.format == 'ampm' then @state.time.getHours() % 12 || 12 else @state.time.getHours()
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
className = " "
|
||||
className += " display-#{@state.display}"
|
||||
className += " format-#{dateUtils.timeMode(@state.time)}"
|
||||
|
||||
<Dialog ref="dialog" type={css.dialog} className={className} actions={@state.actions}>
|
||||
<header className={css.header}>
|
||||
<span className={css.hours} onClick={@displayHours} >
|
||||
{_twoDigits(@_formatHours())}
|
||||
</span>
|
||||
<span className={css.separator}>:</span>
|
||||
<span className={css.minutes} onClick={@displayMinutes}>
|
||||
{_twoDigits(@state.time.getMinutes())}
|
||||
</span>
|
||||
{
|
||||
if @props.format == 'ampm'
|
||||
<div className={css.ampm}>
|
||||
<span className={css.am} onClick={@toggleTimeMode}>AM</span>
|
||||
<span className={css.pm} onClick={@toggleTimeMode}>PM</span>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
<Clock ref="clock"
|
||||
display={@state.display}
|
||||
format={@props.format}
|
||||
initialTime={@props.initialTime}
|
||||
onChange={@onClockChange} />
|
||||
</Dialog>
|
||||
|
||||
# -- Private helpers
|
||||
_twoDigits = (number) ->
|
||||
('0' + number).slice(-2)
|
|
@ -0,0 +1,112 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import css from './style';
|
||||
import time from '../utils/time';
|
||||
import Clock from '../clock';
|
||||
import Dialog from '../dialog';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'TimePickerDialog',
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
initialTime: React.PropTypes.object,
|
||||
format: React.PropTypes.oneOf(['24hr', 'ampm']),
|
||||
onTimeSelected: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
initialTime: new Date(),
|
||||
format: '24hr'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
display: 'hours',
|
||||
time: this.props.initialTime,
|
||||
actions: [
|
||||
{ label: 'Cancel', type: 'flat accent', onClick: this.onTimeCancel },
|
||||
{ label: 'Ok', type: 'flat accent', onClick: this.onTimeSelected }
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
onClockChange (newTime) {
|
||||
this.setState({time: newTime});
|
||||
},
|
||||
|
||||
onTimeCancel () {
|
||||
this.refs.dialog.hide();
|
||||
},
|
||||
|
||||
onTimeSelected () {
|
||||
if (this.props.onTimeSelected) this.props.onTimeSelected(this.state.time);
|
||||
this.refs.dialog.hide();
|
||||
},
|
||||
|
||||
displayMinutes () {
|
||||
this.setState({display: 'minutes'});
|
||||
},
|
||||
|
||||
displayHours () {
|
||||
this.setState({display: 'hours'});
|
||||
},
|
||||
|
||||
toggleTimeMode () {
|
||||
this.refs.clock.toggleTimeMode();
|
||||
},
|
||||
|
||||
show () {
|
||||
this.refs.dialog.show();
|
||||
setTimeout(this.refs.clock.calculateShape, 1000);
|
||||
},
|
||||
|
||||
formatHours () {
|
||||
if (this.props.format === 'ampm') {
|
||||
return this.state.time.getHours() % 12 || 12;
|
||||
} else {
|
||||
return this.state.time.getHours();
|
||||
}
|
||||
},
|
||||
|
||||
renderAMPMLabels () {
|
||||
if (this.props.format === 'ampm') {
|
||||
return (<div className={css.ampm}>
|
||||
<span className={css.am} onClick={this.toggleTimeMode}>AM</span>
|
||||
<span className={css.pm} onClick={this.toggleTimeMode}>PM</span>
|
||||
</div>);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let className = ` display-${this.state.display}`;
|
||||
className += ` format-${time.getTimeMode(this.state.time)}`;
|
||||
|
||||
return (
|
||||
<Dialog ref="dialog" className={className} type={css.dialog} actions={this.state.actions}>
|
||||
<header className={css.header}>
|
||||
<span className={css.hours} onClick={this.displayHours}>
|
||||
{ ('0' + this.formatHours()).slice(-2) }
|
||||
</span>
|
||||
<span className={css.separator}>:</span>
|
||||
<span className={css.minutes} onClick={this.displayMinutes}>
|
||||
{ ('0' + this.state.time.getMinutes()).slice(-2) }
|
||||
</span>
|
||||
{ this.renderAMPMLabels() }
|
||||
</header>
|
||||
<Clock
|
||||
ref="clock"
|
||||
display={this.state.display}
|
||||
format={this.props.format}
|
||||
initialTime={this.props.initialTime}
|
||||
onChange={this.onClockChange} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
css = require './style'
|
||||
Input = require '../input'
|
||||
TimeDialog = require './dialog'
|
||||
|
||||
module.exports = React.createClass
|
||||
displayName : 'TimePicker'
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
format : React.PropTypes.oneOf(['24hr', 'ampm'])
|
||||
value : React.PropTypes.object
|
||||
|
||||
getDefaultProps: ->
|
||||
format : '24hr'
|
||||
|
||||
getInitialState: ->
|
||||
value : @props.value
|
||||
|
||||
# -- Events
|
||||
onTimeSelected: (time) ->
|
||||
@refs.input.setValue(@formatTime(time))
|
||||
@setState value: time
|
||||
|
||||
openTimeDialog: ->
|
||||
@refs.dialog.show()
|
||||
|
||||
# -- Private methods
|
||||
formatTime: (date) ->
|
||||
hours = date.getHours()
|
||||
mins = date.getMinutes().toString()
|
||||
|
||||
if (@props.format == "ampm")
|
||||
isAM = hours < 12
|
||||
hours = hours % 12
|
||||
additional = if isAM then " am" else " pm"
|
||||
hours = (hours || 12).toString()
|
||||
mins = "0" + mins if (mins.length < 2 )
|
||||
return hours + (if mins == "00" then "" else ":" + mins) + additional
|
||||
|
||||
hours = hours.toString()
|
||||
hours = "0" + hours if (hours.length < 2)
|
||||
mins = "0" + mins if (mins.length < 2)
|
||||
return hours + ":" + mins
|
||||
|
||||
# -- Public methods
|
||||
getValue: ->
|
||||
@state.value
|
||||
|
||||
# -- Render
|
||||
render: ->
|
||||
<div>
|
||||
<Input
|
||||
ref="input"
|
||||
type="text"
|
||||
disabled={true}
|
||||
onClick={@openTimeDialog}
|
||||
placeholder="Pick up time"
|
||||
value={@formatTime(@state.value) if @state.value} />
|
||||
<TimeDialog
|
||||
ref="dialog"
|
||||
initialTime={@state.value}
|
||||
format={@props.format}
|
||||
onTimeSelected={@onTimeSelected} />
|
||||
</div>
|
|
@ -0,0 +1,67 @@
|
|||
/* global React */
|
||||
|
||||
import { addons } from 'react/addons';
|
||||
import time from '../utils/time';
|
||||
import Input from '../input';
|
||||
import TimeDialog from './dialog';
|
||||
|
||||
export default React.createClass({
|
||||
mixins: [addons.PureRenderMixin],
|
||||
|
||||
displayName: 'TimePicker',
|
||||
|
||||
propTypes: {
|
||||
format: React.PropTypes.oneOf(['24hr', 'ampm']),
|
||||
value: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
format: '24hr'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
value: this.props.value
|
||||
};
|
||||
},
|
||||
|
||||
onTimeSelected (newTime) {
|
||||
this.refs.input.setValue(time.formatTime(newTime, this.props.format));
|
||||
this.setState({value: newTime});
|
||||
},
|
||||
|
||||
openTimeDialog () {
|
||||
this.refs.dialog.show();
|
||||
},
|
||||
|
||||
formatTime () {
|
||||
if (this.state.value) {
|
||||
return time.formatTime(this.state.value, this.props.format);
|
||||
}
|
||||
},
|
||||
|
||||
getValue () {
|
||||
return this.state.value;
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
ref="input"
|
||||
type="text"
|
||||
disabled={true}
|
||||
onClick={this.openTimeDialog}
|
||||
placeholder="Pick up time"
|
||||
value={this.formatTime()} />
|
||||
<TimeDialog
|
||||
ref="dialog"
|
||||
initialTime={this.state.value}
|
||||
format={this.props.format}
|
||||
onTimeSelected={this.onTimeSelected} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
module.exports = {
|
||||
|
||||
getMousePosition (event) {
|
||||
return {
|
||||
x: event.pageX,
|
||||
y: event.pageY
|
||||
};
|
||||
},
|
||||
|
||||
getTouchPosition (event) {
|
||||
return {
|
||||
x: event.touches[0].pageX,
|
||||
y: event.touches[0].pageY
|
||||
};
|
||||
},
|
||||
|
||||
pauseEvent (event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.returnValue = false;
|
||||
event.cancelBubble = true;
|
||||
},
|
||||
|
||||
addEventsToDocument (eventMap) {
|
||||
for (let key in eventMap) {
|
||||
document.addEventListener(key, eventMap[key], false);
|
||||
}
|
||||
},
|
||||
|
||||
removeEventsFromDocument (eventMap) {
|
||||
for (let key in eventMap) {
|
||||
document.removeEventListener(key, eventMap[key], false);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
module.exports = {
|
||||
|
||||
angleFromPositions (cx, cy, ex, ey) {
|
||||
let theta = Math.atan2(ey - cy, ex - cx) + Math.PI / 2;
|
||||
return theta * 180 / Math.PI;
|
||||
},
|
||||
|
||||
angle360FromPositions (cx, cy, ex, ey) {
|
||||
let angle = this.angleFromPositions(cx, cy, ex, ey);
|
||||
return angle < 0 ? 360 + angle : angle;
|
||||
},
|
||||
|
||||
range (start = 0, stop = null, step = 1) {
|
||||
let [_start, _stop] = (stop !== null) ? [start, stop] : [0, start];
|
||||
let length = Math.max(Math.ceil((_stop - _start) / step), 0);
|
||||
let range = Array(length);
|
||||
|
||||
for (let idx = 0; idx < length; idx++, _start += step) {
|
||||
range[idx] = _start;
|
||||
}
|
||||
|
||||
return range;
|
||||
},
|
||||
|
||||
round (number, decimals) {
|
||||
if (!isNaN(parseFloat(number)) && isFinite(number)) {
|
||||
let decimalPower = Math.pow(10, decimals);
|
||||
return Math.round(parseFloat(number) * decimalPower) / decimalPower;
|
||||
}
|
||||
return NaN;
|
||||
},
|
||||
|
||||
events: require('./events'),
|
||||
prefixer: require('./prefixer'),
|
||||
time: require('./time'),
|
||||
testing: require('./testing')
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
const WEBKIT = 'Webkit';
|
||||
const MICROSOFT = 'Ms';
|
||||
|
||||
const properties = {
|
||||
transform: [WEBKIT, MICROSOFT]
|
||||
};
|
||||
|
||||
function capitalize (string) {
|
||||
return string.charAt(0).toUpperCase() + string.substr(1);
|
||||
}
|
||||
|
||||
function getPrefixes (property, value) {
|
||||
return properties[property].reduce(function (acc, item) {
|
||||
acc[`${item}${capitalize(property)}`] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function prefixer (style) {
|
||||
let _style = style;
|
||||
|
||||
for (let property in properties) {
|
||||
if (style[property]) {
|
||||
_style = Object.assign(_style, getPrefixes(property, style[property]));
|
||||
}
|
||||
}
|
||||
|
||||
return _style;
|
||||
}
|
||||
|
||||
module.exports = prefixer;
|
|
@ -0,0 +1,19 @@
|
|||
/* global React */
|
||||
|
||||
const TestUtils = React.addons.TestUtils;
|
||||
|
||||
module.exports = {
|
||||
|
||||
renderComponent (Component, props = {}, state = {}) {
|
||||
let component = TestUtils.renderIntoDocument(<Component {...props} />);
|
||||
if (state !== {}) { component.setState(state); }
|
||||
return component;
|
||||
},
|
||||
|
||||
shallowRenderComponent (component, props, ...children) {
|
||||
let shallowRenderer = TestUtils.createRenderer();
|
||||
shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0]));
|
||||
return shallowRenderer.getRenderOutput();
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,177 @@
|
|||
module.exports = {
|
||||
|
||||
getDaysInMonth (d) {
|
||||
let resultDate = this.getFirstDayOfMonth(d);
|
||||
resultDate.setMonth(resultDate.getMonth() + 1);
|
||||
resultDate.setDate(resultDate.getDate() - 1);
|
||||
return resultDate.getDate();
|
||||
},
|
||||
|
||||
getFirstDayOfMonth (d) {
|
||||
return new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
},
|
||||
|
||||
getFirstWeekDay (d) {
|
||||
return this.getFirstDayOfMonth(d).getDay();
|
||||
},
|
||||
|
||||
getTimeMode (d) {
|
||||
return d.getHours() >= 12 ? 'pm' : 'am';
|
||||
},
|
||||
|
||||
getFullMonth (d) {
|
||||
let month = d.getMonth();
|
||||
switch (month) {
|
||||
default: return 'Unknown';
|
||||
case 0: return 'January';
|
||||
case 1: return 'February';
|
||||
case 2: return 'March';
|
||||
case 3: return 'April';
|
||||
case 4: return 'May';
|
||||
case 5: return 'June';
|
||||
case 6: return 'July';
|
||||
case 7: return 'August';
|
||||
case 8: return 'September';
|
||||
case 9: return 'October';
|
||||
case 10: return 'November';
|
||||
case 11: return 'December';
|
||||
}
|
||||
},
|
||||
|
||||
getShortMonth (d) {
|
||||
let month = d.getMonth();
|
||||
switch (month) {
|
||||
default: return 'Unknown';
|
||||
case 0: return 'Jan';
|
||||
case 1: return 'Feb';
|
||||
case 2: return 'Mar';
|
||||
case 3: return 'Apr';
|
||||
case 4: return 'May';
|
||||
case 5: return 'Jun';
|
||||
case 6: return 'Jul';
|
||||
case 7: return 'Aug';
|
||||
case 8: return 'Sep';
|
||||
case 9: return 'Oct';
|
||||
case 10: return 'Nov';
|
||||
case 11: return 'Dec';
|
||||
}
|
||||
},
|
||||
|
||||
getFullDayOfWeek (day) {
|
||||
switch (day) {
|
||||
default: return 'Unknown';
|
||||
case 0: return 'Sunday';
|
||||
case 1: return 'Monday';
|
||||
case 2: return 'Tuesday';
|
||||
case 3: return 'Wednesday';
|
||||
case 4: return 'Thursday';
|
||||
case 5: return 'Friday';
|
||||
case 6: return 'Saturday';
|
||||
}
|
||||
},
|
||||
|
||||
getShortDayOfWeek (day) {
|
||||
switch (day) {
|
||||
default: return 'Unknown';
|
||||
case 0: return 'Sun';
|
||||
case 1: return 'Mon';
|
||||
case 2: return 'Tue';
|
||||
case 3: return 'Wed';
|
||||
case 4: return 'Thu';
|
||||
case 5: return 'Fri';
|
||||
case 6: return 'Sat';
|
||||
}
|
||||
},
|
||||
|
||||
clone (d) {
|
||||
return new Date(d.getTime());
|
||||
},
|
||||
|
||||
cloneAsDate (d) {
|
||||
let clonedDate = this.clone(d);
|
||||
clonedDate.setHours(0, 0, 0, 0);
|
||||
return clonedDate;
|
||||
},
|
||||
|
||||
isDateObject (d) {
|
||||
return d instanceof Date;
|
||||
},
|
||||
|
||||
addDays (d, days) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setDate(d.getDate() + days);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
addMonths (d, months) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setMonth(d.getMonth() + months);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
addYears (d, years) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setFullYear(d.getFullYear() + years);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
setDay (d, day) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setDate(day);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
setMonth (d, month) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setMonth(month);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
setYear (d, year) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setFullYear(year);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
setHours (d, hours) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setHours(hours);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
setMinutes (d, minutes) {
|
||||
let newDate = this.clone(d);
|
||||
newDate.setMinutes(minutes);
|
||||
return newDate;
|
||||
},
|
||||
|
||||
toggleTimeMode (d) {
|
||||
let newDate = this.clone(d);
|
||||
let hours = newDate.getHours();
|
||||
|
||||
newDate.setHours(hours - (hours > 12 ? -12 : 12));
|
||||
return newDate;
|
||||
},
|
||||
|
||||
formatTime (date, format) {
|
||||
let hours = date.getHours();
|
||||
let mins = date.getMinutes().toString();
|
||||
|
||||
if (format === 'ampm') {
|
||||
let isAM = hours < 12;
|
||||
let additional = isAM ? ' am' : ' pm';
|
||||
|
||||
hours = hours % 12;
|
||||
hours = (hours || 12).toString();
|
||||
if (mins.length < 2) mins = '0' + mins;
|
||||
|
||||
return hours + (mins === '00' ? '' : ':' + mins) + additional;
|
||||
}
|
||||
|
||||
hours = hours.toString();
|
||||
if (hours.length < 2) hours = '0' + hours;
|
||||
if (mins.length < 2) mins = '0' + mins;
|
||||
return hours + ':' + mins;
|
||||
}
|
||||
|
||||
};
|
|
@ -19,6 +19,9 @@
|
|||
"react": ">=0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^5.8.23",
|
||||
"babel-loader": "^5.3.2",
|
||||
"babel-runtime": "^5.8.20",
|
||||
"coffee-jsx-loader": "^0.1.2",
|
||||
"css-loader": "^0.15.1",
|
||||
"extract-text-webpack-plugin": "^0.8.2",
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
# React-Toolbox full dependency way:
|
||||
# Toolbox = require 'react-toolbox'
|
||||
# Button = Toolbox.Button
|
||||
# Form = Toolbox.Form
|
||||
|
||||
# Standalone dependencies way:
|
||||
Button = require 'react-toolbox/components/button'
|
||||
Form = require 'react-toolbox/components/form'
|
||||
|
||||
App = React.createClass
|
||||
|
||||
# --
|
||||
getInitialState: ->
|
||||
fields: [
|
||||
ref: "username", label: "Your username", required: true
|
||||
,
|
||||
ref: "password", type: "password", label: "Your password", required: true
|
||||
,
|
||||
type: "submit", caption: "Login", disabled: true
|
||||
]
|
||||
|
||||
render: ->
|
||||
<app data-toolbox={true}>
|
||||
<h1>Hello React-Toolbox</h1>
|
||||
<Form attributes={@state.fields} />
|
||||
<Button caption="Hello world!" type="square" style="primary"/>
|
||||
<Button icon="adb" type="circle" style="accent" />
|
||||
</app>
|
||||
|
||||
React.render <App/>, document.body
|
|
@ -0,0 +1,33 @@
|
|||
/* global React */
|
||||
|
||||
// React-Toolbox full dependency way:
|
||||
// import {Button, Form} from 'react-toolbox'
|
||||
|
||||
// Standalone dependencies way:
|
||||
import Button from 'react-toolbox/components/button';
|
||||
import Form from 'react-toolbox/components/form';
|
||||
|
||||
const App = React.createClass({
|
||||
getInitialState () {
|
||||
return {
|
||||
fields: [
|
||||
{ ref: 'username', label: 'Your username', required: true},
|
||||
{ ref: 'password', type: 'password', label: 'Your password', required: true},
|
||||
{ type: 'submit', label: 'Login', disabled: true}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<app data-toolbox={true}>
|
||||
<h1>Hello React-Toolbox</h1>
|
||||
<Form attributes={this.state.fields} />
|
||||
<Button label='Hello world!' type='square' style='primary'/>
|
||||
<Button icon='adb' type='circle' style='accent' />
|
||||
</app>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<App />, document.body);
|
|
@ -1,38 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
pkg = require './package.json'
|
||||
node_modules = __dirname + '/node_modules'
|
||||
ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
environment = process.env.NODE_ENV
|
||||
|
||||
module.exports =
|
||||
cache : true
|
||||
resolve : extensions: ['', '.cjsx', '.coffee', '.js', '.json', '.styl']
|
||||
context : __dirname
|
||||
|
||||
entry:
|
||||
commons : ['./node_modules/react-toolbox/components/commons.styl']
|
||||
test : ['webpack/hot/dev-server', './src/app.cjsx']
|
||||
|
||||
output:
|
||||
path : if environment is 'production' then './dist' else './build'
|
||||
filename : pkg.name + '.[name].js'
|
||||
publicPath : '/build/'
|
||||
|
||||
devServer:
|
||||
host : 'localhost'
|
||||
port : 8080
|
||||
inline : true
|
||||
|
||||
module:
|
||||
loaders: [
|
||||
test : /\.cjsx$/, loader: 'coffee-jsx-loader'
|
||||
,
|
||||
test : /\.coffee$/, loader: 'coffee-jsx-loader'
|
||||
,
|
||||
test : /\.styl$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!stylus-loader!')
|
||||
]
|
||||
|
||||
plugins: [
|
||||
new ExtractTextPlugin pkg.name + '.[name].css', allChunks: false
|
||||
]
|
|
@ -0,0 +1,36 @@
|
|||
var pkg = require('./package.json');
|
||||
var node_modules = __dirname + '/node_modules';
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
var environment = process.env.NODE_ENV;
|
||||
|
||||
module.exports = {
|
||||
cache: true,
|
||||
resolve: {
|
||||
extensions: ['', '.jsx', '.cjsx', '.coffee', '.js', '.json', '.styl']
|
||||
},
|
||||
context: __dirname,
|
||||
entry: {
|
||||
commons: ['./node_modules/react-toolbox/components/commons.styl'],
|
||||
test: ['webpack/hot/dev-server', './src/app.jsx']
|
||||
},
|
||||
output: {
|
||||
path: environment === 'production' ? './dist' : './build',
|
||||
filename: pkg.name + '.[name].js',
|
||||
publicPath: '/build/'
|
||||
},
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
port: 8080,
|
||||
inline: true
|
||||
},
|
||||
module: {
|
||||
noParse: [node_modules + '/react/dist/*.js'],
|
||||
loaders: [
|
||||
{ test: /(\.js|\.jsx)$/, exclude: /(node_modules)/, loader: 'babel?optional=runtime'},
|
||||
{ test: /\.cjsx$/, loader: 'coffee-jsx-loader'},
|
||||
{ test: /\.coffee$/, loader: 'coffee-jsx-loader'},
|
||||
{ test: /\.styl$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader!stylus-loader!')}
|
||||
]
|
||||
},
|
||||
plugins: [new ExtractTextPlugin(pkg.name + '.[name].css', {allChunks: false})]
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue