From 3fd2af60f6b3e345d7c5b0c9f3474a8af93a18d0 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 10 Jul 2021 16:17:48 +0800 Subject: [PATCH 1/2] refactor(progress): use composition API --- components/progress/Circle.tsx | 112 +++++++++ components/progress/Line.tsx | 131 ++++++++++ components/progress/Steps.tsx | 56 +++++ .../__snapshots__/index.test.js.snap | 210 +++++++++++----- components/progress/__tests__/index.test.js | 225 +++++++++++++++++- components/progress/circle.tsx | 86 ------- components/progress/index.ts | 2 +- components/progress/line.tsx | 93 -------- components/progress/progress.tsx | 142 ++++++----- components/progress/props.ts | 46 +++- components/progress/style/index.less | 27 ++- components/progress/style/rtl.less | 37 +++ components/progress/utils.ts | 25 +- components/style/themes/default.less | 7 +- 14 files changed, 874 insertions(+), 325 deletions(-) create mode 100644 components/progress/Circle.tsx create mode 100644 components/progress/Line.tsx create mode 100644 components/progress/Steps.tsx delete mode 100644 components/progress/circle.tsx delete mode 100644 components/progress/line.tsx create mode 100644 components/progress/style/rtl.less diff --git a/components/progress/Circle.tsx b/components/progress/Circle.tsx new file mode 100644 index 0000000000..8d8ac2eb4d --- /dev/null +++ b/components/progress/Circle.tsx @@ -0,0 +1,112 @@ +import type { CSSProperties, ExtractPropTypes } from 'vue'; +import { computed, defineComponent } from 'vue'; +import { Circle as VCCircle } from '../vc-progress'; +import { getSuccessPercent, validProgress } from './utils'; +import { progressProps } from './props'; +import PropTypes from '../_util/vue-types'; + +const circleProps = { + ...progressProps, + prefixCls: PropTypes.string, + // progressStatus: PropTypes.string, +}; +export type CircleProps = Partial>; + +const statusColorMap = { + normal: '#108ee9', + exception: '#ff5500', + success: '#87d068', +}; + +function getPercentage( + percent: CircleProps['percent'], + success: CircleProps['success'], + successPercent: CircleProps['successPercent'], +) { + const ptg = validProgress(percent); + const realSuccessPercent = getSuccessPercent(success, successPercent); + if (!realSuccessPercent) { + return ptg; + } + return [ + validProgress(realSuccessPercent), + validProgress(ptg - validProgress(realSuccessPercent)), + ]; +} + +function getStrokeColor( + success: CircleProps['success'], + strokeColor: CircleProps['strokeColor'], + successPercent: CircleProps['successPercent'], +) { + const color = strokeColor || null; + const realSuccessPercent = getSuccessPercent(success, successPercent); + if (!realSuccessPercent) { + return color; + } + return [statusColorMap.success, color]; +} + +export default defineComponent({ + props: progressProps, + inheritAttrs: false, + setup(props, { slots }) { + const gapDeg = computed(() => { + // Support gapDeg = 0 when type = 'dashboard' + if (props.gapDegree || props.gapDegree === 0) { + return props.gapDegree; + } + if (props.type === 'dashboard') { + return 75; + } + return undefined; + }); + + const circleStyle = computed(() => { + const circleSize = props.width || 120; + return { + width: typeof circleSize === 'number' ? `${circleSize}px` : circleSize, + height: typeof circleSize === 'number' ? `${circleSize}px` : circleSize, + fontSize: `${circleSize * 0.15 + 6}px`, + }; + }); + + const circleWidth = computed(() => props.strokeWidth || 6); + const gapPos = computed( + () => props.gapPosition || (props.type === 'dashboard' && 'bottom') || 'top', + ); + + // using className to style stroke color + const strokeColor = computed(() => + getStrokeColor(props.success, props.strokeColor, props.successPercent), + ); + const percent = computed(() => + getPercentage(props.percent, props.success, props.successPercent), + ); + const isGradient = computed( + () => Object.prototype.toString.call(strokeColor.value) === '[object Object]', + ); + + const wrapperClassName = computed(() => ({ + [`${props.prefixCls}-inner`]: true, + [`${props.prefixCls}-circle-gradient`]: isGradient.value, + })); + + return () => ( +
+ + {slots.default?.()} +
+ ); + }, +}); diff --git a/components/progress/Line.tsx b/components/progress/Line.tsx new file mode 100644 index 0000000000..ca01f86719 --- /dev/null +++ b/components/progress/Line.tsx @@ -0,0 +1,131 @@ +import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; +import { computed, defineComponent } from 'vue'; +import type { Direction } from '../config-provider'; +import PropTypes from '../_util/vue-types'; +import type { StringGradients, ProgressGradient } from './props'; +import { progressProps } from './props'; +import { getSuccessPercent, validProgress } from './utils'; + +const lineProps = { + ...progressProps, + prefixCls: PropTypes.string, + direction: { + type: String as PropType, + }, +}; + +export type LineProps = Partial>; + +/** + * { + * '0%': '#afc163', + * '75%': '#009900', + * '50%': 'green', ====> '#afc163 0%, #66FF00 25%, #00CC00 50%, #009900 75%, #ffffff 100%' + * '25%': '#66FF00', + * '100%': '#ffffff' + * } + */ +export const sortGradient = (gradients: StringGradients) => { + let tempArr = []; + Object.keys(gradients).forEach(key => { + const formattedKey = parseFloat(key.replace(/%/g, '')); + if (!isNaN(formattedKey)) { + tempArr.push({ + key: formattedKey, + value: gradients[key], + }); + } + }); + tempArr = tempArr.sort((a, b) => a.key - b.key); + return tempArr.map(({ key, value }) => `${value} ${key}%`).join(', '); +}; + +/** + * Then this man came to realize the truth: Besides six pence, there is the moon. Besides bread and + * butter, there is the bug. And... Besides women, there is the code. + * + * @example + * { + * "0%": "#afc163", + * "25%": "#66FF00", + * "50%": "#00CC00", // ====> linear-gradient(to right, #afc163 0%, #66FF00 25%, + * "75%": "#009900", // #00CC00 50%, #009900 75%, #ffffff 100%) + * "100%": "#ffffff" + * } + */ +export const handleGradient = (strokeColor: ProgressGradient, directionConfig: Direction) => { + const { + from = '#1890ff', + to = '#1890ff', + direction = directionConfig === 'rtl' ? 'to left' : 'to right', + ...rest + } = strokeColor; + if (Object.keys(rest).length !== 0) { + const sortedGradients = sortGradient(rest as StringGradients); + return { backgroundImage: `linear-gradient(${direction}, ${sortedGradients})` }; + } + return { backgroundImage: `linear-gradient(${direction}, ${from}, ${to})` }; +}; + +export default defineComponent({ + props: lineProps, + setup(props, { slots }) { + const backgroundProps = computed(() => { + const { strokeColor, direction } = props; + return strokeColor && typeof strokeColor !== 'string' + ? handleGradient(strokeColor, direction) + : { + background: strokeColor, + }; + }); + + const trailStyle = computed(() => + props.trailColor + ? { + backgroundColor: props.trailColor, + } + : undefined, + ); + + const percentStyle = computed(() => { + const { percent, strokeWidth, strokeLinecap, size } = props; + return { + width: `${validProgress(percent)}%`, + height: `${strokeWidth || (size === 'small' ? 6 : 8)}px`, + borderRadius: strokeLinecap === 'square' ? 0 : '', + ...(backgroundProps.value as any), + }; + }); + + const successPercent = computed(() => { + return getSuccessPercent(props.success, props.successPercent); + }); + const successPercentStyle = computed(() => { + const { strokeWidth, size, strokeLinecap, success } = props; + return { + width: `${validProgress(successPercent.value)}%`, + height: `${strokeWidth || (size === 'small' ? 6 : 8)}px`, + borderRadius: strokeLinecap === 'square' ? 0 : '', + backgroundColor: success?.strokeColor, + }; + }); + + const successSegment = computed(() => + successPercent.value !== undefined ? ( +
+ ) : null, + ); + + return () => ( + <> +
+
+
+ {successSegment.value} +
+
+ {slots.default?.()} + + ); + }, +}); diff --git a/components/progress/Steps.tsx b/components/progress/Steps.tsx new file mode 100644 index 0000000000..e50518bff0 --- /dev/null +++ b/components/progress/Steps.tsx @@ -0,0 +1,56 @@ +import { computed, ExtractPropTypes, PropType, VNodeChild } from 'vue'; +import { defineComponent } from 'vue'; +import PropTypes from '../_util/vue-types'; +import type { ProgressSize } from './props'; +import { progressProps } from './props'; + +const stepsProps = { + ...progressProps, + steps: PropTypes.number, + size: { + type: String as PropType, + }, + strokeColor: PropTypes.string, + trailColor: PropTypes.string, +}; + +export type StepsProps = Partial>; + +export default defineComponent({ + props: stepsProps, + setup(props, { slots }) { + const current = computed(() => Math.round(props.steps * ((props.percent || 0) / 100))); + const stepWidth = computed(() => (props.size === 'small' ? 2 : 14)); + + const styledSteps = computed(() => { + const { steps, strokeWidth = 8, strokeColor, trailColor, prefixCls } = props; + + const temp: VNodeChild[] = []; + for (let i = 0; i < steps; i += 1) { + const cls = { + [`${prefixCls}-steps-item`]: true, + [`${prefixCls}-steps-item-active`]: i <= current.value - 1, + }; + temp.push( +
, + ); + } + return temp; + }); + + return () => ( +
+ {styledSteps.value} + {slots.default?.()} +
+ ); + }, +}); diff --git a/components/progress/__tests__/__snapshots__/index.test.js.snap b/components/progress/__tests__/__snapshots__/index.test.js.snap index 0cb227835b..cc19fbf18d 100644 --- a/components/progress/__tests__/__snapshots__/index.test.js.snap +++ b/components/progress/__tests__/__snapshots__/index.test.js.snap @@ -1,7 +1,115 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Progress render dashboard 295 gapDegree 1`] = ` +
+
+ + + + 0%
+
+`; + +exports[`Progress render dashboard 296 gapDegree 1`] = ` +
+
+ + + + 0%
+
+`; + +exports[`Progress render dashboard zero gapDegree 1`] = ` +
+
+ + + + 0%
+
+`; + exports[`Progress render format 1`] = ` -
+
+
+
+
+
+
+
50 10 +
+`; + +exports[`Progress render negative progress 1`] = ` +
+
+
+
+ +
+
0% +
+`; + +exports[`Progress render negative successPercent 1`] = ` +
+
+
+
+
+
+
50% +
+`; + +exports[`Progress render normal progress 1`] = ` +
+
+
+
+ +
+
0% +
+`; + +exports[`Progress render out-of-range progress 1`] = ` +
+
+
+
+ +
+
+
+`; + +exports[`Progress render out-of-range progress with info 1`] = ` +
+
+
+
+ +
+
+
+`; + +exports[`Progress render strokeColor 1`] = ` +
-
-
-
-
- -
-
0% -
+exports[`Progress render strokeColor 2`] = ` +
+
+
+
+ +
+
50%
`; -exports[`Progress render negetive successPercent 1`] = ` -
-
-
-
-
-
-
-
50% -
+exports[`Progress render strokeColor 3`] = ` +
+
+
+
+ +
+
50%
`; -exports[`Progress render negetive successPercent 2`] = ` -
-
-
-
-
-
-
-
50 10 -
+exports[`Progress render successColor progress 1`] = ` +
+
+
+
+
+
+
60%
`; -exports[`Progress render normal progress 1`] = ` -
-
-
-
-
- -
-
0% -
+exports[`Progress render trailColor progress 1`] = ` +
+
+
+
+ +
+
0%
`; -exports[`Progress render out-of-range progress 1`] = ` -
-
-
-
-
- -
-
+exports[`Progress should support steps 1`] = ` +
+
+
+
+
0%
`; -exports[`Progress render out-of-range progress with info 1`] = ` -
-
-
-
-
- -
-
-
+exports[`Progress steps should have default percent 0 1`] = ` +
+
`; diff --git a/components/progress/__tests__/index.test.js b/components/progress/__tests__/index.test.js index 1eadc98aee..218521be35 100644 --- a/components/progress/__tests__/index.test.js +++ b/components/progress/__tests__/index.test.js @@ -1,13 +1,15 @@ import { mount } from '@vue/test-utils'; import { asyncExpect } from '@/tests/utils'; +import { handleGradient, sortGradient } from '../Line'; import Progress from '..'; +import ProgressSteps from '../Steps'; describe('Progress', () => { it('successPercent should decide the progress status when it exists', async () => { const wrapper = mount(Progress, { props: { percent: 100, - successPercent: 50, + success: { percent: 50 }, }, sync: false, }); @@ -15,12 +17,12 @@ describe('Progress', () => { expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(0); }); - wrapper.setProps({ percent: 50, successPercent: 100 }); + wrapper.setProps({ percent: 50, success: { percent: 100 } }); await asyncExpect(() => { expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(1); }); - wrapper.setProps({ percent: 100, successPercent: 0 }); + wrapper.setProps({ percent: 100, success: { percent: 0 } }); await asyncExpect(() => { expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(0); }); @@ -51,7 +53,7 @@ describe('Progress', () => { }); }); - it('render negetive progress', async () => { + it('render negative progress', async () => { const wrapper = mount(Progress, { props: { percent: -20, @@ -63,11 +65,11 @@ describe('Progress', () => { }); }); - it('render negetive successPercent', async () => { + it('render negative successPercent', async () => { const wrapper = mount(Progress, { props: { percent: 50, - successPercent: -20, + success: { percent: -20 }, }, sync: false, }); @@ -76,7 +78,7 @@ describe('Progress', () => { }); }); - it('render negetive successPercent', async () => { + it('render format', async () => { const wrapper = mount(Progress, { props: { percent: 50, @@ -90,7 +92,7 @@ describe('Progress', () => { }); }); - it('render format', async () => { + it('render strokeColor', async () => { const wrapper = mount(Progress, { props: { percent: 50, @@ -102,10 +104,217 @@ describe('Progress', () => { await asyncExpect(() => { expect(wrapper.html()).toMatchSnapshot(); }); + wrapper.setProps({ + strokeColor: { + from: '#108ee9', + to: '#87d068', + }, + type: 'line', + }); + await asyncExpect(() => { + expect(wrapper.html()).toMatchSnapshot(); + }); + wrapper.setProps({ + strokeColor: { + '0%': '#108ee9', + '100%': '#87d068', + }, + }); + await asyncExpect(() => { + expect(wrapper.html()).toMatchSnapshot(); + }); }); it('render normal progress', () => { const wrapper = mount(Progress, { props: { status: 'normal' } }); expect(wrapper.html()).toMatchSnapshot(); }); + + it('render trailColor progress', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('render successColor progress', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('render dashboard zero gapDegree', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('render dashboard 295 gapDegree', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('render dashboard 296 gapDegree', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('get correct line-gradient', () => { + expect(handleGradient({ from: 'test', to: 'test' }).backgroundImage).toBe( + 'linear-gradient(to right, test, test)', + ); + expect(handleGradient({}).backgroundImage).toBe('linear-gradient(to right, #1890ff, #1890ff)'); + expect(handleGradient({ from: 'test', to: 'test', '0%': 'test' }).backgroundImage).toBe( + 'linear-gradient(to right, test 0%)', + ); + }); + + it('sort gradients correctly', () => { + expect(sortGradient({ '10%': 'test10', '30%': 'test30', '20%': 'test20' })).toBe( + 'test10 10%, test20 20%, test30 30%', + ); + expect(sortGradient({ '10%': 'test10', '30%': 'test30', '20%': 'test20', dummy: 'test' })).toBe( + 'test10 10%, test20 20%, test30 30%', + ); + }); + + it('should show success status when percent is 100', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(1); + }); + + // https://github.com/ant-design/ant-design/issues/15950 + it('should show success status when percent is 100 and status is undefined', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(1); + }); + + // https://github.com/ant-design/ant-design/pull/15951#discussion_r273062969 + it('should show success status when status is invalid', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(1); + errorSpy.mockRestore(); + }); + + it('should support steps', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('steps should be changable', async () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.findAll('.ant-progress-steps-item-active').length).toBe(3); + wrapper.setProps({ percent: 40 }); + await asyncExpect(() => { + expect(wrapper.findAll('.ant-progress-steps-item-active').length).toBe(2); + }); + }); + + it('steps should be changable when has strokeColor', async () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.findAll('.ant-progress-steps-item')[0].element.style.backgroundColor).toBe( + 'rgb(24, 144, 255)', + ); + wrapper.setProps({ percent: 40 }); + await asyncExpect(() => { + expect(wrapper.findAll('.ant-progress-steps-item')[2].element.style.backgroundColor).toBe(''); + expect(wrapper.findAll('.ant-progress-steps-item')[1].element.style.backgroundColor).toBe( + 'rgb(24, 144, 255)', + ); + }); + }); + + it('steps should support trailColor', () => { + const wrapper = mount(); + expect(wrapper.findAll('.ant-progress-steps-item')[1].element.style.backgroundColor).toBe( + 'rgb(24, 144, 238)', + ); + }); + + it('should display correct step', async () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.findAll('.ant-progress-steps-item-active').length).toBe(2); + wrapper.setProps({ percent: 33.33 }); + await asyncExpect(() => { + expect(wrapper.findAll('.ant-progress-steps-item-active').length).toBe(3); + }); + wrapper.setProps({ percent: 44.44 }); + await asyncExpect(() => { + expect(wrapper.findAll('.ant-progress-steps-item-active').length).toBe(4); + }); + }); + + it('steps should have default percent 0', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should warning if use `progress` in success', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + mount(); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [ant-design-vue: Progress] `success.progress` is deprecated. Please use `success.percent` instead.', + ); + }); + + it('should warning if use `progress` in success in type Circle', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + mount({ + render() { + return ; + }, + }); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [ant-design-vue: Progress] `success.progress` is deprecated. Please use `success.percent` instead.', + ); + }); }); diff --git a/components/progress/circle.tsx b/components/progress/circle.tsx deleted file mode 100644 index bbf6867e77..0000000000 --- a/components/progress/circle.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type { ExtractPropTypes } from 'vue'; -import { defineComponent } from 'vue'; -import { Circle as VCCircle } from '../vc-progress'; -import PropTypes from '../_util/vue-types'; -import { validProgress } from './utils'; -import { ProgressProps } from './props'; - -const CircleProps = { - ...ProgressProps, - progressStatus: PropTypes.string, -}; - -const statusColorMap: Record = { - normal: '#108ee9', - exception: '#ff5500', - success: '#87d068', -}; - -export type ICircleProps = ExtractPropTypes; - -function getPercentage({ percent, successPercent }: ICircleProps) { - const ptg = validProgress(percent); - if (!successPercent) return ptg; - - const successPtg = validProgress(successPercent); - return [successPercent, validProgress(ptg - successPtg)]; -} - -function getStrokeColor({ progressStatus, successPercent, strokeColor }: ICircleProps) { - const color = strokeColor || statusColorMap[progressStatus]; - if (!successPercent) return color; - return [statusColorMap.success, color]; -} - -const Circle = defineComponent({ - props: CircleProps, - setup(props, { slots }) { - return () => { - const { - prefixCls, - width, - strokeWidth, - trailColor, - strokeLinecap, - gapPosition, - gapDegree, - type, - } = props; - const circleSize = width || 120; - const circleStyle = { - width: typeof circleSize === 'number' ? `${circleSize}px` : circleSize, - height: typeof circleSize === 'number' ? `${circleSize}px` : circleSize, - fontSize: `${circleSize * 0.15 + 6}px`, - }; - const circleWidth = strokeWidth || 6; - const gapPos = gapPosition || (type === 'dashboard' && 'bottom') || 'top'; - const gapDeg = gapDegree || (type === 'dashboard' && 75); - const strokeColor = getStrokeColor(props); - const isGradient = Object.prototype.toString.call(strokeColor) === '[object Object]'; - - const wrapperClassName = { - [`${prefixCls}-inner`]: true, - [`${prefixCls}-circle-gradient`]: isGradient, - }; - - return ( -
- - {slots?.default()} -
- ); - }; - }, -}); - -export default Circle; diff --git a/components/progress/index.ts b/components/progress/index.ts index ef45c8e8e1..2505b42832 100644 --- a/components/progress/index.ts +++ b/components/progress/index.ts @@ -1,6 +1,6 @@ import Progress from './progress'; import { withInstall } from '../_util/type'; -export { ProgressProps } from './props'; +export type { ProgressProps } from './props'; export default withInstall(Progress); diff --git a/components/progress/line.tsx b/components/progress/line.tsx deleted file mode 100644 index 06afa96854..0000000000 --- a/components/progress/line.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { validProgress } from './utils'; - -/** - * { - * '0%': '#afc163', - * '75%': '#009900', - * '50%': 'green', ====> '#afc163 0%, #66FF00 25%, #00CC00 50%, #009900 75%, #ffffff 100%' - * '25%': '#66FF00', - * '100%': '#ffffff' - * } - */ -export const sortGradient = gradients => { - let tempArr = []; - // eslint-disable-next-line no-restricted-syntax - for (const [key, value] of Object.entries(gradients)) { - const formatKey = parseFloat(key.replace(/%/g, '')); - if (isNaN(formatKey)) { - return {}; - } - tempArr.push({ - key: formatKey, - value, - }); - } - tempArr = tempArr.sort((a, b) => a.key - b.key); - return tempArr.map(({ key, value }) => `${value} ${key}%`).join(', '); -}; - -/** - * { - * '0%': '#afc163', - * '25%': '#66FF00', - * '50%': '#00CC00', ====> linear-gradient(to right, #afc163 0%, #66FF00 25%, - * '75%': '#009900', #00CC00 50%, #009900 75%, #ffffff 100%) - * '100%': '#ffffff' - * } - * - * Then this man came to realize the truth: - * Besides six pence, there is the moon. - * Besides bread and butter, there is the bug. - * And... - * Besides women, there is the code. - */ -export const handleGradient = strokeColor => { - const { from = '#1890ff', to = '#1890ff', direction = 'to right', ...rest } = strokeColor; - if (Object.keys(rest).length !== 0) { - const sortedGradients = sortGradient(rest); - return { backgroundImage: `linear-gradient(${direction}, ${sortedGradients})` }; - } - return { backgroundImage: `linear-gradient(${direction}, ${from}, ${to})` }; -}; - -const Line = (_, { attrs, slots }) => { - const { prefixCls, percent, successPercent, strokeWidth, size, strokeColor, strokeLinecap } = - attrs; - let backgroundProps; - if (strokeColor && typeof strokeColor !== 'string') { - backgroundProps = handleGradient(strokeColor); - } else { - backgroundProps = { - background: strokeColor, - }; - } - const percentStyle = { - width: `${validProgress(percent)}%`, - height: `${strokeWidth || (size === 'small' ? 6 : 8)}px`, - background: strokeColor, - borderRadius: strokeLinecap === 'square' ? 0 : '100px', - ...backgroundProps, - }; - const successPercentStyle = { - width: `${validProgress(successPercent)}%`, - height: `${strokeWidth || (size === 'small' ? 6 : 8)}px`, - borderRadius: strokeLinecap === 'square' ? 0 : '', - }; - const successSegment = - successPercent !== undefined ? ( -
- ) : null; - return ( -
-
-
-
- {successSegment} -
-
- {slots?.default()} -
- ); -}; - -export default Line; diff --git a/components/progress/progress.tsx b/components/progress/progress.tsx index 118732db82..c8d2e1c700 100644 --- a/components/progress/progress.tsx +++ b/components/progress/progress.tsx @@ -1,20 +1,22 @@ -import { defineComponent, inject } from 'vue'; -import classNames from '../_util/classNames'; -import { getOptionProps } from '../_util/props-util'; +import type { VNodeChild } from 'vue'; +import { computed, defineComponent } from 'vue'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { defaultConfigProvider } from '../config-provider'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import CheckOutlined from '@ant-design/icons-vue/CheckOutlined'; import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled'; import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; -import Line from './line'; -import Circle from './circle'; -import { validProgress } from './utils'; -import { ProgressProps, ProgressStatuses } from './props'; +import Line from './Line'; +import Circle from './Circle'; +import Steps from './Steps'; +import { getSuccessPercent, validProgress } from './utils'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import devWarning from '../vc-util/devWarning'; +import type { ProgressStatusesType } from './props'; +import { progressProps, progressStatuses } from './props'; export default defineComponent({ name: 'AProgress', - props: initDefaultProps(ProgressProps, { + props: initDefaultProps(progressProps, { type: 'line', percent: 0, showInfo: true, @@ -24,37 +26,49 @@ export default defineComponent({ gapDegree: 0, strokeLinecap: 'round', }), - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - methods: { - getPercentNumber() { - const { successPercent, percent = 0 } = this.$props; + setup(props, { slots }) { + const { prefixCls, direction } = useConfigInject('progress', props); + + const classString = computed(() => { + const { type, showInfo, size } = props; + const pre = prefixCls.value; + return { + [pre]: true, + [`${pre}-${(type === 'dashboard' && 'circle') || type}`]: true, + [`${pre}-show-info`]: showInfo, + [`${pre}-${size}`]: size, + [`${pre}-rtl`]: direction.value === 'rtl', + }; + }); + + const getPercentNumber = () => { + const { percent = 0 } = props; + const successPercent = getSuccessPercent(props.success, props.successPercent); return parseInt( successPercent !== undefined ? successPercent.toString() : percent.toString(), 10, ); - }, + }; - getProgressStatus() { - const { status } = this.$props; - if (ProgressStatuses.indexOf(status) < 0 && this.getPercentNumber() >= 100) { + const getProgressStatus = () => { + const { status } = props; + if (progressStatuses.indexOf(status) < 0 && getPercentNumber() >= 100) { return 'success'; } return status || 'normal'; - }, - renderProcessInfo(prefixCls: string, progressStatus: typeof ProgressStatuses[number]) { - const { showInfo, format, type, percent, successPercent } = this.$props; + }; + + const renderProcessInfo = (prefixCls: string, progressStatus: ProgressStatusesType) => { + const { showInfo, format, type, percent } = props; + const successPercent = getSuccessPercent(props.success, props.successPercent); if (!showInfo) return null; - let text; - const textFormatter = format || this.$slots.format || (percentNumber => `${percentNumber}%`); + let text: VNodeChild; + const textFormatter = format || slots?.format || (percentNumber => `${percentNumber}%`); const isLineType = type === 'line'; if ( format || - this.$slots.format || + slots?.format || (progressStatus !== 'exception' && progressStatus !== 'success') ) { text = textFormatter(validProgress(percent), validProgress(successPercent)); @@ -68,44 +82,50 @@ export default defineComponent({ {text} ); - }, - }, - render() { - const props = getOptionProps(this); - const { prefixCls: customizePrefixCls, size, type, showInfo } = props; - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('progress', customizePrefixCls); - const progressStatus = this.getProgressStatus(); - const progressInfo = this.renderProcessInfo(prefixCls, progressStatus); + }; - let progress; + return () => { + const { type, steps, strokeColor } = props; + const progressStatus = getProgressStatus(); + const progressInfo = renderProcessInfo(prefixCls.value, progressStatus); - // Render progress shape - if (type === 'line') { - const lineProps = { - ...props, - prefixCls, - }; - progress = {progressInfo}; - } else if (type === 'circle' || type === 'dashboard') { - const circleProps = { - ...props, - prefixCls, - progressStatus, - }; - progress = {progressInfo}; - } + devWarning( + props.successPercent == undefined, + 'Progress', + '`successPercent` is deprecated. Please use `success.percent` instead.', + ); - const classString = classNames(prefixCls, { - [`${prefixCls}-${(type === 'dashboard' && 'circle') || type}`]: true, - [`${prefixCls}-status-${progressStatus}`]: true, - [`${prefixCls}-show-info`]: showInfo, - [`${prefixCls}-${size}`]: size, - }); + let progress: VNodeChild; + // Render progress shape + if (type === 'line') { + progress = steps ? ( + + {progressInfo} + + ) : ( + + {progressInfo} + + ); + } else if (type === 'circle' || type === 'dashboard') { + progress = ( + + {progressInfo} + + ); + } + + const classNames = { + ...classString.value, + [`${prefixCls.value}-status-${progressStatus}`]: true, + }; - const progressProps = { - class: classString, + return
{progress}
; }; - return
{progress}
; }, }); diff --git a/components/progress/props.ts b/components/progress/props.ts index a18b1c7c24..7933fdf472 100644 --- a/components/progress/props.ts +++ b/components/progress/props.ts @@ -1,24 +1,48 @@ import PropTypes from '../_util/vue-types'; import { tuple } from '../_util/type'; +import type { PropType, VNodeChild, ExtractPropTypes } from 'vue'; -export const ProgressStatuses = tuple('normal', 'exception', 'active', 'success'); -export const ProgressType = PropTypes.oneOf(tuple('line', 'circle', 'dashboard')); -export const ProgressSize = PropTypes.oneOf(tuple('default', 'small')); +export const progressStatuses = tuple('normal', 'exception', 'active', 'success'); +export type ProgressStatusesType = typeof progressStatuses[number]; +const ProgressType = tuple('line', 'circle', 'dashboard'); +export type ProgressType = typeof ProgressType[number]; +const ProgressSize = tuple('default', 'small'); +export type ProgressSize = typeof ProgressSize[number]; +export type StringGradients = { [percentage: string]: string }; +type FromToGradients = { from: string; to: string }; +export type ProgressGradient = { direction?: string } & (StringGradients | FromToGradients); -export const ProgressProps = { +export interface SuccessProps { + percent?: number; + /** @deprecated Use `percent` instead */ + progress?: number; + strokeColor?: string; +} + +export const progressProps = { prefixCls: PropTypes.string, - type: ProgressType, + type: PropTypes.oneOf(ProgressType), percent: PropTypes.number, - successPercent: PropTypes.number, - format: PropTypes.func, - status: PropTypes.oneOf(ProgressStatuses), + format: { type: Function as PropType<(percent?: number, successPercent?: number) => VNodeChild> }, + status: PropTypes.oneOf(progressStatuses), showInfo: PropTypes.looseBool, strokeWidth: PropTypes.number, - strokeLinecap: PropTypes.oneOf(['butt', 'round', 'square']), - strokeColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + strokeLinecap: PropTypes.oneOf(tuple('butt', 'round', 'square')), + strokeColor: { + type: [String, Object] as PropType, + }, trailColor: PropTypes.string, width: PropTypes.number, + success: { + type: Object as PropType, + default: (): SuccessProps => ({}), + }, gapDegree: PropTypes.number, gapPosition: PropTypes.oneOf(tuple('top', 'bottom', 'left', 'right')), - size: ProgressSize, + size: PropTypes.oneOf(ProgressSize), + steps: PropTypes.number, + /** @deprecated Use `success` instead */ + successPercent: PropTypes.number, }; + +export type ProgressProps = Partial>; diff --git a/components/progress/style/index.less b/components/progress/style/index.less index f2f0bd5e8c..45fc23e7c0 100644 --- a/components/progress/style/index.less +++ b/components/progress/style/index.less @@ -14,6 +14,26 @@ font-size: @font-size-base; } + &-steps { + display: inline-block; + &-outer { + display: flex; + flex-direction: row; + align-items: center; + } + &-item { + flex-shrink: 0; + min-width: 2px; + margin-right: 2px; + background: @progress-steps-item-bg; + transition: all 0.3s; + + &-active { + background: @progress-default-color; + } + } + } + &-small&-line, &-small&-line &-text .@{iconfont-css-prefix} { font-size: @font-size-sm; @@ -73,8 +93,8 @@ display: inline-block; width: 2em; margin-left: 8px; - color: @text-color-secondary; - font-size: 1em; + color: @progress-info-text-color; + font-size: @progress-text-font-size; line-height: 1; white-space: nowrap; text-align: left; @@ -144,6 +164,7 @@ margin: 0; padding: 0; color: @progress-text-color; + font-size: @progress-circle-text-font-size; line-height: 1; white-space: normal; text-align: center; @@ -180,3 +201,5 @@ opacity: 0; } } + +@import './rtl'; diff --git a/components/progress/style/rtl.less b/components/progress/style/rtl.less new file mode 100644 index 0000000000..0756b5f847 --- /dev/null +++ b/components/progress/style/rtl.less @@ -0,0 +1,37 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; + +@progress-prefix-cls: ~'@{ant-prefix}-progress'; + +.@{progress-prefix-cls} { + &-rtl { + direction: rtl; + } + + &-outer { + .@{progress-prefix-cls}-show-info & { + .@{progress-prefix-cls}-rtl& { + margin-right: 0; + margin-left: ~'calc(-2em - 8px)'; + padding-right: 0; + padding-left: ~'calc(2em + 8px)'; + } + } + } + + &-success-bg { + .@{progress-prefix-cls}-rtl & { + right: 0; + left: auto; + } + } + + &-line &-text, + &-steps &-text { + .@{progress-prefix-cls}-rtl& { + margin-right: 8px; + margin-left: 0; + text-align: right; + } + } +} diff --git a/components/progress/utils.ts b/components/progress/utils.ts index 14a930d882..df8b332265 100644 --- a/components/progress/utils.ts +++ b/components/progress/utils.ts @@ -1,4 +1,7 @@ -export function validProgress(progress?: number) { +import devWarning from '../vc-util/devWarning'; +import type { ProgressProps } from './props'; + +export function validProgress(progress: number | undefined) { if (!progress || progress < 0) { return 0; } @@ -7,3 +10,23 @@ export function validProgress(progress?: number) { } return progress; } + +export function getSuccessPercent( + success?: ProgressProps['success'], + successPercent?: ProgressProps['successPercent'], +) { + let percent = successPercent; + /** @deprecated Use `percent` instead */ + if (success && 'progress' in success) { + devWarning( + false, + 'Progress', + '`success.progress` is deprecated. Please use `success.percent` instead.', + ); + percent = success.progress; + } + if (success && 'percent' in success) { + percent = success.percent; + } + return percent; +} diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 1b9b0c3013..dd886957e1 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -496,9 +496,12 @@ // -- @progress-default-color: @processing-color; @progress-remaining-color: @background-color-base; -@progress-text-color: @text-color; +@progress-info-text-color: @progress-text-color; @progress-radius: 100px; - +@progress-steps-item-bg: #f3f3f3; +@progress-text-font-size: 1em; +@progress-text-color: @text-color; // This is for circle text color, should be renamed better +@progress-circle-text-font-size: 1em; // Menu // --- @menu-inline-toplevel-item-height: 40px; From 4df31d3ff909514a0566d41fc54d4e63bdcea9a4 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 10 Jul 2021 16:54:11 +0800 Subject: [PATCH 2/2] refactor(vc-progress): update --- components/vc-progress/{index.js => index.ts} | 2 +- components/vc-progress/src/Circle.js | 191 ------------------ components/vc-progress/src/Circle.tsx | 174 ++++++++++++++++ components/vc-progress/src/Line.js | 84 -------- components/vc-progress/src/Line.tsx | 96 +++++++++ components/vc-progress/src/common.ts | 43 ++++ components/vc-progress/src/enhancer.js | 30 --- .../vc-progress/src/{index.js => index.ts} | 3 +- components/vc-progress/src/types.js | 30 --- components/vc-progress/src/types.ts | 31 +++ 10 files changed, 347 insertions(+), 337 deletions(-) rename components/vc-progress/{index.js => index.ts} (62%) delete mode 100644 components/vc-progress/src/Circle.js create mode 100644 components/vc-progress/src/Circle.tsx delete mode 100644 components/vc-progress/src/Line.js create mode 100644 components/vc-progress/src/Line.tsx create mode 100644 components/vc-progress/src/common.ts delete mode 100644 components/vc-progress/src/enhancer.js rename components/vc-progress/src/{index.js => index.ts} (53%) delete mode 100644 components/vc-progress/src/types.js create mode 100644 components/vc-progress/src/types.ts diff --git a/components/vc-progress/index.js b/components/vc-progress/index.ts similarity index 62% rename from components/vc-progress/index.js rename to components/vc-progress/index.ts index 5dc2fdf679..199a021db2 100644 --- a/components/vc-progress/index.js +++ b/components/vc-progress/index.ts @@ -1,5 +1,5 @@ // based on rc-progress 2.5.2 -import Progress, { Line, Circle } from './src/'; +import Progress, { Line, Circle } from './src'; export { Line, Circle }; diff --git a/components/vc-progress/src/Circle.js b/components/vc-progress/src/Circle.js deleted file mode 100644 index 8b951253df..0000000000 --- a/components/vc-progress/src/Circle.js +++ /dev/null @@ -1,191 +0,0 @@ -import PropTypes, { withUndefined } from '../../_util/vue-types'; -import { initDefaultProps } from '../../_util/props-util'; -import enhancer from './enhancer'; -import { propTypes, defaultProps } from './types'; -import { defineComponent } from 'vue'; - -const circlePropTypes = { - ...propTypes, - gapPosition: PropTypes.oneOf(['top', 'bottom', 'left', 'right']), - gapDegree: withUndefined( - PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.looseBool]), - ), -}; - -const circleDefaultProps = { - ...defaultProps, - gapPosition: 'top', -}; - -let gradientSeed = 0; - -function stripPercentToNumber(percent) { - return +percent.replace('%', ''); -} - -function toArray(symArray) { - return Array.isArray(symArray) ? symArray : [symArray]; -} - -function getPathStyles(offset, percent, strokeColor, strokeWidth, gapDegree = 0, gapPosition) { - const radius = 50 - strokeWidth / 2; - let beginPositionX = 0; - let beginPositionY = -radius; - let endPositionX = 0; - let endPositionY = -2 * radius; - switch (gapPosition) { - case 'left': - beginPositionX = -radius; - beginPositionY = 0; - endPositionX = 2 * radius; - endPositionY = 0; - break; - case 'right': - beginPositionX = radius; - beginPositionY = 0; - endPositionX = -2 * radius; - endPositionY = 0; - break; - case 'bottom': - beginPositionY = radius; - endPositionY = 2 * radius; - break; - default: - } - const pathString = `M 50,50 m ${beginPositionX},${beginPositionY} - a ${radius},${radius} 0 1 1 ${endPositionX},${-endPositionY} - a ${radius},${radius} 0 1 1 ${-endPositionX},${endPositionY}`; - const len = Math.PI * 2 * radius; - - const pathStyle = { - stroke: strokeColor, - strokeDasharray: `${(percent / 100) * (len - gapDegree)}px ${len}px`, - strokeDashoffset: `-${gapDegree / 2 + (offset / 100) * (len - gapDegree)}px`, - transition: - 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s', // eslint-disable-line - }; - - return { - pathString, - pathStyle, - }; -} - -const Circle = defineComponent({ - name: 'Circle', - props: initDefaultProps(circlePropTypes, circleDefaultProps), - created() { - this.paths = {}; - this.gradientId = gradientSeed; - gradientSeed += 1; - }, - methods: { - getStokeList() { - const { - prefixCls, - percent, - strokeColor, - strokeWidth, - strokeLinecap, - gapDegree, - gapPosition, - } = this.$props; - const percentList = toArray(percent); - const strokeColorList = toArray(strokeColor); - - let stackPtg = 0; - return percentList.map((ptg, index) => { - const color = strokeColorList[index] || strokeColorList[strokeColorList.length - 1]; - const stroke = - Object.prototype.toString.call(color) === '[object Object]' - ? `url(#${prefixCls}-gradient-${this.gradientId})` - : ''; - const { pathString, pathStyle } = getPathStyles( - stackPtg, - ptg, - color, - strokeWidth, - gapDegree, - gapPosition, - ); - - stackPtg += ptg; - - const pathProps = { - key: index, - d: pathString, - stroke, - 'stroke-linecap': strokeLinecap, - 'stroke-width': strokeWidth, - opacity: ptg === 0 ? 0 : 1, - 'fill-opacity': '0', - class: `${prefixCls}-circle-path`, - style: pathStyle, - }; - return (this.paths[index] = c)} {...pathProps} />; - }); - }, - }, - - render() { - const { - prefixCls, - strokeWidth, - trailWidth, - gapDegree, - gapPosition, - trailColor, - strokeLinecap, - strokeColor, - ...restProps - } = this.$props; - const { pathString, pathStyle } = getPathStyles( - 0, - 100, - trailColor, - strokeWidth, - gapDegree, - gapPosition, - ); - delete restProps.percent; - const strokeColorList = toArray(strokeColor); - const gradient = strokeColorList.find( - color => Object.prototype.toString.call(color) === '[object Object]', - ); - const pathFirst = { - d: pathString, - stroke: trailColor, - 'stroke-linecap': strokeLinecap, - 'stroke-width': trailWidth || strokeWidth, - 'fill-opacity': '0', - class: `${prefixCls}-circle-trail`, - style: pathStyle, - }; - - return ( - - {gradient && ( - - - {Object.keys(gradient) - .sort((a, b) => stripPercentToNumber(a) - stripPercentToNumber(b)) - .map((key, index) => ( - - ))} - - - )} - - {this.getStokeList().reverse()} - - ); - }, -}); - -export default enhancer(Circle); diff --git a/components/vc-progress/src/Circle.tsx b/components/vc-progress/src/Circle.tsx new file mode 100644 index 0000000000..abb5c7cfbf --- /dev/null +++ b/components/vc-progress/src/Circle.tsx @@ -0,0 +1,174 @@ +import { useTransitionDuration, defaultProps } from './common'; +import { propTypes, GapPositionType } from './types'; +import { computed, defineComponent, ref } from 'vue'; +import initDefaultProps from '../../_util/props-util/initDefaultProps'; + +let gradientSeed = 0; + +function stripPercentToNumber(percent: string) { + return +percent.replace('%', ''); +} + +function toArray(value: any) { + return Array.isArray(value) ? value : [value]; +} + +function getPathStyles( + offset: number, + percent: number, + strokeColor: string, + strokeWidth: number, + gapDegree = 0, + gapPosition: GapPositionType, +) { + const radius = 50 - strokeWidth / 2; + let beginPositionX = 0; + let beginPositionY = -radius; + let endPositionX = 0; + let endPositionY = -2 * radius; + switch (gapPosition) { + case 'left': + beginPositionX = -radius; + beginPositionY = 0; + endPositionX = 2 * radius; + endPositionY = 0; + break; + case 'right': + beginPositionX = radius; + beginPositionY = 0; + endPositionX = -2 * radius; + endPositionY = 0; + break; + case 'bottom': + beginPositionY = radius; + endPositionY = 2 * radius; + break; + default: + } + const pathString = `M 50,50 m ${beginPositionX},${beginPositionY} + a ${radius},${radius} 0 1 1 ${endPositionX},${-endPositionY} + a ${radius},${radius} 0 1 1 ${-endPositionX},${endPositionY}`; + const len = Math.PI * 2 * radius; + + const pathStyle = { + stroke: strokeColor, + strokeDasharray: `${(percent / 100) * (len - gapDegree)}px ${len}px`, + strokeDashoffset: `-${gapDegree / 2 + (offset / 100) * (len - gapDegree)}px`, + transition: + 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s', // eslint-disable-line + }; + + return { + pathString, + pathStyle, + }; +} + +export default defineComponent({ + name: 'VCCircle', + props: initDefaultProps(propTypes, defaultProps), + setup(props) { + gradientSeed += 1; + const gradientId = ref(gradientSeed); + const percentList = computed(() => toArray(props.percent)); + const strokeColorList = computed(() => toArray(props.strokeColor)); + + const paths = useTransitionDuration(percentList); + + const getStokeList = () => { + const { prefixCls, strokeWidth, strokeLinecap, gapDegree, gapPosition } = props; + + let stackPtg = 0; + return percentList.value.map((ptg, index) => { + const color = + strokeColorList.value[index] || strokeColorList.value[strokeColorList.value.length - 1]; + const stroke = + Object.prototype.toString.call(color) === '[object Object]' + ? `url(#${prefixCls}-gradient-${gradientId.value})` + : ''; + const { pathString, pathStyle } = getPathStyles( + stackPtg, + ptg, + color, + strokeWidth, + gapDegree, + gapPosition, + ); + + stackPtg += ptg; + + const pathProps = { + key: index, + d: pathString, + stroke, + 'stroke-linecap': strokeLinecap, + 'stroke-width': strokeWidth, + opacity: ptg === 0 ? 0 : 1, + 'fill-opacity': '0', + class: `${prefixCls}-circle-path`, + style: pathStyle, + }; + return (paths.value[index].value = c)} {...pathProps} />; + }); + }; + + return () => { + const { + prefixCls, + strokeWidth, + trailWidth, + gapDegree, + gapPosition, + trailColor, + strokeLinecap, + strokeColor, + ...restProps + } = props; + const { pathString, pathStyle } = getPathStyles( + 0, + 100, + trailColor, + strokeWidth, + gapDegree, + gapPosition, + ); + delete restProps.percent; + const gradient = strokeColorList.value.find( + color => Object.prototype.toString.call(color) === '[object Object]', + ); + const pathFirst = { + d: pathString, + stroke: trailColor, + 'stroke-linecap': strokeLinecap, + 'stroke-width': trailWidth || strokeWidth, + 'fill-opacity': '0', + class: `${prefixCls}-circle-trail`, + style: pathStyle, + }; + + return ( + + {gradient && ( + + + {Object.keys(gradient) + .sort((a, b) => stripPercentToNumber(a) - stripPercentToNumber(b)) + .map((key, index) => ( + + ))} + + + )} + + {getStokeList().reverse()} + + ); + }; + }, +}); diff --git a/components/vc-progress/src/Line.js b/components/vc-progress/src/Line.js deleted file mode 100644 index 3e881964f4..0000000000 --- a/components/vc-progress/src/Line.js +++ /dev/null @@ -1,84 +0,0 @@ -import { defineComponent } from 'vue'; -import { initDefaultProps } from '../../_util/props-util'; -import enhancer from './enhancer'; -import { propTypes, defaultProps } from './types'; - -const Line = defineComponent({ - name: 'Line', - props: initDefaultProps(propTypes, defaultProps), - created() { - this.paths = {}; - }, - render() { - const { - percent, - prefixCls, - strokeColor, - strokeLinecap, - strokeWidth, - trailColor, - trailWidth, - transition, - ...restProps - } = this.$props; - - delete restProps.gapPosition; - - const percentList = Array.isArray(percent) ? percent : [percent]; - const strokeColorList = Array.isArray(strokeColor) ? strokeColor : [strokeColor]; - - const center = strokeWidth / 2; - const right = 100 - strokeWidth / 2; - const pathString = `M ${strokeLinecap === 'round' ? center : 0},${center} - L ${strokeLinecap === 'round' ? right : 100},${center}`; - const viewBoxString = `0 0 100 ${strokeWidth}`; - - let stackPtg = 0; - - const pathFirst = { - d: pathString, - 'stroke-linecap': strokeLinecap, - stroke: trailColor, - 'stroke-width': trailWidth || strokeWidth, - 'fill-opacity': '0', - class: `${prefixCls}-line-trail`, - }; - return ( - - - {percentList.map((ptg, index) => { - const pathStyle = { - strokeDasharray: `${ptg}px, 100px`, - strokeDashoffset: `-${stackPtg}px`, - transition: - transition || - 'stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear', - }; - const color = strokeColorList[index] || strokeColorList[strokeColorList.length - 1]; - - stackPtg += ptg; - - const pathProps = { - key: index, - d: pathString, - 'stroke-linecap': strokeLinecap, - stroke: color, - 'stroke-width': strokeWidth, - 'fill-opacity': '0', - class: `${prefixCls}-line-path`, - style: pathStyle, - }; - - return (this.paths[index] = c)} {...pathProps} />; - })} - - ); - }, -}); - -export default enhancer(Line); diff --git a/components/vc-progress/src/Line.tsx b/components/vc-progress/src/Line.tsx new file mode 100644 index 0000000000..d61a5280d8 --- /dev/null +++ b/components/vc-progress/src/Line.tsx @@ -0,0 +1,96 @@ +import { computed, defineComponent } from 'vue'; +import initDefaultProps from '../../_util/props-util/initDefaultProps'; +import { useTransitionDuration, defaultProps } from './common'; +import { propTypes } from './types'; + +export default defineComponent({ + name: 'Line', + props: initDefaultProps(propTypes, defaultProps), + setup(props) { + const percentList = computed(() => { + const { percent } = props; + return Array.isArray(percent) ? percent : [percent]; + }); + + const strokeColorList = computed(() => { + const { strokeColor } = props; + return Array.isArray(strokeColor) ? strokeColor : [strokeColor]; + }); + + const paths = useTransitionDuration(percentList); + const center = computed(() => props.strokeWidth / 2); + const right = computed(() => 100 - props.strokeWidth / 2); + + const pathString = computed( + () => `M ${props.strokeLinecap === 'round' ? center.value : 0},${center.value} + L ${props.strokeLinecap === 'round' ? right.value : 100},${center.value}`, + ); + + const viewBoxString = computed(() => `0 0 100 ${props.strokeWidth}`); + + const pathFirst = computed(() => ({ + d: pathString.value, + 'stroke-linecap': props.strokeLinecap, + stroke: props.trailColor, + 'stroke-width': props.trailWidth || props.strokeWidth, + 'fill-opacity': '0', + class: `${props.prefixCls}-line-trail`, + })); + + return () => { + const { + percent, + prefixCls, + strokeColor, + strokeLinecap, + strokeWidth, + trailColor, + trailWidth, + transition, + ...restProps + } = props; + + delete restProps.gapPosition; + + let stackPtg = 0; + + return ( + + + {percentList.value.map((ptg, index) => { + const pathStyle = { + strokeDasharray: `${ptg}px, 100px`, + strokeDashoffset: `-${stackPtg}px`, + transition: + transition || + 'stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear', + }; + const color = + strokeColorList.value[index] || + strokeColorList.value[strokeColorList.value.length - 1]; + + stackPtg += ptg; + + const pathProps = { + key: index, + d: pathString.value, + 'stroke-linecap': strokeLinecap, + stroke: color as string, + 'stroke-width': strokeWidth, + 'fill-opacity': '0', + class: `${prefixCls}-line-path`, + style: pathStyle, + }; + + return (paths.value[index].value = c)} {...pathProps} />; + })} + + ); + }; + }, +}); diff --git a/components/vc-progress/src/common.ts b/components/vc-progress/src/common.ts new file mode 100644 index 0000000000..334e9577c2 --- /dev/null +++ b/components/vc-progress/src/common.ts @@ -0,0 +1,43 @@ +import type { Ref } from 'vue'; +import { ref, onUpdated, computed } from 'vue'; +import type { ProgressProps } from './types'; + +export const defaultProps: Partial = { + percent: 0, + prefixCls: 'vc-progress', + strokeColor: '#2db7f5', + strokeLinecap: 'round', + strokeWidth: 1, + trailColor: '#D9D9D9', + trailWidth: 1, +}; + +export const useTransitionDuration = (percentList: Ref) => { + const paths = computed(() => percentList.value.map(() => ref())); + const prevTimeStamp = ref(null); + + onUpdated(() => { + const now = Date.now(); + let updated = false; + + Object.keys(paths.value).forEach(key => { + const path = paths.value[key].value; + if (!path) { + return; + } + updated = true; + const pathStyle = path.style; + pathStyle.transitionDuration = '.3s, .3s, .3s, .06s'; + + if (prevTimeStamp.value && now - prevTimeStamp.value < 100) { + pathStyle.transitionDuration = '0s, 0s'; + } + }); + + if (updated) { + prevTimeStamp.value = Date.now(); + } + }); + + return paths; +}; diff --git a/components/vc-progress/src/enhancer.js b/components/vc-progress/src/enhancer.js deleted file mode 100644 index 43ad4794ec..0000000000 --- a/components/vc-progress/src/enhancer.js +++ /dev/null @@ -1,30 +0,0 @@ -function enhancer(Component) { - return { - ...Component, - updated() { - const now = Date.now(); - let updated = false; - - Object.keys(this.paths).forEach(key => { - const path = this.paths[key]; - - if (!path) { - return; - } - - updated = true; - const pathStyle = path.style; - pathStyle.transitionDuration = '.3s, .3s, .3s, .06s'; - - if (this.prevTimeStamp && now - this.prevTimeStamp < 100) { - pathStyle.transitionDuration = '0s, 0s'; - } - }); - if (updated) { - this.prevTimeStamp = Date.now(); - } - }, - }; -} - -export default enhancer; diff --git a/components/vc-progress/src/index.js b/components/vc-progress/src/index.ts similarity index 53% rename from components/vc-progress/src/index.js rename to components/vc-progress/src/index.ts index 01fb4abd79..e6e5727860 100644 --- a/components/vc-progress/src/index.js +++ b/components/vc-progress/src/index.ts @@ -1,7 +1,8 @@ import Line from './Line'; import Circle from './Circle'; +import type { ProgressProps } from './types'; -export { Line, Circle }; +export { Line, Circle, ProgressProps }; export default { Line, diff --git a/components/vc-progress/src/types.js b/components/vc-progress/src/types.js deleted file mode 100644 index f027d27d29..0000000000 --- a/components/vc-progress/src/types.js +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from '../../_util/vue-types'; - -export const defaultProps = { - // className: '', - percent: 0, - prefixCls: 'rc-progress', - strokeColor: '#2db7f5', - strokeLinecap: 'round', - strokeWidth: 1, - // style: {}, - trailColor: '#D9D9D9', - trailWidth: 1, -}; -const mixedType = PropTypes.oneOfType([PropTypes.number, PropTypes.string]); - -export const propTypes = { - // className: PropTypes.string, - percent: PropTypes.oneOfType([mixedType, PropTypes.arrayOf(mixedType)]), - prefixCls: PropTypes.string, - strokeColor: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object])), - PropTypes.object, - ]), - strokeLinecap: PropTypes.oneOf(['butt', 'round', 'square']), - strokeWidth: mixedType, - // style: PropTypes.object, - trailColor: PropTypes.string, - trailWidth: mixedType, -}; diff --git a/components/vc-progress/src/types.ts b/components/vc-progress/src/types.ts new file mode 100644 index 0000000000..474ed7970f --- /dev/null +++ b/components/vc-progress/src/types.ts @@ -0,0 +1,31 @@ +import type { PropType, ExtractPropTypes } from 'vue'; +import PropTypes from '../../_util/vue-types'; + +export type StrokeColorType = string | string[] | object; + +export type GapPositionType = 'top' | 'right' | 'bottom' | 'left'; + +export type StrokeLinecapType = 'round' | 'butt' | 'square'; + +export const propTypes = { + gapDegree: PropTypes.number, + gapPosition: { + type: String as PropType, + }, + percent: { + type: [Array, Number] as PropType, + }, + prefixCls: PropTypes.string, + strokeColor: { + type: [Object, String, Array] as PropType, + }, + strokeLinecap: { + type: String as PropType, + }, + strokeWidth: PropTypes.number, + trailColor: PropTypes.string, + trailWidth: PropTypes.number, + transition: PropTypes.string, +}; + +export type ProgressProps = Partial>;