@6thquake/react-material
Version:
React components that implement Google's Material Design.
316 lines (283 loc) • 6.82 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import Popover from '../Popover';
import withStyles from '../styles/withStyles';
import TextField from '../TextField';
import CascadeOption from './CascadeOption';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import InputAdornment from '../InputAdornment';
export const styles = theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'column'
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
minWidth: 200
},
arrowDown: {
color: 'rgba(0, 0, 0, 0.54)'
},
menuBox: {
// width: 200,
display: 'flex',
flexDirection: 'row'
},
noData: {
padding: theme.spacing(1),
minWidth: 200
}
});
class CascadeSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
opens: [true, false, false, false, false],
data: [this.props.options],
textFieldValue: '',
checkes: [-1, -1, -1, -1, -1]
};
this.handleChange = name => event => {
this.setState({
[name]: event.target.value
});
};
this.handleInputClick = e => {
this.setState({
open: true,
anchorEl: e.currentTarget
});
};
this.handlePopOverClose = () => {
this.setState({
open: false
});
};
this.handelMenuChange = e => {
const {
level,
next
} = e;
this.updateSeries(e);
this.updateMenuData(next, level);
this.updateNextMenu(e);
this.setTextFieldValue();
this.checkChange(e); // 将信息传给父组件
this.props.onChange && this.props.onChange(this.series);
};
this.updateSeries = e => {
// 清空当前级之后的数据 并更新当前级数据
const {
item,
level,
index
} = e;
this.series.splice(level);
this.series[level] = item[index];
};
this.updateMenuData = (next, level) => {
const opens = this.state.opens;
for (let i = level; i < opens.length - 1; i++) {
opens[i + 1] = false;
}
this.setState({
opens
});
};
this.updateNextMenu = e => {
const {
level,
next
} = e;
const data = next;
if (this.state.opens[level + 1] === false && data.length > 0) {
const opens = this.state.opens;
opens[level + 1] = true;
const items = this.state.data;
items[level + 1] = data;
this.setState({
opens,
data: items
});
}
};
this.setTextFieldValue = () => {
const {
mapper
} = this.props;
const selections = this.series.map((item, index) => {
let {
label,
value
} = item;
if (typeof mapper.label === 'function') {
label = mapper.label(item, index);
}
return {
value,
label
};
});
const node = this.renderSelections(selections);
this.setState({
textFieldValue: node
});
};
this.renderSelections = list => {
const {
separator,
renderValue
} = this.props;
if (renderValue) {
return renderValue(list);
}
return list.map(item => {
return item.label;
}).join(` ${separator} `);
};
this.series = [];
}
checkChange(e) {
const {
level,
index
} = e;
const checkes = this.state.checkes;
for (let i = level; i < checkes.length - 1; i++) {
checkes[i + 1] = -1;
}
checkes[level] = index;
this.setState({
checkes
});
}
renderMenuItems() {
const levels = [0, 1, 2, 3, 4];
const {
children,
mapper
} = this.props;
const list = levels.map((item, index) => {
return React.createElement(CascadeOption, {
children: children,
key: item,
level: item,
open: this.state.opens[item],
options: this.state.data[item],
onChange: this.handelMenuChange,
checkedIndex: this.state.checkes[item],
mapper: mapper
});
});
return list;
}
render() {
const {
classes,
label,
options,
notFound,
width
} = this.props;
const {
open,
anchorEl
} = this.state;
const hasData = options && options.length > 0;
const t = React.createElement("div", {
className: classes.noData
}, notFound);
return React.createElement("div", {
className: classes.root
}, React.createElement("div", {
className: classes.inputBox
}, React.createElement(TextField, {
InputProps: {
endAdornment: React.createElement(InputAdornment, {
position: "end"
}, React.createElement(ArrowDropDown, {
className: classes.arrowDown
}))
},
style: {
width
},
onClick: this.handleInputClick,
id: "select",
label: label,
className: classes.textField,
value: this.state.textFieldValue,
onChange: this.handleChange(),
margin: "normal"
})), React.createElement(Popover, {
anchorEl: anchorEl,
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left'
},
transformOrigin: {
vertical: 'top',
horizontal: 'left'
},
open: open,
onClose: this.handlePopOverClose
}, React.createElement("div", {
className: classes.menuBox
}, hasData ? this.renderMenuItems() : t)));
}
}
process.env.NODE_ENV !== "production" ? CascadeSelect.propTypes = {
/**
* Override or extend the styles applied to the component.
* See [CSS API](#css-api) below for more details.
*/
classes: PropTypes.object.isRequired,
/**
* The label content.
*/
label: PropTypes.node,
/**
* render maps,
*/
mapper: PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
}),
/**
* Callback when finishing cascader select
*/
onChange: PropTypes.func,
/**
* data options of cascade
*/
options: PropTypes.array.isRequired,
/**
* Render the selected item.
*
* @param {array} list The selected items `array`.
* @returns {String}
*/
renderValue: PropTypes.func,
/**
* The separator between different levels
*/
separator: PropTypes.string,
/**
* The width of cascader
*/
width: PropTypes.number
} : void 0;
CascadeSelect.defaultProps = {
mapper: {
label: 'label',
value: 'value'
},
width: 150,
separator: '/'
};
export default withStyles(styles, {
name: 'RMCascadeSelect'
})(CascadeSelect);