@stll/ui 0.0.1-placeholder.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +84 -0
  2. package/dist/components/accordion.d.ts +8 -0
  3. package/dist/components/accordion.js +38 -0
  4. package/dist/components/alert-dialog.d.ts +32 -0
  5. package/dist/components/alert-dialog.js +75 -0
  6. package/dist/components/avatar.d.ts +7 -0
  7. package/dist/components/avatar.js +22 -0
  8. package/dist/components/bidi-text.d.ts +24 -0
  9. package/dist/components/bidi-text.js +38 -0
  10. package/dist/components/brand-icons.d.ts +7 -0
  11. package/dist/components/brand-icons.js +18 -0
  12. package/dist/components/breadcrumb.d.ts +12 -0
  13. package/dist/components/breadcrumb.js +61 -0
  14. package/dist/components/button-variants.d.ts +17 -0
  15. package/dist/components/button-variants.js +43 -0
  16. package/dist/components/button.d.ts +20 -0
  17. package/dist/components/button.js +53 -0
  18. package/dist/components/checkbox.d.ts +5 -0
  19. package/dist/components/checkbox.js +44 -0
  20. package/dist/components/color-picker.d.ts +47 -0
  21. package/dist/components/color-picker.js +249 -0
  22. package/dist/components/combobox.d.ts +40 -0
  23. package/dist/components/combobox.js +208 -0
  24. package/dist/components/command.d.ts +34 -0
  25. package/dist/components/command.js +100 -0
  26. package/dist/components/date-picker-popover.d.ts +29 -0
  27. package/dist/components/date-picker-popover.js +548 -0
  28. package/dist/components/date-picker-popover.logic.d.ts +14 -0
  29. package/dist/components/date-picker-popover.logic.js +26 -0
  30. package/dist/components/destructive-action-confirmation.d.ts +21 -0
  31. package/dist/components/destructive-action-confirmation.js +60 -0
  32. package/dist/components/destructive-confirm-dialog.d.ts +17 -0
  33. package/dist/components/destructive-confirm-dialog.js +49 -0
  34. package/dist/components/dialog.d.ts +37 -0
  35. package/dist/components/dialog.js +92 -0
  36. package/dist/components/directional-icon.d.ts +30 -0
  37. package/dist/components/directional-icon.js +12 -0
  38. package/dist/components/field.d.ts +11 -0
  39. package/dist/components/field.js +34 -0
  40. package/dist/components/form.d.ts +5 -0
  41. package/dist/components/form.js +12 -0
  42. package/dist/components/frame.d.ts +10 -0
  43. package/dist/components/frame.js +35 -0
  44. package/dist/components/hex-color-picker.d.ts +11 -0
  45. package/dist/components/hex-color-picker.js +366 -0
  46. package/dist/components/input-group.d.ts +15 -0
  47. package/dist/components/input-group.js +53 -0
  48. package/dist/components/input-otp.d.ts +13 -0
  49. package/dist/components/input-otp.js +39 -0
  50. package/dist/components/input.d.ts +12 -0
  51. package/dist/components/input.js +46 -0
  52. package/dist/components/label.d.ts +5 -0
  53. package/dist/components/label.js +18 -0
  54. package/dist/components/menu.d.ts +44 -0
  55. package/dist/components/menu.js +152 -0
  56. package/dist/components/outline-rail.d.ts +43 -0
  57. package/dist/components/outline-rail.js +416 -0
  58. package/dist/components/pagination.d.ts +17 -0
  59. package/dist/components/pagination.js +78 -0
  60. package/dist/components/popover.d.ts +34 -0
  61. package/dist/components/popover.js +67 -0
  62. package/dist/components/preview-card.d.ts +12 -0
  63. package/dist/components/preview-card.js +25 -0
  64. package/dist/components/preview-pane.d.ts +23 -0
  65. package/dist/components/preview-pane.js +33 -0
  66. package/dist/components/scroll-area.d.ts +19 -0
  67. package/dist/components/scroll-area.js +38 -0
  68. package/dist/components/scroll-to-top.d.ts +12 -0
  69. package/dist/components/scroll-to-top.js +43 -0
  70. package/dist/components/secret-input.d.ts +9 -0
  71. package/dist/components/secret-input.js +38 -0
  72. package/dist/components/segmented-icon-toggle.d.ts +22 -0
  73. package/dist/components/segmented-icon-toggle.js +30 -0
  74. package/dist/components/select.d.ts +25 -0
  75. package/dist/components/select.js +196 -0
  76. package/dist/components/separator.d.ts +16 -0
  77. package/dist/components/separator.js +26 -0
  78. package/dist/components/sheet.d.ts +29 -0
  79. package/dist/components/sheet.js +87 -0
  80. package/dist/components/skeleton.d.ts +4 -0
  81. package/dist/components/skeleton.js +10 -0
  82. package/dist/components/stella-mark.d.ts +5 -0
  83. package/dist/components/stella-mark.js +12 -0
  84. package/dist/components/stella-wordmark.d.ts +5 -0
  85. package/dist/components/stella-wordmark.js +21 -0
  86. package/dist/components/table.d.ts +20 -0
  87. package/dist/components/table.js +80 -0
  88. package/dist/components/tabs.d.ts +11 -0
  89. package/dist/components/tabs.js +46 -0
  90. package/dist/components/textarea.d.ts +9 -0
  91. package/dist/components/textarea.js +31 -0
  92. package/dist/components/toast.d.ts +60 -0
  93. package/dist/components/toast.js +261 -0
  94. package/dist/components/tooltip-trigger-helper.d.ts +11 -0
  95. package/dist/components/tooltip-trigger-helper.js +14 -0
  96. package/dist/components/tooltip.d.ts +15 -0
  97. package/dist/components/tooltip.js +32 -0
  98. package/dist/data-table/index.d.ts +2 -0
  99. package/dist/data-table/index.js +2 -0
  100. package/dist/data-table/schema.d.ts +70 -0
  101. package/dist/data-table/schema.js +40 -0
  102. package/dist/hooks/use-contained-handler.d.ts +55 -0
  103. package/dist/hooks/use-contained-handler.js +54 -0
  104. package/dist/hooks/use-content-dir.d.ts +48 -0
  105. package/dist/hooks/use-content-dir.js +45 -0
  106. package/dist/hooks/use-mobile.d.ts +4 -0
  107. package/dist/hooks/use-mobile.js +16 -0
  108. package/dist/hooks/use-viewport-width.d.ts +5 -0
  109. package/dist/hooks/use-viewport-width.js +14 -0
  110. package/dist/index.d.ts +67 -0
  111. package/dist/index.js +65 -0
  112. package/dist/inspector/chrome.d.ts +72 -0
  113. package/dist/inspector/chrome.js +153 -0
  114. package/dist/inspector/dock.d.ts +48 -0
  115. package/dist/inspector/dock.js +36 -0
  116. package/dist/inspector/index.d.ts +6 -0
  117. package/dist/inspector/index.js +6 -0
  118. package/dist/inspector/layout-tokens.d.ts +28 -0
  119. package/dist/inspector/layout-tokens.js +28 -0
  120. package/dist/inspector/pane-width.d.ts +64 -0
  121. package/dist/inspector/pane-width.js +53 -0
  122. package/dist/inspector/use-pane-width.d.ts +99 -0
  123. package/dist/inspector/use-pane-width.js +149 -0
  124. package/dist/kanban/card-properties.d.ts +27 -0
  125. package/dist/kanban/card-properties.js +19 -0
  126. package/dist/kanban/card-shell.d.ts +32 -0
  127. package/dist/kanban/card-shell.js +46 -0
  128. package/dist/kanban/column-header.d.ts +23 -0
  129. package/dist/kanban/column-header.js +24 -0
  130. package/dist/kanban/grouping.d.ts +84 -0
  131. package/dist/kanban/grouping.js +76 -0
  132. package/dist/kanban/index.d.ts +5 -0
  133. package/dist/kanban/index.js +5 -0
  134. package/dist/lib/button-disposition.d.ts +63 -0
  135. package/dist/lib/button-disposition.js +52 -0
  136. package/dist/lib/option-color.d.ts +28 -0
  137. package/dist/lib/option-color.js +59 -0
  138. package/dist/lib/overlay-layer.d.ts +22 -0
  139. package/dist/lib/overlay-layer.js +21 -0
  140. package/dist/lib/tab-order.d.ts +15 -0
  141. package/dist/lib/tab-order.js +15 -0
  142. package/dist/lib/tooltip-content.d.ts +13 -0
  143. package/dist/lib/tooltip-content.js +12 -0
  144. package/dist/lib/utils.d.ts +20 -0
  145. package/dist/lib/utils.js +41 -0
  146. package/dist/lib/week.d.ts +25 -0
  147. package/dist/lib/week.js +40 -0
  148. package/dist/styles/theme.css +1209 -0
  149. package/package.json +518 -2
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { useContentDir } from "../hooks/use-content-dir.js";
4
+ import { DialogContent as DialogPopup } from "./dialog.js";
5
+ import { SearchIcon } from "lucide-react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { Autocomplete } from "@base-ui/react/autocomplete";
8
+ import { Dialog } from "@base-ui/react/dialog";
9
+ //#region src/components/command.tsx
10
+ const Command = ({ autoHighlight = "always", keepHighlight = true, open = true, ...props }) => /* @__PURE__ */ jsx(Autocomplete.Root, {
11
+ autoHighlight,
12
+ keepHighlight,
13
+ open,
14
+ ...props
15
+ });
16
+ const CommandDialog = Dialog.Root;
17
+ const CommandDialogTrigger = (props) => /* @__PURE__ */ jsx(Dialog.Trigger, {
18
+ "data-slot": "command-dialog-trigger",
19
+ ...props
20
+ });
21
+ const CommandDialogPopup = ({ className, showCloseButton = false, ...props }) => /* @__PURE__ */ jsx(DialogPopup, {
22
+ className: cn("overflow-hidden p-0", className),
23
+ "data-slot": "command-dialog-popup",
24
+ showCloseButton,
25
+ ...props
26
+ });
27
+ const CommandInput = ({ className, size = "lg", wrapperClassName, dir, onChange, ...props }) => {
28
+ const tracked = useContentDir({
29
+ dir: void 0,
30
+ value: void 0,
31
+ defaultValue: void 0
32
+ });
33
+ const handleChange = (event) => {
34
+ tracked.trackValue(event.currentTarget.value);
35
+ onChange?.(event);
36
+ };
37
+ return /* @__PURE__ */ jsxs("div", {
38
+ className: cn("flex min-w-0 flex-1 items-center gap-3", wrapperClassName),
39
+ "data-slot": "command-input-wrapper",
40
+ children: [/* @__PURE__ */ jsx(SearchIcon, { className: "text-muted-foreground size-5 shrink-0" }), /* @__PURE__ */ jsx(Autocomplete.Input, {
41
+ className: cn("placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent text-base outline-none disabled:cursor-not-allowed disabled:opacity-64 sm:text-sm", size === "sm" && "h-7 text-sm", size === "default" && "h-8", size === "lg" && "h-9", className),
42
+ "data-slot": "command-input",
43
+ size: typeof size === "number" ? size : void 0,
44
+ ...props,
45
+ dir: dir ?? tracked.dir,
46
+ onChange: handleChange
47
+ })]
48
+ });
49
+ };
50
+ const CommandPanel = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
51
+ className: cn("bg-popover text-popover-foreground flex min-h-0 flex-col rounded-lg border shadow-lg/5", className),
52
+ "data-slot": "command-panel",
53
+ ...props
54
+ });
55
+ const CommandEmpty = ({ className, ...props }) => /* @__PURE__ */ jsx(Autocomplete.Empty, {
56
+ className: cn("text-muted-foreground py-6 text-center text-sm", className),
57
+ "data-slot": "command-empty",
58
+ ...props
59
+ });
60
+ const CommandList = ({ className, ...props }) => /* @__PURE__ */ jsx(Autocomplete.List, {
61
+ className: cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className),
62
+ "data-slot": "command-list",
63
+ ...props
64
+ });
65
+ const CommandGroup = ({ className, ...props }) => /* @__PURE__ */ jsx(Autocomplete.Group, {
66
+ className: cn("text-foreground overflow-hidden p-1", className),
67
+ "data-slot": "command-group",
68
+ ...props
69
+ });
70
+ const CommandGroupLabel = ({ className, ...props }) => /* @__PURE__ */ jsx(Autocomplete.GroupLabel, {
71
+ className: cn("text-muted-foreground px-2 py-1.5 text-xs font-medium", className),
72
+ "data-slot": "command-group-label",
73
+ ...props
74
+ });
75
+ const CommandCollection = (props) => /* @__PURE__ */ jsx(Autocomplete.Collection, {
76
+ "data-slot": "command-collection",
77
+ ...props
78
+ });
79
+ const CommandItem = ({ className, ...props }) => /* @__PURE__ */ jsx(Autocomplete.Item, {
80
+ className: cn("data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-64 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
81
+ "data-slot": "command-item",
82
+ ...props
83
+ });
84
+ const CommandSeparator = ({ className, ...props }) => /* @__PURE__ */ jsx(Autocomplete.Separator, {
85
+ className: cn("bg-border -mx-1 h-px", className),
86
+ "data-slot": "command-separator",
87
+ ...props
88
+ });
89
+ const CommandShortcut = ({ className, ...props }) => /* @__PURE__ */ jsx("span", {
90
+ className: cn("text-muted-foreground ms-auto text-xs", className),
91
+ "data-slot": "command-shortcut",
92
+ ...props
93
+ });
94
+ const CommandFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
95
+ className: cn("text-muted-foreground flex items-center justify-between border-t px-3 py-2 text-xs", className),
96
+ "data-slot": "command-footer",
97
+ ...props
98
+ });
99
+ //#endregion
100
+ export { Command, CommandCollection, CommandDialog, CommandDialogPopup, CommandDialogTrigger, CommandEmpty, CommandFooter, CommandGroup, CommandGroupLabel, CommandInput, CommandItem, CommandList, CommandPanel, CommandSeparator, CommandShortcut };
@@ -0,0 +1,29 @@
1
+ import { OverlayLayer } from "../lib/overlay-layer.js";
2
+ //#region src/components/date-picker-popover.d.ts
3
+ type DatePickerPopoverProps = {
4
+ id?: string;
5
+ /** ID of the visible field label, when the picker is part of a form field. */
6
+ labelledBy?: string;
7
+ value: string | Date | null;
8
+ onChange: (value: string | null) => void;
9
+ locale?: string;
10
+ isOverdue?: boolean;
11
+ showIcon?: boolean;
12
+ /** Shown in the trigger when no date is set. Falls back to an em dash so
13
+ * the control still has height; pass a call-to-action ("Select date…") to
14
+ * make the empty state self-explanatory. */
15
+ placeholderLabel?: string;
16
+ clearLabel?: string;
17
+ defaultOpen?: boolean;
18
+ onOpenChange?: (open: boolean) => void;
19
+ /** Label for the "go to today" button. Auto-localized from the locale when omitted. */
20
+ todayLabel?: string;
21
+ overdueLabel?: string;
22
+ minDate?: string;
23
+ maxDate?: string;
24
+ isDateDisabled?: (date: string) => boolean;
25
+ layer?: OverlayLayer;
26
+ };
27
+ declare const DatePickerPopover: (props: DatePickerPopoverProps) => import("react").JSX.Element;
28
+ //#endregion
29
+ export { DatePickerPopover, type DatePickerPopoverProps };
@@ -0,0 +1,548 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { DirectionalIcon } from "./directional-icon.js";
4
+ import { Button } from "./button.js";
5
+ import { getLocaleWeekInfo, getWeekendDays } from "../lib/week.js";
6
+ import { localDateFromTimestamp, millisecondsUntilNextLocalDate, resolveCalendarViewMonth } from "./date-picker-popover.logic.js";
7
+ import { Popover, PopoverContent as PopoverPopup, PopoverTrigger } from "./popover.js";
8
+ import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import { useCallback, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
11
+ //#region src/components/date-picker-popover.tsx
12
+ const toISODate = (date) => date.toISOString().slice(0, 10);
13
+ const HYDRATION_DATE = "1970-01-01";
14
+ const HYDRATION_LOCALE = "en";
15
+ const noopSubscribe = (_onStoreChange) => () => void 0;
16
+ const getLocalToday = () => localDateFromTimestamp(Date.now());
17
+ const localDateListeners = /* @__PURE__ */ new Set();
18
+ let localDateTimeoutId;
19
+ const notifyLocalDateListeners = () => {
20
+ for (const listener of localDateListeners) listener();
21
+ };
22
+ const scheduleNextLocalDate = () => {
23
+ if (localDateTimeoutId !== void 0) clearTimeout(localDateTimeoutId);
24
+ localDateTimeoutId = setTimeout(() => {
25
+ notifyLocalDateListeners();
26
+ if (localDateListeners.size > 0) scheduleNextLocalDate();
27
+ }, millisecondsUntilNextLocalDate(Date.now()));
28
+ };
29
+ const refreshLocalDateEnvironment = () => {
30
+ notifyLocalDateListeners();
31
+ if (localDateListeners.size > 0) scheduleNextLocalDate();
32
+ };
33
+ const startLocalDateSubscription = () => {
34
+ scheduleNextLocalDate();
35
+ globalThis.addEventListener("focus", refreshLocalDateEnvironment);
36
+ };
37
+ const stopLocalDateSubscription = () => {
38
+ globalThis.removeEventListener("focus", refreshLocalDateEnvironment);
39
+ if (localDateTimeoutId !== void 0) {
40
+ clearTimeout(localDateTimeoutId);
41
+ localDateTimeoutId = void 0;
42
+ }
43
+ };
44
+ const subscribeToLocalDate = (onStoreChange) => {
45
+ localDateListeners.add(onStoreChange);
46
+ if (localDateListeners.size === 1) startLocalDateSubscription();
47
+ return () => {
48
+ localDateListeners.delete(onStoreChange);
49
+ if (localDateListeners.size === 0) stopLocalDateSubscription();
50
+ };
51
+ };
52
+ const useHydrationSafeToday = () => useSyncExternalStore(subscribeToLocalDate, getLocalToday, () => HYDRATION_DATE);
53
+ const useHydrationSafeBrowserLocale = () => useSyncExternalStore(noopSubscribe, () => navigator.language || HYDRATION_LOCALE, () => HYDRATION_LOCALE);
54
+ const getFirstDayOfWeek = (locale) => {
55
+ const info = getLocaleWeekInfo(locale);
56
+ if (!info) return 0;
57
+ return info.firstDay === 7 ? 6 : info.firstDay - 1;
58
+ };
59
+ const getMonthDays = (year, month, firstDow, weekendDays, today) => {
60
+ const days = [];
61
+ const first = new Date(Date.UTC(year, month, 1));
62
+ const startOffset = ((first.getUTCDay() + 6) % 7 - firstDow + 7) % 7;
63
+ const start = new Date(first);
64
+ start.setUTCDate(start.getUTCDate() - startOffset);
65
+ for (let i = 0; i < 42; i++) {
66
+ const d = new Date(start);
67
+ d.setUTCDate(d.getUTCDate() + i);
68
+ const iso = toISODate(d);
69
+ days.push({
70
+ date: iso,
71
+ isCurrentMonth: d.getUTCMonth() === month,
72
+ isToday: iso === today,
73
+ isWeekend: weekendDays.has(d.getUTCDay())
74
+ });
75
+ }
76
+ return days;
77
+ };
78
+ const weekdayFormatters = /* @__PURE__ */ new Map();
79
+ const getWeekdayFormatter = (locale) => {
80
+ const fmt = weekdayFormatters.get(locale) ?? new Intl.DateTimeFormat(locale, {
81
+ weekday: "short",
82
+ timeZone: "UTC"
83
+ });
84
+ weekdayFormatters.set(locale, fmt);
85
+ return fmt;
86
+ };
87
+ const getWeekdayLabels = (locale, firstDow, weekendDays) => {
88
+ const fmt = getWeekdayFormatter(locale);
89
+ return Array.from({ length: 7 }, (_, i) => {
90
+ const d = new Date(Date.UTC(2024, 0, 1 + (i + firstDow) % 7));
91
+ return {
92
+ isWeekend: weekendDays.has(d.getUTCDay()),
93
+ label: fmt.format(d)
94
+ };
95
+ });
96
+ };
97
+ const monthFormatters = /* @__PURE__ */ new Map();
98
+ const getMonthFormatter = (locale, format) => {
99
+ const key = `${locale}:${format}`;
100
+ const fmt = monthFormatters.get(key) ?? new Intl.DateTimeFormat(locale, {
101
+ month: format,
102
+ calendar: "gregory",
103
+ timeZone: "UTC"
104
+ });
105
+ monthFormatters.set(key, fmt);
106
+ return fmt;
107
+ };
108
+ const getMonthLabels = (locale, format = "long") => {
109
+ const fmt = getMonthFormatter(locale, format);
110
+ return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(Date.UTC(2024, i, 1))));
111
+ };
112
+ const monthYearFormatters = /* @__PURE__ */ new Map();
113
+ const getMonthYearFormatter = (locale) => {
114
+ const fmt = monthYearFormatters.get(locale) ?? new Intl.DateTimeFormat(locale, {
115
+ month: "long",
116
+ year: "numeric",
117
+ calendar: "gregory",
118
+ timeZone: "UTC"
119
+ });
120
+ monthYearFormatters.set(locale, fmt);
121
+ return fmt;
122
+ };
123
+ const formatMonthYear = (locale, year, month) => getMonthYearFormatter(locale).format(new Date(Date.UTC(year, month, 1)));
124
+ const relativeTimeFormatters = /* @__PURE__ */ new Map();
125
+ const getRelativeTimeFormatter = (locale) => {
126
+ const fmt = relativeTimeFormatters.get(locale) ?? new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
127
+ relativeTimeFormatters.set(locale, fmt);
128
+ return fmt;
129
+ };
130
+ /** Derive a locale-correct "Today" label (e.g., "Dnes", "Heute", "Today"). */
131
+ const deriveTodayLabel = (locale) => {
132
+ const raw = getRelativeTimeFormatter(locale).format(0, "day");
133
+ return raw.charAt(0).toUpperCase() + raw.slice(1);
134
+ };
135
+ const normalizeDate = (v) => {
136
+ if (v === null || v === void 0) return "";
137
+ if (v instanceof Date) return v.toISOString().slice(0, 10);
138
+ return v.length >= 10 ? v.slice(0, 10) : v;
139
+ };
140
+ const addDays = (iso, n) => {
141
+ const d = /* @__PURE__ */ new Date(`${iso}T00:00:00Z`);
142
+ d.setUTCDate(d.getUTCDate() + n);
143
+ return toISODate(d);
144
+ };
145
+ const isBefore = (a, b) => a < b;
146
+ const isAfter = (a, b) => a > b;
147
+ /** Round down to the start of a decade (e.g. 2026 → 2020). */
148
+ const decadeStart = (year) => Math.floor(year / 10) * 10;
149
+ const DECADE_SIZE = 12;
150
+ const DatePickerPopoverContent = ({ id, labelledBy, value: rawValue, onChange, locale, isOverdue = false, showIcon = true, placeholderLabel, clearLabel = "Clear date", defaultOpen = false, onOpenChange, todayLabel: todayLabelProp, overdueLabel, minDate, maxDate, isDateDisabled, layer = "default", today }) => {
151
+ const value = normalizeDate(rawValue);
152
+ const displayValueId = useId();
153
+ const todayLabel = todayLabelProp ?? deriveTodayLabel(locale);
154
+ const firstDow = useMemo(() => getFirstDayOfWeek(locale), [locale]);
155
+ const weekendDays = useMemo(() => getWeekendDays(locale), [locale]);
156
+ const [viewMonthOverride, setViewMonthOverride] = useState(null);
157
+ const { month: viewMonth, year: viewYear } = resolveCalendarViewMonth({
158
+ override: viewMonthOverride,
159
+ today,
160
+ value
161
+ });
162
+ const [view, setView] = useState("days");
163
+ const [decadeBaseOverride, setDecadeBaseOverride] = useState(null);
164
+ const decadeBase = decadeBaseOverride ?? decadeStart(viewYear);
165
+ const days = useMemo(() => getMonthDays(viewYear, viewMonth, firstDow, weekendDays, today), [
166
+ viewYear,
167
+ viewMonth,
168
+ firstDow,
169
+ weekendDays,
170
+ today
171
+ ]);
172
+ const weekdays = useMemo(() => getWeekdayLabels(locale, firstDow, weekendDays), [
173
+ locale,
174
+ firstDow,
175
+ weekendDays
176
+ ]);
177
+ const [focusedDate, setFocusedDate] = useState("");
178
+ const gridRef = useRef(null);
179
+ const isDayDisabled = useCallback((date) => {
180
+ if (minDate && isBefore(date, minDate)) return true;
181
+ if (maxDate && isAfter(date, maxDate)) return true;
182
+ if (isDateDisabled?.(date)) return true;
183
+ return false;
184
+ }, [
185
+ minDate,
186
+ maxDate,
187
+ isDateDisabled
188
+ ]);
189
+ const displayLabel = value ? (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toLocaleDateString(locale, {
190
+ month: "short",
191
+ day: "numeric",
192
+ year: "numeric",
193
+ calendar: "gregory",
194
+ timeZone: "UTC"
195
+ }) : placeholderLabel ?? "—";
196
+ const formatDayLabel = useCallback((iso) => (/* @__PURE__ */ new Date(`${iso}T00:00:00Z`)).toLocaleDateString(locale, {
197
+ weekday: "long",
198
+ month: "long",
199
+ day: "numeric",
200
+ year: "numeric",
201
+ calendar: "gregory",
202
+ timeZone: "UTC"
203
+ }), [locale]);
204
+ const handleGridKeyDown = useCallback((e) => {
205
+ const firstDay = days.at(0);
206
+ if (!firstDay) return;
207
+ const current = focusedDate || value || firstDay.date;
208
+ let next = null;
209
+ const horizontalStep = gridRef.current !== null && getComputedStyle(gridRef.current).direction === "rtl" ? -1 : 1;
210
+ if (e.key === "ArrowRight") next = addDays(current, horizontalStep);
211
+ else if (e.key === "ArrowLeft") next = addDays(current, -horizontalStep);
212
+ else if (e.key === "ArrowDown") next = addDays(current, 7);
213
+ else if (e.key === "ArrowUp") next = addDays(current, -7);
214
+ else if (e.key === "Home") {
215
+ const offset = (((/* @__PURE__ */ new Date(`${current}T00:00:00Z`)).getUTCDay() + 6) % 7 - firstDow + 7) % 7;
216
+ next = addDays(current, -offset);
217
+ } else if (e.key === "End") {
218
+ const offset = (((/* @__PURE__ */ new Date(`${current}T00:00:00Z`)).getUTCDay() + 6) % 7 - firstDow + 7) % 7;
219
+ next = addDays(current, 6 - offset);
220
+ } else if (e.key === "PageUp") {
221
+ const d = /* @__PURE__ */ new Date(`${current}T00:00:00Z`);
222
+ if (e.shiftKey) d.setUTCFullYear(d.getUTCFullYear() - 1);
223
+ else d.setUTCMonth(d.getUTCMonth() - 1);
224
+ next = toISODate(d);
225
+ } else if (e.key === "PageDown") {
226
+ const d = /* @__PURE__ */ new Date(`${current}T00:00:00Z`);
227
+ if (e.shiftKey) d.setUTCFullYear(d.getUTCFullYear() + 1);
228
+ else d.setUTCMonth(d.getUTCMonth() + 1);
229
+ next = toISODate(d);
230
+ } else {
231
+ if (e.key === "Enter" || e.key === " ") {
232
+ e.preventDefault();
233
+ if (!isDayDisabled(current)) onChange(current);
234
+ }
235
+ return;
236
+ }
237
+ e.preventDefault();
238
+ if (next) {
239
+ setFocusedDate(next);
240
+ const nextDate = /* @__PURE__ */ new Date(`${next}T00:00:00Z`);
241
+ const nextMonth = nextDate.getUTCMonth();
242
+ const nextYear = nextDate.getUTCFullYear();
243
+ if (nextMonth !== viewMonth || nextYear !== viewYear) setViewMonthOverride({
244
+ month: nextMonth,
245
+ year: nextYear
246
+ });
247
+ requestAnimationFrame(() => {
248
+ (gridRef.current?.querySelector(`[data-date="${next}"]`))?.focus();
249
+ });
250
+ }
251
+ }, [
252
+ focusedDate,
253
+ value,
254
+ days,
255
+ firstDow,
256
+ viewMonth,
257
+ viewYear,
258
+ isDayDisabled,
259
+ onChange,
260
+ setViewMonthOverride
261
+ ]);
262
+ const handlePrev = () => {
263
+ if (view === "days") if (viewMonth === 0) setViewMonthOverride({
264
+ month: 11,
265
+ year: viewYear - 1
266
+ });
267
+ else setViewMonthOverride({
268
+ month: viewMonth - 1,
269
+ year: viewYear
270
+ });
271
+ else if (view === "months") setViewMonthOverride({
272
+ month: viewMonth,
273
+ year: viewYear - 1
274
+ });
275
+ else setDecadeBaseOverride(decadeBase - 10);
276
+ };
277
+ const handleNext = () => {
278
+ if (view === "days") if (viewMonth === 11) setViewMonthOverride({
279
+ month: 0,
280
+ year: viewYear + 1
281
+ });
282
+ else setViewMonthOverride({
283
+ month: viewMonth + 1,
284
+ year: viewYear
285
+ });
286
+ else if (view === "months") setViewMonthOverride({
287
+ month: viewMonth,
288
+ year: viewYear + 1
289
+ });
290
+ else setDecadeBaseOverride(decadeBase + 10);
291
+ };
292
+ const headerLabel = (() => {
293
+ if (view === "days") return formatMonthYear(locale, viewYear, viewMonth);
294
+ if (view === "months") return String(viewYear);
295
+ return `${decadeBase}\u2013${decadeBase + 9}`;
296
+ })();
297
+ const handleHeaderClick = () => {
298
+ if (view === "days") setView("months");
299
+ else if (view === "months") {
300
+ setDecadeBaseOverride(decadeStart(viewYear));
301
+ setView("years");
302
+ }
303
+ };
304
+ const handleMonthSelect = (month) => {
305
+ setViewMonthOverride({
306
+ month,
307
+ year: viewYear
308
+ });
309
+ setView("days");
310
+ };
311
+ const handleYearSelect = (year) => {
312
+ setViewMonthOverride({
313
+ month: viewMonth,
314
+ year
315
+ });
316
+ setDecadeBaseOverride(decadeStart(year));
317
+ setView("months");
318
+ };
319
+ const selectedYear = value ? (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).getUTCFullYear() : null;
320
+ const selectedMonth = value ? (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).getUTCMonth() : null;
321
+ const handleOpenChange = (open) => {
322
+ onOpenChange?.(open);
323
+ if (!open) {
324
+ setView("days");
325
+ setDecadeBaseOverride(decadeStart(viewYear));
326
+ }
327
+ };
328
+ return /* @__PURE__ */ jsxs(Popover, {
329
+ defaultOpen,
330
+ onOpenChange: handleOpenChange,
331
+ children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
332
+ render: /* @__PURE__ */ jsx("button", {
333
+ "aria-label": !labelledBy && value ? displayLabel : void 0,
334
+ "aria-labelledby": labelledBy ? `${labelledBy} ${displayValueId}` : void 0,
335
+ className: cn("flex h-auto min-h-7 w-full min-w-0 items-center gap-1.5", "rounded-md px-1.5 text-sm", "hover:bg-muted transition-colors", (() => {
336
+ if (isOverdue) return "text-destructive";
337
+ if (value) return "text-foreground";
338
+ return "text-muted-foreground";
339
+ })()),
340
+ id,
341
+ type: "button"
342
+ }),
343
+ children: [
344
+ showIcon && /* @__PURE__ */ jsx(CalendarIcon, { className: "size-3.5 shrink-0" }),
345
+ /* @__PURE__ */ jsx("span", {
346
+ className: "min-w-0 flex-1 overflow-hidden text-start wrap-break-word text-ellipsis",
347
+ id: labelledBy ? displayValueId : void 0,
348
+ children: displayLabel
349
+ }),
350
+ isOverdue && overdueLabel && /* @__PURE__ */ jsx("span", {
351
+ className: "text-destructive text-xs",
352
+ children: overdueLabel
353
+ })
354
+ ]
355
+ }), /* @__PURE__ */ jsx(PopoverPopup, {
356
+ className: "*:data-[slot=popover-viewport]:p-2!",
357
+ layer,
358
+ side: "bottom",
359
+ sideOffset: 4,
360
+ children: /* @__PURE__ */ jsxs("div", {
361
+ className: "w-60",
362
+ role: "dialog",
363
+ "aria-label": "Date picker",
364
+ children: [
365
+ /* @__PURE__ */ jsxs("div", {
366
+ className: "flex items-center justify-between gap-1 pb-1",
367
+ children: [
368
+ /* @__PURE__ */ jsx(Button, {
369
+ "aria-label": (() => {
370
+ if (view === "days") return "Previous month";
371
+ if (view === "months") return "Previous year";
372
+ return "Previous decade";
373
+ })(),
374
+ onClick: handlePrev,
375
+ size: "icon-xs",
376
+ variant: "ghost",
377
+ children: /* @__PURE__ */ jsx(DirectionalIcon, { icon: ChevronLeftIcon })
378
+ }),
379
+ /* @__PURE__ */ jsx("button", {
380
+ className: cn("text-xs font-medium transition-colors", view !== "years" && "hover:bg-muted cursor-pointer rounded-md px-2 py-0.5", view === "years" && "cursor-default"),
381
+ onClick: handleHeaderClick,
382
+ tabIndex: view === "years" ? -1 : 0,
383
+ type: "button",
384
+ children: headerLabel
385
+ }),
386
+ /* @__PURE__ */ jsx(Button, {
387
+ "aria-label": (() => {
388
+ if (view === "days") return "Next month";
389
+ if (view === "months") return "Next year";
390
+ return "Next decade";
391
+ })(),
392
+ onClick: handleNext,
393
+ size: "icon-xs",
394
+ variant: "ghost",
395
+ children: /* @__PURE__ */ jsx(DirectionalIcon, { icon: ChevronRightIcon })
396
+ })
397
+ ]
398
+ }),
399
+ view === "days" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
400
+ className: "grid grid-cols-7 gap-0",
401
+ role: "row",
402
+ "aria-hidden": "true",
403
+ children: weekdays.map((weekday) => /* @__PURE__ */ jsx("span", {
404
+ className: cn("py-1 text-center text-[10px]", weekday.isWeekend ? "text-muted-foreground" : "text-foreground-label"),
405
+ children: weekday.label
406
+ }, weekday.label))
407
+ }), /* @__PURE__ */ jsx("div", {
408
+ "aria-label": headerLabel,
409
+ className: "grid grid-cols-7 gap-0",
410
+ onKeyDown: handleGridKeyDown,
411
+ ref: gridRef,
412
+ role: "grid",
413
+ tabIndex: -1,
414
+ children: days.map((day) => {
415
+ const isSelected = day.date === value;
416
+ const isFocused = day.date === focusedDate || !focusedDate && isSelected || !focusedDate && !value && day.isToday;
417
+ const disabled = isDayDisabled(day.date);
418
+ return /* @__PURE__ */ jsx("button", {
419
+ "aria-current": day.isToday ? "date" : void 0,
420
+ "aria-disabled": disabled || void 0,
421
+ "aria-label": formatDayLabel(day.date),
422
+ "aria-selected": isSelected || void 0,
423
+ className: cn("flex size-8 items-center justify-center", "rounded-full text-xs transition-colors", "focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none", disabled ? "text-foreground-disabled cursor-not-allowed" : "hover:bg-muted cursor-pointer", !day.isCurrentMonth && !disabled && "text-foreground-disabled", day.isWeekend && day.isCurrentMonth && !disabled && !isSelected && "text-foreground-label", day.isToday && !isSelected && !disabled && "ring-foreground font-medium ring-1", isSelected && "bg-primary text-primary-foreground hover:bg-primary/90"),
424
+ "data-date": day.date,
425
+ onClick: () => {
426
+ if (!disabled) onChange(day.date);
427
+ },
428
+ role: "gridcell",
429
+ tabIndex: isFocused ? 0 : -1,
430
+ type: "button",
431
+ children: Number.parseInt(day.date.slice(8), 10)
432
+ }, day.date);
433
+ })
434
+ })] }),
435
+ view === "months" && /* @__PURE__ */ jsx(MonthGrid, {
436
+ currentMonth: selectedMonth,
437
+ currentYear: selectedYear,
438
+ locale,
439
+ onSelect: handleMonthSelect,
440
+ today,
441
+ viewYear
442
+ }),
443
+ view === "years" && /* @__PURE__ */ jsx(YearGrid, {
444
+ currentYear: selectedYear,
445
+ decadeBase,
446
+ onSelect: handleYearSelect,
447
+ today
448
+ }),
449
+ /* @__PURE__ */ jsxs("div", {
450
+ className: "mt-1 flex items-center gap-1 border-t pt-1",
451
+ children: [/* @__PURE__ */ jsx(Button, {
452
+ className: "flex-1",
453
+ onClick: () => {
454
+ const todayDate = /* @__PURE__ */ new Date(`${today}T00:00:00Z`);
455
+ setViewMonthOverride({
456
+ month: todayDate.getUTCMonth(),
457
+ year: todayDate.getUTCFullYear()
458
+ });
459
+ setView("days");
460
+ },
461
+ size: "xs",
462
+ variant: "ghost",
463
+ children: todayLabel
464
+ }), value && /* @__PURE__ */ jsx(Button, {
465
+ className: "flex-1",
466
+ onClick: () => onChange(null),
467
+ size: "xs",
468
+ variant: "ghost",
469
+ children: clearLabel
470
+ })]
471
+ })
472
+ ]
473
+ })
474
+ })]
475
+ });
476
+ };
477
+ const DatePickerPopover = (props) => {
478
+ const browserLocale = useHydrationSafeBrowserLocale();
479
+ const today = useHydrationSafeToday();
480
+ const locale = props.locale ?? browserLocale;
481
+ return /* @__PURE__ */ jsx(DatePickerPopoverContent, {
482
+ ...props,
483
+ locale,
484
+ today
485
+ });
486
+ };
487
+ const MONTHS_PER_ROW = 3;
488
+ const MonthGrid = ({ locale, viewYear, currentMonth, currentYear, onSelect, today }) => {
489
+ const labels = useMemo(() => getMonthLabels(locale, "short"), [locale]);
490
+ const now = /* @__PURE__ */ new Date(`${today}T00:00:00Z`);
491
+ const todayMonth = now.getUTCMonth();
492
+ const todayYear = now.getUTCFullYear();
493
+ const rows = [];
494
+ for (let r = 0; r < 12; r += MONTHS_PER_ROW) rows.push(Array.from({ length: MONTHS_PER_ROW }, (_, c) => r + c));
495
+ return /* @__PURE__ */ jsx("div", {
496
+ className: "grid grid-cols-3 gap-1 py-1",
497
+ role: "grid",
498
+ children: rows.map((row) => /* @__PURE__ */ jsx("div", {
499
+ className: "contents",
500
+ role: "row",
501
+ children: row.map((month) => {
502
+ const isSelected = currentMonth !== null && currentYear !== null && month === currentMonth && viewYear === currentYear;
503
+ return /* @__PURE__ */ jsxs("button", {
504
+ "aria-selected": isSelected || void 0,
505
+ className: cn("rounded-md px-2 py-1.5 text-xs transition-colors", "hover:bg-muted cursor-pointer", "focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none", month === todayMonth && viewYear === todayYear && !isSelected && "ring-foreground font-medium ring-1", isSelected && "bg-primary text-primary-foreground hover:bg-primary/90"),
506
+ onClick: () => onSelect(month),
507
+ role: "gridcell",
508
+ type: "button",
509
+ children: [!/^\d/u.test(labels[month] ?? "") && /* @__PURE__ */ jsx("span", {
510
+ "aria-hidden": "true",
511
+ className: cn("me-1 text-[10px] tabular-nums opacity-50", !isSelected && "text-muted-foreground"),
512
+ children: month + 1
513
+ }), labels[month]]
514
+ }, month);
515
+ })
516
+ }, row[0]))
517
+ });
518
+ };
519
+ const YEARS_PER_ROW = 3;
520
+ const YearGrid = ({ decadeBase, currentYear, onSelect, today }) => {
521
+ const todayYear = (/* @__PURE__ */ new Date(`${today}T00:00:00Z`)).getUTCFullYear();
522
+ const startYear = decadeBase - 1;
523
+ const rows = [];
524
+ for (let r = 0; r < DECADE_SIZE; r += YEARS_PER_ROW) rows.push(Array.from({ length: Math.min(YEARS_PER_ROW, DECADE_SIZE - r) }, (_, c) => r + c));
525
+ return /* @__PURE__ */ jsx("div", {
526
+ className: "grid grid-cols-3 gap-1 py-1",
527
+ role: "grid",
528
+ children: rows.map((row) => /* @__PURE__ */ jsx("div", {
529
+ className: "contents",
530
+ role: "row",
531
+ children: row.map((i) => {
532
+ const year = startYear + i;
533
+ const isOutside = i === 0 || i === 11;
534
+ const isSelected = currentYear !== null && year === currentYear;
535
+ return /* @__PURE__ */ jsx("button", {
536
+ "aria-selected": isSelected || void 0,
537
+ className: cn("rounded-md px-2 py-1.5 text-xs transition-colors", "hover:bg-muted cursor-pointer", "focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none", isOutside && !isSelected && "text-foreground-subtle", year === todayYear && !isSelected && "ring-foreground font-medium ring-1", isSelected && "bg-primary text-primary-foreground hover:bg-primary/90"),
538
+ onClick: () => onSelect(year),
539
+ role: "gridcell",
540
+ type: "button",
541
+ children: year
542
+ }, year);
543
+ })
544
+ }, row[0]))
545
+ });
546
+ };
547
+ //#endregion
548
+ export { DatePickerPopover };
@@ -0,0 +1,14 @@
1
+ //#region src/components/date-picker-popover.logic.d.ts
2
+ type CalendarMonth = {
3
+ month: number;
4
+ year: number;
5
+ };
6
+ declare const localDateFromTimestamp: (timestamp: number) => string;
7
+ declare const resolveCalendarViewMonth: ({ override, today, value }: {
8
+ override: CalendarMonth | null;
9
+ today: string;
10
+ value: string;
11
+ }) => CalendarMonth;
12
+ declare const millisecondsUntilNextLocalDate: (timestamp: number) => number;
13
+ //#endregion
14
+ export { CalendarMonth, localDateFromTimestamp, millisecondsUntilNextLocalDate, resolveCalendarViewMonth };