cynx-ui 1.3.22 → 1.3.24
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/Select.jsx +39 -28
- package/components/UptimeBar.jsx +76 -141
- package/package.json +1 -1
package/components/Select.jsx
CHANGED
|
@@ -37,6 +37,7 @@ export default function Select({
|
|
|
37
37
|
size = 'md',
|
|
38
38
|
style = {},
|
|
39
39
|
rounded = false,
|
|
40
|
+
showCount = false,
|
|
40
41
|
}) {
|
|
41
42
|
const [open, setOpen] = useState(false);
|
|
42
43
|
const [query, setQuery] = useState('');
|
|
@@ -217,32 +218,42 @@ export default function Select({
|
|
|
217
218
|
>
|
|
218
219
|
<div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center' }}>
|
|
219
220
|
{multi && selectedValues.length > 0 ? (
|
|
220
|
-
|
|
221
|
-
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
221
|
+
showCount ? (
|
|
222
|
+
<span style={{ fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: 'var(--t,var(--primary-text, #0f172a))' }}>
|
|
223
|
+
{typeof showCount === 'function'
|
|
224
|
+
? showCount(selectedValues.length)
|
|
225
|
+
: selectedValues.length === 1
|
|
226
|
+
? getLabel(selectedValues[0])
|
|
227
|
+
: `Выбрано: ${selectedValues.length}`}
|
|
228
|
+
</span>
|
|
229
|
+
) : (
|
|
230
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, flex: 1, minWidth: 0 }}>
|
|
231
|
+
{selectedValues.map(v => (
|
|
232
|
+
<span key={v} style={{
|
|
233
|
+
display: 'inline-flex',
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
gap: 4,
|
|
236
|
+
padding: '2px 6px',
|
|
237
|
+
background: 'var(--pd,var(--accent-bg, rgba(249,158,44,.1)))',
|
|
238
|
+
border: '1px solid var(--accent, #f99e2c)',
|
|
239
|
+
borderRadius: 4,
|
|
240
|
+
fontSize: 11,
|
|
241
|
+
fontWeight: 600,
|
|
242
|
+
color: 'var(--accent, #f99e2c)',
|
|
243
|
+
whiteSpace: 'nowrap',
|
|
244
|
+
maxWidth: 140,
|
|
245
|
+
opacity: 0.85,
|
|
246
|
+
}}>
|
|
247
|
+
{getLabel(v)}
|
|
248
|
+
<X
|
|
249
|
+
size={11}
|
|
250
|
+
style={{ cursor: 'pointer', flexShrink: 0, opacity: 0.5 }}
|
|
251
|
+
onMouseDown={(e) => removeTag(e, v)}
|
|
252
|
+
/>
|
|
253
|
+
</span>
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
)
|
|
246
257
|
) : !multi && selectedValues.length > 0 ? (
|
|
247
258
|
<span style={{ fontWeight: 400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
248
259
|
{getLabel(selectedValues[0]) || '\u00A0'}
|
|
@@ -251,12 +262,12 @@ export default function Select({
|
|
|
251
262
|
<span style={{ color: 'var(--t3,var(--grey-3, #cbd5e1))', fontWeight: 400 }}>{placeholder}</span>
|
|
252
263
|
)}
|
|
253
264
|
</div>
|
|
254
|
-
{
|
|
265
|
+
{selectedValues.length > 0 && selectedValues[0] !== '' && selectedValues[0] !== null && (clearable || !!onClear) ? (
|
|
255
266
|
<span
|
|
256
267
|
onClick={(e) => {
|
|
257
268
|
e.stopPropagation();
|
|
258
269
|
if (onClear) onClear();
|
|
259
|
-
else onChange?.('');
|
|
270
|
+
else onChange?.(multi ? [] : '');
|
|
260
271
|
}}
|
|
261
272
|
title="Сбросить выбор"
|
|
262
273
|
style={{
|
package/components/UptimeBar.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
const card = {
|
|
4
4
|
background: 'var(--surface,#fff)',
|
|
@@ -7,64 +7,26 @@ const card = {
|
|
|
7
7
|
boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04))',
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
11
|
-
|
|
12
|
-
function formatDate(date) {
|
|
13
|
-
return `${MONTHS[date.getMonth()]} ${date.getDate()}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
10
|
export default function UptimeBar({
|
|
17
|
-
title
|
|
11
|
+
title,
|
|
18
12
|
subtitle,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
totalSlots = 90,
|
|
22
|
-
greenColor = 'var(--success, #27ae60)',
|
|
23
|
-
redColor = 'var(--danger, #f64747)',
|
|
24
|
-
emptyColor = 'var(--grey-1, #f5f5f8)',
|
|
25
|
-
barHeight = 28,
|
|
26
|
-
barRadius = 3,
|
|
13
|
+
items = [],
|
|
14
|
+
value,
|
|
27
15
|
leftLabel,
|
|
28
16
|
rightLabel,
|
|
17
|
+
legend,
|
|
29
18
|
showDot = true,
|
|
30
|
-
dotColor,
|
|
19
|
+
dotColor = 'var(--success, #27ae60)',
|
|
20
|
+
barHeight = 28,
|
|
21
|
+
barRadius = 3,
|
|
22
|
+
emptyColor = 'var(--grey-1, #f5f5f8)',
|
|
31
23
|
style,
|
|
24
|
+
renderTooltip,
|
|
32
25
|
}) {
|
|
33
26
|
const [hovered, setHovered] = useState(null);
|
|
34
27
|
const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
|
|
35
28
|
const containerRef = useRef(null);
|
|
36
29
|
|
|
37
|
-
const bars = useMemo(() => {
|
|
38
|
-
const len = Math.max(successValues.length, failedValues.length, totalSlots);
|
|
39
|
-
const result = [];
|
|
40
|
-
const stepS = successValues.length / len;
|
|
41
|
-
const stepF = failedValues.length / len;
|
|
42
|
-
for (let i = 0; i < len; i++) {
|
|
43
|
-
const si = Math.min(Math.floor(i * stepS), successValues.length - 1);
|
|
44
|
-
const fi = Math.min(Math.floor(i * stepF), failedValues.length - 1);
|
|
45
|
-
const s = successValues[si] || 0;
|
|
46
|
-
const f = failedValues[fi] || 0;
|
|
47
|
-
result.push({ success: s, failed: f });
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
}, [successValues, failedValues, totalSlots]);
|
|
51
|
-
|
|
52
|
-
const dates = useMemo(() => {
|
|
53
|
-
const result = [];
|
|
54
|
-
const now = new Date();
|
|
55
|
-
for (let i = 0; i < bars.length; i++) {
|
|
56
|
-
const d = new Date(now);
|
|
57
|
-
d.setDate(d.getDate() - (bars.length - 1 - i));
|
|
58
|
-
result.push(d);
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
61
|
-
}, [bars.length]);
|
|
62
|
-
|
|
63
|
-
const totalSuccess = bars.reduce((a, b) => a + b.success, 0);
|
|
64
|
-
const totalFailed = bars.reduce((a, b) => a + b.failed, 0);
|
|
65
|
-
const totalAll = totalSuccess + totalFailed;
|
|
66
|
-
const uptimePercent = totalAll > 0 ? Math.round((totalSuccess / totalAll) * 100) : 100;
|
|
67
|
-
|
|
68
30
|
function handleMouseEnter(e, idx) {
|
|
69
31
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
70
32
|
const containerRect = containerRef.current.getBoundingClientRect();
|
|
@@ -75,126 +37,99 @@ export default function UptimeBar({
|
|
|
75
37
|
setHovered(idx);
|
|
76
38
|
}
|
|
77
39
|
|
|
40
|
+
// Smart tooltip shift so edge items are never clipped
|
|
41
|
+
const tooltipShift = hovered !== null
|
|
42
|
+
? (hovered > items.length - 8 ? '-92%' : hovered < 8 ? '-8%' : '-50%')
|
|
43
|
+
: '-50%';
|
|
44
|
+
|
|
78
45
|
return (
|
|
79
46
|
<div ref={containerRef} style={{ ...card, padding: '14px 18px 14px', position: 'relative', ...style }}>
|
|
80
47
|
{/* Header */}
|
|
81
|
-
|
|
82
|
-
<div style={{ display: 'flex', alignItems: 'center',
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
48
|
+
{(title || subtitle || (Array.isArray(legend) && legend.length > 0)) && (
|
|
49
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
|
|
50
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
51
|
+
{showDot && (
|
|
52
|
+
<span style={{
|
|
53
|
+
width: 8, height: 8, borderRadius: '50%',
|
|
54
|
+
background: dotColor, flexShrink: 0,
|
|
55
|
+
}} />
|
|
56
|
+
)}
|
|
57
|
+
{title && (
|
|
58
|
+
<span style={{ fontSize: '.78rem', fontWeight: 700, color: 'var(--primary-text, #0f172a)', letterSpacing: '-.01em' }}>
|
|
59
|
+
{title}
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
62
|
+
{subtitle && (
|
|
63
|
+
<span style={{ fontSize: '.65rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>
|
|
64
|
+
{subtitle}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
{/* Legend */}
|
|
69
|
+
{Array.isArray(legend) && legend.length > 0 && (
|
|
70
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
71
|
+
{legend.map((item, idx) => (
|
|
72
|
+
<div key={idx} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
73
|
+
<span style={{ width: 7, height: 7, borderRadius: 2, background: item.color, flexShrink: 0 }} />
|
|
74
|
+
<span style={{ fontSize: '.55rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>{item.label}</span>
|
|
75
|
+
</div>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
88
78
|
)}
|
|
89
|
-
<span style={{ fontSize: '.78rem', fontWeight: 700, color: 'var(--primary-text, #0f172a)', letterSpacing: '-.01em' }}>{title}</span>
|
|
90
|
-
{subtitle && <span style={{ fontSize: '.65rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>{subtitle}</span>}
|
|
91
|
-
</div>
|
|
92
|
-
{/* Legend */}
|
|
93
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
94
|
-
<LegendDot color={greenColor} label="Success" />
|
|
95
|
-
<LegendDot color={redColor} label="Failed" />
|
|
96
|
-
<LegendDot color={emptyColor} label="None" />
|
|
97
79
|
</div>
|
|
98
|
-
|
|
80
|
+
)}
|
|
99
81
|
|
|
100
|
-
{/* Bars */}
|
|
82
|
+
{/* Bars: purely renders precomputed items */}
|
|
101
83
|
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: barHeight, marginBottom: 6 }}>
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
height: '100%',
|
|
116
|
-
borderRadius: barRadius,
|
|
117
|
-
overflow: 'hidden',
|
|
118
|
-
display: 'flex',
|
|
119
|
-
flexDirection: 'column',
|
|
120
|
-
background: hasData ? 'transparent' : emptyColor,
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
{hasData && (
|
|
124
|
-
<>
|
|
125
|
-
{/* Failed on bottom */}
|
|
126
|
-
{b.failed > 0 && (
|
|
127
|
-
<div style={{ height: `${failedPct}%`, background: redColor, marginTop: 'auto' }} />
|
|
128
|
-
)}
|
|
129
|
-
{/* Success on top */}
|
|
130
|
-
{b.success > 0 && (
|
|
131
|
-
<div style={{ height: `${successPct}%`, background: greenColor }} />
|
|
132
|
-
)}
|
|
133
|
-
</>
|
|
134
|
-
)}
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
})}
|
|
84
|
+
{items.map((item, i) => (
|
|
85
|
+
<div
|
|
86
|
+
key={item.id || i}
|
|
87
|
+
onMouseEnter={(e) => handleMouseEnter(e, i)}
|
|
88
|
+
onMouseLeave={() => setHovered(null)}
|
|
89
|
+
style={{
|
|
90
|
+
flex: 1,
|
|
91
|
+
height: '100%',
|
|
92
|
+
borderRadius: barRadius,
|
|
93
|
+
background: item.color || emptyColor,
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
))}
|
|
138
97
|
</div>
|
|
139
98
|
|
|
140
|
-
{/* Tooltip */}
|
|
141
|
-
{hovered !== null && (
|
|
99
|
+
{/* Tooltip: renders caller content only */}
|
|
100
|
+
{hovered !== null && items[hovered] && (renderTooltip || items[hovered].tooltip) && (
|
|
142
101
|
<div style={{
|
|
143
102
|
position: 'absolute',
|
|
144
103
|
left: tooltipPos.x,
|
|
145
104
|
top: tooltipPos.y,
|
|
146
|
-
transform:
|
|
105
|
+
transform: `translate(${tooltipShift}, -100%)`,
|
|
147
106
|
background: 'var(--surface,#fff)',
|
|
148
107
|
border: '1px solid var(--border, #e2e8f0)',
|
|
149
108
|
borderRadius: 'var(--rads, 8px)',
|
|
150
109
|
boxShadow: 'var(--smd, 0 4px 12px rgba(0,0,0,.12))',
|
|
151
110
|
padding: '8px 12px',
|
|
152
111
|
pointerEvents: 'none',
|
|
153
|
-
zIndex:
|
|
112
|
+
zIndex: 1000,
|
|
154
113
|
whiteSpace: 'nowrap',
|
|
155
114
|
}}>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<div style={{ fontSize: '.7rem', fontWeight: 700, color: greenColor }}>
|
|
162
|
-
{bars[hovered].success} success login{bars[hovered].success !== 1 ? 's' : ''}
|
|
163
|
-
{bars[hovered].failed > 0 && <span style={{ fontWeight: 500, color: 'var(--muted, #797b8d)' }}> ({Math.round((bars[hovered].success / (bars[hovered].success + bars[hovered].failed)) * 100)}%)</span>}
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
{bars[hovered].failed > 0 && (
|
|
167
|
-
<div style={{ fontSize: '.7rem', fontWeight: 700, color: redColor }}>
|
|
168
|
-
{bars[hovered].failed} failed login{bars[hovered].failed !== 1 ? 's' : ''}
|
|
169
|
-
{bars[hovered].success > 0 && <span style={{ fontWeight: 500, color: 'var(--muted, #797b8d)' }}> ({Math.round((bars[hovered].failed / (bars[hovered].success + bars[hovered].failed)) * 100)}%)</span>}
|
|
170
|
-
</div>
|
|
171
|
-
)}
|
|
172
|
-
{bars[hovered].success === 0 && bars[hovered].failed === 0 && (
|
|
173
|
-
<div style={{ fontSize: '.7rem', fontWeight: 500, color: 'var(--muted, #797b8d)' }}>
|
|
174
|
-
No logins
|
|
175
|
-
</div>
|
|
176
|
-
)}
|
|
177
|
-
</div>
|
|
115
|
+
{renderTooltip
|
|
116
|
+
? renderTooltip(items[hovered], hovered)
|
|
117
|
+
: typeof items[hovered].tooltip === 'function'
|
|
118
|
+
? items[hovered].tooltip(items[hovered], hovered)
|
|
119
|
+
: items[hovered].tooltip}
|
|
178
120
|
</div>
|
|
179
121
|
)}
|
|
180
122
|
|
|
181
123
|
{/* Footer */}
|
|
182
124
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
183
|
-
<span style={{ fontSize: '.58rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>{leftLabel
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
125
|
+
<span style={{ fontSize: '.58rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>{leftLabel}</span>
|
|
126
|
+
{value && (
|
|
127
|
+
<span style={{ fontSize: '.78rem', fontWeight: 700, color: dotColor, fontFamily: 'var(--mono, ui-monospace, monospace)' }}>
|
|
128
|
+
{value}
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
<span style={{ fontSize: '.58rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>{rightLabel}</span>
|
|
188
132
|
</div>
|
|
189
133
|
</div>
|
|
190
134
|
);
|
|
191
135
|
}
|
|
192
|
-
|
|
193
|
-
function LegendDot({ color, label }) {
|
|
194
|
-
return (
|
|
195
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
196
|
-
<span style={{ width: 7, height: 7, borderRadius: 2, background: color, flexShrink: 0 }} />
|
|
197
|
-
<span style={{ fontSize: '.55rem', color: 'var(--muted, #797b8d)', fontWeight: 500 }}>{label}</span>
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
}
|