refactor(progress): use composition API (#4355)

* refactor(progress): use composition API

* refactor(vc-progress): update
refactor-progress^2
John 2021-07-11 15:25:40 +08:00 committed by GitHub
parent 8ce46ab1a1
commit 2ba963babf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1237 additions and 678 deletions

View File

@ -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<ExtractPropTypes<typeof circleProps>>;
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<CSSProperties>(() => {
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 () => (
<div class={wrapperClassName.value} style={circleStyle.value}>
<VCCircle
percent={percent.value}
strokeWidth={circleWidth.value}
trailWidth={circleWidth.value}
strokeColor={strokeColor.value}
strokeLinecap={props.strokeLinecap}
trailColor={props.trailColor}
prefixCls={props.prefixCls}
gapDegree={gapDeg.value}
gapPosition={gapPos.value}
/>
{slots.default?.()}
</div>
);
},
});

View File

@ -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<Direction>,
},
};
export type LineProps = Partial<ExtractPropTypes<typeof lineProps>>;
/**
* {
* '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<CSSProperties>(() => {
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<CSSProperties>(() => {
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 ? (
<div class={`${props.prefixCls}-success-bg`} style={successPercentStyle.value} />
) : null,
);
return () => (
<>
<div class={`${props.prefixCls}-outer`}>
<div class={`${props.prefixCls}-inner`} style={trailStyle.value}>
<div class={`${props.prefixCls}-bg`} style={percentStyle.value} />
{successSegment.value}
</div>
</div>
{slots.default?.()}
</>
);
},
});

View File

@ -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<ProgressSize>,
},
strokeColor: PropTypes.string,
trailColor: PropTypes.string,
};
export type StepsProps = Partial<ExtractPropTypes<typeof stepsProps>>;
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(
<div
key={i}
class={cls}
style={{
backgroundColor: i <= current.value - 1 ? strokeColor : trailColor,
width: `${stepWidth.value}px`,
height: `${strokeWidth}px`,
}}
/>,
);
}
return temp;
});
return () => (
<div class={`${props.prefixCls}-steps-outer`}>
{styledSteps.value}
{slots.default?.()}
</div>
);
},
});

View File

@ -1,7 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Progress render dashboard 295 gapDegree 1`] = `
<div class="ant-progress ant-progress-circle ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-inner" style="width: 120px; height: 120px; font-size: 24px;"><svg class="ant-progress-circle" viewBox="0 0 100 100">
<!---->
<path d="M 50,50 m 0,47
a 47,47 0 1 1 0,-94
a 47,47 0 1 1 0,94" stroke-linecap="round" stroke-width="6" fill-opacity="0" class="ant-progress-circle-trail" style="stroke-dasharray: 0.3097094374405742px 295.3097094374406px; stroke-dashoffset: -147.5px; transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;"></path>
<path d="M 50,50 m 0,47
a 47,47 0 1 1 0,-94
a 47,47 0 1 1 0,94" stroke="" stroke-linecap="round" stroke-width="6" opacity="0" fill-opacity="0" class="ant-progress-circle-path" style="stroke-dasharray: 0px 295.3097094374406px; stroke-dashoffset: -147.5px; transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;"></path>
</svg><span class="ant-progress-text" title="0%">0%</span></div>
</div>
`;
exports[`Progress render dashboard 296 gapDegree 1`] = `
<div class="ant-progress ant-progress-circle ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-inner" style="width: 120px; height: 120px; font-size: 24px;"><svg class="ant-progress-circle" viewBox="0 0 100 100">
<!---->
<path d="M 50,50 m 0,47
a 47,47 0 1 1 0,-94
a 47,47 0 1 1 0,94" stroke-linecap="round" stroke-width="6" fill-opacity="0" class="ant-progress-circle-trail" style="stroke-dasharray: -0.6902905625594258px 295.3097094374406px; stroke-dashoffset: -148px; transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;"></path>
<path d="M 50,50 m 0,47
a 47,47 0 1 1 0,-94
a 47,47 0 1 1 0,94" stroke="" stroke-linecap="round" stroke-width="6" opacity="0" fill-opacity="0" class="ant-progress-circle-path" style="stroke-dasharray: 0px 295.3097094374406px; stroke-dashoffset: -148px; transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;"></path>
</svg><span class="ant-progress-text" title="0%">0%</span></div>
</div>
`;
exports[`Progress render dashboard zero gapDegree 1`] = `
<div class="ant-progress ant-progress-circle ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-inner" style="width: 120px; height: 120px; font-size: 24px;"><svg class="ant-progress-circle" viewBox="0 0 100 100">
<!---->
<path d="M 50,50 m 0,47
a 47,47 0 1 1 0,-94
a 47,47 0 1 1 0,94" stroke-linecap="round" stroke-width="6" fill-opacity="0" class="ant-progress-circle-trail" style="stroke-dasharray: 295.3097094374406px 295.3097094374406px; stroke-dashoffset: -0px; transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;"></path>
<path d="M 50,50 m 0,47
a 47,47 0 1 1 0,-94
a 47,47 0 1 1 0,94" stroke="" stroke-linecap="round" stroke-width="6" opacity="0" fill-opacity="0" class="ant-progress-circle-path" style="stroke-dasharray: 0px 295.3097094374406px; stroke-dashoffset: -0px; transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;"></path>
</svg><span class="ant-progress-text" title="0%">0%</span></div>
</div>
`;
exports[`Progress render format 1`] = `
<div class="ant-progress ant-progress-circle ant-progress-status-normal ant-progress-show-info ant-progress-default">
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 50%; height: 8px;"></div>
<div class="ant-progress-success-bg" style="width: 10%; height: 8px;"></div>
</div>
</div><span class="ant-progress-text" title="50 10">50 10</span>
</div>
`;
exports[`Progress render negative progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 0%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="0%">0%</span>
</div>
`;
exports[`Progress render negative successPercent 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 50%; height: 8px;"></div>
<div class="ant-progress-success-bg" style="width: 0%; height: 8px;"></div>
</div>
</div><span class="ant-progress-text" title="50%">50%</span>
</div>
`;
exports[`Progress render normal progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 0%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="0%">0%</span>
</div>
`;
exports[`Progress render out-of-range progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-success">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 100%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></span></span>
</div>
`;
exports[`Progress render out-of-range progress with info 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-success">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 100%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></span></span>
</div>
`;
exports[`Progress render strokeColor 1`] = `
<div class="ant-progress ant-progress-circle ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-inner" style="width: 120px; height: 120px; font-size: 24px;"><svg class="ant-progress-circle" viewBox="0 0 100 100">
<!---->
<path d="M 50,50 m 0,-47
@ -14,80 +122,62 @@ exports[`Progress render format 1`] = `
</div>
`;
exports[`Progress render negetive progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-show-info ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 0%; height: 8px; border-radius: 100px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="0%">0%</span>
exports[`Progress render strokeColor 2`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 50%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="50%">50%</span>
</div>
`;
exports[`Progress render strokeColor 3`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 50%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="50%">50%</span>
</div>
`;
exports[`Progress render successColor progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 60%; height: 8px;"></div>
<div class="ant-progress-success-bg" style="width: 30%; height: 8px; background-color: rgb(255, 255, 255);"></div>
</div>
</div><span class="ant-progress-text" title="60%">60%</span>
</div>
`;
exports[`Progress render trailColor progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-outer">
<div class="ant-progress-inner" style="background-color: rgb(255, 255, 255);">
<div class="ant-progress-bg" style="width: 0%; height: 8px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="0%">0%</span>
</div>
`;
exports[`Progress should support steps 1`] = `
<div class="ant-progress ant-progress-line ant-progress-show-info ant-progress-default ant-progress-status-normal">
<div class="ant-progress-steps-outer">
<div class="ant-progress-steps-item" style="width: 14px; height: 8px;"></div>
<div class="ant-progress-steps-item" style="width: 14px; height: 8px;"></div>
<div class="ant-progress-steps-item" style="width: 14px; height: 8px;"></div><span class="ant-progress-text" title="0%">0%</span>
</div>
</div>
`;
exports[`Progress render negetive successPercent 1`] = `
<div class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-show-info ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 50%; height: 8px; border-radius: 100px;"></div>
<div class="ant-progress-success-bg" style="width: 0%; height: 8px;"></div>
</div>
</div><span class="ant-progress-text" title="50%">50%</span>
</div>
</div>
`;
exports[`Progress render negetive successPercent 2`] = `
<div class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-show-info ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 50%; height: 8px; border-radius: 100px;"></div>
<div class="ant-progress-success-bg" style="width: 10%; height: 8px;"></div>
</div>
</div><span class="ant-progress-text" title="50 10">50 10</span>
</div>
</div>
`;
exports[`Progress render normal progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-show-info ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 0%; height: 8px; border-radius: 100px;"></div>
<!---->
</div>
</div><span class="ant-progress-text" title="0%">0%</span>
</div>
</div>
`;
exports[`Progress render out-of-range progress 1`] = `
<div class="ant-progress ant-progress-line ant-progress-status-success ant-progress-show-info ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 100%; height: 8px; border-radius: 100px;"></div>
<!---->
</div>
</div><span class="ant-progress-text"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></span></span>
</div>
</div>
`;
exports[`Progress render out-of-range progress with info 1`] = `
<div class="ant-progress ant-progress-line ant-progress-status-success ant-progress-show-info ant-progress-default">
<div>
<div class="ant-progress-outer">
<div class="ant-progress-inner">
<div class="ant-progress-bg" style="width: 100%; height: 8px; border-radius: 100px;"></div>
<!---->
</div>
</div><span class="ant-progress-text"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></span></span>
</div>
exports[`Progress steps should have default percent 0 1`] = `
<div class="undefined-steps-outer">
<!---->
</div>
`;

View File

@ -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 <Progress status="normal" trailColor="#ffffff" />;
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('render successColor progress', () => {
const wrapper = mount({
render() {
return <Progress percent={60} success={{ percent: 30, strokeColor: '#ffffff' }} />;
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('render dashboard zero gapDegree', () => {
const wrapper = mount({
render() {
return <Progress type="dashboard" gapDegree={0} />;
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('render dashboard 295 gapDegree', () => {
const wrapper = mount({
render() {
return <Progress type="dashboard" gapDegree={295} />;
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('render dashboard 296 gapDegree', () => {
const wrapper = mount({
render() {
return <Progress type="dashboard" gapDegree={296} />;
},
});
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 <Progress percent={100} />;
},
});
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 <Progress percent={100} status={undefined} />;
},
});
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 <Progress percent={100} status="invalid" />;
},
});
expect(wrapper.findAll('.ant-progress-status-success')).toHaveLength(1);
errorSpy.mockRestore();
});
it('should support steps', () => {
const wrapper = mount({
render() {
return <Progress steps={3} />;
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('steps should be changable', async () => {
const wrapper = mount({
render() {
return <Progress steps={5} percent={60} />;
},
});
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 <Progress steps={5} percent={60} strokeColor="#1890ff" />;
},
});
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(<Progress steps={5} percent={20} trailColor="#1890ee" />);
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 <Progress steps={9} percent={22.22} />;
},
});
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 <ProgressSteps />;
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should warning if use `progress` in success', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Progress percent={60} success={{ progress: 30 }} />);
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 <Progress percent={60} success={{ progress: 30 }} type="circle" />;
},
});
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [ant-design-vue: Progress] `success.progress` is deprecated. Please use `success.percent` instead.',
);
});
});

View File

@ -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<string, string> = {
normal: '#108ee9',
exception: '#ff5500',
success: '#87d068',
};
export type ICircleProps = ExtractPropTypes<typeof CircleProps>;
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 (
<div class={wrapperClassName} style={circleStyle}>
<VCCircle
percent={getPercentage(props)}
strokeWidth={circleWidth}
trailWidth={circleWidth}
strokeColor={strokeColor}
strokeLinecap={strokeLinecap}
trailColor={trailColor}
prefixCls={prefixCls}
gapDegree={gapDeg}
gapPosition={gapPos}
/>
{slots?.default()}
</div>
);
};
},
});
export default Circle;

View File

@ -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);

View File

@ -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 ? (
<div class={`${prefixCls}-success-bg`} style={successPercentStyle} />
) : null;
return (
<div>
<div class={`${prefixCls}-outer`}>
<div class={`${prefixCls}-inner`}>
<div class={`${prefixCls}-bg`} style={percentStyle} />
{successSegment}
</div>
</div>
{slots?.default()}
</div>
);
};
export default Line;

View File

@ -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}
</span>
);
},
},
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;
// Render progress shape
if (type === 'line') {
const lineProps = {
...props,
prefixCls,
};
progress = <Line {...lineProps}>{progressInfo}</Line>;
} else if (type === 'circle' || type === 'dashboard') {
const circleProps = {
...props,
prefixCls,
progressStatus,
};
progress = <Circle {...circleProps}>{progressInfo}</Circle>;
}
const classString = classNames(prefixCls, {
[`${prefixCls}-${(type === 'dashboard' && 'circle') || type}`]: true,
[`${prefixCls}-status-${progressStatus}`]: true,
[`${prefixCls}-show-info`]: showInfo,
[`${prefixCls}-${size}`]: size,
});
const progressProps = {
class: classString,
};
return <div {...progressProps}>{progress}</div>;
return () => {
const { type, steps, strokeColor } = props;
const progressStatus = getProgressStatus();
const progressInfo = renderProcessInfo(prefixCls.value, progressStatus);
devWarning(
props.successPercent == undefined,
'Progress',
'`successPercent` is deprecated. Please use `success.percent` instead.',
);
let progress: VNodeChild;
// Render progress shape
if (type === 'line') {
progress = steps ? (
<Steps
{...props}
strokeColor={typeof strokeColor === 'string' ? strokeColor : undefined}
prefixCls={prefixCls.value}
steps={steps}
>
{progressInfo}
</Steps>
) : (
<Line {...props} prefixCls={prefixCls.value}>
{progressInfo}
</Line>
);
} else if (type === 'circle' || type === 'dashboard') {
progress = (
<Circle {...props} prefixCls={prefixCls.value}>
{progressInfo}
</Circle>
);
}
const classNames = {
...classString.value,
[`${prefixCls.value}-status-${progressStatus}`]: true,
};
return <div class={classNames}>{progress}</div>;
};
},
});

View File

@ -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<string | ProgressGradient>,
},
trailColor: PropTypes.string,
width: PropTypes.number,
success: {
type: Object as PropType<SuccessProps>,
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<ExtractPropTypes<typeof progressProps>>;

View File

@ -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';

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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 };

View File

@ -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 <path ref={c => (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 (
<svg class={`${prefixCls}-circle`} viewBox="0 0 100 100" {...restProps}>
{gradient && (
<defs>
<linearGradient
id={`${prefixCls}-gradient-${this.gradientId}`}
x1="100%"
y1="0%"
x2="0%"
y2="0%"
>
{Object.keys(gradient)
.sort((a, b) => stripPercentToNumber(a) - stripPercentToNumber(b))
.map((key, index) => (
<stop key={index} offset={key} stop-color={gradient[key]} />
))}
</linearGradient>
</defs>
)}
<path {...pathFirst} />
{this.getStokeList().reverse()}
</svg>
);
},
});
export default enhancer(Circle);

View File

@ -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 <path ref={c => (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 (
<svg class={`${prefixCls}-circle`} viewBox="0 0 100 100" {...restProps}>
{gradient && (
<defs>
<linearGradient
id={`${prefixCls}-gradient-${gradientId.value}`}
x1="100%"
y1="0%"
x2="0%"
y2="0%"
>
{Object.keys(gradient)
.sort((a, b) => stripPercentToNumber(a) - stripPercentToNumber(b))
.map((key, index) => (
<stop key={index} offset={key} stop-color={gradient[key]} />
))}
</linearGradient>
</defs>
)}
<path {...pathFirst} />
{getStokeList().reverse()}
</svg>
);
};
},
});

View File

@ -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 (
<svg
class={`${prefixCls}-line`}
viewBox={viewBoxString}
preserveAspectRatio="none"
{...restProps}
>
<path {...pathFirst} />
{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 <path ref={c => (this.paths[index] = c)} {...pathProps} />;
})}
</svg>
);
},
});
export default enhancer(Line);

View File

@ -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 (
<svg
class={`${prefixCls}-line`}
viewBox={viewBoxString.value}
preserveAspectRatio="none"
{...restProps}
>
<path {...pathFirst.value} />
{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 <path ref={c => (paths.value[index].value = c)} {...pathProps} />;
})}
</svg>
);
};
},
});

View File

@ -0,0 +1,43 @@
import type { Ref } from 'vue';
import { ref, onUpdated, computed } from 'vue';
import type { ProgressProps } from './types';
export const defaultProps: Partial<ProgressProps> = {
percent: 0,
prefixCls: 'vc-progress',
strokeColor: '#2db7f5',
strokeLinecap: 'round',
strokeWidth: 1,
trailColor: '#D9D9D9',
trailWidth: 1,
};
export const useTransitionDuration = (percentList: Ref<number[]>) => {
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;
};

View File

@ -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;

View File

@ -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,

View File

@ -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,
};

View File

@ -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<GapPositionType>,
},
percent: {
type: [Array, Number] as PropType<number | number[]>,
},
prefixCls: PropTypes.string,
strokeColor: {
type: [Object, String, Array] as PropType<StrokeColorType>,
},
strokeLinecap: {
type: String as PropType<StrokeLinecapType>,
},
strokeWidth: PropTypes.number,
trailColor: PropTypes.string,
trailWidth: PropTypes.number,
transition: PropTypes.string,
};
export type ProgressProps = Partial<ExtractPropTypes<typeof propTypes>>;