react-ui-component
Version:
some component build with ReactJs
326 lines (285 loc) • 9.34 kB
JavaScript
const React = require('react')
const ReactDOM = require('react-dom')
const PropTypes = require('prop-types')
const Component = React.Component
const documentClickCmp = require('./high-order/documentClickCmp')
const domUtil = require('./util/dom')
const klassName = require('./util/className')
const { hasClass, removeClass, addClass } = domUtil
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
current: props.current
}
}
getChildContext(){
const {current} = this.state
const {mode} = this.props
return {
current,
mode,
paddingLeft: this.props.paddingLeft,
onMenuSelect: this.handleMenuSelect.bind(this),
mutexSubmenu: this.clearSubmenuOpenStatus.bind(this),
}
}
// submenu open status
clearSubmenuOpenStatus(ref){
const baseNode = ref ? ReactDOM.findDOMNode(ref).parentNode : ReactDOM.findDOMNode(this)
let submenus = baseNode.querySelectorAll('._submenu')
removeClass(submenus, '_open')
}
// submenu active status
triggerSubmenuActiveStatus(selectNode){
let submenus = ReactDOM.findDOMNode(this).querySelectorAll('._submenu')
selectNode = ReactDOM.findDOMNode(selectNode)
submenus.forEach(dom => {
removeClass(dom, '_active')
if (dom.contains(selectNode)) {
addClass(dom, '_active')
}
})
}
handleMenuSelect(current, selectNode){
this.setState({
current
});
const {onChange, mode} = this.props
if (['horizontal', 'popup'].indexOf(mode) !== -1) {
this.triggerSubmenuActiveStatus(selectNode)
let that = this
setTimeout(() => {
that.clearSubmenuOpenStatus()
}, 200)
}
if (onChange) {
onChange(current)
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.current !== this.props.current) {
this.setState({
current: nextProps.current
});
}
}
render() {
let newProps = Object.assign({}, this.props)
let {className, mode} = this.props
className = klassName('menu', mode ? `_${mode}` : '_default', className)
delete newProps.paddingLeft
delete newProps.current
return (
<ul {...newProps} className={className}></ul>
);
}
}
Menu.childContextTypes = {
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
mode: PropTypes.oneOf(['', 'accordion', 'horizontal', 'popup']),
onMenuSelect: PropTypes.func,
mutexSubmenu: PropTypes.func,
paddingLeft: PropTypes.number,
}
Menu.propTypes = {
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChange: PropTypes.func,
paddingLeft: PropTypes.number,
mode: PropTypes.oneOf(['', 'accordion', 'horizontal', 'popup']),
}
Menu.defaultProps = {
paddingLeft: 16,
mode: '',
}
const generateMenuItemByChildren = (children, opt) => {
opt = opt || {}
let type = opt.type || 'group'
let level = opt.level || 1
if (!children) {
return null
}
if (children instanceof Array) {
return (
children.map((item, index) => {
if (item.type === MenuItem) {
return (
<MenuItem key={index}
{...item.props} level={level}
className={`_${type}-item`}>
{item.props.children}
</MenuItem>
)
}
if (item.type === SubMenu) {
return (
<SubMenu
key={`submenu-${index}`}
{...item.props} level={level}>
</SubMenu>
)
}
if (level !== 1 && item.type === MenuGroup) {
return (
<MenuGroup
key={`group-${index}`}
{...item.props} level={level}>
</MenuGroup>
)
}
return null
})
)
}
if (children.type === MenuItem) {
return (
<MenuItem {...children.props} level={level}
className={`_${type}-item`}>
</MenuItem>
)
}
if (children.type === SubMenu) {
return (
<SubMenu {...children.props} level={level}>
</SubMenu>
)
}
if (level !== 1 && children.type === MenuGroup) {
return (
<MenuGroup {...children.props} level={level}>
</MenuGroup>
)
}
return children
}
class SubMenu extends Component {
constructor(props) {
super(props);
this.toggleSubmenu = this.toggleSubmenu.bind(this)
}
toggleSubmenu(status){
let node = ReactDOM.findDOMNode(this)
const {mode} = this.context
let _open = status !== undefined ? !status : hasClass(node, '_open')
if (['accordion', 'horizontal'].indexOf(mode) !== -1) {
this.context.mutexSubmenu(this)
}
_open ? removeClass(node, '_open') : addClass(node, '_open')
}
render() {
let newProps = Object.assign({}, this.props)
let {title, level, active, className} = newProps
let {paddingLeft, mode} = this.context
className = klassName('_submenu', className, active ? '_open': '')
if (['popup', 'horizontal'].indexOf(mode) !== -1) {
newProps.onMouseEnter = e => {
e.preventDefault()
this.toggleSubmenu(true)
}
newProps.onMouseLeave = e => {
e.preventDefault()
this.toggleSubmenu(false)
}
}
delete newProps.title
delete newProps.level
delete newProps.active
return (
<li {...newProps} className={className}>
<div className="_title" style={{'paddingLeft': `${paddingLeft * level}px`}}
onClick={() => this.toggleSubmenu()}>
{title}
</div>
<ul className="_content">
{generateMenuItemByChildren(newProps.children, { type: 'submenu', level: level + 1 })}
</ul>
</li>
)
}
}
SubMenu.propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]).isRequired,
active: PropTypes.bool,
}
SubMenu.contextTypes = {
paddingLeft: PropTypes.number,
mutexSubmenu: PropTypes.func,
mode: PropTypes.oneOf(['', 'accordion', 'popup', 'horizontal']),
}
SubMenu.defaultProps = {
level: 1,
active: false,
}
const MenuGroup = (props, context) => {
let newProps = Object.assign({}, props)
let {title, level} = newProps
delete newProps.title
delete newProps.level
return (
<li className="_group" {...newProps}>
<div className="_title" style={{'paddingLeft': `${context.paddingLeft * level}px`}}>
{title}
</div>
<ul className="_content">
{generateMenuItemByChildren(newProps.children, { level: level + 1 })}
</ul>
</li>
)
}
MenuGroup.propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]).isRequired
}
MenuGroup.contextTypes = {
paddingLeft: PropTypes.number,
}
MenuGroup.defaultProps = {
level: 1,
}
class MenuItem extends Component {
render() {
const newProps = Object.assign({}, this.props)
let {index, className, disabled, level} = newProps
delete newProps.index
delete newProps.level
if (!index) {
throw Error('index is needed on MenuItem')
}
const { current, onMenuSelect, paddingLeft } = this.context
let active = index === current
className = klassName(className, active ? '_active _item' : '_item', disabled ? '_disabled' : '')
return (
<li {...newProps} className={className}
style={{'paddingLeft': `${paddingLeft * level}px`}}
onClick={() => {
if (disabled) {
return
}
onMenuSelect(index, this)
if (newProps.onClick) {
newProps.onClick(index)
}
}}>
</li>
)
}
}
MenuItem.propTypes = {
onClick: PropTypes.func,
level: PropTypes.number,
}
MenuItem.contextTypes = {
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
paddingLeft: PropTypes.number,
onMenuSelect: PropTypes.func,
};
MenuItem.defaultProps = {
level: 1,
};
module.exports = {
Menu: documentClickCmp(Menu),
MenuGroup,
MenuItem,
SubMenu
}