material-ui
Version:
Material Design UI components built with React
349 lines (310 loc) • 10.3 kB
JSX
const React = require('react');
const ReactDOM = require('react-dom');
const StylePropable = require('./mixins/style-propable');
const Transitions = require('./styles/transitions');
const KeyCode = require('./utils/key-code');
const DropDownArrow = require('./svg-icons/navigation/arrow-drop-down');
const Paper = require('./paper');
const Menu = require('./menu/menu');
const ClearFix = require('./clearfix');
const DefaultRawTheme = require('./styles/raw-themes/light-raw-theme');
const ThemeManager = require('./styles/theme-manager');
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: {
className: React.PropTypes.string,
displayMember: React.PropTypes.string,
valueMember: React.PropTypes.string,
autoWidth: React.PropTypes.bool,
disabled: 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() {
return {
autoWidth: true,
disabled: false,
valueMember: 'payload',
displayMember: 'text',
};
},
getInitialState() {
return {
open: false,
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: 'static',
height: '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: {
transition: Transitions.easeOut(),
lineHeight: spacing.desktopToolbarHeight + 'px',
position: 'absolute',
paddingLeft: spacing.desktopGutter,
top: 0,
opacity: 1,
color: disabled ? this.state.muiTheme.rawTheme.palette.disabledColor : this.state.muiTheme.rawTheme.palette.textColor,
},
underline: {
borderTop: 'solid 1px ' + accentColor,
margin: '-1px ' + spacing.desktopGutter + 'px',
},
menu: {
zIndex: zIndex + 1,
},
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 / 2,
},
overlay: {
height: '100%',
width: '100%',
position: 'fixed',
top: 0,
left: 0,
zIndex: zIndex,
},
};
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() {
let _this = this;
let styles = this.getStyles();
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 (this.props.valueMember && this._isControlled()) {
let value = this.props.hasOwnProperty('value') ? this.props.value : this.props.valueLink.value;
if (value !== null && value !== undefined) {
for (let i = 0; i < this.props.menuItems.length; i++) {
if (this.props.menuItems[i][this.props.valueMember] === value) {
selectedIndex = i;
}
}
}
}
}
let selectedItem = this.props.menuItems[selectedIndex];
if (selectedItem) {
displayValue = selectedItem[this.props.displayMember];
}
let menuItems = this.props.menuItems.map((item) => {
item.text = item[_this.props.displayMember];
item.payload = item[_this.props.valueMember];
return item;
});
return (
<div
ref="root"
onKeyDown={this._onKeyDown}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
className={this.props.className}
style={this.prepareStyles(
styles.root,
this.state.open && styles.rootWhenOpen,
this.props.style)} >
<ClearFix style={this.mergeStyles(styles.control)} onTouchTap={this._onControlClick}>
<Paper style={this.mergeStyles(styles.controlBg)} zDepth={0} />
<div style={this.prepareStyles(styles.label, this.state.open && styles.labelWhenOpen, this.props.labelStyle)}>
{displayValue}
</div>
<DropDownArrow style={this.mergeStyles(styles.icon, this.props.iconStyle)}/>
<div style={this.prepareStyles(styles.underline, this.props.underlineStyle)}/>
</ClearFix>
<Menu
ref="menuItems"
autoWidth={this.props.autoWidth}
selectedIndex={selectedIndex}
menuItems={menuItems}
style={styles.menu}
menuItemStyle={this.mergeStyles(styles.menuItem, this.props.menuItemStyle)}
hideable={true}
visible={this.state.open}
onRequestClose={this._onMenuRequestClose}
onItemTap={this._onMenuItemClick} />
{this.state.open && <div style={this.prepareStyles(styles.overlay)} onTouchTap={this._handleOverlayTouchTap} />}
</div>
);
},
_setWidth() {
let el = ReactDOM.findDOMNode(this);
let menuItemsDom = ReactDOM.findDOMNode(this.refs.menuItems);
if (!this.props.style || !this.props.style.hasOwnProperty('width')) {
el.style.width = 'auto';
el.style.width = menuItemsDom.offsetWidth + 'px';
}
},
_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});
},
_onControlClick() {
if (!this.props.disabled) {
this.setState({ open: !this.state.open });
}
},
_onKeyDown(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(e, key, payload) {
if (this.props.onChange && this.state.selectedIndex !== key) {
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.setState({
selectedIndex: key,
value: e.target.value,
open: false,
});
},
_onMenuRequestClose() {
this.setState({open:false});
},
_selectPreviousItem() {
this.setState({selectedIndex: Math.max(this.state.selectedIndex - 1, 0)});
},
_selectNextItem() {
this.setState({selectedIndex: Math.min(this.state.selectedIndex + 1, this.props.menuItems.length - 1)});
},
_handleOverlayTouchTap() {
this.setState({
open: false,
});
},
_isControlled() {
return this.props.hasOwnProperty('value') ||
this.props.hasOwnProperty('valueLink');
},
});
module.exports = DropDownMenu;