Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(circle,line): enable indeterminate mode #216

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ export default () => (
<tr>
<td>percent</td>
<td>Number | Number[]</td>
<td>0</td>
<td>the percent of the progress</td>
<td>null</td>
<td>the percent of the progress, if it is not defined the indeterminate mode will be enabled</td>
</tr>
<tr>
<td>gapDegree</td>
Expand Down
3 changes: 3 additions & 0 deletions docs/demo/indeterminate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## indeterminate

<code src="../examples/indeterminate.tsx">
13 changes: 13 additions & 0 deletions docs/examples/indeterminate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import { Line, Circle } from 'rc-progress';

const Indeterminate = () => {
return (
<div style={{ margin: 10, width: 200 }}>
<Circle />
<Line />
</div>
);
};

export default Indeterminate;
18 changes: 10 additions & 8 deletions src/Circle.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import * as React from 'react';
import classNames from 'classnames';
import { defaultProps, useTransitionDuration } from './common';
import { defaultProps, useTransitionDuration, toArray } from './common';
import type { ProgressProps } from './interface';
import useId from './hooks/useId';
import getIndeterminateCircle from './utils/getIndeterminateCircle';

function stripPercentToNumber(percent: string) {
return +percent.replace('%', '');
}

function toArray<T>(value: T | T[]): T[] {
const mergedValue = value ?? [];
return Array.isArray(mergedValue) ? mergedValue : [mergedValue];
}

const VIEW_BOX_SIZE = 100;

const getCircleStyle = (
Expand Down Expand Up @@ -86,6 +82,11 @@ const Circle: React.FC<ProgressProps> = ({
const perimeterWithoutGap = perimeter * ((360 - gapDegree) / 360);
const { count: stepCount, space: stepSpace } =
typeof steps === 'object' ? steps : { count: steps, space: 2 };
const {
indeterminateStylePops,
indeterminateStyleTag,
percent: _percent,
} = getIndeterminateCircle({ percent });

const circleStyle = getCircleStyle(
perimeter,
Expand All @@ -99,7 +100,7 @@ const Circle: React.FC<ProgressProps> = ({
strokeLinecap,
strokeWidth,
);
const percentList = toArray(percent);
const percentList = toArray(_percent);
const strokeColorList = toArray(strokeColor);
const gradient = strokeColorList.find((color) => color && typeof color === 'object');

Expand Down Expand Up @@ -135,7 +136,7 @@ const Circle: React.FC<ProgressProps> = ({
strokeLinecap={strokeLinecap}
strokeWidth={strokeWidth}
opacity={ptg === 0 ? 0 : 1}
style={circleStyleForStack}
style={{ ...circleStyleForStack, ...indeterminateStylePops }}
ref={(elem) => {
// https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
// React will call the ref callback with the DOM element when the component mounts,
Expand Down Expand Up @@ -229,6 +230,7 @@ const Circle: React.FC<ProgressProps> = ({
/>
)}
{stepCount ? getStepStokeList() : getStokeList()}
{indeterminateStyleTag}
</svg>
);
};
Expand Down
12 changes: 10 additions & 2 deletions src/Line.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import classNames from 'classnames';
import { useTransitionDuration, defaultProps } from './common';
import { useTransitionDuration, defaultProps, toArray } from './common';
import type { ProgressProps } from './interface';
import getIndeterminateLine from './utils/getIndeterminateLine';

const Line: React.FC<ProgressProps> = ({
className,
Expand All @@ -18,7 +19,12 @@ const Line: React.FC<ProgressProps> = ({
}) => {
// eslint-disable-next-line no-param-reassign
delete restProps.gapPosition;
const percentList = Array.isArray(percent) ? percent : [percent];
const {
indeterminateStylePops,
indeterminateStyleTag,
percent: _percent,
} = getIndeterminateLine({ percent, strokeLinecap, strokeWidth });
const percentList = toArray(_percent);
const strokeColorList = Array.isArray(strokeColor) ? strokeColor : [strokeColor];

const paths = useTransitionDuration();
Expand Down Expand Up @@ -64,6 +70,7 @@ const Line: React.FC<ProgressProps> = ({
transition:
transition ||
'stroke-dashoffset 0.3s ease 0s, stroke-dasharray .3s ease 0s, stroke 0.3s linear',
...indeterminateStylePops,
};
const color = strokeColorList[index] || strokeColorList[strokeColorList.length - 1];
stackPtg += ptg;
Expand All @@ -88,6 +95,7 @@ const Line: React.FC<ProgressProps> = ({
/>
);
})}
{indeterminateStyleTag}
</svg>
);
};
Expand Down
7 changes: 6 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ProgressProps } from './interface';

export const defaultProps: Partial<ProgressProps> = {
className: '',
percent: 0,
percent: null,
prefixCls: 'rc-progress',
strokeColor: '#2db7f5',
strokeLinecap: 'round',
Expand Down Expand Up @@ -43,3 +43,8 @@ export const useTransitionDuration = (): SVGPathElement[] => {

return pathsRef.current;
};

export const toArray = <T>(value: T | T[]): T[] => {
const mergedValue = value ?? [];
return Array.isArray(mergedValue) ? mergedValue : [mergedValue];
};
32 changes: 32 additions & 0 deletions src/utils/getIndeterminateCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
interface IndeterminateOption {
percent: number | number[];
}

export default (options: IndeterminateOption) => {
if (options.percent !== null) {
return {
...options,
indeterminateStylePops: {},
indeterminateStyleTag: null,
};
}

const animationName = 'circle-indeterminate-animate';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not hard code this. Follow id generation logic to dynamic create this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zombieJ
Just added could you check please, and one more thing is when we are using useId hook inside Line component it performs another render which changes the transition-duration like in Circle, so that's why i updated the snapshot.

arfter this change i tested on demo and i see no impacts.

const percent = 40;

return {
percent,
indeterminateStylePops: {
transform: 'rotate(0deg)',
animation: `${animationName} 1s linear infinite`,
},
indeterminateStyleTag: (
<style>
{`@keyframes ${animationName} {
0 % { transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}`}
Comment on lines +25 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

修复 keyframes 语法中的空格问题

当前 keyframes 定义中的百分比值与大括号之间存在多余的空格,这可能导致样式解析错误。

建议修改如下:

 {`@keyframes ${animationName} {
-    0 % { transform: rotate(0deg);}
-    100% {transform: rotate(360deg);}
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
   }`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{`@keyframes ${animationName} {
0 % { transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}`}
{`@keyframes ${animationName} {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}`}

</style>
),
};
};
36 changes: 36 additions & 0 deletions src/utils/getIndeterminateLine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
interface IndeterminateOption {
percent: number | number[];
strokeLinecap: string;
strokeWidth: number;
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议使用更严格的类型定义

strokeLinecap 应使用 StrokeLinecapType 类型而不是 string,以确保类型安全。

建议修改如下:

 interface IndeterminateOption {
   id: string;
   loading: boolean;
   percent: number;
-  strokeLinecap: string;
+  strokeLinecap: 'round' | 'butt' | 'square';
   strokeWidth: number;
 }

}

export default (options: IndeterminateOption) => {
if (options.percent !== null) {
return {
percent: options.percent,
indeterminateStylePops: {},
indeterminateStyleTag: null,
};
}
const animationName = 'line-indeterminate-animate';
const percent = 40;
const strokeDashOffset =
100 - (percent + (options.strokeLinecap === 'round' ? options.strokeWidth : 0));

return {
percent,
indeterminateStylePops: {
strokeDasharray: `${percent} 100`,
animation: `${animationName} .6s linear alternate infinite`,
strokeDashoffset: 0,
},
indeterminateStyleTag: (
<style>
{`@keyframes ${animationName} {
0% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: -${strokeDashOffset};
}`}
</style>
),
};
};
22 changes: 22 additions & 0 deletions tests/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,26 @@ describe('Progress', () => {
expect(circle.find(Circle).props().percent).toEqual([20, 20, 20, 20]);
circle.unmount();
});

it('should support indeterminate mode', () => {
const Indeterminate = () => {
return (
<>
<Circle />
<Line />
SaidMarar marked this conversation as resolved.
Show resolved Hide resolved
</>
);
};

const wrapper = mount(<Indeterminate />);
expect(wrapper.find(Circle).find('style')).toBeDefined();
expect(
wrapper.find(Circle).find('.rc-progress-circle-path').at(0).getDOMNode().style.animation,
).toContain('circle-indeterminate-animate');
expect(wrapper.find(Line).find('style')).toBeDefined();
expect(
wrapper.find(Line).find('.rc-progress-line-path').at(0).getDOMNode().style.animation,
).toContain('line-indeterminate-animate');
wrapper.unmount();
});
});