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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cynx-lib",
3
- "version": "1.3.66",
3
+ "version": "1.3.68",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -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
- const DAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
17
- const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
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
- export function DatePicker({ value, onChange, label, placeholder = 'Select date', badges = {}, minDate, maxDate, disabled, showStepper = true, stepperPosition = 'around', style }) {
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
- return () => document.removeEventListener('mousedown', close, true);
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
- setOpen(false);
157
- setView('day');
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: 32, height: 32, borderRadius: 'var(--rads, 8px)',
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={15} />
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: 32, height: 32, borderRadius: 'var(--rads, 8px)',
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={15} />
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: '5px 10px', borderRadius: 'var(--rads, 8px)',
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: '.8rem', color: formatted ? 'var(--primary-text, #0f172a)' : 'var(--muted, #797b8d)',
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: 140, height: 32, boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
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={13} style={{ color: 'var(--muted, #797b8d)', flexShrink: 0 }} />
505
- <span style={{ flex: 1, textAlign: 'left' }}>{formatted || placeholder}</span>
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: 13, lineHeight: 1, padding: '0 2px' }}
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={{ display: 'inline-flex', flexDirection: 'column', position: 'relative', width: style?.width || 'auto', boxSizing: 'border-box', minWidth: 0, ...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={{ position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 4, width: '100%' }}>
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;
@@ -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