cynx-ui 1.2.36 → 1.2.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,14 +10,16 @@ const SIZES = {
10
10
 
11
11
  export default function CircularProgress({
12
12
  value = 0,
13
+ segments,
13
14
  size = 'md',
14
15
  customSize,
15
16
  color = 'var(--blue)',
16
17
  trackColor = 'var(--bd2, #e0e0e4)',
17
18
  label = '',
19
+ centerText,
18
20
  showPercent = true,
19
21
  animated = true,
20
- rounded = false,
22
+ rounded = true,
21
23
  indeterminate = false,
22
24
  speed = 1,
23
25
  strokeWidth,
@@ -35,38 +37,36 @@ export default function CircularProgress({
35
37
  const c = 2 * Math.PI * r;
36
38
  const pct = Math.min(100, Math.max(0, value));
37
39
  const BIG = c * 4;
38
- const cap = rounded ? 'round' : 'butt';
40
+ const isRounded = rounded !== false;
41
+ const cap = isRounded ? 'round' : 'butt';
39
42
 
40
- const userGap = gap !== undefined ? gap : (rounded ? c * 0.05 : c * 0.03);
41
- const capOverrun = rounded ? stroke : 0;
43
+ const userGap = gap !== undefined ? gap : (isRounded ? c * 0.05 : c * 0.03);
44
+ const capOverrun = isRounded ? stroke : 0;
42
45
  const gapSize = userGap + capOverrun;
43
46
  const total = c - 2 * gapSize;
44
47
 
45
48
  const isFull = pct >= 100;
46
49
  const isEmpty = pct <= 0;
47
50
 
51
+ const hasSegments = Array.isArray(segments) && segments.length > 0;
52
+ const rawSum = hasSegments ? segments.reduce((sum, s) => sum + (Number(s.value) || 0), 0) : 0;
53
+
48
54
  let indDash, indOffset, trackDash, trackOffset, indOpacity;
49
- let indStartDeg, indEndDeg, trackStartDeg, trackEndDeg;
50
55
 
51
56
  if (isEmpty) {
52
57
  indDash = `0,${BIG}`; indOffset = 0;
53
58
  trackDash = `${c},${BIG}`; trackOffset = 0;
54
59
  indOpacity = 0;
55
- indStartDeg = indEndDeg = trackStartDeg = trackEndDeg = 0;
56
60
  } else if (isFull) {
57
61
  indDash = `${c + stroke * 2},${BIG}`; indOffset = 0;
58
62
  trackDash = `0,${BIG}`; trackOffset = 0;
59
63
  indOpacity = 1;
60
- indStartDeg = 0; indEndDeg = 360;
61
- trackStartDeg = trackEndDeg = 0;
62
64
  } else {
63
65
  const activeLen = (pct / 100) * total;
64
66
  const inactiveLen = total - activeLen;
65
67
  indDash = `${activeLen},${BIG}`; indOffset = 0;
66
68
  trackDash = `${inactiveLen},${BIG}`; trackOffset = -(activeLen + gapSize);
67
69
  indOpacity = 1;
68
- indStartDeg = 0; indEndDeg = (pct / 100) * 360;
69
- trackStartDeg = (pct / 100) * 360; trackEndDeg = 360;
70
70
  }
71
71
 
72
72
  const gRef = useRef(null);
@@ -118,6 +118,59 @@ export default function CircularProgress({
118
118
  fill="none" stroke={color} strokeWidth={stroke} strokeLinecap={cap}
119
119
  />
120
120
  </g>
121
+ ) : hasSegments ? (
122
+ <>
123
+ {rawSum <= 0 ? (
124
+ <circle
125
+ cx={cx} cy={cy} r={r}
126
+ fill="none" stroke={trackColor} strokeWidth={stroke}
127
+ />
128
+ ) : (
129
+ (() => {
130
+ const activeSegs = segments.filter((seg) => (Number(seg.value) || 0) > 0);
131
+ const segCount = activeSegs.length;
132
+ if (segCount === 0) {
133
+ return (
134
+ <circle
135
+ cx={cx} cy={cy} r={r}
136
+ fill="none" stroke={trackColor} strokeWidth={stroke}
137
+ />
138
+ );
139
+ }
140
+
141
+ const gapPx = gap !== undefined ? gap : 4;
142
+ const capOverrun = isRounded ? stroke : 0;
143
+ const gapArc = segCount > 1 ? (gapPx + capOverrun) : 0;
144
+ const availableC = Math.max(0, c - segCount * gapArc);
145
+
146
+ let accumulatedArc = isRounded ? stroke / 2 : 0;
147
+
148
+ return activeSegs.map((seg, idx) => {
149
+ const val = Number(seg.value) || 0;
150
+ const segLen = (val / rawSum) * availableC;
151
+ const currentOffset = accumulatedArc;
152
+ accumulatedArc += segLen + gapArc;
153
+
154
+ return (
155
+ <circle
156
+ key={idx}
157
+ cx={cx} cy={cy} r={r}
158
+ fill="none" stroke={seg.color || color} strokeWidth={stroke}
159
+ strokeLinecap={cap}
160
+ strokeDasharray={`${Math.max(0.1, segLen).toFixed(2)}, ${BIG}`}
161
+ strokeDashoffset={(-currentOffset).toFixed(2)}
162
+ style={{
163
+ transform: 'rotate(-90deg)', transformOrigin: '50% 50%',
164
+ transition: animated ? 'stroke-dasharray .5s cubic-bezier(0.4,0,0.2,1), stroke-dashoffset .5s cubic-bezier(0.4,0,0.2,1)' : 'none',
165
+ }}
166
+ >
167
+ {seg.label && <title>{`${seg.label}: ${val}`}</title>}
168
+ </circle>
169
+ );
170
+ });
171
+ })()
172
+ )}
173
+ </>
121
174
  ) : (
122
175
  <>
123
176
  <circle
@@ -151,7 +204,7 @@ export default function CircularProgress({
151
204
  fontSize, fontWeight: 600, color: 'var(--primary-text)',
152
205
  fontVariantNumeric: 'tabular-nums',
153
206
  }}>
154
- {Math.round(pct)}%
207
+ {centerText !== undefined ? centerText : hasSegments ? rawSum : `${Math.round(pct)}%`}
155
208
  </div>
156
209
  )}
157
210
  </div>
@@ -21,6 +21,17 @@ function toKey(y, m, d) { return `${y}-${pad(m+1)}-${pad(d)}`; }
21
21
  function toDate(val) {
22
22
  if (!val) return null;
23
23
  if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
24
+ if (typeof val === 'string') {
25
+ const parts = val.split('-');
26
+ if (parts.length === 3) {
27
+ const y = parseInt(parts[0], 10);
28
+ const m = parseInt(parts[1], 10) - 1;
29
+ const d = parseInt(parts[2], 10);
30
+ if (!isNaN(y) && !isNaN(m) && !isNaN(d)) {
31
+ return new Date(y, m, d);
32
+ }
33
+ }
34
+ }
24
35
  const d = new Date(val);
25
36
  return isNaN(d.getTime()) ? null : d;
26
37
  }
@@ -10,6 +10,7 @@ const SIZES = {
10
10
 
11
11
  export default function LineProgress({
12
12
  value = 0,
13
+ segments,
13
14
  size = 'md',
14
15
  customSize,
15
16
  color = 'var(--blue)',
@@ -57,13 +58,15 @@ export default function LineProgress({
57
58
  }, [indeterminate, speed]);
58
59
 
59
60
  const transitionStyle = animated ? 'flex .5s cubic-bezier(0.4,0,0.2,1)' : 'none';
61
+ const hasSegments = Array.isArray(segments) && segments.length > 0;
62
+ const rawSum = hasSegments ? segments.reduce((sum, s) => sum + (Number(s.value) || 0), 0) : 0;
60
63
 
61
64
  return (
62
65
  <div className={className} style={{ display: 'inline-flex', flexDirection: 'column', gap: labelGap, width: '100%', ...style }}>
63
66
  {(label || showPercent) && (
64
67
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
65
68
  {label && <span style={{ fontSize, fontWeight: 600, color: 'var(--primary-text, #1a1a2e)' }}>{label}</span>}
66
- {showPercent && !indeterminate && (
69
+ {showPercent && !indeterminate && !hasSegments && (
67
70
  <span style={{ fontSize, fontWeight: 600, color: 'var(--muted, #9ca3af)', fontVariantNumeric: 'tabular-nums', marginLeft: label ? 0 : 'auto' }}>
68
71
  {Math.round(pct)}%
69
72
  </span>
@@ -87,6 +90,27 @@ export default function LineProgress({
87
90
  }}
88
91
  />
89
92
  </>
93
+ ) : hasSegments && rawSum > 0 ? (
94
+ <>
95
+ {segments.map((seg, idx) => {
96
+ const segVal = Number(seg.value) || 0;
97
+ if (segVal <= 0) return null;
98
+ const ratioPct = (segVal / rawSum) * 100;
99
+ return (
100
+ <div
101
+ key={idx}
102
+ title={seg.label ? `${seg.label}: ${segVal}` : undefined}
103
+ style={{
104
+ flex: ratioPct,
105
+ height: '100%',
106
+ borderRadius: radius,
107
+ background: seg.color || color,
108
+ transition: transitionStyle,
109
+ }}
110
+ />
111
+ );
112
+ })}
113
+ </>
90
114
  ) : (
91
115
  <>
92
116
  <div ref={barRef} style={{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cynx-ui",
3
- "version": "1.2.36",
3
+ "version": "1.2.38",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js"