diff --git a/docs/src/app/components/Toolbars/ExampleSimple.jsx b/docs/src/app/components/Toolbars/ExampleSimple.jsx index 100a25b94c0098..0c6fb5d1e4e394 100644 --- a/docs/src/app/components/Toolbars/ExampleSimple.jsx +++ b/docs/src/app/components/Toolbars/ExampleSimple.jsx @@ -11,22 +11,20 @@ import ToolbarGroup from 'material-ui/lib/toolbar/toolbar-group'; import ToolbarSeparator from 'material-ui/lib/toolbar/toolbar-separator'; import ToolbarTitle from 'material-ui/lib/toolbar/toolbar-title'; -const menuItems = [ - {payload: '1', text: 'All Broadcasts'}, - {payload: '2', text: 'All Voice'}, - {payload: '3', text: 'All Text'}, - {payload: '4', text: 'Complete Voice'}, - {payload: '5', text: 'Complete Text'}, - {payload: '6', text: 'Active Voice'}, - {payload: '7', text: 'Active Text'}, -]; - const ToolbarsExamplesSimple = React.createClass({ render() { return ( - + + + + + + + + + diff --git a/docs/src/app/components/pages/components/DropDownMenu/ExampleLabeled.jsx b/docs/src/app/components/pages/components/DropDownMenu/ExampleLabeled.jsx new file mode 100644 index 00000000000000..81ecd4a6087460 --- /dev/null +++ b/docs/src/app/components/pages/components/DropDownMenu/ExampleLabeled.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import DropDownMenu from 'material-ui/lib/DropDownMenu'; +import MenuItem from 'material-ui/lib/menus/menu-item'; + +export default class DropDownMenuLabeledExample extends React.Component { + + constructor(props) { + super(props); + this.state = {value: 2}; + } + + handleChange = (e, index, value) => this.setState({value}); + + render() { + return ( + + + + + + + ); + } +} diff --git a/docs/src/app/components/pages/components/DropDownMenu/ExampleLongMenu.jsx b/docs/src/app/components/pages/components/DropDownMenu/ExampleLongMenu.jsx new file mode 100644 index 00000000000000..35ec4017107be6 --- /dev/null +++ b/docs/src/app/components/pages/components/DropDownMenu/ExampleLongMenu.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import DropDownMenu from 'material-ui/lib/DropDownMenu'; +import MenuItem from 'material-ui/lib/menus/menu-item'; + +const items = []; +for (let i = 0; i < 100; i++ ) { + items.push(); +} + +export default class DropDownMenuLongMenuExample extends React.Component { + + constructor(props) { + super(props); + this.state = {value: 10}; + } + + handleChange = (e, index, value) => this.setState({value}); + + render() { + return ( + + {items} + + ); + } +} diff --git a/docs/src/app/components/pages/components/DropDownMenu/ExampleSimple.jsx b/docs/src/app/components/pages/components/DropDownMenu/ExampleSimple.jsx new file mode 100644 index 00000000000000..ba3832a8359448 --- /dev/null +++ b/docs/src/app/components/pages/components/DropDownMenu/ExampleSimple.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import DropDownMenu from 'material-ui/lib/DropDownMenu'; +import MenuItem from 'material-ui/lib/menus/menu-item'; + +export default class DropDownMenuSimpleExample extends React.Component { + + constructor(props) { + super(props); + this.state = {value: 2}; + } + + handleChange = (e, index, value) => this.setState({value}); + + render() { + return ( + + + + + + + + ); + } +} diff --git a/docs/src/app/components/pages/components/DropDownMenu/README.md b/docs/src/app/components/pages/components/DropDownMenu/README.md new file mode 100644 index 00000000000000..5c25e907f03ce8 --- /dev/null +++ b/docs/src/app/components/pages/components/DropDownMenu/README.md @@ -0,0 +1,6 @@ +## Drop Down Menu + +To find out more about the `DropDownMenu` component please visit the Material Design's +specifications [here](https://www.google.com/design/spec/components/menus.html#menus-usage). + +### Examples diff --git a/docs/src/app/components/pages/components/drop-down-menu.jsx b/docs/src/app/components/pages/components/drop-down-menu.jsx index 8b84dc2d9833fd..3b28d2b7c29b80 100644 --- a/docs/src/app/components/pages/components/drop-down-menu.jsx +++ b/docs/src/app/components/pages/components/drop-down-menu.jsx @@ -1,173 +1,32 @@ import React from 'react'; -import {DropDownMenu, Paper} from 'material-ui'; -import ComponentDoc from '../../component-doc'; -import Code from 'drop-down-menu-code'; +import MarkdownElement from '../../MarkdownElement'; import CodeExample from '../../code-example/code-example'; -import CodeBlock from '../../code-example/code-block'; - -export default class DropDownMenuPage extends React.Component { - +import PropTypeDescription from '../../PropTypeDescription'; +import dropDownMenuReadmeText from './DropDownMenu/README'; +import DropDownMenuSimpleExample from './DropDownMenu/ExampleSimple'; +import dropDownMenuSimpleExampleCode from '!raw!./DropDownMenu/ExampleSimple'; +import DropDownMenuLongMenuExample from './DropDownMenu/ExampleLongMenu'; +import dropDownMenuLongMenuExampleCode from '!raw!./DropDownMenu/ExampleLongMenu'; +import DropDownMenuLabeledExample from './DropDownMenu/ExampleLabeled'; +import dropDownMenuLabeledExampleCode from '!raw!./DropDownMenu/ExampleLabeled'; +import dropDownMenuCode from '!raw!material-ui/lib/DropDownMenu/DropDownMenu'; + +export default class LeftNavPage extends React.Component { render() { - - let menuItems = [ - {payload: '1', text: 'Never'}, - {payload: '2', text: 'Every Night'}, - {payload: '3', text: 'Weeknights'}, - {payload: '4', text: 'Weekends'}, - {payload: '5', text: 'Weekly'}, - ]; - - let longMenuItems = []; - for (let i = 0; i < 100; i++ ) { - longMenuItems.push({ - payload: i.toString(), - text: 'Item ' + i, - }); - } - - let componentInfo = [ - { - name: 'Props', - infoArray: [ - { - name: 'displayMember', - type: 'string', - header: 'default: text', - desc: 'DropDownMenu will use text as default value, with this ' + - 'property you can choose another name.', - }, - { - name: 'valueMember', - type: 'string', - header: 'default: payload', - desc: 'DropDownMenu will use payload as default value, with this ' + - 'property you can choose another name.', - }, - { - name: 'labelMember', - type: 'string', - header: 'default: text', - desc: 'DropDownMenu will use text as default value, with this ' + - 'property you can choose another name.', - }, - { - name: 'autoWidth', - type: 'bool', - header: 'default: true', - desc: 'The width will automatically be set according to the items ' + - 'inside the menu. To control this width in Css instead, set this ' + - 'prop to false.', - }, - { - name: 'menuItems', - type: 'array', - header: 'required', - desc: 'JSON data representing all menu items in the dropdown.', - }, - { - name: 'menuItemStyle', - type: 'object', - header: 'optional', - desc: 'Overrides the inline-styles of the MenuItems when the ' + - 'DropDownMenu is expanded.', - }, - { - name: 'menuStyle', - type: 'object', - header: 'optional', - desc: 'Overrides the inline-styles of the Menu when the ' + - 'DropDownMenu is expanded.', - }, - { - name: 'selectedIndex', - type: 'number', - header: 'default: 0', - desc: 'Index of the item selected.', - }, - { - name: 'underlineStyle', - type: 'object', - header: 'optional', - desc: 'Overrides the styles of DropDownMenu\'s underline.', - }, - { - name: 'iconStyle', - type: 'object', - header: 'optional', - desc: 'Overrides the styles of DropDownMenu\'s icon element.', - }, - { - name: 'labelStyle', - type: 'object', - header: 'optional', - desc: 'Overrides the styles of DropDownMenu\'s label when the DropDownMenu is inactive.', - }, - { - name: 'style', - type: 'object', - header: 'optional', - desc: 'Overrides the inline-styles of DropDownMenu\'s root element.', - }, - { - name: 'disabled', - type: 'bool', - header: 'default: false', - desc: 'Disables the menu.', - }, - { - name: 'openImmediately', - type: 'bool', - header: 'default: false', - desc: 'Set to true to have the DropDownMenu automatically open on mount.', - }, - ], - }, - { - name: 'Events', - infoArray: [ - { - name: 'onChange', - header: 'function(event, selectedIndex, menuItem)', - desc: 'Fired when a menu item is clicked that is not the one currently ' + - 'selected.', - }, - ], - }, - ]; - - let menuItemsWithLabel = [ - {payload: '1', text: 'Morning', period: '5 am - 12 pm'}, - {payload: '2', text: 'Afternoon', period: '12 pm - 5 pm'}, - {payload: '3', text: 'Evening', period: '5 pm to 9 pm'}, - {payload: '4', text: 'Night', period: '9 pm to 4 am'}, - ]; - return ( - - - - - { - '//Import statement:\nimport DropDownMenu from \'material-ui/lib/drop-down-menu\';\n\n' + - '//See material-ui/lib/index.js for more\n' - } - - - - -
- - -
- +
+ + + - + + + + + + + +
); } - } diff --git a/docs/src/app/components/pages/components/select-fields.jsx b/docs/src/app/components/pages/components/select-fields.jsx index 15b59b7441c45d..d67e1aadbb4a11 100644 --- a/docs/src/app/components/pages/components/select-fields.jsx +++ b/docs/src/app/components/pages/components/select-fields.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import {ClearFix, Mixins, SelectField, Paper} from 'material-ui'; +import {ClearFix, Mixins, SelectField, Paper, MenuItem} from 'material-ui'; import ComponentDoc from '../../component-doc'; const {StyleResizable} = Mixins; import Code from 'select-fields-code'; @@ -15,9 +15,10 @@ const SelectFieldsPage = React.createClass({ return { selectValue: undefined, selectValue2: 1, - selectValue3: '1', - selectValueLinkValue: 4, - selectValueLinkValue2: 3, + selectValue3: 1, + selectValue4: 4, + selectValue5: 3, + selectValue6: 2, }; }, @@ -188,27 +189,6 @@ const SelectFieldsPage = React.createClass({ }, ]; - let menuItems = [ - {payload: '1', text: 'Never'}, - {payload: '2', text: 'Every Night'}, - {payload: '3', text: 'Weeknights'}, - {payload: '4', text: 'Weekends'}, - {payload: '5', text: 'Weekly'}, - ]; - let arbitraryArrayMenuItems = [ - {id: 1, name: 'Never'}, - {id: 2, name: 'Every Night'}, - {id: 3, name: 'Weeknights'}, - {id: 4, name: 'Weekends'}, - {id: 5, name: 'Weekly'}, - ]; - let menuItemsWithLabel = [ - {payload: '1', text: 'Morning', period: '5 am - 12 pm'}, - {payload: '2', text: 'Afternoon', period: '12 pm - 5 pm'}, - {payload: '3', text: 'Evening', period: '5 pm to 9 pm'}, - {payload: '4', text: 'Night', period: '9 pm to 4 am'}, - ]; - let styles = this.getStyles(); return ( @@ -234,60 +214,118 @@ const SelectFieldsPage = React.createClass({ value={this.state.selectValue} onChange={this._handleSelectValueChange.bind(null, 'selectValue')} hintText="Hint Text" - menuItems={menuItems} />
-
+ > + + + + + +
+

+ > + + + + + +
+
+ + + + + + +

+ > + + + + + +
+

+ > + + + + + +
+

+ value={4} + > + + + + + +
+

+ > + + + + + +
+

+ errorText="This is always wrong" + > + + + + + +
+

+ errorText="This is always wrong" + > + + + + + +
+
); }, - _handleSelectValueChange(name, e) { - let change = {}; - change[name] = e.target.value; - this.setState(change); + _handleSelectValueChange(name, e, index, value) { + this.setState({[name]: value}); }, }); diff --git a/docs/src/app/components/pages/customization/themes.jsx b/docs/src/app/components/pages/customization/themes.jsx index a45e8db6fa40f3..f524687226ce1d 100644 --- a/docs/src/app/components/pages/customization/themes.jsx +++ b/docs/src/app/components/pages/customization/themes.jsx @@ -479,15 +479,7 @@ const ThemesPage = React.createClass({ }, getComponentGroup() { - let styles = this.getStyles(); - - let menuItems = [ - {payload: '1', text: 'Never'}, - {payload: '2', text: 'Every Night'}, - {payload: '3', text: 'Weeknights'}, - {payload: '4', text: 'Weekends'}, - {payload: '5', text: 'Weekly'}, - ]; + const styles = this.getStyles(); return ( @@ -562,7 +554,13 @@ const ThemesPage = React.createClass({ style={{width: '100%'}}/>
- + + + + + + +
diff --git a/docs/src/app/components/raw-code/drop-down-menu-code.txt b/docs/src/app/components/raw-code/drop-down-menu-code.txt deleted file mode 100644 index 3c4185c9d22864..00000000000000 --- a/docs/src/app/components/raw-code/drop-down-menu-code.txt +++ /dev/null @@ -1,16 +0,0 @@ -let menuItems = [ - { payload: '1', text: 'Never' }, - { payload: '2', text: 'Every Night' }, - { payload: '3', text: 'Weeknights' }, - { payload: '4', text: 'Weekends' }, - { payload: '5', text: 'Weekly' }, -]; - - -let menuItemsWithLabel = [ - { payload: '1', text: 'Morning', period: '5 am - 12 pm' }, - { payload: '2', text: 'Afternoon', period: '12 pm - 5 pm' }, - { payload: '3', text: 'Evening', period: '5 pm to 9 pm' }, - { payload: '4', text: 'Night', period: '9 pm to 4 am' }, -]; - diff --git a/src/DropDownMenu/DropDownMenu.jsx b/src/DropDownMenu/DropDownMenu.jsx new file mode 100644 index 00000000000000..8dfe716ad2f51d --- /dev/null +++ b/src/DropDownMenu/DropDownMenu.jsx @@ -0,0 +1,443 @@ +import React from 'react'; +import Transitions from '../styles/transitions'; +import DropDownArrow from '../svg-icons/navigation/arrow-drop-down'; +import Menu from '../menus/menu'; +import MenuItem from '../menus/menu-item'; +import DefaultRawTheme from '../styles/raw-themes/light-raw-theme'; +import ClearFix from '../clearfix'; +import ThemeManager from '../styles/theme-manager'; +import Popover from '../popover/popover'; +import PopoverAnimationFromTop from '../popover/popover-animation-from-top'; +import styleUtils from '../utils/styles'; +import warning from 'warning'; + +const DropDownMenu = React.createClass({ + + contextTypes: { + muiTheme: React.PropTypes.object, + }, + + //for passing default theme context to children + childContextTypes: { + muiTheme: React.PropTypes.object, + }, + + getChildContext() { + return { + muiTheme: this.state.muiTheme, + }; + }, + + // The nested styles for drop-down-menu are modified by toolbar and possibly + // other user components, so it will give full access to its js styles rather + // than just the parent. + propTypes: { + /** + * The width will automatically be set according to the items inside the menu. + * To control this width in css instead, set this prop to false. + */ + autoWidth: React.PropTypes.bool, + + /** + * The `MenuItem`s to populate the `Menu` with. If the `MenuItems` have the + * prop `label` that value will be used to render the representation of that + * item within the field. + */ + children: React.PropTypes.node, + + /** + * The css class name of the root element. + */ + className: React.PropTypes.string, + + /** + * Disables the menu. + */ + disabled: React.PropTypes.bool, + + /** + * **DEPRECATED** `DropDownMenu` will use this member to display + * the name of the item. + */ + displayMember: React.PropTypes.string, + + /** + * Overrides the styles of icon element. + */ + iconStyle: React.PropTypes.object, + + /** + * **DEPRECATED** `DropDownMenu` will use this member to display + * the name of the item on the label. + */ + labelMember: React.PropTypes.string, + + /** + * Overrides the styles of label when the `DropDownMenu` is inactive. + */ + labelStyle: React.PropTypes.object, + + /** + * The maximum height of the `Menu` when it is displayed. + */ + maxHeight: React.PropTypes.number, + + /** + * **DEPRECATED** JSON data representing all menu items in the dropdown. + */ + menuItems: React.PropTypes.array, + + /** + * Overrides the styles of `Menu` when the `DropDownMenu` is displayed. + */ + menuStyle: React.PropTypes.object, + + /** + * Fired when a menu item is clicked that is not the one currently selected. + */ + onChange: React.PropTypes.func, + + /** + * Set to true to have the `DropDownMenu` automatically open on mount. + */ + openImmediately: React.PropTypes.bool, + + /** + * **DEPRECATED** Index of the item selected. + */ + selectedIndex: React.PropTypes.number, + + /** + * Override the inline-styles of the root element. + */ + style: React.PropTypes.object, + + /** + * Overrides the inline-styles of the underline. + */ + underlineStyle: React.PropTypes.object, + + /** + * The value that is currently selected. + */ + value: React.PropTypes.any, + + /** + * **DEPRECATED** Two-way binding link. + */ + valueLink: React.PropTypes.object, + + /** + * **DEPRECATED** `DropDownMenu` will use this member as the value representing an item. + */ + valueMember: React.PropTypes.string, + }, + + getDefaultProps() { + return { + autoWidth: true, + disabled: false, + valueMember: 'payload', + displayMember: 'text', + openImmediately: false, + maxHeight: 500, + labelMember: 'text', + }; + }, + + getInitialState() { + this._testDeprecations(); + return { + open: this.props.openImmediately, + selectedIndex: this._isControlled() ? null : (this.props.selectedIndex || 0), + muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme), + }; + }, + + componentDidMount() { + if (this.props.autoWidth) this._setWidth(); + if (this.props.hasOwnProperty('selectedIndex')) this._setSelectedIndex(this.props.selectedIndex); + }, + + componentWillReceiveProps(nextProps, nextContext) { + const newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; + this.setState({muiTheme: newMuiTheme}); + + if (this.props.autoWidth) this._setWidth(); + if (nextProps.hasOwnProperty('value') || nextProps.hasOwnProperty('valueLink')) { + return; + } + else if (nextProps.hasOwnProperty('selectedIndex')) { + this._setSelectedIndex(nextProps.selectedIndex); + } + }, + + getStyles() { + const {disabled} = this.props; + const spacing = this.state.muiTheme.rawTheme.spacing; + const palette = this.state.muiTheme.rawTheme.palette; + const accentColor = this.state.muiTheme.dropDownMenu.accentColor; + return { + control: { + cursor: disabled ? 'not-allowed' : 'pointer', + height: '100%', + position: 'relative', + width: '100%', + }, + icon: { + fill: accentColor, + position: 'absolute', + right: spacing.desktopGutterLess, + top: ((spacing.desktopToolbarHeight - 24) / 2), + }, + label: { + color: disabled ? palette.disabledColor : palette.textColor, + lineHeight: `${spacing.desktopToolbarHeight}px`, + opacity: 1, + position: 'relative', + paddingLeft: spacing.desktopGutter, + paddingRight: spacing.iconSize + + spacing.desktopGutterLess + + spacing.desktopGutterMini, + top: 0, + }, + labelWhenOpen: { + opacity: 0, + top: (spacing.desktopToolbarHeight / 8), + }, + rootWhenOpen: { + opacity: 1, + }, + root: { + display: 'inline-block', + fontSize: spacing.desktopDropDownMenuFontSize, + height: spacing.desktopSubheaderHeight, + outline: 'none', + position: 'relative', + transition: Transitions.easeOut(), + }, + underline: { + borderTop: `solid 1px ${accentColor}`, + bottom: 1, + left: 0, + margin: `-1px ${spacing.desktopGutter}px`, + right: 0, + position: 'absolute', + }, + }; + }, + + getInputNode() { + warning(false, `The imperative method getInputNode will be removed in favor of composability.`); + const root = this.refs.root; + const item = this.props.menuItems && this.props.menuItems[this.state.selectedIndex]; + if (item) { + root.value = item[this.props.displayMember]; + } + + return root; + }, + + render() { + const { + autoWidth, + children, + className, + displayMember, + iconStyle, + labelMember, + labelStyle, + maxHeight, + menuItems, + menuStyle, + style, + underlineStyle, + valueLink, + valueMember, + ...other, + } = this.props; + + const { + anchorEl, + open, + muiTheme, + } = this.state; + + const styles = this.getStyles(); + + let value; + let selectedIndex = this._isControlled() ? null : this.state.selectedIndex; + + if (menuItems && typeof selectedIndex === 'number') { + warning(menuItems[selectedIndex], + `SelectedIndex of ${selectedIndex} does not exist in menuItems.`); + } + + if (valueMember && this._isControlled()) { + value = this.props.hasOwnProperty('value') ? this.props.value : valueLink.value; + if (menuItems && value !== null && value !== undefined) { + for (let i = 0; i < menuItems.length; i++) { + if (menuItems[i][valueMember] === value) { + selectedIndex = i; + } + } + } + } + + let displayValue = ''; + if (menuItems) { + const selectedItem = menuItems[selectedIndex]; + if (selectedItem) { + displayValue = selectedItem[labelMember] || selectedItem[displayMember]; + } + } else { + React.Children.forEach(children, child => { + if (value === child.props.value) { + // This will need to be improved (in case primaryText is a node) + displayValue = child.props.label || child.props.primaryText; + } + }); + } + + let index = 0; + const menuItemElements = menuItems + ? menuItems.map((item, idx) => ( + + )) + : React.Children.map(children, child => { + const clone = React.cloneElement(child, { + onTouchTap: this._onMenuItemTouchTap.bind(this, index, child.props.value), + }, child.props.children); + index += 1; + return clone; + }); + + let popoverStyle; + if (anchorEl && !autoWidth) { + popoverStyle = {width: anchorEl.clientWidth}; + } + + return ( +
+ +
+ {displayValue} +
+ +
+ + + + {menuItemElements} + + +
+ ); + }, + + _testDeprecations() { + warning(this.props.displayMember === 'text', + 'displayMember will be removed in favor of composability. refer to the documentation for more information'); + + warning(this.props.labelMember === 'text', + 'labelMember will be removed in favor of composability. refer to the documentation for more information'); + + warning(!this.props.hasOwnProperty('menuItems'), + 'menuItems will be removed in favor of composability. refer to the documentation for more information'); + + warning(!this.props.hasOwnProperty('selectedIndex'), + 'selectedIndex will be removed. use value instead to control the component.'); + + warning(!this.props.hasOwnProperty('valueLink'), + 'valueLink will be removed. use value and onChange'); + + warning(this.props.valueMember === 'payload', + 'valueMember will be removed in favor of composability. refer to the documentation for more information'); + }, + + _setWidth() { + const el = this.refs.root; + if (!this.props.style || !this.props.style.hasOwnProperty('width')) { + el.style.width = 'auto'; + } + }, + + _setSelectedIndex(index) { + warning(index >= 0, 'Cannot set selectedIndex to a negative index.'); + this.setState({selectedIndex: (index >= 0) ? index : 0}); + }, + + _onControlTouchTap(event) { + event.preventDefault(); + if (!this.props.disabled) { + this.setState({ + open: !this.state.open, + anchorEl: this.refs.root, + }); + } + }, + + _onMenuItemTouchTap(key, payload, e) { + const { + onChange, + menuItems, + value, + valueLink, + valueMember, + } = this.props; + + if (menuItems && (this.state.selectedIndex !== key || e.target.value !== value)) { + const selectedItem = menuItems[key]; + if (selectedItem) { + e.target.value = selectedItem[valueMember]; + } + this._onMenuRequestClose(); + } + + if (valueLink) { + valueLink.requestChange(e.target.value); + } else if (onChange) { + onChange(e, key, payload); + } + + this.setState({ + selectedIndex: key, + open: false, + }); + }, + + _onMenuRequestClose() { + this.setState({ + open: false, + anchorEl: null, + }); + }, + + _isControlled() { + return this.props.hasOwnProperty('value') || + this.props.hasOwnProperty('valueLink'); + }, + +}); + +export default DropDownMenu; diff --git a/src/DropDownMenu/index.js b/src/DropDownMenu/index.js new file mode 100644 index 00000000000000..9ada99da98cb8b --- /dev/null +++ b/src/DropDownMenu/index.js @@ -0,0 +1,3 @@ +import DropDownMenu from './DropDownMenu'; + +export default DropDownMenu; diff --git a/src/drop-down-menu.jsx b/src/drop-down-menu.jsx index 8f69a679cba060..9ada99da98cb8b 100644 --- a/src/drop-down-menu.jsx +++ b/src/drop-down-menu.jsx @@ -1,392 +1,3 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import StylePropable from './mixins/style-propable'; -import Transitions from './styles/transitions'; -import KeyCode from './utils/key-code'; -import DropDownArrow from './svg-icons/navigation/arrow-drop-down'; -import Menu from './menus/menu'; -import MenuItem from './menus/menu-item'; -import DefaultRawTheme from './styles/raw-themes/light-raw-theme'; -import Paper from './paper'; -import ClearFix from './clearfix'; -import ThemeManager from './styles/theme-manager'; -import Popover from './popover/popover'; -import PopoverAnimationFromTop from './popover/popover-animation-from-top'; - -const DropDownMenu = React.createClass({ - - mixins: [StylePropable], - - contextTypes: { - muiTheme: React.PropTypes.object, - }, - - //for passing default theme context to children - childContextTypes: { - muiTheme: React.PropTypes.object, - }, - - getChildContext() { - return { - muiTheme: this.state.muiTheme, - }; - }, - - // The nested styles for drop-down-menu are modified by toolbar and possibly - // other user components, so it will give full access to its js styles rather - // than just the parent. - propTypes: { - autoWidth: React.PropTypes.bool, - className: React.PropTypes.string, - disabled: React.PropTypes.bool, - displayMember: React.PropTypes.string, - iconStyle: React.PropTypes.object, - labelMember: React.PropTypes.string, - labelStyle: React.PropTypes.object, - maxHeight: React.PropTypes.number, - menuItemStyle: React.PropTypes.object, - menuItems: React.PropTypes.array.isRequired, - menuStyle: React.PropTypes.object, - onChange: React.PropTypes.func, - openImmediately: React.PropTypes.bool, - selectedIndex: React.PropTypes.number, - style: React.PropTypes.object, - underlineStyle: React.PropTypes.object, - value: React.PropTypes.any, - valueLink: React.PropTypes.object, - valueMember: React.PropTypes.string, - }, - - getDefaultProps() { - return { - autoWidth: true, - disabled: false, - valueMember: 'payload', - displayMember: 'text', - openImmediately: false, - maxHeight: 500, - labelMember: 'text', - }; - }, - - getInitialState() { - return { - open: this.props.openImmediately, - selectedIndex: this._isControlled() ? null : (this.props.selectedIndex || 0), - muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme), - }; - }, - - componentDidMount() { - if (this.props.autoWidth) this._setWidth(); - if (this.props.hasOwnProperty('selectedIndex')) this._setSelectedIndex(this.props); - }, - - componentWillReceiveProps(nextProps, nextContext) { - let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; - this.setState({muiTheme: newMuiTheme}); - - if (this.props.autoWidth) this._setWidth(); - if (nextProps.hasOwnProperty('value') || nextProps.hasOwnProperty('valueLink')) { - return; - } - else if (nextProps.hasOwnProperty('selectedIndex')) { - this._setSelectedIndex(nextProps); - } - }, - - getStyles() { - const {disabled} = this.props; - let zIndex = 5; // As AppBar - let spacing = this.state.muiTheme.rawTheme.spacing; - let accentColor = this.state.muiTheme.dropDownMenu.accentColor; - let backgroundColor = this.state.muiTheme.menu.backgroundColor; - let styles = { - root: { - transition: Transitions.easeOut(), - position: 'relative', - display: 'inline-block', - height: spacing.desktopSubheaderHeight, - fontSize: spacing.desktopDropDownMenuFontSize, - outline: 'none', - }, - control: { - cursor: disabled ? 'not-allowed' : 'pointer', - position: 'relative', - height: '100%', - width: '100%', - }, - controlBg: { - transition: Transitions.easeOut(), - backgroundColor: backgroundColor, - height: '100%', - width: '100%', - opacity: 0, - }, - icon: { - position: 'absolute', - top: ((spacing.desktopToolbarHeight - 24) / 2), - right: spacing.desktopGutterLess, - fill: this.state.muiTheme.dropDownMenu.accentColor, - }, - label: { - lineHeight: spacing.desktopToolbarHeight + 'px', - position: 'relative', - paddingLeft: spacing.desktopGutter, - paddingRight: spacing.iconSize + - spacing.desktopGutterLess + - spacing.desktopGutterMini, - top: 0, - opacity: 1, - color: disabled ? this.state.muiTheme.rawTheme.palette.disabledColor - : this.state.muiTheme.rawTheme.palette.textColor, - }, - underline: { - position: 'absolute', - borderTop: 'solid 1px ' + accentColor, - margin: '-1px ' + spacing.desktopGutter + 'px', - bottom: 1, - left: 0, - right: 0, - }, - menu: { - zIndex: zIndex + 1, - position: 'relative', - }, - menuItem: { - paddingRight: spacing.iconSize + - spacing.desktopGutterLess + - spacing.desktopGutterMini, - height: spacing.desktopDropDownMenuItemHeight, - lineHeight: spacing.desktopDropDownMenuItemHeight + 'px', - whiteSpace: 'nowrap', - }, - rootWhenOpen: { - opacity: 1, - }, - labelWhenOpen: { - opacity: 0, - top: (spacing.desktopToolbarHeight / 8), - }, - }; - - return styles; - }, - - getInputNode() { - let root = this.refs.root; - let item = this.props.menuItems[this.state.selectedIndex]; - if (item) { - root.value = item[this.props.displayMember]; - } - - return root; - }, - - render() { - const { - autoWidth, - className, - displayMember, - iconStyle, - menuStyle, - labelMember, - labelStyle, - maxHeight, - menuItemStyle, - style, - underlineStyle, - valueLink, - valueMember, - ...other, - } = this.props; - let value; - let styles = this.getStyles(); - let popoverStyle; - let selectedIndex = this._isControlled() ? null : this.state.selectedIndex; - let 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 (valueMember && this._isControlled()) { - value = this.props.hasOwnProperty('value') ? this.props.value : valueLink.value; - if (value !== null && value !== undefined) { - for (let i = 0; i < this.props.menuItems.length; i++) { - if (this.props.menuItems[i][valueMember] === value) { - selectedIndex = i; - } - } - } - } - let selectedItem = this.props.menuItems[selectedIndex]; - if (selectedItem) { - displayValue = selectedItem[labelMember] || selectedItem[displayMember]; - } - - let menuItems = this.props.menuItems.map((item, idx) => - - ); - - if (this.state.anchorEl && !autoWidth) { - popoverStyle = {width: this.state.anchorEl.clientWidth}; - } - - return ( -
- -
- {displayValue} -
- - -
- - - - {menuItems} - - -
- ); - }, - - _setWidth() { - let el = ReactDOM.findDOMNode(this); - if (!this.props.style || !this.props.style.hasOwnProperty('width')) { - el.style.width = 'auto'; - } - }, - - _setSelectedIndex(props) { - let selectedIndex = props.selectedIndex; - - if (process.env.NODE_ENV !== 'production' && selectedIndex < 0) { - console.warn('Cannot set selectedIndex to a negative index.', selectedIndex); - } - - this.setState({selectedIndex: (selectedIndex > -1) ? selectedIndex : 0}); - }, - - _onControlTouchTap(event) { - event.preventDefault(); - if (!this.props.disabled) { - this.setState({ - open: !this.state.open, - anchorEl: ReactDOM.findDOMNode(this), - }); - } - }, - - _onKeyDown(e) { - switch (e.which) { - case KeyCode.UP: - if (!this.state.open) { - this._selectPreviousItem(e); - } - 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(e); - } - } - break; - case KeyCode.ENTER: - case KeyCode.SPACE: - this.setState({open: true}); - break; - default: - return; //important - } - e.preventDefault(); - }, - - _onMenuItemTouchTap(key, payload, e) { - if (this.props.onChange && this.state.selectedIndex !== key || e.target.value !== this.props.value) { - let selectedItem = this.props.menuItems[key]; - 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._onMenuRequestClose(); - } - - this.setState({ - selectedIndex: key, - value: e.target.value, - open: false, - }); - }, - - _onMenuRequestClose() { - this.setState({ - open: false, - anchorEl: null, - }); - }, - - _selectPreviousItem(e) { - const index = Math.max(this.state.selectedIndex - 1, 0); - this.setState({selectedIndex: index}); - if (this.props.onChange) { - this.props.onChange(e, index, this.props.menuItems[index]); - } - }, - - _selectNextItem(e) { - const index = Math.min(this.state.selectedIndex + 1, this.props.menuItems.length - 1); - this.setState({selectedIndex: index}); - if (this.props.onChange) { - this.props.onChange(e, index, this.props.menuItems[index]); - } - }, - - _isControlled() { - return this.props.hasOwnProperty('value') || - this.props.hasOwnProperty('valueLink'); - }, - -}); +import DropDownMenu from './DropDownMenu'; export default DropDownMenu; diff --git a/src/select-field.jsx b/src/select-field.jsx index 10e11d805657e6..72e11f5a0d924d 100644 --- a/src/select-field.jsx +++ b/src/select-field.jsx @@ -28,6 +28,7 @@ const SelectField = React.createClass({ propTypes: { autoWidth: React.PropTypes.bool, + children: React.PropTypes.node, disabled: React.PropTypes.bool, errorStyle: React.PropTypes.object, errorText: React.PropTypes.node, @@ -36,26 +37,19 @@ const SelectField = React.createClass({ fullWidth: React.PropTypes.bool, hintText: React.PropTypes.node, iconStyle: React.PropTypes.object, - id: React.PropTypes.string, - labelMember: React.PropTypes.string, - labelStyle: React.PropTypes.object, - menuItemStyle: React.PropTypes.object, - menuItems: React.PropTypes.array.isRequired, - multiLine: React.PropTypes.bool, + labelMember: React.PropTypes.string, //DEPRECATE + labelStyle: React.PropTypes.object, //DEPRECATE + menuItems: React.PropTypes.array, //DEPRECATE onBlur: React.PropTypes.func, onChange: React.PropTypes.func, - onEnterKeyDown: React.PropTypes.func, onFocus: React.PropTypes.func, - onKeyDown: React.PropTypes.func, - rows: React.PropTypes.number, selectFieldRoot: React.PropTypes.object, - selectedIndex: React.PropTypes.number, + selectedIndex: React.PropTypes.number, //DEPRECATE /** * Override the inline-styles of the root element. */ style: React.PropTypes.object, - type: React.PropTypes.string, underlineDisabledStyle: React.PropTypes.object, underlineFocusStyle: React.PropTypes.object, underlineStyle: React.PropTypes.object, @@ -81,8 +75,8 @@ const SelectField = React.createClass({ getDefaultProps() { return { + autoWidth: false, fullWidth: false, - labelMember: 'text', }; }, @@ -94,31 +88,28 @@ const SelectField = React.createClass({ }, getStyles() { - const styles = { + const {floatingLabelText} = this.props; + + return { label: { paddingLeft: 0, - top: -4, + top: floatingLabelText ? 6 : -4, }, icon: { right: 0, - top: 14, + top: floatingLabelText ? 22 : 14, }, hideDropDownUnderline: { borderTop: 'none', }, }; - - if (this.props.floatingLabelText) { - styles.icon.top = 22; - styles.label.top = 6; - } - - return styles; }, render() { const styles = this.getStyles(); const { + autoWidth, + children, style, labelStyle, iconStyle, @@ -127,7 +118,6 @@ const SelectField = React.createClass({ underlineStyle, errorStyle, selectFieldRoot, - menuItems, disabled, floatingLabelText, floatingLabelStyle, @@ -136,7 +126,8 @@ const SelectField = React.createClass({ errorText, onFocus, onBlur, - labelMember, + onChange, + value, ...other, } = this.props; @@ -156,16 +147,18 @@ const SelectField = React.createClass({ underlineFocusStyle={underlineFocusStyle} > + > + {children} + ); },