UNPKG

zent

Version:

一套前端设计语言和基于React的实现

358 lines (313 loc) 8.55 kB
import React, { Component, PureComponent } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import Popover from 'popover'; import Tabs from 'tabs'; import Icon from 'icon'; import forEach from 'lodash/forEach'; import find from 'lodash/find'; import noop from 'lodash/noop'; const PopoverContent = Popover.Content; const withPopover = Popover.withPopover; const TabPanel = Tabs.TabPanel; class PopoverClickTrigger extends Popover.Trigger.Click { getTriggerProps(child) { return { onClick: evt => { if (this.props.contentVisible) { this.props.close(); } else { this.props.open(); } this.triggerEvent(child, 'onClick', evt); } }; } } class Cascader extends (PureComponent || Component) { constructor(props) { super(props); this.state = { value: props.value, options: props.options, onChangeValue: [], activeId: 1, open: false }; } componentWillMount() { this.resetCascaderValue(null, null, false); } componentWillReceiveProps(nextProps) { let { loadMore } = this.props; if (nextProps.hasOwnProperty('value')) { let nextValue = nextProps.value || []; if (!loadMore) { this.setState({ value: nextValue }); } this.resetCascaderValue(nextValue, nextProps.options, false); } if (this.props.options !== nextProps.options) { this.setState({ options: nextProps.options }); } } resetCascaderValue(value, options, isTriggerChange = true) { let onChangeValue = []; let activeId = 1; let { onChange } = this.props; let state = this.state; value = value || state.value; options = options || state.options; if (options && options.length > 0 && value && value.length > 0) { activeId = 0; forEach(value, id => { let nextOption = find(options, { id }); activeId++; options = nextOption.children; onChangeValue.push({ id: nextOption.id, title: nextOption.title }); }); } if (isTriggerChange) { onChange(onChangeValue); } this.setState({ onChangeValue, activeId }); } onShow = () => { this.setState({ open: true }); }; onClose = () => { this.setState({ open: false }); }; onTabChange = id => { this.setState({ activeId: id }); }; clickHandler = (item, stage, popover) => { let { loadMore } = this.props; let { options } = this.state; if ( !item.isLeaf && loadMore && (!item.children || item.children.length === 0) ) { this.setState({ isLoading: true, loadingStage: stage }); loadMore(item, stage).then(children => { item.children = children; this.expandHandler(item, stage, popover); this.setState({ options, isLoading: false }); }); } else { this.expandHandler(item, stage, popover); } }; expandHandler = (item, stage, popover) => { let { value } = this.state; let { changeOnSelect } = this.props; let hasClose = false; value = value.slice(0, stage - 1); value.push(item.id); let obj = { value }; if (item.children) { obj.activeId = ++stage; } else { hasClose = true; popover.close(); } if (hasClose || changeOnSelect) { this.resetCascaderValue(value); } this.setState(obj); }; recursiveNextOptions(options, id) { if (options && options.length > 0) { let currOptions = find(options, { id }); if (currOptions && currOptions.children) { return currOptions.children; } } } renderPanels(popover) { let PanelEls = []; let tabIndex = 1; let { title } = this.props; let { options, value } = this.state; let tabTitle = '标题'; title = title || []; if (title.length > 0) { tabTitle = title[0]; } PanelEls.push( <TabPanel tab={this.renderTabTitle(tabTitle, tabIndex)} id={tabIndex} key={tabIndex} > {this.renderCascaderItems(options, tabIndex, popover)} </TabPanel> ); if (value && value.length > 0) { for (let i = 0; i < value.length; i++) { tabIndex++; options = this.recursiveNextOptions(options, value[i]); if (title.length >= tabIndex) { tabTitle = title[tabIndex - 1]; } else { tabTitle = '标题'; } if (options) { PanelEls.push( <TabPanel tab={this.renderTabTitle(tabTitle, tabIndex)} id={tabIndex} key={tabIndex} > {this.renderCascaderItems(options, tabIndex, popover)} </TabPanel> ); } } } return PanelEls; } renderCascaderItems(items, stage, popover) { let { prefix } = this.props; let { value } = this.state; let cascaderItems = items.map(item => { let cascaderItemCls = classnames({ [`${prefix}-cascader__list-link`]: true, active: item.id === value[stage - 1] }); return ( <div className={`${prefix}-cascader__list-item`} key={item.id}> <span className={cascaderItemCls} title={item.title} onClick={() => this.clickHandler(item, stage, popover)} > {item.title} </span> </div> ); }); return <div className={`${prefix}-cascader__list`}>{cascaderItems}</div>; } renderTabTitle(title, stage) { let { prefix } = this.props; let { isLoading, loadingStage } = this.state; if (isLoading && stage === loadingStage) { return ( <div className={`${prefix}-cascader__loading`}> <div className={`${prefix}-cascader__loading-label`}>{title}</div> <div className={`${prefix}-cascader__loading-icon`} /> </div> ); } return title; } render() { let { prefix, className, popClassName, placeholder } = this.props; let { onChangeValue, open, activeId } = this.state; const CascaderPopoverContent = withPopover(({ popover }) => { return ( <div className={`${prefix}-cascader__popup-inner`}> <Tabs activeId={activeId} onTabChange={this.onTabChange} className={`${prefix}-cascader__tabs`} > {this.renderPanels(popover)} </Tabs> </div> ); }); let cascaderValue = placeholder; let hasValue = false; if (onChangeValue && onChangeValue.length > 0) { hasValue = true; cascaderValue = onChangeValue.map(valueItem => { return valueItem.title; }); cascaderValue = cascaderValue.join(' / '); } let cascaderCls = classnames({ [`${prefix}-cascader`]: true, [className]: true, open }); let selectTextCls = classnames({ [`${prefix}-cascader__select-text`]: true, 'is-placeholder': !hasValue }); return ( <div className={cascaderCls}> <Popover className={popClassName} position={Popover.Position.BottomLeft} onShow={this.onShow} onClose={this.onClose} > <PopoverClickTrigger> <div className={`${prefix}-cascader__select`}> <div className={selectTextCls}> <span className={`${prefix}-cascader__select-text-content`}> {cascaderValue} </span> <Icon type="caret-down" /> </div> </div> </PopoverClickTrigger> <PopoverContent> <CascaderPopoverContent ref={ref => (this.cascader = ref)} /> </PopoverContent> </Popover> </div> ); } } Cascader.propTypes = { prefix: PropTypes.string, className: PropTypes.string, popClassName: PropTypes.string, onChange: PropTypes.func, loadMore: PropTypes.func, value: PropTypes.array, options: PropTypes.array, placeholder: PropTypes.string, changeOnSelect: PropTypes.bool, title: PropTypes.array }; Cascader.defaultProps = { prefix: 'zent', className: '', popClassName: 'zent-cascader__popup', onChange: noop, value: [], options: [], placeholder: '请选择', changeOnSelect: false, title: ['省份', '城市', '县区'] }; export default Cascader;