@vaam-apps/ui 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/data/code.d.ts +0 -1
  3. package/dist/components/data/code.js +0 -1
  4. package/dist/components/data/copy-button.d.ts +0 -1
  5. package/dist/components/data/copy-button.js +0 -1
  6. package/dist/components/data/detail-row.d.ts +0 -1
  7. package/dist/components/data/detail-row.js +0 -1
  8. package/dist/components/data/id-display.d.ts +0 -1
  9. package/dist/components/data/id-display.js +0 -1
  10. package/dist/components/data/masked-value.d.ts +0 -1
  11. package/dist/components/data/masked-value.js +0 -1
  12. package/dist/components/data/money.d.ts +0 -1
  13. package/dist/components/data/money.js +0 -1
  14. package/dist/components/data/phone-display.d.ts +0 -1
  15. package/dist/components/data/phone-display.js +0 -1
  16. package/dist/components/data/stat-tile.d.ts +0 -1
  17. package/dist/components/data/stat-tile.js +0 -1
  18. package/dist/components/data/timestamp-display.d.ts +0 -1
  19. package/dist/components/data/timestamp-display.js +0 -1
  20. package/dist/components/patterns/inline-banner.d.ts +0 -1
  21. package/dist/components/patterns/inline-banner.js +0 -1
  22. package/dist/components/patterns/inline-empty-state.d.ts +0 -1
  23. package/dist/components/patterns/inline-empty-state.js +0 -1
  24. package/dist/components/patterns/live-row.d.ts +0 -1
  25. package/dist/components/patterns/live-row.js +0 -1
  26. package/dist/components/patterns/payload-inspector.d.ts +0 -1
  27. package/dist/components/patterns/payload-inspector.js +0 -1
  28. package/dist/components/patterns/route-skeleton.d.ts +0 -1
  29. package/dist/components/patterns/route-skeleton.js +0 -1
  30. package/dist/components/patterns/screen-layout.d.ts +0 -1
  31. package/dist/components/patterns/screen-layout.js +0 -1
  32. package/dist/components/patterns/stale-write-banner.d.ts +0 -1
  33. package/dist/components/patterns/stale-write-banner.js +0 -1
  34. package/dist/components/patterns/state-timeline.d.ts +0 -1
  35. package/dist/components/patterns/state-timeline.js +0 -1
  36. package/dist/components/primitives/badge.d.ts +0 -1
  37. package/dist/components/primitives/badge.js +0 -1
  38. package/dist/components/primitives/button.d.ts +0 -1
  39. package/dist/components/primitives/button.js +0 -1
  40. package/dist/components/primitives/calendar.d.ts +0 -1
  41. package/dist/components/primitives/calendar.js +0 -1
  42. package/dist/components/primitives/card.d.ts +0 -1
  43. package/dist/components/primitives/card.js +0 -1
  44. package/dist/components/primitives/checkbox.d.ts +0 -1
  45. package/dist/components/primitives/checkbox.js +0 -1
  46. package/dist/components/primitives/chip-select.d.ts +0 -1
  47. package/dist/components/primitives/chip-select.js +0 -1
  48. package/dist/components/primitives/command-menu.d.ts +0 -1
  49. package/dist/components/primitives/command-menu.js +0 -1
  50. package/dist/components/primitives/confirm-dialog.d.ts +0 -1
  51. package/dist/components/primitives/confirm-dialog.js +0 -1
  52. package/dist/components/primitives/date-picker.d.ts +0 -1
  53. package/dist/components/primitives/date-picker.js +0 -1
  54. package/dist/components/primitives/dialog.d.ts +0 -1
  55. package/dist/components/primitives/dialog.js +0 -1
  56. package/dist/components/primitives/drawer.d.ts +0 -1
  57. package/dist/components/primitives/drawer.js +0 -1
  58. package/dist/components/primitives/dropdown-menu.d.ts +0 -1
  59. package/dist/components/primitives/dropdown-menu.js +0 -1
  60. package/dist/components/primitives/form-field.d.ts +0 -1
  61. package/dist/components/primitives/form-field.js +0 -1
  62. package/dist/components/primitives/inline-confirm.d.ts +0 -1
  63. package/dist/components/primitives/inline-confirm.js +0 -1
  64. package/dist/components/primitives/input.d.ts +0 -1
  65. package/dist/components/primitives/input.js +0 -1
  66. package/dist/components/primitives/label.d.ts +0 -1
  67. package/dist/components/primitives/label.js +0 -1
  68. package/dist/components/primitives/pagination.d.ts +0 -1
  69. package/dist/components/primitives/pagination.js +0 -1
  70. package/dist/components/primitives/popover.d.ts +0 -1
  71. package/dist/components/primitives/popover.js +0 -1
  72. package/dist/components/primitives/progress.d.ts +0 -1
  73. package/dist/components/primitives/progress.js +0 -1
  74. package/dist/components/primitives/radio-group.d.ts +0 -1
  75. package/dist/components/primitives/radio-group.js +0 -1
  76. package/dist/components/primitives/select.d.ts +0 -1
  77. package/dist/components/primitives/select.js +0 -1
  78. package/dist/components/primitives/separator.d.ts +0 -1
  79. package/dist/components/primitives/separator.js +0 -1
  80. package/dist/components/primitives/side-nav.d.ts +0 -1
  81. package/dist/components/primitives/side-nav.js +0 -1
  82. package/dist/components/primitives/skeleton.d.ts +0 -1
  83. package/dist/components/primitives/skeleton.js +0 -1
  84. package/dist/components/primitives/spinner.d.ts +0 -1
  85. package/dist/components/primitives/spinner.js +0 -1
  86. package/dist/components/primitives/switch.d.ts +0 -1
  87. package/dist/components/primitives/switch.js +0 -1
  88. package/dist/components/primitives/table.d.ts +0 -1
  89. package/dist/components/primitives/table.js +0 -1
  90. package/dist/components/primitives/tabs.d.ts +0 -1
  91. package/dist/components/primitives/tabs.js +0 -1
  92. package/dist/components/primitives/textarea.d.ts +0 -1
  93. package/dist/components/primitives/textarea.js +0 -1
  94. package/dist/components/primitives/toast.d.ts +0 -1
  95. package/dist/components/primitives/toast.js +0 -1
  96. package/dist/components/primitives/tooltip.d.ts +0 -1
  97. package/dist/components/primitives/tooltip.js +0 -1
  98. package/dist/components/status/state-chip.d.ts +0 -1
  99. package/dist/components/status/state-chip.js +0 -1
  100. package/dist/components/status/state-mark.d.ts +0 -1
  101. package/dist/components/status/state-mark.js +0 -1
  102. package/dist/components/status/status-pill.d.ts +0 -1
  103. package/dist/components/status/status-pill.js +0 -1
  104. package/dist/components/status/status-tokens.d.ts +0 -1
  105. package/dist/components/status/status-tokens.js +0 -1
  106. package/dist/index.d.ts +0 -1
  107. package/dist/index.js +0 -1
  108. package/dist/lib/cn.d.ts +0 -1
  109. package/dist/lib/cn.js +0 -1
  110. package/dist/lib/money.d.ts +0 -1
  111. package/dist/lib/money.js +0 -1
  112. package/dist/lib/omit-undefined.d.ts +0 -1
  113. package/dist/lib/omit-undefined.js +0 -1
  114. package/package.json +3 -7
  115. package/dist/components/data/code.d.ts.map +0 -1
  116. package/dist/components/data/code.js.map +0 -1
  117. package/dist/components/data/copy-button.d.ts.map +0 -1
  118. package/dist/components/data/copy-button.js.map +0 -1
  119. package/dist/components/data/detail-row.d.ts.map +0 -1
  120. package/dist/components/data/detail-row.js.map +0 -1
  121. package/dist/components/data/id-display.d.ts.map +0 -1
  122. package/dist/components/data/id-display.js.map +0 -1
  123. package/dist/components/data/masked-value.d.ts.map +0 -1
  124. package/dist/components/data/masked-value.js.map +0 -1
  125. package/dist/components/data/money.d.ts.map +0 -1
  126. package/dist/components/data/money.js.map +0 -1
  127. package/dist/components/data/phone-display.d.ts.map +0 -1
  128. package/dist/components/data/phone-display.js.map +0 -1
  129. package/dist/components/data/stat-tile.d.ts.map +0 -1
  130. package/dist/components/data/stat-tile.js.map +0 -1
  131. package/dist/components/data/timestamp-display.d.ts.map +0 -1
  132. package/dist/components/data/timestamp-display.js.map +0 -1
  133. package/dist/components/patterns/inline-banner.d.ts.map +0 -1
  134. package/dist/components/patterns/inline-banner.js.map +0 -1
  135. package/dist/components/patterns/inline-empty-state.d.ts.map +0 -1
  136. package/dist/components/patterns/inline-empty-state.js.map +0 -1
  137. package/dist/components/patterns/live-row.d.ts.map +0 -1
  138. package/dist/components/patterns/live-row.js.map +0 -1
  139. package/dist/components/patterns/payload-inspector.d.ts.map +0 -1
  140. package/dist/components/patterns/payload-inspector.js.map +0 -1
  141. package/dist/components/patterns/route-skeleton.d.ts.map +0 -1
  142. package/dist/components/patterns/route-skeleton.js.map +0 -1
  143. package/dist/components/patterns/screen-layout.d.ts.map +0 -1
  144. package/dist/components/patterns/screen-layout.js.map +0 -1
  145. package/dist/components/patterns/stale-write-banner.d.ts.map +0 -1
  146. package/dist/components/patterns/stale-write-banner.js.map +0 -1
  147. package/dist/components/patterns/state-timeline.d.ts.map +0 -1
  148. package/dist/components/patterns/state-timeline.js.map +0 -1
  149. package/dist/components/primitives/badge.d.ts.map +0 -1
  150. package/dist/components/primitives/badge.js.map +0 -1
  151. package/dist/components/primitives/button.d.ts.map +0 -1
  152. package/dist/components/primitives/button.js.map +0 -1
  153. package/dist/components/primitives/calendar.d.ts.map +0 -1
  154. package/dist/components/primitives/calendar.js.map +0 -1
  155. package/dist/components/primitives/card.d.ts.map +0 -1
  156. package/dist/components/primitives/card.js.map +0 -1
  157. package/dist/components/primitives/checkbox.d.ts.map +0 -1
  158. package/dist/components/primitives/checkbox.js.map +0 -1
  159. package/dist/components/primitives/chip-select.d.ts.map +0 -1
  160. package/dist/components/primitives/chip-select.js.map +0 -1
  161. package/dist/components/primitives/command-menu.d.ts.map +0 -1
  162. package/dist/components/primitives/command-menu.js.map +0 -1
  163. package/dist/components/primitives/confirm-dialog.d.ts.map +0 -1
  164. package/dist/components/primitives/confirm-dialog.js.map +0 -1
  165. package/dist/components/primitives/date-picker.d.ts.map +0 -1
  166. package/dist/components/primitives/date-picker.js.map +0 -1
  167. package/dist/components/primitives/dialog.d.ts.map +0 -1
  168. package/dist/components/primitives/dialog.js.map +0 -1
  169. package/dist/components/primitives/drawer.d.ts.map +0 -1
  170. package/dist/components/primitives/drawer.js.map +0 -1
  171. package/dist/components/primitives/dropdown-menu.d.ts.map +0 -1
  172. package/dist/components/primitives/dropdown-menu.js.map +0 -1
  173. package/dist/components/primitives/form-field.d.ts.map +0 -1
  174. package/dist/components/primitives/form-field.js.map +0 -1
  175. package/dist/components/primitives/inline-confirm.d.ts.map +0 -1
  176. package/dist/components/primitives/inline-confirm.js.map +0 -1
  177. package/dist/components/primitives/input.d.ts.map +0 -1
  178. package/dist/components/primitives/input.js.map +0 -1
  179. package/dist/components/primitives/label.d.ts.map +0 -1
  180. package/dist/components/primitives/label.js.map +0 -1
  181. package/dist/components/primitives/pagination.d.ts.map +0 -1
  182. package/dist/components/primitives/pagination.js.map +0 -1
  183. package/dist/components/primitives/popover.d.ts.map +0 -1
  184. package/dist/components/primitives/popover.js.map +0 -1
  185. package/dist/components/primitives/progress.d.ts.map +0 -1
  186. package/dist/components/primitives/progress.js.map +0 -1
  187. package/dist/components/primitives/radio-group.d.ts.map +0 -1
  188. package/dist/components/primitives/radio-group.js.map +0 -1
  189. package/dist/components/primitives/select.d.ts.map +0 -1
  190. package/dist/components/primitives/select.js.map +0 -1
  191. package/dist/components/primitives/separator.d.ts.map +0 -1
  192. package/dist/components/primitives/separator.js.map +0 -1
  193. package/dist/components/primitives/side-nav.d.ts.map +0 -1
  194. package/dist/components/primitives/side-nav.js.map +0 -1
  195. package/dist/components/primitives/skeleton.d.ts.map +0 -1
  196. package/dist/components/primitives/skeleton.js.map +0 -1
  197. package/dist/components/primitives/spinner.d.ts.map +0 -1
  198. package/dist/components/primitives/spinner.js.map +0 -1
  199. package/dist/components/primitives/switch.d.ts.map +0 -1
  200. package/dist/components/primitives/switch.js.map +0 -1
  201. package/dist/components/primitives/table.d.ts.map +0 -1
  202. package/dist/components/primitives/table.js.map +0 -1
  203. package/dist/components/primitives/tabs.d.ts.map +0 -1
  204. package/dist/components/primitives/tabs.js.map +0 -1
  205. package/dist/components/primitives/textarea.d.ts.map +0 -1
  206. package/dist/components/primitives/textarea.js.map +0 -1
  207. package/dist/components/primitives/toast.d.ts.map +0 -1
  208. package/dist/components/primitives/toast.js.map +0 -1
  209. package/dist/components/primitives/tooltip.d.ts.map +0 -1
  210. package/dist/components/primitives/tooltip.js.map +0 -1
  211. package/dist/components/status/state-chip.d.ts.map +0 -1
  212. package/dist/components/status/state-chip.js.map +0 -1
  213. package/dist/components/status/state-mark.d.ts.map +0 -1
  214. package/dist/components/status/state-mark.js.map +0 -1
  215. package/dist/components/status/status-pill.d.ts.map +0 -1
  216. package/dist/components/status/status-pill.js.map +0 -1
  217. package/dist/components/status/status-tokens.d.ts.map +0 -1
  218. package/dist/components/status/status-tokens.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/lib/cn.d.ts.map +0 -1
  222. package/dist/lib/cn.js.map +0 -1
  223. package/dist/lib/money.d.ts.map +0 -1
  224. package/dist/lib/money.js.map +0 -1
  225. package/dist/lib/omit-undefined.d.ts.map +0 -1
  226. package/dist/lib/omit-undefined.js.map +0 -1
  227. package/src/components/data/code.tsx +0 -43
  228. package/src/components/data/copy-button.tsx +0 -97
  229. package/src/components/data/detail-row.tsx +0 -113
  230. package/src/components/data/id-display.tsx +0 -49
  231. package/src/components/data/masked-value.tsx +0 -96
  232. package/src/components/data/money.tsx +0 -46
  233. package/src/components/data/phone-display.tsx +0 -60
  234. package/src/components/data/stat-tile.tsx +0 -68
  235. package/src/components/data/timestamp-display.tsx +0 -106
  236. package/src/components/patterns/inline-banner.tsx +0 -60
  237. package/src/components/patterns/inline-empty-state.tsx +0 -45
  238. package/src/components/patterns/live-row.tsx +0 -75
  239. package/src/components/patterns/payload-inspector.tsx +0 -157
  240. package/src/components/patterns/route-skeleton.tsx +0 -77
  241. package/src/components/patterns/screen-layout.tsx +0 -44
  242. package/src/components/patterns/stale-write-banner.tsx +0 -44
  243. package/src/components/patterns/state-timeline.tsx +0 -229
  244. package/src/components/primitives/badge.tsx +0 -40
  245. package/src/components/primitives/button.tsx +0 -54
  246. package/src/components/primitives/calendar.tsx +0 -136
  247. package/src/components/primitives/card.tsx +0 -70
  248. package/src/components/primitives/checkbox.tsx +0 -101
  249. package/src/components/primitives/chip-select.tsx +0 -105
  250. package/src/components/primitives/command-menu.tsx +0 -92
  251. package/src/components/primitives/confirm-dialog.tsx +0 -109
  252. package/src/components/primitives/date-picker.tsx +0 -271
  253. package/src/components/primitives/dialog.tsx +0 -195
  254. package/src/components/primitives/drawer.tsx +0 -326
  255. package/src/components/primitives/dropdown-menu.tsx +0 -128
  256. package/src/components/primitives/form-field.tsx +0 -136
  257. package/src/components/primitives/inline-confirm.tsx +0 -118
  258. package/src/components/primitives/input.tsx +0 -21
  259. package/src/components/primitives/label.tsx +0 -43
  260. package/src/components/primitives/pagination.tsx +0 -110
  261. package/src/components/primitives/popover.tsx +0 -41
  262. package/src/components/primitives/progress.tsx +0 -66
  263. package/src/components/primitives/radio-group.tsx +0 -90
  264. package/src/components/primitives/select.tsx +0 -254
  265. package/src/components/primitives/separator.tsx +0 -49
  266. package/src/components/primitives/side-nav.tsx +0 -253
  267. package/src/components/primitives/skeleton.tsx +0 -25
  268. package/src/components/primitives/spinner.tsx +0 -39
  269. package/src/components/primitives/switch.tsx +0 -74
  270. package/src/components/primitives/table.tsx +0 -157
  271. package/src/components/primitives/tabs.tsx +0 -163
  272. package/src/components/primitives/textarea.tsx +0 -21
  273. package/src/components/primitives/toast.tsx +0 -117
  274. package/src/components/primitives/tooltip.tsx +0 -38
  275. package/src/components/status/state-chip.tsx +0 -70
  276. package/src/components/status/state-mark.tsx +0 -154
  277. package/src/components/status/status-pill.tsx +0 -153
  278. package/src/components/status/status-tokens.ts +0 -173
  279. package/src/index.ts +0 -96
  280. package/src/lib/cn.ts +0 -168
  281. package/src/lib/money.ts +0 -164
  282. package/src/lib/omit-undefined.ts +0 -25
  283. /package/{src → dist}/styles/theme.css +0 -0
@@ -1,271 +0,0 @@
1
- "use client";
2
-
3
- import { Popover as HeadlessPopover, PopoverButton, PopoverPanel } from "@headlessui/react";
4
- import { CalendarDays, X } from "lucide-react";
5
- import type { DateRange } from "react-day-picker";
6
- import { cn } from "../../lib/cn";
7
- import { omitUndefined } from "../../lib/omit-undefined";
8
- import { Calendar } from "./calendar";
9
-
10
- export type { DateRange };
11
-
12
- /**
13
- * `2026-09-11` — ISO 8601 calendar date, no time, no zone.
14
- *
15
- * Every date this module exchanges with a caller is one of these, not a
16
- * `Date`. A `Date` is an instant, and an instant rendered in a different
17
- * zone is a different calendar day — which is how a filter for "today"
18
- * silently returns yesterday's rows for anyone west of the server. The
19
- * `Date` objects `react-day-picker` works in are confined to this file.
20
- */
21
- export type IsoDate = string;
22
-
23
- /** `Date` (local midnight) → `YYYY-MM-DD`, without going through UTC. */
24
- export function toIsoDate(date: Date): IsoDate {
25
- const year = date.getFullYear();
26
- const month = String(date.getMonth() + 1).padStart(2, "0");
27
- const day = String(date.getDate()).padStart(2, "0");
28
- return `${year}-${month}-${day}`;
29
- }
30
-
31
- /**
32
- * `YYYY-MM-DD` → `Date` at *local* midnight.
33
- *
34
- * Deliberately not `new Date("2026-09-11")`, which the spec parses as
35
- * UTC midnight — in any negative-offset zone that is the previous day,
36
- * so a calendar built on it highlights the wrong cell.
37
- */
38
- export function fromIsoDate(iso: IsoDate): Date | undefined {
39
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
40
- if (match === null) return undefined;
41
- const [, year, month, day] = match as unknown as [string, string, string, string];
42
- return new Date(Number(year), Number(month) - 1, Number(day));
43
- }
44
-
45
- function displayLabel(iso: IsoDate | undefined, placeholder: string): string {
46
- return iso === undefined || iso === "" ? placeholder : iso;
47
- }
48
-
49
- interface TriggerProps {
50
- /** Shown when nothing is selected. */
51
- placeholder: string;
52
- label: string;
53
- hasValue: boolean;
54
- disabled: boolean | undefined;
55
- onClear: (() => void) | undefined;
56
- className: string | undefined;
57
- }
58
-
59
- /**
60
- * The field-looking button that opens the calendar.
61
- *
62
- * A `<button>`, not an `<input>`: the value is chosen from the calendar,
63
- * and a text input that looks editable but is not is worse than a button
64
- * that looks like a field. Callers who want typed entry should use a
65
- * plain `<Input type="date">` — the browser's own picker is better than
66
- * anything worth reimplementing here.
67
- */
68
- function PickerTrigger({
69
- placeholder,
70
- label,
71
- hasValue,
72
- disabled,
73
- onClear,
74
- className,
75
- }: TriggerProps) {
76
- return (
77
- <div className={cn("relative inline-flex w-full items-center", className)}>
78
- <PopoverButton
79
- {...omitUndefined({ disabled })}
80
- className={cn(
81
- "input input-bordered flex w-full items-center gap-2 text-left font-sans text-prose",
82
- !hasValue && "text-subtle-foreground",
83
- "disabled:cursor-not-allowed disabled:opacity-50",
84
- hasValue && onClear !== undefined && "pr-9",
85
- )}
86
- >
87
- <CalendarDays size={14} strokeWidth={1.5} className="shrink-0 text-subtle-foreground" />
88
- <span className="truncate font-mono tabular-nums">{label}</span>
89
- <span className="sr-only">{placeholder}</span>
90
- </PopoverButton>
91
- {hasValue && onClear !== undefined && (
92
- // Outside the PopoverButton on purpose: nesting a button inside a
93
- // button is invalid HTML, and browsers resolve it by hoisting the
94
- // inner one out, which silently detaches the click handler.
95
- <button
96
- type="button"
97
- onClick={onClear}
98
- aria-label={`Clear ${placeholder}`}
99
- className="-translate-y-1/2 absolute top-1/2 right-2 text-subtle-foreground hover:text-foreground"
100
- >
101
- <X size={14} strokeWidth={1.5} />
102
- </button>
103
- )}
104
- </div>
105
- );
106
- }
107
-
108
- const PANEL_CLASS = cn(
109
- "z-50 rounded-md border border-edge bg-surface-2 shadow-[var(--shadow-popover)]",
110
- "[--anchor-gap:6px] focus:outline-none",
111
- "origin-top transition duration-100 ease-out data-closed:scale-95 data-closed:opacity-0",
112
- );
113
-
114
- export interface DatePickerProps {
115
- value: IsoDate | undefined;
116
- onValueChange: (value: IsoDate | undefined) => void;
117
- /** Shown when empty, and used as the control's accessible name. */
118
- placeholder?: string;
119
- /** Earliest selectable date, inclusive. */
120
- min?: IsoDate | undefined;
121
- /** Latest selectable date, inclusive. */
122
- max?: IsoDate | undefined;
123
- disabled?: boolean | undefined;
124
- /** Show an inline clear affordance once a date is picked. */
125
- clearable?: boolean;
126
- className?: string | undefined;
127
- }
128
-
129
- /** A single calendar date, chosen from a popover calendar. */
130
- export function DatePicker({
131
- value,
132
- onValueChange,
133
- placeholder = "Pick a date",
134
- min,
135
- max,
136
- disabled,
137
- clearable = true,
138
- className,
139
- }: DatePickerProps) {
140
- const selected = value === undefined ? undefined : fromIsoDate(value);
141
-
142
- return (
143
- <HeadlessPopover className={cn("relative", className)}>
144
- {({ close }) => (
145
- <>
146
- <PickerTrigger
147
- placeholder={placeholder}
148
- label={displayLabel(value, placeholder)}
149
- hasValue={value !== undefined && value !== ""}
150
- disabled={disabled}
151
- onClear={clearable ? () => onValueChange(undefined) : undefined}
152
- className={undefined}
153
- />
154
- <PopoverPanel anchor="bottom start" transition className={PANEL_CLASS}>
155
- <Calendar
156
- mode="single"
157
- {...omitUndefined({
158
- selected,
159
- defaultMonth: selected,
160
- startMonth: min === undefined ? undefined : fromIsoDate(min),
161
- endMonth: max === undefined ? undefined : fromIsoDate(max),
162
- })}
163
- onSelect={(next) => {
164
- onValueChange(next === undefined ? undefined : toIsoDate(next));
165
- // Closing on pick is right for a single date and wrong for
166
- // a range, where the first click is only half the answer.
167
- if (next !== undefined) close();
168
- }}
169
- />
170
- </PopoverPanel>
171
- </>
172
- )}
173
- </HeadlessPopover>
174
- );
175
- }
176
-
177
- /** Two ISO dates, inclusive at both ends. `undefined` at either end means open. */
178
- export interface IsoDateRange {
179
- from?: IsoDate | undefined;
180
- to?: IsoDate | undefined;
181
- }
182
-
183
- export interface DateRangePickerProps {
184
- value: IsoDateRange | undefined;
185
- onValueChange: (value: IsoDateRange | undefined) => void;
186
- placeholder?: string;
187
- min?: IsoDate | undefined;
188
- max?: IsoDate | undefined;
189
- disabled?: boolean | undefined;
190
- clearable?: boolean;
191
- /** Months shown side by side. Two makes a cross-month range selectable
192
- * without navigating, which is the common case for "last 30 days". */
193
- numberOfMonths?: number;
194
- className?: string | undefined;
195
- }
196
-
197
- function rangeLabel(value: IsoDateRange | undefined, placeholder: string): string {
198
- if (value === undefined) return placeholder;
199
- const { from, to } = value;
200
- if (from === undefined && to === undefined) return placeholder;
201
- if (from !== undefined && to === undefined) return `${from} → …`;
202
- if (from === undefined && to !== undefined) return `… → ${to}`;
203
- return `${from} → ${to}`;
204
- }
205
-
206
- /**
207
- * A start and end date as one control.
208
- *
209
- * Exists because the alternative — two `<input type="date">` side by side
210
- * — is what applications actually ship, and it cannot express the one
211
- * thing a range needs: that the two ends constrain each other. Here the
212
- * second click is always after the first, the fill between them is
213
- * visible, and a half-finished range is representable (`from` set, `to`
214
- * open) rather than being a validation error.
215
- */
216
- export function DateRangePicker({
217
- value,
218
- onValueChange,
219
- placeholder = "Pick a date range",
220
- min,
221
- max,
222
- disabled,
223
- clearable = true,
224
- numberOfMonths = 2,
225
- className,
226
- }: DateRangePickerProps) {
227
- const selected: DateRange | undefined =
228
- value === undefined
229
- ? undefined
230
- : {
231
- from: value.from === undefined ? undefined : fromIsoDate(value.from),
232
- to: value.to === undefined ? undefined : fromIsoDate(value.to),
233
- };
234
-
235
- const hasValue = value !== undefined && (value.from !== undefined || value.to !== undefined);
236
-
237
- return (
238
- <HeadlessPopover className={cn("relative", className)}>
239
- <PickerTrigger
240
- placeholder={placeholder}
241
- label={rangeLabel(value, placeholder)}
242
- hasValue={hasValue}
243
- disabled={disabled}
244
- onClear={clearable ? () => onValueChange(undefined) : undefined}
245
- className={undefined}
246
- />
247
- <PopoverPanel anchor="bottom start" transition className={PANEL_CLASS}>
248
- <Calendar
249
- mode="range"
250
- numberOfMonths={numberOfMonths}
251
- {...omitUndefined({
252
- selected,
253
- defaultMonth: selected?.from,
254
- startMonth: min === undefined ? undefined : fromIsoDate(min),
255
- endMonth: max === undefined ? undefined : fromIsoDate(max),
256
- })}
257
- onSelect={(next) => {
258
- if (next === undefined) {
259
- onValueChange(undefined);
260
- return;
261
- }
262
- onValueChange({
263
- from: next.from === undefined ? undefined : toIsoDate(next.from),
264
- to: next.to === undefined ? undefined : toIsoDate(next.to),
265
- });
266
- }}
267
- />
268
- </PopoverPanel>
269
- </HeadlessPopover>
270
- );
271
- }
@@ -1,195 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- DialogBackdrop,
5
- DialogPanel,
6
- Dialog as HeadlessDialog,
7
- DialogDescription as HeadlessDialogDescription,
8
- DialogTitle as HeadlessDialogTitle,
9
- } from "@headlessui/react";
10
- import { X } from "lucide-react";
11
- import {
12
- type ComponentPropsWithoutRef,
13
- createContext,
14
- type ElementType,
15
- type MouseEvent,
16
- type ReactNode,
17
- useContext,
18
- useState,
19
- } from "react";
20
- import { cn } from "../../lib/cn";
21
-
22
- interface DialogContextValue {
23
- open: boolean;
24
- setOpen: (open: boolean) => void;
25
- }
26
- const DialogContext = createContext<DialogContextValue | null>(null);
27
-
28
- function useDialogContext(component: string): DialogContextValue {
29
- const ctx = useContext(DialogContext);
30
- if (ctx === null) {
31
- throw new Error(`<${component} /> must be rendered inside <Dialog>.`);
32
- }
33
- return ctx;
34
- }
35
-
36
- export interface DialogProps {
37
- open?: boolean;
38
- defaultOpen?: boolean;
39
- onOpenChange?: (open: boolean) => void;
40
- children: ReactNode;
41
- }
42
-
43
- /**
44
- * D3: Radix `Dialog` → Headless UI `Dialog`/`DialogPanel`/`DialogTitle`/`DialogBackdrop`.
45
- *
46
- * `Dialog` itself is **not** a Headless UI component — it's a plain context
47
- * provider. Headless UI's own `Dialog` only wraps the modal
48
- * backdrop/panel (`DialogContent` below) and is controlled-only
49
- * (`open`/`onClose`); it has no equivalent to Radix's `Dialog.Trigger`,
50
- * which rendered in place (outside any portal) and could itself drive an
51
- * *uncontrolled* open state. Two real consumers need both shapes:
52
- * `providers-screen.tsx`'s edit dialog is fully controlled
53
- * (`open={selectedId !== null}`, no `DialogTrigger` at all), while the
54
- * gallery's demo dialog is uncontrolled, opened by its own in-place
55
- * `DialogTrigger`. This root supplies the open/close plumbing for both,
56
- * the same controlled/uncontrolled duality `Select` (D17) uses.
57
- */
58
- export function Dialog({ open, defaultOpen = false, onOpenChange, children }: DialogProps) {
59
- const [internalOpen, setInternalOpen] = useState(defaultOpen);
60
- const isControlled = open !== undefined;
61
- const currentOpen = isControlled ? open : internalOpen;
62
-
63
- function setOpen(next: boolean) {
64
- if (!isControlled) setInternalOpen(next);
65
- onOpenChange?.(next);
66
- }
67
-
68
- return (
69
- <DialogContext.Provider value={{ open: currentOpen, setOpen }}>
70
- {children}
71
- </DialogContext.Provider>
72
- );
73
- }
74
-
75
- type TriggerProps<T extends ElementType> = { as?: T } & Omit<ComponentPropsWithoutRef<T>, "as">;
76
-
77
- /**
78
- * Renders in place, not portaled — `as` selects the rendered element/component,
79
- * matching Headless UI's own polymorphism convention rather than Radix's
80
- * `asChild` (D4 already retired that pattern for `Button`; this is the
81
- * identical call applied to a trigger). Replaces
82
- * `<DialogTrigger asChild><Button variant="secondary">…</Button></DialogTrigger>`
83
- * with `<DialogTrigger as={Button} variant="secondary">…</DialogTrigger>`
84
- * (the gallery's own only consumer, updated in this same change).
85
- */
86
- export function DialogTrigger<T extends ElementType = "button">({
87
- as,
88
- onClick,
89
- ...props
90
- }: TriggerProps<T>) {
91
- const { setOpen } = useDialogContext("DialogTrigger");
92
- const Component = (as ?? "button") as ElementType;
93
- return (
94
- <Component
95
- type={as === undefined ? "button" : undefined}
96
- onClick={(event: MouseEvent) => {
97
- (onClick as ((e: MouseEvent) => void) | undefined)?.(event);
98
- setOpen(true);
99
- }}
100
- {...props}
101
- />
102
- );
103
- }
104
-
105
- /** Unconsumed today (grepped across `admin/`) — kept for API parity. Same
106
- * `as`-polymorphic shape as `DialogTrigger`. */
107
- export function DialogClose<T extends ElementType = "button">({
108
- as,
109
- onClick,
110
- ...props
111
- }: TriggerProps<T>) {
112
- const { setOpen } = useDialogContext("DialogClose");
113
- const Component = (as ?? "button") as ElementType;
114
- return (
115
- <Component
116
- type={as === undefined ? "button" : undefined}
117
- onClick={(event: MouseEvent) => {
118
- (onClick as ((e: MouseEvent) => void) | undefined)?.(event);
119
- setOpen(false);
120
- }}
121
- {...props}
122
- />
123
- );
124
- }
125
-
126
- // Radius bumps to --radius-md here on purpose (design doc §3.5, unchanged by
127
- // D8's radius-scale rewrite — see theme.css): a floating layer reads as
128
- // detached from the grid beneath it.
129
- export function DialogContent({ className, children, ...props }: ComponentPropsWithoutRef<"div">) {
130
- const { open, setOpen } = useDialogContext("DialogContent");
131
- return (
132
- <HeadlessDialog open={open} onClose={setOpen} className="relative z-50">
133
- <DialogBackdrop
134
- transition
135
- className="fixed inset-0 bg-black/50 duration-150 ease-out data-closed:opacity-0"
136
- />
137
- <div className="fixed inset-0 flex w-screen items-center justify-center p-4">
138
- <DialogPanel
139
- transition
140
- className={cn(
141
- "relative w-full max-w-[480px] rounded-md border border-edge bg-surface-2 p-6 shadow-[var(--shadow-dialog)]",
142
- "duration-150 ease-out data-closed:scale-95 data-closed:opacity-0",
143
- className,
144
- )}
145
- {...props}
146
- >
147
- {children}
148
- <button
149
- type="button"
150
- aria-label="Close"
151
- onClick={() => setOpen(false)}
152
- className="absolute top-4 right-4 text-subtle-foreground hover:text-foreground"
153
- >
154
- <X size={16} strokeWidth={1.5} />
155
- </button>
156
- </DialogPanel>
157
- </div>
158
- </HeadlessDialog>
159
- );
160
- }
161
-
162
- export function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
163
- return <div className={cn("mb-4 flex flex-col gap-1", className)} {...props} />;
164
- }
165
-
166
- /** #56: the confirm/requeue dialog's own action row. Mirrors `DialogHeader`'s
167
- * shape (a thin, class-composing div, not a Headless-UI-wrapped primitive —
168
- * a footer has no accessibility semantics either library needs to own). */
169
- export function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
170
- return <div className={cn("mt-6 flex items-center justify-end gap-2", className)} {...props} />;
171
- }
172
-
173
- export function DialogTitle({
174
- className,
175
- ...props
176
- }: ComponentPropsWithoutRef<typeof HeadlessDialogTitle>) {
177
- return (
178
- <HeadlessDialogTitle
179
- className={cn("font-medium text-foreground text-title-sm", className)}
180
- {...props}
181
- />
182
- );
183
- }
184
-
185
- export function DialogDescription({
186
- className,
187
- ...props
188
- }: ComponentPropsWithoutRef<typeof HeadlessDialogDescription>) {
189
- return (
190
- <HeadlessDialogDescription
191
- className={cn("text-muted-foreground text-prose", className)}
192
- {...props}
193
- />
194
- );
195
- }