cynx-lib 1.3.66 → 1.3.68
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/package.json +1 -1
- package/ui/components/DatePicker.jsx +163 -23
- package/ui/components/Drawer.jsx +208 -0
- package/ui/index.cjs +3 -0
- package/ui/index.js +2 -0
- package/vue/components/Badge.vue +8 -0
- package/vue/components/DatePicker.vue +978 -0
- package/vue/components/Drawer.vue +243 -0
- package/vue/components/Select.vue +10 -6
- package/vue/index.js +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
|
3
|
+
import { ChevronLeft, ChevronRight, Calendar, Clock } from 'lucide-react';
|
|
4
|
+
import { TimePicker } from './TimePicker.jsx';
|
|
4
5
|
|
|
5
6
|
// DatePicker — reusable date picker component
|
|
6
7
|
// Props:
|
|
@@ -12,9 +13,28 @@ import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
|
|
12
13
|
// maxDate – Date object, days after this are disabled
|
|
13
14
|
// disabled – boolean
|
|
14
15
|
// style – container style overrides
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// showTime – boolean, enables integrated TimePicker in dropdown
|
|
17
|
+
// timeValue – string, time string e.g. "14:30"
|
|
18
|
+
// onTimeChange – function(timeString)
|
|
19
|
+
// timeFormat – "24h" | "12h"
|
|
20
|
+
// showSeconds – boolean
|
|
21
|
+
|
|
22
|
+
const LOCALES = {
|
|
23
|
+
en: {
|
|
24
|
+
days: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
|
25
|
+
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
26
|
+
today: 'Today',
|
|
27
|
+
time: 'Time:',
|
|
28
|
+
ok: 'OK',
|
|
29
|
+
},
|
|
30
|
+
ru: {
|
|
31
|
+
days: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
|
|
32
|
+
months: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
|
|
33
|
+
today: 'Сегодня',
|
|
34
|
+
time: 'Время:',
|
|
35
|
+
ok: 'OK',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
18
38
|
|
|
19
39
|
function pad(n) { return String(n).padStart(2, '0'); }
|
|
20
40
|
function toKey(y, m, d) { return `${y}-${pad(m+1)}-${pad(d)}`; }
|
|
@@ -42,7 +62,40 @@ function isSameDay(a, b) {
|
|
|
42
62
|
}
|
|
43
63
|
function isToday(y, m, d) { const t = new Date(); return t.getFullYear()===y && t.getMonth()===m && t.getDate()===d; }
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
const SIZES = {
|
|
66
|
+
xs: { height: 24, fontSize: 11, paddingH: 6, iconSize: 11, btnSize: 24, closeSize: 11 },
|
|
67
|
+
sm: { height: 28, fontSize: 11.5, paddingH: 8, iconSize: 12, btnSize: 28, closeSize: 12 },
|
|
68
|
+
md: { height: 32, fontSize: 13, paddingH: 10, iconSize: 13, btnSize: 32, closeSize: 13 },
|
|
69
|
+
lg: { height: 36, fontSize: 14, paddingH: 12, iconSize: 14, btnSize: 36, closeSize: 14 },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function DatePicker({
|
|
73
|
+
value,
|
|
74
|
+
onChange,
|
|
75
|
+
label,
|
|
76
|
+
placeholder = 'Select date',
|
|
77
|
+
badges = {},
|
|
78
|
+
minDate,
|
|
79
|
+
maxDate,
|
|
80
|
+
disabled,
|
|
81
|
+
size = 'md',
|
|
82
|
+
showStepper = false,
|
|
83
|
+
stepperPosition = 'off',
|
|
84
|
+
locale = 'en',
|
|
85
|
+
style,
|
|
86
|
+
fullWidth = false,
|
|
87
|
+
showTime = false,
|
|
88
|
+
timeValue,
|
|
89
|
+
onTimeChange,
|
|
90
|
+
timeFormat = '24h',
|
|
91
|
+
showSeconds = false,
|
|
92
|
+
timePlaceholder = '00:00',
|
|
93
|
+
timeProps = {},
|
|
94
|
+
}) {
|
|
95
|
+
const loc = typeof locale === 'object' && locale !== null ? locale : (LOCALES[locale] || LOCALES.en);
|
|
96
|
+
const DAYS = loc.days;
|
|
97
|
+
const MONTHS = loc.months;
|
|
98
|
+
const s = SIZES[size] || SIZES.md;
|
|
46
99
|
const [open, setOpen] = useState(false);
|
|
47
100
|
const [view, setView] = useState('day'); // 'day' | 'month' | 'year'
|
|
48
101
|
const [cursor, setCursor] = useState(() => toDate(value) || new Date());
|
|
@@ -77,10 +130,24 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
77
130
|
function close(e) {
|
|
78
131
|
if (ref.current?.contains(e.target)) return;
|
|
79
132
|
if (dropRef.current?.contains(e.target)) return;
|
|
133
|
+
if (e.target?.closest?.('.cynx-timepicker-drop') || e.target?.closest?.('.cynx-timepicker-cell')) return;
|
|
134
|
+
if (document.querySelector('.cynx-timepicker-drop')) return;
|
|
135
|
+
if (typeof window !== 'undefined') window.__cynx_last_portal_closed_at = Date.now();
|
|
80
136
|
setOpen(false); setView('day');
|
|
81
137
|
}
|
|
138
|
+
function onKey(e) {
|
|
139
|
+
if (e.key === 'Escape') {
|
|
140
|
+
if (document.querySelector('.cynx-timepicker-drop')) return;
|
|
141
|
+
if (typeof window !== 'undefined') window.__cynx_last_portal_closed_at = Date.now();
|
|
142
|
+
setOpen(false); setView('day');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
82
145
|
document.addEventListener('mousedown', close, true);
|
|
83
|
-
|
|
146
|
+
document.addEventListener('keydown', onKey);
|
|
147
|
+
return () => {
|
|
148
|
+
document.removeEventListener('mousedown', close, true);
|
|
149
|
+
document.removeEventListener('keydown', onKey);
|
|
150
|
+
};
|
|
84
151
|
}, [open]);
|
|
85
152
|
|
|
86
153
|
const [stepperPosState, setStepperPosState] = useState(() => typeof window !== 'undefined' ? localStorage.getItem('datepicker_stepper_position') : null);
|
|
@@ -153,8 +220,10 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
153
220
|
} else {
|
|
154
221
|
onChange?.(date);
|
|
155
222
|
}
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
if (!showTime) {
|
|
224
|
+
setOpen(false);
|
|
225
|
+
setView('day');
|
|
226
|
+
}
|
|
158
227
|
}
|
|
159
228
|
|
|
160
229
|
const isNavNextDisabled = () => {
|
|
@@ -212,9 +281,10 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
212
281
|
}
|
|
213
282
|
|
|
214
283
|
const valDate = toDate(value);
|
|
284
|
+
const formattedTime = showTime && timeValue ? ` ${timeValue}` : '';
|
|
215
285
|
const formatted = valDate
|
|
216
|
-
? `${MONTHS[valDate.getMonth()]} ${valDate.getDate()}, ${valDate.getFullYear()}`
|
|
217
|
-
: (typeof value === 'string' ? value : null);
|
|
286
|
+
? `${MONTHS[valDate.getMonth()]} ${valDate.getDate()}, ${valDate.getFullYear()}${formattedTime}`
|
|
287
|
+
: (typeof value === 'string' && value ? `${value}${formattedTime}` : null);
|
|
218
288
|
|
|
219
289
|
// ── Day grid ─────────────────────────────────────────────────
|
|
220
290
|
function renderDays() {
|
|
@@ -450,7 +520,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
450
520
|
onClick={(e) => { e.stopPropagation(); changeDay(-1); }}
|
|
451
521
|
style={{
|
|
452
522
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
453
|
-
width:
|
|
523
|
+
width: s.btnSize, height: s.btnSize, borderRadius: 'var(--rads, 8px)',
|
|
454
524
|
border: '1px solid var(--border, #e2e8f0)', background: 'var(--surface, #ffffff)',
|
|
455
525
|
cursor: dis ? 'not-allowed' : 'pointer', opacity: dis ? 0.35 : 1,
|
|
456
526
|
color: 'var(--muted, #797b8d)', transition: 'all .15s', boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
|
|
@@ -460,7 +530,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
460
530
|
onMouseEnter={e => { if (!dis) { e.currentTarget.style.borderColor = 'var(--accent, #f99e2c)'; e.currentTarget.style.color = 'var(--accent, #f99e2c)'; } }}
|
|
461
531
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border, #e2e8f0)'; e.currentTarget.style.color = 'var(--muted, #797b8d)'; }}
|
|
462
532
|
>
|
|
463
|
-
<ChevronLeft size={
|
|
533
|
+
<ChevronLeft size={s.iconSize + 2} />
|
|
464
534
|
</button>
|
|
465
535
|
);
|
|
466
536
|
};
|
|
@@ -474,7 +544,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
474
544
|
onClick={(e) => { e.stopPropagation(); changeDay(1); }}
|
|
475
545
|
style={{
|
|
476
546
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
477
|
-
width:
|
|
547
|
+
width: s.btnSize, height: s.btnSize, borderRadius: 'var(--rads, 8px)',
|
|
478
548
|
border: '1px solid var(--border, #e2e8f0)', background: 'var(--surface, #ffffff)',
|
|
479
549
|
cursor: dis ? 'not-allowed' : 'pointer', opacity: dis ? 0.35 : 1,
|
|
480
550
|
color: 'var(--muted, #797b8d)', transition: 'all .15s', boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
|
|
@@ -484,7 +554,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
484
554
|
onMouseEnter={e => { if (!dis) { e.currentTarget.style.borderColor = 'var(--accent, #f99e2c)'; e.currentTarget.style.color = 'var(--accent, #f99e2c)'; } }}
|
|
485
555
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border, #e2e8f0)'; e.currentTarget.style.color = 'var(--muted, #797b8d)'; }}
|
|
486
556
|
>
|
|
487
|
-
<ChevronRight size={
|
|
557
|
+
<ChevronRight size={s.iconSize + 2} />
|
|
488
558
|
</button>
|
|
489
559
|
);
|
|
490
560
|
};
|
|
@@ -492,21 +562,24 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
492
562
|
const renderTriggerBtn = () => (
|
|
493
563
|
<button ref={btnRef} type="button" onClick={toggleOpen} style={{
|
|
494
564
|
display: 'flex', alignItems: 'center', gap: 7,
|
|
495
|
-
padding:
|
|
565
|
+
padding: `0 ${s.paddingH}px`, borderRadius: 'var(--rads, 8px)',
|
|
496
566
|
border: '1px solid var(--border, #e2e8f0)', background: 'var(--surface, #ffffff)',
|
|
497
567
|
cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
|
|
498
|
-
fontSize:
|
|
568
|
+
fontSize: s.fontSize, color: formatted ? 'var(--primary-text, #0f172a)' : 'var(--muted, #797b8d)',
|
|
499
569
|
fontFamily: 'var(--sans, inherit)', transition: 'border-color .15s',
|
|
500
|
-
whiteSpace: 'nowrap', minWidth:
|
|
570
|
+
whiteSpace: 'nowrap', minWidth: 0, flex: 1, height: s.height, boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
|
|
571
|
+
overflow: 'hidden'
|
|
501
572
|
}}
|
|
502
573
|
onMouseEnter={e => { if (!disabled) e.currentTarget.style.borderColor = 'var(--accent, #f99e2c)'; }}
|
|
503
574
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border, #e2e8f0)'; }}>
|
|
504
|
-
<Calendar size={
|
|
505
|
-
<span style={{ flex: 1, textAlign: 'left'
|
|
575
|
+
<Calendar size={s.iconSize} style={{ color: 'var(--muted, #797b8d)', flexShrink: 0 }} />
|
|
576
|
+
<span style={{ flex: 1, textAlign: 'left', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }}>
|
|
577
|
+
{formatted || placeholder}
|
|
578
|
+
</span>
|
|
506
579
|
{value && (
|
|
507
580
|
<span
|
|
508
581
|
onClick={e => { e.stopPropagation(); onChange?.(null); }}
|
|
509
|
-
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--muted, #797b8d)', cursor: 'pointer', flexShrink: 0, fontSize:
|
|
582
|
+
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--muted, #797b8d)', cursor: 'pointer', flexShrink: 0, fontSize: s.closeSize, lineHeight: 1, padding: '0 2px' }}
|
|
510
583
|
onMouseEnter={e => { e.currentTarget.style.color = 'var(--danger, #f64747)'; }}
|
|
511
584
|
onMouseLeave={e => { e.currentTarget.style.color = 'var(--muted, #797b8d)'; }}>
|
|
512
585
|
✕
|
|
@@ -522,13 +595,30 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
522
595
|
: 'around';
|
|
523
596
|
|
|
524
597
|
return (
|
|
525
|
-
<div style={{
|
|
598
|
+
<div style={{
|
|
599
|
+
display: fullWidth || style?.display === 'flex' ? 'flex' : 'inline-flex',
|
|
600
|
+
flexDirection: 'column',
|
|
601
|
+
position: 'relative',
|
|
602
|
+
width: style?.width || (fullWidth ? '100%' : 'auto'),
|
|
603
|
+
maxWidth: style?.maxWidth || (fullWidth ? '100%' : 320),
|
|
604
|
+
boxSizing: 'border-box',
|
|
605
|
+
minWidth: 0,
|
|
606
|
+
...style,
|
|
607
|
+
}}>
|
|
526
608
|
{label && (
|
|
527
609
|
<label style={{ fontSize: 11, fontWeight: 600, color: 'var(--t2, var(--muted, #797b8d))', textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 4 }}>
|
|
528
610
|
{label}
|
|
529
611
|
</label>
|
|
530
612
|
)}
|
|
531
|
-
<div ref={ref} style={{
|
|
613
|
+
<div ref={ref} style={{
|
|
614
|
+
position: 'relative',
|
|
615
|
+
display: 'inline-flex',
|
|
616
|
+
alignItems: 'center',
|
|
617
|
+
gap: 4,
|
|
618
|
+
width: '100%',
|
|
619
|
+
maxWidth: '100%',
|
|
620
|
+
minWidth: 0,
|
|
621
|
+
}}>
|
|
532
622
|
{showStepperEffective && posMode === 'around' && renderPrevBtn()}
|
|
533
623
|
{showStepperEffective && posMode === 'left' && renderPrevBtn()}
|
|
534
624
|
{showStepperEffective && posMode === 'left' && renderNextBtn()}
|
|
@@ -542,7 +632,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
542
632
|
|
|
543
633
|
{/* Dropdown via portal */}
|
|
544
634
|
{open && createPortal(
|
|
545
|
-
<div ref={dropRef} style={{
|
|
635
|
+
<div ref={dropRef} className="cynx-datepicker-drop" style={{
|
|
546
636
|
position: 'fixed', top: pos.top, left: pos.left, width: pos.width, zIndex: 9000,
|
|
547
637
|
background: 'var(--surface, #ffffff)', border: '1px solid var(--border, #e2e8f0)',
|
|
548
638
|
borderRadius: 'var(--rad, 10px)', boxShadow: 'var(--shadow-lg, 0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -4px rgba(0,0,0,.06))',
|
|
@@ -595,6 +685,56 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
595
685
|
{view === 'month' && renderMonths()}
|
|
596
686
|
{view === 'year' && renderYears()}
|
|
597
687
|
|
|
688
|
+
{/* Integrated Time Picker footer */}
|
|
689
|
+
{showTime && (
|
|
690
|
+
<div
|
|
691
|
+
style={{
|
|
692
|
+
borderTop: '1px solid var(--border-light, #ebebf0)',
|
|
693
|
+
marginTop: 8,
|
|
694
|
+
paddingTop: 8,
|
|
695
|
+
display: 'flex',
|
|
696
|
+
alignItems: 'center',
|
|
697
|
+
justifyContent: 'space-between',
|
|
698
|
+
gap: 8,
|
|
699
|
+
}}
|
|
700
|
+
>
|
|
701
|
+
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: '.76rem', color: 'var(--muted, #797b8d)', fontWeight: 600 }}>
|
|
702
|
+
<Clock size={13} style={{ opacity: 0.85 }} />
|
|
703
|
+
<span>{loc.time || 'Time:'}</span>
|
|
704
|
+
</div>
|
|
705
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
706
|
+
<TimePicker
|
|
707
|
+
value={timeValue || ''}
|
|
708
|
+
onChange={(t) => onTimeChange?.(t)}
|
|
709
|
+
format={timeFormat}
|
|
710
|
+
showSeconds={showSeconds}
|
|
711
|
+
placeholder={timePlaceholder}
|
|
712
|
+
size={timeProps.size || size || 'md'}
|
|
713
|
+
{...timeProps}
|
|
714
|
+
/>
|
|
715
|
+
<button
|
|
716
|
+
type="button"
|
|
717
|
+
onClick={() => { setOpen(false); setView('day'); }}
|
|
718
|
+
style={{
|
|
719
|
+
padding: '4px 10px',
|
|
720
|
+
borderRadius: 'var(--radxs, 6px)',
|
|
721
|
+
background: 'var(--accent, #f99e2c)',
|
|
722
|
+
color: 'var(--def-btn-text, #ffffff)',
|
|
723
|
+
border: 'none',
|
|
724
|
+
fontSize: '11.5px',
|
|
725
|
+
fontWeight: 600,
|
|
726
|
+
cursor: 'pointer',
|
|
727
|
+
transition: 'opacity 0.15s ease',
|
|
728
|
+
}}
|
|
729
|
+
onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.9'; }}
|
|
730
|
+
onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
|
|
731
|
+
>
|
|
732
|
+
{loc.ok || 'OK'}
|
|
733
|
+
</button>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
)}
|
|
737
|
+
|
|
598
738
|
</div>,
|
|
599
739
|
document.body
|
|
600
740
|
)}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
const DRAWER_STYLES = `
|
|
6
|
+
@keyframes cynxDrawerFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
7
|
+
@keyframes cynxDrawerFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
|
8
|
+
@keyframes cynxDrawerSlideInRight { from { transform: translateX(100%); } to { transform: translateX(0); } }
|
|
9
|
+
@keyframes cynxDrawerSlideOutRight { from { transform: translateX(0); } to { transform: translateX(100%); } }
|
|
10
|
+
@keyframes cynxDrawerSlideInLeft { from { transform: translateX(-100%); } to { transform: translateX(0); } }
|
|
11
|
+
@keyframes cynxDrawerSlideOutLeft { from { transform: translateX(0); } to { transform: translateX(-100%); } }
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
let _drawer_id = 'cynx-drawer-styles';
|
|
15
|
+
function ensureDrawerStyles() {
|
|
16
|
+
if (typeof document === 'undefined') return;
|
|
17
|
+
if (document.getElementById(_drawer_id)) return;
|
|
18
|
+
const tag = document.createElement('style');
|
|
19
|
+
tag.id = _drawer_id;
|
|
20
|
+
tag.textContent = DRAWER_STYLES;
|
|
21
|
+
document.head.appendChild(tag);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Drawer({
|
|
25
|
+
open = false,
|
|
26
|
+
onClose,
|
|
27
|
+
title = '',
|
|
28
|
+
description = '',
|
|
29
|
+
width = 560,
|
|
30
|
+
side = 'right',
|
|
31
|
+
showClose = true,
|
|
32
|
+
children,
|
|
33
|
+
footer,
|
|
34
|
+
header,
|
|
35
|
+
style = {},
|
|
36
|
+
bodyStyle = {},
|
|
37
|
+
}) {
|
|
38
|
+
const [closing, setClosing] = useState(false);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
ensureDrawerStyles();
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const handleClose = () => {
|
|
45
|
+
if (closing) return;
|
|
46
|
+
setClosing(true);
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
setClosing(false);
|
|
49
|
+
onClose?.();
|
|
50
|
+
}, 260);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (typeof document === 'undefined') return;
|
|
55
|
+
const handleKeydown = (e) => {
|
|
56
|
+
if (e.key === 'Escape' && open) handleClose();
|
|
57
|
+
};
|
|
58
|
+
if (open) {
|
|
59
|
+
document.body.style.overflow = 'hidden';
|
|
60
|
+
window.addEventListener('keydown', handleKeydown);
|
|
61
|
+
} else {
|
|
62
|
+
document.body.style.overflow = '';
|
|
63
|
+
window.removeEventListener('keydown', handleKeydown);
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
document.body.style.overflow = '';
|
|
67
|
+
window.removeEventListener('keydown', handleKeydown);
|
|
68
|
+
};
|
|
69
|
+
}, [open]);
|
|
70
|
+
|
|
71
|
+
if (!open && !closing) return null;
|
|
72
|
+
if (typeof document === 'undefined') return null;
|
|
73
|
+
|
|
74
|
+
const resolvedWidth = typeof width === 'number' ? `${width}px` : width;
|
|
75
|
+
const slideInAnim = side === 'left' ? 'cynxDrawerSlideInLeft' : 'cynxDrawerSlideInRight';
|
|
76
|
+
const slideOutAnim = side === 'left' ? 'cynxDrawerSlideOutLeft' : 'cynxDrawerSlideOutRight';
|
|
77
|
+
|
|
78
|
+
return createPortal(
|
|
79
|
+
<div
|
|
80
|
+
style={{
|
|
81
|
+
position: 'fixed',
|
|
82
|
+
inset: 0,
|
|
83
|
+
background: 'rgba(20, 22, 41, .45)',
|
|
84
|
+
zIndex: 8500,
|
|
85
|
+
display: 'flex',
|
|
86
|
+
justifyContent: side === 'left' ? 'flex-start' : 'flex-end',
|
|
87
|
+
animation: `${closing ? 'cynxDrawerFadeOut' : 'cynxDrawerFadeIn'} .26s ease forwards`,
|
|
88
|
+
}}
|
|
89
|
+
onMouseDown={(e) => {
|
|
90
|
+
if (e.target === e.currentTarget) handleClose();
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<div
|
|
94
|
+
style={{
|
|
95
|
+
background: 'var(--surface, #ffffff)',
|
|
96
|
+
borderLeft: side === 'right' ? '1px solid var(--border, var(--bd, #e2e8f0))' : 'none',
|
|
97
|
+
borderRight: side === 'left' ? '1px solid var(--border, var(--bd, #e2e8f0))' : 'none',
|
|
98
|
+
width: resolvedWidth,
|
|
99
|
+
maxWidth: '100vw',
|
|
100
|
+
height: '100%',
|
|
101
|
+
boxShadow: 'var(--shadow-lg, 0 20px 60px rgba(0, 0, 0, .25))',
|
|
102
|
+
display: 'flex',
|
|
103
|
+
flexDirection: 'column',
|
|
104
|
+
overflow: 'hidden',
|
|
105
|
+
animation: `${closing ? slideOutAnim : slideInAnim} .26s cubic-bezier(.16, 1, .3, 1) forwards`,
|
|
106
|
+
...style,
|
|
107
|
+
}}
|
|
108
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
109
|
+
>
|
|
110
|
+
{/* Header */}
|
|
111
|
+
<div
|
|
112
|
+
style={{
|
|
113
|
+
display: 'flex',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
justifyContent: 'space-between',
|
|
116
|
+
padding: '16px 20px',
|
|
117
|
+
borderBottom: '1px solid var(--border-light, #ebebf0)',
|
|
118
|
+
flexShrink: 0,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{header ? (
|
|
122
|
+
header
|
|
123
|
+
) : (
|
|
124
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
125
|
+
<span
|
|
126
|
+
style={{
|
|
127
|
+
fontSize: '1rem',
|
|
128
|
+
fontWeight: 700,
|
|
129
|
+
letterSpacing: '-.01em',
|
|
130
|
+
color: 'var(--t, var(--primary-text, #0f111c))',
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
{{ title }}
|
|
134
|
+
</span>
|
|
135
|
+
{description && (
|
|
136
|
+
<span
|
|
137
|
+
style={{
|
|
138
|
+
fontSize: '.75rem',
|
|
139
|
+
color: 'var(--muted, #797b8d)',
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
{description}
|
|
143
|
+
</span>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{showClose && (
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
style={{
|
|
152
|
+
background: 'none',
|
|
153
|
+
border: 'none',
|
|
154
|
+
cursor: 'pointer',
|
|
155
|
+
padding: '6px',
|
|
156
|
+
borderRadius: '6px',
|
|
157
|
+
color: 'var(--muted, #797b8d)',
|
|
158
|
+
display: 'flex',
|
|
159
|
+
alignItems: 'center',
|
|
160
|
+
justifyContent: 'center',
|
|
161
|
+
transition: 'all .14s',
|
|
162
|
+
}}
|
|
163
|
+
onClick={handleClose}
|
|
164
|
+
>
|
|
165
|
+
<X size={18} />
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Body */}
|
|
171
|
+
<div
|
|
172
|
+
className="custom-scrollbar"
|
|
173
|
+
style={{
|
|
174
|
+
padding: '20px',
|
|
175
|
+
overflowX: 'hidden',
|
|
176
|
+
overflowY: 'auto',
|
|
177
|
+
flex: '1 1 auto',
|
|
178
|
+
minHeight: 0,
|
|
179
|
+
color: 'var(--t, var(--primary-text, #0f111c))',
|
|
180
|
+
...bodyStyle,
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{children}
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{/* Footer */}
|
|
187
|
+
{footer && (
|
|
188
|
+
<div
|
|
189
|
+
style={{
|
|
190
|
+
display: 'flex',
|
|
191
|
+
justifyContent: 'flex-end',
|
|
192
|
+
gap: 8,
|
|
193
|
+
padding: '14px 20px',
|
|
194
|
+
borderTop: '1px solid var(--border-light, #ebebf0)',
|
|
195
|
+
background: 'var(--grey-1, #f5f5f8)',
|
|
196
|
+
flexShrink: 0,
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
{footer}
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
</div>,
|
|
204
|
+
document.body
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default Drawer;
|
package/ui/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
ConfirmRoot: () => ConfirmRoot,
|
|
42
42
|
DatePicker: () => DatePicker,
|
|
43
43
|
Divider: () => Divider,
|
|
44
|
+
Drawer: () => Drawer,
|
|
44
45
|
Dropzone: () => Dropzone,
|
|
45
46
|
FilePicker: () => FilePicker,
|
|
46
47
|
HoverCard: () => HoverCard,
|
|
@@ -111,6 +112,7 @@ var import_PulseDot = __toESM(require("./components/PulseDot.jsx"), 1);
|
|
|
111
112
|
var import_CircularProgress = __toESM(require("./components/CircularProgress.jsx"), 1);
|
|
112
113
|
var import_Skeleton = __toESM(require("./components/Skeleton.jsx"), 1);
|
|
113
114
|
var import_Modal = require("./components/Modal.jsx");
|
|
115
|
+
var import_Drawer = require("./components/Drawer.jsx");
|
|
114
116
|
var import_Toast = require("./components/Toast.jsx");
|
|
115
117
|
var import_Confirm = require("./components/Confirm.jsx");
|
|
116
118
|
var import_Pagination = require("./components/Pagination.jsx");
|
|
@@ -157,6 +159,7 @@ const PulseDot = import_PulseDot.default;
|
|
|
157
159
|
const CircularProgress = import_CircularProgress.default;
|
|
158
160
|
const Skeleton = import_Skeleton.default;
|
|
159
161
|
const Modal = import_Modal.Modal;
|
|
162
|
+
const Drawer = import_Drawer.Drawer;
|
|
160
163
|
const Confirm = import_Modal.Confirm;
|
|
161
164
|
const toast = import_Toast.toast;
|
|
162
165
|
const useToast = import_Toast.useToast;
|
package/ui/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { default as _PulseDot } from './components/PulseDot.jsx';
|
|
|
21
21
|
import { default as _CircularProgress } from './components/CircularProgress.jsx';
|
|
22
22
|
import { default as _Skeleton } from './components/Skeleton.jsx';
|
|
23
23
|
import { Modal as _Modal, Confirm as _Confirm } from './components/Modal.jsx';
|
|
24
|
+
import { Drawer as _Drawer } from './components/Drawer.jsx';
|
|
24
25
|
import { toast as _toast, useToast as _useToast, Toast as _Toast } from './components/Toast.jsx';
|
|
25
26
|
import { ConfirmRoot as _ConfirmRoot, confirm as _confirm } from './components/Confirm.jsx';
|
|
26
27
|
import { Pagination as _Pagination } from './components/Pagination.jsx';
|
|
@@ -69,6 +70,7 @@ export const PulseDot = _PulseDot;
|
|
|
69
70
|
export const CircularProgress = _CircularProgress;
|
|
70
71
|
export const Skeleton = _Skeleton;
|
|
71
72
|
export const Modal = _Modal;
|
|
73
|
+
export const Drawer = _Drawer;
|
|
72
74
|
export const Confirm = _Confirm;
|
|
73
75
|
export const toast = _toast;
|
|
74
76
|
export const useToast = _useToast;
|
package/vue/components/Badge.vue
CHANGED
|
@@ -3,11 +3,19 @@ import { computed } from "vue";
|
|
|
3
3
|
|
|
4
4
|
const VARIANTS: Record<string, any> = {
|
|
5
5
|
ok: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
|
|
6
|
+
success: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
|
|
6
7
|
err: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
|
|
8
|
+
danger: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
|
|
9
|
+
error: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
|
|
7
10
|
warn: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
|
|
11
|
+
warning: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
|
|
8
12
|
blue: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
|
|
13
|
+
info: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
|
|
9
14
|
teal: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
|
|
15
|
+
primary: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
|
|
10
16
|
dim: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
|
|
17
|
+
neutral: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
|
|
18
|
+
secondary: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
|
|
11
19
|
ghost: { bg: "transparent", color: "var(--t2, #64748b)", border: "var(--bd, #e2e8f0)", dot: "var(--t3, #94a3b8)" },
|
|
12
20
|
};
|
|
13
21
|
|