@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.
- package/README.md +84 -0
- package/dist/components/accordion.d.ts +8 -0
- package/dist/components/accordion.js +38 -0
- package/dist/components/alert-dialog.d.ts +32 -0
- package/dist/components/alert-dialog.js +75 -0
- package/dist/components/avatar.d.ts +7 -0
- package/dist/components/avatar.js +22 -0
- package/dist/components/bidi-text.d.ts +24 -0
- package/dist/components/bidi-text.js +38 -0
- package/dist/components/brand-icons.d.ts +7 -0
- package/dist/components/brand-icons.js +18 -0
- package/dist/components/breadcrumb.d.ts +12 -0
- package/dist/components/breadcrumb.js +61 -0
- package/dist/components/button-variants.d.ts +17 -0
- package/dist/components/button-variants.js +43 -0
- package/dist/components/button.d.ts +20 -0
- package/dist/components/button.js +53 -0
- package/dist/components/checkbox.d.ts +5 -0
- package/dist/components/checkbox.js +44 -0
- package/dist/components/color-picker.d.ts +47 -0
- package/dist/components/color-picker.js +249 -0
- package/dist/components/combobox.d.ts +40 -0
- package/dist/components/combobox.js +208 -0
- package/dist/components/command.d.ts +34 -0
- package/dist/components/command.js +100 -0
- package/dist/components/date-picker-popover.d.ts +29 -0
- package/dist/components/date-picker-popover.js +548 -0
- package/dist/components/date-picker-popover.logic.d.ts +14 -0
- package/dist/components/date-picker-popover.logic.js +26 -0
- package/dist/components/destructive-action-confirmation.d.ts +21 -0
- package/dist/components/destructive-action-confirmation.js +60 -0
- package/dist/components/destructive-confirm-dialog.d.ts +17 -0
- package/dist/components/destructive-confirm-dialog.js +49 -0
- package/dist/components/dialog.d.ts +37 -0
- package/dist/components/dialog.js +92 -0
- package/dist/components/directional-icon.d.ts +30 -0
- package/dist/components/directional-icon.js +12 -0
- package/dist/components/field.d.ts +11 -0
- package/dist/components/field.js +34 -0
- package/dist/components/form.d.ts +5 -0
- package/dist/components/form.js +12 -0
- package/dist/components/frame.d.ts +10 -0
- package/dist/components/frame.js +35 -0
- package/dist/components/hex-color-picker.d.ts +11 -0
- package/dist/components/hex-color-picker.js +366 -0
- package/dist/components/input-group.d.ts +15 -0
- package/dist/components/input-group.js +53 -0
- package/dist/components/input-otp.d.ts +13 -0
- package/dist/components/input-otp.js +39 -0
- package/dist/components/input.d.ts +12 -0
- package/dist/components/input.js +46 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.js +18 -0
- package/dist/components/menu.d.ts +44 -0
- package/dist/components/menu.js +152 -0
- package/dist/components/outline-rail.d.ts +43 -0
- package/dist/components/outline-rail.js +416 -0
- package/dist/components/pagination.d.ts +17 -0
- package/dist/components/pagination.js +78 -0
- package/dist/components/popover.d.ts +34 -0
- package/dist/components/popover.js +67 -0
- package/dist/components/preview-card.d.ts +12 -0
- package/dist/components/preview-card.js +25 -0
- package/dist/components/preview-pane.d.ts +23 -0
- package/dist/components/preview-pane.js +33 -0
- package/dist/components/scroll-area.d.ts +19 -0
- package/dist/components/scroll-area.js +38 -0
- package/dist/components/scroll-to-top.d.ts +12 -0
- package/dist/components/scroll-to-top.js +43 -0
- package/dist/components/secret-input.d.ts +9 -0
- package/dist/components/secret-input.js +38 -0
- package/dist/components/segmented-icon-toggle.d.ts +22 -0
- package/dist/components/segmented-icon-toggle.js +30 -0
- package/dist/components/select.d.ts +25 -0
- package/dist/components/select.js +196 -0
- package/dist/components/separator.d.ts +16 -0
- package/dist/components/separator.js +26 -0
- package/dist/components/sheet.d.ts +29 -0
- package/dist/components/sheet.js +87 -0
- package/dist/components/skeleton.d.ts +4 -0
- package/dist/components/skeleton.js +10 -0
- package/dist/components/stella-mark.d.ts +5 -0
- package/dist/components/stella-mark.js +12 -0
- package/dist/components/stella-wordmark.d.ts +5 -0
- package/dist/components/stella-wordmark.js +21 -0
- package/dist/components/table.d.ts +20 -0
- package/dist/components/table.js +80 -0
- package/dist/components/tabs.d.ts +11 -0
- package/dist/components/tabs.js +46 -0
- package/dist/components/textarea.d.ts +9 -0
- package/dist/components/textarea.js +31 -0
- package/dist/components/toast.d.ts +60 -0
- package/dist/components/toast.js +261 -0
- package/dist/components/tooltip-trigger-helper.d.ts +11 -0
- package/dist/components/tooltip-trigger-helper.js +14 -0
- package/dist/components/tooltip.d.ts +15 -0
- package/dist/components/tooltip.js +32 -0
- package/dist/data-table/index.d.ts +2 -0
- package/dist/data-table/index.js +2 -0
- package/dist/data-table/schema.d.ts +70 -0
- package/dist/data-table/schema.js +40 -0
- package/dist/hooks/use-contained-handler.d.ts +55 -0
- package/dist/hooks/use-contained-handler.js +54 -0
- package/dist/hooks/use-content-dir.d.ts +48 -0
- package/dist/hooks/use-content-dir.js +45 -0
- package/dist/hooks/use-mobile.d.ts +4 -0
- package/dist/hooks/use-mobile.js +16 -0
- package/dist/hooks/use-viewport-width.d.ts +5 -0
- package/dist/hooks/use-viewport-width.js +14 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +65 -0
- package/dist/inspector/chrome.d.ts +72 -0
- package/dist/inspector/chrome.js +153 -0
- package/dist/inspector/dock.d.ts +48 -0
- package/dist/inspector/dock.js +36 -0
- package/dist/inspector/index.d.ts +6 -0
- package/dist/inspector/index.js +6 -0
- package/dist/inspector/layout-tokens.d.ts +28 -0
- package/dist/inspector/layout-tokens.js +28 -0
- package/dist/inspector/pane-width.d.ts +64 -0
- package/dist/inspector/pane-width.js +53 -0
- package/dist/inspector/use-pane-width.d.ts +99 -0
- package/dist/inspector/use-pane-width.js +149 -0
- package/dist/kanban/card-properties.d.ts +27 -0
- package/dist/kanban/card-properties.js +19 -0
- package/dist/kanban/card-shell.d.ts +32 -0
- package/dist/kanban/card-shell.js +46 -0
- package/dist/kanban/column-header.d.ts +23 -0
- package/dist/kanban/column-header.js +24 -0
- package/dist/kanban/grouping.d.ts +84 -0
- package/dist/kanban/grouping.js +76 -0
- package/dist/kanban/index.d.ts +5 -0
- package/dist/kanban/index.js +5 -0
- package/dist/lib/button-disposition.d.ts +63 -0
- package/dist/lib/button-disposition.js +52 -0
- package/dist/lib/option-color.d.ts +28 -0
- package/dist/lib/option-color.js +59 -0
- package/dist/lib/overlay-layer.d.ts +22 -0
- package/dist/lib/overlay-layer.js +21 -0
- package/dist/lib/tab-order.d.ts +15 -0
- package/dist/lib/tab-order.js +15 -0
- package/dist/lib/tooltip-content.d.ts +13 -0
- package/dist/lib/tooltip-content.js +12 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.js +41 -0
- package/dist/lib/week.d.ts +25 -0
- package/dist/lib/week.js +40 -0
- package/dist/styles/theme.css +1209 -0
- 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 };
|