cynx-ui 1.2.37 → 1.2.39

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.
@@ -41,8 +41,9 @@ function ensureStyles() {
41
41
  }
42
42
 
43
43
  const SIZE_MAP = {
44
+ xs: { padding: '1.5px 6px', fontSize: '9px' },
44
45
  sm: { padding: '2px 7px', fontSize: '10px' },
45
- md: { padding: '3px 9px', fontSize: '10.5px' },
46
+ md: { padding: '3px 9px', fontSize: '11px' },
46
47
  lg: { padding: '4px 12px', fontSize: '12px' },
47
48
  };
48
49
 
@@ -53,9 +54,31 @@ const SHAPE_STYLE = {
53
54
  semicircle:(c) => ({ width: 7, height: 7, borderRadius: '50%', border: 'none', borderTop: '2px solid var(--bg,#fff)', background: c }),
54
55
  };
55
56
 
57
+ const COLOR_MAP = {
58
+ green: '#10b981', passed: '#10b981', ok: '#10b981',
59
+ yellow: '#f97316', violation: '#f97316', warn: '#f97316', amber: '#f97316', orange: '#f97316',
60
+ red: '#ef4444', failed: '#ef4444', err: '#ef4444', danger: '#ef4444',
61
+ blue: '#3b82f6', conditional: '#3b82f6',
62
+ purple: '#8b5cf6',
63
+ indigo: '#6366f1',
64
+ cyan: '#06b6d4',
65
+ teal: '#14b8a6',
66
+ pink: '#ec4899',
67
+ grey: '#6b7280', gray: '#6b7280', dim: '#6b7280',
68
+ };
69
+
70
+ function resolveBadgeColor(cStr) {
71
+ if (!cStr) return null;
72
+ const c = cStr.toLowerCase();
73
+ if (COLOR_MAP[c]) return COLOR_MAP[c];
74
+ if (c.startsWith('#') || c.startsWith('rgb') || c.startsWith('hsl') || c.startsWith('var(')) return cStr;
75
+ return '#3b82f6';
76
+ }
77
+
56
78
  export default function Badge({
57
79
  children,
58
80
  variant = 'dim',
81
+ color = null,
59
82
  size = 'md',
60
83
  dot = false,
61
84
  shape = null,
@@ -69,7 +92,14 @@ export default function Badge({
69
92
  const v = VARIANTS[variant] || VARIANTS.dim;
70
93
  const sz = SIZE_MAP[size] || SIZE_MAP.md;
71
94
  const shapeType = (shape === 'none' || shape === false) ? null : (shape || SHAPES[variant] || null);
72
- const dotColor = v.dot || v.color;
95
+
96
+ const customHex = resolveBadgeColor(color);
97
+ const colorStyle = customHex ? {
98
+ color: customHex,
99
+ background: `${customHex}18`,
100
+ border: `1px solid ${customHex}33`,
101
+ } : {};
102
+ const dotColor = customHex || v.dot || v.color;
73
103
 
74
104
  return (
75
105
  <span
@@ -88,6 +118,7 @@ export default function Badge({
88
118
  whiteSpace: 'nowrap',
89
119
  transition: 'all .15s',
90
120
  cursor: onClick ? 'pointer' : undefined,
121
+ ...colorStyle,
91
122
  ...style,
92
123
  }}
93
124
  onClick={onClick}
@@ -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>
@@ -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.37",
3
+ "version": "1.2.39",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js"