From e96d313b6b820d447fa8fd1e7170adc509cd5853 Mon Sep 17 00:00:00 2001 From: Chris McVittie Date: Sun, 14 Jun 2015 21:17:42 +0100 Subject: [PATCH] Keyboard Friendly Dropdown --- docs/gulp/tasks/jshint.js | 2 +- .../pages/components/text-fields.jsx | 73 ++++++- package.json | 2 +- src/drop-down-menu.jsx | 132 ++++++++++-- src/index.js | 1 + src/menu/link-menu-item.jsx | 5 +- src/menu/menu-item.jsx | 23 +-- src/menu/menu.jsx | 191 ++++++++++++++++-- src/select-field.jsx | 83 ++++++++ src/text-field.jsx | 58 ++++-- 10 files changed, 495 insertions(+), 75 deletions(-) create mode 100644 src/select-field.jsx diff --git a/docs/gulp/tasks/jshint.js b/docs/gulp/tasks/jshint.js index df35a4bc2cbdac..e4536e13228635 100644 --- a/docs/gulp/tasks/jshint.js +++ b/docs/gulp/tasks/jshint.js @@ -3,5 +3,5 @@ var shell = require('gulp-shell'); var handleErrors = require('../util/handleErrors'); gulp.task('jshint', shell.task([ - '../node_modules/.bin/jsxhint --harmony "../src/**" "./src/app/**" --exclude ../src/utils/modernizr.custom.js' + '"../node_modules/.bin/jsxhint" --harmony "../src/**" "./src/app/**" --exclude ../src/utils/modernizr.custom.js' ])).on('error', handleErrors); diff --git a/docs/src/app/components/pages/components/text-fields.jsx b/docs/src/app/components/pages/components/text-fields.jsx index 47971cca6a1a98..fdb373fb43d42d 100644 --- a/docs/src/app/components/pages/components/text-fields.jsx +++ b/docs/src/app/components/pages/components/text-fields.jsx @@ -2,6 +2,7 @@ var React = require('react/addons'); var mui = require('mui'); var ClearFix = mui.ClearFix; var TextField = mui.TextField; +var SelectField = mui.SelectField; var StyleResizable = mui.Mixins.StyleResizable; var ComponentDoc = require('../../component-doc.jsx'); @@ -18,6 +19,8 @@ var TextFieldsPage = React.createClass({ propValue: 'Prop Value', floatingPropValue: 'Prop Value', valueLinkValue: 'Value Link', + selectValue: undefined, + selectValueLinkValue: 4, floatingValueLinkValue: 'Value Link' }; }, @@ -76,6 +79,17 @@ var TextFieldsPage = React.createClass({ ' hintText="Disabled Hint Text"\n' + ' disabled={true}\n' + ' defaultValue="Disabled With Value" />\n\n' + + '\n'+ + '\n'+ '//Floating Hint Text Labels\n' + ''; + ' floatingLabelText="Floating Label Text" />\n'+ + '\n' + + ' \n' + + '\n'+ + '\n' + + ' \n' + + ''; var desc = 'This component extends the current input element and will support all of its props and events. It supports ' + 'valueLink and can be controlled or uncontrolled.' ; @@ -231,6 +258,20 @@ var TextFieldsPage = React.createClass({ ]; var styles = this.getStyles(); + var menuItems = [ + { payload: '1', text: 'Never' }, + { payload: '2', text: 'Every Night' }, + { payload: '3', text: 'Weeknights' }, + { payload: '4', text: 'Weekends' }, + { payload: '5', text: 'Weekly' }, + ]; + var arbitraryArrayMenuItems = [ + {id:1, name:'Never'}, + {id:2, name:'Every Night'}, + {id:3, name:'Weeknights'}, + {id:4, name:'Weekends'}, + {id:5, name:'Weekly'} + ]; return (
+ +

+ + + + + +
@@ -363,6 +428,12 @@ var TextFieldsPage = React.createClass({ }); }, + _handleSelectValueChange: function(e) { + this.setState({ + selectValue: e.target.value + }); + }, + _handleFloatingInputChange: function(e) { this.setState({ floatingPropValue: e.target.value diff --git a/package.json b/package.json index 1362dd74bdef2f..0eadac70578fd8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "prebuild": "rm -rf lib", - "jshint": "./node_modules/.bin/jsxhint --harmony 'src/**' --exclude src/utils/modernizr.custom.js", + "jshint": "'./node_modules/.bin/jsxhint' --harmony 'src/**' --exclude src/utils/modernizr.custom.js", "build": "npm run jshint && ./node_modules/.bin/babel --stage 1 ./src --out-dir ./lib", "prepublish": "npm run build" }, diff --git a/src/drop-down-menu.jsx b/src/drop-down-menu.jsx index 46d7b0b00bdb48..c9952aab89616d 100644 --- a/src/drop-down-menu.jsx +++ b/src/drop-down-menu.jsx @@ -2,6 +2,7 @@ var React = require('react'); var StylePropable = require('./mixins/style-propable'); var Transitions = require('./styles/transitions'); var ClickAwayable = require('./mixins/click-awayable'); +var KeyCode = require('./utils/key-code'); var DropDownArrow = require('./svg-icons/drop-down-arrow'); var Paper = require('./paper'); var Menu = require('./menu/menu'); @@ -19,16 +20,23 @@ var DropDownMenu = React.createClass({ // than just the parent. propTypes: { className: React.PropTypes.string, + displayMember: React.PropTypes.string, + valueMember: React.PropTypes.string, autoWidth: React.PropTypes.bool, onChange: React.PropTypes.func, menuItems: React.PropTypes.array.isRequired, menuItemStyle: React.PropTypes.object, + underlineStyle:React.PropTypes.object, + iconStyle:React.PropTypes.object, + labelStyle:React.PropTypes.object, selectedIndex: React.PropTypes.number }, getDefaultProps: function() { return { - autoWidth: true + autoWidth: true, + valueMember:'payload', + displayMember:'text' }; }, @@ -36,12 +44,13 @@ var DropDownMenu = React.createClass({ return { open: false, isHovered: false, - selectedIndex: this.props.selectedIndex || 0 + selectedIndex: (this.props.hasOwnProperty('value') || this.props.hasOwnProperty('valueLink')) ? null :(this.props.selectedIndex || 0), }; }, componentClickAway: function() { - this.setState({ open: false }); + this.state.open = false; + this.setState(this.state); }, componentDidMount: function() { @@ -50,7 +59,9 @@ var DropDownMenu = React.createClass({ }, componentWillReceiveProps: function(nextProps) { - if (nextProps.hasOwnProperty('selectedIndex')) { + if (nextProps.hasOwnProperty('value') || nextProps.hasOwnProperty('valueLink')) { + return; + } else if (nextProps.hasOwnProperty('selectedIndex')) { this._setSelectedIndex(nextProps); } }, @@ -72,7 +83,8 @@ var DropDownMenu = React.createClass({ position: 'relative', display: 'inline-block', height: this.getSpacing().desktopToolbarHeight, - fontSize: this.getSpacing().desktopDropDownMenuFontSize + fontSize: this.getSpacing().desktopDropDownMenuFontSize, + outline:'none' }, control: { cursor: 'pointer', @@ -84,8 +96,7 @@ var DropDownMenu = React.createClass({ backgroundColor: backgroundColor, height: '100%', width: '100%', - opacity: (this.state.open) ? 0 : - (this.state.isHovered) ? 1 : 0 + opacity:0 }, icon: { position: 'absolute', @@ -125,18 +136,52 @@ var DropDownMenu = React.createClass({ return styles; }, + getInputNode: function() { + var root = this.refs.root; + var item = this.props.menuItems[this.state.selectedIndex]; + if (item) + root.value = item[this.props.displayMember]; + return root; + }, + render: function() { + var _this = this; var styles = this.getStyles(); - - if (process.env.NODE_ENV !== 'production') { - console.assert(!!this.props.menuItems[this.state.selectedIndex], 'SelectedIndex of ' + this.state.selectedIndex + ' does not exist in menuItems.'); + var selectedIndex = this.state.selectedIndex; + var displayValue = ""; + if (selectedIndex) { + if (process.env.NODE_ENV !== 'production') { + console.assert(!!this.props.menuItems[selectedIndex], 'SelectedIndex of ' + selectedIndex + ' does not exist in menuItems.'); + } + } + else { + if (this.props.valueMember && (this.props.valueLink || this.props.value)) + { + var value = this.props.value || this.props.valueLink.value; + for (var i in this.props.menuItems) + if (this.props.menuItems[i][this.props.valueMember] === value) + selectedIndex = i; + } } + var selectedItem = this.props.menuItems[selectedIndex]; + if (selectedItem) + displayValue = selectedItem[this.props.displayMember]; + + var menuItems = this.props.menuItems.map(function(item){ + item.text = item[_this.props.displayMember]; + item.payload = item[_this.props.valueMember]; + return item; + }); + return (
-
- {this.props.menuItems[this.state.selectedIndex].text} +
+ {displayValue}
- -
+ +
); @@ -187,20 +233,70 @@ var DropDownMenu = React.createClass({ this.setState({ open: !this.state.open }); }, + _onKeyDown: function(e) { + switch(e.which) { + case KeyCode.UP: + if (!this.state.open) + this._selectPreviousItem(); + else + if (e.altKey) + this.setState({open:false}); + break; + case KeyCode.DOWN: + if (!this.state.open) + if (e.altKey) + this.setState({open:true}); + else + this._selectNextItem(); + break; + case KeyCode.ENTER: + case KeyCode.SPACE: + this.setState({open:true}); + break; + default: + return; //important + } + e.preventDefault(); + }, + _onMenuItemClick: function(e, key, payload) { - if (this.props.onChange && this.state.selectedIndex !== key) this.props.onChange(e, key, payload); + if (this.props.onChange && this.state.selectedIndex !== key) { + var selectedItem = this.props.menuItems[this.state.selectedIndex]; + if (selectedItem) + e.target.value = selectedItem[this.props.valueMember]; + + if (this.props.valueLink) + this.props.valueLink.requestChange(e.target.value) + else + this.props.onChange(e, key, payload); + } + this.setState({ selectedIndex: key, - open: false + value:e.target.value, + open: false, + isHovered:false }); }, + _onMenuRequestClose: function() { + this.setState({open:false}); + }, + _handleMouseOver: function() { this.setState({isHovered: true}); }, _handleMouseOut: function() { this.setState({isHovered: false}); + }, + + _selectPreviousItem: function() { + this.setState({selectedIndex: Math.max(this.state.selectedIndex - 1, 0)}); + }, + + _selectNextItem: function() { + this.setState({selectedIndex: Math.min(this.state.selectedIndex + 1, this.props.menuItems.length - 1)}); } }); diff --git a/src/index.js b/src/index.js index 4738ce5696c330..1f129a35bb9cbf 100644 --- a/src/index.js +++ b/src/index.js @@ -34,6 +34,7 @@ module.exports = { RadioButton: require('./radio-button'), RadioButtonGroup: require('./radio-button-group'), RaisedButton: require('./raised-button'), + SelectField: require('./select-field'), Slider: require('./slider'), SvgIcon: require('./svg-icon'), Icons: { diff --git a/src/menu/link-menu-item.jsx b/src/menu/link-menu-item.jsx index 561f8419512f48..401825ee5e95ea 100644 --- a/src/menu/link-menu-item.jsx +++ b/src/menu/link-menu-item.jsx @@ -14,12 +14,14 @@ var LinkMenuItem = React.createClass({ payload: React.PropTypes.string.isRequired, text: React.PropTypes.string.isRequired, target: React.PropTypes.string, + active: React.PropTypes.bool, disabled: React.PropTypes.bool, className: React.PropTypes.string, }, getDefaultProps: function() { return { + active:false, disabled: false }; }, @@ -71,7 +73,8 @@ var LinkMenuItem = React.createClass({ this.mergeAndPrefix( styles.root, this.props.selected && styles.rootWhenSelected, - (this.state.hovered && !this.props.disabled) && styles.rootWhenHovered, + this.props.selected && styles.rootWhenSelected, + (this.props.active && !this.props.disabled) && styles.rootWhenHovered, this.props.style, this.props.disabled && styles.rootWhenDisabled); diff --git a/src/menu/menu-item.jsx b/src/menu/menu-item.jsx index 31e8cc8b94a11f..9b1c3c8cae47ec 100644 --- a/src/menu/menu-item.jsx +++ b/src/menu/menu-item.jsx @@ -31,7 +31,8 @@ var MenuItem = React.createClass({ disabled: React.PropTypes.bool, onTouchTap: React.PropTypes.func, onToggle: React.PropTypes.func, - selected: React.PropTypes.bool + selected: React.PropTypes.bool, + active: React.PropTypes.bool }, statics: { @@ -41,16 +42,11 @@ var MenuItem = React.createClass({ getDefaultProps: function() { return { toggle: false, - disabled: false + disabled: false, + active:false }; }, - getInitialState: function() { - return { - hovered: false - } - }, - getTheme: function() { return this.context.muiTheme.component.menuItem; }, @@ -153,11 +149,11 @@ var MenuItem = React.createClass({ className={this.props.className} onTouchTap={this._handleTouchTap} onMouseOver={this._handleMouseOver} - onMouseOut={this._handleMouseOut} + onMouseOut={this._handleMouseOut}f style={this.mergeAndPrefix( styles.root, this.props.selected && styles.rootWhenSelected, - (this.state.hovered && !this.props.disabled) && styles.rootWhenHovered, + (this.props.active && !this.props.disabled) && styles.rootWhenHovered, this.props.style, this.props.disabled && styles.rootWhenDisabled)}> @@ -182,15 +178,12 @@ var MenuItem = React.createClass({ }, _handleMouseOver: function(e) { - this.setState({hovered: true}); - if (!this.props.disabled && this.props.onMouseOver) this.props.onMouseOver(e); + if (!this.props.disabled && this.props.onMouseOver) this.props.onMouseOver(e, this.props.index); }, _handleMouseOut: function(e) { - this.setState({hovered: false}); - if (!this.props.disabled && this.props.onMouseOut) this.props.onMouseOut(e); + if (!this.props.disabled && this.props.onMouseOut) this.props.onMouseOut(e,this.props.index); } - }); module.exports = MenuItem; diff --git a/src/menu/menu.jsx b/src/menu/menu.jsx index 5477d133ea0e1b..0eeddd06fdf9f8 100644 --- a/src/menu/menu.jsx +++ b/src/menu/menu.jsx @@ -2,6 +2,7 @@ var React = require('react'); var CssEvent = require('../utils/css-event'); var Dom = require('../utils/dom'); var KeyLine = require('../utils/key-line'); +var KeyCode = require('../utils/key-code'); var StylePropable = require('../mixins/style-propable'); var Transitions = require('../styles/transitions'); var ClickAwayable = require('../mixins/click-awayable'); @@ -9,6 +10,7 @@ var Paper = require('../paper'); var MenuItem = require('./menu-item'); var LinkMenuItem = require('./link-menu-item'); var SubheaderMenuItem = require('./subheader-menu-item'); +var WindowListenable = require('../mixins/window-listenable'); /*********************** * Nested Menu Component @@ -27,6 +29,7 @@ var NestedMenuItem = React.createClass({ menuItems: React.PropTypes.array.isRequired, zDepth: React.PropTypes.number, disabled: React.PropTypes.bool, + active: React.PropTypes.bool, onItemTap: React.PropTypes.func, menuItemStyle: React.PropTypes.object, }, @@ -38,7 +41,10 @@ var NestedMenuItem = React.createClass({ }, getInitialState: function() { - return { open: false } + return { + open: false , + activeIndex:0 + } }, componentClickAway: function() { @@ -47,6 +53,8 @@ var NestedMenuItem = React.createClass({ componentDidMount: function() { this._positionNestedMenu(); + var el = this.getDOMNode(); + el.focus(); }, componentDidUpdate: function() { @@ -57,8 +65,47 @@ var NestedMenuItem = React.createClass({ return this.context.muiTheme.spacing; }, + getStyles: function() { + var styles = { + root: { + userSelect: 'none', + cursor: 'pointer', + lineHeight: this.getTheme().height + 'px', + color: this.context.muiTheme.palette.textColor + }, + icon: { + float: 'left', + lineHeight: this.getTheme().height + 'px', + marginRight: this.getSpacing().desktopGutter + }, + toggle: { + marginTop: ((this.getTheme().height - this.context.muiTheme.component.radioButton.size) / 2), + float: 'right', + width: 42 + }, + rootWhenHovered: { + backgroundColor: this.getTheme().hoverColor + }, + rootWhenSelected: { + color: this.getTheme().selectedTextColor + }, + rootWhenDisabled: { + cursor: 'default', + color: this.context.muiTheme.palette.disabledColor + } + }; + return styles; + }, + + getTheme: function() { + return this.context.muiTheme.component.menuItem; + }, + render: function() { - var styles = this.mergeAndPrefix({ + + var styles = this.getStyles(); + styles = this.mergeAndPrefix(styles.root, + (this.props.active && !this.props.disabled) && styles.rootWhenHovered, { position: 'relative' }, this.props.style); @@ -74,7 +121,13 @@ var NestedMenuItem = React.createClass({ } = this.props; return ( -
+
); }, + toggleNestedMenu: function() { + if (!this.props.disabled) this.setState({ open: !this.state.open }); + }, + + isOpen: function() { + return this.state.open; + }, + _positionNestedMenu: function() { var el = React.findDOMNode(this); var nestedMenu = React.findDOMNode(this.refs.nestedMenu); - nestedMenu.style.left = el.offsetWidth + 'px'; }, @@ -109,19 +170,23 @@ var NestedMenuItem = React.createClass({ _closeNestedMenu: function() { this.setState({ open: false }); - }, - - _toggleNestedMenu: function() { - if (!this.props.disabled) this.setState({ open: !this.state.open }); + React.findDOMNode(this).focus(); }, _onParentItemTap: function() { - this._toggleNestedMenu(); + this.toggleNestedMenu(); }, _onMenuItemTap: function(e, index, menuItem) { if (this.props.onItemTap) this.props.onItemTap(e, index, menuItem); this._closeNestedMenu(); + }, + _handleMouseOver: function(e) { + if (!this.props.disabled && this.props.onMouseOver) this.props.onMouseOver(e, this.props.index); + }, + + _handleMouseOut: function(e) { + if (!this.props.disabled && this.props.onMouseOut) this.props.onMouseOut(e,this.props.index); } }); @@ -142,6 +207,7 @@ var Menu = React.createClass({ autoWidth: React.PropTypes.bool, onItemTap: React.PropTypes.func, onToggle: React.PropTypes.func, + onRequestClose: React.PropTypes.func, menuItems: React.PropTypes.array.isRequired, selectedIndex: React.PropTypes.number, hideable: React.PropTypes.bool, @@ -156,7 +222,10 @@ var Menu = React.createClass({ }, getInitialState: function() { - return { nestedMenuShown: false } + return { + nestedMenuShown: false, + activeIndex:0 + } }, getDefaultProps: function() { @@ -165,6 +234,7 @@ var Menu = React.createClass({ hideable: false, visible: true, zDepth: 1, + onRequestClose: function() {} }; }, @@ -199,7 +269,8 @@ var Menu = React.createClass({ backgroundColor: this.getTheme().containerBackgroundColor, paddingTop: this.getSpacing().desktopGutterMini, paddingBottom: this.getSpacing().desktopGutterMini, - transition: Transitions.easeOut(null, 'height') + transition: Transitions.easeOut(null, 'height'), + outline:'none !important' }, subheader: { paddingLeft: this.context.muiTheme.component.menuSubheader.padding, @@ -221,6 +292,8 @@ var Menu = React.createClass({ return ( ); this._nestedChildren.push(i); @@ -318,6 +396,7 @@ var Menu = React.createClass({ selected={isSelected} key={i} index={i} + active={this.state.activeIndex == i} icon={menuItem.icon} data={menuItem.data} className={this.props.menuItemClassName} @@ -327,15 +406,18 @@ var Menu = React.createClass({ toggle={menuItem.toggle} onToggle={this.props.onToggle} disabled={isDisabled} - onTouchTap={this._onItemTap}> + onTouchTap={this._onItemTap} + onMouseOver={this._onItemActivated} + onMouseOut={this._onItemDeactivated} + > {menuItem.text} ); } - children.push(itemComponent); + this._children.push(itemComponent); } - return children; + return this._children; }, _setKeyWidth: function(el) { @@ -374,6 +456,7 @@ var Menu = React.createClass({ //Make sure the menu is open before setting the overflow. //This is to accout for fast clicks if (this.props.visible) container.style.overflow = 'visible'; + el.focus(); }.bind(this)); } else { @@ -397,7 +480,81 @@ var Menu = React.createClass({ _onItemToggle: function(e, index, toggled) { if (this.props.onItemToggle) this.props.onItemToggle(e, index, this.props.menuItems[index], toggled); - } + }, + _onItemActivated: function(e, index) { + this.setState({activeIndex:index}) + }, + _onItemDeactivated: function(e, index) { + if (this.state.activeKey == index) + this.setState({activeIndex:0}) + }, + + _onKeyDown: function(e) { + if (!(this.state.open || this.props.visible)) + return; + + var nested = this._children[this.state.activeIndex]; + if (nested && nested.props.nested && this.refs[this.state.activeIndex].isOpen()) + return; + + switch(e.which) { + case KeyCode.UP: + this._activatePreviousItem(); + break; + case KeyCode.DOWN: + this._activateNextItem(); + break; + case KeyCode.RIGHT: + this._tryToggleNested(this.state.activeIndex); + break; + case KeyCode.LEFT: + this._close(); + break; + case KeyCode.ESC: + this._close(); + break; + case KeyCode.TAB: + this._close(); + return; // so the tab key can propagate + case KeyCode.ENTER: + case KeyCode.SPACE: + e.stopPropagation(); // needs called before the close + this._triggerSelection(e); + break; + default: + return; //important + } + e.preventDefault(); + e.stopPropagation(); + }, + + _activatePreviousItem:function() { + var active = this.state.activeIndex || 0; + active = Math.max(active - 1, 0); + this.setState({activeIndex:active}); + }, + + _activateNextItem: function() { + var active = this.state.activeIndex || 0; + active = Math.min(active+1, this._children.length -1); + this.setState({activeIndex:active}); + }, + + _triggerSelection: function(e) { + var index = this.state.activeIndex || 0; + this._onItemTap(e, index) + }, + + _close: function() { + this.props.onRequestClose(); + }, + + _tryToggleNested: function(index) { + var item = this.refs[index]; + var toggleMenu = item.toggleNestedMenu; + if (item && item.toggleNestedMenu) + item.toggleNestedMenu(); + }, }); diff --git a/src/select-field.jsx b/src/select-field.jsx new file mode 100644 index 00000000000000..cd6cc2b7759962 --- /dev/null +++ b/src/select-field.jsx @@ -0,0 +1,83 @@ +var React = require('react'); +var StylePropable = require('./mixins/style-propable'); +var Transitions = require('./styles/transitions'); +var TextField = require('./text-field'); +var DropDownMenu = require('./drop-down-menu'); +var SelectField = React.createClass({ + + mixins: [StylePropable], + + contextTypes: { + muiTheme: React.PropTypes.object + }, + + propTypes: { + errorText: React.PropTypes.string, + floatingLabelText: React.PropTypes.string, + hintText: React.PropTypes.string, + id: React.PropTypes.string, + multiLine: React.PropTypes.bool, + onBlur: React.PropTypes.func, + onChange: React.PropTypes.func, + onFocus: React.PropTypes.func, + onKeyDown: React.PropTypes.func, + onEnterKeyDown: React.PropTypes.func, + type: React.PropTypes.string, + rows: React.PropTypes.number, + inputStyle: React.PropTypes.object, + floatingLabelStyle: React.PropTypes.object, + autoWidth: React.PropTypes.bool, + menuItems: React.PropTypes.array.isRequired, + menuItemStyle: React.PropTypes.object, + selectedIndex: React.PropTypes.number + }, + + getDefaultProps: function() { + return {}; + }, + + getStyles: function() { + var styles = { + selectfield:{ + label: { + paddingLeft:0, + top:20, + }, + icon: { + top:36, + right:0 + }, + underline: { + borderTop:'none' + } + } + }; + return styles; + }, + + onChange: function(e, index, payload) { + e.target.value = payload; + if (this.props.onChange) + this.props.onChange(e) + }, + + render: function() { + var styles = this.getStyles(); + return ( + + + + ); + }, + + + +}); + +module.exports = SelectField; diff --git a/src/text-field.jsx b/src/text-field.jsx index 3a6eec4ef82438..d2b5ce199e6b99 100644 --- a/src/text-field.jsx +++ b/src/text-field.jsx @@ -41,10 +41,13 @@ var TextField = React.createClass({ }, getInitialState: function() { + var props = this.props; + if (props.children) + props = props.children.props; return { errorText: this.props.errorText, - hasValue: this.props.value || this.props.defaultValue || - (this.props.valueLink && this.props.valueLink.value) + hasValue: props.value || props.defaultValue || + (props.valueLink && props.valueLink.value) }; }, @@ -54,20 +57,26 @@ var TextField = React.createClass({ componentWillReceiveProps: function(nextProps) { var hasErrorProp = nextProps.hasOwnProperty('errorText'); + var newState = {}; + + if (hasErrorProp) newState.errorText = nextProps.errorText; + if (nextProps.children && nextProps.children.props) + { + nextProps = nextProps.children.props; + } + var hasValueLinkProp = nextProps.hasOwnProperty('valueLink'); var hasValueProp = nextProps.hasOwnProperty('value'); var hasNewDefaultValue = nextProps.defaultValue !== this.props.defaultValue; - var newState = {}; - if (hasValueProp) { - newState.hasValue = nextProps.value; - } else if (hasValueLinkProp) { + if (hasValueLinkProp) { newState.hasValue = nextProps.valueLink.value; + } else if (hasValueProp) { + newState.hasValue = nextProps.value; } else if (hasNewDefaultValue) { newState.hasValue = nextProps.defaultValue; } - if (hasErrorProp) newState.errorText = nextProps.errorText; if (newState) this.setState(newState); }, @@ -234,26 +243,31 @@ var TextField = React.createClass({ style: this.mergeAndPrefix(styles.input, this.props.inputStyle), onBlur: this._handleInputBlur, onFocus: this._handleInputFocus, + disabled: this.props.disabled, onKeyDown: this._handleInputKeyDown }; if (!this.props.hasOwnProperty('valueLink')) { inputProps.onChange = this._handleInputChange; } - - inputElement = this.props.multiLine ? ( - - ) : ( - - ); + onHeightChange={this._handleTextAreaHeightChange} + textareaStyle={this.mergeAndPrefix(styles.textarea)} /> + ) : ( + + ); + } var underlineElement = this.props.disabled ? (
@@ -314,7 +328,7 @@ var TextField = React.createClass({ }, _getInputNode: function() { - return this.props.multiLine ? + return (this.props.children || this.props.multiLine) ? this.refs[this._getRef()].getInputNode() : React.findDOMNode(this.refs[this._getRef()]); }, @@ -329,6 +343,8 @@ var TextField = React.createClass({ }, _handleInputFocus: function(e) { + if (this.props.disabled) + return this.setState({isFocused: true}); if (this.props.onFocus) this.props.onFocus(e); },