@caratlogic/design-system 0.1.5 → 0.1.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/ConfirmDialog/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAoE/C,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"ConfirmDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/ConfirmDialog/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuE/C,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/Modal/Modal.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,eAAe,CAAC;AAE3D,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS3D;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,IAAW,EACX,eAAuB,EACvB,SAAS,EACT,aAAa,GACd,EAAE,UAAU,2CA4CZ"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/Modal/Modal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,eAAe,CAAC;AAE3D,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS3D;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,IAAW,EACX,eAAuB,EACvB,SAAS,EACT,aAAa,GACd,EAAE,UAAU,2CA8CZ"}
@@ -0,0 +1,3 @@
1
+ import type { TimePickerProps } from './TimePicker.types';
2
+ export declare function TimePicker({ value, onChange, onBlur, name, className, disabled, placeholder, use24Hour, minuteStep, hideFooter, }: TimePickerProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=TimePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimePicker.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TimePicker/TimePicker.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAY1D,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,WAA2B,EAC3B,SAAiB,EACjB,UAAc,EACd,UAAkB,GACnB,EAAE,eAAe,2CAiJjB"}
@@ -0,0 +1,8 @@
1
+ import type { TimeColumnProps } from './TimePicker.types';
2
+ /**
3
+ * One scrolling column of the picker (hours, minutes or period). The selected
4
+ * row is scrolled into view whenever it changes, so reopening the popover lands
5
+ * on the current value instead of at midnight.
6
+ */
7
+ export declare function TimeColumn({ label, options, selected, onSelect, }: TimeColumnProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=TimePicker.parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimePicker.parts.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TimePicker/TimePicker.parts.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,OAAO,EACP,QAAQ,EACR,QAAQ,GACT,EAAE,eAAe,2CAmDjB"}
@@ -0,0 +1,33 @@
1
+ export interface TimePickerProps {
2
+ /** 24-hour `HH:mm` (`HH:mm:ss` is accepted and truncated). Empty when unset. */
3
+ value?: string;
4
+ /** Fires with `HH:mm`, or `undefined` when cleared. */
5
+ onChange: (value: string | undefined) => void;
6
+ onBlur?: () => void;
7
+ name?: string;
8
+ className?: string;
9
+ disabled?: boolean;
10
+ placeholder?: string;
11
+ /** Show and pick in 24-hour time. The stored value is always 24-hour. */
12
+ use24Hour?: boolean;
13
+ /** Granularity of the minute column. @default 1 */
14
+ minuteStep?: number;
15
+ /** Hide the Clear / Now row under the columns. */
16
+ hideFooter?: boolean;
17
+ }
18
+ export interface ParsedTime {
19
+ /** 0–23. */
20
+ hour: number;
21
+ /** 0–59. */
22
+ minute: number;
23
+ }
24
+ export interface TimeColumnProps {
25
+ label: string;
26
+ options: {
27
+ value: number;
28
+ label: string;
29
+ }[];
30
+ selected: number | null;
31
+ onSelect: (value: number) => void;
32
+ }
33
+ //# sourceMappingURL=TimePicker.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimePicker.types.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TimePicker/TimePicker.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC"}
@@ -0,0 +1,20 @@
1
+ import type { ParsedTime } from './TimePicker.types';
2
+ /** Reads `HH:mm` / `HH:mm:ss`. Returns null for anything out of range. */
3
+ export declare function parseTimeValue(value?: string): ParsedTime | null;
4
+ /** The stored form — always 24-hour, whatever the display format. */
5
+ export declare function toTimeValue(hour: number, minute: number): string;
6
+ export declare function formatTimeDisplay(time: ParsedTime, use24Hour: boolean): string;
7
+ export declare function buildHourOptions(use24Hour: boolean): {
8
+ value: number;
9
+ label: string;
10
+ }[];
11
+ export declare function buildMinuteOptions(step: number): {
12
+ value: number;
13
+ label: string;
14
+ }[];
15
+ /** The hour as the 12-hour column shows it (12, 1…11). */
16
+ export declare function toHour12(hour: number): number;
17
+ /** Rebuilds a 24-hour hour from a 12-hour column value and its period. */
18
+ export declare function fromHour12(hour12: number, period: 'AM' | 'PM'): number;
19
+ export declare function periodOf(hour: number): 'AM' | 'PM';
20
+ //# sourceMappingURL=TimePicker.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimePicker.utils.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TimePicker/TimePicker.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMrD,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAShE;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,OAAO,GACjB,MAAM,CAMR;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,OAAO,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAYpC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAOpC;AAED,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAGtE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAElD"}
@@ -0,0 +1,3 @@
1
+ export { TimePicker } from './TimePicker';
2
+ export type { TimePickerProps } from './TimePicker.types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TimePicker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.cjs.js CHANGED
@@ -4375,13 +4375,19 @@ const notify = {
4375
4375
  promise: sonner.toast.promise.bind(sonner.toast),
4376
4376
  };
4377
4377
 
4378
+ /**
4379
+ * Only overlays that actually lock `<body>` count. `[data-radix-popper-content-wrapper]`
4380
+ * is deliberately absent: a tooltip renders into one, so including it made an
4381
+ * open tooltip — say, on the Edit button you just clicked — look like a live
4382
+ * modal and suppress the release, leaving the page unclickable. Modal poppers
4383
+ * (menus, selects) still match through their roles.
4384
+ */
4378
4385
  const OVERLAY_CONTENT_SELECTOR = [
4379
4386
  '[data-slot="dialog-content"]',
4380
4387
  '[role="dialog"]',
4381
4388
  '[role="alertdialog"]',
4382
4389
  '[role="menu"]',
4383
4390
  '[role="listbox"]',
4384
- '[data-radix-popper-content-wrapper]',
4385
4391
  ].join(',');
4386
4392
  /**
4387
4393
  * `@radix-ui/react-dismissable-layer` keeps the value it must restore to
@@ -4393,6 +4399,36 @@ const OVERLAY_CONTENT_SELECTOR = [
4393
4399
  * Clearing is only safe once nothing is on screen, so this is a no-op while any
4394
4400
  * overlay content is still mounted.
4395
4401
  */
4402
+ /**
4403
+ * Watches `<body>` and clears a lock that outlives every overlay.
4404
+ *
4405
+ * The per-overlay hooks handle the normal close path; this is the backstop for
4406
+ * the ones they cannot see — an overlay unmounted by a route change, a layer
4407
+ * combination nobody anticipated, a third-party popup. Returns a disposer.
4408
+ *
4409
+ * Cheap: an attribute-scoped MutationObserver that only reacts when something
4410
+ * actually writes `style` or `data-scroll-locked` on `<body>`.
4411
+ */
4412
+ function watchOverlayBodyLock() {
4413
+ if (typeof document === 'undefined')
4414
+ return () => { };
4415
+ let timer;
4416
+ const check = () => {
4417
+ window.clearTimeout(timer);
4418
+ // Outlive the exit animation, so a legitimately-open overlay is still
4419
+ // mounted when `releaseStaleOverlayBodyLock` looks for it.
4420
+ timer = window.setTimeout(() => releaseStaleOverlayBodyLock(), 500);
4421
+ };
4422
+ const observer = new MutationObserver(check);
4423
+ observer.observe(document.body, {
4424
+ attributes: true,
4425
+ attributeFilter: ['style', 'data-scroll-locked'],
4426
+ });
4427
+ return () => {
4428
+ window.clearTimeout(timer);
4429
+ observer.disconnect();
4430
+ };
4431
+ }
4396
4432
  function releaseStaleOverlayBodyLock() {
4397
4433
  if (typeof document === 'undefined')
4398
4434
  return;
@@ -12123,6 +12159,9 @@ function useRefundToWallet(open) {
12123
12159
 
12124
12160
  const ConfirmDialog = ({ open, onOpenChange, title, description, info, confirmLabel = 'Yes', cancelLabel = 'Cancel', confirmVariant = 'default', icon: Icon = Trash2, disabled = false, onConfirm, onCancel, }) => {
12125
12161
  const { refundToWallet, setRefundToWallet } = useRefundToWallet(open);
12162
+ // Opened from a modal menu row, two dismissable layers overlap and Radix
12163
+ // restores `pointer-events: none` to <body> on close, freezing the page.
12164
+ useOverlayBodyLockRelease(open);
12126
12165
  return (jsxRuntime.jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: jsxRuntime.jsxs(DialogContent, { className: "gap-3 p-5", children: [jsxRuntime.jsxs(DialogHeader, { className: "gap-1.5", children: [jsxRuntime.jsxs(DialogTitle, { className: "flex items-center gap-2", children: [confirmVariant === 'destructive' && (jsxRuntime.jsx("span", { className: "inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-destructive/10", children: jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5 text-destructive" }) })), jsxRuntime.jsx("span", { children: title })] }), description && jsxRuntime.jsx(DialogDescription, { children: description }), info && (jsxRuntime.jsx(InfoCard, { info: info, title: title, refundToWallet: refundToWallet, onRefundChange: setRefundToWallet }))] }), jsxRuntime.jsxs(DialogFooter, { className: "mt-1", children: [jsxRuntime.jsx(Button$1, { variant: "outline", onClick: () => {
12127
12166
  onCancel?.();
12128
12167
  onOpenChange(false);
@@ -28606,10 +28645,12 @@ const SIZE_WIDTH = {
28606
28645
  */
28607
28646
  function Modal({ open, onClose, title, icon, children, footer, size = 'md', hideCloseButton = false, className, bodyClassName, }) {
28608
28647
  const maxWidth = SIZE_WIDTH[size];
28648
+ // See ConfirmDialog: overlapping dismissable layers leak the <body> lock.
28649
+ useOverlayBodyLockRelease(open);
28609
28650
  return (jsxRuntime.jsx(Dialog, { open: open, onOpenChange: (o) => !o && onClose(), children: jsxRuntime.jsxs(DialogContent, { hideCloseButton: true, style: {
28610
28651
  width: `min(${maxWidth}px, calc(100vw - 2rem))`,
28611
28652
  maxWidth: `${maxWidth}px`,
28612
- }, className: cn('flex max-h-[calc(100vh-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-none', className), children: [jsxRuntime.jsxs("header", { className: "flex items-center justify-between gap-4 border-b border-border px-6 py-4", children: [jsxRuntime.jsx(DialogPrimitive__namespace.Title, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-base font-medium text-foreground", children: [icon, jsxRuntime.jsx("span", { children: title })] }) }), !hideCloseButton && (jsxRuntime.jsx(DialogPrimitive__namespace.Close, { "aria-label": "Close", className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-label transition-colors hover:bg-gray-100 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30", children: jsxRuntime.jsx(X$2, { className: "h-4 w-4" }) }))] }), jsxRuntime.jsx("div", { className: cn('flex-1 overflow-y-auto', bodyClassName), children: children }), footer && (jsxRuntime.jsx("footer", { className: "flex items-center justify-end gap-3 border-t border-border px-6 py-4", children: footer }))] }) }));
28653
+ }, className: cn('flex max-h-[calc(100vh-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-none', className), children: [jsxRuntime.jsxs("header", { className: "flex items-center justify-between gap-4 border-b border-border px-6 py-4", children: [jsxRuntime.jsx(DialogPrimitive__namespace.Title, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-base font-medium text-foreground", children: [icon, jsxRuntime.jsx("span", { children: title })] }) }), !hideCloseButton && (jsxRuntime.jsx(DialogPrimitive__namespace.Close, { "aria-label": "Close", className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-label transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30", children: jsxRuntime.jsx(X$2, { className: "h-4 w-4" }) }))] }), jsxRuntime.jsx("div", { className: cn('flex-1 overflow-y-auto', bodyClassName), children: children }), footer && (jsxRuntime.jsx("footer", { className: "flex items-center justify-end gap-3 border-t border-border px-6 py-4", children: footer }))] }) }));
28613
28654
  }
28614
28655
 
28615
28656
  /**
@@ -57696,6 +57737,158 @@ function TagsInput({ value, onChange, placeholder, helperText, uppercase = false
57696
57737
  return (jsxRuntime.jsxs("div", { className: "w-full", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx(Input, { value: input, onChange: onInputChange, onKeyDown: onInputKeyDown, placeholder: placeholder || 'Type and Press Enter', className: "flex-1 bg-formInput" }), jsxRuntime.jsxs(Button$1, { type: "button", onClick: addTag, disabled: addDisabled, className: "h-9 gap-1.5 disabled:opacity-50", children: [jsxRuntime.jsx(Plus, { className: "h-4 w-4" }), "Add"] })] }), helperText && (jsxRuntime.jsx("p", { className: "mt-1.5 text-xs text-muted-foreground", children: helperText })), value.length > 0 && (jsxRuntime.jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: value.map((tag, idx) => (jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md border border-border bg-secondary px-2 py-1 text-xs font-medium text-foreground", children: [tag, jsxRuntime.jsx("button", { type: "button", onClick: () => removeTag(idx), "aria-label": `Remove ${tag}`, className: "cursor-pointer text-muted-foreground transition-colors hover:text-foreground", children: jsxRuntime.jsx(X$2, { className: "h-3 w-3" }) })] }, idx))) }))] }));
57697
57738
  }
57698
57739
 
57740
+ /**
57741
+ * One scrolling column of the picker (hours, minutes or period). The selected
57742
+ * row is scrolled into view whenever it changes, so reopening the popover lands
57743
+ * on the current value instead of at midnight.
57744
+ */
57745
+ function TimeColumn({ label, options, selected, onSelect, }) {
57746
+ const listRef = React.useRef(null);
57747
+ const activeRef = React.useRef(null);
57748
+ React.useEffect(() => {
57749
+ const list = listRef.current;
57750
+ const active = activeRef.current;
57751
+ if (!list || !active)
57752
+ return;
57753
+ // `scrollIntoView` would scroll the page as well as the column.
57754
+ list.scrollTop =
57755
+ active.offsetTop - list.clientHeight / 2 + active.clientHeight / 2;
57756
+ }, [selected]);
57757
+ return (jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col", children: [jsxRuntime.jsx("span", { className: "px-1 pb-1.5 text-center text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: label }), jsxRuntime.jsx("div", { ref: listRef, role: "listbox", "aria-label": label, className: "scrollbar-thin h-[184px] overflow-y-auto px-1", children: jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children: options.map((option) => {
57758
+ const isSelected = option.value === selected;
57759
+ return (jsxRuntime.jsx("button", { ref: isSelected ? activeRef : undefined, type: "button", role: "option", "aria-selected": isSelected, onClick: () => onSelect(option.value), className: cn('rounded-md px-3 py-1.5 text-center text-sm tabular-nums transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', isSelected
57760
+ ? 'bg-primary text-primary-foreground'
57761
+ : 'text-foreground hover:bg-muted'), children: option.label }, option.value));
57762
+ }) }) })] }));
57763
+ }
57764
+
57765
+ const TIME_PATTERN = /^(\d{1,2}):(\d{2})(?::\d{2})?$/;
57766
+ const pad = (n) => String(n).padStart(2, '0');
57767
+ /** Reads `HH:mm` / `HH:mm:ss`. Returns null for anything out of range. */
57768
+ function parseTimeValue(value) {
57769
+ const match = TIME_PATTERN.exec((value ?? '').trim());
57770
+ if (!match)
57771
+ return null;
57772
+ const hour = Number(match[1]);
57773
+ const minute = Number(match[2]);
57774
+ if (hour > 23 || minute > 59)
57775
+ return null;
57776
+ return { hour, minute };
57777
+ }
57778
+ /** The stored form — always 24-hour, whatever the display format. */
57779
+ function toTimeValue(hour, minute) {
57780
+ return `${pad(hour)}:${pad(minute)}`;
57781
+ }
57782
+ function formatTimeDisplay(time, use24Hour) {
57783
+ if (use24Hour)
57784
+ return toTimeValue(time.hour, time.minute);
57785
+ // 0 and 12 both map to 12 — midnight is 12 AM, noon is 12 PM.
57786
+ const hour12 = time.hour % 12 === 0 ? 12 : time.hour % 12;
57787
+ const period = time.hour < 12 ? 'AM' : 'PM';
57788
+ return `${pad(hour12)}:${pad(time.minute)} ${period}`;
57789
+ }
57790
+ function buildHourOptions(use24Hour) {
57791
+ if (use24Hour) {
57792
+ return Array.from({ length: 24 }, (_, hour) => ({
57793
+ value: hour,
57794
+ label: pad(hour),
57795
+ }));
57796
+ }
57797
+ // 12 first, matching how a clock reads.
57798
+ return Array.from({ length: 12 }, (_, index) => {
57799
+ const hour12 = index === 0 ? 12 : index;
57800
+ return { value: hour12, label: pad(hour12) };
57801
+ });
57802
+ }
57803
+ function buildMinuteOptions(step) {
57804
+ const safeStep = Number.isFinite(step) && step > 0 ? Math.floor(step) : 1;
57805
+ const options = [];
57806
+ for (let minute = 0; minute < 60; minute += safeStep) {
57807
+ options.push({ value: minute, label: pad(minute) });
57808
+ }
57809
+ return options;
57810
+ }
57811
+ /** The hour as the 12-hour column shows it (12, 1…11). */
57812
+ function toHour12(hour) {
57813
+ return hour % 12 === 0 ? 12 : hour % 12;
57814
+ }
57815
+ /** Rebuilds a 24-hour hour from a 12-hour column value and its period. */
57816
+ function fromHour12(hour12, period) {
57817
+ const base = hour12 % 12;
57818
+ return period === 'PM' ? base + 12 : base;
57819
+ }
57820
+ function periodOf(hour) {
57821
+ return hour < 12 ? 'AM' : 'PM';
57822
+ }
57823
+
57824
+ // Only one TimePicker popover should be open at a time — same coordination the
57825
+ // DateInput uses, for the same reason: each is its own `modal` Radix Popover, so
57826
+ // opening a second does not dismiss the first by outside-click.
57827
+ const TIMEPICKER_OPEN_EVENT = 'caratlogic:timepicker-open';
57828
+ const PERIOD_OPTIONS = [
57829
+ { value: 0, label: 'AM' },
57830
+ { value: 1, label: 'PM' },
57831
+ ];
57832
+ function TimePicker({ value, onChange, onBlur, name, className, disabled, placeholder = 'Select time', use24Hour = false, minuteStep = 1, hideFooter = false, }) {
57833
+ const [open, setOpen] = React.useState(false);
57834
+ const instanceId = React.useId();
57835
+ const parsed = parseTimeValue(value);
57836
+ React.useEffect(() => {
57837
+ const onOtherOpen = (e) => {
57838
+ if (e.detail !== instanceId)
57839
+ setOpen(false);
57840
+ };
57841
+ window.addEventListener(TIMEPICKER_OPEN_EVENT, onOtherOpen);
57842
+ return () => window.removeEventListener(TIMEPICKER_OPEN_EVENT, onOtherOpen);
57843
+ }, [instanceId]);
57844
+ const handleOpenChange = (next) => {
57845
+ setOpen(next);
57846
+ if (next) {
57847
+ window.dispatchEvent(new CustomEvent(TIMEPICKER_OPEN_EVENT, { detail: instanceId }));
57848
+ }
57849
+ };
57850
+ /** Picking one column before the others commits against a 00:00 baseline. */
57851
+ const commit = (hour, minute) => {
57852
+ onChange(toTimeValue(hour, minute));
57853
+ };
57854
+ const handleHour = (picked) => {
57855
+ const minute = parsed?.minute ?? 0;
57856
+ if (use24Hour) {
57857
+ commit(picked, minute);
57858
+ return;
57859
+ }
57860
+ commit(fromHour12(picked, parsed ? periodOf(parsed.hour) : 'AM'), minute);
57861
+ };
57862
+ const handleMinute = (picked) => {
57863
+ commit(parsed?.hour ?? 0, picked);
57864
+ };
57865
+ const handlePeriod = (picked) => {
57866
+ const period = picked === 0 ? 'AM' : 'PM';
57867
+ const hour12 = parsed ? toHour12(parsed.hour) : 12;
57868
+ commit(fromHour12(hour12, period), parsed?.minute ?? 0);
57869
+ };
57870
+ const selectedHour = parsed
57871
+ ? use24Hour
57872
+ ? parsed.hour
57873
+ : toHour12(parsed.hour)
57874
+ : null;
57875
+ // NOTE: `open` is driven from the button's onClick rather than
57876
+ // <PopoverTrigger>, matching DateInput — inside a modal <SlideOut> the
57877
+ // trigger's built-in toggle does not fire in Safari. <PopoverAnchor> is pure
57878
+ // positioning, so a single controlled toggle stays the only source of truth.
57879
+ return (jsxRuntime.jsxs(Popover, { open: open, onOpenChange: handleOpenChange, modal: true, children: [jsxRuntime.jsx(PopoverAnchor, { asChild: true, children: jsxRuntime.jsxs("button", { type: "button", name: name, onBlur: onBlur, disabled: disabled, "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => {
57880
+ if (!disabled)
57881
+ handleOpenChange(!open);
57882
+ }, "data-slot": "input", "data-empty": !parsed || undefined, className: cn('border-input-border flex h-9 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-1 text-left shadow-[0_1px_2px_0_#0000000d] transition-[color,box-shadow] outline-none', 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', 'aria-invalid:ring-destructive/20 aria-invalid:border-destructive', disabled && 'cursor-not-allowed bg-muted/50 text-label', className), children: [jsxRuntime.jsx("span", { className: cn('truncate text-sm md:text-sm', parsed ? 'text-foreground' : 'text-placeholder'), children: parsed ? formatTimeDisplay(parsed, use24Hour) : placeholder }), jsxRuntime.jsx("span", { className: "inline-flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground", children: jsxRuntime.jsx(Clock, { className: "h-4 w-4 stroke-[1.75]" }) })] }) }), jsxRuntime.jsxs(PopoverContent, { align: "start", className: "w-auto p-3", children: [jsxRuntime.jsxs("div", { className: "flex gap-1", children: [jsxRuntime.jsx(TimeColumn, { label: "Hour", options: buildHourOptions(use24Hour), selected: selectedHour, onSelect: handleHour }), jsxRuntime.jsx(TimeColumn, { label: "Min", options: buildMinuteOptions(minuteStep), selected: parsed?.minute ?? null, onSelect: handleMinute }), !use24Hour && (jsxRuntime.jsx(TimeColumn, { label: "AM/PM", options: PERIOD_OPTIONS, selected: parsed ? (periodOf(parsed.hour) === 'AM' ? 0 : 1) : null, onSelect: handlePeriod }))] }), !hideFooter && (jsxRuntime.jsxs("div", { className: "mt-2 flex items-center justify-between gap-2 border-t border-border pt-2", children: [jsxRuntime.jsx("button", { type: "button", onClick: () => {
57883
+ onChange(undefined);
57884
+ setOpen(false);
57885
+ }, className: "rounded-md px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: "Clear" }), jsxRuntime.jsx("button", { type: "button", onClick: () => {
57886
+ const now = new Date();
57887
+ commit(now.getHours(), now.getMinutes());
57888
+ setOpen(false);
57889
+ }, className: "rounded-md px-2 py-1 text-xs font-medium text-primary transition-colors hover:text-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: "Now" })] }))] })] }));
57890
+ }
57891
+
57699
57892
  function mapToItem(w, separator) {
57700
57893
  const id = w.id || w.warehouseId || '';
57701
57894
  if (!id)
@@ -67858,6 +68051,7 @@ exports.TabsList = TabsList;
67858
68051
  exports.TabsTrigger = TabsTrigger;
67859
68052
  exports.TagsInput = TagsInput;
67860
68053
  exports.TextHyperlink = TextHyperlink;
68054
+ exports.TimePicker = TimePicker;
67861
68055
  exports.ToggleSwitch = ToggleSwitch;
67862
68056
  exports.TruncateTooltip = TruncateTooltip;
67863
68057
  exports.WarehouseComboBox = WarehouseComboBox;
@@ -67891,4 +68085,5 @@ exports.useDropdownMenu = useDropdownMenu;
67891
68085
  exports.useNavbar = useNavbar;
67892
68086
  exports.useSidebar = useSidebar;
67893
68087
  exports.videoThumbnailSrc = videoThumbnailSrc;
68088
+ exports.watchOverlayBodyLock = watchOverlayBodyLock;
67894
68089
  //# sourceMappingURL=index.cjs.js.map