cynx-ui 1.3.13 → 1.3.15

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.
@@ -1,133 +1,133 @@
1
- import { useEffect, useRef } from 'react';
2
-
3
- const SIZES = {
4
- xs: { height: 4, fontSize: '.68rem' },
5
- sm: { height: 5, fontSize: '.75rem' },
6
- md: { height: 6, fontSize: '.82rem' },
7
- lg: { height: 8, fontSize: '.9rem' },
8
- xl: { height: 10, fontSize: '1rem' },
9
- };
10
-
11
- export default function LineProgress({
12
- value = 0,
13
- segments,
14
- size = 'md',
15
- customSize,
16
- color = 'var(--blue)',
17
- trackColor = 'var(--bd2, #e0e0e4)',
18
- label = '',
19
- showPercent = true,
20
- animated = true,
21
- rounded = true,
22
- indeterminate = false,
23
- speed = 1,
24
- height,
25
- gap = 5,
26
- labelGap = 2,
27
- className = '',
28
- style = {},
29
- }) {
30
- const base = SIZES[size] || SIZES.md;
31
- const barHeight = height ?? (customSize || base.height);
32
- const fontSize = customSize ? `${Math.max(11, customSize * 1.6)}px` : base.fontSize;
33
- const pct = Math.min(100, Math.max(0, value));
34
- const radius = rounded ? barHeight / 2 : 0;
35
-
36
- const barRef = useRef(null);
37
-
38
- // Indeterminate animation
39
- useEffect(() => {
40
- if (!indeterminate || !barRef.current) return;
41
- let pos = -40, direction = 1, raf;
42
- let lastTime = performance.now();
43
-
44
- function tick(now) {
45
- const dt = (now - lastTime) / 16.67;
46
- lastTime = now;
47
- pos += direction * 1.2 * speed * dt;
48
- if (pos > 100) { pos = 100; direction = -1; }
49
- if (pos < -40) { pos = -40; direction = 1; }
50
- if (barRef.current) {
51
- barRef.current.style.left = pos + '%';
52
- barRef.current.style.width = '40%';
53
- }
54
- raf = requestAnimationFrame(tick);
55
- }
56
- raf = requestAnimationFrame(tick);
57
- return () => cancelAnimationFrame(raf);
58
- }, [indeterminate, speed]);
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;
63
-
64
- return (
65
- <div className={className} style={{ display: 'inline-flex', flexDirection: 'column', gap: labelGap, width: '100%', ...style }}>
66
- {(label || showPercent) && (
67
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
68
- {label && <span style={{ fontSize, fontWeight: 600, color: 'var(--primary-text, #1a1a2e)' }}>{label}</span>}
69
- {showPercent && !indeterminate && !hasSegments && (
70
- <span style={{ fontSize, fontWeight: 600, color: 'var(--muted, #9ca3af)', fontVariantNumeric: 'tabular-nums', marginLeft: label ? 0 : 'auto' }}>
71
- {Math.round(pct)}%
72
- </span>
73
- )}
74
- </div>
75
- )}
76
- <div style={{
77
- height: barHeight, borderRadius: radius,
78
- background: 'transparent', position: 'relative',
79
- display: 'flex', gap: gap, overflow: 'hidden',
80
- }}>
81
- {indeterminate ? (
82
- <>
83
- <div style={{ flex: 1, borderRadius: radius, background: trackColor }} />
84
- <div
85
- ref={barRef}
86
- style={{
87
- position: 'absolute', top: 0, height: '100%',
88
- width: '40%', borderRadius: radius,
89
- background: color, opacity: 0.85,
90
- }}
91
- />
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
- </>
114
- ) : (
115
- <>
116
- <div ref={barRef} style={{
117
- flex: pct > 0 ? pct : 0, minWidth: pct > 0 ? barHeight : 0,
118
- borderRadius: radius, background: color,
119
- transition: transitionStyle,
120
- }} />
121
- {pct < 100 && (
122
- <div style={{
123
- flex: 100 - pct,
124
- borderRadius: radius, background: trackColor,
125
- transition: transitionStyle,
126
- }} />
127
- )}
128
- </>
129
- )}
130
- </div>
131
- </div>
132
- );
133
- }
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ const SIZES = {
4
+ xs: { height: 4, fontSize: '.68rem' },
5
+ sm: { height: 5, fontSize: '.75rem' },
6
+ md: { height: 6, fontSize: '.82rem' },
7
+ lg: { height: 8, fontSize: '.9rem' },
8
+ xl: { height: 10, fontSize: '1rem' },
9
+ };
10
+
11
+ export default function LineProgress({
12
+ value = 0,
13
+ segments,
14
+ size = 'md',
15
+ customSize,
16
+ color = 'var(--blue)',
17
+ trackColor = 'var(--bd2, #e0e0e4)',
18
+ label = '',
19
+ showPercent = true,
20
+ animated = true,
21
+ rounded = true,
22
+ indeterminate = false,
23
+ speed = 1,
24
+ height,
25
+ gap = 5,
26
+ labelGap = 2,
27
+ className = '',
28
+ style = {},
29
+ }) {
30
+ const base = SIZES[size] || SIZES.md;
31
+ const barHeight = height ?? (customSize || base.height);
32
+ const fontSize = customSize ? `${Math.max(11, customSize * 1.6)}px` : base.fontSize;
33
+ const pct = Math.min(100, Math.max(0, value));
34
+ const radius = rounded ? barHeight / 2 : 0;
35
+
36
+ const barRef = useRef(null);
37
+
38
+ // Indeterminate animation
39
+ useEffect(() => {
40
+ if (!indeterminate || !barRef.current) return;
41
+ let pos = -40, direction = 1, raf;
42
+ let lastTime = performance.now();
43
+
44
+ function tick(now) {
45
+ const dt = (now - lastTime) / 16.67;
46
+ lastTime = now;
47
+ pos += direction * 1.2 * speed * dt;
48
+ if (pos > 100) { pos = 100; direction = -1; }
49
+ if (pos < -40) { pos = -40; direction = 1; }
50
+ if (barRef.current) {
51
+ barRef.current.style.left = pos + '%';
52
+ barRef.current.style.width = '40%';
53
+ }
54
+ raf = requestAnimationFrame(tick);
55
+ }
56
+ raf = requestAnimationFrame(tick);
57
+ return () => cancelAnimationFrame(raf);
58
+ }, [indeterminate, speed]);
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;
63
+
64
+ return (
65
+ <div className={className} style={{ display: 'inline-flex', flexDirection: 'column', gap: labelGap, width: '100%', ...style }}>
66
+ {(label || showPercent) && (
67
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
68
+ {label && <span style={{ fontSize, fontWeight: 600, color: 'var(--primary-text, #1a1a2e)' }}>{label}</span>}
69
+ {showPercent && !indeterminate && !hasSegments && (
70
+ <span style={{ fontSize, fontWeight: 600, color: 'var(--muted, #9ca3af)', fontVariantNumeric: 'tabular-nums', marginLeft: label ? 0 : 'auto' }}>
71
+ {Math.round(pct)}%
72
+ </span>
73
+ )}
74
+ </div>
75
+ )}
76
+ <div style={{
77
+ height: barHeight, borderRadius: radius,
78
+ background: 'transparent', position: 'relative',
79
+ display: 'flex', gap: gap, overflow: 'hidden',
80
+ }}>
81
+ {indeterminate ? (
82
+ <>
83
+ <div style={{ flex: 1, borderRadius: radius, background: trackColor }} />
84
+ <div
85
+ ref={barRef}
86
+ style={{
87
+ position: 'absolute', top: 0, height: '100%',
88
+ width: '40%', borderRadius: radius,
89
+ background: color, opacity: 0.85,
90
+ }}
91
+ />
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
+ </>
114
+ ) : (
115
+ <>
116
+ <div ref={barRef} style={{
117
+ flex: pct > 0 ? pct : 0, minWidth: pct > 0 ? barHeight : 0,
118
+ borderRadius: radius, background: color,
119
+ transition: transitionStyle,
120
+ }} />
121
+ {pct < 100 && (
122
+ <div style={{
123
+ flex: 100 - pct,
124
+ borderRadius: radius, background: trackColor,
125
+ transition: transitionStyle,
126
+ }} />
127
+ )}
128
+ </>
129
+ )}
130
+ </div>
131
+ </div>
132
+ );
133
+ }
@@ -1,41 +1,41 @@
1
- import Button from './Button';
2
- import { ChevronLeft, ChevronRight } from 'lucide-react';
3
-
4
- export function Pagination({ page, pages, total, perPage, onChange }) {
5
- const totalPages = pages || (total && perPage ? Math.ceil(total / perPage) : 0);
6
- if (!totalPages || totalPages <= 1) return null;
7
-
8
- const items = [];
9
- for (let i = 1; i <= totalPages; i++) {
10
- if (i === 1 || i === totalPages || (i >= page - 1 && i <= page + 1)) items.push(i);
11
- else if (items[items.length - 1] !== '...') items.push('...');
12
- }
13
-
14
- const start = perPage ? (page - 1) * perPage + 1 : null;
15
- const end = perPage ? Math.min(page * perPage, total) : null;
16
-
17
- return (
18
- <div style={{
19
- display: 'flex', alignItems: 'center', justifyContent: 'space-between',
20
- padding: '10px 16px',
21
- borderTop: '1px solid var(--border-light)',
22
- background: 'var(--surface)',
23
- borderRadius: '0 0 var(--rad) var(--rad)',
24
- }}>
25
- {start !== null && (
26
- <div style={{ fontSize: '.72rem', color: 'var(--muted)', fontFamily: 'var(--mono)' }}>
27
- {start}–{end} of {total}
28
- </div>
29
- )}
30
- <div style={{ display: 'flex', gap: 4, alignItems: 'center', marginLeft: start !== null ? 'auto' : 0 }}>
31
- <Button variant="ghost" size="xs" icon={<ChevronLeft size={14} />} onClick={() => onChange(page - 1)} disabled={page <= 1} />
32
- {items.map((item, i) =>
33
- item === '...'
34
- ? <span key={`e${i}`} style={{ padding: '0 4px', color: 'var(--muted)', fontSize: '.78rem' }}>…</span>
35
- : <Button key={item} variant={item === page ? 'primary' : 'no-line'} size="xs" onClick={() => onChange(item)} style={{ minWidth: 28 }}>{item}</Button>
36
- )}
37
- <Button variant="ghost" size="xs" icon={<ChevronRight size={14} />} onClick={() => onChange(page + 1)} disabled={page >= totalPages} />
38
- </div>
39
- </div>
40
- );
41
- }
1
+ import Button from './Button';
2
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
3
+
4
+ export function Pagination({ page, pages, total, perPage, onChange }) {
5
+ const totalPages = pages || (total && perPage ? Math.ceil(total / perPage) : 0);
6
+ if (!totalPages || totalPages <= 1) return null;
7
+
8
+ const items = [];
9
+ for (let i = 1; i <= totalPages; i++) {
10
+ if (i === 1 || i === totalPages || (i >= page - 1 && i <= page + 1)) items.push(i);
11
+ else if (items[items.length - 1] !== '...') items.push('...');
12
+ }
13
+
14
+ const start = perPage ? (page - 1) * perPage + 1 : null;
15
+ const end = perPage ? Math.min(page * perPage, total) : null;
16
+
17
+ return (
18
+ <div style={{
19
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
20
+ padding: '10px 16px',
21
+ borderTop: '1px solid var(--border-light)',
22
+ background: 'var(--surface)',
23
+ borderRadius: '0 0 var(--rad) var(--rad)',
24
+ }}>
25
+ {start !== null && (
26
+ <div style={{ fontSize: '.72rem', color: 'var(--muted)', fontFamily: 'var(--mono)' }}>
27
+ {start}–{end} of {total}
28
+ </div>
29
+ )}
30
+ <div style={{ display: 'flex', gap: 4, alignItems: 'center', marginLeft: start !== null ? 'auto' : 0 }}>
31
+ <Button variant="ghost" size="xs" icon={<ChevronLeft size={14} />} onClick={() => onChange(page - 1)} disabled={page <= 1} />
32
+ {items.map((item, i) =>
33
+ item === '...'
34
+ ? <span key={`e${i}`} style={{ padding: '0 4px', color: 'var(--muted)', fontSize: '.78rem' }}>…</span>
35
+ : <Button key={item} variant={item === page ? 'primary' : 'no-line'} size="xs" onClick={() => onChange(item)} style={{ minWidth: 28 }}>{item}</Button>
36
+ )}
37
+ <Button variant="ghost" size="xs" icon={<ChevronRight size={14} />} onClick={() => onChange(page + 1)} disabled={page >= totalPages} />
38
+ </div>
39
+ </div>
40
+ );
41
+ }
@@ -1,25 +1,25 @@
1
- import LineProgress from './LineProgress';
2
-
3
- export default function StatBarCard({ title, icon, items, style }) {
4
- const total = items.reduce((s, i) => s + (i.value || 0), 0);
5
-
6
- return (
7
- <div style={{
8
- background: 'var(--surface,#fff)', border: '1px solid var(--border)', borderRadius: 'var(--rad)',
9
- boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04))',
10
- padding: '12px 14px', minWidth: 220, ...style,
11
- }}>
12
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
13
- <div style={{ fontSize: '.58rem', fontWeight: 700, color: 'var(--muted, #9ca3af)', textTransform: 'uppercase', letterSpacing: '.08em' }}>
14
- {title}{total ? ` (${total})` : ''}
15
- </div>
16
- {icon && <span style={{ color: 'var(--muted, #9ca3af)' }}>{icon}</span>}
17
- </div>
18
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
19
- {items.map((item, i) => (
20
- <LineProgress key={i} value={item.value} total={total} color={item.color} label={item.label} size="sm" />
21
- ))}
22
- </div>
23
- </div>
24
- );
25
- }
1
+ import LineProgress from './LineProgress';
2
+
3
+ export default function StatBarCard({ title, icon, items, style }) {
4
+ const total = items.reduce((s, i) => s + (i.value || 0), 0);
5
+
6
+ return (
7
+ <div style={{
8
+ background: 'var(--surface,#fff)', border: '1px solid var(--border)', borderRadius: 'var(--rad)',
9
+ boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04))',
10
+ padding: '12px 14px', minWidth: 220, ...style,
11
+ }}>
12
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
13
+ <div style={{ fontSize: '.58rem', fontWeight: 700, color: 'var(--muted, #9ca3af)', textTransform: 'uppercase', letterSpacing: '.08em' }}>
14
+ {title}{total ? ` (${total})` : ''}
15
+ </div>
16
+ {icon && <span style={{ color: 'var(--muted, #9ca3af)' }}>{icon}</span>}
17
+ </div>
18
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
19
+ {items.map((item, i) => (
20
+ <LineProgress key={i} value={item.value} total={total} color={item.color} label={item.label} size="sm" />
21
+ ))}
22
+ </div>
23
+ </div>
24
+ );
25
+ }