Skip to content

Commit 202afb8

Browse files
authored
feat(circle,line): enable indeterminate mode (#216)
* feat(circle,line): enable indeterminate mode * add loading property to enable indeterminate mode * update code * fix coverage and generate id * fix: extra space, type and coverage * fix lint
1 parent bfe42ea commit 202afb8

File tree

11 files changed

+183
-4
lines changed

11 files changed

+183
-4
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ export default () => (
136136
<td>top</td>
137137
<td>the gap position: can be `top`, `bottom`, `left`, or `right`. </td>
138138
</tr>
139+
<tr>
140+
<td>loading</td>
141+
<td>Boolean</td>
142+
<td>false</td>
143+
<td>If it is true the indeterminate progress will be enabled.</td>
144+
</tr>
139145
</tbody>
140146
</table>
141147

docs/demo/loading.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: loading
3+
nav:
4+
title: Demo
5+
path: /demo
6+
---
7+
8+
<code src="../examples/loading.tsx"></code>

docs/examples/loading.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as React from 'react';
2+
import { Line, Circle } from 'rc-progress';
3+
4+
const Loading = () => {
5+
return (
6+
<div style={{ margin: 10, width: 200 }}>
7+
<Circle loading percent={10} />
8+
<Line loading percent={50} />
9+
</div>
10+
);
11+
};
12+
13+
export default Loading;

src/Circle/index.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ProgressProps } from '../interface';
55
import useId from '../hooks/useId';
66
import PtgCircle from './PtgCircle';
77
import { VIEW_BOX_SIZE, getCircleStyle } from './util';
8+
import getIndeterminateCircle from '../utils/getIndeterminateCircle';
89

910
function toArray<T>(value: T | T[]): T[] {
1011
const mergedValue = value ?? [];
@@ -26,6 +27,7 @@ const Circle: React.FC<ProgressProps> = (props) => {
2627
className,
2728
strokeColor,
2829
percent,
30+
loading,
2931
...restProps
3032
} = {
3133
...defaultProps,
@@ -51,6 +53,10 @@ const Circle: React.FC<ProgressProps> = (props) => {
5153
>;
5254
const isConicGradient = gradient && typeof gradient === 'object';
5355
const mergedStrokeLinecap = isConicGradient ? 'butt' : strokeLinecap;
56+
const { indeterminateStyleProps, indeterminateStyleAnimation } = getIndeterminateCircle({
57+
id: mergedId,
58+
loading,
59+
});
5460

5561
const circleStyle = getCircleStyle(
5662
perimeter,
@@ -94,7 +100,7 @@ const Circle: React.FC<ProgressProps> = (props) => {
94100
radius={radius}
95101
prefixCls={prefixCls}
96102
gradientId={gradientId}
97-
style={circleStyleForStack}
103+
style={{ ...circleStyleForStack, ...indeterminateStyleProps }}
98104
strokeLinecap={mergedStrokeLinecap}
99105
strokeWidth={strokeWidth}
100106
gapDegree={gapDegree}
@@ -180,6 +186,7 @@ const Circle: React.FC<ProgressProps> = (props) => {
180186
/>
181187
)}
182188
{stepCount ? getStepStokeList() : getStokeList()}
189+
{indeterminateStyleAnimation}
183190
</svg>
184191
);
185192
};

src/Line.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import * as React from 'react';
22
import classNames from 'classnames';
33
import { useTransitionDuration, defaultProps } from './common';
44
import type { ProgressProps } from './interface';
5+
import getIndeterminateLine from './utils/getIndeterminateLine';
6+
import useId from './hooks/useId';
57

68
const Line: React.FC<ProgressProps> = (props) => {
79
const {
10+
id,
811
className,
912
percent,
1013
prefixCls,
@@ -15,12 +18,15 @@ const Line: React.FC<ProgressProps> = (props) => {
1518
trailColor,
1619
trailWidth,
1720
transition,
21+
loading,
1822
...restProps
1923
} = {
2024
...defaultProps,
2125
...props,
2226
};
2327

28+
const mergedId = useId(id);
29+
2430
// eslint-disable-next-line no-param-reassign
2531
delete restProps.gapPosition;
2632
const percentList = Array.isArray(percent) ? percent : [percent];
@@ -34,6 +40,14 @@ const Line: React.FC<ProgressProps> = (props) => {
3440
L ${strokeLinecap === 'round' ? right : 100},${center}`;
3541
const viewBoxString = `0 0 100 ${strokeWidth}`;
3642
let stackPtg = 0;
43+
const { indeterminateStyleProps, indeterminateStyleAnimation } = getIndeterminateLine({
44+
id: mergedId,
45+
loading,
46+
percent: percentList[0],
47+
strokeLinecap,
48+
strokeWidth,
49+
});
50+
3751
return (
3852
<svg
3953
className={classNames(`${prefixCls}-line`, className)}
@@ -69,6 +83,7 @@ const Line: React.FC<ProgressProps> = (props) => {
6983
transition:
7084
transition ||
7185
'stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear',
86+
...indeterminateStyleProps,
7287
};
7388
const color = strokeColorList[index] || strokeColorList[strokeColorList.length - 1];
7489
stackPtg += ptg;
@@ -93,6 +108,7 @@ const Line: React.FC<ProgressProps> = (props) => {
93108
/>
94109
);
95110
})}
111+
{indeterminateStyleAnimation}
96112
</svg>
97113
);
98114
};

src/common.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const defaultProps: Partial<ProgressProps> = {
1010
trailColor: '#D9D9D9',
1111
trailWidth: 1,
1212
gapPosition: 'bottom',
13+
loading: false,
1314
};
1415

1516
export const useTransitionDuration = (): SVGPathElement[] => {

src/interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ProgressProps {
1414
transition?: string;
1515
onClick?: React.MouseEventHandler;
1616
steps?: number | { count: number; gap: number };
17+
loading?: boolean;
1718
}
1819

1920
export type StrokeColorObject = Record<string, string | boolean>;

src/utils/getIndeterminateCircle.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
3+
interface IndeterminateOption {
4+
id: string;
5+
loading: boolean;
6+
}
7+
8+
export default ({ id, loading }: IndeterminateOption) => {
9+
if (!loading) {
10+
return {
11+
indeterminateStyleProps: {},
12+
indeterminateStyleAnimation: null,
13+
};
14+
}
15+
16+
const animationName = `${id}-indeterminate-animate`;
17+
18+
return {
19+
indeterminateStyleProps: {
20+
transform: 'rotate(0deg)',
21+
animation: `${animationName} 1s linear infinite`,
22+
},
23+
indeterminateStyleAnimation: (
24+
<style>
25+
{`@keyframes ${animationName} {
26+
0% { transform: rotate(0deg); }
27+
100% { transform: rotate(360deg); }
28+
}`}
29+
</style>
30+
),
31+
};
32+
};

src/utils/getIndeterminateLine.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { StrokeLinecapType } from '@/interface';
2+
import React from 'react';
3+
4+
interface IndeterminateOption {
5+
id: string;
6+
loading: boolean;
7+
percent: number;
8+
strokeLinecap: StrokeLinecapType;
9+
strokeWidth: number;
10+
}
11+
12+
export default (options: IndeterminateOption) => {
13+
const { id, percent, strokeLinecap, strokeWidth, loading } = options;
14+
if (!loading) {
15+
return {
16+
indeterminateStyleProps: {},
17+
indeterminateStyleAnimation: null,
18+
};
19+
}
20+
const animationName = `${id}-indeterminate-animate`;
21+
const strokeDashOffset = 100 - (percent + (strokeLinecap === 'round' ? strokeWidth : 0));
22+
23+
return {
24+
indeterminateStyleProps: {
25+
strokeDasharray: `${percent} 100`,
26+
animation: `${animationName} .6s linear alternate infinite`,
27+
strokeDashoffset: 0,
28+
},
29+
indeterminateStyleAnimation: (
30+
<style>
31+
{`@keyframes ${animationName} {
32+
0% { stroke-dashoffset: 0; }
33+
100% { stroke-dashoffset: -${strokeDashOffset};
34+
}`}
35+
</style>
36+
),
37+
};
38+
};

tests/__snapshots__/index.spec.js.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ exports[` 1`] = `
2424
stroke="#2db7f5"
2525
stroke-linecap="butt"
2626
stroke-width="1"
27-
style="stroke-dasharray: 20px, 100px; stroke-dashoffset: -0px; transition: stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear; transition-duration: .3s, .3s, .3s, .06s;"
27+
style="stroke-dasharray: 20px, 100px; stroke-dashoffset: -0px; transition: stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear; transition-duration: 0s, 0s;"
2828
/>
2929
</svg>
3030
<br />
@@ -50,7 +50,7 @@ exports[` 1`] = `
5050
stroke="#2db7f5"
5151
stroke-linecap="round"
5252
stroke-width="1"
53-
style="stroke-dasharray: 19.8px, 100px; stroke-dashoffset: -0px; transition: stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear; transition-duration: .3s, .3s, .3s, .06s;"
53+
style="stroke-dasharray: 19.8px, 100px; stroke-dashoffset: -0px; transition: stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear; transition-duration: 0s, 0s;"
5454
/>
5555
</svg>
5656
<br />
@@ -76,7 +76,7 @@ exports[` 1`] = `
7676
stroke="#2db7f5"
7777
stroke-linecap="square"
7878
stroke-width="1"
79-
style="stroke-dasharray: 19.9px, 100px; stroke-dashoffset: -0px; transition: stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear; transition-duration: .3s, .3s, .3s, .06s;"
79+
style="stroke-dasharray: 19.9px, 100px; stroke-dashoffset: -0px; transition: stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear; transition-duration: 0s, 0s;"
8080
/>
8181
</svg>
8282
</div>

tests/indeterminate.spec.tsx

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import { Circle, Line } from '../src';
3+
import { render } from '@testing-library/react';
4+
5+
describe('(Circle | Line).indeterminate', () => {
6+
describe('Line', () => {
7+
it('should render indeterminate style', () => {
8+
const { container, rerender } = render(<Line loading />);
9+
const line: HTMLElement = container.querySelector('.rc-progress-line-path');
10+
11+
expect(line.style.animation).toContain('indeterminate-animate');
12+
13+
rerender(<Line />);
14+
15+
expect(line.style.animation).not.toContain('indeterminate-animate');
16+
});
17+
18+
it('should render indeterminate with percent and rerennder without it', () => {
19+
const { container, rerender } = render(<Line percent={20} loading />);
20+
const line: HTMLElement = container.querySelector('.rc-progress-line-path');
21+
22+
expect(line.style.animation).toContain('indeterminate-animate');
23+
expect(line.style.strokeDasharray).toEqual('20 100');
24+
25+
rerender(<Line percent={20} />);
26+
27+
expect(line.style.animation).not.toContain('indeterminate-animate');
28+
expect(line.style.strokeDasharray).not.toEqual('20 100');
29+
});
30+
});
31+
32+
describe('Circle', () => {
33+
it('should render indeterminate style', () => {
34+
const { container, rerender } = render(<Circle loading />);
35+
const circle: HTMLElement = container.querySelector('.rc-progress-circle-path');
36+
37+
expect(circle.style.animation).toContain('indeterminate-animate');
38+
39+
rerender(<Circle />);
40+
41+
expect(circle.style.animation).not.toContain('indeterminate-animate');
42+
});
43+
44+
it('should rerender indeterminate with percent Circle', () => {
45+
const { container, rerender } = render(<Circle percent={20} loading />);
46+
const circle: HTMLElement = container.querySelector('.rc-progress-circle-path');
47+
48+
expect(circle.style.animation).toContain('indeterminate-animate');
49+
expect(circle.style.transform).toEqual('rotate(0deg)');
50+
51+
rerender(<Circle percent={20} />);
52+
53+
expect(circle.style.animation).not.toContain('indeterminate-animate');
54+
expect(circle.style.transform).not.toEqual('rotate(0deg)');
55+
});
56+
});
57+
});

0 commit comments

Comments
 (0)