@misoto22/design 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/article.css +393 -0
  2. package/dist/components/AppShell/AppShell.d.ts +15 -1
  3. package/dist/components/AppShell/AppShell.js +8 -4
  4. package/dist/components/AppShell/AppShell.js.map +1 -1
  5. package/dist/components/Article/Article.d.ts +51 -0
  6. package/dist/components/Article/Article.js +21 -0
  7. package/dist/components/Article/Article.js.map +1 -0
  8. package/dist/components/Button/Button.js +2 -2
  9. package/dist/components/Button/Button.js.map +1 -1
  10. package/dist/components/Calendar/Calendar.d.ts +1 -1
  11. package/dist/components/Calendar/Calendar.js +259 -24
  12. package/dist/components/Calendar/Calendar.js.map +1 -1
  13. package/dist/components/Checkbox/Checkbox.js +1 -1
  14. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  15. package/dist/components/Collapsible/Collapsible.d.ts +15 -1
  16. package/dist/components/Collapsible/Collapsible.js +18 -0
  17. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  18. package/dist/components/Combobox/Combobox.js +1 -1
  19. package/dist/components/Combobox/Combobox.js.map +1 -1
  20. package/dist/components/Command/Command.js +7 -7
  21. package/dist/components/Command/Command.js.map +1 -1
  22. package/dist/components/ContextMenu/ContextMenu.js +2 -2
  23. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  24. package/dist/components/Diagram/Diagram.d.ts +81 -0
  25. package/dist/components/Diagram/Diagram.js +139 -0
  26. package/dist/components/Diagram/Diagram.js.map +1 -0
  27. package/dist/components/Dialog/Dialog.js +1 -1
  28. package/dist/components/Dialog/Dialog.js.map +1 -1
  29. package/dist/components/DropdownMenu/DropdownMenu.js +2 -2
  30. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  31. package/dist/components/FigureBand/FigureBand.js +41 -30
  32. package/dist/components/FigureBand/FigureBand.js.map +1 -1
  33. package/dist/components/Popover/Popover.js +1 -1
  34. package/dist/components/Popover/Popover.js.map +1 -1
  35. package/dist/components/Select/Select.js +2 -2
  36. package/dist/components/Select/Select.js.map +1 -1
  37. package/dist/components/Sheet/Sheet.js +1 -1
  38. package/dist/components/Sheet/Sheet.js.map +1 -1
  39. package/dist/components/Slider/Slider.js +1 -1
  40. package/dist/components/Slider/Slider.js.map +1 -1
  41. package/dist/components/Steps/Steps.d.ts +65 -0
  42. package/dist/components/Steps/Steps.js +55 -0
  43. package/dist/components/Steps/Steps.js.map +1 -0
  44. package/dist/components/Switch/Switch.js +1 -1
  45. package/dist/components/Switch/Switch.js.map +1 -1
  46. package/dist/components/Tooltip/Tooltip.js +1 -1
  47. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  48. package/dist/index.d.ts +4 -1
  49. package/dist/index.js +3 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/useSelectionIndicator.js +14 -10
  52. package/dist/lib/useSelectionIndicator.js.map +1 -1
  53. package/dist/styles.css +1 -1
  54. package/dist/themes.css +53 -7
  55. package/dist/tokens.css +75 -10
  56. package/dist/tokens.d.ts +8 -0
  57. package/dist/tokens.js +53 -8
  58. package/dist/tokens.json +53 -8
  59. package/package.json +22 -19
@@ -1,38 +1,252 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { ChevronLeft, ChevronRight } from "lucide-react";
4
- import { DayPicker } from "react-day-picker";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ import {
6
+ DayPicker,
7
+ useDayPicker
8
+ } from "react-day-picker";
5
9
  import { cn } from "../../lib/cn.js";
6
- import { Select, SelectItem } from "../Select/Select.js";
7
- function CalendarDropdown({ options, value, onChange, "aria-label": label }) {
10
+ const DEFAULT_SPAN = 10;
11
+ const YEAR_PAGE = 24;
12
+ const PickerContext = createContext({
13
+ open: null,
14
+ setOpen: () => {
15
+ },
16
+ startMonth: /* @__PURE__ */ new Date(),
17
+ endMonth: /* @__PURE__ */ new Date()
18
+ });
19
+ function useLabels() {
20
+ const { dayPickerProps } = useDayPicker();
21
+ const code = dayPickerProps.locale?.code ?? "en-US";
22
+ return useMemo(
23
+ () => ({
24
+ caption: new Intl.DateTimeFormat(code, { month: "long", year: "numeric" }),
25
+ month: new Intl.DateTimeFormat(code, { month: "short" })
26
+ }),
27
+ [code]
28
+ );
29
+ }
30
+ const STEP = "grid size-8 shrink-0 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none";
31
+ const CELL = "rounded-(--radius-row) py-2 text-sm transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none";
32
+ const CELL_ON = "bg-(--accent) text-(--accent-foreground)";
33
+ const CELL_OFF = "text-(--ink-2) hover:bg-(--stone) hover:text-(--ink)";
34
+ function MonthYearPanel({
35
+ index,
36
+ view,
37
+ anchor,
38
+ year: browsing,
39
+ month
40
+ }) {
41
+ const { setOpen, startMonth, endMonth } = useContext(PickerContext);
42
+ const { goToMonth } = useDayPicker();
43
+ const labels = useLabels();
44
+ const year = browsing ?? month.getFullYear();
45
+ const showing = year === month.getFullYear() ? month.getMonth() : -1;
46
+ const firstYear = startMonth.getFullYear();
47
+ const lastYear = endMonth.getFullYear();
48
+ const go = (nextYear, nextMonth) => {
49
+ goToMonth(new Date(nextYear, nextMonth - index, 1));
50
+ setOpen(null);
51
+ };
52
+ const inRange = (nextYear, nextMonth) => {
53
+ const value = nextYear * 12 + nextMonth;
54
+ const low = startMonth.getFullYear() * 12 + startMonth.getMonth();
55
+ const high = endMonth.getFullYear() * 12 + endMonth.getMonth();
56
+ return value >= low && value <= high;
57
+ };
58
+ if (view === "years") {
59
+ const page = (candidate) => Math.floor((candidate - firstYear) / YEAR_PAGE) * YEAR_PAGE + firstYear;
60
+ const start = anchor ?? page(year);
61
+ const years = Array.from({ length: YEAR_PAGE }, (_, offset) => start + offset).filter(
62
+ (candidate) => candidate >= firstYear && candidate <= lastYear
63
+ );
64
+ const back = start - YEAR_PAGE >= firstYear;
65
+ const forward = start + YEAR_PAGE <= lastYear;
66
+ const move = (delta) => setOpen({ index, view: "years", anchor: start + delta * YEAR_PAGE });
67
+ return /* @__PURE__ */ jsxs(Panel, { label: "Year", children: [
68
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-1", children: [
69
+ /* @__PURE__ */ jsx(
70
+ "button",
71
+ {
72
+ type: "button",
73
+ "aria-label": "Earlier years",
74
+ disabled: !back,
75
+ onClick: () => move(-1),
76
+ className: STEP,
77
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16, strokeWidth: 1.5, "aria-hidden": true })
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsxs("span", { className: "mono-meta tabular-nums text-(--ink-3-aa)", children: [
81
+ years[0],
82
+ " \u2013 ",
83
+ years.at(-1)
84
+ ] }),
85
+ /* @__PURE__ */ jsx(
86
+ "button",
87
+ {
88
+ type: "button",
89
+ "aria-label": "Later years",
90
+ disabled: !forward,
91
+ onClick: () => move(1),
92
+ className: STEP,
93
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16, strokeWidth: 1.5, "aria-hidden": true })
94
+ }
95
+ )
96
+ ] }),
97
+ /* @__PURE__ */ jsx("div", { "data-picker-grid": true, className: "grid grid-cols-4 gap-1", children: years.map((candidate) => /* @__PURE__ */ jsx(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ "aria-current": candidate === year ? "true" : void 0,
102
+ onClick: () => setOpen({ index, view: "months", year: candidate }),
103
+ className: cn(CELL, "tabular-nums", candidate === year ? CELL_ON : CELL_OFF),
104
+ children: candidate
105
+ },
106
+ candidate
107
+ )) })
108
+ ] }, "years");
109
+ }
110
+ return /* @__PURE__ */ jsxs(Panel, { label: "Month and year", children: [
111
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-1", children: [
112
+ /* @__PURE__ */ jsx(
113
+ "button",
114
+ {
115
+ type: "button",
116
+ "aria-label": "Previous year",
117
+ disabled: year <= firstYear,
118
+ onClick: () => setOpen({ index, view: "months", year: year - 1 }),
119
+ className: STEP,
120
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16, strokeWidth: 1.5, "aria-hidden": true })
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ onClick: () => setOpen({ index, view: "years", anchor }),
128
+ className: "rounded-(--radius-row) px-3 py-1 font-heading text-[length:var(--fs-item)] tabular-nums text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)",
129
+ children: year
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsx(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ "aria-label": "Next year",
137
+ disabled: year >= lastYear,
138
+ onClick: () => setOpen({ index, view: "months", year: year + 1 }),
139
+ className: STEP,
140
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16, strokeWidth: 1.5, "aria-hidden": true })
141
+ }
142
+ )
143
+ ] }),
144
+ /* @__PURE__ */ jsx("div", { "data-picker-grid": true, className: "grid grid-cols-3 gap-1", children: Array.from({ length: 12 }, (_, monthIndex) => /* @__PURE__ */ jsx(
145
+ "button",
146
+ {
147
+ type: "button",
148
+ "aria-current": monthIndex === showing ? "true" : void 0,
149
+ disabled: !inRange(year, monthIndex),
150
+ onClick: () => go(year, monthIndex),
151
+ className: cn(CELL, monthIndex === showing ? CELL_ON : CELL_OFF),
152
+ children: labels.month.format(new Date(year, monthIndex, 1))
153
+ },
154
+ monthIndex
155
+ )) })
156
+ ] }, "months");
157
+ }
158
+ function Panel({ label, children }) {
159
+ const box = useRef(null);
160
+ useEffect(() => {
161
+ const grid = box.current?.querySelector("[data-picker-grid]");
162
+ const target = box.current?.querySelector("[aria-current]") ?? grid?.querySelector("button:not(:disabled)");
163
+ target?.focus();
164
+ }, []);
8
165
  return /* @__PURE__ */ jsx(
9
- Select,
166
+ "div",
10
167
  {
11
- label: label ?? "Select",
12
- value: value === void 0 ? void 0 : String(value),
13
- onValueChange: (next) => onChange?.({ target: { value: next } }),
14
- contentClassName: "max-h-[10rem]",
15
- className: "h-8 w-auto min-w-24 gap-1.5 border-transparent px-2 py-1 font-heading text-[length:var(--fs-item)] hover:border-(--rule-2)",
16
- children: (options ?? []).map((option) => /* @__PURE__ */ jsx(SelectItem, { value: String(option.value), disabled: option.disabled, children: option.label }, option.value))
168
+ ref: box,
169
+ role: "group",
170
+ "aria-label": label,
171
+ className: "absolute inset-x-0 bottom-0 top-12 z-2 flex flex-col justify-center bg-(--paper)",
172
+ children
17
173
  }
18
174
  );
19
175
  }
20
- const DEFAULT_SPAN = 10;
21
- function Calendar({ className, classNames, captionLayout, components, ...props }) {
176
+ function CalendarCaption({ calendarMonth, displayIndex, className, ...rest }) {
177
+ const { open, setOpen } = useContext(PickerContext);
178
+ const labels = useLabels();
179
+ const trigger = useRef(null);
180
+ const mine = open !== null && open.index === displayIndex;
181
+ return /* @__PURE__ */ jsxs(
182
+ "div",
183
+ {
184
+ className,
185
+ onKeyDown: (event) => {
186
+ if (event.key !== "Escape" || !mine) return;
187
+ event.stopPropagation();
188
+ setOpen(null);
189
+ trigger.current?.focus();
190
+ },
191
+ ...rest,
192
+ children: [
193
+ /* @__PURE__ */ jsxs(
194
+ "button",
195
+ {
196
+ ref: trigger,
197
+ type: "button",
198
+ "aria-expanded": mine,
199
+ onClick: () => setOpen(mine ? null : { index: displayIndex, view: "months" }),
200
+ className: "group flex items-center gap-1.5 rounded-(--radius-row) px-2 py-1 font-heading text-[length:var(--fs-item)] text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)",
201
+ children: [
202
+ labels.caption.format(calendarMonth.date),
203
+ /* @__PURE__ */ jsx(
204
+ ChevronDown,
205
+ {
206
+ size: 16,
207
+ strokeWidth: 1.5,
208
+ "aria-hidden": true,
209
+ className: cn(
210
+ "shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast)",
211
+ mine && "rotate-180"
212
+ )
213
+ }
214
+ )
215
+ ]
216
+ }
217
+ ),
218
+ mine && open !== null && /* @__PURE__ */ jsx(
219
+ MonthYearPanel,
220
+ {
221
+ index: displayIndex,
222
+ view: open.view,
223
+ anchor: open.anchor,
224
+ year: open.year,
225
+ month: calendarMonth.date
226
+ }
227
+ )
228
+ ]
229
+ }
230
+ );
231
+ }
232
+ function Calendar({ className, classNames, components, ...props }) {
22
233
  const now = /* @__PURE__ */ new Date();
234
+ const [open, setOpen] = useState(null);
235
+ const startMonth = props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0);
236
+ const endMonth = props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11);
23
237
  const isRange = props.mode === "range";
24
238
  const FILL = "[&>button]:bg-(--accent) [&>button]:text-(--accent-foreground) [&>button:hover]:bg-(--accent) [&>button:hover]:text-(--accent-foreground)";
25
- return /* @__PURE__ */ jsx(
239
+ return /* @__PURE__ */ jsx(PickerContext, { value: { open, setOpen, startMonth, endMonth }, children: /* @__PURE__ */ jsx(
26
240
  DayPicker,
27
241
  {
28
242
  showOutsideDays: true,
29
- captionLayout: captionLayout ?? "dropdown",
30
- startMonth: props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0),
31
- endMonth: props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11),
243
+ captionLayout: "label",
244
+ startMonth,
245
+ endMonth,
32
246
  className: cn("w-fit p-3", className),
33
247
  components: {
34
248
  Chevron: ({ orientation, ...rest }) => orientation === "left" ? /* @__PURE__ */ jsx(ChevronLeft, { size: 16, strokeWidth: 1.5, "aria-hidden": true, ...rest }) : /* @__PURE__ */ jsx(ChevronRight, { size: 16, strokeWidth: 1.5, "aria-hidden": true, ...rest }),
35
- Dropdown: CalendarDropdown,
249
+ MonthCaption: CalendarCaption,
36
250
  ...components
37
251
  },
38
252
  classNames: {
@@ -43,12 +257,33 @@ function Calendar({ className, classNames, captionLayout, components, ...props }
43
257
  // The nav renders BEFORE the months in the DOM, so left in the flow it
44
258
  // stacks its two arrows above the grid. Taking it out of the flow puts
45
259
  // them where they belong — one at each end of the caption.
46
- nav: "absolute inset-x-3 top-3 z-1 flex items-center justify-between",
260
+ //
261
+ // `h-9` is what makes them line up. The caption is a 36px row and the
262
+ // nav was a 16px glyph pinned at the same top edge, so the arrows sat
263
+ // ten pixels above the month they belong to — close enough to read as
264
+ // a mistake and not close enough to read as a decision. Same height,
265
+ // same centre line.
266
+ //
267
+ // It steps away while the picker is open: two ways to change the
268
+ // month, one of them behind a panel, is one too many.
269
+ //
270
+ // `pointer-events-none` on the STRIP and back on for the two buttons:
271
+ // at the caption's full height it lies across the caption, and a bar
272
+ // with nothing in the middle of it was still eating the click on the
273
+ // month label underneath.
274
+ nav: cn(
275
+ "pointer-events-none absolute inset-x-3 top-3 z-1 flex h-9 items-center justify-between transition-opacity duration-(--duration-fast)",
276
+ open && "opacity-0"
277
+ ),
278
+ // The arrows are pointer targets, not glyphs: a 16px chevron with no
279
+ // box is a 16px hit area, which is under every guideline there is.
280
+ button_previous: cn(STEP, open ? "pointer-events-none" : "pointer-events-auto"),
281
+ button_next: cn(STEP, open ? "pointer-events-none" : "pointer-events-auto"),
47
282
  months: "flex flex-col gap-4 sm:flex-row",
48
- month: "flex flex-col gap-3",
283
+ // Positioned, because the month picker covers this month's grid and
284
+ // nothing else — in a two-month range picker each pane opens its own.
285
+ month: "relative flex flex-col gap-3",
49
286
  month_caption: "flex h-9 items-center justify-center",
50
- dropdowns: "flex items-center gap-1",
51
- dropdown_root: "relative inline-flex items-center",
52
287
  month_grid: "w-full border-collapse",
53
288
  weekdays: "flex",
54
289
  weekday: "w-9 mono-meta font-normal text-(--ink-3-aa)",
@@ -77,7 +312,7 @@ function Calendar({ className, classNames, captionLayout, components, ...props }
77
312
  },
78
313
  ...props
79
314
  }
80
- );
315
+ ) });
81
316
  }
82
317
  var Calendar_default = Calendar;
83
318
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["'use client'\n\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { DayPicker, type DayPickerProps, type DropdownProps } from 'react-day-picker'\nimport { cn } from '../../lib/cn'\nimport { Select, SelectItem } from '../Select/Select'\n\nexport type CalendarProps = DayPickerProps\n\n/**\n * Month and year, as the system's own `Select`.\n *\n * The library renders a native `<select>` here. Two problems with that: the\n * option list is drawn by the operating system, so it carries none of these\n * tokens and looks like a different product the moment it opens; and with a\n * wide year range it becomes a hundred-row list the platform renders as one\n * enormous column.\n *\n * The adapter is the small awkward part — the library hands back a native\n * change event, so a value from our Select is wrapped to look like one. That is\n * contained here rather than leaking into every call site.\n */\nfunction CalendarDropdown({ options, value, onChange, 'aria-label': label }: DropdownProps) {\n return (\n <Select\n label={label ?? 'Select'}\n value={value === undefined ? undefined : String(value)}\n onValueChange={(next) =>\n onChange?.({ target: { value: next } } as React.ChangeEvent<HTMLSelectElement>)\n }\n // A third of the calendar's height. The default list is as tall as the\n // month grid, so opening the year picker blotted out the thing the reader\n // opened it in order to change.\n contentClassName=\"max-h-[10rem]\"\n className=\"h-8 w-auto min-w-24 gap-1.5 border-transparent px-2 py-1 font-heading text-[length:var(--fs-item)] hover:border-(--rule-2)\"\n >\n {(options ?? []).map((option) => (\n <SelectItem key={option.value} value={String(option.value)} disabled={option.disabled}>\n {option.label}\n </SelectItem>\n ))}\n </Select>\n )\n}\n\n/** Ten years either side of now — see the note on `startMonth` below. */\nconst DEFAULT_SPAN = 10\n\n/**\n * A month, as a grid of days.\n *\n * Wraps react-day-picker rather than building a calendar, and the reason is the\n * long tail: a month grid is easy, and everything around it is not — the\n * keyboard contract (arrows move a day, Page moves a month, Home and End reach\n * the week's ends), the `aria-live` announcement when the month changes, week\n * numbering, the first day of the week varying by locale, and the fact that\n * \"today\" and \"selected\" are different states a screen reader must hear apart.\n *\n * Every class is replaced rather than layered over the library's stylesheet, so\n * nothing here depends on a CSS file this package does not control — but the\n * library's OWN class is kept on each slot, because passing a class REPLACES it\n * and every `.rdp-*` selector downstream depends on those.\n *\n * @example\n * <Calendar mode=\"single\" selected={date} onSelect={setDate} />\n * @example\n * // A birth date: widen the years, since the default span is deliberately short.\n * <Calendar mode=\"single\" startMonth={new Date(1920, 0)} endMonth={new Date()} />\n */\nexport function Calendar({ className, classNames, captionLayout, components, ...props }: CalendarProps) {\n const now = new Date()\n\n /**\n * A one-day range is marked start AND end AND middle, all three.\n *\n * That is what made the earlier arrangement collapse: `range_middle` cleared\n * the button's fill so a middle day would not look chosen, and on a one-day\n * range that same rule cleared the fill of the only day there was. Deciding\n * by mode instead of fighting it with `!important` means the two never\n * describe the same button — in a range the fill belongs to the ends, and in\n * single mode it belongs to `selected`.\n */\n const isRange = props.mode === 'range'\n const FILL =\n '[&>button]:bg-(--accent) [&>button]:text-(--accent-foreground) [&>button:hover]:bg-(--accent) [&>button:hover]:text-(--accent-foreground)'\n\n return (\n <DayPicker\n showOutsideDays\n // Month and year as dropdowns, not two arrows. Stepping a month at a time\n // is fine for \"next Tuesday\" and useless for a date two years back.\n captionLayout={captionLayout ?? 'dropdown'}\n // Ten years either side, NOT a century. A hundred-and-eleven-row year\n // list is a scroll, not a choice — and the case that needs one (a birth\n // date) is rare enough to ask for it explicitly. Both ends are props, so\n // widening is one line at the call site.\n startMonth={props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0)}\n endMonth={props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11)}\n className={cn('w-fit p-3', className)}\n components={{\n Chevron: ({ orientation, ...rest }) =>\n orientation === 'left' ? (\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ) : (\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ),\n Dropdown: CalendarDropdown,\n ...components,\n }}\n classNames={{\n // `rdp-root` is kept deliberately. Passing a class for a slot REPLACES\n // the library's own, so overriding `root` silently removed the hook\n // that every `.rdp-*` selector — ours and a consumer's — depends on.\n root: 'rdp-root relative',\n // The nav renders BEFORE the months in the DOM, so left in the flow it\n // stacks its two arrows above the grid. Taking it out of the flow puts\n // them where they belong — one at each end of the caption.\n nav: 'absolute inset-x-3 top-3 z-1 flex items-center justify-between',\n months: 'flex flex-col gap-4 sm:flex-row',\n month: 'flex flex-col gap-3',\n month_caption: 'flex h-9 items-center justify-center',\n dropdowns: 'flex items-center gap-1',\n dropdown_root: 'relative inline-flex items-center',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'w-9 mono-meta font-normal text-(--ink-3-aa)',\n week: 'mt-1 flex w-full',\n // The band lives on the CELL and the mark lives on the BUTTON. Keeping\n // them apart is what lets a range be one continuous wash while both of\n // its ends stay round — the previous arrangement flattened the corners\n // of whichever day was an end, and a one-day range is BOTH ends, so it\n // came out as a square.\n day: 'relative p-0 text-center',\n day_button:\n 'relative z-1 inline-flex size-9 items-center justify-center rounded-(--radius-pill) text-sm text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n selected: isRange ? '' : FILL,\n // Today is an outline, not a fill — it is a fact about the calendar,\n // not a choice the reader made, and the two must not look alike.\n today: '[&>button]:ring-1 [&>button]:ring-(--rule-hard) [&>button]:ring-inset',\n outside: '[&>button]:text-(--ink-3-aa)',\n disabled: '[&>button]:opacity-(--disabled-opacity) [&>button]:pointer-events-none',\n // The wash lives on the CELL and the mark on the BUTTON, which is what\n // lets a range read as one continuous band while both of its ends stay\n // round. A middle day gets the wash and no fill; the ends get both.\n range_middle: 'bg-(--accent-muted) [&>button]:text-(--ink) [&>button:hover]:bg-(--stone)',\n range_start: cn('bg-(--accent-muted) rounded-s-(--radius-pill)', FILL),\n range_end: cn('bg-(--accent-muted) rounded-e-(--radius-pill)', FILL),\n hidden: 'invisible',\n ...classNames,\n }}\n {...props}\n />\n )\n}\n\nexport default Calendar\n"],"mappings":";AAqCQ;AAnCR,SAAS,aAAa,oBAAoB;AAC1C,SAAS,iBAA0D;AACnE,SAAS,UAAU;AACnB,SAAS,QAAQ,kBAAkB;AAiBnC,SAAS,iBAAiB,EAAE,SAAS,OAAO,UAAU,cAAc,MAAM,GAAkB;AAC1F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,SAAS;AAAA,MAChB,OAAO,UAAU,SAAY,SAAY,OAAO,KAAK;AAAA,MACrD,eAAe,CAAC,SACd,WAAW,EAAE,QAAQ,EAAE,OAAO,KAAK,EAAE,CAAyC;AAAA,MAKhF,kBAAiB;AAAA,MACjB,WAAU;AAAA,MAER,sBAAW,CAAC,GAAG,IAAI,CAAC,WACpB,oBAAC,cAA8B,OAAO,OAAO,OAAO,KAAK,GAAG,UAAU,OAAO,UAC1E,iBAAO,SADO,OAAO,KAExB,CACD;AAAA;AAAA,EACH;AAEJ;AAGA,MAAM,eAAe;AAuBd,SAAS,SAAS,EAAE,WAAW,YAAY,eAAe,YAAY,GAAG,MAAM,GAAkB;AACtG,QAAM,MAAM,oBAAI,KAAK;AAYrB,QAAM,UAAU,MAAM,SAAS;AAC/B,QAAM,OACJ;AAEF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,iBAAe;AAAA,MAGf,eAAe,iBAAiB;AAAA,MAKhC,YAAY,MAAM,cAAc,IAAI,KAAK,IAAI,YAAY,IAAI,cAAc,CAAC;AAAA,MAC5E,UAAU,MAAM,YAAY,IAAI,KAAK,IAAI,YAAY,IAAI,cAAc,EAAE;AAAA,MACzE,WAAW,GAAG,aAAa,SAAS;AAAA,MACpC,YAAY;AAAA,QACV,SAAS,CAAC,EAAE,aAAa,GAAG,KAAK,MAC/B,gBAAgB,SACd,oBAAC,eAAY,MAAM,IAAI,aAAa,KAAK,eAAW,MAAE,GAAG,MAAM,IAE/D,oBAAC,gBAAa,MAAM,IAAI,aAAa,KAAK,eAAW,MAAE,GAAG,MAAM;AAAA,QAEpE,UAAU;AAAA,QACV,GAAG;AAAA,MACL;AAAA,MACA,YAAY;AAAA;AAAA;AAAA;AAAA,QAIV,MAAM;AAAA;AAAA;AAAA;AAAA,QAIN,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,eAAe;AAAA,QACf,WAAW;AAAA,QACX,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMN,KAAK;AAAA,QACL,YACE;AAAA,QACF,UAAU,UAAU,KAAK;AAAA;AAAA;AAAA,QAGzB,OAAO;AAAA,QACP,SAAS;AAAA,QACT,UAAU;AAAA;AAAA;AAAA;AAAA,QAIV,cAAc;AAAA,QACd,aAAa,GAAG,iDAAiD,IAAI;AAAA,QACrE,WAAW,GAAG,iDAAiD,IAAI;AAAA,QACnE,QAAQ;AAAA,QACR,GAAG;AAAA,MACL;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["'use client'\n\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react'\nimport { createContext, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport {\n DayPicker,\n useDayPicker,\n type DayPickerProps,\n type MonthCaptionProps,\n} from 'react-day-picker'\nimport { cn } from '../../lib/cn'\n\nexport type CalendarProps = DayPickerProps\n\n/** Ten years either side of now — see the note on `startMonth` below. */\nconst DEFAULT_SPAN = 10\n\n/** How many years one page of the year grid holds: four columns, six rows. */\nconst YEAR_PAGE = 24\n\ntype View = 'months' | 'years'\n\ninterface PickerOpen {\n /** Which displayed month has its picker open. */\n index: number\n view: View\n /** The first year of the page the year grid is showing, when it is showing. */\n anchor?: number\n /**\n * The year the MONTH grid is showing, when it is not the displayed month's.\n *\n * The picker browses; it does not navigate. Stepping the year used to call\n * `goToMonth` and close the panel, so `‹` on \"2026\" left the reader looking\n * at a different month with the picker gone — one click that did two things,\n * neither of them the one asked for. Now the arrows move this, the grid\n * re-renders under them, and the calendar moves only when a month is chosen.\n */\n year?: number\n}\n\ninterface PickerState {\n open: PickerOpen | null\n setOpen: (next: PickerOpen | null) => void\n startMonth: Date\n endMonth: Date\n}\n\nconst PickerContext = createContext<PickerState>({\n open: null,\n setOpen: () => {},\n startMonth: new Date(),\n endMonth: new Date(),\n})\n\n/** `Intl` beats the library's formatter here: it takes a plain BCP 47 tag. */\nfunction useLabels() {\n const { dayPickerProps } = useDayPicker()\n const code = dayPickerProps.locale?.code ?? 'en-US'\n return useMemo(\n () => ({\n caption: new Intl.DateTimeFormat(code, { month: 'long', year: 'numeric' }),\n month: new Intl.DateTimeFormat(code, { month: 'short' }),\n }),\n [code],\n )\n}\n\n/** The calendar's chrome buttons: the same corner every other control draws. */\nconst STEP =\n 'grid size-8 shrink-0 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\nconst CELL =\n 'rounded-(--radius-row) py-2 text-sm transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n/** The one that is showing, marked the way a chosen day is marked. */\nconst CELL_ON = 'bg-(--accent) text-(--accent-foreground)'\nconst CELL_OFF = 'text-(--ink-2) hover:bg-(--stone) hover:text-(--ink)'\n\n/**\n * The month and year picker, drawn IN PLACE of the day grid.\n *\n * The previous arrangement was two `Select`s in the caption, which portalled\n * their option lists over the grid — a ten-rem box showing three months at a\n * time with a scroll arrow at each end, floating on top of the calendar the\n * reader opened it to change. Three visible options out of twelve is not a\n * picker, and it covered the thing being picked for.\n *\n * Twelve months fit in a 3×4 grid at the exact size of the grid they replace,\n * so nothing overlaps, nothing scrolls, and the panel is the same shape as the\n * surface underneath it. Twenty years fit the same box in 4×5, which is why the\n * default span is ten years either side: one page, no paging.\n */\nfunction MonthYearPanel({\n index,\n view,\n anchor,\n year: browsing,\n month,\n}: {\n index: number\n view: View\n anchor?: number\n year?: number\n month: Date\n}) {\n const { setOpen, startMonth, endMonth } = useContext(PickerContext)\n const { goToMonth } = useDayPicker()\n const labels = useLabels()\n\n /** The year being browsed, which is the displayed one until an arrow moves it. */\n const year = browsing ?? month.getFullYear()\n /** The month marked as current — only when the browsed year IS the shown one. */\n const showing = year === month.getFullYear() ? month.getMonth() : -1\n const firstYear = startMonth.getFullYear()\n const lastYear = endMonth.getFullYear()\n\n /**\n * The offset is what makes this work in a two-month range picker: `goToMonth`\n * always sets the FIRST displayed month, so picking September in the\n * right-hand pane has to ask for August.\n */\n const go = (nextYear: number, nextMonth: number) => {\n goToMonth(new Date(nextYear, nextMonth - index, 1))\n setOpen(null)\n }\n\n const inRange = (nextYear: number, nextMonth: number) => {\n const value = nextYear * 12 + nextMonth\n const low = startMonth.getFullYear() * 12 + startMonth.getMonth()\n const high = endMonth.getFullYear() * 12 + endMonth.getMonth()\n return value >= low && value <= high\n }\n\n if (view === 'years') {\n /**\n * Pages TILE the range from its first year, rather than being centred on\n * whichever year is showing. Centring left the first year of a range on no\n * page at all when the range was not a whole number of pages long — the\n * calendar offered a year it could never reach, and nothing said so.\n */\n const page = (candidate: number) =>\n Math.floor((candidate - firstYear) / YEAR_PAGE) * YEAR_PAGE + firstYear\n const start = anchor ?? page(year)\n const years = Array.from({ length: YEAR_PAGE }, (_, offset) => start + offset).filter(\n (candidate) => candidate >= firstYear && candidate <= lastYear,\n )\n const back = start - YEAR_PAGE >= firstYear\n const forward = start + YEAR_PAGE <= lastYear\n const move = (delta: number) =>\n setOpen({ index, view: 'years', anchor: start + delta * YEAR_PAGE })\n\n return (\n <Panel key=\"years\" label=\"Year\">\n <div className=\"mb-1 flex items-center justify-between gap-1\">\n <button\n type=\"button\"\n aria-label=\"Earlier years\"\n disabled={!back}\n onClick={() => move(-1)}\n className={STEP}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n <span className=\"mono-meta tabular-nums text-(--ink-3-aa)\">\n {years[0]} – {years.at(-1)}\n </span>\n <button\n type=\"button\"\n aria-label=\"Later years\"\n disabled={!forward}\n onClick={() => move(1)}\n className={STEP}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </div>\n <div data-picker-grid className=\"grid grid-cols-4 gap-1\">\n {years.map((candidate) => (\n <button\n key={candidate}\n type=\"button\"\n aria-current={candidate === year ? 'true' : undefined}\n onClick={() => setOpen({ index, view: 'months', year: candidate })}\n className={cn(CELL, 'tabular-nums', candidate === year ? CELL_ON : CELL_OFF)}\n >\n {candidate}\n </button>\n ))}\n </div>\n </Panel>\n )\n }\n\n return (\n <Panel key=\"months\" label=\"Month and year\">\n <div className=\"mb-1 flex items-center justify-between gap-1\">\n <button\n type=\"button\"\n aria-label=\"Previous year\"\n disabled={year <= firstYear}\n onClick={() => setOpen({ index, view: 'months', year: year - 1 })}\n className={STEP}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n <button\n type=\"button\"\n onClick={() => setOpen({ index, view: 'years', anchor })}\n className=\"rounded-(--radius-row) px-3 py-1 font-heading text-[length:var(--fs-item)] tabular-nums text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)\"\n >\n {year}\n </button>\n <button\n type=\"button\"\n aria-label=\"Next year\"\n disabled={year >= lastYear}\n onClick={() => setOpen({ index, view: 'months', year: year + 1 })}\n className={STEP}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </div>\n <div data-picker-grid className=\"grid grid-cols-3 gap-1\">\n {Array.from({ length: 12 }, (_, monthIndex) => (\n <button\n key={monthIndex}\n type=\"button\"\n aria-current={monthIndex === showing ? 'true' : undefined}\n disabled={!inRange(year, monthIndex)}\n onClick={() => go(year, monthIndex)}\n className={cn(CELL, monthIndex === showing ? CELL_ON : CELL_OFF)}\n >\n {labels.month.format(new Date(year, monthIndex, 1))}\n </button>\n ))}\n </div>\n </Panel>\n )\n}\n\n/**\n * The panel's box.\n *\n * `top-12` is the caption's own height plus the gap under it, so the panel\n * starts exactly where the weekday row does and ends where the last week does.\n * Sized against the grid rather than given a height of its own, because a fixed\n * height would be wrong in the month that needs a sixth week.\n *\n * Focus moves in when it opens and the month showing is what receives it, so a\n * keyboard reader lands on the current value rather than at the top of a grid\n * they then have to traverse. It is a `group` rather than a `dialog`: nothing\n * here is modal, and announcing a dialog that Tab walks straight out of is\n * worse than announcing nothing.\n */\nfunction Panel({ label, children }: { label: string; children: ReactNode }) {\n const box = useRef<HTMLDivElement>(null)\n\n // Keyed by view at the call site, so switching from months to years remounts\n // this and the effect runs again. Without that the year grid replaced the\n // button that had just been clicked, focus fell back to `<body>`, and Escape\n // no longer reached the handler that closes the panel.\n useEffect(() => {\n // The current value if the panel is showing it, and otherwise the first\n // cell in the grid. Browsing to another year leaves nothing marked current,\n // and a panel that focuses nothing drops focus to `<body>` — where Escape\n // no longer reaches the handler that closes it.\n const grid = box.current?.querySelector('[data-picker-grid]')\n const target =\n box.current?.querySelector<HTMLButtonElement>('[aria-current]') ??\n grid?.querySelector<HTMLButtonElement>('button:not(:disabled)')\n target?.focus()\n }, [])\n\n return (\n <div\n ref={box}\n role=\"group\"\n aria-label={label}\n className=\"absolute inset-x-0 bottom-0 top-12 z-2 flex flex-col justify-center bg-(--paper)\"\n >\n {children}\n </div>\n )\n}\n\n/**\n * The caption: the month and year, as one control that opens the picker.\n *\n * One button rather than two dropdowns. \"September 2026\" is how the date is\n * said, and splitting it into two controls with a chevron each put four\n * interactive things in a 250px caption that also has to hold two arrows.\n */\nfunction CalendarCaption({ calendarMonth, displayIndex, className, ...rest }: MonthCaptionProps) {\n const { open, setOpen } = useContext(PickerContext)\n const labels = useLabels()\n const trigger = useRef<HTMLButtonElement>(null)\n const mine = open !== null && open.index === displayIndex\n\n return (\n <div\n className={className}\n // Escape closes the picker and puts focus back where it came from. A\n // panel that covers the grid and cannot be dismissed from the keyboard is\n // a trap, however briefly.\n onKeyDown={(event) => {\n if (event.key !== 'Escape' || !mine) return\n event.stopPropagation()\n setOpen(null)\n trigger.current?.focus()\n }}\n {...rest}\n >\n <button\n ref={trigger}\n type=\"button\"\n aria-expanded={mine}\n onClick={() => setOpen(mine ? null : { index: displayIndex, view: 'months' })}\n className=\"group flex items-center gap-1.5 rounded-(--radius-row) px-2 py-1 font-heading text-[length:var(--fs-item)] text-(--ink) transition-colors duration-(--duration-fast) hover:bg-(--stone)\"\n >\n {labels.caption.format(calendarMonth.date)}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className={cn(\n 'shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast)',\n mine && 'rotate-180',\n )}\n />\n </button>\n {mine && open !== null && (\n <MonthYearPanel\n index={displayIndex}\n view={open.view}\n anchor={open.anchor}\n year={open.year}\n month={calendarMonth.date}\n />\n )}\n </div>\n )\n}\n\n/**\n * A month, as a grid of days.\n *\n * Wraps react-day-picker rather than building a calendar, and the reason is the\n * long tail: a month grid is easy, and everything around it is not — the\n * keyboard contract (arrows move a day, Page moves a month, Home and End reach\n * the week's ends), the `aria-live` announcement when the month changes, week\n * numbering, the first day of the week varying by locale, and the fact that\n * \"today\" and \"selected\" are different states a screen reader must hear apart.\n *\n * Every class is replaced rather than layered over the library's stylesheet, so\n * nothing here depends on a CSS file this package does not control — but the\n * library's OWN class is kept on each slot, because passing a class REPLACES it\n * and every `.rdp-*` selector downstream depends on those.\n *\n * @example\n * <Calendar mode=\"single\" selected={date} onSelect={setDate} />\n * @example\n * // A birth date: widen the years, since the default span is deliberately short.\n * <Calendar mode=\"single\" startMonth={new Date(1920, 0)} endMonth={new Date()} />\n */\nexport function Calendar({ className, classNames, components, ...props }: CalendarProps) {\n const now = new Date()\n const [open, setOpen] = useState<PickerState['open']>(null)\n\n // Ten years either side, NOT a century. A hundred-and-eleven-row year list is\n // a scroll, not a choice — and the case that needs one (a birth date) is rare\n // enough to ask for it explicitly. Both ends are props, so widening is one\n // line at the call site.\n const startMonth = props.startMonth ?? new Date(now.getFullYear() - DEFAULT_SPAN, 0)\n const endMonth = props.endMonth ?? new Date(now.getFullYear() + DEFAULT_SPAN, 11)\n\n /**\n * A one-day range is marked start AND end AND middle, all three.\n *\n * That is what made the earlier arrangement collapse: `range_middle` cleared\n * the button's fill so a middle day would not look chosen, and on a one-day\n * range that same rule cleared the fill of the only day there was. Deciding\n * by mode instead of fighting it with `!important` means the two never\n * describe the same button — in a range the fill belongs to the ends, and in\n * single mode it belongs to `selected`.\n */\n const isRange = props.mode === 'range'\n const FILL =\n '[&>button]:bg-(--accent) [&>button]:text-(--accent-foreground) [&>button:hover]:bg-(--accent) [&>button:hover]:text-(--accent-foreground)'\n\n return (\n <PickerContext value={{ open, setOpen, startMonth, endMonth }}>\n <DayPicker\n showOutsideDays\n // The caption is one control that swaps the grid for a month picker, so\n // the library's own dropdown layout is not wanted.\n captionLayout=\"label\"\n startMonth={startMonth}\n endMonth={endMonth}\n className={cn('w-fit p-3', className)}\n components={{\n Chevron: ({ orientation, ...rest }) =>\n orientation === 'left' ? (\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ) : (\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden {...rest} />\n ),\n MonthCaption: CalendarCaption,\n ...components,\n }}\n classNames={{\n // `rdp-root` is kept deliberately. Passing a class for a slot REPLACES\n // the library's own, so overriding `root` silently removed the hook\n // that every `.rdp-*` selector — ours and a consumer's — depends on.\n root: 'rdp-root relative',\n // The nav renders BEFORE the months in the DOM, so left in the flow it\n // stacks its two arrows above the grid. Taking it out of the flow puts\n // them where they belong — one at each end of the caption.\n //\n // `h-9` is what makes them line up. The caption is a 36px row and the\n // nav was a 16px glyph pinned at the same top edge, so the arrows sat\n // ten pixels above the month they belong to — close enough to read as\n // a mistake and not close enough to read as a decision. Same height,\n // same centre line.\n //\n // It steps away while the picker is open: two ways to change the\n // month, one of them behind a panel, is one too many.\n //\n // `pointer-events-none` on the STRIP and back on for the two buttons:\n // at the caption's full height it lies across the caption, and a bar\n // with nothing in the middle of it was still eating the click on the\n // month label underneath.\n nav: cn(\n 'pointer-events-none absolute inset-x-3 top-3 z-1 flex h-9 items-center justify-between transition-opacity duration-(--duration-fast)',\n open && 'opacity-0',\n ),\n // The arrows are pointer targets, not glyphs: a 16px chevron with no\n // box is a 16px hit area, which is under every guideline there is.\n button_previous: cn(STEP, open ? 'pointer-events-none' : 'pointer-events-auto'),\n button_next: cn(STEP, open ? 'pointer-events-none' : 'pointer-events-auto'),\n months: 'flex flex-col gap-4 sm:flex-row',\n // Positioned, because the month picker covers this month's grid and\n // nothing else — in a two-month range picker each pane opens its own.\n month: 'relative flex flex-col gap-3',\n month_caption: 'flex h-9 items-center justify-center',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'w-9 mono-meta font-normal text-(--ink-3-aa)',\n week: 'mt-1 flex w-full',\n // The band lives on the CELL and the mark lives on the BUTTON. Keeping\n // them apart is what lets a range be one continuous wash while both of\n // its ends stay round — the previous arrangement flattened the corners\n // of whichever day was an end, and a one-day range is BOTH ends, so it\n // came out as a square.\n day: 'relative p-0 text-center',\n day_button:\n 'relative z-1 inline-flex size-9 items-center justify-center rounded-(--radius-pill) text-sm text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n selected: isRange ? '' : FILL,\n // Today is an outline, not a fill — it is a fact about the calendar,\n // not a choice the reader made, and the two must not look alike.\n today: '[&>button]:ring-1 [&>button]:ring-(--rule-hard) [&>button]:ring-inset',\n outside: '[&>button]:text-(--ink-3-aa)',\n disabled: '[&>button]:opacity-(--disabled-opacity) [&>button]:pointer-events-none',\n // The wash lives on the CELL and the mark on the BUTTON, which is what\n // lets a range read as one continuous band while both of its ends stay\n // round. A middle day gets the wash and no fill; the ends get both.\n range_middle: 'bg-(--accent-muted) [&>button]:text-(--ink) [&>button:hover]:bg-(--stone)',\n range_start: cn('bg-(--accent-muted) rounded-s-(--radius-pill)', FILL),\n range_end: cn('bg-(--accent-muted) rounded-e-(--radius-pill)', FILL),\n hidden: 'invisible',\n ...classNames,\n }}\n {...props}\n />\n </PickerContext>\n )\n}\n\nexport default Calendar\n"],"mappings":"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","names":[]}
@@ -8,7 +8,7 @@ function Checkbox({ className, ...props }) {
8
8
  CheckboxPrimitive.Root,
9
9
  {
10
10
  className: cn(
11
- "inline-flex size-[18px] shrink-0 items-center justify-center rounded-(--radius-sm) border border-(--rule-2) bg-(--paper) text-(--accent-foreground) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) data-[state=checked]:bg-(--accent) data-[state=indeterminate]:border-(--accent) data-[state=indeterminate]:bg-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
11
+ "inline-flex size-[18px] shrink-0 items-center justify-center rounded-(--radius-xs) border border-(--rule-2) bg-(--paper) text-(--accent-foreground) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) data-[state=checked]:bg-(--accent) data-[state=indeterminate]:border-(--accent) data-[state=indeterminate]:bg-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
12
12
  className
13
13
  ),
14
14
  ...props,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["'use client'\n\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport { Check, Minus } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport type CheckboxProps = ComponentProps<typeof CheckboxPrimitive.Root>\n\n/**\n * A choice that takes effect when the form is submitted.\n *\n * Supports the indeterminate state (`checked=\"indeterminate\"`), which is what a\n * \"select all\" header needs when only some rows are selected — a plain\n * unchecked box there tells the reader the opposite of the truth.\n *\n * Pair with `Field`, or wrap it in a `<label>` at the call site so the words\n * beside it are part of the click target.\n *\n * @example\n * <label className=\"flex items-center gap-2.5 text-sm\">\n * <Checkbox defaultChecked /> Subscribe to updates\n * </label>\n */\nexport function Checkbox({ className, ...props }: CheckboxProps) {\n return (\n <CheckboxPrimitive.Root\n className={cn(\n 'inline-flex size-[18px] shrink-0 items-center justify-center rounded-(--radius-sm) border border-(--rule-2) bg-(--paper) text-(--accent-foreground) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) data-[state=checked]:bg-(--accent) data-[state=indeterminate]:border-(--accent) data-[state=indeterminate]:bg-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"flex items-center justify-center text-current\">\n {props.checked === 'indeterminate' ? (\n <Minus className=\"size-3\" strokeWidth={3} aria-hidden />\n ) : (\n <Check className=\"size-3\" strokeWidth={3} aria-hidden />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n}\n\nexport default Checkbox\n"],"mappings":";AAmCU;AAjCV,YAAY,uBAAuB;AACnC,SAAS,OAAO,aAAa;AAE7B,SAAS,UAAU;AAmBZ,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAkB;AAC/D,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,kBAAkB,WAAlB,EAA4B,WAAU,iDACpC,gBAAM,YAAY,kBACjB,oBAAC,SAAM,WAAU,UAAS,aAAa,GAAG,eAAW,MAAC,IAEtD,oBAAC,SAAM,WAAU,UAAS,aAAa,GAAG,eAAW,MAAC,GAE1D;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["'use client'\n\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport { Check, Minus } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport type CheckboxProps = ComponentProps<typeof CheckboxPrimitive.Root>\n\n/**\n * A choice that takes effect when the form is submitted.\n *\n * Supports the indeterminate state (`checked=\"indeterminate\"`), which is what a\n * \"select all\" header needs when only some rows are selected — a plain\n * unchecked box there tells the reader the opposite of the truth.\n *\n * Pair with `Field`, or wrap it in a `<label>` at the call site so the words\n * beside it are part of the click target.\n *\n * @example\n * <label className=\"flex items-center gap-2.5 text-sm\">\n * <Checkbox defaultChecked /> Subscribe to updates\n * </label>\n */\nexport function Checkbox({ className, ...props }: CheckboxProps) {\n return (\n <CheckboxPrimitive.Root\n className={cn(\n 'inline-flex size-[18px] shrink-0 items-center justify-center rounded-(--radius-xs) border border-(--rule-2) bg-(--paper) text-(--accent-foreground) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) data-[state=checked]:bg-(--accent) data-[state=indeterminate]:border-(--accent) data-[state=indeterminate]:bg-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"flex items-center justify-center text-current\">\n {props.checked === 'indeterminate' ? (\n <Minus className=\"size-3\" strokeWidth={3} aria-hidden />\n ) : (\n <Check className=\"size-3\" strokeWidth={3} aria-hidden />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n}\n\nexport default Checkbox\n"],"mappings":";AAmCU;AAjCV,YAAY,uBAAuB;AACnC,SAAS,OAAO,aAAa;AAE7B,SAAS,UAAU;AAmBZ,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAkB;AAC/D,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,kBAAkB,WAAlB,EAA4B,WAAU,iDACpC,gBAAM,YAAY,kBACjB,oBAAC,SAAM,WAAU,UAAS,aAAa,GAAG,eAAW,MAAC,IAEtD,oBAAC,SAAM,WAAU,UAAS,aAAa,GAAG,eAAW,MAAC,GAE1D;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
@@ -4,6 +4,20 @@ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
4
4
 
5
5
  /** Radix Collapsible root, as a typed passthrough. */
6
6
  declare const Collapsible: react.ForwardRefExoticComponent<CollapsiblePrimitive.CollapsibleProps & react.RefAttributes<HTMLDivElement>>;
7
+ /**
8
+ * The trigger and the panel, unstyled, for a disclosure that needs its own
9
+ * layout — a sidebar group whose header carries a count and a chevron on
10
+ * opposite sides, say. `CollapsibleSection` is the composed version and is what
11
+ * most call sites want; these two exist so the ones that do not have to reach
12
+ * for Radix directly and re-derive the keyboard and `aria-expanded` wiring.
13
+ *
14
+ * The panel animates on `--radix-collapsible-content-height`, which Radix
15
+ * measures — so it opens to its real height rather than to a guessed
16
+ * `max-height`, which is what makes a long group and a short one take the same
17
+ * time instead of the long one appearing to stall.
18
+ */
19
+ declare const CollapsibleTrigger: react.ForwardRefExoticComponent<CollapsiblePrimitive.CollapsibleTriggerProps & react.RefAttributes<HTMLButtonElement>>;
20
+ declare function CollapsibleContent({ className, ...props }: ComponentProps<typeof CollapsiblePrimitive.Content>): react.JSX.Element;
7
21
  interface CollapsibleSectionProps extends Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'> {
8
22
  /** What the trigger says. */
9
23
  title: ReactNode;
@@ -28,4 +42,4 @@ interface CollapsibleSectionProps extends Omit<ComponentProps<typeof Collapsible
28
42
  */
29
43
  declare function CollapsibleSection({ title, children, className, ...props }: CollapsibleSectionProps): react.JSX.Element;
30
44
 
31
- export { Collapsible, CollapsibleSection, type CollapsibleSectionProps, Collapsible as default };
45
+ export { Collapsible, CollapsibleContent, CollapsibleSection, type CollapsibleSectionProps, CollapsibleTrigger, Collapsible as default };
@@ -4,6 +4,22 @@ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4
4
  import { ChevronDown } from "lucide-react";
5
5
  import { cn } from "../../lib/cn.js";
6
6
  const Collapsible = CollapsiblePrimitive.Root;
7
+ const CollapsibleTrigger = CollapsiblePrimitive.Trigger;
8
+ function CollapsibleContent({
9
+ className,
10
+ ...props
11
+ }) {
12
+ return /* @__PURE__ */ jsx(
13
+ CollapsiblePrimitive.Content,
14
+ {
15
+ className: cn(
16
+ "overflow-hidden data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)] motion-reduce:animate-none",
17
+ className
18
+ ),
19
+ ...props
20
+ }
21
+ );
22
+ }
7
23
  function CollapsibleSection({
8
24
  title,
9
25
  children,
@@ -29,7 +45,9 @@ function CollapsibleSection({
29
45
  var Collapsible_default = Collapsible;
30
46
  export {
31
47
  Collapsible,
48
+ CollapsibleContent,
32
49
  CollapsibleSection,
50
+ CollapsibleTrigger,
33
51
  Collapsible_default as default
34
52
  };
35
53
  //# sourceMappingURL=Collapsible.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Collapsible/Collapsible.tsx"],"sourcesContent":["'use client'\n\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\nimport { ChevronDown } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Collapsible root, as a typed passthrough. */\nexport const Collapsible = CollapsiblePrimitive.Root\n\nexport interface CollapsibleSectionProps\n extends Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'> {\n /** What the trigger says. */\n title: ReactNode\n children: ReactNode\n}\n\n/**\n * One thing that opens, on its own.\n *\n * The difference from `Accordion` is arithmetic: an accordion is a SET, and a\n * set can coordinate — opening one closes another. A collapsible is one\n * disclosure with nothing to coordinate with. Reaching for an accordion of one\n * gets you a component managing a value you never read.\n *\n * The marker here is a chevron rather than the accordion's plus, and\n * deliberately: this reveals more of the same thing, where an accordion row\n * opens a distinct answer.\n *\n * @example\n * <CollapsibleSection title=\"Advanced settings\">\n * <Field label=\"Retries\"><Input type=\"number\" /></Field>\n * </CollapsibleSection>\n */\nexport function CollapsibleSection({\n title,\n children,\n className,\n ...props\n}: CollapsibleSectionProps) {\n return (\n <CollapsiblePrimitive.Root className={cn('w-full', className)} {...props}>\n <CollapsiblePrimitive.Trigger className=\"group flex w-full items-center justify-between gap-4 py-3 text-start text-sm text-(--ink) transition-colors duration-(--duration-fast) hover:text-(--ink-2)\">\n {title}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]:rotate-180\"\n />\n </CollapsiblePrimitive.Trigger>\n <CollapsiblePrimitive.Content className=\"overflow-hidden text-sm leading-relaxed text-(--ink-2) data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]\">\n <div className=\"pb-3\">{children}</div>\n </CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n )\n}\n\nexport default Collapsible\n"],"mappings":";AA0CM,SAEE,KAFF;AAxCN,YAAY,0BAA0B;AACtC,SAAS,mBAAmB;AAE5B,SAAS,UAAU;AAGZ,MAAM,cAAc,qBAAqB;AA0BzC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA4B;AAC1B,SACE,qBAAC,qBAAqB,MAArB,EAA0B,WAAW,GAAG,UAAU,SAAS,GAAI,GAAG,OACjE;AAAA,yBAAC,qBAAqB,SAArB,EAA6B,WAAU,+JACrC;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,aAAa;AAAA,UACb,eAAW;AAAA,UACX,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IACA,oBAAC,qBAAqB,SAArB,EAA6B,WAAU,8NACtC,8BAAC,SAAI,WAAU,QAAQ,UAAS,GAClC;AAAA,KACF;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Collapsible/Collapsible.tsx"],"sourcesContent":["'use client'\n\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\nimport { ChevronDown } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Collapsible root, as a typed passthrough. */\nexport const Collapsible = CollapsiblePrimitive.Root\n\n/**\n * The trigger and the panel, unstyled, for a disclosure that needs its own\n * layout — a sidebar group whose header carries a count and a chevron on\n * opposite sides, say. `CollapsibleSection` is the composed version and is what\n * most call sites want; these two exist so the ones that do not have to reach\n * for Radix directly and re-derive the keyboard and `aria-expanded` wiring.\n *\n * The panel animates on `--radix-collapsible-content-height`, which Radix\n * measures — so it opens to its real height rather than to a guessed\n * `max-height`, which is what makes a long group and a short one take the same\n * time instead of the long one appearing to stall.\n */\nexport const CollapsibleTrigger = CollapsiblePrimitive.Trigger\n\nexport function CollapsibleContent({\n className,\n ...props\n}: ComponentProps<typeof CollapsiblePrimitive.Content>) {\n return (\n <CollapsiblePrimitive.Content\n className={cn(\n 'overflow-hidden data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)] motion-reduce:animate-none',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CollapsibleSectionProps\n extends Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'> {\n /** What the trigger says. */\n title: ReactNode\n children: ReactNode\n}\n\n/**\n * One thing that opens, on its own.\n *\n * The difference from `Accordion` is arithmetic: an accordion is a SET, and a\n * set can coordinate — opening one closes another. A collapsible is one\n * disclosure with nothing to coordinate with. Reaching for an accordion of one\n * gets you a component managing a value you never read.\n *\n * The marker here is a chevron rather than the accordion's plus, and\n * deliberately: this reveals more of the same thing, where an accordion row\n * opens a distinct answer.\n *\n * @example\n * <CollapsibleSection title=\"Advanced settings\">\n * <Field label=\"Retries\"><Input type=\"number\" /></Field>\n * </CollapsibleSection>\n */\nexport function CollapsibleSection({\n title,\n children,\n className,\n ...props\n}: CollapsibleSectionProps) {\n return (\n <CollapsiblePrimitive.Root className={cn('w-full', className)} {...props}>\n <CollapsiblePrimitive.Trigger className=\"group flex w-full items-center justify-between gap-4 py-3 text-start text-sm text-(--ink) transition-colors duration-(--duration-fast) hover:text-(--ink-2)\">\n {title}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]:rotate-180\"\n />\n </CollapsiblePrimitive.Trigger>\n <CollapsiblePrimitive.Content className=\"overflow-hidden text-sm leading-relaxed text-(--ink-2) data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]\">\n <div className=\"pb-3\">{children}</div>\n </CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n )\n}\n\nexport default Collapsible\n"],"mappings":";AA6BI,cA0CE,YA1CF;AA3BJ,YAAY,0BAA0B;AACtC,SAAS,mBAAmB;AAE5B,SAAS,UAAU;AAGZ,MAAM,cAAc,qBAAqB;AAczC,MAAM,qBAAqB,qBAAqB;AAEhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AA0BO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA4B;AAC1B,SACE,qBAAC,qBAAqB,MAArB,EAA0B,WAAW,GAAG,UAAU,SAAS,GAAI,GAAG,OACjE;AAAA,yBAAC,qBAAqB,SAArB,EAA6B,WAAU,+JACrC;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,aAAa;AAAA,UACb,eAAW;AAAA,UACX,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IACA,oBAAC,qBAAqB,SAArB,EAA6B,WAAU,8NACtC,8BAAC,SAAI,WAAU,QAAQ,UAAS,GAClC;AAAA,KACF;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
@@ -82,7 +82,7 @@ function Combobox(props) {
82
82
  event.stopPropagation();
83
83
  commit([]);
84
84
  },
85
- className: "grid size-5 cursor-pointer place-items-center rounded-full text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)",
85
+ className: "grid size-5 cursor-pointer place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)",
86
86
  children: /* @__PURE__ */ jsx(X, { size: 12, strokeWidth: 2, "aria-hidden": true })
87
87
  }
88
88
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["'use client'\n\nimport { Check, ChevronsUpDown, X } from 'lucide-react'\nimport { useState } from 'react'\nimport { cn } from '../../lib/cn'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../Command/Command'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport interface ComboboxOption {\n value: string\n label: string\n /** Extra text matched by the filter but not shown as the label. */\n keywords?: string[]\n disabled?: boolean\n}\n\ninterface CommonProps {\n options: ComboboxOption[]\n /** Names the control. Required — the trigger's text is a value, not a label. */\n label: string\n /** Shown on the trigger when nothing is chosen. */\n placeholder?: string\n /** Placeholder inside the filter field. */\n searchPlaceholder?: string\n /** Shown when the filter matches nothing. Say what WOULD match. */\n emptyMessage?: string\n disabled?: boolean\n className?: string\n}\n\ninterface SingleProps extends CommonProps {\n multiple?: false\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\ninterface MultipleProps extends CommonProps {\n /**\n * Several at once. The trigger then summarises the choice rather than naming\n * it — \"3 selected\" past a threshold, because a trigger that grows with its\n * value reflows the form every time someone picks another one.\n */\n multiple: true\n value?: string[]\n defaultValue?: string[]\n onValueChange?: (value: string[]) => void\n}\n\nexport type ComboboxProps = SingleProps | MultipleProps\n\n/** How many labels the trigger prints before it starts counting instead. */\nconst SUMMARISE_AFTER = 2\n\n/**\n * A select you can type into, choosing one or several.\n *\n * The line against `Select` is length, and it is not a matter of taste: a\n * styled select is better up to roughly a dozen options, because a list nobody\n * can filter is faster to scan than one they have to think about. Past that,\n * this is the right answer.\n *\n * Filtering, the highlighted row and the arrow keys come from cmdk, which\n * implements the ARIA combobox pattern properly: the highlight moves through\n * `aria-activedescendant` while focus stays in the input. Hand-rolled comboboxes\n * move focus into the list instead, and the typed text stops being editable.\n *\n * Controlled or uncontrolled, like every other form control here.\n *\n * @example\n * <Combobox label=\"Framework\" options={FRAMEWORKS} placeholder=\"Pick one\" />\n * @example\n * <Combobox multiple label=\"Tags\" options={TAGS} />\n */\nexport function Combobox(props: ComboboxProps) {\n const {\n options,\n label,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyMessage = 'Nothing matches.',\n disabled = false,\n className,\n } = props\n const multiple = props.multiple === true\n\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<string[]>(() => {\n if (props.multiple === true) return props.defaultValue ?? []\n return props.defaultValue ? [props.defaultValue] : []\n })\n\n const controlled =\n props.multiple === true\n ? props.value\n : props.value === undefined\n ? undefined\n : props.value === ''\n ? []\n : [props.value]\n const current = controlled ?? uncontrolled\n\n const commit = (next: string[]) => {\n if (controlled === undefined) setUncontrolled(next)\n if (props.multiple === true) props.onValueChange?.(next)\n else props.onValueChange?.(next[0] ?? '')\n }\n\n const choose = (option: string) => {\n if (multiple) {\n // Toggling, and the panel stays open — picking three things should not\n // cost three round trips through the trigger.\n commit(current.includes(option) ? current.filter((v) => v !== option) : [...current, option])\n return\n }\n // Choosing the selected option again clears it, which is what a reader\n // expects from a control whose value is optional.\n commit(current.includes(option) ? [] : [option])\n setOpen(false)\n }\n\n const chosen = options.filter((option) => current.includes(option.value))\n const summary =\n chosen.length === 0\n ? placeholder\n : chosen.length <= SUMMARISE_AFTER\n ? chosen.map((option) => option.label).join(', ')\n : `${chosen.length} selected`\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n // A combobox trigger is a button that OPENS a listbox; the roles for\n // the list itself belong to cmdk inside the panel.\n role=\"combobox\"\n aria-expanded={open}\n aria-label={label}\n disabled={disabled}\n className={cn(\n 'group flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n chosen.length > 0 ? 'text-(--ink)' : 'text-(--ink-3-aa)',\n className,\n )}\n >\n <span className=\"truncate\">{summary}</span>\n <span className=\"flex shrink-0 items-center gap-1\">\n {multiple && chosen.length > 0 && (\n // A `<span role=\"button\">` rather than a nested `<button>`, which\n // is invalid inside the trigger and which browsers reparent — the\n // clear control then falls outside the field entirely.\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Clear ${label}`}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n className=\"grid size-5 cursor-pointer place-items-center rounded-full text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={12} strokeWidth={2} aria-hidden />\n </span>\n )}\n <ChevronsUpDown size={14} strokeWidth={1.5} aria-hidden className=\"text-(--ink-3-aa)\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n label={label}\n // `overflow-hidden` so the panel's own corners clip the square-cornered\n // command list inside it; without it the list's edges poked through the\n // radius and the two bottom corners looked chipped.\n className=\"w-(--radix-popover-trigger-width) overflow-hidden p-0\"\n align=\"start\"\n >\n <Command label={`${label}: ${searchPlaceholder}`} className=\"rounded-none border-0\">\n {/* The panel's own name is \"<label>: <placeholder>\", set on Command\n above. cmdk points the input's aria-labelledby at it, and\n aria-labelledby beats aria-label — so naming the input directly\n did nothing, and a screen reader met two comboboxes called \"Tags\",\n one inside the other. */}\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => {\n const selected = current.includes(option.value)\n return (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, ...(option.keywords ?? [])]}\n disabled={option.disabled}\n onSelect={choose}\n aria-selected={selected}\n >\n <span\n className={cn(\n 'grid size-4 shrink-0 place-items-center transition-opacity duration-(--duration-fast)',\n multiple &&\n 'rounded-(--radius-sm) border border-(--rule-2) text-(--accent-foreground) data-[on=true]:border-(--accent) data-[on=true]:bg-(--accent)',\n )}\n data-on={selected}\n >\n <Check\n size={12}\n strokeWidth={2.5}\n aria-hidden\n className={selected ? 'opacity-100' : 'opacity-0'}\n />\n </span>\n {option.label}\n </CommandItem>\n )\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default Combobox\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["'use client'\n\nimport { Check, ChevronsUpDown, X } from 'lucide-react'\nimport { useState } from 'react'\nimport { cn } from '../../lib/cn'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../Command/Command'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport interface ComboboxOption {\n value: string\n label: string\n /** Extra text matched by the filter but not shown as the label. */\n keywords?: string[]\n disabled?: boolean\n}\n\ninterface CommonProps {\n options: ComboboxOption[]\n /** Names the control. Required — the trigger's text is a value, not a label. */\n label: string\n /** Shown on the trigger when nothing is chosen. */\n placeholder?: string\n /** Placeholder inside the filter field. */\n searchPlaceholder?: string\n /** Shown when the filter matches nothing. Say what WOULD match. */\n emptyMessage?: string\n disabled?: boolean\n className?: string\n}\n\ninterface SingleProps extends CommonProps {\n multiple?: false\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\ninterface MultipleProps extends CommonProps {\n /**\n * Several at once. The trigger then summarises the choice rather than naming\n * it — \"3 selected\" past a threshold, because a trigger that grows with its\n * value reflows the form every time someone picks another one.\n */\n multiple: true\n value?: string[]\n defaultValue?: string[]\n onValueChange?: (value: string[]) => void\n}\n\nexport type ComboboxProps = SingleProps | MultipleProps\n\n/** How many labels the trigger prints before it starts counting instead. */\nconst SUMMARISE_AFTER = 2\n\n/**\n * A select you can type into, choosing one or several.\n *\n * The line against `Select` is length, and it is not a matter of taste: a\n * styled select is better up to roughly a dozen options, because a list nobody\n * can filter is faster to scan than one they have to think about. Past that,\n * this is the right answer.\n *\n * Filtering, the highlighted row and the arrow keys come from cmdk, which\n * implements the ARIA combobox pattern properly: the highlight moves through\n * `aria-activedescendant` while focus stays in the input. Hand-rolled comboboxes\n * move focus into the list instead, and the typed text stops being editable.\n *\n * Controlled or uncontrolled, like every other form control here.\n *\n * @example\n * <Combobox label=\"Framework\" options={FRAMEWORKS} placeholder=\"Pick one\" />\n * @example\n * <Combobox multiple label=\"Tags\" options={TAGS} />\n */\nexport function Combobox(props: ComboboxProps) {\n const {\n options,\n label,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyMessage = 'Nothing matches.',\n disabled = false,\n className,\n } = props\n const multiple = props.multiple === true\n\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<string[]>(() => {\n if (props.multiple === true) return props.defaultValue ?? []\n return props.defaultValue ? [props.defaultValue] : []\n })\n\n const controlled =\n props.multiple === true\n ? props.value\n : props.value === undefined\n ? undefined\n : props.value === ''\n ? []\n : [props.value]\n const current = controlled ?? uncontrolled\n\n const commit = (next: string[]) => {\n if (controlled === undefined) setUncontrolled(next)\n if (props.multiple === true) props.onValueChange?.(next)\n else props.onValueChange?.(next[0] ?? '')\n }\n\n const choose = (option: string) => {\n if (multiple) {\n // Toggling, and the panel stays open — picking three things should not\n // cost three round trips through the trigger.\n commit(current.includes(option) ? current.filter((v) => v !== option) : [...current, option])\n return\n }\n // Choosing the selected option again clears it, which is what a reader\n // expects from a control whose value is optional.\n commit(current.includes(option) ? [] : [option])\n setOpen(false)\n }\n\n const chosen = options.filter((option) => current.includes(option.value))\n const summary =\n chosen.length === 0\n ? placeholder\n : chosen.length <= SUMMARISE_AFTER\n ? chosen.map((option) => option.label).join(', ')\n : `${chosen.length} selected`\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n // A combobox trigger is a button that OPENS a listbox; the roles for\n // the list itself belong to cmdk inside the panel.\n role=\"combobox\"\n aria-expanded={open}\n aria-label={label}\n disabled={disabled}\n className={cn(\n 'group flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n chosen.length > 0 ? 'text-(--ink)' : 'text-(--ink-3-aa)',\n className,\n )}\n >\n <span className=\"truncate\">{summary}</span>\n <span className=\"flex shrink-0 items-center gap-1\">\n {multiple && chosen.length > 0 && (\n // A `<span role=\"button\">` rather than a nested `<button>`, which\n // is invalid inside the trigger and which browsers reparent — the\n // clear control then falls outside the field entirely.\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Clear ${label}`}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n className=\"grid size-5 cursor-pointer place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={12} strokeWidth={2} aria-hidden />\n </span>\n )}\n <ChevronsUpDown size={14} strokeWidth={1.5} aria-hidden className=\"text-(--ink-3-aa)\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n label={label}\n // `overflow-hidden` so the panel's own corners clip the square-cornered\n // command list inside it; without it the list's edges poked through the\n // radius and the two bottom corners looked chipped.\n className=\"w-(--radix-popover-trigger-width) overflow-hidden p-0\"\n align=\"start\"\n >\n <Command label={`${label}: ${searchPlaceholder}`} className=\"rounded-none border-0\">\n {/* The panel's own name is \"<label>: <placeholder>\", set on Command\n above. cmdk points the input's aria-labelledby at it, and\n aria-labelledby beats aria-label — so naming the input directly\n did nothing, and a screen reader met two comboboxes called \"Tags\",\n one inside the other. */}\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => {\n const selected = current.includes(option.value)\n return (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, ...(option.keywords ?? [])]}\n disabled={option.disabled}\n onSelect={choose}\n aria-selected={selected}\n >\n <span\n className={cn(\n 'grid size-4 shrink-0 place-items-center transition-opacity duration-(--duration-fast)',\n multiple &&\n 'rounded-(--radius-sm) border border-(--rule-2) text-(--accent-foreground) data-[on=true]:border-(--accent) data-[on=true]:bg-(--accent)',\n )}\n data-on={selected}\n >\n <Check\n size={12}\n strokeWidth={2.5}\n aria-hidden\n className={selected ? 'opacity-100' : 'opacity-0'}\n />\n </span>\n {option.label}\n </CommandItem>\n )\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default Combobox\n"],"mappings":"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","names":[]}