cynx-ui 1.2.48 → 1.3.0

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.
@@ -0,0 +1,1069 @@
1
+ import { useState, useRef, useEffect, useCallback, useLayoutEffect } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { Clock, ChevronDown, ChevronUp, Check, X, Globe, List, Sliders } from 'lucide-react';
4
+
5
+ const STYLES = `
6
+ @keyframes cynxTimePickerPopIn {
7
+ 0% {
8
+ opacity: 0;
9
+ transform: translateY(-8px) scale(0.96);
10
+ }
11
+ 100% {
12
+ opacity: 1;
13
+ transform: translateY(0) scale(1);
14
+ }
15
+ }
16
+
17
+ @keyframes cynxTimePickerPopOut {
18
+ 0% {
19
+ opacity: 1;
20
+ transform: translateY(0) scale(1);
21
+ }
22
+ 100% {
23
+ opacity: 0;
24
+ transform: translateY(-8px) scale(0.96);
25
+ }
26
+ }
27
+
28
+ .cynx-timepicker-drop {
29
+ animation: cynxTimePickerPopIn 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards;
30
+ will-change: opacity, transform;
31
+ }
32
+
33
+ .cynx-timepicker-drop.closing {
34
+ animation: cynxTimePickerPopOut 0.14s cubic-bezier(0.16, 1, 0.3, 1) forwards;
35
+ }
36
+
37
+ .cynx-timepicker-cell {
38
+ transition: background-color 0.12s ease, color 0.12s ease;
39
+ }
40
+
41
+ .cynx-timepicker-cell:hover {
42
+ background-color: var(--grey-1, var(--surface-hover));
43
+ }
44
+
45
+ .cynx-timepicker-stepper-btn {
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ width: 100%;
50
+ height: 28px;
51
+ border-radius: var(--rads, 6px);
52
+ border: 1px solid var(--border-light, var(--border));
53
+ background: var(--grey-1, var(--surface-hover));
54
+ color: var(--fg, var(--t1));
55
+ cursor: pointer;
56
+ transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
57
+ }
58
+
59
+ .cynx-timepicker-stepper-btn:hover {
60
+ background: var(--surface, var(--bg));
61
+ border-color: var(--accent, var(--blue));
62
+ color: var(--accent, var(--blue));
63
+ }
64
+
65
+ .cynx-timepicker-stepper-btn:active {
66
+ background: var(--accent-bg, rgba(37,99,235,0.08));
67
+ }
68
+ `;
69
+
70
+ const STYLE_ID = 'cynx-timepicker-styles';
71
+
72
+ function ensureStyles() {
73
+ if (typeof document === 'undefined') return;
74
+ if (document.getElementById(STYLE_ID)) return;
75
+ const tag = document.createElement('style');
76
+ tag.id = STYLE_ID;
77
+ tag.textContent = STYLES;
78
+ document.head.appendChild(tag);
79
+ }
80
+
81
+ const COMMON_TIMEZONES = [
82
+ { value: 'system', label: 'Системное время (Local)' },
83
+ { value: 'Asia/Tashkent', label: 'Ташкент (UTC+5)' },
84
+ { value: 'America/New_York', label: 'Нью-Йорк (UTC-5)' },
85
+ { value: 'Europe/London', label: 'Лондон (UTC+0)' },
86
+ { value: 'Asia/Tokyo', label: 'Токио (UTC+9)' },
87
+ { value: 'UTC', label: 'UTC' },
88
+ ];
89
+
90
+ function pad(n) {
91
+ return String(n).padStart(2, '0');
92
+ }
93
+
94
+ function to12h(h24) {
95
+ const h = parseInt(h24, 10);
96
+ if (isNaN(h)) return { h12: '12', ampm: 'AM' };
97
+ const ampm = h >= 12 ? 'PM' : 'AM';
98
+ let h12 = h % 12;
99
+ if (h12 === 0) h12 = 12;
100
+ return { h12: pad(h12), ampm };
101
+ }
102
+
103
+ function to24h(h12, ampm) {
104
+ let h = parseInt(h12, 10);
105
+ if (isNaN(h)) return '00';
106
+ if (ampm === 'PM' && h < 12) h += 12;
107
+ if (ampm === 'AM' && h === 12) h = 0;
108
+ return pad(h);
109
+ }
110
+
111
+ function parseTime(val, is12h, showSeconds) {
112
+ if (!val || typeof val !== 'string') return { h24: '00', h12: '12', m: '00', s: '00', ampm: 'AM' };
113
+
114
+ let str = val.trim();
115
+ let ampm = 'AM';
116
+ if (/pm/i.test(str)) { ampm = 'PM'; str = str.replace(/pm/i, '').trim(); }
117
+ else if (/am/i.test(str)) { ampm = 'AM'; str = str.replace(/am/i, '').trim(); }
118
+
119
+ let p0 = 0, p1 = 0, p2 = 0;
120
+
121
+ if (str.includes(':')) {
122
+ const parts = str.split(':').map(p => p.trim());
123
+ p0 = parseInt(parts[0], 10) || 0;
124
+ p1 = parseInt(parts[1], 10) || 0;
125
+ p2 = parseInt(parts[2], 10) || 0;
126
+ } else if (/^\d{3,6}$/.test(str)) {
127
+ p0 = parseInt(str.slice(0, 2), 10) || 0;
128
+ p1 = parseInt(str.slice(2, 4), 10) || 0;
129
+ p2 = parseInt(str.slice(4, 6), 10) || 0;
130
+ } else {
131
+ p0 = parseInt(str, 10) || 0;
132
+ }
133
+
134
+ let h24 = '00';
135
+ let h12 = '12';
136
+
137
+ if (is12h) {
138
+ if (p0 > 12) {
139
+ h24 = pad(Math.min(23, Math.max(0, p0)));
140
+ const conv = to12h(h24);
141
+ h12 = conv.h12;
142
+ ampm = conv.ampm;
143
+ } else {
144
+ let hNum = Math.min(12, Math.max(1, p0));
145
+ h12 = pad(hNum);
146
+ h24 = to24h(h12, ampm);
147
+ }
148
+ } else {
149
+ let hNum = Math.min(23, Math.max(0, p0));
150
+ h24 = pad(hNum);
151
+ const conv = to12h(h24);
152
+ h12 = conv.h12;
153
+ ampm = conv.ampm;
154
+ }
155
+
156
+ const m = pad(Math.min(59, Math.max(0, p1)));
157
+ const s = pad(Math.min(59, Math.max(0, p2)));
158
+
159
+ return { h24, h12, m, s, ampm };
160
+ }
161
+
162
+ export function TimePicker({
163
+ value = '',
164
+ onChange,
165
+ variant = 'list', // 'stepper' (+/- buttons) | 'list' (full 00..59 scrollable list)
166
+ showVariantToggle = false, // Default false: clean list mode by default!
167
+ format = '24h', // '24h' | '12h'
168
+ showSeconds = false,
169
+ showIcon = false, // Default false: clock icon hidden by default, set showIcon={true} to display
170
+ hideIcon = false, // Legacy prop alias for showIcon = false
171
+ showFormatToggle = false, // Minimal default: false
172
+ showPresets = false, // Minimal default: false
173
+ showTimezoneSelect = false, // Minimal default: false
174
+ timezone = 'Asia/Tashkent',
175
+ label,
176
+ placeholder,
177
+ size = 'sm', // 'xs' | 'sm' | 'md'
178
+ disabled = false,
179
+ readOnly = false,
180
+ style = {},
181
+ className = '',
182
+ }) {
183
+ const displayClockIcon = showIcon && !hideIcon;
184
+ const [open, setOpen] = useState(false);
185
+ const [rendered, setRendered] = useState(false);
186
+ const [closing, setClosing] = useState(false);
187
+
188
+ const [activeVariant, setActiveVariant] = useState(variant);
189
+ const [mode, setMode] = useState(format);
190
+ const [tz, setTz] = useState(timezone);
191
+ const [showTzPicker, setShowTzPicker] = useState(false);
192
+
193
+ const btnRef = useRef(null);
194
+ const dropRef = useRef(null);
195
+ const isTypingRef = useRef(false);
196
+
197
+ const [pos, setPos] = useState({ top: 0, left: 0, width: 230 });
198
+
199
+ ensureStyles();
200
+
201
+ useEffect(() => { setActiveVariant(variant); }, [variant]);
202
+ useEffect(() => { setMode(format); }, [format]);
203
+ useEffect(() => { setTz(timezone); }, [timezone]);
204
+
205
+ const openPicker = useCallback(() => {
206
+ if (disabled || readOnly) return;
207
+ setRendered(true);
208
+ setClosing(false);
209
+ setOpen(true);
210
+ }, [disabled, readOnly]);
211
+
212
+ const closePicker = useCallback(() => {
213
+ if (!open) return;
214
+ setClosing(true);
215
+ setOpen(false);
216
+ setShowTzPicker(false);
217
+ setTimeout(() => {
218
+ setRendered(false);
219
+ setClosing(false);
220
+ }, 140);
221
+ }, [open]);
222
+
223
+ const togglePicker = useCallback(() => {
224
+ if (open) closePicker();
225
+ else openPicker();
226
+ }, [open, closePicker, openPicker]);
227
+
228
+ const formatOutput = useCallback((parsedObj, useMode = mode) => {
229
+ const { h24, h12, m, s, ampm } = parsedObj;
230
+ if (useMode === '12h') {
231
+ return showSeconds ? `${h12}:${m}:${s} ${ampm}` : `${h12}:${m} ${ampm}`;
232
+ }
233
+ return showSeconds ? `${h24}:${m}:${s}` : `${h24}:${m}`;
234
+ }, [mode, showSeconds]);
235
+
236
+ const parsed = parseTime(value, mode === '12h', showSeconds);
237
+ const [inputValue, setInputValue] = useState(() => value ? formatOutput(parsed) : '');
238
+
239
+ // Keep inputValue in sync with props ONLY when user is NOT actively typing!
240
+ useEffect(() => {
241
+ if (!isTypingRef.current) {
242
+ if (value) {
243
+ const p = parseTime(value, mode === '12h', showSeconds);
244
+ setInputValue(formatOutput(p));
245
+ } else {
246
+ setInputValue('');
247
+ }
248
+ }
249
+ }, [value, mode, showSeconds, formatOutput]);
250
+
251
+ const calcPos = useCallback(() => {
252
+ if (!btnRef.current) return;
253
+ const r = btnRef.current.getBoundingClientRect();
254
+ const dropW = mode === '12h' ? 290 : 230;
255
+ const dropH = dropRef.current ? dropRef.current.offsetHeight : 230;
256
+
257
+ let left = r.left;
258
+ if (left + dropW > window.innerWidth - 12) {
259
+ left = Math.max(12, r.right - dropW);
260
+ }
261
+ if (left < 12) left = 12;
262
+
263
+ let top = r.bottom + 4;
264
+ if (top + dropH > window.innerHeight - 12 && r.top - 4 - dropH > 12) {
265
+ top = r.top - 4 - dropH;
266
+ }
267
+
268
+ setPos({ top, left, width: Math.max(r.width, dropW) });
269
+ }, [mode]);
270
+
271
+ useLayoutEffect(() => {
272
+ if (!rendered) return;
273
+ calcPos();
274
+ }, [rendered, calcPos]);
275
+
276
+ useEffect(() => {
277
+ if (!rendered || closing) return;
278
+ function onDown(e) {
279
+ const inBtn = btnRef.current && btnRef.current.contains(e.target);
280
+ const inDrop = dropRef.current && dropRef.current.contains(e.target);
281
+ if (!inBtn && !inDrop) {
282
+ closePicker();
283
+ }
284
+ }
285
+ const timer = setTimeout(() => {
286
+ document.addEventListener('mousedown', onDown, true);
287
+ }, 0);
288
+ window.addEventListener('scroll', calcPos, true);
289
+ window.addEventListener('resize', calcPos);
290
+ return () => {
291
+ clearTimeout(timer);
292
+ document.removeEventListener('mousedown', onDown, true);
293
+ window.removeEventListener('scroll', calcPos, true);
294
+ window.removeEventListener('resize', calcPos);
295
+ };
296
+ }, [rendered, closing, calcPos, closePicker]);
297
+
298
+ const emitChange = (parsedObj, useMode = mode) => {
299
+ const formatted = formatOutput(parsedObj, useMode);
300
+ setInputValue(formatted);
301
+ if (onChange) {
302
+ onChange(formatted, {
303
+ hours: parseInt(parsedObj.h24, 10),
304
+ minutes: parseInt(parsedObj.m, 10),
305
+ seconds: parseInt(parsedObj.s, 10),
306
+ ampm: parsedObj.ampm,
307
+ format: useMode,
308
+ timezone: tz,
309
+ variant: activeVariant,
310
+ });
311
+ }
312
+ };
313
+
314
+ const handleKeyDown = (e) => {
315
+ if (
316
+ ['Backspace', 'Delete', 'Tab', 'Escape', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key) ||
317
+ e.ctrlKey || e.metaKey || e.altKey
318
+ ) {
319
+ return;
320
+ }
321
+ const allowed = mode === '12h' ? /^[0-9:\sAPMapm]$/ : /^[0-9:]$/;
322
+ if (!allowed.test(e.key)) {
323
+ e.preventDefault();
324
+ }
325
+ };
326
+
327
+ const handleInputChange = (e) => {
328
+ let raw = e.target.value;
329
+ isTypingRef.current = true;
330
+
331
+ // 1. Strict filter: allow ONLY digits and colon (and AM/PM in 12h)
332
+ if (mode === '12h') {
333
+ raw = raw.replace(/[^0-9:\sAPMapm]/g, '');
334
+ } else {
335
+ raw = raw.replace(/[^0-9:]/g, '');
336
+ }
337
+
338
+ // 2. Single-digit hour shortcut (e.g. typing "3" -> "03:")
339
+ if (mode === '24h' && /^[3-9]$/.test(raw)) {
340
+ raw = '0' + raw + ':';
341
+ } else if (mode === '12h' && /^[2-9]$/.test(raw) && !/pm|am/i.test(raw)) {
342
+ raw = '0' + raw + ':';
343
+ }
344
+
345
+ // 3. Auto-append 1st colon after 2 hour digits + cap hour max (23 in 24h, 12 in 12h)
346
+ if (/^\d{2}$/.test(raw) && !inputValue.endsWith(':')) {
347
+ const hNum = parseInt(raw, 10);
348
+ if (mode === '24h' && hNum > 23) raw = '23:';
349
+ else if (mode === '12h' && hNum > 12) raw = '12:';
350
+ else raw = raw + ':';
351
+ }
352
+
353
+ // 4. Minutes tens digit validation: first digit of minute (after 1st colon) CANNOT be > 5!
354
+ if (/^\d{2}:[6-9]/.test(raw)) {
355
+ raw = raw.replace(/^(\d{2}:)[6-9]/, '$15');
356
+ }
357
+
358
+ // 5. Auto-append 2nd colon after 2 minute digits if showSeconds is true (e.g. "16:16" -> "16:16:")
359
+ if (showSeconds && /^\d{2}:\d{2}$/.test(raw) && !inputValue.endsWith(':')) {
360
+ const mNum = parseInt(raw.slice(3, 5), 10);
361
+ if (mNum > 59) raw = raw.slice(0, 3) + '59:';
362
+ else raw = raw + ':';
363
+ }
364
+
365
+ // 6. Seconds tens digit validation (if showSeconds): first digit of second CANNOT be > 5!
366
+ if (showSeconds && /^\d{2}:\d{2}:[6-9]/.test(raw)) {
367
+ raw = raw.replace(/^(\d{2}:\d{2}:)[6-9]/, '$15');
368
+ }
369
+
370
+ // 7. Prevent 3rd minute digit when showSeconds is FALSE! (e.g. "16:166" -> "16:16")
371
+ if (!showSeconds && /^\d{2}:\d{3,}/.test(raw)) {
372
+ raw = raw.slice(0, 5);
373
+ }
374
+
375
+ // 8. Prevent 3rd second digit when showSeconds is TRUE! (e.g. "16:16:666" -> "16:16:66")
376
+ if (showSeconds && /^\d{2}:\d{2}:\d{3,}/.test(raw)) {
377
+ raw = raw.slice(0, 8);
378
+ }
379
+
380
+ // 9. Strict Max length capping
381
+ const maxLen = mode === '12h' ? (showSeconds ? 11 : 8) : (showSeconds ? 8 : 5);
382
+ if (raw.length > maxLen) {
383
+ raw = raw.slice(0, maxLen);
384
+ }
385
+
386
+ e.target.value = raw;
387
+ setInputValue(raw);
388
+
389
+ if (!raw) {
390
+ if (onChange) onChange('', { hours: null, minutes: null, seconds: null, ampm: null, format: mode, timezone: tz, variant: activeVariant });
391
+ return;
392
+ }
393
+
394
+ // Emit change if raw matches a complete valid time pattern
395
+ if (/^\d{1,2}:\d{1,2}(:\d{1,2})?(\s*[ap]m)?$/i.test(raw) || /^\d{4}$/.test(raw)) {
396
+ const p = parseTime(raw, mode === '12h', showSeconds);
397
+ const formatted = formatOutput(p);
398
+ if (onChange) {
399
+ onChange(formatted, {
400
+ hours: parseInt(p.h24, 10),
401
+ minutes: parseInt(p.m, 10),
402
+ seconds: parseInt(p.s, 10),
403
+ ampm: p.ampm,
404
+ format: mode,
405
+ timezone: tz,
406
+ variant: activeVariant,
407
+ });
408
+ }
409
+ }
410
+ };
411
+
412
+ const handleInputBlur = () => {
413
+ isTypingRef.current = false;
414
+ if (!inputValue) return;
415
+ const p = parseTime(inputValue, mode === '12h', showSeconds);
416
+ emitChange(p);
417
+ };
418
+
419
+ // Stepper Up/Down (+/-) handlers
420
+ const stepHour = (delta) => {
421
+ isTypingRef.current = false;
422
+ let curH = mode === '12h' ? parseInt(parsed.h12, 10) : parseInt(parsed.h24, 10);
423
+ if (isNaN(curH)) curH = mode === '12h' ? 12 : 0;
424
+
425
+ if (mode === '12h') {
426
+ let nextH12 = curH + delta;
427
+ let nextAmPm = parsed.ampm;
428
+ if (nextH12 > 12) { nextH12 = 1; }
429
+ else if (nextH12 < 1) { nextH12 = 12; }
430
+ const h12 = pad(nextH12);
431
+ const h24 = to24h(h12, nextAmPm);
432
+ emitChange({ ...parsed, h12, h24, ampm: nextAmPm });
433
+ } else {
434
+ let nextH24 = (curH + delta + 24) % 24;
435
+ const h24 = pad(nextH24);
436
+ const { h12, ampm } = to12h(h24);
437
+ emitChange({ ...parsed, h24, h12, ampm });
438
+ }
439
+ };
440
+
441
+ const stepMinute = (delta) => {
442
+ isTypingRef.current = false;
443
+ let curM = parseInt(parsed.m, 10);
444
+ if (isNaN(curM)) curM = 0;
445
+ let nextM = (curM + delta + 60) % 60;
446
+ const m = pad(nextM);
447
+ emitChange({ ...parsed, m });
448
+ };
449
+
450
+ const stepSecond = (delta) => {
451
+ isTypingRef.current = false;
452
+ let curS = parseInt(parsed.s, 10);
453
+ if (isNaN(curS)) curS = 0;
454
+ let nextS = (curS + delta + 60) % 60;
455
+ const s = pad(nextS);
456
+ emitChange({ ...parsed, s });
457
+ };
458
+
459
+ // List direct selection handlers
460
+ const selectH24 = (hNum) => {
461
+ isTypingRef.current = false;
462
+ const h24 = pad(hNum);
463
+ const { h12, ampm } = to12h(h24);
464
+ emitChange({ ...parsed, h24, h12, ampm });
465
+ };
466
+
467
+ const selectH12 = (hNum12) => {
468
+ isTypingRef.current = false;
469
+ const h12 = pad(hNum12);
470
+ const h24 = to24h(h12, parsed.ampm);
471
+ emitChange({ ...parsed, h24, h12 });
472
+ };
473
+
474
+ const selectMin = (mNum) => {
475
+ isTypingRef.current = false;
476
+ const m = pad(mNum);
477
+ emitChange({ ...parsed, m });
478
+ };
479
+
480
+ const selectSec = (sNum) => {
481
+ isTypingRef.current = false;
482
+ const s = pad(sNum);
483
+ emitChange({ ...parsed, s });
484
+ };
485
+
486
+ const toggleAmPm = (targetAmPm) => {
487
+ isTypingRef.current = false;
488
+ const ampm = targetAmPm;
489
+ const h24 = to24h(parsed.h12, ampm);
490
+ emitChange({ ...parsed, ampm, h24 });
491
+ };
492
+
493
+ const switchMode = (newMode) => {
494
+ isTypingRef.current = false;
495
+ setMode(newMode);
496
+ emitChange(parsed, newMode);
497
+ };
498
+
499
+ const clearTime = (e) => {
500
+ e?.stopPropagation();
501
+ isTypingRef.current = false;
502
+ setInputValue('');
503
+ if (onChange) onChange('', { hours: null, minutes: null, seconds: null, ampm: null, format: mode, timezone: tz, variant: activeVariant });
504
+ closePicker();
505
+ };
506
+
507
+ const defaultPlaceholder = mode === '12h'
508
+ ? (showSeconds ? 'hh:mm:ss AM' : 'hh:mm AM')
509
+ : (showSeconds ? 'HH:MM:SS' : 'HH:MM');
510
+ const ph = placeholder || defaultPlaceholder;
511
+
512
+ const hours24List = Array.from({ length: 24 }, (_, i) => i);
513
+ const hours12List = Array.from({ length: 12 }, (_, i) => i + 1);
514
+ const fullMinutesList = Array.from({ length: 60 }, (_, i) => i);
515
+
516
+ const presets24 = ['00:00', '07:00', '08:00', '09:00', '12:00', '18:00', '23:00'];
517
+ const presets12 = ['07:00 AM', '09:00 AM', '12:00 PM', '03:00 PM', '06:00 PM', '11:00 PM'];
518
+
519
+ const inputHeight = size === 'sm' ? '32px' : '38px';
520
+ const fontSize = size === 'sm' ? '.78rem' : '.85rem';
521
+
522
+ return (
523
+ <div
524
+ className={`cynx-timepicker-wrap ${className}`}
525
+ style={{ display: 'inline-flex', flexDirection: 'column', position: 'relative', width: style?.width || 'auto', boxSizing: 'border-box', minWidth: 0, ...style }}
526
+ >
527
+ {label && (
528
+ <label
529
+ style={{ fontSize: 11, fontWeight: 600, color: 'var(--t2, var(--muted))', textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 4 }}
530
+ >
531
+ {label}
532
+ </label>
533
+ )}
534
+
535
+ {/* Input Trigger Field */}
536
+ <div ref={btnRef} style={{ position: 'relative', display: 'flex', alignItems: 'center', width: '100%', boxSizing: 'border-box', minWidth: 0 }}>
537
+ {displayClockIcon && (
538
+ <button
539
+ type="button"
540
+ onClick={togglePicker}
541
+ disabled={disabled || readOnly}
542
+ title="Выбрать время"
543
+ style={{
544
+ position: 'absolute',
545
+ left: 10,
546
+ background: 'none',
547
+ border: 'none',
548
+ cursor: (disabled || readOnly) ? 'default' : 'pointer',
549
+ padding: 0,
550
+ display: 'flex',
551
+ alignItems: 'center',
552
+ justifyContent: 'center',
553
+ color: open ? 'var(--accent, var(--blue))' : 'var(--muted, var(--t2))',
554
+ zIndex: 1,
555
+ transition: 'color .15s ease',
556
+ }}
557
+ >
558
+ <Clock size={size === 'sm' ? 14 : 16} />
559
+ </button>
560
+ )}
561
+
562
+ <input
563
+ type="text"
564
+ value={inputValue}
565
+ onChange={handleInputChange}
566
+ onKeyDown={handleKeyDown}
567
+ onBlur={handleInputBlur}
568
+ onFocus={openPicker}
569
+ maxLength={mode === '12h' ? (showSeconds ? 11 : 8) : (showSeconds ? 8 : 5)}
570
+ placeholder={ph}
571
+ disabled={disabled}
572
+ readOnly={readOnly}
573
+ style={{
574
+ display: 'flex',
575
+ width: '100%',
576
+ minWidth: 0,
577
+ maxWidth: '100%',
578
+ boxSizing: 'border-box',
579
+ height: inputHeight,
580
+ paddingLeft: displayClockIcon ? 28 : 10,
581
+ paddingRight: 26,
582
+ border: `1px solid ${open ? 'var(--accent, var(--blue))' : 'var(--border-light, var(--border))'}`,
583
+ borderRadius: 'var(--rads, 6px)',
584
+ fontSize: fontSize,
585
+ background: disabled ? 'var(--grey-1, var(--surface-hover))' : 'var(--bg, var(--surface))',
586
+ color: 'var(--fg, var(--t1, var(--primary-text)))',
587
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
588
+ letterSpacing: '0.03em',
589
+ fontWeight: 600,
590
+ fontVariantNumeric: 'tabular-nums',
591
+ textAlign: 'left',
592
+ outline: 'none',
593
+ boxShadow: open ? 'var(--pd, rgba(37, 99, 235, 0.12))' : 'var(--shadow, none)',
594
+ transition: 'all .15s cubic-bezier(.16,1,.3,1)',
595
+ }}
596
+ />
597
+
598
+ <div style={{ position: 'absolute', right: 8, display: 'flex', alignItems: 'center' }}>
599
+ {inputValue && !disabled && !readOnly ? (
600
+ <button
601
+ type="button"
602
+ onClick={clearTime}
603
+ title="Очистить"
604
+ style={{
605
+ background: 'none',
606
+ border: 'none',
607
+ cursor: 'pointer',
608
+ color: 'var(--muted, var(--t2))',
609
+ padding: 2,
610
+ display: 'flex',
611
+ alignItems: 'center',
612
+ justifyContent: 'center',
613
+ transition: 'color .14s ease',
614
+ }}
615
+ onMouseEnter={e => e.currentTarget.style.color = 'var(--danger)'}
616
+ onMouseLeave={e => e.currentTarget.style.color = 'var(--muted, var(--t2))'}
617
+ >
618
+ <X size={13} />
619
+ </button>
620
+ ) : (
621
+ <ChevronDown
622
+ size={13}
623
+ style={{
624
+ color: open ? 'var(--accent, var(--blue))' : 'var(--muted, var(--t2))',
625
+ transform: open ? 'rotate(180deg)' : 'none',
626
+ transition: 'transform .2s ease, color .15s ease',
627
+ pointerEvents: 'none',
628
+ }}
629
+ />
630
+ )}
631
+ </div>
632
+ </div>
633
+
634
+ {/* PORTAL DROPDOWN POPUP */}
635
+ {rendered && createPortal(
636
+ <div
637
+ ref={dropRef}
638
+ className={`cynx-timepicker-drop ${closing ? 'closing' : ''}`}
639
+ style={{
640
+ position: 'fixed',
641
+ top: pos.top,
642
+ left: pos.left,
643
+ zIndex: 999999,
644
+ background: 'var(--surface, var(--bg))',
645
+ border: '1px solid var(--border-light, var(--border))',
646
+ borderRadius: 'var(--rad, 10px)',
647
+ boxShadow: 'var(--shadow-lg, var(--shadow))',
648
+ padding: 10,
649
+ width: mode === '12h' ? 290 : 240,
650
+ maxWidth: 'calc(100vw - 24px)',
651
+ boxSizing: 'border-box',
652
+ fontSize: '.78rem',
653
+ userSelect: 'none',
654
+ fontFamily: 'var(--sans, inherit)',
655
+ }}
656
+ >
657
+ {/* Header Bar: Variant Switcher */}
658
+ {showVariantToggle && (
659
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, paddingBottom: 6, borderBottom: '1px solid var(--border-light, var(--border))' }}>
660
+ <span style={{ fontSize: '.68rem', fontWeight: 700, color: 'var(--fg, var(--t1))', textTransform: 'uppercase', letterSpacing: '.05em' }}>
661
+ Режим
662
+ </span>
663
+
664
+ <div style={{ display: 'inline-flex', background: 'var(--grey-1, var(--surface-hover))', padding: 2, borderRadius: 6, border: '1px solid var(--border-light, var(--border))' }}>
665
+ <button
666
+ type="button"
667
+ onClick={() => setActiveVariant('stepper')}
668
+ className="cynx-timepicker-cell"
669
+ style={{
670
+ padding: '2px 8px',
671
+ borderRadius: 4,
672
+ border: 'none',
673
+ fontSize: '.68rem',
674
+ fontWeight: activeVariant === 'stepper' ? 700 : 500,
675
+ background: activeVariant === 'stepper' ? 'var(--surface, var(--bg))' : 'transparent',
676
+ color: activeVariant === 'stepper' ? 'var(--accent, var(--blue))' : 'var(--muted, var(--t2))',
677
+ boxShadow: activeVariant === 'stepper' ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
678
+ cursor: 'pointer',
679
+ display: 'flex',
680
+ alignItems: 'center',
681
+ gap: 3,
682
+ }}
683
+ >
684
+ <Sliders size={11} /> Спиннер (+/-)
685
+ </button>
686
+
687
+ <button
688
+ type="button"
689
+ onClick={() => setActiveVariant('list')}
690
+ className="cynx-timepicker-cell"
691
+ style={{
692
+ padding: '2px 8px',
693
+ borderRadius: 4,
694
+ border: 'none',
695
+ fontSize: '.68rem',
696
+ fontWeight: activeVariant === 'list' ? 700 : 500,
697
+ background: activeVariant === 'list' ? 'var(--surface, var(--bg))' : 'transparent',
698
+ color: activeVariant === 'list' ? 'var(--accent, var(--blue))' : 'var(--muted, var(--t2))',
699
+ boxShadow: activeVariant === 'list' ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
700
+ cursor: 'pointer',
701
+ display: 'flex',
702
+ alignItems: 'center',
703
+ gap: 3,
704
+ }}
705
+ >
706
+ <List size={11} /> Список (00-59)
707
+ </button>
708
+ </div>
709
+ </div>
710
+ )}
711
+
712
+ {/* Optional Format Switcher (24h / 12h) */}
713
+ {showFormatToggle && (
714
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, paddingBottom: 6, borderBottom: '1px solid var(--border-light, var(--border))' }}>
715
+ <span style={{ fontSize: '.68rem', fontWeight: 700, color: 'var(--fg, var(--t1))', textTransform: 'uppercase', letterSpacing: '.05em' }}>
716
+ Формат
717
+ </span>
718
+
719
+ <div style={{ display: 'inline-flex', background: 'var(--grey-1, var(--surface-hover))', padding: 2, borderRadius: 6, border: '1px solid var(--border-light, var(--border))' }}>
720
+ <button
721
+ type="button"
722
+ onClick={() => switchMode('24h')}
723
+ className="cynx-timepicker-cell"
724
+ style={{
725
+ padding: '2px 8px',
726
+ borderRadius: 4,
727
+ border: 'none',
728
+ fontSize: '.68rem',
729
+ fontWeight: mode === '24h' ? 700 : 500,
730
+ background: mode === '24h' ? 'var(--surface, var(--bg))' : 'transparent',
731
+ color: mode === '24h' ? 'var(--accent, var(--blue))' : 'var(--muted, var(--t2))',
732
+ boxShadow: mode === '24h' ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
733
+ cursor: 'pointer',
734
+ }}
735
+ >
736
+ 24h
737
+ </button>
738
+ <button
739
+ type="button"
740
+ onClick={() => switchMode('12h')}
741
+ className="cynx-timepicker-cell"
742
+ style={{
743
+ padding: '2px 8px',
744
+ borderRadius: 4,
745
+ border: 'none',
746
+ fontSize: '.68rem',
747
+ fontWeight: mode === '12h' ? 700 : 500,
748
+ background: mode === '12h' ? 'var(--surface, var(--bg))' : 'transparent',
749
+ color: mode === '12h' ? 'var(--accent, var(--blue))' : 'var(--muted, var(--t2))',
750
+ boxShadow: mode === '12h' ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
751
+ cursor: 'pointer',
752
+ }}
753
+ >
754
+ 12h AM/PM
755
+ </button>
756
+ </div>
757
+ </div>
758
+ )}
759
+
760
+ {/* Optional Timezone Selector Header */}
761
+ {showTimezoneSelect && (
762
+ <div style={{ marginBottom: 8 }}>
763
+ <button
764
+ type="button"
765
+ onClick={() => setShowTzPicker(prev => !prev)}
766
+ className="cynx-timepicker-cell"
767
+ style={{
768
+ display: 'flex',
769
+ alignItems: 'center',
770
+ justifyContent: 'space-between',
771
+ width: '100%',
772
+ padding: '4px 8px',
773
+ borderRadius: 6,
774
+ border: '1px solid var(--border-light, var(--border))',
775
+ background: 'var(--grey-1, var(--surface-hover))',
776
+ fontSize: '.68rem',
777
+ color: 'var(--fg, var(--t1))',
778
+ cursor: 'pointer',
779
+ fontWeight: 500,
780
+ }}
781
+ >
782
+ <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
783
+ <Globe size={12} color="var(--accent, var(--blue))" />
784
+ {COMMON_TIMEZONES.find(t => t.value === tz)?.label || tz}
785
+ </span>
786
+ <ChevronDown size={12} color="var(--muted, var(--t2))" />
787
+ </button>
788
+
789
+ {showTzPicker && (
790
+ <div style={{ marginTop: 4, background: 'var(--surface, var(--bg))', border: '1px solid var(--border-light, var(--border))', borderRadius: 6, boxShadow: 'var(--shadow)', padding: 4, display: 'flex', flexDirection: 'column', gap: 2 }}>
791
+ {COMMON_TIMEZONES.map(t => (
792
+ <button
793
+ key={t.value}
794
+ type="button"
795
+ onClick={() => { setTz(t.value); setShowTzPicker(false); }}
796
+ className="cynx-timepicker-cell"
797
+ style={{
798
+ padding: '4px 6px',
799
+ borderRadius: 4,
800
+ border: 'none',
801
+ textAlign: 'left',
802
+ fontSize: '.68rem',
803
+ background: tz === t.value ? 'var(--accent, var(--blue))' : 'transparent',
804
+ color: tz === t.value ? '#ffffff' : 'var(--fg, var(--t1))',
805
+ cursor: 'pointer',
806
+ fontWeight: tz === t.value ? 600 : 400,
807
+ }}
808
+ >
809
+ {t.label}
810
+ </button>
811
+ ))}
812
+ </div>
813
+ )}
814
+ </div>
815
+ )}
816
+
817
+ {/* Optional Quick Presets Bar */}
818
+ {showPresets && (
819
+ <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 8, paddingBottom: 6, borderBottom: '1px solid var(--border-light, var(--border))' }}>
820
+ {(mode === '12h' ? presets12 : presets24).map(p => (
821
+ <button
822
+ key={p}
823
+ type="button"
824
+ onClick={() => {
825
+ const newP = parseTime(p, mode === '12h', showSeconds);
826
+ emitChange(newP);
827
+ }}
828
+ className="cynx-timepicker-cell"
829
+ style={{
830
+ padding: '3px 6px',
831
+ borderRadius: 4,
832
+ fontSize: '.68rem',
833
+ border: '1px solid var(--border-light, var(--border))',
834
+ background: inputValue === p ? 'var(--accent, var(--blue))' : 'var(--grey-1, var(--surface-hover))',
835
+ color: inputValue === p ? '#ffffff' : 'var(--fg, var(--t1))',
836
+ cursor: 'pointer',
837
+ fontWeight: 500,
838
+ }}
839
+ >
840
+ {p}
841
+ </button>
842
+ ))}
843
+ </div>
844
+ )}
845
+
846
+ {/* AM / PM Segment Selector (Only for 12h mode) */}
847
+ {mode === '12h' && (
848
+ <div style={{ display: 'flex', gap: 6, marginBottom: 8, justifyContent: 'center' }}>
849
+ <button
850
+ type="button"
851
+ onClick={() => toggleAmPm('AM')}
852
+ className="cynx-timepicker-cell"
853
+ style={{
854
+ flex: 1,
855
+ padding: '4px 0',
856
+ borderRadius: 6,
857
+ border: '1px solid var(--border-light, var(--border))',
858
+ fontSize: '.75rem',
859
+ fontWeight: parsed.ampm === 'AM' ? 700 : 500,
860
+ background: parsed.ampm === 'AM' ? 'var(--accent, var(--blue))' : 'var(--grey-1, var(--surface-hover))',
861
+ color: parsed.ampm === 'AM' ? '#ffffff' : 'var(--fg, var(--t1))',
862
+ cursor: 'pointer',
863
+ textAlign: 'center',
864
+ }}
865
+ >
866
+ AM (Утро)
867
+ </button>
868
+ <button
869
+ type="button"
870
+ onClick={() => toggleAmPm('PM')}
871
+ className="cynx-timepicker-cell"
872
+ style={{
873
+ flex: 1,
874
+ padding: '4px 0',
875
+ borderRadius: 6,
876
+ border: '1px solid var(--border-light, var(--border))',
877
+ fontSize: '.75rem',
878
+ fontWeight: parsed.ampm === 'PM' ? 700 : 500,
879
+ background: parsed.ampm === 'PM' ? 'var(--accent, var(--blue))' : 'var(--grey-1, var(--surface-hover))',
880
+ color: parsed.ampm === 'PM' ? '#ffffff' : 'var(--fg, var(--t1))',
881
+ cursor: 'pointer',
882
+ textAlign: 'center',
883
+ }}
884
+ >
885
+ PM (Вечер)
886
+ </button>
887
+ </div>
888
+ )}
889
+
890
+ {/* VARIANT 1: STEPPER MODE (Up/Down +/- Buttons) */}
891
+ {activeVariant === 'stepper' ? (
892
+ <div style={{ display: 'grid', gridTemplateColumns: showSeconds ? '1fr 1fr 1fr' : '1fr 1fr', gap: 10, padding: '4px 0' }}>
893
+ {/* Hours Stepper */}
894
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
895
+ <div style={{ fontSize: '.65rem', fontWeight: 600, color: 'var(--muted, var(--t2))' }}>
896
+ {mode === '12h' ? 'Час (1-12)' : 'Час (0-23)'}
897
+ </div>
898
+ <button type="button" className="cynx-timepicker-stepper-btn" onClick={() => stepHour(1)} title="+1 Час">
899
+ <ChevronUp size={14} />
900
+ </button>
901
+ <div style={{ fontSize: '1.2rem', fontWeight: 700, fontVariantNumeric: 'tabular-nums', padding: '6px 0', color: 'var(--accent, var(--blue))' }}>
902
+ {mode === '12h' ? parsed.h12 : parsed.h24}
903
+ </div>
904
+ <button type="button" className="cynx-timepicker-stepper-btn" onClick={() => stepHour(-1)} title="-1 Час">
905
+ <ChevronDown size={14} />
906
+ </button>
907
+ </div>
908
+
909
+ {/* Minutes Stepper */}
910
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
911
+ <div style={{ fontSize: '.65rem', fontWeight: 600, color: 'var(--muted, var(--t2))' }}>Мин (0-59)</div>
912
+ <button type="button" className="cynx-timepicker-stepper-btn" onClick={() => stepMinute(1)} title="+1 Мин">
913
+ <ChevronUp size={14} />
914
+ </button>
915
+ <div style={{ fontSize: '1.2rem', fontWeight: 700, fontVariantNumeric: 'tabular-nums', padding: '6px 0', color: 'var(--accent, var(--blue))' }}>
916
+ {parsed.m}
917
+ </div>
918
+ <button type="button" className="cynx-timepicker-stepper-btn" onClick={() => stepMinute(-1)} title="-1 Мин">
919
+ <ChevronDown size={14} />
920
+ </button>
921
+ </div>
922
+
923
+ {/* Seconds Stepper */}
924
+ {showSeconds && (
925
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
926
+ <div style={{ fontSize: '.65rem', fontWeight: 600, color: 'var(--muted, var(--t2))' }}>Сек (0-59)</div>
927
+ <button type="button" className="cynx-timepicker-stepper-btn" onClick={() => stepSecond(1)} title="+1 Сек">
928
+ <ChevronUp size={14} />
929
+ </button>
930
+ <div style={{ fontSize: '1.2rem', fontWeight: 700, fontVariantNumeric: 'tabular-nums', padding: '6px 0', color: 'var(--accent, var(--blue))' }}>
931
+ {parsed.s}
932
+ </div>
933
+ <button type="button" className="cynx-timepicker-stepper-btn" onClick={() => stepSecond(-1)} title="-1 Сек">
934
+ <ChevronDown size={14} />
935
+ </button>
936
+ </div>
937
+ )}
938
+ </div>
939
+ ) : (
940
+ /* VARIANT 2: LIST MODE (Full List 00..59) */
941
+ <div style={{ display: 'grid', gridTemplateColumns: showSeconds ? '1fr 1fr 1fr' : '1fr 1fr', gap: 6 }}>
942
+ {/* Hours Column */}
943
+ <div>
944
+ <div style={{ fontSize: '.65rem', fontWeight: 600, color: 'var(--muted, var(--t2))', marginBottom: 4, textAlign: 'center' }}>
945
+ {mode === '12h' ? 'Час (1-12)' : 'Час (0-23)'}
946
+ </div>
947
+ <div style={{ maxHeight: 150, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 2, paddingRight: 2 }}>
948
+ {(mode === '12h' ? hours12List : hours24List).map(hNum => {
949
+ const isSelected = mode === '12h' ? parsed.h12 === pad(hNum) : parsed.h24 === pad(hNum);
950
+ return (
951
+ <button
952
+ key={hNum}
953
+ type="button"
954
+ onClick={() => (mode === '12h' ? selectH12(hNum) : selectH24(hNum))}
955
+ className="cynx-timepicker-cell"
956
+ style={{
957
+ padding: '3px 6px',
958
+ borderRadius: 4,
959
+ border: 'none',
960
+ textAlign: 'center',
961
+ fontSize: '.75rem',
962
+ cursor: 'pointer',
963
+ fontWeight: isSelected ? 700 : 400,
964
+ background: isSelected ? 'var(--accent, var(--blue))' : 'transparent',
965
+ color: isSelected ? '#ffffff' : 'var(--fg, var(--t1))',
966
+ }}
967
+ >
968
+ {pad(hNum)}
969
+ </button>
970
+ );
971
+ })}
972
+ </div>
973
+ </div>
974
+
975
+ {/* Minutes Column: FULL LIST 00 to 59! */}
976
+ <div>
977
+ <div style={{ fontSize: '.65rem', fontWeight: 600, color: 'var(--muted, var(--t2))', marginBottom: 4, textAlign: 'center' }}>Мин (00-59)</div>
978
+ <div style={{ maxHeight: 150, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 2, paddingRight: 2 }}>
979
+ {fullMinutesList.map(mNum => {
980
+ const mStr = pad(mNum);
981
+ const isSelected = parsed.m === mStr;
982
+ return (
983
+ <button
984
+ key={mNum}
985
+ type="button"
986
+ onClick={() => selectMin(mNum)}
987
+ className="cynx-timepicker-cell"
988
+ style={{
989
+ padding: '3px 6px',
990
+ borderRadius: 4,
991
+ border: 'none',
992
+ textAlign: 'center',
993
+ fontSize: '.75rem',
994
+ cursor: 'pointer',
995
+ fontWeight: isSelected ? 700 : 400,
996
+ background: isSelected ? 'var(--accent, var(--blue))' : 'transparent',
997
+ color: isSelected ? '#ffffff' : 'var(--fg, var(--t1))',
998
+ }}
999
+ >
1000
+ {mStr}
1001
+ </button>
1002
+ );
1003
+ })}
1004
+ </div>
1005
+ </div>
1006
+
1007
+ {/* Seconds Column (if showSeconds): FULL LIST 00 to 59! */}
1008
+ {showSeconds && (
1009
+ <div>
1010
+ <div style={{ fontSize: '.65rem', fontWeight: 600, color: 'var(--muted, var(--t2))', marginBottom: 4, textAlign: 'center' }}>Сек (00-59)</div>
1011
+ <div style={{ maxHeight: 150, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 2 }}>
1012
+ {fullMinutesList.map(sNum => {
1013
+ const sStr = pad(sNum);
1014
+ const isSelected = parsed.s === sStr;
1015
+ return (
1016
+ <button
1017
+ key={sNum}
1018
+ type="button"
1019
+ onClick={() => selectSec(sNum)}
1020
+ className="cynx-timepicker-cell"
1021
+ style={{
1022
+ padding: '3px 6px',
1023
+ borderRadius: 4,
1024
+ border: 'none',
1025
+ textAlign: 'center',
1026
+ fontSize: '.75rem',
1027
+ cursor: 'pointer',
1028
+ fontWeight: isSelected ? 700 : 400,
1029
+ background: isSelected ? 'var(--accent, var(--blue))' : 'transparent',
1030
+ color: isSelected ? '#ffffff' : 'var(--fg, var(--t1))',
1031
+ }}
1032
+ >
1033
+ {sStr}
1034
+ </button>
1035
+ );
1036
+ })}
1037
+ </div>
1038
+ </div>
1039
+ )}
1040
+ </div>
1041
+ )}
1042
+
1043
+ {/* Footer Actions */}
1044
+ <div style={{ marginTop: 8, paddingTop: 6, borderTop: '1px solid var(--border-light, var(--border))', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
1045
+ <button
1046
+ type="button"
1047
+ onClick={clearTime}
1048
+ className="cynx-timepicker-cell"
1049
+ style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', fontSize: '.7rem', fontWeight: 500 }}
1050
+ >
1051
+ Сбросить
1052
+ </button>
1053
+ <button
1054
+ type="button"
1055
+ onClick={closePicker}
1056
+ className="cynx-timepicker-cell"
1057
+ style={{ padding: '3px 10px', borderRadius: 4, border: 'none', background: 'var(--accent, var(--blue))', color: '#ffffff', fontSize: '.7rem', cursor: 'pointer', fontWeight: 600, display: 'flex', alignItems: 'center', gap: 4 }}
1058
+ >
1059
+ <Check size={12} /> Готово
1060
+ </button>
1061
+ </div>
1062
+ </div>,
1063
+ document.body
1064
+ )}
1065
+ </div>
1066
+ );
1067
+ }
1068
+
1069
+ export default TimePicker;
package/index.js CHANGED
@@ -28,6 +28,7 @@ import { ConfirmRoot as _ConfirmRoot, confirm as _confirm } from './components/C
28
28
  import { SearchableDropdown as _SearchableDropdown } from './components/SearchableDropdown.jsx';
29
29
  import { Pagination as _Pagination } from './components/Pagination.jsx';
30
30
  import { DatePicker as _DatePicker } from './components/DatePicker.jsx';
31
+ import { TimePicker as _TimePicker } from './components/TimePicker.jsx';
31
32
  import { ActionsMenu as _ActionsMenu } from './components/ActionsMenu.jsx';
32
33
  import { BrandingCard as _BrandingCard } from './components/BrandingCard.jsx';
33
34
  import { Dropzone as _Dropzone } from './components/Dropzone.jsx';
@@ -72,6 +73,7 @@ export const confirm = _confirm;
72
73
  export const SearchableDropdown = _SearchableDropdown;
73
74
  export const Pagination = _Pagination;
74
75
  export const DatePicker = _DatePicker;
76
+ export const TimePicker = _TimePicker;
75
77
  export const ActionsMenu = _ActionsMenu;
76
78
  export const BrandingCard = _BrandingCard;
77
79
  export const Dropzone = _Dropzone;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cynx-ui",
3
- "version": "1.2.48",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js"