cortena-ui 1.3.0 → 1.4.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 +74 -3
- package/dist/a2ui/catalogue-doc.d.ts +44 -0
- package/dist/a2ui/catalogue-doc.js +169 -0
- package/dist/a2ui/catalogue-doc.js.map +1 -0
- package/dist/a2ui/catalogue.d.ts +44 -0
- package/dist/a2ui/catalogue.js +942 -0
- package/dist/a2ui/catalogue.js.map +1 -0
- package/dist/a2ui/index.d.ts +7 -0
- package/dist/a2ui/message.d.ts +232 -0
- package/dist/a2ui/message.js +365 -0
- package/dist/a2ui/message.js.map +1 -0
- package/dist/a2ui/renderer.d.ts +46 -0
- package/dist/a2ui/renderer.js +252 -0
- package/dist/a2ui/renderer.js.map +1 -0
- package/dist/a2ui/views.d.ts +80 -0
- package/dist/a2ui/views.js +596 -0
- package/dist/a2ui/views.js.map +1 -0
- package/dist/a2ui.d.ts +8 -0
- package/dist/a2ui.js +7 -0
- package/dist/chart.d.ts +9 -0
- package/dist/chart.js +9 -0
- package/dist/components/accordion.d.ts +22 -0
- package/dist/components/accordion.js +63 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert.d.ts +15 -0
- package/dist/components/alert.js +64 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/avatar.d.ts +18 -0
- package/dist/components/avatar.js +40 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +12 -0
- package/dist/components/badge.js +47 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.d.ts +71 -0
- package/dist/components/breadcrumb.js +123 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button-link.d.ts +28 -0
- package/dist/components/button-link.js +24 -0
- package/dist/components/button-link.js.map +1 -0
- package/dist/components/button.d.ts +24 -0
- package/dist/components/button.js +65 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +28 -0
- package/dist/components/calendar.js +123 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/card.d.ts +32 -0
- package/dist/components/card.js +57 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/chart/chart.d.ts +71 -0
- package/dist/components/chart/chart.js +167 -0
- package/dist/components/chart/chart.js.map +1 -0
- package/dist/components/chart/container.d.ts +88 -0
- package/dist/components/chart/container.js +189 -0
- package/dist/components/chart/container.js.map +1 -0
- package/dist/components/chart/data.js +128 -0
- package/dist/components/chart/data.js.map +1 -0
- package/dist/components/chart/index.d.ts +9 -0
- package/dist/components/chart/index.js +10 -0
- package/dist/components/chart/nivo-charts.js +225 -0
- package/dist/components/chart/nivo-charts.js.map +1 -0
- package/dist/components/chart/nivo-lazy.js +50 -0
- package/dist/components/chart/nivo-lazy.js.map +1 -0
- package/dist/components/chart/nivo-theme.d.ts +156 -0
- package/dist/components/chart/nivo-theme.js +161 -0
- package/dist/components/chart/nivo-theme.js.map +1 -0
- package/dist/components/chart/recharts-charts.js +276 -0
- package/dist/components/chart/recharts-charts.js.map +1 -0
- package/dist/components/chart/recharts-lazy.js +58 -0
- package/dist/components/chart/recharts-lazy.js.map +1 -0
- package/dist/components/chart/recharts-parts.d.ts +10 -0
- package/dist/components/chart/recharts-parts.js +25 -0
- package/dist/components/chart/recharts-parts.js.map +1 -0
- package/dist/components/chart/tokens.d.ts +62 -0
- package/dist/components/chart/tokens.js +192 -0
- package/dist/components/chart/tokens.js.map +1 -0
- package/dist/components/chart/types.d.ts +114 -0
- package/dist/components/chart/types.js +27 -0
- package/dist/components/chart/types.js.map +1 -0
- package/dist/components/checkbox.d.ts +20 -0
- package/dist/components/checkbox.js +24 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +17 -0
- package/dist/components/chip.js +47 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/combobox.d.ts +76 -0
- package/dist/components/combobox.js +201 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/command.d.ts +50 -0
- package/dist/components/command.js +117 -0
- package/dist/components/command.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +65 -0
- package/dist/components/data-table/data-table.js +588 -0
- package/dist/components/data-table/data-table.js.map +1 -0
- package/dist/components/data-table/export.d.ts +23 -0
- package/dist/components/data-table/export.js +95 -0
- package/dist/components/data-table/export.js.map +1 -0
- package/dist/components/data-table/features.d.ts +66 -0
- package/dist/components/data-table/features.js +103 -0
- package/dist/components/data-table/features.js.map +1 -0
- package/dist/components/data-table/index.d.ts +8 -0
- package/dist/components/data-table/parts.d.ts +79 -0
- package/dist/components/data-table/parts.js +400 -0
- package/dist/components/data-table/parts.js.map +1 -0
- package/dist/components/data-table/system-columns.js +84 -0
- package/dist/components/data-table/system-columns.js.map +1 -0
- package/dist/components/data-table/types.d.ts +172 -0
- package/dist/components/data-table/use-data-table.d.ts +19 -0
- package/dist/components/data-table/use-data-table.js +171 -0
- package/dist/components/data-table/use-data-table.js.map +1 -0
- package/dist/components/data-table/use-server-source.js +200 -0
- package/dist/components/data-table/use-server-source.js.map +1 -0
- package/dist/components/date-field.d.ts +54 -0
- package/dist/components/date-field.js +235 -0
- package/dist/components/date-field.js.map +1 -0
- package/dist/components/date-picker.d.ts +42 -0
- package/dist/components/date-picker.js +125 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +36 -0
- package/dist/components/dialog.js +97 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +54 -0
- package/dist/components/dropdown-menu.js +191 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/dropzone.d.ts +48 -0
- package/dist/components/dropzone.js +185 -0
- package/dist/components/dropzone.js.map +1 -0
- package/dist/components/empty-state.d.ts +22 -0
- package/dist/components/empty-state.js +46 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/error-banner.d.ts +17 -0
- package/dist/components/error-banner.js +52 -0
- package/dist/components/error-banner.js.map +1 -0
- package/dist/components/field.d.ts +64 -0
- package/dist/components/field.js +133 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/form.d.ts +37 -0
- package/dist/components/form.js +103 -0
- package/dist/components/form.js.map +1 -0
- package/dist/components/input.d.ts +17 -0
- package/dist/components/input.js +26 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +11 -0
- package/dist/components/kbd.js +20 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +14 -0
- package/dist/components/label.js +28 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/loading-skeleton.d.ts +22 -0
- package/dist/components/loading-skeleton.js +123 -0
- package/dist/components/loading-skeleton.js.map +1 -0
- package/dist/components/markdown.d.ts +44 -0
- package/dist/components/markdown.js +231 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/page-header.d.ts +26 -0
- package/dist/components/page-header.js +53 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/popover.d.ts +29 -0
- package/dist/components/popover.js +74 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +22 -0
- package/dist/components/progress.js +49 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/radio-group.d.ts +20 -0
- package/dist/components/radio-group.js +29 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts +25 -0
- package/dist/components/scroll-area.js +39 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/section-card.d.ts +29 -0
- package/dist/components/section-card.js +55 -0
- package/dist/components/section-card.js.map +1 -0
- package/dist/components/segmented.d.ts +27 -0
- package/dist/components/segmented.js +32 -0
- package/dist/components/segmented.js.map +1 -0
- package/dist/components/select.d.ts +62 -0
- package/dist/components/select.js +97 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +19 -0
- package/dist/components/separator.js +23 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +35 -0
- package/dist/components/sheet.js +131 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +12 -0
- package/dist/components/skeleton.js +22 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/sortable-list.d.ts +67 -0
- package/dist/components/sortable-list.js +206 -0
- package/dist/components/sortable-list.js.map +1 -0
- package/dist/components/spinner.d.ts +29 -0
- package/dist/components/spinner.js +34 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/status-dot.d.ts +26 -0
- package/dist/components/status-dot.js +47 -0
- package/dist/components/status-dot.js.map +1 -0
- package/dist/components/switch.d.ts +21 -0
- package/dist/components/switch.js +21 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +24 -0
- package/dist/components/tabs.js +60 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +19 -0
- package/dist/components/textarea.js +47 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toast.d.ts +47 -0
- package/dist/components/toast.js +106 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toolbar.d.ts +19 -0
- package/dist/components/toolbar.js +45 -0
- package/dist/components/toolbar.js.map +1 -0
- package/dist/components/tooltip.d.ts +28 -0
- package/dist/components/tooltip.js +59 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/core.d.ts +44 -0
- package/dist/core.js +44 -0
- package/dist/data-table.d.ts +9 -0
- package/dist/data-table.js +7 -0
- package/dist/form.d.ts +7 -0
- package/dist/form.js +7 -0
- package/dist/hooks/use-cortena-theme.d.ts +98 -0
- package/dist/hooks/use-cortena-theme.js +145 -0
- package/dist/hooks/use-cortena-theme.js.map +1 -0
- package/dist/index.d.ts +75 -2678
- package/dist/index.js +66 -8598
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.js +15 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/render.d.ts +23 -0
- package/dist/lib/render.js +20 -0
- package/dist/lib/render.js.map +1 -0
- package/dist/lib/shakeable.js +39 -0
- package/dist/lib/shakeable.js.map +1 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/markdown.js +3 -0
- package/dist/sortable-list.d.ts +3 -0
- package/dist/sortable-list.js +3 -0
- package/package.json +37 -14
- package/src/a2ui/catalogue.ts +140 -103
- package/src/a2ui/views.tsx +5 -2
- package/src/components/alert.tsx +1 -1
- package/src/components/badge.tsx +1 -1
- package/src/components/button.tsx +1 -1
- package/src/components/chart/chart.tsx +72 -25
- package/src/components/chart/container.tsx +24 -39
- package/src/components/chart/index.tsx +4 -2
- package/src/components/chart/nivo-charts.tsx +41 -13
- package/src/components/chart/nivo-lazy.tsx +52 -0
- package/src/components/chart/recharts-charts.tsx +98 -3
- package/src/components/chart/recharts-lazy.tsx +72 -0
- package/src/components/chart/recharts-parts.tsx +34 -0
- package/src/components/chart/tokens.ts +1 -1
- package/src/components/chart/types.ts +14 -0
- package/src/components/chip.tsx +1 -1
- package/src/components/combobox.tsx +2 -2
- package/src/components/data-table/export.ts +18 -2
- package/src/components/data-table/features.ts +77 -45
- package/src/components/data-table/parts.tsx +15 -0
- package/src/components/data-table/use-data-table.ts +7 -2
- package/src/components/data-table/use-server-source.ts +17 -4
- package/src/components/date-field.tsx +1 -1
- package/src/components/error-banner.tsx +1 -1
- package/src/components/field.tsx +2 -2
- package/src/components/form.tsx +2 -2
- package/src/components/input.tsx +1 -1
- package/src/components/label.tsx +1 -1
- package/src/components/markdown.tsx +5 -4
- package/src/components/sheet.tsx +2 -2
- package/src/components/sortable-list.tsx +2 -2
- package/src/components/textarea.tsx +1 -1
- package/src/components/toast.tsx +1 -1
- package/src/entries/a2ui.ts +11 -0
- package/src/entries/chart.ts +10 -0
- package/src/entries/core.ts +91 -0
- package/src/entries/data-table.ts +9 -0
- package/src/entries/form.ts +24 -0
- package/src/entries/markdown.ts +9 -0
- package/src/entries/sortable-list.ts +13 -0
- package/src/index.ts +32 -98
- package/src/lib/shakeable.ts +33 -0
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
import { Input } from "./input.js";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
7
|
+
import { Calendar } from "./calendar.js";
|
|
8
|
+
import { CalendarIcon } from "lucide-react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
//#region src/components/date-field.tsx
|
|
12
|
+
/**
|
|
13
|
+
* DateField.
|
|
14
|
+
*
|
|
15
|
+
* A form-friendly date input: our `<Input>` showing the formatted date, with
|
|
16
|
+
* the DatePicker's calendar behind a trailing icon button. Typing a date the
|
|
17
|
+
* parser accepts commits it (the default parser reads ISO `yyyy-mm-dd`, and
|
|
18
|
+
* the English `15 Jan 2026` / `15 January 2026` shapes the formatters below
|
|
19
|
+
* produce); picking a day from the calendar writes it back through
|
|
20
|
+
* `formatDate`. Non-empty text the parser rejects, or a date outside
|
|
21
|
+
* `min`/`max`, clears the value and marks the input invalid so a Field can
|
|
22
|
+
* show the error.
|
|
23
|
+
*
|
|
24
|
+
* The date helpers are hand-written for `en-GB` (day month year, no library);
|
|
25
|
+
* any other `locale` goes through the browser's `Intl`.
|
|
26
|
+
*/
|
|
27
|
+
const MONTHS_LONG = [
|
|
28
|
+
"January",
|
|
29
|
+
"February",
|
|
30
|
+
"March",
|
|
31
|
+
"April",
|
|
32
|
+
"May",
|
|
33
|
+
"June",
|
|
34
|
+
"July",
|
|
35
|
+
"August",
|
|
36
|
+
"September",
|
|
37
|
+
"October",
|
|
38
|
+
"November",
|
|
39
|
+
"December"
|
|
40
|
+
];
|
|
41
|
+
const MONTHS_SHORT = /* @__PURE__ */ MONTHS_LONG.map((m) => m.slice(0, 3));
|
|
42
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
43
|
+
const isEn = (locale) => /^en(-GB)?$/i.test(locale);
|
|
44
|
+
/** `2026-01-05`. */
|
|
45
|
+
function formatDateISO(date) {
|
|
46
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
47
|
+
}
|
|
48
|
+
/** `5 Jan 2026` for en-GB; other locales via `Intl.DateTimeFormat`. */
|
|
49
|
+
function formatDateShort(date, locale = "en-GB") {
|
|
50
|
+
if (isEn(locale)) return `${date.getDate()} ${MONTHS_SHORT[date.getMonth()]} ${date.getFullYear()}`;
|
|
51
|
+
return new Intl.DateTimeFormat(locale, {
|
|
52
|
+
day: "numeric",
|
|
53
|
+
month: "short",
|
|
54
|
+
year: "numeric"
|
|
55
|
+
}).format(date);
|
|
56
|
+
}
|
|
57
|
+
/** `5 January 2026` for en-GB; other locales via `Intl.DateTimeFormat`. */
|
|
58
|
+
function formatDateLong(date, locale = "en-GB") {
|
|
59
|
+
if (isEn(locale)) return `${date.getDate()} ${MONTHS_LONG[date.getMonth()]} ${date.getFullYear()}`;
|
|
60
|
+
return new Intl.DateTimeFormat(locale, {
|
|
61
|
+
day: "numeric",
|
|
62
|
+
month: "long",
|
|
63
|
+
year: "numeric"
|
|
64
|
+
}).format(date);
|
|
65
|
+
}
|
|
66
|
+
const DAY_MS = 864e5;
|
|
67
|
+
function startOfDay(date) {
|
|
68
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Calendar-day distance in words: `today`, `yesterday`, `tomorrow`, `3 days
|
|
72
|
+
* ago`, `in 2 weeks`, `4 months ago`, `in 2 years`. Compares local calendar
|
|
73
|
+
* days, so 23:59 to 00:01 is `tomorrow`. Other locales use
|
|
74
|
+
* `Intl.RelativeTimeFormat`.
|
|
75
|
+
*/
|
|
76
|
+
function formatRelative(date, options = {}) {
|
|
77
|
+
const { now = /* @__PURE__ */ new Date(), locale = "en-GB" } = options;
|
|
78
|
+
const days = Math.round((startOfDay(date) - startOfDay(now)) / DAY_MS);
|
|
79
|
+
const abs = Math.abs(days);
|
|
80
|
+
let unit;
|
|
81
|
+
let count;
|
|
82
|
+
if (abs < 7) {
|
|
83
|
+
unit = "day";
|
|
84
|
+
count = days;
|
|
85
|
+
} else if (abs < 30) {
|
|
86
|
+
unit = "week";
|
|
87
|
+
count = Math.round(days / 7);
|
|
88
|
+
} else if (abs < 365) {
|
|
89
|
+
unit = "month";
|
|
90
|
+
count = Math.round(days / 30);
|
|
91
|
+
} else {
|
|
92
|
+
unit = "year";
|
|
93
|
+
count = Math.round(days / 365);
|
|
94
|
+
}
|
|
95
|
+
if (!isEn(locale)) return new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(count, unit);
|
|
96
|
+
if (days === 0) return "today";
|
|
97
|
+
if (days === -1) return "yesterday";
|
|
98
|
+
if (days === 1) return "tomorrow";
|
|
99
|
+
const n = Math.abs(count);
|
|
100
|
+
const label = `${n} ${unit}${n === 1 ? "" : "s"}`;
|
|
101
|
+
return count < 0 ? `${label} ago` : `in ${label}`;
|
|
102
|
+
}
|
|
103
|
+
function makeDate(year, month, day) {
|
|
104
|
+
const date = new Date(year, month - 1, day);
|
|
105
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) return;
|
|
106
|
+
return date;
|
|
107
|
+
}
|
|
108
|
+
/** Strict `yyyy-mm-dd`; rejects impossible days such as `2026-02-30`. */
|
|
109
|
+
function parseDateISO(text) {
|
|
110
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(text.trim());
|
|
111
|
+
if (!m) return void 0;
|
|
112
|
+
return makeDate(Number(m[1]), Number(m[2]), Number(m[3]));
|
|
113
|
+
}
|
|
114
|
+
/** ISO first, then the English `5 Jan 2026` / `5 January 2026` shapes. */
|
|
115
|
+
function parseDateDefault(text) {
|
|
116
|
+
const iso = parseDateISO(text);
|
|
117
|
+
if (iso) return iso;
|
|
118
|
+
const m = /^(\d{1,2})\s+([a-z]+)\s+(\d{4})$/i.exec(text.trim());
|
|
119
|
+
if (!m) return void 0;
|
|
120
|
+
const name = (m[2] ?? "").toLowerCase();
|
|
121
|
+
const month = MONTHS_LONG.findIndex((full) => full.toLowerCase() === name || full.slice(0, 3).toLowerCase() === name);
|
|
122
|
+
if (month < 0) return void 0;
|
|
123
|
+
return makeDate(Number(m[3]), month + 1, Number(m[1]));
|
|
124
|
+
}
|
|
125
|
+
function sameDay(a, b) {
|
|
126
|
+
if (!a || !b) return a === b;
|
|
127
|
+
return startOfDay(a) === startOfDay(b);
|
|
128
|
+
}
|
|
129
|
+
function inRange(date, min, max) {
|
|
130
|
+
const t = startOfDay(date);
|
|
131
|
+
if (min && t < startOfDay(min)) return false;
|
|
132
|
+
if (max && t > startOfDay(max)) return false;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
function DateField(props) {
|
|
136
|
+
const { value, defaultValue, onValueChange, placeholder = "yyyy-mm-dd", formatDate = formatDateISO, parseDate = parseDateDefault, min, max, disabled, invalid, calendarProps, wrapperClassName, className, onBlur, ...inputProps } = props;
|
|
137
|
+
const controlled = "value" in props;
|
|
138
|
+
const [inner, setInner] = React.useState(defaultValue);
|
|
139
|
+
const date = controlled ? value : inner;
|
|
140
|
+
const [text, setText] = React.useState(() => date ? formatDate(date) : "");
|
|
141
|
+
const [textInvalid, setTextInvalid] = React.useState(false);
|
|
142
|
+
const [open, setOpen] = React.useState(false);
|
|
143
|
+
const committed = React.useRef(date);
|
|
144
|
+
const commit = React.useCallback((next) => {
|
|
145
|
+
committed.current = next;
|
|
146
|
+
if (!controlled) setInner(next);
|
|
147
|
+
onValueChange?.(next);
|
|
148
|
+
}, [controlled, onValueChange]);
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
if (sameDay(date, committed.current)) return;
|
|
151
|
+
committed.current = date;
|
|
152
|
+
setText(date ? formatDate(date) : "");
|
|
153
|
+
setTextInvalid(false);
|
|
154
|
+
}, [date, formatDate]);
|
|
155
|
+
const onChange = (event) => {
|
|
156
|
+
const next = event.target.value;
|
|
157
|
+
setText(next);
|
|
158
|
+
if (next.trim() === "") {
|
|
159
|
+
setTextInvalid(false);
|
|
160
|
+
if (date !== void 0) commit(void 0);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const parsed = parseDate(next);
|
|
164
|
+
const ok = parsed !== void 0 && inRange(parsed, min, max);
|
|
165
|
+
setTextInvalid(!ok);
|
|
166
|
+
if (ok) {
|
|
167
|
+
if (!sameDay(parsed, date)) commit(parsed);
|
|
168
|
+
} else if (date !== void 0) commit(void 0);
|
|
169
|
+
};
|
|
170
|
+
const pick = (next) => {
|
|
171
|
+
setOpen(false);
|
|
172
|
+
setTextInvalid(false);
|
|
173
|
+
setText(next ? formatDate(next) : "");
|
|
174
|
+
if (!sameDay(next, date)) commit(next);
|
|
175
|
+
};
|
|
176
|
+
const matchers = [];
|
|
177
|
+
if (min) matchers.push({ before: min });
|
|
178
|
+
if (max) matchers.push({ after: max });
|
|
179
|
+
const isInvalid = invalid || textInvalid;
|
|
180
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
181
|
+
"data-slot": "date-field",
|
|
182
|
+
"data-invalid": isInvalid || void 0,
|
|
183
|
+
"data-disabled": disabled || void 0,
|
|
184
|
+
className: cn("relative w-60", wrapperClassName),
|
|
185
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
186
|
+
type: "text",
|
|
187
|
+
inputMode: "numeric",
|
|
188
|
+
autoComplete: "off",
|
|
189
|
+
"data-slot": "date-field-input",
|
|
190
|
+
value: text,
|
|
191
|
+
placeholder,
|
|
192
|
+
disabled,
|
|
193
|
+
"aria-invalid": isInvalid || void 0,
|
|
194
|
+
onChange,
|
|
195
|
+
onBlur: (event) => {
|
|
196
|
+
if (date && !textInvalid) setText(formatDate(date));
|
|
197
|
+
onBlur?.(event);
|
|
198
|
+
},
|
|
199
|
+
className: cn("pr-10", className),
|
|
200
|
+
...inputProps
|
|
201
|
+
}), /* @__PURE__ */ jsxs(Popover, {
|
|
202
|
+
open,
|
|
203
|
+
onOpenChange: setOpen,
|
|
204
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
205
|
+
"data-slot": "date-field-trigger",
|
|
206
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
207
|
+
variant: "ghost",
|
|
208
|
+
size: "icon-sm"
|
|
209
|
+
}),
|
|
210
|
+
"aria-label": "Open calendar",
|
|
211
|
+
disabled,
|
|
212
|
+
className: cn("absolute top-1/2 right-1 -translate-y-1/2", "text-[color:var(--ds-muted-foreground)] hover:text-[color:var(--ds-foreground)]"),
|
|
213
|
+
children: /* @__PURE__ */ jsx(CalendarIcon, {})
|
|
214
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
215
|
+
"data-slot": "date-field-content",
|
|
216
|
+
align: "end",
|
|
217
|
+
className: "w-auto p-0",
|
|
218
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
219
|
+
...calendarProps,
|
|
220
|
+
mode: "single",
|
|
221
|
+
defaultMonth: calendarProps?.defaultMonth ?? date,
|
|
222
|
+
selected: date,
|
|
223
|
+
onSelect: pick,
|
|
224
|
+
disabled: matchers.length > 0 ? matchers : void 0,
|
|
225
|
+
startMonth: calendarProps?.startMonth ?? min,
|
|
226
|
+
endMonth: calendarProps?.endMonth ?? max
|
|
227
|
+
})
|
|
228
|
+
})]
|
|
229
|
+
})]
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
export { DateField, formatDateISO, formatDateLong, formatDateShort, formatRelative, parseDateDefault, parseDateISO };
|
|
234
|
+
|
|
235
|
+
//# sourceMappingURL=date-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-field.js","names":[],"sources":["../../src/components/date-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { CalendarIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport type { Matcher } from \"react-day-picker\";\nimport { Button } from \"@/components/button\";\nimport { Calendar, type CalendarProps } from \"@/components/calendar\";\nimport { Input } from \"@/components/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/popover\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * DateField.\n *\n * A form-friendly date input: our `<Input>` showing the formatted date, with\n * the DatePicker's calendar behind a trailing icon button. Typing a date the\n * parser accepts commits it (the default parser reads ISO `yyyy-mm-dd`, and\n * the English `15 Jan 2026` / `15 January 2026` shapes the formatters below\n * produce); picking a day from the calendar writes it back through\n * `formatDate`. Non-empty text the parser rejects, or a date outside\n * `min`/`max`, clears the value and marks the input invalid so a Field can\n * show the error.\n *\n * The date helpers are hand-written for `en-GB` (day month year, no library);\n * any other `locale` goes through the browser's `Intl`.\n */\n\nconst MONTHS_LONG = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst MONTHS_SHORT = /* @__PURE__ */ MONTHS_LONG.map((m) => m.slice(0, 3));\n\nconst pad = (n: number) => String(n).padStart(2, \"0\");\n\nconst isEn = (locale: string) => /^en(-GB)?$/i.test(locale);\n\n/** `2026-01-05`. */\nfunction formatDateISO(date: Date): string {\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n}\n\n/** `5 Jan 2026` for en-GB; other locales via `Intl.DateTimeFormat`. */\nfunction formatDateShort(date: Date, locale = \"en-GB\"): string {\n if (isEn(locale)) {\n return `${date.getDate()} ${MONTHS_SHORT[date.getMonth()]} ${date.getFullYear()}`;\n }\n return new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"short\", year: \"numeric\" }).format(\n date,\n );\n}\n\n/** `5 January 2026` for en-GB; other locales via `Intl.DateTimeFormat`. */\nfunction formatDateLong(date: Date, locale = \"en-GB\"): string {\n if (isEn(locale)) {\n return `${date.getDate()} ${MONTHS_LONG[date.getMonth()]} ${date.getFullYear()}`;\n }\n return new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"long\", year: \"numeric\" }).format(\n date,\n );\n}\n\nconst DAY_MS = 86_400_000;\n\nfunction startOfDay(date: Date): number {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\n}\n\n/**\n * Calendar-day distance in words: `today`, `yesterday`, `tomorrow`, `3 days\n * ago`, `in 2 weeks`, `4 months ago`, `in 2 years`. Compares local calendar\n * days, so 23:59 to 00:01 is `tomorrow`. Other locales use\n * `Intl.RelativeTimeFormat`.\n */\nfunction formatRelative(date: Date, options: { now?: Date; locale?: string } = {}): string {\n const { now = new Date(), locale = \"en-GB\" } = options;\n const days = Math.round((startOfDay(date) - startOfDay(now)) / DAY_MS);\n const abs = Math.abs(days);\n let unit: \"day\" | \"week\" | \"month\" | \"year\";\n let count: number;\n if (abs < 7) {\n unit = \"day\";\n count = days;\n } else if (abs < 30) {\n unit = \"week\";\n count = Math.round(days / 7);\n } else if (abs < 365) {\n unit = \"month\";\n count = Math.round(days / 30);\n } else {\n unit = \"year\";\n count = Math.round(days / 365);\n }\n if (!isEn(locale)) {\n return new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" }).format(count, unit);\n }\n if (days === 0) return \"today\";\n if (days === -1) return \"yesterday\";\n if (days === 1) return \"tomorrow\";\n const n = Math.abs(count);\n const label = `${n} ${unit}${n === 1 ? \"\" : \"s\"}`;\n return count < 0 ? `${label} ago` : `in ${label}`;\n}\n\nfunction makeDate(year: number, month: number, day: number): Date | undefined {\n const date = new Date(year, month - 1, day);\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return undefined;\n }\n return date;\n}\n\n/** Strict `yyyy-mm-dd`; rejects impossible days such as `2026-02-30`. */\nfunction parseDateISO(text: string): Date | undefined {\n const m = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/.exec(text.trim());\n if (!m) return undefined;\n return makeDate(Number(m[1]), Number(m[2]), Number(m[3]));\n}\n\n/** ISO first, then the English `5 Jan 2026` / `5 January 2026` shapes. */\nfunction parseDateDefault(text: string): Date | undefined {\n const iso = parseDateISO(text);\n if (iso) return iso;\n const m = /^(\\d{1,2})\\s+([a-z]+)\\s+(\\d{4})$/i.exec(text.trim());\n if (!m) return undefined;\n const name = (m[2] ?? \"\").toLowerCase();\n const month = MONTHS_LONG.findIndex(\n (full) => full.toLowerCase() === name || full.slice(0, 3).toLowerCase() === name,\n );\n if (month < 0) return undefined;\n return makeDate(Number(m[3]), month + 1, Number(m[1]));\n}\n\nfunction sameDay(a: Date | undefined, b: Date | undefined): boolean {\n if (!a || !b) return a === b;\n return startOfDay(a) === startOfDay(b);\n}\n\nfunction inRange(date: Date, min: Date | undefined, max: Date | undefined): boolean {\n const t = startOfDay(date);\n if (min && t < startOfDay(min)) return false;\n if (max && t > startOfDay(max)) return false;\n return true;\n}\n\ntype CalendarPassthrough = Omit<\n CalendarProps,\n \"mode\" | \"selected\" | \"onSelect\" | \"required\" | \"disabled\"\n>;\n\nexport interface DateFieldProps extends Omit<\n React.ComponentProps<\"input\">,\n \"value\" | \"defaultValue\" | \"onChange\" | \"min\" | \"max\" | \"type\"\n> {\n /** The date (controlled). */\n value?: Date | undefined;\n /** The initial date (uncontrolled). */\n defaultValue?: Date | undefined;\n /** Called with the parsed or picked date, or `undefined` when the text is cleared or invalid. */\n onValueChange?: (date: Date | undefined) => void;\n /** Input placeholder; defaults to the ISO shape. */\n placeholder?: string;\n /** Formats the date into the input; defaults to ISO `yyyy-mm-dd`. */\n formatDate?: (date: Date) => string;\n /** Parses typed text; defaults to ISO plus the English day-month-year shapes. */\n parseDate?: (text: string) => Date | undefined;\n /** Earliest selectable day. */\n min?: Date;\n /** Latest selectable day. */\n max?: Date;\n disabled?: boolean;\n /** Force the invalid look (e.g. from a form validator). */\n invalid?: boolean;\n /** Extra props for the Calendar (e.g. `captionLayout`, `today`). */\n calendarProps?: CalendarPassthrough;\n /** Class name for the wrapper; use `className` for the input itself. */\n wrapperClassName?: string;\n}\n\nfunction DateField(props: DateFieldProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n placeholder = \"yyyy-mm-dd\",\n formatDate = formatDateISO,\n parseDate = parseDateDefault,\n min,\n max,\n disabled,\n invalid,\n calendarProps,\n wrapperClassName,\n className,\n onBlur,\n ...inputProps\n } = props;\n const controlled = \"value\" in props;\n const [inner, setInner] = React.useState<Date | undefined>(defaultValue);\n const date = controlled ? value : inner;\n\n const [text, setText] = React.useState(() => (date ? formatDate(date) : \"\"));\n const [textInvalid, setTextInvalid] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const committed = React.useRef<Date | undefined>(date);\n\n const commit = React.useCallback(\n (next: Date | undefined) => {\n committed.current = next;\n if (!controlled) setInner(next);\n onValueChange?.(next);\n },\n [controlled, onValueChange],\n );\n\n // An external value change (form reset, server data) rewrites the text; a\n // change this field committed itself leaves the user's text alone.\n React.useEffect(() => {\n if (sameDay(date, committed.current)) return;\n committed.current = date;\n setText(date ? formatDate(date) : \"\");\n setTextInvalid(false);\n }, [date, formatDate]);\n\n const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n setText(next);\n if (next.trim() === \"\") {\n setTextInvalid(false);\n if (date !== undefined) commit(undefined);\n return;\n }\n const parsed = parseDate(next);\n const ok = parsed !== undefined && inRange(parsed, min, max);\n setTextInvalid(!ok);\n if (ok) {\n if (!sameDay(parsed, date)) commit(parsed);\n } else if (date !== undefined) {\n commit(undefined);\n }\n };\n\n const pick = (next: Date | undefined) => {\n setOpen(false);\n setTextInvalid(false);\n setText(next ? formatDate(next) : \"\");\n if (!sameDay(next, date)) commit(next);\n };\n\n const matchers: Matcher[] = [];\n if (min) matchers.push({ before: min });\n if (max) matchers.push({ after: max });\n const isInvalid = invalid || textInvalid;\n\n return (\n <div\n data-slot=\"date-field\"\n data-invalid={isInvalid || undefined}\n data-disabled={disabled || undefined}\n className={cn(\"relative w-60\", wrapperClassName)}\n >\n <Input\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n data-slot=\"date-field-input\"\n value={text}\n placeholder={placeholder}\n disabled={disabled}\n aria-invalid={isInvalid || undefined}\n onChange={onChange}\n onBlur={(event) => {\n if (date && !textInvalid) setText(formatDate(date));\n onBlur?.(event);\n }}\n className={cn(\"pr-10\", className)}\n {...inputProps}\n />\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n data-slot=\"date-field-trigger\"\n render={<Button variant=\"ghost\" size=\"icon-sm\" />}\n aria-label=\"Open calendar\"\n disabled={disabled}\n className={cn(\n \"absolute top-1/2 right-1 -translate-y-1/2\",\n \"text-[color:var(--ds-muted-foreground)] hover:text-[color:var(--ds-foreground)]\",\n )}\n >\n <CalendarIcon />\n </PopoverTrigger>\n <PopoverContent data-slot=\"date-field-content\" align=\"end\" className=\"w-auto p-0\">\n <Calendar\n {...calendarProps}\n mode=\"single\"\n defaultMonth={calendarProps?.defaultMonth ?? date}\n selected={date}\n onSelect={pick}\n disabled={matchers.length > 0 ? matchers : undefined}\n startMonth={calendarProps?.startMonth ?? min}\n endMonth={calendarProps?.endMonth ?? max}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n\nexport {\n DateField,\n formatDateISO,\n formatDateShort,\n formatDateLong,\n formatRelative,\n parseDateISO,\n parseDateDefault,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { CalendarProps } from "./calendar.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { DateRange } from "react-day-picker";
|
|
5
|
+
//#region src/components/date-picker.d.ts
|
|
6
|
+
/** en-GB order, no library: `15 January 2026`. */
|
|
7
|
+
declare function formatDateDefault(date: Date): string;
|
|
8
|
+
type CalendarPassthrough = Omit<CalendarProps, "mode" | "selected" | "onSelect" | "required" | "disabled">;
|
|
9
|
+
export interface DatePickerProps {
|
|
10
|
+
/** The selected date (controlled). */
|
|
11
|
+
value?: Date | undefined;
|
|
12
|
+
/** The initial date (uncontrolled). */
|
|
13
|
+
defaultValue?: Date | undefined;
|
|
14
|
+
/** Called with the picked date, or `undefined` when the selected day is clicked again. */
|
|
15
|
+
onValueChange?: (date: Date | undefined) => void;
|
|
16
|
+
/** Trigger text while nothing is selected. */
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
/** Formats the trigger text; defaults to `15 January 2026`. */
|
|
19
|
+
formatDate?: (date: Date) => string;
|
|
20
|
+
/** Disables the trigger. */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Class name for the trigger button. */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Extra props for the Calendar (e.g. `startMonth`, `captionLayout`, `disabled` matchers via `calendarProps.hidden`). */
|
|
25
|
+
calendarProps?: CalendarPassthrough & {
|
|
26
|
+
disabled?: CalendarProps["disabled"];
|
|
27
|
+
};
|
|
28
|
+
/** Accessible name for the trigger. */
|
|
29
|
+
"aria-label"?: string;
|
|
30
|
+
}
|
|
31
|
+
declare function DatePicker(props: DatePickerProps): React.JSX.Element;
|
|
32
|
+
export interface DateRangePickerProps extends Omit<DatePickerProps, "value" | "defaultValue" | "onValueChange"> {
|
|
33
|
+
value?: DateRange | undefined;
|
|
34
|
+
defaultValue?: DateRange | undefined;
|
|
35
|
+
onValueChange?: (range: DateRange | undefined) => void;
|
|
36
|
+
/** Separator between the two ends; defaults to an en dash. */
|
|
37
|
+
separator?: string;
|
|
38
|
+
}
|
|
39
|
+
declare function DateRangePicker(props: DateRangePickerProps): React.JSX.Element;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { DatePicker, DateRangePicker, formatDateDefault };
|
|
42
|
+
//# sourceMappingURL=date-picker.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
6
|
+
import { Calendar } from "./calendar.js";
|
|
7
|
+
import { CalendarIcon } from "lucide-react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
//#region src/components/date-picker.tsx
|
|
11
|
+
/**
|
|
12
|
+
* DatePicker.
|
|
13
|
+
*
|
|
14
|
+
* Our outline Button as the trigger, showing the formatted value or a muted
|
|
15
|
+
* placeholder, opening a Popover that holds a single-select Calendar. Picking
|
|
16
|
+
* a day commits the value and closes the popup. Controlled through `value` /
|
|
17
|
+
* `onValueChange`, uncontrolled through `defaultValue`.
|
|
18
|
+
*
|
|
19
|
+
* `DateRangePicker` is the same shape over `mode="range"`. Its popup stays
|
|
20
|
+
* open while both ends are chosen (react-day-picker's first click sets
|
|
21
|
+
* `from === to`, the second widens it), so the user dismisses it.
|
|
22
|
+
*/
|
|
23
|
+
const MONTHS = [
|
|
24
|
+
"January",
|
|
25
|
+
"February",
|
|
26
|
+
"March",
|
|
27
|
+
"April",
|
|
28
|
+
"May",
|
|
29
|
+
"June",
|
|
30
|
+
"July",
|
|
31
|
+
"August",
|
|
32
|
+
"September",
|
|
33
|
+
"October",
|
|
34
|
+
"November",
|
|
35
|
+
"December"
|
|
36
|
+
];
|
|
37
|
+
/** en-GB order, no library: `15 January 2026`. */
|
|
38
|
+
function formatDateDefault(date) {
|
|
39
|
+
return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()}`;
|
|
40
|
+
}
|
|
41
|
+
function useControllable(controlled, value, defaultValue, onChange) {
|
|
42
|
+
const [inner, setInner] = React.useState(defaultValue);
|
|
43
|
+
return [controlled ? value : inner, React.useCallback((next) => {
|
|
44
|
+
if (!controlled) setInner(next);
|
|
45
|
+
onChange?.(next);
|
|
46
|
+
}, [controlled, onChange])];
|
|
47
|
+
}
|
|
48
|
+
function PickerTrigger({ slot, placeholder, label, className, ...props }) {
|
|
49
|
+
return /* @__PURE__ */ jsxs(PopoverTrigger, {
|
|
50
|
+
"data-slot": slot,
|
|
51
|
+
"data-empty": label ? void 0 : true,
|
|
52
|
+
render: /* @__PURE__ */ jsx(Button, { variant: "outline" }),
|
|
53
|
+
className: cn("w-60 justify-start font-normal", "data-[empty]:text-[color:var(--ds-muted-foreground)]", "[&_svg]:text-[color:var(--ds-muted-foreground)]", className),
|
|
54
|
+
...props,
|
|
55
|
+
children: [/* @__PURE__ */ jsx(CalendarIcon, {}), /* @__PURE__ */ jsx("span", {
|
|
56
|
+
"data-slot": `${slot}-value`,
|
|
57
|
+
className: "truncate",
|
|
58
|
+
children: label ?? placeholder
|
|
59
|
+
})]
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function DatePicker(props) {
|
|
63
|
+
const { value, defaultValue, onValueChange, placeholder = "Pick a date", formatDate = formatDateDefault, disabled, className, calendarProps, ...rest } = props;
|
|
64
|
+
const [date, setDate] = useControllable("value" in props, value, defaultValue, onValueChange);
|
|
65
|
+
const [open, setOpen] = React.useState(false);
|
|
66
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
67
|
+
open,
|
|
68
|
+
onOpenChange: setOpen,
|
|
69
|
+
children: [/* @__PURE__ */ jsx(PickerTrigger, {
|
|
70
|
+
slot: "date-picker-trigger",
|
|
71
|
+
placeholder,
|
|
72
|
+
label: date ? formatDate(date) : void 0,
|
|
73
|
+
disabled,
|
|
74
|
+
className,
|
|
75
|
+
...rest
|
|
76
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
77
|
+
"data-slot": "date-picker-content",
|
|
78
|
+
align: "start",
|
|
79
|
+
className: "w-auto p-0",
|
|
80
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
81
|
+
...calendarProps,
|
|
82
|
+
mode: "single",
|
|
83
|
+
defaultMonth: calendarProps?.defaultMonth ?? date,
|
|
84
|
+
selected: date,
|
|
85
|
+
onSelect: (next) => {
|
|
86
|
+
setDate(next);
|
|
87
|
+
setOpen(false);
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
})]
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function formatRange(range, formatDate, separator) {
|
|
94
|
+
if (!range?.from) return void 0;
|
|
95
|
+
if (!range.to || range.to.getTime() === range.from.getTime()) return formatDate(range.from);
|
|
96
|
+
return `${formatDate(range.from)}${separator}${formatDate(range.to)}`;
|
|
97
|
+
}
|
|
98
|
+
function DateRangePicker(props) {
|
|
99
|
+
const { value, defaultValue, onValueChange, placeholder = "Pick a date range", formatDate = formatDateDefault, separator = " – ", disabled, className, calendarProps, ...rest } = props;
|
|
100
|
+
const [range, setRange] = useControllable("value" in props, value, defaultValue, onValueChange);
|
|
101
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PickerTrigger, {
|
|
102
|
+
slot: "date-range-picker-trigger",
|
|
103
|
+
placeholder,
|
|
104
|
+
label: formatRange(range, formatDate, separator),
|
|
105
|
+
disabled,
|
|
106
|
+
className: cn("w-72", className),
|
|
107
|
+
...rest
|
|
108
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
109
|
+
"data-slot": "date-range-picker-content",
|
|
110
|
+
align: "start",
|
|
111
|
+
className: "w-auto p-0",
|
|
112
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
113
|
+
numberOfMonths: 2,
|
|
114
|
+
...calendarProps,
|
|
115
|
+
mode: "range",
|
|
116
|
+
defaultMonth: calendarProps?.defaultMonth ?? range?.from,
|
|
117
|
+
selected: range,
|
|
118
|
+
onSelect: setRange
|
|
119
|
+
})
|
|
120
|
+
})] });
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { DatePicker, DateRangePicker, formatDateDefault };
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=date-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-picker.js","names":[],"sources":["../../src/components/date-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { CalendarIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport type { DateRange } from \"react-day-picker\";\nimport { Button } from \"@/components/button\";\nimport { Calendar, type CalendarProps } from \"@/components/calendar\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/popover\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * DatePicker.\n *\n * Our outline Button as the trigger, showing the formatted value or a muted\n * placeholder, opening a Popover that holds a single-select Calendar. Picking\n * a day commits the value and closes the popup. Controlled through `value` /\n * `onValueChange`, uncontrolled through `defaultValue`.\n *\n * `DateRangePicker` is the same shape over `mode=\"range\"`. Its popup stays\n * open while both ends are chosen (react-day-picker's first click sets\n * `from === to`, the second widens it), so the user dismisses it.\n */\n\nconst MONTHS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** en-GB order, no library: `15 January 2026`. */\nfunction formatDateDefault(date: Date): string {\n return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()}`;\n}\n\nfunction useControllable<T>(\n controlled: boolean,\n value: T | undefined,\n defaultValue: T | undefined,\n onChange: ((next: T) => void) | undefined,\n): [T | undefined, (next: T) => void] {\n const [inner, setInner] = React.useState<T | undefined>(defaultValue);\n const current = controlled ? value : inner;\n const set = React.useCallback(\n (next: T) => {\n if (!controlled) setInner(next);\n onChange?.(next);\n },\n [controlled, onChange],\n );\n return [current, set];\n}\n\ntype CalendarPassthrough = Omit<\n CalendarProps,\n \"mode\" | \"selected\" | \"onSelect\" | \"required\" | \"disabled\"\n>;\n\ninterface PickerTriggerProps {\n slot: string;\n placeholder: string;\n label: string | undefined;\n disabled?: boolean | undefined;\n className?: string | undefined;\n \"aria-label\"?: string | undefined;\n}\n\nfunction PickerTrigger({ slot, placeholder, label, className, ...props }: PickerTriggerProps) {\n return (\n <PopoverTrigger\n data-slot={slot}\n data-empty={label ? undefined : true}\n render={<Button variant=\"outline\" />}\n className={cn(\n \"w-60 justify-start font-normal\",\n \"data-[empty]:text-[color:var(--ds-muted-foreground)]\",\n \"[&_svg]:text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n >\n <CalendarIcon />\n <span data-slot={`${slot}-value`} className=\"truncate\">\n {label ?? placeholder}\n </span>\n </PopoverTrigger>\n );\n}\n\nexport interface DatePickerProps {\n /** The selected date (controlled). */\n value?: Date | undefined;\n /** The initial date (uncontrolled). */\n defaultValue?: Date | undefined;\n /** Called with the picked date, or `undefined` when the selected day is clicked again. */\n onValueChange?: (date: Date | undefined) => void;\n /** Trigger text while nothing is selected. */\n placeholder?: string;\n /** Formats the trigger text; defaults to `15 January 2026`. */\n formatDate?: (date: Date) => string;\n /** Disables the trigger. */\n disabled?: boolean;\n /** Class name for the trigger button. */\n className?: string;\n /** Extra props for the Calendar (e.g. `startMonth`, `captionLayout`, `disabled` matchers via `calendarProps.hidden`). */\n calendarProps?: CalendarPassthrough & { disabled?: CalendarProps[\"disabled\"] };\n /** Accessible name for the trigger. */\n \"aria-label\"?: string;\n}\n\nfunction DatePicker(props: DatePickerProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n placeholder = \"Pick a date\",\n formatDate = formatDateDefault,\n disabled,\n className,\n calendarProps,\n ...rest\n } = props;\n const [date, setDate] = useControllable<Date | undefined>(\n \"value\" in props,\n value,\n defaultValue,\n onValueChange,\n );\n const [open, setOpen] = React.useState(false);\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PickerTrigger\n slot=\"date-picker-trigger\"\n placeholder={placeholder}\n label={date ? formatDate(date) : undefined}\n disabled={disabled}\n className={className}\n {...rest}\n />\n <PopoverContent data-slot=\"date-picker-content\" align=\"start\" className=\"w-auto p-0\">\n <Calendar\n {...calendarProps}\n mode=\"single\"\n defaultMonth={calendarProps?.defaultMonth ?? date}\n selected={date}\n onSelect={(next) => {\n setDate(next);\n setOpen(false);\n }}\n />\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface DateRangePickerProps extends Omit<\n DatePickerProps,\n \"value\" | \"defaultValue\" | \"onValueChange\"\n> {\n value?: DateRange | undefined;\n defaultValue?: DateRange | undefined;\n onValueChange?: (range: DateRange | undefined) => void;\n /** Separator between the two ends; defaults to an en dash. */\n separator?: string;\n}\n\nfunction formatRange(\n range: DateRange | undefined,\n formatDate: (d: Date) => string,\n separator: string,\n): string | undefined {\n if (!range?.from) return undefined;\n if (!range.to || range.to.getTime() === range.from.getTime()) return formatDate(range.from);\n return `${formatDate(range.from)}${separator}${formatDate(range.to)}`;\n}\n\nfunction DateRangePicker(props: DateRangePickerProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n placeholder = \"Pick a date range\",\n formatDate = formatDateDefault,\n separator = \" \\u2013 \",\n disabled,\n className,\n calendarProps,\n ...rest\n } = props;\n const [range, setRange] = useControllable<DateRange | undefined>(\n \"value\" in props,\n value,\n defaultValue,\n onValueChange,\n );\n\n return (\n <Popover>\n <PickerTrigger\n slot=\"date-range-picker-trigger\"\n placeholder={placeholder}\n label={formatRange(range, formatDate, separator)}\n disabled={disabled}\n className={cn(\"w-72\", className)}\n {...rest}\n />\n <PopoverContent data-slot=\"date-range-picker-content\" align=\"start\" className=\"w-auto p-0\">\n <Calendar\n numberOfMonths={2}\n {...calendarProps}\n mode=\"range\"\n defaultMonth={calendarProps?.defaultMonth ?? range?.from}\n selected={range}\n onSelect={setRange}\n />\n </PopoverContent>\n </Popover>\n );\n}\n\nexport { DatePicker, DateRangePicker, formatDateDefault };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
4
|
+
//#region src/components/dialog.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Dialog.
|
|
7
|
+
*
|
|
8
|
+
* `DialogContent` composes Base UI's Portal + Backdrop + Popup so consumers
|
|
9
|
+
* keep writing `<DialogContent>…</DialogContent>`. The Radix `Overlay` part is
|
|
10
|
+
* exported as `DialogOverlay` (a styled Backdrop) for anyone composing by hand.
|
|
11
|
+
*
|
|
12
|
+
* Radix users: `asChild` becomes `render={...}`; `onOpenChange` receives
|
|
13
|
+
* `(open, eventDetails)` rather than just `open`; the popup is only
|
|
14
|
+
* dismissable via pointer when `modal` is true (the default), and
|
|
15
|
+
* `disablePointerDismissal` replaces `onPointerDownOutside` preventDefault.
|
|
16
|
+
*/
|
|
17
|
+
declare function Dialog$1(props: Dialog.Root.Props): React.JSX.Element;
|
|
18
|
+
declare function DialogTrigger(props: Dialog.Trigger.Props): React.JSX.Element;
|
|
19
|
+
declare function DialogPortal(props: Dialog.Portal.Props): React.JSX.Element;
|
|
20
|
+
declare function DialogClose(props: Dialog.Close.Props): React.JSX.Element;
|
|
21
|
+
declare function DialogOverlay({ className, ...props }: Dialog.Backdrop.Props): React.JSX.Element;
|
|
22
|
+
export interface DialogContentProps extends Dialog.Popup.Props {
|
|
23
|
+
/** Keep the dialog mounted in the DOM while hidden. */
|
|
24
|
+
keepMounted?: boolean;
|
|
25
|
+
/** Where to portal the dialog; defaults to `<body>`. */
|
|
26
|
+
container?: Dialog.Portal.Props["container"];
|
|
27
|
+
}
|
|
28
|
+
declare function DialogContent({ className, children, keepMounted, container, ...props }: DialogContentProps): React.JSX.Element;
|
|
29
|
+
declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
30
|
+
declare function DialogBody({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
31
|
+
declare function DialogFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
32
|
+
declare function DialogTitle({ className, ...props }: Dialog.Title.Props): React.JSX.Element;
|
|
33
|
+
declare function DialogDescription({ className, ...props }: Dialog.Description.Props): React.JSX.Element;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { Dialog$1 as Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };
|
|
36
|
+
//# sourceMappingURL=dialog.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
6
|
+
//#region src/components/dialog.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Dialog.
|
|
9
|
+
*
|
|
10
|
+
* `DialogContent` composes Base UI's Portal + Backdrop + Popup so consumers
|
|
11
|
+
* keep writing `<DialogContent>…</DialogContent>`. The Radix `Overlay` part is
|
|
12
|
+
* exported as `DialogOverlay` (a styled Backdrop) for anyone composing by hand.
|
|
13
|
+
*
|
|
14
|
+
* Radix users: `asChild` becomes `render={...}`; `onOpenChange` receives
|
|
15
|
+
* `(open, eventDetails)` rather than just `open`; the popup is only
|
|
16
|
+
* dismissable via pointer when `modal` is true (the default), and
|
|
17
|
+
* `disablePointerDismissal` replaces `onPointerDownOutside` preventDefault.
|
|
18
|
+
*/
|
|
19
|
+
function Dialog$1(props) {
|
|
20
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { ...props });
|
|
21
|
+
}
|
|
22
|
+
function DialogTrigger(props) {
|
|
23
|
+
return /* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
24
|
+
"data-slot": "dialog-trigger",
|
|
25
|
+
...props
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function DialogPortal(props) {
|
|
29
|
+
return /* @__PURE__ */ jsx(Dialog.Portal, {
|
|
30
|
+
"data-slot": "dialog-portal",
|
|
31
|
+
...props
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function DialogClose(props) {
|
|
35
|
+
return /* @__PURE__ */ jsx(Dialog.Close, {
|
|
36
|
+
"data-slot": "dialog-close",
|
|
37
|
+
...props
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function DialogOverlay({ className, ...props }) {
|
|
41
|
+
return /* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
42
|
+
"data-slot": "dialog-overlay",
|
|
43
|
+
className: cn("fixed inset-0 z-50 backdrop-blur-[2px]", "bg-[color-mix(in_oklab,var(--ds-ink-950)_70%,transparent)]", "open:animate-in open:fade-in-0 closed:animate-out closed:fade-out-0", "duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]", className),
|
|
44
|
+
...props
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function DialogContent({ className, children, keepMounted, container, ...props }) {
|
|
48
|
+
return /* @__PURE__ */ jsxs(DialogPortal, {
|
|
49
|
+
keepMounted,
|
|
50
|
+
container,
|
|
51
|
+
children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsx(Dialog.Popup, {
|
|
52
|
+
"data-slot": "dialog-content",
|
|
53
|
+
className: cn("fixed left-1/2 top-1/2 z-50 grid w-full max-w-[440px] -translate-x-1/2 -translate-y-1/2 overflow-hidden outline-none", "rounded-[var(--ds-radius-xl)] border border-[var(--ds-border)]", "bg-[var(--ds-popover)] text-[color:var(--ds-popover-foreground)] shadow-[var(--ds-shadow-lg)]", "open:animate-in open:fade-in-0 open:zoom-in-95", "closed:animate-out closed:fade-out-0 closed:zoom-out-95", "duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]", className),
|
|
54
|
+
...props,
|
|
55
|
+
children
|
|
56
|
+
})]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function DialogHeader({ className, ...props }) {
|
|
60
|
+
return /* @__PURE__ */ jsx("div", {
|
|
61
|
+
"data-slot": "dialog-header",
|
|
62
|
+
className: cn("flex flex-col gap-1.5 px-5 pb-1 pt-5 text-left", className),
|
|
63
|
+
...props
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function DialogBody({ className, ...props }) {
|
|
67
|
+
return /* @__PURE__ */ jsx("div", {
|
|
68
|
+
"data-slot": "dialog-body",
|
|
69
|
+
className: cn("px-5 pb-5 pt-1", className),
|
|
70
|
+
...props
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function DialogFooter({ className, ...props }) {
|
|
74
|
+
return /* @__PURE__ */ jsx("div", {
|
|
75
|
+
"data-slot": "dialog-footer",
|
|
76
|
+
className: cn("flex flex-col-reverse gap-2 border-t border-[var(--ds-border-subtle)] bg-[var(--ds-card)] px-5 py-3.5 sm:flex-row sm:justify-end", className),
|
|
77
|
+
...props
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function DialogTitle({ className, ...props }) {
|
|
81
|
+
return /* @__PURE__ */ jsx(Dialog.Title, {
|
|
82
|
+
"data-slot": "dialog-title",
|
|
83
|
+
className: cn("text-[length:var(--ds-text-body-md)] font-semibold leading-none tracking-[-0.01em] text-[color:var(--ds-foreground)]", className),
|
|
84
|
+
...props
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function DialogDescription({ className, ...props }) {
|
|
88
|
+
return /* @__PURE__ */ jsx(Dialog.Description, {
|
|
89
|
+
"data-slot": "dialog-description",
|
|
90
|
+
className: cn("text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]", className),
|
|
91
|
+
...props
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { Dialog$1 as Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog.js","names":["Dialog","DialogPrimitive"],"sources":["../../src/components/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Dialog.\n *\n * `DialogContent` composes Base UI's Portal + Backdrop + Popup so consumers\n * keep writing `<DialogContent>…</DialogContent>`. The Radix `Overlay` part is\n * exported as `DialogOverlay` (a styled Backdrop) for anyone composing by hand.\n *\n * Radix users: `asChild` becomes `render={...}`; `onOpenChange` receives\n * `(open, eventDetails)` rather than just `open`; the popup is only\n * dismissable via pointer when `modal` is true (the default), and\n * `disablePointerDismissal` replaces `onPointerDownOutside` preventDefault.\n */\nfunction Dialog(props: DialogPrimitive.Root.Props) {\n return <DialogPrimitive.Root {...props} />;\n}\n\nfunction DialogTrigger(props: DialogPrimitive.Trigger.Props) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal(props: DialogPrimitive.Portal.Props) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose(props: DialogPrimitive.Close.Props) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {\n return (\n <DialogPrimitive.Backdrop\n data-slot=\"dialog-overlay\"\n className={cn(\n \"fixed inset-0 z-50 backdrop-blur-[2px]\",\n \"bg-[color-mix(in_oklab,var(--ds-ink-950)_70%,transparent)]\",\n \"open:animate-in open:fade-in-0 closed:animate-out closed:fade-out-0\",\n \"duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface DialogContentProps extends DialogPrimitive.Popup.Props {\n /** Keep the dialog mounted in the DOM while hidden. */\n keepMounted?: boolean;\n /** Where to portal the dialog; defaults to `<body>`. */\n container?: DialogPrimitive.Portal.Props[\"container\"];\n}\n\nfunction DialogContent({ className, children, keepMounted, container, ...props }: DialogContentProps) {\n return (\n <DialogPortal keepMounted={keepMounted} container={container}>\n <DialogOverlay />\n <DialogPrimitive.Popup\n data-slot=\"dialog-content\"\n className={cn(\n \"fixed left-1/2 top-1/2 z-50 grid w-full max-w-[440px] -translate-x-1/2 -translate-y-1/2 overflow-hidden outline-none\",\n \"rounded-[var(--ds-radius-xl)] border border-[var(--ds-border)]\",\n \"bg-[var(--ds-popover)] text-[color:var(--ds-popover-foreground)] shadow-[var(--ds-shadow-lg)]\",\n \"open:animate-in open:fade-in-0 open:zoom-in-95\",\n \"closed:animate-out closed:fade-out-0 closed:zoom-out-95\",\n \"duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]\",\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\"flex flex-col gap-1.5 px-5 pb-1 pt-5 text-left\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"dialog-body\" className={cn(\"px-5 pb-5 pt-1\", className)} {...props} />;\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n \"flex flex-col-reverse gap-2 border-t border-[var(--ds-border-subtle)] bg-[var(--ds-card)] px-5 py-3.5 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\n \"text-[length:var(--ds-text-body-md)] font-semibold leading-none tracking-[-0.01em] text-[color:var(--ds-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\n \"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogBody,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAASA,SAAO,OAAmC;CACjD,OAAO,oBAACC,OAAgB,MAAjB,EAAsB,GAAI,MAAQ,CAAA;AAC3C;AAEA,SAAS,cAAc,OAAsC;CAC3D,OAAO,oBAACA,OAAgB,SAAjB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,aAAa,OAAqC;CACzD,OAAO,oBAACA,OAAgB,QAAjB;EAAwB,aAAU;EAAgB,GAAI;CAAQ,CAAA;AACvE;AAEA,SAAS,YAAY,OAAoC;CACvD,OAAO,oBAACA,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAyC;CAC9E,OACE,oBAACA,OAAgB,UAAjB;EACE,aAAU;EACV,WAAW,GACT,0CACA,8DACA,uEACA,gEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AASA,SAAS,cAAc,EAAE,WAAW,UAAU,aAAa,WAAW,GAAG,SAA6B;CACpG,OACE,qBAAC,cAAD;EAA2B;EAAwB;EAAnD,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAChB,oBAACA,OAAgB,OAAjB;GACE,aAAU;GACV,WAAW,GACT,wHACA,kEACA,iGACA,kDACA,2DACA,gEACA,SACF;GACA,GAAI;GAEH;EACoB,CAAA,CACX;;AAElB;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;CAC1E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,kDAAkD,SAAS;EACzE,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAsC;CACxE,OAAO,oBAAC,OAAD;EAAK,aAAU;EAAc,WAAW,GAAG,kBAAkB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAC9F;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;CAC1E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,oIACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAsC;CACzE,OACE,oBAACA,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GACT,wHACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAA4C;CACrF,OACE,oBAACA,OAAgB,aAAjB;EACE,aAAU;EACV,WAAW,GACT,mFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|