diff --git a/.npmignore b/.npmignore index 7aa526911..31d9b81b1 100644 --- a/.npmignore +++ b/.npmignore @@ -31,3 +31,4 @@ Desktop.ini Thumbs.db .Spotlight-V100 .Trashes +postcss.config.js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e71106fd3..d6c089e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # 更新日志 +## 3.4.0 + +- 优化组件弹出层自动计算合适的左右位置 [#1494](https://github.com/XiaoMi/hiui/issues/1494) +- 新增 `Select SelectTree Cascader DatePicker Input` 等组件无边框形态 [#1553](https://github.com/XiaoMi/hiui/issues/1553) +- 新增 `Preview` 预览组件 [#1546](https://github.com/XiaoMi/hiui/issues/1546) +- 新增 `Select` 组件 onSearch、onOverlayScroll 方法 [#1522](https://github.com/XiaoMi/hiui/issues/1522) +- 修复 `SelectTree` 搜索输入框在输入值时失焦问题 [#1491](https://github.com/XiaoMi/hiui/issues/1491) +- 修复 `SelectTree` 单选形态下受控问题 [#1519](https://github.com/XiaoMi/hiui/issues/1519) +- 修复 `Select` 组件分组形态 filterOption 函数无法使用问题 [#1497](https://github.com/XiaoMi/hiui/issues/1497) +- 修复 `Select` 组件分组形态全选以及受控问题 [#1501](https://github.com/XiaoMi/hiui/issues/1501) +- 修复 `Select` 异步数据请求返回结果顺序异常 [#1543](https://github.com/XiaoMi/hiui/issues/1543) +- 修复 `Tabs` 组件垂直方向样式显示异常问题 [#1493](https://github.com/XiaoMi/hiui/issues/1493) +- 修复 `Form` DatePicker、SelectTree 在 Form.Item 中点击清空Icon 无效问题 [#1524](https://github.com/XiaoMi/hiui/issues/1524) +- 修复 `DatePicker` minDate、maxDate、disabledDate 在非 date 类型下不生效问题 [#1547](https://github.com/XiaoMi/hiui/issues/1547) +- 优化 `Checkbox` 样式相关内容 [#1482](https://github.com/XiaoMi/hiui/issues/1482) +- 优化 `SelectTree` 异步受控数据返显问题 [#1510](https://github.com/XiaoMi/hiui/issues/1510) +- 优化 `Select SelectTree` 计数根据窗口自动调整 [#1527](https://github.com/XiaoMi/hiui/issues/1527) +- 优化 `Drawer` 组件支持className属性 [#1536](https://github.com/XiaoMi/hiui/issues/1536) + ## 3.3.0 + - 新增 `Card` 模式模式下 loading 加载中状态 [#1454](https://github.com/XiaoMi/hiui/issues/1454) - 新增 `Table` loading 加载中状态 [#1466](https://github.com/XiaoMi/hiui/issues/1466) - 新增 `Table` 列冻结结合树形使用 [#1424](https://github.com/XiaoMi/hiui/issues/1424) diff --git a/components/_util/EventEmitter.js b/components/_util/EventEmitter.js index a271531ea..e9eb39969 100644 --- a/components/_util/EventEmitter.js +++ b/components/_util/EventEmitter.js @@ -10,7 +10,6 @@ class EventEmitter { // 触发事件 emit(type, ...arg) { - console.log(this.event) this.event[type] && this.event[type](...arg) } diff --git a/components/button/ButtonGroup.js b/components/button/ButtonGroup.js index 40ff4ea3a..26f5fdeb4 100644 --- a/components/button/ButtonGroup.js +++ b/components/button/ButtonGroup.js @@ -6,14 +6,14 @@ class ButtonGroup extends Component { prefixCls: 'hi-btn-group' } - render () { - const { - prefixCls, - style, - className - } = this.props + render() { + const { prefixCls, style, className } = this.props const classes = classNames(`${prefixCls}`, className) - return
{this.props.children}
+ return ( +
+ {this.props.children} +
+ ) } } diff --git a/components/button/index.d.ts b/components/button/index.d.ts index 6a41363d3..484be4ebf 100644 --- a/components/button/index.d.ts +++ b/components/button/index.d.ts @@ -11,5 +11,14 @@ interface ButtonProps { icon?: string onClick?: () => void } -declare const Button: React.ComponentType +interface ButtonGroupProps { + className?: string + style?: object + prefixCls?: string +} +declare class ButtonGroup extends React.Component { +} +declare class Button extends React.Component { + static Group = ButtonGroup +} export default Button diff --git a/components/card/index.d.ts b/components/card/index.d.ts index a269f7216..be5f77daf 100644 --- a/components/card/index.d.ts +++ b/components/card/index.d.ts @@ -8,6 +8,8 @@ interface Props { extraType?: 'default' | 'hover' coverUrl?: string content?: string | JSX.Element + style?: CSSProperties + className?: string } declare class Card extends React.Component { } diff --git a/components/carousel/index.d.ts b/components/carousel/index.d.ts index 518e97804..aab7f57d8 100644 --- a/components/carousel/index.d.ts +++ b/components/carousel/index.d.ts @@ -5,6 +5,8 @@ interface Props { defaultActive?: number showPages?: boolean children: JSX.Element[] + style?: CSSProperties + className?: string } declare const Carousel: React.ComponentType export default Carousel diff --git a/components/carousel/index.js b/components/carousel/index.js index a1c52e777..e2257bbb2 100644 --- a/components/carousel/index.js +++ b/components/carousel/index.js @@ -71,6 +71,15 @@ class Carousel extends Component { this.setState({ showArrow }) } + handleKeyDown = (evt) => { + const { keyCode } = evt + if ([39, 37].includes(keyCode)) { + evt.preventDefault() + evt.stopPropagation() + this.preNextEvent(keyCode === 37 ? -1 : 1) + } + } + render() { const { rootWidth, active, showArrow } = this.state const { showDots, showArrows, showPages } = this.props @@ -81,6 +90,8 @@ class Carousel extends Component {
diff --git a/components/carousel/style/index.scss b/components/carousel/style/index.scss index 135e9e19a..dec306aad 100644 --- a/components/carousel/style/index.scss +++ b/components/carousel/style/index.scss @@ -3,6 +3,7 @@ color: #fff; overflow: hidden; position: relative; + outline: none; &__container { height: 100%; @@ -54,6 +55,7 @@ visibility: hidden; height: 0; z-index: 2; + &--show { visibility: visible; } diff --git a/components/cascader/Cascader.js b/components/cascader/Cascader.js index 71af10b7e..66e4bdb6e 100644 --- a/components/cascader/Cascader.js +++ b/components/cascader/Cascader.js @@ -1,7 +1,7 @@ import React, { useRef, useState, useCallback, useEffect } from 'react' import classNames from 'classnames' - +import _ from 'lodash' import Popper from '../popper' import Menu from './Menu' @@ -25,12 +25,9 @@ const Cascader = (props) => { changeOnSelect = false, localeDatas, onChange = noop, - onActiveItemChange = noop + onActiveItemChange = noop, + bordered = true } = props - const data = props.data || props.options // 兼容1.x API 2.x 改为data - const emptyContent = props.emptyContent || props.noFoundTip // 兼容1.x API 2.x改为 emptyContent - const expandTrigger = props.expandTrigger || props.trigger || 'click' // 兼容2.x API 1.x改为 expandTrigger - const getLabelKey = useCallback(() => { return fieldNames.label || 'content' }, []) @@ -42,7 +39,25 @@ const Cascader = (props) => { const getChildrenKey = useCallback(() => { return fieldNames.children || 'children' }, []) - + const parseData = useCallback( + (data) => { + const _data = _.cloneDeep(data) + const setDataItemPath = (data = [], parent) => { + data.forEach((item, index) => { + item._path = parent ? parent._path + '-' + index : index + if (item[getChildrenKey()]) { + setDataItemPath(item[getChildrenKey()], item) + } + }) + } + setDataItemPath(_data) + return _data + }, + [props.data, props.options] + ) + const data = parseData(props.data || props.options) // 兼容1.x API 2.x 改为data + const emptyContent = props.emptyContent || props.noFoundTip // 兼容1.x API 2.x改为 emptyContent + const expandTrigger = props.expandTrigger || props.trigger || 'click' // 兼容2.x API 1.x改为 expandTrigger const getCascaderLabel = useCallback( (values, dataList) => { if (displayRender) { @@ -83,7 +98,9 @@ const Cascader = (props) => { const [cacheValue, setCacheValue] = useState(value || defaultValue || []) const [cascaderValue, setCascaderValue] = useState(value || defaultValue || []) const [cascaderLabel, setCascaderLabel] = useState(getCascaderLabel(value || defaultValue || [])) - + const [focusOptionIndex, setFocusOptionIndex] = useState(-1) + const targetByKeyDown = useRef(false) + const currentDeep = useRef(0) const [popperShow, setPopperShow] = useState(false) const [keyword, setKeyword] = useState('') @@ -97,6 +114,10 @@ const Cascader = (props) => { setCacheValue(value) } }, [value]) + useEffect(() => { + setFocusOptionIndex(-1) + currentDeep.current = 0 + }, [popperShow]) useEffect(() => { setCascaderLabel(getCascaderLabel(cacheValue)) @@ -355,12 +376,160 @@ const Cascader = (props) => { [popperShow, cacheValue, cascaderValue, keyword] ) + const parseFocusOptionIndex = useCallback( + (deep = currentDeep.current) => { + const optionIndexs = focusOptionIndex < 0 ? [focusOptionIndex] : String(focusOptionIndex).split('-') + return { + optionIndexs: optionIndexs.slice(0, deep + 1), + focusOptionIndex: optionIndexs[deep] || -1 + } + }, + [focusOptionIndex, currentDeep.current] + ) + // 获取不同深度的数据, 该深度的全部数据而不是单条数据 + const getDeepData = useCallback( + (deep = currentDeep.current) => { + const { optionIndexs } = parseFocusOptionIndex(deep) + let _data = data + if (optionIndexs.length <= 1) { + return _data + } + _data = optionIndexs.reduce((deepData, current, index) => { + if (index === 0) return deepData + const _index = optionIndexs[index - 1] + return deepData[_index][getChildrenKey()] + }, data) + return _data || [] + }, + [parseFocusOptionIndex, data, currentDeep.current] + ) + // 上下按键 + const moveFocusedIndex = useCallback( + (direction) => { + targetByKeyDown.current = true + const _data = currentDeep.current === 0 ? data : getDeepData() + const { focusOptionIndex: _focusOptionIndex } = parseFocusOptionIndex() + const isAllDisabled = _data.every((item) => { + return item.disabled + }) + let index = direction === 'down' ? _focusOptionIndex / 1 + 1 : _focusOptionIndex / 1 - 1 + if (index < 0) { + index = _data.length - 1 + } else if (index >= _data.length) { + index = 0 + } + if (!isAllDisabled) { + while (data[index] && data[index].disabled) { + index++ + } + if (currentDeep.current > 0) { + const _focusOptionIndex = String(focusOptionIndex).split('-') + _focusOptionIndex[currentDeep.current] = index + index = _focusOptionIndex.join('-') + } else { + index = String(index) + } + setFocusOptionIndex(index) + } else { + setFocusOptionIndex(-1) + } + }, + [targetByKeyDown.current, currentDeep.current, data, getDeepData, parseFocusOptionIndex, focusOptionIndex] + ) + // 右方向按键 + const rightHandle = useCallback(() => { + targetByKeyDown.current = true + currentDeep.current++ + const { optionIndexs } = parseFocusOptionIndex() + const optionValues = [] + const l = optionIndexs.length + optionIndexs.map((item, index) => { + const _data = getDeepData(index) + optionValues.push(_data[item].id) + }) + const currentItem = getDeepData(l - 1)[optionIndexs[l - 1]] || {} + const children = currentItem[getChildrenKey()] || [] + const hasChildren = !!children.length + onChangeValue(optionValues, hasChildren) + let index = 0 + while (children[index] && children[index].disabled) { + index++ + } + index = hasChildren ? focusOptionIndex + '-' + index : focusOptionIndex + setFocusOptionIndex(index) + }, [targetByKeyDown.current, parseFocusOptionIndex, focusOptionIndex, onChangeValue, currentDeep.current]) + // 左方向按键 + const leftHandle = useCallback(() => { + targetByKeyDown.current = true + + const optionsIndex = focusOptionIndex.split('-') + if (cascaderValue.length === 0) { + currentDeep.current = 0 + return + } + currentDeep.current-- + setFocusOptionIndex(optionsIndex.splice(0, optionsIndex.length - 1).join('-')) + onChangeValue(cascaderValue.splice(0, cascaderValue.length - 1), true) + }, [focusOptionIndex, targetByKeyDown.current, onChangeValue, currentDeep.current, cascaderValue]) + // 按键操作 + const handleKeyDown = useCallback( + (evt) => { + // space + if (evt.keyCode === 32 && !searchable) { + evt.preventDefault() + evt.stopPropagation() + setPopperShow(!popperShow) + } + // esc + if (evt.keyCode === 27) { + evt.stopPropagation() + setPopperShow(false) + } + if (popperShow) { + // down + if (evt.keyCode === 40) { + evt.stopPropagation() + evt.preventDefault() + moveFocusedIndex('down') + } + // up + if (evt.keyCode === 38) { + evt.preventDefault() + evt.stopPropagation() + moveFocusedIndex('up') + } + // right + if (evt.keyCode === 39 || evt.keyCode === 13) { + evt.preventDefault() + evt.stopPropagation() + rightHandle() + } + // left + if (evt.keyCode === 37) { + evt.preventDefault() + evt.stopPropagation() + leftHandle() + } + } + }, + [moveFocusedIndex, rightHandle, leftHandle] + ) + const expandIcon = popperShow ? 'icon-up' : 'icon-down' const placeholder = cascaderLabel || localeDatasProps('placeholder') - return ( -
-
+
+
{ childrenKey={getChildrenKey()} onSelect={onChangeValue} onHover={onHover} + currentDeep={currentDeep} expandTrigger={expandTrigger} + focusOptionIndex={focusOptionIndex} + setFocusOptionIndex={setFocusOptionIndex} + targetByKeyDown={targetByKeyDown} emptyContent={emptyContent} localeDatasProps={localeDatasProps} /> diff --git a/components/cascader/Menu.js b/components/cascader/Menu.js index d18d4da5b..207fe52c2 100644 --- a/components/cascader/Menu.js +++ b/components/cascader/Menu.js @@ -19,7 +19,11 @@ const Menu = forwardRef( onHover, expandTrigger, localeDatasProps, - emptyContent + emptyContent, + focusOptionIndex, + currentDeep, + targetByKeyDown, + setFocusOptionIndex }, ref ) => { @@ -30,9 +34,9 @@ const Menu = forwardRef( const menus = [] while (currentOptions) { const currentValue = value[deep] - const _currentOptions = currentOptions.slice() currentOptions = false + // currentDeep.current = deep if ((isFiltered && value.length > deep) || !isFiltered) { menus.push(
    { e.stopPropagation() - !option.disabled && onSelect(optionValues, hasChildren) + if (!option.disabled) { + onSelect(optionValues, hasChildren) + targetByKeyDown.current = false + setFocusOptionIndex(_path) + currentDeep.current = _deep + } }} onMouseEnter={(e) => { e.stopPropagation() - !option.disabled && expandTrigger === 'hover' && onHover(optionValues, hasChildren) + if (!option.disabled && expandTrigger === 'hover') { + onHover(optionValues, hasChildren) + targetByKeyDown.current = false + setFocusOptionIndex(_path) + currentDeep.current = _deep + } }} key={optionValue + index} > @@ -86,7 +102,7 @@ const Menu = forwardRef( } } return menus - }, [options, value, valueKey]) + }, [options, value, valueKey, focusOptionIndex]) const getOptionValues = useCallback((values, optionValue, index) => { if (index === 0) { @@ -109,7 +125,7 @@ const Menu = forwardRef( {value.length === 0 && isFiltered && ( <> -

    {emptyContent || localeDatasProps('emptyContent')}

    ' ' +

    {emptyContent || localeDatasProps('emptyContent')}

    )}
diff --git a/components/cascader/index.d.ts b/components/cascader/index.d.ts index 00260c43f..988b01fad 100644 --- a/components/cascader/index.d.ts +++ b/components/cascader/index.d.ts @@ -15,6 +15,7 @@ interface Props { defaultValue: string[] | number[] expandTrigger?: 'click' | 'hover' searchable?: boolean + bordered?: boolean filterOption?: (keyword: string, item: DataItem) => boolean clearable?: boolean disabled?: boolean @@ -25,6 +26,8 @@ interface Props { style?: object onChange?: (value: string[] | number[]) => void overlayClassName?: string + style?: CSSProperties + className?: string } declare const Card: React.ComponentType export default Card diff --git a/components/cascader/style/cascader.scss b/components/cascader/style/cascader.scss index 2aa476777..aef4c0a9b 100644 --- a/components/cascader/style/cascader.scss +++ b/components/cascader/style/cascader.scss @@ -3,6 +3,8 @@ $cascader: 'hi-cascader' !default; .#{$cascader} { + outline: none; + &.hi-cascader--focused { .hi-cascader { &__input-container { @@ -41,7 +43,11 @@ $cascader: 'hi-cascader' !default; display: flex; align-items: center; padding: 5px 12px; - border: 1px solid #d8d8d8; + box-sizing: border-box; + + &.bordered { + border: 1px solid #d8d8d8; + } } &__input-keyword { diff --git a/components/cascader/style/menu.scss b/components/cascader/style/menu.scss index cddb7c11e..5e237959c 100644 --- a/components/cascader/style/menu.scss +++ b/components/cascader/style/menu.scss @@ -54,7 +54,7 @@ $AnimationClassName: 'hi-cascader_transition' !default; padding-right: 25px; } - &.hi-cascader-menu__item-active { + &.hi-cascader-menu__item-active:not(.hi-cascader-menu__item-focus) { color: #4284f5; background-color: transparent !important; @@ -67,6 +67,10 @@ $AnimationClassName: 'hi-cascader_transition' !default; background-color: #f5f5f5; } + &-focus { + background-color: rgba(0, 0, 0, 0.1); + } + &--icon { position: absolute; right: 12px; diff --git a/components/checkbox/style/index.scss b/components/checkbox/style/index.scss index 59fc1208b..3bbc2f9e4 100644 --- a/components/checkbox/style/index.scss +++ b/components/checkbox/style/index.scss @@ -44,9 +44,8 @@ $prefixCls: '.hi-checkbox' !default; @include component-reset(); input { - position: absolute; - width: 16px; - height: 16px; + width: 0; + height: 0; opacity: 0; } diff --git a/components/collapse/index.d.ts b/components/collapse/index.d.ts index 92ed5593f..9de69bb7d 100644 --- a/components/collapse/index.d.ts +++ b/components/collapse/index.d.ts @@ -6,6 +6,8 @@ interface Props { showArrow?: boolean onChange?: () => void children: Collapse.Panel + style?: CSSProperties + className?: string } interface PanelProps { id?: string diff --git a/components/date-picker/BasePicker.jsx b/components/date-picker/BasePicker.jsx index b0843d0b8..655279302 100644 --- a/components/date-picker/BasePicker.jsx +++ b/components/date-picker/BasePicker.jsx @@ -24,8 +24,6 @@ const BasePicker = ({ clearable = true, width = 'auto', weekOffset, - min = null, - max = null, hourStep, minuteStep, secondStep, @@ -42,8 +40,22 @@ const BasePicker = ({ placement = 'top-bottom-start', inputReadOnly, locale, + bordered = true, + disabledDate, ...otherPorps }) => { + // 兼容2.x api -> max,min + const [max, setMax] = useState(otherPorps.max || otherPorps.maxDate || null) + const [min, setMin] = useState(otherPorps.min || otherPorps.minDate || null) + + useEffect(() => { + setMax(otherPorps.max || otherPorps.maxDate || null) + }, [otherPorps.max, otherPorps.maxDate]) + + useEffect(() => { + setMin(otherPorps.min || otherPorps.minDate || null) + }, [otherPorps.min, otherPorps.minDate]) + const cacheDate = useRef(null) const [inputFocus, setInputFocus] = useState(false) const [type, setType] = useState(propType) @@ -179,13 +191,16 @@ const BasePicker = ({ minuteStep, secondStep, inputReadOnly, - value + value, + bordered, + disabledDate }} > { setShowPanel(true) diff --git a/components/date-picker/TimePicker.d.ts b/components/date-picker/TimePicker.d.ts index a856c306d..b0ad888cc 100644 --- a/components/date-picker/TimePicker.d.ts +++ b/components/date-picker/TimePicker.d.ts @@ -3,6 +3,8 @@ interface TimeProps extends CommonProps { disabledHours?: () => number[] disabledMinutes?: (selectedHour: number) => number[] disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[] + style?: CSSProperties + className?: string } declare const TimePicker: React.ComponentType export default TimePicker diff --git a/components/date-picker/components/Calender.jsx b/components/date-picker/components/Calender.jsx index 9ea5ffdcb..a71038fd4 100644 --- a/components/date-picker/components/Calender.jsx +++ b/components/date-picker/components/Calender.jsx @@ -43,7 +43,8 @@ const Calender = ({ view = 'date', originDate, onPick, range, mouseMove, panelPo dateMarkRender, dateMarkPreset, altCalendarPresetData, - dateMarkPresetData + dateMarkPresetData, + disabledDate } = useContext(DPContext) const largeCell = altCalendar || altCalendarPreset || dateMarkRender || dateMarkPreset @@ -61,7 +62,8 @@ const Calender = ({ view = 'date', originDate, onPick, range, mouseMove, panelPo range, min, max, - panelPosition + panelPosition, + disabledDate }) const [calenderCls, setCalenderCls] = useState('hi-datepicker__calender') @@ -97,7 +99,6 @@ const Calender = ({ view = 'date', originDate, onPick, range, mouseMove, panelPo const onTableMouseMove = (e) => { const ele = e.target - // let {year, month} = deconstructDate(date) const panelDate = _.cloneDeep(renderDate) if (type.includes('range')) { const val = ele.getAttribute('value') @@ -171,9 +172,9 @@ const Calender = ({ view = 'date', originDate, onPick, range, mouseMove, panelPo } return (
- -
-
{holidayFullName}
+ +
+
{holidayFullName}
@@ -203,12 +204,12 @@ const Calender = ({ view = 'date', originDate, onPick, range, mouseMove, panelPo // className='hi-datepicker__cell' className={getTDClass(cell, largeCell)} > -
+
{parseInt(cell.text || cell.value) < 10 ? '0' + (cell.text || cell.value) diff --git a/components/date-picker/components/Root.jsx b/components/date-picker/components/Root.jsx index 0f6ff8344..9cf5e2578 100644 --- a/components/date-picker/components/Root.jsx +++ b/components/date-picker/components/Root.jsx @@ -27,7 +27,8 @@ const Root = ({ theme, width, value, - format + format, + bordered } = useContext(DPContext) const [inputData, setInputData] = useState(outDate) const inputRef = useRef(null) @@ -58,7 +59,8 @@ const Root = ({ inputFocus && 'hi-datepicker__picker--focus', disabled && 'hi-datepicker__picker--disabled', showTime && 'hi-datepicker__picker--hastime', - rangeInputIsError && 'hi-datepicker__picker--error' + rangeInputIsError && 'hi-datepicker__picker--error', + { bordered } ) const renderRange = type.includes('range') || type === 'timeperiod' diff --git a/components/date-picker/hooks/useCalenderData.js b/components/date-picker/hooks/useCalenderData.js index c05a19d40..0ac99b2cb 100644 --- a/components/date-picker/hooks/useCalenderData.js +++ b/components/date-picker/hooks/useCalenderData.js @@ -3,7 +3,7 @@ import moment from 'moment' import { DAY_MILLISECONDS } from '../constants' import _ from 'lodash' -const getYearOrMonthRows = ({ originDate, renderDate, type, view, range, localeDatas }) => { +const getYearOrMonthRows = ({ originDate, renderDate, type, view, range, localeDatas, min, max, disabledDate }) => { const _date = renderDate ? moment(renderDate) : moment() const start = view === 'year' ? _date.year() - 4 : 0 const trs = [[], [], [], []] @@ -42,6 +42,44 @@ const getYearOrMonthRows = ({ originDate, renderDate, type, view, range, localeD if (originDate && (y === originDate.year() || y === originDate.month())) { col.type = 'selected' } + // 判断年月可选状态 + const _y = currentYM.year() + const _m = currentYM.month() + + if (disabledDate && view.includes('year')) { + col.type = disabledDate(_y) ? 'disabled' : col.type + } + if (disabledDate && view.includes('mouth')) { + col.type = disabledDate(_y + '-' + _m) ? 'disabled' : col.type + } + // 年的状态 + if (view.includes('year') && (min || max)) { + if (min) { + const minYear = moment(min).year() + col.type = _y < minYear ? 'disabled' : col.type + } + if (max) { + const maxYear = moment(max).year() + col.type = _y > maxYear ? 'disabled' : col.type + } + } + + if (view.includes('month') && (min || max)) { + if (min) { + const minMoment = moment(min) + const minYear = minMoment.year() + const minMonth = minMoment.month() + col.type = _y < minYear ? 'disabled' : col.type + col.type = _y === minYear && _m < minMonth ? 'disabled' : col.type + } + if (max) { + const maxMoment = moment(max) + const maxYear = maxMoment.year() + const maxMonth = maxMoment.month() + col.type = _y < maxYear ? 'disabled' : col.type + col.type = _y === maxYear && _m > maxMonth ? 'disabled' : col.type + } + } } } return trs @@ -51,7 +89,7 @@ const getTime = (week, y, m) => { const t = r.getTime() - week * DAY_MILLISECONDS return t } -const getDateRows = ({ originDate, range, type, weekOffset, min, max, renderDate, view }) => { +const getDateRows = ({ originDate, range, type, weekOffset, min, max, renderDate, view, disabledDate }) => { const rows = [[], [], [], [], [], []] const today = moment() const _date = moment(renderDate) @@ -82,7 +120,10 @@ const getDateRows = ({ originDate, range, type, weekOffset, min, max, renderDate }) const currentTime = moment(startTimeByCurrentPanel + DAY_MILLISECONDS * (i * 7 + j)) let isPN = false // is Prev Or Next Month - const isDisabled = currentTime.isBefore(moment(min)) || currentTime.isAfter(moment(max)) // isDisabled cell + const isDisabled = + currentTime.isBefore(moment(min)) || + currentTime.isAfter(moment(max)) || + (disabledDate && disabledDate(currentTime)) // isDisabled cell if (i === 0) { // 处理第一行的日期数据 if (j >= firstDayWeek) { @@ -110,7 +151,7 @@ const getDateRows = ({ originDate, range, type, weekOffset, min, max, renderDate if (isDisabled) { col.type = 'disabled' } - if (!isPN && currentTime.isSame(today, 'day')) { + if (!isPN && currentTime.isSame(today, 'day') && col.type !== 'disabled') { col.type = 'today' } if (type.includes('range') && !isPN) { @@ -154,7 +195,19 @@ const getDateRows = ({ originDate, range, type, weekOffset, min, max, renderDate } return rows } -const useDate = ({ view, date, originDate, weekOffset, localeDatas, range, type, min, max, renderDate }) => { +const useDate = ({ + view, + date, + originDate, + weekOffset, + localeDatas, + range, + type, + min, + max, + renderDate, + disabledDate +}) => { const [rows, setRows] = useState([]) useEffect(() => { const _rows = @@ -165,7 +218,10 @@ const useDate = ({ view, date, originDate, weekOffset, localeDatas, range, type, type, view, localeDatas, - range + range, + min, + max, + disabledDate }) : getDateRows({ originDate, @@ -175,10 +231,11 @@ const useDate = ({ view, date, originDate, weekOffset, localeDatas, range, type, min, max, renderDate, - view + view, + disabledDate }) setRows(_rows) - }, [renderDate, view, range, type]) + }, [renderDate, view, range, type, disabledDate]) return [rows] } diff --git a/components/date-picker/index.d.ts b/components/date-picker/index.d.ts index 8288996ab..f632128a8 100644 --- a/components/date-picker/index.d.ts +++ b/components/date-picker/index.d.ts @@ -32,6 +32,7 @@ interface DateProps extends CommonProps { minDate?: Date max?: Date maxDate?: Date + bordered?: boolean disabledDate?: (currentDate: Date) => boolean showTime?: boolean shortcuts?: string[] | Shortcuts[] @@ -41,6 +42,8 @@ interface DateProps extends CommonProps { dateMarkRender?: (currentDate: Date, today: Date) => JSX.Element dateMarkPreset?: 'zh-CN' overlayClassName?: string + style?: CSSProperties + className?: string } declare const DatePicker: React.ComponentType diff --git a/components/date-picker/style/index.scss b/components/date-picker/style/index.scss index 40388cef0..0af30b132 100644 --- a/components/date-picker/style/index.scss +++ b/components/date-picker/style/index.scss @@ -45,7 +45,6 @@ $error-color: get-color($palette-secondary, 'danger') !default; &__picker { background: use-color('white'); - border: 1px solid use-color('gray-30'); border-radius: 2px; height: 32px; width: 180px; @@ -55,15 +54,19 @@ $error-color: get-color($palette-secondary, 'danger') !default; justify-content: space-around; box-sizing: border-box; - &:hover:not(.hi-datepicker__picker--disabled):not(.hi-datepicker__picker--error) { + &.bordered { + border: 1px solid use-color('gray-30'); + } + + &:hover:not(.hi-datepicker__picker--disabled):not(.hi-datepicker__picker--error).bordered { border: 1px solid use-color('primary'); } - &--focus { + &--focus.bordered { border: 1px solid use-color('primary'); } - &--error { + &--error.bordered { border: 1px solid $error-color; } diff --git a/components/drawer/index.d.ts b/components/drawer/index.d.ts index 6417f7d67..7b86c9baf 100644 --- a/components/drawer/index.d.ts +++ b/components/drawer/index.d.ts @@ -10,6 +10,8 @@ interface Props { footer?: JSX.Element placement?: 'left' | 'right' onClose?: (e: MouseEvent) => void + style?: CSSProperties + className?: string } declare const Drawer: React.ComponentType export default Drawer diff --git a/components/drawer/index.jsx b/components/drawer/index.jsx index f604b0709..0588037a0 100644 --- a/components/drawer/index.jsx +++ b/components/drawer/index.jsx @@ -25,7 +25,8 @@ const DrawerComp = ({ footer, width, showMask = true, - placement = 'right' + placement = 'right', + className }) => { // TODO: 整体可以抽成一个 hooks 供 modal 和 drawer 复用 const defaultContainer = useRef(false) @@ -79,7 +80,7 @@ const DrawerComp = ({ }, [visible, container]) return ReactDOM.createPortal( -
+
{showMask && (
{ }, [props.labelWidth, formProps.labelWidth]) const setEvent = (eventName, component, componentProps, e, ...args) => { - e.persist && e.persist() + const beObject = Object.prototype.toString.call(e) === '[object Object]' + beObject && Object.prototype.toString.call(e.persist) === '[object Function]' && e.persist() const displayName = component && component.type && component.type.displayName const _props = componentProps || children.props eventName === 'onChange' && _props.onChange && _props.onChange(e, ...args) eventName === 'onBlur' && _props.onBlur && _props.onBlur(e, ...args) - let value = e.target && Object.prototype.hasOwnProperty.call(e.target, valuePropName) ? e.target[valuePropName] : e + let value = + beObject && e.target && Object.prototype.hasOwnProperty.call(e.target, valuePropName) + ? e.target[valuePropName] + : e if (displayName === 'Counter') { value = args[0] } diff --git a/components/form/index.d.ts b/components/form/index.d.ts index fb9a28c1d..53f2264f8 100644 --- a/components/form/index.d.ts +++ b/components/form/index.d.ts @@ -11,6 +11,8 @@ interface FormProps { placement?: 'horizontal' | 'vertical' showColon?: boolean children: Form.Item + style?: CSSProperties + className?: string } interface ItemProps { diff --git a/components/hi-base-table/index.d.ts b/components/hi-base-table/index.d.ts new file mode 100644 index 000000000..bf5ed43d1 --- /dev/null +++ b/components/hi-base-table/index.d.ts @@ -0,0 +1,675 @@ +declare module 'react-base-table' { + export type SortOrder = 'asc' | 'desc'; + + export type Alignment = 'left' | 'right' | 'center'; + + export type FrozenDirection = 'left' | 'right' | true | false; + + export type RowKey = string | number; + + export type Size = { width: number; height: number }; + + export type CallOrReturn = T | (P extends any[] ? (...p: P) => T : (p: P) => T); + + export interface ColumnShape { + /** + * Unique key for each column + */ + key: React.Key; + /** + * Class name for the column cell + */ + className?: CallOrReturn< + string, + { + cellData: any; + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: T; + rowIndex: number; + } + >; + /** + * Class name for the column header + */ + headerClassName?: CallOrReturn< + string, + { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + headerIndex: number; + } + >; + /** + * Custom style for the column cell, including the header cells + */ + style?: React.CSSProperties; + /** + * Title of the column header + */ + title?: string; + /** + * Data key for the cell value, could be "a.b.c" + */ + dataKey?: string; + /** + * Custom cell data getter + * The handler is of the shape of `({ columns, column, columnIndex, rowData, rowIndex }) => node` + */ + dataGetter?: CallOrReturn< + React.ReactNode, + { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: T; + rowIndex: number; + } + >; + /** + * Alignment of the column cell + */ + align?: Alignment; + /** + * Flex grow style, defaults to 0 + */ + flexGrow?: number; + /** + * Flex shrink style, defaults to 1 for flexible table and 0 for fixed table + */ + flexShrink?: number; + /** + * The width of the column, gutter width is not included + */ + width: number; + /** + * Maximum width of the column, used if the column is resizable + */ + maxWidth?: number; + /** + * Minimum width of the column, used if the column is resizable + */ + minWidth?: number; + /** + * Whether the column is frozen and what's the frozen side + */ + frozen?: FrozenDirection; + /** + * Whether the column is hidden + */ + hidden?: boolean; + /** + * Whether the column is resizable, defaults to false + */ + resizable?: boolean; + /** + * Whether the column is sortable, defaults to false + */ + sortable?: boolean; + /** + * Custom column cell renderer + * The renderer receives props `{ cellData, columns, column, columnIndex, rowData, rowIndex, container, isScrolling }` + */ + cellRenderer?: CallOrReturn< + React.ReactNode, + { + cellData: any; + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: T; + rowIndex: number; + container: BaseTable; + isScrolling?: boolean; + } + >; + /** + * Custom column header renderer + * The renderer receives props `{ columns, column, columnIndex, headerIndex, container }` + */ + headerRenderer?: CallOrReturn< + React.ReactNode, + { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + headerIndex: number; + container: BaseTable; + } + >; + [key: string]: any; + } + + export class Column extends React.Component> { + static readonly Alignment: { + readonly LEFT: 'left'; + readonly CENTER: 'center'; + readonly RIGHT: 'right'; + }; + static readonly FrozenDirection: { + readonly LEFT: 'left'; + readonly RIGHT: 'right'; + readonly DEFAULT: true; + readonly NONE: false; + }; + } + type FixedOption = { + left?: string + right?: string + } + export interface BaseTableProps { + sticky?: boolean + stickyTop?: number + bordered?: boolean, + autoResize?: boolean, + bordered?: boolean, + fixedToColumn?: string | FixedOption, + /** + * Prefix for table's inner className + */ + classPrefix?: string; + /** + * Class name for the table + */ + className?: string; + /** + * Custom style for the table + */ + style?: React.CSSProperties; + /** + * A collection of Column + */ + children?: React.ReactNode; + /** + * Columns for the table + */ + columns?: ColumnShape[]; + /** + * The data for the table + */ + data?: T[]; + /** + * The data to be frozen to top, `rowIndex` is negative and starts from `-1` + */ + frozenData?: T[]; + /** + * The key field of each data item + */ + rowKey?: RowKey; + /** + * The width of the table + */ + width: number; + /** + * The height of the table, will be ignored if `maxHeight` is set + */ + height?: number; + /** + * The max height of the table, the table's height will auto change when data changes, + * will turns to vertical scroll if reaches the max height + */ + maxHeight?: number; + /** + * The height of each table row, will be ignored if `estimatedRowHeight` is set + */ + rowHeight?: number; + /** + * Estimated row height, the real height will be measure dynamically according to the content + * The callback is of the shape of `({ rowData, rowIndex }) => number` + */ + estimatedRowHeight?: CallOrReturn< + number, + { + rowData: T; + rowIndex: number; + } + >; + /** + * The height of the table header, set to 0 to hide the header, could be an array to render multi headers. + */ + headerHeight?: number | number[]; + /** + * The height of the table footer + */ + footerHeight?: number; + /** + * Whether the width of the columns are fixed or flexible + */ + fixed?: boolean; + /** + * Whether the table is disabled + */ + disabled?: boolean; + /** + * Custom renderer on top of the table component + */ + overlayRenderer?: CallOrReturn; + /** + * Custom renderer when the length of data is 0 + */ + emptyRenderer?: CallOrReturn; + /** + * Custom footer renderer, available only if `footerHeight` is larger then 0 + */ + footerRenderer?: CallOrReturn; + /** + * Custom header renderer + * The renderer receives props `{ cells, columns, headerIndex }` + */ + headerRenderer?: CallOrReturn< + React.ReactNode, + { + cells: React.ReactNode[]; + columns: ColumnShape; + headerIndex: number; + } + >; + /** + * Custom row renderer + * The renderer receives props `{ isScrolling, cells, columns, rowData, rowIndex, depth }` + */ + rowRenderer?: CallOrReturn< + React.ReactNode, + { + isScrolling?: boolean; + cells: React.ReactNode[]; + columns: ColumnShape; + rowData: T; + rowIndex: number; + depth: number; + } + >; + /** + * Class name for the table header, could be a callback to return the class name + * The callback is of the shape of `({ columns, headerIndex }) => string` + */ + headerClassName?: CallOrReturn< + string, + { + columns: ColumnShape[]; + headerIndex: number; + } + >; + /** + * Class name for the table row, could be a callback to return the class name + * The callback is of the shape of `({ columns, rowData, rowIndex }) => string` + */ + rowClassName?: CallOrReturn< + string, + { + columns: ColumnShape[]; + rowData: T; + rowIndex: number; + } + >; + /** + * Extra props applied to header element + * The handler is of the shape of `({ columns, headerIndex }) object` + */ + headerProps?: CallOrReturn< + object, + { + columns: ColumnShape[]; + headerIndex: number; + } + >; + /** + * Extra props applied to header cell element + * The handler is of the shape of `({ columns, column, columnIndex, headerIndex }) => object` + */ + headerCellProps?: CallOrReturn< + object, + { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + headerIndex: number; + } + >; + /** + * Extra props applied to row element + * The handler is of the shape of `({ columns, rowData, rowIndex }) => object` + */ + rowProps?: CallOrReturn< + object, + { + columns: ColumnShape[]; + rowData: T; + rowIndex: number; + } + >; + /** + * Extra props applied to row cell element + * The handler is of the shape of `({ columns, column, columnIndex, rowData, rowIndex }) => object` + */ + cellProps?: CallOrReturn< + object, + { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: T; + rowIndex: number; + } + >; + /** + * Extra props applied to ExpandIcon component + * The handler is of the shape of `({ rowData, rowIndex, depth, expandable, expanded }) => object` + */ + expandIconProps?: CallOrReturn< + object, + { + rowData: T; + rowIndex: number; + depth: number; + expandable: boolean; + expanded: boolean; + } + >; + /** + * The key for the expand column which render the expand icon if the data is a tree + */ + expandColumnKey?: string; + /** + * Default expanded row keys when initialize the table + */ + defaultExpandedRowKeys?: RowKey[]; + /** + * Controlled expanded row keys + */ + expandedRowKeys?: RowKey[]; + /** + * A callback function when expand or collapse a tree node + * The handler is of the shape of `({ expanded, rowData, rowIndex, rowKey }) => *` + */ + onRowExpand?: (args: { expanded: boolean; rowData: T; rowIndex: number; rowKey: RowKey }) => void; + /** + * A callback function when the expanded row keys changed + * The handler is of the shape of `(expandedRowKeys) => *` + */ + onExpandedRowsChange?: (expandedRowKeys: RowKey[]) => void; + /** + * The sort state for the table, will be ignored if `sortState` is set + */ + sortBy?: { + key: React.Key; + order: SortOrder; + }; + /** + * Multiple columns sort state for the table + * + * example: + * ```js + * { + * 'column-0': SortOrder.ASC, + * 'column-1': SortOrder.DESC, + * } + * ``` + */ + sortState?: { + [key in React.Key]: SortOrder; + }; + /** + * A callback function for the header cell click event + * The handler is of the shape of `({ column, key, order }) => *` + */ + onColumnSort?: (args: { column: ColumnShape; key: React.Key; order: SortOrder }) => void; + /** + * A callback function when resizing the column width + * The handler is of the shape of `({ column, width }) => *` + */ + onColumnResize?: (args: { column: ColumnShape; width: number }) => void; + /** + * A callback function when resizing the column width ends + * The handler is of the shape of `({ column, width }) => *` + */ + onColumnResizeEnd?: (args: { column: ColumnShape; width: number }) => void; + /** + * Adds an additional isScrolling parameter to the row renderer. + * This parameter can be used to show a placeholder row while scrolling. + */ + useIsScrolling?: boolean; + /** + * Number of rows to render above/below the visible bounds of the list + */ + overscanRowCount?: number; + /** + * Custom scrollbar size measurement + */ + getScrollbarSize?: () => number; + /** + * A callback function when scrolling the table + * The handler is of the shape of `({ scrollLeft, scrollTop, horizontalScrollDirection, verticalScrollDirection, scrollUpdateWasRequested }) => *` + * + * `scrollLeft` and `scrollTop` are numbers. + * + * `horizontalDirection` and `verticalDirection` are either `forward` or `backward`. + * + * `scrollUpdateWasRequested` is a boolean. This value is true if the scroll was caused by `scrollTo*`, + * and false if it was the result of a user interaction in the browser. + */ + onScroll?: (args: { + scrollLeft: number; + scrollTop: number; + horizontalScrollDirection: 'forward' | 'backward'; + verticalScrollDirection: 'forward' | 'backward'; + scrollUpdateWasRequested: boolean; + }) => void; + /** + * A callback function when scrolling the table within `onEndReachedThreshold` of the bottom + * The handler is of the shape of `({ distanceFromEnd }) => *` + */ + onEndReached?: (args: { distanceFromEnd: number }) => void; + /** + * Threshold in pixels for calling `onEndReached`. + */ + onEndReachedThreshold?: number; + /** + * A callback function with information about the slice of rows that were just rendered + * The handler is of the shape of `({ overscanStartIndex, overscanStopIndex, startIndex, stopIndex }) => *` + */ + onRowsRendered?: (args: { + overscanStartIndex: number; + overscanStopIndex: number; + startIndex: number; + stopIndex: number; + }) => void; + /** + * A callback function when the scrollbar presence state changed + * The handler is of the shape of `({ size, vertical, horizontal }) => *` + */ + onScrollbarPresenceChange?: (args: { size: number; vertical: boolean; horizontal: boolean }) => void; + /** + * A object for the row event handlers + * Each of the keys is row event name, like `onClick`, `onDoubleClick` and etc. + * Each of the handlers is of the shape of `({ rowData, rowIndex, rowKey, event }) => *` + */ + rowEventHandlers?: { + [key: string]: (args: { rowData: T; rowIndex: number; rowKey: RowKey; event: React.SyntheticEvent }) => void; + }; + /** + * whether to ignore function properties while comparing column definition + */ + ignoreFunctionInColumnCompare?: boolean; + /** + * A object for the custom components, like `ExpandIcon` and `SortIndicator` + */ + components?: TableComponents; + [key: string]: any; + } + + export interface TableComponents { + TableCell?: React.ElementType<{ + className: string; + isScrolling?: boolean; + cellData: any; + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: T; + rowIndex: number; + container: BaseTable; + }>; + TableHeaderCell?: React.ElementType<{ + className: string; + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + headerIndex: number; + container: BaseTable; + }>; + ExpandIcon?: React.ElementType<{ + depth: number; + expandable: boolean; + expanded: boolean; + onExpand: (expanded: boolean) => void; + [key: string]: any; + }>; + SortIndicator?: React.ElementType<{ + sortOrder: SortOrder; + className: string; + }>; + } + + export default class BaseTable extends React.Component, any> { + static readonly Column: typeof Column; + static readonly PlaceholderKey = '__placeholder__'; + static defaultProps: Partial; + static propTypes: React.WeakValidationMap; + + /** + * Get the DOM node of the table + */ + getDOMNode(): HTMLDivElement | null; + /** + * Get the column manager + */ + getColumnManager(): any; + /** + * Get internal `expandedRowKeys` state + */ + getExpandedRowKeys(): RowKey[]; + /** + * Get the expanded state, fallback to normal state if not expandable. + */ + getExpandedState(): { + expandedData: T[]; + expandedRowKeys: RowKey[]; + expandedDepthMap: { [key in RowKey]: number }; + }; + /** + * Get the total height of all rows, including expanded rows. + */ + getTotalRowsHeight(): number; + /** + * Get the total width of all columns. + */ + getTotalColumnsWidth(): number; + /** + * Forcefully re-render the inner Grid component. + * + * Calling `forceUpdate` on `Table` may not re-render the inner Grid since it uses `shallowCompare` as a performance optimization. + * Use this method if you want to manually trigger a re-render. + * This may be appropriate if the underlying row data has changed but the row sizes themselves have not. + */ + forceUpdateTable(): void; + /** + * Reset cached offsets for positioning after a specific rowIndex, should be used only in dynamic mode(estimatedRowHeight is provided) + */ + resetAfterRowIndex(rowIndex?: number, shouldForceUpdate?: boolean): void; + /** + * Reset row height cache, useful if `data` changed entirely, should be used only in dynamic mode(estimatedRowHeight is provided) + */ + resetRowHeightCache(): void; + /** + * Scroll to the specified offset. + * Useful for animating position changes. + */ + scrollToPosition(offset: { scrollLeft: number; scrollTop: number }): void; + /** + * Scroll to the specified offset vertically. + */ + scrollToTop(scrollTop: number): void; + /** + * Scroll to the specified offset horizontally. + */ + scrollToLeft(scrollLeft: number): void; + /** + * Scroll to the specified row. + * By default, the table will scroll as little as possible to ensure the row is visible. + * You can control the alignment of the row though by specifying an align property. Acceptable values are: + * + * - `auto` (default) - Scroll as little as possible to ensure the row is visible. + * - `smart` - Same as `auto` if it is less than one viewport away, or it's the same as`center`. + * - `center` - Center align the row within the table. + * - `end` - Align the row to the bottom side of the table. + * - `start` - Align the row to the top side of the table. + */ + scrollToRow(rowIndex?: number, align?: 'auto' | 'smart' | 'center' | 'end' | 'start'): void; + /** + * Set `expandedRowKeys` manually. + * This method is available only if `expandedRowKeys` is uncontrolled. + */ + setExpandedRowKeys(expandedRowKeys: RowKey[]): void; + } + + export interface AutoResizerProps { + /** + * Class name for the component + */ + className?: string; + /** + * the width of the component, will be the container's width if not set + */ + width?: number; + /** + * the height of the component, will be the container's width if not set + */ + height?: number; + /** + * A callback function to render the children component + * The handler is of the shape of `({ width, height }) => node` + */ + children: (size: Size) => React.ReactNode; + /** + * A callback function when the size of the table container changed if the width and height are not set + * The handler is of the shape of `({ width, height }) => *` + */ + onResize?: (size: Size) => void; + } + + export const AutoResizer: React.FC; + + export function renderElement( + renderer: React.ReactElement | ((props: Partial) => React.ReactNode), + props?: T + ): React.ReactNode; + + export function normalizeColumns(elements: React.ReactNode[]): ColumnShape[]; + + export function isObjectEqual(objA: object, objB: object, ignoreFunction?: boolean): boolean; + + export function callOrReturn(funcOrValue: CallOrReturn, ...args: P): T; + + export function hasChildren(data: object): boolean; + + export function unflatten( + array: T[], + rootId?: any, + dataKey?: string, + parentKey?: string + ): (T & { children?: T[] })[]; + + export function flattenOnKeys( + tree: T[], + keys?: RowKey[], + depthMap?: { [key in RowKey]: number }, + dataKey?: string + ): T[]; + + export function getValue(object: any, path?: string, defaultValue?: any): any; + + export function getScrollbarSize(recalculate?: boolean): number; +} diff --git a/components/hi-base-table/index.js b/components/hi-base-table/index.js new file mode 100644 index 000000000..e19a06807 --- /dev/null +++ b/components/hi-base-table/index.js @@ -0,0 +1,170 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react' +import BaseTable, { Column, AutoResizer, SortOrder } from 'react-base-table' +import classNames from 'classnames' +import _ from 'lodash' +import './style/index' +const classPrefix = 'hiui-basetable' +// 格式化 Columns +// 冻结处理 +const fixedColumns = (columns, fixedToColumn) => { + let leftIndex = 0 + let rightIndex = columns.length + if (Object.prototype.toString.call(fixedToColumn) === '[object String]') { + leftIndex = columns.findIndex((item) => { + return item.dataKey === fixedToColumn + }) + } else { + const { left, right } = fixedToColumn + leftIndex = columns.findIndex((item) => { + return item.dataKey === left + }) + rightIndex = columns.findIndex((item) => { + return item.dataKey === right + }) + } + return columns.map((column, columnIndex) => { + let frozen + if (columnIndex <= leftIndex) frozen = Column.FrozenDirection.LEFT + if (columnIndex >= rightIndex) frozen = Column.FrozenDirection.RIGHT + return { ...column, frozen } + }) +} +const generateColumns = (columns) => { + return columns.map((column) => { + return { + ...column, + key: column.dataKey, + dataKey: column.dataKey, + title: column.title, + width: column.width || 100 + } + }) +} +const generateData = (data) => { + const _data = _.cloneDeep(data) + const setDataId = (data, parentId) => { + data.forEach((item, itemIndex) => { + const id = typeof parentId !== 'undefined' ? parentId + '-' + itemIndex : itemIndex + item.id = id + item.parentId = parentId + if (item.children) { + setDataId(item.children, id) + } + }) + } + setDataId(_data) + return _data +} + +const HiBaseTable = React.forwardRef((props, ref) => { + const wrapperRef = useRef() + const timeId = useRef() + let { + data, + columns, + bordered, + className, + height, + width, + autoResize = true, + fixedToColumn, + sticky, + stickyTop = 0 + } = props + columns = generateColumns(columns || []) + const _defaultWidth = columns.reduce((pre, now) => { + return pre + now.width + }, 0) + const [defaultWidth, setDefaultWidth] = useState(_defaultWidth) + const getDefaultWidth = useCallback(() => { + const rect = wrapperRef.current.parentNode.getBoundingClientRect() + return rect.width || _defaultWidth + }, [wrapperRef]) + const resize = useCallback(() => { + clearTimeout(timeId.current) + timeId.current = setTimeout(() => { + setDefaultWidth(getDefaultWidth()) + }, [60]) + }, []) + const scroll = useCallback(() => { + if (wrapperRef.current) { + if (sticky) { + const { height: headerH } = wrapperRef.current.querySelector('.hiui-basetable__header').getBoundingClientRect() + const { top, height } = wrapperRef.current.getBoundingClientRect() + const header = wrapperRef.current.querySelectorAll('.hiui-basetable__header') + header.forEach((element) => { + if ( + element.parentNode.classList.contains('hiui-basetable__table-frozen-right') || + element.parentNode.classList.contains('hiui-basetable__table-frozen-left') + ) { + if (top <= stickyTop) { + element.classList.add('header__sticky-row') + element.style.top = stickyTop + 'px' + let _w = element.getAttribute('cacheWidth') || parseInt(element.style.width) + element.setAttribute('cacheWidth', _w) + _w = _w - 20 + element.style.width = _w + 'px' + } else { + element.style.top = 0 + element.classList.remove('header__sticky-row') + let _w = element.getAttribute('cacheWidth') || parseInt(element.style.width) + element.setAttribute('cacheWidth', _w) + _w = _w + 20 + element.style.width = _w + 'px' + } + } else { + element.style.position = 'sticky' + element.style.top = stickyTop + 'px' + } + }) + if (top + height - headerH < stickyTop) { + header.forEach((element) => { + if ( + element.parentNode.classList.contains('hiui-basetable__table-frozen-right') || + element.parentNode.classList.contains('hiui-basetable__table-frozen-left') + ) { + element.style.top = 0 + element.classList.remove('header__sticky-row') + } else { + element.style.position = 'static' + } + }) + } + } + } + }, [stickyTop, wrapperRef, sticky]) + useEffect(() => { + window.addEventListener('resize', resize) + window.addEventListener('scroll', scroll) + setDefaultWidth(getDefaultWidth()) + return () => { + window.removeEventListener('resize', resize) + window.removeEventListener('scroll', scroll) + } + }, []) + return ( +
+ +
+ ) +}) +HiBaseTable.Column = Column +HiBaseTable.PlaceholderKey = BaseTable.PlaceholderKey + +export default HiBaseTable +export { AutoResizer, SortOrder } diff --git a/components/hi-base-table/style/hiui-basetable-theme.scss b/components/hi-base-table/style/hiui-basetable-theme.scss new file mode 100644 index 000000000..4a0b33fcf --- /dev/null +++ b/components/hi-base-table/style/hiui-basetable-theme.scss @@ -0,0 +1,47 @@ +$table-prefix: hiui-basetable !default; +$table-font-size: 14px !default; +$header-background-color: #fbfbfb !default; +$header-font-weight: 500 !default; +$border: 1px solid #e6e7e8 !default; +$row-hovered-background-color: var(--color-primary-20) !default; + +@import '../../../node_modules/react-base-table/es/_BaseTable.scss'; + +.#{$table-prefix} { + box-shadow: none; + + &__table-main { + outline: none; + } + + &.bordered { + border: 1px solid var(--color-gray-20); + border-right: none; + + .hiui-basetable__row-cell { + border-right: 1px solid var(--color-gray-20); + } + } + + &__header-cell { + color: var(--color-gray-70); + + &-text { + cursor: text; + } + } + + &.autoResize { + .#{$table-prefix}__header-cell { + flex: 1 1 !important; + } + + .#{$table-prefix}__row-cell { + flex: 1 1 !important; + } + } +} + +.header__sticky-row { + position: fixed !important; +} diff --git a/components/hi-base-table/style/index.js b/components/hi-base-table/style/index.js new file mode 100644 index 000000000..ea3232ccb --- /dev/null +++ b/components/hi-base-table/style/index.js @@ -0,0 +1 @@ +import './hiui-basetable-theme.scss' diff --git a/components/input/Input.js b/components/input/Input.js index ad84333ee..f01668eef 100644 --- a/components/input/Input.js +++ b/components/input/Input.js @@ -93,7 +93,7 @@ class Input extends Component { renderText() { const { hover, active, value } = this.state // clearableTrigger 为内部预留,主要表示清除按钮的触发形态,类型分为 'hover' 和 ‘always’ - const { disabled, type, id, placeholder, clearable, clearableTrigger = 'hover' } = this.props + const { disabled, type, id, placeholder, clearable, clearableTrigger = 'hover', bordered = true } = this.props const { prefix, suffix, prepend, append } = this.state const noClear = ['textarea'] const prefixId = id ? id + '_prefix' : '' @@ -123,7 +123,13 @@ class Input extends Component { // 前置元素 prepend && {prepend} } -
+
{ // 前缀 prefix && ( diff --git a/components/input/index.d.ts b/components/input/index.d.ts index 5bde14b7b..530116245 100644 --- a/components/input/index.d.ts +++ b/components/input/index.d.ts @@ -8,6 +8,8 @@ interface Props { clearable?: boolean placeholder?: string style?: CSSProperties + className?: string + bordered?: boolean ref?: string | ((instance: HTMLInputElement | null) => void) | React.RefObject | null | undefined onFocus?: (e: React.FocusEvent) => void onBlur?: (e: React.FocusEvent) => void diff --git a/components/input/style/index.scss b/components/input/style/index.scss index aa337c67f..99b31126f 100644 --- a/components/input/style/index.scss +++ b/components/input/style/index.scss @@ -113,12 +113,15 @@ textarea.#{$input} { position: relative; display: flex; width: 100%; - border: 1px solid use-color('gray-30'); box-sizing: border-box; border-radius: 2px; background-color: use-color('white'); transition: border-color 0.3s; + &.bordered { + border: 1px solid use-color('gray-30'); + } + &:not(.disabled):hover { z-index: 1; border-color: use-color('primary'); diff --git a/components/list/index.d.ts b/components/list/index.d.ts index a0091d43d..bab684db2 100644 --- a/components/list/index.d.ts +++ b/components/list/index.d.ts @@ -5,6 +5,8 @@ type DataItem = { description?: string | JSX.Element extra?: string | string[] avatar?: string + style?: CSSProperties + className?: string } interface Props { type?: 'default' | 'card' @@ -18,6 +20,8 @@ interface Props { hoverable?: boolean layout?: 'vertical' | 'horizontal' emptyText?: string | JSX.Element + style?: CSSProperties + className?: string } declare const List: React.ComponentType export default List diff --git a/components/loading/index.d.ts b/components/loading/index.d.ts index c6a993c3e..2f1cfc4e6 100644 --- a/components/loading/index.d.ts +++ b/components/loading/index.d.ts @@ -3,6 +3,8 @@ interface Props { content?: string | JSX.Element visible?: boolean full?: boolean + style?: CSSProperties + className?: string } type Options = { diff --git a/components/menu/index.d.ts b/components/menu/index.d.ts index d1da36b67..8ae822aa1 100644 --- a/components/menu/index.d.ts +++ b/components/menu/index.d.ts @@ -13,6 +13,8 @@ interface Props { showCollapse?: boolean showAllSubMenus?: boolean accordion?: boolean + style?: CSSProperties + className?: string onClick?: (activeId: string | number, prevActiveId: string | number) => void onClickSubMenu?: (subMenuIndexs: number) => void onCollapse?: (collapsed: boolean) => void diff --git a/components/message/index.d.ts b/components/message/index.d.ts index 6893812e8..a8609c168 100644 --- a/components/message/index.d.ts +++ b/components/message/index.d.ts @@ -5,6 +5,8 @@ type Options = { type?: 'info' | 'success' | 'error' | 'warning' title: string duration?: number + style?: CSSProperties + className?: string } const OpenFun: (options: Options) => void declare class Message extends React.Component { diff --git a/components/modal/index.d.ts b/components/modal/index.d.ts index 346619b59..04a7bd232 100644 --- a/components/modal/index.d.ts +++ b/components/modal/index.d.ts @@ -11,6 +11,8 @@ interface Props { footer?: JSX.Element | null onCancel?: (e: MouseEvent) => void onConfirm?: (e: MouseEvent) => void + style?: CSSProperties + className?: string } declare const Modal: React.ComponentType export default Modal diff --git a/components/notification/index.d.ts b/components/notification/index.d.ts index 840330d6e..40828482e 100644 --- a/components/notification/index.d.ts +++ b/components/notification/index.d.ts @@ -6,6 +6,8 @@ interface Options { closeable?: boolean duration?: number confirmText?: string + style?: CSSProperties + className?: string onClose?: (e: MouseEvent) => void onConfirm?: () => void } diff --git a/components/pagination/index.d.ts b/components/pagination/index.d.ts index a6bac4d13..25bd3dc08 100644 --- a/components/pagination/index.d.ts +++ b/components/pagination/index.d.ts @@ -8,6 +8,8 @@ export interface PaginationProps { pageSizeOptions?: number[] autoHide?: boolean showJumper?: boolean + style?: CSSProperties + className?: string onJump?: (current: number) => void onChange?: (currentPage: number, prevPage: number, pageSize: number) => void onPageSizeChange?: (changeSize: number, currentPage: number) => void diff --git a/components/popover/index.d.ts b/components/popover/index.d.ts index 124a86acd..7263264c5 100644 --- a/components/popover/index.d.ts +++ b/components/popover/index.d.ts @@ -4,6 +4,8 @@ interface Props { placement?: 'top' | 'right' | 'bottom' | 'left' trigger?: 'click' | 'focus' | 'hover' visible?: boolean + style?: CSSProperties + className?: string } declare const Popover: React.ComponentType export default Popover diff --git a/components/popper/Overlay.js b/components/popper/Overlay.js index 95807efc9..7348ceedf 100644 --- a/components/popper/Overlay.js +++ b/components/popper/Overlay.js @@ -1,7 +1,6 @@ import React, { useState, useRef, useEffect, useCallback } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' -import _ from 'lodash' import PopperJS from './utils/popper' import { getOffset } from './utils/positionUtils' import useClickOutside from './utils/useClickOutside' @@ -59,19 +58,22 @@ const Overlay = (props) => { } const scrollCallBack = useCallback(() => { - const offset = getOffset(props, state) - offsetData.current = offset - if (staticPopperRef) { - setState( - Object.assign({}, state, { - popperRef: staticPopperRef.current - }) - ) + if (props.attachEle) { + const offset = getOffset(props, state) + offsetData.current = offset + if (staticPopperRef) { + setState( + Object.assign({}, state, { + popperRef: staticPopperRef.current + }) + ) + } } }, [props, state]) useEffect(() => { const { attachEle, container, show } = props + if (attachEle) return const { cacheContainerPosition } = state const offset = getOffset(props, state) offsetData.current = offset @@ -142,13 +144,16 @@ const Overlay = (props) => { ) }, []) - if (!(attachEle && show && children)) return null - - const { offset = getOffset(props, state) } = state - const width = offset.width - const left = offset.left + 'px' - const top = offset.top + 'px' - + if (!(show && children)) return null + let { width, left = 0, top = 0 } = props + let { offset } = state + if (attachEle) { + offset = state.offset || getOffset(props, state) + width = offset.width + left = offset.left + 'px' + top = offset.top + 'px' + } + const placementClassName = offset ? `hi-popper__content--${offset.placement}` : '' return (
{ ref={(node) => { staticPopperRef.current = node }} - className={classNames(className, 'hi-popper__content', `hi-popper__content--${offset.placement}`, { + className={classNames(className, placementClassName, 'hi-popper__content', { 'hi-popper__content--hide': popperHeight === 0 || popperWidth === 0 })} style={{ width, height }} diff --git a/components/popper/README.md b/components/popper/README.md new file mode 100644 index 000000000..0cbdbb880 --- /dev/null +++ b/components/popper/README.md @@ -0,0 +1,79 @@ +# Popper + +基础弹层组件 + +## 快速使用 + +## 依附元素 + +```js +import Popper from "@hi-ui/hiui/es/popper" +import React, { useState, useRef } from "react" +const demo = () => { + const PopperAttachEle = useRef() + const [showPopper, setShowPopper] = useState(false) + return ( +
+
popper-attachEle
+ { + setShowPopper(false) + }} + > +
Popper Content
+
+
+ ) +} +``` + +## 无依附元素 + +```js +import Popper from @hi-ui/hiui/es/popper +import React, { useState, useRef } from react +const demo = () => { + const [showPopper, setShowPopper] = useState(false) + return ( +
+ { + setShowPopper(false) + }} + > +
Popper Content
+
+
+ ) +} +``` + +## Props + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| --------- | -------------------------------------------- | ----------- | ------------- | ------ | +| show | 弹出层显示隐藏 | boolean | - | false | +| attachEle | 依附元素,会自动显示到该元素下方,并跟随自动 | HTMLElement | - | - | +| container | 弹出层依赖定位的元素,也就是弹出层参考定位的元素 | HTMLElement | - | - | +| width | 弹层宽度,如果存在**attachEle**参数且宽度未传入的情况下,会根据**attachEle**的宽度进行计算,其他情况请传入宽度 | number \| string \| bool | - | - | +| topGap | 距离依附元素的上偏移量,存在 **attachEle** 属性时有效, | number | 0 | 0 | +| leftGap | 距离依附元素的左偏移量,存在 **attachEle** 属性时有效, | number | 0 | 0 | +| zIndex | 堆叠顺序 | number | - | 1060 | +| placement | 位于依附元素的方位 | string | bottom \| bottom-start \| bottom-end \| top \| top-start \| top-end \| left \| left-start \| left-end \| right \| right-start \| right-end \| top-bottom-start(使用该属性会自动根据依附元素距离可视区域自动翻转) \| top-bottom \| left-right \| left-right-start | bottom-start | + +## Events + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| --------- | -------------------------------------------- | ----------- | ------------- | ------ | +| onClickOutside | 点击该元素外的回调方法 | function | - |-| +| setOverlayContainer | 如遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位 (3.0 新增) | function(triggerNode) | - | () => document.body | diff --git a/components/popper/utils/positionUtils.js b/components/popper/utils/positionUtils.js index 59bf8a4ea..4d29ebf2a 100644 --- a/components/popper/utils/positionUtils.js +++ b/components/popper/utils/positionUtils.js @@ -60,57 +60,93 @@ const positionAuto = (attachEleRect, popperHeight, popperRef, height, containerH } return placement } -const getPlacement = (attachEleRect, container, props, state) => { - let { popperHeight, popperRef } = state + +const caclLeftOrRightPlacement = (leftPlacement, rightPlacement, popperRef, widthConstant) => { + let { width, popperWidth, poperLeft, containerWidth, attachEleWidth } = widthConstant + let placement = rightPlacement + if (popperWidth === undefined) { + const clientWidth = popperRef || {} + popperWidth = clientWidth || 0 + } // 自动探测边界,第一次时需设置为不可见,否则会闪跳,用来设置class hi-popper__content--hide + if (popperRef || width) { + // 元素已挂载到dom且当前popper处于显示状态 + if (width) { + popperWidth = width + } else if (popperRef.clientWidth && popperWidth !== popperRef.clientWidth) { + popperWidth = popperRef.clientWidth + } + poperLeft = poperLeft + (popperWidth - (attachEleWidth || 0)) + if (poperLeft >= containerWidth) { + placement = leftPlacement + } + } + return placement +} +// 计算popper在元素上面或下面 +const caclBottomOrTopPlacement = (bottomPlacement, topPlacement, popperRef, heightConstant, widthConstant) => { + let { popperHeight, height, poperTop, containerHeight } = heightConstant + let placement = bottomPlacement + popperHeight === undefined && (popperHeight = 0) // 自动探测边界,第一次时需设置为不可见,否则会闪跳,用来设置class hi-popper__content--hide + if (popperRef || height) { + // 元素已挂载到dom且当前popper处于显示状态 + if (height) { + popperHeight = height + } else if (popperRef.clientHeight && popperHeight !== popperRef.clientHeight) { + popperHeight = popperRef.clientHeight + } + poperTop += popperHeight + if (poperTop >= containerHeight) { + placement = topPlacement + } + } + const topOrbottom = placement === 'top-start' ? 'top' : 'bottom' + placement = topOrbottom + '-' + caclLeftOrRightPlacement('end', 'start', popperRef, widthConstant) + return placement +} +const getPlacement = (attachEleRect, container, props, state, attachEleWidth) => { + const { popperHeight, popperRef, popperWidth } = state let { attachEle, placement, height, width = 0, leftGap = 0 } = props if (!attachEle) return let containerHeight = document.documentElement.clientHeight || document.body.clientHeight + let containerWidth = document.documentElement.clientWidth || document.body.clientWidth if (isFixed(attachEle)) { containerHeight = container.clientHeight + containerWidth = container.clientWidth } if (isBody(container)) { containerHeight = document.documentElement.clientHeight || document.body.clientHeight + containerWidth = document.documentElement.clientWidth || document.body.clientWidth } - let poperTop = attachEleRect.top + attachEleRect.height - const caclBottomOrTopPlacement = (bottomPlacement, topPlacement) => { - // 计算popper在元素上面或下面 - placement = bottomPlacement - popperHeight === undefined && (popperHeight = 0) // 自动探测边界,第一次时需设置为不可见,否则会闪跳,用来设置class hi-popper__content--hide - if (popperRef || height) { - // 元素已挂载到dom且当前popper处于显示状态 - if (height) { - popperHeight = height - } else if (popperRef.clientHeight && popperHeight !== popperRef.clientHeight) { - popperHeight = popperRef.clientHeight - } - poperTop += popperHeight - if (poperTop >= containerHeight) { - placement = topPlacement - } - } - } - const caclLeftOrRightPlacement = (leftPlacement, RightPlacement) => { - // 计算popper在元素上面或下面 - placement = leftPlacement - const _width = popperRef ? popperRef.clientWidth : width - if (attachEleRect.right > _width + leftGap) { - placement = RightPlacement - } - if (attachEleRect.left > _width + leftGap) { - placement = leftPlacement - } + const poperTop = attachEleRect.top + attachEleRect.height + const poperLeft = attachEleRect.left + attachEleRect.width + + const widthConstant = { + width, + popperWidth, + poperLeft, + containerWidth, + attachEleWidth, + leftGap + } + const heightConstant = { + popperHeight, + height, + poperTop, + containerHeight, + leftGap } + if (placement === 'top-bottom-start') { - caclBottomOrTopPlacement('bottom-start', 'top-start') + placement = caclBottomOrTopPlacement('bottom-start', 'top-start', popperRef, heightConstant, widthConstant) } else if (placement === 'top-bottom') { - caclBottomOrTopPlacement('bottom', 'top') + placement = caclBottomOrTopPlacement('bottom', 'top', popperRef, heightConstant, widthConstant) } else if (placement === 'left-right-start') { - caclLeftOrRightPlacement('left-start', 'right-start') + placement = caclLeftOrRightPlacement('left-start', 'right-start', popperRef, widthConstant) } else if (placement === 'left-right') { - caclLeftOrRightPlacement('left', 'right') + placement = caclLeftOrRightPlacement('left', 'right', popperRef, width, widthConstant) } else if (placement === 'auto') { positionAuto(attachEleRect, popperHeight, popperRef, height, containerHeight) } @@ -120,7 +156,7 @@ export const getOffset = (props, state, status) => { let { attachEle, topGap, leftGap, width, container, preventOverflow } = props if (!attachEle) return - const { popperHeight } = state + const { popperHeight, popperWidth } = state let rect = attachEle.getBoundingClientRect() if (isFixed(attachEle) && !isBody(container)) { @@ -137,10 +173,9 @@ export const getOffset = (props, state, status) => { let top = rect.top + _scrollTop let left = rect.left + _scrollLeft - - width = width === false ? '' : width === undefined ? rect.width : width - - let placement = getPlacement(rect, container, props, state) || 'bottom-start' + const _width = width === false ? '' : width === undefined ? rect.width : width + width = width === false ? popperWidth : width === undefined ? rect.width : width + let placement = getPlacement(rect, container, props, state, rect.width) || 'bottom-start' const rectHeight = rect.height switch (placement) { case 'bottom': @@ -173,11 +208,11 @@ export const getOffset = (props, state, status) => { break case 'left-start': top = top + topGap - left = left - rect.width + left = left - width break case 'left-end': top = top + rect.height - topGap - popperHeight - left = left - rect.width + left = left - width break case 'right': @@ -190,7 +225,7 @@ export const getOffset = (props, state, status) => { break case 'right-end': top = top + rect.height - topGap - popperHeight - left = left + rect.width + leftGap + left = left + width + leftGap break } @@ -199,7 +234,7 @@ export const getOffset = (props, state, status) => { } return { - width, + width: _width, top, left, placement: placement diff --git a/components/preview/index.d.ts b/components/preview/index.d.ts new file mode 100644 index 000000000..54e2bb673 --- /dev/null +++ b/components/preview/index.d.ts @@ -0,0 +1,13 @@ +interface Props { + visible?: boolean + showBar?: boolean + showArrow?: boolean + showCount?: boolean + images?: string[] | object[] + simpleData?: boolean + activeIndex?: number + style?: CSSProperties + className?: string +} +declare const Preview: React.ComponentType +export default Preview diff --git a/components/preview/index.js b/components/preview/index.js new file mode 100644 index 000000000..04d6440c8 --- /dev/null +++ b/components/preview/index.js @@ -0,0 +1,351 @@ +import React, { useRef, useState, useEffect, useCallback } from 'react' +import { createPortal } from 'react-dom' +import ReactCSSTransitionGroup from 'react-addons-css-transition-group' +import classNames from 'classnames' +import Icon from '../icon' +import Loading from '../loading' +import './style/index.js' +// unstable_batchedUpdates +const node = document.createElement('div') +document.body.appendChild(node) +const Preview = ({ + className, + visible: propsvisible, + simpleData, + images, + activeIndex: propsActiveIndex = 0, + duration, + showBar = true, + onClose: propsOnClose, + showArrow, + showCount +}) => { + if (images.length === 0 || !propsvisible) { + return null + } + const previewRef = useRef() + const imgRef = useRef() + const [isLoaded, setIsLoaded] = useState(false) + const [visible, setVisible] = useState(false) + const [isMouseDown, setIsMouseDown] = useState(false) + const [mouseCoord, setMouseCoord] = useState({ mouseX: 0, mouseY: 0 }) + const [activeIndex, setactiveIndex] = useState(propsActiveIndex) + const [style, setStyle] = useState({ + width: 0, + height: 0, + left: 0, + top: 0, + rotate: 0, + scaleX: 1, + scaleY: 1 + }) + const [compileStyle, setCompileStyle] = useState({}) + const autoPlayTimer = useRef(null) + + useEffect(() => { + setVisible(propsvisible) + }, [propsvisible]) + + useEffect(() => { + setactiveIndex(propsActiveIndex) + }, [propsActiveIndex]) + + /** + * 修改图片样式 + * @param {*} args 计算后的部分样式 + */ + const changeImageState = useCallback( + (args) => { + setStyle({ ...Object.assign({}, style, { ...args }) }) + }, + [style] + ) + + useEffect(() => { + if (isLoaded) { + const compileStyle = { + width: style.width, + height: style.height, + transform: `translateX(${style.left}px) translateY(${style.top}px) rotate(${style.rotate}deg) scaleX(${style.scaleX}) scaleY(${style.scaleY})` + } + setCompileStyle({ + ...compileStyle + }) + } + }, [style, isLoaded]) + + const handleMouseMove = useCallback( + (e) => { + if (isMouseDown) { + const diffX = e.clientX - mouseCoord.mouseX + const diffY = e.clientY - mouseCoord.mouseY + setMouseCoord({ + mouseX: e.clientX, + mouseY: e.clientY + }) + changeImageState({ + left: style.left + diffX, + top: style.top + diffY + }) + } + }, + [isMouseDown, mouseCoord, changeImageState] + ) + const getImageCenterXY = useCallback(() => { + const { left, top, width, height } = style + return { + x: left + width / 2, + y: top + height / 2 + } + }, [style]) + /** + * 缩放事件 + * @param {*} x 鼠标位置 x + * @param {*} y 鼠标位置 y + * @param {*} direct 方向 + */ + const handleZoom = useCallback( + (x, y, direct) => { + const speed = 0.05 + const imgCenterXY = getImageCenterXY() + const diffX = x - imgCenterXY.x + const diffY = y - imgCenterXY.y + const { left, top, scaleX, scaleY } = style + const directX = scaleX > 0 ? 1 : -1 + const directY = scaleY > 0 ? 1 : -1 + const _scaleX = scaleX + speed * direct * directX + const _scaleY = scaleY + speed * direct * directY + if (Math.abs(_scaleX) < 0.1 || Math.abs(_scaleY) < 0.1) { + return + } + changeImageState({ + scaleX: _scaleX, + scaleY: _scaleY, + top: top + ((-direct * diffY) / scaleX) * speed * directX, + left: left + ((-direct * diffX) / scaleY) * speed * directY + }) + }, + [getImageCenterXY, changeImageState] + ) + const handleMouseWheel = useCallback( + (e) => { + e.preventDefault() + const direct = e.deltaY + if (direct !== 0) { + const x = e.clientX + const y = e.clientY + handleZoom(x, y, direct) + } + }, + [handleZoom] + ) + const handleMouseDown = useCallback( + (e) => { + e.preventDefault() + e.stopPropagation() + setIsMouseDown(true) + setMouseCoord({ + mouseX: e.nativeEvent.clientX, + mouseY: e.nativeEvent.clientY + }) + }, + [isMouseDown, mouseCoord] + ) + const handleMouseUp = useCallback(() => { + setIsMouseDown(false) + }, [isMouseDown]) + const imageMouseOver = useCallback(() => { + clearInterval(autoPlayTimer.current) + }, []) + const getImgWidthHeight = useCallback( + (imgWidth, imgHeight) => { + let width = 0 + let height = 0 + const { innerHeight, innerWidth } = window + const maxWidth = innerWidth * 0.8 + const maxHeight = innerHeight * 0.8 + let style = {} + width = Math.min(maxWidth, imgWidth) + height = (width / imgWidth) * imgHeight + if (height > maxHeight) { + height = maxHeight + width = (height / imgHeight) * imgWidth + } + style = { + width, + height, + left: (innerWidth - width) / 2, + top: (innerHeight - height) / 2, + scaleX: 1, + scaleY: 1, + rotate: 0 + } + changeImageState(style) + }, + [style] + ) + /** + * 加载图片 加载完成后生成计算样式 + * @param {*} imageIndex 加载的图片索引 + */ + const loadImg = useCallback( + (imageIndex) => { + if (!images || images.length === 0) { + return + } + if (imageIndex >= images.length) { + imageIndex = 0 + } + if (imageIndex < 0) { + imageIndex = images.length - 1 + } + const currentImage = images[imageIndex] + const img = new window.Image() + img.onload = () => { + setactiveIndex(imageIndex) + setIsLoaded(true) + getImgWidthHeight(img.width, img.height) + } + img.src = simpleData ? currentImage : currentImage.url + }, + [images, activeIndex, isLoaded] + ) + /** + * 自动滚动函数,目前 API 未暴露 + */ + const addAutoPlayEvent = useCallback(() => { + if (duration && duration > 0) { + autoPlayTimer.current = setInterval(() => { + loadImg(activeIndex + 1) + }, duration) + } + }, [duration, loadImg]) + const imageMouseOut = useCallback(() => { + clearInterval(autoPlayTimer.current) + addAutoPlayEvent() + }, []) + const handleRotate = useCallback( + (isRight) => { + const { rotate } = style + changeImageState({ + rotate: rotate + 90 * (isRight ? 1 : -1) + }) + }, + [style, changeImageState] + ) + const onClose = useCallback( + (e) => { + setStyle({}) + propsOnClose && propsOnClose() + }, + [propsOnClose] + ) + const clickEvent = useCallback( + (type, event) => { + event.stopPropagation() + event.preventDefault() + const { x, y } = getImageCenterXY() + switch (type) { + case 'zoomIn': + handleZoom(x, y, 1) + break + case 'zoomOut': + handleZoom(x, y, -1) + break + case 'reset': + loadImg(activeIndex) + break + case 'leftRotate': + handleRotate() + break + case 'rightRotate': + handleRotate(true) + break + case 'prev': + loadImg(activeIndex - 1) + break + case 'next': + loadImg(activeIndex + 1) + break + case 'close': + onClose() + break + default: + break + } + }, + [activeIndex, getImageCenterXY] + ) + useEffect(() => { + loadImg(activeIndex) + addAutoPlayEvent() + return () => { + clearInterval(autoPlayTimer.current) + } + }, []) + return createPortal( + +
+ {isLoaded ? ( + + ) : ( + + )} + {showBar && ( +
{ + e.stopPropagation() + e.preventDefault() + }} + > + clickEvent('zoomOut', e)} /> + clickEvent('zoomIn', e)} /> + clickEvent('prev', e)} /> + clickEvent('reset', e)} /> + clickEvent('next', e)} /> + clickEvent('leftRotate', e)} /> + clickEvent('rightRotate', e)} /> +
+ )} +
+ +
+ {showArrow && ( +
+ clickEvent('prev', e)} /> + clickEvent('next', e)} /> +
+ )} + {showCount && ( +
+ {activeIndex + 1} +  /  + {images.length} +
+ )} +
+
, + node + ) +} +export default Preview diff --git a/components/preview/style/index.js b/components/preview/style/index.js new file mode 100644 index 000000000..63810a681 --- /dev/null +++ b/components/preview/style/index.js @@ -0,0 +1 @@ +import './index.scss' diff --git a/components/preview/style/index.scss b/components/preview/style/index.scss new file mode 100644 index 000000000..998806959 --- /dev/null +++ b/components/preview/style/index.scss @@ -0,0 +1,77 @@ +.hi-preview { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.54); + z-index: 9999; + user-select: none; + + &__image { + transform: all 0.3s; + + &:hover { + cursor: move; + } + } + + &-toolbar { + position: absolute; + bottom: 20px; + left: 50%; + transform: translate(-50%, -50%); + background: #fff; + padding: 0 32px; + height: 48px; + display: flex; + align-items: center; + border-radius: 28px; + + .hi-icon { + margin: 0 12px; + font-size: 20px; + cursor: pointer; + } + } + + &__close { + position: absolute; + right: 10px; + top: 10px; + width: 36px; + height: 36px; + line-height: 1; + border-radius: 50%; + text-align: center; + cursor: pointer; + } + + &__wrapper-arrow { + position: absolute; + font-size: 72px; + top: 50%; + transform: translateY(-50%); + display: flex; + justify-content: space-between; + color: #fff; + width: 100%; + z-index: -1; + + .hi-icon { + cursor: pointer; + } + } + + &__count { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + background: #fff; + padding: 4px 12px; + border-radius: 16px; + min-width: 48px; + text-align: center; + } +} diff --git a/components/progress/index.d.ts b/components/progress/index.d.ts index a28782802..799da43fb 100644 --- a/components/progress/index.d.ts +++ b/components/progress/index.d.ts @@ -9,6 +9,8 @@ interface Props { placement?: 'inside' | 'outside' width?: string | number height?: string | number + style?: CSSProperties + className?: string } declare const Progress: React.ComponentType export default Progress diff --git a/components/radio/index.d.ts b/components/radio/index.d.ts index 02a966638..43e68dc28 100644 --- a/components/radio/index.d.ts +++ b/components/radio/index.d.ts @@ -8,6 +8,8 @@ interface Props { autoFocus?: boolean checked?: boolean disabled?: boolean + style?: CSSProperties + className?: string onChange?: (event: ChangeEvent) => void } interface GroupProps { diff --git a/components/rate/index.d.ts b/components/rate/index.d.ts index c4465fdeb..a302edc25 100644 --- a/components/rate/index.d.ts +++ b/components/rate/index.d.ts @@ -13,6 +13,8 @@ interface Props { color?: string characterRender?: (value: number, index: number) => JSX.Element onChange?: (value: number) => void + style?: CSSProperties + className?: string } declare const Rate: React.ComponentType export default Rate diff --git a/components/search/index.d.ts b/components/search/index.d.ts index 4303b0823..80189abd7 100644 --- a/components/search/index.d.ts +++ b/components/search/index.d.ts @@ -13,8 +13,10 @@ interface Props { placeholder?: string data?: DataItem onSearch?: (inputVal: string, item?: DataItem) => void - onChange?: (e: React.ChangeEvent) => void + onChange?: (e: string) => void overlayClassName?: string + style?: CSSProperties + className?: string } declare const Search: React.ComponentType export default Search diff --git a/components/select-tree/SelectTreeHook.js b/components/select-tree/SelectTreeHook.js index 858cef036..7fc75d6dd 100644 --- a/components/select-tree/SelectTreeHook.js +++ b/components/select-tree/SelectTreeHook.js @@ -54,7 +54,8 @@ const SelectTree = ({ optionWidth, autoload: propsAutoload, placement = 'top-bottom-start', - emptyContent + emptyContent, + bordered = true }) => { const [isFocus, setIsFocus] = useState(false) const placeholder = propsPlaceholder || localeDatas.selectTree.placeholder @@ -82,6 +83,7 @@ const SelectTree = ({ checked: [], semiChecked: [] }) + const resizeTimeId = useRef() // 拉平的数据 const [flattenData, setFlattenData] = useState([]) // 关键字搜索值 @@ -114,7 +116,7 @@ const SelectTree = ({ // 依赖 flattenData & value 解析生成 checkedNodes 或 selectedItems useEffect(() => { - if (flattenData.length > 0) { + if (flattenData.length > 0 && value) { if (type === 'multiple') { const cstatus = parseCheckStatusData( value, @@ -130,7 +132,7 @@ const SelectTree = ({ } } } - }, [value]) + }, [value, flattenData]) // 依赖展开项生成展开节点数据 useEffect(() => { @@ -146,8 +148,7 @@ const SelectTree = ({ setActiveId(flattenData[0].id) } }, [expandIdsProps, flattenData]) - - useEffect(() => { + const getShowCount = useCallback(() => { if (selectedItemsRef.current) { const sref = selectedItemsRef.current // 多选超过一行时以数字显示 @@ -166,8 +167,21 @@ const SelectTree = ({ } setShowCount(num) } + }, [showCount, selectedItems]) + + useEffect(() => { + getShowCount() }, [selectedItems]) + + const resize = useCallback(() => { + clearTimeout(resizeTimeId.current) + resizeTimeId.current = setTimeout(() => { + getShowCount() + }, [60]) + }, [getShowCount]) + useEffect(() => { + window.addEventListener('resize', resize) if (data) { const { flattenData = [], nodeEntries } = flattenNodesData( data, @@ -196,6 +210,9 @@ const SelectTree = ({ } else if (Array.isArray(data) && data.length > 0) { setActiveId(data[0].id) } + return () => { + window.removeEventListener('resize', resize) + } }, []) // 过滤方法 const searchTreeNode = (val) => { @@ -384,13 +401,13 @@ const SelectTree = ({ */ const selectedEvents = useCallback( (node) => { - setSelectedItems([node]) + typeof value === 'undefined' && setSelectedItems([node]) const n = clearReturnData(node) onChange(node.id, n, n) setShow(false) setActiveId(node.id) }, - [onChange, show, selectedItems] + [onChange, show, selectedItems, value] ) /** @@ -422,7 +439,6 @@ const SelectTree = ({ } // 搜索框的值改变时的事件 const changeEvents = (val) => { - setSearchValue(val) if (dataSource && val.length) { setAutoload(true) onTrigger(val) @@ -430,13 +446,12 @@ const SelectTree = ({ searchTreeNode(val) } } - const debouncedFilterItems = _.debounce(changeEvents, 100) + const debouncedFilterItems = _.debounce(changeEvents, 300) const searchable = searchMode === 'filter' || searchMode === 'highlight' // 按键操作 const handleKeyDown = useCallback( (evt) => { - evt.stopPropagation() // space if ( evt.keyCode === 32 && @@ -444,36 +459,64 @@ const SelectTree = ({ !show ) { evt.preventDefault() + evt.stopPropagation() setShow(true) } // esc if (evt.keyCode === 27) { + evt.stopPropagation() + setShow(false) } if (show) { // down if (evt.keyCode === 40) { + evt.stopPropagation() evt.preventDefault() setActiveId(moveFocusedIndex('down', activeId, selectTreeRoot)) } // up if (evt.keyCode === 38) { evt.preventDefault() + evt.stopPropagation() + setActiveId(moveFocusedIndex('up', activeId, selectTreeRoot)) } // right if (evt.keyCode === 39) { evt.preventDefault() + evt.stopPropagation() + rightHandle({ activeId, flattenData, expandIds, expandEvents, setActiveId, mode }) } // left if (evt.keyCode === 37) { evt.preventDefault() + evt.stopPropagation() + leftHandle({ activeId, flattenData, expandIds, expandEvents, setActiveId, mode }) } + // enter + if (evt.keyCode === 13) { + evt.preventDefault() + evt.stopPropagation() + if (mode !== 'breadcrumb') { + type === 'multiple' + ? checkedEvents( + !selectedItems.some((item) => { + return activeId === item.id + }), + getNodeByIdTitle(activeId, flattenData), + checkedNodes + ) + : selectedEvents(getNodeByIdTitle(activeId, flattenData)) + } + } // space 选中 if (evt.keyCode === 32 && !document.activeElement.classList.value.includes('hi-selecttree__searchinput')) { evt.preventDefault() + evt.stopPropagation() + if (mode !== 'breadcrumb') { type === 'multiple' ? checkedEvents( @@ -515,6 +558,7 @@ const SelectTree = ({ placeholder={placeholder} checkedEvents={checkedEvents} onTrigger={onTrigger} + bordered={bordered} onClear={handleClear} /> { @@ -545,14 +589,15 @@ const SelectTree = ({ className="hi-selecttree__searchinput" placeholder={localeDatas.selectTree.search} clearable="true" - value={searchValue} clearabletrigger="always" + value={searchValue} onKeyDown={(e) => { if (e.keyCode === '13') { searchTreeNode(e.target.value) } }} onChange={(e) => { + setSearchValue(e.target.value) debouncedFilterItems(e.target.value) }} /> diff --git a/components/select-tree/components/Trigger.jsx b/components/select-tree/components/Trigger.jsx index 04d917177..1686d0c6d 100644 --- a/components/select-tree/components/Trigger.jsx +++ b/components/select-tree/components/Trigger.jsx @@ -14,7 +14,8 @@ const Trigger = ({ selectedItemsRef, placeholder, valueRender, - isFocus + isFocus, + bordered }) => { return (
{selectedItems.map((node, index) => ( - {node.title || ''} + {(node && node.title) || ''} ))}
{selectedItems.length === 0 && {placeholder}} @@ -46,7 +48,7 @@ const Trigger = ({ selectedItems.slice(0, showCount || 1).map((node, index) => { return (
-
{node.title || ''}
+
{(node && node.title) || ''}
{type === 'multiple' && ( +
+{selectedItems.length - showCount}
)} diff --git a/components/select-tree/components/tree/style/index.scss b/components/select-tree/components/tree/style/index.scss index deb1926ae..b4faba1d3 100644 --- a/components/select-tree/components/tree/style/index.scss +++ b/components/select-tree/components/tree/style/index.scss @@ -10,7 +10,6 @@ $tree: 'hi-select-tree' !default; &--empty { display: inline-block; - margin-bottom: 12px; } &__nodes { @@ -100,6 +99,7 @@ $tree: 'hi-select-tree' !default; .hi-select-tree--empty { padding: 12px 0 0 12px; + margin-bottom: 12px; } } diff --git a/components/select-tree/components/tree/util.js b/components/select-tree/components/tree/util.js index 688b1e144..86e3ff9ad 100644 --- a/components/select-tree/components/tree/util.js +++ b/components/select-tree/components/tree/util.js @@ -353,10 +353,12 @@ export const clearReturnData = (arg) => { arg = _.cloneDeep(arg) if (arg instanceof Array) { arg = arg.map((node) => { - delete node.ancestors - delete node.pId + if (node) { + delete node.ancestors + delete node.pId + } return node - }) + }).filter(Boolean) } else { delete arg.ancestors delete arg.pId diff --git a/components/select-tree/index.d.ts b/components/select-tree/index.d.ts index 4cc60d627..adcea667f 100644 --- a/components/select-tree/index.d.ts +++ b/components/select-tree/index.d.ts @@ -28,6 +28,7 @@ interface Props { showCheckedMode?: 'ALL' | 'PARENT' | 'CHILD' mode?: 'normal' | 'breadcrumb' defaultExpandAll?: boolean + bordered?: boolean defaultExpandIds?: string[] | number[] expandIds?: string[] | number[] dataSource?: DataSource | DataSourFun @@ -38,6 +39,7 @@ interface Props { valueRender?: (item: DataItem) => JSX.Element style?: CSSProperties value?: DataItem[] | string[] | number[] | string + className?: string } declare const SelectTree: React.ComponentType export default SelectTree diff --git a/components/select-tree/style/index.scss b/components/select-tree/style/index.scss index e46cbe722..a21e34548 100644 --- a/components/select-tree/style/index.scss +++ b/components/select-tree/style/index.scss @@ -7,7 +7,6 @@ &__input { height: 32px; line-height: 32px; - border: 1px solid use-color('gray-30'); box-sizing: border-box; border-radius: 2px; text-align: left; @@ -17,8 +16,29 @@ display: flex; justify-content: space-between; + &.bordered { + border: 1px solid use-color('gray-30'); + } + + .hi-icon + .hi-selecttree__icon-close { + margin-left: 0; + } + + &-items--left { + display: flex; + align-items: center; + padding: 0 8px; + height: 22px; + line-height: 22px; + font-size: 12px; + color: var(--color-black); + background-color: var(--color-gray-10); + border-radius: 2px; + box-sizing: border-box; + } + &:hover { - border: 1px solid use-color('primary'); + border-color: use-color('primary'); .hi-selecttree__input--expand.clearable { display: none; @@ -34,7 +54,7 @@ } &--focus { - border: 1px solid use-color('primary'); + border-color: use-color('primary'); } &--single { diff --git a/components/select/MultipleInput.js b/components/select/MultipleInput.js index d4eafee4e..08751617f 100644 --- a/components/select/MultipleInput.js +++ b/components/select/MultipleInput.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react' +import React, { useState, useEffect, useRef, useCallback } from 'react' import classNames from 'classnames' import _ from 'lodash' import Icon from '../icon' @@ -20,26 +20,28 @@ const MultipleInput = ({ onClickOption, onClear, fieldNames, - isFocus + isFocus, + bordered }) => { const icon = dropdownShow ? 'up' : 'down' const [showCount, setShowCount] = useState(0) const tagWrapperRef = useRef('') const calShowCountFlag = useRef(true) // 在渲染完成进行测试是否展示 +1 const selectedItems = _.uniqBy(cacheSelectItem.concat(propsSelectItem), transKeys(fieldNames, 'id')) - - useEffect(() => { + const resizeTimeId = useRef() + const getShowCount = useCallback(() => { if (multipleMode === 'nowrap' && calShowCountFlag.current && tagWrapperRef.current) { // 多选超过一行时以数字显示 const tagWrapperRect = tagWrapperRef.current.getBoundingClientRect() + let width = 0 let showCountIndex = 0 // 在第几个开始显示折行 const tags = tagWrapperRef.current.querySelectorAll('.hi-select__input--item') tags.forEach((tag, index) => { const tagRect = tag.getBoundingClientRect() width += tagRect.width - if (width + 50 > tagWrapperRect.width && calShowCountFlag.current) { - // 50是留给显示剩余选项的空间 + if (width + 110 > tagWrapperRect.width && calShowCountFlag.current) { + // 110是留给显示剩余选项的空间 calShowCountFlag.current = false showCountIndex = index } @@ -48,7 +50,27 @@ const MultipleInput = ({ } else { calShowCountFlag.current = true } - }) + }, [showCount, selectedItems]) + const resize = useCallback(() => { + clearTimeout(resizeTimeId.current) + resizeTimeId.current = setTimeout(() => { + calShowCountFlag.current = true + setShowCount(0) + getShowCount() + }, [60]) + }, [getShowCount, showCount]) + + useEffect(() => { + window.addEventListener('resize', resize) + return () => { + window.removeEventListener('resize', resize) + } + }, []) + + useEffect(() => { + calShowCountFlag.current = true + getShowCount() + }, [selectedItems]) const handleClear = (e) => { e.stopPropagation() @@ -63,6 +85,7 @@ const MultipleInput = ({ 'multiple-values', `theme__${theme}`, { + bordered, disabled }, { diff --git a/components/select/Select.js b/components/select/Select.js index ec272ba04..9b36e1312 100644 --- a/components/select/Select.js +++ b/components/select/Select.js @@ -7,7 +7,7 @@ import SelectInput from './SelectInput' import SelectDropdown from './SelectDropdown' import Provider from '../context' import HiRequest from '../_util/hi-request' -import { resetSelectedItems, transKeys } from './utils' +import { resetSelectedItems, transKeys, uniqBy } from './utils' const InternalSelect = (props) => { const { @@ -30,14 +30,17 @@ const InternalSelect = (props) => { localeDatas, preventOverflow, placement, + onSearch, onChange: propsonChange, + onOverlayScroll, value, defaultValue, autoload, searchable: propsSearchable, fieldNames, overlayClassName, - setOverlayContainer + setOverlayContainer, + bordered = true } = props const selectInputContainer = useRef() const historyData = useRef([]) @@ -46,6 +49,9 @@ const InternalSelect = (props) => { const [focusedIndex, setFocusedIndex] = useState(0) const [isFocus, setIsFouces] = useState(false) const SelectWrapper = useRef() + const targetByKeyDown = useRef(false) + const CancelToken = HiRequest.CancelToken + let cancel // 存储问题 const [cacheSelectItem, setCacheSelectItem] = useState([]) @@ -83,7 +89,7 @@ const InternalSelect = (props) => { }) ) } - historyData.current = _.uniqBy(data.concat(dropdownItems, historyData.current), transKeys(fieldNames, 'id')) + historyData.current = uniqBy(data.concat(dropdownItems, historyData.current), transKeys(fieldNames, 'id')) }, [dropdownItems, data]) useEffect(() => { @@ -103,7 +109,7 @@ const InternalSelect = (props) => { // 处理默认值的问题 const selectedItems = resetSelectedItems( value, - _.uniqBy(cacheSelectItem.concat(dropdownItems), transKeys(fieldNames, 'id')), + uniqBy(cacheSelectItem.concat(dropdownItems), transKeys(fieldNames, 'id')), transKeys(fieldNames, 'id') ) setSelectedItems(selectedItems) @@ -211,7 +217,8 @@ const InternalSelect = (props) => { : dropdownItems[direction === 'down' ? 0 : l - 1][transKeys(fieldNames, 'children')] focusedGroup[1] = direction === 'down' ? -1 : _dropdownItems.length } else { - _dropdownItems = dropdownItems[focusedGroup[0]][transKeys(fieldNames, 'children')] || [] + _dropdownItems = + (dropdownItems[focusedGroup[0]] && dropdownItems[focusedGroup[0]][transKeys(fieldNames, 'children')]) || [] _group = focusedGroup[0] } return { _dropdownItems, _focusedIndex: focusedGroup[1], group: _group } @@ -221,6 +228,7 @@ const InternalSelect = (props) => { // 方向键的回调 const moveFocusedIndex = useCallback( (direction) => { + targetByKeyDown.current = true let _focusedIndex = focusedIndex let _dropdownItems = dropdownItems let group = 0 @@ -290,7 +298,7 @@ const InternalSelect = (props) => { } setFocusedIndex(isGroup ? group + '-' + _focusedIndex : _focusedIndex) }, - [focusedIndex, dropdownItems, fieldNames] + [focusedIndex, dropdownItems, fieldNames, targetByKeyDown.current] ) // 点击回车选中 const onEnterSelect = useCallback(() => { @@ -331,7 +339,7 @@ const InternalSelect = (props) => { } } }, - [onEnterSelect, moveFocusedIndex] + [onEnterSelect, moveFocusedIndex, targetByKeyDown.current] ) // 对关键字的校验 对数据的过滤 const matchFilter = useCallback( @@ -397,11 +405,16 @@ const InternalSelect = (props) => { options.params = key ? { [key]: keyword, ...params } : params const _withCredentials = withCredentials || credentials === 'include' - + // 取消上一次的请求 + const CANCEL_STATE = 'Cancel' + typeof cancel === 'function' && cancel(CANCEL_STATE) HiRequest({ url, method, data: data, + cancelToken: new CancelToken((c) => { + cancel = c + }), withCredentials: _withCredentials, error, beforeRequest: (config) => { @@ -409,18 +422,22 @@ const InternalSelect = (props) => { return config }, errorCallback: (err) => { - setLoading(false) + const { message = 'normal' } = err + setLoading(message === CANCEL_STATE) error && error(err) }, ...options }).then( (response) => { - setLoading(false) - const dataItems = transformResponse && transformResponse(response.data, response) - if (Array.isArray(dataItems)) { - setDropdownItems(dataItems) - } else { - console.error('transformResponse return data is not array') + const { message = 'normal' } = response + if (message !== CANCEL_STATE) { + setLoading(false) + const dataItems = transformResponse && transformResponse(response.data, response) + if (Array.isArray(dataItems)) { + setDropdownItems(dataItems) + } else { + console.error('transformResponse return data is not array') + } } }, (error) => { @@ -432,7 +449,10 @@ const InternalSelect = (props) => { // 过滤筛选项 const onFilterItems = (keyword) => { setKeyword(keyword) - + if (typeof onSearch === 'function') { + onSearch(keyword) + return + } if (dataSource && (autoload || keyword)) { remoteSearch(keyword) } @@ -519,17 +539,21 @@ const InternalSelect = (props) => { } const _selectedItems = [...selectedItems] const changedItems = [] - filterItems.forEach((item) => { - if (!item[transKeys(fieldNames, 'disabled')] && matchFilter(item)) { - if ( - !_selectedItems - .map((selectItem) => selectItem[transKeys(fieldNames, 'id')]) - .includes(item[transKeys(fieldNames, 'id')]) - ) { - _selectedItems.push(item) - changedItems.push(item) + filterItems.forEach((filterItem) => { + const filterItemOrGroupChilds = isArray(filterItem.children) ? filterItem.children : [filterItem] + filterItemOrGroupChilds.forEach((item) => { + if (!item[transKeys(fieldNames, 'disabled')] && matchFilter(item)) { + if ( + !_selectedItems.some((selectItem) => { + const key = transKeys(fieldNames, 'id') + return selectItem[key] === item[key] + }) + ) { + _selectedItems.push(item) + changedItems.push(item) + } } - } + }) }) onChange(_selectedItems, changedItems, () => {}) } @@ -581,6 +605,7 @@ const InternalSelect = (props) => { fieldNames={fieldNames} isFocus={isFocus} value={value} + bordered={bordered} onClick={() => { handleInputClick() }} @@ -612,16 +637,20 @@ const InternalSelect = (props) => { fieldNames={fieldNames} localeMap={localeDatas.select || {}} mode={type} + onOverlayScroll={onOverlayScroll} + targetByKeyDown={targetByKeyDown} searchPlaceholder={searchPlaceholder} theme={theme} onFocus={onFocus} - isOnSearch={dataSource} + isByRemoteSearch={dataSource} + isByCustomSearch={onSearch} onSearch={debouncedFilterItems} searchable={searchable} showCheckAll={showCheckAll} checkAll={checkAll} loading={loading} focusedIndex={focusedIndex} + setFocusedIndex={setFocusedIndex} showJustSelected={showJustSelected} filterOption={filterOption} matchFilter={matchFilter} diff --git a/components/select/SelectDropdown.js b/components/select/SelectDropdown.js index 541c6cb90..d9977491f 100644 --- a/components/select/SelectDropdown.js +++ b/components/select/SelectDropdown.js @@ -21,7 +21,8 @@ const SelectDropdown = ({ dropdownItems, localeMap, onSearch, - isOnSearch, + isByRemoteSearch, + isByCustomSearch, onClickOption, checkAll, selectInputWidth, @@ -29,18 +30,22 @@ const SelectDropdown = ({ show, fieldNames, focusedIndex, - isGroup + isGroup, + onOverlayScroll, + setFocusedIndex, + targetByKeyDown }) => { const [filterItems, setFilterItems] = useState(dropdownItems) const [searchbarValue, setSearchbarValue] = useState('') - const [ischeckAll, setIscheckAll] = useState(false) + const [isCheckAll, setIsCheckAll] = useState(false) const searchbar = useRef('') const dropdownWrapper = useRef('') + useEffect(() => { setFilterItems(dropdownItems) }, [dropdownItems]) useEffect(() => { - if (dropdownWrapper.current) { + if (dropdownWrapper.current && targetByKeyDown.current) { let _focusedIndex = focusedIndex if (isGroup) { const focusedGroup = _focusedIndex.split('-') @@ -51,13 +56,26 @@ const SelectDropdown = ({ group-- } } - dropdownWrapper.current.scrollTop = (_focusedIndex - 6) * 36 + const _scrollTop = dropdownWrapper.current.scrollTop + const focusedIndexTop = (_focusedIndex - 6) * 36 + // 防止点击上下滚动问题 + dropdownWrapper.current.scrollTop = + _scrollTop >= focusedIndexTop && focusedIndexTop > 0 ? _scrollTop : focusedIndexTop } - }, [focusedIndex]) + }, [focusedIndex, targetByKeyDown.current]) // 监控全选功能 useEffect(() => { - setIscheckAll(selectedItems.length > 0 && selectedItems.length === filterItems.length) + const selectedItemIds = selectedItems.map((item) => { + return item[transKeys(fieldNames, 'id')] + }) + const _isCheckAll = filterItems.every((filterItem) => { + const filterItemOrGroupChilds = Array.isArray(filterItem.children) ? filterItem.children : [filterItem] + return filterItemOrGroupChilds.every((item) => { + return selectedItemIds.includes(item[transKeys(fieldNames, 'id')]) + }) + }) + setIsCheckAll(_isCheckAll) }, [selectedItems, filterItems]) // 让搜索框获取焦点 useEffect(() => { @@ -87,7 +105,7 @@ const SelectDropdown = ({ useEffect(() => { const _filterItems = dropdownItems setFilterItems(_filterItems) - }, [mode, isOnSearch, dropdownItems, show]) + }, [mode, isByRemoteSearch, dropdownItems, show]) let matched = 0 const style = optionWidth && { @@ -133,6 +151,7 @@ const SelectDropdown = ({ (e, item, index) => { e.stopPropagation() e.preventDefault() + setFocusedIndex(index) if (item[transKeys(fieldNames, 'disabled')]) { return } @@ -191,7 +210,7 @@ const SelectDropdown = ({ disabled={item[transKeys(fieldNames, 'disabled')]} >
- {isOnSearch + {isByRemoteSearch ? item[transKeys(fieldNames, 'title')] : hightlightKeyword(item[transKeys(fieldNames, 'title')], item[transKeys(fieldNames, 'id')])}
@@ -199,7 +218,7 @@ const SelectDropdown = ({ )} {mode === 'single' && (
- {isOnSearch + {isByRemoteSearch ? item[transKeys(fieldNames, 'title')] : hightlightKeyword(item[transKeys(fieldNames, 'title')], item[transKeys(fieldNames, 'id')])}
@@ -219,7 +238,7 @@ const SelectDropdown = ({ ) renderGroup.push(label) filterGroupItem[transKeys(fieldNames, 'children')].forEach((item, index) => { - renderGroup.push(normalItem(item, filterItemsIndex + '-' + index, true)) + matchFilter(item) && renderGroup.push(normalItem(item, filterItemsIndex + '-' + index, true)) }) return renderGroup } @@ -234,12 +253,15 @@ const SelectDropdown = ({ 'is-active': isSelected, 'is-disabled': isDisabled, 'hi-select__dropdown--item--child': isChildItem, - 'is-focus': filterItemsIndex === focusedIndex, + 'is-focus': targetByKeyDown.current && filterItemsIndex === focusedIndex, 'hi-select__dropdown--item-default': !item[transKeys(fieldNames, 'children')] && !dropdownRender })} onClick={(e) => onClickOptionIntal(e, item, _filterItemsIndex)} key={item[transKeys(fieldNames, 'id')]} index={filterItemsIndex} + onMouseEnter={() => { + !targetByKeyDown.current && setFocusedIndex(_filterItemsIndex) + }} > {renderOption(isSelected, item, filterItemsIndex)} @@ -247,7 +269,16 @@ const SelectDropdown = ({ } const renderItems = () => { return ( -
    +
      { + targetByKeyDown.current = false + }} + onScroll={(e) => { + onOverlayScroll && onOverlayScroll(e) + }} + > {filterItems && filterItems.map((item, filterItemsIndex) => { if (matchFilter(item)) { @@ -269,7 +300,7 @@ const SelectDropdown = ({ } return (
      - {searchable && ( + {(searchable || isByCustomSearch) && (
      @@ -306,7 +337,7 @@ const SelectDropdown = ({
      {showCheckAll && ( { checkAll(e, filterItems, e.target.checked) }} diff --git a/components/select/SingleInput.js b/components/select/SingleInput.js index f79ec5156..e7eefa45e 100644 --- a/components/select/SingleInput.js +++ b/components/select/SingleInput.js @@ -16,7 +16,8 @@ const SingleInput = (props) => { selectedItems: propsSelectItem, onClear, fieldNames, - isFocus + isFocus, + bordered } = props const [cacheselectedItems, setCacheselectedItems] = useState(propsSelectItem || []) useEffect(() => { @@ -35,14 +36,13 @@ const SingleInput = (props) => { const selectedItems = propsSelectItem.length > 0 ? propsSelectItem : cacheselectedItems placeholder = selectedItems.length > 0 ? selectedItems[0][transKeys(fieldNames, 'title')] : placeholder - return (
      void + onSearch?: (keyword: string) => void + onOverlayScroll?: (e: Event) => void render?: (item: DataItem, selected: boolean) => JSX.Element overlayClassName?: string setOverlayContainer?: (triggerNode: any) => any diff --git a/components/select/style/select-dropdown.scss b/components/select/style/select-dropdown.scss index 409c45d13..e8d959c5c 100644 --- a/components/select/style/select-dropdown.scss +++ b/components/select/style/select-dropdown.scss @@ -99,11 +99,11 @@ background-color: use-color('primary-20'); } - &:hover { - background-color: use-color('primary-20'); + &.is-focus { + background-color: use-color('primary-10'); } - &.is-focus { + &:hover { background-color: use-color('primary-20'); } diff --git a/components/select/style/select-input.scss b/components/select/style/select-input.scss index 60ba65157..3b6c795fd 100644 --- a/components/select/style/select-input.scss +++ b/components/select/style/select-input.scss @@ -8,7 +8,6 @@ color: use-color('black'); line-height: 1; border-radius: 2px; - border: 1px solid use-color('gray-30'); cursor: pointer; outline: none; user-select: none; @@ -24,6 +23,10 @@ transition: margin 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); } + &.bordered { + border: 1px solid use-color('gray-30'); + } + &.disabled { cursor: not-allowed; background-color: use-color('gray-10'); diff --git a/components/select/utils.js b/components/select/utils.js index b804adac3..02d52d6b5 100644 --- a/components/select/utils.js +++ b/components/select/utils.js @@ -14,7 +14,6 @@ export const resetSelectedItems = (value, dropdownItems = [], key) => { const selectedItems = dropdownItems.filter((item) => { return values.includes(item[key]) }) - // 处理子节点 dropdownItems.forEach((item) => { if (item.children) { @@ -29,3 +28,18 @@ export const resetSelectedItems = (value, dropdownItems = [], key) => { export const transKeys = (fieldNames, key) => { return fieldNames[key] || key } +/** + * 数组去重 + * @param {Array} array 需要去重的数组 + * @param {String} key 判断重复的属性 + */ +export const uniqBy = (array, key) => { + const haseMap = {} + array.forEach((item) => { + const _key = typeof item.groupId !== 'undefined' ? item.groupId : item[key] + haseMap[_key] = item + }) + return Object.keys(haseMap).map((key) => { + return haseMap[key] + }) +} diff --git a/components/slider/index.d.ts b/components/slider/index.d.ts index 5bad12c34..6f3e44bfa 100644 --- a/components/slider/index.d.ts +++ b/components/slider/index.d.ts @@ -4,6 +4,8 @@ interface Props { value?: number min?: number max?: number + style?: CSSProperties + className?: string disabled?: boolean tipFormatter?: (value: number) => JSX.Element marks?: { diff --git a/components/switch/index.d.ts b/components/switch/index.d.ts index 63030e7b3..b41c9a72a 100644 --- a/components/switch/index.d.ts +++ b/components/switch/index.d.ts @@ -3,6 +3,8 @@ interface Props { checked?: boolean defaultChecked?: boolean disabled?: boolean + style?: CSSProperties + className?: string onChange?: (checked: boolean) => void } declare const Switch: React.ComponentType diff --git a/components/table/index.d.ts b/components/table/index.d.ts index c9a6600a7..93021a360 100644 --- a/components/table/index.d.ts +++ b/components/table/index.d.ts @@ -55,6 +55,8 @@ interface Props { emptyContent?: string | JSX.Element columns: ColumnItem[] data: object[] + style?: CSSProperties + className?: string } declare const Table: React.ComponentType export default Table diff --git a/components/tabs/index.d.ts b/components/tabs/index.d.ts index cb9a0412c..f196a3fde 100644 --- a/components/tabs/index.d.ts +++ b/components/tabs/index.d.ts @@ -22,6 +22,8 @@ interface Props { max?: number canScroll?: boolean draggable?: boolean + style?: CSSProperties + className?: string onTabClick?: (tabKey: string | number, event: MouseEvent) => void onEdit?: (action: 'add' | 'delete', index: number, tabKey: string | number) => void onDragStart?: (dragNode: NodeData) => void @@ -38,6 +40,8 @@ interface PaneProps { closeable?: boolean disabled?: boolean animation?: boolean + style?: CSSProperties + className?: string } declare class Pane extends React.Component { } diff --git a/components/tabs/style/index.scss b/components/tabs/style/index.scss index 6aa5eb597..afdf20ccd 100644 --- a/components/tabs/style/index.scss +++ b/components/tabs/style/index.scss @@ -15,6 +15,7 @@ $prefix: 'hi-tabs' !default; cursor: pointer; border: 1px solid transparent; transition: all 300ms; + &:not(&--disabled):focus { outline: none; color: use-color('primary'); @@ -112,6 +113,12 @@ $prefix: 'hi-tabs' !default; } } + &.hi-tabs--line:not(.hi-tabs--vertical) { + .hi-tabs__item:first-of-type { + padding-left: 0; + } + } + &--card.#{$prefix}--vertical { display: flex; flex-wrap: nowrap; @@ -290,10 +297,6 @@ $prefix: 'hi-tabs' !default; padding: 0 16px; box-sizing: border-box; - &:first-of-type { - padding-left: 0; - } - &:hover { color: use-color('primary'); } diff --git a/components/tag/index.d.ts b/components/tag/index.d.ts index 20d710bbf..b2c8200d9 100644 --- a/components/tag/index.d.ts +++ b/components/tag/index.d.ts @@ -1,5 +1,7 @@ interface Props { type?: 'primary' | 'success' | 'warning' | 'danger' + style?: CSSProperties + className?: string appearance?: 'default' | 'line' onClick?: (e: MouseEvent) => void } diff --git a/components/tree/index.d.ts b/components/tree/index.d.ts index e389048b8..5f2ae26c6 100644 --- a/components/tree/index.d.ts +++ b/components/tree/index.d.ts @@ -38,6 +38,8 @@ interface Props { openIcon?: string closeIcon?: string apperance?: 'default' | 'line' | 'folder' + style?: CSSProperties + className?: string contextMenu?: ContextMenuOption[] | ContextMenuOptionFun onChange?: (data: TreeNode[]) => void onExpand?: (expanded: boolean, expandIds: string[], expandedNode: TreeNode) => void diff --git a/components/upload/index.d.ts b/components/upload/index.d.ts index 09fa06460..80d93c664 100644 --- a/components/upload/index.d.ts +++ b/components/upload/index.d.ts @@ -25,6 +25,8 @@ interface Props { defaultFileList?: FileItem[] fileList?: FileItem[] loading?: boolean + style?: CSSProperties + className?: string beforeUpload?: (files: FileItem[], fileList: FileItem[]) => boolean customUpload?: (files: FileItem[]) => void onChange?: (file: FileItem, fileList: FileItem[], response: object) => boolean diff --git a/components/watermark/index.d.ts b/components/watermark/index.d.ts index 26d6c8775..82a63853e 100644 --- a/components/watermark/index.d.ts +++ b/components/watermark/index.d.ts @@ -3,6 +3,8 @@ interface Props { content?: strgin | string[] logo?: any opacity?: number + style?: CSSProperties + className?: string } declare const Watermark: React.ComponentType export default Watermark diff --git a/docs/demo/base-table/section-base.jsx b/docs/demo/base-table/section-base.jsx new file mode 100644 index 000000000..8f1ea1e2c --- /dev/null +++ b/docs/demo/base-table/section-base.jsx @@ -0,0 +1,142 @@ +import React from 'react' +import DocViewer from '../../../libs/doc-viewer' +import HiBaseTable, { AutoResizer } from '../../../components/hi-base-table' +import Watermark from '../../../components/watermark' +const prefix = 'table-base' +const rightOptions = ['基础', '10000条数据'] +const code = [ + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = [ + { + title: '商品名', + dataKey: 'name' + }, + { + title: '品类', + dataKey: 'type' + }, + { + title: '规格', + dataKey: 'size' + }, + { + title: '单价', + dataKey: 'price' + }, + { + title: '门店', + dataKey: 'address' + }, + { + title: '库存', + dataKey: 'stock' + } + ] + + this.data = [ + { + name: '小米9', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '3299.00', + address: '华润五彩城店', + stock: '29,000', + key: 1 + }, + { + name: '小米9 SE', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '1999.00', + address: '清河店', + stock: '10,000', + key: 2 + }, + { + name: '小米8', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '2599.00', + address: '双安店', + stock: '12,000', + key: 3 + }, + { + name: 'Redmi Note7', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '999.00', + address: '华润五彩城店', + stock: '140,000', + key: 4 + }, + { + name: '小米8 SE', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '699.00', + address: '双安店', + stock: '12,000', + key: 5 + } + ] + } + render() { + return + } + }`, + opt: ['基础'] + }, + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = this.generateColumns(10) + + this.data = this.generateData(this.columns, 10000) + } + // 格式化 Columns + generateColumns(count = 10, prefix = 'column-', props){ + return new Array(count).fill(0).map((column, columnIndex) => ({ + ...props, + key: prefix + columnIndex, + dataKey: prefix+columnIndex, + title: "column" + columnIndex, + width: 150 + })) + } + + + generateData(columns, count = 200, prefix = 'row-'){ + return new Array(count).fill(0).map((row, rowIndex) => { + return columns.reduce( + (rowData, column, columnIndex) => { + rowData[column.dataKey] = "Row" + rowIndex +" - Col" + columnIndex + return rowData + }, + { + key: prefix + rowIndex, + } + ) + }) + } + + render() { + return + } + }`, + opt: ['10000条数据'] + } +] + +const DemoBase = () => ( + +) +export default DemoBase diff --git a/docs/demo/base-table/section-bigdata.jsx b/docs/demo/base-table/section-bigdata.jsx new file mode 100644 index 000000000..e5639dd50 --- /dev/null +++ b/docs/demo/base-table/section-bigdata.jsx @@ -0,0 +1,158 @@ +import React from 'react' +import DocViewer from '../../../libs/doc-viewer' +import HiBaseTable, { AutoResizer } from '../../../components/hi-base-table' +import Watermark from '../../../components/watermark' +const prefix = 'table-bigdata' +const rightOptions = ['10000 条数据'] +// 格式化 Columns +const code = [ + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = this.generateColumns(10) + + this.data = this.generateData(this.columns, 10000) + } + generateColumns(count = 10, prefix = 'column-', props){ + return new Array(count).fill(0).map((column, columnIndex) => ({ + ...props, + key: prefix + columnIndex, + dataKey: prefix+columnIndex, + title: "column" + columnIndex, + width: 150 + })) + } + + + generateData(columns, count = 200, prefix = 'row-'){ + return new Array(count).fill(0).map((row, rowIndex) => { + return columns.reduce( + (rowData, column, columnIndex) => { + rowData[column.dataKey] = "Row" + rowIndex +" - Col" + columnIndex + return rowData + }, + { + key: prefix + rowIndex, + } + ) + }) + } + render() { + return
      + 123 + +
      + } + }`, + opt: ['10000 条数据'] + }, + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = [ + { + title: '商品名', + dataKey: 'name' + }, + { + title: '品类', + dataKey: 'type' + }, + { + title: '规格', + dataKey: 'size' + }, + { + title: '单价', + dataKey: 'price' + }, + { + title: '门店', + dataKey: 'address' + }, + { + title: '库存', + dataKey: 'stock' + } + ] + + this.data = [ + { + name: '小米9', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '3299.00', + address: '华润五彩城店', + stock: '29,000', + key: 1 + }, + { + name: '小米9 SE', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '1999.00', + address: '清河店', + stock: '10,000', + key: 2 + }, + { + name: '小米8', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '2599.00', + address: '双安店', + stock: '12,000', + key: 3 + }, + { + name: 'Redmi Note7', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '999.00', + address: '华润五彩城店', + stock: '140,000', + key: 4 + }, + { + name: '小米8 SE', + type: '手机', + size: '6G+64G 全息幻彩蓝', + price: '699.00', + address: '双安店', + stock: '12,000', + key: 5 + } + ] + } + render() { + return
      + + {(size) => { + const { width, height } = size + return ( + + ) + }} + +
      + } + }`, + opt: ['自适应'] + } +] + +const DemoBase = () => ( + +) +export default DemoBase diff --git a/docs/demo/base-table/section-tooltip.jsx b/docs/demo/base-table/section-tooltip.jsx new file mode 100644 index 000000000..ec2b61405 --- /dev/null +++ b/docs/demo/base-table/section-tooltip.jsx @@ -0,0 +1,371 @@ +import React from 'react' +import DocViewer from '../../../libs/doc-viewer' +import HiBaseTable, { AutoResizer, SortOrder } from '../../../components/hi-base-table' +import Watermark from '../../../components/watermark' +import Tooltip from '../../../components/tooltip' +const prefix = 'table-bigdata' +const rightOptions = ['结合Tooltip', '树形表格', '列冻结', '树形 + 列冻结 + 吸顶 + 排序'] +// 格式化 Columns +const code = [ + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = this.generateColumns(10) + + this.data = this.generateData(this.columns, 10) + } + // 格式化 Columns + generateColumns(count = 10, prefix = 'column-', props){ + return new Array(count).fill(0).map((column, columnIndex) => ({ + ...props, + key: prefix + columnIndex, + dataKey: prefix+columnIndex, + title: "column" + columnIndex, + width: 150 + })) + } + + generateData(columns, count = 200, prefix = 'row-'){ + return new Array(count).fill(0).map((row, rowIndex) => { + return columns.reduce( + (rowData, column, columnIndex) => { + rowData[column.dataKey] = "Row" + rowIndex +" - Col" + "Row" + '-' + rowIndex +" - Col" + return rowData + }, + { + key: prefix + rowIndex, + } + ) + }) + } + TableCell({ className, cellData }){ + return +

      + {cellData} +

      +
      + } + render() { + return + } +}`, + opt: ['结合Tooltip'] + }, + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + } + + render() { + return ( + + ) + } + }`, + opt: ['树形表格'] + }, + { + code: `import React from 'react' + import HiBaseTable , {SortOrder} from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = this.generateColumns(10) + + this.data = this.generateData(this.columns, 100) + } + // 格式化 Columns + generateColumns(count = 10, prefix = 'column-', props){ + return new Array(count).fill(0).map((column, columnIndex) => ({ + ...props, + key: prefix + columnIndex, + dataKey: prefix+columnIndex, + title: "column" + columnIndex, + width: 150 + })) + } + + + generateData(columns, count = 200, prefix = 'row-'){ + return new Array(count).fill(0).map((row, rowIndex) => { + return columns.reduce( + (rowData, column, columnIndex) => { + rowData[column.dataKey] = "Row" + rowIndex +" - Col" + columnIndex + return rowData + }, + { + key: prefix + rowIndex, + } + ) + }) + } + + render() { + return + } + }`, + opt: ['列冻结'] + }, + { + code: `import React from 'react' + import HiBaseTable from '@hi-ui/hiui/es/hi-base-table'\n + class Demo extends React.Component { + constructor(props){ + super(props) + this.columns = [ + { + key: 'column-0', + dataKey: 'column-0', + title: 'Column 0', + width: 150, + sortable: true + }, + { key: 'column-1', dataKey: 'column-1', title: 'Column 1', width: 150, sortable: true }, + { key: 'column-2', dataKey: 'column-2', title: 'Column 2', width: 150 }, + { key: 'column-3', dataKey: 'column-3', title: 'Column 3', width: 150 }, + { key: 'column-4', dataKey: 'column-4', title: 'Column 4', width: 150 }, + { key: 'column-5', dataKey: 'column-5', title: 'Column 5', width: 150 }, + { key: 'column-6', dataKey: 'column-6', title: 'Column 6', width: 150 }, + { key: 'column-7', dataKey: 'column-7', title: 'Column 7', width: 150 }, + { key: 'column-8', dataKey: 'column-8', title: 'Column 8', width: 150 }, + { key: 'column-9', dataKey: 'column-9', title: 'Column 9', width: 150 }, + { key: 'column-10', dataKey: 'column-10', title: 'Column 10', width: 150 }, + { key: 'column-11', dataKey: 'column-11', title: 'Column 11', width: 150 }, + { key: 'column-12', dataKey: 'column-12', title: 'Column 12', width: 150 }, + { key: 'column-13', dataKey: 'column-13', title: 'Column 13', width: 150 }, + { + key: 'column-14', + dataKey: 'column-14', + title: 'Column 14', + width: 150, + } + ] + this.state = { + data:[ + { + 'column-0': 'Row 0 - Col 0', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [ + { + 'column-0': 'Sub 0', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [ + { + 'column-0': 'Sub 0', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [ + { + 'column-0': 'Sub-Sub 0', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [] + } + ] + }, + { + 'column-0': 'Sub 1', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [ + { + 'column-0': 'Sub-Sub 1', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [] + } + ] + } + ] + }, + { + 'column-0': 'Sub 1', + 'column-1': 'Row 0 - Col 1', + 'column-2': 'Row 0 - Col 2', + 'column-3': 'Row 0 - Col 3', + 'column-4': 'Row 0 - Col 4', + 'column-5': 'Row 0 - Col 5', + 'column-6': 'Row 0 - Col 6', + 'column-7': 'Row 0 - Col 7', + 'column-8': 'Row 0 - Col 8', + 'column-9': 'Row 0 - Col 9', + 'column-10': 'Row 0 - Col 10', + 'column-11': 'Row 0 - Col 11', + 'column-12': 'Row 0 - Col 12', + 'column-13': 'Row 0 - Col 13', + 'column-14': 'Row 0 - Col 14', + children: [] + } + ] + }, + { + 'column-0': 'Row 1 - Col 0', + 'column-1': 'Row 1 - Col 1', + 'column-2': 'Row 1 - Col 2', + 'column-3': 'Row 1 - Col 3', + 'column-4': 'Row 1 - Col 4', + 'column-5': 'Row 1 - Col 5', + 'column-6': 'Row 1 - Col 6', + 'column-7': 'Row 1 - Col 7', + 'column-8': 'Row 1 - Col 8', + 'column-9': 'Row 1 - Col 9', + 'column-10': 'Row 1 - Col 10', + 'column-11': 'Row 1 - Col 11', + 'column-12': 'Row 1 - Col 12', + 'column-13': 'Row 1 - Col 13', + 'column-14': 'Row 1 - Col 14', + children: [] + } + ] + } + } + + + render() { + return { + console.log('column, key, order', column, key, order) + this.setState({ + data: this.state.data.reverse() + }) + }} /> + } + }`, + opt: ['树形 + 列冻结 + 吸顶 + 排序'] + } +] + +const DemoBase = () => ( + +) +export default DemoBase diff --git a/docs/demo/cascader/section-basic.jsx b/docs/demo/cascader/section-basic.jsx index 1c9fef2ca..c70c46992 100644 --- a/docs/demo/cascader/section-basic.jsx +++ b/docs/demo/cascader/section-basic.jsx @@ -3,7 +3,7 @@ import DocViewer from '../../../libs/doc-viewer' import Cascader from '../../../components/cascader' const prefix = 'section-basic' const desc = '展示从多个收起的备选项中选出的一个选项' -const rightOptions = ['基础', '带默认值', '可清空', '禁用'] +const rightOptions = ['基础', '带默认值', '可清空', '无边框', '禁用'] const code = [ { code: `import React from 'react' @@ -76,7 +76,6 @@ class Demo extends React.Component { }) }} - value={value} data={this.state.options} style={{ width: 240 }} /> @@ -162,6 +161,81 @@ class Demo extends React.Component { { code: `import React from 'react' import Cascader from '@hi-ui/hiui/es/cascader'\n +class Demo extends React.Component { + constructor () { + super() + this.state = { + options: [ + { + id: '手机', + content: '手机', + children: [ + { + id: '小米', + content: '小米', + children: [ + { + id: '小米3', + content: '小米3' + }, + { + id: '小米4', + content: '小米4' + }, + ] + }, + { + id: '红米', + content: '红米', + children: [ + { + id: '红米3', + content: '红米3' + }, + { + id: '红米4', + content: '红米4' + } + ] + } + ] + }, + { + id: '电视', + content: '电视', + children: [ + { + id: '小米电视4A', + content: '小米电视4A' + }, + { + id: '小米电视4C', + content: '小米电视4C' + } + ] + } + ] + } + } + render(){ + return( + { + console.log('on change', id) + }} + defaultValue={['电视','小米电视4C']} + data={this.state.options} + bordered={false} + style={{ width: 200 }} + /> + ) + } +}`, + opt: ['无边框'] + }, + { + code: `import React from 'react' +import Cascader from '@hi-ui/hiui/es/cascader'\n class Demo extends React.Component { constructor () { super() @@ -310,12 +384,6 @@ class Demo extends React.Component { ] const DemoBasic = () => ( - + ) export default DemoBasic diff --git a/docs/demo/date-picker/section-normal.jsx b/docs/demo/date-picker/section-normal.jsx index 6a0dbd553..6f266af0e 100644 --- a/docs/demo/date-picker/section-normal.jsx +++ b/docs/demo/date-picker/section-normal.jsx @@ -3,7 +3,7 @@ import DocViewer from '../../../libs/doc-viewer' import DatePicker from '../../../components/date-picker' const prefix = 'date-picker-normal' const desc = '以天为粒度,展示“YYYY-MM-DD”' -const rightOptions = ['基础', '带默认值', '受控', '禁用', '限制范围'] +const rightOptions = ['基础', '带默认值', '受控', '禁用', '限制范围', '无边框'] const code = [ { code: `import React from 'react' @@ -88,6 +88,21 @@ class Demo extends React.Component { } }`, opt: ['限制范围'] + }, + { + code: `import React from 'react' +import DatePicker from '@hi-ui/hiui/es/date-picker'\n +class Demo extends React.Component { + render () { + return ( + {console.log('onChange', date, dateStr)}} + /> + ) + } +}`, + opt: ['无边框'] } ] const DemoNormal = () => ( diff --git a/docs/demo/form/section-dynamic.jsx b/docs/demo/form/section-dynamic.jsx index 36dd6f2b0..a493ad2df 100644 --- a/docs/demo/form/section-dynamic.jsx +++ b/docs/demo/form/section-dynamic.jsx @@ -12,6 +12,7 @@ import Switch from '../../../components/switch' import DatePicker from '../../../components/date-picker' import Rate from '../../../components/rate' import Upload from '../../../components/upload' +import SelectTree from '../../../components/select-tree' import Grid from '../../../components/grid' const prefix = 'form-dynamic' @@ -175,7 +176,7 @@ const code = [ { controlCounter[0] === 'show' && - + } @@ -195,7 +196,6 @@ const code = [ {console.log('onChange DatePicker', date, dateStr)}} /> @@ -391,7 +391,8 @@ const DemoRow = () => ( DatePicker, Rate, Upload, - Grid + Grid, + SelectTree }} prefix={prefix} leftOptions={leftOptions} diff --git a/docs/demo/input/section-state.jsx b/docs/demo/input/section-state.jsx index 510972485..9d8f4ee06 100644 --- a/docs/demo/input/section-state.jsx +++ b/docs/demo/input/section-state.jsx @@ -4,7 +4,7 @@ import Grid from '../../../components/grid' import Input from '../../../components/input' import Radio from '../../../components/radio' import Button from '../../../components/button' -const leftOptions = ['基础', '受控', '默认值', '禁用', '可清除', '自动聚焦', '手动聚焦'] +const leftOptions = ['基础', '受控', '默认值', '禁用', '无边框', '可清除', '自动聚焦', '手动聚焦'] const prefix = 'input-state' const desc = '可获取有限长度的字符串,不折行显示' const code = [ @@ -77,6 +77,24 @@ class Demo extends React.Component { import Grid from '@hi-ui/hiui/es/grid' import Radio from '@hi-ui/hiui/es/radio' import Input from '@hi-ui/hiui/es/input'\n +class Demo extends React.Component { + render() { + return ( + + ) + } +}`, + opt: ['无边框'] + }, + { + code: `import React from 'react' +import Grid from '@hi-ui/hiui/es/grid' +import Radio from '@hi-ui/hiui/es/radio' +import Input from '@hi-ui/hiui/es/input'\n class Demo extends React.Component { render() { return ( diff --git a/docs/demo/preview/section-base.jsx b/docs/demo/preview/section-base.jsx new file mode 100644 index 000000000..ba1a08f83 --- /dev/null +++ b/docs/demo/preview/section-base.jsx @@ -0,0 +1,59 @@ +import React from 'react' +import DocViewer from '../../../libs/doc-viewer' +import Preview from '../../../components/preview/index.js' +import Grid from '../../../components/grid' +const prefix = 'Preview-base' +const desc = '用于大图预览' +const code = `import React from 'react' +import Button from '@hi-ui/hiui/es/button' +import Preview from '@hi-ui/hiui/es/preview'\n +import Grid from '@hi-ui/hiui/es/grid'\n +class Demo extends React.Component { + constructor() { + super() + this.state = { + previewShow: false, + activeIndex: 0, + images: [ + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_8.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_7.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_6.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_5.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_4.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_3.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_2.png', + 'http://i1.mifile.cn/f/i/hiui/docs/card/pic_1.png', + ] + } + } + render() { + const { images, previewShow, activeIndex } = this.state + const Row = Grid.Row + const Col = Grid.Col + return ( +
      + + { + this.state.images.slice(0, 4).map((url, index) => { + return
+ this.setState({previewShow: true,activeIndex: index })}/> + + }) + } + + this.setState({previewShow: false})} + /> + + ) + } +}` + +const DemoBase = () => +export default DemoBase diff --git a/docs/demo/select-tree/section-single.jsx b/docs/demo/select-tree/section-single.jsx index 18021350a..ef1d36d2b 100644 --- a/docs/demo/select-tree/section-single.jsx +++ b/docs/demo/select-tree/section-single.jsx @@ -3,7 +3,7 @@ import DocViewer from '../../../libs/doc-viewer' import SelectTree from '../../../components/select-tree' import Button from '../../../components/button' const prefix = 'tree-select-single' -const rightOptions = ['基础', '默认值', '默认展开'] +const rightOptions = ['基础', '默认值', '无边框', '默认展开'] const desc = '展示从多个收起的备选项中选出的一个选项' const defaultJson = `constructor () { super() @@ -126,6 +126,26 @@ const code = [ } }`, opt: ['默认展开'] + }, + { + code: `import React from 'react' + import SelectTree from '@hi-ui/hiui/es/select-tree'\n + class Demo extends React.Component { + ${defaultJson} + render () { + const { value, singleList } = this.state + return ( + + ) + } + }`, + opt: ['无边框'] } ] const DemoType = () => ( diff --git a/docs/demo/select/section-async.jsx b/docs/demo/select/section-async.jsx index 6beee9f47..8166d9532 100644 --- a/docs/demo/select/section-async.jsx +++ b/docs/demo/select/section-async.jsx @@ -25,7 +25,7 @@ class Demo extends React.Component { key: 'id', url: 'https://mife-gallery.test.mi.com/hiui/stores', transformResponse: (res) => { - if(res.code === 200){ + if(res && res.code === 200){ return res.data } return [] @@ -68,7 +68,7 @@ class Demo extends React.Component { url: 'https://mife-gallery.test.mi.com/hiui/stores', params:{id: keyword}, transformResponse: (res) => { - if(res.code === 200){ + if(res && res.code === 200){ return res.data } return [] @@ -127,7 +127,7 @@ class Demo extends React.Component { url: 'https://mife-gallery.test.mi.com/hiui/stores', params:{id: keyword}, transformResponse: (res) => { - if(res.code === 200){ + if(res && res.code === 200){ return res.data } return [] diff --git a/docs/demo/select/section-type.jsx b/docs/demo/select/section-type.jsx index 0d26fc904..9ecf27be4 100644 --- a/docs/demo/select/section-type.jsx +++ b/docs/demo/select/section-type.jsx @@ -2,7 +2,7 @@ import React from 'react' import DocViewer from '../../../libs/doc-viewer' import Select from '../../../components/select' const prefix = 'alert-autoClose' -const rightOptions = ['基础', '受控', '带默认值', '可清空', '禁用'] +const rightOptions = ['基础', '受控', '带默认值', '可清空', '无边框', '禁用'] const desc = '展示从多个收起的备选项中选出的一个选项' const code = [ { @@ -120,6 +120,40 @@ class Demo extends React.Component { } } + render () { + const { value, singleList } = this.state + return ( + { this.setState({ diff --git a/docs/zh-CN/components/cascader.mdx b/docs/zh-CN/components/cascader.mdx index 802c24636..4aa773046 100644 --- a/docs/zh-CN/components/cascader.mdx +++ b/docs/zh-CN/components/cascader.mdx @@ -38,6 +38,7 @@ import DemoAdvanced from '../../demo/cascader/section-advanced.jsx' | defaultValue | 设置当前选中值默认值 (3.0 新增) | string[] | - | [] | | expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | 'click' \| 'hover' | 'click' | | searchable | 是否可搜索 | boolean | true \| false | false | +| bordered | 是否有边框 | boolean | true \| false | true | | filterOption | 第一个参数为输入的关键字,第二个为数据项,返回值为 true 时将出现在结果项。仅在 searchable 为 true 时有效 | (keyword: string, item: DataItem) => boolean | - | - | | clearable | 是否可清空 | boolean | true \| false | true | | disabled | 是否禁止使用 | boolean | true \| false | false | diff --git a/docs/zh-CN/components/date-picker.mdx b/docs/zh-CN/components/date-picker.mdx index 774d1e468..9f38f361d 100755 --- a/docs/zh-CN/components/date-picker.mdx +++ b/docs/zh-CN/components/date-picker.mdx @@ -55,8 +55,9 @@ import DemoCalendar from '../../demo/date-picker/section-calendar.jsx' | minDate | 最小日期 | Date | null | null | | maxDate | 最大日期 | Date | null | null | | disabled | 是否禁用输入框 | boolean | true \| false | false | +| bordered | 是否有边框 | boolean | true \| false | true | | inputReadOnly | 设置输入框为只读 | boolean | true \| false | false | -| disabledDate | 不可选择的日期 | (currentDate: Date) => boolean | true \| false | false | +| disabledDate | 不可选择的日期(返回true为不可选) | (currentDate: Date) => boolean | true \| false | false | | clearable | 是否可以清空 | boolean | true \| false | true | | showTime | 是否在日期选择器中显示时间选择器,在使用时请注意 format 的设置 | boolean | true \| false | false | | format | 展示的日期格式,配置参考 [moment.js](http://momentjs.cn/docs/#/displaying/format/) | string | - | - | diff --git a/docs/zh-CN/components/input.mdx b/docs/zh-CN/components/input.mdx index e5d83590f..91d0b33da 100755 --- a/docs/zh-CN/components/input.mdx +++ b/docs/zh-CN/components/input.mdx @@ -51,6 +51,7 @@ import DemoTextarea from '../../demo/input/section-textarea.jsx' | value | 设置输入框的值 | string | - | - | | defaultValue | 设置输入框的默认值 | string | - | - | | type | 设置输入框类型 | string | 'text' \| 'textarea' \| 'id' \| 'tel' \| 'card' \| 'amount' \| 'email' | 'text' | +| bordered | 是否有边框 | boolean | true \| false | true | | prepend | 输入框前置内容 | string \| ReactNode | - | - | | append | 输入框后置内容 | string \| ReactNode | - | - | | disabled | 是否禁用 | boolean | true \| false | false | diff --git a/docs/zh-CN/components/preview.mdx b/docs/zh-CN/components/preview.mdx new file mode 100644 index 000000000..bafb677f9 --- /dev/null +++ b/docs/zh-CN/components/preview.mdx @@ -0,0 +1,22 @@ +# Preview 图片预览 + +预览当前图片 + + +## 基础 + +import DemoBase from '../../demo/preview/section-base.jsx' + + + +## Props + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| --------- | -------------------------------------------------- | ------------------- | -------------------------------------- | ------- | +| visible | 是否显示预览窗体 | boolean | - | false | +| showBar | 是否显示预览窗体下方工具条 | boolean | true \| false | true | +| showArrow | 是否显示预览窗体两侧切换按钮 | boolean | true \| false | false | +| showCount | 是否显示预览窗体上方图片数量 | boolean | true \| false | false | +| images | 图片列表 | String[] \| Object[] | - | [] | +| simpleData| 是否为简单数据: | boolean | true \| false | false | +| activeIndex| 激活图片索引,从0开始 | number | true \| false | 0 | \ No newline at end of file diff --git a/docs/zh-CN/components/select-tree.mdx b/docs/zh-CN/components/select-tree.mdx index 39634e726..e77accbfe 100644 --- a/docs/zh-CN/components/select-tree.mdx +++ b/docs/zh-CN/components/select-tree.mdx @@ -47,6 +47,7 @@ import DemoBread from '../../demo/select-tree/section-bread' | showCheckedMode | 数据回显模式 | string | ALL: 所有被选中节点,不区分父子节点
PARENT: 当所有子节点被选中时将只保留父节点
CHILD:仅显示子节点 | ALL | | mode | 数据展示形式 | string | normal \| breadcrumb | normal | | type | 数据选择类型 | string | single \| multiple | single | +| bordered | 是否有边框 | boolean | true \| false | true | | defaultExpandAll | 是否默认展开所有树节点 | boolean | true \| false | false | | defaultExpandIds | 默认高亮的节点(非受控) | string[] | - | - | | defaultValue | 默认选中项 (非受控) | DataItem[] \| string[] \| string | - | - | diff --git a/docs/zh-CN/components/select.mdx b/docs/zh-CN/components/select.mdx index 25f6937b9..e7cfd007e 100755 --- a/docs/zh-CN/components/select.mdx +++ b/docs/zh-CN/components/select.mdx @@ -81,9 +81,12 @@ import DemoSelectsearchbypinyin from '../../demo/select/section-searchbypinyin' | placeholder | 输入框占位 | string | - | 请选择 | | emptyContent | 没有选项时的提示 | string \| ReactNode | - | 无内容 | | style | 自定义样式 | object | - | | +| bordered | 是否有边框 | boolean | true \| false | true | | optionWidth | 自定义下拉选项宽度 | number | - | | | render | 自定义下拉菜单渲染函数,回调值为选项数据和是否被选中 | (item: DataItem, selected: boolean) => ReactNode | - | 无内容 | | overlayClassName | 下拉根元素的类名称 (3.0 新增) | string | - | - | +| onOverlayScroll | 下拉列表滚动时的回调 | function | - | - | +| onSearch | 下拉弹层中文本框值变化时回调 | function(value: string) | - | - | | setOverlayContainer | 如遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位 (3.0 新增) | function(triggerNode) | - | () => document.body | > 注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 setOverlayContainer={triggerNode => triggerNode.parentElement} 将下拉弹层渲染节点固定在触发器的父元素中。 diff --git a/docs/zh-CN/docs/changelog.mdx b/docs/zh-CN/docs/changelog.mdx index d30a8d3b6..ebe50de0b 100644 --- a/docs/zh-CN/docs/changelog.mdx +++ b/docs/zh-CN/docs/changelog.mdx @@ -1,6 +1,25 @@ # 更新日志 +## 3.4.0 + +- 优化组件弹出层自动计算合适的左右位置 [#1494](https://github.com/XiaoMi/hiui/issues/1494) +- 新增 `Select SelectTree Cascader DatePicker Input` 等组件无边框形态 [#1553](https://github.com/XiaoMi/hiui/issues/1553) +- 新增 `Preview` 预览组件 [#1546](https://github.com/XiaoMi/hiui/issues/1546) +- 新增 `Select` 组件 onSearch、onOverlayScroll 方法 [#1522](https://github.com/XiaoMi/hiui/issues/1522) +- 修复 `SelectTree` 搜索输入框在输入值时失焦问题 [#1491](https://github.com/XiaoMi/hiui/issues/1491) +- 修复 `SelectTree` 单选形态下受控问题 [#1519](https://github.com/XiaoMi/hiui/issues/1519) +- 修复 `Select` 组件分组形态 filterOption 函数无法使用问题 [#1497](https://github.com/XiaoMi/hiui/issues/1497) +- 修复 `Select` 组件分组形态全选以及受控问题 [#1501](https://github.com/XiaoMi/hiui/issues/1501) +- 修复 `Select` 异步数据请求返回结果顺序异常 [#1543](https://github.com/XiaoMi/hiui/issues/1543) +- 修复 `Tabs` 组件垂直方向样式显示异常问题 [#1493](https://github.com/XiaoMi/hiui/issues/1493) +- 修复 `Form` DatePicker、SelectTree 在 Form.Item 中点击清空Icon 无效问题 [#1524](https://github.com/XiaoMi/hiui/issues/1524) +- 修复 `DatePicker` minDate、maxDate、disabledDate 在非 date 类型下不生效问题 [#1547](https://github.com/XiaoMi/hiui/issues/1547) +- 优化 `Checkbox` 样式相关内容 [#1482](https://github.com/XiaoMi/hiui/issues/1482) +- 优化 `SelectTree` 异步受控数据返显问题 [#1510](https://github.com/XiaoMi/hiui/issues/1510) +- 优化 `Select SelectTree` 计数根据窗口自动调整 [#1527](https://github.com/XiaoMi/hiui/issues/1527) +- 优化 `Drawer` 组件支持className属性 [#1536](https://github.com/XiaoMi/hiui/issues/1536) ## 3.3.0 + - 新增 `Card` 模式模式下 loading 加载中状态 [#1454](https://github.com/XiaoMi/hiui/issues/1454) - 新增 `Table` loading 加载中状态 [#1466](https://github.com/XiaoMi/hiui/issues/1466) - 新增 `Table` 列冻结结合树形使用 [#1424](https://github.com/XiaoMi/hiui/issues/1424) diff --git a/docs/zh-CN/docs/hi-basetable.mdx b/docs/zh-CN/docs/hi-basetable.mdx new file mode 100644 index 000000000..74f2b7d14 --- /dev/null +++ b/docs/zh-CN/docs/hi-basetable.mdx @@ -0,0 +1,76 @@ +# HiBaseTable + +为满足开发对 table 的多样化需求,特基于[BaseTable](https://autodesk.github.io/react-base-table/docs/get-started)进行封装,本次推荐仅仅做了部分功能封装;更多功能请参考[BaseTable文档](https://autodesk.github.io/react-base-table/docs/get-started)&&[更多示例](https://autodesk.github.io/react-base-table/examples/default) + +## 以下情况下建议使用HiBaseTable + + - 数据量较多时候 + - 显示灵活度较高时 + +## 使用方法 + + +1. 安装基础依赖 react-base-table + +```shell +# npm +npm install react-base-table --save +# yarn +yarn add react-base-table + +``` +## 基础用法 + +## 基础用法 + +import DemoBase from '../../demo/base-table/section-base.jsx' + + + +## 其他用法 + +import DemoTooltip from '../../demo/base-table/section-tooltip.jsx' + + + +## Props + +| 属性名 | 描述 | 类型 | 可选值 | 默认值 | +| ------------------ | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------- | ---------- | +| data | 表格数据 | object[] | - | - | +| columns | 表格列配置信息 | ColumnItem[] | - | - | +| sticky | 是否支持表头吸顶 | boolean | true \| false | false | +| stickyTop | 表头吸顶距离视口顶部距离 | number | - | 0 | +| bordered | 是否显示边框(表头分组模式下,表格自带边框) | boolean | true \| false | false | +| autoResize | 是否自适应(设置为**false**后, 需要手动定义**column**的宽度) | boolean | true \| false | true | +| fixedToColumn | 表格列冻结设置,为 string 时仅支持从左侧冻结至某一列 | string \| FixedOption | columns 中对应的 dataKey | null | + +[更多Table设置](https://autodesk.github.io/react-base-table/api/basetable) + +### ColumnItem + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | ---------------------- | -------------------------------------------------------------------------------- | ----------------- | ------ | +| title | 列标题 | string | - | - | +| dataKey | 列对应数据项的唯一标识 | string \| number | - | - | +| align | 列对齐方式 | string | 'left' \|'center' \|'right' | 'left' | +| width | 该列宽度 | number | - | - | + +[更多ColumnItem设置](https://autodesk.github.io/react-base-table/api/column) + + +### FixedOption + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ----- | -------------------- | ------ | ------ | ------ | +| left | 表格从左侧冻结至某列 | string | columns 中对应的 dataKey | - | +| right | 表格从右侧冻结至某列 | string | columns 中对应的 dataKey | - | + +### + + + +## 其他 + +- [更多示例](https://autodesk.github.io/react-base-table/examples/default) +- [SortOrder](https://github.com/Autodesk/react-base-table/blob/master/src/SortOrder.js) \ No newline at end of file diff --git a/package.json b/package.json index 9238fc566..083fe4171 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hi-ui/hiui", - "version": "3.3.0", + "version": "3.4.0", "description": "HIUI for React", "scripts": { "test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'", @@ -136,6 +136,7 @@ "prismjs": "^1.16.0", "raw-loader": "^0.5.1", "react": "^16.9.0", + "react-base-table": "^1.12.0", "react-codemirror2": "^7.2.1", "react-custom-scrollbars": "^4.2.1", "react-dnd-test-backend": "^7.7.0", diff --git a/site/locales/en-US.js b/site/locales/en-US.js index 232715096..5d7768fd5 100644 --- a/site/locales/en-US.js +++ b/site/locales/en-US.js @@ -49,7 +49,8 @@ module.exports = { timeline: 'Timeline', transfer: 'Transfer', switch: 'Switch', - rate: 'Rate' + rate: 'Rate', + preview: 'preview' }, designs: { 'design-patterns': '设计模式', diff --git a/site/locales/zh-CN.js b/site/locales/zh-CN.js index ea43e5cda..a0f85a412 100755 --- a/site/locales/zh-CN.js +++ b/site/locales/zh-CN.js @@ -7,6 +7,7 @@ module.exports = { palette: '配色主题', changelog: '更新日志', 'hi-request': 'HiRequest 请求工具', + 'hi-basetable': 'BaseTable 表格', 'group-basic': '通用', 'community-recommended': '社区推荐', 'code-editor': 'CodeEditor 代码编辑器', @@ -131,7 +132,8 @@ module.exports = { list: 'List 列表', filter: 'Filter 筛选', charts: 'Charts 图表', - 'rich-text-editor': 'RichTextEditor 富文本编辑' + 'rich-text-editor': 'RichTextEditor 富文本编辑', + preview: 'preview 预览' }, designs: { summarize: '概述', diff --git a/site/pages/components/index.js b/site/pages/components/index.js index 920dd9a95..7eb557fa9 100755 --- a/site/pages/components/index.js +++ b/site/pages/components/index.js @@ -58,6 +58,7 @@ export default { 'group-tips': { modal: components.modal, drawer: components.drawer, + preview: components.preview, notification: components.notification, message: components.message, alert: components.alert, diff --git a/site/pages/docs/index.js b/site/pages/docs/index.js index 8a4378810..981c0696f 100755 --- a/site/pages/docs/index.js +++ b/site/pages/docs/index.js @@ -7,7 +7,8 @@ files.keys().forEach((key) => { export default { components: { 'community-recommended': { - 'code-editor': docs['code-editor'] + 'code-editor': docs['code-editor'], + 'hi-basetable': docs['hi-basetable'] } }, documents: {