cynx-ui 1.2.6 → 1.2.7
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.
- package/components/ActionsMenu.jsx +149 -149
- package/components/ActivityCard.jsx +184 -184
- package/components/Badge.jsx +129 -105
- package/components/Button.jsx +126 -43
- package/components/Card.jsx +62 -62
- package/components/CircularProgress.jsx +163 -163
- package/components/Dropzone.jsx +29 -29
- package/components/HoverCard.jsx +81 -81
- package/components/Input.jsx +181 -121
- package/components/LineProgress.jsx +109 -109
- package/components/Modal.jsx +124 -8
- package/components/Pagination.jsx +41 -41
- package/components/PulseDot.jsx +76 -58
- package/components/Select.jsx +175 -91
- package/components/Skeleton.jsx +31 -47
- package/components/Spinner.jsx +36 -36
- package/components/StatBarCard.jsx +25 -25
- package/components/StatsCard.jsx +114 -40
- package/components/Tabs.jsx +16 -16
- package/components/Toast.jsx +57 -57
- package/components/Toggle.jsx +56 -56
- package/components/UptimeBar.jsx +200 -200
- package/components/XTable.jsx +22 -22
- package/index.js +94 -94
- package/package.json +18 -18
package/components/Select.jsx
CHANGED
|
@@ -2,69 +2,17 @@ import { useState, useRef, useEffect, useMemo } from 'react';
|
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { ChevronDown, Check, X } from 'lucide-react';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
.sel-wrap{position:relative;width:100%;font-family:var(--f,var(--sans));font-size:var(--fsz,13px)}
|
|
7
|
-
.sel-wrap.disabled{opacity:.5;pointer-events:none}
|
|
8
|
-
.sel-trigger{
|
|
9
|
-
width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;
|
|
10
|
-
padding:8px 11px;height:35px;
|
|
11
|
-
background:var(--sf,var(--surface));border:1.5px solid var(--bd2,var(--border));
|
|
12
|
-
border-radius:var(--rmd,var(--rads));cursor:pointer;
|
|
13
|
-
font-family:var(--f,var(--sans,inherit));font-size:12.5px;line-height:1;
|
|
14
|
-
color:var(--t,var(--primary-text));outline:none;
|
|
15
|
-
transition:border-color var(--tr,.15s),box-shadow var(--tr,.15s);
|
|
16
|
-
box-shadow:var(--shadow,0 1px 3px rgba(20,22,41,.1))
|
|
17
|
-
}
|
|
18
|
-
.sel-trigger:hover{border-color:var(--bd2,var(--grey-2))}
|
|
19
|
-
.sel-trigger.open{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.1)}
|
|
20
|
-
.sel-trigger-inner{flex:1;min-width:0;display:flex;align-items:center}
|
|
21
|
-
.sel-value{font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
22
|
-
.sel-placeholder{color:var(--t3,var(--grey-3));font-weight:400}
|
|
23
|
-
.sel-chevron{flex-shrink:0;color:var(--t3,var(--grey-3));transition:transform .2s ease}
|
|
24
|
-
.sel-trigger.open .sel-chevron{transform:rotate(180deg)}
|
|
25
|
-
.sel-tags{display:flex;flex-wrap:wrap;gap:4px;flex:1;min-width:0}
|
|
26
|
-
.sel-tag{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;background:var(--pd,var(--accent-bg));border:1px solid var(--accent);border-radius:4px;font-size:11px;font-weight:600;color:var(--accent);white-space:nowrap;max-width:140px;opacity:.85}
|
|
27
|
-
.sel-tag-x{cursor:pointer;flex-shrink:0;opacity:.5;transition:opacity .15s}
|
|
28
|
-
.sel-tag-x:hover{opacity:1}
|
|
29
|
-
.sel-dropdown{
|
|
30
|
-
position:fixed;z-index:9000;
|
|
31
|
-
background:var(--sf,var(--surface));border:1px solid var(--bd,var(--border));
|
|
32
|
-
border-radius:var(--rmd,var(--rads));box-shadow:var(--shadow,0 1px 3px rgba(20,22,41,.1));
|
|
33
|
-
padding:4px;animation:selFadeIn .15s ease;overflow:hidden
|
|
34
|
-
}
|
|
35
|
-
@keyframes selFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
|
|
36
|
-
.sel-search-wrap{padding:4px}
|
|
37
|
-
.sel-search{
|
|
38
|
-
width:100%;padding:7px 10px;height:35px;
|
|
39
|
-
background:var(--sf2,var(--grey-1));border:1.5px solid var(--bd2,var(--border));
|
|
40
|
-
border-radius:var(--rmd,var(--rads));font-family:var(--f,var(--sans));font-size:12.5px;line-height:1;
|
|
41
|
-
color:var(--t,var(--primary-text));outline:none;
|
|
42
|
-
transition:border-color var(--tr,.15s),box-shadow var(--tr,.15s)
|
|
43
|
-
}
|
|
44
|
-
.sel-search::placeholder{color:var(--t3,var(--grey-3))}
|
|
45
|
-
.sel-search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.1)}
|
|
46
|
-
.sel-options{max-height:200px;overflow-y:auto}
|
|
47
|
-
.sel-options::-webkit-scrollbar{width:4px}
|
|
48
|
-
.sel-options::-webkit-scrollbar-thumb{background:var(--bd2,var(--grey-2));border-radius:99px}
|
|
49
|
-
.sel-option{
|
|
50
|
-
display:flex;align-items:center;gap:8px;padding:8px 10px;
|
|
51
|
-
border-radius:var(--rmd,var(--rads));cursor:pointer;
|
|
52
|
-
color:var(--t,var(--primary-text));font-size:12.5px;
|
|
53
|
-
font-weight:400 !important;transition:background .1s;user-select:none
|
|
54
|
-
}
|
|
55
|
-
.sel-option:hover{background:var(--sf2,var(--hover-bg))}
|
|
56
|
-
.sel-option.selected{color:var(--accent);background:var(--pd,var(--accent-bg))}
|
|
57
|
-
.sel-option.disabled{opacity:.4;cursor:not-allowed;pointer-events:none}
|
|
58
|
-
.sel-opt-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
59
|
-
.sel-check{flex-shrink:0;color:var(--accent)}
|
|
60
|
-
.sel-check-box{width:15px;height:15px;border:1.5px solid var(--bd2,var(--border));border-radius:3px;flex-shrink:0;position:relative;transition:all .15s}
|
|
61
|
-
.sel-check-box.checked{background:var(--accent);border-color:var(--accent)}
|
|
62
|
-
.sel-check-box.checked::after{content:'';position:absolute;top:1px;left:3.5px;width:4.5px;height:7px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
|
|
63
|
-
.sel-empty{padding:14px 10px;text-align:center;color:var(--t3,var(--grey-3));font-size:11px}
|
|
64
|
-
`;
|
|
65
|
-
|
|
5
|
+
const KEYFRAMES = `@keyframes selFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}`;
|
|
66
6
|
let styleInjected = false;
|
|
67
7
|
|
|
8
|
+
function ensureStyles() {
|
|
9
|
+
if (styleInjected || typeof document === 'undefined') return;
|
|
10
|
+
const tag = document.createElement('style');
|
|
11
|
+
tag.textContent = KEYFRAMES;
|
|
12
|
+
document.head.appendChild(tag);
|
|
13
|
+
styleInjected = true;
|
|
14
|
+
}
|
|
15
|
+
|
|
68
16
|
export default function Select({
|
|
69
17
|
options = [],
|
|
70
18
|
value,
|
|
@@ -82,14 +30,7 @@ export default function Select({
|
|
|
82
30
|
const dropRef = useRef(null);
|
|
83
31
|
const inputRef = useRef(null);
|
|
84
32
|
|
|
85
|
-
|
|
86
|
-
if (!styleInjected) {
|
|
87
|
-
const tag = document.createElement('style');
|
|
88
|
-
tag.textContent = STYLES;
|
|
89
|
-
document.head.appendChild(tag);
|
|
90
|
-
styleInjected = true;
|
|
91
|
-
}
|
|
92
|
-
}, []);
|
|
33
|
+
ensureStyles();
|
|
93
34
|
|
|
94
35
|
useEffect(() => {
|
|
95
36
|
if (!open) return;
|
|
@@ -165,47 +106,190 @@ export default function Select({
|
|
|
165
106
|
const selectedValues = multi ? (Array.isArray(value) ? value : []) : (value ? [value] : []);
|
|
166
107
|
|
|
167
108
|
return (
|
|
168
|
-
<div
|
|
169
|
-
|
|
170
|
-
|
|
109
|
+
<div
|
|
110
|
+
ref={wrapRef}
|
|
111
|
+
style={{
|
|
112
|
+
position: 'relative',
|
|
113
|
+
width: '100%',
|
|
114
|
+
fontFamily: 'var(--f,var(--sans))',
|
|
115
|
+
fontSize: 'var(--fsz,13px)',
|
|
116
|
+
opacity: disabled ? 0.5 : 1,
|
|
117
|
+
pointerEvents: disabled ? 'none' : undefined,
|
|
118
|
+
...style,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<div
|
|
122
|
+
onClick={handleOpen}
|
|
123
|
+
style={{
|
|
124
|
+
width: '100%',
|
|
125
|
+
display: 'flex',
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
justifyContent: 'space-between',
|
|
128
|
+
gap: 8,
|
|
129
|
+
padding: '8px 11px',
|
|
130
|
+
height: 35,
|
|
131
|
+
background: 'var(--sf,var(--surface))',
|
|
132
|
+
border: `1.5px solid ${open ? 'var(--accent)' : 'var(--bd2,var(--border))'}`,
|
|
133
|
+
borderRadius: 'var(--rmd,var(--rads))',
|
|
134
|
+
cursor: 'pointer',
|
|
135
|
+
fontFamily: 'var(--f,var(--sans,inherit))',
|
|
136
|
+
fontSize: '12.5px',
|
|
137
|
+
lineHeight: 1,
|
|
138
|
+
color: 'var(--t,var(--primary-text))',
|
|
139
|
+
outline: 'none',
|
|
140
|
+
transition: 'border-color var(--tr,.15s), box-shadow var(--tr,.15s)',
|
|
141
|
+
boxShadow: open ? '0 0 0 3px rgba(37,99,235,.1)' : 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center' }}>
|
|
171
145
|
{multi && selectedValues.length > 0 ? (
|
|
172
|
-
<div
|
|
146
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, flex: 1, minWidth: 0 }}>
|
|
173
147
|
{selectedValues.map(v => (
|
|
174
|
-
<span key={v}
|
|
148
|
+
<span key={v} style={{
|
|
149
|
+
display: 'inline-flex',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
gap: 4,
|
|
152
|
+
padding: '2px 6px',
|
|
153
|
+
background: 'var(--pd,var(--accent-bg))',
|
|
154
|
+
border: '1px solid var(--accent)',
|
|
155
|
+
borderRadius: 4,
|
|
156
|
+
fontSize: 11,
|
|
157
|
+
fontWeight: 600,
|
|
158
|
+
color: 'var(--accent)',
|
|
159
|
+
whiteSpace: 'nowrap',
|
|
160
|
+
maxWidth: 140,
|
|
161
|
+
opacity: 0.85,
|
|
162
|
+
}}>
|
|
175
163
|
{getLabel(v)}
|
|
176
|
-
<X
|
|
164
|
+
<X
|
|
165
|
+
size={11}
|
|
166
|
+
style={{ cursor: 'pointer', flexShrink: 0, opacity: 0.5 }}
|
|
167
|
+
onMouseDown={(e) => removeTag(e, v)}
|
|
168
|
+
/>
|
|
177
169
|
</span>
|
|
178
170
|
))}
|
|
179
171
|
</div>
|
|
180
172
|
) : !multi && selectedValues.length > 0 ? (
|
|
181
|
-
<span
|
|
173
|
+
<span style={{ fontWeight: 400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
174
|
+
{getLabel(selectedValues[0])}
|
|
175
|
+
</span>
|
|
182
176
|
) : (
|
|
183
|
-
<span
|
|
177
|
+
<span style={{ color: 'var(--t3,var(--grey-3))', fontWeight: 400 }}>{placeholder}</span>
|
|
184
178
|
)}
|
|
185
179
|
</div>
|
|
186
|
-
<ChevronDown
|
|
180
|
+
<ChevronDown
|
|
181
|
+
size={13}
|
|
182
|
+
style={{
|
|
183
|
+
flexShrink: 0,
|
|
184
|
+
color: 'var(--t3,var(--grey-3))',
|
|
185
|
+
transition: 'transform .2s ease',
|
|
186
|
+
transform: open ? 'rotate(180deg)' : undefined,
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
187
189
|
</div>
|
|
188
190
|
|
|
189
191
|
{open && createPortal(
|
|
190
|
-
<div
|
|
192
|
+
<div
|
|
193
|
+
ref={dropRef}
|
|
194
|
+
style={{
|
|
195
|
+
position: 'fixed',
|
|
196
|
+
zIndex: 9000,
|
|
197
|
+
top: pos.top,
|
|
198
|
+
left: pos.left,
|
|
199
|
+
width: pos.width,
|
|
200
|
+
background: 'var(--sf,var(--surface))',
|
|
201
|
+
border: '1px solid var(--bd,var(--border))',
|
|
202
|
+
borderRadius: 'var(--rmd,var(--rads))',
|
|
203
|
+
boxShadow: 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
|
|
204
|
+
padding: 4,
|
|
205
|
+
animation: 'selFadeIn .15s ease',
|
|
206
|
+
overflow: 'hidden',
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
191
209
|
{searchable && (
|
|
192
|
-
<div
|
|
193
|
-
<input
|
|
194
|
-
|
|
195
|
-
|
|
210
|
+
<div style={{ padding: 4 }}>
|
|
211
|
+
<input
|
|
212
|
+
ref={inputRef}
|
|
213
|
+
type="text"
|
|
214
|
+
placeholder="Search..."
|
|
215
|
+
value={query}
|
|
216
|
+
onChange={e => setQuery(e.target.value)}
|
|
217
|
+
onMouseDown={e => e.stopPropagation()}
|
|
218
|
+
style={{
|
|
219
|
+
width: '100%',
|
|
220
|
+
padding: '7px 10px',
|
|
221
|
+
height: 35,
|
|
222
|
+
background: 'var(--sf2,var(--grey-1))',
|
|
223
|
+
border: '1.5px solid var(--bd2,var(--border))',
|
|
224
|
+
borderRadius: 'var(--rmd,var(--rads))',
|
|
225
|
+
fontFamily: 'var(--f,var(--sans))',
|
|
226
|
+
fontSize: '12.5px',
|
|
227
|
+
lineHeight: 1,
|
|
228
|
+
color: 'var(--t,var(--primary-text))',
|
|
229
|
+
outline: 'none',
|
|
230
|
+
transition: 'border-color var(--tr,.15s), box-shadow var(--tr,.15s)',
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
196
233
|
</div>
|
|
197
234
|
)}
|
|
198
|
-
<div
|
|
235
|
+
<div style={{ maxHeight: 200, overflowY: 'auto' }}>
|
|
199
236
|
{filtered.length === 0 ? (
|
|
200
|
-
<div
|
|
237
|
+
<div style={{ padding: '14px 10px', textAlign: 'center', color: 'var(--t3,var(--grey-3))', fontSize: 11 }}>
|
|
238
|
+
No options found
|
|
239
|
+
</div>
|
|
201
240
|
) : (
|
|
202
241
|
filtered.map(opt => (
|
|
203
|
-
<div
|
|
204
|
-
|
|
205
|
-
onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); if (!opt.disabled) toggleOption(opt.value); }}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
242
|
+
<div
|
|
243
|
+
key={opt.value}
|
|
244
|
+
onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); if (!opt.disabled) toggleOption(opt.value); }}
|
|
245
|
+
style={{
|
|
246
|
+
display: 'flex',
|
|
247
|
+
alignItems: 'center',
|
|
248
|
+
gap: 8,
|
|
249
|
+
padding: '8px 10px',
|
|
250
|
+
borderRadius: 'var(--rmd,var(--rads))',
|
|
251
|
+
cursor: opt.disabled ? 'not-allowed' : 'pointer',
|
|
252
|
+
color: isSelected(opt.value) ? 'var(--accent)' : 'var(--t,var(--primary-text))',
|
|
253
|
+
background: isSelected(opt.value) ? 'var(--pd,var(--accent-bg))' : undefined,
|
|
254
|
+
fontSize: '12.5px',
|
|
255
|
+
fontWeight: 400,
|
|
256
|
+
transition: 'background .1s',
|
|
257
|
+
userSelect: 'none',
|
|
258
|
+
opacity: opt.disabled ? 0.4 : 1,
|
|
259
|
+
pointerEvents: opt.disabled ? 'none' : undefined,
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
{multi && (
|
|
263
|
+
<span style={{
|
|
264
|
+
width: 15,
|
|
265
|
+
height: 15,
|
|
266
|
+
border: `1.5px solid ${isSelected(opt.value) ? 'var(--accent)' : 'var(--bd2,var(--border))'}`,
|
|
267
|
+
borderRadius: 3,
|
|
268
|
+
flexShrink: 0,
|
|
269
|
+
position: 'relative',
|
|
270
|
+
transition: 'all .15s',
|
|
271
|
+
background: isSelected(opt.value) ? 'var(--accent)' : undefined,
|
|
272
|
+
}}>
|
|
273
|
+
{isSelected(opt.value) && (
|
|
274
|
+
<span style={{
|
|
275
|
+
position: 'absolute',
|
|
276
|
+
top: 1,
|
|
277
|
+
left: 3.5,
|
|
278
|
+
width: 4.5,
|
|
279
|
+
height: 7,
|
|
280
|
+
border: 'solid #fff',
|
|
281
|
+
borderWidth: '0 1.5px 1.5px 0',
|
|
282
|
+
transform: 'rotate(45deg)',
|
|
283
|
+
}} />
|
|
284
|
+
)}
|
|
285
|
+
</span>
|
|
286
|
+
)}
|
|
287
|
+
<span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
288
|
+
{opt.label}
|
|
289
|
+
</span>
|
|
290
|
+
{!multi && isSelected(opt.value) && (
|
|
291
|
+
<Check size={13} style={{ flexShrink: 0, color: 'var(--accent)' }} />
|
|
292
|
+
)}
|
|
209
293
|
</div>
|
|
210
294
|
))
|
|
211
295
|
)}
|
package/components/Skeleton.jsx
CHANGED
|
@@ -1,58 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* <Skeleton /> — single block (default 100% x 12px)
|
|
6
|
-
* <Skeleton width={80} height={12} /> — custom size
|
|
7
|
-
* <Skeleton circle size={32} /> — circle (avatar, icon)
|
|
8
|
-
* <SkeletonText lines={3} /> — multiple text lines
|
|
9
|
-
* <SkeletonCard /> — card-shaped skeleton
|
|
10
|
-
* <SkeletonCard rows={4} /> — card with more rows
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const STYLES = `
|
|
14
|
-
@keyframes sk-shimmer {
|
|
15
|
-
0% { background-position: -600px 0; }
|
|
16
|
-
100% { background-position: 600px 0; }
|
|
17
|
-
}
|
|
18
|
-
.sk {
|
|
19
|
-
background: linear-gradient(90deg, var(--grey-1, #f0f1f5) 25%, var(--border-light, #e8e8ec) 50%, var(--grey-1, #f0f1f5) 75%);
|
|
20
|
-
background-size: 600px 100%;
|
|
21
|
-
animation: sk-shimmer 1.4s ease infinite;
|
|
22
|
-
border-radius: var(--radxs, 4px);
|
|
23
|
-
flex-shrink: 0;
|
|
24
|
-
}
|
|
25
|
-
.sk-card {
|
|
26
|
-
background: var(--surface, #fff);
|
|
27
|
-
border: 1px solid var(--border-light, #ebebf0);
|
|
28
|
-
border-radius: var(--rad, 10px);
|
|
29
|
-
padding: 14px 16px;
|
|
30
|
-
box-shadow: 0 1px 3px rgba(0,0,0,.04);
|
|
31
|
-
}
|
|
32
|
-
.sk-card-head {
|
|
33
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
34
|
-
margin-bottom: 12px;
|
|
35
|
-
}
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
let injected = false;
|
|
1
|
+
const KEYFRAMES = `@keyframes sk-shimmer{0%{background-position:-600px 0}100%{background-position:600px 0}}`;
|
|
2
|
+
let styleInjected = false;
|
|
39
3
|
|
|
40
4
|
function ensureStyles() {
|
|
41
|
-
if (
|
|
5
|
+
if (styleInjected || typeof document === 'undefined') return;
|
|
42
6
|
const tag = document.createElement('style');
|
|
43
|
-
tag.textContent =
|
|
7
|
+
tag.textContent = KEYFRAMES;
|
|
44
8
|
document.head.appendChild(tag);
|
|
45
|
-
|
|
9
|
+
styleInjected = true;
|
|
46
10
|
}
|
|
47
11
|
|
|
12
|
+
const SK_BASE = {
|
|
13
|
+
background: 'linear-gradient(90deg, var(--grey-1, #f0f1f5) 25%, var(--border-light, #e8e8ec) 50%, var(--grey-1, #f0f1f5) 75%)',
|
|
14
|
+
backgroundSize: '600px 100%',
|
|
15
|
+
animation: 'sk-shimmer 1.4s ease infinite',
|
|
16
|
+
borderRadius: 'var(--radxs, 4px)',
|
|
17
|
+
flexShrink: 0,
|
|
18
|
+
};
|
|
19
|
+
|
|
48
20
|
export function Skeleton({ width = '100%', height = 12, circle, size, radius, style = {}, className = '' }) {
|
|
49
21
|
ensureStyles();
|
|
50
22
|
const w = circle ? size : width;
|
|
51
23
|
const h = circle ? size : height;
|
|
52
24
|
return (
|
|
53
25
|
<div
|
|
54
|
-
className={
|
|
26
|
+
className={className}
|
|
55
27
|
style={{
|
|
28
|
+
...SK_BASE,
|
|
56
29
|
width: w,
|
|
57
30
|
height: h,
|
|
58
31
|
borderRadius: circle ? '50%' : radius,
|
|
@@ -71,11 +44,10 @@ export function SkeletonText({ lines = 3, widths, style = {} }) {
|
|
|
71
44
|
{Array.from({ length: lines }, (_, i) => (
|
|
72
45
|
<div
|
|
73
46
|
key={i}
|
|
74
|
-
className="sk"
|
|
75
47
|
style={{
|
|
48
|
+
...SK_BASE,
|
|
76
49
|
width: ws[i % ws.length],
|
|
77
50
|
height: 12,
|
|
78
|
-
borderRadius: 'var(--radxs, 4px)',
|
|
79
51
|
}}
|
|
80
52
|
/>
|
|
81
53
|
))}
|
|
@@ -86,8 +58,20 @@ export function SkeletonText({ lines = 3, widths, style = {} }) {
|
|
|
86
58
|
export function SkeletonCard({ rows = 3, style = {} }) {
|
|
87
59
|
ensureStyles();
|
|
88
60
|
return (
|
|
89
|
-
<div
|
|
90
|
-
|
|
61
|
+
<div style={{
|
|
62
|
+
background: 'var(--surface, #fff)',
|
|
63
|
+
border: '1px solid var(--border-light, #ebebf0)',
|
|
64
|
+
borderRadius: 'var(--rad, 10px)',
|
|
65
|
+
padding: '14px 16px',
|
|
66
|
+
boxShadow: '0 1px 3px rgba(0,0,0,.04)',
|
|
67
|
+
...style,
|
|
68
|
+
}}>
|
|
69
|
+
<div style={{
|
|
70
|
+
display: 'flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
justifyContent: 'space-between',
|
|
73
|
+
marginBottom: 12,
|
|
74
|
+
}}>
|
|
91
75
|
<Skeleton width={140} height={14} />
|
|
92
76
|
<Skeleton width={70} height={26} radius="var(--rads, 6px)" />
|
|
93
77
|
</div>
|
|
@@ -103,7 +87,7 @@ export function SkeletonRows({ cols = 5, rows = 5 }) {
|
|
|
103
87
|
<tr key={i}>
|
|
104
88
|
{Array.from({ length: cols }).map((_, j) => (
|
|
105
89
|
<td key={j}>
|
|
106
|
-
<div
|
|
90
|
+
<div style={{ ...SK_BASE, width: `${50 + Math.random() * 40}%`, height: 12 }} />
|
|
107
91
|
</td>
|
|
108
92
|
))}
|
|
109
93
|
</tr>
|
package/components/Spinner.jsx
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
export function Spinner({ size = 20, inline = false, color = 'currentColor', fullPage = false }) {
|
|
2
|
-
const s = inline ? 14 : size;
|
|
3
|
-
const svg = (
|
|
4
|
-
<svg className="spin" width={s} height={s} viewBox="0 0 24 24" fill="none"
|
|
5
|
-
stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
6
|
-
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
|
|
7
|
-
</svg>
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
if (fullPage) {
|
|
11
|
-
return (
|
|
12
|
-
<div style={{
|
|
13
|
-
position: 'fixed', inset: 0,
|
|
14
|
-
background: 'var(--bg)',
|
|
15
|
-
display: 'flex', flexDirection: 'column',
|
|
16
|
-
alignItems: 'center', justifyContent: 'center', gap: 16,
|
|
17
|
-
zIndex: 9999,
|
|
18
|
-
}}>
|
|
19
|
-
{svg}
|
|
20
|
-
<div style={{ fontSize: '.75rem', fontFamily: 'var(--mono)', color: 'var(--muted)' }}>
|
|
21
|
-
Loading...
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (inline) return svg;
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '48px 0' }}>
|
|
31
|
-
{svg}
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default Spinner;
|
|
1
|
+
export function Spinner({ size = 20, inline = false, color = 'currentColor', fullPage = false }) {
|
|
2
|
+
const s = inline ? 14 : size;
|
|
3
|
+
const svg = (
|
|
4
|
+
<svg className="spin" width={s} height={s} viewBox="0 0 24 24" fill="none"
|
|
5
|
+
stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
6
|
+
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
|
|
7
|
+
</svg>
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
if (fullPage) {
|
|
11
|
+
return (
|
|
12
|
+
<div style={{
|
|
13
|
+
position: 'fixed', inset: 0,
|
|
14
|
+
background: 'var(--bg)',
|
|
15
|
+
display: 'flex', flexDirection: 'column',
|
|
16
|
+
alignItems: 'center', justifyContent: 'center', gap: 16,
|
|
17
|
+
zIndex: 9999,
|
|
18
|
+
}}>
|
|
19
|
+
{svg}
|
|
20
|
+
<div style={{ fontSize: '.75rem', fontFamily: 'var(--mono)', color: 'var(--muted)' }}>
|
|
21
|
+
Loading...
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (inline) return svg;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '48px 0' }}>
|
|
31
|
+
{svg}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default Spinner;
|
|
@@ -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: '#fff', border: '1px solid var(--border)', borderRadius: 'var(--rad)',
|
|
9
|
-
boxShadow: '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: '#fff', border: '1px solid var(--border)', borderRadius: 'var(--rad)',
|
|
9
|
+
boxShadow: '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
|
+
}
|