@crewlethq/ui 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,4518 @@
1
+ import './Accordion-NIMJQIOI.css';
2
+ import React, { createContext, forwardRef, useId, useRef, useImperativeHandle, useEffect, useState, useCallback, useMemo, useContext, Children, isValidElement, cloneElement, useLayoutEffect, Fragment as Fragment$1 } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import './AppShell-ZW3NSFBP.css';
5
+ import './Avatar-V6NG6Q45.css';
6
+ import './Button-XGCTXXYP.css';
7
+ import './Callout-LEGDIMAM.css';
8
+ import './Card-BOZGBML7.css';
9
+ import './CodeBlock-IEMBNIQC.css';
10
+ import './Copyable-IB57OTHZ.css';
11
+ import './Container-LTKBA2FW.css';
12
+ import './DataTable-3GALZUW2.css';
13
+ import { createPortal } from 'react-dom';
14
+ import './DateTimePicker-NMSSGVPK.css';
15
+ import './Eyebrow-AVBORAS2.css';
16
+ import './IconButton-GB4PJDG2.css';
17
+ import './ImageUpload-3HQDF2KC.css';
18
+ import './Checkbox-OCUICIIZ.css';
19
+ import './Input-ZHUTN7CU.css';
20
+ import './Kbd-Q77CDZY7.css';
21
+ import './Modal-FJVG66S4.css';
22
+ import './Popover-FUFULUE2.css';
23
+ import './PricingCard-NKYV3JHA.css';
24
+ import './Section-665ZIPG6.css';
25
+ import './Select-GPRF7PRB.css';
26
+ import './Skeleton-3WEFLRDN.css';
27
+ import './StatCard-RTJIVTRB.css';
28
+ import './Table-AR3TRY2E.css';
29
+ import './Tabs-XZ4JIH3E.css';
30
+ import './TimeWindowPicker-L3G5Q7UT.css';
31
+ import './Tag-R5QDJLYJ.css';
32
+ import './Toaster-2O65VOR4.css';
33
+
34
+ // src/Accordion/index.ts
35
+ var AccordionContext = createContext(null);
36
+ var toArray = (input) => {
37
+ if (input === void 0) return [];
38
+ return Array.isArray(input) ? input : [input];
39
+ };
40
+ var AccordionRoot = ({
41
+ type = "single",
42
+ collapsible = true,
43
+ defaultValue,
44
+ value,
45
+ onValueChange,
46
+ className = "",
47
+ children,
48
+ ...rest
49
+ }) => {
50
+ const isControlled = value !== void 0;
51
+ const [internal, setInternal] = useState(() => toArray(defaultValue));
52
+ const open = isControlled ? toArray(value) : internal;
53
+ const commit = useCallback(
54
+ (next) => {
55
+ if (!isControlled) setInternal(next);
56
+ if (onValueChange) onValueChange(type === "single" ? next[0] ?? "" : next);
57
+ },
58
+ [isControlled, onValueChange, type]
59
+ );
60
+ const isOpen = useCallback((item) => open.includes(item), [open]);
61
+ const toggle = useCallback(
62
+ (item) => {
63
+ const currentlyOpen = open.includes(item);
64
+ if (type === "single") {
65
+ if (currentlyOpen) commit(collapsible ? [] : [item]);
66
+ else commit([item]);
67
+ return;
68
+ }
69
+ commit(currentlyOpen ? open.filter((entry) => entry !== item) : [...open, item]);
70
+ },
71
+ [open, type, collapsible, commit]
72
+ );
73
+ const ctx = useMemo(() => ({ isOpen, toggle }), [isOpen, toggle]);
74
+ const classes = ["crewlet-accordion", className].filter(Boolean).join(" ");
75
+ return /* @__PURE__ */ jsx(AccordionContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { ...rest, className: classes, children }) });
76
+ };
77
+ var moveFocus = (current, direction) => {
78
+ const root = current.closest(".crewlet-accordion");
79
+ if (!root) return;
80
+ const triggers = Array.from(
81
+ root.querySelectorAll(".crewlet-accordion__trigger:not([disabled])")
82
+ );
83
+ if (triggers.length === 0) return;
84
+ const index = triggers.indexOf(current);
85
+ const target = direction === "next" ? triggers[(index + 1) % triggers.length] : direction === "prev" ? triggers[(index - 1 + triggers.length) % triggers.length] : direction === "first" ? triggers[0] : triggers[triggers.length - 1];
86
+ target?.focus();
87
+ };
88
+ var AccordionItem = ({
89
+ value,
90
+ title,
91
+ disabled = false,
92
+ className = "",
93
+ children,
94
+ ...rest
95
+ }) => {
96
+ const ctx = useContext(AccordionContext);
97
+ if (!ctx) {
98
+ throw new Error("AccordionItem must be rendered inside an Accordion.");
99
+ }
100
+ const open = ctx.isOpen(value);
101
+ const rid = useId();
102
+ const triggerId = `${rid}-trigger`;
103
+ const panelId = `${rid}-panel`;
104
+ const onKeyDown = (event) => {
105
+ switch (event.key) {
106
+ case "ArrowDown":
107
+ event.preventDefault();
108
+ moveFocus(event.currentTarget, "next");
109
+ break;
110
+ case "ArrowUp":
111
+ event.preventDefault();
112
+ moveFocus(event.currentTarget, "prev");
113
+ break;
114
+ case "Home":
115
+ event.preventDefault();
116
+ moveFocus(event.currentTarget, "first");
117
+ break;
118
+ case "End":
119
+ event.preventDefault();
120
+ moveFocus(event.currentTarget, "last");
121
+ break;
122
+ }
123
+ };
124
+ const classes = [
125
+ "crewlet-accordion__item",
126
+ open ? "is-open" : "",
127
+ disabled ? "is-disabled" : "",
128
+ className
129
+ ].filter(Boolean).join(" ");
130
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: classes, "data-state": open ? "open" : "closed", children: [
131
+ /* @__PURE__ */ jsx("h3", { className: "crewlet-accordion__heading", children: /* @__PURE__ */ jsxs(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ id: triggerId,
136
+ className: "crewlet-accordion__trigger",
137
+ "aria-expanded": open,
138
+ "aria-controls": panelId,
139
+ disabled,
140
+ onClick: () => ctx.toggle(value),
141
+ onKeyDown,
142
+ children: [
143
+ /* @__PURE__ */ jsx("span", { className: "crewlet-accordion__title", children: title }),
144
+ /* @__PURE__ */ jsx("span", { className: "crewlet-accordion__icon material-symbols-outlined", "aria-hidden": true, children: "expand_more" })
145
+ ]
146
+ }
147
+ ) }),
148
+ /* @__PURE__ */ jsx(
149
+ "div",
150
+ {
151
+ id: panelId,
152
+ role: "region",
153
+ "aria-labelledby": triggerId,
154
+ className: "crewlet-accordion__panel",
155
+ hidden: !open,
156
+ children: /* @__PURE__ */ jsx("div", { className: "crewlet-accordion__panel-inner", children })
157
+ }
158
+ )
159
+ ] });
160
+ };
161
+ var Accordion = Object.assign(AccordionRoot, { Item: AccordionItem });
162
+ var AppShell = ({
163
+ sidebar,
164
+ topbar,
165
+ footer,
166
+ children,
167
+ sidebarWidth = 280,
168
+ topbarHeight = 64,
169
+ className = "",
170
+ style,
171
+ ...rest
172
+ }) => {
173
+ const composedStyle = {
174
+ ...style,
175
+ ["--crewlet-app-shell-sidebar-width"]: `${sidebarWidth}px`,
176
+ ["--crewlet-app-shell-topbar-height"]: `${topbarHeight}px`
177
+ };
178
+ return /* @__PURE__ */ jsxs("div", { className: `crewlet-app-shell ${className}`.trim(), style: composedStyle, ...rest, children: [
179
+ sidebar,
180
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-app-shell__column", children: [
181
+ topbar,
182
+ /* @__PURE__ */ jsx("main", { className: "crewlet-app-shell__main", children }),
183
+ footer
184
+ ] })
185
+ ] });
186
+ };
187
+ var getInitials = (name) => {
188
+ if (!name) {
189
+ return "?";
190
+ }
191
+ const words = name.trim().split(/\s+/).filter(Boolean);
192
+ if (words.length === 0) {
193
+ return "?";
194
+ }
195
+ return words.slice(0, 2).map((word) => (Array.from(word)[0] ?? "").toUpperCase()).join("");
196
+ };
197
+ var TINT_COUNT = 10;
198
+ var avatarTint = (seed) => {
199
+ let hash = 0;
200
+ for (let i = 0; i < seed.length; i += 1) {
201
+ hash = hash * 31 + seed.charCodeAt(i) >>> 0;
202
+ }
203
+ return hash % TINT_COUNT;
204
+ };
205
+ var Avatar = ({
206
+ src,
207
+ name,
208
+ size = 64,
209
+ shape = "circle",
210
+ colorSeed,
211
+ className = "",
212
+ style,
213
+ ...rest
214
+ }) => {
215
+ const [imageFailed, setImageFailed] = useState(false);
216
+ useEffect(() => {
217
+ setImageFailed(false);
218
+ }, [src]);
219
+ const dimensions = { width: size, height: size };
220
+ const shapeClass = `crewlet-avatar--${shape}`;
221
+ const classes = (...extra) => ["crewlet-avatar", ...extra, shapeClass, className].filter(Boolean).join(" ");
222
+ if (src && !imageFailed) {
223
+ return /* @__PURE__ */ jsx(
224
+ "img",
225
+ {
226
+ ...rest,
227
+ className: classes("crewlet-avatar--image"),
228
+ style: { ...dimensions, ...style },
229
+ src,
230
+ alt: name ?? "",
231
+ onError: () => setImageFailed(true)
232
+ }
233
+ );
234
+ }
235
+ return /* @__PURE__ */ jsx(
236
+ "span",
237
+ {
238
+ ...rest,
239
+ className: classes(
240
+ "crewlet-avatar--fallback",
241
+ ...colorSeed ? ["crewlet-avatar--tint", `crewlet-avatar--tint-${avatarTint(colorSeed)}`] : []
242
+ ),
243
+ style: { ...dimensions, fontSize: Math.max(12, Math.round(size / 3)), ...style },
244
+ role: "img",
245
+ "aria-label": name ? `${name} avatar` : "Avatar",
246
+ children: getInitials(name)
247
+ }
248
+ );
249
+ };
250
+ var renderInner = (leadingIcon, children, loading = false) => /* @__PURE__ */ jsxs(Fragment, { children: [
251
+ loading ? /* @__PURE__ */ jsx("span", { className: "crewlet-btn__spinner", "aria-hidden": true }) : leadingIcon ? /* @__PURE__ */ jsx("span", { className: "crewlet-btn__icon", children: leadingIcon }) : null,
252
+ /* @__PURE__ */ jsx("span", { className: "crewlet-btn__label", children })
253
+ ] });
254
+ var Button = ({
255
+ variant = "primary",
256
+ size = "medium",
257
+ shape = "square",
258
+ leadingIcon,
259
+ className = "",
260
+ type = "button",
261
+ asChild = false,
262
+ loading = false,
263
+ disabled,
264
+ children,
265
+ ...rest
266
+ }) => {
267
+ const classes = [
268
+ "crewlet-btn",
269
+ `crewlet-btn--${variant}`,
270
+ `crewlet-btn--${size}`,
271
+ `crewlet-btn--${shape}`,
272
+ loading ? "crewlet-btn--loading" : "",
273
+ className
274
+ ].filter(Boolean).join(" ");
275
+ if (asChild) {
276
+ const child = Children.only(children);
277
+ if (!isValidElement(child)) {
278
+ throw new Error("Button with asChild expects exactly one valid React element child.");
279
+ }
280
+ const childEl = child;
281
+ const mergedClassName = [childEl.props.className, classes].filter(Boolean).join(" ");
282
+ return cloneElement(childEl, { ...rest, className: mergedClassName }, renderInner(
283
+ leadingIcon,
284
+ childEl.props.children
285
+ ));
286
+ }
287
+ return /* @__PURE__ */ jsx(
288
+ "button",
289
+ {
290
+ type,
291
+ className: classes,
292
+ disabled: disabled || loading,
293
+ "aria-busy": loading || void 0,
294
+ ...rest,
295
+ children: renderInner(leadingIcon, children, loading)
296
+ }
297
+ );
298
+ };
299
+ var Callout = ({
300
+ variant = "info",
301
+ title,
302
+ icon,
303
+ className = "",
304
+ children,
305
+ ...rest
306
+ }) => {
307
+ const classes = ["crewlet-callout", `crewlet-callout--${variant}`, className].filter(Boolean).join(" ");
308
+ return /* @__PURE__ */ jsxs("div", { role: "status", ...rest, className: classes, children: [
309
+ icon ? /* @__PURE__ */ jsx("span", { className: "crewlet-callout__icon", "aria-hidden": true, children: icon }) : null,
310
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-callout__content", children: [
311
+ title ? /* @__PURE__ */ jsx("span", { className: "crewlet-callout__title", children: title }) : null,
312
+ children
313
+ ] })
314
+ ] });
315
+ };
316
+ var CardRoot = ({
317
+ variant = "default",
318
+ padding = "md",
319
+ interactive = false,
320
+ className = "",
321
+ children,
322
+ ...rest
323
+ }) => {
324
+ const classes = [
325
+ "crewlet-card",
326
+ `crewlet-card--${variant}`,
327
+ `crewlet-card--p-${padding}`,
328
+ interactive ? "is-interactive" : "",
329
+ className
330
+ ].filter(Boolean).join(" ");
331
+ return /* @__PURE__ */ jsx("div", { ...rest, className: classes, children });
332
+ };
333
+ var CardHeader = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-card__header ${className}`.trim(), children });
334
+ var CardBody = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-card__body ${className}`.trim(), children });
335
+ var CardFooter = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-card__footer ${className}`.trim(), children });
336
+ var CardTitle = ({ as: As = "h3", className = "", children, ...rest }) => /* @__PURE__ */ jsx(As, { ...rest, className: `crewlet-card__title ${className}`.trim(), children });
337
+ var CardDescription = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("p", { ...rest, className: `crewlet-card__description ${className}`.trim(), children });
338
+ var Card = Object.assign(CardRoot, {
339
+ Header: CardHeader,
340
+ Body: CardBody,
341
+ Footer: CardFooter,
342
+ Title: CardTitle,
343
+ Description: CardDescription
344
+ });
345
+ var COPIED_DURATION_MS = 1500;
346
+ var truncateValue = (text, length, variant) => {
347
+ if (text.length <= length) return text;
348
+ if (variant === "chip") {
349
+ const head = Math.max(4, length - 5);
350
+ const tail = Math.min(4, Math.max(2, length - head - 1));
351
+ return `${text.slice(0, head)}\u2026${text.slice(-tail)}`;
352
+ }
353
+ return `${text.slice(0, length)}\u2026`;
354
+ };
355
+ var writeToClipboard = async (text) => {
356
+ if (typeof navigator !== "undefined" && navigator.clipboard && window.isSecureContext) {
357
+ await navigator.clipboard.writeText(text);
358
+ return;
359
+ }
360
+ const ta = document.createElement("textarea");
361
+ ta.value = text;
362
+ ta.style.position = "fixed";
363
+ ta.style.opacity = "0";
364
+ document.body.appendChild(ta);
365
+ ta.focus();
366
+ ta.select();
367
+ document.execCommand("copy");
368
+ document.body.removeChild(ta);
369
+ };
370
+ var Copyable = ({
371
+ value,
372
+ display,
373
+ variant = "chip",
374
+ monospace = false,
375
+ truncate = false,
376
+ truncateLength,
377
+ ariaLabel,
378
+ className = ""
379
+ }) => {
380
+ const text = value === void 0 || value === null ? "" : String(value);
381
+ const [copied, setCopied] = useState(false);
382
+ const timerRef = useRef(null);
383
+ useEffect(() => () => {
384
+ if (timerRef.current) clearTimeout(timerRef.current);
385
+ }, []);
386
+ if (!text) return null;
387
+ const resolvedLength = truncateLength ?? (variant === "chip" ? 14 : 24);
388
+ const shouldTruncate = truncate || variant === "chip" && text.length > resolvedLength;
389
+ const rendered = display ?? (shouldTruncate ? truncateValue(text, resolvedLength, variant) : text);
390
+ const onCopy = async (event) => {
391
+ event.stopPropagation();
392
+ event.preventDefault();
393
+ try {
394
+ await writeToClipboard(text);
395
+ setCopied(true);
396
+ if (timerRef.current) clearTimeout(timerRef.current);
397
+ timerRef.current = setTimeout(() => setCopied(false), COPIED_DURATION_MS);
398
+ } catch {
399
+ }
400
+ };
401
+ const classes = [
402
+ "crewlet-copyable",
403
+ `crewlet-copyable--${variant}`,
404
+ monospace ? "crewlet-copyable--monospace" : "",
405
+ copied ? "is-copied" : "",
406
+ className
407
+ ].filter(Boolean).join(" ");
408
+ return /* @__PURE__ */ jsxs("span", { className: classes, children: [
409
+ /* @__PURE__ */ jsx(
410
+ "span",
411
+ {
412
+ className: "crewlet-copyable__value",
413
+ title: text,
414
+ role: "button",
415
+ tabIndex: 0,
416
+ onClick: onCopy,
417
+ onKeyDown: (e) => {
418
+ if (e.key === "Enter" || e.key === " ") onCopy(e);
419
+ },
420
+ children: rendered
421
+ }
422
+ ),
423
+ /* @__PURE__ */ jsx(
424
+ "button",
425
+ {
426
+ type: "button",
427
+ className: "crewlet-copyable__button",
428
+ onClick: onCopy,
429
+ "aria-label": ariaLabel || "Copy to clipboard",
430
+ title: "Copy",
431
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: copied ? "check" : "content_copy" })
432
+ }
433
+ )
434
+ ] });
435
+ };
436
+ var CodeBlock = ({
437
+ code,
438
+ language,
439
+ filename,
440
+ showLineNumbers = false,
441
+ copyable = true,
442
+ prompt,
443
+ className = ""
444
+ }) => {
445
+ const lines = code.replace(/\n+$/, "").split("\n");
446
+ const showHeader = Boolean(filename) || Boolean(language) || copyable;
447
+ const classes = [
448
+ "crewlet-codeblock",
449
+ showLineNumbers ? "crewlet-codeblock--numbered" : "",
450
+ className
451
+ ].filter(Boolean).join(" ");
452
+ return /* @__PURE__ */ jsxs("div", { className: classes, children: [
453
+ showHeader ? /* @__PURE__ */ jsxs("div", { className: "crewlet-codeblock__header", children: [
454
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-codeblock__meta", children: [
455
+ filename ? /* @__PURE__ */ jsx("span", { className: "crewlet-codeblock__filename", children: filename }) : null,
456
+ language && !filename ? /* @__PURE__ */ jsx("span", { className: "crewlet-codeblock__lang", children: language }) : null
457
+ ] }),
458
+ copyable ? /* @__PURE__ */ jsx(
459
+ Copyable,
460
+ {
461
+ value: code,
462
+ display: "Copy",
463
+ variant: "inline",
464
+ ariaLabel: "Copy code to clipboard",
465
+ className: "crewlet-codeblock__copy"
466
+ }
467
+ ) : null
468
+ ] }) : null,
469
+ /* @__PURE__ */ jsx("pre", { className: "crewlet-codeblock__pre", "data-language": language, children: /* @__PURE__ */ jsx("code", { className: "crewlet-codeblock__code", children: lines.map((line2, index) => /* @__PURE__ */ jsxs("span", { className: "crewlet-codeblock__line", children: [
470
+ showLineNumbers ? /* @__PURE__ */ jsx("span", { className: "crewlet-codeblock__ln", "aria-hidden": true, children: index + 1 }) : null,
471
+ prompt ? /* @__PURE__ */ jsx("span", { className: "crewlet-codeblock__prompt", "aria-hidden": true, children: prompt }) : null,
472
+ /* @__PURE__ */ jsx("span", { className: "crewlet-codeblock__linecontent", children: line2 || "\xA0" })
473
+ ] }, `${index}-${line2}`)) }) })
474
+ ] });
475
+ };
476
+ var Container = ({
477
+ size = "lg",
478
+ className = "",
479
+ children,
480
+ ...rest
481
+ }) => {
482
+ const classes = ["crewlet-container", `crewlet-container--${size}`, className].filter(Boolean).join(" ");
483
+ return /* @__PURE__ */ jsx("div", { ...rest, className: classes, children });
484
+ };
485
+ var Modal = ({
486
+ open,
487
+ onClose,
488
+ title,
489
+ subtitle,
490
+ footer,
491
+ children,
492
+ size = "sm",
493
+ closeOnBackdrop = true,
494
+ showCloseButton = true,
495
+ className = "",
496
+ labelledBy,
497
+ describedBy
498
+ }) => {
499
+ const dialogRef = useRef(null);
500
+ const onCloseRef = useRef(onClose);
501
+ useEffect(() => {
502
+ onCloseRef.current = onClose;
503
+ });
504
+ useEffect(() => {
505
+ if (!open) return void 0;
506
+ const onKey = (event) => {
507
+ if (event.key === "Escape") onCloseRef.current();
508
+ };
509
+ document.addEventListener("keydown", onKey);
510
+ const prevOverflow = document.body.style.overflow;
511
+ document.body.style.overflow = "hidden";
512
+ const previouslyFocused = document.activeElement;
513
+ requestAnimationFrame(() => {
514
+ dialogRef.current?.focus();
515
+ });
516
+ return () => {
517
+ document.removeEventListener("keydown", onKey);
518
+ document.body.style.overflow = prevOverflow;
519
+ previouslyFocused?.focus?.();
520
+ };
521
+ }, [open]);
522
+ if (!open || typeof document === "undefined") return null;
523
+ const onBackdropMouseDown = () => {
524
+ if (closeOnBackdrop) onClose();
525
+ };
526
+ const stopPropagation = (event) => event.stopPropagation();
527
+ return createPortal(
528
+ /* @__PURE__ */ jsx(
529
+ "div",
530
+ {
531
+ className: "crewlet-modal-overlay",
532
+ role: "presentation",
533
+ onMouseDown: onBackdropMouseDown,
534
+ children: /* @__PURE__ */ jsxs(
535
+ "div",
536
+ {
537
+ ref: dialogRef,
538
+ className: `crewlet-modal crewlet-modal--${size} ${className}`.trim(),
539
+ role: "dialog",
540
+ "aria-modal": "true",
541
+ "aria-labelledby": labelledBy,
542
+ "aria-describedby": describedBy,
543
+ tabIndex: -1,
544
+ onMouseDown: stopPropagation,
545
+ children: [
546
+ (title || subtitle || showCloseButton) && /* @__PURE__ */ jsxs("div", { className: "crewlet-modal__header", children: [
547
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-modal__heading", children: [
548
+ title ? /* @__PURE__ */ jsx("h2", { className: "crewlet-modal__title", id: labelledBy, children: title }) : null,
549
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "crewlet-modal__subtitle", children: subtitle }) : null
550
+ ] }),
551
+ showCloseButton ? /* @__PURE__ */ jsx(
552
+ "button",
553
+ {
554
+ type: "button",
555
+ className: "crewlet-modal__close",
556
+ onClick: onClose,
557
+ "aria-label": "Close",
558
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "close" })
559
+ }
560
+ ) : null
561
+ ] }),
562
+ children !== void 0 ? /* @__PURE__ */ jsx("div", { className: "crewlet-modal__body", children }) : null,
563
+ footer ? /* @__PURE__ */ jsx("div", { className: "crewlet-modal__footer", children: footer }) : null
564
+ ]
565
+ }
566
+ )
567
+ }
568
+ ),
569
+ document.body
570
+ );
571
+ };
572
+ var COPIED_DURATION_MS2 = 1500;
573
+ var writeToClipboard2 = async (text) => {
574
+ if (typeof navigator !== "undefined" && navigator.clipboard && window.isSecureContext) {
575
+ await navigator.clipboard.writeText(text);
576
+ return;
577
+ }
578
+ const ta = document.createElement("textarea");
579
+ ta.value = text;
580
+ ta.style.position = "fixed";
581
+ ta.style.opacity = "0";
582
+ document.body.appendChild(ta);
583
+ ta.focus();
584
+ ta.select();
585
+ document.execCommand("copy");
586
+ document.body.removeChild(ta);
587
+ };
588
+ var CopyableCell = ({ value, children, ariaLabel }) => {
589
+ const text = value === void 0 || value === null ? "" : String(value);
590
+ const [copied, setCopied] = useState(false);
591
+ const timer = useRef(null);
592
+ useEffect(() => () => {
593
+ if (timer.current) clearTimeout(timer.current);
594
+ }, []);
595
+ const onCopy = async (event) => {
596
+ event.stopPropagation();
597
+ if (!text) return;
598
+ try {
599
+ await writeToClipboard2(text);
600
+ setCopied(true);
601
+ if (timer.current) clearTimeout(timer.current);
602
+ timer.current = setTimeout(() => setCopied(false), COPIED_DURATION_MS2);
603
+ } catch {
604
+ }
605
+ };
606
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-data-table__copy-cell", children: [
607
+ /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__copy-cell-value", children }),
608
+ text && /* @__PURE__ */ jsx(
609
+ "button",
610
+ {
611
+ type: "button",
612
+ className: `crewlet-data-table__copy-cell-button${copied ? " is-copied" : ""}`,
613
+ onClick: onCopy,
614
+ "aria-label": ariaLabel || "Copy to clipboard",
615
+ title: "Copy",
616
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: copied ? "check" : "content_copy" })
617
+ }
618
+ )
619
+ ] });
620
+ };
621
+ function Popover({
622
+ trigger,
623
+ children,
624
+ align = "start",
625
+ side = "bottom",
626
+ width = "auto",
627
+ className = "",
628
+ defaultOpen = false,
629
+ onOpenChange
630
+ }) {
631
+ const [open, setOpen] = useState(defaultOpen);
632
+ useEffect(() => {
633
+ onOpenChange?.(open);
634
+ }, [open, onOpenChange]);
635
+ const [coords, setCoords] = useState(
636
+ { top: 0, left: 0, width: 0, side }
637
+ );
638
+ const triggerRef = useRef(null);
639
+ const panelRef = useRef(null);
640
+ const close = () => setOpen(false);
641
+ const toggle = () => setOpen((o) => !o);
642
+ const reposition = () => {
643
+ const trig = triggerRef.current;
644
+ if (!trig) return;
645
+ const rect = trig.getBoundingClientRect();
646
+ const panelHeight = panelRef.current?.offsetHeight ?? 200;
647
+ const viewportH = window.innerHeight;
648
+ let chosenSide = side;
649
+ if (side === "bottom" && rect.bottom + panelHeight + 12 > viewportH) {
650
+ chosenSide = "top";
651
+ }
652
+ const top = chosenSide === "bottom" ? rect.bottom + window.scrollY + 6 : rect.top + window.scrollY - panelHeight - 6;
653
+ const left = align === "end" ? rect.right + window.scrollX : rect.left + window.scrollX;
654
+ setCoords({ top, left, width: rect.width, side: chosenSide });
655
+ };
656
+ useEffect(() => {
657
+ if (!open) return void 0;
658
+ reposition();
659
+ const onClick = (event) => {
660
+ const target = event.target;
661
+ if (target && panelRef.current?.contains(target)) return;
662
+ if (target && triggerRef.current?.contains(target)) return;
663
+ setOpen(false);
664
+ };
665
+ const onKey = (event) => {
666
+ if (event.key === "Escape") {
667
+ event.preventDefault();
668
+ setOpen(false);
669
+ triggerRef.current?.focus();
670
+ }
671
+ };
672
+ const onLayout = () => reposition();
673
+ document.addEventListener("mousedown", onClick);
674
+ document.addEventListener("keydown", onKey);
675
+ window.addEventListener("resize", onLayout);
676
+ window.addEventListener("scroll", onLayout, true);
677
+ return () => {
678
+ document.removeEventListener("mousedown", onClick);
679
+ document.removeEventListener("keydown", onKey);
680
+ window.removeEventListener("resize", onLayout);
681
+ window.removeEventListener("scroll", onLayout, true);
682
+ };
683
+ }, [open, align, side]);
684
+ const triggerEl = trigger(open, toggle);
685
+ const triggerWithRef = React.cloneElement(triggerEl, {
686
+ ref: triggerRef,
687
+ "aria-expanded": open,
688
+ "aria-haspopup": "menu"
689
+ });
690
+ const panelStyle = {
691
+ top: coords.top,
692
+ [align === "end" ? "right" : "left"]: align === "end" ? typeof document !== "undefined" ? document.documentElement.clientWidth - coords.left : 0 : coords.left
693
+ };
694
+ if (width === "match") panelStyle.width = coords.width;
695
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
696
+ triggerWithRef,
697
+ open && createPortal(
698
+ /* @__PURE__ */ jsx(
699
+ "div",
700
+ {
701
+ ref: panelRef,
702
+ className: `crewlet-popover crewlet-popover--${coords.side} ${className}`,
703
+ style: panelStyle,
704
+ role: "menu",
705
+ tabIndex: -1,
706
+ onMouseDown: (e) => e.stopPropagation(),
707
+ children: typeof children === "function" ? children(close) : children
708
+ }
709
+ ),
710
+ document.body
711
+ )
712
+ ] });
713
+ }
714
+ var RowActionsMenu = ({ actions, ariaLabel = "Row actions" }) => {
715
+ const items = (actions || []).filter(
716
+ (a) => !!a && a.visible !== false
717
+ );
718
+ if (items.length === 0) return null;
719
+ return /* @__PURE__ */ jsx(
720
+ Popover,
721
+ {
722
+ align: "end",
723
+ side: "bottom",
724
+ width: "auto",
725
+ className: "crewlet-data-table__row-actions-popover",
726
+ trigger: (open, toggle) => /* @__PURE__ */ jsx(
727
+ "button",
728
+ {
729
+ type: "button",
730
+ className: `crewlet-data-table__row-actions-trigger${open ? " is-open" : ""}`,
731
+ onClick: (e) => {
732
+ e.stopPropagation();
733
+ toggle();
734
+ },
735
+ "aria-label": ariaLabel,
736
+ "aria-haspopup": "menu",
737
+ "aria-expanded": open,
738
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "more_vert" })
739
+ }
740
+ ),
741
+ children: (close) => /* @__PURE__ */ jsx("ul", { className: "crewlet-data-table__row-actions-list", role: "menu", children: items.map((action, i) => {
742
+ const cls = [
743
+ "crewlet-data-table__row-actions-item",
744
+ action.variant && `crewlet-data-table__row-actions-item--${action.variant}`,
745
+ action.disabled && "is-disabled"
746
+ ].filter(Boolean).join(" ");
747
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
748
+ action.divider === "before" && /* @__PURE__ */ jsx("li", { className: "crewlet-data-table__row-actions-divider", role: "separator" }),
749
+ /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsxs(
750
+ "button",
751
+ {
752
+ type: "button",
753
+ role: "menuitem",
754
+ className: cls,
755
+ disabled: action.disabled,
756
+ onClick: (e) => {
757
+ e.stopPropagation();
758
+ if (action.disabled) return;
759
+ action.onClick?.();
760
+ close();
761
+ },
762
+ children: [
763
+ action.icon && /* @__PURE__ */ jsx(
764
+ "span",
765
+ {
766
+ className: "material-symbols-outlined crewlet-data-table__row-actions-item-icon",
767
+ "aria-hidden": true,
768
+ children: action.icon
769
+ }
770
+ ),
771
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-data-table__row-actions-item-text", children: [
772
+ /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__row-actions-item-label", children: action.label }),
773
+ action.description && /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__row-actions-item-description", children: action.description })
774
+ ] })
775
+ ]
776
+ }
777
+ ) }),
778
+ action.divider === "after" && /* @__PURE__ */ jsx("li", { className: "crewlet-data-table__row-actions-divider", role: "separator" })
779
+ ] }, i);
780
+ }) })
781
+ }
782
+ );
783
+ };
784
+ var DataTable = ({
785
+ data = [],
786
+ columns = {},
787
+ storageKey,
788
+ defaultColumnOrder = null,
789
+ defaultItemsPerPage = 10,
790
+ minColumnWidth = 80,
791
+ resizable = true,
792
+ itemsPerPageOptions = [5, 10, 20, 50, 100],
793
+ onRowAction = null,
794
+ actionLabel = "Delete",
795
+ actionIcon = "delete",
796
+ getRowKey,
797
+ rowKey,
798
+ emptyMessage = "No data available",
799
+ showActions = true,
800
+ className = "",
801
+ variant = "default",
802
+ density = "compact",
803
+ sort: sortProp,
804
+ onSortChange,
805
+ rowActions,
806
+ archivedPredicate,
807
+ archivedLabel = "archived",
808
+ loading = false,
809
+ skeletonRows = 5,
810
+ error = null,
811
+ paginated = true,
812
+ showSettings = true,
813
+ onRowClick,
814
+ renderAccessoryRow,
815
+ renderToolbar,
816
+ onClearFilters,
817
+ filtersActive = false,
818
+ clearFiltersLabel = "Clear filters",
819
+ renderExpandedRow,
820
+ defaultExpandedKey = null,
821
+ hasMore = false,
822
+ onLoadMore,
823
+ loadingMore = false,
824
+ loadMoreLabel = "Load more",
825
+ title,
826
+ description,
827
+ icon,
828
+ settingsVariant,
829
+ defaultWrapLines
830
+ }) => {
831
+ const headerVisibleFlag = title != null || description != null || icon != null;
832
+ const effectiveSettingsVariant = settingsVariant ?? (headerVisibleFlag ? "compact" : "rich");
833
+ const effectiveDefaultWrapLines = defaultWrapLines ?? true;
834
+ const resolveRowKey = (row) => {
835
+ if (getRowKey) return getRowKey(row);
836
+ if (rowKey) return row?.[rowKey];
837
+ return row?.id;
838
+ };
839
+ const isCompact = variant === "compact";
840
+ const settingsEnabled = showSettings;
841
+ const paginationEnabled = paginated;
842
+ const settingsInHeader = settingsEnabled && !isCompact;
843
+ const generateDefaultColumnWidths = () => {
844
+ const widths = {};
845
+ Object.keys(columns).forEach((key) => {
846
+ const col = columns[key];
847
+ const rawExplicit = col?.width ?? col?.defaultWidth;
848
+ let px = null;
849
+ if (rawExplicit != null) {
850
+ const parsed = typeof rawExplicit === "number" ? rawExplicit : parseInt(String(rawExplicit), 10);
851
+ if (Number.isFinite(parsed)) px = parsed;
852
+ }
853
+ if (px == null) {
854
+ const labelStr = typeof col?.label === "string" ? col.label : key;
855
+ const estimated = Math.ceil(labelStr.length * 7.5) + 56;
856
+ px = Math.max(120, estimated);
857
+ }
858
+ widths[key] = Math.max(minColumnWidth, px);
859
+ });
860
+ if (showActions) {
861
+ widths.__actions = 60;
862
+ }
863
+ return widths;
864
+ };
865
+ const generateDefaultVisibleColumns = () => {
866
+ const visible = {};
867
+ Object.keys(columns).forEach((key) => {
868
+ visible[key] = columns[key]?.defaultVisible !== false;
869
+ });
870
+ return visible;
871
+ };
872
+ const generateDefaultColumnOrder = () => {
873
+ return defaultColumnOrder || Object.keys(columns);
874
+ };
875
+ const defaultColumnWidths = generateDefaultColumnWidths();
876
+ const defaultVisibleColumns = generateDefaultVisibleColumns();
877
+ const defaultOrder = generateDefaultColumnOrder();
878
+ const [columnWidths, setColumnWidths] = useState(() => {
879
+ if (!storageKey) return defaultColumnWidths;
880
+ const saved = localStorage.getItem(`${storageKey}_columnWidths`);
881
+ if (!saved) return defaultColumnWidths;
882
+ try {
883
+ const parsed = JSON.parse(saved);
884
+ const merged = { ...defaultColumnWidths };
885
+ Object.entries(parsed).forEach(([key, value]) => {
886
+ const px = typeof value === "number" ? value : parseInt(String(value), 10);
887
+ if (!Number.isFinite(px)) return;
888
+ const col = columns[key];
889
+ if (col?.width != null) return;
890
+ merged[key] = key.startsWith("__") ? px : Math.max(minColumnWidth, px);
891
+ });
892
+ return merged;
893
+ } catch {
894
+ return defaultColumnWidths;
895
+ }
896
+ });
897
+ const [itemsPerPage, setItemsPerPage] = useState(() => {
898
+ if (!storageKey) return defaultItemsPerPage;
899
+ const saved = localStorage.getItem(`${storageKey}_itemsPerPage`);
900
+ return saved ? parseInt(saved, 10) : defaultItemsPerPage;
901
+ });
902
+ const [visibleColumns, setVisibleColumns] = useState(() => {
903
+ if (!storageKey) return defaultVisibleColumns;
904
+ const saved = localStorage.getItem(`${storageKey}_visibleColumns`);
905
+ if (saved) {
906
+ const savedVisible = JSON.parse(saved);
907
+ const validVisible = {};
908
+ Object.keys(columns).forEach((key) => {
909
+ validVisible[key] = Object.prototype.hasOwnProperty.call(savedVisible, key) ? savedVisible[key] : columns[key]?.defaultVisible !== false;
910
+ });
911
+ return validVisible;
912
+ }
913
+ return defaultVisibleColumns;
914
+ });
915
+ const [columnOrder, setColumnOrder] = useState(() => {
916
+ if (!storageKey) return defaultOrder;
917
+ const saved = localStorage.getItem(`${storageKey}_columnOrder`);
918
+ if (saved) {
919
+ const savedOrder = JSON.parse(saved);
920
+ const validOrder = savedOrder.filter((key) => Object.prototype.hasOwnProperty.call(columns, key));
921
+ const newColumns = defaultOrder.filter((key) => !validOrder.includes(key));
922
+ return validOrder.length > 0 ? [...validOrder, ...newColumns] : defaultOrder;
923
+ }
924
+ return defaultOrder;
925
+ });
926
+ const [sortConfig, setSortConfig] = useState(
927
+ { key: null, direction: null }
928
+ );
929
+ const [tableSettingsOpen, setTableSettingsOpen] = useState(false);
930
+ const [wrapLines, setWrapLines] = useState(() => {
931
+ if (!storageKey) return effectiveDefaultWrapLines;
932
+ const saved = localStorage.getItem(`${storageKey}_wrapLines`);
933
+ return saved == null ? effectiveDefaultWrapLines : saved === "true";
934
+ });
935
+ const [draftItemsPerPage, setDraftItemsPerPage] = useState(0);
936
+ const [draftWrapLines, setDraftWrapLines] = useState(effectiveDefaultWrapLines);
937
+ const [draftColumnWidths, setDraftColumnWidths] = useState({});
938
+ const [draftVisibleColumns, setDraftVisibleColumns] = useState({});
939
+ const [draftColumnOrder, setDraftColumnOrder] = useState([]);
940
+ const [draftSortConfig, setDraftSortConfig] = useState(
941
+ { key: null, direction: null }
942
+ );
943
+ const [resetConfirmed, setResetConfirmed] = useState(false);
944
+ const resetConfirmTimer = useRef(null);
945
+ const pendingFitOnApplyRef = useRef(false);
946
+ const [expandedKeys, setExpandedKeys] = useState(
947
+ () => defaultExpandedKey != null ? /* @__PURE__ */ new Set([defaultExpandedKey]) : /* @__PURE__ */ new Set()
948
+ );
949
+ const toggleExpanded = (key) => {
950
+ setExpandedKeys((prev) => {
951
+ const next = new Set(prev);
952
+ if (next.has(key)) {
953
+ next.delete(key);
954
+ } else {
955
+ next.add(key);
956
+ }
957
+ return next;
958
+ });
959
+ };
960
+ const [draggedColumn, setDraggedColumn] = useState(null);
961
+ const [resizingColumn, setResizingColumn] = useState(null);
962
+ const [startX, setStartX] = useState(0);
963
+ const [startWidth, setStartWidth] = useState(0);
964
+ const dragTimeoutRef = useRef(null);
965
+ const settingsButtonRef = useRef(null);
966
+ const scrollRef = useRef(null);
967
+ const [needsFit, setNeedsFit] = useState(true);
968
+ const resetToDefault = () => {
969
+ const newDefaultWidths = generateDefaultColumnWidths();
970
+ const newDefaultVisible = generateDefaultVisibleColumns();
971
+ const newDefaultOrder = generateDefaultColumnOrder();
972
+ setColumnWidths(newDefaultWidths);
973
+ setVisibleColumns(newDefaultVisible);
974
+ setColumnOrder(newDefaultOrder);
975
+ setItemsPerPage(defaultItemsPerPage);
976
+ setSortConfig({ key: null, direction: null });
977
+ setNeedsFit(true);
978
+ if (storageKey) {
979
+ localStorage.removeItem(`${storageKey}_columnWidths`);
980
+ localStorage.removeItem(`${storageKey}_visibleColumns`);
981
+ localStorage.removeItem(`${storageKey}_columnOrder`);
982
+ localStorage.removeItem(`${storageKey}_itemsPerPage`);
983
+ }
984
+ };
985
+ useLayoutEffect(() => {
986
+ if (!needsFit) return;
987
+ const stickyColumnPresent = columnOrder.some(
988
+ (k) => columns[k]?.sticky === "right" && visibleColumns[k] !== false
989
+ );
990
+ if (!stickyColumnPresent && storageKey && localStorage.getItem(`${storageKey}_columnWidths`)) {
991
+ setNeedsFit(false);
992
+ return;
993
+ }
994
+ const el = scrollRef.current;
995
+ if (!el) return;
996
+ const containerWidth = el.clientWidth;
997
+ if (containerWidth <= 0) return;
998
+ const visibleAllKeys = columnOrder.filter((key) => visibleColumns[key] !== false);
999
+ const isFixedWidthColumn = (key) => {
1000
+ const col = columns[key];
1001
+ if (!col) return false;
1002
+ if (col.sticky === "right") return false;
1003
+ return col.width != null;
1004
+ };
1005
+ const visibleStickyKeys = visibleAllKeys.filter((key) => columns[key]?.sticky === "right");
1006
+ const visibleFixedKeys = visibleAllKeys.filter((key) => isFixedWidthColumn(key));
1007
+ const visibleDataKeys = visibleAllKeys.filter(
1008
+ (key) => columns[key]?.sticky !== "right" && !isFixedWidthColumn(key)
1009
+ );
1010
+ if (visibleDataKeys.length === 0) {
1011
+ setNeedsFit(false);
1012
+ return;
1013
+ }
1014
+ let chromeWidth = 0;
1015
+ if (expandable) chromeWidth += 32;
1016
+ if (hasActionColumn) chromeWidth += isCompact ? 48 : columnWidths.__actions ?? 60;
1017
+ chromeWidth += visibleStickyKeys.reduce(
1018
+ (sum, key) => sum + (columnWidths[key] ?? minColumnWidth),
1019
+ 0
1020
+ );
1021
+ chromeWidth += visibleFixedKeys.reduce(
1022
+ (sum, key) => sum + (columnWidths[key] ?? minColumnWidth),
1023
+ 0
1024
+ );
1025
+ if (visibleStickyKeys.length > 0) {
1026
+ chromeWidth += 24;
1027
+ }
1028
+ const dataTotal = visibleDataKeys.reduce(
1029
+ (sum, key) => sum + (columnWidths[key] ?? minColumnWidth),
1030
+ 0
1031
+ );
1032
+ const available = containerWidth - chromeWidth;
1033
+ const leftover = available - dataTotal;
1034
+ setNeedsFit(false);
1035
+ if (Math.abs(leftover) <= 1) return;
1036
+ const next = { ...columnWidths };
1037
+ let distributed = 0;
1038
+ visibleDataKeys.forEach((key, idx) => {
1039
+ const current = columnWidths[key] ?? minColumnWidth;
1040
+ const rawShare = idx === visibleDataKeys.length - 1 ? leftover - distributed : Math.round(leftover * (current / dataTotal));
1041
+ const proposed = current + rawShare;
1042
+ const clamped = Math.max(minColumnWidth, proposed);
1043
+ const realShare = clamped - current;
1044
+ next[key] = clamped;
1045
+ distributed += realShare;
1046
+ });
1047
+ setColumnWidths(next);
1048
+ }, [needsFit]);
1049
+ useEffect(() => {
1050
+ const el = scrollRef.current;
1051
+ if (!el) return;
1052
+ let lastWidth = el.clientWidth;
1053
+ const observer = new ResizeObserver(() => {
1054
+ const w = el.clientWidth;
1055
+ if (w > 0 && w !== lastWidth) {
1056
+ lastWidth = w;
1057
+ setNeedsFit(true);
1058
+ }
1059
+ });
1060
+ observer.observe(el);
1061
+ return () => observer.disconnect();
1062
+ }, []);
1063
+ useEffect(() => {
1064
+ if (storageKey) {
1065
+ localStorage.setItem(`${storageKey}_columnWidths`, JSON.stringify(columnWidths));
1066
+ }
1067
+ }, [columnWidths, storageKey]);
1068
+ useEffect(() => {
1069
+ if (storageKey) {
1070
+ localStorage.setItem(`${storageKey}_itemsPerPage`, itemsPerPage.toString());
1071
+ }
1072
+ }, [itemsPerPage, storageKey]);
1073
+ useEffect(() => {
1074
+ if (storageKey) {
1075
+ localStorage.setItem(`${storageKey}_visibleColumns`, JSON.stringify(visibleColumns));
1076
+ }
1077
+ }, [visibleColumns, storageKey]);
1078
+ useEffect(() => {
1079
+ if (storageKey) {
1080
+ localStorage.setItem(`${storageKey}_columnOrder`, JSON.stringify(columnOrder));
1081
+ }
1082
+ }, [columnOrder, storageKey]);
1083
+ useEffect(() => {
1084
+ if (storageKey) {
1085
+ localStorage.setItem(`${storageKey}_wrapLines`, String(wrapLines));
1086
+ }
1087
+ }, [wrapLines, storageKey]);
1088
+ const handleMouseDownResize = (e, columnKey) => {
1089
+ e.preventDefault();
1090
+ e.stopPropagation();
1091
+ setResizingColumn(columnKey);
1092
+ setStartX(e.clientX);
1093
+ setStartWidth(columnWidths[columnKey] ?? minColumnWidth);
1094
+ };
1095
+ useEffect(() => {
1096
+ const handleMouseMove = (e) => {
1097
+ if (resizingColumn) {
1098
+ const diff = e.clientX - startX;
1099
+ const newWidth = Math.max(minColumnWidth, startWidth + diff);
1100
+ setColumnWidths((prev) => ({
1101
+ ...prev,
1102
+ [resizingColumn]: newWidth
1103
+ }));
1104
+ }
1105
+ };
1106
+ const handleMouseUp = () => {
1107
+ if (resizingColumn) {
1108
+ setResizingColumn(null);
1109
+ }
1110
+ };
1111
+ if (resizingColumn) {
1112
+ document.body.classList.add("crewlet-data-table-resizing");
1113
+ document.addEventListener("mousemove", handleMouseMove);
1114
+ document.addEventListener("mouseup", handleMouseUp);
1115
+ return () => {
1116
+ document.body.classList.remove("crewlet-data-table-resizing");
1117
+ document.removeEventListener("mousemove", handleMouseMove);
1118
+ document.removeEventListener("mouseup", handleMouseUp);
1119
+ };
1120
+ }
1121
+ return void 0;
1122
+ }, [resizingColumn, startX, startWidth]);
1123
+ const controlledSort = sortProp !== void 0;
1124
+ const effectiveSort = controlledSort ? sortProp ? { key: sortProp.key, direction: sortProp.direction } : { key: null, direction: null } : sortConfig;
1125
+ const handleSort = (columnKey) => {
1126
+ if (!columns[columnKey]?.sortable && columns[columnKey]?.sortable !== void 0) {
1127
+ return;
1128
+ }
1129
+ let direction = "asc";
1130
+ if (effectiveSort.key === columnKey) {
1131
+ if (effectiveSort.direction === "asc") {
1132
+ direction = "desc";
1133
+ } else if (effectiveSort.direction === "desc") {
1134
+ direction = null;
1135
+ }
1136
+ }
1137
+ const next = direction ? { key: columnKey, direction } : null;
1138
+ if (controlledSort) {
1139
+ onSortChange?.(next);
1140
+ } else {
1141
+ setSortConfig({ key: next?.key ?? null, direction: next?.direction ?? null });
1142
+ }
1143
+ };
1144
+ const getSortedData = () => {
1145
+ const sortKey = effectiveSort.key;
1146
+ if (!sortKey || !effectiveSort.direction) {
1147
+ return [...data];
1148
+ }
1149
+ const column = columns[sortKey];
1150
+ const accessor = column?.sortValue ?? ((row) => row[sortKey]);
1151
+ return [...data].sort((a, b) => {
1152
+ const aValue = accessor(a);
1153
+ const bValue = accessor(b);
1154
+ if (aValue === null || aValue === void 0) return 1;
1155
+ if (bValue === null || bValue === void 0) return -1;
1156
+ let comparison = 0;
1157
+ if (typeof aValue === "string" && typeof bValue === "string") {
1158
+ comparison = aValue.localeCompare(bValue, void 0, { numeric: true, sensitivity: "base" });
1159
+ } else if (typeof aValue === "number" && typeof bValue === "number") {
1160
+ comparison = aValue - bValue;
1161
+ } else if (aValue instanceof Date && bValue instanceof Date) {
1162
+ comparison = aValue.getTime() - bValue.getTime();
1163
+ } else {
1164
+ comparison = String(aValue).localeCompare(String(bValue), void 0, { numeric: true, sensitivity: "base" });
1165
+ }
1166
+ return effectiveSort.direction === "asc" ? comparison : -comparison;
1167
+ });
1168
+ };
1169
+ const [currentPage, setCurrentPage] = useState(1);
1170
+ const sortedDataMemo = getSortedData();
1171
+ const totalPages = paginationEnabled ? Math.max(1, Math.ceil(sortedDataMemo.length / Math.max(1, itemsPerPage))) : 1;
1172
+ useEffect(() => {
1173
+ if (currentPage > totalPages) setCurrentPage(totalPages);
1174
+ if (currentPage < 1) setCurrentPage(1);
1175
+ }, [totalPages, currentPage]);
1176
+ useEffect(() => {
1177
+ setCurrentPage(1);
1178
+ }, [effectiveSort.key, effectiveSort.direction, sortedDataMemo.length]);
1179
+ const getDisplayedData = () => {
1180
+ if (!paginationEnabled) return sortedDataMemo;
1181
+ const start = (currentPage - 1) * itemsPerPage;
1182
+ return sortedDataMemo.slice(start, start + itemsPerPage);
1183
+ };
1184
+ const handleToggleColumn = (columnKey) => {
1185
+ setDraftVisibleColumns((prev) => ({ ...prev, [columnKey]: !prev[columnKey] }));
1186
+ };
1187
+ const handleItemsPerPageChange = (value) => {
1188
+ setDraftItemsPerPage(value);
1189
+ };
1190
+ const handleResetTableSettings = () => {
1191
+ setDraftColumnWidths(generateDefaultColumnWidths());
1192
+ setDraftItemsPerPage(defaultItemsPerPage);
1193
+ setDraftVisibleColumns(generateDefaultVisibleColumns());
1194
+ setDraftColumnOrder(generateDefaultColumnOrder());
1195
+ setDraftWrapLines(effectiveDefaultWrapLines);
1196
+ setDraftSortConfig({ key: null, direction: null });
1197
+ pendingFitOnApplyRef.current = true;
1198
+ setResetConfirmed(true);
1199
+ if (resetConfirmTimer.current) clearTimeout(resetConfirmTimer.current);
1200
+ resetConfirmTimer.current = setTimeout(() => setResetConfirmed(false), 1500);
1201
+ };
1202
+ const openTableSettings = () => {
1203
+ setDraftColumnWidths({ ...columnWidths });
1204
+ setDraftItemsPerPage(itemsPerPage);
1205
+ setDraftVisibleColumns({ ...visibleColumns });
1206
+ setDraftColumnOrder([...columnOrder]);
1207
+ setDraftWrapLines(wrapLines);
1208
+ setDraftSortConfig({ key: effectiveSort.key, direction: effectiveSort.direction });
1209
+ setResetConfirmed(false);
1210
+ pendingFitOnApplyRef.current = false;
1211
+ if (resetConfirmTimer.current) {
1212
+ clearTimeout(resetConfirmTimer.current);
1213
+ resetConfirmTimer.current = null;
1214
+ }
1215
+ setTableSettingsOpen(true);
1216
+ };
1217
+ const applyTableSettings = () => {
1218
+ setColumnWidths(draftColumnWidths);
1219
+ setItemsPerPage(draftItemsPerPage);
1220
+ setVisibleColumns(draftVisibleColumns);
1221
+ setColumnOrder(draftColumnOrder);
1222
+ setWrapLines(draftWrapLines);
1223
+ if (controlledSort) {
1224
+ onSortChange?.(
1225
+ draftSortConfig.key && draftSortConfig.direction ? { key: draftSortConfig.key, direction: draftSortConfig.direction } : null
1226
+ );
1227
+ } else {
1228
+ setSortConfig(draftSortConfig);
1229
+ }
1230
+ if (pendingFitOnApplyRef.current) {
1231
+ pendingFitOnApplyRef.current = false;
1232
+ if (storageKey) {
1233
+ localStorage.removeItem(`${storageKey}_columnWidths`);
1234
+ }
1235
+ setNeedsFit(true);
1236
+ }
1237
+ setTableSettingsOpen(false);
1238
+ };
1239
+ const cancelTableSettings = () => {
1240
+ pendingFitOnApplyRef.current = false;
1241
+ setTableSettingsOpen(false);
1242
+ };
1243
+ const handleDragStart = (e, columnKey) => {
1244
+ setDraggedColumn(columnKey);
1245
+ e.dataTransfer.effectAllowed = "move";
1246
+ };
1247
+ const handleDragOver = (e, columnKey) => {
1248
+ e.preventDefault();
1249
+ e.dataTransfer.dropEffect = "move";
1250
+ if (columnKey && draggedColumn && draggedColumn !== columnKey) {
1251
+ if (dragTimeoutRef.current) {
1252
+ clearTimeout(dragTimeoutRef.current);
1253
+ }
1254
+ dragTimeoutRef.current = setTimeout(() => {
1255
+ setDraftColumnOrder((prev) => {
1256
+ const newOrder = [...prev];
1257
+ const draggedIndex = newOrder.indexOf(draggedColumn);
1258
+ const targetIndex = newOrder.indexOf(columnKey);
1259
+ if (draggedIndex === -1 || targetIndex === -1 || draggedIndex === targetIndex) {
1260
+ return prev;
1261
+ }
1262
+ newOrder.splice(draggedIndex, 1);
1263
+ newOrder.splice(targetIndex, 0, draggedColumn);
1264
+ return newOrder;
1265
+ });
1266
+ }, 5);
1267
+ }
1268
+ };
1269
+ const handleDrop = (e, _targetColumnKey) => {
1270
+ e.preventDefault();
1271
+ setDraggedColumn(null);
1272
+ };
1273
+ const handleDragEnd = () => {
1274
+ if (dragTimeoutRef.current) {
1275
+ clearTimeout(dragTimeoutRef.current);
1276
+ }
1277
+ setDraggedColumn(null);
1278
+ };
1279
+ useEffect(() => {
1280
+ const handleEscKey = (event) => {
1281
+ if (event.key === "Escape" && tableSettingsOpen) {
1282
+ setTableSettingsOpen(false);
1283
+ }
1284
+ };
1285
+ document.addEventListener("keydown", handleEscKey);
1286
+ return () => {
1287
+ document.removeEventListener("keydown", handleEscKey);
1288
+ };
1289
+ }, [tableSettingsOpen]);
1290
+ const renderCellContent = (row, columnKey) => {
1291
+ const column = columns[columnKey];
1292
+ const value = row[columnKey];
1293
+ if (column?.render) {
1294
+ return column.render(row, value);
1295
+ }
1296
+ return value !== null && value !== void 0 ? value : "\u2013";
1297
+ };
1298
+ const renderBodyCell = (row, columnKey) => {
1299
+ const column = columns[columnKey];
1300
+ if (!column) return null;
1301
+ const style = {};
1302
+ if (isCompact) {
1303
+ if (columnWidths[columnKey] != null) {
1304
+ style.width = `${columnWidths[columnKey]}px`;
1305
+ }
1306
+ if (column.mono) {
1307
+ style.fontFamily = "var(--font-family-mono)";
1308
+ style.fontSize = 11;
1309
+ }
1310
+ } else {
1311
+ style.width = `${columnWidths[columnKey]}px`;
1312
+ }
1313
+ const rendered = renderCellContent(row, columnKey);
1314
+ let content = rendered;
1315
+ if (isCompact && column.copyable) {
1316
+ const raw = column.copyValue ? column.copyValue(row) : row[columnKey];
1317
+ const copyText = raw !== void 0 && raw !== null ? String(raw) : "";
1318
+ content = /* @__PURE__ */ jsx(CopyableCell, { value: copyText, ariaLabel: `Copy ${typeof column.label === "string" ? column.label : columnKey}`, children: rendered });
1319
+ }
1320
+ const tdClassName = isCompact && column.sticky === "right" ? "crewlet-data-table__td--sticky-right" : void 0;
1321
+ return /* @__PURE__ */ jsx("td", { style, className: tdClassName, children: content }, columnKey);
1322
+ };
1323
+ const renderHeaderCell = (columnKey) => {
1324
+ if (!visibleColumns[columnKey]) return null;
1325
+ const column = columns[columnKey];
1326
+ if (!column) return null;
1327
+ const isSortable = column.sortable !== false;
1328
+ const isActive = effectiveSort.key === columnKey;
1329
+ const arrow = !isSortable ? null : isActive ? effectiveSort.direction === "asc" ? "arrow_upward" : "arrow_downward" : "unfold_more";
1330
+ if (isCompact) {
1331
+ const style = {};
1332
+ if (columnWidths[columnKey] != null) {
1333
+ style.width = `${columnWidths[columnKey]}px`;
1334
+ }
1335
+ const isSticky = column.sticky === "right";
1336
+ const visibleStickyExists = columnOrder.some(
1337
+ (k) => columns[k]?.sticky === "right" && visibleColumns[k] !== false
1338
+ );
1339
+ const suppressResizer = !resizable || isSticky || visibleStickyExists;
1340
+ const resizer = suppressResizer ? null : /* @__PURE__ */ jsx(
1341
+ "span",
1342
+ {
1343
+ className: "crewlet-data-table__resizer",
1344
+ onMouseDown: (e) => {
1345
+ e.stopPropagation();
1346
+ handleMouseDownResize(e, columnKey);
1347
+ },
1348
+ onClick: (e) => e.stopPropagation(),
1349
+ "aria-hidden": true
1350
+ }
1351
+ );
1352
+ const stickyClass = isSticky ? " crewlet-data-table__th--sticky-right" : "";
1353
+ if (!isSortable) {
1354
+ return /* @__PURE__ */ jsxs("th", { style, className: stickyClass.trim() || void 0, children: [
1355
+ column.label,
1356
+ resizer
1357
+ ] }, columnKey);
1358
+ }
1359
+ const thClass = `crewlet-data-table__th--sortable${isActive ? " is-active" : ""}${stickyClass}`;
1360
+ return /* @__PURE__ */ jsxs("th", { style, className: thClass, children: [
1361
+ /* @__PURE__ */ jsxs(
1362
+ "button",
1363
+ {
1364
+ type: "button",
1365
+ className: "crewlet-data-table__sort-button",
1366
+ onClick: () => handleSort(columnKey),
1367
+ children: [
1368
+ /* @__PURE__ */ jsx("span", { children: column.label }),
1369
+ arrow && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: arrow })
1370
+ ]
1371
+ }
1372
+ ),
1373
+ resizer
1374
+ ] }, columnKey);
1375
+ }
1376
+ return /* @__PURE__ */ jsxs(
1377
+ "th",
1378
+ {
1379
+ style: { width: `${columnWidths[columnKey]}px` },
1380
+ className: isSortable ? "crewlet-data-table__th--sortable" : void 0,
1381
+ onClick: isSortable ? () => handleSort(columnKey) : void 0,
1382
+ children: [
1383
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__header-content", children: [
1384
+ /* @__PURE__ */ jsx("span", { children: column.label }),
1385
+ isSortable && /* @__PURE__ */ jsx("span", { className: `material-symbols-outlined crewlet-data-table__sort-icon${isActive ? " is-active" : ""}`, children: arrow })
1386
+ ] }),
1387
+ /* @__PURE__ */ jsx(
1388
+ "div",
1389
+ {
1390
+ className: "crewlet-data-table__column-resizer",
1391
+ onMouseDown: (e) => handleMouseDownResize(e, columnKey),
1392
+ onClick: (e) => e.stopPropagation()
1393
+ }
1394
+ )
1395
+ ]
1396
+ },
1397
+ columnKey
1398
+ );
1399
+ };
1400
+ const hasActionColumn = !!rowActions || showActions && !!onRowAction;
1401
+ const renderActionsHeader = () => {
1402
+ if (!hasActionColumn) return null;
1403
+ if (!settingsInHeader) {
1404
+ return /* @__PURE__ */ jsx(
1405
+ "th",
1406
+ {
1407
+ className: "crewlet-data-table__th--chrome crewlet-data-table__th--actions",
1408
+ style: { width: 1 }
1409
+ },
1410
+ "__actions"
1411
+ );
1412
+ }
1413
+ return /* @__PURE__ */ jsxs("th", { style: { width: `${columnWidths.__actions}px`, position: "relative" }, children: [
1414
+ "Actions",
1415
+ /* @__PURE__ */ jsx(
1416
+ "button",
1417
+ {
1418
+ type: "button",
1419
+ className: "crewlet-data-table__header-btn crewlet-data-table__header-btn--reset",
1420
+ onClick: (e) => {
1421
+ e.stopPropagation();
1422
+ resetToDefault();
1423
+ },
1424
+ title: "Reset to Default",
1425
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "settings_backup_restore" })
1426
+ }
1427
+ ),
1428
+ /* @__PURE__ */ jsx(
1429
+ "button",
1430
+ {
1431
+ type: "button",
1432
+ ref: settingsButtonRef,
1433
+ className: "crewlet-data-table__header-btn crewlet-data-table__header-btn--settings",
1434
+ onClick: (e) => {
1435
+ e.stopPropagation();
1436
+ openTableSettings();
1437
+ },
1438
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "settings" })
1439
+ }
1440
+ )
1441
+ ] }, "__actions");
1442
+ };
1443
+ const renderActionsCell = (row) => {
1444
+ if (!hasActionColumn) return null;
1445
+ if (rowActions) {
1446
+ const actions = rowActions(row);
1447
+ return /* @__PURE__ */ jsx(
1448
+ "td",
1449
+ {
1450
+ className: "crewlet-data-table__td--actions",
1451
+ style: { textAlign: "right", width: 1 },
1452
+ children: /* @__PURE__ */ jsx(RowActionsMenu, { actions })
1453
+ },
1454
+ "__actions"
1455
+ );
1456
+ }
1457
+ return /* @__PURE__ */ jsx(
1458
+ "td",
1459
+ {
1460
+ className: "crewlet-data-table__td--actions",
1461
+ style: { width: `${columnWidths.__actions}px`, textAlign: "center" },
1462
+ children: /* @__PURE__ */ jsx(
1463
+ "button",
1464
+ {
1465
+ type: "button",
1466
+ className: "crewlet-data-table__action-btn",
1467
+ onClick: () => onRowAction(row),
1468
+ title: actionLabel,
1469
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: actionIcon })
1470
+ }
1471
+ )
1472
+ },
1473
+ "__actions"
1474
+ );
1475
+ };
1476
+ const displayedData = getDisplayedData();
1477
+ const visibleColumnKeys = columnOrder.filter((key) => visibleColumns[key]);
1478
+ const spacerCols = isCompact ? 1 : 0;
1479
+ const colSpan = visibleColumnKeys.length + (hasActionColumn ? 1 : 0) + spacerCols;
1480
+ const wrapLinesClass = wrapLines ? " crewlet-data-table--wrap-lines" : "";
1481
+ const rootClass = isCompact ? `crewlet-data-table crewlet-data-table--compact crewlet-data-table--density-${density}${wrapLinesClass} ${className}`.trim() : `crewlet-data-table crewlet-data-table--default${wrapLinesClass} ${className}`.trim();
1482
+ const expandable = !!renderExpandedRow;
1483
+ const expandColSpan = colSpan + (expandable ? 1 : 0);
1484
+ const visibleRowKeys = displayedData.map((row) => resolveRowKey(row));
1485
+ const allExpanded = expandable && visibleRowKeys.length > 0 && visibleRowKeys.every((k) => expandedKeys.has(k));
1486
+ const toggleExpandAll = () => {
1487
+ setExpandedKeys(() => allExpanded ? /* @__PURE__ */ new Set() : new Set(visibleRowKeys));
1488
+ };
1489
+ const renderExpandHeader = () => {
1490
+ if (!expandable) return null;
1491
+ return /* @__PURE__ */ jsx(
1492
+ "th",
1493
+ {
1494
+ className: "crewlet-data-table__expand-header",
1495
+ style: { width: 32 },
1496
+ children: /* @__PURE__ */ jsx(
1497
+ "button",
1498
+ {
1499
+ type: "button",
1500
+ className: "crewlet-data-table__expand-all-btn",
1501
+ onClick: toggleExpandAll,
1502
+ "aria-label": allExpanded ? "Collapse all rows" : "Expand all rows",
1503
+ "aria-expanded": allExpanded,
1504
+ title: allExpanded ? "Collapse all" : "Expand all",
1505
+ children: /* @__PURE__ */ jsx(
1506
+ "span",
1507
+ {
1508
+ className: `material-symbols-outlined crewlet-data-table__chevron${allExpanded ? " is-open" : ""}`,
1509
+ "aria-hidden": true,
1510
+ children: "chevron_right"
1511
+ }
1512
+ )
1513
+ }
1514
+ )
1515
+ },
1516
+ "__expand"
1517
+ );
1518
+ };
1519
+ const renderExpandCell = (_row, isOpen, canExpand) => {
1520
+ if (!expandable) return null;
1521
+ return /* @__PURE__ */ jsx("td", { className: "crewlet-data-table__expand-cell", "aria-hidden": true, style: { width: 32 }, children: canExpand && /* @__PURE__ */ jsx(
1522
+ "span",
1523
+ {
1524
+ className: `material-symbols-outlined crewlet-data-table__chevron${isOpen ? " is-open" : ""}`,
1525
+ children: "chevron_right"
1526
+ }
1527
+ ) }, "__expand");
1528
+ };
1529
+ const topControlsVisible = isCompact;
1530
+ const headerVisible = title != null || description != null || icon != null;
1531
+ const renderTopControls = topControlsVisible ? /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__toolbar-end", children: [
1532
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__pagination", "aria-label": "Pagination", children: [
1533
+ /* @__PURE__ */ jsx(
1534
+ "button",
1535
+ {
1536
+ type: "button",
1537
+ className: "crewlet-data-table__page-btn",
1538
+ onClick: () => setCurrentPage((p) => Math.max(1, p - 1)),
1539
+ disabled: currentPage <= 1,
1540
+ "aria-label": "Previous page",
1541
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "chevron_left" })
1542
+ }
1543
+ ),
1544
+ /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__page-number", "aria-live": "polite", children: currentPage }),
1545
+ /* @__PURE__ */ jsx(
1546
+ "button",
1547
+ {
1548
+ type: "button",
1549
+ className: "crewlet-data-table__page-btn",
1550
+ onClick: () => setCurrentPage((p) => Math.min(totalPages, p + 1)),
1551
+ disabled: currentPage >= totalPages,
1552
+ "aria-label": "Next page",
1553
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "chevron_right" })
1554
+ }
1555
+ )
1556
+ ] }),
1557
+ settingsEnabled && /* @__PURE__ */ jsx(
1558
+ "button",
1559
+ {
1560
+ type: "button",
1561
+ ref: settingsButtonRef,
1562
+ className: "crewlet-data-table__settings-btn",
1563
+ onClick: openTableSettings,
1564
+ "aria-label": "Table settings",
1565
+ title: "Table settings",
1566
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "settings" })
1567
+ }
1568
+ )
1569
+ ] }) : null;
1570
+ const toolbarVisible = renderToolbar != null || topControlsVisible && !headerVisible;
1571
+ return /* @__PURE__ */ jsxs("div", { className: rootClass, children: [
1572
+ headerVisible && /* @__PURE__ */ jsxs("header", { className: "crewlet-data-table__header", children: [
1573
+ icon != null && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-data-table__header-icon", "aria-hidden": true, children: icon }),
1574
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__header-text", children: [
1575
+ title != null && /* @__PURE__ */ jsx("h3", { className: "crewlet-data-table__title", children: title }),
1576
+ description != null && /* @__PURE__ */ jsx("p", { className: "crewlet-data-table__description", children: description })
1577
+ ] }),
1578
+ renderTopControls
1579
+ ] }),
1580
+ toolbarVisible && /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__toolbar", children: [
1581
+ renderToolbar != null && /* @__PURE__ */ jsx("div", { className: "crewlet-data-table__toolbar-start", children: renderToolbar }),
1582
+ onClearFilters != null && filtersActive && /* @__PURE__ */ jsx(
1583
+ "button",
1584
+ {
1585
+ type: "button",
1586
+ className: "crewlet-data-table__clear-filters",
1587
+ onClick: onClearFilters,
1588
+ children: clearFiltersLabel
1589
+ }
1590
+ ),
1591
+ topControlsVisible && !headerVisible && renderTopControls
1592
+ ] }),
1593
+ /* @__PURE__ */ jsx(
1594
+ "div",
1595
+ {
1596
+ className: [
1597
+ "crewlet-data-table__rail",
1598
+ /*
1599
+ * When the table carries a sticky-right action column the
1600
+ * row is treated as a frozen, screen-fitting surface: the
1601
+ * fit pass redistributes regular columns to consume only
1602
+ * the room left after the sticky pane and the gutter, the
1603
+ * resize handles are suppressed, and the scroll container
1604
+ * locks horizontal overflow. The modifier class flips that
1605
+ * behaviour on so the CSS can also drop the scrollbar mask
1606
+ * that would otherwise float over rows when no scrollbar
1607
+ * is present.
1608
+ */
1609
+ isCompact && columnOrder.some(
1610
+ (k) => columns[k]?.sticky === "right" && visibleColumns[k] !== false
1611
+ ) ? "crewlet-data-table__rail--frozen" : void 0
1612
+ ].filter(Boolean).join(" "),
1613
+ style: isCompact ? {
1614
+ "--crewlet-data-table-sticky-pane-width": `${columnOrder.filter((key) => columns[key]?.sticky === "right" && visibleColumns[key] !== false).reduce((sum, key) => sum + (columnWidths[key] ?? minColumnWidth), 0)}px`
1615
+ } : void 0,
1616
+ children: /* @__PURE__ */ jsx("div", { className: "crewlet-data-table__scroll", ref: scrollRef, children: /* @__PURE__ */ jsxs("table", { className: "crewlet-data-table__table", children: [
1617
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1618
+ renderExpandHeader(),
1619
+ columnOrder.filter((key) => columns[key]?.sticky !== "right").map((columnKey) => renderHeaderCell(columnKey)),
1620
+ isCompact && /* @__PURE__ */ jsx(
1621
+ "th",
1622
+ {
1623
+ className: "crewlet-data-table__th--chrome crewlet-data-table__th--spacer",
1624
+ "aria-hidden": true
1625
+ },
1626
+ "__spacer"
1627
+ ),
1628
+ columnOrder.filter((key) => columns[key]?.sticky === "right").map((columnKey) => renderHeaderCell(columnKey)),
1629
+ renderActionsHeader()
1630
+ ] }) }),
1631
+ /* @__PURE__ */ jsx("tbody", { children: loading && displayedData.length === 0 ? (
1632
+ /*
1633
+ * Skeleton rows live inside the real <tbody> and reuse the
1634
+ * live column set + widths, so the shimmer lines up exactly
1635
+ * with the data that replaces it. Bar widths cycle a fixed
1636
+ * pattern (not Math.random) so re-renders never reshuffle
1637
+ * the shimmer.
1638
+ */
1639
+ Array.from({ length: Math.max(1, skeletonRows) }, (_, rowIdx) => {
1640
+ const widths = [72, 46, 84, 58, 64];
1641
+ const visibleKeys = columnOrder.filter(
1642
+ (key) => columns[key]?.sticky !== "right" && visibleColumns[key] !== false
1643
+ );
1644
+ return /* @__PURE__ */ jsxs("tr", { className: "crewlet-data-table__skeleton-row", "aria-hidden": true, children: [
1645
+ expandable && /* @__PURE__ */ jsx("td", { className: "crewlet-data-table__expand-cell", style: { width: 32 } }, "__expand"),
1646
+ visibleKeys.map((columnKey, colIdx) => /* @__PURE__ */ jsx(
1647
+ "td",
1648
+ {
1649
+ style: columnWidths[columnKey] != null ? { width: `${columnWidths[columnKey]}px` } : void 0,
1650
+ children: /* @__PURE__ */ jsx(
1651
+ "span",
1652
+ {
1653
+ className: "crewlet-data-table__skeleton-bar",
1654
+ style: {
1655
+ width: `${widths[(rowIdx + colIdx) % widths.length]}%`,
1656
+ animationDelay: `${rowIdx * 0.1}s`
1657
+ }
1658
+ }
1659
+ )
1660
+ },
1661
+ columnKey
1662
+ )),
1663
+ isCompact && /* @__PURE__ */ jsx("td", { className: "crewlet-data-table__td--spacer", "aria-hidden": true }, "__spacer"),
1664
+ columnOrder.filter((key) => columns[key]?.sticky === "right" && visibleColumns[key] !== false).map((columnKey) => /* @__PURE__ */ jsx("td", { className: "crewlet-data-table__td--sticky-right" }, columnKey)),
1665
+ hasActionColumn && /* @__PURE__ */ jsx("td", { className: "crewlet-data-table__td--actions" }, "__actions")
1666
+ ] }, `__skeleton_${rowIdx}`);
1667
+ })
1668
+ ) : error ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1669
+ "td",
1670
+ {
1671
+ colSpan: expandColSpan,
1672
+ className: "crewlet-data-table__empty",
1673
+ children: error
1674
+ }
1675
+ ) }) : displayedData.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1676
+ "td",
1677
+ {
1678
+ colSpan: expandColSpan,
1679
+ className: "crewlet-data-table__empty",
1680
+ children: emptyMessage
1681
+ }
1682
+ ) }) : displayedData.map((row) => {
1683
+ const trKey = resolveRowKey(row);
1684
+ const isArchived = archivedPredicate ? archivedPredicate(row) : false;
1685
+ const expanded = expandable ? expandedKeys.has(trKey) : false;
1686
+ const expandedContent = expandable ? renderExpandedRow(row) : null;
1687
+ const canExpand = expandable && expandedContent != null;
1688
+ const isClickable = !!onRowClick || canExpand;
1689
+ const trClass = [
1690
+ isArchived ? "crewlet-data-table__row--archived" : void 0,
1691
+ isClickable ? "crewlet-data-table__row--clickable" : void 0,
1692
+ canExpand ? "crewlet-data-table__row--expandable" : void 0,
1693
+ expanded ? "is-expanded" : void 0
1694
+ ].filter(Boolean).join(" ");
1695
+ const accessory = renderAccessoryRow ? renderAccessoryRow(row) : null;
1696
+ const onRowSurfaceClick = canExpand ? () => toggleExpanded(trKey) : onRowClick ? () => onRowClick(row) : void 0;
1697
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1698
+ /* @__PURE__ */ jsxs(
1699
+ "tr",
1700
+ {
1701
+ className: trClass || void 0,
1702
+ title: isArchived ? `This row is ${archivedLabel}` : void 0,
1703
+ onClick: onRowSurfaceClick,
1704
+ tabIndex: canExpand ? 0 : void 0,
1705
+ "aria-expanded": canExpand ? expanded : void 0,
1706
+ onKeyDown: canExpand ? (e) => {
1707
+ if (e.key === "Enter" || e.key === " ") {
1708
+ e.preventDefault();
1709
+ toggleExpanded(trKey);
1710
+ }
1711
+ } : void 0,
1712
+ children: [
1713
+ renderExpandCell(row, expanded, canExpand),
1714
+ columnOrder.filter((columnKey) => columns[columnKey]?.sticky !== "right").map((columnKey) => {
1715
+ if (!visibleColumns[columnKey]) return null;
1716
+ return renderBodyCell(row, columnKey);
1717
+ }),
1718
+ isCompact && /* @__PURE__ */ jsx("td", { className: "crewlet-data-table__td--spacer", "aria-hidden": true }, "__spacer"),
1719
+ columnOrder.filter((columnKey) => columns[columnKey]?.sticky === "right").map((columnKey) => {
1720
+ if (!visibleColumns[columnKey]) return null;
1721
+ return renderBodyCell(row, columnKey);
1722
+ }),
1723
+ renderActionsCell(row)
1724
+ ]
1725
+ }
1726
+ ),
1727
+ expanded && expandedContent != null && /* @__PURE__ */ jsx(
1728
+ "tr",
1729
+ {
1730
+ className: [
1731
+ "crewlet-data-table__expanded-row",
1732
+ isArchived ? "crewlet-data-table__row--archived" : void 0
1733
+ ].filter(Boolean).join(" "),
1734
+ children: /* @__PURE__ */ jsx("td", { colSpan: expandColSpan, className: "crewlet-data-table__expanded-cell", children: expandedContent })
1735
+ }
1736
+ ),
1737
+ accessory != null && /* @__PURE__ */ jsx(
1738
+ "tr",
1739
+ {
1740
+ className: [
1741
+ "crewlet-data-table__accessory-row",
1742
+ isArchived ? "crewlet-data-table__row--archived" : void 0
1743
+ ].filter(Boolean).join(" "),
1744
+ children: /* @__PURE__ */ jsx("td", { colSpan, className: "crewlet-data-table__accessory-cell", children: accessory })
1745
+ }
1746
+ )
1747
+ ] }, trKey ?? JSON.stringify(row));
1748
+ }) })
1749
+ ] }) })
1750
+ }
1751
+ ),
1752
+ hasMore && onLoadMore && /* @__PURE__ */ jsx("div", { className: "crewlet-data-table__load-more", children: /* @__PURE__ */ jsx(
1753
+ Button,
1754
+ {
1755
+ variant: "outline",
1756
+ size: "small",
1757
+ onClick: onLoadMore,
1758
+ disabled: loadingMore,
1759
+ children: loadingMore ? "Loading\u2026" : loadMoreLabel
1760
+ }
1761
+ ) }),
1762
+ /* @__PURE__ */ jsxs(
1763
+ Modal,
1764
+ {
1765
+ open: tableSettingsOpen,
1766
+ onClose: cancelTableSettings,
1767
+ title: "Table Settings",
1768
+ size: effectiveSettingsVariant === "compact" ? "sm" : "lg",
1769
+ footer: /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__settings-footer", children: [
1770
+ /* @__PURE__ */ jsx(
1771
+ Button,
1772
+ {
1773
+ variant: "secondary",
1774
+ onClick: handleResetTableSettings,
1775
+ leadingIcon: resetConfirmed ? /* @__PURE__ */ jsx(
1776
+ "span",
1777
+ {
1778
+ className: "material-symbols-outlined crewlet-data-table__reset-confirm-icon",
1779
+ "aria-hidden": true,
1780
+ children: "check"
1781
+ }
1782
+ ) : void 0,
1783
+ "aria-live": "polite",
1784
+ children: "Reset to Default"
1785
+ }
1786
+ ),
1787
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__settings-footer-right", children: [
1788
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: cancelTableSettings, children: "Cancel" }),
1789
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: applyTableSettings, children: "Apply" })
1790
+ ] })
1791
+ ] }),
1792
+ children: [
1793
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__settings-section", children: [
1794
+ /* @__PURE__ */ jsx("h3", { className: "crewlet-data-table__settings-section-title", children: effectiveSettingsVariant === "compact" ? "Page size" : "Items per page" }),
1795
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__items-per-page-options", children: [
1796
+ itemsPerPageOptions.map((value) => /* @__PURE__ */ jsx(
1797
+ "button",
1798
+ {
1799
+ type: "button",
1800
+ className: `crewlet-data-table__items-per-page-btn${draftItemsPerPage === value ? " is-active" : ""}`,
1801
+ onClick: () => handleItemsPerPageChange(value),
1802
+ children: value
1803
+ },
1804
+ value
1805
+ )),
1806
+ /* @__PURE__ */ jsx(
1807
+ "button",
1808
+ {
1809
+ type: "button",
1810
+ className: `crewlet-data-table__items-per-page-btn${draftItemsPerPage === data.length ? " is-active" : ""}`,
1811
+ onClick: () => handleItemsPerPageChange(data.length),
1812
+ children: "All"
1813
+ }
1814
+ )
1815
+ ] })
1816
+ ] }),
1817
+ /* @__PURE__ */ jsx("div", { className: "crewlet-data-table__settings-section", children: /* @__PURE__ */ jsxs("label", { className: "crewlet-data-table__toggle-row", children: [
1818
+ /* @__PURE__ */ jsx(
1819
+ "input",
1820
+ {
1821
+ type: "checkbox",
1822
+ className: "crewlet-data-table__toggle-checkbox",
1823
+ checked: draftWrapLines,
1824
+ onChange: (e) => setDraftWrapLines(e.target.checked)
1825
+ }
1826
+ ),
1827
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-data-table__toggle-text", children: [
1828
+ /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__toggle-label", children: "Wrap lines" }),
1829
+ /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__toggle-hint", children: "Enable to wrap table cell content, disable to truncate text." })
1830
+ ] })
1831
+ ] }) }),
1832
+ effectiveSettingsVariant === "rich" && /* @__PURE__ */ jsxs("div", { className: "crewlet-data-table__settings-section", children: [
1833
+ /* @__PURE__ */ jsx("h3", { className: "crewlet-data-table__settings-section-title", children: "Column Order & Visibility" }),
1834
+ /* @__PURE__ */ jsx("p", { className: "crewlet-data-table__settings-section-hint", children: "Drag to reorder, check/uncheck to show/hide" }),
1835
+ /* @__PURE__ */ jsx("div", { className: "crewlet-data-table__column-toggles", children: draftColumnOrder.map((key) => /* @__PURE__ */ jsxs(
1836
+ "div",
1837
+ {
1838
+ className: `crewlet-data-table__column-toggle-item${draggedColumn === key ? " is-dragging" : ""}`,
1839
+ draggable: true,
1840
+ onDragStart: (e) => handleDragStart(e, key),
1841
+ onDragOver: (e) => handleDragOver(e, key),
1842
+ onDrop: (e) => handleDrop(e),
1843
+ onDragEnd: handleDragEnd,
1844
+ children: [
1845
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-data-table__column-drag-handle", children: "drag_indicator" }),
1846
+ /* @__PURE__ */ jsx(
1847
+ "input",
1848
+ {
1849
+ type: "checkbox",
1850
+ checked: !!draftVisibleColumns[key],
1851
+ onChange: () => handleToggleColumn(key),
1852
+ className: "crewlet-data-table__column-toggle-checkbox"
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ jsx("span", { className: "crewlet-data-table__column-toggle-label", children: columns[key]?.label && String(columns[key]?.label).trim() !== "" ? columns[key]?.label : key })
1856
+ ]
1857
+ },
1858
+ key
1859
+ )) })
1860
+ ] })
1861
+ ]
1862
+ }
1863
+ )
1864
+ ] });
1865
+ };
1866
+ var pad = (n) => String(n).padStart(2, "0");
1867
+ var formatValue = (p) => {
1868
+ return `${p.year}-${pad(p.month + 1)}-${pad(p.day)}T${pad(p.hour)}:${pad(p.minute)}:${pad(p.second)}`;
1869
+ };
1870
+ var parseValue = (s) => {
1871
+ if (!s) return null;
1872
+ const tIndex = s.indexOf("T");
1873
+ const datePart = tIndex >= 0 ? s.slice(0, tIndex) : s;
1874
+ const timePart = tIndex >= 0 ? s.slice(tIndex + 1) : "00:00:00";
1875
+ const dateBits = datePart.split("-");
1876
+ if (dateBits.length < 3) return null;
1877
+ const y = parseInt(dateBits[0], 10);
1878
+ const m = parseInt(dateBits[1], 10);
1879
+ const d = parseInt(dateBits[2], 10);
1880
+ if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return null;
1881
+ const timeBits = timePart.split(":");
1882
+ const h = parseInt(timeBits[0] || "0", 10);
1883
+ const mn = parseInt(timeBits[1] || "0", 10);
1884
+ const sec = parseInt(timeBits[2] || "0", 10);
1885
+ return {
1886
+ year: y,
1887
+ month: m - 1,
1888
+ day: d,
1889
+ hour: Number.isFinite(h) ? h : 0,
1890
+ minute: Number.isFinite(mn) ? mn : 0,
1891
+ second: Number.isFinite(sec) ? sec : 0
1892
+ };
1893
+ };
1894
+ var daysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
1895
+ var startWeekday = (year, month, firstDay) => {
1896
+ const d = new Date(year, month, 1).getDay();
1897
+ return (d - firstDay + 7) % 7;
1898
+ };
1899
+ var MONTH_NAMES = [
1900
+ "January",
1901
+ "February",
1902
+ "March",
1903
+ "April",
1904
+ "May",
1905
+ "June",
1906
+ "July",
1907
+ "August",
1908
+ "September",
1909
+ "October",
1910
+ "November",
1911
+ "December"
1912
+ ];
1913
+ var WEEKDAY_LABELS_SUN = ["S", "M", "T", "W", "T", "F", "S"];
1914
+ var WEEKDAY_LABELS_MON = ["M", "T", "W", "T", "F", "S", "S"];
1915
+ var formatLabel = (s, showTime) => {
1916
+ const parsed = parseValue(s);
1917
+ if (!parsed) return "";
1918
+ const date = new Date(parsed.year, parsed.month, parsed.day, parsed.hour, parsed.minute, parsed.second);
1919
+ if (showTime) {
1920
+ return date.toLocaleString(void 0, {
1921
+ year: "numeric",
1922
+ month: "short",
1923
+ day: "numeric",
1924
+ hour: "2-digit",
1925
+ minute: "2-digit",
1926
+ second: "2-digit",
1927
+ hour12: false
1928
+ });
1929
+ }
1930
+ return date.toLocaleDateString(void 0, {
1931
+ year: "numeric",
1932
+ month: "short",
1933
+ day: "numeric"
1934
+ });
1935
+ };
1936
+ var compareDates = (a, b) => {
1937
+ if (a.year !== b.year) return a.year - b.year;
1938
+ if (a.month !== b.month) return a.month - b.month;
1939
+ return a.day - b.day;
1940
+ };
1941
+ var parseBoundDate = (s) => {
1942
+ const p = parseValue(s);
1943
+ if (!p) return null;
1944
+ return { year: p.year, month: p.month, day: p.day };
1945
+ };
1946
+ function DateTimePicker({
1947
+ value,
1948
+ onChange,
1949
+ placeholder = "Pick a date",
1950
+ showTime = true,
1951
+ minDate,
1952
+ maxDate,
1953
+ firstDayOfWeek = 0,
1954
+ ariaLabel,
1955
+ className = "",
1956
+ disabled = false,
1957
+ size = "md"
1958
+ }) {
1959
+ const parsed = useMemo(() => parseValue(value), [value]);
1960
+ const minBound = useMemo(() => parseBoundDate(minDate), [minDate]);
1961
+ const maxBound = useMemo(() => parseBoundDate(maxDate), [maxDate]);
1962
+ const today = useMemo(() => {
1963
+ const now = /* @__PURE__ */ new Date();
1964
+ return { year: now.getFullYear(), month: now.getMonth(), day: now.getDate() };
1965
+ }, []);
1966
+ const [viewMonth, setViewMonth] = useState(() => {
1967
+ if (parsed) return { year: parsed.year, month: parsed.month };
1968
+ return { year: today.year, month: today.month };
1969
+ });
1970
+ useEffect(() => {
1971
+ if (parsed) setViewMonth({ year: parsed.year, month: parsed.month });
1972
+ }, [parsed?.year, parsed?.month]);
1973
+ const weekdayLabels = firstDayOfWeek === 1 ? WEEKDAY_LABELS_MON : WEEKDAY_LABELS_SUN;
1974
+ const totalDays = daysInMonth(viewMonth.year, viewMonth.month);
1975
+ const leading = startWeekday(viewMonth.year, viewMonth.month, firstDayOfWeek);
1976
+ const cells = useMemo(() => {
1977
+ const out = [];
1978
+ const prevTotal = daysInMonth(
1979
+ viewMonth.month === 0 ? viewMonth.year - 1 : viewMonth.year,
1980
+ viewMonth.month === 0 ? 11 : viewMonth.month - 1
1981
+ );
1982
+ for (let i = 0; i < leading; i += 1) {
1983
+ out.push({ day: prevTotal - leading + 1 + i, inMonth: false });
1984
+ }
1985
+ for (let d = 1; d <= totalDays; d += 1) {
1986
+ out.push({ day: d, inMonth: true });
1987
+ }
1988
+ while (out.length < 42) {
1989
+ out.push({ day: out.length - leading - totalDays + 1, inMonth: false });
1990
+ }
1991
+ return out;
1992
+ }, [viewMonth.year, viewMonth.month, leading, totalDays]);
1993
+ const stepMonth = (delta) => {
1994
+ setViewMonth((prev) => {
1995
+ const next = prev.month + delta;
1996
+ const carry = Math.floor(next / 12);
1997
+ const normalised = (next % 12 + 12) % 12;
1998
+ return { year: prev.year + carry, month: normalised };
1999
+ });
2000
+ };
2001
+ const stepYear = (delta) => {
2002
+ setViewMonth((prev) => ({ year: prev.year + delta, month: prev.month }));
2003
+ };
2004
+ const pickDay = (day, inMonth) => {
2005
+ let targetYear = viewMonth.year;
2006
+ let targetMonth = viewMonth.month;
2007
+ if (!inMonth) {
2008
+ if (day > 20) {
2009
+ targetMonth -= 1;
2010
+ if (targetMonth < 0) {
2011
+ targetMonth = 11;
2012
+ targetYear -= 1;
2013
+ }
2014
+ } else {
2015
+ targetMonth += 1;
2016
+ if (targetMonth > 11) {
2017
+ targetMonth = 0;
2018
+ targetYear += 1;
2019
+ }
2020
+ }
2021
+ setViewMonth({ year: targetYear, month: targetMonth });
2022
+ }
2023
+ onChange(formatValue({
2024
+ year: targetYear,
2025
+ month: targetMonth,
2026
+ day,
2027
+ hour: parsed?.hour ?? 0,
2028
+ minute: parsed?.minute ?? 0,
2029
+ second: parsed?.second ?? 0
2030
+ }));
2031
+ };
2032
+ const setTime = (hour, minute, second) => {
2033
+ const base = parsed ?? {
2034
+ year: today.year,
2035
+ month: today.month,
2036
+ day: today.day,
2037
+ hour: 0,
2038
+ minute: 0,
2039
+ second: 0
2040
+ };
2041
+ onChange(formatValue({ ...base, hour, minute, second }));
2042
+ };
2043
+ const isOutOfBounds = (cellDate) => {
2044
+ if (minBound && compareDates(cellDate, minBound) < 0) return true;
2045
+ if (maxBound && compareDates(cellDate, maxBound) > 0) return true;
2046
+ return false;
2047
+ };
2048
+ const isToday = (cellYear, cellMonth, cellDay) => cellYear === today.year && cellMonth === today.month && cellDay === today.day;
2049
+ const isSelected = (cellYear, cellMonth, cellDay) => {
2050
+ if (!parsed) return false;
2051
+ return cellYear === parsed.year && cellMonth === parsed.month && cellDay === parsed.day;
2052
+ };
2053
+ const triggerLabel = formatLabel(value, showTime);
2054
+ const wrapClass = [
2055
+ "crewlet-datetime",
2056
+ `crewlet-datetime--${size}`,
2057
+ value ? "is-set" : "",
2058
+ disabled ? "is-disabled" : "",
2059
+ className
2060
+ ].filter(Boolean).join(" ");
2061
+ return /* @__PURE__ */ jsx(
2062
+ Popover,
2063
+ {
2064
+ align: "start",
2065
+ side: "bottom",
2066
+ width: "match",
2067
+ className: "crewlet-datetime__popover",
2068
+ trigger: (open, toggle) => /* @__PURE__ */ jsxs(
2069
+ "button",
2070
+ {
2071
+ type: "button",
2072
+ className: `${wrapClass}${open ? " is-open" : ""}`,
2073
+ onClick: () => {
2074
+ if (!disabled) toggle();
2075
+ },
2076
+ disabled,
2077
+ "aria-haspopup": "dialog",
2078
+ "aria-expanded": open,
2079
+ "aria-label": ariaLabel || "Date",
2080
+ children: [
2081
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-datetime__icon", "aria-hidden": true, children: "calendar_today" }),
2082
+ /* @__PURE__ */ jsx("span", { className: `crewlet-datetime__label${value ? "" : " is-placeholder"}`, children: value ? triggerLabel : placeholder }),
2083
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-datetime__chevron", "aria-hidden": true, children: open ? "expand_less" : "expand_more" })
2084
+ ]
2085
+ }
2086
+ ),
2087
+ children: (close) => /* @__PURE__ */ jsxs("div", { className: "crewlet-datetime__panel", role: "dialog", "aria-label": "Date picker", children: [
2088
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-datetime__nav", children: [
2089
+ /* @__PURE__ */ jsx(
2090
+ "button",
2091
+ {
2092
+ type: "button",
2093
+ className: "crewlet-datetime__nav-btn",
2094
+ onClick: () => stepYear(-1),
2095
+ "aria-label": "Previous year",
2096
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "keyboard_double_arrow_left" })
2097
+ }
2098
+ ),
2099
+ /* @__PURE__ */ jsx(
2100
+ "button",
2101
+ {
2102
+ type: "button",
2103
+ className: "crewlet-datetime__nav-btn",
2104
+ onClick: () => stepMonth(-1),
2105
+ "aria-label": "Previous month",
2106
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "chevron_left" })
2107
+ }
2108
+ ),
2109
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-datetime__title", "aria-live": "polite", children: [
2110
+ MONTH_NAMES[viewMonth.month],
2111
+ " ",
2112
+ viewMonth.year
2113
+ ] }),
2114
+ /* @__PURE__ */ jsx(
2115
+ "button",
2116
+ {
2117
+ type: "button",
2118
+ className: "crewlet-datetime__nav-btn",
2119
+ onClick: () => stepMonth(1),
2120
+ "aria-label": "Next month",
2121
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "chevron_right" })
2122
+ }
2123
+ ),
2124
+ /* @__PURE__ */ jsx(
2125
+ "button",
2126
+ {
2127
+ type: "button",
2128
+ className: "crewlet-datetime__nav-btn",
2129
+ onClick: () => stepYear(1),
2130
+ "aria-label": "Next year",
2131
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "keyboard_double_arrow_right" })
2132
+ }
2133
+ )
2134
+ ] }),
2135
+ /* @__PURE__ */ jsx("div", { className: "crewlet-datetime__weekdays", "aria-hidden": true, children: weekdayLabels.map((label, i) => /* @__PURE__ */ jsx("span", { className: "crewlet-datetime__weekday", children: label }, i)) }),
2136
+ /* @__PURE__ */ jsx("div", { className: "crewlet-datetime__grid", role: "grid", children: cells.map((cell, i) => {
2137
+ let cellYear = viewMonth.year;
2138
+ let cellMonth = viewMonth.month;
2139
+ if (!cell.inMonth) {
2140
+ if (cell.day > 20) {
2141
+ cellMonth -= 1;
2142
+ if (cellMonth < 0) {
2143
+ cellMonth = 11;
2144
+ cellYear -= 1;
2145
+ }
2146
+ } else {
2147
+ cellMonth += 1;
2148
+ if (cellMonth > 11) {
2149
+ cellMonth = 0;
2150
+ cellYear += 1;
2151
+ }
2152
+ }
2153
+ }
2154
+ const outOfBounds = isOutOfBounds({ year: cellYear, month: cellMonth, day: cell.day });
2155
+ const todayCell = isToday(cellYear, cellMonth, cell.day);
2156
+ const selected = isSelected(cellYear, cellMonth, cell.day);
2157
+ const cls = [
2158
+ "crewlet-datetime__cell",
2159
+ cell.inMonth ? "" : "is-outside",
2160
+ outOfBounds ? "is-disabled" : "",
2161
+ todayCell ? "is-today" : "",
2162
+ selected ? "is-selected" : ""
2163
+ ].filter(Boolean).join(" ");
2164
+ return /* @__PURE__ */ jsx(
2165
+ "button",
2166
+ {
2167
+ type: "button",
2168
+ role: "gridcell",
2169
+ className: cls,
2170
+ disabled: outOfBounds,
2171
+ onClick: () => {
2172
+ if (outOfBounds) return;
2173
+ pickDay(cell.day, cell.inMonth);
2174
+ if (!showTime) close();
2175
+ },
2176
+ children: cell.day
2177
+ },
2178
+ i
2179
+ );
2180
+ }) }),
2181
+ showTime && /* @__PURE__ */ jsxs("div", { className: "crewlet-datetime__time", children: [
2182
+ /* @__PURE__ */ jsx("span", { className: "crewlet-datetime__time-label", children: "Time" }),
2183
+ /* @__PURE__ */ jsx(
2184
+ TimeUnitInput,
2185
+ {
2186
+ value: parsed?.hour ?? 0,
2187
+ max: 23,
2188
+ ariaLabel: "Hour",
2189
+ onCommit: (n) => setTime(n, parsed?.minute ?? 0, parsed?.second ?? 0)
2190
+ }
2191
+ ),
2192
+ /* @__PURE__ */ jsx("span", { className: "crewlet-datetime__time-sep", children: ":" }),
2193
+ /* @__PURE__ */ jsx(
2194
+ TimeUnitInput,
2195
+ {
2196
+ value: parsed?.minute ?? 0,
2197
+ max: 59,
2198
+ ariaLabel: "Minute",
2199
+ onCommit: (n) => setTime(parsed?.hour ?? 0, n, parsed?.second ?? 0)
2200
+ }
2201
+ ),
2202
+ /* @__PURE__ */ jsx("span", { className: "crewlet-datetime__time-sep", children: ":" }),
2203
+ /* @__PURE__ */ jsx(
2204
+ TimeUnitInput,
2205
+ {
2206
+ value: parsed?.second ?? 0,
2207
+ max: 59,
2208
+ ariaLabel: "Second",
2209
+ onCommit: (n) => setTime(parsed?.hour ?? 0, parsed?.minute ?? 0, n)
2210
+ }
2211
+ )
2212
+ ] }),
2213
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-datetime__footer", children: [
2214
+ /* @__PURE__ */ jsx(
2215
+ "button",
2216
+ {
2217
+ type: "button",
2218
+ className: "crewlet-datetime__footer-btn",
2219
+ onClick: () => {
2220
+ onChange("");
2221
+ },
2222
+ children: "Clear"
2223
+ }
2224
+ ),
2225
+ /* @__PURE__ */ jsx("span", { className: "crewlet-datetime__footer-spacer" }),
2226
+ /* @__PURE__ */ jsx(
2227
+ "button",
2228
+ {
2229
+ type: "button",
2230
+ className: "crewlet-datetime__footer-btn is-primary",
2231
+ onClick: () => close(),
2232
+ children: "Done"
2233
+ }
2234
+ )
2235
+ ] })
2236
+ ] })
2237
+ }
2238
+ );
2239
+ }
2240
+ function TimeUnitInput({ value, max, ariaLabel, onCommit }) {
2241
+ const [display, setDisplay] = useState(() => pad(value));
2242
+ useEffect(() => {
2243
+ setDisplay(pad(value));
2244
+ }, [value]);
2245
+ const commit = () => {
2246
+ const n = Math.max(0, Math.min(max, parseInt(display, 10) || 0));
2247
+ setDisplay(pad(n));
2248
+ if (n !== value) onCommit(n);
2249
+ };
2250
+ return /* @__PURE__ */ jsx(
2251
+ "input",
2252
+ {
2253
+ type: "text",
2254
+ inputMode: "numeric",
2255
+ pattern: "[0-9]*",
2256
+ maxLength: 2,
2257
+ className: "crewlet-datetime__time-input",
2258
+ value: display,
2259
+ onChange: (e) => setDisplay(e.target.value.replace(/[^0-9]/g, "").slice(0, 2)),
2260
+ onBlur: commit,
2261
+ onKeyDown: (e) => {
2262
+ if (e.key === "Enter") {
2263
+ e.preventDefault();
2264
+ e.currentTarget.blur();
2265
+ }
2266
+ },
2267
+ onFocus: (e) => e.currentTarget.select(),
2268
+ "aria-label": ariaLabel
2269
+ }
2270
+ );
2271
+ }
2272
+ var Eyebrow = ({
2273
+ as: As = "span",
2274
+ variant = "muted",
2275
+ className = "",
2276
+ children,
2277
+ ...rest
2278
+ }) => {
2279
+ const classes = ["crewlet-eyebrow", `crewlet-eyebrow--${variant}`, className].filter(Boolean).join(" ");
2280
+ return /* @__PURE__ */ jsx(As, { ...rest, className: classes, children });
2281
+ };
2282
+ var IconButton = ({
2283
+ size = "sm",
2284
+ variant = "ghost",
2285
+ className = "",
2286
+ type = "button",
2287
+ asChild = false,
2288
+ children,
2289
+ ...rest
2290
+ }) => {
2291
+ const classes = [
2292
+ "crewlet-icon-btn",
2293
+ `crewlet-icon-btn--${size}`,
2294
+ `crewlet-icon-btn--${variant}`,
2295
+ className
2296
+ ].filter(Boolean).join(" ");
2297
+ if (asChild) {
2298
+ const child = Children.only(children);
2299
+ if (!isValidElement(child)) {
2300
+ throw new Error("IconButton with asChild expects exactly one valid React element child.");
2301
+ }
2302
+ const childEl = child;
2303
+ const merged = [childEl.props.className, classes].filter(Boolean).join(" ");
2304
+ return cloneElement(childEl, { ...rest, className: merged });
2305
+ }
2306
+ return /* @__PURE__ */ jsx("button", { type, className: classes, ...rest, children });
2307
+ };
2308
+ var ImageUpload = ({
2309
+ src,
2310
+ name,
2311
+ size = 64,
2312
+ shape = "circle",
2313
+ readOnly = false,
2314
+ uploading = false,
2315
+ accept = "image/*",
2316
+ onSelect,
2317
+ onRemove,
2318
+ className = "",
2319
+ style,
2320
+ ...rest
2321
+ }) => {
2322
+ const inputRef = useRef(null);
2323
+ const interactive = !readOnly && !uploading;
2324
+ const canRemove = Boolean(src) && !readOnly && Boolean(onRemove);
2325
+ const openPicker = () => {
2326
+ if (!interactive) {
2327
+ return;
2328
+ }
2329
+ inputRef.current?.click();
2330
+ };
2331
+ const handleChange = (event) => {
2332
+ const file = event.target.files?.[0];
2333
+ event.target.value = "";
2334
+ if (file) {
2335
+ onSelect(file);
2336
+ }
2337
+ };
2338
+ const handleRemove = (event) => {
2339
+ event.stopPropagation();
2340
+ onRemove?.();
2341
+ };
2342
+ const empty = !src && !readOnly;
2343
+ const wrapperClasses = [
2344
+ "crewlet-image-upload",
2345
+ `crewlet-image-upload--${shape}`,
2346
+ uploading ? "is-uploading" : "",
2347
+ empty ? "is-empty" : "",
2348
+ className
2349
+ ].filter(Boolean).join(" ");
2350
+ const sizeStyle = {
2351
+ "--crewlet-image-upload-size": `${size}px`,
2352
+ ...style
2353
+ };
2354
+ const triggerLabel = uploading ? "Uploading image" : src ? "Change image" : "Upload image";
2355
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: wrapperClasses, style: sizeStyle, children: [
2356
+ /* @__PURE__ */ jsxs(
2357
+ "button",
2358
+ {
2359
+ type: "button",
2360
+ className: "crewlet-image-upload__trigger",
2361
+ onClick: openPicker,
2362
+ disabled: !interactive,
2363
+ "aria-label": triggerLabel,
2364
+ "aria-busy": uploading,
2365
+ children: [
2366
+ /* @__PURE__ */ jsx(Avatar, { src, name, size, shape }),
2367
+ !readOnly ? /* @__PURE__ */ jsx("span", { className: "crewlet-image-upload__overlay", "aria-hidden": true, children: uploading ? /* @__PURE__ */ jsx("span", { className: "crewlet-image-upload__spinner" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2368
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "photo_camera" }),
2369
+ /* @__PURE__ */ jsx("span", { className: "crewlet-image-upload__overlay-text", children: src ? "Change" : "Upload" })
2370
+ ] }) }) : null
2371
+ ]
2372
+ }
2373
+ ),
2374
+ canRemove ? /* @__PURE__ */ jsx(
2375
+ "button",
2376
+ {
2377
+ type: "button",
2378
+ className: "crewlet-image-upload__remove",
2379
+ onClick: handleRemove,
2380
+ disabled: uploading,
2381
+ "aria-label": "Remove image",
2382
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "close" })
2383
+ }
2384
+ ) : null,
2385
+ !readOnly ? /* @__PURE__ */ jsx(
2386
+ "input",
2387
+ {
2388
+ ref: inputRef,
2389
+ type: "file",
2390
+ accept,
2391
+ className: "crewlet-image-upload__input",
2392
+ onChange: handleChange,
2393
+ tabIndex: -1
2394
+ }
2395
+ ) : null
2396
+ ] });
2397
+ };
2398
+ var Checkbox = forwardRef(function Checkbox2({
2399
+ label,
2400
+ description,
2401
+ tone = "default",
2402
+ className = "",
2403
+ disabled,
2404
+ id,
2405
+ ...rest
2406
+ }, ref) {
2407
+ const generatedId = useId();
2408
+ const inputId = id ?? generatedId;
2409
+ const describedBy = description ? `${inputId}-description` : void 0;
2410
+ const classes = [
2411
+ "crewlet-checkbox",
2412
+ tone === "danger" ? "crewlet-checkbox--danger" : "",
2413
+ disabled ? "is-disabled" : "",
2414
+ className
2415
+ ].filter(Boolean).join(" ");
2416
+ return /* @__PURE__ */ jsxs("div", { className: classes, children: [
2417
+ /* @__PURE__ */ jsx(
2418
+ "input",
2419
+ {
2420
+ ref,
2421
+ id: inputId,
2422
+ type: "checkbox",
2423
+ className: "crewlet-checkbox__control",
2424
+ disabled,
2425
+ "aria-describedby": describedBy,
2426
+ ...rest
2427
+ }
2428
+ ),
2429
+ /* @__PURE__ */ jsxs("label", { className: "crewlet-checkbox__text", htmlFor: inputId, children: [
2430
+ /* @__PURE__ */ jsx("span", { className: "crewlet-checkbox__label", children: label }),
2431
+ description && /* @__PURE__ */ jsx("span", { className: "crewlet-checkbox__description", id: describedBy, children: description })
2432
+ ] })
2433
+ ] });
2434
+ });
2435
+ var Input = forwardRef(function Input2({
2436
+ inputSize = "md",
2437
+ error = false,
2438
+ className = "",
2439
+ containerClassName = "",
2440
+ trailing,
2441
+ disabled,
2442
+ ...rest
2443
+ }, ref) {
2444
+ const containerClasses = [
2445
+ "crewlet-input",
2446
+ `crewlet-input--${inputSize}`,
2447
+ error ? "is-error" : "",
2448
+ disabled ? "is-disabled" : "",
2449
+ containerClassName
2450
+ ].filter(Boolean).join(" ");
2451
+ return /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
2452
+ /* @__PURE__ */ jsx(
2453
+ "input",
2454
+ {
2455
+ ref,
2456
+ className: `crewlet-input__control ${className}`.trim(),
2457
+ disabled,
2458
+ ...rest
2459
+ }
2460
+ ),
2461
+ trailing ? /* @__PURE__ */ jsx("span", { className: "crewlet-input__trailing", children: trailing }) : null
2462
+ ] });
2463
+ });
2464
+ var Textarea = forwardRef(function Textarea2({ error = false, autoResize = false, trailing, className = "", value, disabled, ...rest }, ref) {
2465
+ const localRef = useRef(null);
2466
+ useImperativeHandle(ref, () => localRef.current);
2467
+ useEffect(() => {
2468
+ if (!autoResize) return;
2469
+ const el = localRef.current;
2470
+ if (!el) return;
2471
+ el.style.height = "auto";
2472
+ el.style.height = `${el.scrollHeight}px`;
2473
+ }, [autoResize, value]);
2474
+ const classes = [
2475
+ "crewlet-textarea",
2476
+ error ? "is-error" : "",
2477
+ disabled ? "is-disabled" : "",
2478
+ autoResize ? "is-auto-resize" : "",
2479
+ className
2480
+ ].filter(Boolean).join(" ");
2481
+ const control = /* @__PURE__ */ jsx(
2482
+ "textarea",
2483
+ {
2484
+ ref: localRef,
2485
+ className: classes,
2486
+ value,
2487
+ disabled,
2488
+ ...rest
2489
+ }
2490
+ );
2491
+ if (!trailing) return control;
2492
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-textarea-shell", children: [
2493
+ control,
2494
+ /* @__PURE__ */ jsx("span", { className: "crewlet-input__trailing", children: trailing })
2495
+ ] });
2496
+ });
2497
+ var Label = ({ required, action, className = "", children, ...rest }) => {
2498
+ const classes = ["crewlet-label", action ? "crewlet-label--with-action" : "", className].filter(Boolean).join(" ");
2499
+ if (action) {
2500
+ return /* @__PURE__ */ jsxs("div", { className: classes, children: [
2501
+ /* @__PURE__ */ jsxs("label", { ...rest, className: "crewlet-label__text", children: [
2502
+ children,
2503
+ required ? /* @__PURE__ */ jsx("span", { className: "crewlet-label__required", "aria-hidden": true, children: "*" }) : null
2504
+ ] }),
2505
+ /* @__PURE__ */ jsx("span", { className: "crewlet-label__action", children: action })
2506
+ ] });
2507
+ }
2508
+ return /* @__PURE__ */ jsxs("label", { ...rest, className: classes, children: [
2509
+ children,
2510
+ required ? /* @__PURE__ */ jsx("span", { className: "crewlet-label__required", "aria-hidden": true, children: "*" }) : null
2511
+ ] });
2512
+ };
2513
+ var FormField = ({
2514
+ label,
2515
+ required,
2516
+ helper,
2517
+ error,
2518
+ labelAction,
2519
+ htmlFor,
2520
+ className = "",
2521
+ children,
2522
+ ...rest
2523
+ }) => {
2524
+ const autoId = useId();
2525
+ const id = htmlFor ?? autoId;
2526
+ const helperId = helper ? `${id}-helper` : void 0;
2527
+ const errorId = error ? `${id}-error` : void 0;
2528
+ const describedBy = errorId ?? helperId;
2529
+ const classes = ["crewlet-form-field", error ? "is-error" : "", className].filter(Boolean).join(" ");
2530
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: classes, children: [
2531
+ label !== void 0 ? /* @__PURE__ */ jsx(Label, { htmlFor: id, required, action: labelAction, children: label }) : null,
2532
+ typeof children === "function" ? children({ id, describedBy }) : children,
2533
+ error ? /* @__PURE__ */ jsx("div", { id: errorId, className: "crewlet-form-field__error", role: "alert", children: error }) : helper ? /* @__PURE__ */ jsx("div", { id: helperId, className: "crewlet-form-field__helper", children: helper }) : null
2534
+ ] });
2535
+ };
2536
+ var FormRow = ({ columns = 2, className = "", children, ...rest }) => {
2537
+ const classes = ["crewlet-form-row", `crewlet-form-row--${columns}`, className].filter(Boolean).join(" ");
2538
+ return /* @__PURE__ */ jsx("div", { ...rest, className: classes, children });
2539
+ };
2540
+ var Kbd = ({ subtle = false, className = "", children, ...rest }) => /* @__PURE__ */ jsx("kbd", { ...rest, className: ["crewlet-kbd", subtle ? "crewlet-kbd--subtle" : "", className].filter(Boolean).join(" "), children });
2541
+ var ConfirmModal = ({
2542
+ open,
2543
+ onClose,
2544
+ onConfirm,
2545
+ title,
2546
+ message,
2547
+ children,
2548
+ confirmLabel = "Confirm",
2549
+ cancelLabel = "Cancel",
2550
+ destructive = false,
2551
+ confirmVariant,
2552
+ confirmDisabled = false,
2553
+ submitting = false,
2554
+ size = "sm"
2555
+ }) => {
2556
+ const resolvedVariant = confirmVariant ?? (destructive ? "danger" : "primary");
2557
+ return /* @__PURE__ */ jsxs(
2558
+ Modal,
2559
+ {
2560
+ open,
2561
+ onClose,
2562
+ title,
2563
+ size,
2564
+ closeOnBackdrop: !submitting,
2565
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
2566
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: onClose, disabled: submitting, children: cancelLabel }),
2567
+ /* @__PURE__ */ jsx(
2568
+ Button,
2569
+ {
2570
+ variant: resolvedVariant,
2571
+ onClick: () => {
2572
+ void onConfirm();
2573
+ },
2574
+ disabled: confirmDisabled || submitting,
2575
+ children: submitting ? "Working\u2026" : confirmLabel
2576
+ }
2577
+ )
2578
+ ] }),
2579
+ children: [
2580
+ message ? /* @__PURE__ */ jsx("p", { className: "crewlet-modal__message", children: message }) : null,
2581
+ children
2582
+ ]
2583
+ }
2584
+ );
2585
+ };
2586
+ var PricingCardRoot = forwardRef(function PricingCardRoot2({
2587
+ selected = false,
2588
+ pending = false,
2589
+ unavailable = false,
2590
+ disabled = false,
2591
+ interactive = true,
2592
+ className = "",
2593
+ type,
2594
+ children,
2595
+ onClick,
2596
+ ...rest
2597
+ }, ref) {
2598
+ const classes = [
2599
+ "crewlet-pricing-card",
2600
+ selected ? "is-selected" : "",
2601
+ pending ? "is-pending" : "",
2602
+ unavailable ? "is-unavailable" : "",
2603
+ disabled ? "is-disabled" : "",
2604
+ interactive ? "is-interactive" : "",
2605
+ className
2606
+ ].filter(Boolean).join(" ");
2607
+ if (!interactive) {
2608
+ const { "aria-pressed": _ignored, ...divRest } = rest;
2609
+ return /* @__PURE__ */ jsx("div", { className: classes, ...divRest, children });
2610
+ }
2611
+ const onClickGuarded = disabled || unavailable ? void 0 : onClick;
2612
+ return /* @__PURE__ */ jsx(
2613
+ "button",
2614
+ {
2615
+ ref,
2616
+ type: type ?? "button",
2617
+ className: classes,
2618
+ disabled: disabled || void 0,
2619
+ "aria-pressed": selected || void 0,
2620
+ "aria-disabled": unavailable && !disabled ? true : void 0,
2621
+ onClick: onClickGuarded,
2622
+ ...rest,
2623
+ children
2624
+ }
2625
+ );
2626
+ });
2627
+ var Header = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-pricing-card__header ${className}`.trim(), children });
2628
+ var Name = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("span", { ...rest, className: `crewlet-pricing-card__name ${className}`.trim(), children });
2629
+ var Tag = ({ variant = "neutral", className = "", children, ...rest }) => /* @__PURE__ */ jsx(
2630
+ "span",
2631
+ {
2632
+ ...rest,
2633
+ className: `crewlet-pricing-card__tag crewlet-pricing-card__tag--${variant} ${className}`.trim(),
2634
+ children
2635
+ }
2636
+ );
2637
+ var Price = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-pricing-card__price ${className}`.trim(), children });
2638
+ var Amount = ({ strike = false, discounted = false, className = "", children, ...rest }) => {
2639
+ const classes = [
2640
+ "crewlet-pricing-card__amount",
2641
+ strike ? "crewlet-pricing-card__amount--strike" : "",
2642
+ discounted ? "crewlet-pricing-card__amount--discounted" : "",
2643
+ className
2644
+ ].filter(Boolean).join(" ");
2645
+ return /* @__PURE__ */ jsx("span", { ...rest, className: classes, children });
2646
+ };
2647
+ var Cadence = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("span", { ...rest, className: `crewlet-pricing-card__cadence ${className}`.trim(), children });
2648
+ var PriceNote = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("span", { ...rest, className: `crewlet-pricing-card__price-note ${className}`.trim(), children });
2649
+ var Body = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-pricing-card__body ${className}`.trim(), children });
2650
+ var Footer = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-pricing-card__footer ${className}`.trim(), children });
2651
+ var Grid = ({ className = "", children, ...rest }) => /* @__PURE__ */ jsx("div", { ...rest, className: `crewlet-pricing-card-grid ${className}`.trim(), children });
2652
+ var PricingCard = Object.assign(PricingCardRoot, {
2653
+ Header,
2654
+ Name,
2655
+ Tag,
2656
+ Price,
2657
+ Amount,
2658
+ Cadence,
2659
+ PriceNote,
2660
+ Body,
2661
+ Footer,
2662
+ Grid
2663
+ });
2664
+ var Section = ({
2665
+ as: As = "section",
2666
+ spacing = "md",
2667
+ className = "",
2668
+ children,
2669
+ ...rest
2670
+ }) => {
2671
+ const classes = ["crewlet-section", `crewlet-section--${spacing}`, className].filter(Boolean).join(" ");
2672
+ return /* @__PURE__ */ jsx(As, { ...rest, className: classes, children });
2673
+ };
2674
+ function Select({
2675
+ id,
2676
+ value,
2677
+ onChange,
2678
+ options,
2679
+ placeholder = "Select an option",
2680
+ disabled = false,
2681
+ className = "",
2682
+ menuClassName = "",
2683
+ size = "md",
2684
+ align = "left",
2685
+ renderTrigger,
2686
+ renderOption,
2687
+ ariaLabel,
2688
+ name,
2689
+ searchable = false,
2690
+ searchPlaceholder = "Search\u2026",
2691
+ multiple = false,
2692
+ renderMultiLabel
2693
+ }) {
2694
+ const selectedValues = useMemo(() => {
2695
+ if (multiple) {
2696
+ return Array.isArray(value) ? value : [];
2697
+ }
2698
+ if (value === void 0 || value === null) return [];
2699
+ return Array.isArray(value) ? value : [value];
2700
+ }, [value, multiple]);
2701
+ const isValueSelected = useCallback(
2702
+ (v) => selectedValues.some((sv) => String(sv) === String(v)),
2703
+ [selectedValues]
2704
+ );
2705
+ const [open, setOpen] = useState(false);
2706
+ const [highlight, setHighlight] = useState(-1);
2707
+ const [menuRect, setMenuRect] = useState(
2708
+ { top: 0, left: 0, width: 0, side: "bottom" }
2709
+ );
2710
+ const [search, setSearch] = useState("");
2711
+ const wrapRef = useRef(null);
2712
+ const triggerRef = useRef(null);
2713
+ const panelRef = useRef(null);
2714
+ const searchRef = useRef(null);
2715
+ const reposition = useCallback(() => {
2716
+ const trig = triggerRef.current;
2717
+ if (!trig) return;
2718
+ const rect = trig.getBoundingClientRect();
2719
+ const panelHeight = panelRef.current?.offsetHeight ?? 240;
2720
+ const viewportH = window.innerHeight;
2721
+ let chosenSide = "bottom";
2722
+ if (rect.bottom + panelHeight + 12 > viewportH && rect.top > panelHeight + 12) {
2723
+ chosenSide = "top";
2724
+ }
2725
+ setMenuRect({
2726
+ top: chosenSide === "bottom" ? rect.bottom + window.scrollY + 6 : rect.top + window.scrollY - panelHeight - 6,
2727
+ left: rect.left + window.scrollX,
2728
+ width: rect.width,
2729
+ side: chosenSide
2730
+ });
2731
+ }, []);
2732
+ const selectedIndex = useMemo(() => {
2733
+ if (multiple) return -1;
2734
+ if (selectedValues.length === 0) return -1;
2735
+ return options.findIndex((o) => String(o.value) === String(selectedValues[0]));
2736
+ }, [options, selectedValues, multiple]);
2737
+ const selectedOption = selectedIndex >= 0 ? options[selectedIndex] ?? null : null;
2738
+ const selectedOptions = useMemo(
2739
+ () => options.filter((o) => isValueSelected(o.value)),
2740
+ [options, isValueSelected]
2741
+ );
2742
+ const visibleOptions = useMemo(() => {
2743
+ if (!searchable) return options;
2744
+ const needle = search.trim().toLowerCase();
2745
+ if (needle === "") return options;
2746
+ return options.filter((opt) => {
2747
+ const label = String(opt.label ?? "").toLowerCase();
2748
+ if (label.includes(needle)) return true;
2749
+ const desc = String(opt.description ?? "").toLowerCase();
2750
+ return desc.length > 0 && desc.includes(needle);
2751
+ });
2752
+ }, [options, searchable, search]);
2753
+ useEffect(() => {
2754
+ if (!open) return;
2755
+ if (searchable && search.trim() !== "") {
2756
+ setHighlight(0);
2757
+ return;
2758
+ }
2759
+ setHighlight(selectedIndex >= 0 ? selectedIndex : 0);
2760
+ }, [open, selectedIndex, searchable, search]);
2761
+ useEffect(() => {
2762
+ if (!searchable) return;
2763
+ if (open) {
2764
+ setSearch("");
2765
+ requestAnimationFrame(() => {
2766
+ searchRef.current?.focus();
2767
+ });
2768
+ }
2769
+ }, [open, searchable]);
2770
+ useEffect(() => {
2771
+ if (!open) return void 0;
2772
+ const onDocMouseDown = (e) => {
2773
+ const target = e.target;
2774
+ if (target && wrapRef.current?.contains(target)) return;
2775
+ if (target && panelRef.current?.contains(target)) return;
2776
+ setOpen(false);
2777
+ };
2778
+ document.addEventListener("mousedown", onDocMouseDown);
2779
+ return () => document.removeEventListener("mousedown", onDocMouseDown);
2780
+ }, [open]);
2781
+ useLayoutEffect(() => {
2782
+ if (!open) return void 0;
2783
+ reposition();
2784
+ const onLayout = () => reposition();
2785
+ window.addEventListener("resize", onLayout);
2786
+ window.addEventListener("scroll", onLayout, true);
2787
+ return () => {
2788
+ window.removeEventListener("resize", onLayout);
2789
+ window.removeEventListener("scroll", onLayout, true);
2790
+ };
2791
+ }, [open, reposition]);
2792
+ const choose = useCallback(
2793
+ (option) => {
2794
+ if (option?.disabled) return;
2795
+ if (multiple) {
2796
+ const next = isValueSelected(option.value) ? selectedValues.filter((v) => String(v) !== String(option.value)) : [...selectedValues, option.value];
2797
+ onChange?.(next, option);
2798
+ return;
2799
+ }
2800
+ onChange?.(option.value, option);
2801
+ setOpen(false);
2802
+ triggerRef.current?.focus();
2803
+ },
2804
+ [onChange, multiple, isValueSelected, selectedValues]
2805
+ );
2806
+ const onTriggerKeyDown = (e) => {
2807
+ if (disabled) return;
2808
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
2809
+ e.preventDefault();
2810
+ setOpen(true);
2811
+ }
2812
+ };
2813
+ const onPanelKeyDown = (e) => {
2814
+ const list = visibleOptions;
2815
+ if (e.key === "Escape") {
2816
+ e.preventDefault();
2817
+ setOpen(false);
2818
+ triggerRef.current?.focus();
2819
+ return;
2820
+ }
2821
+ if (list.length === 0) return;
2822
+ if (e.key === "ArrowDown") {
2823
+ e.preventDefault();
2824
+ setHighlight((h) => {
2825
+ const next = h < 0 ? 0 : (h + 1) % list.length;
2826
+ let probe = next;
2827
+ for (let i = 0; i < list.length; i += 1) {
2828
+ if (!list[probe]?.disabled) return probe;
2829
+ probe = (probe + 1) % list.length;
2830
+ }
2831
+ return h;
2832
+ });
2833
+ return;
2834
+ }
2835
+ if (e.key === "ArrowUp") {
2836
+ e.preventDefault();
2837
+ setHighlight((h) => {
2838
+ const len = list.length;
2839
+ const next = h < 0 ? len - 1 : (h - 1 + len) % len;
2840
+ let probe = next;
2841
+ for (let i = 0; i < len; i += 1) {
2842
+ if (!list[probe]?.disabled) return probe;
2843
+ probe = (probe - 1 + len) % len;
2844
+ }
2845
+ return h;
2846
+ });
2847
+ return;
2848
+ }
2849
+ if (e.key === "Enter") {
2850
+ e.preventDefault();
2851
+ const target = list[highlight];
2852
+ if (target && !target.disabled) choose(target);
2853
+ return;
2854
+ }
2855
+ if (e.key === "Home") {
2856
+ e.preventDefault();
2857
+ setHighlight(0);
2858
+ return;
2859
+ }
2860
+ if (e.key === "End") {
2861
+ e.preventDefault();
2862
+ setHighlight(list.length - 1);
2863
+ }
2864
+ };
2865
+ let defaultMultiLabel = placeholder;
2866
+ if (multiple && selectedOptions.length === 1) {
2867
+ defaultMultiLabel = selectedOptions[0].label;
2868
+ } else if (multiple && selectedOptions.length > 1) {
2869
+ defaultMultiLabel = `${selectedOptions.length} selected`;
2870
+ }
2871
+ const hasMultiSelection = multiple && selectedOptions.length > 0;
2872
+ const triggerContent = renderTrigger ? renderTrigger(selectedOption, { open }) : multiple ? /* @__PURE__ */ jsx("span", { className: `crewlet-select__label${hasMultiSelection ? "" : " is-placeholder"}`, children: renderMultiLabel ? renderMultiLabel(selectedOptions) : defaultMultiLabel }) : /* @__PURE__ */ jsx("span", { className: `crewlet-select__label${selectedOption ? "" : " is-placeholder"}`, children: selectedOption ? selectedOption.label : placeholder });
2873
+ const wrapClass = [
2874
+ "crewlet-select",
2875
+ `crewlet-select--${size}`,
2876
+ open ? "is-open" : "",
2877
+ disabled ? "is-disabled" : "",
2878
+ className
2879
+ ].filter(Boolean).join(" ");
2880
+ const menuClass = [
2881
+ "crewlet-select__menu",
2882
+ `crewlet-select__menu--${align}`,
2883
+ // Propagate the size onto the menu too. The wrapper class lives
2884
+ // outside the portaled menu, so without this the sm trigger
2885
+ // would still open a default-padded menu.
2886
+ `crewlet-select__menu--${size}`,
2887
+ menuClassName
2888
+ ].filter(Boolean).join(" ");
2889
+ return /* @__PURE__ */ jsxs("div", { className: wrapClass, ref: wrapRef, children: [
2890
+ /* @__PURE__ */ jsxs(
2891
+ "button",
2892
+ {
2893
+ ref: triggerRef,
2894
+ type: "button",
2895
+ id,
2896
+ name,
2897
+ className: "crewlet-select__trigger",
2898
+ "aria-haspopup": "listbox",
2899
+ "aria-expanded": open,
2900
+ "aria-controls": id ? `${id}-listbox` : void 0,
2901
+ "aria-label": ariaLabel,
2902
+ disabled,
2903
+ onClick: () => setOpen((o) => !o),
2904
+ onKeyDown: onTriggerKeyDown,
2905
+ children: [
2906
+ triggerContent,
2907
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-select__chevron", "aria-hidden": true, children: open ? "expand_less" : "expand_more" })
2908
+ ]
2909
+ }
2910
+ ),
2911
+ open && createPortal(
2912
+ /* @__PURE__ */ jsxs(
2913
+ "div",
2914
+ {
2915
+ ref: (el) => {
2916
+ panelRef.current = el;
2917
+ if (!searchable) el?.focus();
2918
+ },
2919
+ className: `${menuClass} crewlet-select__menu--${menuRect.side}`,
2920
+ style: {
2921
+ position: "absolute",
2922
+ top: menuRect.top,
2923
+ left: menuRect.left,
2924
+ width: menuRect.width
2925
+ },
2926
+ tabIndex: -1,
2927
+ onKeyDown: onPanelKeyDown,
2928
+ onMouseLeave: () => setHighlight(-1),
2929
+ onMouseDown: (e) => e.stopPropagation(),
2930
+ children: [
2931
+ searchable && /* @__PURE__ */ jsxs("div", { className: "crewlet-select__search", children: [
2932
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "search" }),
2933
+ /* @__PURE__ */ jsx(
2934
+ "input",
2935
+ {
2936
+ ref: searchRef,
2937
+ type: "text",
2938
+ className: "crewlet-select__search-input",
2939
+ placeholder: searchPlaceholder,
2940
+ value: search,
2941
+ onChange: (e) => setSearch(e.target.value),
2942
+ onKeyDown: onPanelKeyDown,
2943
+ "aria-label": "Search options"
2944
+ }
2945
+ )
2946
+ ] }),
2947
+ /* @__PURE__ */ jsxs(
2948
+ "ul",
2949
+ {
2950
+ id: id ? `${id}-listbox` : void 0,
2951
+ className: "crewlet-select__list",
2952
+ role: "listbox",
2953
+ children: [
2954
+ visibleOptions.length === 0 && /* @__PURE__ */ jsx("li", { className: "crewlet-select__empty", "aria-hidden": true, children: "No matches" }),
2955
+ visibleOptions.map((opt, i) => {
2956
+ const isSelected = isValueSelected(opt.value);
2957
+ const isHighlight = i === highlight;
2958
+ const itemClass = [
2959
+ "crewlet-select__option",
2960
+ multiple ? "crewlet-select__option--multi" : "",
2961
+ isSelected ? "is-selected" : "",
2962
+ isHighlight ? "is-highlight" : "",
2963
+ opt.disabled ? "is-disabled" : ""
2964
+ ].filter(Boolean).join(" ");
2965
+ return /* @__PURE__ */ jsx(
2966
+ "li",
2967
+ {
2968
+ role: "option",
2969
+ "aria-selected": isSelected,
2970
+ "aria-disabled": opt.disabled || void 0,
2971
+ className: itemClass,
2972
+ onMouseEnter: () => !opt.disabled && setHighlight(i),
2973
+ onMouseDown: (e) => {
2974
+ e.preventDefault();
2975
+ choose(opt);
2976
+ },
2977
+ children: renderOption ? renderOption(opt, { active: isHighlight, selected: isSelected }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2978
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-select__option-row", children: [
2979
+ multiple && /* @__PURE__ */ jsx(
2980
+ "span",
2981
+ {
2982
+ className: `crewlet-select__option-checkbox${isSelected ? " is-checked" : ""}`,
2983
+ "aria-hidden": true,
2984
+ children: isSelected && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "check" })
2985
+ }
2986
+ ),
2987
+ /* @__PURE__ */ jsx("span", { className: "crewlet-select__option-label", children: opt.label }),
2988
+ !multiple && isSelected && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-select__option-check", "aria-hidden": true, children: "check" })
2989
+ ] }),
2990
+ opt.description && /* @__PURE__ */ jsx("div", { className: "crewlet-select__option-desc", children: opt.description })
2991
+ ] })
2992
+ },
2993
+ opt.value
2994
+ );
2995
+ })
2996
+ ]
2997
+ }
2998
+ )
2999
+ ]
3000
+ }
3001
+ ),
3002
+ document.body
3003
+ )
3004
+ ] });
3005
+ }
3006
+ var range = (n) => Array.from({ length: Math.max(0, n) }, (_, i) => i);
3007
+ var clampColumns = (n) => Math.min(4, Math.max(1, Math.round(n)));
3008
+ var cx = (...classes) => classes.filter(Boolean).join(" ");
3009
+ var line = (...modifiers) => cx("crewlet-skeleton__line", ...modifiers.map((modifier) => `crewlet-skeleton__line--${modifier}`));
3010
+ var Skeleton = ({
3011
+ variant = "card",
3012
+ rows = 1,
3013
+ columns = 1,
3014
+ count = 4,
3015
+ className = "",
3016
+ style,
3017
+ width = "100%",
3018
+ height = 400,
3019
+ borderRadius = 12,
3020
+ marginTop = 32,
3021
+ marginBottom = 0,
3022
+ glowColor = "blue",
3023
+ animationDuration = 2
3024
+ }) => {
3025
+ const rootClass = (...modifiers) => cx("crewlet-skeleton", `crewlet-skeleton--${variant}`, ...modifiers, className);
3026
+ switch (variant) {
3027
+ case "box":
3028
+ return /* @__PURE__ */ jsx("div", { className: rootClass(), style: { marginTop, marginBottom, ...style }, "aria-hidden": true, children: /* @__PURE__ */ jsx(
3029
+ "div",
3030
+ {
3031
+ className: "crewlet-skeleton__glow-box",
3032
+ style: {
3033
+ height: typeof height === "number" ? `${height}px` : height,
3034
+ width: typeof width === "number" ? `${width}px` : width,
3035
+ borderRadius: `${borderRadius}px`
3036
+ },
3037
+ children: /* @__PURE__ */ jsx(
3038
+ "div",
3039
+ {
3040
+ className: `crewlet-skeleton__glow-wave crewlet-skeleton__glow-wave--${glowColor}`,
3041
+ style: { animationDuration: `${animationDuration}s` }
3042
+ }
3043
+ )
3044
+ }
3045
+ ) });
3046
+ case "text":
3047
+ return /* @__PURE__ */ jsx("div", { className: rootClass(), style, "aria-hidden": true, children: range(rows).map((i) => /* @__PURE__ */ jsx("div", { className: line(i === 0 ? "title" : "text", ...i === rows - 1 ? ["short"] : []) }, i)) });
3048
+ case "card":
3049
+ return /* @__PURE__ */ jsxs("div", { className: rootClass(), style, "aria-hidden": true, children: [
3050
+ /* @__PURE__ */ jsx("div", { className: line("title") }),
3051
+ /* @__PURE__ */ jsx("div", { className: line("text") }),
3052
+ /* @__PURE__ */ jsx("div", { className: line("text", "short") })
3053
+ ] });
3054
+ case "grid": {
3055
+ const cols = clampColumns(columns);
3056
+ return /* @__PURE__ */ jsx("div", { className: rootClass(`crewlet-skeleton--cols-${cols}`), style, "aria-hidden": true, children: range(rows * cols).map((i) => /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__card", children: [
3057
+ /* @__PURE__ */ jsx("div", { className: line("title") }),
3058
+ /* @__PURE__ */ jsx("div", { className: line("text") }),
3059
+ /* @__PURE__ */ jsx("div", { className: line("text", "short") })
3060
+ ] }, i)) });
3061
+ }
3062
+ case "list":
3063
+ return /* @__PURE__ */ jsx("div", { className: rootClass(), style, "aria-hidden": true, children: range(rows).map((i) => /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__list-item", children: [
3064
+ /* @__PURE__ */ jsx("div", { className: "crewlet-skeleton__avatar" }),
3065
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__list-content", children: [
3066
+ /* @__PURE__ */ jsx("div", { className: line("title") }),
3067
+ /* @__PURE__ */ jsx("div", { className: line("text", "short") })
3068
+ ] })
3069
+ ] }, i)) });
3070
+ case "table":
3071
+ return /* @__PURE__ */ jsx("div", { className: rootClass(), style, "aria-hidden": true, children: /* @__PURE__ */ jsxs("table", { className: "crewlet-skeleton__table", children: [
3072
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: range(columns).map((i) => /* @__PURE__ */ jsx("th", { className: "crewlet-skeleton__th", children: /* @__PURE__ */ jsx("div", { className: line("title") }) }, i)) }) }),
3073
+ /* @__PURE__ */ jsx("tbody", { children: range(rows).map((rowIndex) => /* @__PURE__ */ jsx("tr", { className: "crewlet-skeleton__tr", children: range(columns).map((colIndex) => /* @__PURE__ */ jsx("td", { className: "crewlet-skeleton__td", children: /* @__PURE__ */ jsx("div", { className: line("text") }) }, colIndex)) }, rowIndex)) })
3074
+ ] }) });
3075
+ case "info-grid": {
3076
+ const cols = clampColumns(columns);
3077
+ return /* @__PURE__ */ jsx("div", { className: rootClass(`crewlet-skeleton--cols-${cols}`), style, "aria-hidden": true, children: range(rows * cols).map((i) => /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__info-item", children: [
3078
+ /* @__PURE__ */ jsx("div", { className: line("label") }),
3079
+ /* @__PURE__ */ jsx("div", { className: line("value") })
3080
+ ] }, i)) });
3081
+ }
3082
+ case "pricing-card":
3083
+ return /* @__PURE__ */ jsx("div", { className: rootClass(), style, "aria-hidden": true, children: range(count).map((i) => /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__pricing-card", children: [
3084
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__pricing-card-header", children: [
3085
+ /* @__PURE__ */ jsx("div", { className: "crewlet-skeleton__circle" }),
3086
+ /* @__PURE__ */ jsx("div", { className: line("heading") })
3087
+ ] }),
3088
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__pricing-card-description", children: [
3089
+ /* @__PURE__ */ jsx("div", { className: line("text") }),
3090
+ /* @__PURE__ */ jsx("div", { className: line("text", "wide") }),
3091
+ /* @__PURE__ */ jsx("div", { className: line("text", "three-quarter") })
3092
+ ] }),
3093
+ /* @__PURE__ */ jsx("div", { className: "crewlet-skeleton__pricing-card-features", children: range(5).map((f) => /* @__PURE__ */ jsxs("div", { className: "crewlet-skeleton__pricing-card-feature", children: [
3094
+ /* @__PURE__ */ jsx("div", { className: "crewlet-skeleton__circle crewlet-skeleton__circle--small" }),
3095
+ /* @__PURE__ */ jsx("div", { className: line("feature") })
3096
+ ] }, f)) }),
3097
+ /* @__PURE__ */ jsx("div", { className: "crewlet-skeleton__button" })
3098
+ ] }, i)) });
3099
+ default:
3100
+ return /* @__PURE__ */ jsx("div", { className: cx("crewlet-skeleton", className), style, "aria-hidden": true, children: /* @__PURE__ */ jsx("div", { className: "crewlet-skeleton__line" }) });
3101
+ }
3102
+ };
3103
+ var StatCard = ({
3104
+ label,
3105
+ value,
3106
+ sub,
3107
+ icon,
3108
+ tone = "neutral",
3109
+ loading = false,
3110
+ className = "",
3111
+ ...rest
3112
+ }) => {
3113
+ const classes = ["crewlet-statcard", `crewlet-statcard--${tone}`, className].filter(Boolean).join(" ");
3114
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: classes, children: [
3115
+ icon ? /* @__PURE__ */ jsx("span", { className: "crewlet-statcard__icon", "aria-hidden": true, children: icon }) : null,
3116
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-statcard__body", children: [
3117
+ /* @__PURE__ */ jsx("div", { className: "crewlet-statcard__value", children: loading ? /* @__PURE__ */ jsx("span", { className: "crewlet-statcard__placeholder", children: "--" }) : value }),
3118
+ /* @__PURE__ */ jsx("div", { className: "crewlet-statcard__label", children: label }),
3119
+ sub ? /* @__PURE__ */ jsx("div", { className: "crewlet-statcard__sub", children: sub }) : null
3120
+ ] })
3121
+ ] });
3122
+ };
3123
+ var Table = ({
3124
+ headers,
3125
+ data,
3126
+ emptyMessage = "No data available",
3127
+ actionButton,
3128
+ className = ""
3129
+ }) => {
3130
+ return /* @__PURE__ */ jsxs("div", { className: `crewlet-table ${className}`, children: [
3131
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-table__container", children: [
3132
+ /* @__PURE__ */ jsx("div", { className: "crewlet-table__header", style: { gridTemplateColumns: `repeat(${headers.length}, 1fr)` }, children: headers.map((header, index) => /* @__PURE__ */ jsx("span", { children: header }, index)) }),
3133
+ data && data.length > 0 ? /* @__PURE__ */ jsx("div", { className: "crewlet-table__body", children: data.map((row, rowIndex) => /* @__PURE__ */ jsx(
3134
+ "div",
3135
+ {
3136
+ className: "crewlet-table__row",
3137
+ style: { gridTemplateColumns: `repeat(${headers.length}, 1fr)` },
3138
+ children: row.map((cell, cellIndex) => /* @__PURE__ */ jsx("span", { children: cell }, cellIndex))
3139
+ },
3140
+ rowIndex
3141
+ )) }) : /* @__PURE__ */ jsx("div", { className: "crewlet-table__empty", children: /* @__PURE__ */ jsx("span", { children: emptyMessage }) })
3142
+ ] }),
3143
+ actionButton && /* @__PURE__ */ jsx(
3144
+ "button",
3145
+ {
3146
+ className: "crewlet-table__action-btn",
3147
+ onClick: actionButton.onClick,
3148
+ children: /* @__PURE__ */ jsx("span", { className: "crewlet-table__action-text", children: actionButton.label })
3149
+ }
3150
+ )
3151
+ ] });
3152
+ };
3153
+ function Tabs({
3154
+ items,
3155
+ variant = "pill",
3156
+ size = "md",
3157
+ ariaLabel,
3158
+ className = "",
3159
+ value,
3160
+ onValueChange,
3161
+ renderItem
3162
+ }) {
3163
+ const navRef = useRef(null);
3164
+ useLayoutEffect(() => {
3165
+ if (variant !== "underline") return;
3166
+ const nav = navRef.current;
3167
+ if (!nav) return;
3168
+ const update = () => {
3169
+ const active = nav.querySelector(".crewlet-tabs__tab.is-active");
3170
+ if (!active) {
3171
+ nav.style.setProperty("--crewlet-tabs-slider-width", "0px");
3172
+ return;
3173
+ }
3174
+ const navRect = nav.getBoundingClientRect();
3175
+ const activeRect = active.getBoundingClientRect();
3176
+ const left = activeRect.left - navRect.left;
3177
+ const width = activeRect.width;
3178
+ nav.style.setProperty("--crewlet-tabs-slider-left", `${left}px`);
3179
+ nav.style.setProperty("--crewlet-tabs-slider-width", `${width}px`);
3180
+ };
3181
+ update();
3182
+ const mutation = new MutationObserver(update);
3183
+ nav.querySelectorAll(".crewlet-tabs__tab").forEach((tab) => {
3184
+ mutation.observe(tab, {
3185
+ attributes: true,
3186
+ attributeFilter: ["class"]
3187
+ });
3188
+ });
3189
+ const resize = new ResizeObserver(update);
3190
+ resize.observe(nav);
3191
+ return () => {
3192
+ mutation.disconnect();
3193
+ resize.disconnect();
3194
+ };
3195
+ }, [variant, value, items]);
3196
+ const rootClass = [
3197
+ "crewlet-tabs",
3198
+ `crewlet-tabs--${variant}`,
3199
+ `crewlet-tabs--${size}`,
3200
+ className
3201
+ ].filter(Boolean).join(" ");
3202
+ return /* @__PURE__ */ jsx(
3203
+ "nav",
3204
+ {
3205
+ ref: navRef,
3206
+ className: rootClass,
3207
+ "aria-label": ariaLabel,
3208
+ role: "tablist",
3209
+ children: items.map((item) => {
3210
+ const isActive = value !== void 0 && value === item.value;
3211
+ const tabClassName = [
3212
+ "crewlet-tabs__tab",
3213
+ isActive ? "is-active" : void 0,
3214
+ item.disabled ? "is-disabled" : void 0
3215
+ ].filter(Boolean).join(" ");
3216
+ const children = /* @__PURE__ */ jsxs(Fragment, { children: [
3217
+ item.icon ? /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-tabs__icon", "aria-hidden": true, children: item.icon }) : null,
3218
+ /* @__PURE__ */ jsx("span", { className: "crewlet-tabs__label", children: item.label })
3219
+ ] });
3220
+ const onSelect = () => {
3221
+ if (item.disabled) return;
3222
+ onValueChange?.(item.value);
3223
+ };
3224
+ if (renderItem) {
3225
+ return /* @__PURE__ */ jsx(React.Fragment, { children: renderItem({ item, className: tabClassName, isActive, onSelect, children }) }, item.value);
3226
+ }
3227
+ return /* @__PURE__ */ jsx(
3228
+ "button",
3229
+ {
3230
+ type: "button",
3231
+ role: "tab",
3232
+ "aria-selected": isActive,
3233
+ "aria-current": isActive ? "page" : void 0,
3234
+ disabled: item.disabled,
3235
+ className: tabClassName,
3236
+ onClick: onSelect,
3237
+ children
3238
+ },
3239
+ item.value
3240
+ );
3241
+ })
3242
+ }
3243
+ );
3244
+ }
3245
+ var DURATION_RX = /^\s*(\d+)\s*(m|h|d|w)\s*$/i;
3246
+ function parseTimeWindowDuration(s) {
3247
+ const m = DURATION_RX.exec(s);
3248
+ if (!m) return 0;
3249
+ const n = parseInt(m[1], 10);
3250
+ if (!Number.isFinite(n)) return 0;
3251
+ const unit = m[2].toLowerCase();
3252
+ const factor = unit === "m" ? 6e4 : unit === "h" ? 36e5 : unit === "d" ? 864e5 : 6048e5;
3253
+ return n * factor;
3254
+ }
3255
+ function resolveTimeWindow(value) {
3256
+ if (!value) return { from: void 0, to: void 0 };
3257
+ if (value.kind === "relative" && value.duration) {
3258
+ const ms = parseTimeWindowDuration(value.duration);
3259
+ if (ms <= 0) return { from: void 0, to: void 0 };
3260
+ return {
3261
+ from: new Date(Date.now() - ms).toISOString(),
3262
+ to: void 0
3263
+ };
3264
+ }
3265
+ if (value.kind === "absolute") {
3266
+ const suffix = value.timezone === "utc" ? "Z" : "";
3267
+ return {
3268
+ from: value.from ? new Date(value.from + suffix).toISOString() : void 0,
3269
+ to: value.to ? new Date(value.to + suffix).toISOString() : void 0
3270
+ };
3271
+ }
3272
+ return { from: void 0, to: void 0 };
3273
+ }
3274
+ var CHIP_GROUPS = [
3275
+ { label: "Minutes", unit: "m", values: [5, 10, 15, 30, 45] },
3276
+ { label: "Hours", unit: "h", values: [1, 2, 3, 6, 8, 12] },
3277
+ { label: "Days", unit: "d", values: [1, 2, 3, 4, 5, 6] },
3278
+ { label: "Weeks", unit: "w", values: [1, 2, 3, 4] }
3279
+ ];
3280
+ var UNIT_LABELS = {
3281
+ m: "Minutes",
3282
+ h: "Hours",
3283
+ d: "Days",
3284
+ w: "Weeks"
3285
+ };
3286
+ var pad2 = (n) => String(n).padStart(2, "0");
3287
+ var parseDateTime = (s) => {
3288
+ if (!s) return null;
3289
+ const tIndex = s.indexOf("T");
3290
+ const datePart = tIndex >= 0 ? s.slice(0, tIndex) : s;
3291
+ const timePart = tIndex >= 0 ? s.slice(tIndex + 1) : "00:00:00";
3292
+ const dateBits = datePart.split("-");
3293
+ if (dateBits.length < 3) return null;
3294
+ const y = parseInt(dateBits[0], 10);
3295
+ const m = parseInt(dateBits[1], 10);
3296
+ const d = parseInt(dateBits[2], 10);
3297
+ if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return null;
3298
+ const timeBits = timePart.split(":");
3299
+ const h = parseInt(timeBits[0] || "0", 10);
3300
+ const mn = parseInt(timeBits[1] || "0", 10);
3301
+ const sec = parseInt(timeBits[2] || "0", 10);
3302
+ return {
3303
+ year: y,
3304
+ month: m - 1,
3305
+ day: d,
3306
+ hour: Number.isFinite(h) ? h : 0,
3307
+ minute: Number.isFinite(mn) ? mn : 0,
3308
+ second: Number.isFinite(sec) ? sec : 0
3309
+ };
3310
+ };
3311
+ var formatDateTime = (p) => `${p.year}-${pad2(p.month + 1)}-${pad2(p.day)}T${pad2(p.hour)}:${pad2(p.minute)}:${pad2(p.second)}`;
3312
+ var formatDateOnly = (p) => `${p.year}/${pad2(p.month + 1)}/${pad2(p.day)}`;
3313
+ var formatTimeOnly = (p) => `${pad2(p.hour)}:${pad2(p.minute)}:${pad2(p.second)}`;
3314
+ var parseDateOnly = (s) => {
3315
+ const m = /^(\d{4})[/-](\d{1,2})[/-](\d{1,2})$/.exec(s.trim());
3316
+ if (!m) return null;
3317
+ return {
3318
+ year: parseInt(m[1], 10),
3319
+ month: parseInt(m[2], 10) - 1,
3320
+ day: parseInt(m[3], 10)
3321
+ };
3322
+ };
3323
+ var parseTimeOnly = (s) => {
3324
+ const m = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/.exec(s.trim());
3325
+ if (!m) return null;
3326
+ return {
3327
+ hour: parseInt(m[1], 10),
3328
+ minute: parseInt(m[2], 10),
3329
+ second: m[3] ? parseInt(m[3], 10) : 0
3330
+ };
3331
+ };
3332
+ var dateDigitFilter = (digit, pos, display) => {
3333
+ switch (pos) {
3334
+ // YYYY: 2000-2030
3335
+ case 0:
3336
+ return digit === "2";
3337
+ // must start with 2
3338
+ case 1:
3339
+ return digit === "0";
3340
+ // 20xx
3341
+ case 2:
3342
+ return /[0-3]/.test(digit);
3343
+ // 2000/2010/2020/2030
3344
+ case 3:
3345
+ return display[2] === "3" ? digit === "0" : /[0-9]/.test(digit);
3346
+ // MM (month): 01-12
3347
+ case 5:
3348
+ return /[01]/.test(digit);
3349
+ // 0x or 1x
3350
+ case 6:
3351
+ if (display[5] === "0") return /[1-9]/.test(digit);
3352
+ if (display[5] === "1") return /[0-2]/.test(digit);
3353
+ return true;
3354
+ // DD: 01-31
3355
+ case 8:
3356
+ return /[0-3]/.test(digit);
3357
+ case 9:
3358
+ if (display[8] === "0") return /[1-9]/.test(digit);
3359
+ if (display[8] === "3") return /[01]/.test(digit);
3360
+ return /[0-9]/.test(digit);
3361
+ default:
3362
+ return true;
3363
+ }
3364
+ };
3365
+ var timeDigitFilter = (digit, pos, display) => {
3366
+ switch (pos) {
3367
+ // HH: 00-24
3368
+ case 0:
3369
+ return /[0-2]/.test(digit);
3370
+ case 1:
3371
+ return display[0] === "2" ? /[0-4]/.test(digit) : /[0-9]/.test(digit);
3372
+ // MM (minute): 00-60
3373
+ case 3:
3374
+ return /[0-6]/.test(digit);
3375
+ case 4:
3376
+ return display[3] === "6" ? digit === "0" : /[0-9]/.test(digit);
3377
+ // SS: 00-60
3378
+ case 6:
3379
+ return /[0-6]/.test(digit);
3380
+ case 7:
3381
+ return display[6] === "6" ? digit === "0" : /[0-9]/.test(digit);
3382
+ default:
3383
+ return true;
3384
+ }
3385
+ };
3386
+ var isValidDateInput = (s) => {
3387
+ const m = /^(\d{4})\/(\d{2})\/(\d{2})$/.exec(s);
3388
+ if (!m) return false;
3389
+ const y = parseInt(m[1], 10);
3390
+ const mo = parseInt(m[2], 10);
3391
+ const d = parseInt(m[3], 10);
3392
+ return y >= 2e3 && y <= 2030 && mo >= 1 && mo <= 12 && d >= 1 && d <= 31;
3393
+ };
3394
+ var isValidTimeInput = (s) => {
3395
+ const m = /^(\d{2}):(\d{2}):(\d{2})$/.exec(s);
3396
+ if (!m) return false;
3397
+ const h = parseInt(m[1], 10);
3398
+ const mi = parseInt(m[2], 10);
3399
+ const se = parseInt(m[3], 10);
3400
+ return h >= 0 && h <= 24 && mi >= 0 && mi <= 60 && se >= 0 && se <= 60;
3401
+ };
3402
+ var DATE_SEGMENTS = [
3403
+ { start: 0, end: 3, min: 2e3, max: 2030 },
3404
+ // YYYY
3405
+ { start: 5, end: 6, min: 1, max: 12 },
3406
+ // MM
3407
+ { start: 8, end: 9, min: 1, max: 31 }
3408
+ // DD
3409
+ ];
3410
+ var TIME_SEGMENTS = [
3411
+ { start: 0, end: 1, min: 0, max: 24 },
3412
+ // HH
3413
+ { start: 3, end: 4, min: 0, max: 60 },
3414
+ // MM
3415
+ { start: 6, end: 7, min: 0, max: 60 }
3416
+ // SS
3417
+ ];
3418
+ var MONTH_NAMES2 = [
3419
+ "January",
3420
+ "February",
3421
+ "March",
3422
+ "April",
3423
+ "May",
3424
+ "June",
3425
+ "July",
3426
+ "August",
3427
+ "September",
3428
+ "October",
3429
+ "November",
3430
+ "December"
3431
+ ];
3432
+ var WEEKDAY_LABELS_SUN2 = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
3433
+ var WEEKDAY_LABELS_MON2 = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
3434
+ var daysInMonth2 = (year, month) => new Date(year, month + 1, 0).getDate();
3435
+ var startWeekday2 = (year, month, firstDay) => {
3436
+ const d = new Date(year, month, 1).getDay();
3437
+ return (d - firstDay + 7) % 7;
3438
+ };
3439
+ var compareDates2 = (a, b) => {
3440
+ if (a.year !== b.year) return a.year - b.year;
3441
+ if (a.month !== b.month) return a.month - b.month;
3442
+ return a.day - b.day;
3443
+ };
3444
+ var isBetween = (cell, lo, hi) => compareDates2(cell, lo) >= 0 && compareDates2(cell, hi) <= 0;
3445
+ function Calendar({
3446
+ year,
3447
+ month,
3448
+ rangeStart,
3449
+ rangeEnd,
3450
+ onSelectDate,
3451
+ onHoverDate,
3452
+ firstDayOfWeek = 0,
3453
+ onPrev,
3454
+ onNext
3455
+ }) {
3456
+ const totalDays = daysInMonth2(year, month);
3457
+ const leading = startWeekday2(year, month, firstDayOfWeek);
3458
+ const weekdayLabels = firstDayOfWeek === 1 ? WEEKDAY_LABELS_MON2 : WEEKDAY_LABELS_SUN2;
3459
+ const today = useMemo(() => {
3460
+ const now = /* @__PURE__ */ new Date();
3461
+ return { year: now.getFullYear(), month: now.getMonth(), day: now.getDate() };
3462
+ }, []);
3463
+ const cells = useMemo(() => {
3464
+ const out = [];
3465
+ const prevTotal = daysInMonth2(
3466
+ month === 0 ? year - 1 : year,
3467
+ month === 0 ? 11 : month - 1
3468
+ );
3469
+ for (let i = 0; i < leading; i += 1) {
3470
+ out.push({ day: prevTotal - leading + 1 + i, inMonth: false });
3471
+ }
3472
+ for (let d = 1; d <= totalDays; d += 1) {
3473
+ out.push({ day: d, inMonth: true });
3474
+ }
3475
+ while (out.length < 42) {
3476
+ out.push({ day: out.length - leading - totalDays + 1, inMonth: false });
3477
+ }
3478
+ return out;
3479
+ }, [year, month, leading, totalDays]);
3480
+ const titleClass = [
3481
+ "crewlet-time-window__calendar-title",
3482
+ onPrev ? "has-prev" : "",
3483
+ onNext ? "has-next" : ""
3484
+ ].filter(Boolean).join(" ");
3485
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__calendar", children: [
3486
+ /* @__PURE__ */ jsxs("div", { className: titleClass, children: [
3487
+ onPrev && /* @__PURE__ */ jsx(
3488
+ "button",
3489
+ {
3490
+ type: "button",
3491
+ className: "crewlet-time-window__calendar-nav crewlet-time-window__calendar-nav--prev",
3492
+ onClick: onPrev,
3493
+ "aria-label": "Previous month",
3494
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "chevron_left" })
3495
+ }
3496
+ ),
3497
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-time-window__calendar-title-text", children: [
3498
+ MONTH_NAMES2[month],
3499
+ " ",
3500
+ year
3501
+ ] }),
3502
+ onNext && /* @__PURE__ */ jsx(
3503
+ "button",
3504
+ {
3505
+ type: "button",
3506
+ className: "crewlet-time-window__calendar-nav crewlet-time-window__calendar-nav--next",
3507
+ onClick: onNext,
3508
+ "aria-label": "Next month",
3509
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "chevron_right" })
3510
+ }
3511
+ )
3512
+ ] }),
3513
+ /* @__PURE__ */ jsx("div", { className: "crewlet-time-window__weekdays", "aria-hidden": true, children: weekdayLabels.map((label, i) => /* @__PURE__ */ jsx("span", { className: "crewlet-time-window__weekday", children: label }, i)) }),
3514
+ /* @__PURE__ */ jsx(
3515
+ "div",
3516
+ {
3517
+ className: "crewlet-time-window__grid",
3518
+ role: "grid",
3519
+ onMouseLeave: onHoverDate ? () => onHoverDate(null) : void 0,
3520
+ children: cells.map((cell, i) => {
3521
+ let cellYear = year;
3522
+ let cellMonth = month;
3523
+ if (!cell.inMonth) {
3524
+ if (cell.day > 20) {
3525
+ cellMonth -= 1;
3526
+ if (cellMonth < 0) {
3527
+ cellMonth = 11;
3528
+ cellYear -= 1;
3529
+ }
3530
+ } else {
3531
+ cellMonth += 1;
3532
+ if (cellMonth > 11) {
3533
+ cellMonth = 0;
3534
+ cellYear += 1;
3535
+ }
3536
+ }
3537
+ }
3538
+ const cellCoord = { year: cellYear, month: cellMonth, day: cell.day };
3539
+ const isFuture = compareDates2(cellCoord, today) > 0;
3540
+ const isStart = !isFuture && !!rangeStart && compareDates2(cellCoord, rangeStart) === 0;
3541
+ const isEnd = !isFuture && !!rangeEnd && compareDates2(cellCoord, rangeEnd) === 0;
3542
+ const isSel = isStart || isEnd;
3543
+ let inRange = false;
3544
+ if (!isFuture && rangeStart && rangeEnd && !isSel) {
3545
+ const lo = compareDates2(rangeStart, rangeEnd) <= 0 ? rangeStart : rangeEnd;
3546
+ const hi = compareDates2(rangeStart, rangeEnd) <= 0 ? rangeEnd : rangeStart;
3547
+ inRange = isBetween(cellCoord, lo, hi);
3548
+ }
3549
+ const isToday = today.year === cellYear && today.month === cellMonth && today.day === cell.day;
3550
+ const cls = [
3551
+ "crewlet-time-window__cell",
3552
+ cell.inMonth ? "" : "is-outside",
3553
+ isFuture ? "is-future" : "",
3554
+ isToday ? "is-today" : "",
3555
+ isSel ? "is-selected" : "",
3556
+ inRange ? "is-in-range" : ""
3557
+ ].filter(Boolean).join(" ");
3558
+ return /* @__PURE__ */ jsx(
3559
+ "button",
3560
+ {
3561
+ type: "button",
3562
+ role: "gridcell",
3563
+ className: cls,
3564
+ disabled: isFuture,
3565
+ onClick: () => !isFuture && onSelectDate(cellCoord),
3566
+ onMouseEnter: onHoverDate && !isFuture ? () => onHoverDate(cellCoord) : void 0,
3567
+ children: cell.day
3568
+ },
3569
+ i
3570
+ );
3571
+ })
3572
+ }
3573
+ )
3574
+ ] });
3575
+ }
3576
+ var formatLocalDateLabel = (s) => {
3577
+ if (!s) return "";
3578
+ const d = new Date(s);
3579
+ if (Number.isNaN(d.getTime())) return s;
3580
+ const pad22 = (n) => String(n).padStart(2, "0");
3581
+ return `${d.getFullYear()}/${pad22(d.getMonth() + 1)}/${pad22(d.getDate())} ${pad22(d.getHours())}:${pad22(d.getMinutes())}:${pad22(d.getSeconds())}`;
3582
+ };
3583
+ var triggerLabelFor = (value, placeholder) => {
3584
+ if (value?.kind === "relative" && value.duration) {
3585
+ const m = DURATION_RX.exec(value.duration);
3586
+ if (m) {
3587
+ const n = parseInt(m[1], 10);
3588
+ const unit = m[2].toLowerCase();
3589
+ return `Last ${n} ${UNIT_LABELS[unit]?.toLowerCase() || unit}`;
3590
+ }
3591
+ return `Last ${value.duration}`;
3592
+ }
3593
+ if (value?.kind === "absolute") {
3594
+ if (!value.from && !value.to) return placeholder;
3595
+ const from = value.from ? formatLocalDateLabel(value.from) : "earliest";
3596
+ const to = value.to ? formatLocalDateLabel(value.to) : "now";
3597
+ return `${from} \u2192 ${to}`;
3598
+ }
3599
+ return placeholder;
3600
+ };
3601
+ function TimeWindowPicker({
3602
+ value,
3603
+ onChange,
3604
+ allowRelative = true,
3605
+ allowAbsolute = true,
3606
+ placeholder = "Pick a time window",
3607
+ ariaLabel,
3608
+ className = "",
3609
+ disabled = false,
3610
+ size = "md"
3611
+ }) {
3612
+ const triggerLabel = triggerLabelFor(value, placeholder);
3613
+ const isSet = !!(value?.kind === "relative" && value.duration || value?.kind === "absolute" && (value.from || value.to));
3614
+ const wrapClass = [
3615
+ "crewlet-time-window",
3616
+ `crewlet-time-window--${size}`,
3617
+ isSet ? "is-set" : "",
3618
+ disabled ? "is-disabled" : "",
3619
+ className
3620
+ ].filter(Boolean).join(" ");
3621
+ return /* @__PURE__ */ jsx(
3622
+ Popover,
3623
+ {
3624
+ align: "start",
3625
+ side: "bottom",
3626
+ width: "auto",
3627
+ className: "crewlet-time-window__popover",
3628
+ trigger: (open, toggle) => /* @__PURE__ */ jsxs(
3629
+ "button",
3630
+ {
3631
+ type: "button",
3632
+ className: `${wrapClass}${open ? " is-open" : ""}`,
3633
+ onClick: () => {
3634
+ if (!disabled) toggle();
3635
+ },
3636
+ disabled,
3637
+ "aria-haspopup": "dialog",
3638
+ "aria-expanded": open,
3639
+ "aria-label": ariaLabel || "Time window",
3640
+ children: [
3641
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-time-window__icon", "aria-hidden": true, children: "schedule" }),
3642
+ /* @__PURE__ */ jsx("span", { className: `crewlet-time-window__label${isSet ? "" : " is-placeholder"}`, children: triggerLabel }),
3643
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined crewlet-time-window__chevron", "aria-hidden": true, children: open ? "expand_less" : "expand_more" })
3644
+ ]
3645
+ }
3646
+ ),
3647
+ children: (close) => /* @__PURE__ */ jsx(
3648
+ PanelContent,
3649
+ {
3650
+ value,
3651
+ onChange,
3652
+ close,
3653
+ allowRelative,
3654
+ allowAbsolute
3655
+ }
3656
+ )
3657
+ }
3658
+ );
3659
+ }
3660
+ function PanelContent({ value, onChange, close, allowRelative, allowAbsolute }) {
3661
+ const initialMode = (() => {
3662
+ if (value?.kind === "absolute" && allowAbsolute) return "absolute";
3663
+ if (allowRelative) return "relative";
3664
+ return "absolute";
3665
+ })();
3666
+ const [mode, setMode] = useState(initialMode);
3667
+ const [draft, setDraft] = useState(value || { kind: initialMode });
3668
+ const handleApply = () => {
3669
+ onChange(draft);
3670
+ close();
3671
+ };
3672
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__panel", children: [
3673
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__header", children: [
3674
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__tabs", role: "tablist", "aria-label": "Time window mode", children: [
3675
+ allowAbsolute && /* @__PURE__ */ jsx(
3676
+ "button",
3677
+ {
3678
+ type: "button",
3679
+ role: "tab",
3680
+ "aria-selected": mode === "absolute",
3681
+ className: `crewlet-time-window__tab${mode === "absolute" ? " is-active" : ""}`,
3682
+ onClick: () => setMode("absolute"),
3683
+ children: "Absolute"
3684
+ }
3685
+ ),
3686
+ allowRelative && /* @__PURE__ */ jsx(
3687
+ "button",
3688
+ {
3689
+ type: "button",
3690
+ role: "tab",
3691
+ "aria-selected": mode === "relative",
3692
+ className: `crewlet-time-window__tab${mode === "relative" ? " is-active" : ""}`,
3693
+ onClick: () => setMode("relative"),
3694
+ children: "Relative"
3695
+ }
3696
+ )
3697
+ ] }),
3698
+ /* @__PURE__ */ jsx("div", { className: "crewlet-time-window__tz", children: /* @__PURE__ */ jsx(
3699
+ Select,
3700
+ {
3701
+ value: draft.timezone || "local",
3702
+ onChange: (v) => setDraft({ ...draft, timezone: v }),
3703
+ options: [
3704
+ { value: "local", label: "Local time zone" },
3705
+ { value: "utc", label: "UTC" }
3706
+ ],
3707
+ size: "sm",
3708
+ align: "right",
3709
+ ariaLabel: "Time zone"
3710
+ }
3711
+ ) })
3712
+ ] }),
3713
+ mode === "absolute" && allowAbsolute && /* @__PURE__ */ jsx(AbsolutePane, { draft, setDraft }),
3714
+ mode === "relative" && allowRelative && /* @__PURE__ */ jsx(RelativePane, { draft, setDraft }),
3715
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__footer", children: [
3716
+ /* @__PURE__ */ jsx(
3717
+ "button",
3718
+ {
3719
+ type: "button",
3720
+ className: "crewlet-time-window__footer-btn",
3721
+ onClick: close,
3722
+ children: "Cancel"
3723
+ }
3724
+ ),
3725
+ /* @__PURE__ */ jsx(
3726
+ "button",
3727
+ {
3728
+ type: "button",
3729
+ className: "crewlet-time-window__footer-btn is-primary",
3730
+ onClick: handleApply,
3731
+ children: "Apply"
3732
+ }
3733
+ )
3734
+ ] })
3735
+ ] });
3736
+ }
3737
+ function AbsolutePane({ draft, setDraft }) {
3738
+ const fromParsed = parseDateTime(draft.from);
3739
+ const toParsed = parseDateTime(draft.to);
3740
+ const [leftMonth, setLeftMonth] = useState(() => {
3741
+ if (fromParsed) return { year: fromParsed.year, month: fromParsed.month };
3742
+ const now = /* @__PURE__ */ new Date();
3743
+ return { year: now.getFullYear(), month: now.getMonth() };
3744
+ });
3745
+ const rightMonth = useMemo(() => {
3746
+ let m = leftMonth.month + 1;
3747
+ let y = leftMonth.year;
3748
+ if (m > 11) {
3749
+ m = 0;
3750
+ y += 1;
3751
+ }
3752
+ return { year: y, month: m };
3753
+ }, [leftMonth]);
3754
+ const stepLeft = (delta) => {
3755
+ let m = leftMonth.month + delta;
3756
+ let y = leftMonth.year;
3757
+ while (m < 0) {
3758
+ m += 12;
3759
+ y -= 1;
3760
+ }
3761
+ while (m > 11) {
3762
+ m -= 12;
3763
+ y += 1;
3764
+ }
3765
+ setLeftMonth({ year: y, month: m });
3766
+ };
3767
+ const commitFromDate = (s) => {
3768
+ const date = parseDateOnly(s);
3769
+ if (!date) return;
3770
+ const time = fromParsed ? { hour: fromParsed.hour, minute: fromParsed.minute, second: fromParsed.second } : { hour: 0, minute: 0, second: 0 };
3771
+ setDraft({
3772
+ ...draft,
3773
+ kind: "absolute",
3774
+ from: formatDateTime({ ...date, ...time }),
3775
+ to: draft.to || ""
3776
+ });
3777
+ };
3778
+ const commitFromTime = (s) => {
3779
+ const time = parseTimeOnly(s);
3780
+ if (!time) return;
3781
+ const date = fromParsed ? { year: fromParsed.year, month: fromParsed.month, day: fromParsed.day } : null;
3782
+ if (!date) return;
3783
+ setDraft({
3784
+ ...draft,
3785
+ kind: "absolute",
3786
+ from: formatDateTime({ ...date, ...time }),
3787
+ to: draft.to || ""
3788
+ });
3789
+ };
3790
+ const commitToDate = (s) => {
3791
+ const date = parseDateOnly(s);
3792
+ if (!date) return;
3793
+ const time = toParsed ? { hour: toParsed.hour, minute: toParsed.minute, second: toParsed.second } : { hour: 23, minute: 59, second: 59 };
3794
+ setDraft({
3795
+ ...draft,
3796
+ kind: "absolute",
3797
+ from: draft.from || "",
3798
+ to: formatDateTime({ ...date, ...time })
3799
+ });
3800
+ };
3801
+ const commitToTime = (s) => {
3802
+ const time = parseTimeOnly(s);
3803
+ if (!time) return;
3804
+ const date = toParsed ? { year: toParsed.year, month: toParsed.month, day: toParsed.day } : null;
3805
+ if (!date) return;
3806
+ setDraft({
3807
+ ...draft,
3808
+ kind: "absolute",
3809
+ from: draft.from || "",
3810
+ to: formatDateTime({ ...date, ...time })
3811
+ });
3812
+ };
3813
+ const fromCoord = fromParsed ? { year: fromParsed.year, month: fromParsed.month, day: fromParsed.day } : null;
3814
+ const toCoord = toParsed ? { year: toParsed.year, month: toParsed.month, day: toParsed.day } : null;
3815
+ const [phase, setPhase] = useState(
3816
+ fromCoord && !toCoord ? "end" : "start"
3817
+ );
3818
+ const [hoverCoord, setHoverCoord] = useState(null);
3819
+ const pickDay = (date) => {
3820
+ if (phase === "start") {
3821
+ const time = fromParsed ?? { hour: 0, minute: 0, second: 0 };
3822
+ setDraft({
3823
+ ...draft,
3824
+ kind: "absolute",
3825
+ from: formatDateTime({ ...date, hour: time.hour, minute: time.minute, second: time.second }),
3826
+ to: ""
3827
+ });
3828
+ setPhase("end");
3829
+ return;
3830
+ }
3831
+ const fromCmp = fromCoord ? compareDates2(date, fromCoord) : 0;
3832
+ if (fromCoord && fromCmp < 0) {
3833
+ const toTime = { hour: 23, minute: 59, second: 59 };
3834
+ setDraft({
3835
+ ...draft,
3836
+ kind: "absolute",
3837
+ from: formatDateTime({ ...date, hour: 0, minute: 0, second: 0 }),
3838
+ to: formatDateTime({ ...fromCoord, hour: toTime.hour, minute: toTime.minute, second: toTime.second })
3839
+ });
3840
+ } else {
3841
+ const time = toParsed ?? { hour: 23, minute: 59, second: 59 };
3842
+ setDraft({
3843
+ ...draft,
3844
+ kind: "absolute",
3845
+ from: draft.from || "",
3846
+ to: formatDateTime({ ...date, hour: time.hour, minute: time.minute, second: time.second })
3847
+ });
3848
+ }
3849
+ setPhase("start");
3850
+ setHoverCoord(null);
3851
+ };
3852
+ const previewEnd = toCoord ?? (phase === "end" ? hoverCoord : null);
3853
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__absolute", children: [
3854
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__cal-row", children: [
3855
+ /* @__PURE__ */ jsx(
3856
+ Calendar,
3857
+ {
3858
+ year: leftMonth.year,
3859
+ month: leftMonth.month,
3860
+ rangeStart: fromCoord,
3861
+ rangeEnd: previewEnd,
3862
+ onSelectDate: pickDay,
3863
+ onHoverDate: setHoverCoord,
3864
+ onPrev: () => stepLeft(-1)
3865
+ }
3866
+ ),
3867
+ /* @__PURE__ */ jsx(
3868
+ Calendar,
3869
+ {
3870
+ year: rightMonth.year,
3871
+ month: rightMonth.month,
3872
+ rangeStart: fromCoord,
3873
+ rangeEnd: previewEnd,
3874
+ onSelectDate: pickDay,
3875
+ onHoverDate: setHoverCoord,
3876
+ onNext: () => stepLeft(1)
3877
+ }
3878
+ )
3879
+ ] }),
3880
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__inputs", children: [
3881
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__field", children: [
3882
+ /* @__PURE__ */ jsx("span", { className: "crewlet-time-window__field-label", children: "Start date and time" }),
3883
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__field-row", children: [
3884
+ /* @__PURE__ */ jsx(
3885
+ MaskedInput,
3886
+ {
3887
+ mask: "YYYY/MM/DD",
3888
+ value: fromParsed ? formatDateOnly(fromParsed) : "",
3889
+ onCommit: commitFromDate,
3890
+ digitFilter: dateDigitFilter,
3891
+ validate: isValidDateInput,
3892
+ segments: DATE_SEGMENTS,
3893
+ className: "is-date",
3894
+ ariaLabel: "Start date"
3895
+ }
3896
+ ),
3897
+ /* @__PURE__ */ jsx(
3898
+ MaskedInput,
3899
+ {
3900
+ mask: "HH:MM:SS",
3901
+ value: fromParsed ? formatTimeOnly(fromParsed) : "",
3902
+ onCommit: commitFromTime,
3903
+ digitFilter: timeDigitFilter,
3904
+ validate: isValidTimeInput,
3905
+ segments: TIME_SEGMENTS,
3906
+ className: "is-time",
3907
+ ariaLabel: "Start time"
3908
+ }
3909
+ )
3910
+ ] })
3911
+ ] }),
3912
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__field", children: [
3913
+ /* @__PURE__ */ jsx("span", { className: "crewlet-time-window__field-label", children: "End date and time" }),
3914
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__field-row", children: [
3915
+ /* @__PURE__ */ jsx(
3916
+ MaskedInput,
3917
+ {
3918
+ mask: "YYYY/MM/DD",
3919
+ value: toParsed ? formatDateOnly(toParsed) : "",
3920
+ onCommit: commitToDate,
3921
+ digitFilter: dateDigitFilter,
3922
+ validate: isValidDateInput,
3923
+ segments: DATE_SEGMENTS,
3924
+ className: "is-date",
3925
+ ariaLabel: "End date"
3926
+ }
3927
+ ),
3928
+ /* @__PURE__ */ jsx(
3929
+ MaskedInput,
3930
+ {
3931
+ mask: "HH:MM:SS",
3932
+ value: toParsed ? formatTimeOnly(toParsed) : "",
3933
+ onCommit: commitToTime,
3934
+ digitFilter: timeDigitFilter,
3935
+ validate: isValidTimeInput,
3936
+ segments: TIME_SEGMENTS,
3937
+ className: "is-time",
3938
+ ariaLabel: "End time"
3939
+ }
3940
+ )
3941
+ ] })
3942
+ ] })
3943
+ ] })
3944
+ ] });
3945
+ }
3946
+ function RelativePane({ draft, setDraft }) {
3947
+ const m = draft.kind === "relative" && draft.duration ? DURATION_RX.exec(draft.duration) : null;
3948
+ const draftN = m ? parseInt(m[1], 10) : 60;
3949
+ const draftUnit = m ? m[2].toLowerCase() : "m";
3950
+ const draftDuration = draft.kind === "relative" ? draft.duration : "";
3951
+ const [customN, setCustomN] = useState(String(draftN));
3952
+ const [customUnit, setCustomUnit] = useState(draftUnit);
3953
+ useEffect(() => {
3954
+ if (m) {
3955
+ setCustomN(String(parseInt(m[1], 10)));
3956
+ setCustomUnit(m[2].toLowerCase());
3957
+ }
3958
+ }, [draftDuration]);
3959
+ const pickChip = (n, unit) => {
3960
+ setDraft({ kind: "relative", duration: `${n}${unit}` });
3961
+ };
3962
+ const commitCustom = (n, unit) => {
3963
+ const parsedN = parseInt(n, 10);
3964
+ if (!Number.isFinite(parsedN) || parsedN <= 0) return;
3965
+ setDraft({ kind: "relative", duration: `${parsedN}${unit}` });
3966
+ };
3967
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__relative", children: [
3968
+ CHIP_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__chip-row", children: [
3969
+ /* @__PURE__ */ jsx("span", { className: "crewlet-time-window__chip-label", children: group.label }),
3970
+ /* @__PURE__ */ jsx("div", { className: "crewlet-time-window__chips", children: group.values.map((n) => {
3971
+ const dur = `${n}${group.unit}`;
3972
+ const active = draftDuration === dur;
3973
+ return /* @__PURE__ */ jsx(
3974
+ "button",
3975
+ {
3976
+ type: "button",
3977
+ className: `crewlet-time-window__chip${active ? " is-active" : ""}`,
3978
+ onClick: () => pickChip(n, group.unit),
3979
+ children: n
3980
+ },
3981
+ n
3982
+ );
3983
+ }) })
3984
+ ] }, group.unit)),
3985
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-time-window__custom-row", children: [
3986
+ /* @__PURE__ */ jsx(
3987
+ "input",
3988
+ {
3989
+ type: "text",
3990
+ inputMode: "numeric",
3991
+ pattern: "[0-9]*",
3992
+ className: "crewlet-time-window__custom-input",
3993
+ value: customN,
3994
+ onChange: (e) => {
3995
+ const next = e.target.value.replace(/[^0-9]/g, "");
3996
+ setCustomN(next);
3997
+ commitCustom(next, customUnit);
3998
+ },
3999
+ "aria-label": "Custom amount"
4000
+ }
4001
+ ),
4002
+ /* @__PURE__ */ jsx(
4003
+ Select,
4004
+ {
4005
+ value: customUnit,
4006
+ onChange: (v) => {
4007
+ const unit = String(v);
4008
+ setCustomUnit(unit);
4009
+ commitCustom(customN, unit);
4010
+ },
4011
+ options: [
4012
+ { value: "m", label: "Minutes" },
4013
+ { value: "h", label: "Hours" },
4014
+ { value: "d", label: "Days" },
4015
+ { value: "w", label: "Weeks" }
4016
+ ],
4017
+ size: "sm",
4018
+ ariaLabel: "Custom unit"
4019
+ }
4020
+ )
4021
+ ] })
4022
+ ] });
4023
+ }
4024
+ function MaskedInput({
4025
+ mask,
4026
+ value,
4027
+ onCommit,
4028
+ digitFilter,
4029
+ validate,
4030
+ segments,
4031
+ className,
4032
+ ariaLabel
4033
+ }) {
4034
+ const inputRef = useRef(null);
4035
+ const isSepAt = (pos) => {
4036
+ const c = mask[pos];
4037
+ return !!c && !/[A-Za-z]/.test(c);
4038
+ };
4039
+ const projectValueToDisplay = (v) => {
4040
+ let out = "";
4041
+ let vi = 0;
4042
+ for (let i = 0; i < mask.length; i += 1) {
4043
+ if (isSepAt(i)) {
4044
+ out += mask[i];
4045
+ if (vi < v.length && v[vi] === mask[i]) vi += 1;
4046
+ continue;
4047
+ }
4048
+ while (vi < v.length && !/\d/.test(v[vi])) vi += 1;
4049
+ if (vi < v.length) {
4050
+ out += v[vi];
4051
+ vi += 1;
4052
+ } else {
4053
+ out += mask[i];
4054
+ }
4055
+ }
4056
+ return out;
4057
+ };
4058
+ const [display, setDisplay] = useState(() => projectValueToDisplay(value));
4059
+ useEffect(() => {
4060
+ setDisplay(projectValueToDisplay(value));
4061
+ }, [value]);
4062
+ const hasAnyDigit = /\d/.test(display);
4063
+ const nextDigitSlot = (from) => {
4064
+ let p = from;
4065
+ while (p < mask.length && isSepAt(p)) p += 1;
4066
+ return p < mask.length ? p : -1;
4067
+ };
4068
+ const prevDigitSlot = (from) => {
4069
+ let p = from - 1;
4070
+ while (p >= 0 && isSepAt(p)) p -= 1;
4071
+ return p;
4072
+ };
4073
+ const setCursor = (pos) => {
4074
+ requestAnimationFrame(() => {
4075
+ const el = inputRef.current;
4076
+ if (el) el.setSelectionRange(pos, pos);
4077
+ });
4078
+ };
4079
+ const handleKeyDown = (e) => {
4080
+ const el = e.currentTarget;
4081
+ const pos = el.selectionStart ?? 0;
4082
+ if (/^\d$/.test(e.key)) {
4083
+ e.preventDefault();
4084
+ const target = nextDigitSlot(pos);
4085
+ if (target < 0) return;
4086
+ if (digitFilter && !digitFilter(e.key, target, display)) {
4087
+ return;
4088
+ }
4089
+ const next = display.substring(0, target) + e.key + display.substring(target + 1);
4090
+ setDisplay(next);
4091
+ let cursor = target + 1;
4092
+ while (cursor < mask.length && isSepAt(cursor)) cursor += 1;
4093
+ setCursor(cursor);
4094
+ return;
4095
+ }
4096
+ if (e.key === "Backspace") {
4097
+ e.preventDefault();
4098
+ const target = prevDigitSlot(pos);
4099
+ if (target < 0) return;
4100
+ const next = display.substring(0, target) + mask[target] + display.substring(target + 1);
4101
+ setDisplay(next);
4102
+ setCursor(target);
4103
+ return;
4104
+ }
4105
+ if (e.key === "Delete") {
4106
+ e.preventDefault();
4107
+ if (pos < mask.length && !isSepAt(pos)) {
4108
+ const next = display.substring(0, pos) + mask[pos] + display.substring(pos + 1);
4109
+ setDisplay(next);
4110
+ }
4111
+ return;
4112
+ }
4113
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && segments && segments.length > 0) {
4114
+ const seg = segments.find((s) => pos >= s.start && pos <= s.end + 1);
4115
+ if (!seg) return;
4116
+ e.preventDefault();
4117
+ const segText = display.substring(seg.start, seg.end + 1);
4118
+ const hasDigits = /\d/.test(segText);
4119
+ const width = seg.end - seg.start + 1;
4120
+ let nextVal;
4121
+ if (!hasDigits) {
4122
+ nextVal = seg.min;
4123
+ } else {
4124
+ const numeric = segText.replace(/[^0-9]/g, "0");
4125
+ const current = parseInt(numeric, 10);
4126
+ nextVal = e.key === "ArrowUp" ? current + 1 : current - 1;
4127
+ if (nextVal > seg.max) nextVal = seg.max;
4128
+ if (nextVal < seg.min) nextVal = seg.min;
4129
+ }
4130
+ const padded = String(nextVal).padStart(width, "0");
4131
+ const next = display.substring(0, seg.start) + padded + display.substring(seg.end + 1);
4132
+ setDisplay(next);
4133
+ setCursor(seg.end + 1);
4134
+ return;
4135
+ }
4136
+ if (e.key === "ArrowLeft") {
4137
+ e.preventDefault();
4138
+ const target = prevDigitSlot(pos);
4139
+ setCursor(target < 0 ? 0 : target);
4140
+ return;
4141
+ }
4142
+ if (e.key === "ArrowRight") {
4143
+ e.preventDefault();
4144
+ const target = nextDigitSlot(pos + 1);
4145
+ setCursor(target < 0 ? mask.length : target);
4146
+ return;
4147
+ }
4148
+ if (e.key === "Home") {
4149
+ e.preventDefault();
4150
+ const target = nextDigitSlot(0);
4151
+ setCursor(target < 0 ? 0 : target);
4152
+ return;
4153
+ }
4154
+ if (e.key === "End") {
4155
+ e.preventDefault();
4156
+ let target = mask.length;
4157
+ while (target > 0 && isSepAt(target - 1)) target -= 1;
4158
+ setCursor(target);
4159
+ return;
4160
+ }
4161
+ if (e.key === "Tab" || e.key === "Enter" || e.key === "Escape") return;
4162
+ e.preventDefault();
4163
+ };
4164
+ const snapToLeftmost = (el) => {
4165
+ const target = nextDigitSlot(0);
4166
+ const pos = target < 0 ? 0 : target;
4167
+ el.setSelectionRange(pos, pos);
4168
+ };
4169
+ const handleFocus = (e) => {
4170
+ const el = e.currentTarget;
4171
+ if (!hasAnyDigit) {
4172
+ requestAnimationFrame(() => snapToLeftmost(el));
4173
+ }
4174
+ };
4175
+ const handleClick = (e) => {
4176
+ const el = e.currentTarget;
4177
+ requestAnimationFrame(() => {
4178
+ if (!hasAnyDigit) {
4179
+ snapToLeftmost(el);
4180
+ return;
4181
+ }
4182
+ const pos = el.selectionStart ?? 0;
4183
+ if (isSepAt(pos)) {
4184
+ const target = nextDigitSlot(pos);
4185
+ if (target >= 0) el.setSelectionRange(target, target);
4186
+ }
4187
+ });
4188
+ };
4189
+ const handlePaste = (e) => {
4190
+ e.preventDefault();
4191
+ const pasted = e.clipboardData.getData("text");
4192
+ const digits = pasted.replace(/\D/g, "");
4193
+ if (!digits) return;
4194
+ const el = e.currentTarget;
4195
+ const startPos = nextDigitSlot(el.selectionStart ?? 0);
4196
+ if (startPos < 0) return;
4197
+ let out = display;
4198
+ let cursor = startPos;
4199
+ let di = 0;
4200
+ while (cursor < mask.length && di < digits.length) {
4201
+ if (!isSepAt(cursor)) {
4202
+ out = out.substring(0, cursor) + digits[di] + out.substring(cursor + 1);
4203
+ di += 1;
4204
+ }
4205
+ cursor += 1;
4206
+ }
4207
+ setDisplay(out);
4208
+ setCursor(cursor);
4209
+ };
4210
+ const handleBlur = () => {
4211
+ if (validate && !validate(display)) {
4212
+ setDisplay(projectValueToDisplay(value));
4213
+ return;
4214
+ }
4215
+ onCommit(display);
4216
+ };
4217
+ const isPrecedingSegmentFilled = (sepPos) => {
4218
+ for (let p = sepPos - 1; p >= 0; p -= 1) {
4219
+ if (isSepAt(p)) break;
4220
+ if (!/\d/.test(display[p] || "")) return false;
4221
+ }
4222
+ return true;
4223
+ };
4224
+ const overlay = display.split("").map((ch, i) => {
4225
+ const sep = isSepAt(i);
4226
+ const bright = sep ? isPrecedingSegmentFilled(i) : /\d/.test(ch);
4227
+ return /* @__PURE__ */ jsx(
4228
+ "span",
4229
+ {
4230
+ className: `crewlet-time-window__masked-char ${bright ? "is-digit" : "is-mask"}`,
4231
+ children: ch
4232
+ },
4233
+ i
4234
+ );
4235
+ });
4236
+ const wrapClass = [
4237
+ "crewlet-time-window__masked-wrap",
4238
+ className || "",
4239
+ hasAnyDigit ? "" : "is-empty"
4240
+ ].filter(Boolean).join(" ");
4241
+ return /* @__PURE__ */ jsxs("div", { className: wrapClass, children: [
4242
+ /* @__PURE__ */ jsx(
4243
+ "input",
4244
+ {
4245
+ ref: inputRef,
4246
+ type: "text",
4247
+ className: "crewlet-time-window__text-input",
4248
+ value: display,
4249
+ onKeyDown: handleKeyDown,
4250
+ onFocus: handleFocus,
4251
+ onClick: handleClick,
4252
+ onPaste: handlePaste,
4253
+ onChange: () => {
4254
+ },
4255
+ onBlur: handleBlur,
4256
+ "aria-label": ariaLabel
4257
+ }
4258
+ ),
4259
+ /* @__PURE__ */ jsx("div", { className: "crewlet-time-window__masked-overlay", "aria-hidden": true, children: overlay })
4260
+ ] });
4261
+ }
4262
+ var Tag2 = ({
4263
+ variant = "neutral",
4264
+ size = "md",
4265
+ leadingIcon,
4266
+ onRemove,
4267
+ removeAriaLabel = "Remove tag",
4268
+ monospace = false,
4269
+ className = "",
4270
+ children,
4271
+ ...rest
4272
+ }) => {
4273
+ const classes = [
4274
+ "crewlet-tag",
4275
+ `crewlet-tag--${variant}`,
4276
+ `crewlet-tag--${size}`,
4277
+ monospace ? "crewlet-tag--monospace" : "",
4278
+ onRemove ? "is-removable" : "",
4279
+ className
4280
+ ].filter(Boolean).join(" ");
4281
+ return /* @__PURE__ */ jsxs("span", { ...rest, className: classes, children: [
4282
+ leadingIcon ? /* @__PURE__ */ jsx("span", { className: "crewlet-tag__icon", "aria-hidden": true, children: leadingIcon }) : null,
4283
+ /* @__PURE__ */ jsx("span", { className: "crewlet-tag__label", children }),
4284
+ onRemove ? /* @__PURE__ */ jsx(
4285
+ "button",
4286
+ {
4287
+ type: "button",
4288
+ className: "crewlet-tag__remove",
4289
+ onClick: (event) => {
4290
+ event.stopPropagation();
4291
+ onRemove();
4292
+ },
4293
+ "aria-label": removeAriaLabel,
4294
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xD7" })
4295
+ }
4296
+ ) : null
4297
+ ] });
4298
+ };
4299
+ var TagsInput = forwardRef(function TagsInput2({
4300
+ value,
4301
+ onChange,
4302
+ placeholder = "Add a tag, press Enter or comma",
4303
+ tagVariant = "neutral",
4304
+ monospace = false,
4305
+ submitKeys = ["Enter", ","],
4306
+ disabled = false,
4307
+ className = "",
4308
+ id,
4309
+ dedupe = true
4310
+ }, ref) {
4311
+ const [draft, setDraft] = useState("");
4312
+ const inputRef = useRef(null);
4313
+ useImperativeHandle(ref, () => ({
4314
+ focus: () => inputRef.current?.focus()
4315
+ }));
4316
+ const commit = (raw) => {
4317
+ const next = raw.trim();
4318
+ if (!next) return;
4319
+ if (dedupe && value.some((v) => v.toLowerCase() === next.toLowerCase())) {
4320
+ setDraft("");
4321
+ return;
4322
+ }
4323
+ onChange([...value, next]);
4324
+ setDraft("");
4325
+ };
4326
+ const onKeyDown = (event) => {
4327
+ if (submitKeys.includes(event.key)) {
4328
+ event.preventDefault();
4329
+ commit(draft);
4330
+ return;
4331
+ }
4332
+ if (event.key === "Backspace" && draft === "" && value.length > 0) {
4333
+ event.preventDefault();
4334
+ onChange(value.slice(0, -1));
4335
+ }
4336
+ };
4337
+ const onPaste = (event) => {
4338
+ const text = event.clipboardData.getData("text");
4339
+ if (!text.includes(",") && !text.includes("\n")) return;
4340
+ event.preventDefault();
4341
+ const parts = text.split(/[,\n]+/).map((p) => p.trim()).filter(Boolean);
4342
+ if (!parts.length) return;
4343
+ const set = dedupe ? new Set(value.map((v) => v.toLowerCase())) : null;
4344
+ const added = [];
4345
+ for (const p of parts) {
4346
+ if (set && set.has(p.toLowerCase())) continue;
4347
+ added.push(p);
4348
+ set?.add(p.toLowerCase());
4349
+ }
4350
+ if (added.length) onChange([...value, ...added]);
4351
+ };
4352
+ const onInputChange = (event) => {
4353
+ setDraft(event.target.value);
4354
+ };
4355
+ const onContainerClick = () => {
4356
+ inputRef.current?.focus();
4357
+ };
4358
+ const removeAt = (index) => {
4359
+ onChange(value.filter((_, i) => i !== index));
4360
+ };
4361
+ const classes = ["crewlet-tags-input", disabled ? "is-disabled" : "", className].filter(Boolean).join(" ");
4362
+ return /* @__PURE__ */ jsxs("div", { className: classes, onClick: onContainerClick, children: [
4363
+ value.map((tag, index) => /* @__PURE__ */ jsx(
4364
+ Tag2,
4365
+ {
4366
+ variant: tagVariant,
4367
+ monospace,
4368
+ onRemove: disabled ? void 0 : () => removeAt(index),
4369
+ removeAriaLabel: `Remove ${tag}`,
4370
+ children: tag
4371
+ },
4372
+ `${index}-${tag}`
4373
+ )),
4374
+ /* @__PURE__ */ jsx(
4375
+ "input",
4376
+ {
4377
+ ref: inputRef,
4378
+ id,
4379
+ className: "crewlet-tags-input__field",
4380
+ type: "text",
4381
+ value: draft,
4382
+ placeholder,
4383
+ disabled,
4384
+ onChange: onInputChange,
4385
+ onKeyDown,
4386
+ onPaste,
4387
+ onBlur: () => commit(draft)
4388
+ }
4389
+ )
4390
+ ] });
4391
+ });
4392
+ var ICONS = {
4393
+ info: "info",
4394
+ success: "check_circle",
4395
+ warn: "warning",
4396
+ error: "error"
4397
+ };
4398
+ var TITLES = {
4399
+ info: null,
4400
+ success: "Success",
4401
+ warn: "Heads up",
4402
+ error: "Something went wrong"
4403
+ };
4404
+ var isToastAction = (value) => {
4405
+ if (!value || typeof value !== "object") return false;
4406
+ const v = value;
4407
+ return "label" in v && ("onClick" in v || "href" in v);
4408
+ };
4409
+ var ToastItem = ({ toast, onDismiss, renderAction }) => {
4410
+ const variant = toast.variant || "info";
4411
+ const knownVariant = ["info", "success", "warn", "error"].includes(variant);
4412
+ const classes = [
4413
+ "crewlet-toast",
4414
+ `crewlet-toast--${toast.variant ?? "info"}`
4415
+ ].filter(Boolean).join(" ");
4416
+ const role = variant === "error" || toast.variant === "quota" ? "alert" : "status";
4417
+ const icon = toast.icon ?? (knownVariant ? /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: ICONS[variant] }) : null);
4418
+ const fallbackTitle = knownVariant ? TITLES[variant] : null;
4419
+ const renderStandardAction = () => {
4420
+ if (toast.action === void 0 || toast.action === null) return null;
4421
+ if (!isToastAction(toast.action)) {
4422
+ return /* @__PURE__ */ jsx("div", { className: "crewlet-toast__actions", children: toast.action });
4423
+ }
4424
+ const action = toast.action;
4425
+ if (renderAction) {
4426
+ const node = renderAction(action, { onDismiss });
4427
+ return /* @__PURE__ */ jsx("div", { className: "crewlet-toast__actions", children: node });
4428
+ }
4429
+ if (action.href) {
4430
+ const isExternal = action.external ?? /^https?:\/\//.test(action.href);
4431
+ return /* @__PURE__ */ jsx("div", { className: "crewlet-toast__actions", children: /* @__PURE__ */ jsx(
4432
+ "a",
4433
+ {
4434
+ className: "crewlet-toast__action-link",
4435
+ href: action.href,
4436
+ target: isExternal ? "_blank" : void 0,
4437
+ rel: isExternal ? "noreferrer" : void 0,
4438
+ onClick: onDismiss,
4439
+ children: action.label
4440
+ }
4441
+ ) });
4442
+ }
4443
+ return /* @__PURE__ */ jsx("div", { className: "crewlet-toast__actions", children: /* @__PURE__ */ jsx(
4444
+ "button",
4445
+ {
4446
+ type: "button",
4447
+ className: "crewlet-toast__action-btn",
4448
+ onClick: () => {
4449
+ action.onClick?.();
4450
+ onDismiss();
4451
+ },
4452
+ children: action.label
4453
+ }
4454
+ ) });
4455
+ };
4456
+ return /* @__PURE__ */ jsxs("div", { className: classes, role, children: [
4457
+ toast.renderBody ? toast.renderBody({ onDismiss, toast }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4458
+ icon ? /* @__PURE__ */ jsx("span", { className: "crewlet-toast__icon", children: icon }) : null,
4459
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-toast__body", children: [
4460
+ toast.title || fallbackTitle ? /* @__PURE__ */ jsx("div", { className: "crewlet-toast__title", children: toast.title || fallbackTitle }) : null,
4461
+ toast.message ? /* @__PURE__ */ jsx("div", { className: "crewlet-toast__message", children: toast.message }) : null,
4462
+ renderStandardAction()
4463
+ ] })
4464
+ ] }),
4465
+ /* @__PURE__ */ jsx(
4466
+ "button",
4467
+ {
4468
+ type: "button",
4469
+ className: "crewlet-toast__dismiss",
4470
+ onClick: onDismiss,
4471
+ "aria-label": "Dismiss notification",
4472
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: "close" })
4473
+ }
4474
+ ),
4475
+ toast.duration && toast.duration > 0 ? /* @__PURE__ */ jsx(
4476
+ "div",
4477
+ {
4478
+ className: "crewlet-toast__progress",
4479
+ style: { animationDuration: `${toast.duration}ms` },
4480
+ onAnimationEnd: onDismiss,
4481
+ "aria-hidden": true
4482
+ },
4483
+ toast.version ?? 0
4484
+ ) : null
4485
+ ] });
4486
+ };
4487
+ var Toaster = ({
4488
+ toasts,
4489
+ onDismiss,
4490
+ renderAction,
4491
+ position = "bottom-right",
4492
+ className = ""
4493
+ }) => {
4494
+ if (!toasts?.length) return null;
4495
+ const style = void 0;
4496
+ const classes = ["crewlet-toaster", `crewlet-toaster--${position}`, className].filter(Boolean).join(" ");
4497
+ return /* @__PURE__ */ jsx(
4498
+ "div",
4499
+ {
4500
+ className: classes,
4501
+ role: "region",
4502
+ "aria-live": "polite",
4503
+ "aria-label": "Notifications",
4504
+ style,
4505
+ children: toasts.map((toast) => /* @__PURE__ */ jsx(
4506
+ ToastItem,
4507
+ {
4508
+ toast,
4509
+ onDismiss: () => onDismiss(toast.id),
4510
+ renderAction
4511
+ },
4512
+ toast.id
4513
+ ))
4514
+ }
4515
+ );
4516
+ };
4517
+
4518
+ export { Accordion, AccordionItem, AppShell, Avatar, Button, Callout, Card, Checkbox, CodeBlock, ConfirmModal, Container, Copyable, CopyableCell, DataTable, DateTimePicker, Eyebrow, FormField, FormRow, IconButton, ImageUpload, Input, Kbd, Label, Modal, Popover, PricingCard, Section, Select, Skeleton, StatCard, Table, Tabs, Tag2 as Tag, TagsInput, Textarea, TimeWindowPicker, Toaster, getInitials, parseTimeWindowDuration, resolveTimeWindow };