Skip to content

Commit db4a823

Browse files
authored
feat: Circle support conic color (#253)
* docs: update demo * chore: basic color * feat: support conic * feat: support conic * test: update snapshot
1 parent 8c3750f commit db4a823

File tree

11 files changed

+466
-148
lines changed

11 files changed

+466
-148
lines changed

docs/examples/gap.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { Circle, ProgressProps } from 'rc-progress';
2+
import { Circle, type ProgressProps } from 'rc-progress';
33

44
const colorMap = ['#3FC7FA', '#85D262', '#FE8C6A', '#FF5959', '#BC3FFA'];
55

@@ -11,7 +11,7 @@ class Example extends React.Component<ProgressProps, any> {
1111
constructor(props) {
1212
super(props);
1313
this.state = {
14-
percent: 30,
14+
percent: 100,
1515
colorIndex: 0,
1616
subPathsCount: 3,
1717
};
@@ -103,6 +103,30 @@ class Example extends React.Component<ProgressProps, any> {
103103
strokeColor={color}
104104
/>
105105
</div>
106+
<div style={circleContainerStyle}>
107+
<Circle
108+
percent={percent}
109+
gapDegree={70}
110+
strokeWidth={6}
111+
strokeColor={{
112+
'0%': 'red',
113+
'100%': 'blue',
114+
}}
115+
/>
116+
</div>
117+
<div style={circleContainerStyle}>
118+
<Circle
119+
percent={percent}
120+
gapDegree={70}
121+
strokeWidth={6}
122+
strokeColor={{
123+
conic: true,
124+
'0%': 'red',
125+
'99%': 'blue',
126+
'100%': 'green',
127+
}}
128+
/>
129+
</div>
106130
</div>
107131
);
108132
}

docs/examples/gradient-circle.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const Example = () => {
3434
}}
3535
/>
3636
</div>
37+
3738
<h3>Circle With Success Percent {65}%</h3>
3839
<div style={circleContainerStyle}>
3940
<Circle
@@ -49,6 +50,20 @@ const Example = () => {
4950
]}
5051
/>
5152
</div>
53+
54+
<h3>Circle colors</h3>
55+
<div style={circleContainerStyle}>
56+
<Circle
57+
percent={100}
58+
strokeWidth={6}
59+
strokeColor={{
60+
conic: true,
61+
'0%': 'green',
62+
'99%': 'red',
63+
'100%': 'blue',
64+
}}
65+
/>
66+
</div>
5267
</div>
5368
);
5469
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"rc-util": "^5.16.1"
4848
},
4949
"devDependencies": {
50+
"@testing-library/react": "^12.1.5",
5051
"@types/classnames": "^2.2.9",
5152
"@types/jest": "^27.5.0",
5253
"@types/keyv": "3.1.4",

src/Circle/ColorGradient.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react';
2+
3+
function stripPercentToNumber(percent: string) {
4+
return +percent.replace('%', '');
5+
}
6+
7+
export interface ColorGradientProps {
8+
gradientId: string;
9+
gradient?: Record<string, string>;
10+
}
11+
12+
export default function ColorGradient(props: ColorGradientProps) {
13+
const { gradientId, gradient } = props;
14+
15+
return (
16+
<defs>
17+
<linearGradient id={gradientId} x1="100%" y1="0%" x2="0%" y2="0%">
18+
{Object.keys(gradient)
19+
.sort((a, b) => stripPercentToNumber(a) - stripPercentToNumber(b))
20+
.map((key, index) => (
21+
<stop key={index} offset={key} stopColor={gradient[key]} />
22+
))}
23+
</linearGradient>
24+
</defs>
25+
);
26+
}

src/Circle/PtgCircle.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import * as React from 'react';
2+
import type { ProgressProps } from '..';
3+
import type { StrokeColorType } from '../interface';
4+
5+
export interface ColorGradientProps {
6+
prefixCls: string;
7+
gradientId: string;
8+
style: React.CSSProperties;
9+
ptg: number;
10+
radius: number;
11+
strokeLinecap: ProgressProps['strokeLinecap'];
12+
strokeWidth: ProgressProps['strokeWidth'];
13+
size: number;
14+
color: StrokeColorType;
15+
conic: boolean;
16+
gapDegree: number;
17+
}
18+
19+
const PtgCircle = React.forwardRef<SVGCircleElement, ColorGradientProps>((props, ref) => {
20+
const {
21+
prefixCls,
22+
color,
23+
gradientId,
24+
radius,
25+
style: circleStyleForStack,
26+
ptg,
27+
strokeLinecap,
28+
strokeWidth,
29+
size,
30+
conic,
31+
gapDegree,
32+
} = props;
33+
34+
const isGradient = color && typeof color === 'object';
35+
36+
const stroke = React.useMemo(() => {
37+
if (conic) {
38+
return '#FFF';
39+
}
40+
41+
return isGradient ? `url(#${gradientId})` : undefined;
42+
}, [gradientId, isGradient, conic]);
43+
44+
// ========================== Circle ==========================
45+
const halfSize = size / 2;
46+
47+
const circleNode = (
48+
<circle
49+
className={`${prefixCls}-circle-path`}
50+
r={radius}
51+
cx={halfSize}
52+
cy={halfSize}
53+
stroke={stroke}
54+
strokeLinecap={strokeLinecap}
55+
strokeWidth={strokeWidth}
56+
opacity={ptg === 0 ? 0 : 1}
57+
style={circleStyleForStack}
58+
ref={ref}
59+
/>
60+
);
61+
62+
// ========================== Render ==========================
63+
if (!conic) {
64+
return circleNode;
65+
}
66+
67+
const maskId = `${gradientId}-conic`;
68+
const conicColorKeys = Object.keys(color).filter((key) => key !== 'conic');
69+
70+
const fromDeg = gapDegree ? `${180 + gapDegree / 2}deg` : '0deg';
71+
72+
const conicColors = conicColorKeys.map((key) => {
73+
const parsedKey = parseFloat(key);
74+
const ptgKey = `${gapDegree ? Math.floor((parsedKey * (360 - gapDegree)) / 360) : parsedKey}%`;
75+
76+
return `${color[key]} ${ptgKey}`;
77+
});
78+
79+
const conicColorBg = `conic-gradient(from ${fromDeg}, ${conicColors.join(', ')})`;
80+
81+
return (
82+
<>
83+
<mask id={maskId}>{circleNode}</mask>
84+
85+
<foreignObject x={0} y={0} width={size} height={size} mask={`url(#${maskId})`}>
86+
<div
87+
style={{
88+
width: '100%',
89+
height: '100%',
90+
background: conicColorBg,
91+
}}
92+
/>
93+
</foreignObject>
94+
</>
95+
);
96+
});
97+
98+
if (process.env.NODE_ENV !== 'production') {
99+
PtgCircle.displayName = 'PtgCircle';
100+
}
101+
102+
export default PtgCircle;

0 commit comments

Comments
 (0)