From 2552cf04af4b71cce34b9ed166fc52b3a359e81b Mon Sep 17 00:00:00 2001 From: zhangjunjie Date: Wed, 13 Mar 2019 17:08:35 +0800 Subject: [PATCH 001/112] modify:datepicker new design --- components/date-picker/BasePicker.js | 11 +- components/date-picker/Time.js | 121 ++++++++++--------- components/date-picker/TimePanel.js | 11 +- components/date-picker/TimePicker.js | 21 ++-- components/date-picker/TimeRangePanel.js | 39 ++++++ components/date-picker/style/index.scss | 95 ++++++++++++--- components/date-picker/style/timepicker.scss | 2 + docs/zh-CN/time-picker.md | 11 ++ package.json | 1 + 9 files changed, 223 insertions(+), 89 deletions(-) create mode 100644 components/date-picker/TimeRangePanel.js diff --git a/components/date-picker/BasePicker.js b/components/date-picker/BasePicker.js index a9397ff2a..8cf244013 100644 --- a/components/date-picker/BasePicker.js +++ b/components/date-picker/BasePicker.js @@ -139,7 +139,7 @@ class BasePicker extends Component { this._parseProps(nextProps) } onPick (date, showPanel) { - const {type, showTime, onChange, weekOffset, localeDatas} = this.props + const {type, showTime, localeDatas} = this.props const {format} = this.state this.setState({ date, @@ -147,7 +147,15 @@ class BasePicker extends Component { rText: date.endDate && formatterDate(type, date.endDate, format, showTime, localeDatas), showPanel, isFocus: false + }, () => { + if (!showPanel) { + this.callback() + } }) + } + callback () { + const {type, onChange, weekOffset} = this.props + const {date} = this.state if (onChange) { const {startDate, endDate} = date const _weekOffset = {weekStartsOn: weekOffset} @@ -214,6 +222,7 @@ class BasePicker extends Component { if (tar !== this.input && tar !== this.rInput) { this.timeCancel() } + this.callback() } _input (text, ref = 'input') { return ( diff --git a/components/date-picker/Time.js b/components/date-picker/Time.js index 7b52af04a..c08f309e1 100644 --- a/components/date-picker/Time.js +++ b/components/date-picker/Time.js @@ -16,8 +16,8 @@ export default class TimePanel extends Component { seconds: 0 } } - this.liPrefix = props.onlyTime ? [1, 2] : [1, 2, 3] - this.liSuffix = props.onlyTime ? [1, 2] : [1, 2, 3] + this.liPrefix = props.onlyTime ? [1, 2, 3] : [1, 2, 3, 4] + this.liSuffix = props.onlyTime ? [1, 2, 3] : [1, 2, 3, 4] } range (num) { let arr = [] @@ -36,7 +36,7 @@ export default class TimePanel extends Component { } scrollEvent (type, e) { const st = e.target.scrollTop - const val = Math.round(st / 38) + const val = Math.round(st / 32) const {date} = this.state if (type === 'hours') { date.setHours(val) @@ -58,7 +58,7 @@ export default class TimePanel extends Component { completeScrollTop () { const {date} = this.state let {hours, minutes, seconds} = deconstructDate(date) - const dVal = 38 + const dVal = 32 setTimeout(() => { this.hoursList.scrollTop = (hours) * dVal this.minutesList.scrollTop = (minutes) * dVal @@ -102,9 +102,9 @@ export default class TimePanel extends Component { } } mouseOverEvent (e) { - this.activeEl && (this.activeEl.style.background = 'white') + // this.activeEl && (this.activeEl.style.backgroundColor = 'transparent') this.activeEl = e.target - e.target.style.background = '#F2F2F2' + // e.target.style.background = 'rgba(66,132,245,0.08)' } render () { const {date} = this.state @@ -116,55 +116,66 @@ export default class TimePanel extends Component { return
  • }) return ( -
    -
      { this.hoursList = el }} - className='hi-timepicker__list' - onClick={this.clickEvent.bind(this, 'hours')} - onMouseEnter={this.mouseOverEvent.bind(this)} - > - {this.liPrefix} - { - this.range(24).map((m, n) => { - return ( -
    • {m}
    • - ) - }) - } - {this.liSuffix} -
    -
      { this.minutesList = el }} - onClick={this.clickEvent.bind(this, 'minutes')} - onMouseEnter={this.mouseOverEvent.bind(this)} - > - {this.liPrefix} - { - this.range(60).map((m, n) => { - return
    • {m}
    • - }) - } - {this.liSuffix} -
    -
      { this.secondsList = el }} - className='hi-timepicker__list' - onClick={this.clickEvent.bind(this, 'seconds')} - onMouseEnter={this.mouseOverEvent.bind(this)} - > - {this.liPrefix} - { - this.range(60).map((m, n) => { - return
    • {m}
    • - }) - } - {this.liSuffix} -
    -
    +
    +
    + + + +
    +
    +
      { this.hoursList = el }} + className='hi-timepicker__list' + onClick={this.clickEvent.bind(this, 'hours')} + onMouseEnter={this.mouseOverEvent.bind(this)} + > + {this.liPrefix} + { + this.range(24).map((m, n) => { + return ( +
    • {m}
    • + ) + }) + } + {this.liSuffix} +
    +
      { this.minutesList = el }} + onClick={this.clickEvent.bind(this, 'minutes')} + onMouseEnter={this.mouseOverEvent.bind(this)} + > + {this.liPrefix} + { + this.range(60).map((m, n) => { + return
    • {m}
    • + }) + } + {this.liSuffix} +
    +
      { this.secondsList = el }} + className='hi-timepicker__list' + onClick={this.clickEvent.bind(this, 'seconds')} + onMouseEnter={this.mouseOverEvent.bind(this)} + > + {this.liPrefix} + { + this.range(60).map((m, n) => { + return
    • {m}
    • + }) + } + {this.liSuffix} +
    +
    + {/* {hours} + {minutes} + {seconds} */} +
    +
    ) } diff --git a/components/date-picker/TimePanel.js b/components/date-picker/TimePanel.js index cc9c6eb72..f3c097691 100644 --- a/components/date-picker/TimePanel.js +++ b/components/date-picker/TimePanel.js @@ -1,6 +1,5 @@ import React, {Component} from 'react' import Time from './Time' -import Button from '../button' import Provider from '../context' class TimePanel extends Component { @@ -35,11 +34,11 @@ class TimePanel extends Component {
    diff --git a/components/date-picker/TimePicker.js b/components/date-picker/TimePicker.js index 73b0558b4..e4ab2c137 100644 --- a/components/date-picker/TimePicker.js +++ b/components/date-picker/TimePicker.js @@ -5,7 +5,7 @@ import DatePickerType from './Type' import BasePicker from './BasePicker' import TimePanel from './TimePanel' import Provider from '../context' - +import TimeRangePanel from './TimeRangePanel' class TimePicker extends BasePicker { static propTypes = { type: PropTypes.oneOf(Object.values(DatePickerType)), @@ -20,15 +20,18 @@ class TimePicker extends BasePicker { disabled: false } initPanel (state, props) { + console.log(props.type) return ( - + props.type === 'time' + ? + : ) } } diff --git a/components/date-picker/TimeRangePanel.js b/components/date-picker/TimeRangePanel.js new file mode 100644 index 000000000..9486c9373 --- /dev/null +++ b/components/date-picker/TimeRangePanel.js @@ -0,0 +1,39 @@ +import React, {Component} from 'react' +import Time from './Time' +import Provider from '../context' + +class TimeRangePanel extends Component { + constructor (props) { + super(props) + this.state = { + date: props.date, + style: props.style + } + } + onTimePick (date, bol) { + const {showTime} = this.props + if (showTime) { + this.setState({ + date + }) + this.props.onPick(date, true) + } else { + this.setState({ + date + }) + this.props.onPick(date, bol) + } + } + render () { + console.log(11, this.props.style) + return ( +
    +
  • { + if (!disabled) { + onClick(id) + } + }}> +
    + {children} +
    +
  • + ) + } +} + +export default Item diff --git a/components/menu/ItemGroup.js b/components/menu/ItemGroup.js new file mode 100644 index 000000000..e69de29bb diff --git a/components/menu/MixinMenu.js b/components/menu/MixinMenu.js new file mode 100644 index 000000000..f449fa94e --- /dev/null +++ b/components/menu/MixinMenu.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +class MixinMenu extends Component { + root () { + return this.context.component + } + + onClick (id) { + const root = this.root() + + root.onClick(id) + } + + renderChildren (children) { + const { + activeId + } = this.root().state + let childIsActive = false + const enhancedChildren = React.Children.map(children, child => { + if (child.props.id === activeId) { + childIsActive = true + } + if (child.type.componentName === 'MenuItem') { + return React.cloneElement(child, { + onClick: this.onClick.bind(this), + activeId + }) + } + return child + }) + + return { + children: enhancedChildren, + childIsActive + } + } +} + +MixinMenu.contextTypes = { + component: PropTypes.any +} + +export default MixinMenu diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js new file mode 100644 index 000000000..7a1906b2b --- /dev/null +++ b/components/menu/SubMenu.js @@ -0,0 +1,75 @@ +import React from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import MixinMenu from './MixinMenu' +import Popper from '../popper' +import Icon from '../icon' + +export default class SubMenu extends MixinMenu { + static propTypes = { + title: PropTypes.string + } + + constructor (props) { + super(props) + + this.state = { + showSubmenu: false + } + } + + onClick (id) { + this.setState({ + showSubmenu: false + }) + super.onClick(id) + } + + render () { + const { + showSubmenu + } = this.state + const { + title + } = this.props + const { + children, + childIsActive + } = this.renderChildren(this.props.children) + const cls = classNames('hi-menu-item', 'hi-submenu', { + 'hi-menu-item--active': childIsActive + }) + const icon = showSubmenu ? 'up' : 'down' + + return ( +
  • { this.submenu = node }} + onClick={() => { + this.setState({ + showSubmenu: true + }) + }} + > +
    + {title} +
    +
    + +
    + +
      + {children} +
    +
    +
  • + ) + } +} diff --git a/components/menu/VerticalMenu.js b/components/menu/VerticalMenu.js new file mode 100644 index 000000000..e69de29bb diff --git a/components/menu/index.js b/components/menu/index.js index 15fb7746a..9d84a9f10 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -1,23 +1,18 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import classnames from 'classnames' +import classNames from 'classnames' +import HorizontalMenu from './HorizontalMenu' +import Item from './Item' +import SubMenu from './SubMenu' +import ItemGroup from './ItemGroup' import './style/index' class Menu extends Component { static defaultProps = { - mode: 'vertical' + mode: 'vertical', + onClick: () => {}, + activeId: '' } static propTypes = { - // list: PropTypes.oneOfType([ - // PropTypes.string, - // PropTypes.shape({ - // text: PropTypes.string.isRequired, - // value: PropTypes.oneOfType([ - // PropTypes.string, - // PropTypes.number - // ]), - // children: PropTypes.array - // }) - // ]), list: PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ @@ -29,23 +24,61 @@ class Menu extends Component { children: PropTypes.array }) ])), + activeId: PropTypes.string, mode: PropTypes.oneOf(['horizontal', 'vertical']), - onSelect: PropTypes.func, - onOpen: PropTypes.func, - onClose: PropTypes.func + onClick: PropTypes.func } + + constructor (props) { + super(props) + + this.state = { + activeId: this.props.activeId + } + } + + componentWillReceiveProps (nextProps) { + if (nextProps.activeId !== this.props.activeId) { + this.setState({ + activeId: nextProps.activeId + }) + } + } + + getChildContext () { + return { + component: this + } + } + + onClick (id) { + const oldId = this.state.activeId + + this.setState({ + activeId: id + }, () => { + this.props.onClick(id, oldId) + }) + } + render () { - const {list, mode} = this.props - const cls = classnames('hi-menu', `hi-menu-${mode}`) - return + const {children, mode} = this.props + const cls = classNames('hi-menu', `hi-menu-${mode}`) + + return ( +
    + + {children} + +
    + ) } } +Menu.childContextTypes = { + component: PropTypes.any +} + +Menu.Item = Item +Menu.SubMenu = SubMenu +Menu.ItemGroup = ItemGroup export default Menu diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index d3fded393..24929b7ae 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -1,61 +1,71 @@ -.hi-menu { +.hi-menu, +.hi-submenu__popper { ul { margin-top: 0; margin-bottom: 0; padding-left: 0; + font-size: 0px; } - - li + li { - margin-top: 0; - } - li { list-style: none; } +} - &.hi-menu-vertical { - background: #fff; - border: 1px solid #e6e7e8; - box-shadow: 0 2px 8px 0 rgba(56, 62, 71, 0.1); - border-radius: 2px; - padding: 4px 0; - - li { - font-size: 14px; - color: #333; - line-height: 36px; - position: relative; - height: 36px; - cursor: pointer; - padding-left: 15px; - - &:hover { - // border-bottom: 2px solid #4284f5; - color: #4284f5; - } +.hi-menu { + &-items { + display: flex; + } + &-item { + display: flex; + position: relative; + font-size: 14px; + color: #333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + &--active { + color: #4284F5; + } + &__expand-icon { + margin-left: 8px; } } - &.hi-menu-horizontal { - border: 0; - border-bottom: 1px solid #e8e8e8; - height: 48px; - - li { - font-size: 14px; - color: #333; - display: inline-block; - line-height: 20px; - padding: 14px 4px; - margin-right: 40px; - position: relative; - cursor: pointer; - margin-top: -1px; - - &:hover { - border-bottom: 2px solid #4284f5; - color: #4284f5; + &-horizontal { + border-bottom: 1px solid #E6E7E8; + .hi-menu-item { + max-width: 100px; + padding: 12px; + margin-right: 48px; + &:last-child { + margin-right: 0px; + } + &--active:after { + content: ''; + position: absolute; + bottom: 0px; + left: 50%; + transform: translateX(-50%); + width: 32px; + height: 2px; + background-color: #4284F5; } } } } + +.hi-submenu__popper { + width: 200px; + padding: 4px 16px; + background-color: #fff; + box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15); + border: 1px solid rgba(230,231,232,1); + box-sizing: border-box; + .hi-menu-items { + flex-direction: column; + } + .hi-menu-item { + padding: 8px 0; + } +} \ No newline at end of file diff --git a/docs/zh-CN/components/menu.md b/docs/zh-CN/components/menu.md index 53a389e00..198f963c5 100644 --- a/docs/zh-CN/components/menu.md +++ b/docs/zh-CN/components/menu.md @@ -7,22 +7,18 @@ 水平排列 ```js - constructor () { - super() - this.state = { - list: [{ - title: '菜单一' - }, { - title: '菜单二', - }, { - title: '菜单三' - }] - } - } render(){ return(
    - + console.log('-----click', id, prevId)}> + 电视 + 小米MIX + + 小米 + 红米 + + 超长超长超长字符 +
    ) } @@ -30,35 +26,7 @@ ``` ::: -### 竖直排列 - -:::demo - -竖直排列 - -```js - constructor () { - super() - this.state = { - list: [{ - title: '菜单一' - }, { - title: '菜单二' - }, { - title: '菜单三' - }] - } - } - render(){ - return( -
    - -
    - ) - } -``` -::: + +### 竖向展开菜单 + +:::demo + +竖向展开菜单 + +```js + render(){ + const datas = [ + { + content: '电视', + id: 1 + }, + { + content: '小米MIX', + id: 2 + }, + { + content: '手机', + children: [ + { + content: '小米', + children: [ + { + content: '小米9', + id: 'xiaomi9' + }, + { + content: '小米8', + id: 'xiaomi8' + }, + { + content: '小米7', + id: 'xiaomi7' + }, + { + content: '小米6', + id: 'xiaomi6' + }, + { + content: '小米5', + id: 'xiaomi5' + }, + { + content: '小米4', + id: 'xiaomi4' + }, + { + content: '小米3', + id: 'xiaomi3' + } + ] + }, + { + content: '红米', + id: 'hongmi' + }, + { + content: '小米note', + children: [ + { + content: '小米 note7', + id: 'xiaomi note7' + }, + { + content: '小米 note6', + id: 'xiaomi note6' + }, + { + content: '小米 note5', + id: 'xiaomi note5' + }, + { + content: '小米 note4', + id: 'xiaomi note4' + }, + { + content: '小米 note3', + id: 'xiaomi note3' + } + ] + } + ] + }, + { + content: '超长超长超长字符', + id: 4 + } + ] + return( +
    + console.log('-----click', id, prevId)} + datas={datas} + /> +
    + ) + } + +``` +::: + + + +### 竖向收起菜单 + +:::demo + +竖向收起菜单 + +```js + render(){ + const datas = [ + { + content: '电视', + id: 1 + }, + { + content: '小米MIX', + id: 2 + }, + { + content: '手机', + children: [ + { + content: '小米', + children: [ + { + content: '小米9', + id: 'xiaomi9' + }, + { + content: '小米8', + id: 'xiaomi8' + }, + { + content: '小米7', + id: 'xiaomi7' + }, + { + content: '小米6', + id: 'xiaomi6' + }, + { + content: '小米5', + id: 'xiaomi5' + }, + { + content: '小米4', + id: 'xiaomi4' + }, + { + content: '小米3', + id: 'xiaomi3' + } + ] + }, + { + content: '红米', + id: 'hongmi' + }, + { + content: '小米note', + children: [ + { + content: '小米 note7', + id: 'xiaomi note7' + }, + { + content: '小米 note6', + id: 'xiaomi note6' + }, + { + content: '小米 note5', + id: 'xiaomi note5' + }, + { + content: '小米 note4', + id: 'xiaomi note4' + }, + { + content: '小米 note3', + id: 'xiaomi note3' + } + ] + } + ] + }, + { + content: '超长超长超长字符', + id: 4 + } + ] + return( +
    + console.log('-----click', id, prevId)} + datas={datas} + /> +
    + ) + } + +``` +::: + + ### Menu Attributes From fe6a9bdac0bf35021991dcea65eecced54be0a81 Mon Sep 17 00:00:00 2001 From: duanchuanxu Date: Fri, 22 Mar 2019 09:48:06 +0800 Subject: [PATCH 013/112] menu style --- components/menu/HorizontalMenu.js | 13 --- components/menu/Item.js | 9 +- components/menu/MixinMenu.js | 87 --------------- components/menu/SubMenu.js | 30 +++--- components/menu/Title.js | 44 ++++++++ components/menu/VerticalMenu.js | 0 components/menu/index.js | 15 +-- components/menu/style/index.scss | 173 +++++++++++++++++++----------- docs/zh-CN/components/menu.md | 24 +++-- 9 files changed, 202 insertions(+), 193 deletions(-) delete mode 100644 components/menu/HorizontalMenu.js delete mode 100644 components/menu/MixinMenu.js create mode 100644 components/menu/Title.js delete mode 100644 components/menu/VerticalMenu.js diff --git a/components/menu/HorizontalMenu.js b/components/menu/HorizontalMenu.js deleted file mode 100644 index 787127ef9..000000000 --- a/components/menu/HorizontalMenu.js +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from 'react' - -export default class HorizontalMenu extends Component { - static componentName = 'HorizontalMenu' - - render () { - return ( - - ) - } -} diff --git a/components/menu/Item.js b/components/menu/Item.js index 20ee1b53b..232e1cd08 100644 --- a/components/menu/Item.js +++ b/components/menu/Item.js @@ -1,12 +1,14 @@ import React, {Component} from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' +import Title from './Title' class Item extends Component { static componentName = 'MenuItem' static propTypes = { id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), disabled: PropTypes.bool, isActive: PropTypes.bool } @@ -23,9 +25,10 @@ class Item extends Component { onClick, isActive, id, + icon, indexs } = this.props - const cls = classNames('hi-menu-item', { + const cls = classNames('hi-menu-item', 'hi-menu-item__title', 'hi-menu__title', { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive }) @@ -36,9 +39,7 @@ class Item extends Component { onClick(indexs, id) } }}> -
    - {children} -
    + </li> ) } diff --git a/components/menu/MixinMenu.js b/components/menu/MixinMenu.js deleted file mode 100644 index ef15547e6..000000000 --- a/components/menu/MixinMenu.js +++ /dev/null @@ -1,87 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -class MixinMenu extends Component { - root () { - return this.context.component || this.props.rootComponent - } - - onClick (id) { - const root = this.root() - - root.onClick(id) - } - - isActive (children, activeId) { - let active = false - - React.Children.forEach(children, child => { - if (active) { - return - } - if (child.type.componentName === 'MenuItem') { - if (child.props.id === activeId) { - active = true - } - } else { - active = this.isActive(child.props.children, activeId) - } - }) - return active - } - - renderChildren (children, parentComponent) { - const { - activeId - } = this.root().state - let childIsActive = false - let childrenType = 'MenuItem' - const enhancedChildren = React.Children.map(children, child => { - let props = { - parentComponent - } - // console.log('----------child', child) - if (child.type.componentName === 'MenuItem') { - if (child.props.id === activeId) { - childIsActive = true - } - props = Object.assign(props, { - onClick: this.onClick.bind(this), - activeId - }) - } else if (child.type.componentName === 'SubMenu') { - const active = this.isActive(child.props.children, activeId) - if (active) { - childIsActive = active - } - // console.log('----------child', childIsActive) - - props = Object.assign(props, { - // showParentSubmenu: ((this.state && this.state.showSubmenu) || this.props.showParentSubmenu) && active, - rootComponent: this.context.component - }) - } else { - props = Object.assign(props, { - rootComponent: this.context.component - }) - } - if (child.type.componentName !== 'MenuItem') { - childrenType = child.type.componentName - } - - return React.cloneElement(child, props) - }) - - return { - children: enhancedChildren, - childIsActive, - childrenType - } - } -} - -MixinMenu.contextTypes = { - component: PropTypes.any -} - -export default MixinMenu diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index 9316382f4..1d943af30 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types' import classNames from 'classnames' import Popper from '../popper' import Icon from '../icon' +import Title from './Title' export default class SubMenu extends Component { static componentName = 'SubMenu' @@ -54,6 +55,7 @@ export default class SubMenu extends Component { renderPopperMenu (deepSubmenu) { const { + mini, datas, indexs, isExpand, @@ -63,7 +65,7 @@ export default class SubMenu extends Component { let leftGap let topGap let placement - if (deepSubmenu) { + if (deepSubmenu || mini) { leftGap = 16 topGap = -4 placement = 'right-start' @@ -81,7 +83,7 @@ export default class SubMenu extends Component { topGap={topGap} leftGap={leftGap} className={ - classNames('hi-submenu__popper', {'hi-submenu__popper--group': groupSubMenu}) + classNames('hi-submenu__popper', {'hi-submenu__popper--fat': groupSubMenu}) } width={false} placement={placement} @@ -112,6 +114,7 @@ export default class SubMenu extends Component { render () { const { content, + icon, mode, mini, indexs, @@ -119,18 +122,19 @@ export default class SubMenu extends Component { isActive, groupSubMenu } = this.props + const level = indexs.split('-').length const deepSubmenu = indexs.split('-').length > 1 - const cls = classNames('hi-menu-item', 'hi-submenu', { + const cls = classNames('hi-menu-item', 'hi-submenu', `hi-menu--${level}`, { 'hi-menu-item--active': isActive, 'hi-submenu--sub': deepSubmenu, - 'hi-submenu--group': groupSubMenu + 'hi-submenu--fat': groupSubMenu }) - let icon - if (deepSubmenu) { - icon = isExpand ? 'left' : 'right' + let toggleIcon + if (deepSubmenu && (mode === 'horizontal' || mini)) { + toggleIcon = isExpand ? 'left' : 'right' } else { - icon = isExpand ? 'up' : 'down' + toggleIcon = isExpand ? 'up' : 'down' } return ( @@ -142,12 +146,10 @@ export default class SubMenu extends Component { this.onClick(indexs) }} > - <div className='hi-menu-item__main'> - <div className='hi-menu-item__content'> - {content} - </div> - <div className='hi-menu-item__expand-icon'> - <Icon name={icon} /> + <div className='hi-submenu__title hi-menu__title'> + <Title icon={icon} content={content} /> + <div className='hi-menu__title-toggle-icon'> + <Icon name={toggleIcon} /> </div> </div> { diff --git a/components/menu/Title.js b/components/menu/Title.js new file mode 100644 index 000000000..5f8070604 --- /dev/null +++ b/components/menu/Title.js @@ -0,0 +1,44 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import Icon from '../icon' + +export default class Title extends Component { + static propTypes = { + content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]) + } + + renderIcon (icon) { + if (!icon) { + return null + } + let iconEle = icon + if (typeof icon === 'string') { + iconEle = <Icon name={icon} /> + } + + return ( + <div className='hi-menu__title-icon'> + {iconEle} + </div> + ) + } + + render () { + const { + content, + icon + } = this.props + + return ( + <React.Fragment> + { + this.renderIcon(icon) + } + <div className='hi-menu__title-content'> + {content} + </div> + </React.Fragment> + ) + } +} diff --git a/components/menu/VerticalMenu.js b/components/menu/VerticalMenu.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/components/menu/index.js b/components/menu/index.js index 4abc50025..0a7a13ee0 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import Icon from '../icon' +import Title from './Title' import Item from './Item' import SubMenu from './SubMenu' import './style/index' @@ -118,6 +119,7 @@ class Menu extends Component { const mergeProps = Object.assign({ onClick: this.onClick.bind(this), id: data.id, + icon: data.icon, isActive: activeIndexs.indexOf(indexs) === 0, indexs: indexs, key: data.id @@ -130,16 +132,16 @@ class Menu extends Component { ) } - renderGroupSubMenu (datas, indexs) { + renderFatSubMenu (datas, indexs) { let groups = [] datas.forEach((data, groupIndex) => { groups.push( - <li className='hi-menu-group' key={groupIndex}> - <div className='hi-menu-group__title'> - {data.content} + <li className='hi-menu-fat' key={groupIndex}> + <div className='hi-menu-fat__title hi-menu__title'> + <Title icon={data.icon} content={data.content} /> </div> - <ul className='hi-menu-group__content'> + <ul className='hi-menu-fat__content'> { data.children.map((child, index) => { return this.renderItem(child, indexs + '-' + groupIndex + '-' + index) @@ -159,7 +161,7 @@ class Menu extends Component { expandIndexs } = this.state let items = [] - const renderMenu = groupSubMenu ? this.renderGroupSubMenu.bind(this) : this.renderMenu.bind(this) + const renderMenu = groupSubMenu ? this.renderFatSubMenu.bind(this) : this.renderMenu.bind(this) datas.forEach((data, index) => { const indexStr = indexs !== '' ? indexs + '-' + index : '' + index @@ -172,6 +174,7 @@ class Menu extends Component { isActive={activeIndexs.indexOf(indexStr) === 0} isExpand={expandIndexs.indexOf(indexStr) === 0} content={data.content} + icon={data.icon} renderMenu={renderMenu} datas={data.children} mode={mode} diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index 5a88652f2..516ea273c 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -1,3 +1,5 @@ +@import '@hi-ui/core-css/index.scss'; + @mixin ellipsis($max-width: initial, $width: auto) { width: $width; max-width: $max-width; @@ -6,8 +8,22 @@ white-space: nowrap; } +@mixin subMenuPadding($level) { + .hi-menu--#{$level} { + >.hi-menu__title { + padding-left: $level * 32px; + } + .hi-submenu__items { + >.hi-menu__title { + padding-left: ($level + 1) * 32px; + } + } + } +} + .hi-menu, .hi-submenu__popper { + @include component-reset(); ul { margin-top: 0; margin-bottom: 0; @@ -15,37 +31,48 @@ font-size: 0px; } li { + margin: 0px; list-style: none; } -} -.hi-menu { - &-items { + .hi-menu-items { display: flex; } - &-item { + .hi-menu-item { + &--active { + >.hi-menu__title .hi-menu__title-content, + >.hi-menu__title-content { + color: #4284F5; + } + } + } + + .hi-menu__title { display: flex; - position: relative; + line-height: 22px; font-size: 14px; color: #333; cursor: pointer; - &--active { - color: #4284F5; - } - &__expand-icon { + &-icon { flex: none; - margin-left: 8px; + margin-right: 4px; } - &__content { + &-content { flex: auto; - @include ellipsis(100px); + @include ellipsis(); + } + &-toggle-icon { + flex: none; + margin-left: 8px; } } +} +.hi-menu { &--horizontal { border-bottom: 1px solid #E6E7E8; .hi-menu-item { - padding: 12px 0px; + position: relative; margin-right: 48px; &:last-child { margin-right: 0px; @@ -61,10 +88,64 @@ background-color: #4284F5; } } + .hi-menu__title { + padding: 12px 0px; + &-content { + max-width: 100px; + } + } + } + + &--vertical { + display: inline-block; + width: 216px; + padding: 12px 0px; + background:#fff; + box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15); + border: 1px solid rgba(230,231,232,1); + box-sizing: border-box; + .hi-menu-items { + display: flex; + flex-direction: column; + } + .hi-menu__title { + padding: 12px 16px; + &:hover { + background-color: rgba(66,133,244,0.08);; + } + } + .hi-submenu { + display: flex; + flex-direction: column; + } + .hi-menu__title-icon { + font-size: 20px; + } + @include subMenuPadding(1) + @include subMenuPadding(2) + } + + &--mini { + width: 48px; + padding: 12px 0; + overflow: hidden; + .hi-menu__title { + padding: 0px; + } + .hi-menu__title-icon { + width: 48px; + height: 48px; + line-height: 48px; + margin-right: 0px; + text-align: center; + } } } .hi-submenu { + &__items--hide { + display: none; + } &__popper { width: 200px; padding: 4px 16px; @@ -76,63 +157,29 @@ display: flex; flex-direction: column; } - .hi-menu-item { + .hi-menu__title { padding: 8px 0; - &__content { - max-width: initial; - } } - &--group { + &--fat { width: auto; .hi-submenu__items { flex-direction: row; } } - } -} - -.hi-menu-group { - margin-right: 12px; - width: 108px; - &__content { - padding: 8px 0; - font-size: 14px; - color: #999; - box-sizing: border-box; - @include ellipsis() - } -} -.hi-menu-item__main { - display: flex; - flex: auto; - .hi-menu-item__content { - flex: auto; - } -} -.hi-menu-group__title { - font-size: 14px; - color: #999; -} -.hi-menu-item__content, -.hi-menu-group__content { - .hi-icon { - margin-right: 4px; - } -} -.hi-submenu__items--hide { - display: none; -} -.hi-menu--vertical { - .hi-menu-items { - display: flex; - flex-direction: column; - } - .hi-submenu__items { - margin-left: 16px; - } - .hi-submenu { - display: flex; - flex-direction: column; + .hi-menu-fat { + margin-right: 12px; + width: 108px; + &__title{ + padding: 8px 0; + color: #999; + } + &__content { + font-size: 14px; + color: #999; + box-sizing: border-box; + @include ellipsis() + } + } } } \ No newline at end of file diff --git a/docs/zh-CN/components/menu.md b/docs/zh-CN/components/menu.md index 090448525..228a5c0d9 100644 --- a/docs/zh-CN/components/menu.md +++ b/docs/zh-CN/components/menu.md @@ -11,7 +11,8 @@ const datas = [ { content: '电视', - id: 1 + id: 1, + icon: 'internet' }, { content: '小米MIX', @@ -22,6 +23,7 @@ children: [ { content: '小米', + icon: 'phone', children: [ { content: '小米9', @@ -85,7 +87,7 @@ ] }, { - content: '超长超长超长字符', + content: '超长超长超长字符超长超长超长字符', id: 4 } ] @@ -117,7 +119,8 @@ const datas = [ { content: '电视', - id: 1 + id: 1, + icon: 'internet' }, { content: '小米MIX', @@ -125,9 +128,11 @@ }, { content: '手机', + icon: 'phone', children: [ { content: '小米', + icon: 'phone', children: [ { content: '小米9', @@ -212,7 +217,7 @@ ] }, { - content: '超长超长超长字符', + content: '超长超长超长字符超长超长超长字符', id: 4 } ] @@ -245,6 +250,7 @@ const datas = [ { content: '电视', + icon: 'internet', id: 1 }, { @@ -253,6 +259,7 @@ }, { content: '手机', + icon: 'phone', children: [ { content: '小米', @@ -289,6 +296,7 @@ }, { content: '红米', + icon: 'phone', id: 'hongmi' }, { @@ -319,7 +327,7 @@ ] }, { - content: '超长超长超长字符', + content: '超长超长超长字符超长超长超长字符', id: 4 } ] @@ -351,14 +359,17 @@ const datas = [ { content: '电视', + icon: 'internet', id: 1 }, { content: '小米MIX', + icon: 'internet', id: 2 }, { content: '手机', + icon: 'internet', children: [ { content: '小米', @@ -425,7 +436,8 @@ ] }, { - content: '超长超长超长字符', + content: '超长超长超长字符超长超长超长字符', + icon: 'internet', id: 4 } ] From 18c54d6e83ff56a7a9f5d6fe6b81fc154ff8cfbe Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Fri, 22 Mar 2019 10:37:18 +0800 Subject: [PATCH 014/112] menu toggle mini --- components/menu/SubMenu.js | 9 +++++++-- components/menu/index.js | 32 +++++++++++++++++++++++++++----- components/menu/style/index.scss | 23 ++++++++++++++++------- docs/zh-CN/components/menu.md | 1 + 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index 1d943af30..cd1cbc814 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -66,7 +66,8 @@ export default class SubMenu extends Component { let topGap let placement if (deepSubmenu || mini) { - leftGap = 16 + // leftGap = mini && !deepSubmenu ? 0 : 16 // 非mini状态有个16px的right-padding,所以要补偿一下 + leftGap = 0 topGap = -4 placement = 'right-start' } else { @@ -119,6 +120,7 @@ export default class SubMenu extends Component { mini, indexs, isExpand, + disabled, isActive, groupSubMenu } = this.props @@ -126,6 +128,7 @@ export default class SubMenu extends Component { const deepSubmenu = indexs.split('-').length > 1 const cls = classNames('hi-menu-item', 'hi-submenu', `hi-menu--${level}`, { + 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive, 'hi-submenu--sub': deepSubmenu, 'hi-submenu--fat': groupSubMenu @@ -143,7 +146,9 @@ export default class SubMenu extends Component { ref={node => { this.submenuTrigger = node }} onClick={(e) => { e.stopPropagation() - this.onClick(indexs) + if (!disabled) { + this.onClick(indexs) + } }} > <div className='hi-submenu__title hi-menu__title'> diff --git a/components/menu/index.js b/components/menu/index.js index 0a7a13ee0..82cf722d3 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -32,18 +32,25 @@ class Menu extends Component { miniToggle: PropTypes.bool, groupSubMenu: PropTypes.bool, accordion: PropTypes.bool, - onClick: PropTypes.func + onClick: PropTypes.func, + onOpenChange: PropTypes.func, + onMiniChange: PropTypes.func } constructor (props) { super(props) - const activeIndexs = this.getActiveIndexs(this.props.activeId) + const { + activeId, + mini + } = this.props + const activeIndexs = this.getActiveIndexs(activeId) this.state = { activeId: this.props.activeId, activeIndexs, - expandIndexs: '' + expandIndexs: '', + mini } } @@ -56,6 +63,12 @@ class Menu extends Component { activeIndexs }) } + + if (nextProps.mini !== this.props.mini) { + this.setState({ + mini: nextProps.mini + }) + } } getChildContext () { @@ -92,7 +105,13 @@ class Menu extends Component { } toggleMini () { + let mini = !this.state.mini + this.setState({ + mini + }, () => { + this.props.onMiniChange && this.props.onMiniChange(mini) + }) } onClick (indexs, id) { @@ -122,6 +141,7 @@ class Menu extends Component { icon: data.icon, isActive: activeIndexs.indexOf(indexs) === 0, indexs: indexs, + disabled: data.disabled, key: data.id }, props) @@ -173,6 +193,7 @@ class Menu extends Component { groupSubMenu={groupSubMenu} isActive={activeIndexs.indexOf(indexStr) === 0} isExpand={expandIndexs.indexOf(indexStr) === 0} + disabled={data.disabled} content={data.content} icon={data.icon} renderMenu={renderMenu} @@ -190,11 +211,12 @@ class Menu extends Component { } render () { - const {datas, mode, mini, miniToggle} = this.props + const {datas, mode, miniToggle} = this.props + const {mini} = this.state const cls = classNames('hi-menu', `hi-menu--${mode}`, { 'hi-menu--mini': mini }) - const miniIcon = mini ? 'toggle-right' : 'mini-left' + const miniIcon = mini ? 'double-right' : 'double-left' return ( <div className={cls}> diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index 516ea273c..e413c43af 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -10,8 +10,10 @@ @mixin subMenuPadding($level) { .hi-menu--#{$level} { - >.hi-menu__title { - padding-left: $level * 32px; + @if $level > 1 { + >.hi-menu__title { + padding-left: $level * 32px; + } } .hi-submenu__items { >.hi-menu__title { @@ -133,12 +135,19 @@ padding: 0px; } .hi-menu__title-icon { - width: 48px; - height: 48px; - line-height: 48px; + width: 46px; + height: 46px; + line-height: 46px; margin-right: 0px; text-align: center; } + &__toggle { + height: 48px; + line-height: 48px; + padding-left: 14px; + cursor: pointer; + font-size: 20px; + } } } @@ -148,7 +157,7 @@ } &__popper { width: 200px; - padding: 4px 16px; + padding: 4px 0px; background-color: #fff; box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15); border: 1px solid rgba(230,231,232,1); @@ -158,7 +167,7 @@ flex-direction: column; } .hi-menu__title { - padding: 8px 0; + padding: 8px 16px; } &--fat { width: auto; diff --git a/docs/zh-CN/components/menu.md b/docs/zh-CN/components/menu.md index 228a5c0d9..00d27f5d9 100644 --- a/docs/zh-CN/components/menu.md +++ b/docs/zh-CN/components/menu.md @@ -445,6 +445,7 @@ <div> <Menu mini + miniToggle={true} mode="vertical" activeId={'xiaomi9'} onClick={(id, prevId)=>console.log('-----click', id, prevId)} From e1b1d504b5c331cb02dc211148f3d0b5e26dffa7 Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Fri, 22 Mar 2019 11:17:29 +0800 Subject: [PATCH 015/112] menu trigger change event --- components/menu/Item.js | 6 ++- components/menu/SubMenu.js | 46 +++++++++-------- components/menu/index.js | 88 +++++++++++++++----------------- components/menu/style/index.scss | 1 - docs/zh-CN/components/menu.md | 6 ++- 5 files changed, 75 insertions(+), 72 deletions(-) diff --git a/components/menu/Item.js b/components/menu/Item.js index 232e1cd08..aad96dae3 100644 --- a/components/menu/Item.js +++ b/components/menu/Item.js @@ -9,6 +9,8 @@ class Item extends Component { static propTypes = { id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + index: PropTypes.string, + onClick: PropTypes.func, disabled: PropTypes.bool, isActive: PropTypes.bool } @@ -26,7 +28,7 @@ class Item extends Component { isActive, id, icon, - indexs + index } = this.props const cls = classNames('hi-menu-item', 'hi-menu-item__title', 'hi-menu__title', { 'hi-menu-item--disabled': disabled, @@ -36,7 +38,7 @@ class Item extends Component { return ( <li className={cls} onClick={() => { if (!disabled) { - onClick(indexs, id) + onClick(index, id) } }}> <Title icon={icon} content={children} /> diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index cd1cbc814..fd99aa26b 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -10,14 +10,22 @@ export default class SubMenu extends Component { static componentName = 'SubMenu' static propTypes = { + icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - showParentSubmenu: PropTypes.bool, + datas: PropTypes.array, + renderMenu: PropTypes.func, + onClick: PropTypes.func, + index: PropTypes.string, level: PropTypes.number, - groupSubMenu: PropTypes.bool + mode: PropTypes.oneOf(['horizontal', 'vertical']), + mini: PropTypes.bool, + fatMenu: PropTypes.bool, + isExpand: PropTypes.bool, + disabled: PropTypes.bool, + isActive: PropTypes.bool } static defaultProps = { - showParentSubmenu: false, level: 1 } @@ -49,18 +57,18 @@ export default class SubMenu extends Component { this.onClick('') } - onClick (indexs) { - this.props.onClick(indexs) + onClick (index) { + this.props.onClick(index) } renderPopperMenu (deepSubmenu) { const { mini, datas, - indexs, + index, isExpand, renderMenu, - groupSubMenu + fatMenu } = this.props let leftGap let topGap @@ -84,13 +92,13 @@ export default class SubMenu extends Component { topGap={topGap} leftGap={leftGap} className={ - classNames('hi-submenu__popper', {'hi-submenu__popper--fat': groupSubMenu}) + classNames('hi-submenu__popper', {'hi-submenu__popper--fat': fatMenu}) } width={false} placement={placement} > <ul className={classNames('hi-submenu__items')} ref={node => { this.submenuNode = node }}> - { renderMenu(datas, indexs) } + { renderMenu(datas, index) } </ul> </Popper> ) @@ -101,13 +109,13 @@ export default class SubMenu extends Component { isActive, isExpand, datas, - indexs, + index, renderMenu } = this.props return ( <ul className={classNames('hi-submenu__items', {'hi-submenu__items--hide': !isExpand && !isActive})}> - { renderMenu(datas, indexs) } + { renderMenu(datas, index) } </ul> ) } @@ -118,20 +126,20 @@ export default class SubMenu extends Component { icon, mode, mini, - indexs, + index, isExpand, disabled, isActive, - groupSubMenu + fatMenu } = this.props - const level = indexs.split('-').length + const level = index.split('-').length - const deepSubmenu = indexs.split('-').length > 1 + const deepSubmenu = index.split('-').length > 1 const cls = classNames('hi-menu-item', 'hi-submenu', `hi-menu--${level}`, { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive, 'hi-submenu--sub': deepSubmenu, - 'hi-submenu--fat': groupSubMenu + 'hi-submenu--fat': fatMenu }) let toggleIcon if (deepSubmenu && (mode === 'horizontal' || mini)) { @@ -147,7 +155,7 @@ export default class SubMenu extends Component { onClick={(e) => { e.stopPropagation() if (!disabled) { - this.onClick(indexs) + this.onClick(index) } }} > @@ -166,7 +174,3 @@ export default class SubMenu extends Component { ) } } - -SubMenu.contextTypes = { - component: PropTypes.any -} diff --git a/components/menu/index.js b/components/menu/index.js index 82cf722d3..c86488a9b 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -13,7 +13,7 @@ class Menu extends Component { activeId: '', mini: false, miniToggle: false, - groupSubMenu: false, + fatMenu: false, accordion: false } static propTypes = { @@ -23,17 +23,18 @@ class Menu extends Component { PropTypes.string, PropTypes.number ]), + disabled: PropTypes.bool, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), children: PropTypes.array }), activeId: PropTypes.string, mode: PropTypes.oneOf(['horizontal', 'vertical']), - mini: PropTypes.bool, - miniToggle: PropTypes.bool, - groupSubMenu: PropTypes.bool, + mini: PropTypes.bool, // 是否是mini模式,需要同时mode=vertical时才生效 + miniToggle: PropTypes.bool, // mini状态开关,需要同时mode=vertical时才生效 + fatMenu: PropTypes.bool, // 胖菜单,需要同时mode=horizontal时才生效 accordion: PropTypes.bool, onClick: PropTypes.func, - onOpenChange: PropTypes.func, + onClickSubMenu: PropTypes.func, onMiniChange: PropTypes.func } @@ -44,23 +45,23 @@ class Menu extends Component { activeId, mini } = this.props - const activeIndexs = this.getActiveIndexs(activeId) + const activeIndex = this.getActiveIndex(activeId) this.state = { activeId: this.props.activeId, - activeIndexs, - expandIndexs: '', + activeIndex, + expandIndex: '', mini } } componentWillReceiveProps (nextProps) { if (nextProps.activeId !== this.props.activeId) { - const activeIndexs = this.getActiveIndexs(nextProps.activeId) + const activeIndex = this.getActiveIndex(nextProps.activeId) this.setState({ activeId: nextProps.activeId, - activeIndexs + activeIndex }) } @@ -71,37 +72,31 @@ class Menu extends Component { } } - getChildContext () { - return { - component: this - } - } - - getActiveIndexs (activeId) { + getActiveIndex (activeId) { // 获取激活item对应的索引,以'-'拼接成字符串 const { datas } = this.props - let activeIndexs = [] + let activeIndex = [] let level = 0 if (activeId === undefined) { - return activeIndexs + return activeIndex } - const _getActiveIndexs = function (datas) { + const _getActiveIndex = function (datas) { for (const index in datas) { const data = datas[index] - activeIndexs[level] = parseInt(index) + activeIndex[level] = parseInt(index) if (data.children) { level++ - _getActiveIndexs(data.children) + _getActiveIndex(data.children) } else if (data.id === activeId) { break } } } - _getActiveIndexs(datas) - return activeIndexs.join('-') + _getActiveIndex(datas) + return activeIndex.join('-') } toggleMini () { @@ -118,29 +113,31 @@ class Menu extends Component { const oldId = this.state.activeId this.setState({ - activeIndexs: indexs, - expandIndexs: '' + activeIndex: indexs, + expandIndex: '' }, () => { this.props.onClick(id, oldId) }) } - onClickSubMenu (indexs) { + onClickSubMenu (index) { this.setState({ - expandIndexs: indexs + expandIndex: index + }, () => { + index && this.props.onClickSubMenu && this.props.onClickSubMenu(index) }) } - renderItem (data, indexs, props = {}) { + renderItem (data, index, props = {}) { // render menu item const { - activeIndexs + activeIndex } = this.state const mergeProps = Object.assign({ onClick: this.onClick.bind(this), id: data.id, icon: data.icon, - isActive: activeIndexs.indexOf(indexs) === 0, - indexs: indexs, + isActive: activeIndex.indexOf(index) === 0, + index: index, disabled: data.disabled, key: data.id }, props) @@ -152,7 +149,7 @@ class Menu extends Component { ) } - renderFatSubMenu (datas, indexs) { + renderFatSubMenu (datas, parentIndex) { // render胖菜单 let groups = [] datas.forEach((data, groupIndex) => { @@ -164,7 +161,7 @@ class Menu extends Component { <ul className='hi-menu-fat__content'> { data.children.map((child, index) => { - return this.renderItem(child, indexs + '-' + groupIndex + '-' + index) + return this.renderItem(child, parentIndex + '-' + groupIndex + '-' + index) }) } </ul> @@ -174,25 +171,25 @@ class Menu extends Component { return groups } - renderMenu (datas, indexs = '') { - const {groupSubMenu, mode, mini} = this.props + renderMenu (datas, parentIndex = '') { + const {fatMenu, mode, mini} = this.props const { - activeIndexs, - expandIndexs + activeIndex, + expandIndex } = this.state let items = [] - const renderMenu = groupSubMenu ? this.renderFatSubMenu.bind(this) : this.renderMenu.bind(this) + const renderMenu = fatMenu ? this.renderFatSubMenu.bind(this) : this.renderMenu.bind(this) datas.forEach((data, index) => { - const indexStr = indexs !== '' ? indexs + '-' + index : '' + index + const indexStr = parentIndex !== '' ? parentIndex + '-' + index : '' + index if (data.children) { items.push( <SubMenu onClick={this.onClickSubMenu.bind(this)} - indexs={indexStr} - groupSubMenu={groupSubMenu} - isActive={activeIndexs.indexOf(indexStr) === 0} - isExpand={expandIndexs.indexOf(indexStr) === 0} + index={indexStr} + fatMenu={fatMenu} + isActive={activeIndex.indexOf(indexStr) === 0} + isExpand={expandIndex.indexOf(indexStr) === 0} disabled={data.disabled} content={data.content} icon={data.icon} @@ -236,8 +233,5 @@ class Menu extends Component { ) } } -Menu.childContextTypes = { - component: PropTypes.any -} export default Menu diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index e413c43af..5b8ceccfd 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -180,7 +180,6 @@ margin-right: 12px; width: 108px; &__title{ - padding: 8px 0; color: #999; } &__content { diff --git a/docs/zh-CN/components/menu.md b/docs/zh-CN/components/menu.md index 00d27f5d9..ca46c1211 100644 --- a/docs/zh-CN/components/menu.md +++ b/docs/zh-CN/components/menu.md @@ -97,6 +97,7 @@ mode="horizontal" activeId={'xiaomi9'} onClick={(id, prevId)=>console.log('-----click', id, prevId)} + onClickSubMenu={index => console.log('-----onClickSubMenu', index)} datas={datas} /> </div> @@ -226,7 +227,7 @@ <Menu mode="horizontal" activeId={'xiaomi9'} - groupSubMenu + fatMenu onClick={(id, prevId)=>console.log('-----click', id, prevId)} datas={datas} /> @@ -337,6 +338,7 @@ mode="vertical" activeId={'xiaomi9'} onClick={(id, prevId)=>console.log('-----click', id, prevId)} + onClickSubMenu={index => console.log('-----onClickSubMenu', index)} datas={datas} /> </div> @@ -449,6 +451,8 @@ mode="vertical" activeId={'xiaomi9'} onClick={(id, prevId)=>console.log('-----click', id, prevId)} + onMiniChange={toggle => console.log('-----onMiniChange', toggle)} + onClickSubMenu={index => console.log('-----onClickSubMenu', index)} datas={datas} /> </div> From 612e26c50401a947de9f508bbcd25caa7c6194b3 Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Fri, 22 Mar 2019 17:16:34 +0800 Subject: [PATCH 016/112] menu clickOutside --- components/menu/SubMenu.js | 45 ++++++++----------------------------- components/menu/index.js | 32 ++++++++++++++++++++++++++ components/select/Select.js | 3 ++- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index fd99aa26b..dc4fa0bc9 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -1,5 +1,4 @@ import React, { Component } from 'react' -import ReactDOM from 'react-dom' import PropTypes from 'prop-types' import classNames from 'classnames' import Popper from '../popper' @@ -29,34 +28,6 @@ export default class SubMenu extends Component { level: 1 } - constructor (props) { - super(props) - - this.clickOutsideHandel = this.clickOutside.bind(this) - } - - componentDidMount () { - window.addEventListener('click', this.clickOutsideHandel) - } - - componentWillUnmount () { - window.removeEventListener('click', this.clickOutsideHandel) - } - - clickOutside (e) { - if ( - (ReactDOM.findDOMNode(this.submenuTrigger) && ReactDOM.findDOMNode(this.submenuTrigger).contains(e.target)) || - (ReactDOM.findDOMNode(this.submenuNode) && ReactDOM.findDOMNode(this.submenuNode).contains(e.target)) - ) { - return - } - this.hidePopper() - } - - hidePopper () { - this.onClick('') - } - onClick (index) { this.props.onClick(index) } @@ -152,14 +123,16 @@ export default class SubMenu extends Component { <li className={cls} ref={node => { this.submenuTrigger = node }} - onClick={(e) => { - e.stopPropagation() - if (!disabled) { - this.onClick(index) - } - }} > - <div className='hi-submenu__title hi-menu__title'> + <div + className='hi-submenu__title hi-menu__title' + onClick={() => { + if (!disabled) { + this.index = index + this.onClick(index) + } + }} + > <Title icon={icon} content={content} /> <div className='hi-menu__title-toggle-icon'> <Icon name={toggleIcon} /> diff --git a/components/menu/index.js b/components/menu/index.js index c86488a9b..d9775bb48 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -46,6 +46,7 @@ class Menu extends Component { mini } = this.props const activeIndex = this.getActiveIndex(activeId) + this.clickOutsideHandel = this.clickOutside.bind(this) this.state = { activeId: this.props.activeId, @@ -72,6 +73,33 @@ class Menu extends Component { } } + componentDidMount () { + window.addEventListener('click', this.clickOutsideHandel) + } + + componentWillUnmount () { + window.removeEventListener('click', this.clickOutsideHandel) + } + + clickInsideFlag = false // click在menu标识 + prevExpandIndex = null // 上一次展开的submenu + clickOutside () { + let expandIndex = this.state.expandIndex + if (this.clickInsideFlag) { + if (this.prevExpandIndex === expandIndex) { // submenu已打开则关闭 + expandIndex = expandIndex.split('-').slice(0, -1).join('-') + } + } else { + expandIndex = '' + } + + this.setState({ + expandIndex + }) + this.prevExpandIndex = null + this.clickInsideFlag = false + } + getActiveIndex (activeId) { // 获取激活item对应的索引,以'-'拼接成字符串 const { datas @@ -110,9 +138,11 @@ class Menu extends Component { } onClick (indexs, id) { + this.clickInsideFlag = true const oldId = this.state.activeId this.setState({ + activeId: id, activeIndex: indexs, expandIndex: '' }, () => { @@ -121,6 +151,8 @@ class Menu extends Component { } onClickSubMenu (index) { + this.prevExpandIndex = this.state.expandIndex + this.clickInsideFlag = true this.setState({ expandIndex: index }, () => { diff --git a/components/select/Select.js b/components/select/Select.js index d80cd77dd..3de18eb6c 100644 --- a/components/select/Select.js +++ b/components/select/Select.js @@ -100,7 +100,8 @@ class Select extends Component { } clickOutside (e) { - if (ReactDOM.findDOMNode(this.selectInput) && ReactDOM.findDOMNode(this.selectInput).contains(e.target)) { + const selectInput = ReactDOM.findDOMNode(this.selectInput) + if (selectInput && selectInput.contains(e.target)) { return } this.hideDropdown() From dfe310b5e9872e0c3b8573e598f650ec98476c0d Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Sat, 23 Mar 2019 07:19:02 +0800 Subject: [PATCH 017/112] menu remove isActive --- components/menu/Item.js | 7 ++++--- components/menu/SubMenu.js | 36 ++++++++++++++++++++++++------------ components/menu/index.js | 18 ++++++++++-------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/components/menu/Item.js b/components/menu/Item.js index aad96dae3..982decb0a 100644 --- a/components/menu/Item.js +++ b/components/menu/Item.js @@ -12,12 +12,12 @@ class Item extends Component { index: PropTypes.string, onClick: PropTypes.func, disabled: PropTypes.bool, - isActive: PropTypes.bool + activeIndex: PropTypes.string } static defaultProps = { disabled: false, - isActive: false + activeIndex: '' } render () { @@ -25,11 +25,12 @@ class Item extends Component { children, disabled, onClick, - isActive, + activeIndex, id, icon, index } = this.props + const isActive = activeIndex.indexOf(index) === 0 const cls = classNames('hi-menu-item', 'hi-menu-item__title', 'hi-menu__title', { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index dc4fa0bc9..fb8e45851 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -19,9 +19,9 @@ export default class SubMenu extends Component { mode: PropTypes.oneOf(['horizontal', 'vertical']), mini: PropTypes.bool, fatMenu: PropTypes.bool, - isExpand: PropTypes.bool, disabled: PropTypes.bool, - isActive: PropTypes.bool + activeIndex: PropTypes.string, + expandIndex: PropTypes.string } static defaultProps = { @@ -32,12 +32,24 @@ export default class SubMenu extends Component { this.props.onClick(index) } - renderPopperMenu (deepSubmenu) { + checkActive (activeIndex, index) { + return activeIndex.indexOf(index) === 0 + } + + checkExpand (activeIndex, expandIndex, index) { + return expandIndex.indexOf(index) === 0 + // if (expandIndex && activeIndex.indexOf(expandIndex) === 0) { + // return activeIndex.indexOf(index) === 0 + // } else { + // return expandIndex.indexOf(index) === 0 + // } + } + + renderPopperMenu (deepSubmenu, isExpand) { const { mini, datas, index, - isExpand, renderMenu, fatMenu } = this.props @@ -75,10 +87,8 @@ export default class SubMenu extends Component { ) } - renderVerticalMenu () { + renderVerticalMenu (isActive, isExpand) { const { - isActive, - isExpand, datas, index, renderMenu @@ -98,18 +108,20 @@ export default class SubMenu extends Component { mode, mini, index, - isExpand, + activeIndex, + expandIndex, disabled, - isActive, fatMenu } = this.props + const isExpand = this.checkExpand(activeIndex, expandIndex, index) + const isActive = this.checkActive(activeIndex, index) const level = index.split('-').length const deepSubmenu = index.split('-').length > 1 const cls = classNames('hi-menu-item', 'hi-submenu', `hi-menu--${level}`, { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive, - 'hi-submenu--sub': deepSubmenu, + // 'hi-submenu--sub': deepSubmenu, 'hi-submenu--fat': fatMenu }) let toggleIcon @@ -140,8 +152,8 @@ export default class SubMenu extends Component { </div> { !mini && mode === 'vertical' - ? this.renderVerticalMenu() - : this.renderPopperMenu(deepSubmenu) + ? this.renderVerticalMenu(isActive, isExpand) + : this.renderPopperMenu(deepSubmenu, isExpand) } </li> ) diff --git a/components/menu/index.js b/components/menu/index.js index d9775bb48..4cb33e1dc 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -17,7 +17,7 @@ class Menu extends Component { accordion: false } static propTypes = { - datas: PropTypes.shape({ + datas: PropTypes.arrayOf(PropTypes.shape({ content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), id: PropTypes.oneOfType([ PropTypes.string, @@ -26,7 +26,7 @@ class Menu extends Component { disabled: PropTypes.bool, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), children: PropTypes.array - }), + })), activeId: PropTypes.string, mode: PropTypes.oneOf(['horizontal', 'vertical']), mini: PropTypes.bool, // 是否是mini模式,需要同时mode=vertical时才生效 @@ -45,13 +45,14 @@ class Menu extends Component { activeId, mini } = this.props + let expandIndex = '' const activeIndex = this.getActiveIndex(activeId) this.clickOutsideHandel = this.clickOutside.bind(this) this.state = { activeId: this.props.activeId, activeIndex, - expandIndex: '', + expandIndex, mini } } @@ -168,7 +169,7 @@ class Menu extends Component { onClick: this.onClick.bind(this), id: data.id, icon: data.icon, - isActive: activeIndex.indexOf(index) === 0, + activeIndex, index: index, disabled: data.disabled, key: data.id @@ -204,10 +205,11 @@ class Menu extends Component { } renderMenu (datas, parentIndex = '') { - const {fatMenu, mode, mini} = this.props + const {fatMenu, mode} = this.props const { activeIndex, - expandIndex + expandIndex, + mini } = this.state let items = [] const renderMenu = fatMenu ? this.renderFatSubMenu.bind(this) : this.renderMenu.bind(this) @@ -220,8 +222,8 @@ class Menu extends Component { onClick={this.onClickSubMenu.bind(this)} index={indexStr} fatMenu={fatMenu} - isActive={activeIndex.indexOf(indexStr) === 0} - isExpand={expandIndex.indexOf(indexStr) === 0} + activeIndex={activeIndex} + expandIndex={expandIndex} disabled={data.disabled} content={data.content} icon={data.icon} From 096a9d9d1a606545b46d2d07fe0f188ca2b35419 Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Sat, 23 Mar 2019 08:06:03 +0800 Subject: [PATCH 018/112] =?UTF-8?q?menu=20expandIndex=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/menu/Item.js | 3 +- components/menu/SubMenu.js | 27 +++++++++++----- components/menu/index.js | 66 ++++++++++++++++++++++++++------------ 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/components/menu/Item.js b/components/menu/Item.js index 982decb0a..14081cbc3 100644 --- a/components/menu/Item.js +++ b/components/menu/Item.js @@ -37,7 +37,8 @@ class Item extends Component { }) return ( - <li className={cls} onClick={() => { + <li className={cls} onClick={e => { + e.stopPropagation() if (!disabled) { onClick(index, id) } diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index fb8e45851..cec411f2f 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -21,11 +21,12 @@ export default class SubMenu extends Component { fatMenu: PropTypes.bool, disabled: PropTypes.bool, activeIndex: PropTypes.string, - expandIndex: PropTypes.string + expandIndex: PropTypes.array } static defaultProps = { - level: 1 + level: 1, + expandIndex: [] } onClick (index) { @@ -37,7 +38,9 @@ export default class SubMenu extends Component { } checkExpand (activeIndex, expandIndex, index) { - return expandIndex.indexOf(index) === 0 + return expandIndex.some(item => { + return item.indexOf(index) === 0 + }) // if (expandIndex && activeIndex.indexOf(expandIndex) === 0) { // return activeIndex.indexOf(index) === 0 // } else { @@ -51,7 +54,8 @@ export default class SubMenu extends Component { datas, index, renderMenu, - fatMenu + fatMenu, + clickInside } = this.props let leftGap let topGap @@ -80,7 +84,11 @@ export default class SubMenu extends Component { width={false} placement={placement} > - <ul className={classNames('hi-submenu__items')} ref={node => { this.submenuNode = node }}> + <ul + className={classNames('hi-submenu__items')} + ref={node => { this.submenuNode = node }} + onClick={() => clickInside()} // 利用事件冒泡设置clickInsideFlag + > { renderMenu(datas, index) } </ul> </Popper> @@ -91,11 +99,15 @@ export default class SubMenu extends Component { const { datas, index, - renderMenu + renderMenu, + clickInside } = this.props return ( - <ul className={classNames('hi-submenu__items', {'hi-submenu__items--hide': !isExpand && !isActive})}> + <ul + className={classNames('hi-submenu__items', {'hi-submenu__items--hide': !isExpand && !isActive})} + onClick={() => clickInside()} // 利用事件冒泡设置clickInsideFlag + > { renderMenu(datas, index) } </ul> ) @@ -121,7 +133,6 @@ export default class SubMenu extends Component { const cls = classNames('hi-menu-item', 'hi-submenu', `hi-menu--${level}`, { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive, - // 'hi-submenu--sub': deepSubmenu, 'hi-submenu--fat': fatMenu }) let toggleIcon diff --git a/components/menu/index.js b/components/menu/index.js index 4cb33e1dc..1c705ae46 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -14,7 +14,7 @@ class Menu extends Component { mini: false, miniToggle: false, fatMenu: false, - accordion: false + accordion: true } static propTypes = { datas: PropTypes.arrayOf(PropTypes.shape({ @@ -45,14 +45,13 @@ class Menu extends Component { activeId, mini } = this.props - let expandIndex = '' const activeIndex = this.getActiveIndex(activeId) this.clickOutsideHandel = this.clickOutside.bind(this) this.state = { activeId: this.props.activeId, + expandIndex: [], activeIndex, - expandIndex, mini } } @@ -83,24 +82,49 @@ class Menu extends Component { } clickInsideFlag = false // click在menu标识 - prevExpandIndex = null // 上一次展开的submenu clickOutside () { - let expandIndex = this.state.expandIndex - if (this.clickInsideFlag) { - if (this.prevExpandIndex === expandIndex) { // submenu已打开则关闭 - expandIndex = expandIndex.split('-').slice(0, -1).join('-') - } - } else { - expandIndex = '' + if (!this.clickInsideFlag) { + this.setState({ + expandIndex: [] + }) } - this.setState({ - expandIndex - }) - this.prevExpandIndex = null this.clickInsideFlag = false } + clickInside () { + this.clickInsideFlag = true + } + + getExpandIndex (clickedIndex) { + if (clickedIndex === '') { + return [] + } + const { + accordion, + mode + } = this.props + const { + mini, + expandIndex + } = this.state + let _clickedIndex = clickedIndex + const index = expandIndex.indexOf(clickedIndex) + + if (index > -1) { // 点击同一个submenu,如果已展开则关闭 + _clickedIndex = clickedIndex.split('-').slice(0, -1).join('-') + } + + if (!accordion && mode === 'vertical' && !mini) { // 非手风琴模式只有在垂直非mini状态下才生效 + const _expandIndex = expandIndex.slice() + index > -1 ? _expandIndex.splice(index, 1, _clickedIndex) : _expandIndex.push(_clickedIndex) + + return _expandIndex + } else { + return [_clickedIndex] + } + } + getActiveIndex (activeId) { // 获取激活item对应的索引,以'-'拼接成字符串 const { datas @@ -139,23 +163,24 @@ class Menu extends Component { } onClick (indexs, id) { - this.clickInsideFlag = true + const expandIndex = this.getExpandIndex('') const oldId = this.state.activeId this.setState({ activeId: id, activeIndex: indexs, - expandIndex: '' + expandIndex }, () => { this.props.onClick(id, oldId) }) } onClickSubMenu (index) { - this.prevExpandIndex = this.state.expandIndex - this.clickInsideFlag = true + const expandIndex = this.getExpandIndex(index) + + this.clickInside() this.setState({ - expandIndex: index + expandIndex }, () => { index && this.props.onClickSubMenu && this.props.onClickSubMenu(index) }) @@ -220,6 +245,7 @@ class Menu extends Component { items.push( <SubMenu onClick={this.onClickSubMenu.bind(this)} + clickInside={this.clickInside.bind(this)} index={indexStr} fatMenu={fatMenu} activeIndex={activeIndex} From fa57c438598d024828b2e1a6b185ce29b2ad8d94 Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Sat, 23 Mar 2019 10:03:13 +0800 Subject: [PATCH 019/112] =?UTF-8?q?menu=20=E5=B1=95=E5=BC=80=E6=94=B6?= =?UTF-8?q?=E8=B5=B7=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/menu/SubMenu.js | 16 ++++++++++++---- components/menu/index.js | 34 ++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index cec411f2f..5e8ff07fd 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -41,10 +41,19 @@ export default class SubMenu extends Component { return expandIndex.some(item => { return item.indexOf(index) === 0 }) - // if (expandIndex && activeIndex.indexOf(expandIndex) === 0) { + + // const { + // mini, + // mode + // } = this.props + + // if (!mini && mode==='vertical' && expandIndex.length===0) { // return activeIndex.indexOf(index) === 0 // } else { - // return expandIndex.indexOf(index) === 0 + // return expandIndex.some(item => { + // // return item.indexOf(index) === 0 || (activeIndex.indexOf(item) === 0 && activeIndex.indexOf(index) === 0) + // return item.indexOf(index) === 0 + // }) // } } @@ -105,7 +114,7 @@ export default class SubMenu extends Component { return ( <ul - className={classNames('hi-submenu__items', {'hi-submenu__items--hide': !isExpand && !isActive})} + className={classNames('hi-submenu__items', {'hi-submenu__items--hide': !isExpand})} onClick={() => clickInside()} // 利用事件冒泡设置clickInsideFlag > { renderMenu(datas, index) } @@ -151,7 +160,6 @@ export default class SubMenu extends Component { className='hi-submenu__title hi-menu__title' onClick={() => { if (!disabled) { - this.index = index this.onClick(index) } }} diff --git a/components/menu/index.js b/components/menu/index.js index 1c705ae46..9ae72918e 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -43,14 +43,20 @@ class Menu extends Component { const { activeId, - mini + mini, + mode } = this.props const activeIndex = this.getActiveIndex(activeId) + let expandIndex = [] this.clickOutsideHandel = this.clickOutside.bind(this) + if (mode === 'vertical' && !mini) { // 垂直非mini菜单默认打开激活项 + expandIndex = [activeIndex.split('-').slice(0, -1).join('-')] + } + this.state = { activeId: this.props.activeId, - expandIndex: [], + expandIndex, activeIndex, mini } @@ -97,7 +103,7 @@ class Menu extends Component { } getExpandIndex (clickedIndex) { - if (clickedIndex === '') { + if (!clickedIndex) { return [] } const { @@ -109,19 +115,29 @@ class Menu extends Component { expandIndex } = this.state let _clickedIndex = clickedIndex - const index = expandIndex.indexOf(clickedIndex) + let subInExpandIndex = false + + let _expandIndex = expandIndex.filter(item => { // 点击父菜单时,需要把已展开的子菜单过滤掉,因为父菜单关闭时所有子菜单也要关闭 + const flag = item.startsWith(_clickedIndex) + if (flag) { + subInExpandIndex = true + } + return !flag + }) + subInExpandIndex && _expandIndex.push(_clickedIndex) // subInExpandIndex为true说明其有子菜单被展开,在点击需要关闭 + + const index = _expandIndex.indexOf(clickedIndex) if (index > -1) { // 点击同一个submenu,如果已展开则关闭 _clickedIndex = clickedIndex.split('-').slice(0, -1).join('-') } if (!accordion && mode === 'vertical' && !mini) { // 非手风琴模式只有在垂直非mini状态下才生效 - const _expandIndex = expandIndex.slice() index > -1 ? _expandIndex.splice(index, 1, _clickedIndex) : _expandIndex.push(_clickedIndex) return _expandIndex } else { - return [_clickedIndex] + return _clickedIndex ? [_clickedIndex] : [] } } @@ -131,6 +147,7 @@ class Menu extends Component { } = this.props let activeIndex = [] let level = 0 + let matchFlag = false if (activeId === undefined) { return activeIndex @@ -143,7 +160,12 @@ class Menu extends Component { if (data.children) { level++ _getActiveIndex(data.children) + level-- } else if (data.id === activeId) { + matchFlag = true + break + } + if (matchFlag) { break } } From 22b59f3f67019705f39fe205385c0cfdf5d0567e Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Sat, 23 Mar 2019 10:50:30 +0800 Subject: [PATCH 020/112] menu doc --- components/menu/index.js | 23 ++--- components/menu/style/index.scss | 5 ++ docs/zh-CN/components/menu.md | 147 ++++++++++++++++++++++++++++--- 3 files changed, 152 insertions(+), 23 deletions(-) diff --git a/components/menu/index.js b/components/menu/index.js index 9ae72918e..4b2ace681 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -43,14 +43,13 @@ class Menu extends Component { const { activeId, - mini, - mode + mini } = this.props const activeIndex = this.getActiveIndex(activeId) let expandIndex = [] this.clickOutsideHandel = this.clickOutside.bind(this) - if (mode === 'vertical' && !mini) { // 垂直非mini菜单默认打开激活项 + if (this.isNoMiniVertaicalMenu(mini)) { // 垂直非mini菜单默认打开激活项 expandIndex = [activeIndex.split('-').slice(0, -1).join('-')] } @@ -89,7 +88,7 @@ class Menu extends Component { clickInsideFlag = false // click在menu标识 clickOutside () { - if (!this.clickInsideFlag) { + if (!this.clickInsideFlag && !this.isNoMiniVertaicalMenu()) { this.setState({ expandIndex: [] }) @@ -107,11 +106,9 @@ class Menu extends Component { return [] } const { - accordion, - mode + accordion } = this.props const { - mini, expandIndex } = this.state let _clickedIndex = clickedIndex @@ -132,7 +129,7 @@ class Menu extends Component { _clickedIndex = clickedIndex.split('-').slice(0, -1).join('-') } - if (!accordion && mode === 'vertical' && !mini) { // 非手风琴模式只有在垂直非mini状态下才生效 + if (!accordion && this.isNoMiniVertaicalMenu()) { // 非手风琴模式只有在垂直非mini状态下才生效 index > -1 ? _expandIndex.splice(index, 1, _clickedIndex) : _expandIndex.push(_clickedIndex) return _expandIndex @@ -141,6 +138,10 @@ class Menu extends Component { } } + isNoMiniVertaicalMenu (mini = this.state.mini) { // 垂直非mini菜单 + return this.props.mode === 'vertical' && !mini + } + getActiveIndex (activeId) { // 获取激活item对应的索引,以'-'拼接成字符串 const { datas @@ -185,7 +186,7 @@ class Menu extends Component { } onClick (indexs, id) { - const expandIndex = this.getExpandIndex('') + const expandIndex = this.isNoMiniVertaicalMenu() ? this.state.expandIndex : this.getExpandIndex('') // 非mini垂直菜单选中时不需要收起子菜单 const oldId = this.state.activeId this.setState({ @@ -204,7 +205,7 @@ class Menu extends Component { this.setState({ expandIndex }, () => { - index && this.props.onClickSubMenu && this.props.onClickSubMenu(index) + index && this.props.onClickSubMenu && this.props.onClickSubMenu(index.split('-')) }) } @@ -263,6 +264,7 @@ class Menu extends Component { datas.forEach((data, index) => { const indexStr = parentIndex !== '' ? parentIndex + '-' + index : '' + index + if (data.children) { items.push( <SubMenu @@ -279,6 +281,7 @@ class Menu extends Component { datas={data.children} mode={mode} mini={mini} + key={data.content} /> ) } else { diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index 5b8ceccfd..56a14b62c 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -47,6 +47,11 @@ color: #4284F5; } } + &--disabled { + .hi-menu__title { + cursor: not-allowed; + } + } } .hi-menu__title { diff --git a/docs/zh-CN/components/menu.md b/docs/zh-CN/components/menu.md index ca46c1211..dbf583fc9 100644 --- a/docs/zh-CN/components/menu.md +++ b/docs/zh-CN/components/menu.md @@ -31,7 +31,8 @@ }, { content: '小米8', - id: 'xiaomi8' + id: 'xiaomi8', + disabled: true }, { content: '小米7', @@ -61,6 +62,7 @@ }, { content: '小米note', + disabled: true, children: [ { content: '小米 note7', @@ -350,6 +352,117 @@ + +### 非手风琴菜单 + +:::demo + +非手风琴菜单 + +```js + render(){ + const datas = [ + { + content: '电视', + icon: 'internet', + id: 1 + }, + { + content: '小米MIX', + id: 2 + }, + { + content: '手机', + icon: 'phone', + children: [ + { + content: '小米', + children: [ + { + content: '小米9', + id: 'xiaomi9' + }, + { + content: '小米8', + id: 'xiaomi8' + }, + { + content: '小米7', + id: 'xiaomi7' + }, + { + content: '小米6', + id: 'xiaomi6' + }, + { + content: '小米5', + id: 'xiaomi5' + }, + { + content: '小米4', + id: 'xiaomi4' + }, + { + content: '小米3', + id: 'xiaomi3' + } + ] + }, + { + content: '红米', + icon: 'phone', + id: 'hongmi' + }, + { + content: '小米note', + children: [ + { + content: '小米 note7', + id: 'xiaomi note7' + }, + { + content: '小米 note6', + id: 'xiaomi note6' + }, + { + content: '小米 note5', + id: 'xiaomi note5' + }, + { + content: '小米 note4', + id: 'xiaomi note4' + }, + { + content: '小米 note3', + id: 'xiaomi note3' + } + ] + } + ] + }, + { + content: '超长超长超长字符超长超长超长字符', + id: 4 + } + ] + return( + <div> + <Menu + accordion={false} + mode="vertical" + activeId={'xiaomi9'} + onClick={(id, prevId)=>console.log('-----click', id, prevId)} + onClickSubMenu={index => console.log('-----onClickSubMenu', index)} + datas={datas} + /> + </div> + ) + } + +``` +::: + + ### 竖向收起菜单 :::demo @@ -361,7 +474,7 @@ const datas = [ { content: '电视', - icon: 'internet', + icon: 'alarm', id: 1 }, { @@ -371,7 +484,7 @@ }, { content: '手机', - icon: 'internet', + icon: 'phone', children: [ { content: '小米', @@ -439,7 +552,7 @@ }, { content: '超长超长超长字符超长超长超长字符', - icon: 'internet', + icon: 'document', id: 4 } ] @@ -468,15 +581,23 @@ | 参数 | 说明 | 类型 | 可选值 | 默认值 | | -------- | ----- | ---- | ---- | ---- | -| active | 是否激活 | boolean | true / false | false | -| defaultActive | 默认激活(当 active 存在时,此项无效 | boolean | true / false | false | -| text | 文本 | string | - | - | -| value | 文本对应的值,可为空 | - | - | +| data | 菜单选项配置,具体格式见下 | array | - | - | +| activeId | 激活的菜单id | string | - | - | | mode | 菜单排列模式 | string | horizontal / vertical | vertical | +| mini | 垂直菜单时,是否收缩 | boolean | true / false | false | +| miniToggle | 垂直菜单时,是否显示收缩开关 | boolean | true / false | true | +| fatMenu | 胖菜单,需要同时mode=horizontal时才生效 | boolean | true / false | false | +| accordion | 手风琴模式,只有在mode=vertical时生效 | boolean | true / false | true | +| onClick | 点击菜单选项触发的回调 | function(activeId, prevActiveId) | - | - | +| onClickSubMenu | 点击父菜单选项触发的回调 | function(subMenuIndexs) | - | - | +| onMiniChange | 点击收缩开关触发的回调 | function(miniToggle) | - | - | -### Menu Events +### Data Attributes -| 参数 | 说明 | 回调参数 | -| -------- | ----- | ---- | -| onOpen | 打开某菜单事件 | - | -| onClose | 关闭某菜单事件 | - | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| content | 菜单选项标题 | string, node | - | - | +| icon | 菜单选项icon,为string时会作文Icon组件的name| string, node | - | - | +| id | 菜单选项唯一标识 | string, number | - | - | +| disabled | 菜单选项是否禁止 | bool | true / false | false | +| children | 字菜单选项配置 | array | - | - | From aa2f181574402a1206730bffc9a6c4ea46444082 Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Sun, 24 Mar 2019 15:10:44 +0800 Subject: [PATCH 021/112] menu style --- components/menu/Item.js | 4 +++- components/menu/SubMenu.js | 3 +-- components/menu/index.js | 6 ++++-- components/menu/style/index.scss | 5 ++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/components/menu/Item.js b/components/menu/Item.js index 14081cbc3..f8e02c0e6 100644 --- a/components/menu/Item.js +++ b/components/menu/Item.js @@ -9,6 +9,7 @@ class Item extends Component { static propTypes = { id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + level: PropTypes.number, index: PropTypes.string, onClick: PropTypes.func, disabled: PropTypes.bool, @@ -25,13 +26,14 @@ class Item extends Component { children, disabled, onClick, + level, activeIndex, id, icon, index } = this.props const isActive = activeIndex.indexOf(index) === 0 - const cls = classNames('hi-menu-item', 'hi-menu-item__title', 'hi-menu__title', { + const cls = classNames('hi-menu-item', 'hi-menu-item__title', 'hi-menu__title', `hi-menu--${level}`, { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive }) diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index 5e8ff07fd..f63c7682b 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -128,6 +128,7 @@ export default class SubMenu extends Component { icon, mode, mini, + level, index, activeIndex, expandIndex, @@ -136,8 +137,6 @@ export default class SubMenu extends Component { } = this.props const isExpand = this.checkExpand(activeIndex, expandIndex, index) const isActive = this.checkActive(activeIndex, index) - const level = index.split('-').length - const deepSubmenu = index.split('-').length > 1 const cls = classNames('hi-menu-item', 'hi-submenu', `hi-menu--${level}`, { 'hi-menu-item--disabled': disabled, diff --git a/components/menu/index.js b/components/menu/index.js index 4b2ace681..cb8009450 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -242,7 +242,7 @@ class Menu extends Component { <ul className='hi-menu-fat__content'> { data.children.map((child, index) => { - return this.renderItem(child, parentIndex + '-' + groupIndex + '-' + index) + return this.renderItem(child, parentIndex + '-' + groupIndex + '-' + index, {level: 2}) }) } </ul> @@ -264,6 +264,7 @@ class Menu extends Component { datas.forEach((data, index) => { const indexStr = parentIndex !== '' ? parentIndex + '-' + index : '' + index + const level = indexStr.split('-').length if (data.children) { items.push( @@ -271,6 +272,7 @@ class Menu extends Component { onClick={this.onClickSubMenu.bind(this)} clickInside={this.clickInside.bind(this)} index={indexStr} + level={level} fatMenu={fatMenu} activeIndex={activeIndex} expandIndex={expandIndex} @@ -285,7 +287,7 @@ class Menu extends Component { /> ) } else { - items.push(this.renderItem(data, indexStr)) + items.push(this.renderItem(data, indexStr, {level})) } }) diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index 56a14b62c..598c1c95c 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -118,7 +118,7 @@ .hi-menu__title { padding: 12px 16px; &:hover { - background-color: rgba(66,133,244,0.08);; + background-color: rgba(66,133,244,0.08); } } .hi-submenu { @@ -136,6 +136,9 @@ width: 48px; padding: 12px 0; overflow: hidden; + .hi-menu--1.hi-menu-item--active { + background-color: rgba(66,133,244,0.08); + } .hi-menu__title { padding: 0px; } From e30d7c819f927d43e0f8ea83a67afd8d9fe3c17e Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Wed, 3 Apr 2019 17:48:42 +0800 Subject: [PATCH 022/112] =?UTF-8?q?1=E3=80=81=E5=A2=9E=E5=8A=A0Timepicker?= =?UTF-8?q?=20=E7=9A=84=E7=AE=AD=E5=A4=B4=E6=BB=9A=E5=8A=A8=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=202=E3=80=81=E5=A2=9E=E5=8A=A0=20Timepicker=20?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E8=8C=83=E5=9B=B4=E9=80=89=E6=8B=A9=203?= =?UTF-8?q?=E3=80=81=E8=B0=83=E6=95=B4=20Timepicker=20=E7=9A=84=E6=96=B0?= =?UTF-8?q?=20UI=204=E3=80=81=E6=96=B0=E5=A2=9E=20Datepicker=20=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=AE=B5=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20timeperiod=205=E3=80=81=E8=B0=83=E6=95=B4=20Datepicker=20?= =?UTF-8?q?=E5=B8=A6=E6=97=B6=E9=97=B4=E9=80=89=E6=8B=A9=E7=9A=84=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=20UI=20=E5=8F=8A=E9=80=BB=E8=BE=91=206=E3=80=81?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20Datepicker=20=E6=97=A5=E6=9C=9F=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E8=8C=83=E5=9B=B4=E9=80=89=E6=8B=A9=E7=9A=84=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=20UI=20=E5=8F=8A=E9=80=BB=E8=BE=91=207=E3=80=81?= =?UTF-8?q?=E5=85=B6=E5=AE=83=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/date-picker/BasePicker.js | 45 ++-- components/date-picker/Calender.js | 15 +- components/date-picker/DatePanel.js | 85 +++---- components/date-picker/DatePicker.js | 10 + components/date-picker/DateRangePanel.js | 160 +++++++------ components/date-picker/Time.js | 211 ++++++++++++----- components/date-picker/TimePeriodPanel.js | 41 ++++ components/date-picker/TimePicker.js | 1 - components/date-picker/TimeRangePanel.js | 16 +- components/date-picker/Type.js | 3 + components/date-picker/constants.js | 3 +- components/date-picker/dateUtil.js | 10 +- components/date-picker/style/index.scss | 269 ++++++++++++++++------ docs/zh-CN/date-picker.md | 20 +- docs/zh-CN/time-picker.md | 4 +- 15 files changed, 599 insertions(+), 294 deletions(-) create mode 100644 components/date-picker/TimePeriodPanel.js diff --git a/components/date-picker/BasePicker.js b/components/date-picker/BasePicker.js index 8cf244013..a5df7ee8f 100644 --- a/components/date-picker/BasePicker.js +++ b/components/date-picker/BasePicker.js @@ -7,6 +7,7 @@ import PropTypes from 'prop-types' import DatePickerType from './Type' import {startOfDay, endOfDay, parse, startOfWeek, endOfWeek, dateFormat} from './dateUtil' +import { addHours } from 'date-fns' class BasePicker extends Component { inputRoot = null input = null @@ -18,8 +19,8 @@ class BasePicker extends Component { style: {}, date: null, isFocus: false, - text: '', - rText: '', + // input 框内的显示的时间内容 + texts: ['', ''], placeholder: '', format: '' } @@ -60,8 +61,6 @@ class BasePicker extends Component { _parseProps (props, callback) { let {value, showTime, type, format, localeDatas} = props format = format || FORMATS[type] - // let text = formatterDate(type, value, format, showTime) - // let rText = text let date = new Date() // 当前时间 let noText = false /** @@ -81,17 +80,19 @@ class BasePicker extends Component { date = value } - if (type.indexOf('range') !== -1) { + if (type.indexOf('range') !== -1 || type === 'timeperiod') { if (value instanceof Date || !value) { - date = {startDate: startOfDay(date), endDate: endOfDay(date)} + // 如果为时间段选择,则取默认的第一个范围 + date = {startDate: startOfDay(date), endDate: type === 'timeperiod' ? addHours(startOfDay(date), 4) : endOfDay(date)} } if (value && value.start && value.end) { date = {startDate: value.start, endDate: value.end} } } + const leftText = noText ? '' : formatterDate(type, date.startDate || date, format, showTime, localeDatas) + const rightText = noText ? '' : formatterDate(type, date.endDate || date, format, showTime, localeDatas) this.setState({ - text: noText ? '' : formatterDate(type, date.startDate || date, format, showTime, localeDatas), - rText: noText ? '' : formatterDate(type, date.endDate || date, format, showTime, localeDatas), + texts: [leftText, rightText], date, placeholder: localeDatas.datePicker.placeholders[props.type] || localeDatas.datePicker.placeholder, format @@ -127,7 +128,6 @@ class BasePicker extends Component { _top = top - _h + _st } this.setState({ - // value: this.props.placeholder ? '' : formatterDate(type, date, this.state.format, showTime), style: { position: 'absolute', left: left, @@ -143,8 +143,7 @@ class BasePicker extends Component { const {format} = this.state this.setState({ date, - text: formatterDate(type, date.startDate || date, format, showTime, localeDatas), - rText: date.endDate && formatterDate(type, date.endDate, format, showTime, localeDatas), + texts: [formatterDate(type, date.startDate || date, format, showTime, localeDatas), formatterDate(type, date.endDate, format, showTime, localeDatas)], showPanel, isFocus: false }, () => { @@ -166,6 +165,8 @@ class BasePicker extends Component { if (startDate && endDate) { if (type === 'weekrange') { onChange({start: startOfWeek(startDate, _weekOffset), end: endOfWeek(endDate, _weekOffset)}) + } else if (type === 'timerange' || type === 'timeperiod') { + onChange({start: startDate, end: endDate}) } else { onChange({start: startOfDay(startDate), end: endOfDay(endDate)}) } @@ -180,8 +181,7 @@ class BasePicker extends Component { onlyTime && (format = FORMATS['time']) this.setState({ date: date, - text: formatterDate(type, date.startDate || date, format, showTime, localeDatas), - rText: date.endDate && formatterDate(type, date.endDate, format, showTime, localeDatas), + texts: [formatterDate(type, date.startDate || date, format, showTime, localeDatas), formatterDate(type, date.endDate, format, showTime, localeDatas)], showPanel: false, isFocus: false }) @@ -213,8 +213,7 @@ class BasePicker extends Component { const tar = e.target if (tar.className.indexOf('clear') !== -1) { this.setState({ - text: '', - rText: '', + texts: ['', ''], showPanel: false }) return false @@ -254,7 +253,7 @@ class BasePicker extends Component { if (onChange) { onChange(null) } - this.setState({text: '', isFocus: false}) + this.setState({texts: ['', ''], isFocus: false}) } _icon () { const {isFocus} = this.state @@ -283,25 +282,27 @@ class BasePicker extends Component { ) return ( <div className={_cls}> - {this._input(this.state.text, 'input')} + {this._input(this.state.texts[0], 'input')} <span>{localeDatas.datePicker.to}</span> - {this._input(this.state.rText, 'rInput')} + {this._input(this.state.texts[1], 'rInput')} {this._icon()} </div> ) } renderNormalInput () { const { - disabled + disabled, + showTime } = this.props const _cls = classNames( 'hi-datepicker__input', 'hi-datepicker__input--normal', - disabled && 'hi-datepicker__input--disabled' + disabled && 'hi-datepicker__input--disabled', + showTime && 'hi-datepicker__input--middle' ) return ( <div className={_cls}> - {this._input(this.state.text, 'input')} + {this._input(this.state.texts[0], 'input')} {this._icon()} </div> ) @@ -311,7 +312,7 @@ class BasePicker extends Component { return ( <span ref={el => { this.inputRoot = el }} className='hi-datepicker__input-root'> { - type.indexOf('range') !== -1 ? this.renderRangeInput() : this.renderNormalInput() + (type.indexOf('range') !== -1 || type === 'timeperiod') ? this.renderRangeInput() : this.renderNormalInput() } { this.state.showPanel ? ( diff --git a/components/date-picker/Calender.js b/components/date-picker/Calender.js index 658b1ce84..a697a68fa 100644 --- a/components/date-picker/Calender.js +++ b/components/date-picker/Calender.js @@ -11,8 +11,6 @@ import { isSameDay, compareAsc, addMonths, - startOfDay, - endOfDay, isToday } from './dateUtil' import {DAY_MILLISECONDS} from './constants' @@ -78,7 +76,10 @@ class Calender extends Component { col.type = 'next' } } - if (isToday(new Date(time))) { + if (isSameDay(_date, time)) { + col.type = 'current' + } + if (isToday(time)) { col.type = 'today' } if (type === 'daterange' || type === 'weekrange') { @@ -158,10 +159,10 @@ class Calender extends Component { if (range.selecting) { if (range.startDate > newDate) { range.selecting = false - onPick(startOfDay(newDate), endOfDay(range.startDate)) + onPick(newDate, range.startDate) } else { range.selecting = false - onPick(startOfDay(range.startDate), endOfDay(newDate)) + onPick(range.startDate, newDate) } } else { range.selecting = true @@ -201,6 +202,9 @@ class Calender extends Component { case 'today': this.props.type !== 'week' && _class.push('today') break + case 'current': + _class.push('current') + break default: _class.push(td.type) break @@ -224,7 +228,6 @@ class Calender extends Component { const {type} = this.props if ((type === 'week' || type === 'weekrange') && this.weekNum !== num) { this.weekNum = num - console.log(num, this.props.type, this.weekNum) } } render () { diff --git a/components/date-picker/DatePanel.js b/components/date-picker/DatePanel.js index 2ae9d7acd..35226fb51 100644 --- a/components/date-picker/DatePanel.js +++ b/components/date-picker/DatePanel.js @@ -5,16 +5,17 @@ import TimePanel from './TimePanel' import Icon from '../icon' import classNames from 'classnames' import Provider from '../context' +import TimePeriodPanel from './TimePeriodPanel' +import {dateFormat, parse, addHours} from './dateUtil' class DatePanel extends Component { constructor (props) { super(props) this.state = { - date: new Date(props.date), + date: props.date, currentView: props.type, yearData: [], - monthData: [], - tempDate: new Date(props.date) + monthData: [] } } componentWillReceiveProps (nextProps) { @@ -182,16 +183,20 @@ class DatePanel extends Component { } } onDatePick (date) { - const {showTime} = this.props + const {type} = this.props const {hours, minutes, seconds} = deconstructDate(this.state.date) - date.setHours(hours, minutes, seconds) - if (showTime) { - // 又改成了实时生效 - this.setState({date, currentView: 'time'}, () => { this.props.onPick(date, true) }) + if (type === 'timeperiod') { + this.props.onPick({startDate: date, endDate: addHours(this.state.date, 4)}, true) } else { - this.props.onPick(date) + date.setHours(hours, minutes, seconds) + this.props.onPick(date, true) } } + onTimePeriodPick (periodS, periodE) { + const currentDate = dateFormat(this.state.date, 'YYYY-MM-DD') + parse(`${currentDate} ${periodS}`) + this.props.onPick({startDate: parse(`${currentDate} ${periodS}`), endDate: parse(`${currentDate} ${periodE}`)}, true) + } onTimePick (date, bol) { this.setState({ date @@ -216,6 +221,7 @@ class DatePanel extends Component { let component = null switch (currentView) { case 'date': + case 'timeperiod': component = (<Calender date={date} weekOffset={weekOffset} @@ -243,17 +249,6 @@ class DatePanel extends Component { onPick={this.monthPick.bind(this)} />) break - case 'time': - component = ( - <TimePanel - {...this.props} - onPick={this.onTimePick.bind(this)} - date={date} - timeConfirm={this.timeConfirm.bind(this)} - timeCancel={this.timeCancel.bind(this)} - /> - ) - break default: component = (<Calender {...this.props} @@ -266,45 +261,25 @@ class DatePanel extends Component { } return component } - renderTimeHeader () { - const { - localeDatas - } = this.props - return ( - <div className='hi-datepicker__time-header'> - <span onClick={() => this.setState({currentView: 'date'})} className={this.state.currentView === 'date' ? 'hi-datepicker__time-header--active' : ''}>{localeDatas.datePicker.dateChoose}</span> - <em /> - <span onClick={() => this.setState({currentView: 'time'})} className={this.state.currentView === 'time' ? 'hi-datepicker__time-header--active' : ''}>{localeDatas.datePicker.timeChoose}</span> - </div> - ) - } - renderTimeFooter () { - return ( - <div - className='hi-datepicker__time-footer' - onClick={() => this.props.timeConfirm(this.state.date)} - > - ok - </div> - ) - } render () { const {date, currentView} = this.state - const {theme} = this.props + const {theme, showTime, type} = this.props const _c = classNames( 'hi-datepicker', theme && 'theme__' + theme ) + const bodyCls = classNames( + 'hi-datepicker__body', + showTime && 'hi-datepicker__body--hastime', + type === 'timeperiod' && 'hi-datepicker__body--period' + ) return ( <div style={this.props.style} className={_c} > - <div className='hi-datepicker__body'> - { - this.props.showTime && this.renderTimeHeader() - } + <div className={bodyCls}> <div className='hi-datepicker__panel hi-datepicker__panel--left'> {currentView !== 'time' && this.renderHeader(currentView, date)} <div className={`hi-datepicker__calender-container hi-datepicker__calender-container--${currentView}`}> @@ -312,7 +287,21 @@ class DatePanel extends Component { </div> </div> { - this.props.showTime && this.renderTimeFooter() + showTime && <TimePanel + {...this.props} + onPick={this.onTimePick.bind(this)} + date={date} + timeConfirm={this.timeConfirm.bind(this)} + timeCancel={this.timeCancel.bind(this)} + /> + } + { + type === 'timeperiod' && ( + <TimePeriodPanel + onTimePeriodPick={this.onTimePeriodPick.bind(this)} + date={date} + /> + ) } </div> </div> diff --git a/components/date-picker/DatePicker.js b/components/date-picker/DatePicker.js index bd2d7b34f..ecd1f5f73 100644 --- a/components/date-picker/DatePicker.js +++ b/components/date-picker/DatePicker.js @@ -29,6 +29,16 @@ class DatePicker extends BasePicker { /> ) break + case 'timeperiod': + component = ( + <DatePanel + {...props} + date={state.date.startDate} + onPick={this.onPick.bind(this)} + style={state.style} + /> + ) + break case 'daterange': component = ( <DateRangePanel diff --git a/components/date-picker/DateRangePanel.js b/components/date-picker/DateRangePanel.js index e04c322ee..71bed1551 100644 --- a/components/date-picker/DateRangePanel.js +++ b/components/date-picker/DateRangePanel.js @@ -2,10 +2,11 @@ import React, {Component} from 'react' import Calender from './Calender' import {deconstructDate, nextMonth} from './util' import {DAY_MILLISECONDS} from './constants' -import TimePanel from './TimePanel' import Icon from '../icon' import classNames from 'classnames' import Provider from '../context' +import {dateFormat} from './dateUtil' +import TimeRangePanel from './TimeRangePanel' class DatePanel extends Component { constructor (props) { @@ -25,6 +26,7 @@ class DatePanel extends Component { endDate, selecting: false } + this.maskRef = React.createRef() this.state = { date: leftDate, currentView: props.type || 'date', @@ -35,8 +37,7 @@ class DatePanel extends Component { range, leftDate, rightDate, - leftView: props.type, - rightView: props.type, + showMask: false, disableArrow: { month: false, year: false @@ -205,7 +206,7 @@ class DatePanel extends Component { range }) if (endDate) { - onPick(range) + onPick(range, true) } // if (endDate && !showTime) { // onPick(range) @@ -275,107 +276,114 @@ class DatePanel extends Component { timeCancel () { } - renderTimeHeader (flag) { - const { - localeDatas - } = this.props - return ( - <div className='hi-datepicker__time-header'> - <span onClick={() => this.setState({[flag === 'left' ? 'leftView' : 'rightView']: 'date'})}>{localeDatas.datePicker.dateChoose}</span> - <em /> - <span onClick={() => this.setState({[flag === 'left' ? 'leftView' : 'rightView']: 'time'})}>{localeDatas.datePicker.timeChoose}</span> - </div> + getRangeDateStr () { + let {leftDate, rightDate, showMask} = this.state + const format = 'HH:mm:ss' + const cls = classNames( + showMask && 'hi-datepicker__time-text' ) - } - renderTimeFooter () { return ( - <div - className='hi-datepicker__time-footer' - onClick={() => { - this.props.timeConfirm(this.state.range) - // this.props.onPick(this.state.date) - }} - > - ok - </div> + <span className={cls}> + {`${dateFormat(leftDate, format)} - ${dateFormat(rightDate, format)}`} + </span> ) } + selectTimeEvent () { + this.setState({ + showMask: !this.state.showMask + }) + } render () { - let {minDate, maxDate, currentView, range, leftDate, rightDate, leftView, rightView} = this.state + let {minDate, maxDate, currentView, range, leftDate, rightDate, showMask} = this.state // const rightDate = nextMonth(leftDate) - const {shortcuts, theme} = this.props + const {shortcuts, theme, showTime, date} = this.props const _c = classNames( 'hi-datepicker', theme && 'theme__' + theme ) + const bodyCls = classNames( + 'hi-datepicker__body', + 'hi-datepicker__body--range', + shortcuts && 'hi-datepicker__body--shortcuts' + ) return ( <div style={this.props.style} className={_c} > - { - shortcuts && this.renderShortcut(shortcuts) - } - <div className='hi-datepicker__body hi-datepicker__body--range'> + <div className={bodyCls}> + { + shortcuts && this.renderShortcut(shortcuts) + } <div className='hi-datepicker__panel hi-datepicker__panel--left'> { - this.props.showTime && this.renderTimeHeader('left') - } - { - leftView !== 'time' && this.renderHeader(currentView, leftDate, 'left') + this.renderHeader(currentView, leftDate, 'left') } <div className={`hi-datepicker__calender-container hi-datepicker__calender-container--${currentView}`}> - { - leftView === 'time' ? <TimePanel - {...this.props} - onPick={this.onTimePick.bind(this, 'leftDate')} - date={leftDate} - timeConfirm={this.timeConfirm.bind(this)} - timeCancel={this.timeCancel.bind(this)} - /> : <Calender - date={leftDate} - range={range} - type={currentView} - minDate={minDate} - maxDate={maxDate} - onPick={this.pick.bind(this)} - mouseMove={this.onMouseMoveHandler.bind(this)} - /> - } + <Calender + date={leftDate} + range={range} + type={currentView} + minDate={minDate} + maxDate={maxDate} + onPick={this.pick.bind(this)} + mouseMove={this.onMouseMoveHandler.bind(this)} + /> </div> </div> <div className='hi-datepicker__panel hi-datepicker__panel--right'> { - this.props.showTime && this.renderTimeHeader('right') - } - { - rightView !== 'time' && this.renderHeader(currentView, rightDate, 'right') + this.renderHeader(currentView, rightDate, 'right') } <div className={`hi-datepicker__calender-container hi-datepicker__calender-container--${currentView}`}> - { - rightView === 'time' ? <TimePanel - {...this.props} - onPick={this.onTimePick.bind(this, 'rightDate')} - date={rightDate} - timeConfirm={this.timeConfirm.bind(this)} - timeCancel={this.timeCancel.bind(this)} - /> : <Calender - date={rightDate} - range={range} - minDate={minDate} - maxDate={maxDate} - type={currentView} - onPick={this.pick.bind(this)} - mouseMove={this.onMouseMoveHandler.bind(this)} - /> - } + <Calender + date={rightDate} + range={range} + minDate={minDate} + maxDate={maxDate} + type={currentView} + onPick={this.pick.bind(this)} + mouseMove={this.onMouseMoveHandler.bind(this)} + /> </div> </div> - { - this.props.showTime && this.renderTimeFooter() - } </div> + { + showTime && ( + <div + className='hi-datepicker__footer' + onClick={this.selectTimeEvent.bind(this)} + > + {this.getRangeDateStr()} + </div> + ) + } + { + showMask && ( + <div className='hi-datepicker__mask' ref={this.maskRef} onClick={() => { this.setState({showMask: false}) }} /> + ) + } + { + showMask && ( + <TimeRangePanel + {...this.props} + style={{ + position: 'absolute', + top: 5, + left: 89 + }} + date={date} + onPick={(d, r) => { + this.setState({ + leftDate: d.startDate, + rightDate: d.endDate + }) + this.props.onPick(d, r) + }} + /> + ) + } </div> ) } diff --git a/components/date-picker/Time.js b/components/date-picker/Time.js index 9c61c79e4..73f1da075 100644 --- a/components/date-picker/Time.js +++ b/components/date-picker/Time.js @@ -1,11 +1,8 @@ import React, {Component} from 'react' import {deconstructDate} from './util' - +import Icon from '../icon' +import classNames from 'classnames' export default class TimePanel extends Component { - hoursList = null - minutesList = null - secondsList = null - activeEl = null constructor (props) { super(props) this.state = { @@ -14,10 +11,21 @@ export default class TimePanel extends Component { hours: 0, minutes: 0, seconds: 0 - } + }, + hoursArrow: false, + minutesArrow: false, + secondsArrow: false } + this.hoursList = null + this.minutesList = null + this.secondsList = null this.liPrefix = props.onlyTime ? [1, 2, 3] : [1, 2, 3, 4] this.liSuffix = props.onlyTime ? [1, 2, 3] : [1, 2, 3, 4] + this.hoursScrollEvent = this.scrollEvent.bind(this, 'hours') + this.minutesScrollEvent = this.scrollEvent.bind(this, 'minutes') + this.secondsScrollEvent = this.scrollEvent.bind(this, 'seconds') + this.pageUpRef = React.createRef() + this.pageDownRef = React.createRef() } range (num) { let arr = [] @@ -30,9 +38,9 @@ export default class TimePanel extends Component { this.completeScrollTop() } addListener () { - this.hoursList && this.hoursList.addEventListener('scroll', this.scrollEvent.bind(this, 'hours')) - this.minutesList && this.minutesList.addEventListener('scroll', this.scrollEvent.bind(this, 'minutes')) - this.secondsList && this.secondsList.addEventListener('scroll', this.scrollEvent.bind(this, 'seconds')) + this.hoursList && this.hoursList.addEventListener('scroll', this.hoursScrollEvent) + this.minutesList && this.minutesList.addEventListener('scroll', this.minutesScrollEvent) + this.secondsList && this.secondsList.addEventListener('scroll', this.secondsScrollEvent) } scrollEvent (type, e) { const st = e.target.scrollTop @@ -51,9 +59,9 @@ export default class TimePanel extends Component { this.props.onPick(date, true) } componentWillUnmount () { - this.hoursList.removeEventListener('scroll', this.scrollEvent.bind(this, 'hours')) - this.minutesList.removeEventListener('scroll', this.scrollEvent.bind(this, 'minutes')) - this.secondsList.removeEventListener('scroll', this.scrollEvent.bind(this, 'seconds')) + this.hoursList.removeEventListener('scroll', this.hoursScrollEvent) + this.minutesList.removeEventListener('scroll', this.minutesScrollEvent) + this.secondsList.removeEventListener('scroll', this.secondsScrollEvent) } completeScrollTop () { const {date} = this.state @@ -68,9 +76,7 @@ export default class TimePanel extends Component { this.addListener() }, 200) } - componentWillReceiveProps (nextProps) { - // this.completeScrollTop() - } + clickEvent (flag, e) { const li = e.target if (!li.innerText) return @@ -101,13 +107,62 @@ export default class TimePanel extends Component { return <li /> } } - mouseOverEvent (e) { - // this.activeEl && (this.activeEl.style.backgroundColor = 'transparent') - this.activeEl = e.target - // e.target.style.background = 'rgba(66,132,245,0.08)' + scrollPageUp (type) { + const {date} = this.state + let cHours = date.getHours() + let cMinutes = date.getMinutes() + let cSeconds = date.getSeconds() + if (type === 'hours') { + cHours = cHours === 0 ? 0 : cHours - 1 + this.hoursList.scrollTop = cHours * 32 + } + if (type === 'minutes') { + cMinutes = cMinutes === 0 ? 0 : cMinutes - 1 + this.minutesList.scrollTop = cMinutes * 32 + } + if (type === 'seconds') { + cSeconds = cSeconds === 0 ? 0 : cSeconds - 1 + this.secondsList.scrollTop = cSeconds * 32 + } + date.setHours(cHours, cMinutes, cSeconds) + this.setState({date}) } - render () { + scrollPageDown (type) { const {date} = this.state + let cHours = date.getHours() + let cMinutes = date.getMinutes() + let cSeconds = date.getSeconds() + if (type === 'hours') { + cHours = cHours === 23 ? 23 : cHours + 1 + this.hoursList.scrollTop = cHours * 32 + } + if (type === 'minutes') { + cMinutes = cMinutes === 59 ? 59 : cMinutes + 1 + this.minutesList.scrollTop = cMinutes * 32 + } + if (type === 'seconds') { + cSeconds = cSeconds === 59 ? 59 : cSeconds + 1 + this.secondsList.scrollTop = cSeconds * 32 + } + date.setHours(cHours, cMinutes, cSeconds) + this.setState({date}, () => { + this.props.onPick(date, true) + }) + } + renderArrow (type) { + return ( + <React.Fragment> + <span className='hi-timepicker__page-turn' onClick={() => this.scrollPageUp(type)}> + <Icon name='up' /> + </span> + <span className='hi-timepicker__page-turn' onClick={() => this.scrollPageDown(type)}> + <Icon name='down' /> + </span> + </React.Fragment> + ) + } + render () { + const {date, hoursArrow, minutesArrow, secondsArrow} = this.state const {hours, minutes, seconds} = deconstructDate(date) this.liPrefix = this.liPrefix.map((item, index) => { return <li className='hi-timepicker__item hi-timepikcer__item--empty' key={'pre' + index} /> @@ -123,58 +178,86 @@ export default class TimePanel extends Component { <span className='hi-timepicker__mark'>秒</span> </div> <div className='hi-timepicker__timebody'> - <ul - ref={el => { this.hoursList = el }} - className='hi-timepicker__list' - onClick={this.clickEvent.bind(this, 'hours')} - onMouseEnter={this.mouseOverEvent.bind(this)} + <div + className='hi-timepicker__list-container' + onMouseEnter={() => this.setState({hoursArrow: true})} + onMouseLeave={() => this.setState({hoursArrow: false})} > - {this.liPrefix} - { - this.range(24).map((m, n) => { - return ( - <li - key={n} - className={n === hours ? 'hi-timepicker__item hi-timepicker__item--current' : 'hi-timepicker__item'} - >{m}</li> - ) - }) - } - {this.liSuffix} - </ul> - <ul - className='hi-timepicker__list' - ref={el => { this.minutesList = el }} - onClick={this.clickEvent.bind(this, 'minutes')} - onMouseEnter={this.mouseOverEvent.bind(this)} + <ul + ref={el => { this.hoursList = el }} + className='hi-timepicker__list' + onClick={this.clickEvent.bind(this, 'hours')} + > + {this.liPrefix} + { + this.range(24).map((m, n) => { + const _class = classNames( + 'hi-timepicker__item', + n === hours && 'hi-timepicker__item--current' + ) + return ( + <li + key={n} + className={_class} + >{m}</li> + ) + }) + } + {this.liSuffix} + </ul> + {hoursArrow && this.renderArrow('hours')} + </div> + <div + className='hi-timepicker__list-container' + onMouseEnter={() => this.setState({minutesArrow: true})} + onMouseLeave={() => this.setState({minutesArrow: false})} > - {this.liPrefix} - { - this.range(60).map((m, n) => { - return <li key={n} className={n === minutes ? 'hi-timepicker__item hi-timepicker__item--current' : 'hi-timepicker__item'}>{m}</li> - }) - } - {this.liSuffix} - </ul> - <ul - ref={el => { this.secondsList = el }} - className='hi-timepicker__list' - onClick={this.clickEvent.bind(this, 'seconds')} - onMouseEnter={this.mouseOverEvent.bind(this)} + <ul + className='hi-timepicker__list' + ref={el => { this.minutesList = el }} + onClick={this.clickEvent.bind(this, 'minutes')} + > + {this.liPrefix} + { + this.range(60).map((m, n) => { + return <li key={n} className={n === minutes ? 'hi-timepicker__item hi-timepicker__item--current' : 'hi-timepicker__item'}>{m}</li> + }) + } + {this.liSuffix} + </ul> + {minutesArrow && this.renderArrow('minutes')} + </div> + <div + className='hi-timepicker__list-container' + onMouseEnter={() => this.setState({secondsArrow: true})} + onMouseLeave={() => this.setState({secondsArrow: false})} > - {this.liPrefix} - { - this.range(60).map((m, n) => { - return <li key={n} className={n === seconds ? 'hi-timepicker__item hi-timepicker__item--current' : 'hi-timepicker__item'}>{m}</li> - }) - } - {this.liSuffix} - </ul> + <ul + ref={el => { this.secondsList = el }} + className='hi-timepicker__list' + onClick={this.clickEvent.bind(this, 'seconds')} + > + {this.liPrefix} + { + this.range(60).map((m, n) => { + return <li key={n} className={n === seconds ? 'hi-timepicker__item hi-timepicker__item--current' : 'hi-timepicker__item'}>{m}</li> + }) + } + {this.liSuffix} + </ul> + {secondsArrow && this.renderArrow('seconds')} + </div> <div className='hi-timepicker__current-line' style={{top: this.props.onlyTime ? 108 : 140}} > {/* <span>{hours}</span> <span>{minutes}</span> <span>{seconds}</span> */} </div> + {/* <span className='hi-timepicker__page-turn' ref={this.pageUpRef}> + <Icon name='up' /> + </span> + <span className='hi-timepicker__page-turn' ref={this.pageDownRef}> + <Icon name='down' /> + </span> */} </div> </div> ) diff --git a/components/date-picker/TimePeriodPanel.js b/components/date-picker/TimePeriodPanel.js new file mode 100644 index 000000000..47052509e --- /dev/null +++ b/components/date-picker/TimePeriodPanel.js @@ -0,0 +1,41 @@ +import React, {Component} from 'react' +import classNames from 'classnames' +import {getHours} from './dateUtil' +export default class TimePeriodPanel extends Component { + render () { + const list = [ + '00:00 ~ 04:00', + '04:00 ~ 08:00', + '08:00 ~ 12:00', + '12:00 ~ 16:00', + '16:00 ~ 20:00', + '20:00 ~ 24:00' + ] + console.log(this.props.date) + return <div + className='hi-datepicker__time-period' + > + <div className='hi-datepicker__period-header'>时间段</div> + <div className='hi-datepicker__period-body'> + <ul className='hi-datepicker__period-list'> + { + list.map((item, index) => { + const cls = classNames( + 'hi-datepicker__period-item', + getHours(this.props.date) / 4 === index && 'hi-datepicker__period-item--active' + ) + return <li + className={cls} + key={index} + onClick={(e) => { + const ts = item.split(' ~ ') + this.props.onTimePeriodPick(ts[0], ts[1]) + }} + >{item}</li> + }) + } + </ul> + </div> + </div> + } +} diff --git a/components/date-picker/TimePicker.js b/components/date-picker/TimePicker.js index abaaa190a..88465c754 100644 --- a/components/date-picker/TimePicker.js +++ b/components/date-picker/TimePicker.js @@ -20,7 +20,6 @@ class TimePicker extends BasePicker { disabled: false } initPanel (state, props) { - console.log(11, state.date) return ( props.type === 'time' ? <TimePanel diff --git a/components/date-picker/TimeRangePanel.js b/components/date-picker/TimeRangePanel.js index de081a24c..7b967d6fa 100644 --- a/components/date-picker/TimeRangePanel.js +++ b/components/date-picker/TimeRangePanel.js @@ -5,16 +5,20 @@ import Provider from '../context' class TimeRangePanel extends Component { constructor (props) { super(props) - console.log(props.date) this.state = { date: props.date, style: props.style } } - onTimePick (flag, date, bol) { - const {startDate, endDate} = this.props - - this.props.onPick({startDate: flag === 'left' ? date : startDate, endDate: flag === 'right' ? date : endDate}, bol) + onTimePick (flag, _date, bol) { + const {date} = this.props + const sd = flag === 'left' ? _date : date.startDate + const ed = flag === 'right' ? _date : date.endDate + let r = { + startDate: sd, + endDate: ed + } + this.props.onPick(r, bol) } render () { const {startDate, endDate} = this.props.date @@ -22,7 +26,7 @@ class TimeRangePanel extends Component { <div className='hi-timepicker hi-timepicker--timerange' style={this.props.style}> <Time date={startDate} onPick={this.onTimePick.bind(this, 'left')} onlyTime /> <div className='hi-timepicker__split' /> - <Time date={endDate} onPick={this.onTimePick.bind(this, 'right')} onlyTime /> + <Time date={endDate} onPick={this.onTimePick.bind(this, 'right')} onlyTime disableTime={startDate} /> </div> ) } diff --git a/components/date-picker/Type.js b/components/date-picker/Type.js index 2c7edec51..cb923071d 100644 --- a/components/date-picker/Type.js +++ b/components/date-picker/Type.js @@ -17,6 +17,9 @@ export default { WeekRange: 'weekrange', // 时间范围选择 TimeRange: 'timerange', + // 时间段选择 + TimePeriod: 'timeperiod', // 日期时间范围选择 DateTimeRange: 'datetimerange' + } diff --git a/components/date-picker/constants.js b/components/date-picker/constants.js index 15faa20a9..87918e02f 100644 --- a/components/date-picker/constants.js +++ b/components/date-picker/constants.js @@ -13,7 +13,8 @@ export const FORMATS = { time: 'HH:mm:ss', daterange: 'YYYY-MM-DD', week: 'YYYY', - weekrange: 'YYYY' + weekrange: 'YYYY', + timeperiod: 'YYYY-MM-DD HH:mm:ss' } export const isVaildDate = (date) => { diff --git a/components/date-picker/dateUtil.js b/components/date-picker/dateUtil.js index 18b666915..b4e8d62b9 100644 --- a/components/date-picker/dateUtil.js +++ b/components/date-picker/dateUtil.js @@ -16,6 +16,10 @@ import isSameMonth from 'date-fns/is_same_month' import getYear from 'date-fns/get_year' import getMonth from 'date-fns/get_month' import isToday from 'date-fns/is_today' +import getHours from 'date-fns/get_hours' +import getMinutes from 'date-fns/get_minutes' +import getSeconds from 'date-fns/get_seconds' +import addHours from 'date-fns/add_hours' export { getDaysInMonth, // 获取当月的天数 subMonths, // 月份减法 @@ -34,5 +38,9 @@ export { isSameMonth, // 是否是同一个月 getYear, getMonth, - isToday + isToday, + getHours, + getMinutes, + getSeconds, + addHours } diff --git a/components/date-picker/style/index.scss b/components/date-picker/style/index.scss index 9ea788da0..792a39fd9 100644 --- a/components/date-picker/style/index.scss +++ b/components/date-picker/style/index.scss @@ -4,9 +4,11 @@ $basic-color: #4284f5 !default; .hi-datepicker { position: relative; - display: flex; + // display: flex; + background: #fff; z-index: 1060; font-size: 14px; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.12); &__input-root { display: inline-block; @@ -21,13 +23,14 @@ $basic-color: #4284f5 !default; display: flex; align-items: center; justify-content: space-around; - + box-sizing: border-box; + &:hover { border: 1px solid #4284f5; } &--range { - width: 320px; + width: 400px; input { flex: 0 1 40%; @@ -40,8 +43,9 @@ $basic-color: #4284f5 !default; border: 0; min-width: 0; min-height: 0; - height: 30px; + height: 28px; padding-left: 10px; + flex: 1; &::-webkit-input-placeholder { color: #ccc; @@ -56,11 +60,15 @@ $basic-color: #4284f5 !default; color: rgba(44, 48, 78, 0.2); } } + + &--middle { + width: 240px; + } } &__input-icon { height: 100%; - flex: 1 0 32px; + flex: 0 0 32px; line-height: 32px; text-align: center; box-sizing: border-box; @@ -88,91 +96,183 @@ $basic-color: #4284f5 !default; &-text { font-weight: bold; + cursor: pointer; } } &__body { background: #fff; border: 1px solid #d1dbe5; - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15); border-radius: 2px; box-sizing: border-box; width: 288px; - &--range { - width: 578px; + &--hastime { + width: 468px; + display: flex; - .hi-datepicker__panel { - width: 50%; - float: left; + .hi-timepicker { + box-shadow: none; + border-left: 1px solid #f2f2f2; } } - } - &__time-header { - border-bottom: 1px solid rgba(128, 128, 128, 0.2); - padding: 15px; - display: flex; - justify-content: space-around; - font-size: 14px; - - span { - flex: 0 0 50%; - text-align: center; - color: rgba(153, 153, 153, 1); + &--period { + display: flex; + width: 432px; } - em { - flex: 0 0 1px; - background: rgba(128, 128, 128, 0.2); + &--range { + width: 578px; + display: flex; + border: none; + + .hi-datepicker__panel { + flex: 1; + } } - &--active { - color: $basic-color; + &--shortcuts { + width: 683px; } } - &__time-footer { - clear: both; + &__time-period { + width: 144px; + border-left: 1px solid #f2f2f2; text-align: center; - height: 40px; - line-height: 40px; - cursor: pointer; - color: $basic-color; - border-top: 1px solid #f2f2f2; } - .hi-timepicker { - width: 252px; + &__period-header { + line-height: 48px; + height: 48px; + font-weight: 600; + border-bottom: 1px solid #f2f2f2; + box-sizing: border-box; + } - - .hi-timepicker__timebody { - height: 265px; - } + &__period-body { + } - &__list { - height: 265px; - width: 104px; + &__period-list { + list-style: none; + margin: 0; + padding: 0; + margin-top: 8px; + } - &:nth-child(2) { - left: 84px; - } + &__period-item { + padding: 6px; + cursor: pointer; - &:nth-child(3) { - left: 168px; - width: 84px; - } + &:hover { + background: rgba(240, 246, 255, 1); } - &__item { - width: 84px; + &--active { + color: #4284f5; + } + } - &:last-child { - height: 38px; - } + &__footer { + height: 48px; + border-top: 1px solid #f2f2f2; + box-sizing: border-box; + line-height: 48px; + text-align: center; + cursor: pointer; + position: relative; + + &:hover { + color: #4284f5; } } + &__mask { + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.4); + position: absolute; + top: 0; + left: 0; + } + + &__time-text { + position: absolute; + width: 148px; + height: 32px; + border-radius: 2px; + left: 50%; + top: 8px; + transform: translateX(-50%); + background: #fff; + z-index: 2; + line-height: 32px; + } + // &__time-header { + // border-bottom: 1px solid rgba(128, 128, 128, 0.2); + // padding: 15px; + // display: flex; + // justify-content: space-around; + // font-size: 14px; + + // span { + // flex: 0 0 50%; + // text-align: center; + // color: rgba(153, 153, 153, 1); + // } + + // em { + // flex: 0 0 1px; + // background: rgba(128, 128, 128, 0.2); + // } + + // &--active { + // color: $basic-color; + // } + // } + + // &__time-footer { + // clear: both; + // text-align: center; + // height: 40px; + // line-height: 40px; + // cursor: pointer; + // color: $basic-color; + // border-top: 1px solid #f2f2f2; + // } + + // .hi-timepicker { + // width: 252px; + + + // .hi-timepicker__timebody { + // height: 265px; + // } + + // &__list { + // height: 265px; + // width: 104px; + + // &:nth-child(2) { + // left: 84px; + // } + + // &:nth-child(3) { + // left: 168px; + // width: 84px; + // } + // } + + // &__item { + // width: 84px; + + // &:last-child { + // height: 38px; + // } + // } + // } + &__calender-container { padding: 17px 18px 15px; @@ -279,6 +379,13 @@ $basic-color: #4284f5 !default; opacity: 0.8; } } + + &.current:not(.in-range) { + .hi-datepicker__text { + background: $basic-color; + color: #fff; + } + } } &__text { @@ -290,8 +397,8 @@ $basic-color: #4284f5 !default; &__shortcuts { background: #fff; - border: 1px solid #d1dbe5; - width: 100px; + border-right: 1px solid #f2f2f2; + width: 105px; text-align: center; p { @@ -355,21 +462,46 @@ $basic-color: #4284f5 !default; position: relative; box-sizing: border-box; overflow: hidden; - padding: 12px 0; + padding: 0; &::-webkit-scrollbar { display: none; } } - &__list { + &__page-turn { position: absolute; z-index: 2; + width: 16px; + height: 16px; + line-height: 16px; + text-align: center; + font-size: 16px; + left: 22px; + top: 0; + cursor: pointer; + + &:last-child { + top: auto; + bottom: 0; + } + } + + &__list-container { + width: 60px; + overflow: hidden; + position: relative; + padding: 12px 0; + } + + &__list { + // position: absolute; + // z-index: 2; width: 80px; height: 224px; margin: 0; list-style-type: none; - overflow-x: hidden; + // overflow-x: hidden; overflow-y: auto; padding: 0; @@ -403,8 +535,13 @@ $basic-color: #4284f5 !default; &--current { font-weight: bold; } - - &:hover:not(.hi-timepicker__item--current):not(.hi-timepikcer__item--empty) { + + &--disabled { + color: rgba(44, 48, 78, 0.2); + cursor: not-allowed; + } + + &:hover:not(.hi-timepicker__item--current):not(.hi-timepikcer__item--empty):not(.hi-timepicker__item--disabled) { background-color: #eff5fe; } } @@ -423,7 +560,7 @@ $basic-color: #4284f5 !default; box-sizing: border-box; border-top: 1px solid rgba(242, 242, 242, 1); border-bottom: 1px solid rgba(242, 242, 242, 1); - + // background: rgba(247, 247, 247, 1); position: absolute; top: 108px; @@ -434,7 +571,7 @@ $basic-color: #4284f5 !default; text-align: center; align-items: center; margin: 0 12px; - + span { flex: 1; } @@ -505,7 +642,7 @@ $basic-color: #4284f5 !default; } } - &:hover:not(.today):not(.in-range):not(.range-start) { + &:hover:not(.today):not(.current):not(.in-range):not(.range-start) { .hi-datepicker__text { // TODO: 色板值 background: rgba(66, 142, 245, 0.12); diff --git a/docs/zh-CN/date-picker.md b/docs/zh-CN/date-picker.md index 09d57c71e..49af7d294 100644 --- a/docs/zh-CN/date-picker.md +++ b/docs/zh-CN/date-picker.md @@ -311,8 +311,26 @@ render () { ``` ::: +### 日期/时间段选择 -### 日期 + 时间(范围选择) +:::demo + +日期 + 时间 + +```js +render () { + return ( + <DatePicker + type="timeperiod" + value={new Date()} + onChange={(d) => {console.log('sec', d)}} + /> + ) +} +``` +::: + +### 日期/时间(范围选择) :::demo diff --git a/docs/zh-CN/time-picker.md b/docs/zh-CN/time-picker.md index 8c326b886..457f8eb8f 100644 --- a/docs/zh-CN/time-picker.md +++ b/docs/zh-CN/time-picker.md @@ -22,7 +22,7 @@ render() { value={new Date()} type="timerange" format="HH:mm" - onChange={date => console.log('时间', date)} + onChange={date => console.log('时间范围', date)} /> </div> </div> @@ -36,4 +36,4 @@ render() { | 参数 | 说明 | 回调参数 | 说明 | | -------- | ----- | ---- | ---- | -| onChange | 选中回调函数 | (date: Date) | - | \ No newline at end of file +| onChange | 选中回调函数 | (date: Date) | - | From f13e60bb668befabaeac65ca556e711543eae41c Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 4 Apr 2019 15:55:45 +0800 Subject: [PATCH 023/112] =?UTF-8?q?Weekrange=20=E5=A2=9E=E5=8A=A0=E7=A7=BB?= =?UTF-8?q?=E5=85=A5=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/date-picker/Calender.js | 12 +++- components/date-picker/DatePanel.js | 2 +- components/date-picker/style/index.scss | 10 ++++ components/locales/en-US.js | 2 +- components/locales/zh-CN.js | 2 +- docs/zh-CN/date-picker.md | 76 +++---------------------- 6 files changed, 31 insertions(+), 73 deletions(-) diff --git a/components/date-picker/Calender.js b/components/date-picker/Calender.js index a697a68fa..9578df8eb 100644 --- a/components/date-picker/Calender.js +++ b/components/date-picker/Calender.js @@ -56,6 +56,10 @@ class Calender extends Component { let col = row[j] || (row[j] = {type: 'normal', range: false, rangeStart: false, rangeEnd: false}) col.type = 'normal' const time = _time + DAY_MILLISECONDS * (i * 7 + j) // 当前日期的毫秒数 + if (range && (isSameDay(range.startDate, time) || isSameDay(range.endDate, time))) { + console.log(_date, time, range) + col.type = 'current' + } if (i === 0) { // 处理第一行的日期数据 if (j >= firstDayWeek) { // 本月 col.text = ++count @@ -76,9 +80,11 @@ class Calender extends Component { col.type = 'next' } } - if (isSameDay(_date, time)) { - col.type = 'current' - } + + // if (isSameDay(_date, time)) { + // console.log(_date, time, range) + // col.type = 'current' + // } if (isToday(time)) { col.type = 'today' } diff --git a/components/date-picker/DatePanel.js b/components/date-picker/DatePanel.js index 35226fb51..94de2822b 100644 --- a/components/date-picker/DatePanel.js +++ b/components/date-picker/DatePanel.js @@ -83,7 +83,7 @@ class DatePanel extends Component { ) } getYearOrMonthData (val, type) { - const start = type === 'year' ? val - 6 : 1 + const start = type === 'year' ? val - 4 : 1 let trs = [[], [], [], []] let num = 0 for (let i = 0; i < 4; i++) { diff --git a/components/date-picker/style/index.scss b/components/date-picker/style/index.scss index 792a39fd9..7841d5794 100644 --- a/components/date-picker/style/index.scss +++ b/components/date-picker/style/index.scss @@ -300,6 +300,16 @@ $basic-color: #4284f5 !default; width: 48px; } } + + &--week { + .hi-datepicker__row { + &:hover { + .hi-datepicker__content { + background: rgba(66, 132, 245, 0.1); + } + } + } + } } &__calender { diff --git a/components/locales/en-US.js b/components/locales/en-US.js index ee207c02e..84dcbe9ab 100644 --- a/components/locales/en-US.js +++ b/components/locales/en-US.js @@ -25,7 +25,7 @@ module.exports = { weekrange: 'Select Week' }, weekrange: function (year, week) { - return year + 'year ' + week + 'week' + return year + '-W' + week } }, pagination: { diff --git a/components/locales/zh-CN.js b/components/locales/zh-CN.js index b1cb3fc14..ddba63611 100644 --- a/components/locales/zh-CN.js +++ b/components/locales/zh-CN.js @@ -25,7 +25,7 @@ module.exports = { weekrange: '请选择周' }, weekrange: function (year, week) { - return year + '年 第' + week + '周' + return year + '-W' + week } }, pagination: { diff --git a/docs/zh-CN/date-picker.md b/docs/zh-CN/date-picker.md index 49af7d294..5724145c6 100644 --- a/docs/zh-CN/date-picker.md +++ b/docs/zh-CN/date-picker.md @@ -19,70 +19,12 @@ constructor() { render () { return ( <div style={{display:'flex', flexWrap: 'wrap'}}> - <div> - <p>Date 实例:</p> - <DatePicker - value={new Date} - onChange={(d) => { - console.log('value 为 Date 实例', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> - - <div> - <p>毫秒值(number):</p> - <DatePicker - value={1541755800052} - onChange={(d) => { - console.log(' value 为 Number(毫秒数)', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> - <div> - <p>时间字符串:</p> - <DatePicker - value='2018-10-11' - onChange={(d) => { - console.log('value 为 时间字符串', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> - <div> - <p>未传入 value:</p> - <DatePicker - format="YYYY-MM-DD HH:mm:SS" - onChange={(d) => { - console.log('没有 value 属性', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> - <div> - <p>null:</p> - <DatePicker - value={null} - onChange={(d) => { - console.log('value 为 Null', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> - <div> - <p>undefined:</p> - <DatePicker - value={undefined} - onChange={(d) => { - console.log('value 为 undefined', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> - <div> - <p>空字符串:</p> - <DatePicker - value='' - onChange={(d) => { - console.log('value 为 空字符串', DatePicker.format(d, 'YYYY-MM-DD E')) - }} - /> - </div> + <DatePicker + value={new Date} + onChange={(d) => { + console.log('value 为 Date 实例', DatePicker.format(d, 'YYYY-MM-DD E')) + }} + /> </div> ) } @@ -90,7 +32,7 @@ render () { ::: -### 禁用模式(全部禁用) +### 禁用模式 :::demo @@ -410,8 +352,8 @@ render () { | 参数 | 说明 | 类型 | 可选值 |默认值 | | -------- | ----- | ---- | ---- | ---- | -| type | 选择器类型 | String | date 普通日期 <br/> daterange 日期范围<br/> year 年份<br/> month 月份<br/> week 周<br/> weekrange 周范围 | date | -| value | 默认显示的日期 | Date/String/Number/Object/Undefined/Null | -- | null | +| type | 选择器类型 | String | date 普通日期 <br/> daterange 日期范围<br/> year 年份<br/> month 月份<br/> week 周<br/> weekrange 周范围 <br/> timeperiod 时间段(1.5新增) | date | +| value | 默认显示的日期 | Date\|String\|Number\|<br/>Object\|Undefined\|Null | -- | null | | minDate | 最小日期 | Date | null | null | | maxDate | 最大日期 | Date | null | null | | disabled | 是否禁用输入框 | Boolean | true false | false | From 7911e170801587ca6b7601f849a96b6531fff203 Mon Sep 17 00:00:00 2001 From: chenxian <chenxian@xiaomi.com> Date: Mon, 8 Apr 2019 20:03:26 +0800 Subject: [PATCH 024/112] init --- components/tree/Tree.js | 129 +++++++++++++++++-------------- components/tree/style/index.scss | 11 ++- docs/zh-CN/components/tree.md | 71 ++++++++--------- 3 files changed, 113 insertions(+), 98 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 97e214577..3fa50ee4d 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -4,27 +4,21 @@ import PropTypes from 'prop-types' // import Checkbox from '../checkbox/index' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' -import { - calcDropPosition, - deepClone, - getChildren, - getDisabled, - getAll -} from './util' +import { calcDropPosition, deepClone, getChildren, getDisabled, getAll } from './util' import './style/index' const dealData = (data, tempData = {}, parent = null) => { if (data.length === 0) { return data } - data.map((item) => { - tempData[item.id] = {...item} + data.map(item => { + tempData[item.id] = { ...item } if (parent) { tempData[item.id].parent = parent } if (item.children && item.children.length > 0) { const tempArr = [] - item.children.map((i) => { + item.children.map(i => { tempArr.push(i.id) }) tempData[item.id].children = tempArr @@ -114,15 +108,10 @@ export default class Tree extends Component { } onCheckChange (checked, item) { - const { - onChange, - checkedKeys - } = this.props + const { onChange, checkedKeys } = this.props let checkedArr = checkedKeys - let { - all - } = this.state + let { all } = this.state let semiChecked = all.filter(item => item.semi).map(item => item.id) let disabledKeys = all.filter(item => item.disabled).map(item => item.id) let myself = all.find(a => a.id === item.id) @@ -188,7 +177,22 @@ export default class Tree extends Component { } else { semiChecked = semiChecked.filter(s => s !== p) } - console.log('chedked', checked, 'child', child, 'checkedkeys', checkedArr, 'child.length', child.length, 'title', all.find(item => item.id === p).title, 'checknum', num, 'semi', semi) + console.log( + 'chedked', + checked, + 'child', + child, + 'checkedkeys', + checkedArr, + 'child.length', + child.length, + 'title', + all.find(item => item.id === p).title, + 'checknum', + num, + 'semi', + semi + ) }) // let semiChecked = getSemi(this.props.data, checkedArr) @@ -201,7 +205,8 @@ export default class Tree extends Component { // }) // onCheckChange && onCheckChange(checkedArr, item.title, !checked, semiChecked) onChange && onChange(checkedArr, item.title, !checked, semiChecked) - this.props.onCheckChange && this.props.onCheckChange(checkedArr, item.title, !checked, semiChecked) + this.props.onCheckChange && + this.props.onCheckChange(checkedArr, item.title, !checked, semiChecked) } setCheckTreeCheckedChild (id, checked, tempCheckedArr, semi) { @@ -254,7 +259,7 @@ export default class Tree extends Component { } setCheckTreeCheckedParent (id, checked, tempCheckedArr) { - const {dataMap} = this.state + const { dataMap } = this.state if (checked) { if (tempCheckedArr.indexOf(id) >= 0) { tempCheckedArr.splice(tempCheckedArr.indexOf(id), 1) @@ -262,12 +267,15 @@ export default class Tree extends Component { } else { let allChecked = true - dataMap[id].children && dataMap[id].children.map((i) => { - if (tempCheckedArr.indexOf(i) < 0) { - allChecked = false - } - }) - if (allChecked && tempCheckedArr.indexOf(id) < 0) { tempCheckedArr.push(id) } + dataMap[id].children && + dataMap[id].children.map(i => { + if (tempCheckedArr.indexOf(i) < 0) { + allChecked = false + } + }) + if (allChecked && tempCheckedArr.indexOf(id) < 0) { + tempCheckedArr.push(id) + } } if (dataMap[id].parent) { this.setCheckTreeCheckedParent(dataMap[id].parent, checked, tempCheckedArr) @@ -287,33 +295,32 @@ export default class Tree extends Component { }) } - // 当拖拽元素开始被拖拽的时候触发的事件 - onDragStart = (e, data) => { - const { onDragStart } = this.props - e.stopPropagation() - - let expandedArr = this.state.hasExpanded - - if (expandedArr.indexOf(data.id) >= 0) { - expandedArr.splice(expandedArr.indexOf(data.id), 1) - } - - this.dargNode = e.target - this.curData = data - this.setState({ - expandedKeys: expandedArr - }) - if (onDragStart) { - onDragStart(e) - } - - try { - e.dataTransfer.setData('text/plain', '') - } catch (error) { - } - } + // 当拖拽元素开始被拖拽的时候触发的事件 + onDragStart = (e, data) => { + const { onDragStart } = this.props + e.stopPropagation() + + let expandedArr = this.state.hasExpanded + + if (expandedArr.indexOf(data.id) >= 0) { + expandedArr.splice(expandedArr.indexOf(data.id), 1) + } + + this.dargNode = e.target + this.curData = data + this.setState({ + expandedKeys: expandedArr + }) + if (onDragStart) { + onDragStart(e) + } + + try { + e.dataTransfer.setData('text/plain', '') + } catch (error) {} + } // 当拖拽完成后触发的事件 - onDragEnd = (e) => { + onDragEnd = e => { const { onDragEnd } = this.props e.stopPropagation() @@ -350,7 +357,7 @@ export default class Tree extends Component { } } // 拖拽元素在目标元素上移动的时候触发的事件 - onDragOver = (e) => { + onDragOver = e => { const { onDragOver } = this.props e.preventDefault() e.stopPropagation() @@ -360,7 +367,7 @@ export default class Tree extends Component { } } // 当拖拽元素离开目标元素时触发 - onDragLeave = (e) => { + onDragLeave = e => { const { onDragLeave } = this.props e.stopPropagation() @@ -389,7 +396,15 @@ export default class Tree extends Component { } renderTreeNodes (data) { - const { prefixCls, draggable, checkable, closeIcon, openIcon, withLine, highlightable } = this.props + const { + prefixCls, + draggable, + checkable, + closeIcon, + openIcon, + withLine, + highlightable + } = this.props const { dragNode, dragNodePosition } = this.state return ( @@ -429,9 +444,7 @@ export default class Tree extends Component { }) return ( - <div className={classes} - style={style} - > + <div className={classes} style={style}> {this.renderTreeNodes(this.state.data)} </div> ) diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 2bc6969e8..b44504e3c 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -63,9 +63,11 @@ $tree: 'hi-tree' !default; } &_item-text { - width: calc(100% - 18px); + // width: calc(100% - 18px); + cursor: pointer; display: inline-block; vertical-align: middle; + padding: 0 4px; &.dragTo { background-color: #4284f5; @@ -83,13 +85,15 @@ $tree: 'hi-tree' !default; width: calc(100% - 40px); padding-left: 4px; } - + transition: background .3s; &:hover { background-color: rgba(66, 133, 244, 0.08); + color: rgba(56, 62, 71, 1); } &.highlight { - color: #4284f5; + color: #fff; + background-color: #4285f4; } } @@ -97,6 +101,7 @@ $tree: 'hi-tree' !default; display: inline-block; vertical-align: middle; width: 18px; + cursor: pointer; } .#{$tree} { diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 57d8262a4..3c90d1fb3 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -1,8 +1,7 @@ -## Tree控件 +## Tree 控件 树形菜单 - ### 普通用法 普通用法 @@ -18,7 +17,7 @@ constructor(props) { children: [ { id: 2, title: '技术', children: [ - { id: 3, title: '后端', onClick: data => {console.log('后端:', data)} }, + { id: 3, title: '后端', onClick: data => {console.log('后端:', data)} }, { id: 4, title: '运维' }, { id: 5, title: '前端' } ] @@ -30,7 +29,7 @@ constructor(props) { children: [ { id: 22, title: <a>技术</a>, children: [ - { id: 33, title: '后端' }, + { id: 33, title: '后端' }, { id: 44, title: '运维' }, { id: 55, title: '前端' } ] @@ -71,8 +70,8 @@ render() { ) } ``` -::: +::: ### checkbox @@ -88,7 +87,7 @@ constructor(props) { children: [ { id: 2, title: '技术', children: [ - { id: 3, title: '后端',disabled:true }, + { id: 3, title: '后端',disabled:true }, { id: 4, title: '运维' }, { id: 5, title: '前端' } ] @@ -100,7 +99,7 @@ constructor(props) { children: [ { id: 22, title: '技术2', expand: true, children: [ - { id: 33, title: '后端2' }, + { id: 33, title: '后端2' }, { id: 44, title: '运维2' }, { id: 55, title: '前端2' } ] @@ -136,46 +135,44 @@ render() { ) } ``` -::: +::: ### Tree Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ------- | ------- | ------- | ------- | ------- | -| data | 展示数据 | Array | 参见 Tree Attributes-data | - | -| checkable | 节点前添加 Checkbox 复选框 | Boolean | false | - | -| options | 配置选项 | Object | 参见 Tree Attributes-options | - | -| defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | -| checkedKeys | 默认选中的checkbox | Array | - | - | -| openIcon | 表示展开的图标 | String | Icon 图标名称 | - | -| closeIcon | 表示闭合的图标 | String | Icon 图标名称 | - | -| style | 组件整体样式 | Object | - | - | -| highlightable | 高亮 | Boolean -| withLine | 是否显示连接线 | Boolean | - | false | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---------------- | -------------------------- | ------- | ---------------------------- | ------ | +| data | 展示数据 | Array | 参见 Tree Attributes-data | - | +| checkable | 节点前添加 Checkbox 复选框 | Boolean | false | - | +| options | 配置选项 | Object | 参见 Tree Attributes-options | - | +| defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | +| checkedKeys | 默认选中的 checkbox | Array | - | - | +| openIcon | 表示展开的图标 | String | Icon 图标名称 | - | +| closeIcon | 表示闭合的图标 | String | Icon 图标名称 | - | +| style | 组件整体样式 | Object | - | - | +| highlightable | 高亮 | Boolean | +| withLine | 是否显示连接线 | Boolean | - | false | ### Tree Attributes-data -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ------- | ------- | ------- | ------- | ------- | -| expand | 默认是否展开子菜单(优先级高于defaultExpandAll) | Blooean | - | false | -| onClick | 点击每项时触发的事件 | Function | - | - | -| onNodeClick | 点击每项时触发,onClick作用具体绑定的项,onNodeClick作用于所以项 | Function | - | - | -| style | 单个节点样式 | Object | - | - | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ----------- | ------------------------------------------------------------------ | -------- | ------ | ------ | +| expand | 默认是否展开子菜单(优先级高于 defaultExpandAll) | Blooean | - | false | +| onClick | 点击每项时触发的事件 | Function | - | - | +| onNodeClick | 点击每项时触发,onClick 作用具体绑定的项,onNodeClick 作用于所以项 | Function | - | - | +| style | 单个节点样式 | Object | - | - | ### Tree Attributes-options -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ----------| ------- | ------- | ------- | ------- | -| title | 指定节点标签为节点对象的某个属性值 | String | - | title | -| children | 指定子树为节点对象的某个属性值 | String | - | children | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ---------------------------------- | ------ | ------ | -------- | +| title | 指定节点标签为节点对象的某个属性值 | String | - | title | +| children | 指定子树为节点对象的某个属性值 | String | - | children | ### Tree Events -| 参数 | 说明 | 回调参数 | -| -------- | ----- | ---- | -| onChange | 改变复选框状态时触发 | checkedArr, title, isChecked | -| onNodeToggle | 节点被点击(展开/收起)时触发 | (data: Obejct, isExpanded: Boolean) | -| onCheckChange | 节点选中项 | checkedArr, title, isChecked | - - +| 参数 | 说明 | 回调参数 | +| ------------- | --------------------------- | ----------------------------------- | +| onChange | 改变复选框状态时触发 | checkedArr, title, isChecked | +| onNodeToggle | 节点被点击(展开/收起)时触发 | (data: Obejct, isExpanded: Boolean) | +| onCheckChange | 节点选中项 | checkedArr, title, isChecked | From 50bf901eb28e3a5ec3f249e7afd1cfd938183bd7 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Tue, 9 Apr 2019 19:51:09 +0800 Subject: [PATCH 025/112] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=84=E5=BC=9F?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/TreeNode.js | 224 ++++++++++++++++++++++--------- components/tree/style/index.scss | 24 ++++ 2 files changed, 185 insertions(+), 63 deletions(-) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 3824a58f9..a0dccb652 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -1,15 +1,29 @@ import React, { Component } from 'react' import Checkbox from '../table/checkbox/index' import classNames from 'classnames' - +import isEqual from 'lodash/isEqual' +import cloneDeep from 'lodash/cloneDeep' export default class TreeNode extends Component { constructor (props) { super(props) this.state = { - highlight: null + highlight: null, + showRightClickMenu: null, + dataCache: [], + prevData: [] } } - + static getDerivedStateFromProps (props, state) { + console.log('************', state.dataCache) + if (!isEqual(props.data, state.prevData)) { + return { + ...state, + prevData: props.data, + dataCache: props.data + } + } + return state + } onDragEnter (item, data, e) { this.props.onDragEnter(e, item, data) } @@ -28,7 +42,7 @@ export default class TreeNode extends Component { getItem (name, treeItem) { let has = false - this.props[name].map((item) => { + this.props[name].map(item => { if (treeItem.id === item) { has = true } @@ -43,31 +57,84 @@ export default class TreeNode extends Component { onExpanded (expanded, item) { this.props.onExpanded(expanded, item) } - nodeClick = (item) => { + nodeClick = item => { this.props.onNodeClick(item) } - renderSwitcher = (expanded) => { - const { prefixCls, openIcon, closeIcon } = this.props - const switcherClsName = classNames(`${prefixCls}-switcher`, 'hi-icon', `icon-${expanded ? (closeIcon || 'minus') : (openIcon || 'plus')}`) - return ( - <i className={switcherClsName} /> + renderSwitcher = expanded => { + // const { prefixCls, openIcon, closeIcon } = this.props + const { prefixCls } = this.props + const switcherClsName = classNames( + `${prefixCls}-switcher`, + 'hi-icon', + `icon-${expanded ? 'down' : 'right'}` ) + return <i className={switcherClsName} /> } renderItemIcon = () => { const { prefixCls, itemIcon } = this.props - const switcherClsName = classNames(`${prefixCls}-switcher`, 'hi-icon', `icon-${itemIcon || 'document'}`) - return ( - <i - className={switcherClsName} - /> + const switcherClsName = classNames( + `${prefixCls}-switcher`, + 'hi-icon', + `icon-${itemIcon || 'document'}` ) + return <i className={switcherClsName} /> + } + + // 添加兄弟节点 + _addSibNode = (itemId, data) => { + data.forEach((d, index) => { + if (d.id === itemId) { + data.splice(index + 1, 0, { id: '', title: '测试' }) + } else { + if (d.children) { + this._addSibNode(itemId, d.children) + } + } + }) } + addSiblingNode = itemId => { + console.log('^^^', itemId) + const { dataCache } = this.state + console.log(dataCache) + const _dataCache = cloneDeep(dataCache) + this._addSibNode(itemId, _dataCache) + console.log(_dataCache) + this.setState({ dataCache: _dataCache }) + } + // 添加子节点 + addChildNode = itemId => {} + // 编辑节点 + editNode = itemId => {} + // 删除节点 + deleteNode = itemId => {} + // 渲染右键菜单 + renderRightClickMenu = itemId => { + return ( + itemId === this.state.showRightClickMenu && + <ul className='right-click-menu'> + <li onClick={() => this.addSiblingNode(itemId)}>添加节点</li> + <li onClick={() => this.addChildNode()}>添加子节点</li> + <li onClick={() => this.editNode()}>编辑</li> + <li onClick={() => this.deleteNode()}>删除</li> + </ul> + ) + } renderTree (data) { - const {draggable, prefixCls, dragNodePosition, dragNode, withLine, semiChecked, onNodeClick, onClick, highlightable} = this.props - const {highlight} = this.state + const { + draggable, + prefixCls, + dragNodePosition, + dragNode, + withLine, + semiChecked, + onNodeClick, + onClick, + highlightable + } = this.props + const { highlight } = this.state return ( <ul> {data.map(item => { @@ -81,52 +148,83 @@ export default class TreeNode extends Component { this.props.checkable && 'has_checkbox' ) - const itemContainerStyle = classNames( - withLine && 'with-line' - ) + const itemContainerStyle = classNames(withLine && 'with-line') - return (<li - onDragStart={this.onDragStart.bind(this, item, data)} - onDragEnter={this.onDragEnter.bind(this, item, data)} - onDragOver={this.onDragOver.bind(this, item, data)} - onDragLeave={this.onDragLeave.bind(this, item, data)} - onDrop={this.onDrop.bind(this, item, data)} - draggable={draggable} - key={item.id} - className={itemContainerStyle} - > - <span onClick={() => this.onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}>{item.children && item.children.length > 0 ? this.renderSwitcher(expanded) : (withLine && this.renderItemIcon())}</span> + return ( + <li + onDragStart={this.onDragStart.bind(this, item, data)} + onDragEnter={this.onDragEnter.bind(this, item, data)} + onDragOver={this.onDragOver.bind(this, item, data)} + onDragLeave={this.onDragLeave.bind(this, item, data)} + onDrop={this.onDrop.bind(this, item, data)} + draggable={draggable} + key={item.id} + className={itemContainerStyle} + > + <span + onClick={() => this.onExpanded(expanded, item)} + className={`${prefixCls}_item-icon`} + > + {item.children && item.children.length > 0 + ? this.renderSwitcher(expanded) + : withLine && this.renderItemIcon()} + </span> - {this.props.checkable ? <Checkbox - semi={semiChecked.includes(item.id)} - checked={checked} - onChange={() => this.onCheckChange(checked, item)} - onTitleClick={(e) => { - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && this.setState({ - highlight: item.id - }) - e.stopPropagation() - }} - highlight={highlight === item.id} - text={item.title} - disabled={item.disabled} /> - : <span - style={item.style} - className={`${prefixCls}_item-text ${itemStyle} ${highlight === item.id ? 'highlight' : ''}`} - onClick={(e) => { - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && this.setState({ - highlight: item.id - }) - e.stopPropagation() - }} - >{this.renderText(item.title)}</span> - } - {item.children && item.children.length > 0 && expanded ? this.renderTree(item.children) : null} - </li>) + {this.props.checkable + ? <Checkbox + semi={semiChecked.includes(item.id)} + checked={checked} + onChange={() => this.onCheckChange(checked, item)} + onTitleClick={e => { + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && + this.setState({ + highlight: item.id + }) + e.stopPropagation() + }} + highlight={highlight === item.id} + text={item.title} + disabled={item.disabled} + /> + : <span + style={item.style} + className={`${prefixCls}_item-text ${itemStyle} ${highlight === item.id + ? 'highlight' + : ''}`} + onContextMenu={e => { + // + // if (this.props.editable) { + // e.preventDefault() + // } + e.preventDefault() + this.setState({ + showRightClickMenu: item.id, + highlight: item.id + }) + }} + onClick={e => { + this.setState({ + showRightClickMenu: null + }) + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && + this.setState({ + highlight: item.id + }) + e.stopPropagation() + }} + > + {this.renderText(item.title)} + {this.renderRightClickMenu(item.id)} + </span>} + {item.children && item.children.length > 0 && expanded + ? this.renderTree(item.children) + : null} + </li> + ) })} </ul> ) @@ -135,10 +233,10 @@ export default class TreeNode extends Component { return text } render () { - const {data} = this.props + const { dataCache } = this.state return ( <div> - {this.renderTree(data)} + {this.renderTree(dataCache)} </div> ) } diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index b44504e3c..0ddd7cb50 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -68,6 +68,7 @@ $tree: 'hi-tree' !default; display: inline-block; vertical-align: middle; padding: 0 4px; + position: relative; &.dragTo { background-color: #4284f5; @@ -95,6 +96,29 @@ $tree: 'hi-tree' !default; color: #fff; background-color: #4285f4; } + .right-click-menu { + color: #333; + position: absolute; + top: 0; + left: calc(100% + 5px); + z-index: 1; + width: 120px; + background: #fff; + border: 1px solid #e6e7e8; + border-radius: 2px; + box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.1); + box-sizing: border-box; + padding: 4px 1px; + li { + padding-left: 15px; + height: 36px; + line-height: 36px; + transition: background .3s; + &:hover { + background: rgba(66, 133, 244, 0.08); + } + } + } } &_item-icon { From a4ccedec0f4a0677b24949aeeb2ae272175e2afe Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Wed, 10 Apr 2019 14:39:32 +0800 Subject: [PATCH 026/112] fix:style rule --- components/menu/style/index.scss | 98 ++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index 598c1c95c..de2206a61 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -11,12 +11,13 @@ @mixin subMenuPadding($level) { .hi-menu--#{$level} { @if $level > 1 { - >.hi-menu__title { + > .hi-menu__title { padding-left: $level * 32px; } } + .hi-submenu__items { - >.hi-menu__title { + > .hi-menu__title { padding-left: ($level + 1) * 32px; } } @@ -26,30 +27,35 @@ .hi-menu, .hi-submenu__popper { @include component-reset(); + ul { margin-top: 0; margin-bottom: 0; padding-left: 0; - font-size: 0px; + font-size: 0; } + li { - margin: 0px; + margin: 0; list-style: none; } .hi-menu-items { display: flex; } + .hi-menu-item { &--active { - >.hi-menu__title .hi-menu__title-content, - >.hi-menu__title-content { - color: #4284F5; + > .hi-menu__title .hi-menu__title-content, + > .hi-menu__title-content { + color: #4284f5; } } + &--disabled { .hi-menu__title { cursor: not-allowed; + color: #d8d8d8; } } } @@ -60,14 +66,18 @@ font-size: 14px; color: #333; cursor: pointer; + &-icon { flex: none; margin-right: 4px; } + &-content { flex: auto; + @include ellipsis(); } + &-toggle-icon { flex: none; margin-left: 8px; @@ -77,26 +87,31 @@ .hi-menu { &--horizontal { - border-bottom: 1px solid #E6E7E8; + border-bottom: 1px solid #e6e7e8; + .hi-menu-item { position: relative; margin-right: 48px; - &:last-child { - margin-right: 0px; - } + &--active:after { content: ''; position: absolute; - bottom: 0px; + bottom: 0; left: 50%; transform: translateX(-50%); width: 32px; height: 2px; - background-color: #4284F5; + background-color: #4284f5; + } + + &:last-child { + margin-right: 0; } } + .hi-menu__title { - padding: 12px 0px; + padding: 12px 0; + &-content { max-width: 100px; } @@ -106,49 +121,59 @@ &--vertical { display: inline-block; width: 216px; - padding: 12px 0px; - background:#fff; - box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15); - border: 1px solid rgba(230,231,232,1); + padding: 12px 0; + background: #fff; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15); + border: 1px solid rgba(230, 231, 232, 1); box-sizing: border-box; + .hi-menu-items { display: flex; flex-direction: column; } + .hi-menu__title { padding: 12px 16px; + &:hover { - background-color: rgba(66,133,244,0.08); + background-color: rgba(66, 133, 244, 0.08); } } + .hi-submenu { display: flex; flex-direction: column; } + .hi-menu__title-icon { font-size: 20px; } - @include subMenuPadding(1) - @include subMenuPadding(2) + + @include subMenuPadding(1); + @include subMenuPadding(2); } &--mini { width: 48px; padding: 12px 0; overflow: hidden; + .hi-menu--1.hi-menu-item--active { - background-color: rgba(66,133,244,0.08); + background-color: rgba(66, 133, 244, 0.08); } + .hi-menu__title { - padding: 0px; + padding: 0; } + .hi-menu__title-icon { width: 46px; height: 46px; line-height: 46px; - margin-right: 0px; + margin-right: 0; text-align: center; } + &__toggle { height: 48px; line-height: 48px; @@ -163,22 +188,32 @@ &__items--hide { display: none; } + &__popper { width: 200px; - padding: 4px 0px; + padding: 4px 0; background-color: #fff; - box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15); - border: 1px solid rgba(230,231,232,1); + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15); + border: 1px solid rgba(230, 231, 232, 1); box-sizing: border-box; + .hi-submenu__items { display: flex; flex-direction: column; + + .hi-menu-item--disabled { + cursor: not-allowed; + color: #d8d8d8; + } } + .hi-menu__title { padding: 8px 16px; } + &--fat { width: auto; + .hi-submenu__items { flex-direction: row; } @@ -187,15 +222,18 @@ .hi-menu-fat { margin-right: 12px; width: 108px; - &__title{ + + &__title { color: #999; } + &__content { font-size: 14px; color: #999; box-sizing: border-box; - @include ellipsis() + + @include ellipsis(); } } } -} \ No newline at end of file +} From 5d51f45e64562a5134f5368fd6b8e25a29a3212c Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Wed, 10 Apr 2019 14:50:29 +0800 Subject: [PATCH 027/112] npmignore --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index bcf7bc52a..7aa526911 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ # MIFE dotfiles # npmignore +.git/ build/ components/ dist/ From 786b04f1de3d03bfc38b206a27c9730c58b0ceba Mon Sep 17 00:00:00 2001 From: zhan8863 <zhan8863@126.com> Date: Wed, 10 Apr 2019 15:03:36 +0800 Subject: [PATCH 028/112] Hotfix/cascader closepanel (#163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复 Cascader Ref 引起的不能渲染的问题 * 移除 console * 替换 UglifyJS 为 terser-webpack-plugin,以解决 build 编译问题 * 优化 --- build/webpack.production.config.js | 4 ++-- components/popover/index.js | 20 +++++++++++--------- package.json | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/build/webpack.production.config.js b/build/webpack.production.config.js index 5778ec36b..abf2eb0c3 100644 --- a/build/webpack.production.config.js +++ b/build/webpack.production.config.js @@ -3,7 +3,7 @@ const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') const CleanWebpackPlugin = require('clean-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin') -const UglifyJsPlugin = require('uglifyjs-webpack-plugin') +const TerserPlugin = require('terser-webpack-plugin') const basePath = path.resolve(__dirname, '../') module.exports = { @@ -111,7 +111,7 @@ module.exports = { } }, minimizer: [ - new UglifyJsPlugin() + new TerserPlugin() ], usedExports: true, sideEffects: true diff --git a/components/popover/index.js b/components/popover/index.js index 1d038e93b..19f249c08 100644 --- a/components/popover/index.js +++ b/components/popover/index.js @@ -30,7 +30,7 @@ export default class Popover extends Component { } this.eventTarget = null this.popperRef = React.createRef() - this.referenceRef = React.createRef() + // this.referenceRef = React.createRef() } showPopper () { @@ -52,8 +52,9 @@ export default class Popover extends Component { isInPopover () { const popper = this.popperRef.current + const referenceRef = ReactDOM.findDOMNode(this.refs.referenceRef) const bool = !this.element || this.element.contains(this.eventTarget) || - !this.referenceRef.current || this.referenceRef.current.contains(this.eventTarget) || + !referenceRef || referenceRef.contains(this.eventTarget) || !popper || popper.contains(this.eventTarget) this.eventTarget = null return bool @@ -63,11 +64,12 @@ export default class Popover extends Component { const { trigger } = this.props this.element = ReactDOM.findDOMNode(this) + const referenceRef = ReactDOM.findDOMNode(this.refs.referenceRef) // this.reference = ReactDOM.findDOMNode(this.refs.reference) - if (this.referenceRef.current === null) return + if (referenceRef === null) return if (trigger === 'click') { - this.referenceRef.current.addEventListener('click', () => { + referenceRef.addEventListener('click', () => { if (this.state.showPopper) { this.hidePopper() } else { @@ -82,16 +84,16 @@ export default class Popover extends Component { this.hidePopper() }) } else if (trigger === 'hover') { - this.referenceRef.current.addEventListener('mouseenter', e => { + referenceRef.addEventListener('mouseenter', e => { this.eventTarget = e.target this.showPopper() }) - this.referenceRef.current.addEventListener('mouseleave', e => { + referenceRef.addEventListener('mouseleave', e => { this.delayHidePopper(e) }) } else { - this.referenceRef.current.addEventListener('focus', this.showPopper.bind(this)) - this.referenceRef.current.addEventListener('blur', this.hidePopper.bind(this)) + referenceRef.addEventListener('focus', this.showPopper.bind(this)) + referenceRef.addEventListener('blur', this.hidePopper.bind(this)) } } @@ -119,7 +121,7 @@ export default class Popover extends Component { return ( <div className={classNames(className, 'hi-popover')} style={style} ref={node => { this.popoverContainer = node }}> - { React.cloneElement(React.Children.only(this.props.children), { ref: this.referenceRef, tabIndex: '0' }) } + { React.cloneElement(React.Children.only(this.props.children), { ref: 'referenceRef', tabIndex: '0' }) } <Popper className='hi-popover__popper' diff --git a/package.json b/package.json index bb4fb320e..07e6bb41f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hi-ui/hiui", - "version": "1.4.0", + "version": "1.4.1", "description": "HIUI for React", "scripts": { "test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'", @@ -53,7 +53,6 @@ "redux": "^4.0.0", "redux-thunk": "^2.3.0", "shallowequal": "^1.0.2", - "uglifyjs-webpack-plugin": "^2.1.1", "warning": "^3.0.0", "whatwg-fetch": "^2.0.4" }, @@ -106,6 +105,7 @@ "stylelint-config-recommended-scss": "^3.2.0", "stylelint-config-standard": "^18.2.0", "stylelint-scss": "^3.4.0", + "terser-webpack-plugin": "^1.2.3", "through2": "^2.0.3", "url-loader": "^1.0.1", "webpack": "^4.29.0", From 6deb2ca6254210038b6ba3f7517b345b47113aff Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 11 Apr 2019 15:19:03 +0800 Subject: [PATCH 029/112] finish edit --- components/tree/TreeNode.js | 242 +++++++++++++++++++++++++------ components/tree/style/index.scss | 6 +- 2 files changed, 202 insertions(+), 46 deletions(-) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index a0dccb652..da9097baa 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -3,6 +3,8 @@ import Checkbox from '../table/checkbox/index' import classNames from 'classnames' import isEqual from 'lodash/isEqual' import cloneDeep from 'lodash/cloneDeep' +import Input from '../input' +import uuidv4 from 'uuid/v4' export default class TreeNode extends Component { constructor (props) { super(props) @@ -10,11 +12,14 @@ export default class TreeNode extends Component { highlight: null, showRightClickMenu: null, dataCache: [], - prevData: [] + prevData: [], + // 存储编辑节点编辑前的状态 + editNodes: [], + // 存储编辑节点的状态 + editingNodes: [] } } static getDerivedStateFromProps (props, state) { - console.log('************', state.dataCache) if (!isEqual(props.data, state.prevData)) { return { ...state, @@ -81,45 +86,168 @@ export default class TreeNode extends Component { ) return <i className={switcherClsName} /> } - + // TODO:调整添加节点的策略,由深度遍历改为按层修改! // 添加兄弟节点 - _addSibNode = (itemId, data) => { + _addSibNode = (itemId, data, editingNodes) => { data.forEach((d, index) => { if (d.id === itemId) { - data.splice(index + 1, 0, { id: '', title: '测试' }) + const addNode = { id: uuidv4(), title: '', status: 'editable' } + data.splice(index + 1, 0, addNode) + editingNodes.push(addNode) } else { if (d.children) { - this._addSibNode(itemId, d.children) + this._addSibNode(itemId, d.children, editingNodes) } } }) } addSiblingNode = itemId => { - console.log('^^^', itemId) + const { dataCache, editingNodes } = this.state + const _dataCache = cloneDeep(dataCache) + const _editingNodes = [...editingNodes] + this._addSibNode(itemId, _dataCache, _editingNodes) + this.setState({ dataCache: _dataCache, editingNodes: _editingNodes }) + } + + // 取消添加节点 + _cancelAddSiblingNode = (itemId, data) => { + data.forEach((d, index) => { + if (d.id === itemId) { + data.splice(index, 1) + } else { + if (d.children) { + this._cancelAddSiblingNode(itemId, d.children) + } + } + }) + } + cancelAddSiblingNode = itemId => { const { dataCache } = this.state - console.log(dataCache) const _dataCache = cloneDeep(dataCache) - this._addSibNode(itemId, _dataCache) - console.log(_dataCache) + this._cancelAddSiblingNode(itemId, _dataCache) this.setState({ dataCache: _dataCache }) } // 添加子节点 - addChildNode = itemId => {} + _addChildNode = (itemId, data, editingNodes) => { + data.forEach((d, index) => { + if (d.id === itemId) { + if (!d.children) { + d.children = [] + } + const addNode = { id: uuidv4(), title: '', status: 'editable' } + d.children.push(addNode) + editingNodes.push(addNode) + } else { + if (d.children) { + this._addChildNode(itemId, d.children, editingNodes) + } + } + }) + } + addChildNode = item => { + const { dataCache, editingNodes } = this.state + const _dataCache = cloneDeep(dataCache) + const _editingNodes = [...editingNodes] + this._addChildNode(item.id, _dataCache, _editingNodes) + this.setState({ dataCache: _dataCache, editingNodes: _editingNodes }) + } // 编辑节点 - editNode = itemId => {} + editNode = item => { + const _editNodes = [...this.state.editNodes] + const _editingNodes = [...this.state.editingNodes] + _editNodes.push(item) + _editingNodes.push(item) + this.setState({ editNodes: _editNodes, editingNodes: _editingNodes }) + } + // 同步编辑值 + onValueChange = (value, itemId) => { + this.setState({ + editingNodes: this.state.editingNodes + .filter(item => item.id !== itemId) + .concat( + Object.assign({}, this.state.editingNodes.find(item => item.id === itemId), { + title: value + }) + ) + }) + } + // 取消编辑节点 + _cancelEditNode = (itemId, data, nodeBeforeEdit) => { + data.forEach((d, index) => { + if (d.id === itemId) { + d.title = nodeBeforeEdit.title + } else { + if (d.children) { + this._cancelEditNode(itemId, d.children, nodeBeforeEdit) + } + } + }) + } + cancelEditNode = itemId => { + const { editNodes, dataCache, editingNodes } = this.state + const nodeBeforeEdit = editNodes.find(node => node.id === itemId) + const _dataCache = cloneDeep(dataCache) + this._cancelEditNode(itemId, _dataCache, nodeBeforeEdit) + this.setState({ + dataCache: _dataCache, + editNodes: editNodes.filter(node => node.id !== itemId), + editingNodes: editingNodes.filter(node => node.id !== itemId) + }) + } + // 保存编辑 + _saveEditNode = (itemId, data, nodeEdited) => { + data.forEach((d, index) => { + if (d.id === itemId) { + d.title = nodeEdited.title + delete d.status + } else { + if (d.children) { + this._saveEditNode(itemId, d.children, nodeEdited) + } + } + }) + } + saveEditNode = itemId => { + const { editNodes, dataCache, editingNodes } = this.state + const nodeEdited = editingNodes.find(node => node.id === itemId) + const _dataCache = cloneDeep(dataCache) + this._saveEditNode(itemId, _dataCache, nodeEdited) + this.setState({ + dataCache: _dataCache, + editNodes: editNodes.filter(node => node.id !== itemId), + editingNodes: editingNodes.filter(node => node.id !== itemId) + }) + } // 删除节点 - deleteNode = itemId => {} + _deleteNode = (itemId, data) => { + data.forEach((d, index) => { + if (d.id === itemId) { + data.splice(index, 1) + } else { + if (d.children) { + this._deleteNode(itemId, d.children) + } + } + }) + } + deleteNode = itemId => { + const { dataCache } = this.state + const _dataCache = cloneDeep(dataCache) + this._deleteNode(itemId, _dataCache) + this.setState({ dataCache: _dataCache }) + } // 渲染右键菜单 - renderRightClickMenu = itemId => { + renderRightClickMenu = item => { return ( - itemId === this.state.showRightClickMenu && - <ul className='right-click-menu'> - <li onClick={() => this.addSiblingNode(itemId)}>添加节点</li> - <li onClick={() => this.addChildNode()}>添加子节点</li> - <li onClick={() => this.editNode()}>编辑</li> - <li onClick={() => this.deleteNode()}>删除</li> - </ul> + item.id === this.state.showRightClickMenu && ( + <ul className='right-click-menu'> + <li onClick={() => this.addSiblingNode(item.id)}>添加节点</li> + <li onClick={() => this.addChildNode(item)}>添加子节点</li> + <li onClick={() => this.editNode(item)}>编辑</li> + <li onClick={() => this.deleteNode(item.id)}>删除</li> + </ul> + ) ) } renderTree (data) { @@ -134,20 +262,20 @@ export default class TreeNode extends Component { onClick, highlightable } = this.props - const { highlight } = this.state + const { highlight, editNodes, editingNodes } = this.state + return ( <ul> {data.map(item => { const checked = this.getItem('checked', item) const expanded = this.getItem('expanded', item) - const itemStyle = classNames( dragNode === item.id && dragNodePosition === 0 && 'dragTo', dragNode === item.id && dragNodePosition === -1 && 'dragToGapTop', dragNode === item.id && dragNodePosition === 1 && 'dragToGapBottom', this.props.checkable && 'has_checkbox' ) - + console.log(editNodes.map(node => node.id), item.id) const itemContainerStyle = classNames(withLine && 'with-line') return ( @@ -170,8 +298,8 @@ export default class TreeNode extends Component { : withLine && this.renderItemIcon()} </span> - {this.props.checkable - ? <Checkbox + {this.props.checkable ? ( + <Checkbox semi={semiChecked.includes(item.id)} checked={checked} onChange={() => this.onCheckChange(checked, item)} @@ -179,20 +307,51 @@ export default class TreeNode extends Component { onNodeClick && onNodeClick(item) onClick && onClick(item) highlightable && - this.setState({ - highlight: item.id - }) + this.setState({ + highlight: item.id + }) e.stopPropagation() }} highlight={highlight === item.id} text={item.title} disabled={item.disabled} /> - : <span + ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( + <div style={{ display: 'flex' }}> + <Input + placeholder='请输入菜单名称' + value={(editingNodes.find(node => node.id === item.id) || {}).title} + onChange={e => { + this.onValueChange(e.target.value, item.id) + }} + /> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + this.saveEditNode(item.id) + }} + > + 确定 + </span> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + if (editNodes.map(node => node.id).includes(item.id)) { + this.cancelEditNode(item.id) + } else { + this.cancelAddSiblingNode(item.id) + } + }} + > + 取消 + </span> + </div> + ) : ( + <span style={item.style} - className={`${prefixCls}_item-text ${itemStyle} ${highlight === item.id - ? 'highlight' - : ''}`} + className={`${prefixCls}_item-text ${itemStyle} ${ + highlight === item.id ? 'highlight' : '' + }`} onContextMenu={e => { // // if (this.props.editable) { @@ -211,15 +370,16 @@ export default class TreeNode extends Component { onNodeClick && onNodeClick(item) onClick && onClick(item) highlightable && - this.setState({ - highlight: item.id - }) + this.setState({ + highlight: item.id + }) e.stopPropagation() }} > {this.renderText(item.title)} - {this.renderRightClickMenu(item.id)} - </span>} + {this.renderRightClickMenu(item)} + </span> + )} {item.children && item.children.length > 0 && expanded ? this.renderTree(item.children) : null} @@ -234,10 +394,6 @@ export default class TreeNode extends Component { } render () { const { dataCache } = this.state - return ( - <div> - {this.renderTree(dataCache)} - </div> - ) + return <div>{this.renderTree(dataCache)}</div> } } diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 0ddd7cb50..12ca50ab2 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -26,7 +26,7 @@ $tree: 'hi-tree' !default; li { // padding: 6px 0; position: relative; - + // display: flex; &.with-line { &::before { content: ' '; @@ -86,7 +86,7 @@ $tree: 'hi-tree' !default; width: calc(100% - 40px); padding-left: 4px; } - transition: background .3s; + transition: background 0.3s; &:hover { background-color: rgba(66, 133, 244, 0.08); color: rgba(56, 62, 71, 1); @@ -113,7 +113,7 @@ $tree: 'hi-tree' !default; padding-left: 15px; height: 36px; line-height: 36px; - transition: background .3s; + transition: background 0.3s; &:hover { background: rgba(66, 133, 244, 0.08); } From 3067f08bd06f922d2cb835b6660aeea536f02af9 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 12 Apr 2019 18:23:28 +0800 Subject: [PATCH 030/112] finish search tree --- components/tree/Tree.js | 73 ++++++-------------------- components/tree/TreeNode.js | 89 ++++++++++++++++++++++++++++++-- components/tree/style/index.scss | 7 +++ docs/zh-CN/components/tree.md | 2 +- 4 files changed, 110 insertions(+), 61 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 3fa50ee4d..70fca768b 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -44,19 +44,10 @@ export default class Tree extends Component { } } - // updateCheckStatus (props) { - // - // } - // - // componentDidMount () { - // this.updateCheckStatus(this.props) - // } - static propTypes = { data: PropTypes.arrayOf(PropTypes.object), defaultCheckedKeys: PropTypes.arrayOf(PropTypes.any), onDragStart: PropTypes.func, - // render: PropTypes.func, defaultExpandAll: PropTypes.bool, checkable: PropTypes.bool, draggable: PropTypes.bool, @@ -95,13 +86,6 @@ export default class Tree extends Component { if (props.data && props.checkedKeys) { data.all = getAll(props.data, props.checkedKeys) - // data.semiChecked = data.all.filter(item => item.semi).map(item => item.id) - // data.disabledKeys = data.all.filter(item => item.disabled).map(item => { - // return { - // id: item.id, - // checked: props.checkedKeys.includes(item.id) - // } - // }) } return data @@ -195,15 +179,6 @@ export default class Tree extends Component { ) }) - // let semiChecked = getSemi(this.props.data, checkedArr) - // this.setCheckTreeCheckedParent(item.id, checked, checkedArr) - // this.setCheckTreeCheckedChild(item.id, checked, checkedArr, semiChecked.includes(item.id)) - // semiChecked = getSemi(this.props.data, checkedArr) // 重新设置一次 - // this.setState({ - // hasChecked: checkedArr, - // semiChecked - // }) - // onCheckChange && onCheckChange(checkedArr, item.title, !checked, semiChecked) onChange && onChange(checkedArr, item.title, !checked, semiChecked) this.props.onCheckChange && this.props.onCheckChange(checkedArr, item.title, !checked, semiChecked) @@ -226,36 +201,6 @@ export default class Tree extends Component { tempCheckedArr.splice(tempCheckedArr.indexOf(c), 1) } }) - - // console.log(arguments) - // const {dataMap} = this.state - // - // let childNotExist = !(dataMap[id].children && dataMap[id].children.length > 0) - // - // if(!(dataMap[id].children && dataMap[id].children.length > 0)){ - // dataMap[id].children = [] - // }else { - // dataMap[id].children = dataMap[id].children.filter(item => !item.disabled) - // } - // - // if (dataMap[id].children && dataMap[id].children.length > 0) { - // dataMap[id].children.map((i) => { - // if (checked) { - // if (tempCheckedArr.indexOf(i) >= 0) { - // tempCheckedArr.splice(tempCheckedArr.indexOf(i), 1) - // } - // } else { - // if (tempCheckedArr.indexOf(i) < 0) { - // tempCheckedArr.push(i) - // } - // } - // }) - // } - // if (dataMap[id].children) { - // dataMap[id].children.map((i) => { - // this.setCheckTreeCheckedChild(i, checked, tempCheckedArr) - // }) - // } } setCheckTreeCheckedParent (id, checked, tempCheckedArr) { @@ -294,7 +239,21 @@ export default class Tree extends Component { hasExpanded: expandedArr }) } - + // 展开节点 + expandTreeNode = id => { + const _hasExpanded = this.state.hasExpanded + if (!_hasExpanded.includes(id)) { + _hasExpanded.push(id) + this.setState({ + hasExpanded: _hasExpanded + }) + } + } + setExpandTreeNodes = ids => { + this.setState({ + hasExpanded: ids + }) + } // 当拖拽元素开始被拖拽的时候触发的事件 onDragStart = (e, data) => { const { onDragStart } = this.props @@ -420,6 +379,8 @@ export default class Tree extends Component { onClick={this.props.onClick} semiChecked={this.state.all.filter(item => item.semi).map(item => item.id)} expanded={this.state.hasExpanded} + expandTreeNode={this.expandTreeNode} + setExpandTreeNodes={this.setExpandTreeNodes} onCheckChange={this.onCheckChange.bind(this)} hightLightNodes={this.props.hightLightNodes} onHightLightChange={this.props.onHightLightChange} diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index da9097baa..932cdc45c 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -4,7 +4,66 @@ import classNames from 'classnames' import isEqual from 'lodash/isEqual' import cloneDeep from 'lodash/cloneDeep' import Input from '../input' +import Icon from '../icon' import uuidv4 from 'uuid/v4' + +const highlightData = (data, highlightValue) => { + return data.map(item => { + if (item.title.includes(highlightValue)) { + const index = item.title.indexOf(highlightValue) + const beforeStr = item.title.substr(0, index) + const afterStr = item.title.substr(index + highlightValue.length) + item.title = ( + <span> + {beforeStr} + <span style={{ color: '#4284f5' }}>{highlightValue}</span> + {afterStr} + </span> + ) + } + if (item.children) { + highlightData(item.children, highlightValue) + } + return item + }) +} +// 寻找某一节点的父节点 +const getParentId = (id, data) => { + let parentId + data.forEach(item => { + if (item.children) { + if (item.children.some(item => item.id === id)) { + parentId = item.id + } else if (getParentId(id, item.children)) { + parentId = getParentId(id, item.children) + } + } + }) + return parentId +} +// 寻找某一节点的所有祖先节点 +const getAncestorIds = (id, data, arr = []) => { + if (getParentId(id, data)) { + arr.push(getParentId(id, data)) + getAncestorIds(getParentId(id, data), data, arr) + } + return arr +} +// 收集所有需要展开的节点 id +const collectExpandId = (data, searchValue, collection = [], allData) => { + data.forEach(item => { + if (item.title.includes(searchValue)) { + const parentIds = getAncestorIds(item.id, allData, []) + console.log('parentIds', parentIds) + collection.splice(collection.length - 1, 0, ...parentIds) + console.log('collection', collection) + } + if (item.children) { + collectExpandId(item.children, searchValue, collection, allData) + } + }) + return collection +} export default class TreeNode extends Component { constructor (props) { super(props) @@ -16,7 +75,8 @@ export default class TreeNode extends Component { // 存储编辑节点编辑前的状态 editNodes: [], // 存储编辑节点的状态 - editingNodes: [] + editingNodes: [], + searchValue: '' } } static getDerivedStateFromProps (props, state) { @@ -147,6 +207,8 @@ export default class TreeNode extends Component { } addChildNode = item => { const { dataCache, editingNodes } = this.state + const { expandTreeNode } = this.props + expandTreeNode(item.id) const _dataCache = cloneDeep(dataCache) const _editingNodes = [...editingNodes] this._addChildNode(item.id, _dataCache, _editingNodes) @@ -275,7 +337,6 @@ export default class TreeNode extends Component { dragNode === item.id && dragNodePosition === 1 && 'dragToGapBottom', this.props.checkable && 'has_checkbox' ) - console.log(editNodes.map(node => node.id), item.id) const itemContainerStyle = classNames(withLine && 'with-line') return ( @@ -393,7 +454,27 @@ export default class TreeNode extends Component { return text } render () { - const { dataCache } = this.state - return <div>{this.renderTree(dataCache)}</div> + const { dataCache, searchValue } = this.state + return ( + <div> + <div className='hi-tree_searcher'> + <Input + value={this.state.searchValue} + type='text' + placeholder='关键词搜索' + onChange={e => { + this.setState({ searchValue: e.target.value }) + this.props.setExpandTreeNodes( + collectExpandId(dataCache, e.target.value, [], dataCache) + ) + }} + append={<Icon name='search' style={{ color: '#4284F5', fontSize: '24px' }} />} + style={{ width: '250px' }} + /> + </div> + + {this.renderTree(highlightData(cloneDeep(dataCache), searchValue))} + </div> + ) } } diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 12ca50ab2..20cf2d92e 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -3,6 +3,13 @@ $tree: 'hi-tree' !default; .hi-tree { font-size: 14px; + .hi-tree_searcher { + padding-left: 15px; + margin-bottom: 27px; + .hi-input--append { + line-height: 32px; + } + } ul, li { diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 3c90d1fb3..868ffd295 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -27,7 +27,7 @@ constructor(props) { }, { id: 11, title: '小米', children: [ - { id: 22, title: <a>技术</a>, + { id: 22, title: '技术', children: [ { id: 33, title: '后端' }, { id: 44, title: '运维' }, From f92b14c154d06e184ce8375c59cca40ce5a8fcf9 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 15 Apr 2019 20:12:21 +0800 Subject: [PATCH 031/112] dragSource --- components/tree/Tree.js | 26 ++-- components/tree/TreeItem.js | 164 +++++++++++++++++++++ components/tree/TreeNode.js | 283 ++++++++++++++++++++++-------------- package.json | 6 +- 4 files changed, 358 insertions(+), 121 deletions(-) create mode 100644 components/tree/TreeItem.js diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 70fca768b..e8870e011 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -5,6 +5,8 @@ import PropTypes from 'prop-types' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' import { calcDropPosition, deepClone, getChildren, getDisabled, getAll } from './util' +import { DragDropContext } from 'react-dnd' +import HTML5Backend from 'react-dnd-html5-backend' import './style/index' const dealData = (data, tempData = {}, parent = null) => { @@ -27,7 +29,7 @@ const dealData = (data, tempData = {}, parent = null) => { }) } -export default class Tree extends Component { +class Tree extends Component { constructor (props) { super(props) @@ -357,23 +359,23 @@ export default class Tree extends Component { renderTreeNodes (data) { const { prefixCls, - draggable, + // draggable, checkable, closeIcon, openIcon, withLine, highlightable } = this.props - const { dragNode, dragNodePosition } = this.state + // const { dragNode, dragNodePosition } = this.state return ( <TreeNode - draggable={draggable || undefined} - onDragStart={this.onDragStart} - onDragEnter={this.onDragEnter} - onDragOver={this.onDragOver} - onDragLeave={this.onDragLeave} - onDrop={this.onDrop} + // draggable={draggable || undefined} + // onDragStart={this.onDragStart} + // onDragEnter={this.onDragEnter} + // onDragOver={this.onDragOver} + // onDragLeave={this.onDragLeave} + // onDrop={this.onDrop} checked={this.props.checkedKeys || []} onNodeClick={this.props.onNodeClick} onClick={this.props.onClick} @@ -386,8 +388,8 @@ export default class Tree extends Component { onHightLightChange={this.props.onHightLightChange} onExpanded={this.onExpanded.bind(this)} data={data} - dragNodePosition={dragNodePosition} - dragNode={dragNode} + // dragNodePosition={dragNodePosition} + // dragNode={dragNode} prefixCls={prefixCls} checkable={checkable} highlightable={highlightable} @@ -411,3 +413,5 @@ export default class Tree extends Component { ) } } + +export default DragDropContext(HTML5Backend)(Tree) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js new file mode 100644 index 000000000..3ef94b667 --- /dev/null +++ b/components/tree/TreeItem.js @@ -0,0 +1,164 @@ +import React, { Component } from 'react' +import Checkbox from '../table/checkbox/index' +import { DragSource } from 'react-dnd' +import Input from '../input' +const Types = { + TreeNode: 'treeNode' +} +class TreeItem extends Component { + render () { + const { + checked, + expanded, + // draggable, + highlight, + editNodes, + editingNodes, + prefixCls, + // dragNodePosition, + // dragNode, + withLine, + semiChecked, + onNodeClick, + onClick, + highlightable, + item, + // data, + checkable, + itemContainerStyle, + itemStyle, + onExpanded, + onValueChange, + renderItemIcon, + saveEditNode, + cancelEditNode, + cancelAddSiblingNode, + renderTree, + renderRightClickMenu, + renderText, + onCheckChange, + onSetHighlight, + showRightClickMenu, + closeRightClickMenu, + renderSwitcher, + connectDragSource + } = this.props + return connectDragSource( + <li + // onDragStart={this.onDragStart.bind(this, item, data)} + // onDragEnter={this.onDragEnter.bind(this, item, data)} + // onDragOver={this.onDragOver.bind(this, item, data)} + // onDragLeave={this.onDragLeave.bind(this, item, data)} + // onDrop={this.onDrop.bind(this, item, data)} + // draggable={draggable} + key={item.id} + className={itemContainerStyle} + > + <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> + {item.children && item.children.length > 0 + ? renderSwitcher(expanded) + : withLine && renderItemIcon()} + </span> + + {checkable ? ( + <Checkbox + semi={semiChecked.includes(item.id)} + checked={checked} + onChange={() => onCheckChange(checked, item)} + onTitleClick={e => { + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && onSetHighlight(item) + // this.setState({ + // highlight: item.id + // }) + e.stopPropagation() + }} + highlight={highlight === item.id} + text={item.title} + disabled={item.disabled} + /> + ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( + <div style={{ display: 'flex' }}> + <Input + placeholder='请输入菜单名称' + value={(editingNodes.find(node => node.id === item.id) || {}).title} + onChange={e => { + onValueChange(e.target.value, item.id) + }} + /> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + saveEditNode(item.id) + }} + > + 确定 + </span> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + if (editNodes.map(node => node.id).includes(item.id)) { + cancelEditNode(item.id) + } else { + cancelAddSiblingNode(item.id) + } + }} + > + 取消 + </span> + </div> + ) : ( + <span + style={item.style} + className={`${prefixCls}_item-text ${itemStyle} ${ + highlight === item.id ? 'highlight' : '' + }`} + onContextMenu={e => { + // + // if (this.props.editable) { + // e.preventDefault() + // } + e.preventDefault() + // this.setState({ + // showRightClickMenu: item.id, + // highlight: item.id + // }) + showRightClickMenu(item) + }} + onClick={e => { + // this.setState({ + // showRightClickMenu: null + // }) + closeRightClickMenu() + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && + // this.setState({ + // highlight: item.id + // }) + onSetHighlight(item) + e.stopPropagation() + }} + > + {renderText(item.title)} + {renderRightClickMenu(item)} + </span> + )} + {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} + </li> + ) + } +} +const treeNodeSource = { + beginDrag (props) { + return { id: props.item.id } + } +} +function collect (connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging() + } +} +export default DragSource(Types['TreeNode'], treeNodeSource, collect)(TreeItem) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 932cdc45c..9b1a8913c 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -1,12 +1,28 @@ import React, { Component } from 'react' -import Checkbox from '../table/checkbox/index' +// import Checkbox from '../table/checkbox/index' import classNames from 'classnames' import isEqual from 'lodash/isEqual' import cloneDeep from 'lodash/cloneDeep' import Input from '../input' import Icon from '../icon' import uuidv4 from 'uuid/v4' +import TreeItem from './TreeItem' +// const treeNodeSource = { +// beginDrag(props) { +// return { id: props.item.id } +// } +// } +// function collect(connect, monitor) { +// return { +// connectDragSource: connect.dragSource(), +// isDragging: monitor.isDragging() +// } +// } +// const DragSourceWrapper = source => { +// return DragSource(Types.TreeNode, treeNodeSource, collect)(source) +// } +// const DragTargetWrapper = source => {} const highlightData = (data, highlightValue) => { return data.map(item => { if (item.title.includes(highlightValue)) { @@ -64,6 +80,7 @@ const collectExpandId = (data, searchValue, collection = [], allData) => { }) return collection } +// const TreeNoder = DragSourceWrapper(TreeItem) export default class TreeNode extends Component { constructor (props) { super(props) @@ -119,7 +136,7 @@ export default class TreeNode extends Component { this.props.onCheckChange(checked, item) } - onExpanded (expanded, item) { + onExpanded = (expanded, item) => { this.props.onExpanded(expanded, item) } nodeClick = item => { @@ -312,7 +329,24 @@ export default class TreeNode extends Component { ) ) } - renderTree (data) { + //* *** */ + onSetHighlight = item => { + this.setState({ + highlight: item.id + }) + } + showRightClickMenu = item => { + this.setState({ + showRightClickMenu: item.id, + highlight: item.id + }) + } + closeRightClickMenu = () => { + this.setState({ + showRightClickMenu: null + }) + } + renderTree = data => { const { draggable, prefixCls, @@ -322,7 +356,8 @@ export default class TreeNode extends Component { semiChecked, onNodeClick, onClick, - highlightable + highlightable, + checkable } = this.props const { highlight, editNodes, editingNodes } = this.state @@ -340,112 +375,144 @@ export default class TreeNode extends Component { const itemContainerStyle = classNames(withLine && 'with-line') return ( - <li - onDragStart={this.onDragStart.bind(this, item, data)} - onDragEnter={this.onDragEnter.bind(this, item, data)} - onDragOver={this.onDragOver.bind(this, item, data)} - onDragLeave={this.onDragLeave.bind(this, item, data)} - onDrop={this.onDrop.bind(this, item, data)} - draggable={draggable} + <TreeItem key={item.id} - className={itemContainerStyle} - > - <span - onClick={() => this.onExpanded(expanded, item)} - className={`${prefixCls}_item-icon`} - > - {item.children && item.children.length > 0 - ? this.renderSwitcher(expanded) - : withLine && this.renderItemIcon()} - </span> - - {this.props.checkable ? ( - <Checkbox - semi={semiChecked.includes(item.id)} - checked={checked} - onChange={() => this.onCheckChange(checked, item)} - onTitleClick={e => { - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && - this.setState({ - highlight: item.id - }) - e.stopPropagation() - }} - highlight={highlight === item.id} - text={item.title} - disabled={item.disabled} - /> - ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( - <div style={{ display: 'flex' }}> - <Input - placeholder='请输入菜单名称' - value={(editingNodes.find(node => node.id === item.id) || {}).title} - onChange={e => { - this.onValueChange(e.target.value, item.id) - }} - /> - <span - style={{ cursor: 'pointer' }} - onClick={() => { - this.saveEditNode(item.id) - }} - > - 确定 - </span> - <span - style={{ cursor: 'pointer' }} - onClick={() => { - if (editNodes.map(node => node.id).includes(item.id)) { - this.cancelEditNode(item.id) - } else { - this.cancelAddSiblingNode(item.id) - } - }} - > - 取消 - </span> - </div> - ) : ( - <span - style={item.style} - className={`${prefixCls}_item-text ${itemStyle} ${ - highlight === item.id ? 'highlight' : '' - }`} - onContextMenu={e => { - // - // if (this.props.editable) { - // e.preventDefault() - // } - e.preventDefault() - this.setState({ - showRightClickMenu: item.id, - highlight: item.id - }) - }} - onClick={e => { - this.setState({ - showRightClickMenu: null - }) - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && - this.setState({ - highlight: item.id - }) - e.stopPropagation() - }} - > - {this.renderText(item.title)} - {this.renderRightClickMenu(item)} - </span> - )} - {item.children && item.children.length > 0 && expanded - ? this.renderTree(item.children) - : null} - </li> + prefixCls={prefixCls} + draggable={draggable} + checked={checked} + highlight={highlight} + highlightable={highlightable} + editNodes={editNodes} + editingNodes={editingNodes} + expanded={expanded} + itemStyle={itemStyle} + itemContainerStyle={itemContainerStyle} + semiChecked={semiChecked} + checkable={checkable} + onExpanded={this.onExpanded} + onValueChange={this.onValueChange} + renderTree={this.renderTree} + renderSwitcher={this.renderSwitcher} + cancelAddSiblingNode={this.cancelAddSiblingNode} + renderRightClickMenu={this.renderRightClickMenu} + renderText={this.renderText} + onCheckChange={this.onCheckChange} + saveEditNode={this.saveEditNode} + renderItemIcon={this.renderItemIcon} + onNodeClick={onNodeClick} + onClick={onClick} + onSetHighlight={this.onSetHighlight} + showRightClickMenu={this.showRightClickMenu} + closeRightClickMenu={this.closeRightClickMenu} + item={item} + /> ) + // <li + // onDragStart={this.onDragStart.bind(this, item, data)} + // onDragEnter={this.onDragEnter.bind(this, item, data)} + // onDragOver={this.onDragOver.bind(this, item, data)} + // onDragLeave={this.onDragLeave.bind(this, item, data)} + // onDrop={this.onDrop.bind(this, item, data)} + // draggable={draggable} + // key={item.id} + // className={itemContainerStyle} + // > + // <span + // onClick={() => this.onExpanded(expanded, item)} + // className={`${prefixCls}_item-icon`} + // > + // {item.children && item.children.length > 0 + // ? this.renderSwitcher(expanded) + // : withLine && this.renderItemIcon()} + // </span> + + // {this.props.checkable ? ( + // <Checkbox + // semi={semiChecked.includes(item.id)} + // checked={checked} + // onChange={() => this.onCheckChange(checked, item)} + // onTitleClick={e => { + // onNodeClick && onNodeClick(item) + // onClick && onClick(item) + // highlightable && + // this.setState({ + // highlight: item.id + // }) + // e.stopPropagation() + // }} + // highlight={highlight === item.id} + // text={item.title} + // disabled={item.disabled} + // /> + // ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( + // <div style={{ display: 'flex' }}> + // <Input + // placeholder='请输入菜单名称' + // value={(editingNodes.find(node => node.id === item.id) || {}).title} + // onChange={e => { + // this.onValueChange(e.target.value, item.id) + // }} + // /> + // <span + // style={{ cursor: 'pointer' }} + // onClick={() => { + // this.saveEditNode(item.id) + // }} + // > + // 确定 + // </span> + // <span + // style={{ cursor: 'pointer' }} + // onClick={() => { + // if (editNodes.map(node => node.id).includes(item.id)) { + // this.cancelEditNode(item.id) + // } else { + // this.cancelAddSiblingNode(item.id) + // } + // }} + // > + // 取消 + // </span> + // </div> + // ) : ( + // <span + // style={item.style} + // className={`${prefixCls}_item-text ${itemStyle} ${ + // highlight === item.id ? 'highlight' : '' + // }`} + // onContextMenu={e => { + // // + // // if (this.props.editable) { + // // e.preventDefault() + // // } + // e.preventDefault() + // this.setState({ + // showRightClickMenu: item.id, + // highlight: item.id + // }) + // }} + // onClick={e => { + // this.setState({ + // showRightClickMenu: null + // }) + // onNodeClick && onNodeClick(item) + // onClick && onClick(item) + // highlightable && + // this.setState({ + // highlight: item.id + // }) + // e.stopPropagation() + // }} + // > + // {this.renderText(item.title)} + // {this.renderRightClickMenu(item)} + // </span> + // )} + // {item.children && item.children.length > 0 && expanded + // ? this.renderTree(item.children) + // : null} + // </li> + // ) })} </ul> ) diff --git a/package.json b/package.json index 5d050502f..d3a6b0be1 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,13 @@ "axios": "^0.18.0", "classnames": "^2.2.5", "date-fns": "^1.29.0", + "fetch-jsonp": "^1.1.3", "lodash": "^4.17.10", "react": "^16.3.2", "react-addons-css-transition-group": "^15.6.2", "react-click-outside": "^3.0.1", + "react-dnd": "^7.4.5", + "react-dnd-html5-backend": "^7.4.4", "react-dom": "^16.3.2", "react-lifecycles-compat": "^3.0.4", "react-redux": "^5.0.7", @@ -52,8 +55,7 @@ "shallowequal": "^1.0.2", "uglifyjs-webpack-plugin": "^2.1.1", "warning": "^3.0.0", - "whatwg-fetch": "^2.0.4", - "fetch-jsonp": "^1.1.3" + "whatwg-fetch": "^2.0.4" }, "devDependencies": { "@babel/core": "^7.3.3", From 5762b98a6248bd76dde6cf8e5c77862c345960da Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Tue, 16 Apr 2019 14:08:46 +0800 Subject: [PATCH 032/112] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=91=A8=E7=9A=84?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E6=96=B9=E5=BC=8F;=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E8=AE=A1=E7=AE=97=E6=96=B9=E5=BC=8F;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/date-picker/BasePicker.js | 31 ++--- components/date-picker/Calender.js | 80 ++++++------- components/date-picker/DatePanel.js | 14 ++- components/date-picker/DateRangePanel.js | 11 +- components/date-picker/Time.js | 8 ++ components/date-picker/TimePanel.js | 9 +- components/date-picker/TimePeriodPanel.js | 1 - components/date-picker/constants.js | 6 +- components/date-picker/dateUtil.js | 6 +- components/date-picker/style/index.scss | 132 +++++++--------------- components/date-picker/util.js | 18 ++- docs/zh-CN/date-picker.md | 31 ++++- 12 files changed, 165 insertions(+), 182 deletions(-) diff --git a/components/date-picker/BasePicker.js b/components/date-picker/BasePicker.js index a5df7ee8f..ed15e4d2a 100644 --- a/components/date-picker/BasePicker.js +++ b/components/date-picker/BasePicker.js @@ -59,7 +59,7 @@ class BasePicker extends Component { weekOffset: 0 } _parseProps (props, callback) { - let {value, showTime, type, format, localeDatas} = props + let {value, showTime, type, format, localeDatas, weekOffset} = props format = format || FORMATS[type] let date = new Date() // 当前时间 let noText = false @@ -89,8 +89,8 @@ class BasePicker extends Component { date = {startDate: value.start, endDate: value.end} } } - const leftText = noText ? '' : formatterDate(type, date.startDate || date, format, showTime, localeDatas) - const rightText = noText ? '' : formatterDate(type, date.endDate || date, format, showTime, localeDatas) + const leftText = noText ? '' : formatterDate(type, date.startDate || date, format, showTime, localeDatas, weekOffset) + const rightText = noText ? '' : formatterDate(type, date.endDate || date, format, showTime, localeDatas, weekOffset) this.setState({ texts: [leftText, rightText], date, @@ -113,9 +113,12 @@ class BasePicker extends Component { this.calcPanelPos(rect) } calcPanelPos (rect) { - let _w = this.props.type.indexOf('range') !== -1 ? 578 : 288 - let _h = this.props.showTime ? 391 : 298 - this.props.type === 'time' && (_h = 232) + const {showTime, type} = this.props + let _w = type.indexOf('range') !== -1 ? 578 : 288 + let _h = 298 + if (type === 'daterange' && showTime) { + _h = 344 + } const _cw = document.body.clientWidth || document.documentElement.clientWidth const _ch = document.body.clientHeight || document.documentElement.clientHeight const _st = document.body.scrollTop || document.documentElement.scrollTop @@ -139,11 +142,11 @@ class BasePicker extends Component { this._parseProps(nextProps) } onPick (date, showPanel) { - const {type, showTime, localeDatas} = this.props + const {type, showTime, localeDatas, weekOffset} = this.props const {format} = this.state this.setState({ date, - texts: [formatterDate(type, date.startDate || date, format, showTime, localeDatas), formatterDate(type, date.endDate, format, showTime, localeDatas)], + texts: [formatterDate(type, date.startDate || date, format, showTime, localeDatas, weekOffset), formatterDate(type, date.endDate, format, showTime, localeDatas, weekOffset)], showPanel, isFocus: false }, () => { @@ -176,12 +179,12 @@ class BasePicker extends Component { } } timeConfirm (date, onlyTime) { - const {type, showTime, onChange, localeDatas} = this.props + const {type, showTime, onChange, localeDatas, weekOffset} = this.props let {format} = this.state onlyTime && (format = FORMATS['time']) this.setState({ date: date, - texts: [formatterDate(type, date.startDate || date, format, showTime, localeDatas), formatterDate(type, date.endDate, format, showTime, localeDatas)], + texts: [formatterDate(type, date.startDate || date, format, showTime, localeDatas, weekOffset), formatterDate(type, date.endDate, format, showTime, localeDatas, weekOffset)], showPanel: false, isFocus: false }) @@ -195,11 +198,11 @@ class BasePicker extends Component { } timeCancel () { const {tempDate, format} = this.state - const {type, showTime, localeDatas} = this.props + const {type, showTime, localeDatas, weekOffset} = this.props if (tempDate) { this.setState({ date: new Date(tempDate), - text: formatterDate(type, new Date(tempDate), format, showTime, localeDatas), + text: formatterDate(type, new Date(tempDate), format, showTime, localeDatas, weekOffset), showPanel: false }) } else { @@ -273,11 +276,13 @@ class BasePicker extends Component { renderRangeInput () { const { localeDatas, - disabled + disabled, + showTime } = this.props const _cls = classNames( 'hi-datepicker__input', 'hi-datepicker__input--range', + showTime && 'hi-datepicker__input--range-time', disabled && 'hi-datepicker__input--disabled' ) return ( diff --git a/components/date-picker/Calender.js b/components/date-picker/Calender.js index 9578df8eb..c8436a71a 100644 --- a/components/date-picker/Calender.js +++ b/components/date-picker/Calender.js @@ -18,13 +18,9 @@ class Calender extends Component { constructor (props) { super(props) this.state = { - range: this.props.range, - activeRange: false, rows: [[], [], [], [], [], []] } - } - static defaultProps = { - weekOffset: 0 + this.weekNum = 0 } _getTime (week, y, m) { const r = new Date(y, m - 1, 1) @@ -33,18 +29,17 @@ class Calender extends Component { } getRows () { - let {type, range, date, minDate, maxDate, weekOffset} = this.props - let _date = new Date(new Date(date).getTime()) - let {year, month, week} = deconstructDate(_date) + let {type, range, date: _date, minDate, maxDate, weekOffset} = this.props + let {year, month, week} = deconstructDate(_date, weekOffset) let {endDate, startDate} = range || {startDate: null, endDate: null} // * dayCount: 当月天数 // * lastMonthDayCount: 上月总天数 // * firstDayWeek: 当月第一天是周几 let firstDayWeek = getDay(startOfMonth(_date)) - weekOffset if (firstDayWeek <= 0) { // 如果为0 代表该月第一天是周日,在日历上需要第二行开始显示 - firstDayWeek = 7 - weekOffset + firstDayWeek = 7 } - const _time = this._getTime(firstDayWeek, year, month)// 当前日期面板中第一个日期的具体毫秒数(指向上一个月) + const startTimeByCurrentPanel = this._getTime(firstDayWeek, year, month)// 当前日期面板中第一个日期的具体毫秒数(指向上一个月) const dayCount = getDaysInMonth(_date) let lastMonthDayCount = getDaysInMonth(subMonths(_date, 1)) // 上月总天数 @@ -55,48 +50,40 @@ class Calender extends Component { for (let j = 0; j < 7; j++) { let col = row[j] || (row[j] = {type: 'normal', range: false, rangeStart: false, rangeEnd: false}) col.type = 'normal' - const time = _time + DAY_MILLISECONDS * (i * 7 + j) // 当前日期的毫秒数 - if (range && (isSameDay(range.startDate, time) || isSameDay(range.endDate, time))) { - console.log(_date, time, range) - col.type = 'current' - } + const currentTime = startTimeByCurrentPanel + DAY_MILLISECONDS * (i * 7 + j) // 当前日期的毫秒数 if (i === 0) { // 处理第一行的日期数据 if (j >= firstDayWeek) { // 本月 - col.text = ++count - col.value = count + col.value = ++count } else { // 上月 - col.text = lastMonthDayCount - firstDayWeek + j + 1 col.value = lastMonthDayCount - firstDayWeek + j + 1 col.type = 'prev' } } else { - count++ + ++count if (count <= dayCount) { // 本月 - col.text = count col.value = count } else { // 下月 - col.text = count - dayCount col.value = count - dayCount col.type = 'next' } } - - // if (isSameDay(_date, time)) { - // console.log(_date, time, range) + // if (range && (isSameDay(range.startDate, currentTime) || isSameDay(range.endDate, currentTime))) { // col.type = 'current' // } - if (isToday(time)) { + if (isToday(currentTime)) { col.type = 'today' } + if (isSameDay(_date, currentTime) && !range && type !== 'week') { + col.type = 'current' + } if (type === 'daterange' || type === 'weekrange') { - col.rangeStart = startDate && isSameDay(time, startDate) - col.rangeEnd = endDate && isSameDay(time, endDate) - // console.log('----',[startDate, endDate].sort(compareAsc(startDate, endDate))) + col.rangeStart = startDate && isSameDay(currentTime, startDate) + col.rangeEnd = endDate && isSameDay(currentTime, endDate) const _ds = [startDate, endDate].sort(compareAsc) - col.range = endDate && isWithinRange(time, ..._ds) - row.weekNum = getYearWeek(new Date(time)) + col.range = endDate && isWithinRange(currentTime, ..._ds) + row.weekNum = getYearWeek(new Date(currentTime), weekOffset).weekNum } - col.disabled = (minDate && compareAsc(time, minDate) === -1) || (maxDate && compareAsc(time, maxDate) === 1) + col.disabled = (minDate && compareAsc(currentTime, minDate) === -1) || (maxDate && compareAsc(currentTime, maxDate) === 1) } if (type === 'week') { let _month = month @@ -115,11 +102,15 @@ class Calender extends Component { _month = 1 } } - const cw = getYearWeek(new Date(_year, _month - 1, row[1].text)) + // let num = getDay(new Date(year, 0, 1)) + // console.log(num) + // const weekStart = subDays(date, num-1) + const cw = getYearWeek(new Date(_year, _month - 1, row[1].value), weekOffset).weekNum let bol = cw === week - row[0].range = bol + row.forEach(col => { + col.range = bol + }) row[0].rangeStart = bol - row[6].range = bol row[6].rangeEnd = bol row.currentWeek = bol row.weekNum = cw @@ -145,13 +136,14 @@ class Calender extends Component { if ((td.nodeName !== 'SPAN' && td.nodeName !== 'TD' && td.nodeName !== 'DIV') || td.disabled) return false if (cls.indexOf('disabled') !== -1) return false const day = parseInt(value) - let newDate = new Date(year, month - 1, day) + let newDate = new Date(year, month - 1) if (cls.indexOf('prev') !== -1) { newDate = addMonths(newDate, -1) } if (cls.indexOf('next') !== -1) { newDate = addMonths(newDate, 1) } + newDate.setDate(day) newDate.setHours(hours, minutes, seconds) if (type === 'year') { year = parseInt(value) @@ -215,11 +207,9 @@ class Calender extends Component { _class.push(td.type) break } - if (td.range) { - _class.push('in-range') - } + (td.range && _class.push('in-range')) if (td.rangeStart || td.rangeEnd) { - _class.push('week-highlight range-start') + _class.push('range-se') } return _class.join(' ') } @@ -229,7 +219,7 @@ class Calender extends Component { return week.slice(weekOffset).concat(week.slice(0, weekOffset)) } - weekNum = 0 + TRMouseOver (num) { const {type} = this.props if ((type === 'week' || type === 'weekrange') && this.weekNum !== num) { @@ -246,7 +236,7 @@ class Calender extends Component { onMouseMove={this.handlerMouseMove.bind(this)} > { - (type.indexOf('date') !== -1 || type.indexOf('week') !== -1) && ( + (type.indexOf('date') !== -1 || type.indexOf('week') !== -1 || type.indexOf('timeperiod') !== -1) && ( <thead> <tr> { @@ -258,7 +248,7 @@ class Calender extends Component { </thead> ) } - <tbody style={{cursor: 'pointer'}}> + <tbody> { rows.map((row, index) => { return ( @@ -277,7 +267,7 @@ class Calender extends Component { > <div className='hi-datepicker__content' value={cell.value}> <span value={cell.value} className='hi-datepicker__text'> - {cell.text} + {cell.text || cell.value} </span> </div> </td> @@ -293,4 +283,8 @@ class Calender extends Component { ) } } + +Calender.defaultProps = { + weekOffset: 0 +} export default Provider(Calender) diff --git a/components/date-picker/DatePanel.js b/components/date-picker/DatePanel.js index 94de2822b..bc93c6c26 100644 --- a/components/date-picker/DatePanel.js +++ b/components/date-picker/DatePanel.js @@ -86,14 +86,19 @@ class DatePanel extends Component { const start = type === 'year' ? val - 4 : 1 let trs = [[], [], [], []] let num = 0 + const currentYear = new Date().getFullYear() + const currentMonth = new Date().getMonth() for (let i = 0; i < 4; i++) { let row = trs[i] for (let j = 0; j < 3; j++) { let col = row[j] || (row[j] = {type: 'normal'}) const y = start + num - if (y === val) { + if (y === currentYear || y === (currentMonth + 1)) { col.type = 'today' } + if (y === val) { + col.type = 'current' + } type === 'year' ? col.text = y : col.text = this.props.localeDatas.datePicker.month[y - 1] col.value = y num++ @@ -183,13 +188,13 @@ class DatePanel extends Component { } } onDatePick (date) { - const {type} = this.props + const {type, showTime} = this.props const {hours, minutes, seconds} = deconstructDate(this.state.date) if (type === 'timeperiod') { this.props.onPick({startDate: date, endDate: addHours(this.state.date, 4)}, true) } else { date.setHours(hours, minutes, seconds) - this.props.onPick(date, true) + this.props.onPick(date, showTime) } } onTimePeriodPick (periodS, periodE) { @@ -222,6 +227,7 @@ class DatePanel extends Component { switch (currentView) { case 'date': case 'timeperiod': + case 'week': component = (<Calender date={date} weekOffset={weekOffset} @@ -290,7 +296,7 @@ class DatePanel extends Component { showTime && <TimePanel {...this.props} onPick={this.onTimePick.bind(this)} - date={date} + // date={date} timeConfirm={this.timeConfirm.bind(this)} timeCancel={this.timeCancel.bind(this)} /> diff --git a/components/date-picker/DateRangePanel.js b/components/date-picker/DateRangePanel.js index 71bed1551..5f766e702 100644 --- a/components/date-picker/DateRangePanel.js +++ b/components/date-picker/DateRangePanel.js @@ -199,18 +199,17 @@ class DatePanel extends Component { } pick (startDate, endDate) { const {range} = this.state - const {onPick} = this.props + const {onPick, showTime} = this.props range.startDate = startDate range.endDate = endDate this.setState({ - range + range, + leftDate: startDate || this.state.leftDate, + rightDate: endDate || this.state.rightDate }) if (endDate) { - onPick(range, true) + onPick(range, showTime) } - // if (endDate && !showTime) { - // onPick(range) - // } } onMouseMoveHandler (date) { const {range} = this.state diff --git a/components/date-picker/Time.js b/components/date-picker/Time.js index 73f1da075..640ab309a 100644 --- a/components/date-picker/Time.js +++ b/components/date-picker/Time.js @@ -2,6 +2,7 @@ import React, {Component} from 'react' import {deconstructDate} from './util' import Icon from '../icon' import classNames from 'classnames' +import { isSameDay } from './dateUtil' export default class TimePanel extends Component { constructor (props) { super(props) @@ -63,6 +64,13 @@ export default class TimePanel extends Component { this.minutesList.removeEventListener('scroll', this.minutesScrollEvent) this.secondsList.removeEventListener('scroll', this.secondsScrollEvent) } + componentWillReceiveProps (props) { + if (!isSameDay(props.date, this.state.date)) { + this.setState({ + date: props.date + }) + } + } completeScrollTop () { const {date} = this.state let {hours, minutes, seconds} = deconstructDate(date) diff --git a/components/date-picker/TimePanel.js b/components/date-picker/TimePanel.js index f3c097691..06a644c49 100644 --- a/components/date-picker/TimePanel.js +++ b/components/date-picker/TimePanel.js @@ -6,7 +6,6 @@ class TimePanel extends Component { constructor (props) { super(props) this.state = { - date: new Date(props.date), style: props.style } if (this.props.type !== 'time') { @@ -18,21 +17,15 @@ class TimePanel extends Component { onTimePick (date, bol) { const {showTime} = this.props if (showTime) { - this.setState({ - date - }) this.props.onPick(date, true) } else { - this.setState({ - date - }) this.props.onPick(date, bol) } } render () { return ( <div className='hi-timepicker' style={this.state.style}> - <Time date={this.state.date} onPick={this.onTimePick.bind(this)} onlyTime={this.props.type === 'time'} /> + <Time date={this.props.date} onPick={this.onTimePick.bind(this)} onlyTime={this.props.type === 'time'} /> { // this.props.type === 'time' && ( // <div className='hi-timepicker__footer'> diff --git a/components/date-picker/TimePeriodPanel.js b/components/date-picker/TimePeriodPanel.js index 47052509e..6c7c031aa 100644 --- a/components/date-picker/TimePeriodPanel.js +++ b/components/date-picker/TimePeriodPanel.js @@ -11,7 +11,6 @@ export default class TimePeriodPanel extends Component { '16:00 ~ 20:00', '20:00 ~ 24:00' ] - console.log(this.props.date) return <div className='hi-datepicker__time-period' > diff --git a/components/date-picker/constants.js b/components/date-picker/constants.js index 87918e02f..103923c19 100644 --- a/components/date-picker/constants.js +++ b/components/date-picker/constants.js @@ -20,7 +20,7 @@ export const FORMATS = { export const isVaildDate = (date) => { return date && (date instanceof Date || date.startDate || typeof date === 'number') } -export const formatterDate = (type, date, formatter, showTime, localeDatas) => { +export const formatterDate = (type, date, formatter, showTime, localeDatas, weekOffset = 0) => { if (!isVaildDate(date)) { return '' } @@ -33,7 +33,7 @@ export const formatterDate = (type, date, formatter, showTime, localeDatas) => { // break case 'weekrange': // if (date instanceof Date) { date = {startDate: date, endDate: date} } - str = localeDatas.datePicker.weekrange(date.getFullYear(), getYearWeek(date)) + str = localeDatas.datePicker.weekrange(date.getFullYear(), getYearWeek(date, weekOffset).weekNum) break case 'year': case 'month': @@ -41,7 +41,7 @@ export const formatterDate = (type, date, formatter, showTime, localeDatas) => { str = dateFormat(date, formatter) break case 'week': - str = localeDatas.datePicker.weekrange(date.getFullYear(), getYearWeek(date)) + str = localeDatas.datePicker.weekrange(date.getFullYear(), getYearWeek(date, weekOffset).weekNum) break default: str = dateFormat(date, `${formatter}${showTime ? ' HH:mm:ss' : ''}`) diff --git a/components/date-picker/dateUtil.js b/components/date-picker/dateUtil.js index b4e8d62b9..a2c1e03f6 100644 --- a/components/date-picker/dateUtil.js +++ b/components/date-picker/dateUtil.js @@ -20,6 +20,8 @@ import getHours from 'date-fns/get_hours' import getMinutes from 'date-fns/get_minutes' import getSeconds from 'date-fns/get_seconds' import addHours from 'date-fns/add_hours' +import subDays from 'date-fns/sub_days' +import differenceInDays from 'date-fns/difference_in_days' export { getDaysInMonth, // 获取当月的天数 subMonths, // 月份减法 @@ -42,5 +44,7 @@ export { getHours, getMinutes, getSeconds, - addHours + addHours, + subDays, + differenceInDays } diff --git a/components/date-picker/style/index.scss b/components/date-picker/style/index.scss index 7841d5794..afc78de42 100644 --- a/components/date-picker/style/index.scss +++ b/components/date-picker/style/index.scss @@ -30,7 +30,7 @@ $basic-color: #4284f5 !default; } &--range { - width: 400px; + width: 320px; input { flex: 0 1 40%; @@ -162,7 +162,7 @@ $basic-color: #4284f5 !default; } &__period-item { - padding: 6px; + padding: 8px; cursor: pointer; &:hover { @@ -209,69 +209,6 @@ $basic-color: #4284f5 !default; z-index: 2; line-height: 32px; } - // &__time-header { - // border-bottom: 1px solid rgba(128, 128, 128, 0.2); - // padding: 15px; - // display: flex; - // justify-content: space-around; - // font-size: 14px; - - // span { - // flex: 0 0 50%; - // text-align: center; - // color: rgba(153, 153, 153, 1); - // } - - // em { - // flex: 0 0 1px; - // background: rgba(128, 128, 128, 0.2); - // } - - // &--active { - // color: $basic-color; - // } - // } - - // &__time-footer { - // clear: both; - // text-align: center; - // height: 40px; - // line-height: 40px; - // cursor: pointer; - // color: $basic-color; - // border-top: 1px solid #f2f2f2; - // } - - // .hi-timepicker { - // width: 252px; - - - // .hi-timepicker__timebody { - // height: 265px; - // } - - // &__list { - // height: 265px; - // width: 104px; - - // &:nth-child(2) { - // left: 84px; - // } - - // &:nth-child(3) { - // left: 168px; - // width: 84px; - // } - // } - - // &__item { - // width: 84px; - - // &:last-child { - // height: 38px; - // } - // } - // } &__calender-container { padding: 17px 18px 15px; @@ -331,11 +268,11 @@ $basic-color: #4284f5 !default; } &--current-week { - .hi-datepicker__content { - background: map-get(get-palette($basic-color), '10'); - } + // .hi-datepicker__content { + // background: map-get(get-palette($basic-color), '10'); + // } - .week-highlight { + .range-se { .hi-datepicker__content { background: $basic-color; } @@ -343,6 +280,13 @@ $basic-color: #4284f5 !default; } } + // &__content { + // margin: 0 3px; + // background: #fff; + // color: #fff; + // border-radius: 2px; + // } + &__cell { cursor: pointer; text-align: center; @@ -355,19 +299,11 @@ $basic-color: #4284f5 !default; .hi-datepicker__content { cursor: not-allowed; color: map-get(get-palette($basic-color), 'g80'); - background: map-get(get-palette($basic-color), 'g70'); - } - } - - &.prev, - &.next { - .hi-datepicker__text { - color: rgba(44, 48, 78, 0.2); + background: rgba(246, 246, 246, 1); } } - &.range-start, - &.range-end { + &.range-se:not(.prev):not(.next) { .hi-datepicker__content { margin: 0 3px; background: $basic-color; @@ -376,16 +312,22 @@ $basic-color: #4284f5 !default; } } - &.in-range { - .hi-datepicker__content { - background: rgba(66, 132, 245, 0.2); + &.prev, + &.next { + .hi-datepicker__text { + color: rgba(44, 48, 78, 0.2); } } + // &.in-range:not(.next):not(.prev) { + // .hi-datepicker__content { + // background: rgba(66, 132, 245, 0.1); + // } + // } + &.today:not(.in-range) { .hi-datepicker__text { border: 1px solid $basic-color; - border-radius: 2px; opacity: 0.8; } } @@ -403,6 +345,7 @@ $basic-color: #4284f5 !default; width: 24px; height: 24px; box-sizing: border-box; + border-radius: 2px; } &__shortcuts { @@ -617,13 +560,21 @@ $basic-color: #4284f5 !default; &__row { &--current-week { .hi-datepicker__content { - background: map-get(get-palette($value), '10'); + // TODO: 色板值 + // background: map-get(get-palette($value), '10'); + background: rgba(66, 132, 245, 0.1); } - &.week-highlight { + .range-se { .hi-datepicker__content { background: map-get(get-palette($value), '50'); color: #fff; + margin: 0 3px; + border-radius: 2px; + } + + .hi-datepicker__text { + color: #fff; } } } @@ -637,25 +588,24 @@ $basic-color: #4284f5 !default; } } - &.in-range { + &.in-range:not(.next):not(.prev) { .hi-datepicker__content { // TODO: 色板值 // background: map-get(get-palette($value), '10'); - background: rgba(66, 142, 245, 0.12); + background: rgba(66, 142, 245, 0.1); } } - &.range-start, - &.range-end { + &.range-se:not(.next):not(.prev) { .hi-datepicker__content { background: map-get(get-palette($value), '50'); } } - &:hover:not(.today):not(.current):not(.in-range):not(.range-start) { + &:hover:not(.today):not(.current):not(.in-range):not(.range-se):not(.disabled) { .hi-datepicker__text { // TODO: 色板值 - background: rgba(66, 142, 245, 0.12); + background: rgba(66, 142, 245, 0.1); // background: map-get(get-palette($value), '10'); } } diff --git a/components/date-picker/util.js b/components/date-picker/util.js index bb8a9ed42..1cde52bd6 100644 --- a/components/date-picker/util.js +++ b/components/date-picker/util.js @@ -1,11 +1,11 @@ -import {addMonths} from './dateUtil' +import { addMonths, getDay, subDays, differenceInDays, startOfWeek, endOfWeek } from './dateUtil' -export const deconstructDate = (date) => { +export const deconstructDate = (date, weekOffset = 0) => { !(date instanceof Date) && (date = new Date(date)) return { year: date.getFullYear(), month: date.getMonth() + 1, - week: getYearWeek(date), + week: getYearWeek(date, weekOffset).weekNum, day: date.getDate(), hours: date.getHours(), minutes: date.getMinutes(), @@ -13,12 +13,18 @@ export const deconstructDate = (date) => { time: date.getTime() } } -export const getYearWeek = (date) => { +export const getYearWeek = (date, weekStart = 0) => { const year = date.getFullYear() let date1 = new Date(year, parseInt(date.getMonth()), date.getDate()) let date2 = new Date(year, 0, 1) - let d = Math.round((date1.valueOf() - date2.valueOf()) / 86400000) - return Math.ceil((d + ((date2.getDay() + 1) - 1)) / 7) + let num = getDay(date2) + date2 = subDays(date2, weekStart ? (num - 1) : num) // 周日开始 + const din = differenceInDays(date1, date2) + return { + weekNum: Math.ceil((din + 1) / 7), + start: startOfWeek(date1, {weekStartsOn: weekStart}), + end: endOfWeek(date1, {weekStartsOn: weekStart}) + } } export const calcDayCount = (year, month) => { diff --git a/docs/zh-CN/date-picker.md b/docs/zh-CN/date-picker.md index 5724145c6..3dac99382 100644 --- a/docs/zh-CN/date-picker.md +++ b/docs/zh-CN/date-picker.md @@ -87,13 +87,32 @@ render () { ```js render () { + const Row = Grid.Row + const Col = Grid.Col return ( - <DatePicker - type='week' - onChange={(d) => { - console.log('周选择', d) - }} - /> + <div> + <Row gutter={true}> + <Col span={6}> + <p>周一起始</p> + <DatePicker + type='week' + weekOffset={1} + onChange={(d) => { + console.log('周选择', d) + }} + /> + </Col> + <Col span={6}> + <p>周日起始</p> + <DatePicker + type='week' + onChange={(d) => { + console.log('周选择', d) + }} + /> + </Col> + </Row> + </div> ) } ``` From 19147450247678b6487dddfee3ef16d4e2fb12c8 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Wed, 17 Apr 2019 21:11:23 +0800 Subject: [PATCH 033/112] finish drag and drop --- components/tree/Tree.js | 7 + components/tree/TreeDivider.js | 26 +++ components/tree/TreeItem.js | 291 ++++++++++++++++++++----------- components/tree/TreeNode.js | 217 ++++++++++------------- components/tree/style/index.scss | 5 +- 5 files changed, 315 insertions(+), 231 deletions(-) create mode 100644 components/tree/TreeDivider.js diff --git a/components/tree/Tree.js b/components/tree/Tree.js index e8870e011..54a0fff50 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -256,6 +256,12 @@ class Tree extends Component { hasExpanded: ids }) } + closeExpandedTreeNode = id => { + this.setState({ + hasExpanded: this.state.hasExpanded.filter(expandId => expandId !== id) + }) + } + // 当拖拽元素开始被拖拽的时候触发的事件 onDragStart = (e, data) => { const { onDragStart } = this.props @@ -381,6 +387,7 @@ class Tree extends Component { onClick={this.props.onClick} semiChecked={this.state.all.filter(item => item.semi).map(item => item.id)} expanded={this.state.hasExpanded} + closeExpandedTreeNode={this.closeExpandedTreeNode} expandTreeNode={this.expandTreeNode} setExpandTreeNodes={this.setExpandTreeNodes} onCheckChange={this.onCheckChange.bind(this)} diff --git a/components/tree/TreeDivider.js b/components/tree/TreeDivider.js new file mode 100644 index 000000000..7970226da --- /dev/null +++ b/components/tree/TreeDivider.js @@ -0,0 +1,26 @@ +import React from 'react' +const TreeDivider = () => { + return ( + <div + style={{ + position: 'absolute', + display: 'flex', + width: '100%', + alignItems: 'center' + }} + > + <div + style={{ + flex: '0 0 5px', + height: 5, + border: '1px solid rgba(66,132,245,1)', + borderRadius: '2.5px', + boxSizing: 'border-box' + }} + /> + <div style={{ flex: '1', height: '1px', background: 'rgba(66,132,245,1)' }} /> + </div> + ) +} + +export default TreeDivider diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 3ef94b667..23a2d8e90 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -1,15 +1,20 @@ import React, { Component } from 'react' import Checkbox from '../table/checkbox/index' -import { DragSource } from 'react-dnd' +import { DragSource, DropTarget } from 'react-dnd' import Input from '../input' +import { findDOMNode } from 'react-dom' +import TreeDivider from './TreeDivider' const Types = { TreeNode: 'treeNode' } class TreeItem extends Component { render () { const { + // setDraggingNode, + // removeDraggingNode, checked, expanded, + // dropNode, // draggable, highlight, editNodes, @@ -23,7 +28,7 @@ class TreeItem extends Component { onClick, highlightable, item, - // data, + draggingNode, checkable, itemContainerStyle, itemStyle, @@ -41,124 +46,204 @@ class TreeItem extends Component { showRightClickMenu, closeRightClickMenu, renderSwitcher, - connectDragSource + connectDragSource, + connectDropTarget, + targetNode } = this.props - return connectDragSource( - <li - // onDragStart={this.onDragStart.bind(this, item, data)} - // onDragEnter={this.onDragEnter.bind(this, item, data)} - // onDragOver={this.onDragOver.bind(this, item, data)} - // onDragLeave={this.onDragLeave.bind(this, item, data)} - // onDrop={this.onDrop.bind(this, item, data)} - // draggable={draggable} - key={item.id} - className={itemContainerStyle} - > - <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> - {item.children && item.children.length > 0 - ? renderSwitcher(expanded) - : withLine && renderItemIcon()} - </span> + return connectDropTarget( + connectDragSource( + <li + // onDragStart={this.onDragStart.bind(this, item, data)} + // onDragEnter={this.onDragEnter.bind(this, item, data)} + // onDragOver={this.onDragOver.bind(this, item, data)} + // onDragLeave={this.onDragLeave.bind(this, item, data)} + // onDrop={this.onDrop.bind(this, item, data)} + // draggable={draggable} + key={item.id} + className={itemContainerStyle} + > + <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> + {item.children && item.children.length > 0 + ? renderSwitcher(expanded) + : withLine && renderItemIcon()} + </span> - {checkable ? ( - <Checkbox - semi={semiChecked.includes(item.id)} - checked={checked} - onChange={() => onCheckChange(checked, item)} - onTitleClick={e => { - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && onSetHighlight(item) - // this.setState({ - // highlight: item.id - // }) - e.stopPropagation() - }} - highlight={highlight === item.id} - text={item.title} - disabled={item.disabled} - /> - ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( - <div style={{ display: 'flex' }}> - <Input - placeholder='请输入菜单名称' - value={(editingNodes.find(node => node.id === item.id) || {}).title} - onChange={e => { - onValueChange(e.target.value, item.id) + {checkable ? ( + <Checkbox + semi={semiChecked.includes(item.id)} + checked={checked} + onChange={() => onCheckChange(checked, item)} + onTitleClick={e => { + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && onSetHighlight(item) + // this.setState({ + // highlight: item.id + // }) + e.stopPropagation() }} + highlight={highlight === item.id} + text={item.title} + disabled={item.disabled} /> + ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( + <div style={{ display: 'flex' }}> + <Input + placeholder='请输入菜单名称' + value={(editingNodes.find(node => node.id === item.id) || {}).title} + onChange={e => { + onValueChange(e.target.value, item.id) + }} + /> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + saveEditNode(item.id) + }} + > + 确定 + </span> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + if (editNodes.map(node => node.id).includes(item.id)) { + cancelEditNode(item.id) + } else { + cancelAddSiblingNode(item.id) + } + }} + > + 取消 + </span> + </div> + ) : ( <span - style={{ cursor: 'pointer' }} - onClick={() => { - saveEditNode(item.id) + style={item.style} + className={`${prefixCls}_item-text ${itemStyle} ${ + highlight === item.id ? 'highlight' : '' + } ${draggingNode === item.id ? 'dragging' : ''}`} + onContextMenu={e => { + // + // if (this.props.editable) { + // e.preventDefault() + // } + e.preventDefault() + // this.setState({ + // showRightClickMenu: item.id, + // highlight: item.id + // }) + showRightClickMenu(item) }} - > - 确定 - </span> - <span - style={{ cursor: 'pointer' }} - onClick={() => { - if (editNodes.map(node => node.id).includes(item.id)) { - cancelEditNode(item.id) - } else { - cancelAddSiblingNode(item.id) - } + onClick={e => { + // this.setState({ + // showRightClickMenu: null + // }) + closeRightClickMenu() + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && + // this.setState({ + // highlight: item.id + // }) + onSetHighlight(item) + e.stopPropagation() }} > - 取消 + {renderText(item.title)} + {renderRightClickMenu(item)} + {targetNode === item.id && <TreeDivider />} </span> - </div> - ) : ( - <span - style={item.style} - className={`${prefixCls}_item-text ${itemStyle} ${ - highlight === item.id ? 'highlight' : '' - }`} - onContextMenu={e => { - // - // if (this.props.editable) { - // e.preventDefault() - // } - e.preventDefault() - // this.setState({ - // showRightClickMenu: item.id, - // highlight: item.id - // }) - showRightClickMenu(item) - }} - onClick={e => { - // this.setState({ - // showRightClickMenu: null - // }) - closeRightClickMenu() - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && - // this.setState({ - // highlight: item.id - // }) - onSetHighlight(item) - e.stopPropagation() - }} - > - {renderText(item.title)} - {renderRightClickMenu(item)} - </span> - )} - {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} - </li> + )} + {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} + </li> + ) ) } } -const treeNodeSource = { +const source = { beginDrag (props) { - return { id: props.item.id } + // 开始拖拽前,如果已经展开,则需要收起 + if (props.expanded) { + props.closeExpandedTreeNode(props.item.id) + } + return { sourceItem: props.item, originalExpandStatus: props.expanded } + } +} +const target = { + // canDrop () { + // return true + // }, + drop (props, monitor) { + const { sourceItem, originalExpandStatus } = monitor.getItem() + const { + item: targetItem, + dropNode, + removeDraggingNode, + expandTreeNode, + removeTargetNode + } = props + + // 先看下是不是在最近得组件 + if (monitor.isOver({ shallow: true })) { + // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 + if ( + sourceItem.id === targetItem.id || + (targetItem.children && targetItem.children.map(t => t.id).includes(sourceItem.id)) || + (sourceItem.children && sourceItem.children.map(s => s.id).includes(targetItem.id)) + ) { + // 2.如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 + // 如果什么都不做,原来展开则现在还展开 + if (originalExpandStatus) { + expandTreeNode(sourceItem.id) + } + removeDraggingNode() + removeTargetNode() + } else { + // // 3.移动节点到相应位置 + // console.log('>>>>>>>>>>>>>>>>', targetItem, sourceItem) + dropNode(sourceItem, targetItem) + removeDraggingNode() + removeTargetNode() + } + } + }, + hover (props, monitor, component) { + const { sourceItem } = monitor.getItem() + const { item: targetItem, setDraggingNode, setTargetNode } = props + + // 先看下是不是在最近得组件 + if (monitor.isOver({ shallow: true })) { + // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 + if ( + sourceItem.id === targetItem.id || + (targetItem.children && targetItem.children.map(t => t.id).includes(sourceItem.id)) || + (sourceItem.children && sourceItem.children.map(s => s.id).includes(targetItem.id)) + ) { + // 2.如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 + return false + } else { + const clientOffset = monitor.getClientOffset() + const componentRect = findDOMNode(component).getBoundingClientRect() + console.log('>>>>>>>>>>>>>>>>', targetItem.id, sourceItem.id, clientOffset, componentRect) + // // 3.移动节点到相应位置 + // console.log() + setTargetNode(targetItem.id) + setDraggingNode(sourceItem.id) + } + } } } -function collect (connect, monitor) { +function sourceCollect (connect, monitor) { return { connectDragSource: connect.dragSource(), isDragging: monitor.isDragging() } } -export default DragSource(Types['TreeNode'], treeNodeSource, collect)(TreeItem) +function targetCollect (connect) { + return { + connectDropTarget: connect.dropTarget() + } +} +export default DropTarget(Types['TreeNode'], target, targetCollect)( + DragSource(Types['TreeNode'], source, sourceCollect)(TreeItem) +) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 9b1a8913c..6fdcf26c1 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -8,24 +8,9 @@ import Icon from '../icon' import uuidv4 from 'uuid/v4' import TreeItem from './TreeItem' -// const treeNodeSource = { -// beginDrag(props) { -// return { id: props.item.id } -// } -// } -// function collect(connect, monitor) { -// return { -// connectDragSource: connect.dragSource(), -// isDragging: monitor.isDragging() -// } -// } -// const DragSourceWrapper = source => { -// return DragSource(Types.TreeNode, treeNodeSource, collect)(source) -// } -// const DragTargetWrapper = source => {} const highlightData = (data, highlightValue) => { return data.map(item => { - if (item.title.includes(highlightValue)) { + if (typeof item.title === 'string' && item.title.includes(highlightValue)) { const index = item.title.indexOf(highlightValue) const beforeStr = item.title.substr(0, index) const afterStr = item.title.substr(index + highlightValue.length) @@ -70,9 +55,9 @@ const collectExpandId = (data, searchValue, collection = [], allData) => { data.forEach(item => { if (item.title.includes(searchValue)) { const parentIds = getAncestorIds(item.id, allData, []) - console.log('parentIds', parentIds) + // console.log('parentIds', parentIds) collection.splice(collection.length - 1, 0, ...parentIds) - console.log('collection', collection) + // console.log('collection', collection) } if (item.children) { collectExpandId(item.children, searchValue, collection, allData) @@ -93,6 +78,10 @@ export default class TreeNode extends Component { editNodes: [], // 存储编辑节点的状态 editingNodes: [], + // 处于拖拽状态的节点 + draggingNode: null, + // 处于目标状态的节点 + targetNode: null, searchValue: '' } } @@ -178,7 +167,18 @@ export default class TreeNode extends Component { } }) } - + // 设置拖拽中的节点 + setDraggingNode = itemId => { + this.setState({ + draggingNode: itemId + }) + } + // 移除拖拽中的节点 + removeDraggingNode = () => { + this.setState({ + draggingNode: null + }) + } addSiblingNode = itemId => { const { dataCache, editingNodes } = this.state const _dataCache = cloneDeep(dataCache) @@ -298,6 +298,57 @@ export default class TreeNode extends Component { editingNodes: editingNodes.filter(node => node.id !== itemId) }) } + // 删除拖动的节点 + _delDragNode = (itemId, data) => { + data.forEach((d, index) => { + if (d.id === itemId) { + data.splice(index, 1) + } else { + if (d.children) { + this._delDragNode(itemId, d.children) + } + } + }) + } + // 新增放置的节点 + _addDropNode = (targetItemId, sourceItemId, data, allData) => { + data.forEach((d, index) => { + if (d.id === targetItemId) { + const sourceNode = this.findNode(sourceItemId, allData) + if (!d.children) { + d.children = [] + } + d.children.push(sourceNode) + } else { + if (d.children) { + this._addDropNode(targetItemId, sourceItemId, d.children, allData) + } + } + }) + } + findNode = (itemId, data) => { + // console.log('allData', data) + let node + data.forEach((d, index) => { + if (d.id === itemId) { + node = d + } else { + if (d.children && this.findNode(itemId, d.children)) { + node = this.findNode(itemId, d.children) + } + } + }) + return node + } + dropNode = (sourceItem, targetItem) => { + const { dataCache } = this.state + const _dataCache = cloneDeep(dataCache) + this._delDragNode(sourceItem.id, _dataCache) + this._addDropNode(targetItem.id, sourceItem.id, _dataCache, dataCache) + this.setState({ + dataCache: _dataCache + }) + } // 删除节点 _deleteNode = (itemId, data) => { data.forEach((d, index) => { @@ -346,6 +397,12 @@ export default class TreeNode extends Component { showRightClickMenu: null }) } + setTargetNode = id => { + this.setState({ targetNode: id }) + } + removeTargetNode = () => { + this.setState({ targetNode: null }) + } renderTree = data => { const { draggable, @@ -357,9 +414,11 @@ export default class TreeNode extends Component { onNodeClick, onClick, highlightable, - checkable + checkable, + closeExpandedTreeNode, + expandTreeNode } = this.props - const { highlight, editNodes, editingNodes } = this.state + const { highlight, editNodes, editingNodes, draggingNode, targetNode } = this.state return ( <ul> @@ -385,6 +444,7 @@ export default class TreeNode extends Component { editNodes={editNodes} editingNodes={editingNodes} expanded={expanded} + expandTreeNode={expandTreeNode} itemStyle={itemStyle} itemContainerStyle={itemContainerStyle} semiChecked={semiChecked} @@ -404,115 +464,17 @@ export default class TreeNode extends Component { onSetHighlight={this.onSetHighlight} showRightClickMenu={this.showRightClickMenu} closeRightClickMenu={this.closeRightClickMenu} + dropNode={this.dropNode} + setDraggingNode={this.setDraggingNode} + removeDraggingNode={this.removeDraggingNode} + draggingNode={draggingNode} + closeExpandedTreeNode={closeExpandedTreeNode} + setTargetNode={this.setTargetNode} + targetNode={targetNode} + removeTargetNode={this.removeTargetNode} item={item} /> ) - // <li - // onDragStart={this.onDragStart.bind(this, item, data)} - // onDragEnter={this.onDragEnter.bind(this, item, data)} - // onDragOver={this.onDragOver.bind(this, item, data)} - // onDragLeave={this.onDragLeave.bind(this, item, data)} - // onDrop={this.onDrop.bind(this, item, data)} - // draggable={draggable} - // key={item.id} - // className={itemContainerStyle} - // > - // <span - // onClick={() => this.onExpanded(expanded, item)} - // className={`${prefixCls}_item-icon`} - // > - // {item.children && item.children.length > 0 - // ? this.renderSwitcher(expanded) - // : withLine && this.renderItemIcon()} - // </span> - - // {this.props.checkable ? ( - // <Checkbox - // semi={semiChecked.includes(item.id)} - // checked={checked} - // onChange={() => this.onCheckChange(checked, item)} - // onTitleClick={e => { - // onNodeClick && onNodeClick(item) - // onClick && onClick(item) - // highlightable && - // this.setState({ - // highlight: item.id - // }) - // e.stopPropagation() - // }} - // highlight={highlight === item.id} - // text={item.title} - // disabled={item.disabled} - // /> - // ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( - // <div style={{ display: 'flex' }}> - // <Input - // placeholder='请输入菜单名称' - // value={(editingNodes.find(node => node.id === item.id) || {}).title} - // onChange={e => { - // this.onValueChange(e.target.value, item.id) - // }} - // /> - // <span - // style={{ cursor: 'pointer' }} - // onClick={() => { - // this.saveEditNode(item.id) - // }} - // > - // 确定 - // </span> - // <span - // style={{ cursor: 'pointer' }} - // onClick={() => { - // if (editNodes.map(node => node.id).includes(item.id)) { - // this.cancelEditNode(item.id) - // } else { - // this.cancelAddSiblingNode(item.id) - // } - // }} - // > - // 取消 - // </span> - // </div> - // ) : ( - // <span - // style={item.style} - // className={`${prefixCls}_item-text ${itemStyle} ${ - // highlight === item.id ? 'highlight' : '' - // }`} - // onContextMenu={e => { - // // - // // if (this.props.editable) { - // // e.preventDefault() - // // } - // e.preventDefault() - // this.setState({ - // showRightClickMenu: item.id, - // highlight: item.id - // }) - // }} - // onClick={e => { - // this.setState({ - // showRightClickMenu: null - // }) - // onNodeClick && onNodeClick(item) - // onClick && onClick(item) - // highlightable && - // this.setState({ - // highlight: item.id - // }) - // e.stopPropagation() - // }} - // > - // {this.renderText(item.title)} - // {this.renderRightClickMenu(item)} - // </span> - // )} - // {item.children && item.children.length > 0 && expanded - // ? this.renderTree(item.children) - // : null} - // </li> - // ) })} </ul> ) @@ -541,6 +503,7 @@ export default class TreeNode extends Component { </div> {this.renderTree(highlightData(cloneDeep(dataCache), searchValue))} + {/* this.renderTree(cloneDeep(dataCache)) */} </div> ) } diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 20cf2d92e..53bd55aef 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -98,7 +98,10 @@ $tree: 'hi-tree' !default; background-color: rgba(66, 133, 244, 0.08); color: rgba(56, 62, 71, 1); } - + &.dragging { + background-color: rgba(246, 246, 246, 1); + color: rgba(204, 204, 204, 1); + } &.highlight { color: #fff; background-color: #4285f4; From 3bd0b4c4881576ca646153e2f1370e6c82d90121 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 18 Apr 2019 15:32:01 +0800 Subject: [PATCH 034/112] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/card/index.js | 132 +++++++++ components/card/style/index.js | 1 + components/card/style/index.scss | 91 +++++++ components/index.js | 1 + docs/en-US/components/card.md | 407 ++++++++++++++++++++++++++++ docs/zh-CN/components/card.md | 407 ++++++++++++++++++++++++++++ site/locales/en-US.js | 3 +- site/locales/zh-CN.js | 3 +- site/pages/components/card/index.js | 9 + site/pages/components/index.js | 3 +- 10 files changed, 1054 insertions(+), 3 deletions(-) create mode 100644 components/card/index.js create mode 100644 components/card/style/index.js create mode 100644 components/card/style/index.scss create mode 100644 docs/en-US/components/card.md create mode 100644 docs/zh-CN/components/card.md create mode 100644 site/pages/components/card/index.js diff --git a/components/card/index.js b/components/card/index.js new file mode 100644 index 000000000..942bb5ec3 --- /dev/null +++ b/components/card/index.js @@ -0,0 +1,132 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import './style/index' + +class Card extends Component { + constructor (props) { + super(props) + this.state = { + extraShow: props.extraShow === 'stay' + } + } + render () { + const { style, size, hoverable, title, extra, cover, description, children, disabled, extraShow, type } = this.props + const cls = classNames( + 'hi-card', + hoverable && 'hi-card--hover', + size && `hi-card--${size}`, + disabled && 'hi-card--disabled', + !cover && type !== 'simple' && 'hi-card--padding', + type === 'simple' && `hi-card--simple-${size}` + ) + const headerCls = classNames( + 'hi-card__header', + !title && extra && 'hi-card__header--onlyextra' + ) + const coverCls = classNames( + 'hi-card__content', + cover && 'hi-card__content--cover' + ) + const extraCls = classNames( + 'hi-card__extra', + !this.state.extraShow && 'hi-card__extra--hide' + ) + const header = (title || extra) && ( + <div className={headerCls}> + <div className='hi-card__title'> + {title} + </div> + { + extra && ( + <div className={extraCls}> + {extra} + </div> + ) + } + </div> + ) + + let body = ( + <React.Fragment> + {header} + <div className={coverCls}> + {children} + </div> + </React.Fragment> + ) + let coverDom = null + if (cover) { + coverDom = ( + <React.Fragment> + { + React.cloneElement(cover, {style: {width: '100%'}}) + } + <div className={coverCls}> + {header} + {description} + </div> + </React.Fragment> + ) + } + if (type === 'simple') { + body = <div className='hi-card__content hi-card__content--simple'>{children}</div> + coverDom = null + } + return ( + <div + className={cls} + style={style} + onMouseEnter={() => { + extraShow === 'hover' && this.setState({ + extraShow: true + }) + }} + onMouseLeave={() => { + extraShow === 'hover' && this.setState({ + extraShow: false + }) + }} + > + {coverDom || body} + </div> + ) + } +} +Card.defaultProps = { + hoverable: false, + title: '', + extra: '', + extraShow: 'hover' +} +Card.propTypes = { + // 支持三种宽度预设值 small -> 276px middle -> 376px large -> 576px,如果传入 style 则忽略该值,如果缺省 style 及 size 则默认100% + size: PropTypes.oneOf(['small', 'middle', 'large']), + // 一种简单的小卡片 + type: PropTypes.oneOf(['simple']), + style: PropTypes.object, + // 鼠标移入是否显示悬停态(浮起) + hoverable: PropTypes.bool, + // 卡片标题 + title: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.string + ]), + // 扩展工具(出现在卡片右上角) + extra: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.string, + PropTypes.array + ]), + // 封面图(图片卡片) 含有 cover 属性时,title 将会做为图片下文的 title,而不会出现在卡片左上角 + cover: PropTypes.element, + // 用于图片卡片,做为描述 + description: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.string + ]), + disabled: PropTypes.bool, + // 扩展按钮显示模式 + extraShow: PropTypes.oneOf(['stay', 'hover']) +} +export default Card diff --git a/components/card/style/index.js b/components/card/style/index.js new file mode 100644 index 000000000..63810a681 --- /dev/null +++ b/components/card/style/index.js @@ -0,0 +1 @@ +import './index.scss' diff --git a/components/card/style/index.scss b/components/card/style/index.scss new file mode 100644 index 000000000..5d4eabc65 --- /dev/null +++ b/components/card/style/index.scss @@ -0,0 +1,91 @@ +.hi-card { + border-radius: 2px; + border: 1px solid rgba(231, 231, 231, 1); + box-sizing: border-box; + + &--padding { + padding: 24px; + } + + &--hover:not(.hi-card--disabled) { + &:hover { + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.08); + // cursor: pointer; + } + } + + &--disabled { + background: rgba(246, 246, 246, 1); + color: rgba(204, 204, 204, 1); + cursor: not-allowed; + user-select: none; + } + + &__header { + height: 24px; + line-height: 24px; + display: flex; + padding-bottom: 12px; + justify-content: space-between; + + &--onlyextra { + justify-content: flex-end; + } + } + + &__title { + font-size: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__content { + &--cover { + padding: 16px; + } + + &--simple { + height: 40px; + line-height: 40px; + padding: 0 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + &__extra { + cursor: pointer; + color: #4284f5; + font-size: 14px; + + &--hide { + display: none; + } + } + + &--middle { + width: 376px; + } + + &--small { + width: 276px; + } + + &--large { + width: 576px; + } + + &--simple-small { + width: 136px; + } + + &--simple-middle { + width: 156px; + } + + &--simple-large { + width: 176px; + } +} diff --git a/components/index.js b/components/index.js index 4b9d04172..c07a68149 100755 --- a/components/index.js +++ b/components/index.js @@ -36,4 +36,5 @@ export { default as FormItem } from './form/item' export { default as Ficon } from './ficon' export { default as Icon } from './icon' export { default as Progress } from './progress' +export { default as Card } from './card' export { ThemeContext, LocaleContext } from './context' diff --git a/docs/en-US/components/card.md b/docs/en-US/components/card.md new file mode 100644 index 000000000..e0ce5f2f8 --- /dev/null +++ b/docs/en-US/components/card.md @@ -0,0 +1,407 @@ +<style scoped> + p { + margin: 0 !important; + } +</style> + +## Card 卡片 + + +### 基础 + +:::demo + +Card 组件 + +```js +render() { + const Row = Grid.Row + const Col = Grid.Col + return ( + <div> + <Card title="标题"> + <span>普通 Card</span> + </Card> + <br /> + <Card hoverable> + <p>无标题</p> + <p>鼠标移入悬浮效果</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='large' disabled title='标题'> + <p>禁用状态</p> + </Card> + <br /> + <Card hoverable size='large' disabled> + 无标题,禁用状态 + </Card> + </div> + ) +} +``` +::: + + +### 不同大小 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card hoverable style={{width: 200}} title='标题内容-标题内容-标题内容'> + <p>自定义宽度:200px</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='small'> + <p>size = small</p> + <p>无标题</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='middle'> + <p>size = middle</p> + <p>无标题</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='large'> + <p>size = large</p> + <p>无标题</p> + <p>其它额外内容</p> + </Card> + </React.Fragment> + ) +} +``` +::: + +### 额外按钮 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card + hoverable + extra={[<Icon name='edit' key={1}/>, <Icon name='delete' key={2}/>]} + extraShow='stay' + title='这里是标题这里是标题这里是标题' + > + <p>包含额外扩展按钮</p> + <p>扩展按钮常驻</p> + </Card> + <br /> + <Card + hoverable + extra={[<Icon name='edit' key={1} />, <Icon name='delete' key={2}/>]} + extraShow='hover' + > + <p>有扩展按钮,无标题</p> + <p>扩展按钮移入后显示</p> + <p>其它剩余内容;其它剩余内容;其它剩余内容;其它剩余内容;其它剩余内容;</p> + </Card> + </React.Fragment> + ) +} +``` +::: + + +### 图片卡片 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='图片信息描述:size=small;width:276px' + size='small' + ></Card> + <br /> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='图片信息描述:size=middle;width:376px' + size='middle' + ></Card> + + <br /> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='图片信息描述:size=large;width:576px' + size='large' + ></Card> + </React.Fragment> + ) +} +``` +::: + + +### 简易卡片 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card + hoverable + type='simple' + size='small' + > + 简易卡片 + </Card> + <br/> + <Card + hoverable + type='simple' + size='middle' + > + 简易卡片 + </Card> + <br/> + <Card + hoverable + type='simple' + size='large' + disabled + > + 简易卡片 + </Card> + <br/> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </React.Fragment> + ) +} +``` +::: + +### 配合 Gird 布局 - 图片排版 + +:::demo + +配合 Grid 布局实现更加简单 + +```js +render() { + const {Row, Col} = Grid + return ( + <React.Fragment> + <Row gutter={true}> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + </Row> + <Row gutter={true}> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + </Row> + </React.Fragment> + ) +} +``` +::: + +### 配合 Gird 布局 - 展示 + +:::demo + +配合 Grid 布局实现更加简单 + +```js +render() { + const {Row, Col} = Grid + return ( + <React.Fragment> + <Row gutter={true}> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + </Row> + </React.Fragment> + ) +} +``` +::: + + +### 配合 Gird 布局 - 自定义展示 + +:::demo + +配合 Grid 布局实现更加简单 + +```js +render() { + const {Row, Col} = Grid + const colors = ['#46bc99', '#37d5fa', '#b450de', '#fadb14'] + const cols = colors.map((color, index) => { + return ( + <Col span={6} key={index}> + <Card hoverable style={{borderLeft: `2px solid ${color}`,}}> + <p>无标题</p> + <p>鼠标移入悬浮效果</p> + <p>其它额外内容</p> + </Card> + </Col> + ) + }) + return ( + <React.Fragment> + <Row gutter={true}> + {cols} + </Row> + </React.Fragment> + ) +} +``` +::: + + +### Card Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------- | ------- | ------- | ------- | ------- | +| title | 卡片标题 | String \| Element | - | - | +| size | 卡片大小<br/> small -> 276px <br/> middle -> 376px <br/> large -> 576px <br/> 不传入 size 将占100%宽度| String | small \| middle \| large | 100% | +| type | 卡片类型 | String | simple | - | +| style | 自定义样式 | Object | - | - | +| hoverable | 鼠标移入卡片是否显示浮起效果 | Boolean | true \| false | false | +| extra | 卡片右上的扩展按钮 | Array[Elemnt] \| Element | - | - | +| extraShow | 扩展按钮的显示模式 | String | stay \| hover | stay | +| cover | 图片卡片 | ImgElement | - | - | +| description | 图片卡片的信息描述 | String \| Element | - | - | +| disabled | 是否禁用卡片 | Boolean | true \| false | - | diff --git a/docs/zh-CN/components/card.md b/docs/zh-CN/components/card.md new file mode 100644 index 000000000..e0ce5f2f8 --- /dev/null +++ b/docs/zh-CN/components/card.md @@ -0,0 +1,407 @@ +<style scoped> + p { + margin: 0 !important; + } +</style> + +## Card 卡片 + + +### 基础 + +:::demo + +Card 组件 + +```js +render() { + const Row = Grid.Row + const Col = Grid.Col + return ( + <div> + <Card title="标题"> + <span>普通 Card</span> + </Card> + <br /> + <Card hoverable> + <p>无标题</p> + <p>鼠标移入悬浮效果</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='large' disabled title='标题'> + <p>禁用状态</p> + </Card> + <br /> + <Card hoverable size='large' disabled> + 无标题,禁用状态 + </Card> + </div> + ) +} +``` +::: + + +### 不同大小 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card hoverable style={{width: 200}} title='标题内容-标题内容-标题内容'> + <p>自定义宽度:200px</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='small'> + <p>size = small</p> + <p>无标题</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='middle'> + <p>size = middle</p> + <p>无标题</p> + <p>其它额外内容</p> + </Card> + <br /> + <Card hoverable size='large'> + <p>size = large</p> + <p>无标题</p> + <p>其它额外内容</p> + </Card> + </React.Fragment> + ) +} +``` +::: + +### 额外按钮 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card + hoverable + extra={[<Icon name='edit' key={1}/>, <Icon name='delete' key={2}/>]} + extraShow='stay' + title='这里是标题这里是标题这里是标题' + > + <p>包含额外扩展按钮</p> + <p>扩展按钮常驻</p> + </Card> + <br /> + <Card + hoverable + extra={[<Icon name='edit' key={1} />, <Icon name='delete' key={2}/>]} + extraShow='hover' + > + <p>有扩展按钮,无标题</p> + <p>扩展按钮移入后显示</p> + <p>其它剩余内容;其它剩余内容;其它剩余内容;其它剩余内容;其它剩余内容;</p> + </Card> + </React.Fragment> + ) +} +``` +::: + + +### 图片卡片 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='图片信息描述:size=small;width:276px' + size='small' + ></Card> + <br /> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='图片信息描述:size=middle;width:376px' + size='middle' + ></Card> + + <br /> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='图片信息描述:size=large;width:576px' + size='large' + ></Card> + </React.Fragment> + ) +} +``` +::: + + +### 简易卡片 + +:::demo + +Card 组件 + +```js +render() { + return ( + <React.Fragment> + <Card + hoverable + type='simple' + size='small' + > + 简易卡片 + </Card> + <br/> + <Card + hoverable + type='simple' + size='middle' + > + 简易卡片 + </Card> + <br/> + <Card + hoverable + type='simple' + size='large' + disabled + > + 简易卡片 + </Card> + <br/> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </React.Fragment> + ) +} +``` +::: + +### 配合 Gird 布局 - 图片排版 + +:::demo + +配合 Grid 布局实现更加简单 + +```js +render() { + const {Row, Col} = Grid + return ( + <React.Fragment> + <Row gutter={true}> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + </Row> + <Row gutter={true}> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + <Col span={6}> + <Card + hoverable + cover={<img src='https://i.loli.net/2019/04/18/5cb82488403de.png'/>} + title='图片展示' + description='这是图片描述' + ></Card> + </Col> + </Row> + </React.Fragment> + ) +} +``` +::: + +### 配合 Gird 布局 - 展示 + +:::demo + +配合 Grid 布局实现更加简单 + +```js +render() { + const {Row, Col} = Grid + return ( + <React.Fragment> + <Row gutter={true}> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + <Col span={4}> + <Card + hoverable + type='simple' + > + 简易卡片 + </Card> + </Col> + </Row> + </React.Fragment> + ) +} +``` +::: + + +### 配合 Gird 布局 - 自定义展示 + +:::demo + +配合 Grid 布局实现更加简单 + +```js +render() { + const {Row, Col} = Grid + const colors = ['#46bc99', '#37d5fa', '#b450de', '#fadb14'] + const cols = colors.map((color, index) => { + return ( + <Col span={6} key={index}> + <Card hoverable style={{borderLeft: `2px solid ${color}`,}}> + <p>无标题</p> + <p>鼠标移入悬浮效果</p> + <p>其它额外内容</p> + </Card> + </Col> + ) + }) + return ( + <React.Fragment> + <Row gutter={true}> + {cols} + </Row> + </React.Fragment> + ) +} +``` +::: + + +### Card Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------- | ------- | ------- | ------- | ------- | +| title | 卡片标题 | String \| Element | - | - | +| size | 卡片大小<br/> small -> 276px <br/> middle -> 376px <br/> large -> 576px <br/> 不传入 size 将占100%宽度| String | small \| middle \| large | 100% | +| type | 卡片类型 | String | simple | - | +| style | 自定义样式 | Object | - | - | +| hoverable | 鼠标移入卡片是否显示浮起效果 | Boolean | true \| false | false | +| extra | 卡片右上的扩展按钮 | Array[Elemnt] \| Element | - | - | +| extraShow | 扩展按钮的显示模式 | String | stay \| hover | stay | +| cover | 图片卡片 | ImgElement | - | - | +| description | 图片卡片的信息描述 | String \| Element | - | - | +| disabled | 是否禁用卡片 | Boolean | true \| false | - | diff --git a/site/locales/en-US.js b/site/locales/en-US.js index 1ab96d4f1..5458f5b69 100644 --- a/site/locales/en-US.js +++ b/site/locales/en-US.js @@ -42,7 +42,8 @@ module.exports = { grid: 'Grid', stepper: 'Stepper', icon: 'Icon', - progress: 'Progress' + progress: 'Progress', + card: 'Card' }, designs: { 'design-patterns': '设计模式', diff --git a/site/locales/zh-CN.js b/site/locales/zh-CN.js index 8ff0618b6..becbb4d6a 100755 --- a/site/locales/zh-CN.js +++ b/site/locales/zh-CN.js @@ -42,7 +42,8 @@ module.exports = { grid: 'Grid 栅格', stepper: 'Stepper 步骤', icon: 'Icon 图标', - progress: 'Progress 进度条' + progress: 'Progress 进度条', + card: 'Card 卡片' }, designs: { 'design-patterns': '设计模式', diff --git a/site/pages/components/card/index.js b/site/pages/components/card/index.js new file mode 100644 index 000000000..e1269f0c0 --- /dev/null +++ b/site/pages/components/card/index.js @@ -0,0 +1,9 @@ +import Markdown from '../../../../libs/markdown' + +class Card extends Markdown { + document (locale) { + return require(`../../../../docs/${locale}/components/card.md`) + } +} + +export default Card diff --git a/site/pages/components/index.js b/site/pages/components/index.js index e200ba547..94bec3c08 100755 --- a/site/pages/components/index.js +++ b/site/pages/components/index.js @@ -42,7 +42,8 @@ export default { 'collapse': require('./collapse'), 'tooltip': require('./tooltip'), 'popover': require('./popover'), - 'progress': require('./progress') + 'progress': require('./progress'), + 'card': require('./card') }, 'group-tips': { 'modal': require('./modal'), From 8d1d30de1f82de8f1e4b92fffe0b56ed14e81bc6 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Fri, 19 Apr 2019 11:36:41 +0800 Subject: [PATCH 035/112] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/date-picker/BasePicker.js | 2 +- components/date-picker/TimePanel.js | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/components/date-picker/BasePicker.js b/components/date-picker/BasePicker.js index 3c7096678..1bca22941 100644 --- a/components/date-picker/BasePicker.js +++ b/components/date-picker/BasePicker.js @@ -101,7 +101,7 @@ class BasePicker extends Component { date = value } - if (type.indexOf('range') !== -1 || type === 'timeperiod') { + if (type.includes('range') !== -1 || type === 'timeperiod') { if (value instanceof Date || !value) { // 如果为时间段选择,则取默认的第一个范围 date = {startDate: startOfDay(date), endDate: type === 'timeperiod' ? addHours(startOfDay(date), 4) : endOfDay(date)} diff --git a/components/date-picker/TimePanel.js b/components/date-picker/TimePanel.js index 06a644c49..a7a4c9ded 100644 --- a/components/date-picker/TimePanel.js +++ b/components/date-picker/TimePanel.js @@ -26,14 +26,6 @@ class TimePanel extends Component { return ( <div className='hi-timepicker' style={this.state.style}> <Time date={this.props.date} onPick={this.onTimePick.bind(this)} onlyTime={this.props.type === 'time'} /> - { - // this.props.type === 'time' && ( - // <div className='hi-timepicker__footer'> - // <Button type='primary' size='small' onClick={() => this.props.timeConfirm(this.state.date, true)}>{this.props.localeDatas.datePicker.ok}</Button> - // </div> - // ) - } - </div> ) } From 9e5868a27e34a613b02904dbecc0d9b2a040fc68 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Fri, 19 Apr 2019 11:41:32 +0800 Subject: [PATCH 036/112] indexOf -> includes --- components/date-picker/BasePicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/date-picker/BasePicker.js b/components/date-picker/BasePicker.js index 1bca22941..d5c09e2a7 100644 --- a/components/date-picker/BasePicker.js +++ b/components/date-picker/BasePicker.js @@ -101,7 +101,7 @@ class BasePicker extends Component { date = value } - if (type.includes('range') !== -1 || type === 'timeperiod') { + if (type.includes('range') || type === 'timeperiod') { if (value instanceof Date || !value) { // 如果为时间段选择,则取默认的第一个范围 date = {startDate: startOfDay(date), endDate: type === 'timeperiod' ? addHours(startOfDay(date), 4) : endOfDay(date)} From d76f1bc925be33a98cf43d48c24e160827a629b7 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 19 Apr 2019 15:32:58 +0800 Subject: [PATCH 037/112] add style --- components/tree/TreeDivider.js | 20 +++++++++-------- components/tree/TreeItem.js | 29 +++++++++++++++++++------ components/tree/TreeNode.js | 39 +++++++++++++++++++++++++++++----- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/components/tree/TreeDivider.js b/components/tree/TreeDivider.js index 7970226da..e0fe597f7 100644 --- a/components/tree/TreeDivider.js +++ b/components/tree/TreeDivider.js @@ -1,14 +1,16 @@ import React from 'react' -const TreeDivider = () => { +const TreeDivider = props => { + const style = props.top + ? { + position: 'absolute', + display: 'flex', + width: '100%', + alignItems: 'center', + top: 21 + } + : { position: 'absolute', display: 'flex', width: '100%', alignItems: 'center', bottom: 0 } return ( - <div - style={{ - position: 'absolute', - display: 'flex', - width: '100%', - alignItems: 'center' - }} - > + <div style={style}> <div style={{ flex: '0 0 5px', diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 23a2d8e90..f1276f11c 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -10,6 +10,7 @@ const Types = { class TreeItem extends Component { render () { const { + dropDividerPosition, // setDraggingNode, // removeDraggingNode, checked, @@ -62,6 +63,7 @@ class TreeItem extends Component { key={item.id} className={itemContainerStyle} > + {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> {item.children && item.children.length > 0 ? renderSwitcher(expanded) @@ -151,7 +153,7 @@ class TreeItem extends Component { > {renderText(item.title)} {renderRightClickMenu(item)} - {targetNode === item.id && <TreeDivider />} + {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} </span> )} {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} @@ -180,7 +182,8 @@ const target = { dropNode, removeDraggingNode, expandTreeNode, - removeTargetNode + removeTargetNode, + dropDividerPosition } = props // 先看下是不是在最近得组件 @@ -201,7 +204,7 @@ const target = { } else { // // 3.移动节点到相应位置 // console.log('>>>>>>>>>>>>>>>>', targetItem, sourceItem) - dropNode(sourceItem, targetItem) + dropNode(sourceItem, targetItem, dropDividerPosition) removeDraggingNode() removeTargetNode() } @@ -222,12 +225,24 @@ const target = { // 2.如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 return false } else { - const clientOffset = monitor.getClientOffset() - const componentRect = findDOMNode(component).getBoundingClientRect() - console.log('>>>>>>>>>>>>>>>>', targetItem.id, sourceItem.id, clientOffset, componentRect) + const sourcePosition = monitor.getClientOffset() + const targetComponent = findDOMNode(component).getBoundingClientRect() + // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 + console.log( + '>>>>>>>>>>>>>>>>', + targetItem.id, + sourceItem.id, + sourcePosition, + targetComponent + ) // // 3.移动节点到相应位置 // console.log() - setTargetNode(targetItem.id) + if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { + setTargetNode(targetItem.id, 'sub') + } else { + setTargetNode(targetItem.id, 'down') + } + setDraggingNode(sourceItem.id) } } diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 6fdcf26c1..fb4bc9bf3 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -82,6 +82,8 @@ export default class TreeNode extends Component { draggingNode: null, // 处于目标状态的节点 targetNode: null, + // 放置线的位置,分为下线和子线,下线则放置在该节点下侧,子线为放置在该节点内部 + dropDividerPosition: null, searchValue: '' } } @@ -340,11 +342,30 @@ export default class TreeNode extends Component { }) return node } - dropNode = (sourceItem, targetItem) => { + switchDropNode = (targetItemId, sourceItemId, data, allData) => { + data.forEach(item => { + if (item.children) { + if (item.children.some(e => e.id === targetItemId)) { + const index = item.children.findIndex(i => i.id === targetItemId) + const sourceNode = this.findNode(sourceItemId, allData) + item.children.splice(index + 1, 0, sourceNode) + } else { + this.switchDropNode(targetItemId, sourceItemId, item.children, allData) + } + } + }) + } + dropNode = (sourceItem, targetItem, dropDividerPosition) => { const { dataCache } = this.state const _dataCache = cloneDeep(dataCache) this._delDragNode(sourceItem.id, _dataCache) - this._addDropNode(targetItem.id, sourceItem.id, _dataCache, dataCache) + if (dropDividerPosition === 'sub') { + // 这里为什么用 sourceItem.id不用 sourceItem 是因为 sourceItem 有可能是 highlight 过得 + this._addDropNode(targetItem.id, sourceItem.id, _dataCache, dataCache) + } else { + this.switchDropNode(targetItem.id, sourceItem.id, _dataCache, dataCache) + } + this.setState({ dataCache: _dataCache }) @@ -397,8 +418,8 @@ export default class TreeNode extends Component { showRightClickMenu: null }) } - setTargetNode = id => { - this.setState({ targetNode: id }) + setTargetNode = (id, position) => { + this.setState({ targetNode: id, dropDividerPosition: position }) } removeTargetNode = () => { this.setState({ targetNode: null }) @@ -418,7 +439,14 @@ export default class TreeNode extends Component { closeExpandedTreeNode, expandTreeNode } = this.props - const { highlight, editNodes, editingNodes, draggingNode, targetNode } = this.state + const { + highlight, + editNodes, + editingNodes, + draggingNode, + targetNode, + dropDividerPosition + } = this.state return ( <ul> @@ -436,6 +464,7 @@ export default class TreeNode extends Component { return ( <TreeItem key={item.id} + dropDividerPosition={dropDividerPosition} prefixCls={prefixCls} draggable={draggable} checked={checked} From 373227bf7090190843988a66a07caa885b74d560 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 19 Apr 2019 15:40:21 +0800 Subject: [PATCH 038/112] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/TreeItem.js | 58 +++++-------------------------------- 1 file changed, 8 insertions(+), 50 deletions(-) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index f1276f11c..4c9019169 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -11,18 +11,12 @@ class TreeItem extends Component { render () { const { dropDividerPosition, - // setDraggingNode, - // removeDraggingNode, checked, expanded, - // dropNode, - // draggable, highlight, editNodes, editingNodes, prefixCls, - // dragNodePosition, - // dragNode, withLine, semiChecked, onNodeClick, @@ -53,16 +47,7 @@ class TreeItem extends Component { } = this.props return connectDropTarget( connectDragSource( - <li - // onDragStart={this.onDragStart.bind(this, item, data)} - // onDragEnter={this.onDragEnter.bind(this, item, data)} - // onDragOver={this.onDragOver.bind(this, item, data)} - // onDragLeave={this.onDragLeave.bind(this, item, data)} - // onDrop={this.onDrop.bind(this, item, data)} - // draggable={draggable} - key={item.id} - className={itemContainerStyle} - > + <li key={item.id} className={itemContainerStyle}> {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> {item.children && item.children.length > 0 @@ -79,9 +64,6 @@ class TreeItem extends Component { onNodeClick && onNodeClick(item) onClick && onClick(item) highlightable && onSetHighlight(item) - // this.setState({ - // highlight: item.id - // }) e.stopPropagation() }} highlight={highlight === item.id} @@ -125,29 +107,16 @@ class TreeItem extends Component { highlight === item.id ? 'highlight' : '' } ${draggingNode === item.id ? 'dragging' : ''}`} onContextMenu={e => { - // - // if (this.props.editable) { - // e.preventDefault() - // } - e.preventDefault() - // this.setState({ - // showRightClickMenu: item.id, - // highlight: item.id - // }) - showRightClickMenu(item) + if (this.props.editable) { + e.preventDefault() + showRightClickMenu(item) + } }} onClick={e => { - // this.setState({ - // showRightClickMenu: null - // }) closeRightClickMenu() onNodeClick && onNodeClick(item) onClick && onClick(item) - highlightable && - // this.setState({ - // highlight: item.id - // }) - onSetHighlight(item) + highlightable && onSetHighlight(item) e.stopPropagation() }} > @@ -172,9 +141,6 @@ const source = { } } const target = { - // canDrop () { - // return true - // }, drop (props, monitor) { const { sourceItem, originalExpandStatus } = monitor.getItem() const { @@ -203,7 +169,6 @@ const target = { removeTargetNode() } else { // // 3.移动节点到相应位置 - // console.log('>>>>>>>>>>>>>>>>', targetItem, sourceItem) dropNode(sourceItem, targetItem, dropDividerPosition) removeDraggingNode() removeTargetNode() @@ -227,16 +192,9 @@ const target = { } else { const sourcePosition = monitor.getClientOffset() const targetComponent = findDOMNode(component).getBoundingClientRect() + // 3.移动节点到相应位置 // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 - console.log( - '>>>>>>>>>>>>>>>>', - targetItem.id, - sourceItem.id, - sourcePosition, - targetComponent - ) - // // 3.移动节点到相应位置 - // console.log() + if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { setTargetNode(targetItem.id, 'sub') } else { From 4aa6c5ac0f3598f6b8c5b2668360f345622a4665 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 19 Apr 2019 18:40:58 +0800 Subject: [PATCH 039/112] optimize code --- components/tree/Tree.js | 144 +----------------------------------- components/tree/TreeItem.js | 8 +- components/tree/TreeNode.js | 102 +++++-------------------- components/tree/util.js | 126 ++++++++++++++++++++++++------- 4 files changed, 129 insertions(+), 251 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 54a0fff50..bbed04f37 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -1,33 +1,13 @@ import React, { Component } from 'react' import classNames from 'classnames' import PropTypes from 'prop-types' -// import Checkbox from '../checkbox/index' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' -import { calcDropPosition, deepClone, getChildren, getDisabled, getAll } from './util' +import { deepClone, getChildren, getDisabled, getAll, dealData } from './util' import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' import './style/index' -const dealData = (data, tempData = {}, parent = null) => { - if (data.length === 0) { - return data - } - data.map(item => { - tempData[item.id] = { ...item } - if (parent) { - tempData[item.id].parent = parent - } - if (item.children && item.children.length > 0) { - const tempArr = [] - item.children.map(i => { - tempArr.push(i.id) - }) - tempData[item.id].children = tempArr - dealData(item.children, tempData, item.id) - } - }) -} class Tree extends Component { constructor (props) { @@ -70,6 +50,7 @@ class Tree extends Component { if (!isEqual(props.data, prevState.data)) { const dataMap = {} dealData(deepClone(props.data), dataMap) + console.log(props.data, prevState.data, dataMap) data.dataMap = dataMap data.data = props.data @@ -262,126 +243,11 @@ class Tree extends Component { }) } - // 当拖拽元素开始被拖拽的时候触发的事件 - onDragStart = (e, data) => { - const { onDragStart } = this.props - e.stopPropagation() - - let expandedArr = this.state.hasExpanded - - if (expandedArr.indexOf(data.id) >= 0) { - expandedArr.splice(expandedArr.indexOf(data.id), 1) - } - - this.dargNode = e.target - this.curData = data - this.setState({ - expandedKeys: expandedArr - }) - if (onDragStart) { - onDragStart(e) - } - - try { - e.dataTransfer.setData('text/plain', '') - } catch (error) {} - } - // 当拖拽完成后触发的事件 - onDragEnd = e => { - const { onDragEnd } = this.props - e.stopPropagation() - - if (onDragEnd) { - onDragEnd(e) - } - } - - // 当拖曳元素进入目标元素的时候触发的事件 - onDragEnter = (e, data) => { - const { onDragEnter } = this.props - let dropPosition = calcDropPosition(e, e.currentTarget) - - e.preventDefault() - e.stopPropagation() - - if (data.id === this.curData.id && dropPosition === 0) { - this.setState({ - dragNode: '', - dragNodePosition: null - }) - return - } - - setTimeout(() => { - this.setState({ - dragNode: data.id, - dragNodePosition: dropPosition - }) - }, 0) - - if (onDragEnter) { - onDragEnter({ event: e, dropPosition }) - } - } - // 拖拽元素在目标元素上移动的时候触发的事件 - onDragOver = e => { - const { onDragOver } = this.props - e.preventDefault() - e.stopPropagation() - - if (onDragOver) { - onDragOver(e) - } - } - // 当拖拽元素离开目标元素时触发 - onDragLeave = e => { - const { onDragLeave } = this.props - e.stopPropagation() - - this.setState({ - dragNode: '', - dragNodePosition: null - }) - - if (onDragLeave) { - onDragLeave(e) - } - } - // 被拖拽的元素在目标元素上同时鼠标放开触发的事件 - onDrop = (e, data, parentData) => { - const { onDrop } = this.props - e.preventDefault() - e.stopPropagation() - this.setState({ - dragNode: '', - dragNodePosition: null - }) - this.props.dragEnd(this.curData, data, parentData) - if (onDrop) { - onDrop(e) - } - } - renderTreeNodes (data) { - const { - prefixCls, - // draggable, - checkable, - closeIcon, - openIcon, - withLine, - highlightable - } = this.props - // const { dragNode, dragNodePosition } = this.state - + const { prefixCls, checkable, closeIcon, openIcon, withLine, highlightable } = this.props + console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>2', data) return ( <TreeNode - // draggable={draggable || undefined} - // onDragStart={this.onDragStart} - // onDragEnter={this.onDragEnter} - // onDragOver={this.onDragOver} - // onDragLeave={this.onDragLeave} - // onDrop={this.onDrop} checked={this.props.checkedKeys || []} onNodeClick={this.props.onNodeClick} onClick={this.props.onClick} @@ -395,8 +261,6 @@ class Tree extends Component { onHightLightChange={this.props.onHightLightChange} onExpanded={this.onExpanded.bind(this)} data={data} - // dragNodePosition={dragNodePosition} - // dragNode={dragNode} prefixCls={prefixCls} checkable={checkable} highlightable={highlightable} diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 4c9019169..13fd2d7e2 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -10,6 +10,11 @@ const Types = { class TreeItem extends Component { render () { const { + // 节点可编辑 + // editable, + // 节点可拖拽 + // draggable, + // ******************** // dropDividerPosition, checked, expanded, @@ -35,7 +40,6 @@ class TreeItem extends Component { cancelAddSiblingNode, renderTree, renderRightClickMenu, - renderText, onCheckChange, onSetHighlight, showRightClickMenu, @@ -120,7 +124,7 @@ class TreeItem extends Component { e.stopPropagation() }} > - {renderText(item.title)} + {item.title} {renderRightClickMenu(item)} {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} </span> diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index fb4bc9bf3..34a33f226 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -7,7 +7,9 @@ import Input from '../input' import Icon from '../icon' import uuidv4 from 'uuid/v4' import TreeItem from './TreeItem' +import { collectExpandId, findNode } from './util' +// 高亮检索值 const highlightData = (data, highlightValue) => { return data.map(item => { if (typeof item.title === 'string' && item.title.includes(highlightValue)) { @@ -28,43 +30,6 @@ const highlightData = (data, highlightValue) => { return item }) } -// 寻找某一节点的父节点 -const getParentId = (id, data) => { - let parentId - data.forEach(item => { - if (item.children) { - if (item.children.some(item => item.id === id)) { - parentId = item.id - } else if (getParentId(id, item.children)) { - parentId = getParentId(id, item.children) - } - } - }) - return parentId -} -// 寻找某一节点的所有祖先节点 -const getAncestorIds = (id, data, arr = []) => { - if (getParentId(id, data)) { - arr.push(getParentId(id, data)) - getAncestorIds(getParentId(id, data), data, arr) - } - return arr -} -// 收集所有需要展开的节点 id -const collectExpandId = (data, searchValue, collection = [], allData) => { - data.forEach(item => { - if (item.title.includes(searchValue)) { - const parentIds = getAncestorIds(item.id, allData, []) - // console.log('parentIds', parentIds) - collection.splice(collection.length - 1, 0, ...parentIds) - // console.log('collection', collection) - } - if (item.children) { - collectExpandId(item.children, searchValue, collection, allData) - } - }) - return collection -} // const TreeNoder = DragSourceWrapper(TreeItem) export default class TreeNode extends Component { constructor (props) { @@ -97,21 +62,6 @@ export default class TreeNode extends Component { } return state } - onDragEnter (item, data, e) { - this.props.onDragEnter(e, item, data) - } - onDragOver (item, data, e) { - this.props.onDragOver(e, item, data) - } - onDragLeave (item, data, e) { - this.props.onDragLeave(e, item, data) - } - onDrop (item, data, e) { - this.props.onDrop(e, item, data) - } - onDragStart (item, data, e) { - this.props.onDragStart(e, item, data) - } getItem (name, treeItem) { let has = false @@ -154,6 +104,19 @@ export default class TreeNode extends Component { ) return <i className={switcherClsName} /> } + + // 设置拖拽中的节点 + setDraggingNode = itemId => { + this.setState({ + draggingNode: itemId + }) + } + // 移除拖拽中的节点 + removeDraggingNode = () => { + this.setState({ + draggingNode: null + }) + } // TODO:调整添加节点的策略,由深度遍历改为按层修改! // 添加兄弟节点 _addSibNode = (itemId, data, editingNodes) => { @@ -169,18 +132,6 @@ export default class TreeNode extends Component { } }) } - // 设置拖拽中的节点 - setDraggingNode = itemId => { - this.setState({ - draggingNode: itemId - }) - } - // 移除拖拽中的节点 - removeDraggingNode = () => { - this.setState({ - draggingNode: null - }) - } addSiblingNode = itemId => { const { dataCache, editingNodes } = this.state const _dataCache = cloneDeep(dataCache) @@ -316,7 +267,7 @@ export default class TreeNode extends Component { _addDropNode = (targetItemId, sourceItemId, data, allData) => { data.forEach((d, index) => { if (d.id === targetItemId) { - const sourceNode = this.findNode(sourceItemId, allData) + const sourceNode = findNode(sourceItemId, allData) if (!d.children) { d.children = [] } @@ -328,26 +279,13 @@ export default class TreeNode extends Component { } }) } - findNode = (itemId, data) => { - // console.log('allData', data) - let node - data.forEach((d, index) => { - if (d.id === itemId) { - node = d - } else { - if (d.children && this.findNode(itemId, d.children)) { - node = this.findNode(itemId, d.children) - } - } - }) - return node - } + switchDropNode = (targetItemId, sourceItemId, data, allData) => { data.forEach(item => { if (item.children) { if (item.children.some(e => e.id === targetItemId)) { const index = item.children.findIndex(i => i.id === targetItemId) - const sourceNode = this.findNode(sourceItemId, allData) + const sourceNode = findNode(sourceItemId, allData) item.children.splice(index + 1, 0, sourceNode) } else { this.switchDropNode(targetItemId, sourceItemId, item.children, allData) @@ -484,7 +422,6 @@ export default class TreeNode extends Component { renderSwitcher={this.renderSwitcher} cancelAddSiblingNode={this.cancelAddSiblingNode} renderRightClickMenu={this.renderRightClickMenu} - renderText={this.renderText} onCheckChange={this.onCheckChange} saveEditNode={this.saveEditNode} renderItemIcon={this.renderItemIcon} @@ -508,9 +445,6 @@ export default class TreeNode extends Component { </ul> ) } - renderText (text) { - return text - } render () { const { dataCache, searchValue } = this.state return ( diff --git a/components/tree/util.js b/components/tree/util.js index c2e0c515b..89fc1f510 100644 --- a/components/tree/util.js +++ b/components/tree/util.js @@ -85,8 +85,8 @@ export function getOffset (ele) { * @param {ele} tar 目标节点 */ export function insBefore (cur, tar) { - const current = cur.parentNode.parentNode// li - const target = tar.parentNode// li + const current = cur.parentNode.parentNode // li + const target = tar.parentNode // li target.parentNode.insertBefore(current, target) } /** @@ -95,8 +95,8 @@ export function insBefore (cur, tar) { * @param {ele} tar 目标节点 */ export function insAfter (cur, tar) { - const current = cur.parentNode.parentNode// li - const target = tar.parentNode// li + const current = cur.parentNode.parentNode // li + const target = tar.parentNode // li if (target.parentNode.lastChild === target) { target.parentNode.appendChild(current) } else { @@ -109,8 +109,8 @@ export function insAfter (cur, tar) { * @param {ele} tar 目标节点 */ export function insChild (cur, tar) { - const current = cur.parentNode.parentNode// li - const target = tar.parentNode.parentNode// li + const current = cur.parentNode.parentNode // li + const target = tar.parentNode.parentNode // li if (current === target) return if (target.childNodes.length > 1) { tar.parentNode.nextSibling.appendChild(current) @@ -141,12 +141,12 @@ export function deepClone (arr) { return Object.assign({}, arr) } return arr -}; +} export function deepMap (data, parent) { let arr = [] for (let key in data) { - let item = {...data[key]} + let item = { ...data[key] } if (parent) { item.parent = parent } else { @@ -181,29 +181,34 @@ export function getChild (data, id) { export function getSemi (data, checks) { let all = deepMap(data) - let arr = all.map(item => { - item.child = getChild(all, item.id) - item.family = item.parent.concat(item.child) - item.semi = false - let num = 0 - checks.forEach(c => { - if (item.child.includes(c)) { - num = num + 1 - } + let arr = all + .map(item => { + item.child = getChild(all, item.id) + item.family = item.parent.concat(item.child) + item.semi = false + let num = 0 + checks.forEach(c => { + if (item.child.includes(c)) { + num = num + 1 + } + }) + item.num = num + item.semi = num !== 0 && num !== item.child.length + return item }) - item.num = num - item.semi = num !== 0 && num !== item.child.length - return item - }).filter(item => item.semi).map(item => item.id) + .filter(item => item.semi) + .map(item => item.id) return arr } export function getChildren (data, id) { let all = deepMap(data) - return all.map(item => { - item.child = getChild(all, item.id) - return item - }).find(item => item.id === id).child + return all + .map(item => { + item.child = getChild(all, item.id) + return item + }) + .find(item => item.id === id).child } export function getDisabled (data) { @@ -233,3 +238,74 @@ export function getAll (data, checkedKeys) { }) return all } + +export const dealData = (data, tempData = {}, parent = null) => { + if (data.length === 0) { + return data + } + data.map(item => { + tempData[item.id] = { ...item } + if (parent) { + tempData[item.id].parent = parent + } + if (item.children && item.children.length > 0) { + const tempArr = [] + item.children.map(i => { + tempArr.push(i.id) + }) + tempData[item.id].children = tempArr + dealData(item.children, tempData, item.id) + } + }) +} +// 寻找某一节点的父节点 +export const getParentId = (id, data) => { + let parentId + data.forEach(item => { + if (item.children) { + if (item.children.some(item => item.id === id)) { + parentId = item.id + } else if (getParentId(id, item.children)) { + parentId = getParentId(id, item.children) + } + } + }) + return parentId +} +// 寻找某一节点的所有祖先节点 +export const getAncestorIds = (id, data, arr = []) => { + if (getParentId(id, data)) { + arr.push(getParentId(id, data)) + getAncestorIds(getParentId(id, data), data, arr) + } + return arr +} +// 收集所有需要展开的节点 id +export const collectExpandId = (data, searchValue, collection = [], allData) => { + data.forEach(item => { + if (item.title.includes(searchValue)) { + const parentIds = getAncestorIds(item.id, allData, []) + // console.log('parentIds', parentIds) + collection.splice(collection.length - 1, 0, ...parentIds) + // console.log('collection', collection) + } + if (item.children) { + collectExpandId(item.children, searchValue, collection, allData) + } + }) + return collection +} +// 给定一个结合,根据 id 寻找节点 +export const findNode = (itemId, data) => { + let node + data.forEach((d, index) => { + if (d.id === itemId) { + node = d + } else { + if (d.children && findNode(itemId, d.children)) { + node = findNode(itemId, d.children) + } + } + }) + return node +} From 0d1d9f810ac14da4abc806616ed4a22dfffb56a2 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 19 Apr 2019 18:59:48 +0800 Subject: [PATCH 040/112] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 9 ++++----- components/tree/TreeNode.js | 20 +++++--------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index bbed04f37..5ff494da3 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -17,7 +17,7 @@ class Tree extends Component { hasChecked: props.defaultCheckedKeys, hasExpanded: [], dataMap: {}, - data: {}, + data: [], dragNode: '', dragNodePosition: null, semiChecked: [], @@ -54,7 +54,7 @@ class Tree extends Component { data.dataMap = dataMap data.data = props.data - if (Object.keys(prevState.data).length === 0) { + if (prevState.data.length === 0) { if (props.defaultExpandAll) { let tempExpandedArr = [] for (let key in dataMap) { @@ -74,7 +74,7 @@ class Tree extends Component { return data } - onCheckChange (checked, item) { + onCheckChange = (checked, item) => { const { onChange, checkedKeys } = this.props let checkedArr = checkedKeys @@ -245,7 +245,6 @@ class Tree extends Component { renderTreeNodes (data) { const { prefixCls, checkable, closeIcon, openIcon, withLine, highlightable } = this.props - console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>2', data) return ( <TreeNode checked={this.props.checkedKeys || []} @@ -256,7 +255,7 @@ class Tree extends Component { closeExpandedTreeNode={this.closeExpandedTreeNode} expandTreeNode={this.expandTreeNode} setExpandTreeNodes={this.setExpandTreeNodes} - onCheckChange={this.onCheckChange.bind(this)} + onCheckChange={this.onCheckChange} hightLightNodes={this.props.hightLightNodes} onHightLightChange={this.props.onHightLightChange} onExpanded={this.onExpanded.bind(this)} diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 34a33f226..7f2c51364 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -73,19 +73,7 @@ export default class TreeNode extends Component { return has } - onCheckChange (checked, item) { - this.props.onCheckChange(checked, item) - } - - onExpanded = (expanded, item) => { - this.props.onExpanded(expanded, item) - } - nodeClick = item => { - this.props.onNodeClick(item) - } - renderSwitcher = expanded => { - // const { prefixCls, openIcon, closeIcon } = this.props const { prefixCls } = this.props const switcherClsName = classNames( `${prefixCls}-switcher`, @@ -375,7 +363,9 @@ export default class TreeNode extends Component { highlightable, checkable, closeExpandedTreeNode, - expandTreeNode + expandTreeNode, + onCheckChange, + onExpanded } = this.props const { highlight, @@ -416,13 +406,13 @@ export default class TreeNode extends Component { itemContainerStyle={itemContainerStyle} semiChecked={semiChecked} checkable={checkable} - onExpanded={this.onExpanded} + onExpanded={onExpanded} onValueChange={this.onValueChange} renderTree={this.renderTree} renderSwitcher={this.renderSwitcher} cancelAddSiblingNode={this.cancelAddSiblingNode} renderRightClickMenu={this.renderRightClickMenu} - onCheckChange={this.onCheckChange} + onCheckChange={onCheckChange} saveEditNode={this.saveEditNode} renderItemIcon={this.renderItemIcon} onNodeClick={onNodeClick} From 2be49e2fa5bfa4722dce32292c27c89a4b3b6a0a Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 22 Apr 2019 14:35:41 +0800 Subject: [PATCH 041/112] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 16 ++- components/tree/TreeDivider.js | 4 +- components/tree/TreeItem.js | 142 ++++++++++++-------- components/tree/TreeNode.js | 85 +++++++++--- components/tree/style/index.scss | 9 +- docs/zh-CN/components/tree.md | 224 ++++++++++++++++++++++++++++++- 6 files changed, 394 insertions(+), 86 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 5ff494da3..734cf95a5 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -244,7 +244,18 @@ class Tree extends Component { } renderTreeNodes (data) { - const { prefixCls, checkable, closeIcon, openIcon, withLine, highlightable } = this.props + const { + prefixCls, + checkable, + closeIcon, + openIcon, + withLine, + highlightable, + editable, + searchable, + draggable + } = this.props + console.log('123', draggable) return ( <TreeNode checked={this.props.checkedKeys || []} @@ -263,9 +274,12 @@ class Tree extends Component { prefixCls={prefixCls} checkable={checkable} highlightable={highlightable} + editable={editable} + searchable={searchable} openIcon={openIcon} closeIcon={closeIcon} withLine={withLine} + draggable={draggable} /> ) } diff --git a/components/tree/TreeDivider.js b/components/tree/TreeDivider.js index e0fe597f7..bbc8adb19 100644 --- a/components/tree/TreeDivider.js +++ b/components/tree/TreeDivider.js @@ -6,9 +6,9 @@ const TreeDivider = props => { display: 'flex', width: '100%', alignItems: 'center', - top: 21 + top: 22 } - : { position: 'absolute', display: 'flex', width: '100%', alignItems: 'center', bottom: 0 } + : { position: 'absolute', display: 'flex', width: '100%', alignItems: 'center', bottom: -2 } return ( <div style={style}> <div diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 13fd2d7e2..85d4bb079 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -13,7 +13,7 @@ class TreeItem extends Component { // 节点可编辑 // editable, // 节点可拖拽 - // draggable, + draggable, // ******************** // dropDividerPosition, checked, @@ -35,7 +35,6 @@ class TreeItem extends Component { onExpanded, onValueChange, renderItemIcon, - saveEditNode, cancelEditNode, cancelAddSiblingNode, renderTree, @@ -47,64 +46,65 @@ class TreeItem extends Component { renderSwitcher, connectDragSource, connectDropTarget, - targetNode + targetNode, + saveEditNode } = this.props return connectDropTarget( - connectDragSource( - <li key={item.id} className={itemContainerStyle}> - {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} - <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> - {item.children && item.children.length > 0 - ? renderSwitcher(expanded) - : withLine && renderItemIcon()} - </span> + <li key={item.id} className={itemContainerStyle}> + {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} + <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> + {item.children && item.children.length > 0 + ? renderSwitcher(expanded) + : withLine && renderItemIcon()} + </span> - {checkable ? ( - <Checkbox - semi={semiChecked.includes(item.id)} - checked={checked} - onChange={() => onCheckChange(checked, item)} - onTitleClick={e => { - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && onSetHighlight(item) - e.stopPropagation() + {checkable ? ( + <Checkbox + semi={semiChecked.includes(item.id)} + checked={checked} + onChange={() => onCheckChange(checked, item)} + onTitleClick={e => { + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && onSetHighlight(item) + e.stopPropagation() + }} + highlight={highlight === item.id} + text={item.title} + disabled={item.disabled} + /> + ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( + <div className='editing'> + <Input + placeholder='请输入菜单名称' + value={(editingNodes.find(node => node.id === item.id) || {}).title} + onChange={e => { + onValueChange(e.target.value, item.id) }} - highlight={highlight === item.id} - text={item.title} - disabled={item.disabled} /> - ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( - <div style={{ display: 'flex' }}> - <Input - placeholder='请输入菜单名称' - value={(editingNodes.find(node => node.id === item.id) || {}).title} - onChange={e => { - onValueChange(e.target.value, item.id) - }} - /> - <span - style={{ cursor: 'pointer' }} - onClick={() => { - saveEditNode(item.id) - }} - > - 确定 - </span> - <span - style={{ cursor: 'pointer' }} - onClick={() => { - if (editNodes.map(node => node.id).includes(item.id)) { - cancelEditNode(item.id) - } else { - cancelAddSiblingNode(item.id) - } - }} - > - 取消 - </span> - </div> - ) : ( + <span + style={{ cursor: 'pointer', marginRight: 12, color: '#4284F5' }} + onClick={() => { + saveEditNode(item.id) + }} + > + 确定 + </span> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + if (editNodes.map(node => node.id).includes(item.id)) { + cancelEditNode(item.id) + } else { + cancelAddSiblingNode(item.id) + } + }} + > + 取消 + </span> + </div> + ) : draggable ? ( + connectDragSource( <span style={item.style} className={`${prefixCls}_item-text ${itemStyle} ${ @@ -128,10 +128,34 @@ class TreeItem extends Component { {renderRightClickMenu(item)} {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} </span> - )} - {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} - </li> - ) + ) + ) : ( + <span + style={item.style} + className={`${prefixCls}_item-text ${itemStyle} ${ + highlight === item.id ? 'highlight' : '' + } ${draggingNode === item.id ? 'dragging' : ''}`} + onContextMenu={e => { + if (this.props.editable) { + e.preventDefault() + showRightClickMenu(item) + } + }} + onClick={e => { + closeRightClickMenu() + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && onSetHighlight(item) + e.stopPropagation() + }} + > + {item.title} + {renderRightClickMenu(item)} + {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} + </span> + )} + {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} + </li> ) } } diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 7f2c51364..e87571d9b 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -7,6 +7,7 @@ import Input from '../input' import Icon from '../icon' import uuidv4 from 'uuid/v4' import TreeItem from './TreeItem' +import Modal from '../modal' import { collectExpandId, findNode } from './util' // 高亮检索值 @@ -49,7 +50,9 @@ export default class TreeNode extends Component { targetNode: null, // 放置线的位置,分为下线和子线,下线则放置在该节点下侧,子线为放置在该节点内部 dropDividerPosition: null, - searchValue: '' + searchValue: '', + showModal: false, + currentDeleteNode: null } } static getDerivedStateFromProps (props, state) { @@ -322,7 +325,14 @@ export default class TreeNode extends Component { <li onClick={() => this.addSiblingNode(item.id)}>添加节点</li> <li onClick={() => this.addChildNode(item)}>添加子节点</li> <li onClick={() => this.editNode(item)}>编辑</li> - <li onClick={() => this.deleteNode(item.id)}>删除</li> + <li + onClick={() => { + this.setCurrentDeleteNode(item.id) + this.openModal() + }} + > + 删除 + </li> </ul> ) ) @@ -350,6 +360,16 @@ export default class TreeNode extends Component { removeTargetNode = () => { this.setState({ targetNode: null }) } + openModal = () => { + this.setState({ + showModal: true + }) + } + setCurrentDeleteNode = nodeId => { + this.setState({ + currentDeleteNode: nodeId + }) + } renderTree = data => { const { draggable, @@ -365,7 +385,8 @@ export default class TreeNode extends Component { closeExpandedTreeNode, expandTreeNode, onCheckChange, - onExpanded + onExpanded, + editable } = this.props const { highlight, @@ -392,6 +413,7 @@ export default class TreeNode extends Component { return ( <TreeItem key={item.id} + editable={editable} dropDividerPosition={dropDividerPosition} prefixCls={prefixCls} draggable={draggable} @@ -428,6 +450,7 @@ export default class TreeNode extends Component { setTargetNode={this.setTargetNode} targetNode={targetNode} removeTargetNode={this.removeTargetNode} + cancelEditNode={this.cancelEditNode} item={item} /> ) @@ -437,26 +460,48 @@ export default class TreeNode extends Component { } render () { const { dataCache, searchValue } = this.state + const { searchable } = this.props return ( <div> - <div className='hi-tree_searcher'> - <Input - value={this.state.searchValue} - type='text' - placeholder='关键词搜索' - onChange={e => { - this.setState({ searchValue: e.target.value }) - this.props.setExpandTreeNodes( - collectExpandId(dataCache, e.target.value, [], dataCache) - ) - }} - append={<Icon name='search' style={{ color: '#4284F5', fontSize: '24px' }} />} - style={{ width: '250px' }} - /> - </div> + {searchable && ( + <div className='hi-tree_searcher'> + <Input + value={this.state.searchValue} + type='text' + placeholder='关键词搜索' + onChange={e => { + this.setState({ searchValue: e.target.value }) + this.props.setExpandTreeNodes( + collectExpandId(dataCache, e.target.value, [], dataCache) + ) + }} + append={<Icon name='search' style={{ color: '#4284F5', fontSize: '24px' }} />} + style={{ width: '250px' }} + /> + </div> + )} + + {searchable + ? this.renderTree(highlightData(cloneDeep(dataCache), searchValue)) + : this.renderTree(cloneDeep(dataCache))} - {this.renderTree(highlightData(cloneDeep(dataCache), searchValue))} - {/* this.renderTree(cloneDeep(dataCache)) */} + <Modal + title='提示' + show={this.state.showModal} + onConfirm={() => { + this.deleteNode(this.state.currentDeleteNode) + this.setState({ + showModal: false + }) + }} + onCancel={() => { + this.setState({ + showModal: false + }) + }} + > + <span>删除节点将删除所有子节点,确定删除吗?</span> + </Modal> </div> ) } diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 53bd55aef..48f564f40 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -45,7 +45,14 @@ $tree: 'hi-tree' !default; margin: 22px 0; } } - + .editing { + display: flex; + align-items: center; + .hi-input { + width: 240px; + margin-right: 20px; + } + } .hi-checkbox-label { display: none; } diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 868ffd295..042d70858 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -53,9 +53,10 @@ clickEvent () { } render() { return ( - <div style={{width:300}}> + <div style={{width:500}}> <Tree defaultExpandAll + editable={true} data={this.state.treeData} defaultCheckedKeys={[2]} onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} @@ -73,7 +74,7 @@ render() { ::: -### checkbox +### 多选 :::demo @@ -83,7 +84,7 @@ checkbox constructor(props) { super(props) this.treeData = [ - { id: 1, title: '小米', + { id: 1, title: '小米人', children: [ { id: 2, title: '技术', children: [ @@ -118,6 +119,7 @@ render() { <div style={{width:300}}> <Tree checkable + editable={true} data={this.treeData} checkedKeys={this.state.checkedKeys} onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} @@ -138,6 +140,222 @@ render() { ::: +### 可搜索 + +通过搜索框对树进行过滤 + +:::demo + +```js +constructor(props) { + super(props) + this.state = { + treeData: [ + { id: 1, title: '小米快递', + children: [ + { id: 2, title: '技术', + children: [ + { id: 3, title: '后端', onClick: data => {console.log('后端:', data)} }, + { id: 4, title: '运维' }, + { id: 5, title: '前端' } + ] + }, + { id: 6, title: '产品' } + ] + }, + { id: 11, title: '小米', + children: [ + { id: 22, title: '技术', + children: [ + { id: 33, title: '后端' }, + { id: 44, title: '运维' }, + { id: 55, title: '前端' } + ] + }, + { id: 66, title: '产品' } + ] + }, + ] + } + +} +clickEvent () { + const data = this.state.treeData + data.push({ + id: 2, + title: '其它' + }) + this.setState({ + treeData: data + }) +} +render() { + return ( + <div style={{width:500}}> + <Tree + defaultExpandAll + searchable={true} + data={this.state.treeData} + defaultCheckedKeys={[2]} + onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} + onChange={data => {console.log('Tree data:', data)}} + openIcon='down' + closeIcon='up' + highlightable + onNodeClick={(item) => console.log('------click node', item)} + /> + <Button onClick={this.clickEvent.bind(this)}>点击</Button> + </div> + ) +} +``` + +::: + +### 可编辑 + +通过树的节点进行新增、删除、编辑等操作 + +:::demo + +```js +constructor(props) { + super(props) + this.state = { + treeData: [ + { id: 1, title: '小米快递', + children: [ + { id: 2, title: '技术', + children: [ + { id: 3, title: '后端', onClick: data => {console.log('后端:', data)} }, + { id: 4, title: '运维' }, + { id: 5, title: '前端' } + ] + }, + { id: 6, title: '产品' } + ] + }, + { id: 11, title: '小米', + children: [ + { id: 22, title: '技术', + children: [ + { id: 33, title: '后端' }, + { id: 44, title: '运维' }, + { id: 55, title: '前端' } + ] + }, + { id: 66, title: '产品' } + ] + }, + ] + } + +} +clickEvent () { + const data = this.state.treeData + data.push({ + id: 2, + title: '其它' + }) + this.setState({ + treeData: data + }) +} +render() { + return ( + <div style={{width:500}}> + <Tree + defaultExpandAll + editable={true} + data={this.state.treeData} + defaultCheckedKeys={[2]} + onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} + onChange={data => {console.log('Tree data:', data)}} + openIcon='down' + closeIcon='up' + highlightable + onNodeClick={(item) => console.log('------click node', item)} + /> + <Button onClick={this.clickEvent.bind(this)}>点击</Button> + </div> + ) +} +``` + +::: + +### 可拖拽 + +对树的节点进行拖拽操作 + +:::demo + +```js +constructor(props) { + super(props) + this.state = { + treeData: [ + { id: 1, title: '小米快递', + children: [ + { id: 2, title: '技术', + children: [ + { id: 3, title: '后端', onClick: data => {console.log('后端:', data)} }, + { id: 4, title: '运维' }, + { id: 5, title: '前端' } + ] + }, + { id: 6, title: '产品' } + ] + }, + { id: 11, title: '小米', + children: [ + { id: 22, title: '技术', + children: [ + { id: 33, title: '后端' }, + { id: 44, title: '运维' }, + { id: 55, title: '前端' } + ] + }, + { id: 66, title: '产品' } + ] + }, + ] + } + +} +clickEvent () { + const data = this.state.treeData + data.push({ + id: 2, + title: '其它' + }) + this.setState({ + treeData: data + }) +} +render() { + return ( + <div style={{width:500}}> + <Tree + defaultExpandAll + draggable={true} + data={this.state.treeData} + defaultCheckedKeys={[2]} + onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} + onChange={data => {console.log('Tree data:', data)}} + openIcon='down' + closeIcon='up' + highlightable + onNodeClick={(item) => console.log('------click node', item)} + /> + <Button onClick={this.clickEvent.bind(this)}>点击</Button> + </div> + ) +} +``` + +::: + ### Tree Attributes | 参数 | 说明 | 类型 | 可选值 | 默认值 | From 8301add2b46fed442b1c14fdbab91309c991625c Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 22 Apr 2019 16:31:42 +0800 Subject: [PATCH 042/112] =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 7 +- components/tree/TreeDivider.js | 2 +- components/tree/TreeItem.js | 168 ++++++++++++++++--------------- components/tree/TreeNode.js | 106 ++++++++++++------- components/tree/style/index.scss | 10 +- docs/zh-CN/components/tree.md | 47 +-------- 6 files changed, 175 insertions(+), 165 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 734cf95a5..9634816d6 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -33,7 +33,7 @@ class Tree extends Component { defaultExpandAll: PropTypes.bool, checkable: PropTypes.bool, draggable: PropTypes.bool, - withLine: PropTypes.bool, + // withLine: PropTypes.bool, onNodeClick: PropTypes.func, onClick: PropTypes.func, onChange: PropTypes.func @@ -249,13 +249,12 @@ class Tree extends Component { checkable, closeIcon, openIcon, - withLine, + // withLine, highlightable, editable, searchable, draggable } = this.props - console.log('123', draggable) return ( <TreeNode checked={this.props.checkedKeys || []} @@ -278,7 +277,7 @@ class Tree extends Component { searchable={searchable} openIcon={openIcon} closeIcon={closeIcon} - withLine={withLine} + // withLine={withLine} draggable={draggable} /> ) diff --git a/components/tree/TreeDivider.js b/components/tree/TreeDivider.js index bbc8adb19..39f28199d 100644 --- a/components/tree/TreeDivider.js +++ b/components/tree/TreeDivider.js @@ -6,7 +6,7 @@ const TreeDivider = props => { display: 'flex', width: '100%', alignItems: 'center', - top: 22 + top: 21 } : { position: 'absolute', display: 'flex', width: '100%', alignItems: 'center', bottom: -2 } return ( diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 85d4bb079..c039fe055 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -22,7 +22,7 @@ class TreeItem extends Component { editNodes, editingNodes, prefixCls, - withLine, + // withLine, semiChecked, onNodeClick, onClick, @@ -30,11 +30,11 @@ class TreeItem extends Component { item, draggingNode, checkable, - itemContainerStyle, + // itemContainerStyle, itemStyle, onExpanded, onValueChange, - renderItemIcon, + // renderItemIcon, cancelEditNode, cancelAddSiblingNode, renderTree, @@ -50,61 +50,95 @@ class TreeItem extends Component { saveEditNode } = this.props return connectDropTarget( - <li key={item.id} className={itemContainerStyle}> - {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} - <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> + <li key={item.id}> + <div + style={{ + display: 'flex' + }} + > + {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} + {/* <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> {item.children && item.children.length > 0 ? renderSwitcher(expanded) : withLine && renderItemIcon()} - </span> - - {checkable ? ( - <Checkbox - semi={semiChecked.includes(item.id)} - checked={checked} - onChange={() => onCheckChange(checked, item)} - onTitleClick={e => { - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && onSetHighlight(item) - e.stopPropagation() - }} - highlight={highlight === item.id} - text={item.title} - disabled={item.disabled} - /> - ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( - <div className='editing'> - <Input - placeholder='请输入菜单名称' - value={(editingNodes.find(node => node.id === item.id) || {}).title} - onChange={e => { - onValueChange(e.target.value, item.id) - }} - /> - <span - style={{ cursor: 'pointer', marginRight: 12, color: '#4284F5' }} - onClick={() => { - saveEditNode(item.id) - }} - > - 确定 + </span> */} + { + <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> + {item.children && item.children.length > 0 && renderSwitcher(expanded)} </span> - <span - style={{ cursor: 'pointer' }} - onClick={() => { - if (editNodes.map(node => node.id).includes(item.id)) { - cancelEditNode(item.id) - } else { - cancelAddSiblingNode(item.id) - } + } + {checkable ? ( + <Checkbox + semi={semiChecked.includes(item.id)} + checked={checked} + onChange={() => onCheckChange(checked, item)} + onTitleClick={e => { + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && onSetHighlight(item) + e.stopPropagation() }} - > - 取消 - </span> - </div> - ) : draggable ? ( - connectDragSource( + highlight={highlight === item.id} + text={item.title} + disabled={item.disabled} + /> + ) : item.status === 'editable' || editNodes.map(node => node.id).includes(item.id) ? ( + <div className='editing'> + <Input + placeholder='请输入菜单名称' + value={(editingNodes.find(node => node.id === item.id) || {}).title} + onChange={e => { + onValueChange(e.target.value, item.id) + }} + /> + <span + style={{ cursor: 'pointer', marginRight: 12, color: '#4284F5' }} + onClick={() => { + saveEditNode(item.id) + }} + > + 确定 + </span> + <span + style={{ cursor: 'pointer' }} + onClick={() => { + if (editNodes.map(node => node.id).includes(item.id)) { + cancelEditNode(item.id) + } else { + cancelAddSiblingNode(item.id) + } + }} + > + 取消 + </span> + </div> + ) : draggable ? ( + connectDragSource( + <span + style={item.style} + className={`${prefixCls}_item-text ${itemStyle} ${ + highlight === item.id ? 'highlight' : '' + } ${draggingNode === item.id ? 'dragging' : ''}`} + onContextMenu={e => { + if (this.props.editable) { + e.preventDefault() + showRightClickMenu(item) + } + }} + onClick={e => { + closeRightClickMenu() + onNodeClick && onNodeClick(item) + onClick && onClick(item) + highlightable && onSetHighlight(item) + e.stopPropagation() + }} + > + {item.title} + {renderRightClickMenu(item)} + {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} + </span> + ) + ) : ( <span style={item.style} className={`${prefixCls}_item-text ${itemStyle} ${ @@ -128,32 +162,8 @@ class TreeItem extends Component { {renderRightClickMenu(item)} {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} </span> - ) - ) : ( - <span - style={item.style} - className={`${prefixCls}_item-text ${itemStyle} ${ - highlight === item.id ? 'highlight' : '' - } ${draggingNode === item.id ? 'dragging' : ''}`} - onContextMenu={e => { - if (this.props.editable) { - e.preventDefault() - showRightClickMenu(item) - } - }} - onClick={e => { - closeRightClickMenu() - onNodeClick && onNodeClick(item) - onClick && onClick(item) - highlightable && onSetHighlight(item) - e.stopPropagation() - }} - > - {item.title} - {renderRightClickMenu(item)} - {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} - </span> - )} + )} + </div> {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} </li> ) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index e87571d9b..183d5c9a9 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -10,28 +10,6 @@ import TreeItem from './TreeItem' import Modal from '../modal' import { collectExpandId, findNode } from './util' -// 高亮检索值 -const highlightData = (data, highlightValue) => { - return data.map(item => { - if (typeof item.title === 'string' && item.title.includes(highlightValue)) { - const index = item.title.indexOf(highlightValue) - const beforeStr = item.title.substr(0, index) - const afterStr = item.title.substr(index + highlightValue.length) - item.title = ( - <span> - {beforeStr} - <span style={{ color: '#4284f5' }}>{highlightValue}</span> - {afterStr} - </span> - ) - } - if (item.children) { - highlightData(item.children, highlightValue) - } - return item - }) -} -// const TreeNoder = DragSourceWrapper(TreeItem) export default class TreeNode extends Component { constructor (props) { super(props) @@ -52,7 +30,9 @@ export default class TreeNode extends Component { dropDividerPosition: null, searchValue: '', showModal: false, - currentDeleteNode: null + currentDeleteNode: null, + // 总共高亮的项 + highlightNum: 0 } } static getDerivedStateFromProps (props, state) { @@ -75,7 +55,44 @@ export default class TreeNode extends Component { }) return has } - + setHighlightNum = () => { + this.setState({ + highlightNum: this.state.highlightNum + 1 + }) + } + // 高亮检索值 + highlightData = (data, highlightValue) => { + return data.map(item => { + if (typeof item.title === 'string' && item.title.includes(highlightValue)) { + const index = item.title.indexOf(highlightValue) + const beforeStr = item.title.substr(0, index) + const afterStr = item.title.substr(index + highlightValue.length) + item.title = ( + <span> + {beforeStr} + <span style={{ color: '#4284f5' }}>{highlightValue}</span> + {afterStr} + </span> + ) + } + if (item.children) { + this.highlightData(item.children, highlightValue) + } + return item + }) + } + // 高亮检索值 + recordHighlight = (data, highlightValue, count) => { + data.forEach(item => { + if (typeof item.title === 'string' && item.title.includes(highlightValue)) { + count = count + 1 + } + if (item.children) { + this.recordHighlight(item.children, highlightValue, count) + } + }) + return count + } renderSwitcher = expanded => { const { prefixCls } = this.props const switcherClsName = classNames( @@ -374,9 +391,9 @@ export default class TreeNode extends Component { const { draggable, prefixCls, - dragNodePosition, - dragNode, - withLine, + // dragNodePosition, + // dragNode, + // withLine, semiChecked, onNodeClick, onClick, @@ -403,12 +420,12 @@ export default class TreeNode extends Component { const checked = this.getItem('checked', item) const expanded = this.getItem('expanded', item) const itemStyle = classNames( - dragNode === item.id && dragNodePosition === 0 && 'dragTo', - dragNode === item.id && dragNodePosition === -1 && 'dragToGapTop', - dragNode === item.id && dragNodePosition === 1 && 'dragToGapBottom', + // dragNode === item.id && dragNodePosition === 0 && 'dragTo', + // dragNode === item.id && dragNodePosition === -1 && 'dragToGapTop', + // dragNode === item.id && dragNodePosition === 1 && 'dragToGapBottom', this.props.checkable && 'has_checkbox' ) - const itemContainerStyle = classNames(withLine && 'with-line') + // const itemContainerStyle = classNames(withLine && 'with-line') return ( <TreeItem @@ -425,7 +442,7 @@ export default class TreeNode extends Component { expanded={expanded} expandTreeNode={expandTreeNode} itemStyle={itemStyle} - itemContainerStyle={itemContainerStyle} + // itemContainerStyle={itemContainerStyle} semiChecked={semiChecked} checkable={checkable} onExpanded={onExpanded} @@ -459,7 +476,7 @@ export default class TreeNode extends Component { ) } render () { - const { dataCache, searchValue } = this.state + const { dataCache, searchValue, highlightNum } = this.state const { searchable } = this.props return ( <div> @@ -470,19 +487,36 @@ export default class TreeNode extends Component { type='text' placeholder='关键词搜索' onChange={e => { - this.setState({ searchValue: e.target.value }) + this.setState({ + searchValue: e.target.value, + highlightNum: this.recordHighlight(dataCache, e.target.value, 0) + }) + this.props.setExpandTreeNodes( collectExpandId(dataCache, e.target.value, [], dataCache) ) }} append={<Icon name='search' style={{ color: '#4284F5', fontSize: '24px' }} />} - style={{ width: '250px' }} + style={{ width: '272px' }} /> + {highlightNum === 0 && searchValue !== '' && ( + <div + style={{ + position: 'absolute', + top: 34, + color: '#999999', + left: 39, + fontSize: 12 + }} + > + 未找到搜索结果 + </div> + )} </div> )} {searchable - ? this.renderTree(highlightData(cloneDeep(dataCache), searchValue)) + ? this.renderTree(this.highlightData(cloneDeep(dataCache), searchValue)) : this.renderTree(cloneDeep(dataCache))} <Modal diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 48f564f40..62bf5e5c2 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -4,10 +4,18 @@ $tree: 'hi-tree' !default; .hi-tree { font-size: 14px; .hi-tree_searcher { + position: relative; padding-left: 15px; - margin-bottom: 27px; + margin-bottom: 24px; + .hi-input__inner { + flex: 1; + } .hi-input--append { line-height: 32px; + .hi-input__append { + width: 32px; + text-align: center; + } } } diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 042d70858..66cd2bd77 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -41,16 +41,7 @@ constructor(props) { } } -clickEvent () { - const data = this.state.treeData - data.push({ - id: 2, - title: '其它' - }) - this.setState({ - treeData: data - }) -} + render() { return ( <div style={{width:500}}> @@ -66,7 +57,6 @@ render() { highlightable onNodeClick={(item) => console.log('------click node', item)} /> - <Button onClick={this.clickEvent.bind(this)}>点击</Button> </div> ) } @@ -179,16 +169,7 @@ constructor(props) { } } -clickEvent () { - const data = this.state.treeData - data.push({ - id: 2, - title: '其它' - }) - this.setState({ - treeData: data - }) -} + render() { return ( <div style={{width:500}}> @@ -204,7 +185,6 @@ render() { highlightable onNodeClick={(item) => console.log('------click node', item)} /> - <Button onClick={this.clickEvent.bind(this)}>点击</Button> </div> ) } @@ -251,16 +231,6 @@ constructor(props) { } } -clickEvent () { - const data = this.state.treeData - data.push({ - id: 2, - title: '其它' - }) - this.setState({ - treeData: data - }) -} render() { return ( <div style={{width:500}}> @@ -276,7 +246,6 @@ render() { highlightable onNodeClick={(item) => console.log('------click node', item)} /> - <Button onClick={this.clickEvent.bind(this)}>点击</Button> </div> ) } @@ -323,16 +292,7 @@ constructor(props) { } } -clickEvent () { - const data = this.state.treeData - data.push({ - id: 2, - title: '其它' - }) - this.setState({ - treeData: data - }) -} + render() { return ( <div style={{width:500}}> @@ -348,7 +308,6 @@ render() { highlightable onNodeClick={(item) => console.log('------click node', item)} /> - <Button onClick={this.clickEvent.bind(this)}>点击</Button> </div> ) } From 2330057d2ca46669c62c3104906f69a1ae36d9b4 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 22 Apr 2019 17:33:46 +0800 Subject: [PATCH 043/112] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/TreeItem.js | 34 +++++++++++++++++++++------------- components/tree/TreeNode.js | 33 +++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index c039fe055..7c390d0ce 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -11,7 +11,7 @@ class TreeItem extends Component { render () { const { // 节点可编辑 - // editable, + editable, // 节点可拖拽 draggable, // ******************** // @@ -120,7 +120,7 @@ class TreeItem extends Component { highlight === item.id ? 'highlight' : '' } ${draggingNode === item.id ? 'dragging' : ''}`} onContextMenu={e => { - if (this.props.editable) { + if (editable) { e.preventDefault() showRightClickMenu(item) } @@ -215,8 +215,15 @@ const target = { }, hover (props, monitor, component) { const { sourceItem } = monitor.getItem() - const { item: targetItem, setDraggingNode, setTargetNode } = props - + const { + item: targetItem, + setDraggingNode, + setTargetNode, + positionX, + positionY, + setPosition + } = props + console.log('hover') // 先看下是不是在最近得组件 if (monitor.isOver({ shallow: true })) { // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 @@ -230,16 +237,17 @@ const target = { } else { const sourcePosition = monitor.getClientOffset() const targetComponent = findDOMNode(component).getBoundingClientRect() - // 3.移动节点到相应位置 - // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 - - if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { - setTargetNode(targetItem.id, 'sub') - } else { - setTargetNode(targetItem.id, 'down') + if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { + setPosition(sourcePosition.x, sourcePosition.y) + // 3.移动节点到相应位置 + // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 + if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { + setTargetNode(targetItem.id, 'sub') + } else { + setTargetNode(targetItem.id, 'down') + } + setDraggingNode(sourceItem.id) } - - setDraggingNode(sourceItem.id) } } } diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 183d5c9a9..1b80f8a50 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -32,7 +32,9 @@ export default class TreeNode extends Component { showModal: false, currentDeleteNode: null, // 总共高亮的项 - highlightNum: 0 + highlightNum: 0, + positionX: null, + positionY: null } } static getDerivedStateFromProps (props, state) { @@ -45,7 +47,15 @@ export default class TreeNode extends Component { } return state } - + setPosition = (x, y) => { + const { positionX, positionY } = this.state + if (!(x === positionX && y === positionY)) { + this.setState({ + positionX: x, + positionY: y + }) + } + } getItem (name, treeItem) { let has = false this.props[name].map(item => { @@ -391,9 +401,6 @@ export default class TreeNode extends Component { const { draggable, prefixCls, - // dragNodePosition, - // dragNode, - // withLine, semiChecked, onNodeClick, onClick, @@ -411,7 +418,9 @@ export default class TreeNode extends Component { editingNodes, draggingNode, targetNode, - dropDividerPosition + dropDividerPosition, + positionX, + positionY } = this.state return ( @@ -419,13 +428,7 @@ export default class TreeNode extends Component { {data.map(item => { const checked = this.getItem('checked', item) const expanded = this.getItem('expanded', item) - const itemStyle = classNames( - // dragNode === item.id && dragNodePosition === 0 && 'dragTo', - // dragNode === item.id && dragNodePosition === -1 && 'dragToGapTop', - // dragNode === item.id && dragNodePosition === 1 && 'dragToGapBottom', - this.props.checkable && 'has_checkbox' - ) - // const itemContainerStyle = classNames(withLine && 'with-line') + const itemStyle = classNames(this.props.checkable && 'has_checkbox') return ( <TreeItem @@ -442,12 +445,14 @@ export default class TreeNode extends Component { expanded={expanded} expandTreeNode={expandTreeNode} itemStyle={itemStyle} - // itemContainerStyle={itemContainerStyle} semiChecked={semiChecked} checkable={checkable} onExpanded={onExpanded} onValueChange={this.onValueChange} renderTree={this.renderTree} + setPosition={this.setPosition} + positionX={positionX} + positionY={positionY} renderSwitcher={this.renderSwitcher} cancelAddSiblingNode={this.cancelAddSiblingNode} renderRightClickMenu={this.renderRightClickMenu} From e6f46c59929815818e9abbe3794e0fa319ac9820 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 22 Apr 2019 19:59:30 +0800 Subject: [PATCH 044/112] optimize code --- components/tree/README.md | 38 ------------- components/tree/Tree.js | 95 ++++++++++++-------------------- components/tree/TreeItem.js | 44 ++++++--------- components/tree/TreeNode.js | 41 ++++---------- components/tree/style/index.scss | 9 ++- 5 files changed, 70 insertions(+), 157 deletions(-) delete mode 100644 components/tree/README.md diff --git a/components/tree/README.md b/components/tree/README.md deleted file mode 100644 index 1a85a3a1f..000000000 --- a/components/tree/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# 树形菜单 v1.0 - -## Tree props - -| 参数 | 说明 | 类型 | 默认值 | -|------|-----|-----|-------|--------| -| defaultExpandAll | 默认展开所有树节点 | boolean | false | -| defaultCheckedAll | 默认选中所有checkbox | boolean | false | -| checkable | 节点前添加 Checkbox 复选框 | boolean | false | -| draggable | 设置节点可拖拽 | boolean | false | -| render | 自定义渲染方法, 参数为该节点数据 (包含子树) | func(data) | | -| onCheckChange | 点击复选框触发 | func(...) | - | -| onNodeToggle | 节点被点击(展开/收起)时触发 | func({data,isExpanded}) | - | -| onDragStart | 开始拖拽时调用 | func(event) | - | -| onDragEnd | 结束拖拽时调用 | func(event) | - | -| onDragEnter | 拖曳元素进入目标元素的时候触发的事件 | func(event, dropPosition) | - | -| onDragOver | 拖拽元素在目标元素上移动的时候触发的事件 | func(event) | - | -| onDragLeave | 拖拽元素离开目标元素的时候触发的事件 | func(event) | - | -| onDrop | 被拖拽的元素在目标元素上同时鼠标放开触发的事件 | func(event) | - | -| disabled | 禁掉checkbox响应 | boolean | false | -| data | 展示数据 | array | [] | -| options | 配置选项 | object | - | - - -## data - -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | --- | -| url | 如果传入url,则会被渲染为a标签,新窗口打开,默认渲染为span标签 | string | - | -| expand | 默认是否展开子菜单(优先级高于defaultExpandAll) | blooean | false | -| checked | 默认是否选中checkbox | blooean | false | - -## options - -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | --- | -| title | 指定节点标签为节点对象的某个属性值 | string | title | -| children | 指定子树为节点对象的某个属性值 | string | children | \ No newline at end of file diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 9634816d6..eb79e1dba 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -14,7 +14,6 @@ class Tree extends Component { super(props) this.state = { - hasChecked: props.defaultCheckedKeys, hasExpanded: [], dataMap: {}, data: [], @@ -144,22 +143,6 @@ class Tree extends Component { } else { semiChecked = semiChecked.filter(s => s !== p) } - console.log( - 'chedked', - checked, - 'child', - child, - 'checkedkeys', - checkedArr, - 'child.length', - child.length, - 'title', - all.find(item => item.id === p).title, - 'checknum', - num, - 'semi', - semi - ) }) onChange && onChange(checkedArr, item.title, !checked, semiChecked) @@ -209,11 +192,11 @@ class Tree extends Component { this.setCheckTreeCheckedParent(dataMap[id].parent, checked, tempCheckedArr) } } + // 展开、收起节点 + onExpanded = (expanded, item) => { + let expandedArr = [...this.state.hasExpanded] - onExpanded (expanded, item) { - let expandedArr = this.state.hasExpanded - - if (expandedArr.indexOf(item.id) >= 0) { + if (expandedArr.includes(item.id)) { expandedArr.splice(expandedArr.indexOf(item.id), 1) } else { expandedArr.push(item.id) @@ -224,7 +207,7 @@ class Tree extends Component { } // 展开节点 expandTreeNode = id => { - const _hasExpanded = this.state.hasExpanded + const _hasExpanded = [...this.state.hasExpanded] if (!_hasExpanded.includes(id)) { _hasExpanded.push(id) this.setState({ @@ -242,8 +225,7 @@ class Tree extends Component { hasExpanded: this.state.hasExpanded.filter(expandId => expandId !== id) }) } - - renderTreeNodes (data) { + render () { const { prefixCls, checkable, @@ -253,45 +235,36 @@ class Tree extends Component { highlightable, editable, searchable, - draggable + draggable, + style } = this.props + const { data } = this.state return ( - <TreeNode - checked={this.props.checkedKeys || []} - onNodeClick={this.props.onNodeClick} - onClick={this.props.onClick} - semiChecked={this.state.all.filter(item => item.semi).map(item => item.id)} - expanded={this.state.hasExpanded} - closeExpandedTreeNode={this.closeExpandedTreeNode} - expandTreeNode={this.expandTreeNode} - setExpandTreeNodes={this.setExpandTreeNodes} - onCheckChange={this.onCheckChange} - hightLightNodes={this.props.hightLightNodes} - onHightLightChange={this.props.onHightLightChange} - onExpanded={this.onExpanded.bind(this)} - data={data} - prefixCls={prefixCls} - checkable={checkable} - highlightable={highlightable} - editable={editable} - searchable={searchable} - openIcon={openIcon} - closeIcon={closeIcon} - // withLine={withLine} - draggable={draggable} - /> - ) - } - - render () { - const { prefixCls, draggable, style } = this.props - const classes = classNames(`${prefixCls}`, { - 'draggable-tree': draggable - }) - - return ( - <div className={classes} style={style}> - {this.renderTreeNodes(this.state.data)} + <div className={classNames(`${prefixCls}`)} style={style}> + <TreeNode + checked={this.props.checkedKeys || []} + onNodeClick={this.props.onNodeClick} + onClick={this.props.onClick} + semiChecked={this.state.all.filter(item => item.semi).map(item => item.id)} + expanded={this.state.hasExpanded} + closeExpandedTreeNode={this.closeExpandedTreeNode} + expandTreeNode={this.expandTreeNode} + setExpandTreeNodes={this.setExpandTreeNodes} + onCheckChange={this.onCheckChange} + hightLightNodes={this.props.hightLightNodes} + onHightLightChange={this.props.onHightLightChange} + onExpanded={this.onExpanded} + data={data} + prefixCls={prefixCls} + checkable={checkable} + highlightable={highlightable} + editable={editable} + searchable={searchable} + openIcon={openIcon} + closeIcon={closeIcon} + // withLine={withLine} + draggable={draggable} + /> </div> ) } diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 7c390d0ce..f3cf3a1f3 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -14,7 +14,6 @@ class TreeItem extends Component { editable, // 节点可拖拽 draggable, - // ******************** // dropDividerPosition, checked, expanded, @@ -63,7 +62,12 @@ class TreeItem extends Component { : withLine && renderItemIcon()} </span> */} { - <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> + <span + onClick={() => { + onExpanded(expanded, item) + }} + className={`${prefixCls}_item-icon`} + > {item.children && item.children.length > 0 && renderSwitcher(expanded)} </span> } @@ -160,7 +164,6 @@ class TreeItem extends Component { > {item.title} {renderRightClickMenu(item)} - {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} </span> )} </div> @@ -195,8 +198,7 @@ const target = { // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 if ( sourceItem.id === targetItem.id || - (targetItem.children && targetItem.children.map(t => t.id).includes(sourceItem.id)) || - (sourceItem.children && sourceItem.children.map(s => s.id).includes(targetItem.id)) + (targetItem.children && targetItem.children.map(t => t.id).includes(sourceItem.id)) ) { // 2.如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 // 如果什么都不做,原来展开则现在还展开 @@ -223,31 +225,21 @@ const target = { positionY, setPosition } = props - console.log('hover') // 先看下是不是在最近得组件 if (monitor.isOver({ shallow: true })) { // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 - if ( - sourceItem.id === targetItem.id || - (targetItem.children && targetItem.children.map(t => t.id).includes(sourceItem.id)) || - (sourceItem.children && sourceItem.children.map(s => s.id).includes(targetItem.id)) - ) { - // 2.如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 - return false - } else { - const sourcePosition = monitor.getClientOffset() - const targetComponent = findDOMNode(component).getBoundingClientRect() - if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { - setPosition(sourcePosition.x, sourcePosition.y) - // 3.移动节点到相应位置 - // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 - if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { - setTargetNode(targetItem.id, 'sub') - } else { - setTargetNode(targetItem.id, 'down') - } - setDraggingNode(sourceItem.id) + const sourcePosition = monitor.getClientOffset() + const targetComponent = findDOMNode(component).getBoundingClientRect() + if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { + setPosition(sourcePosition.x, sourcePosition.y) + // 3.移动节点到相应位置 + // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 + if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { + setTargetNode(targetItem.id, 'sub') + } else { + setTargetNode(targetItem.id, 'down') } + setDraggingNode(sourceItem.id) } } } diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 1b80f8a50..1471ab600 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -56,15 +56,7 @@ export default class TreeNode extends Component { }) } } - getItem (name, treeItem) { - let has = false - this.props[name].map(item => { - if (treeItem.id === item) { - has = true - } - }) - return has - } + setHighlightNum = () => { this.setState({ highlightNum: this.state.highlightNum + 1 @@ -91,7 +83,7 @@ export default class TreeNode extends Component { return item }) } - // 高亮检索值 + // 统计高亮项 recordHighlight = (data, highlightValue, count) => { data.forEach(item => { if (typeof item.title === 'string' && item.title.includes(highlightValue)) { @@ -364,7 +356,6 @@ export default class TreeNode extends Component { ) ) } - //* *** */ onSetHighlight = item => { this.setState({ highlight: item.id @@ -410,7 +401,9 @@ export default class TreeNode extends Component { expandTreeNode, onCheckChange, onExpanded, - editable + editable, + checked, + expanded } = this.props const { highlight, @@ -426,10 +419,6 @@ export default class TreeNode extends Component { return ( <ul> {data.map(item => { - const checked = this.getItem('checked', item) - const expanded = this.getItem('expanded', item) - const itemStyle = classNames(this.props.checkable && 'has_checkbox') - return ( <TreeItem key={item.id} @@ -437,14 +426,14 @@ export default class TreeNode extends Component { dropDividerPosition={dropDividerPosition} prefixCls={prefixCls} draggable={draggable} - checked={checked} + checked={!!checked.includes(item.id)} highlight={highlight} highlightable={highlightable} editNodes={editNodes} editingNodes={editingNodes} - expanded={expanded} + expanded={!!expanded.includes(item.id)} expandTreeNode={expandTreeNode} - itemStyle={itemStyle} + itemStyle={classNames(checkable && 'has_checkbox')} semiChecked={semiChecked} checkable={checkable} onExpanded={onExpanded} @@ -486,7 +475,7 @@ export default class TreeNode extends Component { return ( <div> {searchable && ( - <div className='hi-tree_searcher'> + <div className='hi-tree__searcher'> <Input value={this.state.searchValue} type='text' @@ -505,17 +494,7 @@ export default class TreeNode extends Component { style={{ width: '272px' }} /> {highlightNum === 0 && searchValue !== '' && ( - <div - style={{ - position: 'absolute', - top: 34, - color: '#999999', - left: 39, - fontSize: 12 - }} - > - 未找到搜索结果 - </div> + <div className='hi-tree__searcher--empty'>未找到搜索结果</div> )} </div> )} diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 62bf5e5c2..46e4511f5 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -3,10 +3,17 @@ $tree: 'hi-tree' !default; .hi-tree { font-size: 14px; - .hi-tree_searcher { + .hi-tree__searcher { position: relative; padding-left: 15px; margin-bottom: 24px; + .hi-tree__searcher--empty { + position: absolute; + top: 34px; + color: #999999; + left: 39; + font-size: 12; + } .hi-input__inner { flex: 1; } From aaa99617ef3de9b314ce098f71e2f24936672bdd Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Tue, 23 Apr 2019 20:24:20 +0800 Subject: [PATCH 045/112] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 152 ++++++++++++++++++++++++++-------------- components/tree/util.js | 10 +++ 2 files changed, 110 insertions(+), 52 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index eb79e1dba..c7cdd846c 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -3,7 +3,7 @@ import classNames from 'classnames' import PropTypes from 'prop-types' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' -import { deepClone, getChildren, getDisabled, getAll, dealData } from './util' +import { getAll, dealData, getChildrenIds, getAncestorIds, findNode } from './util' import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' @@ -17,14 +17,104 @@ class Tree extends Component { hasExpanded: [], dataMap: {}, data: [], - dragNode: '', - dragNodePosition: null, semiChecked: [], disabledKeys: [], - all: [] + all: [], + checkedIds: [], + semiCheckedIds: [] } } - + initCheck = itemId => { + const { checkedIds, semiCheckedIds, data } = this.state + let currentCheckedIds = [...checkedIds] + let currentSemiCheckedIds = [...semiCheckedIds] + const node = findNode(itemId, data) + const childrenIds = getChildrenIds(node, []) + const ancestorIds = getAncestorIds(itemId, data, []) + // 当前是未选 + currentCheckedIds = currentCheckedIds.concat(childrenIds) + ancestorIds.forEach(i => { + const ancestor = findNode(i, data) + if (ancestor.children.every(child => currentCheckedIds)) { + currentCheckedIds.push(i) + } else if ( + ancestor.children.some(child => currentCheckedIds) && + !currentSemiCheckedIds.includes(i) + ) { + currentSemiCheckedIds.push(i) + } + }) + this.setState({ + checkedIds: currentCheckedIds, + semiCheckedIds: currentSemiCheckedIds + }) + } + onCheck = itemId => { + const { checkedIds, semiCheckedIds, data } = this.state + let currentCheckedIds = [...checkedIds] + let currentSemiCheckedIds = [...semiCheckedIds] + const node = findNode(itemId, data) + const childrenIds = getChildrenIds(node, []) + const ancestorIds = getAncestorIds(itemId, data, []) + if (currentCheckedIds.includes(itemId)) { + // 当前是全选 + currentCheckedIds = currentCheckedIds.filter(id => id === itemId) + childrenIds.forEach(i => { + if (currentCheckedIds.includes(i)) { + currentCheckedIds = currentCheckedIds.filter(id => id === itemId) + } + }) + ancestorIds.forEach(i => { + const ancestor = findNode(i, data) + if (ancestor.children.every(child => currentCheckedIds)) { + currentCheckedIds.push(i) + } else if ( + ancestor.children.some(child => currentCheckedIds) && + !currentSemiCheckedIds.includes(i) + ) { + currentSemiCheckedIds.push(i) + } + }) + } else if (currentSemiCheckedIds.includes(itemId)) { + // 当前是半选 + currentSemiCheckedIds = currentSemiCheckedIds.filter(id => id === itemId) + currentCheckedIds.push(itemId) + childrenIds.forEach(i => { + if (!currentCheckedIds.includes(i)) { + currentCheckedIds.push(i) + } + }) + ancestorIds.forEach(i => { + const ancestor = findNode(i, data) + if (ancestor.children.every(child => currentCheckedIds)) { + currentCheckedIds.push(i) + } else if ( + ancestor.children.some(child => currentCheckedIds) && + !currentSemiCheckedIds.includes(i) + ) { + currentSemiCheckedIds.push(i) + } + }) + } else { + // 当前是未选 + currentCheckedIds = currentCheckedIds.concat(childrenIds) + ancestorIds.forEach(i => { + const ancestor = findNode(i, data) + if (ancestor.children.every(child => currentCheckedIds)) { + currentCheckedIds.push(i) + } else if ( + ancestor.children.some(child => currentCheckedIds) && + !currentSemiCheckedIds.includes(i) + ) { + currentSemiCheckedIds.push(i) + } + }) + } + this.setState({ + checkedIds: currentCheckedIds, + semiCheckedIds: currentSemiCheckedIds + }) + } static propTypes = { data: PropTypes.arrayOf(PropTypes.object), defaultCheckedKeys: PropTypes.arrayOf(PropTypes.any), @@ -44,16 +134,16 @@ class Tree extends Component { data: [] } - static getDerivedStateFromProps (props, prevState) { + static getDerivedStateFromProps (props, state) { let data = {} - if (!isEqual(props.data, prevState.data)) { + if (!isEqual(props.data, state.data)) { + console.log('>>>>>>>>>>>>>') const dataMap = {} - dealData(deepClone(props.data), dataMap) - console.log(props.data, prevState.data, dataMap) + dealData(props.data, dataMap) data.dataMap = dataMap data.data = props.data - if (prevState.data.length === 0) { + if (state.data.length === 0) { if (props.defaultExpandAll) { let tempExpandedArr = [] for (let key in dataMap) { @@ -150,48 +240,6 @@ class Tree extends Component { this.props.onCheckChange(checkedArr, item.title, !checked, semiChecked) } - setCheckTreeCheckedChild (id, checked, tempCheckedArr, semi) { - let child = getChildren(this.props.data, id) - let disabled = getDisabled(this.props.data) - child.forEach(c => { - if (disabled.includes(c)) { - return - } - if (semi) { - tempCheckedArr.splice(tempCheckedArr.indexOf(c), 1) - return - } - if (!tempCheckedArr.includes(c) && !disabled.includes(c)) { - tempCheckedArr.push(c) - } else { - tempCheckedArr.splice(tempCheckedArr.indexOf(c), 1) - } - }) - } - - setCheckTreeCheckedParent (id, checked, tempCheckedArr) { - const { dataMap } = this.state - if (checked) { - if (tempCheckedArr.indexOf(id) >= 0) { - tempCheckedArr.splice(tempCheckedArr.indexOf(id), 1) - } - } else { - let allChecked = true - - dataMap[id].children && - dataMap[id].children.map(i => { - if (tempCheckedArr.indexOf(i) < 0) { - allChecked = false - } - }) - if (allChecked && tempCheckedArr.indexOf(id) < 0) { - tempCheckedArr.push(id) - } - } - if (dataMap[id].parent) { - this.setCheckTreeCheckedParent(dataMap[id].parent, checked, tempCheckedArr) - } - } // 展开、收起节点 onExpanded = (expanded, item) => { let expandedArr = [...this.state.hasExpanded] diff --git a/components/tree/util.js b/components/tree/util.js index 89fc1f510..3dd53dcc3 100644 --- a/components/tree/util.js +++ b/components/tree/util.js @@ -258,6 +258,7 @@ export const dealData = (data, tempData = {}, parent = null) => { } }) } + // 寻找某一节点的父节点 export const getParentId = (id, data) => { let parentId @@ -272,6 +273,14 @@ export const getParentId = (id, data) => { }) return parentId } +// 寻找某一节点的所有子节点 +export const getChildrenIds = (node, arr = []) => { + if (node.children) { + arr = node.children.map(i => i.id).concat(arr) + } + node.children.forEach(c => getChildrenIds(c, arr)) + return arr +} // 寻找某一节点的所有祖先节点 export const getAncestorIds = (id, data, arr = []) => { if (getParentId(id, data)) { @@ -280,6 +289,7 @@ export const getAncestorIds = (id, data, arr = []) => { } return arr } + // 收集所有需要展开的节点 id export const collectExpandId = (data, searchValue, collection = [], allData) => { data.forEach(item => { From 018d3171e6dbf9c5fd609bbac5d36dac5373164a Mon Sep 17 00:00:00 2001 From: surui1 <surui1@xiaomi.com> Date: Wed, 24 Apr 2019 16:03:18 +0800 Subject: [PATCH 046/112] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/pagination/Pagination.js | 25 ++++------ components/table/index.js | 73 ++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/components/pagination/Pagination.js b/components/pagination/Pagination.js index ffdd6413f..86f9ea90e 100644 --- a/components/pagination/Pagination.js +++ b/components/pagination/Pagination.js @@ -74,23 +74,16 @@ class Pagination extends Component { if (props.total !== this.props.total) { states.total = props.total } + let current + if (this.state.current !== props.defaultCurrent) { + current = this.getCurrent(props.defaultCurrent, this.calculatePage(props.total, props.pageSize)) + } + if (current) { + states.current = current + states.jumpTo = current + } - this.setState({...states}, () => { - let current - - if (props.defaultCurrent !== this.props.defaultCurrent) { // defaultCurrent改变需重设current - current = props.defaultCurrent - } else { - let newCurrent = this.getCurrent(this.state.current, this.calculatePage(props.total, props.pageSize)) - if (newCurrent !== this.state.current) { // pageSize或者total有改动,导致已有的current超过最大页 - current = newCurrent - } - } - current && this.setState({ - current, - jumpTo: current - }) - }) + this.setState({...states}) } getCurrent (current, maxPage) { diff --git a/components/table/index.js b/components/table/index.js index 12d0e5856..8340cd497 100644 --- a/components/table/index.js +++ b/components/table/index.js @@ -13,9 +13,7 @@ import '../icon/style' import {setKey, scrollTop, getStyle, getPosition} from './tool' import request from 'axios' import qs from 'qs' -let axios = request.create({ - baseURL: '' -}) + class Table extends Component { static propTypes = { data: PropTypes.array, @@ -379,10 +377,10 @@ class Table extends Component { render () { // 多选配置 // noinspection JSAnnotator + let {pagination, name, size = 'normal', bordered = false, striped = false, scrollX} = this.props // noinspection JSAnnotator let {scroll, columnMenu, serverPagination} = this.state - let content // 不滚动 if (!scroll.x && !scroll.y) { @@ -435,6 +433,16 @@ class Table extends Component { serverPagePosition = 'flex-end' } } + + // 分页组件从受控模式,变成非受控模式,兼容处理 + let serverPaginationConfig + if (serverPagination && serverPagination.current) { + serverPaginationConfig = { + ...serverPagination, + defaultCurrent: serverPagination.current + } + delete serverPaginationConfig.current + } return ( <div className={prifix({table: true, [size]: size, bordered, striped})} ref={this.dom}> <div > @@ -452,13 +460,12 @@ class Table extends Component { } </div> } - {serverPagination && serverPagination.current && serverPagination.current && <div style={{display: 'flex', justifyContent: serverPagePosition}} a='1'> + {serverPaginationConfig && serverPaginationConfig.defaultCurrent && <div style={{display: 'flex', justifyContent: serverPagePosition}} a='1'> { <div className={prifix('table-page')} > + {serverPagination.current} <Pagination - pageSize={serverPagination.pageSize} - total={serverPagination.total} - current={serverPagination.current} + {...serverPaginationConfig} onChange={(current) => { this.setState({ serverPagination: { @@ -726,8 +733,8 @@ class Table extends Component { type = 'GET', success = (res) => {}, error = () => {}, + mode = 'json', currentPageName = Table.config.currentPageName - // pageSizeName = Table.config.pageSizeName } = origin let l = loading.open({ @@ -737,11 +744,10 @@ class Table extends Component { serverPagination: {current} } = this.state let requestParams = { + [currentPageName]: current, ...data, ...extra } - requestParams[currentPageName] = current - // requestParams[pageSizeName] = pageSize let options = { url, @@ -750,12 +756,16 @@ class Table extends Component { if (options.method === 'GET') { options.params = requestParams } else { - options.data = qs.stringify(requestParams) + if (mode !== 'json') { + options.data = qs.stringify(requestParams) + } else { + options.data = requestParams + } } if (headers) { options.headers = headers } - axios.request(options).then(res => { + request.create().request(options).then(res => { let {data, columns, page} = success(res) let columnsDetail = this.setColumnsDetail(null, null, columns) this.setState({ @@ -765,22 +775,26 @@ class Table extends Component { }) this.runMemory() l.close() - }).catch(error) + }).catch((e) => { + l.close() + error(e) + }) } reset (props) { // noinspection JSAnnotator - const { origin: { data, url, headers, type = 'GET', + mode = 'json', success = (res) => {}, - error = () => {} + error = () => {}, // pageSize = Table.config.pageSize, - // pageSizeName = Table.config.pageSizeName + // pageSizeName = Table.config.pageSizeName, + currentPageName = Table.config.currentPageName } } = props || this.props @@ -791,7 +805,9 @@ class Table extends Component { let requestParams = { ...data } - // requestParams[pageSizeName] = pageSize + if (!requestParams[currentPageName]) { + requestParams[currentPageName] = 1 + } let options = { method: ['GET', 'get'].includes(type) ? 'GET' : 'POST', url @@ -799,13 +815,17 @@ class Table extends Component { if (options.method === 'GET') { options.params = requestParams } else { - options.data = qs.stringify(requestParams) + if (mode !== 'json') { + options.data = qs.stringify(requestParams) + } else { + options.data = requestParams + } } if (headers) { options.headers = headers } - axios(options).then(res => { + request.create().request(options).then(res => { let {data, columns, page} = success(res) this.setState({ dataSource: data, @@ -819,7 +839,10 @@ class Table extends Component { this.runMemory() l.close() }) - }).catch(error) + }).catch((e) => { + l.close() + error(e) + }) } componentDidMount () { @@ -877,6 +900,7 @@ class Table extends Component { if (props.origin) { let oldOrigin = this.props.origin let newOrigin = props.origin + let bool = oldOrigin.url !== newOrigin.url oldOrigin.data = oldOrigin.data || {} newOrigin.data = newOrigin.data || {} @@ -893,6 +917,13 @@ class Table extends Component { } } + let pageSizeName = oldOrigin.pageSizeName || Table.config.pageSizeName + if (this.state.serverPagination.current !== 1) { + if (oldOrigin.data[pageSizeName] !== newOrigin.data[pageSizeName]) { + bool = false + } + } + let { auto = true, autoDelayTime = Table.config.autoDelayTime From cb04deb4322225f6d5945efb41cab10a94f222bb Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 25 Apr 2019 11:21:51 +0800 Subject: [PATCH 047/112] update --- components/tree/Tree.js | 95 +---------------------------------- docs/zh-CN/components/tree.md | 27 +++++----- 2 files changed, 17 insertions(+), 105 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index c7cdd846c..850c61a41 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -3,7 +3,7 @@ import classNames from 'classnames' import PropTypes from 'prop-types' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' -import { getAll, dealData, getChildrenIds, getAncestorIds, findNode } from './util' +import { getAll, dealData } from './util' import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' @@ -24,97 +24,7 @@ class Tree extends Component { semiCheckedIds: [] } } - initCheck = itemId => { - const { checkedIds, semiCheckedIds, data } = this.state - let currentCheckedIds = [...checkedIds] - let currentSemiCheckedIds = [...semiCheckedIds] - const node = findNode(itemId, data) - const childrenIds = getChildrenIds(node, []) - const ancestorIds = getAncestorIds(itemId, data, []) - // 当前是未选 - currentCheckedIds = currentCheckedIds.concat(childrenIds) - ancestorIds.forEach(i => { - const ancestor = findNode(i, data) - if (ancestor.children.every(child => currentCheckedIds)) { - currentCheckedIds.push(i) - } else if ( - ancestor.children.some(child => currentCheckedIds) && - !currentSemiCheckedIds.includes(i) - ) { - currentSemiCheckedIds.push(i) - } - }) - this.setState({ - checkedIds: currentCheckedIds, - semiCheckedIds: currentSemiCheckedIds - }) - } - onCheck = itemId => { - const { checkedIds, semiCheckedIds, data } = this.state - let currentCheckedIds = [...checkedIds] - let currentSemiCheckedIds = [...semiCheckedIds] - const node = findNode(itemId, data) - const childrenIds = getChildrenIds(node, []) - const ancestorIds = getAncestorIds(itemId, data, []) - if (currentCheckedIds.includes(itemId)) { - // 当前是全选 - currentCheckedIds = currentCheckedIds.filter(id => id === itemId) - childrenIds.forEach(i => { - if (currentCheckedIds.includes(i)) { - currentCheckedIds = currentCheckedIds.filter(id => id === itemId) - } - }) - ancestorIds.forEach(i => { - const ancestor = findNode(i, data) - if (ancestor.children.every(child => currentCheckedIds)) { - currentCheckedIds.push(i) - } else if ( - ancestor.children.some(child => currentCheckedIds) && - !currentSemiCheckedIds.includes(i) - ) { - currentSemiCheckedIds.push(i) - } - }) - } else if (currentSemiCheckedIds.includes(itemId)) { - // 当前是半选 - currentSemiCheckedIds = currentSemiCheckedIds.filter(id => id === itemId) - currentCheckedIds.push(itemId) - childrenIds.forEach(i => { - if (!currentCheckedIds.includes(i)) { - currentCheckedIds.push(i) - } - }) - ancestorIds.forEach(i => { - const ancestor = findNode(i, data) - if (ancestor.children.every(child => currentCheckedIds)) { - currentCheckedIds.push(i) - } else if ( - ancestor.children.some(child => currentCheckedIds) && - !currentSemiCheckedIds.includes(i) - ) { - currentSemiCheckedIds.push(i) - } - }) - } else { - // 当前是未选 - currentCheckedIds = currentCheckedIds.concat(childrenIds) - ancestorIds.forEach(i => { - const ancestor = findNode(i, data) - if (ancestor.children.every(child => currentCheckedIds)) { - currentCheckedIds.push(i) - } else if ( - ancestor.children.some(child => currentCheckedIds) && - !currentSemiCheckedIds.includes(i) - ) { - currentSemiCheckedIds.push(i) - } - }) - } - this.setState({ - checkedIds: currentCheckedIds, - semiCheckedIds: currentSemiCheckedIds - }) - } + static propTypes = { data: PropTypes.arrayOf(PropTypes.object), defaultCheckedKeys: PropTypes.arrayOf(PropTypes.any), @@ -137,7 +47,6 @@ class Tree extends Component { static getDerivedStateFromProps (props, state) { let data = {} if (!isEqual(props.data, state.data)) { - console.log('>>>>>>>>>>>>>') const dataMap = {} dealData(props.data, dataMap) data.dataMap = dataMap diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 66cd2bd77..7f939f475 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -317,18 +317,21 @@ render() { ### Tree Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ---------------- | -------------------------- | ------- | ---------------------------- | ------ | -| data | 展示数据 | Array | 参见 Tree Attributes-data | - | -| checkable | 节点前添加 Checkbox 复选框 | Boolean | false | - | -| options | 配置选项 | Object | 参见 Tree Attributes-options | - | -| defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | -| checkedKeys | 默认选中的 checkbox | Array | - | - | -| openIcon | 表示展开的图标 | String | Icon 图标名称 | - | -| closeIcon | 表示闭合的图标 | String | Icon 图标名称 | - | -| style | 组件整体样式 | Object | - | - | -| highlightable | 高亮 | Boolean | -| withLine | 是否显示连接线 | Boolean | - | false | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---------------- | ----------------------------------------------------------------------- | ------- | ---------------------------- | ------ | +| data | 展示数据 | Array | 参见 Tree Attributes-data | - | +| checkable | 节点前添加 Checkbox 复选框(暂不支持与 draggable 和 editable 同时使用) | Boolean | - | false | +| editable | 节点右键可编辑(添加同级节点、添加子节点、编辑节点、删除节点) | Boolean | - | false | +| draggable | 节点可拖拽 | Boolean | - | false | +| searchable | 节点可拖拽 | Boolean | - | false | +| options | 配置选项 | Object | 参见 Tree Attributes-options | - | +| defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | +| checkedKeys | 默认选中的 checkbox | Array | - | - | +| openIcon | 表示展开的图标 | String | Icon 图标名称 | - | +| closeIcon | 表示闭合的图标 | String | Icon 图标名称 | - | +| style | 组件整体样式 | Object | - | - | +| highlightable | 高亮 | Boolean | +| withLine | 是否显示连接线 | Boolean | - | false | ### Tree Attributes-data From bbbad76934d32b43009a8ba08d7f61e1eee88bed Mon Sep 17 00:00:00 2001 From: surui1 <surui1@xiaomi.com> Date: Fri, 26 Apr 2019 10:45:51 +0800 Subject: [PATCH 048/112] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/table/index.js | 3 +- docs/zh-CN/components/table.md | 51 ++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/components/table/index.js b/components/table/index.js index 8340cd497..ba604d4c4 100644 --- a/components/table/index.js +++ b/components/table/index.js @@ -463,7 +463,6 @@ class Table extends Component { {serverPaginationConfig && serverPaginationConfig.defaultCurrent && <div style={{display: 'flex', justifyContent: serverPagePosition}} a='1'> { <div className={prifix('table-page')} > - {serverPagination.current} <Pagination {...serverPaginationConfig} onChange={(current) => { @@ -918,7 +917,7 @@ class Table extends Component { } let pageSizeName = oldOrigin.pageSizeName || Table.config.pageSizeName - if (this.state.serverPagination.current !== 1) { + if (this.state.serverPagination && this.state.serverPagination.current !== 1) { if (oldOrigin.data[pageSizeName] !== newOrigin.data[pageSizeName]) { bool = false } diff --git a/docs/zh-CN/components/table.md b/docs/zh-CN/components/table.md index 0d2fabb8c..361bbc7cf 100644 --- a/docs/zh-CN/components/table.md +++ b/docs/zh-CN/components/table.md @@ -938,8 +938,8 @@ render() { onChange:(page,pre,size)=>{ this.set(page) }, - position : 'right' // 分页位置 - 默认右侧 - + position : 'center', + }} /> } @@ -952,7 +952,8 @@ render() { constructor (props) { super(props) this.state = { - from: '' + from: '', + pageSize:'' } window.selectTable = this @@ -960,7 +961,8 @@ constructor (props) { render () { const { - from + from, + pageSize } = this.state const rowSelection = { @@ -1003,7 +1005,7 @@ constructor (props) { from, startTime: '', endTime: '', - pageSize:2 + pageSize }, method: "POST", auto:true, // 自动发请求配置(默认false) @@ -1016,6 +1018,22 @@ constructor (props) { serverSort:[{sort:'desc', sort:'adesc'}] //点击排序的箭头图标会将数组中某一项放到请求参数里 }) + const pageSizeOptions = [{ + value: 10, + title: '10' + }, { + value: 20, + title: '20' + }, { + value: 50, + title: '50' + }, { + value: 100, + title: '100' + }] + + + return { data, columns, @@ -1023,7 +1041,16 @@ constructor (props) { pageSize, total: totalNum, current: pageNum, - position: 'middle' + position: 'middle', + showTotal: true, + + pageSizeOptions, + sizeChangeEvent:(pageSize,current) => { + this.setState({ + pageSize + }) + } + } } }, @@ -1071,12 +1098,12 @@ constructor (props) { | 参数 | 说明 | 类型 | 可选值 | 默认值 | | -------- | ----- | ---- | ---- | ---- | -| size | 表格尺寸 | String | large \| small \| normal | normal | -| bordered | 是否显示边框 | Boolean | true \| false | false | -| striped | 斑马纹 | Boolean | true \| false | false | -| columns | 表格数据列对应信息 | Array | - | - | -| data | 表格数据 | Array | - | - | -| emptyText | 数据为空时展示的文案 | String | - | No Data | +| size | 表格尺寸 | String | large,small,normal | normal | +| bordered | 是否显示边框 | Bollean | - | false | +| striped | 斑马纹 | Bollean | - | false | +| columns | 表格数据列对应信息 | array | - | - | +| data | 表格数据 | array | - | - | +| emptyText | 数据为空时展示的文案 | string | - | No Data | | scroll | 设置横向滚动,也可用于指定滚动区域的宽,建议为`x`设置一个数字,如果不设置,默认table宽度为100% | number | true | - | | fixTop | 吸顶 | Number | true | false | | pagination | 查看分页组件配置 | Object | - | false | From 36f960cd27b2457df2c98d9a8c5ff5aabf752a94 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Sun, 28 Apr 2019 12:05:18 +0800 Subject: [PATCH 049/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 7 ++- components/tree/TreeDivider.js | 29 ++++-------- components/tree/TreeItem.js | 13 +++--- components/tree/TreeNode.js | 51 +++++++++++++++++++-- components/tree/style/index.scss | 25 +++++++++- docs/zh-CN/components/tree.md | 79 +++++++++++++++++++++++++++++++- 6 files changed, 168 insertions(+), 36 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 850c61a41..427f629cb 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -32,7 +32,6 @@ class Tree extends Component { defaultExpandAll: PropTypes.bool, checkable: PropTypes.bool, draggable: PropTypes.bool, - // withLine: PropTypes.bool, onNodeClick: PropTypes.func, onClick: PropTypes.func, onChange: PropTypes.func @@ -188,17 +187,18 @@ class Tree extends Component { checkable, closeIcon, openIcon, - // withLine, highlightable, editable, searchable, draggable, - style + style, + origin } = this.props const { data } = this.state return ( <div className={classNames(`${prefixCls}`)} style={style}> <TreeNode + origin={origin} checked={this.props.checkedKeys || []} onNodeClick={this.props.onNodeClick} onClick={this.props.onClick} @@ -219,7 +219,6 @@ class Tree extends Component { searchable={searchable} openIcon={openIcon} closeIcon={closeIcon} - // withLine={withLine} draggable={draggable} /> </div> diff --git a/components/tree/TreeDivider.js b/components/tree/TreeDivider.js index 39f28199d..3b3633e33 100644 --- a/components/tree/TreeDivider.js +++ b/components/tree/TreeDivider.js @@ -1,26 +1,15 @@ import React from 'react' +import ClassNames from 'classnames' const TreeDivider = props => { - const style = props.top - ? { - position: 'absolute', - display: 'flex', - width: '100%', - alignItems: 'center', - top: 21 - } - : { position: 'absolute', display: 'flex', width: '100%', alignItems: 'center', bottom: -2 } return ( - <div style={style}> - <div - style={{ - flex: '0 0 5px', - height: 5, - border: '1px solid rgba(66,132,245,1)', - borderRadius: '2.5px', - boxSizing: 'border-box' - }} - /> - <div style={{ flex: '1', height: '1px', background: 'rgba(66,132,245,1)' }} /> + <div + className={ClassNames( + 'hi-tree__divider', + `hi-tree__divider--${props.top ? 'top' : 'bottom'}` + )} + > + <div className='divider-circle' /> + <div className='divider-line' /> </div> ) } diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index f3cf3a1f3..d67217afc 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -46,7 +46,9 @@ class TreeItem extends Component { connectDragSource, connectDropTarget, targetNode, - saveEditNode + saveEditNode, + origin, + loadChildren } = this.props return connectDropTarget( <li key={item.id}> @@ -56,19 +58,16 @@ class TreeItem extends Component { }} > {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} - {/* <span onClick={() => onExpanded(expanded, item)} className={`${prefixCls}_item-icon`}> - {item.children && item.children.length > 0 - ? renderSwitcher(expanded) - : withLine && renderItemIcon()} - </span> */} { <span onClick={() => { onExpanded(expanded, item) + loadChildren(item.id) }} className={`${prefixCls}_item-icon`} > - {item.children && item.children.length > 0 && renderSwitcher(expanded)} + {((item.children && item.children.length > 0) || (origin && !expanded)) && + renderSwitcher(expanded)} </span> } {checkable ? ( diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 1471ab600..d70c420d3 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -9,6 +9,9 @@ import uuidv4 from 'uuid/v4' import TreeItem from './TreeItem' import Modal from '../modal' import { collectExpandId, findNode } from './util' +import { handleNotificate } from '../notification' +import axios from 'axios' +import qs from 'qs' export default class TreeNode extends Component { constructor (props) { @@ -127,7 +130,6 @@ export default class TreeNode extends Component { draggingNode: null }) } - // TODO:调整添加节点的策略,由深度遍历改为按层修改! // 添加兄弟节点 _addSibNode = (itemId, data, editingNodes) => { data.forEach((d, index) => { @@ -289,7 +291,47 @@ export default class TreeNode extends Component { } }) } - + // 异步加载子节点 + loadChildren = itemId => { + const { origin } = this.props + const { method, url, headers, data, params, func, errorHandler } = origin + const { dataCache } = this.state + const that = this + axios({ + method: method, + url: url, + headers, + data, + params, + paramsSerializer: function (params) { + return qs.stringify(params, { arrayFormat: 'brackets' }) + } + }) + .then(function (res) { + const _dataCache = cloneDeep(dataCache) + const node = findNode(itemId, _dataCache) + if (!node.children) { + node.children = func(res) + that.setState({ + dataCache: _dataCache + }) + } + }) + .catch(error => { + if (errorHandler) { + errorHandler(error) + } else { + handleNotificate({ + type: 'error', + showClose: true, + autoClose: true, + title: 'Error', + duration: 5000, + message: error + }) + } + }) + } switchDropNode = (targetItemId, sourceItemId, data, allData) => { data.forEach(item => { if (item.children) { @@ -403,7 +445,8 @@ export default class TreeNode extends Component { onExpanded, editable, checked, - expanded + expanded, + origin } = this.props const { highlight, @@ -421,6 +464,7 @@ export default class TreeNode extends Component { {data.map(item => { return ( <TreeItem + origin={origin} key={item.id} editable={editable} dropDividerPosition={dropDividerPosition} @@ -463,6 +507,7 @@ export default class TreeNode extends Component { removeTargetNode={this.removeTargetNode} cancelEditNode={this.cancelEditNode} item={item} + loadChildren={this.loadChildren} /> ) })} diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 46e4511f5..859b0b807 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -25,7 +25,30 @@ $tree: 'hi-tree' !default; } } } - + .hi-tree__divider { + position: absolute; + display: flex; + width: 100%; + align-items: center; + &--top { + top: 21px; + } + &--bottom { + bottom: -2px; + } + .divider-line { + flex: 1; + height: 1px; + background-color: rgba(66, 132, 245, 1); + } + .divider-circle { + flex: 0 0 5px; + height: 5px; + border: 1px solid rgba(66, 132, 245, 1); + border-radius: 2.5px; + box-sizing: border-box; + } + } ul, li { margin: 0; diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 7f939f475..5a3589fc4 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -64,6 +64,71 @@ render() { ::: +### 异步加载 + +点击展开异步加载树的子节点 + +:::demo + +```js +constructor(props) { + super(props) + this.state = { + treeData: [ + { id: 1, title: '小米', + children: [ + { id: 2, title: '技术', + children: [ + { id: 3, title: '后端', onClick: data => {console.log('后端:', data)} }, + { id: 4, title: '运维' }, + { id: 5, title: '前端' } + ] + }, + { id: 6, title: '产品' } + ] + }, + { id: 11, title: '小米', + children: [ + { id: 22, title: '技术' + }, + { id: 66, title: '产品' } + ] + }, + ] + } + +} + +render() { + return ( + <div style={{width:500}}> + <Tree + origin={{ + method:'get', + headers:{}, + data:{}, + params:{}, + url:'https://easy-mock.com/mock/5c1b42e3fe5907404e6540e9/hiui/select/options', + func:(res)=>{return res.data.data} + }} + defaultExpandAll + editable={true} + data={this.state.treeData} + defaultCheckedKeys={[2]} + onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} + onChange={data => {console.log('Tree data:', data)}} + openIcon='down' + closeIcon='up' + highlightable + onNodeClick={(item) => console.log('------click node', item)} + /> + </div> + ) +} +``` + +::: + ### 多选 :::demo @@ -337,11 +402,23 @@ render() { | 参数 | 说明 | 类型 | 可选值 | 默认值 | | ----------- | ------------------------------------------------------------------ | -------- | ------ | ------ | -| expand | 默认是否展开子菜单(优先级高于 defaultExpandAll) | Blooean | - | false | +| expand | 默认是否展开子菜单(优先级高于 defaultExpandAll) | Boolean | - | false | | onClick | 点击每项时触发的事件 | Function | - | - | | onNodeClick | 点击每项时触发,onClick 作用具体绑定的项,onNodeClick 作用于所以项 | Function | - | - | | style | 单个节点样式 | Object | - | - | +### Tree Attributes-origin + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------------ | ---------------------------------------------------------- | --------------- | ----------- | ------ | +| method | 异步请求的方法 | String | get \| post | get | +| url | 异步请求的 url | String | - | - | +| headers | 异步请求的请求头 | Object | - | - | +| data | post 请求时的请求体 | Object | - | - | +| params | 异步请求时的 url 参数 | Object | - | - | +| func | 对异步请求结果进行加工处理的函数,返回结果用于生成子节点项 | Function(data) | - | - | +| errorHandler | 对异步请求错误进行自定义处理的函数 | Function(error) | - | - | + ### Tree Attributes-options | 参数 | 说明 | 类型 | 可选值 | 默认值 | From 0d4f7e86e00e0cfe6ba4d118684ecdce2f4e959e Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Sun, 28 Apr 2019 16:00:16 +0800 Subject: [PATCH 050/112] update --- components/tree/Tree.js | 17 +++++++++++-- components/tree/TreeItem.js | 21 +++++++++++++-- components/tree/TreeNode.js | 10 ++++++-- docs/zh-CN/components/tree.md | 48 ++++++++++++++++++----------------- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 427f629cb..e1be559a9 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -192,7 +192,9 @@ class Tree extends Component { searchable, draggable, style, - origin + origin, + onDragStart, + onDrop } = this.props const { data } = this.state return ( @@ -220,10 +222,21 @@ class Tree extends Component { openIcon={openIcon} closeIcon={closeIcon} draggable={draggable} + onDragStart={onDragStart} + onDrop={onDrop} /> </div> ) } } -export default DragDropContext(HTML5Backend)(Tree) +const HOCTree = TreeComponent => { + return class WrapperTree extends Component { + render () { + const { draggable } = this.props + const DraggableTree = DragDropContext(HTML5Backend)(Tree) + return draggable ? <DraggableTree {...this.props} /> : <TreeComponent {...this.props} /> + } + } +} +export default HOCTree(Tree) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index d67217afc..317be07d1 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -50,7 +50,7 @@ class TreeItem extends Component { origin, loadChildren } = this.props - return connectDropTarget( + const treeItem = ( <li key={item.id}> <div style={{ @@ -169,6 +169,7 @@ class TreeItem extends Component { {item.children && item.children.length > 0 && expanded ? renderTree(item.children) : null} </li> ) + return draggable ? connectDropTarget(treeItem) : treeItem } } const source = { @@ -177,6 +178,7 @@ const source = { if (props.expanded) { props.closeExpandedTreeNode(props.item.id) } + props.onDragStart(props.item) return { sourceItem: props.item, originalExpandStatus: props.expanded } } } @@ -254,6 +256,21 @@ function targetCollect (connect) { connectDropTarget: connect.dropTarget() } } -export default DropTarget(Types['TreeNode'], target, targetCollect)( + +const DraggableTreeItem = DropTarget(Types['TreeNode'], target, targetCollect)( DragSource(Types['TreeNode'], source, sourceCollect)(TreeItem) ) +const HOCTreeItem = TreeItemComponent => { + return class WrapperTreeItem extends Component { + render () { + const { draggable } = this.props + + return draggable ? ( + <DraggableTreeItem {...this.props} /> + ) : ( + <TreeItemComponent {...this.props} /> + ) + } + } +} +export default HOCTreeItem(TreeItem) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index d70c420d3..c19afcf13 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -355,7 +355,9 @@ export default class TreeNode extends Component { } else { this.switchDropNode(targetItem.id, sourceItem.id, _dataCache, dataCache) } - + const _sourceItem = findNode(sourceItem.id, dataCache) + const _targetItem = findNode(targetItem.id, dataCache) + this.props.onDrop(_sourceItem, _targetItem) this.setState({ dataCache: _dataCache }) @@ -446,7 +448,9 @@ export default class TreeNode extends Component { editable, checked, expanded, - origin + origin, + onDragStart + // onDrop } = this.props const { highlight, @@ -470,6 +474,8 @@ export default class TreeNode extends Component { dropDividerPosition={dropDividerPosition} prefixCls={prefixCls} draggable={draggable} + onDragStart={onDragStart} + // onDrop={onDrop} checked={!!checked.includes(item.id)} highlight={highlight} highlightable={highlightable} diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 5a3589fc4..1795ec65a 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -366,6 +366,12 @@ render() { draggable={true} data={this.state.treeData} defaultCheckedKeys={[2]} + onDragStart = {(dragNode)=> { + console.log(dragNode) + }} + onDrop = {(dragNode,dropNode)=> { + console.log(dragNode,dropNode) + }} onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} onChange={data => {console.log('Tree data:', data)}} openIcon='down' @@ -382,21 +388,25 @@ render() { ### Tree Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ---------------- | ----------------------------------------------------------------------- | ------- | ---------------------------- | ------ | -| data | 展示数据 | Array | 参见 Tree Attributes-data | - | -| checkable | 节点前添加 Checkbox 复选框(暂不支持与 draggable 和 editable 同时使用) | Boolean | - | false | -| editable | 节点右键可编辑(添加同级节点、添加子节点、编辑节点、删除节点) | Boolean | - | false | -| draggable | 节点可拖拽 | Boolean | - | false | -| searchable | 节点可拖拽 | Boolean | - | false | -| options | 配置选项 | Object | 参见 Tree Attributes-options | - | -| defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | -| checkedKeys | 默认选中的 checkbox | Array | - | - | -| openIcon | 表示展开的图标 | String | Icon 图标名称 | - | -| closeIcon | 表示闭合的图标 | String | Icon 图标名称 | - | -| style | 组件整体样式 | Object | - | - | -| highlightable | 高亮 | Boolean | -| withLine | 是否显示连接线 | Boolean | - | false | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------- | ------ | +| data | 展示数据 | Array | 参见 Tree Attributes-data | - | +| checkable | 节点前添加 Checkbox 复选框(暂不支持与 draggable 和 editable 同时使用) | Boolean | - | false | +| editable | 节点右键可编辑(添加同级节点、添加子节点、编辑节点、删除节点) | Boolean | - | false | +| draggable | 节点可拖拽 | Boolean | - | false | +| searchable | 节点可搜索 | Boolean | - | false | +| options | 配置选项 | Object | 参见 Tree Attributes-options | - | +| defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | +| checkedKeys | 默认选中的 checkbox | Array | - | - | +| openIcon | 表示展开的图标 | String | Icon 图标名称 | - | +| closeIcon | 表示闭合的图标 | String | Icon 图标名称 | - | +| style | 组件整体样式 | Object | - | - | +| highlightable | 高亮 | Boolean | - | - | +| onChange | 改变复选框状态时触发 | Function(checkedArr:Array, title: String, isChecked: Boolean) | - | - | +| onNodeToggle | 节点被点击(展开/收起)时触发 | Function(data: Obejct, isExpanded: Boolean) | - | - | +| onCheckChange | 节点选中项 | Funciton(checkedArr: Array, title: String, isChecked: Boolean) | - | - | +| onDragStart | 节点开始拖拽时触发 | Funciton(dragNode: Object) | - | - | +| onDrop | 节点拖拽成功时触发 | Funciton(dragNode: Object, dropNode: Object) | - | - | ### Tree Attributes-data @@ -425,11 +435,3 @@ render() { | -------- | ---------------------------------- | ------ | ------ | -------- | | title | 指定节点标签为节点对象的某个属性值 | String | - | title | | children | 指定子树为节点对象的某个属性值 | String | - | children | - -### Tree Events - -| 参数 | 说明 | 回调参数 | -| ------------- | --------------------------- | ----------------------------------- | -| onChange | 改变复选框状态时触发 | checkedArr, title, isChecked | -| onNodeToggle | 节点被点击(展开/收起)时触发 | (data: Obejct, isExpanded: Boolean) | -| onCheckChange | 节点选中项 | checkedArr, title, isChecked | From e1317527c45717f78984e2e460c9cb040677611e Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Sun, 28 Apr 2019 16:49:40 +0800 Subject: [PATCH 051/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20onDelete=20onSave?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 19 +++++-------------- components/tree/TreeNode.js | 5 ++++- docs/zh-CN/components/tree.md | 9 +++++++++ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index e1be559a9..535aa0b8a 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -1,6 +1,5 @@ import React, { Component } from 'react' import classNames from 'classnames' -import PropTypes from 'prop-types' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' import { getAll, dealData } from './util' @@ -25,18 +24,6 @@ class Tree extends Component { } } - static propTypes = { - data: PropTypes.arrayOf(PropTypes.object), - defaultCheckedKeys: PropTypes.arrayOf(PropTypes.any), - onDragStart: PropTypes.func, - defaultExpandAll: PropTypes.bool, - checkable: PropTypes.bool, - draggable: PropTypes.bool, - onNodeClick: PropTypes.func, - onClick: PropTypes.func, - onChange: PropTypes.func - } - static defaultProps = { prefixCls: 'hi-tree', defaultCheckedKeys: [], @@ -194,7 +181,9 @@ class Tree extends Component { style, origin, onDragStart, - onDrop + onDrop, + onDelete, + onSave } = this.props const { data } = this.state return ( @@ -224,6 +213,8 @@ class Tree extends Component { draggable={draggable} onDragStart={onDragStart} onDrop={onDrop} + onDelete={onDelete} + onSave={onSave} /> </div> ) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index c19afcf13..70300d314 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -1,5 +1,4 @@ import React, { Component } from 'react' -// import Checkbox from '../table/checkbox/index' import classNames from 'classnames' import isEqual from 'lodash/isEqual' import cloneDeep from 'lodash/cloneDeep' @@ -262,6 +261,8 @@ export default class TreeNode extends Component { editNodes: editNodes.filter(node => node.id !== itemId), editingNodes: editingNodes.filter(node => node.id !== itemId) }) + const node = findNode(itemId, dataCache) + this.props.onSave(node, _dataCache) } // 删除拖动的节点 _delDragNode = (itemId, data) => { @@ -379,6 +380,8 @@ export default class TreeNode extends Component { const _dataCache = cloneDeep(dataCache) this._deleteNode(itemId, _dataCache) this.setState({ dataCache: _dataCache }) + const node = findNode(itemId, dataCache) + this.props.onDelete(node, _dataCache) } // 渲染右键菜单 renderRightClickMenu = item => { diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 1795ec65a..45ac2fa8d 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -305,6 +305,12 @@ render() { data={this.state.treeData} defaultCheckedKeys={[2]} onNodeToggle={(data, isExpanded) => {console.log('toggle: data isExpanded', data, isExpanded)}} + onSave={(saveNode, data) => { + console.log(saveNode, data) + }} + onDelete={(deleteNode, data) => { + console.log(deleteNode, data) + }} onChange={data => {console.log('Tree data:', data)}} openIcon='down' closeIcon='up' @@ -407,6 +413,9 @@ render() { | onCheckChange | 节点选中项 | Funciton(checkedArr: Array, title: String, isChecked: Boolean) | - | - | | onDragStart | 节点开始拖拽时触发 | Funciton(dragNode: Object) | - | - | | onDrop | 节点拖拽成功时触发 | Funciton(dragNode: Object, dropNode: Object) | - | - | +| onDelete | 节点删除时触发 | Funciton(deleteNode: Object, data: Object) | - | - | + +| onSave | 节点保存新增、编辑状态时触发 | Funciton(editNode: Object, data: Object) | - | - | ### Tree Attributes-data From 8066d77960fe6e6395dca729c313fc821da26d15 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 29 Apr 2019 09:41:52 +0800 Subject: [PATCH 052/112] update --- components/tree/TreeItem.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 317be07d1..3857937e0 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -10,9 +10,7 @@ const Types = { class TreeItem extends Component { render () { const { - // 节点可编辑 editable, - // 节点可拖拽 draggable, dropDividerPosition, checked, @@ -21,7 +19,6 @@ class TreeItem extends Component { editNodes, editingNodes, prefixCls, - // withLine, semiChecked, onNodeClick, onClick, @@ -29,11 +26,9 @@ class TreeItem extends Component { item, draggingNode, checkable, - // itemContainerStyle, itemStyle, onExpanded, onValueChange, - // renderItemIcon, cancelEditNode, cancelAddSiblingNode, renderTree, @@ -196,12 +191,11 @@ const target = { // 先看下是不是在最近得组件 if (monitor.isOver({ shallow: true })) { - // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 if ( sourceItem.id === targetItem.id || (targetItem.children && targetItem.children.map(t => t.id).includes(sourceItem.id)) ) { - // 2.如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 + // 如果源节点就是目的节点或者源节点是目的节点的子节点(直系)再或者源节点是目的节点的父节点,那么什么都不做 // 如果什么都不做,原来展开则现在还展开 if (originalExpandStatus) { expandTreeNode(sourceItem.id) @@ -209,7 +203,7 @@ const target = { removeDraggingNode() removeTargetNode() } else { - // // 3.移动节点到相应位置 + // 移动节点到相应位置 dropNode(sourceItem, targetItem, dropDividerPosition) removeDraggingNode() removeTargetNode() @@ -228,12 +222,10 @@ const target = { } = props // 先看下是不是在最近得组件 if (monitor.isOver({ shallow: true })) { - // 1.移入该组件时则其及其所有祖先组件全部展开,移出时,恢复原状 const sourcePosition = monitor.getClientOffset() const targetComponent = findDOMNode(component).getBoundingClientRect() if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { setPosition(sourcePosition.x, sourcePosition.y) - // 3.移动节点到相应位置 // 如果在节点的上半部分,则为移动其内部,如果为下半部分,则为节点下方 if (sourcePosition.y <= targetComponent.y + targetComponent.height / 2) { setTargetNode(targetItem.id, 'sub') From cb779d71067d99c3fd36d1d67a0bfc738127a336 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 29 Apr 2019 09:50:43 +0800 Subject: [PATCH 053/112] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/util.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/tree/util.js b/components/tree/util.js index 3dd53dcc3..674717088 100644 --- a/components/tree/util.js +++ b/components/tree/util.js @@ -295,9 +295,7 @@ export const collectExpandId = (data, searchValue, collection = [], allData) => data.forEach(item => { if (item.title.includes(searchValue)) { const parentIds = getAncestorIds(item.id, allData, []) - // console.log('parentIds', parentIds) collection.splice(collection.length - 1, 0, ...parentIds) - // console.log('collection', collection) } if (item.children) { collectExpandId(item.children, searchValue, collection, allData) From a7a6a290681261f9525c51aca10e727f54c87d51 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 29 Apr 2019 10:02:33 +0800 Subject: [PATCH 054/112] update --- components/tree/TreeNode.js | 2 +- docs/zh-CN/components/tree.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/tree/TreeNode.js b/components/tree/TreeNode.js index 70300d314..2b4c5ca3e 100644 --- a/components/tree/TreeNode.js +++ b/components/tree/TreeNode.js @@ -312,7 +312,7 @@ export default class TreeNode extends Component { const _dataCache = cloneDeep(dataCache) const node = findNode(itemId, _dataCache) if (!node.children) { - node.children = func(res) + node.children = func(res.data) that.setState({ dataCache: _dataCache }) diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 45ac2fa8d..ca0366baf 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -109,7 +109,7 @@ render() { data:{}, params:{}, url:'https://easy-mock.com/mock/5c1b42e3fe5907404e6540e9/hiui/select/options', - func:(res)=>{return res.data.data} + func:(res)=>{return res.data} }} defaultExpandAll editable={true} From d8ee4951f5e234018faf5c7c0e316a53fcd8c168 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 29 Apr 2019 10:16:54 +0800 Subject: [PATCH 055/112] update --- docs/zh-CN/components/tree.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index ca0366baf..f30fadd1c 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -414,8 +414,7 @@ render() { | onDragStart | 节点开始拖拽时触发 | Funciton(dragNode: Object) | - | - | | onDrop | 节点拖拽成功时触发 | Funciton(dragNode: Object, dropNode: Object) | - | - | | onDelete | 节点删除时触发 | Funciton(deleteNode: Object, data: Object) | - | - | - -| onSave | 节点保存新增、编辑状态时触发 | Funciton(editNode: Object, data: Object) | - | - | +| onSave | 节点保存新增、编辑状态时触发 | Funciton(editNode: Object, data: Object) | - | - | ### Tree Attributes-data From 5a56eebf5bd1b3cffe4fc1673a126f652ed1cecf Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 29 Apr 2019 10:20:23 +0800 Subject: [PATCH 056/112] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-CN/components/tree.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index f30fadd1c..9728c5288 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -402,6 +402,7 @@ render() { | draggable | 节点可拖拽 | Boolean | - | false | | searchable | 节点可搜索 | Boolean | - | false | | options | 配置选项 | Object | 参见 Tree Attributes-options | - | +| origin | 异步加载配置项 | Object | 参见 Tree Attributes-origin | - | | defaultExpandAll | 是否默认展开所有树节点 | Boolean | - | false | | checkedKeys | 默认选中的 checkbox | Array | - | - | | openIcon | 表示展开的图标 | String | Icon 图标名称 | - | From fe9d2efdbfdbf4a9a7f5c5235dde6479bb4833d1 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 29 Apr 2019 16:07:40 +0800 Subject: [PATCH 057/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20origin=20=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/TreeItem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index 3857937e0..cedfc4b80 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -57,7 +57,9 @@ class TreeItem extends Component { <span onClick={() => { onExpanded(expanded, item) - loadChildren(item.id) + if (origin) { + loadChildren(item.id) + } }} className={`${prefixCls}_item-icon`} > From f38e1765ec8e2f8c1c4d21ca304f60480c724003 Mon Sep 17 00:00:00 2001 From: surui1 <surui1@xiaomi.com> Date: Mon, 29 Apr 2019 16:18:13 +0800 Subject: [PATCH 058/112] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/table/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/table/index.js b/components/table/index.js index ba604d4c4..c3a55fdd5 100644 --- a/components/table/index.js +++ b/components/table/index.js @@ -466,6 +466,7 @@ class Table extends Component { <Pagination {...serverPaginationConfig} onChange={(current) => { + serverPaginationConfig.onChange(current) this.setState({ serverPagination: { ...serverPagination, From 533d07646c31b76c5b72497f40748c8e8983afff Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Sun, 5 May 2019 17:58:53 +0800 Subject: [PATCH 059/112] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7;=E4=BF=AE=E5=A4=8D=20test=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/date-picker/style/index.scss | 8 ------- components/date-picker/style/timepicker.scss | 2 -- components/menu/style/index.scss | 2 +- components/table/style/Table.scss | 14 +++++------ components/tree/style/index.scss | 25 ++++++++++++++++++-- package.json | 2 +- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/components/date-picker/style/index.scss b/components/date-picker/style/index.scss index afc78de42..9862ba56e 100644 --- a/components/date-picker/style/index.scss +++ b/components/date-picker/style/index.scss @@ -151,9 +151,6 @@ $basic-color: #4284f5 !default; box-sizing: border-box; } - &__period-body { - } - &__period-list { list-style: none; margin: 0; @@ -262,16 +259,11 @@ $basic-color: #4284f5 !default; } &__row { - &::after { content: ''; } &--current-week { - // .hi-datepicker__content { - // background: map-get(get-palette($basic-color), '10'); - // } - .range-se { .hi-datepicker__content { background: $basic-color; diff --git a/components/date-picker/style/timepicker.scss b/components/date-picker/style/timepicker.scss index b689d4077..82d0321ac 100644 --- a/components/date-picker/style/timepicker.scss +++ b/components/date-picker/style/timepicker.scss @@ -2,8 +2,6 @@ width: 180px; background: #fff; - - &__timebody { display: flex; justify-content: space-around; diff --git a/components/menu/style/index.scss b/components/menu/style/index.scss index de2206a61..d44bfef99 100644 --- a/components/menu/style/index.scss +++ b/components/menu/style/index.scss @@ -93,7 +93,7 @@ position: relative; margin-right: 48px; - &--active:after { + &--active::after { content: ''; position: absolute; bottom: 0; diff --git a/components/table/style/Table.scss b/components/table/style/Table.scss index 9e0970132..151351ea5 100644 --- a/components/table/style/Table.scss +++ b/components/table/style/Table.scss @@ -12,7 +12,7 @@ $nornamFontSize: 14px !default; $largeHeight: 64px !default; $largeFontSize: 14px !default; -$borderColor: #e6e7e8; +$borderColor: #e6e7e8 !default; .hi-table-header-menu-small { .hi-table-header-menu { @@ -32,13 +32,13 @@ $borderColor: #e6e7e8; .#{$table} { position: relative; - font-family: PingFangSC-Regular; - td,th{ + + td, + th { box-sizing: border-box; } //width: 100%; - &.striped { table { tr:nth-child(2n) { @@ -50,14 +50,14 @@ $borderColor: #e6e7e8; &.bordered { table { tr { - td, th{ + td, + th { border-left: 1px solid $borderColor; } } } } - &.large { table { tr { @@ -239,7 +239,7 @@ $borderColor: #e6e7e8; } td { - color: #3B4148; + color: #3b4148; word-break: break-word; &.picked { diff --git a/components/tree/style/index.scss b/components/tree/style/index.scss index 859b0b807..fb403b8e1 100644 --- a/components/tree/style/index.scss +++ b/components/tree/style/index.scss @@ -3,44 +3,54 @@ $tree: 'hi-tree' !default; .hi-tree { font-size: 14px; + .hi-tree__searcher { position: relative; padding-left: 15px; margin-bottom: 24px; + .hi-tree__searcher--empty { position: absolute; top: 34px; - color: #999999; + color: #999; left: 39; font-size: 12; } + .hi-input__inner { flex: 1; } + .hi-input--append { line-height: 32px; + .hi-input__append { width: 32px; text-align: center; } } } + .hi-tree__divider { position: absolute; display: flex; width: 100%; align-items: center; + &--top { top: 21px; } + &--bottom { bottom: -2px; } + .divider-line { flex: 1; height: 1px; background-color: rgba(66, 132, 245, 1); } + .divider-circle { flex: 0 0 5px; height: 5px; @@ -49,6 +59,7 @@ $tree: 'hi-tree' !default; box-sizing: border-box; } } + ul, li { margin: 0; @@ -83,14 +94,17 @@ $tree: 'hi-tree' !default; margin: 22px 0; } } + .editing { display: flex; align-items: center; + .hi-input { width: 240px; margin-right: 20px; } } + .hi-checkbox-label { display: none; } @@ -138,19 +152,24 @@ $tree: 'hi-tree' !default; width: calc(100% - 40px); padding-left: 4px; } + transition: background 0.3s; + &:hover { background-color: rgba(66, 133, 244, 0.08); color: rgba(56, 62, 71, 1); } + &.dragging { background-color: rgba(246, 246, 246, 1); color: rgba(204, 204, 204, 1); } + &.highlight { color: #fff; background-color: #4285f4; } + .right-click-menu { color: #333; position: absolute; @@ -161,14 +180,16 @@ $tree: 'hi-tree' !default; background: #fff; border: 1px solid #e6e7e8; border-radius: 2px; - box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); box-sizing: border-box; padding: 4px 1px; + li { padding-left: 15px; height: 36px; line-height: 36px; transition: background 0.3s; + &:hover { background: rgba(66, 133, 244, 0.08); } diff --git a/package.json b/package.json index 325bf9f08..9bc43d233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hi-ui/hiui", - "version": "1.4.1", + "version": "1.5.0-rc.1", "description": "HIUI for React", "scripts": { "test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'", From 6ae863dadc16c776a44f89497b89a45c6bbcc788 Mon Sep 17 00:00:00 2001 From: zhan8863 <zhan8863@126.com> Date: Sun, 5 May 2019 18:54:07 +0800 Subject: [PATCH 060/112] Feature/changelog dev (#189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复问题 * update * 更新版本号 * fix style * 修复 Counter 组件和 Input 组件 bug * 移除 console.log * 调整三目运算符写法 * 更新写法 * changelog --- CHANGELOG.md | 12 +++ components/counter/index.js | 81 ++++++++-------- components/counter/style/index.scss | 1 + components/input/index.js | 143 +++++++++++----------------- components/input/style/index.scss | 1 + components/nav-menu/NavMenu.js | 2 +- components/radio/index.js | 103 +++++++++++--------- 7 files changed, 166 insertions(+), 177 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c317df8..c05efec6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # 更新日志 +## 1.5.0-rc.1 +- 新增:`<Card />` 组件 +- 修改:`<Tree />`、`<Datepicker />` 组件,调整新的 UI,增加新的 API + +## 1.4.1 +- 修复: `<Datepicker />` 快捷选择问题 +- 修复: `<Cascader />` ref 引起的不能正确渲染的问题 +- 修复: `<Cascader />` 面板不能正确关闭的问题 +- 修复: `<Upload />` 上传后的错误信息展示问题 +- 修复: `<Counter />` 、 `<Input />` value 值为字符串的异常问题 +- 修复: `<Navmenu />` 当只有一个标签时导致的渲染问题 + ## 1.4.0 - 新增:部分组件测试用例 - 新增:`<Tabs />`组件 diff --git a/components/counter/index.js b/components/counter/index.js index 8de387929..d933234be 100644 --- a/components/counter/index.js +++ b/components/counter/index.js @@ -30,17 +30,16 @@ class Counter extends React.Component { this.attrs = this.getAttrs(oldProps) - const val = +this.props.value - const value = val > +this.props.max ? +this.props.max : (val < +this.props.min ? +this.props.min : val) - + const { value, min, max } = this.props + const finalValue = Math.min(Math.max(Number(value), Number(min)), Number(max)) this.state = { - value: this.format(value), - valueTrue: this.formatValue(value) + value: this.format(finalValue), + valueTrue: this.formatValue(finalValue) } } componentWillReceiveProps (nextProps) { - if (nextProps.value && (+nextProps.value !== +this.props.value)) { + if (nextProps.value && +nextProps.value !== +this.props.value) { this.setState({ value: this.format(nextProps.value), valueTrue: this.formatValue(nextProps.value) @@ -77,7 +76,7 @@ class Counter extends React.Component { value = '' } - return value.toString().replace(/[^-\d]/g, '') + return isNaN(Number(value)) ? value.toString().replace(/[^-\d]/g, '') : value } /** @@ -86,11 +85,16 @@ class Counter extends React.Component { * @param {string} val 值 */ format (val) { - return val && (val.toString().indexOf('.') !== -1 ? val.toString().replace(/(\d)(?=(\d{3})+\.)/g, ($0, $1) => { - return $1 + ',' - }) : val.toString().replace(/(\d)(?=(\d{3}))/g, ($0, $1) => { - return $1 + ',' - })) + return ( + val && + (val.toString().indexOf('.') !== -1 + ? val.toString().replace(/(\d)(?=(\d{3})+\.)/g, ($0, $1) => { + return $1 + ',' + }) + : val.toString().replace(/(\d)(?=(\d{3}))/g, ($0, $1) => { + return $1 + ',' + })) + ) } /** @@ -114,14 +118,14 @@ class Counter extends React.Component { case 'minus': valueTrue -= steps - if (min !== undefined && valueTrue < min) { + if (valueTrue < min) { valueTrue = min } break case 'plus': valueTrue += steps - if (max !== undefined && valueTrue > max) { + if (valueTrue > max) { valueTrue = max } break @@ -129,7 +133,7 @@ class Counter extends React.Component { } const value = this.format(valueTrue + '') - this.setState({value, valueTrue}, () => { + this.setState({ value, valueTrue }, () => { const e = { target: this._Input } @@ -138,37 +142,32 @@ class Counter extends React.Component { } render () { - const { - className, - id, - disabled - } = this.props + const { className, id, disabled } = this.props const min = +this.props.min const max = +this.props.max - let { - value, - valueTrue - } = this.state + let { value, valueTrue } = this.state return ( - <div - className={`hi-counter ${className || ''}`} - id={id} - > - <div - className={`hi-counter-outer`} - > + <div className={`hi-counter ${className || ''}`} id={id}> + <div className={`hi-counter-outer`}> <span - className={`hi-counter-minus hi-counter-sign ${((min !== undefined && this.state.valueTrue <= min) || disabled) ? 'disabled' : ''}`} + className={`hi-counter-minus hi-counter-sign ${ + (min !== undefined && this.state.valueTrue <= min) || disabled ? 'disabled' : '' + }`} onClick={e => { - this.signEvent('minus', ((min !== undefined && this.state.valueTrue <= min) || disabled)) + this.signEvent( + 'minus', + (min !== undefined && this.state.valueTrue <= min) || disabled + ) }} > - </span> <input id={id ? `${id}_value` : ''} - ref={arg => { this._Input = arg }} + ref={arg => { + this._Input = arg + }} value={this.state.value} disabled={disabled} data-value={this.state.valueTrue} @@ -179,8 +178,7 @@ class Counter extends React.Component { let value = e.target.value value = this.format(value) let valueTrue = this.formatValue(value) - - this.setState({value, valueTrue}) + this.setState({ value, valueTrue }) }} onBlur={e => { e.persist() @@ -190,17 +188,20 @@ class Counter extends React.Component { } else if (typeof max !== 'undefined' && +valueTrue > max) { value = this.format(max) valueTrue = max + } else { + value = this.format(valueTrue) } - - this.setState({value, valueTrue}, () => { + this.setState({ value, valueTrue }, () => { this.props.onChange && this.props.onChange(e, valueTrue) }) }} /> <span - className={`hi-counter-plus hi-counter-sign ${((max !== undefined && this.state.valueTrue >= max) || disabled) ? 'disabled' : ''}`} + className={`hi-counter-plus hi-counter-sign ${ + (max !== undefined && this.state.valueTrue >= max) || disabled ? 'disabled' : '' + }`} onClick={e => { - this.signEvent('plus', ((max !== undefined && this.state.valueTrue >= max) || disabled)) + this.signEvent('plus', (max !== undefined && this.state.valueTrue >= max) || disabled) }} > + diff --git a/components/counter/style/index.scss b/components/counter/style/index.scss index 577591d33..a7cccb672 100644 --- a/components/counter/style/index.scss +++ b/components/counter/style/index.scss @@ -30,6 +30,7 @@ $counter: 'hi-counter' !default; flex: 0 0 auto; width: $m-width-ceil; height: $m-width-ceil; + line-height: $m-width-ceil; border: 1px solid $border-color-normal; border-radius: 2px; font-weight: bold; diff --git a/components/input/index.js b/components/input/index.js index 735407241..bbaaa3774 100644 --- a/components/input/index.js +++ b/components/input/index.js @@ -32,7 +32,8 @@ class Input extends Component { const suffix = typeof this.props.suffix === 'undefined' ? '' : this.props.suffix this.state = { - value: (type === 'string' || type === 'number') ? format(this.props.value, this.props.type) : '', + value: + type === 'string' || type === 'number' ? format(this.props.value, this.props.type) : '', valueTrue: prefix + this.props.value + suffix, hover: false, active: false @@ -53,51 +54,33 @@ class Input extends Component { * 渲染 text 输入框 */ renderText () { - let { - hover, - active - } = this.state - let { - disabled, - type, - prefix, - suffix, - prepend, - append, - id, - placeholder - } = this.props + let { hover, active, value } = this.state + let { disabled, type, prefix, suffix, prepend, append, id, placeholder } = this.props const noClear = ['textarea'] - let prefixId = id ? (id + '_prefix') : '' - let suffixId = id ? (id + '_suffix') : '' + let prefixId = id ? id + '_prefix' : '' + let suffixId = id ? id + '_suffix' : '' return ( <div - className={classNames('hi-input__out', {'hi-input--prepend': prepend, 'hi-input--append': append})} + className={classNames('hi-input__out', { + 'hi-input--prepend': prepend, + 'hi-input--append': append + })} > - { // 前置元素 - prepend && - <span className='hi-input__prepend'> - {prepend} - </span> - } - <div - className={`hi-input__inner${active ? ' active' : ''}${disabled ? ' disabled' : ''}`} - > - { // 前缀 + {// 前置元素 + prepend && <span className='hi-input__prepend'>{prepend}</span>} + <div className={`hi-input__inner${active ? ' active' : ''}${disabled ? ' disabled' : ''}`}> + {// 前缀 prefix && ( - <span - id={prefixId} - className='hi-input__prefix' - data-value={prefix} - > + <span id={prefixId} className='hi-input__prefix' data-value={prefix}> {prefix} </span> - ) - } + )} <input - ref={arg => { this._Input = arg }} + ref={arg => { + this._Input = arg + }} className={`hi-input__text ${disabled ? 'disabled' : ''}`} value={this.state.value} autoComplete='off' @@ -119,11 +102,11 @@ class Input extends Component { value = format(value, type) - this.setState({value, valueTrue}, () => { + this.setState({ value, valueTrue }, () => { this.props.onChange && this.props.onChange(e, valueTrue) }) }} - onBlur={(e) => { + onBlur={e => { e.persist() let value = e.target.value const valueTrue = this.state.valueTrue @@ -133,7 +116,7 @@ class Input extends Component { value = formatAmount(value) } - this.setState({active: false, value}, () => { + this.setState({ active: false, value }, () => { this.props.onBlur && this.props.onBlur(e, valueTrue) }) }} @@ -141,7 +124,7 @@ class Input extends Component { e.persist() const valueTrue = this.state.valueTrue - this.setState({active: true}, () => { + this.setState({ active: true }, () => { this.props.onFocus && this.props.onFocus(e, valueTrue) }) }} @@ -166,10 +149,13 @@ class Input extends Component { this.props.onInput && this.props.onInput(e, valueTrue) }} /> - { // 清除 - noClear.indexOf(type) === -1 && typeof prefix === 'undefined' && typeof suffix === 'undefined' && ( + {// 清除 + noClear.indexOf(type) === -1 && + typeof prefix === 'undefined' && + typeof suffix === 'undefined' && + value !== '' && ( <span - className={`hi-input__fix-box ${(hover) && !disabled ? '' : 'invisible'}`} + className={`hi-input__fix-box ${hover && !disabled ? '' : 'invisible'}`} onClick={() => { this._Input.focus() @@ -177,7 +163,7 @@ class Input extends Component { const suffix = typeof this.props.suffix === 'undefined' ? '' : this.props.suffix const valueTrue = prefix + '' + suffix - this.setState({value: '', valueTrue: valueTrue}, () => { + this.setState({ value: '', valueTrue: valueTrue }, () => { const e = { target: this._Input } @@ -187,26 +173,16 @@ class Input extends Component { > <i className={`hi-input__clear hi-input__suffix__icon hi-icon icon-close-circle`} /> </span> - ) - } - { // 后缀 + )} + {// 后缀 suffix && ( - <span - id={suffixId} - className='hi-input__suffix' - data-value={suffix} - > + <span id={suffixId} className='hi-input__suffix' data-value={suffix}> {suffix} </span> - ) - } + )} </div> - { // 后置元素 - append && - <span className='hi-input__append'> - {append} - </span> - } + {// 后置元素 + append && <span className='hi-input__append'>{append}</span>} </div> ) } @@ -215,23 +191,17 @@ class Input extends Component { * 渲染 textarea */ renderTextarea () { - let { - active - } = this.state - let { - disabled - } = this.props + let { active } = this.state + let { disabled } = this.props return ( - <div - className='hi-input__out' - > - <div - className={`hi-input__inner ${active ? 'active' : ''} ${disabled ? 'disabled' : ''}`} - > + <div className='hi-input__out'> + <div className={`hi-input__inner ${active ? 'active' : ''} ${disabled ? 'disabled' : ''}`}> <textarea autoComplete='off' - ref={arg => { this._Input = arg }} + ref={arg => { + this._Input = arg + }} className={`hi-input__text ${disabled ? 'disabled' : ''}`} value={this.state.value} disabled={disabled} @@ -240,15 +210,15 @@ class Input extends Component { e.persist() let valueTrue = e.target.value - this.setState({value: valueTrue, valueTrue}, () => { + this.setState({ value: valueTrue, valueTrue }, () => { this.props.onChange && this.props.onChange(e, valueTrue) }) }} - onBlur={(e) => { + onBlur={e => { e.persist() let valueTrue = e.target.value - this.setState({active: false}, () => { + this.setState({ active: false }, () => { this.props.onBlur && this.props.onBlur(e, valueTrue) }) }} @@ -256,7 +226,7 @@ class Input extends Component { e.persist() const valueTrue = e.target.value - this.setState({active: true}, () => { + this.setState({ active: true }, () => { this.props.onFocus && this.props.onFocus(e, valueTrue) }) }} @@ -287,21 +257,16 @@ class Input extends Component { } render () { - const { - type - } = this.attrs + const { type } = this.attrs - const { - size, - id, - className, - required - } = this.props + const { size, id, className, required } = this.props return ( <div id={id} - className={`hi-input ${className || ''} ${type || ''}${size ? ' hi-input_' + size : ''}${required ? ' required' : ''}`} + className={`hi-input ${className || ''} ${type || ''}${size ? ' hi-input_' + size : ''}${ + required ? ' required' : '' + }`} style={this.props.style} data-value={this.state.valueTrue} onClick={e => { @@ -318,9 +283,7 @@ class Input extends Component { }) }} > - { - type === 'textarea' ? this.renderTextarea() : this.renderText() - } + {type === 'textarea' ? this.renderTextarea() : this.renderText()} </div> ) } diff --git a/components/input/style/index.scss b/components/input/style/index.scss index 006be2d6d..be81a701c 100644 --- a/components/input/style/index.scss +++ b/components/input/style/index.scss @@ -137,6 +137,7 @@ $input: 'hi-input' !default; flex: 1 0 auto; color: $normal-color; font-size: $font-size; + line-height: 30px; } &__prefix { diff --git a/components/nav-menu/NavMenu.js b/components/nav-menu/NavMenu.js index 175452b2d..818932500 100644 --- a/components/nav-menu/NavMenu.js +++ b/components/nav-menu/NavMenu.js @@ -50,7 +50,7 @@ class NavMenu extends Component { selectedKey, data } = this.props - if ((selectedKey < data.length)) { + if (selectedKey < data.length) { this.handleClick(data[selectedKey], selectedKey, 1) } this.getH && this.setToggleEvent(this.getH) diff --git a/components/radio/index.js b/components/radio/index.js index 7d420a5ab..8b775fa95 100755 --- a/components/radio/index.js +++ b/components/radio/index.js @@ -48,7 +48,7 @@ class Radio extends Component { } } componentDidMount () { - let {checked, list, disabled} = this.props + let { checked, list, disabled } = this.props this.setState(parse(checked, list, disabled)) } static propTypes = { @@ -69,34 +69,37 @@ class Radio extends Component { buttonStyle: 'default' } componentWillReceiveProps (props, state) { - this.setState({ - checked: parse(props.checked, props.list, props.disabled).checked - }) + if (props.checked !== this.props.checked) { + this.setState({ + checked: parse(props.checked, props.list, props.disabled).checked + }) + } } generateUUID () { var d = new Date().getTime() var uuid = 'xxxxxxxx'.replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0 d = Math.floor(d / 16) - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16) }) return uuid } handleChange (item, index, disabled) { - // console.log(labelText, index, event) - // event.stopPropagation() if (disabled) return - this.setState({ - checked: index - }, () => { - if (this.props.onChange) { - this.props.onChange(item.id || item.name, index, item) + this.setState( + { + checked: index + }, + () => { + if (this.props.onChange) { + this.props.onChange(item.id || item.name, index, item) + } } - }) + ) } render () { - let {list, align, layout, mode, buttonStyle} = this.props - const {checked, disabled} = this.state + let { list, align, layout, mode, buttonStyle } = this.props + const { checked, disabled } = this.state const cls = classnames( 'hi-radio', layout === 'vertical' && 'hi-radio--vertical', @@ -104,38 +107,46 @@ class Radio extends Component { ) return ( <div className={cls}> - { - list.map((item, index) => { - let _item = item - if (typeof item === 'string') { - _item = { - name: item, - id: '' - } + {list.map((item, index) => { + let _item = item + if (typeof item === 'string') { + _item = { + name: item, + id: '' } - const _disabled = disabled.indexOf(index) > -1 - const itemCls = classnames( - 'hi-radio__item', - _disabled && 'hi-radio__item--disabled', - checked === index && 'hi-radio__item--checked' - ) - const eles = [ - <span className='hi-radio__label' key={'label' + index}> {_item.name} </span>, - <span className='hi-radio__simulation-input' key={'input' + index} /> - ] - return ( - <div className={itemCls} key={index} onClick={this.handleChange.bind(this, _item, index, _disabled)}> - { - mode === 'normal' - ? <React.Fragment> - {align === 'left' ? eles : eles.reverse()} - </React.Fragment> - : <Button type={buttonStyle} disabled={_disabled} onClick={(e) => this.handleChange.bind(this, _item, index, _disabled)}>{_item.name}</Button> - } - </div> - ) - }) - } + } + const _disabled = disabled.indexOf(index) > -1 + const itemCls = classnames( + 'hi-radio__item', + _disabled && 'hi-radio__item--disabled', + checked === index && 'hi-radio__item--checked' + ) + const eles = [ + <span className='hi-radio__label' key={'label' + index}> + {_item.name} + </span>, + <span className='hi-radio__simulation-input' key={'input' + index} /> + ] + return ( + <div + className={itemCls} + key={index} + onClick={this.handleChange.bind(this, _item, index, _disabled)} + > + {mode === 'normal' ? ( + <React.Fragment>{align === 'left' ? eles : eles.reverse()}</React.Fragment> + ) : ( + <Button + type={buttonStyle} + disabled={_disabled} + onClick={e => this.handleChange.bind(this, _item, index, _disabled)} + > + {_item.name} + </Button> + )} + </div> + ) + })} </div> ) } From d36aab8007ed216420fceb439a2fbce35ee674b4 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 9 May 2019 10:34:12 +0800 Subject: [PATCH 061/112] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20transfer=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/checkbox/style/index.scss | 5 +- components/index.js | 1 + components/transfer/__test__/index.test.js | 5 + components/transfer/index.js | 179 +++++++++++++++++++++ components/transfer/style/index.js | 1 + components/transfer/style/index.scss | 89 ++++++++++ docs/en-US/components/transfer.md | 41 +++++ docs/zh-CN/components/transfer.md | 91 +++++++++++ site/locales/en-US.js | 3 +- site/locales/zh-CN.js | 3 +- site/pages/components/index.js | 3 +- site/pages/components/transfer/index.js | 9 ++ 12 files changed, 425 insertions(+), 5 deletions(-) create mode 100644 components/transfer/__test__/index.test.js create mode 100755 components/transfer/index.js create mode 100644 components/transfer/style/index.js create mode 100755 components/transfer/style/index.scss create mode 100755 docs/en-US/components/transfer.md create mode 100755 docs/zh-CN/components/transfer.md create mode 100755 site/pages/components/transfer/index.js diff --git a/components/checkbox/style/index.scss b/components/checkbox/style/index.scss index 23d6e0a82..4ceaaa392 100644 --- a/components/checkbox/style/index.scss +++ b/components/checkbox/style/index.scss @@ -37,8 +37,8 @@ border: 2px solid #fff; border-left: 0; border-top: 0; - height: 8px; - left: 5px; + height: 7px; + left: 4px; position: absolute; top: 1px; // transform: rotate(45deg) scaleY(0); @@ -75,6 +75,7 @@ border-radius: 2px; vertical-align: middle; position: relative; + box-sizing: border-box; } &__label { diff --git a/components/index.js b/components/index.js index c07a68149..1f5b8915e 100755 --- a/components/index.js +++ b/components/index.js @@ -37,4 +37,5 @@ export { default as Ficon } from './ficon' export { default as Icon } from './icon' export { default as Progress } from './progress' export { default as Card } from './card' +export { default as Transfer } from './transfer' export { ThemeContext, LocaleContext } from './context' diff --git a/components/transfer/__test__/index.test.js b/components/transfer/__test__/index.test.js new file mode 100644 index 000000000..86a3577c5 --- /dev/null +++ b/components/transfer/__test__/index.test.js @@ -0,0 +1,5 @@ +import React from 'react' +import { mount } from 'enzyme' +import renderer from 'react-test-renderer' +import Alert from '..' + diff --git a/components/transfer/index.js b/components/transfer/index.js new file mode 100755 index 000000000..0abb5c15f --- /dev/null +++ b/components/transfer/index.js @@ -0,0 +1,179 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from '../button' +import Checkbox from '../checkbox' +import './style/index' + +class Transfer extends Component { + constructor (props) { + super(props) + this.state = { + sourceList: [], + targetList: [], + sourceSelectedKeys: [], + targetSelectedKeys: [], + emptyContent: ['暂无数据', '暂无数据'] + } + } + componentDidMount () { + this.parseDatas(this.props) + } + parseDatas (props) { + const { data, targetKeys } = props + const sourceList = [] + const targetList = new Array(targetKeys.length) + data.forEach((item, index) => { + const targetIndexKey = targetKeys.indexOf(item.id) + if (targetIndexKey > -1) { + targetList[targetIndexKey] = item + } else { + sourceList.push(item) + } + }) + + this.setState({ + sourceList, + targetList + }) + } + componentWillReceiveProps (props) { + this.parseDatas(props) + } + + getListBydir (dir) { + return dir === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys' + } + clickItemEvent (item, index, dir) { + const { mode, targetKeys } = this.props + const { sourceSelectedKeys, targetSelectedKeys } = this.state + const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] + const selectedIndex = selectedItem.indexOf(item.id) + + if (mode === 'basic') { + if (selectedIndex > -1) { + selectedItem.splice(selectedIndex, 1) + } else { + selectedItem.push(item.id) + } + const newTargetKeys = dir === 'left' ? selectedItem.concat(targetKeys) : targetKeys.filter(tk => selectedItem.indexOf(tk) === -1) + this.setState({ + [this.getListBydir(dir)]: newTargetKeys + }, () => { + this.props.onChange(newTargetKeys) + this.setState({ + [this.getListBydir(dir)]: [] + }) + }) + } + } + checkboxEvent (dir, value, isChecked) { + const { sourceSelectedKeys, targetSelectedKeys } = this.state + const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] + const selectedIndex = selectedItem.indexOf(value) + if (selectedIndex > -1) { + selectedItem.splice(selectedIndex, 1) + } else { + selectedItem.push(value) + } + this.setState({ + [dir === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys']: selectedItem + }) + } + moveTo (dir) { + const { targetKeys } = this.props + const { sourceSelectedKeys, targetSelectedKeys } = this.state + const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] + const newTargetKeys = dir === 'left' ? selectedItem.concat(targetKeys) : targetKeys.filter(tk => selectedItem.indexOf(tk) === -1) + this.setState({ + [this.getListBydir(dir)]: newTargetKeys + }, () => { + this.props.onChange(newTargetKeys) + console.log(this.state.sourceList) + this.setState({ + [this.getListBydir(dir)]: [] + }) + }) + } + + renderEl (dir, datas) { + const { mode, showAllSelect } = this.props + const { emptyContent, sourceSelectedKeys, targetSelectedKeys } = this.state + const sks = dir === 'left' ? sourceSelectedKeys : targetSelectedKeys + return <div className='hi-transfer__container'> + <div className='hi-transfer__title'> + 标题 + </div> + <div className={`hi-transfer__body ${datas.length === 0 ? 'hi-transfer__body--empty' : ''}`}> + { + datas.length > 0 ? <ul className='hi-transfer__list'> + { + datas.map((item, index) => { + return <li + key={index} + className='hi-transfer__item' + onClick={this.clickItemEvent.bind(this, item, index, dir)} + > + {mode !== 'basic' ? <Checkbox + text={item.content} + value={item.id} + checked={sks.includes(item.id)} + onChange={this.checkboxEvent.bind(this, dir)} + /> : item.content} + </li> + }) + } + </ul> : (dir === 'left' ? emptyContent[0] : emptyContent[1]) + } + </div> + { + mode !== 'basic' && showAllSelect && <div className='hi-transfer__footer'> + <input type='checkbox' /> + <span>已选:2</span> + </div> + } + </div> + } + render () { + const { mode } = this.props + const { sourceList, targetList, sourceSelectedKeys, targetSelectedKeys } = this.state + return ( + <div className='hi-transfer'> + {this.renderEl('left', sourceList)} + <div className='hi-transfer__operation'> + { + mode !== 'basic' ? ( + <React.Fragment> + <Button + type={sourceSelectedKeys.length === 0 ? 'default' : 'primary'} + icon='right' + onClick={this.moveTo.bind(this, 'left')} + disabled={sourceSelectedKeys.length === 0} + /> + <span className='hi-transfer__split' /> + <Button + type={targetSelectedKeys.length === 0 ? 'default' : 'primary'} + icon='left' + onClick={this.moveTo.bind(this, 'right')} + disabled={targetSelectedKeys.length === 0} + /> + </React.Fragment> + ) : ( + <Button type='default' icon='right' /> + ) + } + </div> + {this.renderEl('right', targetList)} + </div> + ) + } +} +Transfer.defaultProps = { + mode: 'basic', + targetKeys: [], + showAllSelect: false +} +Transfer.propTypes = { + mode: PropTypes.oneOf(['basic', 'multiple']), + showAllSelect: PropTypes.bool +} +export default Transfer diff --git a/components/transfer/style/index.js b/components/transfer/style/index.js new file mode 100644 index 000000000..63810a681 --- /dev/null +++ b/components/transfer/style/index.js @@ -0,0 +1 @@ +import './index.scss' diff --git a/components/transfer/style/index.scss b/components/transfer/style/index.scss new file mode 100755 index 000000000..771885757 --- /dev/null +++ b/components/transfer/style/index.scss @@ -0,0 +1,89 @@ +@import '../../style/index.scss'; + +.hi-transfer { + display: flex; + + &__container { + width: 240px; + box-sizing: border-box; + background: rgba(255, 255, 255, 1); + border-radius: 2px; + border: 1px solid rgba(231, 231, 231, 1); + } + + &__title { + height: 40px; + background: rgba(246, 246, 246, 1); + line-height: 40px; + padding-left: 15px; + } + + &__operation { + width: 56px; + display: flex; + align-items: center; + flex-wrap: wrap; + align-content: center; + justify-content: center; + + .hi-btn { + margin: 0; + } + } + + &__split { + width: 100%; + height: 8px; + } + + &__body { + height: 222px; + overflow: auto; + border-top: 1px solid rgb(231, 231, 231); + // border-bottom: 1px solid rgb(231, 231, 231); + box-sizing: border-box; + + &--empty { + text-align: center; + line-height: 222px; + color: rgba(153, 153, 153, 1); + } + } + + ul { + margin: 0; + padding: 0; + list-style: none; + } + + &__list { + + } + + &__item { + font-size: 14px; + height: 36px; + line-height: 36px; + padding-left: 15px; + cursor: pointer; + user-select: none; + + &:hover { + background: rgba(66, 132, 245, 0.1); + } + } + + &__footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 15px; + height: 40px; + border-top: 1px solid #e7e7e7; + box-sizing: border-box; + } + + .hi-checkbox__input { + box-sizing: border-box; + } +} diff --git a/docs/en-US/components/transfer.md b/docs/en-US/components/transfer.md new file mode 100755 index 000000000..3f136f8bd --- /dev/null +++ b/docs/en-US/components/transfer.md @@ -0,0 +1,41 @@ +## Transfer + + +### 基础用法 + +:::demo + +基础用法 + +```js +render () { + return ( + <div> + <Transfer /> + + </div> + ) +} +``` +::: + + + +### Alert Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| type | 类型 | String | info \| success \| error \| warning | info | +| message | 提示内容 | String | - | - | +| title | 提示标题 | String | - | - | +| size | 弹框大小类型 | String | small \| middle \| large | middle | +| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | +| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | +| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | + + +### Alert Events + +| 参数 | 说明 | 回调参数 +| ------- | ------- | ------- | +| onClose | 关闭时触发的事件 | - | diff --git a/docs/zh-CN/components/transfer.md b/docs/zh-CN/components/transfer.md new file mode 100755 index 000000000..91f11073a --- /dev/null +++ b/docs/zh-CN/components/transfer.md @@ -0,0 +1,91 @@ +## Transfer + + +### 基础用法 + +:::demo + +基础用法 + +```js +constructor () { + super() + this.state = { + datas1: this.randomDatas(), + datas2: this.randomDatas(), + datas3: this.randomDatas(), + targetKeys1: [2, 3], + targetKeys2: [], + targetKeys3: [] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: '选项'+i + }) + } + return arr +} +onChange (d, movedKeys) { + console.log(d) + this.setState({ + [d]: movedKeys + }) +} +render () { + return ( + <div> + 普通: + <Transfer + mode='basic' + targetKeys={this.state.targetKeys1} + data={this.state.datas1} + onChange={this.onChange.bind(this, 'targetKeys1')} + /> + <br/> + 无全选: + <Transfer + mode='multiple' + targetKeys={this.state.targetKeys2} + data={this.state.datas2} + onChange={this.onChange.bind(this, 'targetKeys2')} + /> + <br/> + 显示全选: + <Transfer + mode='multiple' + showAllSelect + targetKeys={this.state.targetKeys3} + data={this.state.datas3} + onChange={this.onChange.bind(this, 'targetKeys3')} + /> + + </div> + ) +} +``` +::: + + + +### Alert Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| type | 类型 | String | info \| success \| error \| warning | info | +| message | 提示内容 | String | - | - | +| title | 提示标题 | String | - | - | +| size | 弹框大小类型 | String | small \| middle \| large | middle | +| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | +| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | +| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | + + +### Alert Events + +| 参数 | 说明 | 回调参数 +| ------- | ------- | ------- | +| onClose | 关闭时触发的事件 | - | diff --git a/site/locales/en-US.js b/site/locales/en-US.js index 5458f5b69..9db2cee13 100644 --- a/site/locales/en-US.js +++ b/site/locales/en-US.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper', icon: 'Icon', progress: 'Progress', - card: 'Card' + card: 'Card', + transfer: 'Transfer' }, designs: { 'design-patterns': '设计模式', diff --git a/site/locales/zh-CN.js b/site/locales/zh-CN.js index becbb4d6a..262bec4f7 100755 --- a/site/locales/zh-CN.js +++ b/site/locales/zh-CN.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper 步骤', icon: 'Icon 图标', progress: 'Progress 进度条', - card: 'Card 卡片' + card: 'Card 卡片', + transfer: 'Transfer 穿梭框' }, designs: { 'design-patterns': '设计模式', diff --git a/site/pages/components/index.js b/site/pages/components/index.js index 94bec3c08..7b3238f52 100755 --- a/site/pages/components/index.js +++ b/site/pages/components/index.js @@ -43,7 +43,8 @@ export default { 'tooltip': require('./tooltip'), 'popover': require('./popover'), 'progress': require('./progress'), - 'card': require('./card') + 'card': require('./card'), + 'transfer': require('./transfer') }, 'group-tips': { 'modal': require('./modal'), diff --git a/site/pages/components/transfer/index.js b/site/pages/components/transfer/index.js new file mode 100755 index 000000000..414284966 --- /dev/null +++ b/site/pages/components/transfer/index.js @@ -0,0 +1,9 @@ +import Markdown from '../../../../libs/markdown' + +class Transfer extends Markdown { + document (locale) { + return require(`../../../../docs/${locale}/components/transfer.md`) + } +} + +export default Transfer From faeaa29af356719d6a6fbc1eb75dcb2525961913 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 9 May 2019 16:12:06 +0800 Subject: [PATCH 062/112] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=9F=BA=E7=A1=80/?= =?UTF-8?q?=E6=89=B9=E9=87=8F/=E6=95=B0=E9=87=8F=E9=99=90=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/checkbox/Base.js | 11 +- components/transfer/img/switch.png | Bin 0 -> 355 bytes components/transfer/img/warning.png | Bin 0 -> 460 bytes components/transfer/index.js | 157 ++++++++++++++++++--------- components/transfer/style/index.scss | 75 ++++++++++++- docs/zh-CN/components/transfer.md | 39 ++++++- 6 files changed, 222 insertions(+), 60 deletions(-) create mode 100644 components/transfer/img/switch.png create mode 100644 components/transfer/img/warning.png diff --git a/components/checkbox/Base.js b/components/checkbox/Base.js index 7a38ec48d..85d8ceba4 100644 --- a/components/checkbox/Base.js +++ b/components/checkbox/Base.js @@ -21,12 +21,10 @@ class Base extends Component { } componentWillReceiveProps (nextProps) { if ('checked' in nextProps) { - if (nextProps.checked !== this.props.checked) { - this.setState({ - checked: nextProps.checked, - disabled: nextProps.disabled - }) - } + this.setState({ + checked: nextProps.checked, + disabled: nextProps.disabled + }) } } handleChange (data) { @@ -88,7 +86,6 @@ class Base extends Component { const {disabled, checked, part} = this.state const {value, name, all, content} = this.props - const labelClass = classnames( 'hi-checkbox', disabled && 'hi-checkbox--disabled', diff --git a/components/transfer/img/switch.png b/components/transfer/img/switch.png new file mode 100644 index 0000000000000000000000000000000000000000..216ef75cdabfe89e1baa231d8ecb89f8819e254e GIT binary patch literal 355 zcmV-p0i6DcP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80003hNkl<ZSV!&F zu}*|A7zglvg}C_uEJlZm3ol@FffiOSI^D(iaK`AQkri54G`;}SEgBadz|BBE)5Ot( za@Wfxrp#^sKmN@Z0J7%?_MA@~z%}sy8p!kfFw3$p)9JF>K+`k_ZQFiQN^clrg$cqc zKnDsT&Jgh#0IoUb<ru;`KnE5@aSi}ap64y&IF>_*od9%TUDp>~*F9277mTrL9bynr zmgR~NG8?OL@C^YzIOiuD0k#P?-Mh0ZZ~)hUl?H?m?}&J8df1wEt0YNIHyT4JWvrBX z@O}R(ilUb-A%AWk9mA@sSl{>eh<KT%>8owTDB$-4lOW<90L(e(Z#EG|039f$OqEi% zK@c2;VfbweVGPiLQ-3y0kdpu=2Ton%0ImVozz?-ERUs;y@B#n;002ovPDHLkV1m%q Bk8l70 literal 0 HcmV?d00001 diff --git a/components/transfer/img/warning.png b/components/transfer/img/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..1d9dddc45c94d8373a74e9f03e318bb0997d2869 GIT binary patch literal 460 zcmV;-0W<!IP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0004#Nkl<ZI8UXM zPbfrD6vn^v%mQ;KtdyF$p+sb3C0SV5*hw=><&8f@mSTq;*`hFStZYb@78VvtHa1F0 zp*u=hO`1C>Gw<lV%rs`+3%R@7Ip=r3^PPJL|2*VZ&j2Y)>=98uVrE7OV6R+XfJ%rc z89e6{u}y1oTp#=zaJqy*b68vx@r(;#+IV=>odZkAl|wugsHB(#BP!#1&?!)sI04~6 zntJ<3z~eq@I3sQWdzfML8U$vxF973+Lb@#+!&@n&LV?;1&4al|nmVQ|vFIerxL#IC zu@9n4IhI-V1*pxs22|&7ayja6oy^>p0`9g$ZGixv;k~L|PQRoJkZ%f9GPnY!1{v2= zU%eL<eBHM|oR(&Gp#qSyYMU6w6~XR~#9yDh0A4vo1<Dc?bY+QI$HcIy*9$L}t8@W= z{oU^b8=ZK|ME1D|DD%k$xDMNVDaW(l98M+0p%agodZg5Ol?cwmAa_!fN{B8Hj=g5= zrN99d{t6j9L{B@S<HNu?fcifzCLzsv^!YB+Z(*PPy1jeTE#WBu0000<MNUMnLSTZ> CO~w}h literal 0 HcmV?d00001 diff --git a/components/transfer/index.js b/components/transfer/index.js index 0abb5c15f..429527ee6 100755 --- a/components/transfer/index.js +++ b/components/transfer/index.js @@ -2,6 +2,9 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import Button from '../button' import Checkbox from '../checkbox' +import Icon from '../icon' +import Input from '../input' +import classNames from 'classnames' import './style/index' class Transfer extends Component { @@ -12,7 +15,10 @@ class Transfer extends Component { targetList: [], sourceSelectedKeys: [], targetSelectedKeys: [], - emptyContent: ['暂无数据', '暂无数据'] + emptyContent: ['暂无数据', '暂无数据'], + leftFilter: '', + rightFilter: '', + limited: false } } componentDidMount () { @@ -40,74 +46,117 @@ class Transfer extends Component { this.parseDatas(props) } - getListBydir (dir) { + getSelectedKeysByDir (dir) { return dir === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys' } clickItemEvent (item, index, dir) { - const { mode, targetKeys } = this.props - const { sourceSelectedKeys, targetSelectedKeys } = this.state - const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] - const selectedIndex = selectedItem.indexOf(item.id) - + const { mode } = this.props if (mode === 'basic') { - if (selectedIndex > -1) { - selectedItem.splice(selectedIndex, 1) - } else { - selectedItem.push(item.id) - } - const newTargetKeys = dir === 'left' ? selectedItem.concat(targetKeys) : targetKeys.filter(tk => selectedItem.indexOf(tk) === -1) - this.setState({ - [this.getListBydir(dir)]: newTargetKeys - }, () => { - this.props.onChange(newTargetKeys) - this.setState({ - [this.getListBydir(dir)]: [] - }) + this.parseSelectedKeys(dir, item.id, () => { + this.moveTo(dir) }) } } - checkboxEvent (dir, value, isChecked) { + parseSelectedKeys (dir, key, callback) { const { sourceSelectedKeys, targetSelectedKeys } = this.state const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] - const selectedIndex = selectedItem.indexOf(value) + const selectedIndex = selectedItem.indexOf(key) if (selectedIndex > -1) { selectedItem.splice(selectedIndex, 1) } else { - selectedItem.push(value) + selectedItem.push(key) } + console.log(selectedItem) this.setState({ - [dir === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys']: selectedItem + [this.getSelectedKeysByDir(dir)]: selectedItem + }, () => { + callback && callback() + this.isLimited(dir) }) } + checkboxEvent (dir, value, isChecked) { + this.parseSelectedKeys(dir, value, null) + } moveTo (dir) { const { targetKeys } = this.props const { sourceSelectedKeys, targetSelectedKeys } = this.state const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] const newTargetKeys = dir === 'left' ? selectedItem.concat(targetKeys) : targetKeys.filter(tk => selectedItem.indexOf(tk) === -1) this.setState({ - [this.getListBydir(dir)]: newTargetKeys + [this.getSelectedKeysByDir(dir)]: newTargetKeys }, () => { this.props.onChange(newTargetKeys) - console.log(this.state.sourceList) this.setState({ - [this.getListBydir(dir)]: [] + [this.getSelectedKeysByDir(dir)]: [], + [dir + 'Filter']: '', + limited: false }) }) } - - renderEl (dir, datas) { - const { mode, showAllSelect } = this.props - const { emptyContent, sourceSelectedKeys, targetSelectedKeys } = this.state - const sks = dir === 'left' ? sourceSelectedKeys : targetSelectedKeys + allCheckboxEvent (dir, value, isChecked) { + const { sourceList, targetList, leftFilter, rightFilter } = this.state + const arr = [] + const originDatas = dir === 'left' ? sourceList : targetList + const filterText = dir === 'left' ? leftFilter : rightFilter + if (isChecked) { + originDatas.forEach(data => { + data.content.includes(filterText) && arr.push(data.id) + }) + } + this.setState({ + [this.getSelectedKeysByDir(dir)]: arr + }, () => { + this.isLimited(dir) + }) + } + isLimited (dir) { + const { targetList, sourceSelectedKeys } = this.state + const { targetLimit } = this.props + this.setState({ + limited: sourceSelectedKeys.length > targetLimit || (sourceSelectedKeys.length + targetList.length > targetLimit) + }) + } + searchEvent (dir, e) { + this.setState({ + [dir + 'Filter']: e.target.value + }) + } + renderContainer (dir, datas) { + const { mode, showAllSelect, searchable } = this.props + const { emptyContent, sourceSelectedKeys, targetSelectedKeys, leftFilter, rightFilter, limited } = this.state + const selectedKeys = dir === 'left' ? sourceSelectedKeys : targetSelectedKeys + const filterText = dir === 'left' ? leftFilter : rightFilter + const filterResult = datas.filter(item => item.content.includes(filterText)) + const footerCls = classNames( + 'hi-transfer__footer', + selectedKeys.length !== filterResult.length && selectedKeys.length !== 0 && 'hi-transfer__footer--checkbox-part' + ) return <div className='hi-transfer__container'> <div className='hi-transfer__title'> 标题 </div> + { + searchable && <div className='hi-transfer__searchbar'> + <Icon name='search' /> + <Input placeholder='搜索' onChange={this.searchEvent.bind(this, dir)} value={filterText} /> + </div> + } <div className={`hi-transfer__body ${datas.length === 0 ? 'hi-transfer__body--empty' : ''}`}> { - datas.length > 0 ? <ul className='hi-transfer__list'> + filterResult.length > 0 ? <ul className='hi-transfer__list'> + { + dir === 'left' && limited && ( + <li + key='limit-tips' + className='hi-transfer__item hi-transfer__item--limit' + > + <div className='hi-transfer__warning' /> + <span>数量达上限,无法添加</span> + </li> + ) + } { - datas.map((item, index) => { + filterResult.map((item, index) => { return <li key={index} className='hi-transfer__item' @@ -116,7 +165,7 @@ class Transfer extends Component { {mode !== 'basic' ? <Checkbox text={item.content} value={item.id} - checked={sks.includes(item.id)} + checked={selectedKeys.includes(item.id)} onChange={this.checkboxEvent.bind(this, dir)} /> : item.content} </li> @@ -126,28 +175,36 @@ class Transfer extends Component { } </div> { - mode !== 'basic' && showAllSelect && <div className='hi-transfer__footer'> - <input type='checkbox' /> - <span>已选:2</span> + mode !== 'basic' && showAllSelect && <div className={footerCls}> + <Checkbox + text='全选' + checked={(selectedKeys.length !== 0 && selectedKeys.length === filterResult.length && filterResult.length !== 0)} + onChange={this.allCheckboxEvent.bind(this, dir)} + /> + <span>已选:{selectedKeys.length}</span> </div> } </div> } render () { const { mode } = this.props - const { sourceList, targetList, sourceSelectedKeys, targetSelectedKeys } = this.state + const { sourceList, targetList, sourceSelectedKeys, targetSelectedKeys, limited } = this.state + const operCls = classNames( + 'hi-transfer__operation', + mode === 'basic' && 'hi-transfer__operation--basic' + ) return ( <div className='hi-transfer'> - {this.renderEl('left', sourceList)} - <div className='hi-transfer__operation'> + {this.renderContainer('left', sourceList)} + <div className={operCls}> { - mode !== 'basic' ? ( + mode !== 'basic' && <React.Fragment> <Button - type={sourceSelectedKeys.length === 0 ? 'default' : 'primary'} + type={(sourceSelectedKeys.length === 0 || limited) ? 'default' : 'primary'} icon='right' onClick={this.moveTo.bind(this, 'left')} - disabled={sourceSelectedKeys.length === 0} + disabled={(sourceSelectedKeys.length === 0 || limited)} /> <span className='hi-transfer__split' /> <Button @@ -157,12 +214,9 @@ class Transfer extends Component { disabled={targetSelectedKeys.length === 0} /> </React.Fragment> - ) : ( - <Button type='default' icon='right' /> - ) } </div> - {this.renderEl('right', targetList)} + {this.renderContainer('right', targetList)} </div> ) } @@ -170,10 +224,13 @@ class Transfer extends Component { Transfer.defaultProps = { mode: 'basic', targetKeys: [], - showAllSelect: false + showAllSelect: false, + searchable: false } Transfer.propTypes = { mode: PropTypes.oneOf(['basic', 'multiple']), - showAllSelect: PropTypes.bool + showAllSelect: PropTypes.bool, + searchable: PropTypes.bool, + targetLimit: PropTypes.number } export default Transfer diff --git a/components/transfer/style/index.scss b/components/transfer/style/index.scss index 771885757..cb08d9c0a 100755 --- a/components/transfer/style/index.scss +++ b/components/transfer/style/index.scss @@ -9,6 +9,7 @@ background: rgba(255, 255, 255, 1); border-radius: 2px; border: 1px solid rgba(231, 231, 231, 1); + position: relative; } &__title { @@ -16,6 +17,8 @@ background: rgba(246, 246, 246, 1); line-height: 40px; padding-left: 15px; + box-sizing: border-box; + border-bottom: 1px solid #e6e7e8; } &__operation { @@ -26,6 +29,12 @@ align-content: center; justify-content: center; + &--basic { + background: url('../img/switch.png'); + background-repeat: no-repeat; + background-position: center; + } + .hi-btn { margin: 0; } @@ -39,7 +48,7 @@ &__body { height: 222px; overflow: auto; - border-top: 1px solid rgb(231, 231, 231); + // border-top: 1px solid rgb(231, 231, 231); // border-bottom: 1px solid rgb(231, 231, 231); box-sizing: border-box; @@ -71,6 +80,24 @@ &:hover { background: rgba(66, 132, 245, 0.1); } + + &--limit { + height: 40px; + color: #999; + font-size: 14px; + display: flex; + align-items: center; + } + } + + &__warning { + width: 24px; + height: 24px; + display: inline-block; + background: url('../img/warning.png'); + background-repeat: no-repeat; + background-position: left center; + // margin-right: 15px; } &__footer { @@ -81,6 +108,52 @@ height: 40px; border-top: 1px solid #e7e7e7; box-sizing: border-box; + + &--checkbox-part { + .hi-checkbox__input { + background: #4284f5; + + &::after { + content: ''; + position: absolute; + display: block; + border: 1px solid #fff; + margin-top: -1px; + left: 3px; + right: 3px; + top: 50%; + pointer-events: none; + } + } + } + } + + &__searchbar { + height: 40px; + width: 208px; + margin: 0 auto; + border-bottom: 1px solid #e6e7e8; + box-sizing: border-box; + display: flex; + align-items: center; + + .hi-input__inner { + border: none; + } + } + + &__limit-tips { + position: absolute; + background: #4a4a4a; + color: #fff; + font-size: 12px; + padding: 2px 5px; + left: 50%; + transform: translateX(-50%); + top: 40px; + z-index: 2; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; } .hi-checkbox__input { diff --git a/docs/zh-CN/components/transfer.md b/docs/zh-CN/components/transfer.md index 91f11073a..1c092d995 100755 --- a/docs/zh-CN/components/transfer.md +++ b/docs/zh-CN/components/transfer.md @@ -14,9 +14,13 @@ constructor () { datas1: this.randomDatas(), datas2: this.randomDatas(), datas3: this.randomDatas(), + datas4: this.randomDatas(), + datas5: this.randomDatas(), targetKeys1: [2, 3], targetKeys2: [], - targetKeys3: [] + targetKeys3: [], + targetKeys4: [], + targetKeys5: [] } } randomDatas () { @@ -30,7 +34,6 @@ randomDatas () { return arr } onChange (d, movedKeys) { - console.log(d) this.setState({ [d]: movedKeys }) @@ -62,6 +65,38 @@ render () { data={this.state.datas3} onChange={this.onChange.bind(this, 'targetKeys3')} /> + <br/> + 带搜索: + <Transfer + mode='multiple' + showAllSelect + searchable + targetKeys={this.state.targetKeys4} + data={this.state.datas4} + onChange={this.onChange.bind(this, 'targetKeys4')} + /> + <br/> + 带搜索: + <Transfer + mode='multiple' + showAllSelect + searchable + targetKeys={this.state.targetKeys4} + data={this.state.datas4} + onChange={this.onChange.bind(this, 'targetKeys4')} + /> + + <br/> + 目标数量上限: + <Transfer + mode='multiple' + showAllSelect + searchable + targetLimit={5} + targetKeys={this.state.targetKeys5} + data={this.state.datas5} + onChange={this.onChange.bind(this, 'targetKeys5')} + /> </div> ) From 708d9e7acab00dddcd00bf44152f0877233931fb Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Fri, 10 May 2019 14:58:48 +0800 Subject: [PATCH 063/112] =?UTF-8?q?transfer=20=E6=8B=96=E6=8B=BD=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/transfer/Item.js | 104 +++++++++++++++++++++++++++ components/transfer/index.js | 82 +++++++++++++++++---- components/transfer/style/index.scss | 10 +++ docs/zh-CN/components/transfer.md | 15 +++- 4 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 components/transfer/Item.js diff --git a/components/transfer/Item.js b/components/transfer/Item.js new file mode 100644 index 000000000..5e3f4d5a1 --- /dev/null +++ b/components/transfer/Item.js @@ -0,0 +1,104 @@ +import React, {Component} from 'react' +import Checkbox from '../checkbox' +import { DragSource, DropTarget } from 'react-dnd' + +class Item extends Component { + render () { + const { + mode, + item, + onClick, + checkboxOnChange, + checked, + isDragging, + connectDragSource, + connectDropTarget, + targetNode, + sourceNode, + dir + } = this.props + const sourceStyle = (sourceNode === item.id && isDragging) ? { + background: 'rgba(246,246,246,1)', + color: 'rgba(204,204,204,1)' + } : {} + const el = <li + style={sourceStyle} + className='hi-transfer__item' + onClick={onClick.bind(this)} + > + { + targetNode === item.id && <div className='hi-transfer__underline' /> + } + {mode !== 'basic' ? <Checkbox + text={item.content} + value={item.id} + checked={checked} + onChange={checkboxOnChange.bind(this)} + /> : item.content} + </li> + return dir === 'right' ? connectDropTarget( + connectDragSource(el) + ) : el + } +} + +const TYPE = 'CARD' +const source = { + beginDrag (props) { + props.setSourceNode(props.item.id) + return { + sourceItem: props.item + } + }, + + isDragging (props, monitor) { + return props.id === monitor.getItem().id + } +} + +const target = { + canDrop (props, monitor) { + // const { sourceItem } = monitor.getItem() + // const { item: targetItem } = props + return true + }, + + drop (props, monitor, component) { + const { sourceItem } = monitor.getItem() + const { item: targetItem, removeTargetNode, move } = props + move(sourceItem, targetItem) + removeTargetNode() + // const { item } = monitor.getItem() + // const { id: dropId, contains } = props + + // props.move(item, dropId) + }, + hover (props, monitor, component) { + const { item: targetItem, setTargetNode } = props + + setTargetNode(targetItem.id) + } +} +const DragItem = DropTarget(TYPE, target, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget() +}))(DragSource(TYPE, source, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + connectDragPreview: connect.dragPreview(), + isDragging: monitor.isDragging() +}))(Item)) + +const HOCItem = ItemComponent => { + return class WrapperItem extends Component { + render () { + const { dir, draggable } = this.props + + return (draggable && dir === 'right') ? ( + <DragItem {...this.props} /> + ) : ( + <ItemComponent {...this.props} /> + ) + } + } +} +export default HOCItem(Item) +// export default Item diff --git a/components/transfer/index.js b/components/transfer/index.js index 429527ee6..ab2733066 100755 --- a/components/transfer/index.js +++ b/components/transfer/index.js @@ -5,6 +5,9 @@ import Checkbox from '../checkbox' import Icon from '../icon' import Input from '../input' import classNames from 'classnames' +import {DragDropContext} from 'react-dnd' +import HTML5Backend from 'react-dnd-html5-backend' +import Item from './Item' import './style/index' class Transfer extends Component { @@ -18,7 +21,9 @@ class Transfer extends Component { emptyContent: ['暂无数据', '暂无数据'], leftFilter: '', rightFilter: '', - limited: false + limited: false, + targetNode: null, + sourceNode: null } } componentDidMount () { @@ -121,9 +126,40 @@ class Transfer extends Component { [dir + 'Filter']: e.target.value }) } + move (targetItem, sourceItem) { + console.log(targetItem, sourceItem) + const { targetList } = this.state + let tempItem + let fIndex + let tIndex + targetList.forEach((item, index) => { + if (item.id === targetItem.id) { + tempItem = item + fIndex = index + } + if (item.id === sourceItem.id) { + tIndex = index + } + }) + targetList.splice(fIndex, 1) + targetList.splice(tIndex, 0, tempItem) + this.setState({targetList}) + } + setTargetNode (id) { + this.setState({targetNode: id}) + } + removeTargetNode () { + this.setState({ targetNode: null }) + } + setSourceNode (id) { + this.setState({sourceNode: id}) + } + removeSourceNode () { + this.setState({ sourceNode: null }) + } renderContainer (dir, datas) { - const { mode, showAllSelect, searchable } = this.props - const { emptyContent, sourceSelectedKeys, targetSelectedKeys, leftFilter, rightFilter, limited } = this.state + const { mode, showAllSelect, searchable, draggable } = this.props + const { emptyContent, sourceSelectedKeys, targetSelectedKeys, leftFilter, rightFilter, limited, targetNode, sourceNode } = this.state const selectedKeys = dir === 'left' ? sourceSelectedKeys : targetSelectedKeys const filterText = dir === 'left' ? leftFilter : rightFilter const filterResult = datas.filter(item => item.content.includes(filterText)) @@ -157,18 +193,35 @@ class Transfer extends Component { } { filterResult.map((item, index) => { - return <li + // return <li + // key={index} + // className='hi-transfer__item' + // onClick={this.clickItemEvent.bind(this, item, index, dir)} + // > + // {mode !== 'basic' ? <Checkbox + // text={item.content} + // value={item.id} + // checked={selectedKeys.includes(item.id)} + // onChange={this.checkboxEvent.bind(this, dir)} + // /> : item.content} + // </li> + return <Item + dir={dir} + draggable={draggable} key={index} - className='hi-transfer__item' onClick={this.clickItemEvent.bind(this, item, index, dir)} - > - {mode !== 'basic' ? <Checkbox - text={item.content} - value={item.id} - checked={selectedKeys.includes(item.id)} - onChange={this.checkboxEvent.bind(this, dir)} - /> : item.content} - </li> + mode={mode} + item={item} + checked={selectedKeys.includes(item.id)} + checkboxOnChange={this.checkboxEvent.bind(this, dir)} + move={this.move.bind(this)} + setTargetNode={this.setTargetNode.bind(this)} + removeTargetNode={this.removeTargetNode.bind(this)} + targetNode={targetNode} + setSourceNode={this.setSourceNode.bind(this)} + removeSourceNode={this.removeSourceNode.bind(this)} + sourceNode={sourceNode} + /> }) } </ul> : (dir === 'left' ? emptyContent[0] : emptyContent[1]) @@ -186,6 +239,7 @@ class Transfer extends Component { } </div> } + render () { const { mode } = this.props const { sourceList, targetList, sourceSelectedKeys, targetSelectedKeys, limited } = this.state @@ -233,4 +287,4 @@ Transfer.propTypes = { searchable: PropTypes.bool, targetLimit: PropTypes.number } -export default Transfer +export default DragDropContext(HTML5Backend)(Transfer) diff --git a/components/transfer/style/index.scss b/components/transfer/style/index.scss index cb08d9c0a..c46e4b3b6 100755 --- a/components/transfer/style/index.scss +++ b/components/transfer/style/index.scss @@ -76,6 +76,7 @@ padding-left: 15px; cursor: pointer; user-select: none; + position: relative; &:hover { background: rgba(66, 132, 245, 0.1); @@ -156,6 +157,15 @@ border-bottom-right-radius: 2px; } + &__underline { + position: absolute; + width: 100%; + height: 1px; + background-color: rgba(66, 132, 245, 1); + left: 0; + top: 36px; + } + .hi-checkbox__input { box-sizing: border-box; } diff --git a/docs/zh-CN/components/transfer.md b/docs/zh-CN/components/transfer.md index 1c092d995..212894c9c 100755 --- a/docs/zh-CN/components/transfer.md +++ b/docs/zh-CN/components/transfer.md @@ -16,11 +16,13 @@ constructor () { datas3: this.randomDatas(), datas4: this.randomDatas(), datas5: this.randomDatas(), + datas6: this.randomDatas(), targetKeys1: [2, 3], targetKeys2: [], targetKeys3: [], targetKeys4: [], - targetKeys5: [] + targetKeys5: [], + targetKeys6: [] } } randomDatas () { @@ -97,6 +99,17 @@ render () { data={this.state.datas5} onChange={this.onChange.bind(this, 'targetKeys5')} /> + <br/> + 目标数量上限: + <Transfer + mode='multiple' + showAllSelect + searchable + draggable + targetKeys={this.state.targetKeys6} + data={this.state.datas6} + onChange={this.onChange.bind(this, 'targetKeys6')} + /> </div> ) From 072eb6e0f9d71462c60ad07ff12821550319245d Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 13 May 2019 12:12:12 +0800 Subject: [PATCH 064/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8D=A1=E9=A1=BF?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/transfer/Item.js | 73 +++++----- components/transfer/index.js | 255 +++++++++++++++++++++-------------- 2 files changed, 193 insertions(+), 135 deletions(-) diff --git a/components/transfer/Item.js b/components/transfer/Item.js index 5e3f4d5a1..8869be0b2 100644 --- a/components/transfer/Item.js +++ b/components/transfer/Item.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react' +import React, { Component } from 'react' import Checkbox from '../checkbox' import { DragSource, DropTarget } from 'react-dnd' @@ -17,28 +17,29 @@ class Item extends Component { sourceNode, dir } = this.props - const sourceStyle = (sourceNode === item.id && isDragging) ? { - background: 'rgba(246,246,246,1)', - color: 'rgba(204,204,204,1)' - } : {} - const el = <li - style={sourceStyle} - className='hi-transfer__item' - onClick={onClick.bind(this)} - > - { - targetNode === item.id && <div className='hi-transfer__underline' /> - } - {mode !== 'basic' ? <Checkbox - text={item.content} - value={item.id} - checked={checked} - onChange={checkboxOnChange.bind(this)} - /> : item.content} - </li> - return dir === 'right' ? connectDropTarget( - connectDragSource(el) - ) : el + const sourceStyle = + sourceNode === item.id && isDragging + ? { + background: 'rgba(246,246,246,1)', + color: 'rgba(204,204,204,1)' + } + : {} + const el = ( + <li style={sourceStyle} className='hi-transfer__item' onClick={onClick.bind(this)}> + {targetNode === item.id && <div className='hi-transfer__underline' />} + {mode !== 'basic' ? ( + <Checkbox + text={item.content} + value={item.id} + checked={checked} + onChange={checkboxOnChange.bind(this)} + /> + ) : ( + item.content + )} + </li> + ) + return dir === 'right' ? connectDropTarget(connectDragSource(el)) : el } } @@ -74,25 +75,33 @@ const target = { // props.move(item, dropId) }, hover (props, monitor, component) { - const { item: targetItem, setTargetNode } = props - - setTargetNode(targetItem.id) + if (monitor.isOver({ shallow: true })) { + const { item: targetItem, setTargetNode, positionX, positionY, setPosition } = props + const sourcePosition = monitor.getClientOffset() + console.log(positionX, positionY, sourcePosition.x, sourcePosition.y) + if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { + setPosition(sourcePosition.x, sourcePosition.y) + setTargetNode(targetItem.id) + } + } } } const DragItem = DropTarget(TYPE, target, (connect, monitor) => ({ connectDropTarget: connect.dropTarget() -}))(DragSource(TYPE, source, (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - connectDragPreview: connect.dragPreview(), - isDragging: monitor.isDragging() -}))(Item)) +}))( + DragSource(TYPE, source, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + connectDragPreview: connect.dragPreview(), + isDragging: monitor.isDragging() + }))(Item) +) const HOCItem = ItemComponent => { return class WrapperItem extends Component { render () { const { dir, draggable } = this.props - return (draggable && dir === 'right') ? ( + return draggable && dir === 'right' ? ( <DragItem {...this.props} /> ) : ( <ItemComponent {...this.props} /> diff --git a/components/transfer/index.js b/components/transfer/index.js index ab2733066..11927c8b6 100755 --- a/components/transfer/index.js +++ b/components/transfer/index.js @@ -5,7 +5,7 @@ import Checkbox from '../checkbox' import Icon from '../icon' import Input from '../input' import classNames from 'classnames' -import {DragDropContext} from 'react-dnd' +import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' import Item from './Item' import './style/index' @@ -23,12 +23,23 @@ class Transfer extends Component { rightFilter: '', limited: false, targetNode: null, - sourceNode: null + sourceNode: null, + positionX: null, + positionY: null } } componentDidMount () { this.parseDatas(this.props) } + setPosition = (x, y) => { + const { positionX, positionY } = this.state + if (!(x === positionX && y === positionY)) { + this.setState({ + positionX: x, + positionY: y + }) + } + } parseDatas (props) { const { data, targetKeys } = props const sourceList = [] @@ -72,12 +83,15 @@ class Transfer extends Component { selectedItem.push(key) } console.log(selectedItem) - this.setState({ - [this.getSelectedKeysByDir(dir)]: selectedItem - }, () => { - callback && callback() - this.isLimited(dir) - }) + this.setState( + { + [this.getSelectedKeysByDir(dir)]: selectedItem + }, + () => { + callback && callback() + this.isLimited(dir) + } + ) } checkboxEvent (dir, value, isChecked) { this.parseSelectedKeys(dir, value, null) @@ -86,17 +100,23 @@ class Transfer extends Component { const { targetKeys } = this.props const { sourceSelectedKeys, targetSelectedKeys } = this.state const selectedItem = dir === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys] - const newTargetKeys = dir === 'left' ? selectedItem.concat(targetKeys) : targetKeys.filter(tk => selectedItem.indexOf(tk) === -1) - this.setState({ - [this.getSelectedKeysByDir(dir)]: newTargetKeys - }, () => { - this.props.onChange(newTargetKeys) - this.setState({ - [this.getSelectedKeysByDir(dir)]: [], - [dir + 'Filter']: '', - limited: false - }) - }) + const newTargetKeys = + dir === 'left' + ? selectedItem.concat(targetKeys) + : targetKeys.filter(tk => selectedItem.indexOf(tk) === -1) + this.setState( + { + [this.getSelectedKeysByDir(dir)]: newTargetKeys + }, + () => { + this.props.onChange(newTargetKeys) + this.setState({ + [this.getSelectedKeysByDir(dir)]: [], + [dir + 'Filter']: '', + limited: false + }) + } + ) } allCheckboxEvent (dir, value, isChecked) { const { sourceList, targetList, leftFilter, rightFilter } = this.state @@ -108,17 +128,22 @@ class Transfer extends Component { data.content.includes(filterText) && arr.push(data.id) }) } - this.setState({ - [this.getSelectedKeysByDir(dir)]: arr - }, () => { - this.isLimited(dir) - }) + this.setState( + { + [this.getSelectedKeysByDir(dir)]: arr + }, + () => { + this.isLimited(dir) + } + ) } isLimited (dir) { const { targetList, sourceSelectedKeys } = this.state const { targetLimit } = this.props this.setState({ - limited: sourceSelectedKeys.length > targetLimit || (sourceSelectedKeys.length + targetList.length > targetLimit) + limited: + sourceSelectedKeys.length > targetLimit || + sourceSelectedKeys.length + targetList.length > targetLimit }) } searchEvent (dir, e) { @@ -143,56 +168,68 @@ class Transfer extends Component { }) targetList.splice(fIndex, 1) targetList.splice(tIndex, 0, tempItem) - this.setState({targetList}) + this.setState({ targetList }) } setTargetNode (id) { - this.setState({targetNode: id}) + this.setState({ targetNode: id }) } removeTargetNode () { this.setState({ targetNode: null }) } setSourceNode (id) { - this.setState({sourceNode: id}) + this.setState({ sourceNode: id }) } removeSourceNode () { this.setState({ sourceNode: null }) } renderContainer (dir, datas) { const { mode, showAllSelect, searchable, draggable } = this.props - const { emptyContent, sourceSelectedKeys, targetSelectedKeys, leftFilter, rightFilter, limited, targetNode, sourceNode } = this.state + const { + emptyContent, + sourceSelectedKeys, + targetSelectedKeys, + leftFilter, + rightFilter, + limited, + targetNode, + sourceNode, + positionX, + positionY + } = this.state const selectedKeys = dir === 'left' ? sourceSelectedKeys : targetSelectedKeys const filterText = dir === 'left' ? leftFilter : rightFilter const filterResult = datas.filter(item => item.content.includes(filterText)) const footerCls = classNames( 'hi-transfer__footer', - selectedKeys.length !== filterResult.length && selectedKeys.length !== 0 && 'hi-transfer__footer--checkbox-part' + selectedKeys.length !== filterResult.length && + selectedKeys.length !== 0 && + 'hi-transfer__footer--checkbox-part' ) - return <div className='hi-transfer__container'> - <div className='hi-transfer__title'> - 标题 - </div> - { - searchable && <div className='hi-transfer__searchbar'> - <Icon name='search' /> - <Input placeholder='搜索' onChange={this.searchEvent.bind(this, dir)} value={filterText} /> - </div> - } - <div className={`hi-transfer__body ${datas.length === 0 ? 'hi-transfer__body--empty' : ''}`}> - { - filterResult.length > 0 ? <ul className='hi-transfer__list'> - { - dir === 'left' && limited && ( - <li - key='limit-tips' - className='hi-transfer__item hi-transfer__item--limit' - > + return ( + <div className='hi-transfer__container'> + <div className='hi-transfer__title'>标题</div> + {searchable && ( + <div className='hi-transfer__searchbar'> + <Icon name='search' /> + <Input + placeholder='搜索' + onChange={this.searchEvent.bind(this, dir)} + value={filterText} + /> + </div> + )} + <div + className={`hi-transfer__body ${datas.length === 0 ? 'hi-transfer__body--empty' : ''}`} + > + {filterResult.length > 0 ? ( + <ul className='hi-transfer__list'> + {dir === 'left' && limited && ( + <li key='limit-tips' className='hi-transfer__item hi-transfer__item--limit'> <div className='hi-transfer__warning' /> <span>数量达上限,无法添加</span> </li> - ) - } - { - filterResult.map((item, index) => { + )} + {filterResult.map((item, index) => { // return <li // key={index} // className='hi-transfer__item' @@ -205,39 +242,52 @@ class Transfer extends Component { // onChange={this.checkboxEvent.bind(this, dir)} // /> : item.content} // </li> - return <Item - dir={dir} - draggable={draggable} - key={index} - onClick={this.clickItemEvent.bind(this, item, index, dir)} - mode={mode} - item={item} - checked={selectedKeys.includes(item.id)} - checkboxOnChange={this.checkboxEvent.bind(this, dir)} - move={this.move.bind(this)} - setTargetNode={this.setTargetNode.bind(this)} - removeTargetNode={this.removeTargetNode.bind(this)} - targetNode={targetNode} - setSourceNode={this.setSourceNode.bind(this)} - removeSourceNode={this.removeSourceNode.bind(this)} - sourceNode={sourceNode} - /> - }) - } - </ul> : (dir === 'left' ? emptyContent[0] : emptyContent[1]) - } - </div> - { - mode !== 'basic' && showAllSelect && <div className={footerCls}> - <Checkbox - text='全选' - checked={(selectedKeys.length !== 0 && selectedKeys.length === filterResult.length && filterResult.length !== 0)} - onChange={this.allCheckboxEvent.bind(this, dir)} - /> - <span>已选:{selectedKeys.length}</span> + return ( + <Item + dir={dir} + draggable={draggable} + key={index} + onClick={this.clickItemEvent.bind(this, item, index, dir)} + mode={mode} + item={item} + checked={selectedKeys.includes(item.id)} + checkboxOnChange={this.checkboxEvent.bind(this, dir)} + move={this.move.bind(this)} + setTargetNode={this.setTargetNode.bind(this)} + removeTargetNode={this.removeTargetNode.bind(this)} + targetNode={targetNode} + setSourceNode={this.setSourceNode.bind(this)} + removeSourceNode={this.removeSourceNode.bind(this)} + sourceNode={sourceNode} + setPosition={this.setPosition} + positionX={positionX} + positionY={positionY} + /> + ) + })} + </ul> + ) : dir === 'left' ? ( + emptyContent[0] + ) : ( + emptyContent[1] + )} </div> - } - </div> + {mode !== 'basic' && showAllSelect && ( + <div className={footerCls}> + <Checkbox + text='全选' + checked={ + selectedKeys.length !== 0 && + selectedKeys.length === filterResult.length && + filterResult.length !== 0 + } + onChange={this.allCheckboxEvent.bind(this, dir)} + /> + <span>已选:{selectedKeys.length}</span> + </div> + )} + </div> + ) } render () { @@ -251,24 +301,23 @@ class Transfer extends Component { <div className='hi-transfer'> {this.renderContainer('left', sourceList)} <div className={operCls}> - { - mode !== 'basic' && - <React.Fragment> - <Button - type={(sourceSelectedKeys.length === 0 || limited) ? 'default' : 'primary'} - icon='right' - onClick={this.moveTo.bind(this, 'left')} - disabled={(sourceSelectedKeys.length === 0 || limited)} - /> - <span className='hi-transfer__split' /> - <Button - type={targetSelectedKeys.length === 0 ? 'default' : 'primary'} - icon='left' - onClick={this.moveTo.bind(this, 'right')} - disabled={targetSelectedKeys.length === 0} - /> - </React.Fragment> - } + {mode !== 'basic' && ( + <React.Fragment> + <Button + type={sourceSelectedKeys.length === 0 || limited ? 'default' : 'primary'} + icon='right' + onClick={this.moveTo.bind(this, 'left')} + disabled={sourceSelectedKeys.length === 0 || limited} + /> + <span className='hi-transfer__split' /> + <Button + type={targetSelectedKeys.length === 0 ? 'default' : 'primary'} + icon='left' + onClick={this.moveTo.bind(this, 'right')} + disabled={targetSelectedKeys.length === 0} + /> + </React.Fragment> + )} </div> {this.renderContainer('right', targetList)} </div> From a3e76239901e8035d8e2adda7cc973b23d77c5e8 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Mon, 13 May 2019 13:51:34 +0800 Subject: [PATCH 065/112] drag --- components/transfer/Item.js | 5 +++-- components/transfer/index.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/transfer/Item.js b/components/transfer/Item.js index 8869be0b2..a489a067f 100644 --- a/components/transfer/Item.js +++ b/components/transfer/Item.js @@ -15,7 +15,8 @@ class Item extends Component { connectDropTarget, targetNode, sourceNode, - dir + dir, + draggable } = this.props const sourceStyle = sourceNode === item.id && isDragging @@ -39,7 +40,7 @@ class Item extends Component { )} </li> ) - return dir === 'right' ? connectDropTarget(connectDragSource(el)) : el + return (dir === 'right' && draggable) ? connectDropTarget(connectDragSource(el)) : el } } diff --git a/components/transfer/index.js b/components/transfer/index.js index 11927c8b6..dff2a2e18 100755 --- a/components/transfer/index.js +++ b/components/transfer/index.js @@ -152,7 +152,6 @@ class Transfer extends Component { }) } move (targetItem, sourceItem) { - console.log(targetItem, sourceItem) const { targetList } = this.state let tempItem let fIndex From 98e498df254236e6c3c49830f7c1101f0be71445 Mon Sep 17 00:00:00 2001 From: zhan8863 <zhan8863@126.com> Date: Mon, 13 May 2019 14:37:29 +0800 Subject: [PATCH 066/112] Hotfix/menu (#196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Menu 修改 key 问题 * remove console * version * modify key value --- components/input/index.js | 1 - components/menu/Item.js | 8 ++++---- components/menu/SubMenu.js | 3 ++- components/menu/index.js | 9 +++++---- package.json | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/components/input/index.js b/components/input/index.js index bbaaa3774..72053699c 100644 --- a/components/input/index.js +++ b/components/input/index.js @@ -88,7 +88,6 @@ class Input extends Component { {...this.attrs} placeholder={placeholder} onChange={e => { - console.log('change') e.persist() let value = e.target.value let valueTrue = formatValue(value, type) diff --git a/components/menu/Item.js b/components/menu/Item.js index f8e02c0e6..c9746047e 100644 --- a/components/menu/Item.js +++ b/components/menu/Item.js @@ -30,19 +30,19 @@ class Item extends Component { activeIndex, id, icon, - index + index, + data } = this.props const isActive = activeIndex.indexOf(index) === 0 const cls = classNames('hi-menu-item', 'hi-menu-item__title', 'hi-menu__title', `hi-menu--${level}`, { 'hi-menu-item--disabled': disabled, 'hi-menu-item--active': isActive }) - return ( - <li className={cls} onClick={e => { + <li className={cls} key={index} onClick={e => { e.stopPropagation() if (!disabled) { - onClick(index, id) + onClick(index, id, data) } }}> <Title icon={icon} content={children} /> diff --git a/components/menu/SubMenu.js b/components/menu/SubMenu.js index f63c7682b..96a010bd4 100644 --- a/components/menu/SubMenu.js +++ b/components/menu/SubMenu.js @@ -111,11 +111,11 @@ export default class SubMenu extends Component { renderMenu, clickInside } = this.props - return ( <ul className={classNames('hi-submenu__items', {'hi-submenu__items--hide': !isExpand})} onClick={() => clickInside()} // 利用事件冒泡设置clickInsideFlag + key={index} > { renderMenu(datas, index) } </ul> @@ -154,6 +154,7 @@ export default class SubMenu extends Component { <li className={cls} ref={node => { this.submenuTrigger = node }} + key={index} > <div className='hi-submenu__title hi-menu__title' diff --git a/components/menu/index.js b/components/menu/index.js index cb8009450..2fb5c8f6a 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -185,7 +185,7 @@ class Menu extends Component { }) } - onClick (indexs, id) { + onClick (indexs, id, data) { const expandIndex = this.isNoMiniVertaicalMenu() ? this.state.expandIndex : this.getExpandIndex('') // 非mini垂直菜单选中时不需要收起子菜单 const oldId = this.state.activeId @@ -194,7 +194,7 @@ class Menu extends Component { activeIndex: indexs, expandIndex }, () => { - this.props.onClick(id, oldId) + this.props.onClick(id, oldId, data) }) } @@ -220,7 +220,8 @@ class Menu extends Component { activeIndex, index: index, disabled: data.disabled, - key: data.id + key: index, + data }, props) return ( @@ -269,6 +270,7 @@ class Menu extends Component { if (data.children) { items.push( <SubMenu + key={index} onClick={this.onClickSubMenu.bind(this)} clickInside={this.clickInside.bind(this)} index={indexStr} @@ -283,7 +285,6 @@ class Menu extends Component { datas={data.children} mode={mode} mini={mini} - key={data.content} /> ) } else { diff --git a/package.json b/package.json index 8b3d9ca32..88f7a87ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hi-ui/hiui", - "version": "1.5.0-rc.2", + "version": "1.5.0-rc.3", "description": "HIUI for React", "scripts": { "test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'", From c85b95380be472d882855368e00332fb4b63e6e3 Mon Sep 17 00:00:00 2001 From: duanchuanxu <duanchuanxu@xiaomi.com> Date: Tue, 14 May 2019 08:37:32 +0800 Subject: [PATCH 067/112] fix preview image --- components/upload/Preview.js | 27 +++++++++++++++------------ components/upload/style/preview.scss | 6 +++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/components/upload/Preview.js b/components/upload/Preview.js index b92cde3ec..919c10b9a 100644 --- a/components/upload/Preview.js +++ b/components/upload/Preview.js @@ -9,8 +9,10 @@ export default class Preview extends Component { super(props) this.state = { extraClass: '', - style: {} + style: {}, + imgLoaded: false } + this.imgRef = React.createRef() } static propTypes = { @@ -25,16 +27,17 @@ export default class Preview extends Component { onClose () { this.setState({ - // style: {}, - extraClass: '' + style: {}, + extraClass: '', + imgLoaded: false }) this.props.onClose && this.props.onClose() } imgOnLoad () { const radio = 0.6 - const imgWidth = this.imgRef.clientWidth - const imgHeight = this.imgRef.clientHeight + const imgWidth = this.imgRef.current.clientWidth + const imgHeight = this.imgRef.current.clientHeight const windowRadio = window.innerWidth / window.innerHeight const imgRadio = imgWidth / imgHeight if (isNaN(imgRadio)) { @@ -56,7 +59,8 @@ export default class Preview extends Component { this.setState({ extraClass, - style + style, + imgLoaded: true }) } @@ -64,7 +68,8 @@ export default class Preview extends Component { const { show, src } = this.props const { extraClass, - style + style, + imgLoaded } = this.state return ( @@ -74,12 +79,10 @@ export default class Preview extends Component { transitionLeaveTimeout={50} component='div' > - <div key={src} className={classNames('hi-preview', extraClass, {'hi-preview--hide': !show})} onClick={this.onClose.bind(this)}> - <div className='hi-preview-image' style={style}> + <div key={1} className={classNames('hi-preview', extraClass, {'hi-preview--hide': !show})} onClick={this.onClose.bind(this)}> + <div className={classNames('hi-preview-image', {'hi-preview-image--hide': !imgLoaded})} style={style}> <img - ref={node => { - this.imgRef = node - }} + ref={this.imgRef} src={src} onLoad={this.imgOnLoad.bind(this)} /> diff --git a/components/upload/style/preview.scss b/components/upload/style/preview.scss index cfc6f3c51..d913fb237 100644 --- a/components/upload/style/preview.scss +++ b/components/upload/style/preview.scss @@ -51,7 +51,11 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - transition: width 0.3s ease-out, height 0.3s ease-out; + // transition: width 0.3s ease-out, height 0.3s ease-out; overflow: hidden; + + &--hide { + visibility: hidden; + } } } From 48785d0f63e5898879e3b15d8787987b9287c1bf Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Tue, 14 May 2019 11:18:53 +0800 Subject: [PATCH 068/112] finish transfer --- components/checkbox/style/index.scss | 4 + components/lib/withDragDropContext.js | 4 + components/transfer/Item.js | 13 +- components/transfer/index.js | 45 ++-- components/transfer/style/index.scss | 19 ++ components/tree/Tree.js | 5 +- docs/zh-CN/components/transfer.md | 342 +++++++++++++++++++------- 7 files changed, 312 insertions(+), 120 deletions(-) create mode 100644 components/lib/withDragDropContext.js diff --git a/components/checkbox/style/index.scss b/components/checkbox/style/index.scss index 4ceaaa392..5b80beb0b 100644 --- a/components/checkbox/style/index.scss +++ b/components/checkbox/style/index.scss @@ -23,6 +23,10 @@ border-color: #d8d8d8; } } + + .hi-checkbox__label { + color: rgba(204, 204, 204, 1); + } } &--checked { diff --git a/components/lib/withDragDropContext.js b/components/lib/withDragDropContext.js new file mode 100644 index 000000000..d67fb3ba0 --- /dev/null +++ b/components/lib/withDragDropContext.js @@ -0,0 +1,4 @@ +import {DragDropContext} from 'react-dnd' +import HTML5Backend from 'react-dnd-html5-backend' + +export default DragDropContext(HTML5Backend) diff --git a/components/transfer/Item.js b/components/transfer/Item.js index a489a067f..09fb9090c 100644 --- a/components/transfer/Item.js +++ b/components/transfer/Item.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import Checkbox from '../checkbox' import { DragSource, DropTarget } from 'react-dnd' - +import classNames from 'classnames' class Item extends Component { render () { const { @@ -25,14 +25,19 @@ class Item extends Component { color: 'rgba(204,204,204,1)' } : {} + const itemCls = classNames( + 'hi-transfer__item', + item.disabled && 'hi-transfer__item--disabled' + ) const el = ( - <li style={sourceStyle} className='hi-transfer__item' onClick={onClick.bind(this)}> - {targetNode === item.id && <div className='hi-transfer__underline' />} + <li style={sourceStyle} className={itemCls} onClick={onClick.bind(this)}> + {targetNode === item.id && isDragging && <div className='hi-transfer__underline' />} {mode !== 'basic' ? ( <Checkbox text={item.content} value={item.id} checked={checked} + disabled={item.disabled} onChange={checkboxOnChange.bind(this)} /> ) : ( @@ -79,7 +84,7 @@ const target = { if (monitor.isOver({ shallow: true })) { const { item: targetItem, setTargetNode, positionX, positionY, setPosition } = props const sourcePosition = monitor.getClientOffset() - console.log(positionX, positionY, sourcePosition.x, sourcePosition.y) + // console.log(positionX, positionY, sourcePosition.x, sourcePosition.y) if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { setPosition(sourcePosition.x, sourcePosition.y) setTargetNode(targetItem.id) diff --git a/components/transfer/index.js b/components/transfer/index.js index dff2a2e18..bbedd3a7a 100755 --- a/components/transfer/index.js +++ b/components/transfer/index.js @@ -5,8 +5,7 @@ import Checkbox from '../checkbox' import Icon from '../icon' import Input from '../input' import classNames from 'classnames' -import { DragDropContext } from 'react-dnd' -import HTML5Backend from 'react-dnd-html5-backend' +import withDragDropContext from '../lib/withDragDropContext' import Item from './Item' import './style/index' @@ -18,7 +17,6 @@ class Transfer extends Component { targetList: [], sourceSelectedKeys: [], targetSelectedKeys: [], - emptyContent: ['暂无数据', '暂无数据'], leftFilter: '', rightFilter: '', limited: false, @@ -67,6 +65,9 @@ class Transfer extends Component { } clickItemEvent (item, index, dir) { const { mode } = this.props + if (item.disabled) { + return + } if (mode === 'basic') { this.parseSelectedKeys(dir, item.id, () => { this.moveTo(dir) @@ -82,7 +83,6 @@ class Transfer extends Component { } else { selectedItem.push(key) } - console.log(selectedItem) this.setState( { [this.getSelectedKeysByDir(dir)]: selectedItem @@ -125,7 +125,7 @@ class Transfer extends Component { const filterText = dir === 'left' ? leftFilter : rightFilter if (isChecked) { originDatas.forEach(data => { - data.content.includes(filterText) && arr.push(data.id) + data.content.includes(filterText) && !data.disabled && arr.push(data.id) }) } this.setState( @@ -182,9 +182,8 @@ class Transfer extends Component { this.setState({ sourceNode: null }) } renderContainer (dir, datas) { - const { mode, showAllSelect, searchable, draggable } = this.props + const { mode, showAllSelect, searchable, draggable, emptyContent, title, disabled } = this.props const { - emptyContent, sourceSelectedKeys, targetSelectedKeys, leftFilter, @@ -204,9 +203,13 @@ class Transfer extends Component { selectedKeys.length !== 0 && 'hi-transfer__footer--checkbox-part' ) + const _title = dir === 'left' ? title[0] : title[1] || title[0] return ( <div className='hi-transfer__container'> - <div className='hi-transfer__title'>标题</div> + {disabled && <div className='hi-transfer__mask' />} + { + _title && <div className='hi-transfer__title'>{_title}</div> + } {searchable && ( <div className='hi-transfer__searchbar'> <Icon name='search' /> @@ -218,7 +221,7 @@ class Transfer extends Component { </div> )} <div - className={`hi-transfer__body ${datas.length === 0 ? 'hi-transfer__body--empty' : ''}`} + className={`hi-transfer__body ${filterResult.length === 0 ? 'hi-transfer__body--empty' : ''}`} > {filterResult.length > 0 ? ( <ul className='hi-transfer__list'> @@ -229,18 +232,6 @@ class Transfer extends Component { </li> )} {filterResult.map((item, index) => { - // return <li - // key={index} - // className='hi-transfer__item' - // onClick={this.clickItemEvent.bind(this, item, index, dir)} - // > - // {mode !== 'basic' ? <Checkbox - // text={item.content} - // value={item.id} - // checked={selectedKeys.includes(item.id)} - // onChange={this.checkboxEvent.bind(this, dir)} - // /> : item.content} - // </li> return ( <Item dir={dir} @@ -268,7 +259,7 @@ class Transfer extends Component { ) : dir === 'left' ? ( emptyContent[0] ) : ( - emptyContent[1] + emptyContent[1] || emptyContent[0] )} </div> {mode !== 'basic' && showAllSelect && ( @@ -327,12 +318,18 @@ Transfer.defaultProps = { mode: 'basic', targetKeys: [], showAllSelect: false, - searchable: false + searchable: false, + draggable: false, + emptyContent: ['暂无数据', '暂无数据'], + title: ['', ''], + disabled: false } Transfer.propTypes = { mode: PropTypes.oneOf(['basic', 'multiple']), showAllSelect: PropTypes.bool, searchable: PropTypes.bool, + draggable: PropTypes.bool, + disabled: PropTypes.bool, targetLimit: PropTypes.number } -export default DragDropContext(HTML5Backend)(Transfer) +export default withDragDropContext(Transfer) diff --git a/components/transfer/style/index.scss b/components/transfer/style/index.scss index c46e4b3b6..be871edf5 100755 --- a/components/transfer/style/index.scss +++ b/components/transfer/style/index.scss @@ -77,11 +77,21 @@ cursor: pointer; user-select: none; position: relative; + text-align: left; &:hover { background: rgba(66, 132, 245, 0.1); } + &--disabled { + color: rgba(204, 204, 204, 1); + cursor: not-allowed; + + &:hover { + background: inherit; + } + } + &--limit { height: 40px; color: #999; @@ -166,6 +176,15 @@ top: 36px; } + &__mask { + width: 100%; + height: 100%; + position: absolute; + z-index: 2; + background: rgba(246, 246, 246, 0.7); + cursor: not-allowed; + } + .hi-checkbox__input { box-sizing: border-box; } diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 535aa0b8a..98c7fb294 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -3,8 +3,7 @@ import classNames from 'classnames' import TreeNode from './TreeNode' import isEqual from 'lodash/isEqual' import { getAll, dealData } from './util' -import { DragDropContext } from 'react-dnd' -import HTML5Backend from 'react-dnd-html5-backend' +import withDragDropContext from '../lib/withDragDropContext' import './style/index' @@ -225,7 +224,7 @@ const HOCTree = TreeComponent => { return class WrapperTree extends Component { render () { const { draggable } = this.props - const DraggableTree = DragDropContext(HTML5Backend)(Tree) + const DraggableTree = withDragDropContext(Tree) return draggable ? <DraggableTree {...this.props} /> : <TreeComponent {...this.props} /> } } diff --git a/docs/zh-CN/components/transfer.md b/docs/zh-CN/components/transfer.md index 212894c9c..9ef8eba98 100755 --- a/docs/zh-CN/components/transfer.md +++ b/docs/zh-CN/components/transfer.md @@ -1,6 +1,5 @@ ## Transfer - ### 基础用法 :::demo @@ -11,18 +10,8 @@ constructor () { super() this.state = { - datas1: this.randomDatas(), - datas2: this.randomDatas(), - datas3: this.randomDatas(), - datas4: this.randomDatas(), - datas5: this.randomDatas(), - datas6: this.randomDatas(), - targetKeys1: [2, 3], - targetKeys2: [], - targetKeys3: [], - targetKeys4: [], - targetKeys5: [], - targetKeys6: [] + datas: this.randomDatas(), + targetKeys: [2, 3] } } randomDatas () { @@ -30,110 +19,285 @@ randomDatas () { for (let i=1;i<16; i++) { arr.push({ id: i, - content: '选项'+i + content: '选项'+i, + disabled: i%3 === 0 + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='basic' + emptyContent={['空', '无数据']} + title={['左标题', '右标题']} + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + + +### 批量 + +:::demo + +批量 + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [], + disabled: false + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: '选项'+i, + disabled: i%3 === 0 }) } return arr } -onChange (d, movedKeys) { +onChange (movedKeys) { this.setState({ - [d]: movedKeys + targetKeys: movedKeys, + disabled: movedKeys.length > 5 }) } render () { return ( <div> - 普通: - <Transfer - mode='basic' - targetKeys={this.state.targetKeys1} - data={this.state.datas1} - onChange={this.onChange.bind(this, 'targetKeys1')} - /> - <br/> - 无全选: - <Transfer - mode='multiple' - targetKeys={this.state.targetKeys2} - data={this.state.datas2} - onChange={this.onChange.bind(this, 'targetKeys2')} - /> - <br/> - 显示全选: <Transfer mode='multiple' - showAllSelect - targetKeys={this.state.targetKeys3} - data={this.state.datas3} - onChange={this.onChange.bind(this, 'targetKeys3')} + title={['批量']} + disabled={this.state.disabled} + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} /> - <br/> - 带搜索: - <Transfer - mode='multiple' - showAllSelect - searchable - targetKeys={this.state.targetKeys4} - data={this.state.datas4} - onChange={this.onChange.bind(this, 'targetKeys4')} - /> - <br/> - 带搜索: - <Transfer - mode='multiple' - showAllSelect - searchable - targetKeys={this.state.targetKeys4} - data={this.state.datas4} - onChange={this.onChange.bind(this, 'targetKeys4')} - /> - - <br/> - 目标数量上限: - <Transfer - mode='multiple' - showAllSelect - searchable - targetLimit={5} - targetKeys={this.state.targetKeys5} - data={this.state.datas5} - onChange={this.onChange.bind(this, 'targetKeys5')} - /> - <br/> - 目标数量上限: - <Transfer - mode='multiple' - showAllSelect - searchable - draggable - targetKeys={this.state.targetKeys6} - data={this.state.datas6} - onChange={this.onChange.bind(this, 'targetKeys6')} - /> - </div> ) } ``` ::: +### 全选 + +:::demo + +全选 + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: '选项'+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + + +### 搜索 + +:::demo + +搜索 + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: '选项'+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + searchable + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + + +### 目标数量上限 + +:::demo + +目标数量上限 + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: '选项'+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + targetLimit={4} + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + +### 目标区域拖拽 + +:::demo + +目标区域拖拽 + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3, 4, 6, 9] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: '选项'+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + draggable + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: ### Alert Attributes | 参数 | 说明 | 类型 | 可选值 | 默认值 | | -------- | ----- | ---- | ---- | ---- | -| type | 类型 | String | info \| success \| error \| warning | info | -| message | 提示内容 | String | - | - | -| title | 提示标题 | String | - | - | -| size | 弹框大小类型 | String | small \| middle \| large | middle | -| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | -| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | -| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | +| mode | 类型 | String | basic \| multiple | basic | +| showAllSelect | 是否显示全选按钮 | Boolean | true \| false | false | +| title | 标题 <br/> 数组长度1或2位,1位时左右标题将相同,2位时将使用对应索引标题 | Array[String \| Element] | - | '' | +| searchable | 是否可筛选 | Boolean | true \| false | false | +| emptyContent | 数据为空时的显示内容 <br/> 数组长度1或2位,1位时左右内容将相同,2位时将使用对应索引内容| Array[String \| Element] | - | 暂无数据 | +| draggable | 是否允许目标框内拖拽排序 | Boolean | true \| false | false | +| targetKeys | 目标框内的元素id集合 | Array | - | - | +| data | 数据集合 | Array | - | 参见 Data Options | +| targetLimit | 目标框数据上限 | Number | - | null | + + +### Data Options +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| id | 唯一 id | Number | - | - | +| content | 元素内容 | String \| Element | - | - | +| disabled | 该元素是否被禁用 | Boolean | true \| false | false | ### Alert Events | 参数 | 说明 | 回调参数 | ------- | ------- | ------- | -| onClose | 关闭时触发的事件 | - | +| onChange | 选中元素被移动到目标框内后触发的事件函数 | 目标框内元素的 id 集合,如[2,3,4] | From 7efcd502c3832f03d9826251f9bddd383ecd60af Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Wed, 15 May 2019 15:41:28 +0800 Subject: [PATCH 069/112] Switch Component --- components/index.js | 1 + .../__test__/__snapshots__/index.test.js.snap | 34 ++++++++ components/switch/__test__/index.test.js | 32 +++++++ components/switch/index.js | 71 ++++++++++++++++ components/switch/style/index.js | 1 + components/switch/style/index.scss | 84 +++++++++++++++++++ docs/en-US/components/switch.md | 40 +++++++++ docs/zh-CN/components/switch.md | 57 +++++++++++++ package.json | 7 +- site/locales/en-US.js | 3 +- site/locales/zh-CN.js | 3 +- site/pages/components/index.js | 3 +- site/pages/components/switch/index.js | 9 ++ 13 files changed, 340 insertions(+), 5 deletions(-) create mode 100644 components/switch/__test__/__snapshots__/index.test.js.snap create mode 100644 components/switch/__test__/index.test.js create mode 100755 components/switch/index.js create mode 100644 components/switch/style/index.js create mode 100755 components/switch/style/index.scss create mode 100755 docs/en-US/components/switch.md create mode 100755 docs/zh-CN/components/switch.md create mode 100755 site/pages/components/switch/index.js diff --git a/components/index.js b/components/index.js index c07a68149..3accf97ff 100755 --- a/components/index.js +++ b/components/index.js @@ -37,4 +37,5 @@ export { default as Ficon } from './ficon' export { default as Icon } from './icon' export { default as Progress } from './progress' export { default as Card } from './card' +export { default as Switch } from './switch' export { ThemeContext, LocaleContext } from './context' diff --git a/components/switch/__test__/__snapshots__/index.test.js.snap b/components/switch/__test__/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..1ea3722cc --- /dev/null +++ b/components/switch/__test__/__snapshots__/index.test.js.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Switch basic use 1`] = ` +<div> + <span + className="theme__hiui-blue hi-switch hi-switch--closed" + onClick={[Function]} + > + <span + className="hi-switch__text" + /> + </span> + <span + className="theme__hiui-blue hi-switch hi-switch--closed" + onClick={[Function]} + > + <span + className="hi-switch__text" + > + OFF + </span> + </span> + <span + className="theme__hiui-blue hi-switch hi-switch--disabled" + onClick={[Function]} + > + <span + className="hi-switch__text" + > + 开 + </span> + </span> +</div> +`; diff --git a/components/switch/__test__/index.test.js b/components/switch/__test__/index.test.js new file mode 100644 index 000000000..ec96bafc3 --- /dev/null +++ b/components/switch/__test__/index.test.js @@ -0,0 +1,32 @@ +import React from 'react' +import { mount } from 'enzyme' +import renderer from 'react-test-renderer' +import Switch from '..' + +describe('Switch', () => { + beforeAll(() => { + jest.useFakeTimers() + }) + + afterAll(() => { + jest.useRealTimers() + }) + it('basic use', () => { + const wrapper = renderer.create( + <div> + <Switch /> + <Switch content={['ON', 'OFF']} /> + <Switch checked disabled content={['开', '关']} /> + </div> + ) + expect(wrapper).toMatchSnapshot() + }) + it('onchange', () => { + const onChange = jest.fn() + const wrapper = mount( + <Switch onChange={onChange} /> + ) + wrapper.find('.hi-switch').simulate('click') + expect(onChange).toBeCalled() + }) +}) diff --git a/components/switch/index.js b/components/switch/index.js new file mode 100755 index 000000000..931f5edff --- /dev/null +++ b/components/switch/index.js @@ -0,0 +1,71 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import Provider from '../context' +import './style/index' + +class Switch extends Component { + componentDidMount () { + + } + constructor (props) { + super(props) + this.state = { + checked: props.checked + } + } + componentWillReceiveProps (nextProps) { + if (nextProps.checked !== this.state.checked) { + this.setState({ + checked: nextProps.checked + }, () => { + this.props.onChange(nextProps.checked) + }) + } + } + clickEvent () { + const { disabled, onChange } = this.props + if (!disabled) { + this.setState({ + checked: !this.state.checked + }, () => { + onChange(this.state.checked) + }) + } + } + render () { + const { theme, content, disabled } = this.props + const { checked } = this.state + const sCls = classNames( + 'theme__' + theme, + 'hi-switch', + !checked && 'hi-switch--closed', + disabled && 'hi-switch--disabled' + ) + return <span + className={sCls} + onClick={this.clickEvent.bind(this)} + > + <span className='hi-switch__text'> + { + content.length > 0 && ( + checked ? content[0] : content[1] + ) + } + </span> + </span> + } +} +Switch.defaultProps = { + content: [], + checked: false, + disabled: false, + onChange: () => {} +} +Switch.propTypes = { + content: PropTypes.array, + checked: PropTypes.bool, + disabled: PropTypes.bool, + onChange: PropTypes.func +} +export default Provider(Switch) diff --git a/components/switch/style/index.js b/components/switch/style/index.js new file mode 100644 index 000000000..63810a681 --- /dev/null +++ b/components/switch/style/index.js @@ -0,0 +1 @@ +import './index.scss' diff --git a/components/switch/style/index.scss b/components/switch/style/index.scss new file mode 100755 index 000000000..5cee9a3b8 --- /dev/null +++ b/components/switch/style/index.scss @@ -0,0 +1,84 @@ +@import '@hi-ui/core-css/index.scss'; + +// Define component colors +// @mixin hi-btn-style($color: get-color($palette-primary, 'hiui-blue')) { +// & { + +// } +// } +$basic-color: #4284f5 !default; + +@mixin hi-switch-style($color: get-color($palette-primary, 'hiui-blue')) { + & { + background: map-get(get-palette($color), '50'); + + &--closed, + &--disabled { + background: map-get(get-palette($color), 'g60'); + } + + &--disabled { + &::after { + background: map-get(get-palette($color), 'g50'); + } + } + } +} + +.hi-switch { + display: inline-block; + min-width: 48px; + height: 20px; + border-radius: 10px; + cursor: pointer; + position: relative; + + @include hi-switch-style(); + + &::after { + content: ''; + width: 16px; + height: 16px; + background: rgba(255, 255, 255, 1); + position: absolute; + border-radius: 50%; + top: 2px; + left: 100%; + transform: translateX(-100%); + margin-left: -2px; + transition: all 200ms linear; + } + + &__text { + color: #fff; + font-size: 12px; + display: inline-block; + position: absolute; + top: 2px; + left: 6px; + transition: all 200ms linear; + user-select: none; + } + + &--closed { + &::after { + left: 2px; + transform: translateX(2px); + } + + .hi-switch__text { + left: auto; + right: 6px; + } + } + + &--disabled { + cursor: not-allowed; + } +} + +@each $key, $value in $palette-primary { + .theme__#{$key}.hi-switch { + @include hi-switch-style($value); + } +} diff --git a/docs/en-US/components/switch.md b/docs/en-US/components/switch.md new file mode 100755 index 000000000..11ffada80 --- /dev/null +++ b/docs/en-US/components/switch.md @@ -0,0 +1,40 @@ +## Switch + + +### 基础用法 + +:::demo + +基础用法 + +```js +render () { + return ( + <div> + <Switch type="info" message="信息提示的文案" onClose={()=>{console.log('alert关闭回调')}} /> + </div> + ) +} +``` +::: + + + +### Alert Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| type | 类型 | String | info \| success \| error \| warning | info | +| message | 提示内容 | String | - | - | +| title | 提示标题 | String | - | - | +| size | 弹框大小类型 | String | small \| middle \| large | middle | +| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | +| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | +| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | + + +### Alert Events + +| 参数 | 说明 | 回调参数 +| ------- | ------- | ------- | +| onClose | 关闭时触发的事件 | - | diff --git a/docs/zh-CN/components/switch.md b/docs/zh-CN/components/switch.md new file mode 100755 index 000000000..ca6c93f3b --- /dev/null +++ b/docs/zh-CN/components/switch.md @@ -0,0 +1,57 @@ +## Switch + + +### 基础用法 + +:::demo + +基础用法 + +```js +constructor (props) { + super(props) + this.state = { + checked: true, + disabled: true + } +} +onChange (status) { + console.log(status) +} +render () { + return ( + <div> + <p>默认</p> + <Switch /> + <p>自定义内容</p> + <Switch content={['ON', 'OFF']}/> + <Switch content={[<Icon name='check' />, <Icon name='close' />]}/> + <p>禁用状态</p> + <p> + <Button onClick={() => {this.setState({disabled: !this.state.disabled})}}>切换禁用</Button> + <Button onClick={() => {this.setState({checked: !this.state.checked})}}>切换开启</Button> + </p> + <Switch checked={this.state.checked} disabled={this.state.disabled} content={['开', '关']} onChange={this.onChange.bind(this)}/> + </div> + ) +} +``` +::: + + + +### Switch Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| content | 自定义显示内容 | Array[String \| Element] | - | '' | +| checked | 是否默认开启状态 | Boolean | true \| false | false | +| disabled | 是否默认禁用状态 | Boolean | true \| false | false | +| size | 弹框大小类型 | String | small \| middle \| large | middle | + + +### Switch Events + +| 参数 | 说明 | 回调参数 +| ------- | ------- | ------- | +| onChange | 切换的回调函数 | 当前状态 | diff --git a/package.json b/package.json index 88f7a87ac..d8761b0fa 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@babel/standalone": "^7.3.3", "autoprefixer": "^8.5.0", "babel-eslint": "^10.0.1", - "babel-jest": "^24.1.0", + "babel-jest": "^24.8.0", "babel-loader": "^8.0.5", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-remove-console": "^6.9.4", @@ -85,7 +85,7 @@ "gulp-babel": "^8.0.0", "html-webpack-plugin": "^3.2.0", "husky": "^0.14.3", - "jest": "^24.1.0", + "jest": "^24.8.0", "jest-fetch-mock": "^2.1.1", "jsx-loader": "^0.13.2", "lint-staged": "^7.2.2", @@ -116,6 +116,9 @@ "webpack-dev-server": "^3.1.12" }, "standard": { + "env": [ + "jest" + ], "parser": "babel-eslint", "ignore": [ "/es/", diff --git a/site/locales/en-US.js b/site/locales/en-US.js index 5458f5b69..9fcc88f2c 100644 --- a/site/locales/en-US.js +++ b/site/locales/en-US.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper', icon: 'Icon', progress: 'Progress', - card: 'Card' + card: 'Card', + switch: 'Switch' }, designs: { 'design-patterns': '设计模式', diff --git a/site/locales/zh-CN.js b/site/locales/zh-CN.js index becbb4d6a..f4110dddd 100755 --- a/site/locales/zh-CN.js +++ b/site/locales/zh-CN.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper 步骤', icon: 'Icon 图标', progress: 'Progress 进度条', - card: 'Card 卡片' + card: 'Card 卡片', + switch: 'Switch 开关' }, designs: { 'design-patterns': '设计模式', diff --git a/site/pages/components/index.js b/site/pages/components/index.js index 94bec3c08..5c3279d0a 100755 --- a/site/pages/components/index.js +++ b/site/pages/components/index.js @@ -43,7 +43,8 @@ export default { 'tooltip': require('./tooltip'), 'popover': require('./popover'), 'progress': require('./progress'), - 'card': require('./card') + 'card': require('./card'), + 'switch': require('./switch') }, 'group-tips': { 'modal': require('./modal'), diff --git a/site/pages/components/switch/index.js b/site/pages/components/switch/index.js new file mode 100755 index 000000000..5bf34d2c0 --- /dev/null +++ b/site/pages/components/switch/index.js @@ -0,0 +1,9 @@ +import Markdown from '../../../../libs/markdown' + +class Switch extends Markdown { + document (locale) { + return require(`../../../../docs/${locale}/components/switch.md`) + } +} + +export default Switch From 19352c48d172ab8c5f8a5001c33798835e3af54f Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 16 May 2019 11:32:58 +0800 Subject: [PATCH 070/112] doc --- docs/en-US/components/timeline.md | 41 +++++++++++++++++++++++++++++++ docs/zh-CN/components/timeline.md | 41 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100755 docs/en-US/components/timeline.md create mode 100755 docs/zh-CN/components/timeline.md diff --git a/docs/en-US/components/timeline.md b/docs/en-US/components/timeline.md new file mode 100755 index 000000000..40ed99c13 --- /dev/null +++ b/docs/en-US/components/timeline.md @@ -0,0 +1,41 @@ +## Alert + + +### 基础用法 + +:::demo + +基础用法 + +```js +render () { + return ( + <div> + <Timeline /> + </div> + ) +} +``` +::: + + + + +### Alert Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| type | 类型 | String | info \| success \| error \| warning | info | +| message | 提示内容 | String | - | - | +| title | 提示标题 | String | - | - | +| size | 弹框大小类型 | String | small \| middle \| large | middle | +| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | +| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | +| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | + + +### Alert Events + +| 参数 | 说明 | 回调参数 +| ------- | ------- | ------- | +| onClose | 关闭时触发的事件 | - | diff --git a/docs/zh-CN/components/timeline.md b/docs/zh-CN/components/timeline.md new file mode 100755 index 000000000..40ed99c13 --- /dev/null +++ b/docs/zh-CN/components/timeline.md @@ -0,0 +1,41 @@ +## Alert + + +### 基础用法 + +:::demo + +基础用法 + +```js +render () { + return ( + <div> + <Timeline /> + </div> + ) +} +``` +::: + + + + +### Alert Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ----- | ---- | ---- | ---- | +| type | 类型 | String | info \| success \| error \| warning | info | +| message | 提示内容 | String | - | - | +| title | 提示标题 | String | - | - | +| size | 弹框大小类型 | String | small \| middle \| large | middle | +| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | +| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | +| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | + + +### Alert Events + +| 参数 | 说明 | 回调参数 +| ------- | ------- | ------- | +| onClose | 关闭时触发的事件 | - | From 972ce21b66deb1d441475c5d71b471482f127fa3 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 16 May 2019 11:39:21 +0800 Subject: [PATCH 071/112] modify es docs --- docs/en-US/components/switch.md | 46 ++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/docs/en-US/components/switch.md b/docs/en-US/components/switch.md index 11ffada80..8e09e2949 100755 --- a/docs/en-US/components/switch.md +++ b/docs/en-US/components/switch.md @@ -1,17 +1,37 @@ ## Switch -### 基础用法 +### Basic :::demo -基础用法 +Basic ```js +constructor (props) { + super(props) + this.state = { + checked: true, + disabled: true + } +} +onChange (status) { + console.log(status) +} render () { return ( <div> - <Switch type="info" message="信息提示的文案" onClose={()=>{console.log('alert关闭回调')}} /> + <p>Default</p> + <Switch /> + <p>Custom content</p> + <Switch content={['ON', 'OFF']}/> + <Switch content={[<Icon name='check' />, <Icon name='close' />]}/> + <p>Disabled state</p> + <p> + <Button onClick={() => {this.setState({disabled: !this.state.disabled})}}>Toggle disabled</Button> + <Button onClick={() => {this.setState({checked: !this.state.checked})}}>Toggle open</Button> + </p> + <Switch checked={this.state.checked} disabled={this.state.disabled} content={['ON', 'OFF']} onChange={this.onChange.bind(this)}/> </div> ) } @@ -20,21 +40,17 @@ render () { -### Alert Attributes +### Switch Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| Attribute | Description | Type | Options | Default | | -------- | ----- | ---- | ---- | ---- | -| type | 类型 | String | info \| success \| error \| warning | info | -| message | 提示内容 | String | - | - | -| title | 提示标题 | String | - | - | -| size | 弹框大小类型 | String | small \| middle \| large | middle | -| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | -| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | -| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | +| content | Custom display content | Array[String \| Element] | - | '' | +| checked | Specifies whether the switch is checked | Boolean | true \| false | false | +| disabled | Specifies whether the switch is disabled | Boolean | true \| false | false | -### Alert Events +### Switch Events -| 参数 | 说明 | 回调参数 +| Event Name | Description | Parameters | ------- | ------- | ------- | -| onClose | 关闭时触发的事件 | - | +| onChange | callback | current status | From 84c14e54680f49d191efd03dc5e6b55ca6570c64 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 16 May 2019 11:45:45 +0800 Subject: [PATCH 072/112] modify en docs --- docs/en-US/components/transfer.md | 290 ++++++++++++++++++++++++++++-- 1 file changed, 276 insertions(+), 14 deletions(-) diff --git a/docs/en-US/components/transfer.md b/docs/en-US/components/transfer.md index 3f136f8bd..2cf35b709 100755 --- a/docs/en-US/components/transfer.md +++ b/docs/en-US/components/transfer.md @@ -1,41 +1,303 @@ ## Transfer +### Basic -### 基础用法 +:::demo + +Basic + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: 'Option '+i, + disabled: i%3 === 0 + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='basic' + emptyContent={['Null', 'Empty']} + title={['Left Title', 'Right Title']} + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + + +### Batch :::demo -基础用法 +Batch ```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [], + disabled: false + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: 'Option '+i, + disabled: i%3 === 0 + }) + } + return arr +} +onChange (movedKeys) { + this.setState({ + targetKeys: movedKeys, + disabled: movedKeys.length > 5 + }) +} render () { return ( <div> - <Transfer /> - + <Transfer + mode='multiple' + title={['Batch']} + disabled={this.state.disabled} + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> </div> ) } ``` ::: +### All Select + +:::demo + +All Select + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: 'Option '+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + + +### Search + +:::demo + +Search + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: 'Option '+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + searchable + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + + +### Target Limit + +:::demo + +Target Limit + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: 'Option '+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + targetLimit={4} + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: + +### Target Drag + +:::demo + +Target Drag + +```js +constructor () { + super() + this.state = { + datas: this.randomDatas(), + targetKeys: [2, 3, 4, 6, 9] + } +} +randomDatas () { + const arr = [] + for (let i=1;i<16; i++) { + arr.push({ + id: i, + content: 'Option '+i + }) + } + return arr +} +onChange ( movedKeys) { + this.setState({ + targetKeys: movedKeys + }) +} +render () { + return ( + <Transfer + mode='multiple' + showAllSelect + draggable + targetKeys={this.state.targetKeys} + data={this.state.datas} + onChange={this.onChange.bind(this)} + /> + ) +} +``` +::: ### Alert Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| Attribute | Description | Type | Options | Default | | -------- | ----- | ---- | ---- | ---- | -| type | 类型 | String | info \| success \| error \| warning | info | -| message | 提示内容 | String | - | - | -| title | 提示标题 | String | - | - | -| size | 弹框大小类型 | String | small \| middle \| large | middle | -| closeable | 是否显示关闭按钮 | Boolean | true \| false | true | -| autoClose | 是否自动关闭(closeable 为 false 时生效) | Boolean | true \| false | false | -| autoCloseTime | 自动关闭时间,单位为毫秒 | Number | - | 3000 | +| mode | Type | String | basic \| multiple | basic | +| showAllSelect | Specifies whether to display the all-select | Boolean | true \| false | false | +| title | Title | Array[String \| Element] | - | '' | +| searchable | Specifies whether it screenable | Boolean | true \| false | false | +| emptyContent | Display content when the data is empty | Array[String \| Element] | - | Empty | +| draggable | Whether to allow drag and drop sorting within the target box | Boolean | true \| false | false | +| targetKeys | The set of element ids in the target box | Array | - | - | +| data | Datas | Array | - | 参见 Data Options | +| targetLimit | Target frame data limit | Number | - | null | + +### Data Options +| Attribute | Description | Type | Options | Default | +| -------- | ----- | ---- | ---- | ---- | +| id | id | Number | - | - | +| content | Content | String \| Element | - | - | +| disabled | disabled | Boolean | true \| false | false | + ### Alert Events -| 参数 | 说明 | 回调参数 +| Event Name | Description | Parameters | ------- | ------- | ------- | -| onClose | 关闭时触发的事件 | - | +| onChange | callabck | The id collection of the elements in the target box,eg.[2,3,4] | From 3c1dfa3b8ae38f43c4fce6cd312127f905e0eb69 Mon Sep 17 00:00:00 2001 From: bougiel <1742070326@qq.com> Date: Thu, 16 May 2019 16:39:51 +0800 Subject: [PATCH 073/112] feat: rate component --- components/index.js | 1 + components/rate/Icons.js | 310 ++++++++++++++++++++++ components/rate/Rate.js | 153 +++++++++++ components/rate/__test__/index.test.js | 37 +++ components/rate/index.js | 4 + components/rate/style/index.js | 1 + components/rate/style/index.scss | 60 +++++ components/select/style/select-input.scss | 3 + components/upload/style/index.scss | 7 +- docs/en-US/components/rate.md | 112 ++++++++ docs/zh-CN/components/rate.md | 112 ++++++++ site/locales/en-US.js | 3 +- site/locales/zh-CN.js | 3 +- site/pages/components/index.js | 4 +- site/pages/components/rate/index.js | 9 + 15 files changed, 814 insertions(+), 5 deletions(-) create mode 100644 components/rate/Icons.js create mode 100644 components/rate/Rate.js create mode 100644 components/rate/__test__/index.test.js create mode 100644 components/rate/index.js create mode 100644 components/rate/style/index.js create mode 100644 components/rate/style/index.scss create mode 100644 docs/en-US/components/rate.md create mode 100644 docs/zh-CN/components/rate.md create mode 100644 site/pages/components/rate/index.js diff --git a/components/index.js b/components/index.js index c07a68149..6a1089142 100755 --- a/components/index.js +++ b/components/index.js @@ -37,4 +37,5 @@ export { default as Ficon } from './ficon' export { default as Icon } from './icon' export { default as Progress } from './progress' export { default as Card } from './card' +export { default as Rate } from './rate' export { ThemeContext, LocaleContext } from './context' diff --git a/components/rate/Icons.js b/components/rate/Icons.js new file mode 100644 index 000000000..8f7e5e6d2 --- /dev/null +++ b/components/rate/Icons.js @@ -0,0 +1,310 @@ +import React from 'react' + +const iconCls = `hi-rate__icon` + +export const StarDefault = () => ( + <i className={iconCls}> + <svg viewBox='0 0 24 24'> + <path + d='M6.05109079,21.4985411 L11.9999271,18.3760205 L17.9487635,21.4985411 C18.0553353,21.5544802 18.177368,21.5737508 18.2959912,21.5533729 C18.5940667,21.5021677 18.7941945,21.2190197 18.7429892,20.9209442 L17.6071122,14.3087929 L22.4170865,9.62767423 C22.5035315,9.54354506 22.5597804,9.43322778 22.5770992,9.31385229 C22.6205221,9.01454409 22.4130861,8.7367056 22.1137779,8.69328266 L15.4656533,7.72878808 L12.4908438,1.71074009 C12.4375807,1.60298856 12.350346,1.5157539 12.2425945,1.46249073 C11.9714687,1.32846927 11.6430319,1.43961429 11.5090105,1.71074009 L8.53420096,7.72878808 L1.88607638,8.69328266 C1.76670088,8.71060138 1.6563836,8.76685035 1.57225444,8.8532953 C1.36131879,9.07003732 1.36602577,9.41673857 1.58276778,9.62767423 L6.39274205,14.3087929 L5.25686508,20.9209442 C5.23648723,21.0395675 5.2557578,21.1616002 5.31169691,21.268172 C5.45226021,21.5359646 5.78329813,21.6391044 6.05109079,21.4985411 Z' + stroke='#D8D8D8' + fill='#FFFFFF' + /> + </svg> + </i> +) + +export const StarActive = () => ( + <i className={iconCls}> + <svg viewBox='0 0 24 24'> + <path + d='M11.9999271,18.9407142 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.3171711,0.970498831 11.9454849,0.757873569 12.4641603,1.01426246 C12.6702937,1.1161572 12.8371774,1.28304091 12.9390721,1.48917427 L15.7974549,7.27169059 L22.1855652,8.19846291 C22.7581548,8.28153289 23.1549889,8.81305 23.0719189,9.3856396 C23.0387874,9.61401011 22.9311807,9.82505186 22.7658078,9.9859946 L18.144456,14.4835438 L19.235771,20.8362911 C19.3337289,21.4065224 18.9508757,21.9481968 18.3806444,22.0461547 C18.153713,22.0851384 17.9202591,22.0482729 17.7163827,21.941259 L11.9999271,18.9407142 Z' + fill='#FF9900' + /> + </svg> + </i> +) + +export const StarHalfActive = () => ( + <i className={iconCls}> + <svg viewBox='0 0 24 24'> + <path + d='M6.05109079,21.4985411 L11.9999271,18.3760205 L17.9487635,21.4985411 C18.0553353,21.5544802 18.177368,21.5737508 18.2959912,21.5533729 C18.5940667,21.5021677 18.7941945,21.2190197 18.7429892,20.9209442 L17.6071122,14.3087929 L22.4170865,9.62767423 C22.5035315,9.54354506 22.5597804,9.43322778 22.5770992,9.31385229 C22.6205221,9.01454409 22.4130861,8.7367056 22.1137779,8.69328266 L15.4656533,7.72878808 L12.4908438,1.71074009 C12.4375807,1.60298856 12.350346,1.5157539 12.2425945,1.46249073 C11.9714687,1.32846927 11.6430319,1.43961429 11.5090105,1.71074009 L8.53420096,7.72878808 L1.88607638,8.69328266 C1.76670088,8.71060138 1.6563836,8.76685035 1.57225444,8.8532953 C1.36131879,9.07003732 1.36602577,9.41673857 1.58276778,9.62767423 L6.39274205,14.3087929 L5.25686508,20.9209442 C5.23648723,21.0395675 5.2557578,21.1616002 5.31169691,21.268172 C5.45226021,21.5359646 5.78329813,21.6391044 6.05109079,21.4985411 Z' + stroke='#D8D8D8' + fill='#FFFFFF' + /> + <path + d='M12,0.905555466 L12,18.9407525 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.2432922,1.11995607 11.6142669,0.905822043 12,0.905555466 Z' + fill='#FF9900' + /> + </svg> + </i> +) + +export const StarDisable = () => ( + <i className={iconCls}> + <svg viewBox='0 0 24 24'> + <path + d='M11.9999271,18.9407142 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.3171711,0.970498831 11.9454849,0.757873569 12.4641603,1.01426246 C12.6702937,1.1161572 12.8371774,1.28304091 12.9390721,1.48917427 L15.7974549,7.27169059 L22.1855652,8.19846291 C22.7581548,8.28153289 23.1549889,8.81305 23.0719189,9.3856396 C23.0387874,9.61401011 22.9311807,9.82505186 22.7658078,9.9859946 L18.144456,14.4835438 L19.235771,20.8362911 C19.3337289,21.4065224 18.9508757,21.9481968 18.3806444,22.0461547 C18.153713,22.0851384 17.9202591,22.0482729 17.7163827,21.941259 L11.9999271,18.9407142 Z' + fill='#F2F2F2' + /> + </svg> + </i> +) + +export const StarHalfReadonly = () => ( + <i className={iconCls}> + <svg viewBox='0 0 24 24'> + <path + d='M11.9999271,18.9407142 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.3171711,0.970498831 11.9454849,0.757873569 12.4641603,1.01426246 C12.6702937,1.1161572 12.8371774,1.28304091 12.9390721,1.48917427 L15.7974549,7.27169059 L22.1855652,8.19846291 C22.7581548,8.28153289 23.1549889,8.81305 23.0719189,9.3856396 C23.0387874,9.61401011 22.9311807,9.82505186 22.7658078,9.9859946 L18.144456,14.4835438 L19.235771,20.8362911 C19.3337289,21.4065224 18.9508757,21.9481968 18.3806444,22.0461547 C18.153713,22.0851384 17.9202591,22.0482729 17.7163827,21.941259 L11.9999271,18.9407142 Z' + fill='#F2F2F2' + /> + <path + d='M12,0.905555466 L12,18.9407525 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.2432922,1.11995607 11.6142669,0.905822043 12,0.905555466 Z' + fill='#FF9900' + /> + </svg> + </i> +) + +export const EmojiDefault = () => ( + <i className={iconCls}> + <svg> + <g fill='#bfbfbf'> + <path d='m12 23c6.0742 0 11-4.9258 11-11s-4.9258-11-11-11-11 4.9258-11 11 4.9258 11 11 11zm0 1c-6.6289 0-12-5.3711-12-12s5.3711-12 12-12 12 5.3711 12 12-5.3711 12-12 12z' /> + <path + d='m9.5 15h5c0.27734 0 0.5 0.22266 0.5 0.5s-0.22266 0.5-0.5 0.5h-5c-0.27734 0-0.5-0.22266-0.5-0.5s0.22266-0.5 0.5-0.5z' + fillRule='evenodd' + /> + <path + d='m6.875 7c0.75781 0 1.375 0.61719 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375s-1.375-0.61719-1.375-1.375v-2.25c0-0.75781 0.61719-1.375 1.375-1.375z' + fillRule='evenodd' + /> + <path + d='m17.117 7c0.75781 0 1.375 0.61719 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375-0.76172 0-1.375-0.61719-1.375-1.375v-2.25c0-0.75781 0.61328-1.375 1.375-1.375z' + fillRule='evenodd' + /> + </g> + </svg> + </i> +) + +export const EmojiOne = () => ( + <i className={iconCls}> + <svg> + <path + d='m24 12c0 6.6289-5.3711 12-12 12s-12-5.3711-12-12 5.3711-12 12-12 12 5.3711 12 12z' + fill='#ff4c33' + fillRule='evenodd' + /> + <path + d='m5.5 9 2.75 1v2.125c0 0.75781-0.61719 1.375-1.375 1.375s-1.375-0.61719-1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m4.8125 8.1719 4.2266 1.5391c0.26172 0.09375 0.39453 0.37891 0.30078 0.64062-0.09375 0.25781-0.38281 0.39453-0.64062 0.29688l-4.2305-1.5391c-0.25781-0.09375-0.39062-0.37891-0.29688-0.64062 0.09375-0.25781 0.37891-0.39062 0.64062-0.29688z' + fill='#d73620' + fillRule='evenodd' + /> + <path + d='m18.5 9-2.75 1v2.125c0 0.75781 0.61719 1.375 1.375 1.375s1.375-0.61719 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m19.188 8.1719-4.2266 1.5391c-0.26172 0.09375-0.39453 0.37891-0.30078 0.64062 0.09375 0.25781 0.38281 0.39453 0.64062 0.29688l4.2305-1.5391c0.25781-0.09375 0.39062-0.37891 0.29688-0.64062-0.09375-0.25781-0.37891-0.39062-0.64062-0.29688z' + fill='#d73620' + fillRule='evenodd' + /> + <path + d='m3.5 2.5c0-0.27734 0.22266-0.5 0.5-0.5 0.27344 0 0.5 0.22266 0.5 0.5v1.75c0 0.27734-0.22656 0.5-0.5 0.5h-1.75c-0.27734 0-0.5-0.22266-0.5-0.5s0.22266-0.5 0.5-0.5h1.25z' + fill='#d73620' + /> + <path + d='m6 7.5c0 0.27734-0.22266 0.5-0.5 0.5s-0.5-0.22266-0.5-0.5v-1.75c0-0.27734 0.22266-0.5 0.5-0.5h1.75c0.27734 0 0.5 0.22266 0.5 0.5s-0.22266 0.5-0.5 0.5h-1.25z' + fill='#d73620' + /> + <path + d='m3.5 7.5c0 0.27734 0.22266 0.5 0.5 0.5s0.5-0.22266 0.5-0.5v-1.75c0-0.27734-0.22266-0.5-0.5-0.5h-1.75c-0.27734 0-0.5 0.22266-0.5 0.5s0.22266 0.5 0.5 0.5h1.25z' + fill='#d73620' + /> + <path + d='m6 2.5c0-0.27734-0.22266-0.5-0.5-0.5s-0.5 0.22266-0.5 0.5v1.75c0 0.27734 0.22266 0.5 0.5 0.5h1.75c0.27734 0 0.5-0.22266 0.5-0.5s-0.22266-0.5-0.5-0.5h-1.25z' + fill='#d73620' + /> + <path + d='m13.5 17.75c0-1.2422-0.67188-2.25-1.5-2.25s-1.5 1.0078-1.5 2.25 0.67188 2.25 1.5 2.25 1.5-1.0078 1.5-2.25z' + fill='#424242' + fillRule='evenodd' + /> + </svg> + </i> +) + +export const EmojiTwo = () => ( + <i className={iconCls}> + <svg> + <path + d='m24 12c0 6.6289-5.3711 12-12 12s-12-5.3711-12-12 5.3711-12 12-12 12 5.3711 12 12z' + fill='#ffe06e' + fillRule='evenodd' + /> + <path + d='m16 11 4.3047 2c-0.5625 1.2695-1.4297 1.6641-2.6016 1.1875s-1.7383-1.5391-1.7031-3.1875z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m8.3008 11-4.3008 2c0.5625 1.2695 1.4258 1.6641 2.6016 1.1875 1.1719-0.47656 1.7383-1.5391 1.6992-3.1875z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m7.2383 6.3359c0.089844-0.26172 0.375-0.39844 0.63672-0.30859s0.39844 0.375 0.30859 0.63672c-0.52344 1.5078-1.6172 2.2969-3.1836 2.2969-0.27734 0-0.5-0.22266-0.5-0.5 0-0.27734 0.22266-0.5 0.5-0.5 1.1367 0 1.8516-0.51563 2.2383-1.625z' + fill='#fdc959' + /> + <path + d='m16.973 6.3359c-0.089844-0.26172-0.375-0.39844-0.63672-0.30859-0.26172 0.089844-0.39844 0.375-0.30859 0.63672 0.52344 1.5078 1.6172 2.2969 3.1836 2.2969 0.27734 0 0.5-0.22266 0.5-0.5 0-0.27734-0.22266-0.5-0.5-0.5-1.1367 0-1.8516-0.51563-2.2383-1.625z' + fill='#fdc959' + /> + <path + d='m9.1914 18.57c-0.3125 0.10547-0.65234-0.066406-0.76172-0.37891-0.10547-0.3125 0.066406-0.65234 0.37891-0.76172 2.0977-0.70703 4.2305-0.70703 6.3789 0 0.31641 0.10547 0.48438 0.44141 0.38281 0.75781-0.10547 0.31641-0.44141 0.48438-0.75781 0.38281-1.9023-0.625-3.7695-0.625-5.6211 0z' + fill='#424242' + /> + </svg> + </i> +) + +export const EmojiThree = () => ( + <i className={iconCls}> + <svg> + <path + d='m24 12c0 6.6289-5.3711 12-12 12s-12-5.3711-12-12 5.3711-12 12-12 12 5.3711 12 12z' + fill='#ffe06e' + fillRule='evenodd' + /> + <path + d='m9.5 15h5c0.27734 0 0.5 0.22266 0.5 0.5s-0.22266 0.5-0.5 0.5h-5c-0.27734 0-0.5-0.22266-0.5-0.5s0.22266-0.5 0.5-0.5z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m6.875 7c0.75781 0 1.375 0.61719 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375s-1.375-0.61719-1.375-1.375v-2.25c0-0.75781 0.61719-1.375 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m17.117 7c0.75781 0 1.375 0.61719 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375-0.76172 0-1.375-0.61719-1.375-1.375v-2.25c0-0.75781 0.61328-1.375 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + </svg> + </i> +) + +export const EmojiFour = () => ( + <i className={iconCls}> + <svg> + <path + d='m24 12c0 6.6289-5.3711 12-12 12s-12-5.3711-12-12 5.3711-12 12-12 12 5.3711 12 12z' + fill='#ffe06e' + fillRule='evenodd' + /> + <path + d='m6.875 4.6836c0.75781 0 1.375 0.61328 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375s-1.375-0.61719-1.375-1.375v-2.25c0-0.76172 0.61719-1.375 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m17.117 4.6836c0.75781 0 1.375 0.61328 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375-0.76172 0-1.375-0.61719-1.375-1.375v-2.25c0-0.76172 0.61328-1.375 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m23.5 11.75c0-0.69141-1.3438-1.25-3-1.25s-3 0.55859-3 1.25 1.3438 1.25 3 1.25 3-0.55859 3-1.25z' + fill='#ffa5a5' + fillRule='evenodd' + /> + <path + d='m6.5 11.75c0-0.69141-1.3438-1.25-3-1.25s-3 0.55859-3 1.25 1.3438 1.25 3 1.25 3-0.55859 3-1.25z' + fill='#ffa5a5' + fillRule='evenodd' + /> + <path + d='m7.5938 13.328c-0.16016-0.22656-0.10938-0.53906 0.11719-0.69922 0.22266-0.16016 0.53516-0.10547 0.69531 0.11719 0.75781 1.0625 2.1406 1.7383 3.668 1.7383 1.5508 0 2.9453-0.69141 3.6953-1.7695 0.15625-0.22656 0.46875-0.28125 0.69531-0.125 0.22656 0.15625 0.28125 0.46875 0.125 0.69531-0.94141 1.3555-2.6484 2.1992-4.5156 2.1992-1.8438 0-3.5312-0.82422-4.4805-2.1562z' + fill='#424242' + /> + </svg> + </i> +) + +export const EmojiFive = () => ( + <i className={iconCls}> + <svg> + <defs> + <filter id='f' x='0%' y='0%' width='100%' height='100%'> + <feColorMatrix + in='SourceGraphic' + values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0' + /> + </filter> + <mask id='b'> + <g filter='url(#f)'> + <use xlinkHref='#c' /> + </g> + </mask> + <clipPath id='e'> + <rect width='24' height='24' /> + </clipPath> + <g id='a' clipPath='url(#e)'> + <path + d='m18.5 19c0-2.2109-2.9102-4-6.5-4s-6.5 1.7891-6.5 4 2.9102 4 6.5 4 6.5-1.7891 6.5-4z' + fill='#ff8686' + fillRule='evenodd' + /> + </g> + </defs> + <path + d='m24 12c0 6.6289-5.3711 12-12 12s-12-5.3711-12-12 5.3711-12 12-12 12 5.3711 12 12z' + fill='#ffe06e' + fillRule='evenodd' + /> + <use xlinkHref='#d' /> + <path + d='m6.875 4.6836c0.75781 0 1.375 0.61328 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375s-1.375-0.61719-1.375-1.375v-2.25c0-0.76172 0.61719-1.375 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m17.117 4.6836c0.75781 0 1.375 0.61328 1.375 1.375v2.25c0 0.75781-0.61719 1.375-1.375 1.375-0.76172 0-1.375-0.61719-1.375-1.375v-2.25c0-0.76172 0.61328-1.375 1.375-1.375z' + fill='#424242' + fillRule='evenodd' + /> + <path + d='m23.5 11.25c0-0.69141-1.3438-1.25-3-1.25s-3 0.55859-3 1.25 1.3438 1.25 3 1.25 3-0.55859 3-1.25z' + fill='#ffa5a5' + fillRule='evenodd' + /> + <path + d='m6.5 11.25c0-0.69141-1.3438-1.25-3-1.25s-3 0.55859-3 1.25 1.3438 1.25 3 1.25 3-0.55859 3-1.25z' + fill='#ffa5a5' + fillRule='evenodd' + /> + <path + d='m9.082 3.0859c0.10156 0.25781-0.027343 0.54687-0.28516 0.64844-0.25781 0.097656-0.54688-0.027344-0.64844-0.28516-0.22266-0.57031-0.77344-0.95312-1.3945-0.95312-0.61719 0-1.168 0.375-1.3945 0.9375-0.10156 0.25781-0.39453 0.37891-0.64844 0.27734-0.25781-0.10547-0.38281-0.39453-0.27734-0.65234 0.37891-0.9375 1.2891-1.5625 2.3203-1.5625 1.0352 0 1.957 0.63672 2.3281 1.5898z' + fill='#fdc959' + /> + <path + d='m19.582 3.0859c0.10156 0.25781-0.027343 0.54687-0.28516 0.64844-0.25781 0.097656-0.54688-0.027344-0.64844-0.28516-0.22266-0.57031-0.77344-0.95312-1.3945-0.95312-0.61719 0-1.168 0.375-1.3945 0.9375-0.10156 0.25781-0.39453 0.37891-0.64844 0.27734-0.25781-0.10547-0.38281-0.39453-0.27734-0.65234 0.37891-0.9375 1.2891-1.5625 2.3203-1.5625 1.0352 0 1.957 0.63672 2.3281 1.5898z' + fill='#fdc959' + /> + <path + d='m8 12.523c1.2266 0.30469 2.5781 0.47656 4 0.47656s2.7734-0.17188 4-0.47656c-0.007812 3.8555-1.7969 6.9766-4 6.9766s-3.9922-3.1211-4-6.9766z' + fill='#424242' + fillRule='evenodd' + /> + <use mask='url(#b)' xlinkHref='#a' /> + </svg> + </i> +) diff --git a/components/rate/Rate.js b/components/rate/Rate.js new file mode 100644 index 000000000..35e7fcc9b --- /dev/null +++ b/components/rate/Rate.js @@ -0,0 +1,153 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import * as Icons from './Icons' +import ToolTip from '../tooltip' + +class Rate extends Component { + static propTypes = { + useEmoji: PropTypes.bool, + allowClear: PropTypes.bool, + allowHalf: PropTypes.bool, + className: PropTypes.string, + defaultValue: PropTypes.number, + disabled: PropTypes.bool, + style: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + tooltips: PropTypes.arrayOf(PropTypes.string), + value: PropTypes.number, + count: PropTypes.number, + onChange: PropTypes.func + } + static defaultProps = { + allowClear: true, + value: 0, + defaultValue: 0, + count: 5, + prefixCls: 'hi-rate', + tooltips: [], + onChange: () => {} + } + state = { + value: this.props.value || this.props.defaultValue, + hoverValue: 0 + } + renderIcon = idx => { + const { useEmoji, allowHalf, disabled } = this.props + const { value, hoverValue } = this.state + let currentValue = hoverValue || value + if (!allowHalf || useEmoji) { + currentValue = Math.ceil(currentValue) + } + return ( + <Icon {...{ value: idx, currentValue, disabled, useEmoji, allowHalf }} /> + ) + } + handleIconClick = value => { + const { allowHalf, allowClear, onChange, disabled } = this.props + if (disabled) { + return + } + if (!allowHalf) { + value = Math.ceil(value) + } + if (value === this.state.value && allowClear) { + this.setState({ + value: this.props.defaultValue + }) + return + } + onChange && onChange(value) + this.setState({ value }) + } + handleIconEnter = hoverValue => { + if (this.props.disabled) { + return + } + this.setState({ hoverValue }) + } + handleIconLeave = () => { + if (this.props.disabled) { + return + } + this.setState({ hoverValue: 0 }) + } + render () { + const { + className, + style, + count, + useEmoji, + prefixCls, + tooltips, + disabled + } = this.props + const iconCount = Math.ceil(useEmoji ? 5 : count) + const iconHalfCls = `${prefixCls}__star__half` + const starCls = classnames(`${prefixCls}__star`, { + [`${prefixCls}__star--disabled`]: disabled + }) + return ( + <ul + className={classnames(prefixCls, className)} + style={style} + onMouseLeave={this.handleIconLeave} + > + {Array(iconCount) + .fill() + .map((_, idx) => { + const value = idx + 1 + const halfValue = idx + 0.5 + return ( + <ToolTipWrapper title={tooltips[idx]} key={idx}> + <li className={starCls}> + <div + className={classnames(iconHalfCls, `${iconHalfCls}--left`)} + onMouseEnter={() => this.handleIconEnter(halfValue)} + onMouseMove={() => this.handleIconEnter(halfValue)} + onClick={() => this.handleIconClick(halfValue)} + /> + <div + className={classnames(iconHalfCls, `${iconHalfCls}--right`)} + onMouseEnter={() => this.handleIconEnter(value)} + onMouseMove={() => this.handleIconEnter(value)} + onClick={() => this.handleIconClick(value)} + /> + {this.renderIcon(value)} + </li> + </ToolTipWrapper> + ) + })} + </ul> + ) + } +} + +function ToolTipWrapper ({ children, title }) { + return title ? <ToolTip title={title}>{children}</ToolTip> : children +} + +function Icon ({ value, currentValue, disabled, useEmoji, allowHalf }) { + if (useEmoji) { + const Emojis = [ + Icons.EmojiOne, + Icons.EmojiTwo, + Icons.EmojiThree, + Icons.EmojiFour, + Icons.EmojiFive + ] + if (value <= currentValue) { + return React.createElement(Emojis[currentValue - 1]) + } else { + return <Icons.EmojiDefault /> + } + } + if (value <= currentValue) { + return <Icons.StarActive /> + } else if (value === currentValue + 0.5 && allowHalf) { + return disabled ? <Icons.StarHalfReadonly /> : <Icons.StarHalfActive /> + } else { + return disabled ? <Icons.StarDisable /> : <Icons.StarDefault /> + } +} + +export default Rate diff --git a/components/rate/__test__/index.test.js b/components/rate/__test__/index.test.js new file mode 100644 index 000000000..11b7dc4cf --- /dev/null +++ b/components/rate/__test__/index.test.js @@ -0,0 +1,37 @@ +import React from 'react' +import { mount } from 'enzyme' +import Rate from '..' + +let wrapper +const changeCallback = jest.fn(value => value) + +describe('Rate', () => { + afterEach(() => { + wrapper && wrapper.unmount() + changeCallback.mockClear() + }) + + it('basic usage', () => { + wrapper = mount(<Rate defaultValue={3} allowHalf onChange={changeCallback} />) + expect(wrapper.state().value).toEqual(3) + wrapper.find('.hi-rate__star__half').at(2).simulate('click') + expect(wrapper.state().value).toEqual(1.5) + expect(changeCallback).toHaveBeenCalledTimes(1) + }) + + it('custom amount, disabled', () => { + wrapper = mount(<Rate count={10} disabled defaultValue={10} />) + expect(wrapper.find('.hi-rate__star')).toHaveLength(10) + wrapper.find('.hi-rate__star__half').at(3).simulate('click') + expect(wrapper.state().value).toEqual(10) + }) + + it('use emoji', () => { + wrapper = mount(<Rate count={10} allowHalf useEmoji defaultValue={3} />) + // count not work when use emoji + expect(wrapper.find('.hi-rate__star')).toHaveLength(5) + // half star not work whem use emoji + wrapper.find('.hi-rate__star__half').at(3).simulate('click') + expect(wrapper.state().value).toEqual(2) + }) +}) diff --git a/components/rate/index.js b/components/rate/index.js new file mode 100644 index 000000000..a837784b6 --- /dev/null +++ b/components/rate/index.js @@ -0,0 +1,4 @@ +import './style/index' +import Rate from './Rate' + +export default Rate diff --git a/components/rate/style/index.js b/components/rate/style/index.js new file mode 100644 index 000000000..63810a681 --- /dev/null +++ b/components/rate/style/index.js @@ -0,0 +1 @@ +import './index.scss' diff --git a/components/rate/style/index.scss b/components/rate/style/index.scss new file mode 100644 index 000000000..6892ae7ab --- /dev/null +++ b/components/rate/style/index.scss @@ -0,0 +1,60 @@ +.hi-rate { + $h: 24px; + $w: 24px; + + list-style: none; + display: inline-block; + width: fit-content; + padding-left: 0 !important; + margin: 0 !important; + + &__star { + cursor: pointer; + position: relative; + display: inline-block; + transition: 0.3s ease; + margin: 0 6px !important; + width: $w; + height: $h; + + &:active, + &:hover { + transform: scale(1.2); + } + + &--disabled { + cursor: not-allowed; + } + + &__half { + height: 100%; + width: 50%; + display: inline-block; + position: absolute; + z-index: 1; + + &--wrapper { + position: absolute; + top: 0; + left: 0; + height: $h; + width: $w; + } + + &--right { + right: 0; + } + } + } + + &__icon { + display: inline-block; + width: 100%; + height: 100%; + + svg { + width: 100%; + height: 100%; + } + } +} diff --git a/components/select/style/select-input.scss b/components/select/style/select-input.scss index aa36efde2..bdb52300a 100644 --- a/components/select/style/select-input.scss +++ b/components/select/style/select-input.scss @@ -164,9 +164,11 @@ flex: auto; margin-bottom: -4px; overflow: hidden; + &.hi-select__input-items--all { flex-wrap: wrap; } + &--left { display: flex; align-items: center; @@ -178,6 +180,7 @@ background-color: #f2f2f2; border-radius: 10px; box-sizing: border-box; + &-count { } diff --git a/components/upload/style/index.scss b/components/upload/style/index.scss index 9ad458241..b1dae4bb9 100644 --- a/components/upload/style/index.scss +++ b/components/upload/style/index.scss @@ -140,6 +140,7 @@ } } } + .hasborder { border: 1px dotted #bcbcbc; } @@ -585,20 +586,24 @@ &__mask--bottom { position: absolute; background: #000; - opacity: .2; + opacity: 0.2; z-index: 1; } + &__mask--top { top: 0; left: 0; right: 0; } + &__mask--left { left: 0; } + &__mask--right { right: 0; } + &__mask--bottom { bottom: 0; left: 0; diff --git a/docs/en-US/components/rate.md b/docs/en-US/components/rate.md new file mode 100644 index 000000000..1764a2b53 --- /dev/null +++ b/docs/en-US/components/rate.md @@ -0,0 +1,112 @@ +## Rate + +The rate component. + +### Basic + +:::demo + +```js +render() { + return ( + <Form labelWidth="120" labelPosition="left"> + <FormItem label="Basic"> + <Rate defaultValue={3} /> + </FormItem> + <FormItem label="Half star"> + <Rate allowHalf defaultValue={2.5} /> + </FormItem> + </Form> + ) +} +``` + +::: + +### Advanced + +:::demo + +```js +constructor() { + super() + this.state = { + value: 3 + } + this.tooltips = ['terrible', 'bad', 'normal', 'good', 'wonderful'] +} + +render() { + const { value } = this.state + return ( + <Form labelWidth="120" labelPosition="left"> + <FormItem label="Custom amount"> + <Rate count={10} allowHalf defaultValue={9.5} /> + </FormItem> + <FormItem label="Tool tip"> + <Rate tooltips={this.tooltips} onChange={value => { + this.setState({value}) + }} value={value} /> + </FormItem> + <FormItem label="Disabled"> + <Rate allowHalf disabled defaultValue={2.5} /> + </FormItem> + <FormItem label="Forbid clear"> + <Rate allowHalf allowClear={false} defaultValue={2.5} /> + </FormItem> + </Form> + ) +} +``` + +::: + +### Use emoji + +:::demo + +```js +constructor() { + super() + this.tooltips = ['terrible', 'bad', 'normal', 'good', 'wonderful'] +} + +render() { + return ( + <Form labelWidth="120" labelPosition="left"> + <Alert type="warning" message="Can not use half start and star count when use emoji" closeable={false} /> + <br /> + <FormItem label="Basic"> + <Rate defaultValue={3} useEmoji /> + </FormItem> + <FormItem label="Tool tip"> + <Rate defaultValue={3} useEmoji tooltips={this.tooltips} /> + </FormItem> + <FormItem label="Disabled"> + <Rate defaultValue={3} useEmoji disabled /> + </FormItem> + <FormItem label="Forbid clear"> + <Rate defaultValue={3} useEmoji allowClear={false} /> + </FormItem> + </Form> + ) +} +``` + +::: + +### Rate Attributes + +| Property | Description | Type | Options | Default | +| ------------ | --------------------------------------- | ----------------------- | ------- | ------- | +| allowClear | whether to allow clear when click again | boolean | - | true | +| allowHalf | whether to allow semi selection | boolean | - | true | +| useEmoji | whether to use emoji | boolean | - | false | +| className | custom class name of rate | string | - | - | +| count | star count, not work whem use emoji | number | - | 5 | +| defaultValue | default value | number | - | 0 | +| disabled | read only, unable to interact | boolean | - | false | +| style | custom style object of rate | object | - | - | +| tooltips | custom tooltip by each character | string[] | - | - | +| value | current value | number | - | - | +| onChange | callback when select value | (value: number) => void | - | - | diff --git a/docs/zh-CN/components/rate.md b/docs/zh-CN/components/rate.md new file mode 100644 index 000000000..88fff1695 --- /dev/null +++ b/docs/zh-CN/components/rate.md @@ -0,0 +1,112 @@ +## 评分 + +评分组件。 + +### 基础 + +:::demo + +```js +render() { + return ( + <Form labelWidth="80px" labelPosition="left"> + <FormItem label="基础"> + <Rate defaultValue={3} /> + </FormItem> + <FormItem label="半星"> + <Rate allowHalf defaultValue={2.5} /> + </FormItem> + </Form> + ) +} +``` + +::: + +### 高级 + +:::demo + +```js +constructor() { + super() + this.state = { + value: 3 + } + this.tooltips = ['terrible', 'bad', 'normal', 'good', 'wonderful'] +} + +render() { + const { value } = this.state + return ( + <Form labelWidth="80px" labelPosition="left"> + <FormItem label="任意数量"> + <Rate count={10} allowHalf defaultValue={9.5} /> + </FormItem> + <FormItem label="提示"> + <Rate tooltips={this.tooltips} onChange={value => { + this.setState({value}) + }} value={value} /> + </FormItem> + <FormItem label="禁用"> + <Rate allowHalf disabled defaultValue={2.5} /> + </FormItem> + <FormItem label="禁止清除"> + <Rate allowHalf allowClear={false} defaultValue={2.5} /> + </FormItem> + </Form> + ) +} +``` + +::: + +### 使用表情 + +:::demo + +```js +constructor() { + super() + this.tooltips = ['terrible', 'bad', 'normal', 'good', 'wonderful'] +} + +render() { + return ( + <Form labelWidth="80" labelPosition="left"> + <Alert type="warning" message="使用表情后将不可自定义数量,不可定义半星" closeable={false} /> + <br /> + <FormItem label="基础"> + <Rate defaultValue={3} useEmoji /> + </FormItem> + <FormItem label="提示"> + <Rate defaultValue={3} useEmoji tooltips={this.tooltips} /> + </FormItem> + <FormItem label="禁用"> + <Rate defaultValue={3} useEmoji disabled /> + </FormItem> + <FormItem label="禁止清除"> + <Rate defaultValue={3} useEmoji allowClear={false} /> + </FormItem> + </Form> + ) +} +``` + +::: + +### Rate Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------------ | ------------------------- | ----------------------- | ------ | ------ | +| allowClear | 是否允许再次点击后清除 | boolean | - | true | +| allowHalf | 是否允许半选 | boolean | - | true | +| useEmoji | 是否使用表情 | boolean | - | false | +| className | 自定义类名 | string | - | - | +| count | star 数量,Emoji 时不可用 | number | - | 5 | +| defaultValue | 默认值 | number | - | 0 | +| disabled | 只读,无法进行交互 | boolean | - | false | +| style | 自定义样式对象 | object | - | - | +| tooltips | 自定义每项的提示信息 | string[] | - | - | +| value | 当前数,受控值 | number | - | - | +| onChange | 选择时的回调 | (value: number) => void | - | - | diff --git a/site/locales/en-US.js b/site/locales/en-US.js index 5458f5b69..21686c02a 100644 --- a/site/locales/en-US.js +++ b/site/locales/en-US.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper', icon: 'Icon', progress: 'Progress', - card: 'Card' + card: 'Card', + rate: 'Rate' }, designs: { 'design-patterns': '设计模式', diff --git a/site/locales/zh-CN.js b/site/locales/zh-CN.js index becbb4d6a..3cb736d17 100755 --- a/site/locales/zh-CN.js +++ b/site/locales/zh-CN.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper 步骤', icon: 'Icon 图标', progress: 'Progress 进度条', - card: 'Card 卡片' + card: 'Card 卡片', + rate: 'Rate 评分' }, designs: { 'design-patterns': '设计模式', diff --git a/site/pages/components/index.js b/site/pages/components/index.js index 94bec3c08..df1d2a140 100755 --- a/site/pages/components/index.js +++ b/site/pages/components/index.js @@ -32,8 +32,8 @@ export default { 'checkbox': require('./checkbox'), 'date-picker': require('./date-picker'), 'time-picker': require('./time-picker'), - 'upload': require('./upload') - + 'upload': require('./upload'), + 'rate': require('./rate') }, 'group-data': { 'table': require('./table'), diff --git a/site/pages/components/rate/index.js b/site/pages/components/rate/index.js new file mode 100644 index 000000000..4d5f6e9fa --- /dev/null +++ b/site/pages/components/rate/index.js @@ -0,0 +1,9 @@ +import Markdown from '../../../../libs/markdown' + +class Rate extends Markdown { + document (locale) { + return require(`../../../../docs/${locale}/components/rate.md`) + } +} + +export default Rate From 36bba93a1562a35c012138320f8d30e60702e648 Mon Sep 17 00:00:00 2001 From: bougiel <1742070326@qq.com> Date: Thu, 16 May 2019 17:40:10 +0800 Subject: [PATCH 074/112] fix: copywirtting error --- docs/en-US/components/rate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en-US/components/rate.md b/docs/en-US/components/rate.md index 1764a2b53..4206d7051 100644 --- a/docs/en-US/components/rate.md +++ b/docs/en-US/components/rate.md @@ -74,7 +74,7 @@ constructor() { render() { return ( <Form labelWidth="120" labelPosition="left"> - <Alert type="warning" message="Can not use half start and star count when use emoji" closeable={false} /> + <Alert type="warning" message="Can not use half star and star count when use emoji" closeable={false} /> <br /> <FormItem label="Basic"> <Rate defaultValue={3} useEmoji /> From 85ba8d2d479a3081266d363d05da5a8ed1f810d2 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 16 May 2019 18:27:58 +0800 Subject: [PATCH 075/112] timeline --- components/index.js | 1 + components/timeline/index.js | 155 +++++++++++++++++++++ components/timeline/style/index.js | 1 + components/timeline/style/index.scss | 176 ++++++++++++++++++++++++ docs/zh-CN/components/timeline.md | 2 + site/locales/en-US.js | 3 +- site/locales/zh-CN.js | 3 +- site/pages/components/index.js | 3 +- site/pages/components/timeline/index.js | 9 ++ 9 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 components/timeline/index.js create mode 100644 components/timeline/style/index.js create mode 100755 components/timeline/style/index.scss create mode 100644 site/pages/components/timeline/index.js diff --git a/components/index.js b/components/index.js index c07a68149..d8b5661e0 100755 --- a/components/index.js +++ b/components/index.js @@ -37,4 +37,5 @@ export { default as Ficon } from './ficon' export { default as Icon } from './icon' export { default as Progress } from './progress' export { default as Card } from './card' +export { default as Timeline } from './timeline' export { ThemeContext, LocaleContext } from './context' diff --git a/components/timeline/index.js b/components/timeline/index.js new file mode 100644 index 000000000..ede1e28b3 --- /dev/null +++ b/components/timeline/index.js @@ -0,0 +1,155 @@ +import React, { Component } from 'react' +import Icon from '../icon' +import classNames from 'classnames' +import './style/index' + +const Time = (props) => { + return ( + <div className='hi-timeline__time'> + <div style={props.groupTitle ? {marginTop: -20} : {}} className='hi-timeline__group-title'>{props.groupTitle}</div> + {props.item.timestamp} + { + <div className='hi-timeline__extra-time'>{props.item.extraTime}</div> + } + </div> + ) +} +class Timeline extends Component { + constructor (props) { + super(props) + this.datas = [{ + groupTitle: '1月', + children: [{ + dot: <Icon name='circle' style={{fontSize: 16}} />, // 自定节点图标内容 + title: '管理层例会1', // 标题 + description: '毕加索会议室 B2层 可提前预定,毕加索会议室 B2层 可提前预定,毕加索会议室 B2层 可提前预定', // 二级描述 + timestamp: '9:00', // 时间节点 + extraTime: '02-25' + }, { + dot: 'circle', // 自定节点图标内容 + title: '管理层例会2', // 标题 + description: '毕加索会议室 B2层 可提前预定', // 二级描述 + timestamp: '10:00'// 时间节点 + }] + }, { + groupTitle: '2月', + children: [{ + dot: 'circle', // 自定节点图标内容 + title: '管理层例会3', // 标题 + description: '毕加索会议室 B2层 可提前预定', // 二级描述 + timestamp: '12:00', // 时间节点 + folding: true, // 是否可折叠 如果为 false , 将不显示折叠图标 及 忽略下面的 children 也可通过 groupTitle 来区分两种 children + children: [{ + title: '子项', + description: '毕加索会议室 B2层 可提前预定' + }, { + title: '子项', + description: '毕加索会议室 B2层 可提前预定' + }] + }, { + dot: <Icon name='circle' style={{fontSize: 16, color: 'red'}} />, // 自定节点图标内容 + title: '管理层例会4', // 标题 + description: '毕加索会议室 B2层 可提前预定', // 二级描述 + timestamp: '12:00'// 时间节点 + }] + }, { + groupTitle: '3月', + children: [{ + dot: 'circle', // 自定节点图标内容 + title: '管理层例会5', // 标题 + description: '毕加索会议室 B2层 可提前预定', // 二级描述 + timestamp: '11:00', // 时间节点 + extraTime: '11-25' + }, { + dot: 'circle', // 自定节点图标内容 + title: '管理层例会6', // 标题 + description: '毕加索会议室 B2层 可提前预定', // 二级描述 + timestamp: '12:00'// 时间节点 + }] + }] + } + /** + * 渲染每一项 + * @param {*} item 数据 + * @param {*} groupTitle 分组标题 + * @param {*} originIndex 是否是第一组 + * @param {*} isEnd 是否是当前分组的结束 + * @param {*} index 当前索引 + */ + renderItem (item, groupTitle, originIndex, isEnd, index) { + const { layout } = this.props + let style = {} + if (groupTitle) { + if (originIndex === 0) { + style = {marginTop: 20} + } + if (isEnd) { + style = {paddingBottom: 20} + } + } + const dot = item.dot + let dotEl = <div className='hi-timeline__dot' /> + if (dot && typeof dot === 'object') { + dotEl = <div className='hi-timeline__dot hi-timeline__dot--custom' > + {dot} + </div> + } + return <li className='hi-timeline__item' style={style} key={index}> + { + layout === 'normal' && <Time item={item} groupTitle={index === 0 && groupTitle} /> + } + <div className='hi-timeline__row'> + { + layout === 'cross' && <Time item={item} /> + } + <div className='hi-timeline__content-container'> + <div className='hi-timeline__title'>{item.title}</div> + <div className='hi-timeline__desc'>{item.description}</div> + { + layout === 'right' && ( + <div className='hi-timeline__time--extra'>{item.timestamp}</div> + ) + } + </div> + </div> + <div className='hi-timeline__line' /> + {dotEl} + </li> + } + /** + * 渲染列表,被递归调用 + * @param {Array} datas 数据 + * @param {String | React.ReactNode} groupTitle 分组标题 + * @param {Number} originIndex 原始索引,用于判断是否为第一个分组标题 + */ + renderItems (datas, groupTitle, originIndex) { + return datas.map((item, index) => { + const { groupTitle: _groupTitle, folding } = item + if (folding && item.children.length > 0) { + return this.renderItems(item.children) + } + if (_groupTitle) { + return this.renderItems(item.children, _groupTitle, index) + } + return this.renderItem(item, groupTitle, originIndex, index === datas.length - 1, index) + }) + } + render () { + const { layout } = this.props + const rootCls = classNames( + 'hi-timeline', + `hi-timeline--${layout}` + ) + return <div className={rootCls}> + <ul> + { + this.renderItems(this.datas) + } + </ul> + </div> + } +} +Timeline.defaultProps = { + layout: 'normal' +} +export default Timeline diff --git a/components/timeline/style/index.js b/components/timeline/style/index.js new file mode 100644 index 000000000..63810a681 --- /dev/null +++ b/components/timeline/style/index.js @@ -0,0 +1 @@ +import './index.scss' diff --git a/components/timeline/style/index.scss b/components/timeline/style/index.scss new file mode 100755 index 000000000..26fb671dd --- /dev/null +++ b/components/timeline/style/index.scss @@ -0,0 +1,176 @@ +@import '@hi-ui/core-css/index.scss'; + +.hi-timeline { + position: relative; + padding: 8px; + + ul { + padding-left: 0; + } + + &__item { + margin: 0; + padding: 0; + list-style: none; + display: flex; + position: relative; + padding-bottom: 20px; + + &:last-child { + .hi-timeline__line { + display: none; + } + } + } + + &__row { + margin-left: 40px; + display: flex; + } + + &__content-container { + display: inline-block; + } + + &__time { + flex: 0 0 36px; + text-align: right; + display: inline-block; + vertical-align: top; + + &--extra { + width: auto; + text-align: left; + font-size: 12px; + color: #999; + } + } + + &__extra-time { + font-size: 12px; + color: #666; + margin-top: 2px; + } + + &__title { + font-size: 14px; + color: #333; + text-align: left; + } + + &__desc { + margin-top: 2px; + font-size: 12px; + color: #666; + } + + &__line { + position: absolute; + top: 0.75em; + height: 100%; + width: 2px; + background: rgba(242, 242, 242, 1); + left: 55px; + // border-left: 2px solid #e8e8e8; + } + + &__dot { + width: 8px; + height: 8px; + border: 2px solid #4284f5; + border-radius: 50%; + box-sizing: border-box; + position: absolute; + top: 0.5em; + left: 52px; + + &--custom { + border: none; + border-radius: 0; + font-size: 12px; + width: auto; + height: auto; + // left: 4px; + background: #fff; + transform: translate(-50%, -30%); + } + } + + &--right { + .hi-timeline__line { + left: 3px; + } + + .hi-timeline__dot { + left: 0; + + &--custom { + left: 4px; + } + } + } + + &--normal { + .hi-timeline__dot { + &--custom { + left: 55px; + } + } + } + + &--cross { + .hi-timeline__row { + width: 50%; + } + + .hi-timeline__item:nth-child(even) { + .hi-timeline__row { + margin-left: -36px; + text-align: right; + display: inline-block; + + .hi-timeline__time { + float: right; + } + } + + .hi-timeline__content-container { + margin-right: 36px; + } + + .hi-timeline__title { + text-align: right; + } + } + + .hi-timeline__item:nth-child(odd) { + .hi-timeline__row { + margin-left: 50%; + padding-left: 36px; + } + + .hi-timeline__content-container { + margin-left: 36px; + } + } + + .hi-timeline__line { + left: 50%; + transform: translateX(-1px); + } + + .hi-timeline__dot { + left: 50%; + transform: translateX(-4px); + + &--custom { + transform: translate(-50%, -30%); + } + } + } + + &__group-title { + font-size: 12px; + color: #666; + } +} diff --git a/docs/zh-CN/components/timeline.md b/docs/zh-CN/components/timeline.md index 40ed99c13..68ddec33d 100755 --- a/docs/zh-CN/components/timeline.md +++ b/docs/zh-CN/components/timeline.md @@ -12,6 +12,8 @@ render () { return ( <div> <Timeline /> + <Timeline layout='right'/> + <Timeline layout='cross'/> </div> ) } diff --git a/site/locales/en-US.js b/site/locales/en-US.js index 5458f5b69..25a18ca08 100644 --- a/site/locales/en-US.js +++ b/site/locales/en-US.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper', icon: 'Icon', progress: 'Progress', - card: 'Card' + card: 'Card', + timeline: 'Timeline' }, designs: { 'design-patterns': '设计模式', diff --git a/site/locales/zh-CN.js b/site/locales/zh-CN.js index becbb4d6a..9504a1f47 100755 --- a/site/locales/zh-CN.js +++ b/site/locales/zh-CN.js @@ -43,7 +43,8 @@ module.exports = { stepper: 'Stepper 步骤', icon: 'Icon 图标', progress: 'Progress 进度条', - card: 'Card 卡片' + card: 'Card 卡片', + timeline: 'Timeline 时间轴' }, designs: { 'design-patterns': '设计模式', diff --git a/site/pages/components/index.js b/site/pages/components/index.js index 94bec3c08..39c9eb776 100755 --- a/site/pages/components/index.js +++ b/site/pages/components/index.js @@ -43,7 +43,8 @@ export default { 'tooltip': require('./tooltip'), 'popover': require('./popover'), 'progress': require('./progress'), - 'card': require('./card') + 'card': require('./card'), + 'timeline': require('./timeline') }, 'group-tips': { 'modal': require('./modal'), diff --git a/site/pages/components/timeline/index.js b/site/pages/components/timeline/index.js new file mode 100644 index 000000000..b65ad7435 --- /dev/null +++ b/site/pages/components/timeline/index.js @@ -0,0 +1,9 @@ +import Markdown from '../../../../libs/markdown' + +class Timeline extends Markdown { + document (locale) { + return require(`../../../../docs/${locale}/components/timeline.md`) + } +} + +export default Timeline From f1c1e9c3c6441e8a0066d66dd22f440a96f0f593 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 16 May 2019 19:18:11 +0800 Subject: [PATCH 076/112] fix error --- components/switch/index.js | 3 --- docs/zh-CN/components/switch.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/components/switch/index.js b/components/switch/index.js index 931f5edff..3d77c1641 100755 --- a/components/switch/index.js +++ b/components/switch/index.js @@ -5,9 +5,6 @@ import Provider from '../context' import './style/index' class Switch extends Component { - componentDidMount () { - - } constructor (props) { super(props) this.state = { diff --git a/docs/zh-CN/components/switch.md b/docs/zh-CN/components/switch.md index ca6c93f3b..09413ef18 100755 --- a/docs/zh-CN/components/switch.md +++ b/docs/zh-CN/components/switch.md @@ -52,6 +52,6 @@ render () { ### Switch Events -| 参数 | 说明 | 回调参数 +| 参数 | 说明 | 回调参数 | | ------- | ------- | ------- | | onChange | 切换的回调函数 | 当前状态 | From 5c84fc17593a50186dde1eca3fb1b3447511bc5c Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Thu, 16 May 2019 19:48:41 +0800 Subject: [PATCH 077/112] fix --- components/lib/withDragDropContext.js | 2 +- components/transfer/Item.js | 9 --------- docs/zh-CN/components/transfer.md | 10 +++++----- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/components/lib/withDragDropContext.js b/components/lib/withDragDropContext.js index d67fb3ba0..4eaa72da2 100644 --- a/components/lib/withDragDropContext.js +++ b/components/lib/withDragDropContext.js @@ -1,4 +1,4 @@ -import {DragDropContext} from 'react-dnd' +import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' export default DragDropContext(HTML5Backend) diff --git a/components/transfer/Item.js b/components/transfer/Item.js index 09fb9090c..015b0f46f 100644 --- a/components/transfer/Item.js +++ b/components/transfer/Item.js @@ -65,8 +65,6 @@ const source = { const target = { canDrop (props, monitor) { - // const { sourceItem } = monitor.getItem() - // const { item: targetItem } = props return true }, @@ -75,16 +73,11 @@ const target = { const { item: targetItem, removeTargetNode, move } = props move(sourceItem, targetItem) removeTargetNode() - // const { item } = monitor.getItem() - // const { id: dropId, contains } = props - - // props.move(item, dropId) }, hover (props, monitor, component) { if (monitor.isOver({ shallow: true })) { const { item: targetItem, setTargetNode, positionX, positionY, setPosition } = props const sourcePosition = monitor.getClientOffset() - // console.log(positionX, positionY, sourcePosition.x, sourcePosition.y) if (!(sourcePosition.x === positionX && sourcePosition.y === positionY)) { setPosition(sourcePosition.x, sourcePosition.y) setTargetNode(targetItem.id) @@ -97,7 +90,6 @@ const DragItem = DropTarget(TYPE, target, (connect, monitor) => ({ }))( DragSource(TYPE, source, (connect, monitor) => ({ connectDragSource: connect.dragSource(), - connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging() }))(Item) ) @@ -116,4 +108,3 @@ const HOCItem = ItemComponent => { } } export default HOCItem(Item) -// export default Item diff --git a/docs/zh-CN/components/transfer.md b/docs/zh-CN/components/transfer.md index 9ef8eba98..9c50a3af6 100755 --- a/docs/zh-CN/components/transfer.md +++ b/docs/zh-CN/components/transfer.md @@ -25,7 +25,7 @@ randomDatas () { } return arr } -onChange ( movedKeys) { +onChange (movedKeys) { this.setState({ targetKeys: movedKeys }) @@ -119,7 +119,7 @@ randomDatas () { } return arr } -onChange ( movedKeys) { +onChange (movedKeys) { this.setState({ targetKeys: movedKeys }) @@ -163,7 +163,7 @@ randomDatas () { } return arr } -onChange ( movedKeys) { +onChange (movedKeys) { this.setState({ targetKeys: movedKeys }) @@ -208,7 +208,7 @@ randomDatas () { } return arr } -onChange ( movedKeys) { +onChange (movedKeys) { this.setState({ targetKeys: movedKeys }) @@ -252,7 +252,7 @@ randomDatas () { } return arr } -onChange ( movedKeys) { +onChange (movedKeys) { this.setState({ targetKeys: movedKeys }) From 24ee5d6449334de09e4618b539d02388a7981b2e Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 16 May 2019 20:10:08 +0800 Subject: [PATCH 078/112] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20changelog=20?= =?UTF-8?q?=E5=92=8C=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 24 ++++++++++++++++++++---- package.json | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c05efec6f..bafe19a43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,31 @@ # 更新日志 +## 1.5.0-rc.4 + +- [新增:`<Transfer />` 穿梭框组件](#108) +- [新增:`<Rate />` 评分组件](#106) +- [新增:`<Switch />` 开关框组件](#107) +- [修复:`<Menu />` 在某些情况下抛出的 key 的警告问题](#215) + ## 1.5.0-rc.1 + - 新增:`<Card />` 组件 - 修改:`<Tree />`、`<Datepicker />` 组件,调整新的 UI,增加新的 API ## 1.4.1 + - 修复: `<Datepicker />` 快捷选择问题 - 修复: `<Cascader />` ref 引起的不能正确渲染的问题 - 修复: `<Cascader />` 面板不能正确关闭的问题 -- 修复: `<Upload />` 上传后的错误信息展示问题 -- 修复: `<Counter />` 、 `<Input />` value 值为字符串的异常问题 -- 修复: `<Navmenu />` 当只有一个标签时导致的渲染问题 +- 修复: `<Upload />` 上传后的错误信息展示问题 +- 修复: `<Counter />` 、 `<Input />` value 值为字符串的异常问题 +- 修复: `<Navmenu />` 当只有一个标签时导致的渲染问题 ## 1.4.0 + - 新增:部分组件测试用例 - 新增:`<Tabs />`组件 -- 修改:`<Datepicker />` 可以通过placeholder属性传入默认占位符 +- 修改:`<Datepicker />` 可以通过 placeholder 属性传入默认占位符 - 修复: `<Checkbox />` 组件在被销毁时,没有清空已选择项的问题 - 修改: `<Pagination />` 组件 - 新增两种简单分页样式,分别通过 `simple` | `shrink` 属性控制 @@ -32,6 +42,7 @@ - Counter 组件传入的 value/step/min/max 为字符串引起的问题 ## 1.3.5 + - 新增: `<Datepicker />` 组件可通过 placeholder 自定义占位符 - 新增:`<Select />` 组件支持 JSONP 的 origin 配置 - 新增:`<Table />` 组件功能 @@ -39,6 +50,7 @@ - 可通过 `bordered` 属性指定表格是否显示线框 ## 1.3.0 + - 新增:`<Select />` 异步请求时增加 loading; - 修改:优化 `<Form />` 组件横排时的错误文案显示; - 修改:`<Upload />` 组件; @@ -58,6 +70,7 @@ - 修改:修复 `<Pagination />` 组件页岁数过多时的样式问题。 ## 1.2.0 + - 新增:`<Button />` 组件增加图标设置; - 新增:`<Table />` 组件支持外部设置、求和等操作; - 新增:`<Input />` 组件增加支持前、后缀元素。 @@ -70,10 +83,12 @@ - 修改:重新设定所有浮层组件 z-index。 ## 1.1.1 + - 修改:修复 `<Button />` 在使用旧版 api 时的样式兼容; - 修改:修复 `<Checkbox />` 占据整行及点击区域的 bug。 ## 1.1.0 + - 修改:按钮组件 `<Button />` API 调整,向前兼容; - 修改:修改 `<Notification />` 回调改为非必填项,关闭按钮可配置; - 修改:修复 `<Input />` 组件类型为 `textarea` 时高度不自适应的 bug; @@ -83,6 +98,7 @@ - 文档:增加多语言、配色切换文档。 ## 1.0.3 + - 新增:级联选择器 `<Cascader />` 组件; - 新增:进度条 `<Progress />` 组件; - 新增:`<Tree />` 组件增加点击事件; diff --git a/package.json b/package.json index 7f2bdf42f..d4a456fef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hi-ui/hiui", - "version": "1.5.0-rc.3", + "version": "1.5.0-rc.4", "description": "HIUI for React", "scripts": { "test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'", From 8c5dcabdbbcf44a1e26610a2b0cc6645a3326863 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 16 May 2019 20:21:03 +0800 Subject: [PATCH 079/112] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bafe19a43..6de8ae60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ ## 1.5.0-rc.4 -- [新增:`<Transfer />` 穿梭框组件](#108) -- [新增:`<Rate />` 评分组件](#106) -- [新增:`<Switch />` 开关框组件](#107) -- [修复:`<Menu />` 在某些情况下抛出的 key 的警告问题](#215) +- 新增:`<Transfer />` 穿梭框组件 +- 新增:`<Rate />` 评分组件 +- 新增:`<Switch />` 开关组件 +- 修复:`<Menu />` 组件在某些情况下抛出的 key 的警告问题 ## 1.5.0-rc.1 From 0b3b05dc38793d23867bfd640beab8ce8df35a1d Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 16 May 2019 20:36:17 +0800 Subject: [PATCH 080/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20issue=20=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6de8ae60c..b106e0559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ ## 1.5.0-rc.4 -- 新增:`<Transfer />` 穿梭框组件 -- 新增:`<Rate />` 评分组件 -- 新增:`<Switch />` 开关组件 -- 修复:`<Menu />` 组件在某些情况下抛出的 key 的警告问题 +- 新增:`<Transfer />` 穿梭框组件 [#108](https://github.com/XiaoMi/hiui/issues/108) +- 新增:`<Rate />` 评分组件 [#106](https://github.com/XiaoMi/hiui/issues/106) +- 新增:`<Switch />` 开关组件 [#107](https://github.com/XiaoMi/hiui/issues/107) +- 修复:`<Menu />` 组件在某些情况下抛出的 key 的警告问题 [#215](https://github.com/XiaoMi/hiui/issues/215) ## 1.5.0-rc.1 From b39c03cc23de7588529ac442179872da83d2c4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?liuqi=5B=E5=88=98=E7=90=AA=5D?= <liuqi17@xiaomi.com> Date: Fri, 17 May 2019 14:16:30 +0800 Subject: [PATCH 081/112] fix(rate): allow clear --- components/rate/Icons.js | 10 +++++----- components/rate/Rate.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/rate/Icons.js b/components/rate/Icons.js index 8f7e5e6d2..bf84e24c8 100644 --- a/components/rate/Icons.js +++ b/components/rate/Icons.js @@ -4,7 +4,7 @@ const iconCls = `hi-rate__icon` export const StarDefault = () => ( <i className={iconCls}> - <svg viewBox='0 0 24 24'> + <svg> <path d='M6.05109079,21.4985411 L11.9999271,18.3760205 L17.9487635,21.4985411 C18.0553353,21.5544802 18.177368,21.5737508 18.2959912,21.5533729 C18.5940667,21.5021677 18.7941945,21.2190197 18.7429892,20.9209442 L17.6071122,14.3087929 L22.4170865,9.62767423 C22.5035315,9.54354506 22.5597804,9.43322778 22.5770992,9.31385229 C22.6205221,9.01454409 22.4130861,8.7367056 22.1137779,8.69328266 L15.4656533,7.72878808 L12.4908438,1.71074009 C12.4375807,1.60298856 12.350346,1.5157539 12.2425945,1.46249073 C11.9714687,1.32846927 11.6430319,1.43961429 11.5090105,1.71074009 L8.53420096,7.72878808 L1.88607638,8.69328266 C1.76670088,8.71060138 1.6563836,8.76685035 1.57225444,8.8532953 C1.36131879,9.07003732 1.36602577,9.41673857 1.58276778,9.62767423 L6.39274205,14.3087929 L5.25686508,20.9209442 C5.23648723,21.0395675 5.2557578,21.1616002 5.31169691,21.268172 C5.45226021,21.5359646 5.78329813,21.6391044 6.05109079,21.4985411 Z' stroke='#D8D8D8' @@ -16,7 +16,7 @@ export const StarDefault = () => ( export const StarActive = () => ( <i className={iconCls}> - <svg viewBox='0 0 24 24'> + <svg> <path d='M11.9999271,18.9407142 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.3171711,0.970498831 11.9454849,0.757873569 12.4641603,1.01426246 C12.6702937,1.1161572 12.8371774,1.28304091 12.9390721,1.48917427 L15.7974549,7.27169059 L22.1855652,8.19846291 C22.7581548,8.28153289 23.1549889,8.81305 23.0719189,9.3856396 C23.0387874,9.61401011 22.9311807,9.82505186 22.7658078,9.9859946 L18.144456,14.4835438 L19.235771,20.8362911 C19.3337289,21.4065224 18.9508757,21.9481968 18.3806444,22.0461547 C18.153713,22.0851384 17.9202591,22.0482729 17.7163827,21.941259 L11.9999271,18.9407142 Z' fill='#FF9900' @@ -27,7 +27,7 @@ export const StarActive = () => ( export const StarHalfActive = () => ( <i className={iconCls}> - <svg viewBox='0 0 24 24'> + <svg> <path d='M6.05109079,21.4985411 L11.9999271,18.3760205 L17.9487635,21.4985411 C18.0553353,21.5544802 18.177368,21.5737508 18.2959912,21.5533729 C18.5940667,21.5021677 18.7941945,21.2190197 18.7429892,20.9209442 L17.6071122,14.3087929 L22.4170865,9.62767423 C22.5035315,9.54354506 22.5597804,9.43322778 22.5770992,9.31385229 C22.6205221,9.01454409 22.4130861,8.7367056 22.1137779,8.69328266 L15.4656533,7.72878808 L12.4908438,1.71074009 C12.4375807,1.60298856 12.350346,1.5157539 12.2425945,1.46249073 C11.9714687,1.32846927 11.6430319,1.43961429 11.5090105,1.71074009 L8.53420096,7.72878808 L1.88607638,8.69328266 C1.76670088,8.71060138 1.6563836,8.76685035 1.57225444,8.8532953 C1.36131879,9.07003732 1.36602577,9.41673857 1.58276778,9.62767423 L6.39274205,14.3087929 L5.25686508,20.9209442 C5.23648723,21.0395675 5.2557578,21.1616002 5.31169691,21.268172 C5.45226021,21.5359646 5.78329813,21.6391044 6.05109079,21.4985411 Z' stroke='#D8D8D8' @@ -43,7 +43,7 @@ export const StarHalfActive = () => ( export const StarDisable = () => ( <i className={iconCls}> - <svg viewBox='0 0 24 24'> + <svg> <path d='M11.9999271,18.9407142 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.3171711,0.970498831 11.9454849,0.757873569 12.4641603,1.01426246 C12.6702937,1.1161572 12.8371774,1.28304091 12.9390721,1.48917427 L15.7974549,7.27169059 L22.1855652,8.19846291 C22.7581548,8.28153289 23.1549889,8.81305 23.0719189,9.3856396 C23.0387874,9.61401011 22.9311807,9.82505186 22.7658078,9.9859946 L18.144456,14.4835438 L19.235771,20.8362911 C19.3337289,21.4065224 18.9508757,21.9481968 18.3806444,22.0461547 C18.153713,22.0851384 17.9202591,22.0482729 17.7163827,21.941259 L11.9999271,18.9407142 Z' fill='#F2F2F2' @@ -54,7 +54,7 @@ export const StarDisable = () => ( export const StarHalfReadonly = () => ( <i className={iconCls}> - <svg viewBox='0 0 24 24'> + <svg> <path d='M11.9999271,18.9407142 L6.28347165,21.941259 C5.77117265,22.2101627 5.13788272,22.0128518 4.86897901,21.5005528 C4.76196506,21.2966764 4.72509963,21.0632225 4.76408333,20.8362911 L5.85539825,14.4835438 L1.23404653,9.9859946 C0.819409641,9.58246552 0.810404983,8.91921095 1.21393407,8.50457406 C1.37487681,8.3392011 1.58591856,8.23159438 1.81428907,8.19846291 L8.20239945,7.27169059 L11.0607822,1.48917427 C11.3171711,0.970498831 11.9454849,0.757873569 12.4641603,1.01426246 C12.6702937,1.1161572 12.8371774,1.28304091 12.9390721,1.48917427 L15.7974549,7.27169059 L22.1855652,8.19846291 C22.7581548,8.28153289 23.1549889,8.81305 23.0719189,9.3856396 C23.0387874,9.61401011 22.9311807,9.82505186 22.7658078,9.9859946 L18.144456,14.4835438 L19.235771,20.8362911 C19.3337289,21.4065224 18.9508757,21.9481968 18.3806444,22.0461547 C18.153713,22.0851384 17.9202591,22.0482729 17.7163827,21.941259 L11.9999271,18.9407142 Z' fill='#F2F2F2' diff --git a/components/rate/Rate.js b/components/rate/Rate.js index 35e7fcc9b..d0248d848 100644 --- a/components/rate/Rate.js +++ b/components/rate/Rate.js @@ -12,7 +12,7 @@ class Rate extends Component { className: PropTypes.string, defaultValue: PropTypes.number, disabled: PropTypes.bool, - style: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + style: PropTypes.object, tooltips: PropTypes.arrayOf(PropTypes.string), value: PropTypes.number, count: PropTypes.number, @@ -52,7 +52,7 @@ class Rate extends Component { } if (value === this.state.value && allowClear) { this.setState({ - value: this.props.defaultValue + value: 0 }) return } From d030c69fcdfda9875261f9b0fd0f6af4fe854b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?liuqi=5B=E5=88=98=E7=90=AA=5D?= <liuqi17@xiaomi.com> Date: Tue, 21 May 2019 09:56:46 +0800 Subject: [PATCH 082/112] feat(tooltip): add open api --- components/popper/index.js | 8 ++++ components/tooltip/index.js | 72 +++++++++++++++++++++-------- docs/en-US/components/tooltip.md | 79 ++++++++++++++++++++++++++++---- docs/zh-CN/components/tooltip.md | 77 +++++++++++++++++++++++++++---- 4 files changed, 198 insertions(+), 38 deletions(-) diff --git a/components/popper/index.js b/components/popper/index.js index f7edd0706..a5cbf29b2 100644 --- a/components/popper/index.js +++ b/components/popper/index.js @@ -38,6 +38,9 @@ export default class Popper extends Component { componentDidMount () { this.getContainer() + if (this.props.show) { + render(this.renderChildren(), this.container) + } } componentWillUnmount () { @@ -51,6 +54,7 @@ export default class Popper extends Component { leftGap, width } = this.props + if (!attachEle) return const rect = attachEle.getBoundingClientRect() let top = rect.top + (document.documentElement.scrollTop || document.body.scrollTop) let left = rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft) @@ -101,9 +105,11 @@ export default class Popper extends Component { getPlacement (attachEleRect) { let { + attachEle, placement, height } = this.props + if (!attachEle) return const bodyHeight = document.documentElement.clientHeight || document.body.clientHeight let poperTop = attachEleRect.top + attachEleRect.height const caclPlacement = (bottomPlacement, topPlacement) => { // 计算popper在元素上面或下面 @@ -135,12 +141,14 @@ export default class Popper extends Component { renderChildren () { let { + attachEle, children, className, show, height, zIndex } = this.props + if (!attachEle) return const offset = this.getOffset() let width = offset.width let left = offset.left + 'px' diff --git a/components/tooltip/index.js b/components/tooltip/index.js index 3c1683e9c..3bd5de9f7 100644 --- a/components/tooltip/index.js +++ b/components/tooltip/index.js @@ -1,13 +1,14 @@ import React, { Component } from 'react' +import { render, unmountComponentAtNode } from 'react-dom' import PropTypes from 'prop-types' import classNames from 'classnames' import Popper from '../popper' import './style/index' +const prefixCls = 'hi-tooltip' + class Tooltip extends Component { static propTypes = { - color: PropTypes.string, - delay: PropTypes.number, defaultVisible: PropTypes.bool, placement: PropTypes.string, title: PropTypes.string, @@ -16,48 +17,81 @@ class Tooltip extends Component { } static defaultProps = { - color: '#4a4a4a', - delay: 1000, placement: 'top', defaultVisible: false, - prefixCls: 'hi-tooltip', onClick: () => {} - }; + } state = { tooltipShow: this.props.defaultVisible } render () { - const {placement, prefixCls, style, className, onClick} = this.props - const eleClass = classNames(`${prefixCls}-base`, placement && `${prefixCls}-${placement}`) - const {tooltipShow} = this.state + const { placement, style, className, onClick, title, children } = this.props + const eleClass = classNames( + `${prefixCls}-base`, + placement && `${prefixCls}-${placement}` + ) + const { tooltipShow } = this.state return ( <div className={classNames(prefixCls, className)} style={style} - onMouseEnter={() => { this.setState({tooltipShow: true}) }} - onMouseLeave={() => { this.setState({tooltipShow: false}) }} - onClick={() => { onClick && onClick() }} - ref={node => { this.tooltipContainer = node }} + onMouseEnter={() => { + this.setState({ tooltipShow: true }) + }} + onMouseLeave={() => { + this.setState({ tooltipShow: false }) + }} + onClick={() => { + onClick && onClick() + }} + ref={(node) => { + this.tooltipContainer = node + }} > <Popper - className='hi-tooltip__popper' + className={`${prefixCls}__popper`} show={tooltipShow} attachEle={this.tooltipContainer} placement={placement} zIndex={1070} width='auto' > - <div ref='popper' className={classNames(eleClass)}> - {this.props.title} - </div> + <div className={eleClass}>{title}</div> </Popper> - - {this.props.children} + {children} </div> ) } } +function open ({ target, placement = 'top', title }) { + let mountNode = document.createElement('div') + const eleClass = classNames( + `${prefixCls}-base`, + placement && `${prefixCls}-${placement}` + ) + render( + <Popper + className={`${prefixCls}__popper`} + show + attachEle={target} + placement={placement} + zIndex={1070} + width='auto' + > + <div className={eleClass}>{title}</div> + </Popper>, + mountNode + ) + function close () { + mountNode && unmountComponentAtNode(mountNode) + mountNode = undefined + } + return { close } +} + +Tooltip.open = open + export default Tooltip diff --git a/docs/en-US/components/tooltip.md b/docs/en-US/components/tooltip.md index c2f60ac98..4c4e1759c 100644 --- a/docs/en-US/components/tooltip.md +++ b/docs/en-US/components/tooltip.md @@ -2,9 +2,9 @@ Common text prompt component -### Basis +### Basic usage -:::demo +:::demo Tooltip component @@ -12,19 +12,78 @@ Tooltip component render() { return ( <div> - <Tooltip title="tooltip top" style={{margin: '0 10px'}}><Button type="line">Tooltip Top</Button></Tooltip> - <Tooltip title="tooltip right" style={{margin: '0 10px'}} placement="right"><Button type="success">Tooltip Right</Button></Tooltip> - <Tooltip title="tooltip bottom" style={{margin: '0 10px'}} placement="bottom"><Button type="warning">Tooltip Bottom</Button></Tooltip> - <Tooltip title="tooltip left" style={{margin: '0 10px'}} placement="left"><Button type="danger">Tooltip Left</Button></Tooltip> + <Tooltip title="tooltip top" defaultVisible style={{margin: '0 10px'}}> + <Button type="line">Tooltip Top</Button> + </Tooltip> + <Tooltip title="tooltip right" style={{margin: '0 10px'}} placement="right"> + <Button type="success">Tooltip Right</Button> + </Tooltip> + <Tooltip title="tooltip bottom" style={{margin: '0 10px'}} placement="bottom"> + <Button type="warning">Tooltip Bottom</Button> + </Tooltip> + <Tooltip title="tooltip left" style={{margin: '0 10px'}} placement="left"> + <Button type="danger">Tooltip Left</Button> + </Tooltip> </div> ) } ``` + ::: ### API -| Attribute | Description | Type | Options | Default | -| ------- | ------- | ------- | ------- | ------- | -| title | prompt text content | string | - | - | -| placement | tooltip display position | string | top/right/bottom/left | top | +#### `Tooltip.open({ target, title, placement })` + +:::demo + +```js +constructor() { + super() + Object.assign(this, { + state: { + showTooltip: false, + }, + closure: undefined, + toggleTooltip: () => { + !this.state.showTooltip ? + this.closure = Tooltip.open({ target: this.node, title: 'Click again to hide me.', placement: 'right' }) : + this.closure.close() + this.setState(({ showTooltip }) => ({ + showTooltip: !showTooltip + })) + } + }) +} + +render() { + return ( + <div> + <Button type="line" onClick={this.toggleTooltip}>{this.state.showTooltip ? 'Hide' : 'Show'} tooltip</Button> + <p /> + <span ref={node => this.node = node}> + <Button disabled>Show tooltip on me</Button> + </span> + </div> + ) +} +``` + +::: + +### Tooltip Attributes + +| Attribute | Description | Type | Options | Default | +| --------- | ------------------------ | ------ | ------------------------------ | ------- | +| title | prompt text content | String | - | - | +| placement | tooltip display position | String | top \| right \| bottom \| left | top | + +### Tooltip API + +#### `Tooltip.open({ target, title, placement })` + +| Argument | Description | Type | Options | Default | +| --------- | ------------------------ | ----------- | ------------------------------ | ------- | +| target | attached node | HTMLElement | - | - | +| title | display title | String | - | - | +| placement | tooltip display position | String | top \| right \| bottom \| left | top | diff --git a/docs/zh-CN/components/tooltip.md b/docs/zh-CN/components/tooltip.md index 04a0f7fe5..ebd4daee1 100644 --- a/docs/zh-CN/components/tooltip.md +++ b/docs/zh-CN/components/tooltip.md @@ -4,7 +4,7 @@ ### 基础 -:::demo +:::demo Tooltip 组件 @@ -12,19 +12,78 @@ Tooltip 组件 render() { return ( <div> - <Tooltip title="tooltip top" style={{margin: '0 10px'}}><Button type="line">Tooltip Top</Button></Tooltip> - <Tooltip title="tooltip right" style={{margin: '0 10px'}} placement="right"><Button type="success">Tooltip Right</Button></Tooltip> - <Tooltip title="tooltip bottom" style={{margin: '0 10px'}} placement="bottom"><Button type="warning">Tooltip Bottom</Button></Tooltip> - <Tooltip title="tooltip left" style={{margin: '0 10px'}} placement="left"><Button type="danger">Tooltip Left</Button></Tooltip> + <Tooltip title="tooltip top" style={{margin: '0 10px'}}> + <Button type="line">Tooltip Top</Button> + </Tooltip> + <Tooltip title="tooltip right" style={{margin: '0 10px'}} placement="right"> + <Button type="success">Tooltip Right</Button> + </Tooltip> + <Tooltip title="tooltip bottom" style={{margin: '0 10px'}} placement="bottom"> + <Button type="warning">Tooltip Bottom</Button> + </Tooltip> + <Tooltip title="tooltip left" style={{margin: '0 10px'}} placement="left"> + <Button type="danger">Tooltip Left</Button> + </Tooltip> </div> ) } ``` + +::: + +### API + +#### `Tooltip.open({ target, title, placement })` + +:::demo + +```js +constructor() { + super() + Object.assign(this, { + state: { + showTooltip: false, + }, + closure: undefined, + toggleTooltip: () => { + !this.state.showTooltip ? + this.closure = Tooltip.open({ target: this.node, title: 'Click again to hide me.', placement: 'right' }) : + this.closure.close() + this.setState(({ showTooltip }) => ({ + showTooltip: !showTooltip + })) + } + }) +} + +render() { + return ( + <div> + <Button type="line" onClick={this.toggleTooltip}>{this.state.showTooltip ? 'Hide' : 'Show'} tooltip</Button> + <p /> + <span ref={node => this.node = node}> + <Button disabled>Show tooltip on me</Button> + </span> + </div> + ) +} +``` + ::: ### Tooltip Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ------- | ------- | ------- | ------- | ------- | -| title | 提示文字内容 | String | 字符串 | -- | -| placement | tooltip显示的位置 | String | top \| right \| bottom \| left | top | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| --------- | ------------------ | ------ | ------------------------------ | ------ | +| title | 提示文字内容 | String | 字符串 | -- | +| placement | tooltip 显示的位置 | String | top \| right \| bottom \| left | top | + +### Tooltip API + +#### `Tooltip.open({ target, title, placement })` + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| --------- | --------------------- | ----------- | ------------------------------ | ------ | +| target | 要显示 tooptip 的元素 | HTMLElement | - | - | +| title | 提示文字内容 | String | - | - | +| placement | tooltip 显示的位置 | String | top \| right \| bottom \| left | top | From 3badabba558ff06deb373eab3bedb747c78db270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?liuqi=5B=E5=88=98=E7=90=AA=5D?= <liuqi17@xiaomi.com> Date: Tue, 21 May 2019 19:43:46 +0800 Subject: [PATCH 083/112] feat(loading): new loading style --- components/loading/index.js | 132 +++++++----------- components/loading/style/index.scss | 203 ++++++++++++++-------------- docs/en-US/components/loading.md | 7 +- docs/zh-CN/components/loading.md | 55 ++++---- site/view/Home/style/index.scss | 6 +- 5 files changed, 192 insertions(+), 211 deletions(-) diff --git a/components/loading/index.js b/components/loading/index.js index d010edabb..d4ef551ee 100644 --- a/components/loading/index.js +++ b/components/loading/index.js @@ -1,103 +1,75 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import ReactDOM from 'react-dom' +import classNames from 'classnames' import './style/index' + +const prefixCls = 'hi-loading' + class Loading extends Component { - static defaultProps = { - size: 'default' - } - static instanceKey = new Date().getTime().toString() static propTypes = { size: PropTypes.oneOf(['large', 'default', 'small']), full: PropTypes.bool, show: PropTypes.bool, - tip: PropTypes.string + tip: PropTypes.string, + target: PropTypes.any } - constructor (props) { - super(props) - this.state = { - noramLoading: false, - width: document.body.clientWidth || document.documentElement.clientWidth, - height: document.body.clientHeight || document.documentElement.clientHeight - } - } - componentDidMount () { - (this.props.full || (this.props.service && !this.props.target)) && document.body.style.setProperty('overflow', 'hidden') - if (!this.props.children) { - this.setState({ - noramLoading: true - }) - } - } - componentWillUnmount () { - document.body.style.removeProperty('overflow') + static defaultProps = { + size: 'default' } - renderCircle (tip) { + render () { + const { size, full, tip, show, children, target } = this.props + const mountNode = target || (full ? document.body : '') + const iconCls = classNames( + `${prefixCls}__icon`, + `${prefixCls}__icon--${size}` + ) + const maskCls = classNames(`${prefixCls}__mask`, { + [`${prefixCls}__mask--global`]: full, + [`${prefixCls}__mask--part`]: !full, + [`${prefixCls}__mask--hide`]: show === false + }) return ( - <div className='hi-loading__mask'> - <div className='hi-loading__outter'> - <div className={`hi-loading__circle hi-loading__circle--${this.props.size}`}> - { - new Array(12).fill(0).map((item, index) => <div className='hi-loading__circle-item' key={index} />) - } + <PortalWrapper mountNode={mountNode}> + {children} + <div className={maskCls}> + <div className={`${prefixCls}__outter`}> + <div className={iconCls}> + <div /><div /> + </div> + <div className={`${prefixCls}__text`}>{tip}</div> </div> - <div className='hi-loading__text'>{tip}</div> </div> - </div> + </PortalWrapper> ) } - render () { - let {full, show, tip, target, service} = this.props - const {noramLoading, width, height} = this.state - let style = {} - if (full || (!target && service)) { - style = { - position: 'fixed', - bottom: 0, - right: 0, - width, - height - } - } +} - if (target) { - const rect = target.getBoundingClientRect() - const st = document.documentElement.scrollTop || document.body.scrollTop - const sl = document.documentElement.scrollLeft || document.body.scrollLeft - style = { - position: 'absolute', - width: rect.width, - left: rect.left + sl, - top: rect.top + st, - height: rect.height - } - } - return ( - <div className='hi-loading' style={style}> - {(show || full || noramLoading) && this.renderCircle(tip)} - {this.props.children} - </div> - ) - } +function PortalWrapper ({ mountNode, children }) { + return mountNode ? ( + ReactDOM.createPortal(children, mountNode) + ) : ( + <div className={`${prefixCls}__wrapper`}>{children}</div> + ) } -Loading.newInstance = function newNotificationInstance (properties) { - let props = properties || {} - let div = document.createElement('div') - document.body.appendChild(div) - ReactDOM.render(React.createElement(Loading, props), div) - return { - close () { - ReactDOM.unmountComponentAtNode(div) - document.body.removeChild(div) - } +function open ({ target, tip } = {}) { + let renderNode = document.createElement('div') + const mountNode = target || document.body + window.getComputedStyle(mountNode).position === 'absolute' || + mountNode.style.setProperty('position', 'relative') + const full = !target + ReactDOM.render( + <Loading {...{ tip, full, show: true, target: mountNode }} />, + renderNode + ) + function close () { + renderNode && ReactDOM.unmountComponentAtNode(renderNode) + renderNode = undefined } + return { close } } -Loading.open = (arg) => { - return Loading.newInstance({ - ...arg, - service: true - }) -} +Loading.open = open + export default Loading diff --git a/components/loading/style/index.scss b/components/loading/style/index.scss index b11d12741..0e8cdd193 100644 --- a/components/loading/style/index.scss +++ b/components/loading/style/index.scss @@ -1,120 +1,125 @@ -@mixin circle-transform($num) { - &:nth-child(#{$num}) { - transform: rotate(#{$num * 30deg - 30deg}); - - &::before { - animation-delay: -#{1300 - $num * 100}ms; - } - } -} - -@-webkit-keyframes circleBounceDelay { - 0%, - 80%, - 100% { - transform: scale(0); - } - - 40% { - transform: scale(1); - } -} - -@keyframes circleBounceDelay { - 0%, - 80%, - 100% { - transform: scale(0); - } - - 40% { - transform: scale(1); - } -} - .hi-loading { - transition: opacity 0.3s linear; - width: 100%; - height: 100%; - position: relative; - display: block; - text-align: center; - top: 0; - background-color: transparent; //背景透明 - z-index: 9999; - - &::after { - content: ''; - display: inline-block; - } - &__mask { - position: absolute; top: 0; left: 0; - width: 100%; - height: 100%; + bottom: 0; + right: 0; background-color: rgba(255, 255, 255, 0.8); z-index: 9999; - } + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + &--part { + position: absolute; + } - &__outter { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: inline-block; - vertical-align: middle; - //background-color: #222; + &--global { + position: fixed; + } + + &--hide { + display: none; + } } - &__text { - text-align: center; - color: #ff6700; - font-size: 12px; - line-height: 2rem; - padding-left: 0.85rem; - padding-right: 0.26rem; - vertical-align: middle; + &__wrapper { + position: relative; + height: 100%; + width: 100%; } - &__circle { - margin: 15px auto; - width: 30px; - height: 30px; + &__outter { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; position: relative; + } - &--large { - width: 80px; - height: 80px; + &__icon { + transform: translate(-50%); + &--small div { + height: 8px; + width: 8px; } - - &--small { - width: 20px; - height: 20px; + &--default div { + height: 12px; + width: 12px; } - - &-item { - width: 100%; - height: 100%; + &--large div { + height: 16px; + width: 16px; + } + div { + border-radius: 50%; + display: inline-block; position: absolute; - left: 0; - top: 0; - - &::before { - content: ''; - display: block; - margin: 0 auto; - width: 15%; - height: 15%; - background-color: #ff6700; - border-radius: 100%; - animation: circleBounceDelay 1.2s infinite ease-in-out both; + @keyframes animDotF { + 0% { + transform: translateX(-100%); + z-index: 1; + } + 25% { + transform: translateX(0) scale(1.5); + z-index: 1; + } + 50% { + transform: translateX(100%); + z-index: 0; + } + 75% { + transform: scale(0.5); + z-index: 0; + } + 100% { + transform: translateX(-100%); + z-index: 1; + } } - - @for $i from 1 through 12 { - @include circle-transform($i); + @keyframes animDotL { + 0% { + transform: translateX(100%); + z-index: 0; + } + 25% { + transform: translateX(0) scale(0.5); + z-index: 0; + } + 50% { + transform: translateX(-100%); + z-index: 1; + } + 75% { + transform: scale(1.5); + z-index: 1; + } + 100% { + transform: translateX(100%); + z-index: 0; + } + } + &:first-child { + background: #FF6633; + transform: translateX(-100%); + animation: animDotF 1.5s linear infinite; + } + &:last-child { + background: #4284F5; + transform: translateX(100%); + animation: animDotL 1.5s linear infinite; + // display: none; } } } + + &__text { + text-align: center; + color: #ff6700; + font-size: 12px; + line-height: 2em; + transform: translateY(100%); + padding-left: 1em; + } } diff --git a/docs/en-US/components/loading.md b/docs/en-US/components/loading.md index 88b056ccc..25f3dce22 100644 --- a/docs/en-US/components/loading.md +++ b/docs/en-US/components/loading.md @@ -94,8 +94,9 @@ render () { columns={this.columns} data={this.state.list} /> - <br/> - <Button type="primary" onClick={this.clickEvent.bind(this)}>{this.state.btnText}</Button> + <div style={{textAlign: 'center'}}> + <Button type="primary" onClick={this.clickEvent.bind(this)}>{this.state.btnText}</Button> + </div> </Loading> </div> @@ -136,4 +137,4 @@ render () { | size | Size | string | large default small | default | | tip | Text | string | - | | | full | Whether full screen | bool | - | false | -| show | Whether to display the loading animation | boolean | true false | false | \ No newline at end of file +| show | Whether to display the loading animation | boolean | true false | false | diff --git a/docs/zh-CN/components/loading.md b/docs/zh-CN/components/loading.md index ba7ac81a5..f4e041f6e 100644 --- a/docs/zh-CN/components/loading.md +++ b/docs/zh-CN/components/loading.md @@ -4,7 +4,7 @@ ### 基础用法 -:::demo +:::demo ```js render () { @@ -15,10 +15,12 @@ render () { </div> } ``` + ::: ### 局部控制 -:::demo + +:::demo ```js constructor () { @@ -81,34 +83,35 @@ clickEvent () { showLoading: true }) this.mockTableData().then(res => { - this.setState({ - showLoading: false, + showLoading: false, list: res, btnText: '重新加载,模拟3秒返回数据' }) }) } render () { - return <div> - <div style={{width: 500, height:260, border: '1px solid gray'}}> - <Loading tip='拼命加载中' show={this.state.showLoading}> + return ( + <div style={{width: 500, height:260, border: '1px solid gray', position: 'reletive'}}> + <Loading show={this.state.showLoading}> <Table columns={this.columns} data={this.state.list} /> - <br/> - <Button type="primary" onClick={this.clickEvent.bind(this)}>{this.state.btnText}</Button> + <div style={{textAlign: 'center'}}> + <Button type="primary" onClick={this.clickEvent.bind(this)}>{this.state.btnText}</Button> + </div> </Loading> </div> - - </div> + ) } ``` + ::: ### 整页 -:::demo + +:::demo ```js constructor () { @@ -126,15 +129,16 @@ clickEvent () { render () { return <div> <Button type="primary" onClick={this.clickEvent.bind(this)}>整页遮罩,3秒自动关闭</Button> - {this.state.open && <Loading full={true} size='large'/>} + {this.state.open && <Loading full={true} show size='large' tip='加载中' />} </div> } ``` -::: +::: ### 接口调用 -:::demo + +:::demo ```js constructor () { @@ -154,7 +158,7 @@ demoEvent1 () { demoEvent2 () { const l = Loading.open({ target: this.el, - tip: '加载中...' + tip: '加载中' }) setTimeout(() => { l.close() @@ -165,7 +169,7 @@ render () { <Button type="primary" onClick={this.demoEvent1.bind(this)}>整页,3秒后关闭</Button> <Button type="primary" onClick={this.demoEvent2.bind(this)}>指定目标,3秒后关闭</Button> <div ref={(el) => {this.el = el}} style={{margin: 20}}> - <Panel + <Panel title={ <div> <i className="hi-icon icon-user" style={{marginRight: '5px'}}></i> @@ -179,16 +183,15 @@ render () { </div> } ``` -::: - +::: ### Loading Attributes -| 参数 | 说明 | 类型 | 可选值 |默认值 | -| -------- | ----- | ---- | ---- | ---- | -| size | 组件大小 | String | large \| default \| small | default | -| tip | 自定义的旋转动画下的文字 | String | - |- | -| full | 是否全屏 | Boolean | true \| false | false | -| show | 是否显示加载动画 | Boolean | true \| false | false | -| target | 用于指令调用时,Loading 遮罩的元素,为空时将整页遮罩 | Element | - | - | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------ | --------------------------------------------------- | ------- | ------------------------- | ------- | +| size | 组件大小 | String | large \| default \| small | default | +| tip | 自定义的旋转动画下的文字 | String | - | - | +| full | 是否全屏 | Boolean | true \| false | false | +| show | 是否显示加载动画 | Boolean | true \| false | false | +| target | 用于指令调用时,Loading 遮罩的元素,为空时将整页遮罩 | Element | - | - | diff --git a/site/view/Home/style/index.scss b/site/view/Home/style/index.scss index 8119b9127..20fd441f5 100644 --- a/site/view/Home/style/index.scss +++ b/site/view/Home/style/index.scss @@ -4,9 +4,9 @@ $primary-color: get-color($palette-primary, 'hiui-blue') !default; $imgurl: '../../../static/img/home/' !default; .layout--classic { - .layout__header { - z-index: 1999; - } + // .layout__header { + // z-index: 1999; + // } .layout__main { // 默认情况下,元素不会缩短至小于内容框尺寸,若想改变这一状况,请设置元素的min-width flex: 0 1 auto; // overflow: hidden; From 61db3985478806bc49ef263cfc43d3a64f1f4224 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 23 May 2019 16:58:36 +0800 Subject: [PATCH 084/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en-US/components/timeline.md | 42 +++++++++++++---------------- docs/zh-CN/components/timeline.md | 44 ++++++++++++++----------------- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/docs/en-US/components/timeline.md b/docs/en-US/components/timeline.md index ff1b4669a..99a7fc84f 100755 --- a/docs/en-US/components/timeline.md +++ b/docs/en-US/components/timeline.md @@ -1,5 +1,4 @@ -## Switch - +## Timeline ### Basic @@ -48,6 +47,7 @@ render () { ) } ``` + ::: ### Information Flow @@ -63,7 +63,7 @@ render () { description: 'Here are some descriptions', timestamp: '2019.02.24 12:00:00' }, { - dot: 'circle', + dot: 'circle', title: 'Title 2', description: 'Here are some descriptions', timestamp: '2019.02.24 14:24:00' @@ -87,9 +87,8 @@ render () { ) } ``` -::: - +::: ### Foldable @@ -104,7 +103,7 @@ render () { description: 'Here are some descriptions', timestamp: '2019.02.24 12:00:00' }, { - dot: 'circle', + dot: 'circle', title: 'Title 2', description: 'Here are some descriptions', timestamp: '2019.02.24 14:24:00' @@ -141,8 +140,8 @@ render () { ) } ``` -::: +::: ### Cross @@ -209,26 +208,25 @@ render () { ) } ``` -::: - -### Switch Attributes +::: -| Attribute | Description | Type | Options | Default | -| -------- | ----- | ---- | ---- | ---- | -| layout | layout | String | normal <br/> right <br/> cross | normal | -| list | datas | Array | - | - | +### Timeline Attributes +| Attribute | Description | Type | Options | Default | +| --------- | ----------- | ------ | ------------------------------ | ------- | +| layout | layout | String | normal <br/> right <br/> cross | normal | +| list | datas | Array | - | - | ### List Options -| Attribute | Description | Type | Options | Default | -| -------- | ----- | ---- | ---- | ---- | -| title | Display title | String \| Element | - | - | -| description | Display description | String \| Element | - | - | -| timestamp | Display timestamp | String | - | - | -| extraTime | Extra show time | String | - | - | -| dot | Custom icon | Element | - | - | +| Attribute | Description | Type | Options | Default | +| ----------- | ------------------- | ----------------- | ------- | ------- | +| title | Display title | String \| Element | - | - | +| description | Display description | String \| Element | - | - | +| timestamp | Display timestamp | String | - | - | +| extraTime | Extra show time | String | - | - | +| dot | Custom icon | Element | - | - | > Use group timeline, you need to combine groutTitle @@ -238,5 +236,3 @@ render () { children: [{List Options}] } ``` - - diff --git a/docs/zh-CN/components/timeline.md b/docs/zh-CN/components/timeline.md index 61d84366f..3a8a4aa66 100755 --- a/docs/zh-CN/components/timeline.md +++ b/docs/zh-CN/components/timeline.md @@ -1,5 +1,4 @@ -## Switch - +## 时间轴 Timeline ### 基础用法 @@ -48,6 +47,7 @@ render () { ) } ``` + ::: ### 信息流 @@ -63,7 +63,7 @@ render () { description: 'Here are some descriptions', timestamp: '2019.02.24 12:00:00' }, { - dot: 'circle', + dot: 'circle', title: 'Title 2', description: 'Here are some descriptions', timestamp: '2019.02.24 14:24:00' @@ -87,9 +87,8 @@ render () { ) } ``` -::: - +::: ### 可折叠 @@ -104,7 +103,7 @@ render () { description: 'Here are some descriptions', timestamp: '2019.02.24 12:00:00' }, { - dot: 'circle', + dot: 'circle', title: 'Title 2', description: 'Here are some descriptions', timestamp: '2019.02.24 14:24:00' @@ -141,8 +140,8 @@ render () { ) } ``` -::: +::: ### 左右结构 @@ -209,28 +208,27 @@ render () { ) } ``` -::: - -### Switch Attributes +::: -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| -------- | ----- | ---- | ---- | ---- | -| layout | 布局形式 | String | normal:基础布局 <br/> right:靠右布局 <br/> cross:交替布局 | normal | -| list | 数据 | Array | - | - | +### Timeline Attributes +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------ | -------- | ------ | --------------------------------------------------------- | ------ | +| layout | 布局形式 | String | normal:基础布局 <br/> right:靠右布局 <br/> cross:交替布局 | normal | +| list | 数据 | Array | - | - | ### List Options -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| -------- | ----- | ---- | ---- | ---- | -| title | 标题 | String \| Element | - | - | -| description | 描述信息 | String \| Element | - | - | -| timestamp | 时间点 | String | - | - | -| extraTime | 额外展示时间点 | String | - | - | -| dot | 自定义图标 | Element | - | - | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ----------- | -------------- | ----------------- | ------ | ------ | +| title | 标题 | String \| Element | - | - | +| description | 描述信息 | String \| Element | - | - | +| timestamp | 时间点 | String | - | - | +| extraTime | 额外展示时间点 | String | - | - | +| dot | 自定义图标 | Element | - | - | -> 使用分组时间轴,需要结合groutTitle* +> 使用分组时间轴,需要结合 groutTitle\* ```json { @@ -238,5 +236,3 @@ render () { children: [{List Options}] } ``` - - From 10c5dd2bb44de69e74db4ec7e624c5465b929648 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 23 May 2019 19:29:07 +0800 Subject: [PATCH 085/112] update rc version and changelog (#241) --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b106e0559..603a76f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 更新日志 +## 1.5.0-rc.5 + +- 新增:`<Timeline />` 时间轴组件 [#239](https://github.com/XiaoMi/hiui/issues/239) +- 新增:`<Loading />` 组件 UI 样式 [#238](https://github.com/XiaoMi/hiui/issues/238) +- 新增:`<Tooltip />` 组件 api 调用功能 [#240](https://github.com/XiaoMi/hiui/issues/240) + ## 1.5.0-rc.4 - 新增:`<Transfer />` 穿梭框组件 [#108](https://github.com/XiaoMi/hiui/issues/108) diff --git a/package.json b/package.json index d4a456fef..814017ac1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hi-ui/hiui", - "version": "1.5.0-rc.4", + "version": "1.5.0-rc.5", "description": "HIUI for React", "scripts": { "test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'", From 2b93c2854b70366f417eced2a82672966534ffe9 Mon Sep 17 00:00:00 2001 From: bougieL <1742070326@qq.com> Date: Fri, 31 May 2019 14:10:51 +0800 Subject: [PATCH 086/112] fix(form): fix doc, style --- components/form/item.js | 114 +++++++++++++++++-------------- components/form/style/index.scss | 32 ++++----- docs/en-US/components/form.md | 90 ++++++++++++------------ docs/zh-CN/components/form.md | 83 +++++++++++----------- 4 files changed, 164 insertions(+), 155 deletions(-) diff --git a/components/form/item.js b/components/form/item.js index fe2d5367d..a7a4933fe 100644 --- a/components/form/item.js +++ b/components/form/item.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react' +import React, { Component } from 'react' import classNames from 'classnames' import AsyncValidator from 'async-validator' import PropTypes from 'prop-types' @@ -23,7 +23,7 @@ class FormItem extends Component { } componentDidMount () { - const {prop} = this.props + const { prop } = this.props if (prop) { this.parent.addField(this) this.valueInit() @@ -55,7 +55,7 @@ class FormItem extends Component { getFilteredRule (trigger) { const rules = this.getRules() - return rules.filter(rule => { + return rules.filter((rule) => { return !rule.trigger || rule.trigger.indexOf(trigger) !== -1 }) } @@ -67,7 +67,9 @@ class FormItem extends Component { } const keyList = this.props.prop.split(':') - return keyList.length > 1 ? model[keyList[0]][keyList[1]] : model[this.props.prop] + return keyList.length > 1 + ? model[keyList[0]][keyList[1]] + : model[this.props.prop] } validate (trigger, cb) { @@ -88,21 +90,27 @@ class FormItem extends Component { const validator = new AsyncValidator({ [this.props.prop]: rules }) - const model = {[this.props.prop]: this.getfieldValue()} - - validator.validate(model, { - firstFields: true - }, errors => { - this.setState({ - error: errors ? errors[0].message : '', - validating: false, - valid: !errors - }, () => { - if (cb instanceof Function) { - cb(errors) - } - }) - }) + const model = { [this.props.prop]: this.getfieldValue() } + validator.validate( + model, + { + firstFields: true + }, + (errors) => { + this.setState( + { + error: errors ? errors[0].message : '', + validating: false, + valid: !errors + }, + () => { + if (cb instanceof Function) { + cb(errors) + } + } + ) + } + ) } resetValidate () { @@ -117,7 +125,7 @@ class FormItem extends Component { let isRequired = false if (rules && rules.length) { - rules.every(rule => { + rules.every((rule) => { if (rule.required) { isRequired = true return false @@ -139,47 +147,51 @@ class FormItem extends Component { get labelWidth () { const labelWidth = this.props.labelWidth || this.parent.props.labelWidth - return this.parent.props.labelPosition === 'top' ? false : labelWidth && parseInt(labelWidth) + return this.parent.props.labelPosition === 'top' + ? false + : labelWidth && parseInt(labelWidth) } render () { - const {children, label, required, className} = this.props - const {error, validating} = this.state + const { children, label, required, className } = this.props + const { error, validating } = this.state const obj = {} - obj['hi-form-item--error'] = error !== '' + obj['hi-form-item__error'] = error !== '' obj['hi-form-item--validating'] = validating obj['hi-form-item--required'] = this.isRequired() || required return ( <div className={classNames('hi-form-item', className, obj)}> - { - label && ( - <label className={'hi-form-item' + '__label'} style={{ 'width': this.labelWidth }}> - {label} - </label> - ) - } - <div className={'hi-form-item' + '__content'} style={{ 'marginLeft': this.labelWidth }}> - { - (Array.isArray(children) || !children) - ? children - : React.cloneElement(children, { - onChange: (...args) => { - children.props.onChange && children.props.onChange(...args) - setTimeout(() => { - this.handleFieldChange() - }) - }, - onBlur: (...args) => { - children.props.onBlur && children.props.onBlur(...args) - setTimeout(() => { - this.handleFieldBlur() - }) - } - }) - } - { error && <div className='hi-form-item__error'>{error}</div> } + {label && ( + <label + className={'hi-form-item' + '__label'} + style={{ width: this.labelWidth }} + > + {label} + </label> + )} + <div + className={'hi-form-item' + '__content'} + style={{ marginLeft: this.labelWidth }} + > + {Array.isArray(children) || !children + ? children + : React.cloneElement(children, { + onChange: (...args) => { + children.props.onChange && children.props.onChange(...args) + setTimeout(() => { + this.handleFieldChange() + }) + }, + onBlur: (...args) => { + children.props.onBlur && children.props.onBlur(...args) + setTimeout(() => { + this.handleFieldBlur() + }) + } + })} + {error && <div className='hi-form-item--msg__error'>{error}</div>} </div> </div> ) diff --git a/components/form/style/index.scss b/components/form/style/index.scss index 79611fd19..1f31f83e1 100644 --- a/components/form/style/index.scss +++ b/components/form/style/index.scss @@ -30,17 +30,8 @@ .hi-form-item { font-size: $font-size-normal; margin-right: $spacer-2; - // margin-top: 4px; - // margin-bottom: 4px; - padding: $spacer-2 0; - - & + & { - // margin-top: $spacer-2; - - .hi-form--inline & { - // margin-top: 0; - } - } + // padding: $spacer-2 0; + padding: $spacer-3 0; .hi-form--inline & { display: inline-block; @@ -50,7 +41,7 @@ &__label { float: left; box-sizing: border-box; - padding-right: $spacer-3; + padding-right: $spacer-2; line-height: 32px; vertical-align: top; color: $gray-darker; @@ -76,14 +67,19 @@ } &__error { + .hi-input__inner { + border-color: get-color($palette-secondary, 'danger'); + } + } + + &--msg__error { position: absolute; - top: 0; - left: 100%; - margin-left: $spacer-2; + top: 100%; + bottom: 0; font-size: $font-size-small; - line-height: 32px; + line-height: 24px; white-space: nowrap; - color: #ff4949; + color: get-color($palette-secondary, 'danger'); .hi-form--inline & { top: 36px; @@ -96,7 +92,7 @@ .hi-form-item__label::before { margin-right: $spacer-1; content: '*'; - color: #ff4949; + color: get-color($palette-secondary, 'danger'); } } diff --git a/docs/en-US/components/form.md b/docs/en-US/components/form.md index 6c44c9ca6..cf4c8839f 100644 --- a/docs/en-US/components/form.md +++ b/docs/en-US/components/form.md @@ -1,12 +1,8 @@ ## Form - - ### Align -:::demo - - +:::demo ```js @@ -39,12 +35,11 @@ render(){ return ( <div> <div> - <Radio - list={this.state.alignList} + <Radio + list={this.state.alignList} mode='button' checked={checkedIndex} onChange={(data, index) => { - this.setState({ position: data, checkedIndex: index @@ -57,11 +52,9 @@ render(){ <FormItem label={'name'}> <Input placeholder={'username'} /> </FormItem> - <FormItem label={'phone'} > <Input placeholder={'phone'} /> </FormItem> - <FormItem> <Button type={'primary'}>submit</Button> </FormItem> @@ -71,13 +64,12 @@ render(){ ) } ``` + ::: ### Inline -:::demo - - +:::demo ```js @@ -101,13 +93,12 @@ render(){ ) } ``` + ::: ### Form Validation -:::demo - - +:::demo ```js @@ -120,28 +111,30 @@ constructor(props) { form: { name: '', region: '', - count: '' + count: '', + type: [] }, - checkedIndex: -1, rules: { name: [ { required: true, message: <span style={{color: '#ccc'}}>input name</span>, - trigger: 'blur,change' + trigger: 'onBlur,onChange' } ], region: [ { + required: true, message: 'select area', - trigger: 'change' + type: 'number', + trigger: 'onChange' } ], count: [ { required: true, message: 'input count', - trigger: 'change' + trigger: 'onChange' }, { validator: (rule, value, cb) => { @@ -162,7 +155,6 @@ constructor(props) { } handleSubmit() { - this.form.current.validate(valid => { if(valid) { console.log(this.state.form) @@ -179,9 +171,7 @@ handleChange(key, e, value, index) { this.setState({ form: Object.assign({}, this.state.form, {[key]: value}) }) - if(index !== undefined) { - this.setState({ checkedIndex: index }) @@ -190,7 +180,6 @@ handleChange(key, e, value, index) { render(){ const {form, checkedIndex} = this.state - return ( <div> <div> @@ -202,10 +191,18 @@ render(){ <Input value={form.count} placeholder={'count'} onChange={this.handleChange.bind(this, 'count')}/> </FormItem> <FormItem label="Area" prop="region"> - <Radio - list={['Beijing', 'ShangHai', 'ChongQing']} - checked={checkedIndex} - onChange={this.handleChange.bind(this, 'region','')} + <Radio + list={[{ + name: 'BeiJing', + id: 1 + }, { + name: 'ShangHai', + id: 2 + }, { + name: 'WuHan', + id: 3 + }]} + onChange={this.handleChange.bind(this, 'region', null)} /> </FormItem> <FormItem> @@ -217,27 +214,32 @@ render(){ ) } ``` -::: +::: ### Form Attributes -| Attribute | Description | Type | Options | Default | -| --- | --- | --- | ---- | --- | -| model | Form data | object | - | - | -| rules | Form validation rule | object | - | - | -| labelWidth | label width | string | | -| labelPosition | label position | bool |right/left/top|right| -| inline | Whether it is arranged horizontally | bool | - | false| - +| Attribute | Description | Type | Options | Default | +| ------------- | ----------------------------------- | ------- | -------------- | ------- | +| model | Form data | object | - | - | +| rules | Form validation rule | object | - | - | +| labelWidth | label width | string | - | - | +| labelPosition | label position | boolean | right/left/top | right | +| inline | Whether it is arranged horizontally | boolean | - | false | ### FormItem Attributes -| Attribute | Description | Type | Options | Default | -| --- | --- | --- | ---- | --- | -| prop | model field | string | - | - | -| label | label text | string | - | - | -| labelWidth | label width | string | | -| required | required | bool | - | false | +| Attribute | Description | Type | Options | Default | +| ---------- | ----------- | ------ | ------- | ------- | +| prop | model field | string | - | - | +| label | label text | string | - | - | +| labelWidth | label width | string | - | - | +| required | required | bool | - | false | +### Form Methods +| Event Name | Description | Paramerters | +| ----------------------------- | ----------------------- | ---------------------------------------- | +| validate(callback) | validate the whole form | (valid: boolean) => void | +| validateField(prop, callback) | valiate single field | (prop: string, (valid: boolean) => void) | +| resetValidates | reset validate | - | diff --git a/docs/zh-CN/components/form.md b/docs/zh-CN/components/form.md index ec501840b..6810d8b4d 100644 --- a/docs/zh-CN/components/form.md +++ b/docs/zh-CN/components/form.md @@ -1,8 +1,8 @@ -## Form表单组件 +## Form 表单组件 ### 对齐方式 -:::demo +:::demo ```js constructor() { @@ -105,11 +105,12 @@ render(){ ) } ``` + ::: ### 横向表单 -:::demo +:::demo ```js render(){ @@ -132,6 +133,7 @@ render(){ ) } ``` + ::: ### 表单验证 @@ -141,27 +143,25 @@ render(){ ```js constructor(props) { super(props) - this.form = React.createRef() - this.state = { form: { name: '', region: '', count: '' }, - checkedIndex: -1, rules: { name: [ { required: true, - message: <span style={{color: '#ccc'}}>请输入名称</span>, + message: <span><Icon name="close-circle"/> 请输入名称</span>, trigger: 'onBlur,onChange' } ], region: [ { required: true, + type: 'number', message: '请选择区域', trigger: 'onChange' } @@ -207,35 +207,29 @@ cancelSubmit() { form: { name: '', region: '', - count: '' + count: '', + type: '' } }) this.form.resetValidates() } -handleChange(key, e, value, index) { +handleChange(key, e, value) { this.setState({ form: Object.assign({}, this.state.form, {[key]: value}) }) - - if(index !== undefined) { - this.setState({ - checkedIndex: index - }) - } } render(){ const Row = Grid.Row const Col = Grid.Col - const {form, checkedIndex} = this.state + const {form} = this.state return ( <div> <Form ref={node => this.form = node} model={form} rules={this.state.rules} labelWidth='80'> <Row> <Col span={12}> - <FormItem label='名称' prop='name'> <Input value={form.name} placeholder={'name'} onChange={this.handleChange.bind(this, 'name')}/> </FormItem> @@ -244,16 +238,23 @@ render(){ </FormItem> <FormItem label='地区' prop='region'> <Radio - list={['北京', '上海', '重庆']} - checked={checkedIndex} - onChange={this.handleChange.bind(this, 'region','')} + list={[{ + name: '北京', + id: 1 + }, { + name: '上海', + id: 2 + }, { + name: '武汉', + id: 3 + }]} + onChange={this.handleChange.bind(this, 'region', null)} /> </FormItem> <FormItem> <Button type='primary' onClick={this.handleSubmit.bind(this)}>提交</Button> <Button onClick={this.cancelSubmit.bind(this)}>重置</Button> </FormItem> - </Col> </Row> </Form> @@ -261,34 +262,32 @@ render(){ ) } ``` -::: +::: ### Form Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值| -| --- | --- | --- | ---- | --- | -| model | 表单数据 | Object | - | - | -| rules | 表单验证规则 | Object | - | - | -| labelWidth | label宽度 | String | | -| labelPosition | label位置 | String |right \| left \| top|right| -| inline | 是否横向排列 | Boolean | true \| false | false| - +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------------- | ------------ | ------- | -------------------- | ------ | +| model | 表单数据 | object | - | - | +| rules | 表单验证规则 | object | - | - | +| labelWidth | label 宽度 | string | - | - | +| labelPosition | label 位置 | string | right \| left \| top | right | +| inline | 是否横向排列 | boolean | true \| false | false | ### FormItem Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值| -| --- | --- | --- | ---- | --- | -| prop | 表单域model字段 | String | - | - | -| label | 标签文本 | String | - | - | -| labelWidth | label宽度 | String | | -| required | 是否必填 | Boolean | true \| false | false | - +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---------- | ----------------- | ------- | ------------- | ------ | +| prop | 表单域 model 字段 | string | - | - | +| label | 标签文本 | string | - | - | +| labelWidth | label 宽度 | string | - | - | +| required | 是否必填 | boolean | true \| false | false | ### Form Methods -| 方法名| 说明| -| --- | --- | -| validate(callback) | 对整个表单进行校验 | -| validateField(prop, callback) | 对表单字段进行校验 | -| resetValidates | 重置整个表单的验证 | +| 方法名 | 说明 | 回调参数 | +| ----------------------------- | ------------------ | ---------------------------------------- | +| validate(callback) | 对整个表单进行校验 | (valid: boolean) => void | +| validateField(prop, callback) | 对表单字段进行校验 | (prop: string, (valid: boolean) => void) | +| resetValidates | 重置整个表单的验证 | - | From 7604d85a13759083def31be345f67c0ce5f2d8ef Mon Sep 17 00:00:00 2001 From: bougieL <1742070326@qq.com> Date: Fri, 31 May 2019 14:25:12 +0800 Subject: [PATCH 087/112] chore(form): updte doc --- docs/en-US/components/form.md | 140 +++++++++++++++------------------- docs/zh-CN/components/form.md | 115 ++++++++++++---------------- 2 files changed, 112 insertions(+), 143 deletions(-) diff --git a/docs/en-US/components/form.md b/docs/en-US/components/form.md index cf4c8839f..3a65595ea 100644 --- a/docs/en-US/components/form.md +++ b/docs/en-US/components/form.md @@ -8,7 +8,6 @@ constructor() { super() - this.state = { checkedIndex: 0, alignList: [ @@ -29,37 +28,34 @@ constructor() { } } - render(){ const {position, checkedIndex} = this.state return ( <div> - <div> - <Radio - list={this.state.alignList} - mode='button' - checked={checkedIndex} - onChange={(data, index) => { - this.setState({ - position: data, - checkedIndex: index - }) - }} - /> - <br /> - <br /> - <Form labelWidth="80" labelPosition={this.state.position}> - <FormItem label={'name'}> - <Input placeholder={'username'} /> - </FormItem> - <FormItem label={'phone'} > - <Input placeholder={'phone'} /> - </FormItem> - <FormItem> - <Button type={'primary'}>submit</Button> - </FormItem> - </Form> - </div> + <Radio + list={this.state.alignList} + mode='button' + checked={checkedIndex} + onChange={(data, index) => { + this.setState({ + position: data, + checkedIndex: index + }) + }} + /> + <br /> + <br /> + <Form labelWidth="80" labelPosition={this.state.position}> + <FormItem label='name'> + <Input placeholder='username' /> + </FormItem> + <FormItem label='phone' > + <Input placeholder='phone' /> + </FormItem> + <FormItem> + <Button type='primary'>submit</Button> + </FormItem> + </Form> </div> ) } @@ -75,21 +71,17 @@ render(){ render(){ return ( - <div> - <div> - <Form inline={true}> - <FormItem label="username" labelWidth="80"> - <Input placeholder={'username'} /> - </FormItem> - <FormItem label="password" labelWidth="80"> - <Input placeholder={'password'} /> - </FormItem> - <FormItem> - <Button type={'primary'}>submit</Button> - </FormItem> - </Form> - </div> - </div> + <Form inline={true}> + <FormItem label="username" labelWidth="80"> + <Input placeholder='username' /> + </FormItem> + <FormItem label="password" labelWidth="80"> + <Input placeholder='password' /> + </FormItem> + <FormItem> + <Button type='primary'>submit</Button> + </FormItem> + </Form> ) } ``` @@ -104,9 +96,7 @@ render(){ constructor(props) { super(props) - this.form = React.createRef() - this.state = { form: { name: '', @@ -118,7 +108,7 @@ constructor(props) { name: [ { required: true, - message: <span style={{color: '#ccc'}}>input name</span>, + message: <span><Icon name="close-circle"/> input name</span>, trigger: 'onBlur,onChange' } ], @@ -147,7 +137,7 @@ constructor(props) { cb() } }, - trigger: 'change' + trigger: 'onChange' } ] } @@ -181,36 +171,32 @@ handleChange(key, e, value, index) { render(){ const {form, checkedIndex} = this.state return ( - <div> - <div> - <Form ref={this.form} model={form} rules={this.state.rules} labelWidth="80"> - <FormItem label="Name" prop="name"> - <Input value={form.name} placeholder={'name'} onChange={this.handleChange.bind(this, 'name')}/> - </FormItem> - <FormItem label="Count" prop="count"> - <Input value={form.count} placeholder={'count'} onChange={this.handleChange.bind(this, 'count')}/> - </FormItem> - <FormItem label="Area" prop="region"> - <Radio - list={[{ - name: 'BeiJing', - id: 1 - }, { - name: 'ShangHai', - id: 2 - }, { - name: 'WuHan', - id: 3 - }]} - onChange={this.handleChange.bind(this, 'region', null)} - /> - </FormItem> - <FormItem> - <Button type={'primary'} onClick={this.handleSubmit.bind(this)}>Submit</Button> - </FormItem> - </Form> - </div> - </div> + <Form ref={this.form} model={form} rules={this.state.rules} labelWidth="80"> + <FormItem label="Name" prop="name"> + <Input value={form.name} placeholder='name' onChange={this.handleChange.bind(this, 'name')}/> + </FormItem> + <FormItem label="Count" prop="count"> + <Input value={form.count} placeholder='count' onChange={this.handleChange.bind(this, 'count')}/> + </FormItem> + <FormItem label="Area" prop="region"> + <Radio + list={[{ + name: 'BeiJing', + id: 1 + }, { + name: 'ShangHai', + id: 2 + }, { + name: 'WuHan', + id: 3 + }]} + onChange={this.handleChange.bind(this, 'region', null)} + /> + </FormItem> + <FormItem> + <Button type={'primary'} onClick={this.handleSubmit.bind(this)}>Submit</Button> + </FormItem> + </Form> ) } ``` diff --git a/docs/zh-CN/components/form.md b/docs/zh-CN/components/form.md index 6810d8b4d..38bba1c9e 100644 --- a/docs/zh-CN/components/form.md +++ b/docs/zh-CN/components/form.md @@ -7,7 +7,6 @@ ```js constructor() { super() - this.state = { alignCheckedIndex: 0, alignList: [ @@ -45,14 +44,13 @@ constructor() { } render(){ - const {position, alignCheckedIndex, columnCheckedIndex} = this.state + const { position, alignCheckedIndex, columnCheckedIndex } = this.state const Row = Grid.Row const Col = Grid.Col return ( <div> <Row gutter={true}> <Col span={12}> - <Radio list={this.state.alignList} mode='button' @@ -64,10 +62,8 @@ render(){ }) }} /> - </Col> <Col span={12}> - <Radio list={this.state.columnList} mode='button' @@ -79,26 +75,21 @@ render(){ }) }} /> - </Col> </Row> <Row gutter={true}> <Col span={this.state.column}> - <Form labelWidth='80' labelPosition={this.state.position}> - <FormItem label={'姓名'}> - <Input placeholder={'username'} /> + <FormItem label='姓名'> + <Input placeholder='username' /> </FormItem> - - <FormItem label={'手机号码'} > - <Input placeholder={'phone'} /> + <FormItem label='手机号码' > + <Input placeholder='phone' /> </FormItem> - <FormItem> - <Button type={'primary'}>提交</Button> + <Button type='primary'>提交</Button> </FormItem> </Form> - </Col> </Row> </div> @@ -115,21 +106,17 @@ render(){ ```js render(){ return ( - <div> - <div> - <Form inline={true}> - <FormItem label='账号' labelWidth='50'> - <Input placeholder={'账号'} /> - </FormItem> - <FormItem label='密码' labelWidth='50'> - <Input type='password' placeholder={'密码'} /> - </FormItem> - <FormItem> - <Button type={'primary'}>提交</Button> - </FormItem> - </Form> - </div> - </div> + <Form inline={true}> + <FormItem label='账号' labelWidth='50'> + <Input placeholder='账号' /> + </FormItem> + <FormItem label='密码' labelWidth='50'> + <Input type='password' placeholder='密码' /> + </FormItem> + <FormItem> + <Button type='primary'>提交</Button> + </FormItem> + </Form> ) } ``` @@ -207,8 +194,7 @@ cancelSubmit() { form: { name: '', region: '', - count: '', - type: '' + count: '' } }) this.form.resetValidates() @@ -224,41 +210,38 @@ render(){ const Row = Grid.Row const Col = Grid.Col const {form} = this.state - return ( - <div> - <Form ref={node => this.form = node} model={form} rules={this.state.rules} labelWidth='80'> - <Row> - <Col span={12}> - <FormItem label='名称' prop='name'> - <Input value={form.name} placeholder={'name'} onChange={this.handleChange.bind(this, 'name')}/> - </FormItem> - <FormItem label='数量' prop='count'> - <Input value={form.count} placeholder={'count'} onChange={this.handleChange.bind(this, 'count')}/> - </FormItem> - <FormItem label='地区' prop='region'> - <Radio - list={[{ - name: '北京', - id: 1 - }, { - name: '上海', - id: 2 - }, { - name: '武汉', - id: 3 - }]} - onChange={this.handleChange.bind(this, 'region', null)} - /> - </FormItem> - <FormItem> - <Button type='primary' onClick={this.handleSubmit.bind(this)}>提交</Button> - <Button onClick={this.cancelSubmit.bind(this)}>重置</Button> - </FormItem> - </Col> - </Row> - </Form> - </div> + <Form ref={node => this.form = node} model={form} rules={this.state.rules} labelWidth='80'> + <Row> + <Col span={12}> + <FormItem label='名称' prop='name'> + <Input value={form.name} placeholder='name' onChange={this.handleChange.bind(this, 'name')}/> + </FormItem> + <FormItem label='数量' prop='count'> + <Input value={form.count} placeholder='count' onChange={this.handleChange.bind(this, 'count')}/> + </FormItem> + <FormItem label='地区' prop='region'> + <Radio + list={[{ + name: '北京', + id: 1 + }, { + name: '上海', + id: 2 + }, { + name: '武汉', + id: 3 + }]} + onChange={this.handleChange.bind(this, 'region', null)} + /> + </FormItem> + <FormItem> + <Button type='primary' onClick={this.handleSubmit.bind(this)}>提交</Button> + <Button onClick={this.cancelSubmit.bind(this)}>重置</Button> + </FormItem> + </Col> + </Row> + </Form> ) } ``` From 6d26a975345893a254ceb0aceb1e9b5ce8a7a890 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 31 May 2019 19:44:10 +0800 Subject: [PATCH 088/112] #262 --- components/select/Select.js | 16 +++++++++++----- docs/en-US/components/select.md | 3 ++- docs/zh-CN/components/select.md | 3 ++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/select/Select.js b/components/select/Select.js index a0cbd5971..a0394d057 100644 --- a/components/select/Select.js +++ b/components/select/Select.js @@ -34,7 +34,8 @@ class Select extends Component { optionWidth: PropTypes.number, style: PropTypes.object, onChange: PropTypes.func, - dropdownRender: PropTypes.func + dropdownRender: PropTypes.func, + open: PropTypes.bool } static defaultProps = { @@ -47,7 +48,8 @@ class Select extends Component { autoload: false, placeholder: '请选择', noFoundTip: '无内容', - showCheckAll: false + showCheckAll: false, + open: true } constructor (props) { @@ -266,7 +268,7 @@ class Select extends Component { }) } - handleInputClick (e) { + handleInputClick = (e) => { let { dropdownShow } = this.state @@ -514,7 +516,11 @@ class Select extends Component { multipleMode={multipleMode} container={this.selectInputContainer} moveFocusedIndex={this.moveFocusedIndex.bind(this)} - onClick={this.handleInputClick.bind(this)} + onClick={()=>{ + if(this.props.open) { + this.handleInputClick() + } + }} onDelete={this.deleteItem.bind(this)} onClear={this.deleteAllItems.bind(this)} onSearch={this.debouncedFilterItems.bind(this)} @@ -523,7 +529,7 @@ class Select extends Component { </div> { children } <Popper - show={dropdownShow} + show={dropdownShow && this.props.open} attachEle={this.selectInputContainer} zIndex={1050} topGap={5} diff --git a/docs/en-US/components/select.md b/docs/en-US/components/select.md index 2d791ecbf..5ee3ce5cc 100644 --- a/docs/en-US/components/select.md +++ b/docs/en-US/components/select.md @@ -213,7 +213,7 @@ Asynchronous multiple selection render () { return ( <div> - <Select + <Select mode='multiple' style={{width: '300px'}} origin={{ @@ -245,6 +245,7 @@ render () { | list | drop-down box options, typically in the form {name: '', id: ''}. The 'label' attribute is required when mode='label'. | array | - | - | | origin | Asynchronous selection configuration, there are three types of type / url / func, respectively representing the request type / request path / data processing function after the request returns | object | - | - | | value | the value of the selected item/default value of the union drop-down box | string | - | - | +| open (1.5 added) | whether to display the dropdown menu | Boolean | true \| false | true | | disabled | disable the drop-down box | string | - | - | | placeholder | prompt message | string | - | - | | style | custom style | object | - | - | diff --git a/docs/zh-CN/components/select.md b/docs/zh-CN/components/select.md index 73b27e7f3..07934483d 100644 --- a/docs/zh-CN/components/select.md +++ b/docs/zh-CN/components/select.md @@ -281,7 +281,7 @@ render () { render () { return ( <div> - <Select + <Select mode='multiple' autoload={true} style={{width: '300px'}} @@ -319,6 +319,7 @@ render () { | origin | 异步选择配置,详见下表 | Object | - | - | | value | 默认值被选中项,值与被选中的id相同,多个以,分割或者传递数组| String \| Number \| Array | - | - | | showCheckAll | 是否显示全选,只对多选生效 | Boolean | true \| false | false | +| open (1.5新增) | 是否显示下拉菜单 | Boolean | true \| false | true | | searchable | 是否可以筛选 | Boolean | true \| false | false | | clearable | 是否可以清空 | Boolean | true \| false | true | | autoload | origin从远端获取数据,初始时是否自动加载 | Boolean | true \| false | false | From 15d24ffdb2905f628600a77b8b68e5512b9ebc3f Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Mon, 3 Jun 2019 10:34:12 +0800 Subject: [PATCH 089/112] =?UTF-8?q?fix:menu=20mini=20toggle=20=E5=AD=90?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E4=B8=8D=E5=85=B3=E9=97=ADbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/menu/index.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/menu/index.js b/components/menu/index.js index 2fb5c8f6a..839d5409b 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -176,13 +176,17 @@ class Menu extends Component { } toggleMini () { - let mini = !this.state.mini + const mini = !this.state.mini + const expandIndex = mini ? [] : this.state.expandIndex // 切换为mini清空展开态,否则记录展开态 - this.setState({ - mini - }, () => { - this.props.onMiniChange && this.props.onMiniChange(mini) - }) + setTimeout(() => { // fix mini 切换为 非mini 时子菜单不隐藏 + this.setState({ + mini, + expandIndex + }, () => { + this.props.onMiniChange && this.props.onMiniChange(mini) + }) + }, 0) } onClick (indexs, id, data) { From a640f2a6a0307843704cb401f922c5ffb0dc1ea4 Mon Sep 17 00:00:00 2001 From: bougieL <1742070326@qq.com> Date: Mon, 3 Jun 2019 15:59:26 +0800 Subject: [PATCH 090/112] feat(Button): loading button --- components/button/Button.js | 78 +++++++-------- components/button/IconLoading.js | 20 ++++ components/button/style/index.scss | 40 ++++++++ components/loading/style/index.scss | 21 +++- docs/en-US/components/button.md | 143 +++++++++++++++------------- docs/zh-CN/components/button.md | 101 +++++++++++++------- 6 files changed, 261 insertions(+), 142 deletions(-) create mode 100644 components/button/IconLoading.js diff --git a/components/button/Button.js b/components/button/Button.js index 41b831991..a0ec856d2 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -3,11 +3,20 @@ import PropTypes from 'prop-types' import classNames from 'classnames' import Provider from '../context' import Icon from '../icon' +import IconLoading from './IconLoading' import deprecatedPropsCheck from '../_util/deprecatedPropsCheck' class Button extends Component { static propTypes = { - type: PropTypes.oneOf(['primary', 'line', 'success', 'danger', 'default', 'warning', 'info']), + type: PropTypes.oneOf([ + 'primary', + 'line', + 'success', + 'danger', + 'default', + 'warning', + 'info' + ]), size: PropTypes.oneOf(['large', 'small', 'normal']), appearance: PropTypes.oneOf(['link', 'button', 'line']), className: PropTypes.string, @@ -15,6 +24,7 @@ class Button extends Component { disabled: PropTypes.bool, onClick: PropTypes.func, href: PropTypes.string, + loading: PropTypes.bool, target: PropTypes.oneOf(['_self', '_blank', '_parent', '_top']) } @@ -30,72 +40,58 @@ class Button extends Component { size: 'normal' } - clickCb (e) { - if (this.props.onClick) { - this.props.onClick(e) - } - } - render () { const { - type, - disabled, className, size, + disabled, + type, appearance, - style, - title, - href, - target, theme, icon, - children + loading, + children, + ...rest } = this.props const classes = classNames( 'theme__' + theme, `hi-btn`, - className && `${className}`, + className, appearance && `hi-btn--appearance--${appearance}`, size && `hi-btn--size--${size}`, disabled && `hi-btn--disabled`, icon && `hi-btn--icon`, + loading && `hi-btn--loading`, // For version < 1.1.0 - (type === 'primary' && appearance === 'line') + type === 'primary' && appearance === 'line' ? `hi-btn--type--line` : `hi-btn--type--${type}` ) - const disabledBool = !!disabled - deprecatedPropsCheck(this.deprecatedProps, this.props, 'Button') return ( - href - ? <a - className={classes} - onClick={(e) => this.clickCb(e)} - style={style} - title={title} - href={href} - target={target} - > - {icon && <Icon name={icon} /> } - {children} - </a> - : <button - className={classes} - disabled={disabledBool} - onClick={(e) => this.clickCb(e)} - style={style} - title={title} - type='button' - > - {icon && <Icon name={icon} />} - {children} - </button> + <ButtonWrapper className={classes} {...rest}> + {loading && <IconLoading />} + {icon && !loading && <Icon name={icon} />} + {(icon || loading) && children && ( + <span className='hi-btn--icon__spacer' /> + )} + {children} + </ButtonWrapper> ) } } +function ButtonWrapper ({ children, ...props }) { + return props.href ? ( + <a {...props}>{children}</a> + ) : ( + <button {...props} type='button'> + {children} + </button> + ) +} + export default Provider(Button) diff --git a/components/button/IconLoading.js b/components/button/IconLoading.js new file mode 100644 index 000000000..766836e23 --- /dev/null +++ b/components/button/IconLoading.js @@ -0,0 +1,20 @@ +import React from 'react' + +export default function IconLoading () { + const size = '0.8em' + return ( + <React.Fragment> + <i className='hi-icon hi-btn--loading--icon'> + <svg viewBox='0 0 18 18' width={size} height={size} fill='currentColor'> + <g> + <path + d='m9 18c-4.9706 0-9-4.0294-9-9 0-4.9706 4.0294-9 9-9 4.9706 0 9 4.0294 9 9 0 4.9706-4.0294 9-9 9zm0-2c3.866 0 7-3.134 7-7 0-3.866-3.134-7-7-7-3.866 0-7 3.134-7 7 0 3.866 3.134 7 7 7z' + opacity='.15' + /> + <path d='m15.547 2.8242c0.37904 0.40168 0.36068 1.0346-0.040996 1.4136-0.40168 0.37904-1.0346 0.36068-1.4136-0.040996-1.315-1.3935-3.1381-2.1969-5.0922-2.1969-3.866 0-7 3.134-7 7 0 0.55228-0.44772 1-1 1s-1-0.44772-1-1c0-4.9706 4.0294-9 9-9 2.5103 0 4.8578 1.0343 6.5468 2.8242z' /> + </g> + </svg> + </i> + </React.Fragment> + ) +} diff --git a/components/button/style/index.scss b/components/button/style/index.scss index d3c31fced..f8d67f112 100644 --- a/components/button/style/index.scss +++ b/components/button/style/index.scss @@ -110,9 +110,49 @@ outline: 0; } + &::before { + content: ''; + display: block; + position: absolute; + top: -1px; + bottom: -1px; + left: -1px; + right: -1px; + background-color: #fff; + opacity: 0; + transition: opacity 0.3s ease; + } + + &--loading { + &::before { + opacity: 0.4; + } + + &--icon { + width: 1em; + height: 1em; + display: inline-block; + + svg { + @keyframes rotate { + to { + transform: rotate(360deg); + } + } + + animation: rotate 1s linear infinite; + } + } + } + &--icon { padding-left: 8px; padding-right: 8px; + + &__spacer { + width: $spacer-2; + display: inline-block; + } } // Common Styles diff --git a/components/loading/style/index.scss b/components/loading/style/index.scss index 0e8cdd193..1c287b0bb 100644 --- a/components/loading/style/index.scss +++ b/components/loading/style/index.scss @@ -11,6 +11,7 @@ align-items: center; height: 100%; width: 100%; + &--part { position: absolute; } @@ -40,73 +41,89 @@ &__icon { transform: translate(-50%); + &--small div { height: 8px; width: 8px; } + &--default div { height: 12px; width: 12px; } + &--large div { height: 16px; width: 16px; } + div { border-radius: 50%; display: inline-block; position: absolute; + @keyframes animDotF { 0% { transform: translateX(-100%); z-index: 1; } + 25% { transform: translateX(0) scale(1.5); z-index: 1; } + 50% { transform: translateX(100%); z-index: 0; } + 75% { transform: scale(0.5); z-index: 0; } + 100% { transform: translateX(-100%); z-index: 1; } } + @keyframes animDotL { 0% { transform: translateX(100%); z-index: 0; } + 25% { transform: translateX(0) scale(0.5); z-index: 0; } + 50% { transform: translateX(-100%); z-index: 1; } + 75% { transform: scale(1.5); z-index: 1; } + 100% { transform: translateX(100%); z-index: 0; } } + &:first-child { - background: #FF6633; + background: #f63; transform: translateX(-100%); animation: animDotF 1.5s linear infinite; } + &:last-child { - background: #4284F5; + background: #4284f5; transform: translateX(100%); animation: animDotL 1.5s linear infinite; // display: none; diff --git a/docs/en-US/components/button.md b/docs/en-US/components/button.md index 1f554b699..3e9f34e77 100644 --- a/docs/en-US/components/button.md +++ b/docs/en-US/components/button.md @@ -1,12 +1,12 @@ ## Button -Commonly used button +Commonly used button. ### Standard Button -:::demo +:::demo -Button component provides 7 themes by default. Defined by the `type` attribute, default is `default`。 +Button component provides 7 themes by default. Defined by the `type` attribute, default is `default`. ```js render() { @@ -16,30 +16,27 @@ render() { <div> <Row gutter={true}> <Col span={24}> - <Button type="primary">Primary</Button> <Button type="line">Line</Button> <Button type="default">Default</Button> <Button type="primary">Confirm</Button> <Button type="line">Cancel</Button> - </Col> </Row> <Row gutter={true}> <Col span={24}> - <Button type="primary" disabled>Primary</Button> <Button type="line" disabled>Line</Button> <Button type="default" disabled>Default</Button> <Button type="primary" disabled>Confirm</Button> <Button type="line" disabled>Cancel</Button> - </Col> </Row> </div> ) } ``` + ::: ### Status Button @@ -52,24 +49,15 @@ render() { const Col = Grid.Col return ( <div> - <Row gutter={true}> - <Col span={24}> - - <div> - - <Button type="success">Approve</Button> - <Button type="success" disabled>Approve</Button> - <Button type="danger">Decline</Button> - <Button type="danger" disabled>Decline</Button> - - </div> - - </Col> - </Row> + <Button type="success">Approve</Button> + <Button type="success" disabled>Approve</Button> + <Button type="danger">Decline</Button> + <Button type="danger" disabled>Decline</Button> </div> ) } ``` + ::: ### Button Size @@ -78,27 +66,19 @@ render() { ```js render() { - const Row = Grid.Row - const Col = Grid.Col return ( <div> - - <Row gutter={true}> - <Col span={24}> - - <Button type="primary" size="large">Large</Button> - <Button type="primary" size="large" disabled>Large</Button> - <Button type="primary" size="normal">Normal</Button> - <Button type="primary" size="normal" disabled>Normal</Button> - <Button type="primary" size="small">Small</Button> - <Button type="primary" size="small" disabled>Small</Button> - - </Col> - </Row> + <Button type="primary" size="large">Large</Button> + <Button type="primary" size="large" disabled>Large</Button> + <Button type="primary" size="normal">Normal</Button> + <Button type="primary" size="normal" disabled>Normal</Button> + <Button type="primary" size="small">Small</Button> + <Button type="primary" size="small" disabled>Small</Button> </div> ) } ``` + ::: ### Link Button @@ -111,73 +91,108 @@ render() { const Col = Grid.Col return ( <div> - <Row gutter={true}> <Col span={24}> - <Button type="default" appearance="link">Link</Button> <Button type="primary" appearance="link">Primary</Button> <Button type="success" appearance="link">Success</Button> <Button type="danger" appearance="link">Danger</Button> <Button type="primary" appearance="link"><Icon name="edit" /></Button> - </Col> </Row> <Row gutter={true}> <Col span={24}> - <Button type="default" appearance="link" disabled>Link</Button> <Button type="primary" appearance="link" disabled>Primary</Button> <Button type="success" appearance="link" disabled>Success</Button> <Button type="danger" appearance="link" disabled>Danger</Button> <Button type="primary" appearance="link" disabled><Icon name="edit" /></Button> - </Col> </Row> </div> ) } ``` + ::: ### Button Group -:::demo +:::demo ```js render() { - const Row = Grid.Row - const Col = Grid.Col return ( - <div> - <Row gutter={true}> - <Col span={24}> + <Button.Group> + <Button type="default">Action A</Button> + <Button type="default">Action B</Button> + <Button type="default" disabled>Action C</Button> + </Button.Group> + ) +} +``` + +::: - <Button.Group> - <Button type="default">Action A</Button> - <Button type="default">Action B</Button> - <Button type="default" disabled>Action C</Button> - </Button.Group> +### Loading Button - </Col> - </Row> +:::demo + +```js +constructor() { + super() + Object.assign(this, { + state: { + showLoading: false + }, + handleButtonClick() { + this.setState({ + showLoading: true + }, () => { + setTimeout(() => { + this.setState({ + showLoading: false + }) + }, 3000) + }) + } + }) + this.handleButtonClick = this.handleButtonClick.bind(this) +} +render() { + return ( + <div> + <Button type="default" loading>Loading</Button> + <Button type="primary" loading>Loading</Button> + <Button type="danger" loading>Loading</Button> + <Button type="success" loading>Loading</Button> + <p /> + <Button type="primary" icon="upload" size="large" loading={this.state.showLoading} onClick={this.handleButtonClick}>Click me</Button> + <Button type="primary" size="large" loading>Loading</Button> + <Button type="primary" loading>Loading</Button> + <Button type="primary" size='small' loading>Loading</Button> </div> ) } ``` + ::: ### Attributes -| Attribute | Description | Type | Options | Default | -| -------- | ----- | ---- | ---- | ---- | -| type | button type | string | primary,line, success,danger, default | default | -| appearance | display type | string | button, link | button | -| size | button size | string | large, normal, small | normal | -| className | custom class | string | - | - | - +| Attribute | Description | Type | Options | Default | +| ---------- | ------------- | ------- | ---------------------------------------------- | ------- | +| type | button type | string | primary \| line \| success \|danger \| default | default | +| href | target link | string | - | - | +| appearance | display type | string | button \| link | button | +| size | button size | string | large\| normal \| small | normal | +| icon | icon button | string | see [`<Icon />`](/#/en-US/docs/icon) component | - | +| className | custom class | string | - | - | +| style | custom styles | object | - | - | +| loading | show loading | boolean | true \| false | false | ### Events -| Event Name | Description | Parameters -| -------- | ----- | ---- -| onClick | triggers when the Button clicked | - + +| Event Name | Description | Parameters | +| ---------- | -------------------------------- | ---------- | +| onClick | triggers when the Button clicked | Event | diff --git a/docs/zh-CN/components/button.md b/docs/zh-CN/components/button.md index e73736dfb..5f37e1c0d 100644 --- a/docs/zh-CN/components/button.md +++ b/docs/zh-CN/components/button.md @@ -1,4 +1,4 @@ -## Button按钮 +## Button 按钮 常用的操作按钮。 @@ -18,30 +18,27 @@ render() { <div> <Row gutter={true}> <Col span={24}> - <Button type="primary">突出按钮</Button> <Button type="line">普通按钮</Button> <Button type="default">默认按钮</Button> <Button type="primary">确认</Button> <Button type="line">取消</Button> - </Col> </Row> <Row gutter={true}> <Col span={24}> - <Button type="primary" disabled>突出按钮</Button> <Button type="line" disabled>普通按钮</Button> <Button type="default" disabled>默认按钮</Button> <Button type="primary" disabled>确认</Button> <Button type="line" disabled>取消</Button> - </Col> </Row> </div> ) } ``` + ::: ### 辅助按钮 @@ -56,16 +53,15 @@ render() { render() { return ( <div> - <Button type="success">通过</Button> <Button type="success" disabled>通过</Button> <Button type="danger">驳回</Button> <Button type="danger" disabled>驳回</Button> - </div> ) } ``` + ::: ### 按钮类型 @@ -81,7 +77,6 @@ render() { render() { return ( <div> - <Button type="primary" size="large">大按钮</Button> <Button type="primary" size="large" disabled>大按钮</Button> <Button type="primary" size="normal">默认</Button> @@ -94,6 +89,7 @@ render() { ) } ``` + ::: ### 链接按钮 @@ -106,33 +102,29 @@ render() { const Col = Grid.Col return ( <div> - <Row gutter={true}> <Col span={24}> - <Button type="default" appearance="link">默认链接</Button> <Button type="primary" appearance="link">主要链接</Button> <Button type="success" appearance="link">成功链接</Button> <Button type="danger" appearance="link">危险链接</Button> <Button type="primary" appearance="link"><Icon name="edit" /></Button> - </Col> </Row> <Row gutter={true}> <Col span={24}> - <Button type="default" appearance="link" disabled>默认链接</Button> <Button type="primary" appearance="link" disabled>主要链接</Button> <Button type="success" appearance="link" disabled>成功链接</Button> <Button type="danger" appearance="link" disabled>危险链接</Button> <Button type="primary" appearance="link" disabled><Icon name="edit" /></Button> - </Col> </Row> </div> ) } ``` + ::: ### 按钮组合 @@ -141,38 +133,77 @@ render() { ```js render() { - const Row = Grid.Row - const Col = Grid.Col return ( - <div> - <Row gutter={true}> - <Col span={24}> + <Button.Group> + <Button type="default">动作A</Button> + <Button type="default">动作B</Button> + <Button type="default" disabled>动作C</Button> + </Button.Group> + ) +} +``` - <Button.Group> - <Button type="default">动作A</Button> - <Button type="default">动作B</Button> - <Button type="default" disabled>动作C</Button> - </Button.Group> +::: - </Col> - </Row> +### 加载中状态 + +:::demo + +```js +constructor() { + super() + Object.assign(this, { + state: { + showLoading: false + }, + handleButtonClick() { + this.setState({ + showLoading: true + }, () => { + setTimeout(() => { + this.setState({ + showLoading: false + }) + }, 3000) + }) + } + }) + this.handleButtonClick = this.handleButtonClick.bind(this) +} +render() { + return ( + <div> + <Button type="default" loading>Loading</Button> + <Button type="primary" loading>Loading</Button> + <Button type="danger" loading>Loading</Button> + <Button type="success" loading>Loading</Button> + <p /> + <Button type="primary" icon="upload" size="large" loading={this.state.showLoading} onClick={this.handleButtonClick}>Click me</Button> + <Button type="primary" size="large" loading>Loading</Button> + <Button type="primary" loading>Loading</Button> + <Button type="primary" size="small" loading>Loading</Button> </div> ) } ``` + ::: ### Button Attributes -| 参数 | 说明 | 类型 | 可选值 |默认值 | -| -------- | ----- | ---- | ---- | ---- | -| type | 设置按钮类型 | String | primary \| line \| success \| danger \| default | default | -| appearance | 按钮显示类型(按钮或链接) | String | button \| link | button | -| size | 设置按钮大小 | String | large \| normal \| small | normal | -| icon | 设置按钮图标,设置后忽略子元素只渲染对应图标 | String | 详见 `<Icon />` 组件 | - | -| className | 自定义class | String | - | - | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---------- | -------------------------------------------- | ------- | ----------------------------------------------- | ------- | +| type | 设置按钮类型 | string | primary \| line \| success \| danger \| default | default | +| href | 跳转链接,使用 href 后将用 a 渲染按钮 | string | - | - | +| appearance | 按钮显示类型(按钮或链接) | string | button \| link | button | +| size | 设置按钮大小 | string | large \| normal \| small | normal | +| icon | 设置按钮图标,设置后忽略子元素只渲染对应图标 | string | 详见 [`<Icon />`](/#/zh-CN/docs/icon) 组件 | - | +| className | 自定义 class | string | - | - | +| style | 自定义样式 | object | - | - | +| loading | 是否显示 loading | boolean | true \| false | false | ### Button Events -| 参数 | 说明 | 类型 | 可选值 |默认值 | -| -------- | ----- | ---- | ---- | ---- | -| onClick | 点击回调函数 | Function | - | - | + +| 参数 | 说明 | 回调参数 | +| ------- | ------------ | -------- | +| onClick | 点击回调函数 | Event | From dc94667f67cac5cb88cc91711a56f2de9d95d381 Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Mon, 3 Jun 2019 16:06:28 +0800 Subject: [PATCH 091/112] chore(Button): remove useless fragment --- components/button/IconLoading.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/components/button/IconLoading.js b/components/button/IconLoading.js index 766836e23..b335ff24b 100644 --- a/components/button/IconLoading.js +++ b/components/button/IconLoading.js @@ -3,18 +3,16 @@ import React from 'react' export default function IconLoading () { const size = '0.8em' return ( - <React.Fragment> - <i className='hi-icon hi-btn--loading--icon'> - <svg viewBox='0 0 18 18' width={size} height={size} fill='currentColor'> - <g> - <path - d='m9 18c-4.9706 0-9-4.0294-9-9 0-4.9706 4.0294-9 9-9 4.9706 0 9 4.0294 9 9 0 4.9706-4.0294 9-9 9zm0-2c3.866 0 7-3.134 7-7 0-3.866-3.134-7-7-7-3.866 0-7 3.134-7 7 0 3.866 3.134 7 7 7z' - opacity='.15' - /> - <path d='m15.547 2.8242c0.37904 0.40168 0.36068 1.0346-0.040996 1.4136-0.40168 0.37904-1.0346 0.36068-1.4136-0.040996-1.315-1.3935-3.1381-2.1969-5.0922-2.1969-3.866 0-7 3.134-7 7 0 0.55228-0.44772 1-1 1s-1-0.44772-1-1c0-4.9706 4.0294-9 9-9 2.5103 0 4.8578 1.0343 6.5468 2.8242z' /> - </g> - </svg> - </i> - </React.Fragment> + <i className='hi-icon hi-btn--loading--icon'> + <svg viewBox='0 0 18 18' width={size} height={size} fill='currentColor'> + <g> + <path + d='m9 18c-4.9706 0-9-4.0294-9-9 0-4.9706 4.0294-9 9-9 4.9706 0 9 4.0294 9 9 0 4.9706-4.0294 9-9 9zm0-2c3.866 0 7-3.134 7-7 0-3.866-3.134-7-7-7-3.866 0-7 3.134-7 7 0 3.866 3.134 7 7 7z' + opacity='.15' + /> + <path d='m15.547 2.8242c0.37904 0.40168 0.36068 1.0346-0.040996 1.4136-0.40168 0.37904-1.0346 0.36068-1.4136-0.040996-1.315-1.3935-3.1381-2.1969-5.0922-2.1969-3.866 0-7 3.134-7 7 0 0.55228-0.44772 1-1 1s-1-0.44772-1-1c0-4.9706 4.0294-9 9-9 2.5103 0 4.8578 1.0343 6.5468 2.8242z' /> + </g> + </svg> + </i> ) } From 21759ee6f9743abb5f2fc28d2839a2234fa2ab65 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 3 Jun 2019 16:35:25 +0800 Subject: [PATCH 092/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20tree=20=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=8B=96=E5=87=BA=E8=BE=B9=E7=95=8C=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/TreeItem.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/components/tree/TreeItem.js b/components/tree/TreeItem.js index cedfc4b80..2c1c43fc2 100644 --- a/components/tree/TreeItem.js +++ b/components/tree/TreeItem.js @@ -40,6 +40,7 @@ class TreeItem extends Component { renderSwitcher, connectDragSource, connectDropTarget, + isOver, targetNode, saveEditNode, origin, @@ -52,7 +53,9 @@ class TreeItem extends Component { display: 'flex' }} > - {targetNode === item.id && dropDividerPosition === 'down' && <TreeDivider top />} + {targetNode === item.id && dropDividerPosition === 'down' && isOver && ( + <TreeDivider top /> + )} { <span onClick={() => { @@ -135,7 +138,9 @@ class TreeItem extends Component { > {item.title} {renderRightClickMenu(item)} - {targetNode === item.id && dropDividerPosition === 'sub' && <TreeDivider />} + {targetNode === item.id && dropDividerPosition === 'sub' && isOver && ( + <TreeDivider /> + )} </span> ) ) : ( @@ -177,6 +182,14 @@ const source = { } props.onDragStart(props.item) return { sourceItem: props.item, originalExpandStatus: props.expanded } + }, + endDrag (props, monitor) { + const dropResult = monitor.getDropResult() + if (!dropResult) { + const { removeTargetNode, removeDraggingNode } = props + removeDraggingNode() + removeTargetNode() + } } } const target = { @@ -245,9 +258,10 @@ function sourceCollect (connect, monitor) { isDragging: monitor.isDragging() } } -function targetCollect (connect) { +function targetCollect (connect, monitor) { return { - connectDropTarget: connect.dropTarget() + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver() } } From d88c23513b28baa7b6ab93d76ca70421f6f371b7 Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Mon, 3 Jun 2019 16:39:39 +0800 Subject: [PATCH 093/112] fix(Rate): defaultValue bug --- components/rate/Rate.js | 26 +++++++++++++++++++++----- docs/zh-CN/components/rate.md | 12 ++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/components/rate/Rate.js b/components/rate/Rate.js index d0248d848..178e955a5 100644 --- a/components/rate/Rate.js +++ b/components/rate/Rate.js @@ -20,18 +20,34 @@ class Rate extends Component { } static defaultProps = { allowClear: true, - value: 0, + value: undefined, defaultValue: 0, count: 5, prefixCls: 'hi-rate', tooltips: [], onChange: () => {} } + static getDerivedStateFromProps ({ value }) { + if (value || value === 0) { + return { + value + } + } + } state = { - value: this.props.value || this.props.defaultValue, + value: 0, hoverValue: 0 } - renderIcon = idx => { + componentDidMount () { + this.initValue() + } + initValue = () => { + const { value, defaultValue } = this.props + this.setState({ + value: (value || value === 0) ? value : defaultValue + }) + } + renderIcon = (idx) => { const { useEmoji, allowHalf, disabled } = this.props const { value, hoverValue } = this.state let currentValue = hoverValue || value @@ -42,7 +58,7 @@ class Rate extends Component { <Icon {...{ value: idx, currentValue, disabled, useEmoji, allowHalf }} /> ) } - handleIconClick = value => { + handleIconClick = (value) => { const { allowHalf, allowClear, onChange, disabled } = this.props if (disabled) { return @@ -59,7 +75,7 @@ class Rate extends Component { onChange && onChange(value) this.setState({ value }) } - handleIconEnter = hoverValue => { + handleIconEnter = (hoverValue) => { if (this.props.disabled) { return } diff --git a/docs/zh-CN/components/rate.md b/docs/zh-CN/components/rate.md index 88fff1695..6ac39c8fc 100644 --- a/docs/zh-CN/components/rate.md +++ b/docs/zh-CN/components/rate.md @@ -7,6 +7,13 @@ :::demo ```js +constructor() { + super() + this.state = { + value: 1 + } +} + render() { return ( <Form labelWidth="80px" labelPosition="left"> @@ -16,6 +23,11 @@ render() { <FormItem label="半星"> <Rate allowHalf defaultValue={2.5} /> </FormItem> + <FormItem label="受控"> + <Rate value={this.state.value} defaultValue={3} onChange={value => { + this.setState({ value }) + }} /> + </FormItem> </Form> ) } From c56d278b8ec67cc5d008aa586d49cc0359996ffe Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 3 Jun 2019 16:59:17 +0800 Subject: [PATCH 094/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20onCheck=20?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/tree/Tree.js | 6 +++--- docs/zh-CN/components/tree.md | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 98c7fb294..5ba06c0e9 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -58,7 +58,7 @@ class Tree extends Component { } onCheckChange = (checked, item) => { - const { onChange, checkedKeys } = this.props + const { onChange, checkedKeys, onCheckChange, onCheck } = this.props let checkedArr = checkedKeys let { all } = this.state @@ -130,8 +130,8 @@ class Tree extends Component { }) onChange && onChange(checkedArr, item.title, !checked, semiChecked) - this.props.onCheckChange && - this.props.onCheckChange(checkedArr, item.title, !checked, semiChecked) + onCheckChange && onCheckChange(checkedArr, item.title, !checked, semiChecked) + onCheck && onCheck(checkedArr, item, !checked, semiChecked) } // 展开、收起节点 diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index 9728c5288..e33f4adfb 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -184,6 +184,9 @@ render() { checkedKeys }) }} + onCheck={(checkedKeys, item, bool, semi) => { + console.log('on check:', checkedKeys, item, bool ,semi) + }} highlightable onClick={data=>{console.log('tree node click',data)}} withLine @@ -412,6 +415,7 @@ render() { | onChange | 改变复选框状态时触发 | Function(checkedArr:Array, title: String, isChecked: Boolean) | - | - | | onNodeToggle | 节点被点击(展开/收起)时触发 | Function(data: Obejct, isExpanded: Boolean) | - | - | | onCheckChange | 节点选中项 | Funciton(checkedArr: Array, title: String, isChecked: Boolean) | - | - | +| onCheckChange | 点击节点多选框触发 | Funciton(checkedArr: Array, item: Object, isChecked: Boolean) | - | - | | onDragStart | 节点开始拖拽时触发 | Funciton(dragNode: Object) | - | - | | onDrop | 节点拖拽成功时触发 | Funciton(dragNode: Object, dropNode: Object) | - | - | | onDelete | 节点删除时触发 | Funciton(deleteNode: Object, data: Object) | - | - | From 5ea883120683e22e0fd574ac09ce7bb4ea448801 Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Mon, 3 Jun 2019 17:05:45 +0800 Subject: [PATCH 095/112] chore(Rate): update docs --- docs/en-US/components/rate.md | 31 ++++++++++++++++++------------- docs/zh-CN/components/rate.md | 31 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/docs/en-US/components/rate.md b/docs/en-US/components/rate.md index 4206d7051..b4e31cadd 100644 --- a/docs/en-US/components/rate.md +++ b/docs/en-US/components/rate.md @@ -97,16 +97,21 @@ render() { ### Rate Attributes -| Property | Description | Type | Options | Default | -| ------------ | --------------------------------------- | ----------------------- | ------- | ------- | -| allowClear | whether to allow clear when click again | boolean | - | true | -| allowHalf | whether to allow semi selection | boolean | - | true | -| useEmoji | whether to use emoji | boolean | - | false | -| className | custom class name of rate | string | - | - | -| count | star count, not work whem use emoji | number | - | 5 | -| defaultValue | default value | number | - | 0 | -| disabled | read only, unable to interact | boolean | - | false | -| style | custom style object of rate | object | - | - | -| tooltips | custom tooltip by each character | string[] | - | - | -| value | current value | number | - | - | -| onChange | callback when select value | (value: number) => void | - | - | +| Property | Description | Type | Options | Default | +| ------------ | --------------------------------------- | -------- | ------- | ------- | +| allowClear | whether to allow clear when click again | boolean | - | true | +| allowHalf | whether to allow semi selection | boolean | - | true | +| useEmoji | whether to use emoji | boolean | - | false | +| className | custom class name of rate | string | - | - | +| count | star count, not work whem use emoji | number | - | 5 | +| defaultValue | default value | number | - | 0 | +| disabled | read only, unable to interact | boolean | - | false | +| style | custom style object of rate | object | - | - | +| tooltips | custom tooltip by each character | string[] | - | - | +| value | current value | number | - | - | + +### Rate Events + +| Attribute | Description | Parameters | +| --------- | -------------- | --------------- | +| onChange | click callback | (value: number) | diff --git a/docs/zh-CN/components/rate.md b/docs/zh-CN/components/rate.md index 6ac39c8fc..2990da93d 100644 --- a/docs/zh-CN/components/rate.md +++ b/docs/zh-CN/components/rate.md @@ -109,16 +109,21 @@ render() { ### Rate Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -| ------------ | ------------------------- | ----------------------- | ------ | ------ | -| allowClear | 是否允许再次点击后清除 | boolean | - | true | -| allowHalf | 是否允许半选 | boolean | - | true | -| useEmoji | 是否使用表情 | boolean | - | false | -| className | 自定义类名 | string | - | - | -| count | star 数量,Emoji 时不可用 | number | - | 5 | -| defaultValue | 默认值 | number | - | 0 | -| disabled | 只读,无法进行交互 | boolean | - | false | -| style | 自定义样式对象 | object | - | - | -| tooltips | 自定义每项的提示信息 | string[] | - | - | -| value | 当前数,受控值 | number | - | - | -| onChange | 选择时的回调 | (value: number) => void | - | - | +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------------ | ------------------------- | -------- | ------------- | ------ | +| allowClear | 是否允许再次点击后清除 | boolean | true \| false | true | +| allowHalf | 是否允许半选 | boolean | true \| false | true | +| useEmoji | 是否使用表情 | boolean | true \| false | false | +| className | 自定义类名 | string | - | - | +| count | star 数量,Emoji 时不可用 | number | - | 5 | +| defaultValue | 默认值 | number | - | 0 | +| disabled | 只读,无法进行交互 | boolean | true \| false | false | +| style | 自定义样式对象 | object | - | - | +| tooltips | 自定义每项的提示信息 | string[] | - | - | +| value | 当前数,受控值 | number | - | - | + +### Rate Events + +| 参数 | 说明 | 回调参数 | +| -------- | -------------- | --------------- | +| onChange | 改变值时的回调 | (value: number) | From fbe99c2078e12ee51d9db0c7c90b9d11be81aa20 Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Mon, 3 Jun 2019 17:12:24 +0800 Subject: [PATCH 096/112] fix(Rate): allow clear bug --- components/rate/Rate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/rate/Rate.js b/components/rate/Rate.js index 178e955a5..4400f6a44 100644 --- a/components/rate/Rate.js +++ b/components/rate/Rate.js @@ -67,6 +67,7 @@ class Rate extends Component { value = Math.ceil(value) } if (value === this.state.value && allowClear) { + onChange && onChange({value: 0}) this.setState({ value: 0 }) From da114e1ee7a219cdea0e70e50767819abbe98135 Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Mon, 3 Jun 2019 17:40:01 +0800 Subject: [PATCH 097/112] fix(Tabs): trembling when switch --- components/tabs/style/index.scss | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/components/tabs/style/index.scss b/components/tabs/style/index.scss index 95e2420ff..8c6e25641 100644 --- a/components/tabs/style/index.scss +++ b/components/tabs/style/index.scss @@ -10,6 +10,7 @@ $prefix: 'hi-tabs' !default; .#{$prefix}__item { flex: 0 1 auto; cursor: pointer; + border: 1px solid transparent; &:not(&--active) { overflow: hidden; @@ -168,8 +169,10 @@ $prefix: 'hi-tabs' !default; padding: 16px 20px; text-align: center; border: 1px solid #e6e7e8; - border-left-width: 0; box-sizing: border-box; + &:not(:first-child) { + margin-left: -1px; + } &:hover { color: #4284f5; @@ -179,14 +182,10 @@ $prefix: 'hi-tabs' !default; } } - &:first-child { - border-left-width: 1px; - } - &--active { color: #4284f5; border-color: #4284f5; - border-left-width: 1px; + z-index: 1; .#{$prefix}__item-desc { color: #4284f5; From 47d9443bb8ef63f6a3f84ef6354ae094e61b8b97 Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Mon, 3 Jun 2019 17:41:35 +0800 Subject: [PATCH 098/112] chore: run fix --- components/loading/style/index.scss | 21 +++++++++++++++++++-- components/tabs/style/index.scss | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/components/loading/style/index.scss b/components/loading/style/index.scss index 0e8cdd193..1c287b0bb 100644 --- a/components/loading/style/index.scss +++ b/components/loading/style/index.scss @@ -11,6 +11,7 @@ align-items: center; height: 100%; width: 100%; + &--part { position: absolute; } @@ -40,73 +41,89 @@ &__icon { transform: translate(-50%); + &--small div { height: 8px; width: 8px; } + &--default div { height: 12px; width: 12px; } + &--large div { height: 16px; width: 16px; } + div { border-radius: 50%; display: inline-block; position: absolute; + @keyframes animDotF { 0% { transform: translateX(-100%); z-index: 1; } + 25% { transform: translateX(0) scale(1.5); z-index: 1; } + 50% { transform: translateX(100%); z-index: 0; } + 75% { transform: scale(0.5); z-index: 0; } + 100% { transform: translateX(-100%); z-index: 1; } } + @keyframes animDotL { 0% { transform: translateX(100%); z-index: 0; } + 25% { transform: translateX(0) scale(0.5); z-index: 0; } + 50% { transform: translateX(-100%); z-index: 1; } + 75% { transform: scale(1.5); z-index: 1; } + 100% { transform: translateX(100%); z-index: 0; } } + &:first-child { - background: #FF6633; + background: #f63; transform: translateX(-100%); animation: animDotF 1.5s linear infinite; } + &:last-child { - background: #4284F5; + background: #4284f5; transform: translateX(100%); animation: animDotL 1.5s linear infinite; // display: none; diff --git a/components/tabs/style/index.scss b/components/tabs/style/index.scss index 8c6e25641..86208fb5a 100644 --- a/components/tabs/style/index.scss +++ b/components/tabs/style/index.scss @@ -170,6 +170,7 @@ $prefix: 'hi-tabs' !default; text-align: center; border: 1px solid #e6e7e8; box-sizing: border-box; + &:not(:first-child) { margin-left: -1px; } From 1fc16c5d748ecf9d56bddf543738e83c9761074d Mon Sep 17 00:00:00 2001 From: Bougie <1742070326@qq.com> Date: Tue, 4 Jun 2019 15:42:42 +0800 Subject: [PATCH 099/112] fix(Button): localeDatas warning --- components/button/Button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/button/Button.js b/components/button/Button.js index a0ec856d2..4202c21a3 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -84,7 +84,7 @@ class Button extends Component { } } -function ButtonWrapper ({ children, ...props }) { +function ButtonWrapper ({ children, theme, locale, localeDatas, ...props }) { return props.href ? ( <a {...props}>{children}</a> ) : ( From 543e783b99ef0bfd77d04772931fc08b19dfa99d Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Tue, 4 Jun 2019 15:49:55 +0800 Subject: [PATCH 100/112] add tab es md --- docs/en-US/components/tabs.md | 357 ++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 docs/en-US/components/tabs.md diff --git a/docs/en-US/components/tabs.md b/docs/en-US/components/tabs.md new file mode 100644 index 000000000..b52a3c477 --- /dev/null +++ b/docs/en-US/components/tabs.md @@ -0,0 +1,357 @@ +## Tabs + + +### Card Tabs +:::demo + +```js +constructor(props) { + super(props) + this.state = { + panes: [ + { + tabName: 'Tab - 1', + tabKey: 'tabKey-1' + }, + { + tabName: 'Tab - 2', + tabKey: 'tabKey-2', + closable: false + }, + { + tabName: 'Tab - 3', + tabKey: 'tabKey-3' + }, + { + tabName: <span><Icon name="chat-group" />Tab</span>, + tabKey: 'tabKey-4' + }, + { + tabName: 'Tab - 4', + tabKey: 'tabKey-5' + }, + { + tabName: 'Tab - 5', + tabKey: 'tabKey-6' + }, + { + tabName: 'Tab - 6', + tabKey: 'tabKey-7' + }, + { + tabName: 'Tab - 7', + tabKey: 'tabKey-8' + }, + { + tabName: 'Tab - 8', + tabKey: 'tabKey-9' + }, + { + tabName: 'Tab - 9', + tabKey: 'tabKey-10' + } + ] + } +} + +render() { + return ( + <div> + <Tabs activeTabKey="1" onTabClick={(tab,e)=>console.log(tab,e)}> + { + this.state.panes.map((pane, index) => { + return ( + <Tabs.Pane + tabName={pane.tabName} + tabKey={pane.tabKey} + closable={pane.closable} + key={index} + > + <div style={{padding: '16px'}}>{pane.tabName}</div> + </Tabs.Pane> + ) + }) + } + </Tabs> + </div> + ) +} +``` +::: + + +### Add & Close +:::demo + +```js +constructor(props) { + super(props) + this.state = { + panes: [ + { + tabName: 'Tab - 1', + tabKey: 'tabKey-1' + }, + { + tabName: 'Tab - 2', + tabKey: 'tabKey-2', + closable: false + }, + { + tabName: 'Tab - 3', + tabKey: 'tabKey-3' + }, + { + tabName: 'Tab - 4', + tabKey: 'tabKey-4' + } + ] + } +} + +onEdit(action, index, tabKey) { + console.log('----------onEdit', action, index, tabKey) + this[`${action}Tab`](index, tabKey) +} + +addTab() { + const panes = this.state.panes + + this.setState({ + panes: panes.concat([{ + tabName: `New Tab - ${panes.length+1}`, + tabKey: `tabKey-${panes.length+1}` + }]) + }) +} + +deleteTab(index, tabKey) { + const panes = this.state.panes.slice() + panes.splice(index, 1) + + this.setState({ + panes + }) +} + +render() { + return ( + <div> + <Tabs type="editable" activeTabKey="1" onTabClick={(tab,e)=>console.log(tab,e)} editable onEdit={this.onEdit.bind(this)}> + { + this.state.panes.map((pane, index) => { + return ( + <Tabs.Pane + tabName={pane.tabName} + tabKey={pane.tabKey} + closable={pane.closable} + key={index} + > + <div style={{padding: '16px'}}>{pane.tabName}</div> + </Tabs.Pane> + ) + }) + } + </Tabs> + </div> + ) +} +``` +::: + + +### Vertical +:::demo + +```js +constructor(props) { + super(props) + this.state = { + panes: [ + { + tabName: 'Tab - 1', + tabKey: 'tabKey-1' + }, + { + tabName: 'Tab - 2', + tabKey: 'tabKey-2', + closable: false + }, + { + tabName: 'Tab - 3', + tabKey: 'tabKey-3' + }, + { + tabName: 'Tab - 4', + tabKey: 'tabKey-4' + } + ] + } +} + +render() { + return ( + <div> + <Tabs placement="left" activeTabKey="1" onTabClick={(tab,e)=>console.log(tab,e)}> + { + this.state.panes.map((pane, index) => { + return ( + <Tabs.Pane + tabName={pane.tabName} + tabKey={pane.tabKey} + closable={pane.closable} + key={index} + > + <div style={{padding: '16px'}}>{pane.tabName}</div> + </Tabs.Pane> + ) + }) + } + </Tabs> + </div> + ) +} +``` +::: + + +### Description +:::demo + +```js +constructor(props) { + super(props) + this.state = { + panes: [ + { + tabName: 'Tab - 1', + tabKey: 'tabKey-1', + tabDesc: 'Description' + }, + { + tabName: 'Tab - 2', + tabKey: 'tabKey-2', + closable: false, + tabDesc: 'Description' + }, + { + tabName: 'Tab - 3', + tabKey: 'tabKey-3', + tabDesc: 'Description' + }, + { + tabName: 'Tab - 4', + tabKey: 'tabKey-4', + tabDesc: 'Description' + } + ] + } +} + +render() { + return ( + <div> + <Tabs type="desc" activeTabKey="1" onTabClick={(tab,e)=>console.log(tab,e)}> + { + this.state.panes.map((pane, index) => { + return ( + <Tabs.Pane + tabName={pane.tabName} + tabDesc={pane.tabDesc} + tabKey={pane.tabKey} + closable={pane.closable} + key={index} + > + <div style={{padding: '16px'}}>{pane.tabName}</div> + </Tabs.Pane> + ) + }) + } + </Tabs> + </div> + ) +} +``` +::: + + + + +### Button Tab +:::demo + +```js +constructor(props) { + super(props) + this.state = { + panes: [ + { + tabName: 'Tab - 1', + tabKey: 'tabKey-1' + }, + { + tabName: 'Tab - 2', + tabKey: 'tabKey-2', + closable: false + }, + { + tabName: 'Tab - 3', + tabKey: 'tabKey-3' + }, + { + tabName: 'Tab - 4', + tabKey: 'tabKey-4' + } + ] + } +} + +render() { + return ( + <div> + <Tabs type="button" activeTabKey="1" onTabClick={(tab,e)=>console.log(tab,e)}> + { + this.state.panes.map((pane, index) => { + return ( + <Tabs.Pane + tabName={pane.tabName} + tabKey={pane.tabKey} + closable={pane.closable} + key={index} + > + <div style={{padding: '16px'}}>{pane.tabName}</div> + </Tabs.Pane> + ) + }) + } + </Tabs> + </div> + ) +} +``` +::: + + + +### Tabs Attributes + +| Attribute | Description | Type | Options |Default | +| -------- | ----- | ---- | ---- | ---- | +| type | Type | String | desc \| card \| button \| editable | card | +| placement | Label location | String | top \| left | top | +| defaultActiveKey | Default activated tag | String \| number | - | First tab | +| showTabsNum | Number of tabs displayed | number | - | 6 | +| editable | Whether it can be added or deleted,Only valid for type='editable' | Boolean | true \| false | true | +| onTabClick | Triggered when clicking on a tab | func(tabKey , event) | - | - | +| onEdit | Triggered when the tag is newly added,Only valid for type='editable' | func(action, index, tabKey) | - | - | + + + +### Tabs.Pane Attributes + +| Attribute | Description | Type | Options |Default | +| -------- | ----- | ---- | ---- | ---- | +| tabName | tabname | String \| Element | - | - | +| tabDesc | description | String \| Element | - | - | +| tabKey | key | String \| Number | - | - | +| closable | Whether the tag can be closed, only valid for type='editable' | Boolean | true \| false | true | From 81c0bdbdf3447eaac7581d5afbd4111ba1992dd7 Mon Sep 17 00:00:00 2001 From: zhangjunjie <zhangjunjie1@xiaomi.com> Date: Tue, 4 Jun 2019 18:20:38 +0800 Subject: [PATCH 101/112] fix :ref error --- components/popover/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/popover/index.js b/components/popover/index.js index 19f249c08..e242cf4d0 100644 --- a/components/popover/index.js +++ b/components/popover/index.js @@ -30,7 +30,7 @@ export default class Popover extends Component { } this.eventTarget = null this.popperRef = React.createRef() - // this.referenceRef = React.createRef() + this.referenceRef = null } showPopper () { @@ -52,7 +52,7 @@ export default class Popover extends Component { isInPopover () { const popper = this.popperRef.current - const referenceRef = ReactDOM.findDOMNode(this.refs.referenceRef) + const referenceRef = ReactDOM.findDOMNode(this.referenceRef) const bool = !this.element || this.element.contains(this.eventTarget) || !referenceRef || referenceRef.contains(this.eventTarget) || !popper || popper.contains(this.eventTarget) @@ -64,7 +64,7 @@ export default class Popover extends Component { const { trigger } = this.props this.element = ReactDOM.findDOMNode(this) - const referenceRef = ReactDOM.findDOMNode(this.refs.referenceRef) + const referenceRef = ReactDOM.findDOMNode(this.referenceRef) // this.reference = ReactDOM.findDOMNode(this.refs.reference) if (referenceRef === null) return @@ -121,7 +121,7 @@ export default class Popover extends Component { return ( <div className={classNames(className, 'hi-popover')} style={style} ref={node => { this.popoverContainer = node }}> - { React.cloneElement(React.Children.only(this.props.children), { ref: 'referenceRef', tabIndex: '0' }) } + { React.cloneElement(React.Children.only(this.props.children), { ref: (el) => { this.referenceRef = el }, tabIndex: '0' }) } <Popper className='hi-popover__popper' From 2057b06866a4fa020d4df8ce7a5cd7588b2f1535 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 6 Jun 2019 19:33:24 +0800 Subject: [PATCH 102/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=80=92=E5=BD=92?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/menu/index.js | 81 ++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/components/menu/index.js b/components/menu/index.js index 839d5409b..64a589972 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -142,37 +142,62 @@ class Menu extends Component { return this.props.mode === 'vertical' && !mini } - getActiveIndex (activeId) { // 获取激活item对应的索引,以'-'拼接成字符串 - const { - datas - } = this.props - let activeIndex = [] - let level = 0 - let matchFlag = false - - if (activeId === undefined) { - return activeIndex - } - - const _getActiveIndex = function (datas) { - for (const index in datas) { - const data = datas[index] - activeIndex[level] = parseInt(index) - if (data.children) { - level++ - _getActiveIndex(data.children) - level-- - } else if (data.id === activeId) { - matchFlag = true - break - } - if (matchFlag) { - break + getActiveMenus = (menus, activeId, activeMenus = []) => { + let result + for (let index in menus) { + console.log('menus', menus) + let _activeMenus = [...activeMenus] + if (menus[index].children) { + _activeMenus.push(index) + result = this.getActiveMenus(menus[index].children, activeId, _activeMenus) + } else { + if (menus[index].id === activeId) { + _activeMenus.push(index) + result = _activeMenus } } + if (result) { + break + } + } + console.log('result', result) + if (result) { + return result } - _getActiveIndex(datas) - return activeIndex.join('-') + } + + getActiveIndex (activeId) { + // 获取激活item对应的索引,以'-'拼接成字符串 + const { datas } = this.props + // let activeIndex = [] + // let level = 0 + // let matchFlag = false + + if (activeId === undefined || activeId === '') { + return '' + } + + // const _getActiveIndex = function (datas) { + // for (const index in datas) { + // const data = datas[index] + // activeIndex[level] = parseInt(index) + // if (data.children) { + // level++ + // _getActiveIndex(data.children) + // level-- + // } else if (data.id === activeId) { + // matchFlag = true + // break + // } + // if (matchFlag) { + // break + // } + // } + // } + // _getActiveIndex(datas) + // return activeIndex.join('-') + const activeMenus = this.getActiveMenus(datas, activeId, []) + return (activeMenus && activeMenus.join('-')) || '' } toggleMini () { From ce7e3864e0fe142ec2d566f986a3ad7095621fba Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Mon, 10 Jun 2019 09:38:31 +0800 Subject: [PATCH 103/112] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/menu/index.js | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/components/menu/index.js b/components/menu/index.js index 64a589972..de4d2376d 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -145,22 +145,18 @@ class Menu extends Component { getActiveMenus = (menus, activeId, activeMenus = []) => { let result for (let index in menus) { - console.log('menus', menus) let _activeMenus = [...activeMenus] if (menus[index].children) { _activeMenus.push(index) result = this.getActiveMenus(menus[index].children, activeId, _activeMenus) - } else { - if (menus[index].id === activeId) { - _activeMenus.push(index) - result = _activeMenus - } + } else if (menus[index].id === activeId) { + _activeMenus.push(index) + result = _activeMenus } if (result) { break } } - console.log('result', result) if (result) { return result } @@ -169,33 +165,10 @@ class Menu extends Component { getActiveIndex (activeId) { // 获取激活item对应的索引,以'-'拼接成字符串 const { datas } = this.props - // let activeIndex = [] - // let level = 0 - // let matchFlag = false if (activeId === undefined || activeId === '') { return '' } - - // const _getActiveIndex = function (datas) { - // for (const index in datas) { - // const data = datas[index] - // activeIndex[level] = parseInt(index) - // if (data.children) { - // level++ - // _getActiveIndex(data.children) - // level-- - // } else if (data.id === activeId) { - // matchFlag = true - // break - // } - // if (matchFlag) { - // break - // } - // } - // } - // _getActiveIndex(datas) - // return activeIndex.join('-') const activeMenus = this.getActiveMenus(datas, activeId, []) return (activeMenus && activeMenus.join('-')) || '' } From 6589ab5f71a413849d9539e33f205055773c9818 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Wed, 12 Jun 2019 15:46:01 +0800 Subject: [PATCH 104/112] #291 --- components/select/Select.js | 42 ++++++++++++++++----------------- docs/en-US/components/select.md | 26 ++++++++++---------- docs/zh-CN/components/select.md | 30 +++++++++++------------ 3 files changed, 48 insertions(+), 50 deletions(-) diff --git a/components/select/Select.js b/components/select/Select.js index a0394d057..9cbd4c526 100644 --- a/components/select/Select.js +++ b/components/select/Select.js @@ -194,35 +194,32 @@ class Select extends Component { this.onClickOption(item, focusedIndex) } - onChange () { - const { - selectedItems - } = this.state + onChange (changedItems) { + const { selectedItems } = this.state - this.props.onChange && this.props.onChange(selectedItems) + this.props.onChange && this.props.onChange(selectedItems, changedItems) } - checkAll (e) { // 全选 + checkAll (e) { + // 全选 e && e.stopPropagation() - const { - dropdownItems - } = this.state - let selectedItems = this.state.selectedItems.concat() - + const { dropdownItems, selectedItems } = this.state + let _selectedItems = [...selectedItems] + let changedItems = [] dropdownItems.forEach(item => { if (!item.disabled && this.matchFilter(item)) { - let itemIndex = selectedItems.findIndex((sItem) => { - return sItem.id === item.id - }) - itemIndex === -1 && selectedItems.push(item) + if (!_selectedItems.map(selectItem => selectItem.id).includes(item.id)) { + _selectedItems.push(item) + changedItems.push(item) + } } }) this.setState({ - selectedItems + selectedItems: _selectedItems }, () => { this.selectInput.focus() - this.onChange() + this.onChange(changedItems) }) } @@ -256,7 +253,7 @@ class Select extends Component { this.clearKeyword() // 多选状态清空筛选 } - this.onChange() + this.onChange(item) }) } @@ -312,20 +309,21 @@ class Select extends Component { selectedItems }, () => { this.selectInput.focus() - this.onChange() + this.onChange(item) }) } // 全部删除 deleteAllItems () { const focusedIndex = this.resetFocusedIndex() - + const changedItems = [...this.state.selectedItems] this.setState({ focusedIndex, selectedItems: [] }, () => { - this.onChange() + this.onChange(this.props.mode === 'multiple' ? changedItems : changedItems[0]) this.onFilterItems('') - }) + } + ) } remoteSearch (keyword) { diff --git a/docs/en-US/components/select.md b/docs/en-US/components/select.md index 5ee3ce5cc..c6040043f 100644 --- a/docs/en-US/components/select.md +++ b/docs/en-US/components/select.md @@ -34,8 +34,8 @@ render () { placeholder='Please Select' style={{width: '200px'}} value={'3'} - onChange={(item) => { - console.log('Result', item) + onChange={(item, changedItem) => { + console.log('Result', item, changedItem) }} /> </div> @@ -78,8 +78,8 @@ render () { placeholder='Please Select' style={{width: '200px'}} value={'3'} - onChange={(item) => { - console.log('Result', item) + onChange={(item, changedItem) => { + console.log('Result', item, changedItem) }} disabled /> @@ -119,8 +119,8 @@ render () { placeholder='Please Select...' style={{width: '200px'}} value={'3'} - onChange={(item) => { - console.log('Result', item) + onChange={(item, changedItem) => { + console.log('Result', item, changedItem) }} /> </div> @@ -152,8 +152,8 @@ render () { }} placeholder='Please Select...' style={{width: '200px'}} - onChange={(item) => { - console.log('Result', item) + onChange={(item, changedItem) => { + console.log('Result', item, changedItem) }} /> </div> @@ -192,8 +192,8 @@ render () { list={this.state.multipleList} value='4,5' placeholder='Please Select...' - onChange={(item) => { - console.log('Result', item) + onChange={(item, changedItem) => { + console.log('Result', item, changedItem) }} /> </div> @@ -225,8 +225,8 @@ render () { } }} placeholder='Please...' - onChange={(item) => { - console.log('Result', item) + onChange={(item, changedItem) => { + console.log('Result', item, changedItem) }} /> </div> @@ -254,4 +254,4 @@ render () { | Attribute | Description | Parameters | | -------- | ----- | ---- | -| onChange | callback when changing options | (item: Object\|Array) | +| onChange | callback when changing options | (item: Object\|Array, changedItem:Object\|Array) | diff --git a/docs/zh-CN/components/select.md b/docs/zh-CN/components/select.md index 07934483d..b45b0a3a4 100644 --- a/docs/zh-CN/components/select.md +++ b/docs/zh-CN/components/select.md @@ -39,8 +39,8 @@ render () { style={{width: '200px'}} list={this.state.singleList} value={this.state.value} - onChange={(item) => { - console.log('单选结果', item) + onChange={(item, changedItem) => { + console.log('单选结果', item, changedItem) item[0] && this.setState({value: item[0].id}) }} /> @@ -79,8 +79,8 @@ render () { list={this.state.singleList} placeholder='请选择品类' style={{width: '200px'}} - onChange={(item) => { - console.log('单选结果', item) + onChange={(item, changedItem) => { + console.log('单选结果', item, changedItem) }} disabled /> @@ -121,8 +121,8 @@ render () { value={'3'} list={this.state.singleList} searchable={true} - onChange={(item) => { - console.log('单选结果', item) + onChange={(item, changedItem) => { + console.log('单选结果', item, changedItem) }} dropdownRender={(item, isSelected) => { return ( @@ -165,8 +165,8 @@ render () { }} placeholder='请选择种类' style={{width: '200px'}} - onChange={(item) => { - console.log('异步单选结果', item) + onChange={(item, changedItem) => { + console.log('异步单选结果', item, changedItem) }} /> </div> @@ -211,8 +211,8 @@ render () { searchable={true} placeholder='请选择...' noFoundTip='无匹配数据' - onChange={(item) => { - console.log('多选结果', item) + onChange={(item, changedItem) => { + console.log('多选结果', item, changedItem) }} /> </React.Fragment> @@ -260,8 +260,8 @@ render () { showCheckAll={true} placeholder='请选择...' noFoundTip='无匹配数据' - onChange={(item) => { - console.log('多选结果', item) + onChange={(item, changedItem) => { + console.log('多选结果', item, changedItem) }} /> </React.Fragment> @@ -298,8 +298,8 @@ render () { }, error: err => console.log('error:', err) }} - onChange={(item) => { - console.log('异步多选结果', item) + onChange={(item,changedItem) => { + console.log('异步多选结果', item, changedItem) }} /> </div> @@ -356,4 +356,4 @@ render () { | 参数 | 说明 | 回调参数 | | -------- | ----- | ---- | -| onChange | 改变选项时触发函数 | (item: Object\|Array) | +| onChange | 改变选项时触发函数 | (item: Object\|Array, changedItem:Object\|Array) | From efd5056f0c62c866202e217ab2150eb57102929e Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Wed, 12 Jun 2019 15:48:08 +0800 Subject: [PATCH 105/112] update --- components/select/Select.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/select/Select.js b/components/select/Select.js index 9cbd4c526..18f224f27 100644 --- a/components/select/Select.js +++ b/components/select/Select.js @@ -322,8 +322,7 @@ class Select extends Component { }, () => { this.onChange(this.props.mode === 'multiple' ? changedItems : changedItems[0]) this.onFilterItems('') - } - ) + }) } remoteSearch (keyword) { From cc4b716322433990e7f5bffa6995d71180779e59 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 13 Jun 2019 10:38:10 +0800 Subject: [PATCH 106/112] fix #298 --- components/date-picker/BasePicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/date-picker/BasePicker.js b/components/date-picker/BasePicker.js index d5c09e2a7..7dbe8108c 100644 --- a/components/date-picker/BasePicker.js +++ b/components/date-picker/BasePicker.js @@ -189,7 +189,7 @@ class BasePicker extends Component { if (startDate && endDate) { if (type === 'weekrange') { onChange({start: startOfWeek(startDate, _weekOffset), end: endOfWeek(endDate, _weekOffset)}) - } else if (type === 'timerange' || type === 'timeperiod') { + } else if (['timerange', 'timeperiod', 'daterange'].includes(type)) { onChange({start: startDate, end: endDate}) } else { onChange({start: startOfDay(startDate), end: endOfDay(endDate)}) From 4179c9d4148caddd7b770cff7794c2400d394a25 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 13 Jun 2019 11:16:44 +0800 Subject: [PATCH 107/112] fix #292 --- components/transfer/index.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/components/transfer/index.js b/components/transfer/index.js index bbedd3a7a..c3e038a7c 100755 --- a/components/transfer/index.js +++ b/components/transfer/index.js @@ -287,6 +287,8 @@ class Transfer extends Component { 'hi-transfer__operation', mode === 'basic' && 'hi-transfer__operation--basic' ) + const isLeftDisabled = targetSelectedKeys.length === 0 + const isRightDisabled = sourceSelectedKeys.length === 0 || limited return ( <div className='hi-transfer'> {this.renderContainer('left', sourceList)} @@ -294,17 +296,25 @@ class Transfer extends Component { {mode !== 'basic' && ( <React.Fragment> <Button - type={sourceSelectedKeys.length === 0 || limited ? 'default' : 'primary'} + type={isRightDisabled ? 'default' : 'primary'} icon='right' - onClick={this.moveTo.bind(this, 'left')} - disabled={sourceSelectedKeys.length === 0 || limited} + onClick={() => { + if (!isRightDisabled) { + this.moveTo('left') + } + }} + disabled={isRightDisabled} /> <span className='hi-transfer__split' /> <Button - type={targetSelectedKeys.length === 0 ? 'default' : 'primary'} + type={isLeftDisabled ? 'default' : 'primary'} icon='left' - onClick={this.moveTo.bind(this, 'right')} - disabled={targetSelectedKeys.length === 0} + onClick={() => { + if (!isLeftDisabled) { + this.moveTo('right') + } + }} + disabled={isLeftDisabled} /> </React.Fragment> )} From 09effeefbda5d269f32832cc21090a14896544ca Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 13 Jun 2019 16:05:49 +0800 Subject: [PATCH 108/112] fix #283 --- components/tree/Tree.js | 14 ++++++++------ docs/en-US/components/tree.md | 4 +++- docs/zh-CN/components/tree.md | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 5ba06c0e9..6aadb163d 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -34,19 +34,21 @@ class Tree extends Component { if (!isEqual(props.data, state.data)) { const dataMap = {} dealData(props.data, dataMap) + console.log('dataMap', dataMap) data.dataMap = dataMap data.data = props.data if (state.data.length === 0) { - if (props.defaultExpandAll) { - let tempExpandedArr = [] - for (let key in dataMap) { - if (dataMap[key].children && dataMap[key].children.length > 0) { - tempExpandedArr.push(dataMap[key].id) + let defaultExpandedArr = [] + + for (let key in dataMap) { + if (dataMap[key].children && dataMap[key].children.length > 0) { + if ((props.defaultExpandAll && dataMap[key].expanded !== false) || dataMap[key].expanded === true) { + defaultExpandedArr.push(dataMap[key].id) } } - data.hasExpanded = tempExpandedArr } + data.hasExpanded = defaultExpandedArr } } diff --git a/docs/en-US/components/tree.md b/docs/en-US/components/tree.md index 9cb551325..f25d4c21e 100644 --- a/docs/en-US/components/tree.md +++ b/docs/en-US/components/tree.md @@ -10,6 +10,7 @@ this.treeData = [ { id: 1, title: '小米', + expanded: false, children: [ { id: 2, title: '技术', @@ -83,6 +84,7 @@ checkbox this.treeData = [ { id: 1, title: '小米', + expanded: true, children: [ { id: 2, title: '技术', @@ -159,7 +161,7 @@ checkbox | Attribute | Description | Type | Options | Default | | ------- | ------- | ------- | ------- | ------- | -| expand | whether to expand the submenu (priority is higher than defaultExpandAll) | blooean | true, false | false | +| expanded | whether to expand the submenu (priority is higher than defaultExpandAll) | blooean | true, false | - | | onClick | event triggered when clicked | function ### Attribute-options diff --git a/docs/zh-CN/components/tree.md b/docs/zh-CN/components/tree.md index e33f4adfb..6019e941a 100644 --- a/docs/zh-CN/components/tree.md +++ b/docs/zh-CN/components/tree.md @@ -14,6 +14,7 @@ constructor(props) { this.state = { treeData: [ { id: 1, title: '小米', + expanded: false, children: [ { id: 2, title: '技术', children: [ @@ -140,6 +141,7 @@ constructor(props) { super(props) this.treeData = [ { id: 1, title: '小米人', + expanded: true, children: [ { id: 2, title: '技术', children: [ @@ -415,7 +417,7 @@ render() { | onChange | 改变复选框状态时触发 | Function(checkedArr:Array, title: String, isChecked: Boolean) | - | - | | onNodeToggle | 节点被点击(展开/收起)时触发 | Function(data: Obejct, isExpanded: Boolean) | - | - | | onCheckChange | 节点选中项 | Funciton(checkedArr: Array, title: String, isChecked: Boolean) | - | - | -| onCheckChange | 点击节点多选框触发 | Funciton(checkedArr: Array, item: Object, isChecked: Boolean) | - | - | +| onCheck | 点击节点多选框触发 | Funciton(checkedArr: Array, item: Object, isChecked: Boolean) | - | - | | onDragStart | 节点开始拖拽时触发 | Funciton(dragNode: Object) | - | - | | onDrop | 节点拖拽成功时触发 | Funciton(dragNode: Object, dropNode: Object) | - | - | | onDelete | 节点删除时触发 | Funciton(deleteNode: Object, data: Object) | - | - | @@ -425,7 +427,7 @@ render() { | 参数 | 说明 | 类型 | 可选值 | 默认值 | | ----------- | ------------------------------------------------------------------ | -------- | ------ | ------ | -| expand | 默认是否展开子菜单(优先级高于 defaultExpandAll) | Boolean | - | false | +| expanded | 默认是否展开子菜单(优先级高于 defaultExpandAll) | Boolean | - | - | | onClick | 点击每项时触发的事件 | Function | - | - | | onNodeClick | 点击每项时触发,onClick 作用具体绑定的项,onNodeClick 作用于所以项 | Function | - | - | | style | 单个节点样式 | Object | - | - | From 23b54eac5e6867ea8f6d7a74b3617abcbfee2b52 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 13 Jun 2019 16:06:30 +0800 Subject: [PATCH 109/112] remove console --- components/tree/Tree.js | 1 - 1 file changed, 1 deletion(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index 6aadb163d..a2ba01f52 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -34,7 +34,6 @@ class Tree extends Component { if (!isEqual(props.data, state.data)) { const dataMap = {} dealData(props.data, dataMap) - console.log('dataMap', dataMap) data.dataMap = dataMap data.data = props.data From 1d8bdff1bee034975bcd91038a0d6549aec9adc4 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 13 Jun 2019 17:08:04 +0800 Subject: [PATCH 110/112] optimize code --- components/tree/Tree.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index a2ba01f52..bc2bf505d 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -41,10 +41,11 @@ class Tree extends Component { let defaultExpandedArr = [] for (let key in dataMap) { - if (dataMap[key].children && dataMap[key].children.length > 0) { - if ((props.defaultExpandAll && dataMap[key].expanded !== false) || dataMap[key].expanded === true) { - defaultExpandedArr.push(dataMap[key].id) - } + const item = dataMap[key] + const itemHasCHildren = item.children && item.children.length > 0 + const itemShouldExpand = (props.defaultExpandAll && item.expanded !== false) || item.expanded === true + if (itemHasCHildren && itemShouldExpand) { + defaultExpandedArr.push(item.id) } } data.hasExpanded = defaultExpandedArr From 9709c0e5de6b16232aaee067c09dbae6ef852574 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Thu, 13 Jun 2019 17:10:03 +0800 Subject: [PATCH 111/112] update --- components/tree/Tree.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/tree/Tree.js b/components/tree/Tree.js index bc2bf505d..82a8c0fa0 100644 --- a/components/tree/Tree.js +++ b/components/tree/Tree.js @@ -42,9 +42,9 @@ class Tree extends Component { for (let key in dataMap) { const item = dataMap[key] - const itemHasCHildren = item.children && item.children.length > 0 + const itemHasChildren = item.children && item.children.length > 0 const itemShouldExpand = (props.defaultExpandAll && item.expanded !== false) || item.expanded === true - if (itemHasCHildren && itemShouldExpand) { + if (itemHasChildren && itemShouldExpand) { defaultExpandedArr.push(item.id) } } From a0918e574ef4b2705eb7802d2e76c2cf1b091679 Mon Sep 17 00:00:00 2001 From: solarjoker <chenissolar@gmail.com> Date: Fri, 14 Jun 2019 16:09:56 +0800 Subject: [PATCH 112/112] update changelog --- CHANGELOG.md | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79d5deaa3..9b8f1ab97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,40 +3,18 @@ ## 1.5.0 - 新增:`<Select />` 组件 `onChange` 支持获取改变值 [#291](https://github.com/XiaoMi/hiui/issues/291) -- 修复:`<Tree />` 组件 `expand` 无效的问题 [#283](https://github.com/XiaoMi/hiui/issues/283) -- 修复:`<Menu />` 组件高亮计算错误的问题 [#289](https://github.com/XiaoMi/hiui/issues/289) -- 修复:`<Datepicker />` 组件 `onchange` 获取时间错误的问题 [#298](https://github.com/XiaoMi/hiui/issues/298) -- 修复:`<Transfer />`组件数量达到上限仍可穿梭的问题 [#292](https://github.com/XiaoMi/hiui/issues/292) - -## 1.5.0-rc.6 - - 新增:`<Tree />` 组件 `onCheck` 事件 [#267](https://github.com/XiaoMi/hiui/issues/267) - 新增:`<Select />` 组件支持控制下拉菜单展示的属性 [#262](https://github.com/XiaoMi/hiui/issues/262) - 新增:`<Button />` 组件 loading 状态 [#166](https://github.com/XiaoMi/hiui/issues/166) -- 修复:`<Menu />` 组件 mini toggle 子菜单不关闭问题 [#276](https://github.com/XiaoMi/hiui/issues/276) -- 修复:`<Rate />` 组件不受控问题 [#275](https://github.com/XiaoMi/hiui/issues/275) -- 修复:`<Tree />` 组件拖出指定区域的样式问题 [#270](https://github.com/XiaoMi/hiui/issues/270) -- 修复:`<Tabs />` 组件标签切换样式抖动问题 [#260](https://github.com/XiaoMi/hiui/issues/260) -- 修复:`<Form />` 组件表单 label 和 field 的间隔问题 [#245](https://github.com/XiaoMi/hiui/issues/245) -- 修复:`<Form />` 表单项间隔问题 [#244](https://github.com/XiaoMi/hiui/issues/244) - -## 1.5.0-rc.5 - - 新增:`<Timeline />` 时间轴组件 [#239](https://github.com/XiaoMi/hiui/issues/239) - 新增:`<Loading />` 组件 UI 样式 [#238](https://github.com/XiaoMi/hiui/issues/238) - 新增:`<Tooltip />` 组件 api 调用功能 [#240](https://github.com/XiaoMi/hiui/issues/240) - -## 1.5.0-rc.4 - - 新增:`<Transfer />` 穿梭框组件 [#108](https://github.com/XiaoMi/hiui/issues/108) - 新增:`<Rate />` 评分组件 [#106](https://github.com/XiaoMi/hiui/issues/106) - 新增:`<Switch />` 开关组件 [#107](https://github.com/XiaoMi/hiui/issues/107) -- 修复:`<Menu />` 组件在某些情况下抛出的 key 的警告问题 [#215](https://github.com/XiaoMi/hiui/issues/215) - -## 1.5.0-rc.1 - -- 新增:`<Card />` 组件 -- 修改:`<Tree />`、`<Datepicker />` 组件,调整新的 UI,增加新的 API +- 新增:`<Card />` 组件[#113](https://github.com/XiaoMi/hiui/issues/113) +- 优化:`<Datepicker />` 组件,调整新的 UI,增加新的 API [#105](https://github.com/XiaoMi/hiui/issues/105) +- 优化:`<Tree />` 组件,增加搜索、拖拽、编辑功能 [#112](https://github.com/XiaoMi/hiui/issues/112) ## 1.4.6