From 01096b556de5c63601f48fe7723120b13fc1cbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E8=BF=90=E5=A4=A9?= Date: Sat, 17 Oct 2020 11:23:31 +0800 Subject: [PATCH 1/7] refactor: statistic and countdown to ts (#2997) --- components/statistic/Countdown.jsx | 87 --------------------- components/statistic/Countdown.tsx | 79 +++++++++++++++++++ components/statistic/Number.jsx | 48 ------------ components/statistic/Number.tsx | 53 +++++++++++++ components/statistic/Statistic.jsx | 67 ---------------- components/statistic/Statistic.tsx | 63 +++++++++++++++ components/statistic/{index.js => index.ts} | 3 +- components/statistic/{utils.js => utils.ts} | 2 +- 8 files changed, 198 insertions(+), 204 deletions(-) delete mode 100644 components/statistic/Countdown.jsx create mode 100644 components/statistic/Countdown.tsx delete mode 100644 components/statistic/Number.jsx create mode 100644 components/statistic/Number.tsx delete mode 100644 components/statistic/Statistic.jsx create mode 100644 components/statistic/Statistic.tsx rename components/statistic/{index.js => index.ts} (80%) rename components/statistic/{utils.js => utils.ts} (99%) diff --git a/components/statistic/Countdown.jsx b/components/statistic/Countdown.jsx deleted file mode 100644 index 5964383d8..000000000 --- a/components/statistic/Countdown.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import moment from 'moment'; -import interopDefault from '../_util/interopDefault'; -import { initDefaultProps } from '../_util/props-util'; -import Statistic, { StatisticProps } from './Statistic'; -import { formatCountdown } from './utils'; - -const REFRESH_INTERVAL = 1000 / 30; - -function getTime(value) { - return interopDefault(moment)(value).valueOf(); -} - -export default { - name: 'AStatisticCountdown', - props: initDefaultProps(StatisticProps, { - format: 'HH:mm:ss', - }), - - created() { - this.countdownId = undefined; - }, - - mounted() { - this.syncTimer(); - }, - - updated() { - this.syncTimer(); - }, - - beforeUnmount() { - this.stopTimer(); - }, - - methods: { - syncTimer() { - const { value } = this.$props; - const timestamp = getTime(value); - if (timestamp >= Date.now()) { - this.startTimer(); - } else { - this.stopTimer(); - } - }, - - startTimer() { - if (this.countdownId) return; - this.countdownId = window.setInterval(() => { - this.$refs.statistic.$forceUpdate(); - this.syncTimer(); - }, REFRESH_INTERVAL); - }, - - stopTimer() { - const { value } = this.$props; - if (this.countdownId) { - clearInterval(this.countdownId); - this.countdownId = undefined; - - const timestamp = getTime(value); - if (timestamp < Date.now()) { - this.$emit('finish'); - } - } - }, - - formatCountdown({ value, config }) { - const { format } = this.$props; - return formatCountdown(value, { ...config, format }); - }, - - valueRenderHtml: node => node, - }, - - render() { - return ( - - ); - }, -}; diff --git a/components/statistic/Countdown.tsx b/components/statistic/Countdown.tsx new file mode 100644 index 000000000..e09d34038 --- /dev/null +++ b/components/statistic/Countdown.tsx @@ -0,0 +1,79 @@ +import moment from 'moment'; +import interopDefault from '../_util/interopDefault'; +import Statistic, { StatisticProps } from './Statistic'; +import { formatCountdown } from './utils'; +import { defineComponent, ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'; +import PropTypes from '../_util/vue-types/index'; + +const REFRESH_INTERVAL = 1000 / 30; +function getTime(value) { + return interopDefault(moment)(value).valueOf(); +} + +const StatisticCountdown = defineComponent({ + name: 'AStatisticCountdown', + props: { + ...StatisticProps, + format: PropTypes.string.def('HH:mm:ss'), + }, + emits: ['finish'], + setup(props, { emit }) { + let countdownId: number | undefined = undefined; + const renderKey = ref(0); + + const syncTimer = () => { + const timestamp = getTime(props.value); + if (timestamp >= Date.now()) { + startTimer(); + } else { + stopTimer(); + } + }; + + const startTimer = () => { + if (countdownId) return; + countdownId = window.setInterval(() => { + renderKey.value++; + syncTimer(); + }, REFRESH_INTERVAL); + }; + + const stopTimer = () => { + if (countdownId) { + clearInterval(countdownId); + countdownId = undefined; + + const timestamp = getTime(props.value); + if (timestamp < Date.now()) { + emit('finish'); + } + } + }; + + onMounted(() => { + syncTimer(); + }); + + onUpdated(() => { + syncTimer(); + }); + + onBeforeUnmount(() => { + stopTimer(); + }); + + return () => ( + node, + formatter: ({ value }) => formatCountdown(value, { format: props.format }), + }} + /> + ); + }, +}); + +export default StatisticCountdown; diff --git a/components/statistic/Number.jsx b/components/statistic/Number.jsx deleted file mode 100644 index 1063d750a..000000000 --- a/components/statistic/Number.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import padEnd from 'lodash-es/padEnd'; -import { createVNode } from 'vue'; - -const Number = (_, { attrs }) => { - const { value, formatter, precision, decimalSeparator, groupSeparator = '', prefixCls } = attrs; - let valueNode; - - if (typeof formatter === 'function') { - // Customize formatter - valueNode = formatter({ value, h: createVNode }); - } else { - // Internal formatter - const val = String(value); - const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/); - // Process if illegal number - if (!cells) { - valueNode = val; - } else { - const negative = cells[1]; - let int = cells[2] || '0'; - let decimal = cells[4] || ''; - - int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator); - if (typeof precision === 'number') { - decimal = padEnd(decimal, precision, '0').slice(0, precision); - } - - if (decimal) { - decimal = `${decimalSeparator}${decimal}`; - } - - valueNode = [ - - {negative} - {int} - , - decimal && ( - - {decimal} - - ), - ]; - } - } - - return {valueNode}; -}; -export default Number; diff --git a/components/statistic/Number.tsx b/components/statistic/Number.tsx new file mode 100644 index 000000000..040e1567d --- /dev/null +++ b/components/statistic/Number.tsx @@ -0,0 +1,53 @@ +import padEnd from 'lodash-es/padEnd'; +import { FunctionalComponent, VNodeTypes } from 'vue'; +import { StatisticProps, StatisticPropsType } from './Statistic'; +import PropTypes from '../_util/vue-types/index'; + +const StatisticNumber: FunctionalComponent & { + formatter?: VNodeTypes; +}> = props => { + const { formatter, value, groupSeparator, precision, decimalSeparator, prefixCls } = props; + let valueNode: VNodeTypes; + + if (formatter) { + valueNode = formatter; + } else { + const val = String(value); + const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/); + if (!cells) { + valueNode = val; + } else { + const negative = cells[1]; + let int = cells[2] || '0'; + let decimal = cells[4] || ''; + + int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator); + if (typeof precision === 'number') { + decimal = padEnd(decimal, precision, '0').slice(0, precision); + } + + if (decimal) { + decimal = `${decimalSeparator}${decimal}`; + } + valueNode = ( + <> + + {negative} + {int} + + {decimal && ( + + {decimal} + + )} + + ); + } + } + + return {valueNode}; +}; + +StatisticNumber.props = { ...StatisticProps, formatter: PropTypes.VNodeChild }; + +export default StatisticNumber; diff --git a/components/statistic/Statistic.jsx b/components/statistic/Statistic.jsx deleted file mode 100644 index c31c6b4c0..000000000 --- a/components/statistic/Statistic.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import { inject } from 'vue'; -import PropTypes from '../_util/vue-types'; -import { getComponent, initDefaultProps } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; -import StatisticNumber from './Number'; - -export const StatisticProps = { - prefixCls: PropTypes.string, - decimalSeparator: PropTypes.string, - groupSeparator: PropTypes.string, - format: PropTypes.string, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]), - valueStyle: PropTypes.any, - valueRender: PropTypes.any, - formatter: PropTypes.any, - precision: PropTypes.number, - prefix: PropTypes.any, - suffix: PropTypes.any, - title: PropTypes.any, - onFinish: PropTypes.func, -}; - -export default { - name: 'AStatistic', - props: initDefaultProps(StatisticProps, { - decimalSeparator: '.', - groupSeparator: ',', - }), - - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - - render() { - const { prefixCls: customizePrefixCls, value = 0, valueStyle, valueRender } = this.$props; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('statistic', customizePrefixCls); - - const title = getComponent(this, 'title'); - let prefix = getComponent(this, 'prefix'); - let suffix = getComponent(this, 'suffix'); - const formatter = getComponent(this, 'formatter', {}, false); - const props = { - ...this.$props, - prefixCls, - value, - formatter, - }; - let valueNode = ; - if (valueRender) { - valueNode = valueRender(valueNode); - } - - return ( -
- {title &&
{title}
} -
- {prefix && {prefix}} - {valueNode} - {suffix && {suffix}} -
-
- ); - }, -}; diff --git a/components/statistic/Statistic.tsx b/components/statistic/Statistic.tsx new file mode 100644 index 000000000..e7d4892f9 --- /dev/null +++ b/components/statistic/Statistic.tsx @@ -0,0 +1,63 @@ +import { defineComponent, inject, VNodeTypes, computed } from 'vue'; +import { VueTypeValidableDef } from 'vue-types'; +import { defaultConfigProvider } from '../config-provider'; +import { getComponent } from '../_util/props-util'; +import PropTypes from '../_util/vue-types'; +import StatisticNumber from './Number'; + +export const StatisticProps = { + decimalSeparator: PropTypes.string.def('.'), + formatter: PropTypes.func as VueTypeValidableDef<(params: { value: unknown }) => VNodeTypes>, + groupSeparator: PropTypes.string.def(','), + precision: PropTypes.number, + prefix: PropTypes.VNodeChild, + suffix: PropTypes.VNodeChild, + title: PropTypes.VNodeChild, + value: PropTypes.any, + valueStyle: PropTypes.style, + prefixCls: PropTypes.string, + valueRender: PropTypes.func as VueTypeValidableDef<(node: VNodeTypes) => VNodeTypes>, +}; + +export type StatisticPropsType = Parameters['0']; + +const Statistic = defineComponent({ + name: 'AStatistic', + props: StatisticProps, + setup(props) { + const configProvider = inject('configProvider', defaultConfigProvider); + const getPrefixCls = configProvider.getPrefixCls; + const prefixCls = computed(() => getPrefixCls('statistic', props.prefixCls)); + return { prefixCls }; + }, + render() { + const { prefixCls, valueStyle, valueRender, value } = this; + + const valueNode = ( + + ); + + const title = getComponent(this, 'title'); + const prefix = getComponent(this, 'prefix'); + const suffix = getComponent(this, 'suffix'); + + return ( +
+ {title &&
{title}
} +
+ {prefix && {prefix}} + {valueRender ? valueRender(valueNode) : valueNode} + {suffix && {suffix}} +
+
+ ); + }, +}); + +export default Statistic; diff --git a/components/statistic/index.js b/components/statistic/index.ts similarity index 80% rename from components/statistic/index.js rename to components/statistic/index.ts index 86e71d7a8..c7c5eb123 100644 --- a/components/statistic/index.js +++ b/components/statistic/index.ts @@ -1,9 +1,10 @@ +import { App } from 'vue'; import Statistic from './Statistic'; import Countdown from './Countdown'; Statistic.Countdown = Countdown; /* istanbul ignore next */ -Statistic.install = function(app) { +Statistic.install = function(app: App) { app.component(Statistic.name, Statistic); app.component(Statistic.Countdown.name, Statistic.Countdown); return app; diff --git a/components/statistic/utils.js b/components/statistic/utils.ts similarity index 99% rename from components/statistic/utils.js rename to components/statistic/utils.ts index f553b3490..f862244b9 100644 --- a/components/statistic/utils.js +++ b/components/statistic/utils.ts @@ -12,7 +12,7 @@ const timeUnits = [ ['m', 1000 * 60], // minutes ['s', 1000], // seconds ['S', 1], // million seconds -]; +] as const; export function formatTimeStr(duration, format) { let leftDuration = duration; From 8eb6afb9b08e4ca80feb3a8204870a3ed0bd1d4b Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 11:22:41 +0800 Subject: [PATCH 2/7] fix: unit test --- components/_util/responsiveObserve.ts | 4 +- components/affix/__tests__/index.test.js | 15 +-- components/auto-complete/index.tsx | 4 +- .../__snapshots__/index.test.js.snap | 2 +- components/card/Card.tsx | 2 +- components/checkbox/Group.tsx | 8 +- .../config-provider/__tests__/index.test.js | 4 +- components/grid/Col.tsx | 14 +-- components/grid/Row.tsx | 12 +-- .../__snapshots__/index.test.js.snap | 20 +++- components/select/index.tsx | 99 +++++++++++-------- components/vc-select2/generate.tsx | 3 +- 12 files changed, 105 insertions(+), 82 deletions(-) diff --git a/components/_util/responsiveObserve.ts b/components/_util/responsiveObserve.ts index b4b744664..7fc1cbf53 100644 --- a/components/_util/responsiveObserve.ts +++ b/components/_util/responsiveObserve.ts @@ -42,7 +42,7 @@ const responsiveObserve = { if (!subscribers.size) this.unregister(); }, unregister() { - Object.keys(responsiveMap).forEach((screen: Breakpoint) => { + Object.keys(responsiveMap).forEach((screen: string) => { const matchMediaQuery = responsiveMap[screen]!; const handler = this.matchHandlers[matchMediaQuery]; handler?.mql.removeListener(handler?.listener); @@ -50,7 +50,7 @@ const responsiveObserve = { subscribers.clear(); }, register() { - Object.keys(responsiveMap).forEach((screen: Breakpoint) => { + Object.keys(responsiveMap).forEach((screen: string) => { const matchMediaQuery = responsiveMap[screen]!; const listener = ({ matches }: { matches: boolean }) => { this.dispatch({ diff --git a/components/affix/__tests__/index.test.js b/components/affix/__tests__/index.test.js index f0b9354d5..d8422deef 100644 --- a/components/affix/__tests__/index.test.js +++ b/components/affix/__tests__/index.test.js @@ -19,17 +19,10 @@ const AffixMounter = { render() { return ( -
-
- this.$refs.container} - ref="affix" - {...{ props: this.$props }} - > - - -
+
+ this.$refs.container} ref="affix"> + +
); }, diff --git a/components/auto-complete/index.tsx b/components/auto-complete/index.tsx index 08c2063b2..60f946ad6 100644 --- a/components/auto-complete/index.tsx +++ b/components/auto-complete/index.tsx @@ -140,6 +140,6 @@ AutoComplete.install = function(app: App) { }; export default AutoComplete as typeof AutoComplete & { - readonly Option: typeof AutoComplete.Option; - readonly OptGroup: typeof AutoComplete.OptGroup; + readonly Option: typeof Option; + readonly OptGroup: typeof OptGroup; }; diff --git a/components/button/__tests__/__snapshots__/index.test.js.snap b/components/button/__tests__/__snapshots__/index.test.js.snap index b45ab8461..845b99e3a 100644 --- a/components/button/__tests__/__snapshots__/index.test.js.snap +++ b/components/button/__tests__/__snapshots__/index.test.js.snap @@ -59,7 +59,7 @@ exports[`Button should not render as link button when href is undefined 1`] = ` `; exports[`Button should support link button 1`] = ` - + link button `; diff --git a/components/card/Card.tsx b/components/card/Card.tsx index c969ee6a3..6a40f3416 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -96,7 +96,7 @@ const Card = defineComponent({ } = this.$props; const { $slots } = this; const children = getSlot(this); - const getPrefixCls = this.configProvider.getPrefixCls; + const { getPrefixCls } = this.configProvider; const prefixCls = getPrefixCls('card', customizePrefixCls); const tabBarExtraContent = getComponent(this, 'tabBarExtraContent'); diff --git a/components/checkbox/Group.tsx b/components/checkbox/Group.tsx index 1cfe27638..ee3fb8a0e 100644 --- a/components/checkbox/Group.tsx +++ b/components/checkbox/Group.tsx @@ -19,9 +19,9 @@ export default defineComponent({ props: { name: PropTypes.string, prefixCls: PropTypes.string, - defaultValue: { type: Array as PropType>}, - value: { type: Array as PropType>}, - options: {type: Array as PropType>}, + defaultValue: { type: Array as PropType> }, + value: { type: Array as PropType> }, + options: { type: Array as PropType> }, disabled: PropTypes.looseBool, onChange: PropTypes.func, }, @@ -48,7 +48,7 @@ export default defineComponent({ }, methods: { getOptions() { - const { options, $slots } = this; + const { options = [], $slots } = this; return options.map(option => { if (typeof option === 'string') { return { diff --git a/components/config-provider/__tests__/index.test.js b/components/config-provider/__tests__/index.test.js index 34c5b691d..6e563182a 100644 --- a/components/config-provider/__tests__/index.test.js +++ b/components/config-provider/__tests__/index.test.js @@ -8,11 +8,11 @@ describe('ConfigProvider', () => { mountTest({ render() { return ( -
+ <>
-
+ ); }, }); diff --git a/components/grid/Col.tsx b/components/grid/Col.tsx index bac7e59e1..d1b0ee783 100644 --- a/components/grid/Col.tsx +++ b/components/grid/Col.tsx @@ -89,16 +89,16 @@ const ACol = defineComponent({ let mergedStyle: CSSProperties = {}; if (gutter) { mergedStyle = { - ...(gutter[0]! > 0 + ...(gutter[0] > 0 ? { - paddingLeft: gutter[0]! / 2, - paddingRight: gutter[0]! / 2, + paddingLeft: `${gutter[0] / 2}px`, + paddingRight: `${gutter[0] / 2}px`, } : {}), - ...(gutter[1]! > 0 + ...(gutter[1] > 0 ? { - paddingTop: gutter[1]! / 2, - paddingBottom: gutter[1]! / 2, + paddingTop: `${gutter[1] / 2}px`, + paddingBottom: `${gutter[1] / 2}px`, } : {}), ...mergedStyle, @@ -109,7 +109,7 @@ const ACol = defineComponent({ } return ( -
+
{slots.default?.()}
); diff --git a/components/grid/Row.tsx b/components/grid/Row.tsx index 8c7a5f25f..ae7f3a9ee 100644 --- a/components/grid/Row.tsx +++ b/components/grid/Row.tsx @@ -104,16 +104,16 @@ const ARow = defineComponent({ [`${prefixCls}-${align}`]: align, }); const rowStyle = { - ...(gutter[0]! > 0 + ...(gutter[0] > 0 ? { - marginLeft: gutter[0]! / -2, - marginRight: gutter[0]! / -2, + marginLeft: `${gutter[0] / -2}px`, + marginRight: `${gutter[0] / -2}px`, } : {}), - ...(gutter[1]! > 0 + ...(gutter[1] > 0 ? { - marginTop: gutter[1]! / -2, - marginBottom: gutter[1]! / 2, + marginTop: `${gutter[1] / -2}px`, + marginBottom: `${gutter[1] / -2}px`, } : {}), }; diff --git a/components/grid/__tests__/__snapshots__/index.test.js.snap b/components/grid/__tests__/__snapshots__/index.test.js.snap index d0976028f..dd1aea276 100644 --- a/components/grid/__tests__/__snapshots__/index.test.js.snap +++ b/components/grid/__tests__/__snapshots__/index.test.js.snap @@ -3,12 +3,24 @@ exports[`Grid renders wrapped Col correctly 1`] = `
-
+
+ +
+
+
+
-
`; -exports[`Grid should render Col 1`] = `
`; +exports[`Grid should render Col 1`] = ` +
+ +
+`; -exports[`Grid should render Row 1`] = `
`; +exports[`Grid should render Row 1`] = ` +
+ +
+`; diff --git a/components/select/index.tsx b/components/select/index.tsx index 84592ea11..c2062ab6e 100644 --- a/components/select/index.tsx +++ b/components/select/index.tsx @@ -31,26 +31,29 @@ export interface InternalSelectProps extends Omit, 'mode'> } export interface SelectPropsTypes - extends Omit, 'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style'> { + extends Omit< + InternalSelectProps, + 'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style' + > { mode?: 'multiple' | 'tags'; } -export type SelectTypes = SelectPropsTypes +export type SelectTypes = SelectPropsTypes; export const SelectProps = { - ...omit(props, ['inputIcon' ,'mode' ,'getInputElement' ,'backfill' ,'class' ,'style']), + ...omit(props, ['inputIcon', 'mode', 'getInputElement', 'backfill', 'class', 'style']), value: { - type: [Array, Object, String, Number] as PropType + type: [Array, Object, String, Number] as PropType, }, defaultValue: { - type: [Array, Object, String, Number] as PropType + type: [Array, Object, String, Number] as PropType, }, suffixIcon: PropTypes.VNodeChild, itemIcon: PropTypes.VNodeChild, size: PropTypes.oneOf(tuple('small', 'middle', 'large', undefined, 'default')), - mode: PropTypes.oneOf(tuple('multiple', 'tags')), + mode: PropTypes.oneOf(tuple('multiple', 'tags', 'SECRET_COMBOBOX_MODE_DO_NOT_USE')), bordered: PropTypes.looseBool.def(true), transitionName: PropTypes.string.def('slide-up'), choiceTransitionName: PropTypes.string.def(''), -} +}; const Select = defineComponent({ name: 'ASelect', @@ -60,7 +63,7 @@ const Select = defineComponent({ props: SelectProps, SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE', emits: ['change', 'update:value'], - setup(props: any, {attrs, emit}) { + setup(props: any, { attrs, emit }) { const selectRef = ref(null); const configProvider = inject('configProvider', defaultConfigProvider); @@ -77,8 +80,8 @@ const Select = defineComponent({ } }; - const mode = computed(()=>{ - const { mode } = props + const mode = computed(() => { + const { mode } = props; if ((mode as any) === 'combobox') { return undefined; @@ -91,32 +94,41 @@ const Select = defineComponent({ return mode; }); - const mergedClassName = computed(()=> classNames( - { - [`${props.prefixCls}-lg`]: props.size === 'large', - [`${props.prefixCls}-sm`]: props.size === 'small', - [`${props.prefixCls}-rtl`]: props.direction === 'rtl', - [`${props.prefixCls}-borderless`]: !props.bordered, - }, - attrs.class, - )); - const triggerChange=(...args: any[])=>{ - console.log(args) - emit('update:value', ...args) - emit('change', ...args) - } + const mergedClassName = computed(() => + classNames( + { + [`${props.prefixCls}-lg`]: props.size === 'large', + [`${props.prefixCls}-sm`]: props.size === 'small', + [`${props.prefixCls}-rtl`]: props.direction === 'rtl', + [`${props.prefixCls}-borderless`]: !props.bordered, + }, + attrs.class, + ), + ); + const triggerChange = (...args: any[]) => { + console.log(args); + emit('update:value', ...args); + emit('change', ...args); + }; return { mergedClassName, mode, focus, blur, configProvider, - triggerChange - } + triggerChange, + }; }, render() { - const {configProvider, mode, mergedClassName,triggerChange, $slots: slots, $props} = this as any; - const props: SelectTypes = $props + const { + configProvider, + mode, + mergedClassName, + triggerChange, + $slots: slots, + $props, + } = this as any; + const props: SelectTypes = $props; const { prefixCls: customizePrefixCls, notFoundContent, @@ -126,10 +138,14 @@ const Select = defineComponent({ dropdownClassName, direction, virtual, - dropdownMatchSelectWidth + dropdownMatchSelectWidth, } = props; - const { getPrefixCls, renderEmpty, getPopupContainer: getContextPopupContainer } = configProvider + const { + getPrefixCls, + renderEmpty, + getPopupContainer: getContextPopupContainer, + } = configProvider; const prefixCls = getPrefixCls('select', customizePrefixCls); const isMultiple = mode === 'multiple' || mode === 'tags'; @@ -138,8 +154,8 @@ const Select = defineComponent({ let mergedNotFound: VNodeChild; if (notFoundContent !== undefined) { mergedNotFound = notFoundContent; - } else if(slots.notFoundContent){ - mergedNotFound = slots.notFoundContent() + } else if (slots.notFoundContent) { + mergedNotFound = slots.notFoundContent(); } else if (mode === 'combobox') { mergedNotFound = null; } else { @@ -147,11 +163,14 @@ const Select = defineComponent({ } // ===================== Icons ===================== - const { suffixIcon, itemIcon, removeIcon, clearIcon } = getIcons({ - ...this.$props, - multiple: isMultiple, - prefixCls, - }, slots); + const { suffixIcon, itemIcon, removeIcon, clearIcon } = getIcons( + { + ...this.$props, + multiple: isMultiple, + prefixCls, + }, + slots, + ); const selectProps = omit(props, [ 'prefixCls', @@ -187,9 +206,9 @@ const Select = defineComponent({ onChange={triggerChange} > {slots?.default()} - - } -}) + ; + }, +}); /* istanbul ignore next */ Select.install = function(app: App) { app.component(Select.name, Select); diff --git a/components/vc-select2/generate.tsx b/components/vc-select2/generate.tsx index 230ec974a..663c1f286 100644 --- a/components/vc-select2/generate.tsx +++ b/components/vc-select2/generate.tsx @@ -73,7 +73,6 @@ export const props = { // Options options: PropTypes.array, - children: PropTypes.array.def([]), mode: PropTypes.string, // Value @@ -163,7 +162,7 @@ export const props = { * Do not use in production environment. */ internalProps: PropTypes.object.def({}), -} +}; export interface SelectProps { prefixCls?: string; From 49fda0f7c8a1ae65c023bb3033a3062e3b73a5b7 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 11:51:17 +0800 Subject: [PATCH 3/7] fix: refactor statistic to ts --- components/statistic/Countdown.tsx | 103 ++++++++++++++++------------- components/statistic/Number.tsx | 48 +++++++------- components/statistic/Statistic.tsx | 82 ++++++++++++----------- components/statistic/index.ts | 2 +- components/statistic/utils.ts | 32 +++++++-- 5 files changed, 153 insertions(+), 114 deletions(-) diff --git a/components/statistic/Countdown.tsx b/components/statistic/Countdown.tsx index e09d34038..ce4be77ae 100644 --- a/components/statistic/Countdown.tsx +++ b/components/statistic/Countdown.tsx @@ -1,79 +1,88 @@ +import { defineComponent } from 'vue'; import moment from 'moment'; import interopDefault from '../_util/interopDefault'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; import Statistic, { StatisticProps } from './Statistic'; -import { formatCountdown } from './utils'; -import { defineComponent, ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'; -import PropTypes from '../_util/vue-types/index'; +import { formatCountdown, countdownValueType, FormatConfig } from './utils'; const REFRESH_INTERVAL = 1000 / 30; -function getTime(value) { + +function getTime(value?: countdownValueType) { return interopDefault(moment)(value).valueOf(); } -const StatisticCountdown = defineComponent({ +export default defineComponent({ name: 'AStatisticCountdown', - props: { - ...StatisticProps, - format: PropTypes.string.def('HH:mm:ss'), + props: initDefaultProps(StatisticProps, { + format: 'HH:mm:ss', + }), + setup() { + return { + countdownId: undefined, + } as { countdownId: number }; + }, + mounted() { + this.syncTimer(); }, - emits: ['finish'], - setup(props, { emit }) { - let countdownId: number | undefined = undefined; - const renderKey = ref(0); - const syncTimer = () => { - const timestamp = getTime(props.value); + updated() { + this.syncTimer(); + }, + + beforeUnmount() { + this.stopTimer(); + }, + + methods: { + syncTimer() { + const { value } = this.$props; + const timestamp = getTime(value); if (timestamp >= Date.now()) { - startTimer(); + this.startTimer(); } else { - stopTimer(); + this.stopTimer(); } - }; + }, - const startTimer = () => { - if (countdownId) return; - countdownId = window.setInterval(() => { - renderKey.value++; - syncTimer(); + startTimer() { + if (this.countdownId) return; + this.countdownId = window.setInterval(() => { + (this.$refs.statistic as any).$forceUpdate(); + this.syncTimer(); }, REFRESH_INTERVAL); - }; + }, - const stopTimer = () => { - if (countdownId) { - clearInterval(countdownId); - countdownId = undefined; + stopTimer() { + const { value } = this.$props; + if (this.countdownId) { + clearInterval(this.countdownId); + this.countdownId = undefined; - const timestamp = getTime(props.value); + const timestamp = getTime(value); if (timestamp < Date.now()) { - emit('finish'); + this.$emit('finish'); } } - }; + }, - onMounted(() => { - syncTimer(); - }); + formatCountdown({ value, config }: { value: countdownValueType; config: FormatConfig }) { + const { format } = this.$props; + return formatCountdown(value, { ...config, format }); + }, - onUpdated(() => { - syncTimer(); - }); + valueRenderHtml: node => node, + }, - onBeforeUnmount(() => { - stopTimer(); - }); - - return () => ( + render() { + return ( node, - formatter: ({ value }) => formatCountdown(value, { format: props.format }), + ...this.$props, + valueRender: this.valueRenderHtml, + formatter: this.formatCountdown, }} /> ); }, }); - -export default StatisticCountdown; diff --git a/components/statistic/Number.tsx b/components/statistic/Number.tsx index 040e1567d..eb7d65a7e 100644 --- a/components/statistic/Number.tsx +++ b/components/statistic/Number.tsx @@ -1,19 +1,23 @@ import padEnd from 'lodash-es/padEnd'; import { FunctionalComponent, VNodeTypes } from 'vue'; -import { StatisticProps, StatisticPropsType } from './Statistic'; -import PropTypes from '../_util/vue-types/index'; +import { FormatConfig, valueType } from './utils'; -const StatisticNumber: FunctionalComponent & { - formatter?: VNodeTypes; -}> = props => { - const { formatter, value, groupSeparator, precision, decimalSeparator, prefixCls } = props; +interface NumberProps extends FormatConfig { + value: valueType; +} + +const Number: FunctionalComponent = props => { + const { value, formatter, precision, decimalSeparator, groupSeparator = '', prefixCls } = props; let valueNode: VNodeTypes; - if (formatter) { - valueNode = formatter; + if (typeof formatter === 'function') { + // Customize formatter + valueNode = formatter({ value }); } else { + // Internal formatter const val = String(value); const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/); + // Process if illegal number if (!cells) { valueNode = val; } else { @@ -29,25 +33,21 @@ const StatisticNumber: FunctionalComponent if (decimal) { decimal = `${decimalSeparator}${decimal}`; } - valueNode = ( - <> - - {negative} - {int} + + valueNode = [ + + {negative} + {int} + , + decimal && ( + + {decimal} - {decimal && ( - - {decimal} - - )} - - ); + ), + ]; } } return {valueNode}; }; - -StatisticNumber.props = { ...StatisticProps, formatter: PropTypes.VNodeChild }; - -export default StatisticNumber; +export default Number; diff --git a/components/statistic/Statistic.tsx b/components/statistic/Statistic.tsx index e7d4892f9..1af9cae53 100644 --- a/components/statistic/Statistic.tsx +++ b/components/statistic/Statistic.tsx @@ -1,63 +1,71 @@ -import { defineComponent, inject, VNodeTypes, computed } from 'vue'; -import { VueTypeValidableDef } from 'vue-types'; -import { defaultConfigProvider } from '../config-provider'; -import { getComponent } from '../_util/props-util'; +import { defineComponent, inject, PropType } from 'vue'; import PropTypes from '../_util/vue-types'; +import { getComponent } from '../_util/props-util'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; +import { defaultConfigProvider } from '../config-provider'; import StatisticNumber from './Number'; +import { countdownValueType } from './utils'; export const StatisticProps = { - decimalSeparator: PropTypes.string.def('.'), - formatter: PropTypes.func as VueTypeValidableDef<(params: { value: unknown }) => VNodeTypes>, - groupSeparator: PropTypes.string.def(','), + prefixCls: PropTypes.string, + decimalSeparator: PropTypes.string, + groupSeparator: PropTypes.string, + format: PropTypes.string, + value: { + type: [String, Number, Object] as PropType, + }, + valueStyle: PropTypes.style, + valueRender: PropTypes.any, + formatter: PropTypes.any, precision: PropTypes.number, prefix: PropTypes.VNodeChild, suffix: PropTypes.VNodeChild, title: PropTypes.VNodeChild, - value: PropTypes.any, - valueStyle: PropTypes.style, - prefixCls: PropTypes.string, - valueRender: PropTypes.func as VueTypeValidableDef<(node: VNodeTypes) => VNodeTypes>, + onFinish: PropTypes.func, }; -export type StatisticPropsType = Parameters['0']; - -const Statistic = defineComponent({ +export default defineComponent({ name: 'AStatistic', - props: StatisticProps, - setup(props) { - const configProvider = inject('configProvider', defaultConfigProvider); - const getPrefixCls = configProvider.getPrefixCls; - const prefixCls = computed(() => getPrefixCls('statistic', props.prefixCls)); - return { prefixCls }; - }, - render() { - const { prefixCls, valueStyle, valueRender, value } = this; + props: initDefaultProps(StatisticProps, { + decimalSeparator: '.', + groupSeparator: ',', + }), - const valueNode = ( - - ); + setup() { + return { + configProvider: inject('configProvider', defaultConfigProvider), + }; + }, + + render() { + const { prefixCls: customizePrefixCls, value = 0, valueStyle, valueRender } = this.$props; + const { getPrefixCls } = this.configProvider; + const prefixCls = getPrefixCls('statistic', customizePrefixCls); const title = getComponent(this, 'title'); - const prefix = getComponent(this, 'prefix'); - const suffix = getComponent(this, 'suffix'); + let prefix = getComponent(this, 'prefix'); + let suffix = getComponent(this, 'suffix'); + const formatter = getComponent(this, 'formatter', {}, false); + const props = { + ...this.$props, + prefixCls, + value, + formatter, + }; + let valueNode = ; + if (valueRender) { + valueNode = valueRender(valueNode); + } return (
{title &&
{title}
}
{prefix && {prefix}} - {valueRender ? valueRender(valueNode) : valueNode} + {valueNode} {suffix && {suffix}}
); }, }); - -export default Statistic; diff --git a/components/statistic/index.ts b/components/statistic/index.ts index c7c5eb123..5028a5e61 100644 --- a/components/statistic/index.ts +++ b/components/statistic/index.ts @@ -1,6 +1,6 @@ -import { App } from 'vue'; import Statistic from './Statistic'; import Countdown from './Countdown'; +import { App } from 'vue'; Statistic.Countdown = Countdown; /* istanbul ignore next */ diff --git a/components/statistic/utils.ts b/components/statistic/utils.ts index f862244b9..e0f69618e 100644 --- a/components/statistic/utils.ts +++ b/components/statistic/utils.ts @@ -1,10 +1,32 @@ +import { VNodeTypes } from 'vue'; import moment from 'moment'; import padStart from 'lodash-es/padStart'; import interopDefault from '../_util/interopDefault'; +export type valueType = number | string; +export type countdownValueType = valueType | string; + +export type Formatter = + | false + | 'number' + | 'countdown' + | (({ value, config }: { value: valueType; config?: FormatConfig }) => VNodeTypes); + +export interface FormatConfig { + formatter?: Formatter; + decimalSeparator?: string; + groupSeparator?: string; + precision?: number; + prefixCls?: string; +} + +export interface CountdownFormatConfig extends FormatConfig { + format?: string; +} + // Countdown -const timeUnits = [ +const timeUnits: [string, number][] = [ ['Y', 1000 * 60 * 60 * 24 * 365], // years ['M', 1000 * 60 * 60 * 24 * 30], // months ['D', 1000 * 60 * 60 * 24], // days @@ -12,10 +34,10 @@ const timeUnits = [ ['m', 1000 * 60], // minutes ['s', 1000], // seconds ['S', 1], // million seconds -] as const; +]; -export function formatTimeStr(duration, format) { - let leftDuration = duration; +export function formatTimeStr(duration: number, format: string) { + let leftDuration: number = duration; const escapeRegex = /\[[^\]]*\]/g; const keepList = (format.match(escapeRegex) || []).map(str => str.slice(1, -1)); @@ -41,7 +63,7 @@ export function formatTimeStr(duration, format) { }); } -export function formatCountdown(value, config) { +export function formatCountdown(value: countdownValueType, config: CountdownFormatConfig) { const { format = '' } = config; const target = interopDefault(moment)(value).valueOf(); const current = interopDefault(moment)().valueOf(); From df771a8fc66e261f85154a1095d640c7cf1f1c27 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 12:13:55 +0800 Subject: [PATCH 4/7] chore: refactor space --- components/space/index.tsx | 111 ++++++++++-------- components/space/style/{index.js => index.ts} | 0 2 files changed, 60 insertions(+), 51 deletions(-) rename components/space/style/{index.js => index.ts} (100%) diff --git a/components/space/index.tsx b/components/space/index.tsx index 8cd65fa33..410103d5d 100644 --- a/components/space/index.tsx +++ b/components/space/index.tsx @@ -1,6 +1,8 @@ -import { inject, App, CSSProperties, SetupContext } from 'vue'; +import { inject, App, defineComponent, PropType } from 'vue'; +import PropTypes from '../_util/vue-types'; import { filterEmpty } from '../_util/props-util'; import { defaultConfigProvider, SizeType } from '../config-provider'; +import { tuple } from '../_util/type'; const spaceSize = { small: 8, @@ -8,67 +10,74 @@ const spaceSize = { large: 24, }; -export interface SpaceProps { - prefixCls?: string; - className?: string; - style?: CSSProperties; - size?: SizeType | number; - direction?: 'horizontal' | 'vertical'; - // No `stretch` since many components do not support that. - align?: 'start' | 'end' | 'center' | 'baseline'; -} +const Space = defineComponent({ + name: 'ASpace', + props: { + prefixCls: PropTypes.string, + size: { + type: [String, Number] as PropType, + }, + direction: PropTypes.oneOf(tuple('horizontal', 'vertical')), + align: PropTypes.oneOf(tuple('start', 'end', 'center', 'baseline')), + }, + setup(props, { slots }) { + const configProvider = inject('configProvider', defaultConfigProvider); + const { + align, + size = 'small', + direction = 'horizontal', + prefixCls: customizePrefixCls, + } = props; -const Space = (props: SpaceProps, { slots }: SetupContext) => { - const configProvider = inject('configProvider', defaultConfigProvider); - const { align, size = 'small', direction = 'horizontal', prefixCls: customizePrefixCls } = props; + const { getPrefixCls } = configProvider; - const { getPrefixCls } = configProvider; - const prefixCls = getPrefixCls('space', customizePrefixCls); - const items = filterEmpty(slots.default?.()); - const len = items.length; + return () => { + const prefixCls = getPrefixCls('space', customizePrefixCls); + const items = filterEmpty(slots.default?.()); + const len = items.length; - if (len === 0) { - return null; - } + if (len === 0) { + return null; + } - const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align; + const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align; - const someSpaceClass = { - [prefixCls]: true, - [`${prefixCls}-${direction}`]: true, - [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, - }; + const someSpaceClass = { + [prefixCls]: true, + [`${prefixCls}-${direction}`]: true, + [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, + }; - const itemClassName = `${prefixCls}-item`; - const marginDirection = 'marginRight'; // directionConfig === 'rtl' ? 'marginLeft' : 'marginRight'; + const itemClassName = `${prefixCls}-item`; + const marginDirection = 'marginRight'; // directionConfig === 'rtl' ? 'marginLeft' : 'marginRight'; - return ( -
- {items.map((child, i) => ( -
- {child} + return ( +
+ {items.map((child, i) => ( +
+ {child} +
+ ))}
- ))} -
- ); -}; - -Space.displayName = 'ASpace'; + ); + }; + }, +}); /* istanbul ignore next */ Space.install = function(app: App) { - app.component(Space.displayName, Space); + app.component(Space.name, Space); return app; }; diff --git a/components/space/style/index.js b/components/space/style/index.ts similarity index 100% rename from components/space/style/index.js rename to components/space/style/index.ts From 4fc1a3cd782906819d61ab04998854d5339e9561 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 12:14:13 +0800 Subject: [PATCH 5/7] fix: virtual list ts error --- components/_util/createRef.ts | 2 +- components/vc-select2/Selector/MultipleSelector.tsx | 1 - components/vc-virtual-list/Item.tsx | 3 ++- components/vc-virtual-list/List.tsx | 12 ++++++------ .../vc-virtual-list/hooks/useMobileTouchMove.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/_util/createRef.ts b/components/_util/createRef.ts index e64a67115..a13c5a8e1 100644 --- a/components/_util/createRef.ts +++ b/components/_util/createRef.ts @@ -2,7 +2,7 @@ export interface RefObject extends Function { current?: any; } -function createRef(): RefObject { +function createRef(): any { const func: RefObject = (node: any) => { func.current = node; }; diff --git a/components/vc-select2/Selector/MultipleSelector.tsx b/components/vc-select2/Selector/MultipleSelector.tsx index 4856c4d4f..cfbefceb8 100644 --- a/components/vc-select2/Selector/MultipleSelector.tsx +++ b/components/vc-select2/Selector/MultipleSelector.tsx @@ -237,7 +237,6 @@ const SelectSelector = defineComponent({ onInputMouseDown, onInputCompositionStart, onInputCompositionEnd, - choiceTransitionName, } = props; return ( <> diff --git a/components/vc-virtual-list/Item.tsx b/components/vc-virtual-list/Item.tsx index 76119ab80..863ac2dfe 100644 --- a/components/vc-virtual-list/Item.tsx +++ b/components/vc-virtual-list/Item.tsx @@ -9,7 +9,7 @@ const Item: FunctionalComponent = ({ setRef }, { slots }) => { return children && children.length ? cloneVNode(children[0], { - ref: setRef, + ref: setRef as any, }) : children; }; @@ -19,4 +19,5 @@ Item.props = { default: () => {}, }, }; + export default Item; diff --git a/components/vc-virtual-list/List.tsx b/components/vc-virtual-list/List.tsx index 673e2e11e..3615cc768 100644 --- a/components/vc-virtual-list/List.tsx +++ b/components/vc-virtual-list/List.tsx @@ -45,7 +45,7 @@ function renderChildren( }); const key = getKey(item); return ( - setNodeRef(item, ele)}> + setNodeRef(item, ele as HTMLElement)}> {node} ); @@ -102,7 +102,7 @@ const List = defineComponent({ mergedData: computed(() => props.data || EMPTY_DATA) as any, }); - const componentRef = ref(); + const componentRef = ref(); // =============================== Item Key =============================== const getKey = (item: Record) => { @@ -257,8 +257,8 @@ const List = defineComponent({ const removeEventListener = () => { if (componentRef.value) { componentRef.value.removeEventListener('wheel', onRawWheel); - componentRef.value.removeEventListener('DOMMouseScroll' as any, onFireFoxScroll); - componentRef.value.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll); + componentRef.value.removeEventListener('DOMMouseScroll', onFireFoxScroll as any); + componentRef.value.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll as any); } }; watchEffect(() => { @@ -266,8 +266,8 @@ const List = defineComponent({ if (componentRef.value) { removeEventListener(); componentRef.value.addEventListener('wheel', onRawWheel); - componentRef.value.addEventListener('DOMMouseScroll' as any, onFireFoxScroll); - componentRef.value.addEventListener('MozMousePixelScroll', onMozMousePixelScroll); + componentRef.value.addEventListener('DOMMouseScroll', onFireFoxScroll as any); + componentRef.value.addEventListener('MozMousePixelScroll', onMozMousePixelScroll as any); } }); }); diff --git a/components/vc-virtual-list/hooks/useMobileTouchMove.ts b/components/vc-virtual-list/hooks/useMobileTouchMove.ts index 7bbc76ce0..d5ff888b3 100644 --- a/components/vc-virtual-list/hooks/useMobileTouchMove.ts +++ b/components/vc-virtual-list/hooks/useMobileTouchMove.ts @@ -4,7 +4,7 @@ const SMOOTH_PTG = 14 / 15; export default function useMobileTouchMove( inVirtual: Ref, - listRef: Ref, + listRef: Ref, callback: (offsetY: number, smoothOffset?: boolean) => boolean, ) { let touched = false; From c97af1ac1852e01359048c0e463852ef451e3075 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 20:52:58 +0800 Subject: [PATCH 6/7] refactor: progress to ts --- components/progress/circle.jsx | 70 --------------- components/progress/circle.tsx | 85 +++++++++++++++++++ components/progress/{index.jsx => index.ts} | 5 +- components/progress/{line.jsx => line.tsx} | 0 .../progress/{progress.jsx => progress.tsx} | 37 ++------ components/progress/props.ts | 24 ++++++ components/progress/{utils.js => utils.ts} | 2 +- 7 files changed, 121 insertions(+), 102 deletions(-) delete mode 100644 components/progress/circle.jsx create mode 100644 components/progress/circle.tsx rename components/progress/{index.jsx => index.ts} (57%) rename components/progress/{line.jsx => line.tsx} (100%) rename components/progress/{progress.jsx => progress.tsx} (75%) create mode 100644 components/progress/props.ts rename components/progress/{utils.js => utils.ts} (69%) diff --git a/components/progress/circle.jsx b/components/progress/circle.jsx deleted file mode 100644 index 3ba042c18..000000000 --- a/components/progress/circle.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Circle as VCCircle } from '../vc-progress'; -import { validProgress } from './utils'; - -const statusColorMap = { - normal: '#108ee9', - exception: '#ff5500', - success: '#87d068', -}; - -function getPercentage({ percent, successPercent }) { - const ptg = validProgress(percent); - if (!successPercent) return ptg; - - const successPtg = validProgress(successPercent); - return [successPercent, validProgress(ptg - successPtg)]; -} - -function getStrokeColor({ progressStatus, successPercent, strokeColor }) { - const color = strokeColor || statusColorMap[progressStatus]; - if (!successPercent) return color; - return [statusColorMap.success, color]; -} - -const Circle = (_, { attrs, slots }) => { - const { - prefixCls, - width, - strokeWidth, - trailColor, - strokeLinecap, - gapPosition, - gapDegree, - type, - } = attrs; - 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(attrs); - 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/circle.tsx b/components/progress/circle.tsx new file mode 100644 index 000000000..025508c01 --- /dev/null +++ b/components/progress/circle.tsx @@ -0,0 +1,85 @@ +import { defineComponent, ExtractPropTypes } 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.jsx b/components/progress/index.ts similarity index 57% rename from components/progress/index.jsx rename to components/progress/index.ts index ac8b2be7e..c74c457fe 100644 --- a/components/progress/index.jsx +++ b/components/progress/index.ts @@ -1,9 +1,10 @@ +import { App } from 'vue'; import Progress from './progress'; -export { ProgressProps } from './progress'; +export { ProgressProps } from './props'; /* istanbul ignore next */ -Progress.install = function(app) { +Progress.install = function(app: App) { app.component(Progress.name, Progress); return app; }; diff --git a/components/progress/line.jsx b/components/progress/line.tsx similarity index 100% rename from components/progress/line.jsx rename to components/progress/line.tsx diff --git a/components/progress/progress.jsx b/components/progress/progress.tsx similarity index 75% rename from components/progress/progress.jsx rename to components/progress/progress.tsx index a9a8a6d6c..0ba670a51 100644 --- a/components/progress/progress.jsx +++ b/components/progress/progress.tsx @@ -1,7 +1,7 @@ -import { inject } from 'vue'; +import { defineComponent, inject } from 'vue'; import classNames from '../_util/classNames'; -import PropTypes from '../_util/vue-types'; -import { getOptionProps, initDefaultProps } from '../_util/props-util'; +import { getOptionProps } from '../_util/props-util'; +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'; @@ -10,30 +10,9 @@ 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'; -const ProgressStatuses = ['normal', 'exception', 'active', 'success']; -export const ProgressType = PropTypes.oneOf(['line', 'circle', 'dashboard']); -export const ProgressSize = PropTypes.oneOf(['default', 'small']); - -export const ProgressProps = { - prefixCls: PropTypes.string, - type: ProgressType, - percent: PropTypes.number, - successPercent: PropTypes.number, - format: PropTypes.func, - status: PropTypes.oneOf(ProgressStatuses), - showInfo: PropTypes.looseBool, - strokeWidth: PropTypes.number, - strokeLinecap: PropTypes.oneOf(['butt', 'round', 'square']), - strokeColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - trailColor: PropTypes.string, - width: PropTypes.number, - gapDegree: PropTypes.number, - gapPosition: PropTypes.oneOf(['top', 'bottom', 'left', 'right']), - size: ProgressSize, -}; - -export default { +export default defineComponent({ name: 'AProgress', props: initDefaultProps(ProgressProps, { type: 'line', @@ -65,7 +44,7 @@ export default { } return status || 'normal'; }, - renderProcessInfo(prefixCls, progressStatus) { + renderProcessInfo(prefixCls: string, progressStatus: typeof ProgressStatuses[number]) { const { showInfo, format, type, percent, successPercent } = this.$props; if (!showInfo) return null; @@ -93,7 +72,7 @@ export default { render() { const props = getOptionProps(this); const { prefixCls: customizePrefixCls, size, type, showInfo } = props; - const getPrefixCls = this.configProvider.getPrefixCls; + const { getPrefixCls } = this.configProvider; const prefixCls = getPrefixCls('progress', customizePrefixCls); const progressStatus = this.getProgressStatus(); const progressInfo = this.renderProcessInfo(prefixCls, progressStatus); @@ -128,4 +107,4 @@ export default { }; return
{progress}
; }, -}; +}); diff --git a/components/progress/props.ts b/components/progress/props.ts new file mode 100644 index 000000000..a18b1c7c2 --- /dev/null +++ b/components/progress/props.ts @@ -0,0 +1,24 @@ +import PropTypes from '../_util/vue-types'; +import { tuple } from '../_util/type'; + +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 ProgressProps = { + prefixCls: PropTypes.string, + type: ProgressType, + percent: PropTypes.number, + successPercent: PropTypes.number, + format: PropTypes.func, + status: PropTypes.oneOf(ProgressStatuses), + showInfo: PropTypes.looseBool, + strokeWidth: PropTypes.number, + strokeLinecap: PropTypes.oneOf(['butt', 'round', 'square']), + strokeColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + trailColor: PropTypes.string, + width: PropTypes.number, + gapDegree: PropTypes.number, + gapPosition: PropTypes.oneOf(tuple('top', 'bottom', 'left', 'right')), + size: ProgressSize, +}; diff --git a/components/progress/utils.js b/components/progress/utils.ts similarity index 69% rename from components/progress/utils.js rename to components/progress/utils.ts index a2dd16ddf..14a930d88 100644 --- a/components/progress/utils.js +++ b/components/progress/utils.ts @@ -1,4 +1,4 @@ -export function validProgress(progress) { +export function validProgress(progress?: number) { if (!progress || progress < 0) { return 0; } From 90476bb1c5c70533b6c3a8679531ac73fec84628 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 20:53:38 +0800 Subject: [PATCH 7/7] fix: message unit test --- components/message/__tests__/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/message/__tests__/index.test.js b/components/message/__tests__/index.test.js index f87117a21..e60232232 100644 --- a/components/message/__tests__/index.test.js +++ b/components/message/__tests__/index.test.js @@ -121,7 +121,7 @@ describe('message', () => { }, 0); }); it('should allow custom icon', async () => { - message.open({ content: 'Message', icon: () => }); // eslint-disable-line + message.open({ content: 'Message', icon: }); await asyncExpect(() => { expect(document.querySelectorAll('.anticon-smile').length).toBe(1); }, 0);