From 899757a05485ea8c7fa7ca8fc980d81f03a6bd66 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 30 Aug 2021 10:42:07 +0800 Subject: [PATCH] refactor: timeline --- changelog.md | 5 + components/style/themes/default.less | 1 + components/timeline/Timeline.tsx | 134 +++++++++-------- components/timeline/TimelineItem.tsx | 74 +++++----- components/timeline/index.tsx | 6 +- components/timeline/style/index.less | 27 +++- .../timeline/style/{index.ts => index.tsx} | 0 components/timeline/style/rtl.less | 135 ++++++++++++++++++ components/vc-pagination/locale/by_BY.ts | 26 ++-- examples/App.vue | 2 +- v2-doc | 2 +- 11 files changed, 281 insertions(+), 131 deletions(-) create mode 100644 changelog.md rename components/timeline/style/{index.ts => index.tsx} (100%) create mode 100644 components/timeline/style/rtl.less diff --git a/changelog.md b/changelog.md new file mode 100644 index 000000000..b670df9b5 --- /dev/null +++ b/changelog.md @@ -0,0 +1,5 @@ +timeline: 新增 label + +tree、tree-slelct: 新增虚拟滚动、title 逻辑变动 + +date 相关组件: dayjs, UI 变动 diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 627845fe7..026069a46 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -868,6 +868,7 @@ @timeline-dot-border-width: 2px; @timeline-dot-color: @primary-color; @timeline-dot-bg: @component-background; +@timeline-item-padding-bottom: 20px; // Typography // --- diff --git a/components/timeline/Timeline.tsx b/components/timeline/Timeline.tsx index a331d9bcf..e46595f18 100644 --- a/components/timeline/Timeline.tsx +++ b/components/timeline/Timeline.tsx @@ -1,98 +1,92 @@ import type { ExtractPropTypes } from 'vue'; -import { inject, cloneVNode, defineComponent } from 'vue'; +import { cloneVNode, defineComponent } from 'vue'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; -import { getOptionProps, getPropsData, filterEmpty, getComponent } from '../_util/props-util'; +import { filterEmpty } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import type { TimelineItemProps } from './TimelineItem'; import TimelineItem from './TimelineItem'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; -import { defaultConfigProvider } from '../config-provider'; import { tuple } from '../_util/type'; +import useConfigInject from '../_util/hooks/useConfigInject'; -export const timelineProps = { +export const timelineProps = () => ({ prefixCls: PropTypes.string, /** 指定最后一个幽灵节点是否存在或内容 */ pending: PropTypes.any, - pendingDot: PropTypes.string, + pendingDot: PropTypes.any, reverse: PropTypes.looseBool, mode: PropTypes.oneOf(tuple('left', 'alternate', 'right', '')), -}; +}); -export type TimelineProps = Partial>; +export type TimelineProps = Partial>>; export default defineComponent({ name: 'ATimeline', - props: initDefaultProps(timelineProps, { + props: initDefaultProps(timelineProps(), { reverse: false, mode: '', }), - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - render() { - const { prefixCls: customizePrefixCls, reverse, mode } = getOptionProps(this); - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('timeline', customizePrefixCls); - - const pendingDot = getComponent(this, 'pendingDot'); - const pending = getComponent(this, 'pending'); - const pendingNode = typeof pending === 'boolean' ? null : pending; - const classString = classNames(prefixCls, { - [`${prefixCls}-pending`]: !!pending, - [`${prefixCls}-reverse`]: !!reverse, - [`${prefixCls}-${mode}`]: !!mode, - }); - const children = filterEmpty(this.$slots.default?.()); - // // Remove falsy items - // const falsylessItems = filterEmpty(this.$slots.default) - // const items = falsylessItems.map((item, idx) => { - // return cloneElement(item, { - // props: { - // last: falsylessItems.length - 1 === idx, - // }, - // }) - // }) - const pendingItem = pending ? ( - }> - {pendingNode} - - ) : null; - - const timeLineItems = reverse - ? [pendingItem, ...children.reverse()] - : [...children, pendingItem]; - + slots: ['pending', 'pendingDot'], + setup(props, { slots }) { + const { prefixCls, direction } = useConfigInject('timeline', props); const getPositionCls = (ele, idx: number) => { - const eleProps = getPropsData(ele) as TimelineItemProps; - if (mode === 'alternate') { - if (eleProps.position === 'right') return `${prefixCls}-item-right`; - if (eleProps.position === 'left') return `${prefixCls}-item-left`; - return idx % 2 === 0 ? `${prefixCls}-item-left` : `${prefixCls}-item-right`; + const eleProps = ele.props || {}; + if (props.mode === 'alternate') { + if (eleProps.position === 'right') return `${prefixCls.value}-item-right`; + if (eleProps.position === 'left') return `${prefixCls.value}-item-left`; + return idx % 2 === 0 ? `${prefixCls.value}-item-left` : `${prefixCls.value}-item-right`; } - if (mode === 'left') return `${prefixCls}-item-left`; - if (mode === 'right') return `${prefixCls}-item-right`; - if (eleProps.position === 'right') return `${prefixCls}-item-right`; + if (props.mode === 'left') return `${prefixCls.value}-item-left`; + if (props.mode === 'right') return `${prefixCls.value}-item-right`; + if (eleProps.position === 'right') return `${prefixCls.value}-item-right`; return ''; }; - // Remove falsy items - const truthyItems = timeLineItems.filter(item => !!item); - const itemsCount = truthyItems.length; - const lastCls = `${prefixCls}-item-last`; - const items = truthyItems.map((ele, idx) => { - const pendingClass = idx === itemsCount - 2 ? lastCls : ''; - const readyClass = idx === itemsCount - 1 ? lastCls : ''; - return cloneVNode(ele, { - class: classNames([ - !reverse && !!pending ? pendingClass : readyClass, - getPositionCls(ele, idx), - ]), - }); - }); + return () => { + const { + pending = slots.pending?.(), + pendingDot = slots.pendingDot?.(), + reverse, + mode, + } = props; + const pendingNode = typeof pending === 'boolean' ? null : pending; + const children = filterEmpty(slots.default?.()); - return ; + const pendingItem = pending ? ( + }> + {pendingNode} + + ) : null; + + if (pendingItem) { + children.push(pendingItem); + } + + const timeLineItems = reverse ? children.reverse() : children; + + const itemsCount = timeLineItems.length; + const lastCls = `${prefixCls.value}-item-last`; + const items = timeLineItems.map((ele, idx) => { + const pendingClass = idx === itemsCount - 2 ? lastCls : ''; + const readyClass = idx === itemsCount - 1 ? lastCls : ''; + return cloneVNode(ele, { + class: classNames([ + !reverse && !!pending ? pendingClass : readyClass, + getPositionCls(ele, idx), + ]), + }); + }); + const hasLabelItem = timeLineItems.some( + item => !!(item.props?.label || item.children?.label), + ); + const classString = classNames(prefixCls.value, { + [`${prefixCls.value}-pending`]: !!pending, + [`${prefixCls.value}-reverse`]: !!reverse, + [`${prefixCls.value}-${mode}`]: !!mode && !hasLabelItem, + [`${prefixCls.value}-label`]: hasLabelItem, + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + }); + return ; + }; }, }); diff --git a/components/timeline/TimelineItem.tsx b/components/timeline/TimelineItem.tsx index 99a0d162f..81e316f4e 100644 --- a/components/timeline/TimelineItem.tsx +++ b/components/timeline/TimelineItem.tsx @@ -1,60 +1,56 @@ import type { ExtractPropTypes } from 'vue'; -import { defineComponent, inject } from 'vue'; +import { defineComponent } from 'vue'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; -import { getOptionProps, getComponent } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { defaultConfigProvider } from '../config-provider'; import { tuple } from '../_util/type'; +import useConfigInject from '../_util/hooks/useConfigInject'; -export const timelineItemProps = { +export const timelineItemProps = () => ({ prefixCls: PropTypes.string, color: PropTypes.string, dot: PropTypes.any, pending: PropTypes.looseBool, position: PropTypes.oneOf(tuple('left', 'right', '')).def(''), -}; + label: PropTypes.any, +}); -export type TimelineItemProps = Partial>; +export type TimelineItemProps = Partial>>; export default defineComponent({ name: 'ATimelineItem', - props: initDefaultProps(timelineItemProps, { + props: initDefaultProps(timelineItemProps(), { color: 'blue', pending: false, }), - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), + slots: ['dot', 'label'], + setup(props, { slots }) { + const { prefixCls } = useConfigInject('timeline', props); + return () => { + const { color = '', pending, label = slots.label?.(), dot = slots.dot?.() } = props; + const itemClassName = classNames({ + [`${prefixCls.value}-item`]: true, + [`${prefixCls.value}-item-pending`]: pending, + }); + + const dotClassName = classNames({ + [`${prefixCls.value}-item-head`]: true, + [`${prefixCls.value}-item-head-custom`]: dot, + [`${prefixCls.value}-item-head-${color}`]: true, + }); + return ( +
  • + {label &&
    {label}
    } +
    +
    + {dot} +
    +
    {slots.default?.()}
    +
  • + ); }; }, - render() { - const { prefixCls: customizePrefixCls, color = '', pending } = getOptionProps(this); - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('timeline', customizePrefixCls); - - const dot = getComponent(this, 'dot'); - const itemClassName = classNames({ - [`${prefixCls}-item`]: true, - [`${prefixCls}-item-pending`]: pending, - }); - - const dotClassName = classNames({ - [`${prefixCls}-item-head`]: true, - [`${prefixCls}-item-head-custom`]: dot, - [`${prefixCls}-item-head-${color}`]: true, - }); - return ( -
  • -
    -
    - {dot} -
    -
    {this.$slots.default?.()}
    -
  • - ); - }, }); diff --git a/components/timeline/index.tsx b/components/timeline/index.tsx index b421bbffe..663f47c56 100644 --- a/components/timeline/index.tsx +++ b/components/timeline/index.tsx @@ -1,6 +1,6 @@ import type { App, Plugin } from 'vue'; -import Timeline from './Timeline'; -import TimelineItem from './TimelineItem'; +import Timeline, { timelineProps } from './Timeline'; +import TimelineItem, { timelineItemProps } from './TimelineItem'; export type { TimelineProps } from './Timeline'; export type { TimelineItemProps } from './TimelineItem'; @@ -13,7 +13,7 @@ Timeline.install = function (app: App) { app.component(TimelineItem.name, TimelineItem); return app; }; -export { TimelineItem }; +export { TimelineItem, timelineProps, timelineItemProps }; export default Timeline as typeof Timeline & Plugin & { readonly Item: typeof TimelineItem; diff --git a/components/timeline/style/index.less b/components/timeline/style/index.less index f84ba5c51..506e6bace 100644 --- a/components/timeline/style/index.less +++ b/components/timeline/style/index.less @@ -13,7 +13,7 @@ &-item { position: relative; margin: 0; - padding: 0 0 20px; + padding-bottom: @timeline-item-padding-bottom; font-size: @font-size-base; list-style: none; @@ -80,8 +80,8 @@ &-content { position: relative; - top: -((@font-size-base * @line-height-base) - @font-size-base) + 1px; - margin: 0 0 0 18px; + top: -(@font-size-base * @line-height-base - @font-size-base) + 1px; + margin: 0 0 0 @margin-lg + 2px; word-break: break-word; } @@ -96,7 +96,8 @@ } &.@{timeline-prefix-cls}-alternate, - &.@{timeline-prefix-cls}-right { + &.@{timeline-prefix-cls}-right, + &.@{timeline-prefix-cls}-label { .@{timeline-prefix-cls}-item { &-tail, &-head, @@ -106,6 +107,7 @@ &-head { margin-left: -4px; + &-custom { margin-left: 1px; } @@ -164,4 +166,21 @@ min-height: 48px; } } + &.@{timeline-prefix-cls}-label { + .@{timeline-prefix-cls}-item-label { + position: absolute; + top: -(@font-size-base * @line-height-base - @font-size-base) + 1px; + width: calc(50% - 12px); + text-align: right; + } + .@{timeline-prefix-cls}-item-right { + .@{timeline-prefix-cls}-item-label { + left: calc(50% + 14px); + width: calc(50% - 14px); + text-align: left; + } + } + } } + +@import './rtl'; diff --git a/components/timeline/style/index.ts b/components/timeline/style/index.tsx similarity index 100% rename from components/timeline/style/index.ts rename to components/timeline/style/index.tsx diff --git a/components/timeline/style/rtl.less b/components/timeline/style/rtl.less new file mode 100644 index 000000000..011f5aa3b --- /dev/null +++ b/components/timeline/style/rtl.less @@ -0,0 +1,135 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; + +@timeline-prefix-cls: ~'@{ant-prefix}-timeline'; + +.@{timeline-prefix-cls} { + &-rtl { + direction: rtl; + } + + &-item { + &-tail { + .@{timeline-prefix-cls}-rtl & { + right: 4px; + left: auto; + border-right: @timeline-width solid @timeline-color; + border-left: none; + } + } + + &-head-custom { + .@{timeline-prefix-cls}-rtl & { + right: 5px; + left: auto; + transform: translate(50%, -50%); + } + } + + &-content { + .@{timeline-prefix-cls}-rtl & { + margin: 0 18px 0 0; + } + } + } + + &.@{timeline-prefix-cls}-alternate, + &.@{timeline-prefix-cls}-right, + &.@{timeline-prefix-cls}-label { + .@{timeline-prefix-cls}-item { + &-tail, + &-head, + &-head-custom { + .@{timeline-prefix-cls}-rtl& { + right: 50%; + left: auto; + } + } + + &-head { + .@{timeline-prefix-cls}-rtl& { + margin-right: -4px; + margin-left: 0; + } + + &-custom { + .@{timeline-prefix-cls}-rtl& { + margin-right: 1px; + margin-left: 0; + } + } + } + + &-left { + .@{timeline-prefix-cls}-item-content { + .@{timeline-prefix-cls}-rtl& { + right: calc(50% - 4px); + left: auto; + text-align: right; + } + } + } + + &-right { + .@{timeline-prefix-cls}-item-content { + .@{timeline-prefix-cls}-rtl& { + text-align: left; + } + } + } + } + } + + &.@{timeline-prefix-cls}-right { + .@{timeline-prefix-cls}-item-right { + .@{timeline-prefix-cls}-item-tail, + .@{timeline-prefix-cls}-item-head, + .@{timeline-prefix-cls}-item-head-custom { + .@{timeline-prefix-cls}-rtl& { + right: 0; + left: auto; + } + } + + .@{timeline-prefix-cls}-item-content { + .@{timeline-prefix-cls}-rtl& { + width: 100%; + margin-right: 18px; + text-align: right; + } + } + } + } + + &&-pending &-item-last &-item-tail { + .@{timeline-prefix-cls}-rtl& { + border-right: 2px dotted @timeline-color; + border-left: none; + } + } + + &&-reverse &-item-pending { + .@{timeline-prefix-cls}-item-tail { + .@{timeline-prefix-cls}-rtl& { + border-right: 2px dotted @timeline-color; + border-left: none; + } + } + } + + &.@{timeline-prefix-cls}-label { + .@{timeline-prefix-cls}-item-label { + .@{timeline-prefix-cls}-rtl& { + text-align: left; + } + } + .@{timeline-prefix-cls}-item-right { + .@{timeline-prefix-cls}-item-label { + .@{timeline-prefix-cls}-rtl& { + right: calc(50% + 14px); + text-align: right; + } + } + } + } +} diff --git a/components/vc-pagination/locale/by_BY.ts b/components/vc-pagination/locale/by_BY.ts index 36b1886cc..773ddbb99 100644 --- a/components/vc-pagination/locale/by_BY.ts +++ b/components/vc-pagination/locale/by_BY.ts @@ -1,14 +1,14 @@ export default { - // Options.jsx - items_per_page: '/старонка', - jump_to: 'Перайсці', - jump_to_confirm: 'Пацвердзіць', - page: '', - // Pagination.jsx - prev_page: 'Назад', - next_page: 'Наперад', - prev_5: 'Папярэднія 5', - next_5: 'Наступныя 5', - prev_3: 'Папярэднія 3', - next_3: 'Наступныя 3', - }; \ No newline at end of file + // Options.jsx + items_per_page: '/старонка', + jump_to: 'Перайсці', + jump_to_confirm: 'Пацвердзіць', + page: '', + // Pagination.jsx + prev_page: 'Назад', + next_page: 'Наперад', + prev_5: 'Папярэднія 5', + next_5: 'Наступныя 5', + prev_3: 'Папярэднія 3', + next_3: 'Наступныя 3', +}; diff --git a/examples/App.vue b/examples/App.vue index 8b8aefbed..d7858eb35 100644 --- a/examples/App.vue +++ b/examples/App.vue @@ -5,7 +5,7 @@