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