@burtson-labs/ui 0.12.3 → 0.13.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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/components/chat-history.js +3 -2
  3. package/dist/components/chat-history.js.map +1 -1
  4. package/dist/components/checkbox-card.d.ts +25 -0
  5. package/dist/components/checkbox-card.js +65 -0
  6. package/dist/components/checkbox-card.js.map +1 -0
  7. package/dist/components/combobox.js +1 -1
  8. package/dist/components/command.js +4 -4
  9. package/dist/components/command.js.map +1 -1
  10. package/dist/components/composer.js +2 -2
  11. package/dist/components/composer.js.map +1 -1
  12. package/dist/components/context-menu.js +1 -1
  13. package/dist/components/context-menu.js.map +1 -1
  14. package/dist/components/data-table.d.ts +53 -3
  15. package/dist/components/data-table.js +346 -113
  16. package/dist/components/data-table.js.map +1 -1
  17. package/dist/components/dialog.d.ts +6 -1
  18. package/dist/components/dialog.js +21 -10
  19. package/dist/components/dialog.js.map +1 -1
  20. package/dist/components/dropdown-menu.js +1 -1
  21. package/dist/components/dropdown-menu.js.map +1 -1
  22. package/dist/components/input.d.ts +15 -1
  23. package/dist/components/input.js +16 -2
  24. package/dist/components/input.js.map +1 -1
  25. package/dist/components/menubar.js +1 -1
  26. package/dist/components/menubar.js.map +1 -1
  27. package/dist/components/message-actions.js +1 -1
  28. package/dist/components/message-actions.js.map +1 -1
  29. package/dist/components/pagination.d.ts +104 -3
  30. package/dist/components/pagination.js +244 -58
  31. package/dist/components/pagination.js.map +1 -1
  32. package/dist/components/popover.d.ts +7 -2
  33. package/dist/components/popover.js +7 -2
  34. package/dist/components/popover.js.map +1 -1
  35. package/dist/components/secret-input.js +1 -1
  36. package/dist/components/select.js +1 -1
  37. package/dist/components/select.js.map +1 -1
  38. package/dist/components/sheet.d.ts +6 -1
  39. package/dist/components/sheet.js +2 -2
  40. package/dist/components/sheet.js.map +1 -1
  41. package/dist/components/steps.d.ts +15 -2
  42. package/dist/components/steps.js +68 -22
  43. package/dist/components/steps.js.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +5 -4
  46. package/dist/mui/index.js +41 -7
  47. package/dist/mui/index.js.map +1 -1
  48. package/dist/styles.css +1 -1
  49. package/package.json +1 -1
@@ -4,15 +4,116 @@ import * as React from 'react';
4
4
  * either side of the current one, and 'ellipsis' for the gaps.
5
5
  */
6
6
  export declare function paginationRange(page: number, pageCount: number, siblings?: number): (number | 'ellipsis')[];
7
+ /** The page sizes offered by default: 25, 50 or 100 rows. */
8
+ export declare const DEFAULT_PAGE_SIZES: readonly number[];
9
+ export interface PageWindow {
10
+ /** 1-based, clamped to the pages that exist (1 when there are no rows). */
11
+ page: number;
12
+ /** At least 1, so "Page 1 of 1" reads sensibly for an empty list. */
13
+ pageCount: number;
14
+ pageSize: number;
15
+ total: number;
16
+ /** 1-based index of the first row on this page; 0 when there are none. */
17
+ from: number;
18
+ /** 1-based index of the last row on this page; 0 when there are none. */
19
+ to: number;
20
+ }
21
+ /** Pages needed for `total` rows, never less than 1. */
22
+ export declare function pageCountFor(total: number, pageSize: number): number;
23
+ /** A requested page clamped to 1…pageCount. Anything unreadable ("abc", NaN) is page 1. */
24
+ export declare function clampPage(page: unknown, pageCount: number): number;
25
+ /** Where page `page` of `total` rows starts and ends, for server-side paging. */
26
+ export declare function pageWindow({ page, pageSize, total, }: {
27
+ page: unknown;
28
+ pageSize: number;
29
+ total: number;
30
+ }): PageWindow;
31
+ /** One page of `rows`, with the numbers for the page controls. */
32
+ export declare function paginate<T>(rows: readonly T[], page: unknown, pageSize: number): PageWindow & {
33
+ rows: T[];
34
+ };
35
+ /**
36
+ * "1–25 of 312", "7 of 7" when everything fits, "0 of 0" when empty. Numbers
37
+ * use the locale's grouping ("1,025"); pass `locale` to pin it.
38
+ */
39
+ export declare function paginationSummary({ from, to, total }: Pick<PageWindow, 'from' | 'to' | 'total'>, locale?: string): string;
40
+ export interface UsePaginationOptions {
41
+ /** Controlled page (e.g. read from the URL). Leave out to let the hook keep it. */
42
+ page?: number;
43
+ /** Starting page when uncontrolled. */
44
+ defaultPage?: number;
45
+ onPageChange?: (page: number) => void;
46
+ /** Controlled page size. */
47
+ pageSize?: number;
48
+ /** Starting page size when uncontrolled; the first option by default. */
49
+ defaultPageSize?: number;
50
+ onPageSizeChange?: (pageSize: number) => void;
51
+ /** Sizes offered by the page-size menu. */
52
+ pageSizeOptions?: readonly number[];
53
+ /**
54
+ * Whatever decides which rows are listed (search text, a filter, a tab).
55
+ * When it changes, paging goes back to page 1. The first render never
56
+ * resets, so a link to page 3 stays on page 3.
57
+ */
58
+ resetKey?: unknown;
59
+ /** Remembers the chosen page size in localStorage under this key. */
60
+ storageKey?: string;
61
+ /**
62
+ * The full row count when the server pages: `rows` is then already the
63
+ * current page and is not sliced.
64
+ */
65
+ total?: number;
66
+ /** Locale for the summary's numbers. */
67
+ locale?: string;
68
+ }
69
+ export interface UsePaginationResult<T> extends PageWindow {
70
+ /** The rows to render on this page. */
71
+ rows: T[];
72
+ pageSizeOptions: readonly number[];
73
+ /** "1–25 of 312". */
74
+ summary: string;
75
+ /** More rows than the smallest page size: worth showing controls for. */
76
+ hasPages: boolean;
77
+ setPage: (page: number) => void;
78
+ /** Changes the size and goes back to page 1. */
79
+ setPageSize: (pageSize: number) => void;
80
+ /** Spread onto Pagination (or pass to DataTable's paging props). */
81
+ paginationProps: Pick<PaginationProps, 'page' | 'pageCount' | 'onPageChange' | 'pageSize' | 'pageSizeOptions' | 'onPageSizeChange'> & {
82
+ summary: string;
83
+ };
84
+ }
85
+ /**
86
+ * Headless paging for a list or table: slices `rows`, clamps the page,
87
+ * returns to page 1 when `resetKey` changes, and optionally remembers the
88
+ * page size. Controlled `page` / `pageSize` let an app keep them in the URL.
89
+ */
90
+ export declare function usePagination<T>(rows: readonly T[], options?: UsePaginationOptions): UsePaginationResult<T>;
7
91
  export interface PaginationProps extends Omit<React.ComponentProps<'nav'>, 'onChange'> {
8
92
  /** 1-based current page. */
9
93
  page: number;
10
94
  pageCount: number;
11
95
  onPageChange: (page: number) => void;
12
96
  siblings?: number;
13
- /** Optional summary on the left, e.g. "1–25 of 312". */
97
+ /** Summary on the left, e.g. "1–25 of 312". Worked out from `total` when left out. */
14
98
  summary?: React.ReactNode;
99
+ /** Total rows: with `pageSize`, gives the default summary. */
100
+ total?: number;
101
+ /** Rows per page, shown in the page-size menu. */
102
+ pageSize?: number;
103
+ /** Sizes in the page-size menu (25, 50, 100 by default). */
104
+ pageSizeOptions?: readonly number[];
105
+ /** Adds a rows-per-page menu. */
106
+ onPageSizeChange?: (pageSize: number) => void;
107
+ /** The page-size menu's accessible name. */
108
+ pageSizeLabel?: string;
109
+ /** An option's text, e.g. "25 per page". */
110
+ formatPageSize?: (size: number) => string;
15
111
  }
16
- /** Page controls for a table or list. Buttons are real buttons, keyboard-reachable. */
17
- declare function Pagination({ page: requestedPage, pageCount: requestedCount, onPageChange, siblings, summary, className, ...props }: PaginationProps): React.JSX.Element;
112
+ /**
113
+ * Page controls for a table or list: a summary, the pages, and optionally a
114
+ * rows-per-page menu. Below 640px it becomes "Page 2 of 13" between large
115
+ * previous and next buttons. Buttons are real buttons, keyboard-reachable,
116
+ * and 44px on touch screens.
117
+ */
118
+ declare function Pagination({ page: requestedPage, pageCount: requestedCount, onPageChange, siblings, summary, total, pageSize, pageSizeOptions, onPageSizeChange, pageSizeLabel, formatPageSize, className, ...props }: PaginationProps): React.JSX.Element;
18
119
  export { Pagination };
@@ -1,6 +1,7 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { Button } from "./button.js";
3
- import "react";
3
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select.js";
4
+ import * as React from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
  import ChevronRight from "@burtson-labs/icons/react/chevron-right";
6
7
  import ChevronLeft from "@burtson-labs/icons/react/chevron-left";
@@ -25,74 +26,259 @@ function paginationRange(page, pageCount, siblings = 1) {
25
26
  out.push(pageCount);
26
27
  return out;
27
28
  }
28
- /** Page controls for a table or list. Buttons are real buttons, keyboard-reachable. */
29
- function Pagination({ page: requestedPage, pageCount: requestedCount, onPageChange, siblings = 1, summary, className, ...props }) {
29
+ /** The page sizes offered by default: 25, 50 or 100 rows. */
30
+ var DEFAULT_PAGE_SIZES = [
31
+ 25,
32
+ 50,
33
+ 100
34
+ ];
35
+ /** Pages needed for `total` rows, never less than 1. */
36
+ function pageCountFor(total, pageSize) {
37
+ if (!Number.isFinite(total) || total <= 0) return 1;
38
+ return Math.max(1, Math.ceil(total / (Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : 1)));
39
+ }
40
+ /** A requested page clamped to 1…pageCount. Anything unreadable ("abc", NaN) is page 1. */
41
+ function clampPage(page, pageCount) {
42
+ const n = typeof page === "number" ? page : Number(page);
43
+ if (!Number.isFinite(n)) return 1;
44
+ return Math.min(Math.max(1, Math.floor(n)), Math.max(1, Math.floor(pageCount) || 1));
45
+ }
46
+ /** Where page `page` of `total` rows starts and ends, for server-side paging. */
47
+ function pageWindow({ page, pageSize, total }) {
48
+ const size = Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : 1;
49
+ const safeTotal = Number.isFinite(total) ? Math.max(0, Math.floor(total)) : 0;
50
+ const pageCount = pageCountFor(safeTotal, size);
51
+ const p = clampPage(page, pageCount);
52
+ const start = (p - 1) * size;
53
+ const to = Math.min(start + size, safeTotal);
54
+ return {
55
+ page: p,
56
+ pageCount,
57
+ pageSize: size,
58
+ total: safeTotal,
59
+ from: safeTotal ? start + 1 : 0,
60
+ to: safeTotal ? to : 0
61
+ };
62
+ }
63
+ /** One page of `rows`, with the numbers for the page controls. */
64
+ function paginate(rows, page, pageSize) {
65
+ const w = pageWindow({
66
+ page,
67
+ pageSize,
68
+ total: rows.length
69
+ });
70
+ return {
71
+ ...w,
72
+ rows: rows.slice(w.from ? w.from - 1 : 0, w.to)
73
+ };
74
+ }
75
+ /**
76
+ * "1–25 of 312", "7 of 7" when everything fits, "0 of 0" when empty. Numbers
77
+ * use the locale's grouping ("1,025"); pass `locale` to pin it.
78
+ */
79
+ function paginationSummary({ from, to, total }, locale) {
80
+ const fmt = new Intl.NumberFormat(locale);
81
+ if (!total) return "0 of 0";
82
+ if (from === 1 && to === total) return `${fmt.format(total)} of ${fmt.format(total)}`;
83
+ return `${fmt.format(from)}–${fmt.format(to)} of ${fmt.format(total)}`;
84
+ }
85
+ var storedSizeKey = (key) => `bl-page-size:${key}`;
86
+ function readStoredSize(key, options) {
87
+ if (!key) return void 0;
88
+ try {
89
+ const n = Number(globalThis.localStorage?.getItem(storedSizeKey(key)));
90
+ return options.includes(n) ? n : void 0;
91
+ } catch {
92
+ return;
93
+ }
94
+ }
95
+ function writeStoredSize(key, size) {
96
+ if (!key) return;
97
+ try {
98
+ globalThis.localStorage?.setItem(storedSizeKey(key), String(size));
99
+ } catch {}
100
+ }
101
+ /**
102
+ * Headless paging for a list or table: slices `rows`, clamps the page,
103
+ * returns to page 1 when `resetKey` changes, and optionally remembers the
104
+ * page size. Controlled `page` / `pageSize` let an app keep them in the URL.
105
+ */
106
+ function usePagination(rows, options = {}) {
107
+ const { page: pageProp, defaultPage = 1, onPageChange, pageSize: pageSizeProp, defaultPageSize, onPageSizeChange, pageSizeOptions = DEFAULT_PAGE_SIZES, resetKey, storageKey, total: serverTotal, locale } = options;
108
+ const [pageState, setPageState] = React.useState(defaultPage);
109
+ const [sizeState, setSizeState] = React.useState(() => readStoredSize(storageKey, pageSizeOptions) ?? defaultPageSize ?? pageSizeOptions[0] ?? 25);
110
+ const page = pageProp ?? pageState;
111
+ const pageSize = pageSizeProp ?? sizeState;
112
+ const latest = React.useRef({
113
+ onPageChange,
114
+ onPageSizeChange,
115
+ pageProp,
116
+ storageKey
117
+ });
118
+ React.useEffect(() => {
119
+ latest.current = {
120
+ onPageChange,
121
+ onPageSizeChange,
122
+ pageProp,
123
+ storageKey
124
+ };
125
+ });
126
+ const setPage = React.useCallback((next) => {
127
+ setPageState(next);
128
+ latest.current.onPageChange?.(next);
129
+ }, []);
130
+ const setPageSize = React.useCallback((next) => {
131
+ writeStoredSize(latest.current.storageKey, next);
132
+ setSizeState(next);
133
+ setPageState(1);
134
+ latest.current.onPageSizeChange?.(next);
135
+ latest.current.onPageChange?.(1);
136
+ }, []);
137
+ const [lastKey, setLastKey] = React.useState(resetKey);
138
+ if (!Object.is(lastKey, resetKey)) {
139
+ setLastKey(resetKey);
140
+ setPageState(1);
141
+ }
142
+ const committedKey = React.useRef(resetKey);
143
+ React.useEffect(() => {
144
+ if (Object.is(committedKey.current, resetKey)) return;
145
+ committedKey.current = resetKey;
146
+ const { pageProp: controlled, onPageChange: report } = latest.current;
147
+ if (controlled !== void 0 && controlled !== 1) report?.(1);
148
+ }, [resetKey]);
149
+ const w = serverTotal === void 0 ? paginate(rows, page, pageSize) : {
150
+ ...pageWindow({
151
+ page,
152
+ pageSize,
153
+ total: serverTotal
154
+ }),
155
+ rows: [...rows]
156
+ };
157
+ const summary = paginationSummary(w, locale);
158
+ const hasPages = w.total > Math.min(...pageSizeOptions, w.pageSize);
159
+ return {
160
+ ...w,
161
+ pageSizeOptions,
162
+ summary,
163
+ hasPages,
164
+ setPage,
165
+ setPageSize,
166
+ paginationProps: {
167
+ page: w.page,
168
+ pageCount: w.pageCount,
169
+ onPageChange: setPage,
170
+ pageSize: w.pageSize,
171
+ pageSizeOptions,
172
+ onPageSizeChange: setPageSize,
173
+ summary
174
+ }
175
+ };
176
+ }
177
+ /**
178
+ * Page controls for a table or list: a summary, the pages, and optionally a
179
+ * rows-per-page menu. Below 640px it becomes "Page 2 of 13" between large
180
+ * previous and next buttons. Buttons are real buttons, keyboard-reachable,
181
+ * and 44px on touch screens.
182
+ */
183
+ function Pagination({ page: requestedPage, pageCount: requestedCount, onPageChange, siblings = 1, summary, total, pageSize, pageSizeOptions = DEFAULT_PAGE_SIZES, onPageSizeChange, pageSizeLabel = "Rows per page", formatPageSize = (size) => `${size} per page`, className, ...props }) {
30
184
  const pageCount = Number.isFinite(requestedCount) ? Math.max(0, Math.floor(requestedCount)) : 0;
31
185
  const page = pageCount === 0 ? 0 : Number.isFinite(requestedPage) ? Math.max(1, Math.min(pageCount, Math.floor(requestedPage))) : 1;
32
186
  const pages = paginationRange(page, pageCount, siblings);
33
187
  const go = (p) => onPageChange(Math.min(Math.max(1, p), Math.max(1, pageCount)));
188
+ const shownSummary = summary ?? (total !== void 0 && pageSize ? paginationSummary(pageWindow({
189
+ page: page || 1,
190
+ pageSize,
191
+ total
192
+ })) : null);
193
+ const sizes = pageSize !== void 0 && !pageSizeOptions.includes(pageSize) ? [...pageSizeOptions, pageSize].sort((a, b) => a - b) : pageSizeOptions;
194
+ const position = `Page ${page} of ${pageCount}`;
34
195
  return /* @__PURE__ */ jsxs("nav", {
35
196
  "aria-label": "Pagination",
36
197
  "data-slot": "pagination",
37
- className: cn("flex flex-wrap items-center justify-between gap-3", className),
198
+ className: cn("flex flex-wrap items-center justify-between gap-x-3 gap-y-2", className),
38
199
  ...props,
39
- children: [/* @__PURE__ */ jsx("div", {
40
- className: "text-xs text-muted-foreground tabular-nums",
41
- children: summary
42
- }), /* @__PURE__ */ jsxs("ul", {
43
- className: "flex items-center gap-1",
44
- children: [
45
- /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Button, {
46
- variant: "ghost",
47
- size: "sm",
48
- onClick: () => go(page - 1),
49
- disabled: page <= 1,
50
- "aria-label": "Previous page",
51
- children: [/* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": true }), /* @__PURE__ */ jsx("span", {
52
- className: "hidden sm:inline",
53
- children: "Previous"
54
- })]
55
- }) }),
56
- pages.map((p, i) => p === "ellipsis" ? /* @__PURE__ */ jsx("li", {
57
- "aria-hidden": true,
58
- className: "px-1 text-muted-foreground",
59
- children: "…"
60
- }, `e${i}`) : /* @__PURE__ */ jsx("li", {
61
- className: "hidden sm:block",
62
- children: /* @__PURE__ */ jsx(Button, {
63
- variant: p === page ? "outline" : "ghost",
64
- size: "icon-sm",
65
- onClick: () => go(p),
66
- "aria-current": p === page ? "page" : void 0,
67
- "aria-label": `Page ${p}`,
68
- className: cn("tabular-nums", p === page && "border-brand/40 text-brand"),
69
- children: p
70
- })
71
- }, p)),
72
- /* @__PURE__ */ jsxs("li", {
73
- className: "px-2 text-xs text-muted-foreground tabular-nums sm:hidden",
74
- children: [
75
- page,
76
- " / ",
77
- pageCount
78
- ]
79
- }),
80
- /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Button, {
81
- variant: "ghost",
200
+ children: [
201
+ /* @__PURE__ */ jsx("div", {
202
+ "data-slot": "pagination-summary",
203
+ className: "min-w-0 text-xs text-muted-foreground tabular-nums max-sm:order-2",
204
+ children: shownSummary
205
+ }),
206
+ /* @__PURE__ */ jsx("span", {
207
+ role: "status",
208
+ className: "sr-only",
209
+ children: pageCount > 0 ? position : ""
210
+ }),
211
+ /* @__PURE__ */ jsxs("ul", {
212
+ className: "flex items-center gap-1 max-sm:order-1 max-sm:w-full max-sm:justify-between",
213
+ children: [
214
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Button, {
215
+ variant: "ghost",
216
+ size: "sm",
217
+ onClick: () => go(page - 1),
218
+ disabled: page <= 1,
219
+ "aria-label": "Previous page",
220
+ className: "max-sm:size-11 max-sm:border max-sm:border-border max-sm:px-0",
221
+ children: [/* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": true }), /* @__PURE__ */ jsx("span", {
222
+ className: "hidden sm:inline",
223
+ children: "Previous"
224
+ })]
225
+ }) }),
226
+ pages.map((p, i) => p === "ellipsis" ? /* @__PURE__ */ jsx("li", {
227
+ "aria-hidden": true,
228
+ className: "hidden px-1 text-muted-foreground sm:block",
229
+ children: "…"
230
+ }, `e${i}`) : /* @__PURE__ */ jsx("li", {
231
+ className: "hidden sm:block",
232
+ children: /* @__PURE__ */ jsx(Button, {
233
+ variant: p === page ? "outline" : "ghost",
234
+ size: "icon-sm",
235
+ onClick: () => go(p),
236
+ "aria-current": p === page ? "page" : void 0,
237
+ "aria-label": `Page ${p}`,
238
+ className: cn("tabular-nums", p === page && "border-brand/40 text-brand"),
239
+ children: p
240
+ })
241
+ }, p)),
242
+ /* @__PURE__ */ jsx("li", {
243
+ "aria-hidden": true,
244
+ "data-slot": "pagination-position",
245
+ className: "px-2 text-sm font-medium tabular-nums sm:hidden",
246
+ children: position
247
+ }),
248
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Button, {
249
+ variant: "ghost",
250
+ size: "sm",
251
+ onClick: () => go(page + 1),
252
+ disabled: page >= pageCount,
253
+ "aria-label": "Next page",
254
+ className: "max-sm:size-11 max-sm:border max-sm:border-border max-sm:px-0",
255
+ children: [/* @__PURE__ */ jsx("span", {
256
+ className: "hidden sm:inline",
257
+ children: "Next"
258
+ }), /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true })]
259
+ }) })
260
+ ]
261
+ }),
262
+ onPageSizeChange && /* @__PURE__ */ jsxs(Select, {
263
+ value: pageSize !== void 0 ? String(pageSize) : void 0,
264
+ onValueChange: (v) => onPageSizeChange(Number(v)),
265
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
82
266
  size: "sm",
83
- onClick: () => go(page + 1),
84
- disabled: page >= pageCount,
85
- "aria-label": "Next page",
86
- children: [/* @__PURE__ */ jsx("span", {
87
- className: "hidden sm:inline",
88
- children: "Next"
89
- }), /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true })]
90
- }) })
91
- ]
92
- })]
267
+ "aria-label": pageSizeLabel,
268
+ className: "w-auto gap-1.5 text-xs tabular-nums max-sm:order-3 pointer-coarse:h-11",
269
+ children: /* @__PURE__ */ jsx(SelectValue, {})
270
+ }), /* @__PURE__ */ jsx(SelectContent, {
271
+ align: "end",
272
+ children: sizes.map((s) => /* @__PURE__ */ jsx(SelectItem, {
273
+ value: String(s),
274
+ children: formatPageSize(s)
275
+ }, s))
276
+ })]
277
+ })
278
+ ]
93
279
  });
94
280
  }
95
281
  //#endregion
96
- export { Pagination, paginationRange };
282
+ export { DEFAULT_PAGE_SIZES, Pagination, clampPage, pageCountFor, pageWindow, paginate, paginationRange, paginationSummary, usePagination };
97
283
 
98
284
  //# sourceMappingURL=pagination.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.js","names":[],"sources":["../../src/components/pagination.tsx"],"sourcesContent":["import ChevronLeft from '@burtson-labs/icons/react/chevron-left';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\n/**\n * The page numbers to show: always the first and last page, `siblings` pages\n * either side of the current one, and 'ellipsis' for the gaps.\n */\nexport function paginationRange(\n page: number,\n pageCount: number,\n siblings = 1,\n): (number | 'ellipsis')[] {\n pageCount = Number.isFinite(pageCount) ? Math.max(0, Math.floor(pageCount)) : 0;\n siblings = Number.isFinite(siblings) ? Math.min(5, Math.max(0, Math.floor(siblings))) : 1;\n page = Number.isFinite(page) ? Math.max(1, Math.min(pageCount, Math.floor(page))) : 1;\n if (pageCount <= 0) return [];\n const window = siblings * 2 + 5; // first, last, current, siblings, two gaps\n if (pageCount <= window) return Array.from({ length: pageCount }, (_, i) => i + 1);\n const start = Math.max(2, page - siblings);\n const end = Math.min(pageCount - 1, page + siblings);\n const out: (number | 'ellipsis')[] = [1];\n if (start > 2) out.push('ellipsis');\n for (let p = start; p <= end; p += 1) out.push(p);\n if (end < pageCount - 1) out.push('ellipsis');\n out.push(pageCount);\n return out;\n}\n\nexport interface PaginationProps extends Omit<React.ComponentProps<'nav'>, 'onChange'> {\n /** 1-based current page. */\n page: number;\n pageCount: number;\n onPageChange: (page: number) => void;\n siblings?: number;\n /** Optional summary on the left, e.g. \"1–25 of 312\". */\n summary?: React.ReactNode;\n}\n\n/** Page controls for a table or list. Buttons are real buttons, keyboard-reachable. */\nfunction Pagination({\n page: requestedPage,\n pageCount: requestedCount,\n onPageChange,\n siblings = 1,\n summary,\n className,\n ...props\n}: PaginationProps) {\n const pageCount = Number.isFinite(requestedCount) ? Math.max(0, Math.floor(requestedCount)) : 0;\n const page =\n pageCount === 0\n ? 0\n : Number.isFinite(requestedPage)\n ? Math.max(1, Math.min(pageCount, Math.floor(requestedPage)))\n : 1;\n const pages = paginationRange(page, pageCount, siblings);\n const go = (p: number) => onPageChange(Math.min(Math.max(1, p), Math.max(1, pageCount)));\n return (\n <nav\n aria-label=\"Pagination\"\n data-slot=\"pagination\"\n className={cn('flex flex-wrap items-center justify-between gap-3', className)}\n {...props}\n >\n <div className=\"text-xs text-muted-foreground tabular-nums\">{summary}</div>\n <ul className=\"flex items-center gap-1\">\n <li>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => go(page - 1)}\n disabled={page <= 1}\n aria-label=\"Previous page\"\n >\n <ChevronLeft aria-hidden />\n <span className=\"hidden sm:inline\">Previous</span>\n </Button>\n </li>\n {pages.map((p, i) =>\n p === 'ellipsis' ? (\n <li key={`e${i}`} aria-hidden className=\"px-1 text-muted-foreground\">\n …\n </li>\n ) : (\n <li key={p} className=\"hidden sm:block\">\n <Button\n variant={p === page ? 'outline' : 'ghost'}\n size=\"icon-sm\"\n onClick={() => go(p)}\n aria-current={p === page ? 'page' : undefined}\n aria-label={`Page ${p}`}\n className={cn('tabular-nums', p === page && 'border-brand/40 text-brand')}\n >\n {p}\n </Button>\n </li>\n ),\n )}\n <li className=\"px-2 text-xs text-muted-foreground tabular-nums sm:hidden\">\n {page} / {pageCount}\n </li>\n <li>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => go(page + 1)}\n disabled={page >= pageCount}\n aria-label=\"Next page\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight aria-hidden />\n </Button>\n </li>\n </ul>\n </nav>\n );\n}\n\nexport { Pagination };\n"],"mappings":"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"}
1
+ {"version":3,"file":"pagination.js","names":[],"sources":["../../src/components/pagination.tsx"],"sourcesContent":["import ChevronLeft from '@burtson-labs/icons/react/chevron-left';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';\n\n/**\n * The page numbers to show: always the first and last page, `siblings` pages\n * either side of the current one, and 'ellipsis' for the gaps.\n */\nexport function paginationRange(\n page: number,\n pageCount: number,\n siblings = 1,\n): (number | 'ellipsis')[] {\n pageCount = Number.isFinite(pageCount) ? Math.max(0, Math.floor(pageCount)) : 0;\n siblings = Number.isFinite(siblings) ? Math.min(5, Math.max(0, Math.floor(siblings))) : 1;\n page = Number.isFinite(page) ? Math.max(1, Math.min(pageCount, Math.floor(page))) : 1;\n if (pageCount <= 0) return [];\n const window = siblings * 2 + 5; // first, last, current, siblings, two gaps\n if (pageCount <= window) return Array.from({ length: pageCount }, (_, i) => i + 1);\n const start = Math.max(2, page - siblings);\n const end = Math.min(pageCount - 1, page + siblings);\n const out: (number | 'ellipsis')[] = [1];\n if (start > 2) out.push('ellipsis');\n for (let p = start; p <= end; p += 1) out.push(p);\n if (end < pageCount - 1) out.push('ellipsis');\n out.push(pageCount);\n return out;\n}\n\n/* ------------------------------------------------------------------------ */\n/* Paging arithmetic: no React, for any list, table or server query. */\n/* ------------------------------------------------------------------------ */\n\n/** The page sizes offered by default: 25, 50 or 100 rows. */\nexport const DEFAULT_PAGE_SIZES: readonly number[] = [25, 50, 100];\n\nexport interface PageWindow {\n /** 1-based, clamped to the pages that exist (1 when there are no rows). */\n page: number;\n /** At least 1, so \"Page 1 of 1\" reads sensibly for an empty list. */\n pageCount: number;\n pageSize: number;\n total: number;\n /** 1-based index of the first row on this page; 0 when there are none. */\n from: number;\n /** 1-based index of the last row on this page; 0 when there are none. */\n to: number;\n}\n\n/** Pages needed for `total` rows, never less than 1. */\nexport function pageCountFor(total: number, pageSize: number): number {\n if (!Number.isFinite(total) || total <= 0) return 1;\n const size = Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : 1;\n return Math.max(1, Math.ceil(total / size));\n}\n\n/** A requested page clamped to 1…pageCount. Anything unreadable (\"abc\", NaN) is page 1. */\nexport function clampPage(page: unknown, pageCount: number): number {\n const n = typeof page === 'number' ? page : Number(page);\n if (!Number.isFinite(n)) return 1;\n return Math.min(Math.max(1, Math.floor(n)), Math.max(1, Math.floor(pageCount) || 1));\n}\n\n/** Where page `page` of `total` rows starts and ends, for server-side paging. */\nexport function pageWindow({\n page,\n pageSize,\n total,\n}: {\n page: unknown;\n pageSize: number;\n total: number;\n}): PageWindow {\n const size = Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : 1;\n const safeTotal = Number.isFinite(total) ? Math.max(0, Math.floor(total)) : 0;\n const pageCount = pageCountFor(safeTotal, size);\n const p = clampPage(page, pageCount);\n const start = (p - 1) * size;\n const to = Math.min(start + size, safeTotal);\n return {\n page: p,\n pageCount,\n pageSize: size,\n total: safeTotal,\n from: safeTotal ? start + 1 : 0,\n to: safeTotal ? to : 0,\n };\n}\n\n/** One page of `rows`, with the numbers for the page controls. */\nexport function paginate<T>(\n rows: readonly T[],\n page: unknown,\n pageSize: number,\n): PageWindow & { rows: T[] } {\n const w = pageWindow({ page, pageSize, total: rows.length });\n return { ...w, rows: rows.slice(w.from ? w.from - 1 : 0, w.to) };\n}\n\n/**\n * \"1–25 of 312\", \"7 of 7\" when everything fits, \"0 of 0\" when empty. Numbers\n * use the locale's grouping (\"1,025\"); pass `locale` to pin it.\n */\nexport function paginationSummary(\n { from, to, total }: Pick<PageWindow, 'from' | 'to' | 'total'>,\n locale?: string,\n): string {\n const fmt = new Intl.NumberFormat(locale);\n if (!total) return '0 of 0';\n if (from === 1 && to === total) return `${fmt.format(total)} of ${fmt.format(total)}`;\n return `${fmt.format(from)}–${fmt.format(to)} of ${fmt.format(total)}`;\n}\n\nconst storedSizeKey = (key: string) => `bl-page-size:${key}`;\n\nfunction readStoredSize(key: string | undefined, options: readonly number[]): number | undefined {\n if (!key) return undefined;\n try {\n const n = Number(globalThis.localStorage?.getItem(storedSizeKey(key)));\n return options.includes(n) ? n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeStoredSize(key: string | undefined, size: number) {\n if (!key) return;\n try {\n globalThis.localStorage?.setItem(storedSizeKey(key), String(size));\n } catch {\n // Storage blocked (private mode, a sandboxed frame): the size is not remembered.\n }\n}\n\nexport interface UsePaginationOptions {\n /** Controlled page (e.g. read from the URL). Leave out to let the hook keep it. */\n page?: number;\n /** Starting page when uncontrolled. */\n defaultPage?: number;\n onPageChange?: (page: number) => void;\n /** Controlled page size. */\n pageSize?: number;\n /** Starting page size when uncontrolled; the first option by default. */\n defaultPageSize?: number;\n onPageSizeChange?: (pageSize: number) => void;\n /** Sizes offered by the page-size menu. */\n pageSizeOptions?: readonly number[];\n /**\n * Whatever decides which rows are listed (search text, a filter, a tab).\n * When it changes, paging goes back to page 1. The first render never\n * resets, so a link to page 3 stays on page 3.\n */\n resetKey?: unknown;\n /** Remembers the chosen page size in localStorage under this key. */\n storageKey?: string;\n /**\n * The full row count when the server pages: `rows` is then already the\n * current page and is not sliced.\n */\n total?: number;\n /** Locale for the summary's numbers. */\n locale?: string;\n}\n\nexport interface UsePaginationResult<T> extends PageWindow {\n /** The rows to render on this page. */\n rows: T[];\n pageSizeOptions: readonly number[];\n /** \"1–25 of 312\". */\n summary: string;\n /** More rows than the smallest page size: worth showing controls for. */\n hasPages: boolean;\n setPage: (page: number) => void;\n /** Changes the size and goes back to page 1. */\n setPageSize: (pageSize: number) => void;\n /** Spread onto Pagination (or pass to DataTable's paging props). */\n paginationProps: Pick<\n PaginationProps,\n 'page' | 'pageCount' | 'onPageChange' | 'pageSize' | 'pageSizeOptions' | 'onPageSizeChange'\n > & { summary: string };\n}\n\n/**\n * Headless paging for a list or table: slices `rows`, clamps the page,\n * returns to page 1 when `resetKey` changes, and optionally remembers the\n * page size. Controlled `page` / `pageSize` let an app keep them in the URL.\n */\nexport function usePagination<T>(\n rows: readonly T[],\n options: UsePaginationOptions = {},\n): UsePaginationResult<T> {\n const {\n page: pageProp,\n defaultPage = 1,\n onPageChange,\n pageSize: pageSizeProp,\n defaultPageSize,\n onPageSizeChange,\n pageSizeOptions = DEFAULT_PAGE_SIZES,\n resetKey,\n storageKey,\n total: serverTotal,\n locale,\n } = options;\n\n const [pageState, setPageState] = React.useState(defaultPage);\n const [sizeState, setSizeState] = React.useState<number>(\n () =>\n readStoredSize(storageKey, pageSizeOptions) ?? defaultPageSize ?? pageSizeOptions[0] ?? 25,\n );\n const page = pageProp ?? pageState;\n const pageSize = pageSizeProp ?? sizeState;\n\n // Keep the latest callbacks without making the setters change every render.\n const latest = React.useRef({ onPageChange, onPageSizeChange, pageProp, storageKey });\n React.useEffect(() => {\n latest.current = { onPageChange, onPageSizeChange, pageProp, storageKey };\n });\n\n const setPage = React.useCallback((next: number) => {\n setPageState(next);\n latest.current.onPageChange?.(next);\n }, []);\n const setPageSize = React.useCallback((next: number) => {\n writeStoredSize(latest.current.storageKey, next);\n setSizeState(next);\n setPageState(1);\n latest.current.onPageSizeChange?.(next);\n latest.current.onPageChange?.(1);\n }, []);\n\n // A new filter starts again at page 1: during render for local state (no\n // flash of an empty page), and after it for a controlled page.\n const [lastKey, setLastKey] = React.useState(resetKey);\n if (!Object.is(lastKey, resetKey)) {\n setLastKey(resetKey);\n setPageState(1);\n }\n const committedKey = React.useRef(resetKey);\n React.useEffect(() => {\n if (Object.is(committedKey.current, resetKey)) return;\n committedKey.current = resetKey;\n const { pageProp: controlled, onPageChange: report } = latest.current;\n if (controlled !== undefined && controlled !== 1) report?.(1);\n }, [resetKey]);\n\n const w =\n serverTotal === undefined\n ? paginate(rows, page, pageSize)\n : { ...pageWindow({ page, pageSize, total: serverTotal }), rows: [...rows] };\n const summary = paginationSummary(w, locale);\n const hasPages = w.total > Math.min(...pageSizeOptions, w.pageSize);\n\n return {\n ...w,\n pageSizeOptions,\n summary,\n hasPages,\n setPage,\n setPageSize,\n paginationProps: {\n page: w.page,\n pageCount: w.pageCount,\n onPageChange: setPage,\n pageSize: w.pageSize,\n pageSizeOptions,\n onPageSizeChange: setPageSize,\n summary,\n },\n };\n}\n\n/* ------------------------------------------------------------------------ */\n/* The controls. */\n/* ------------------------------------------------------------------------ */\n\nexport interface PaginationProps extends Omit<React.ComponentProps<'nav'>, 'onChange'> {\n /** 1-based current page. */\n page: number;\n pageCount: number;\n onPageChange: (page: number) => void;\n siblings?: number;\n /** Summary on the left, e.g. \"1–25 of 312\". Worked out from `total` when left out. */\n summary?: React.ReactNode;\n /** Total rows: with `pageSize`, gives the default summary. */\n total?: number;\n /** Rows per page, shown in the page-size menu. */\n pageSize?: number;\n /** Sizes in the page-size menu (25, 50, 100 by default). */\n pageSizeOptions?: readonly number[];\n /** Adds a rows-per-page menu. */\n onPageSizeChange?: (pageSize: number) => void;\n /** The page-size menu's accessible name. */\n pageSizeLabel?: string;\n /** An option's text, e.g. \"25 per page\". */\n formatPageSize?: (size: number) => string;\n}\n\n/**\n * Page controls for a table or list: a summary, the pages, and optionally a\n * rows-per-page menu. Below 640px it becomes \"Page 2 of 13\" between large\n * previous and next buttons. Buttons are real buttons, keyboard-reachable,\n * and 44px on touch screens.\n */\nfunction Pagination({\n page: requestedPage,\n pageCount: requestedCount,\n onPageChange,\n siblings = 1,\n summary,\n total,\n pageSize,\n pageSizeOptions = DEFAULT_PAGE_SIZES,\n onPageSizeChange,\n pageSizeLabel = 'Rows per page',\n formatPageSize = (size) => `${size} per page`,\n className,\n ...props\n}: PaginationProps) {\n const pageCount = Number.isFinite(requestedCount) ? Math.max(0, Math.floor(requestedCount)) : 0;\n const page =\n pageCount === 0\n ? 0\n : Number.isFinite(requestedPage)\n ? Math.max(1, Math.min(pageCount, Math.floor(requestedPage)))\n : 1;\n const pages = paginationRange(page, pageCount, siblings);\n const go = (p: number) => onPageChange(Math.min(Math.max(1, p), Math.max(1, pageCount)));\n const shownSummary =\n summary ??\n (total !== undefined && pageSize\n ? paginationSummary(pageWindow({ page: page || 1, pageSize, total }))\n : null);\n const sizes =\n pageSize !== undefined && !pageSizeOptions.includes(pageSize)\n ? [...pageSizeOptions, pageSize].sort((a, b) => a - b)\n : pageSizeOptions;\n const position = `Page ${page} of ${pageCount}`;\n return (\n <nav\n aria-label=\"Pagination\"\n data-slot=\"pagination\"\n className={cn('flex flex-wrap items-center justify-between gap-x-3 gap-y-2', className)}\n {...props}\n >\n <div\n data-slot=\"pagination-summary\"\n className=\"min-w-0 text-xs text-muted-foreground tabular-nums max-sm:order-2\"\n >\n {shownSummary}\n </div>\n {/* Announced when the page changes; the visible \"Page 2 of 13\" is for phones. */}\n <span role=\"status\" className=\"sr-only\">\n {pageCount > 0 ? position : ''}\n </span>\n <ul className=\"flex items-center gap-1 max-sm:order-1 max-sm:w-full max-sm:justify-between\">\n <li>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => go(page - 1)}\n disabled={page <= 1}\n aria-label=\"Previous page\"\n className=\"max-sm:size-11 max-sm:border max-sm:border-border max-sm:px-0\"\n >\n <ChevronLeft aria-hidden />\n <span className=\"hidden sm:inline\">Previous</span>\n </Button>\n </li>\n {pages.map((p, i) =>\n p === 'ellipsis' ? (\n <li key={`e${i}`} aria-hidden className=\"hidden px-1 text-muted-foreground sm:block\">\n …\n </li>\n ) : (\n <li key={p} className=\"hidden sm:block\">\n <Button\n variant={p === page ? 'outline' : 'ghost'}\n size=\"icon-sm\"\n onClick={() => go(p)}\n aria-current={p === page ? 'page' : undefined}\n aria-label={`Page ${p}`}\n className={cn('tabular-nums', p === page && 'border-brand/40 text-brand')}\n >\n {p}\n </Button>\n </li>\n ),\n )}\n <li\n aria-hidden\n data-slot=\"pagination-position\"\n className=\"px-2 text-sm font-medium tabular-nums sm:hidden\"\n >\n {position}\n </li>\n <li>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => go(page + 1)}\n disabled={page >= pageCount}\n aria-label=\"Next page\"\n className=\"max-sm:size-11 max-sm:border max-sm:border-border max-sm:px-0\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight aria-hidden />\n </Button>\n </li>\n </ul>\n {onPageSizeChange && (\n <Select\n value={pageSize !== undefined ? String(pageSize) : undefined}\n onValueChange={(v) => onPageSizeChange(Number(v))}\n >\n <SelectTrigger\n size=\"sm\"\n aria-label={pageSizeLabel}\n className=\"w-auto gap-1.5 text-xs tabular-nums max-sm:order-3 pointer-coarse:h-11\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent align=\"end\">\n {sizes.map((s) => (\n <SelectItem key={s} value={String(s)}>\n {formatPageSize(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </nav>\n );\n}\n\nexport { Pagination };\n"],"mappings":"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"}
@@ -5,8 +5,13 @@ import * as React from 'react';
5
5
  * with a strong border, and the one place a heavy shadow is allowed.
6
6
  */
7
7
  export declare const surfaceClasses = "z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]";
8
- /** A menu row: 32px, 8px radius; the focused row is a quiet fill, like an active tab. */
9
- export declare const menuItemClasses = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
8
+ /**
9
+ * A menu row: 32px, 8px radius (the surface's 12px less its 4px padding); the
10
+ * focused row is a quiet secondary fill (muted is too close to the raised
11
+ * surface in dark mode to read). The outline is suppressed
12
+ * with !important so a host page's global focus outline cannot box the row.
13
+ */
14
+ export declare const menuItemClasses = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden! select-none focus:bg-secondary focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
10
15
  declare function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element;
11
16
  declare function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element;
12
17
  declare function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>): React.JSX.Element;
@@ -8,8 +8,13 @@ import { jsx } from "react/jsx-runtime";
8
8
  * with a strong border, and the one place a heavy shadow is allowed.
9
9
  */
10
10
  var surfaceClasses = "z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]";
11
- /** A menu row: 32px, 8px radius; the focused row is a quiet fill, like an active tab. */
12
- var menuItemClasses = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
11
+ /**
12
+ * A menu row: 32px, 8px radius (the surface's 12px less its 4px padding); the
13
+ * focused row is a quiet secondary fill (muted is too close to the raised
14
+ * surface in dark mode to read). The outline is suppressed
15
+ * with !important so a host page's global focus outline cannot box the row.
16
+ */
17
+ var menuItemClasses = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden! select-none focus:bg-secondary focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
13
18
  function Popover$1(props) {
14
19
  return /* @__PURE__ */ jsx(Popover.Root, {
15
20
  "data-slot": "popover",
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":["Popover","PopoverPrimitive"],"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { Popover as PopoverPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\n/**\n * The floating surface shared by popovers, menus and select lists: raised,\n * with a strong border, and the one place a heavy shadow is allowed.\n */\nexport const surfaceClasses =\n 'z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]';\n\n/** A menu row: 32px, 8px radius; the focused row is a quiet fill, like an active tab. */\nexport const menuItemClasses =\n \"relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\";\n\nfunction Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = 'center',\n sideOffset = 6,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'w-72 max-w-[calc(100vw-2rem)] max-h-(--radix-popover-content-available-height) overflow-y-auto origin-(--radix-popover-content-transform-origin) p-4 outline-hidden',\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };\n"],"mappings":";;;;;;;;;AASA,IAAa,iBACX;;AAGF,IAAa,kBACX;AAEF,SAASA,UAAQ,OAA2D;CAC1E,OAAO,oBAACC,QAAiB,MAAlB;EAAuB,aAAU;EAAU,GAAI;CAAQ,CAAA;AAChE;AAEA,SAAS,eAAe,OAA8D;CACpF,OAAO,oBAACA,QAAiB,SAAlB;EAA0B,aAAU;EAAkB,GAAI;CAAQ,CAAA;AAC3E;AAEA,SAAS,cAAc,OAA6D;CAClF,OAAO,oBAACA,QAAiB,QAAlB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,eAAe,EACtB,WACA,QAAQ,UACR,aAAa,GACb,GAAG,SACqD;CACxD,OACE,oBAACA,QAAiB,QAAlB,EAAA,UACE,oBAACA,QAAiB,SAAlB;EACE,aAAU;EACH;EACK;EACZ,WAAW,GACT,gBACA,uKACA,SACF;EACA,GAAI;CACL,CAAA,EACsB,CAAA;AAE7B"}
1
+ {"version":3,"file":"popover.js","names":["Popover","PopoverPrimitive"],"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { Popover as PopoverPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\n/**\n * The floating surface shared by popovers, menus and select lists: raised,\n * with a strong border, and the one place a heavy shadow is allowed.\n */\nexport const surfaceClasses =\n 'z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]';\n\n/**\n * A menu row: 32px, 8px radius (the surface's 12px less its 4px padding); the\n * focused row is a quiet secondary fill (muted is too close to the raised\n * surface in dark mode to read). The outline is suppressed\n * with !important so a host page's global focus outline cannot box the row.\n */\nexport const menuItemClasses =\n \"relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden! select-none focus:bg-secondary focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\";\n\nfunction Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = 'center',\n sideOffset = 6,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'w-72 max-w-[calc(100vw-2rem)] max-h-(--radix-popover-content-available-height) overflow-y-auto origin-(--radix-popover-content-transform-origin) p-4 outline-hidden',\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };\n"],"mappings":";;;;;;;;;AASA,IAAa,iBACX;;;;;;;AAQF,IAAa,kBACX;AAEF,SAASA,UAAQ,OAA2D;CAC1E,OAAO,oBAACC,QAAiB,MAAlB;EAAuB,aAAU;EAAU,GAAI;CAAQ,CAAA;AAChE;AAEA,SAAS,eAAe,OAA8D;CACpF,OAAO,oBAACA,QAAiB,SAAlB;EAA0B,aAAU;EAAkB,GAAI;CAAQ,CAAA;AAC3E;AAEA,SAAS,cAAc,OAA6D;CAClF,OAAO,oBAACA,QAAiB,QAAlB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,eAAe,EACtB,WACA,QAAQ,UACR,aAAa,GACb,GAAG,SACqD;CACxD,OACE,oBAACA,QAAiB,QAAlB,EAAA,UACE,oBAACA,QAAiB,SAAlB;EACE,aAAU;EACH;EACK;EACZ,WAAW,GACT,gBACA,uKACA,SACF;EACA,GAAI;CACL,CAAA,EACsB,CAAA;AAE7B"}
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../lib/utils.js";
2
- import { IconButton } from "./icon-button.js";
3
2
  import { Input } from "./input.js";
3
+ import { IconButton } from "./icon-button.js";
4
4
  import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import Eye from "@burtson-labs/icons/react/eye";
@@ -79,7 +79,7 @@ function SelectLabel({ className, ...props }) {
79
79
  function SelectItem({ className, children, ...props }) {
80
80
  return /* @__PURE__ */ jsxs(Select.Item, {
81
81
  "data-slot": "select-item",
82
- className: cn("relative flex min-h-8 w-full cursor-default items-center gap-2 rounded-sm pr-8 pl-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[state=checked]:font-semibold data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
82
+ className: cn("relative flex min-h-8 w-full cursor-default items-center gap-2 rounded-sm pr-8 pl-2 text-[13px] outline-hidden! select-none focus:bg-secondary focus:text-foreground data-[state=checked]:font-semibold data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
83
83
  ...props,
84
84
  children: [/* @__PURE__ */ jsx("span", {
85
85
  className: "absolute right-2 flex size-3.5 items-center justify-center",
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","names":["Select","SelectPrimitive"],"sources":["../../src/components/select.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronDown from '@burtson-labs/icons/react/chevron-down';\nimport ChevronUp from '@burtson-labs/icons/react/chevron-up';\nimport { Select as SelectPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { fieldClasses } from './input';\nimport { surfaceClasses } from './popover';\n\nfunction Select(props: React.ComponentProps<typeof SelectPrimitive.Root>) {\n return <SelectPrimitive.Root data-slot=\"select\" {...props} />;\n}\n\nfunction SelectGroup(props: React.ComponentProps<typeof SelectPrimitive.Group>) {\n return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>) {\n return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({\n className,\n size = 'default',\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: 'sm' | 'default' }) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n fieldClasses,\n \"flex w-fit items-center justify-between gap-2 py-2 whitespace-nowrap data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"size-4 opacity-60\" aria-hidden />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({\n className,\n children,\n position = 'popper',\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n surfaceClasses,\n 'relative max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto',\n position === 'popper' &&\n 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n className,\n )}\n position={position}\n {...props}\n >\n <SelectPrimitive.ScrollUpButton className=\"flex cursor-default items-center justify-center py-1\">\n <ChevronUp className=\"size-4\" aria-hidden />\n </SelectPrimitive.ScrollUpButton>\n <SelectPrimitive.Viewport\n className={cn(\n 'p-1',\n position === 'popper' &&\n 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1',\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectPrimitive.ScrollDownButton className=\"flex cursor-default items-center justify-center py-1\">\n <ChevronDown className=\"size-4\" aria-hidden />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n}\n\nfunction SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n data-slot=\"select-label\"\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"relative flex min-h-8 w-full cursor-default items-center gap-2 rounded-sm pr-8 pl-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[state=checked]:font-semibold data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"size-4 text-brand\" aria-hidden />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n"],"mappings":";;;;;;;;;;AAWA,SAASA,SAAO,OAA0D;CACxE,OAAO,oBAACC,OAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ,CAAA;AAC9D;AAEA,SAAS,YAAY,OAA2D;CAC9E,OAAO,oBAACA,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,SAAS,YAAY,OAA2D;CAC9E,OAAO,oBAACA,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,SAAS,cAAc,EACrB,WACA,OAAO,WACP,UACA,GAAG,SACkF;CACrF,OACE,qBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,cACA,+XACA,SACF;EACA,GAAI;EARN,UAAA,CAUG,UACD,oBAACA,OAAgB,MAAjB;GAAsB,SAAA;GACpB,UAAA,oBAAC,aAAD;IAAa,WAAU;IAAoB,eAAA;GAAa,CAAA;EACpC,CAAA,CACC;;AAE7B;AAEA,SAAS,cAAc,EACrB,WACA,UACA,WAAW,UACX,GAAG,SACoD;CACvD,OACE,oBAACA,OAAgB,QAAjB,EAAA,UACE,qBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,WAAW,GACT,gBACA,4JACA,aAAa,YACX,mIACF,SACF;EACU;EACV,GAAI;EAVN,UAAA;GAYE,oBAACA,OAAgB,gBAAjB;IAAgC,WAAU;IACxC,UAAA,oBAAC,WAAD;KAAW,WAAU;KAAS,eAAA;IAAa,CAAA;GACb,CAAA;GAChC,oBAACA,OAAgB,UAAjB;IACE,WAAW,GACT,OACA,aAAa,YACX,2FACJ;IAEC;GACuB,CAAA;GAC1B,oBAACA,OAAgB,kBAAjB;IAAkC,WAAU;IAC1C,UAAA,oBAAC,aAAD;KAAa,WAAU;KAAS,eAAA;IAAa,CAAA;GACb,CAAA;EACX;CACH,CAAA,EAAA,CAAA;AAE5B;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GACT,8FACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAClB,WACA,UACA,GAAG,SACiD;CACpD,OACE,qBAACA,OAAgB,MAAjB;EACE,aAAU;EACV,WAAW,GACT,uVACA,SACF;EACA,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,OAAgB,eAAjB,EAAA,UACE,oBAAC,OAAD;IAAO,WAAU;IAAoB,eAAA;GAAa,CAAA,EACrB,CAAA;EAC3B,CAAA,GACN,oBAACA,OAAgB,UAAjB,EAA2B,SAAmC,CAAA,CAC1C;;AAE1B;AAEA,SAAS,gBAAgB,EACvB,WACA,GAAG,SACsD;CACzD,OACE,oBAACA,OAAgB,WAAjB;EACE,aAAU;EACV,WAAW,GAAG,iDAAiD,SAAS;EACxE,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"select.js","names":["Select","SelectPrimitive"],"sources":["../../src/components/select.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronDown from '@burtson-labs/icons/react/chevron-down';\nimport ChevronUp from '@burtson-labs/icons/react/chevron-up';\nimport { Select as SelectPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { fieldClasses } from './input';\nimport { surfaceClasses } from './popover';\n\nfunction Select(props: React.ComponentProps<typeof SelectPrimitive.Root>) {\n return <SelectPrimitive.Root data-slot=\"select\" {...props} />;\n}\n\nfunction SelectGroup(props: React.ComponentProps<typeof SelectPrimitive.Group>) {\n return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>) {\n return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({\n className,\n size = 'default',\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: 'sm' | 'default' }) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n fieldClasses,\n \"flex w-fit items-center justify-between gap-2 py-2 whitespace-nowrap data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"size-4 opacity-60\" aria-hidden />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({\n className,\n children,\n position = 'popper',\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n surfaceClasses,\n 'relative max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto',\n position === 'popper' &&\n 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n className,\n )}\n position={position}\n {...props}\n >\n <SelectPrimitive.ScrollUpButton className=\"flex cursor-default items-center justify-center py-1\">\n <ChevronUp className=\"size-4\" aria-hidden />\n </SelectPrimitive.ScrollUpButton>\n <SelectPrimitive.Viewport\n className={cn(\n 'p-1',\n position === 'popper' &&\n 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1',\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectPrimitive.ScrollDownButton className=\"flex cursor-default items-center justify-center py-1\">\n <ChevronDown className=\"size-4\" aria-hidden />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n}\n\nfunction SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n data-slot=\"select-label\"\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"relative flex min-h-8 w-full cursor-default items-center gap-2 rounded-sm pr-8 pl-2 text-[13px] outline-hidden! select-none focus:bg-secondary focus:text-foreground data-[state=checked]:font-semibold data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"size-4 text-brand\" aria-hidden />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n"],"mappings":";;;;;;;;;;AAWA,SAASA,SAAO,OAA0D;CACxE,OAAO,oBAACC,OAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ,CAAA;AAC9D;AAEA,SAAS,YAAY,OAA2D;CAC9E,OAAO,oBAACA,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,SAAS,YAAY,OAA2D;CAC9E,OAAO,oBAACA,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,SAAS,cAAc,EACrB,WACA,OAAO,WACP,UACA,GAAG,SACkF;CACrF,OACE,qBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,cACA,+XACA,SACF;EACA,GAAI;EARN,UAAA,CAUG,UACD,oBAACA,OAAgB,MAAjB;GAAsB,SAAA;GACpB,UAAA,oBAAC,aAAD;IAAa,WAAU;IAAoB,eAAA;GAAa,CAAA;EACpC,CAAA,CACC;;AAE7B;AAEA,SAAS,cAAc,EACrB,WACA,UACA,WAAW,UACX,GAAG,SACoD;CACvD,OACE,oBAACA,OAAgB,QAAjB,EAAA,UACE,qBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,WAAW,GACT,gBACA,4JACA,aAAa,YACX,mIACF,SACF;EACU;EACV,GAAI;EAVN,UAAA;GAYE,oBAACA,OAAgB,gBAAjB;IAAgC,WAAU;IACxC,UAAA,oBAAC,WAAD;KAAW,WAAU;KAAS,eAAA;IAAa,CAAA;GACb,CAAA;GAChC,oBAACA,OAAgB,UAAjB;IACE,WAAW,GACT,OACA,aAAa,YACX,2FACJ;IAEC;GACuB,CAAA;GAC1B,oBAACA,OAAgB,kBAAjB;IAAkC,WAAU;IAC1C,UAAA,oBAAC,aAAD;KAAa,WAAU;KAAS,eAAA;IAAa,CAAA;GACb,CAAA;EACX;CACH,CAAA,EAAA,CAAA;AAE5B;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GACT,8FACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAClB,WACA,UACA,GAAG,SACiD;CACpD,OACE,qBAACA,OAAgB,MAAjB;EACE,aAAU;EACV,WAAW,GACT,4VACA,SACF;EACA,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,OAAgB,eAAjB,EAAA,UACE,oBAAC,OAAD;IAAO,WAAU;IAAoB,eAAA;GAAa,CAAA,EACrB,CAAA;EAC3B,CAAA,GACN,oBAACA,OAAgB,UAAjB,EAA2B,SAAmC,CAAA,CAC1C;;AAE1B;AAEA,SAAS,gBAAgB,EACvB,WACA,GAAG,SACsD;CACzD,OACE,oBAACA,OAAgB,WAAjB;EACE,aAAU;EACV,WAAW,GAAG,iDAAiD,SAAS;EACxE,GAAI;CACL,CAAA;AAEL"}