@velocis/timepicker1 0.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,671 @@
1
+ 'use client';
2
+
3
+ // src/TimePicker1.tsx
4
+ import { cn as cn2, Portal, useDirection, useFloatingPosition } from "@velocis/core";
5
+ import {
6
+ useCallback,
7
+ useEffect as useEffect2,
8
+ useRef as useRef2,
9
+ useState
10
+ } from "react";
11
+
12
+ // src/TimePicker1.defaults.ts
13
+ var TIMEPICKER1_DEFAULTS = {
14
+ contentWidth: "w-40",
15
+ contentZIndex: 9999,
16
+ closeOnScroll: true,
17
+ fullWidth: false,
18
+ disabled: false,
19
+ defaultOpen: false,
20
+ closeOnSelect: true,
21
+ minuteStep: 15,
22
+ use24Hour: false,
23
+ slotCount: 48,
24
+ startFromNow: true,
25
+ listHeight: 280
26
+ };
27
+ var TIMEPICKER1_DEFAULT_COLORS = {
28
+ panelBgColor: "#141414",
29
+ activeItemColor: "#FF6B3D",
30
+ scrollbarThumbColor: "rgba(255, 255, 255, 0.18)",
31
+ scrollbarWidth: 2
32
+ };
33
+
34
+ // src/TimePicker1.variants.ts
35
+ import { cva } from "class-variance-authority";
36
+ var timePicker1TriggerVariants = cva(
37
+ "flex items-center gap-2 rounded-2xl px-4 py-2 text-sm font-medium shadow-sm ring-1 ring-inset transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-velocis-primary/40 bg-velocis-background text-velocis-foreground ring-velocis-border hover:bg-velocis-border/30",
38
+ {
39
+ variants: {
40
+ fullWidth: {
41
+ true: "w-full justify-between",
42
+ false: ""
43
+ },
44
+ disabled: {
45
+ true: "cursor-not-allowed opacity-60",
46
+ false: ""
47
+ }
48
+ },
49
+ defaultVariants: {
50
+ fullWidth: false,
51
+ disabled: false
52
+ }
53
+ }
54
+ );
55
+ var timePicker1ContentVariants = cva(
56
+ "fixed rounded-2xl border border-velocis-border p-4 shadow-2xl focus:outline-none"
57
+ );
58
+ var timePicker1ItemVariants = cva(
59
+ "flex h-9 w-full items-center justify-center rounded-2xl text-sm transition-colors"
60
+ );
61
+
62
+ // src/components/TimePickerPanel.tsx
63
+ import { cn } from "@velocis/core";
64
+ import { useEffect, useMemo, useRef } from "react";
65
+
66
+ // src/presets/timepicker1Styles.ts
67
+ var TIMEPICKER1_SCROLLBAR_WIDTH_PX = 2;
68
+ var timepicker1ListScrollbarStyles = "velocis-timepicker1-scroll";
69
+ function resolveListHeight(height) {
70
+ return typeof height === "number" ? `${height}px` : height;
71
+ }
72
+ function resolveScrollbarStyle(colors) {
73
+ const width = colors?.scrollbarWidth ?? TIMEPICKER1_DEFAULT_COLORS.scrollbarWidth;
74
+ const thumb = colors?.scrollbarThumbColor ?? TIMEPICKER1_DEFAULT_COLORS.scrollbarThumbColor;
75
+ return {
76
+ scrollbarWidth: "thin",
77
+ scrollbarColor: `${thumb} transparent`,
78
+ ["--velocis-timepicker1-scrollbar-thumb"]: thumb,
79
+ ["--velocis-timepicker1-scrollbar-width"]: `${width}px`
80
+ };
81
+ }
82
+
83
+ // src/styles/ensureScrollbarStyles.ts
84
+ var SCROLLBAR_STYLE_ID = "velocis-timepicker1-scrollbar";
85
+ function ensureTimePicker1ScrollbarStyles() {
86
+ if (typeof document === "undefined") return;
87
+ if (document.getElementById(SCROLLBAR_STYLE_ID)) return;
88
+ const style = document.createElement("style");
89
+ style.id = SCROLLBAR_STYLE_ID;
90
+ style.textContent = `
91
+ .velocis-timepicker1-scroll {
92
+ scrollbar-width: thin;
93
+ scrollbar-color: var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18)) transparent;
94
+ }
95
+ .velocis-timepicker1-scroll::-webkit-scrollbar {
96
+ width: var(--velocis-timepicker1-scrollbar-width, 2px);
97
+ }
98
+ .velocis-timepicker1-scroll::-webkit-scrollbar-track {
99
+ background: transparent;
100
+ }
101
+ .velocis-timepicker1-scroll::-webkit-scrollbar-thumb {
102
+ min-height: 28px;
103
+ border-radius: 9999px;
104
+ background-color: var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18));
105
+ transition: background-color 0.2s ease;
106
+ }
107
+ .velocis-timepicker1-scroll:hover::-webkit-scrollbar-thumb {
108
+ background-color: color-mix(
109
+ in srgb,
110
+ var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18)) 55%,
111
+ white
112
+ );
113
+ }
114
+ .velocis-timepicker1-scroll:active::-webkit-scrollbar-thumb {
115
+ background-color: color-mix(
116
+ in srgb,
117
+ var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18)) 40%,
118
+ white
119
+ );
120
+ }
121
+ `;
122
+ document.head.appendChild(style);
123
+ }
124
+
125
+ // src/utils/timepicker1DisabledRules.ts
126
+ function timeToMinutes(time) {
127
+ return time.hours * 60 + time.minutes;
128
+ }
129
+ function isSameTimeValue(a, b) {
130
+ return a.hours === b.hours && a.minutes === b.minutes;
131
+ }
132
+ function isTimeRuleDisabled(time, rule) {
133
+ switch (rule.type) {
134
+ case "time":
135
+ return isSameTimeValue(time, rule.time);
136
+ case "times":
137
+ return rule.times.some((t) => isSameTimeValue(time, t));
138
+ case "hour":
139
+ return time.hours === rule.hour;
140
+ case "minute":
141
+ return time.minutes === rule.minute;
142
+ case "hourMinute":
143
+ return time.hours === rule.hour && time.minutes === rule.minute;
144
+ case "custom":
145
+ return rule.match(time);
146
+ default:
147
+ return false;
148
+ }
149
+ }
150
+ function isTimeDisabledByRules(time, rules) {
151
+ if (!rules?.length) return false;
152
+ return rules.some((rule) => isTimeRuleDisabled(time, rule));
153
+ }
154
+ function isTimePickerTimeDisabled(time, constraints = {}) {
155
+ const { minTime, maxTime, disabledRules } = constraints;
156
+ const total = timeToMinutes(time);
157
+ if (minTime !== void 0 && total < timeToMinutes(minTime)) return true;
158
+ if (maxTime !== void 0 && total > timeToMinutes(maxTime)) return true;
159
+ return isTimeDisabledByRules(time, disabledRules);
160
+ }
161
+
162
+ // src/utils/timeUtils.ts
163
+ function timeToMinutes2(time) {
164
+ return time.hours * 60 + time.minutes;
165
+ }
166
+ function minutesToTime(totalMinutes) {
167
+ const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
168
+ return {
169
+ hours: Math.floor(normalized / 60),
170
+ minutes: normalized % 60
171
+ };
172
+ }
173
+ function isSameTime(a, b) {
174
+ return a.hours === b.hours && a.minutes === b.minutes;
175
+ }
176
+ function isTimeInRange(time, minTime, maxTime) {
177
+ const total = timeToMinutes2(time);
178
+ if (minTime !== void 0 && total < timeToMinutes2(minTime)) return false;
179
+ if (maxTime !== void 0 && total > timeToMinutes2(maxTime)) return false;
180
+ return true;
181
+ }
182
+ function ceilTimeToStep(time, step) {
183
+ const total = timeToMinutes2(time);
184
+ return minutesToTime(Math.ceil(total / step) * step);
185
+ }
186
+ function getNextSlotAfterNow(step = 15) {
187
+ const now = /* @__PURE__ */ new Date();
188
+ const totalMinutes = now.getHours() * 60 + now.getMinutes();
189
+ const nextTotal = Math.ceil((totalMinutes + 1) / step) * step;
190
+ return minutesToTime(nextTotal);
191
+ }
192
+ function getEffectiveSlotStart(options) {
193
+ const candidates = [0];
194
+ if (options.startFromNow) {
195
+ candidates.push(timeToMinutes2(getNextSlotAfterNow(options.minuteStep)));
196
+ }
197
+ if (options.minTime !== void 0) {
198
+ candidates.push(timeToMinutes2(ceilTimeToStep(options.minTime, options.minuteStep)));
199
+ }
200
+ return minutesToTime(Math.max(...candidates));
201
+ }
202
+ function generateBoundedTimeSlots(options) {
203
+ const start = getEffectiveSlotStart({
204
+ startFromNow: options.startFromNow,
205
+ minTime: options.minTime,
206
+ minuteStep: options.minuteStep
207
+ });
208
+ const slots = [];
209
+ let current = timeToMinutes2(start);
210
+ const maxMinutes = options.maxTime !== void 0 ? timeToMinutes2(options.maxTime) : 24 * 60 - options.minuteStep;
211
+ while (slots.length < options.slotCount && current <= maxMinutes) {
212
+ const slot = minutesToTime(current);
213
+ if (isTimeInRange(slot, options.minTime, options.maxTime) && !isTimePickerTimeDisabled(slot, {
214
+ minTime: options.minTime,
215
+ maxTime: options.maxTime,
216
+ disabledRules: options.disabledRules
217
+ })) {
218
+ slots.push(slot);
219
+ }
220
+ current += options.minuteStep;
221
+ if (current >= 24 * 60) break;
222
+ }
223
+ return slots;
224
+ }
225
+ function padTwo(n) {
226
+ return n.toString().padStart(2, "0");
227
+ }
228
+ function formatTimeSlot(time, locale, use24Hour) {
229
+ if (!time) return "";
230
+ if (use24Hour) {
231
+ return `${padTwo(time.hours)}:${padTwo(time.minutes)}`;
232
+ }
233
+ if (locale.startsWith("ar")) {
234
+ const h12 = time.hours % 12 || 12;
235
+ const period = time.hours < 12 ? "\u0635" : "\u0645";
236
+ return `${h12}:${padTwo(time.minutes)} ${period}`;
237
+ }
238
+ const date = /* @__PURE__ */ new Date();
239
+ date.setHours(time.hours, time.minutes, 0, 0);
240
+ return date.toLocaleTimeString(locale, {
241
+ hour: "numeric",
242
+ minute: "2-digit",
243
+ hour12: true
244
+ });
245
+ }
246
+ function clampTimeToRange(time, minTime, maxTime) {
247
+ let total = timeToMinutes2(time);
248
+ if (minTime !== void 0) {
249
+ total = Math.max(total, timeToMinutes2(minTime));
250
+ }
251
+ if (maxTime !== void 0) {
252
+ total = Math.min(total, timeToMinutes2(maxTime));
253
+ }
254
+ return minutesToTime(total);
255
+ }
256
+
257
+ // src/components/TimePickerPanel.tsx
258
+ import { jsx } from "react/jsx-runtime";
259
+ function TimePickerPanel({
260
+ value,
261
+ colors,
262
+ locale,
263
+ use24Hour,
264
+ minuteStep,
265
+ slotCount,
266
+ startFromNow,
267
+ isOpen,
268
+ minTime,
269
+ maxTime,
270
+ disabledRules,
271
+ listHeight,
272
+ listClassName,
273
+ onChange,
274
+ className
275
+ }) {
276
+ const listRef = useRef(null);
277
+ const selectedRef = useRef(null);
278
+ const resolvedColors = {
279
+ panelBgColor: colors.panelBgColor ?? TIMEPICKER1_DEFAULT_COLORS.panelBgColor,
280
+ activeItemColor: colors.activeItemColor ?? TIMEPICKER1_DEFAULT_COLORS.activeItemColor
281
+ };
282
+ const slots = useMemo(
283
+ () => generateBoundedTimeSlots({
284
+ startFromNow,
285
+ minTime,
286
+ maxTime,
287
+ disabledRules,
288
+ minuteStep,
289
+ slotCount
290
+ }),
291
+ [startFromNow, minTime, maxTime, disabledRules, minuteStep, slotCount, isOpen]
292
+ );
293
+ const timeConstraints = useMemo(
294
+ () => ({ minTime, maxTime, disabledRules }),
295
+ [minTime, maxTime, disabledRules]
296
+ );
297
+ useEffect(() => {
298
+ ensureTimePicker1ScrollbarStyles();
299
+ }, []);
300
+ useEffect(() => {
301
+ if (value) {
302
+ selectedRef.current?.scrollIntoView({ block: "nearest" });
303
+ }
304
+ }, [value, slots]);
305
+ return /* @__PURE__ */ jsx("div", { className: cn("text-start", className), children: /* @__PURE__ */ jsx(
306
+ "div",
307
+ {
308
+ ref: listRef,
309
+ role: "listbox",
310
+ "aria-label": locale.startsWith("ar") ? "\u0627\u062E\u062A\u0631 \u0627\u0644\u0648\u0642\u062A" : "Select time",
311
+ className: cn(
312
+ "overflow-y-auto rounded-2xl",
313
+ timepicker1ListScrollbarStyles,
314
+ listClassName
315
+ ),
316
+ style: {
317
+ height: resolveListHeight(listHeight),
318
+ ...resolveScrollbarStyle(colors)
319
+ },
320
+ children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 p-1", children: slots.map((slot) => {
321
+ const isActive = value ? isSameTime(slot, value) : false;
322
+ const isDisabled = isTimePickerTimeDisabled(slot, timeConstraints);
323
+ const label = formatTimeSlot(slot, locale, use24Hour);
324
+ return /* @__PURE__ */ jsx(
325
+ "button",
326
+ {
327
+ ref: isActive ? selectedRef : void 0,
328
+ type: "button",
329
+ role: "option",
330
+ "aria-selected": isActive,
331
+ disabled: isDisabled,
332
+ onClick: () => !isDisabled && onChange(slot),
333
+ className: cn(
334
+ timePicker1ItemVariants(),
335
+ "w-full justify-start px-3",
336
+ isDisabled && "cursor-not-allowed opacity-40",
337
+ !isActive && !isDisabled && "text-velocis-foreground hover:bg-velocis-border/30"
338
+ ),
339
+ style: isActive ? { backgroundColor: resolvedColors.activeItemColor, color: "#ffffff" } : void 0,
340
+ children: label
341
+ },
342
+ `${slot.hours}-${slot.minutes}`
343
+ );
344
+ }) })
345
+ }
346
+ ) });
347
+ }
348
+
349
+ // src/positioning/computePopoverStyle.ts
350
+ import {
351
+ computeFloatingPosition,
352
+ floatingPositionToStyle
353
+ } from "@velocis/core";
354
+ function parseContentWidthClass(contentWidth) {
355
+ const match = contentWidth.match(/^w-(\d+(?:\.\d+)?|\[\d+px\])$/);
356
+ if (!match?.[1]) return void 0;
357
+ const value = match[1];
358
+ if (value.startsWith("[")) {
359
+ return parseInt(value.slice(1, -2), 10);
360
+ }
361
+ const remMap = {
362
+ "72": 288,
363
+ "80": 320,
364
+ "96": 384
365
+ };
366
+ return remMap[value];
367
+ }
368
+
369
+ // src/TimePicker1.tsx
370
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
371
+ function useControllableOpen(controlledOpen, defaultOpen, onOpenChange) {
372
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
373
+ const isControlled = controlledOpen !== void 0;
374
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
375
+ const setOpen = useCallback(
376
+ (next) => {
377
+ if (!isControlled) {
378
+ setUncontrolledOpen(next);
379
+ }
380
+ onOpenChange?.(next);
381
+ },
382
+ [isControlled, onOpenChange]
383
+ );
384
+ return { open, setOpen };
385
+ }
386
+ function useControllableValue(controlledValue, defaultValue, onChange) {
387
+ const [uncontrolledValue, setUncontrolledValue] = useState(
388
+ defaultValue ?? null
389
+ );
390
+ const isControlled = controlledValue !== void 0;
391
+ const value = isControlled ? controlledValue ?? null : uncontrolledValue;
392
+ const setValue = useCallback(
393
+ (next) => {
394
+ if (!isControlled) {
395
+ setUncontrolledValue(next);
396
+ }
397
+ onChange?.(next);
398
+ },
399
+ [isControlled, onChange]
400
+ );
401
+ return { value, setValue };
402
+ }
403
+ function ClockIcon({ className }) {
404
+ return /* @__PURE__ */ jsxs(
405
+ "svg",
406
+ {
407
+ className,
408
+ width: "16",
409
+ height: "16",
410
+ viewBox: "0 0 16 16",
411
+ fill: "none",
412
+ "aria-hidden": true,
413
+ children: [
414
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "1.5" }),
415
+ /* @__PURE__ */ jsx2("path", { d: "M8 5v3.5l2.5 1.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
416
+ ]
417
+ }
418
+ );
419
+ }
420
+ function TimePicker1Root({
421
+ trigger,
422
+ value: controlledValue,
423
+ defaultValue,
424
+ onChange,
425
+ open: controlledOpen,
426
+ defaultOpen = TIMEPICKER1_DEFAULTS.defaultOpen,
427
+ onOpenChange,
428
+ colors,
429
+ locale: localeProp,
430
+ use24Hour = TIMEPICKER1_DEFAULTS.use24Hour,
431
+ minuteStep = TIMEPICKER1_DEFAULTS.minuteStep,
432
+ slotCount = TIMEPICKER1_DEFAULTS.slotCount,
433
+ startFromNow = TIMEPICKER1_DEFAULTS.startFromNow,
434
+ minTime,
435
+ maxTime,
436
+ disabledRules,
437
+ listHeight = TIMEPICKER1_DEFAULTS.listHeight,
438
+ listClassName,
439
+ triggerClassName,
440
+ contentClassName,
441
+ contentWidth = TIMEPICKER1_DEFAULTS.contentWidth,
442
+ fullWidth = TIMEPICKER1_DEFAULTS.fullWidth,
443
+ disabled = TIMEPICKER1_DEFAULTS.disabled,
444
+ closeOnSelect = TIMEPICKER1_DEFAULTS.closeOnSelect,
445
+ closeOnScroll = TIMEPICKER1_DEFAULTS.closeOnScroll,
446
+ contentZIndex = TIMEPICKER1_DEFAULTS.contentZIndex,
447
+ testId,
448
+ contentTestId
449
+ }) {
450
+ const direction = useDirection();
451
+ const locale = localeProp ?? (direction === "rtl" ? "ar" : "en");
452
+ const { open, setOpen } = useControllableOpen(controlledOpen, defaultOpen, onOpenChange);
453
+ const { value, setValue } = useControllableValue(controlledValue, defaultValue, onChange);
454
+ const contentRef = useRef2(null);
455
+ const buttonRef = useRef2(null);
456
+ const getPanelWidth = useCallback(
457
+ (anchorRect) => {
458
+ if (fullWidth) return anchorRect.width;
459
+ const contentWidthPx = parseContentWidthClass(contentWidth);
460
+ return contentWidthPx ?? Math.min(320, window.innerWidth - 16);
461
+ },
462
+ [contentWidth, fullWidth]
463
+ );
464
+ const positionStyle = useFloatingPosition({
465
+ open,
466
+ anchorRef: buttonRef,
467
+ contentRef,
468
+ direction,
469
+ getWidth: getPanelWidth,
470
+ offset: 4,
471
+ preferredPlacement: "bottom",
472
+ fullWidth
473
+ });
474
+ const resolvedColors = {
475
+ panelBgColor: colors?.panelBgColor ?? TIMEPICKER1_DEFAULT_COLORS.panelBgColor,
476
+ activeItemColor: colors?.activeItemColor ?? TIMEPICKER1_DEFAULT_COLORS.activeItemColor
477
+ };
478
+ useEffect2(() => {
479
+ if (!open) return;
480
+ const handleClickOutside = (event) => {
481
+ if (buttonRef.current && !buttonRef.current.contains(event.target) && !contentRef.current?.contains(event.target)) {
482
+ setOpen(false);
483
+ }
484
+ };
485
+ const handleKeyDown = (event) => {
486
+ if (event.key === "Escape") {
487
+ setOpen(false);
488
+ }
489
+ };
490
+ const handleScroll = (event) => {
491
+ if (!closeOnScroll) return;
492
+ const target = event.target;
493
+ if (target && !contentRef.current?.contains(target) && !buttonRef.current?.contains(target)) {
494
+ setOpen(false);
495
+ }
496
+ };
497
+ document.addEventListener("mousedown", handleClickOutside);
498
+ document.addEventListener("keydown", handleKeyDown);
499
+ document.addEventListener("scroll", handleScroll, true);
500
+ return () => {
501
+ document.removeEventListener("mousedown", handleClickOutside);
502
+ document.removeEventListener("keydown", handleKeyDown);
503
+ document.removeEventListener("scroll", handleScroll, true);
504
+ };
505
+ }, [closeOnScroll, open, setOpen]);
506
+ const handleChange = (time) => {
507
+ if (!isTimeInRange(time, minTime, maxTime)) return;
508
+ if (isTimePickerTimeDisabled(time, { minTime, maxTime, disabledRules })) return;
509
+ setValue(time);
510
+ if (closeOnSelect) {
511
+ setOpen(false);
512
+ }
513
+ };
514
+ const displayTrigger = trigger ?? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
515
+ /* @__PURE__ */ jsx2(ClockIcon, { className: "text-velocis-muted" }),
516
+ value ? formatTimeSlot(value, locale, use24Hour) : locale.startsWith("ar") ? "\u0627\u062E\u062A\u0631 \u0627\u0644\u0648\u0642\u062A" : "Select time"
517
+ ] });
518
+ const pickerContent = open ? /* @__PURE__ */ jsx2(
519
+ "div",
520
+ {
521
+ ref: contentRef,
522
+ role: "dialog",
523
+ "aria-modal": "false",
524
+ "data-testid": contentTestId,
525
+ dir: direction,
526
+ className: cn2(timePicker1ContentVariants(), contentClassName),
527
+ style: {
528
+ ...positionStyle,
529
+ zIndex: contentZIndex,
530
+ backgroundColor: resolvedColors.panelBgColor
531
+ },
532
+ children: /* @__PURE__ */ jsx2(
533
+ TimePickerPanel,
534
+ {
535
+ value,
536
+ colors: resolvedColors,
537
+ locale,
538
+ use24Hour,
539
+ minuteStep,
540
+ slotCount,
541
+ startFromNow,
542
+ isOpen: open,
543
+ minTime,
544
+ maxTime,
545
+ disabledRules,
546
+ listHeight,
547
+ listClassName,
548
+ onChange: handleChange
549
+ }
550
+ )
551
+ }
552
+ ) : null;
553
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
554
+ /* @__PURE__ */ jsx2(
555
+ "div",
556
+ {
557
+ className: cn2(
558
+ "relative text-start",
559
+ fullWidth ? "block w-full" : "inline-block"
560
+ ),
561
+ children: /* @__PURE__ */ jsx2(
562
+ "button",
563
+ {
564
+ ref: buttonRef,
565
+ type: "button",
566
+ disabled,
567
+ "aria-expanded": open,
568
+ "aria-haspopup": "dialog",
569
+ onClick: () => !disabled && setOpen(!open),
570
+ "data-testid": testId,
571
+ className: cn2(
572
+ timePicker1TriggerVariants({ fullWidth, disabled }),
573
+ triggerClassName
574
+ ),
575
+ children: displayTrigger
576
+ }
577
+ )
578
+ }
579
+ ),
580
+ open && /* @__PURE__ */ jsx2(Portal, { children: pickerContent })
581
+ ] });
582
+ }
583
+
584
+ // src/hooks/useTimePicker1.ts
585
+ import { useCallback as useCallback2, useRef as useRef3, useState as useState2 } from "react";
586
+ function useTimePicker1(options) {
587
+ const [open, setOpen] = useState2(options?.defaultOpen ?? false);
588
+ const anchorRef = useRef3(null);
589
+ const isValueControlled = options?.value !== void 0;
590
+ const [uncontrolledValue, setUncontrolledValue] = useState2(
591
+ options?.defaultValue ?? null
592
+ );
593
+ const value = isValueControlled ? options?.value ?? null : uncontrolledValue;
594
+ const setValue = useCallback2(
595
+ (time) => {
596
+ if (!isValueControlled) {
597
+ setUncontrolledValue(time);
598
+ }
599
+ options?.onChange?.(time);
600
+ },
601
+ [isValueControlled, options]
602
+ );
603
+ const isOpenControlled = options?.open !== void 0;
604
+ const resolvedOpen = isOpenControlled ? options?.open ?? false : open;
605
+ const setOpenState = useCallback2(
606
+ (next) => {
607
+ if (!isOpenControlled) {
608
+ setOpen(next);
609
+ }
610
+ options?.onOpenChange?.(next);
611
+ },
612
+ [isOpenControlled, options]
613
+ );
614
+ const show = useCallback2(() => setOpenState(true), [setOpenState]);
615
+ const hide = useCallback2(() => setOpenState(false), [setOpenState]);
616
+ return {
617
+ value,
618
+ setValue,
619
+ open: resolvedOpen,
620
+ setOpen: setOpenState,
621
+ show,
622
+ hide,
623
+ anchorRef,
624
+ timePickerProps: {
625
+ value,
626
+ onChange: setValue,
627
+ open: resolvedOpen,
628
+ onOpenChange: setOpenState,
629
+ colors: options?.colors,
630
+ locale: options?.locale,
631
+ use24Hour: options?.use24Hour,
632
+ minuteStep: options?.minuteStep,
633
+ slotCount: options?.slotCount,
634
+ startFromNow: options?.startFromNow,
635
+ minTime: options?.minTime,
636
+ maxTime: options?.maxTime,
637
+ disabledRules: options?.disabledRules,
638
+ listHeight: options?.listHeight,
639
+ listClassName: options?.listClassName,
640
+ closeOnSelect: options?.closeOnSelect,
641
+ contentWidth: options?.contentWidth,
642
+ fullWidth: options?.fullWidth
643
+ }
644
+ };
645
+ }
646
+
647
+ // src/index.ts
648
+ var TimePicker1 = Object.assign(TimePicker1Root, {});
649
+ export {
650
+ TIMEPICKER1_DEFAULTS,
651
+ TIMEPICKER1_DEFAULT_COLORS,
652
+ TIMEPICKER1_SCROLLBAR_WIDTH_PX,
653
+ TimePicker1,
654
+ TimePicker1Root,
655
+ TimePickerPanel,
656
+ clampTimeToRange,
657
+ formatTimeSlot,
658
+ generateBoundedTimeSlots,
659
+ getNextSlotAfterNow,
660
+ isTimeDisabledByRules,
661
+ isTimeInRange,
662
+ isTimePickerTimeDisabled,
663
+ isTimeRuleDisabled,
664
+ resolveListHeight,
665
+ resolveScrollbarStyle,
666
+ timePicker1ContentVariants,
667
+ timePicker1ItemVariants,
668
+ timePicker1TriggerVariants,
669
+ timepicker1ListScrollbarStyles,
670
+ useTimePicker1
671
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@velocis/timepicker1",
3
+ "version": "0.2.0",
4
+ "license": "MIT",
5
+ "sideEffects": false,
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "peerDependencies": {
21
+ "react": "^18.2.0 || ^19.0.0",
22
+ "react-dom": "^18.2.0 || ^19.0.0"
23
+ },
24
+ "dependencies": {
25
+ "class-variance-authority": "^0.7.1",
26
+ "@velocis/core": "0.1.1",
27
+ "@velocis/theme": "0.2.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19.0.2",
31
+ "eslint": "^9.17.0",
32
+ "react": "^19.0.0",
33
+ "react-dom": "^19.0.0",
34
+ "tsup": "^8.3.5",
35
+ "typescript": "^5.7.2",
36
+ "@velocis/eslint-config": "0.0.0",
37
+ "@velocis/tsconfig": "0.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "lint": "eslint src",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }