@braedonsaunders/appkit-ui 0.2.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 (300) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +11 -0
  3. package/account-menu.d.ts +49 -0
  4. package/account-menu.d.ts.map +1 -0
  5. package/account-menu.js +66 -0
  6. package/account-menu.js.map +1 -0
  7. package/alert.d.ts +13 -0
  8. package/alert.d.ts.map +1 -0
  9. package/alert.js +22 -0
  10. package/alert.js.map +1 -0
  11. package/animated-number.d.ts +13 -0
  12. package/animated-number.d.ts.map +1 -0
  13. package/animated-number.js +53 -0
  14. package/animated-number.js.map +1 -0
  15. package/api-reference.d.ts +44 -0
  16. package/api-reference.d.ts.map +1 -0
  17. package/api-reference.js +82 -0
  18. package/api-reference.js.map +1 -0
  19. package/app-shell.d.ts +39 -0
  20. package/app-shell.d.ts.map +1 -0
  21. package/app-shell.js +21 -0
  22. package/app-shell.js.map +1 -0
  23. package/app-sidebar.d.ts +22 -0
  24. package/app-sidebar.d.ts.map +1 -0
  25. package/app-sidebar.js +30 -0
  26. package/app-sidebar.js.map +1 -0
  27. package/avatar.d.ts +9 -0
  28. package/avatar.d.ts.map +1 -0
  29. package/avatar.js +18 -0
  30. package/avatar.js.map +1 -0
  31. package/badge.d.ts +9 -0
  32. package/badge.d.ts.map +1 -0
  33. package/badge.js +23 -0
  34. package/badge.js.map +1 -0
  35. package/button.d.ts +24 -0
  36. package/button.d.ts.map +1 -0
  37. package/button.js +50 -0
  38. package/button.js.map +1 -0
  39. package/card.d.ts +21 -0
  40. package/card.d.ts.map +1 -0
  41. package/card.js +36 -0
  42. package/card.js.map +1 -0
  43. package/checkbox.d.ts +4 -0
  44. package/checkbox.d.ts.map +1 -0
  45. package/checkbox.js +6 -0
  46. package/checkbox.js.map +1 -0
  47. package/confirm-dialog.d.ts +23 -0
  48. package/confirm-dialog.d.ts.map +1 -0
  49. package/confirm-dialog.js +67 -0
  50. package/confirm-dialog.js.map +1 -0
  51. package/context-menu.d.ts +47 -0
  52. package/context-menu.d.ts.map +1 -0
  53. package/context-menu.js +90 -0
  54. package/context-menu.js.map +1 -0
  55. package/dialog.d.ts +25 -0
  56. package/dialog.d.ts.map +1 -0
  57. package/dialog.js +89 -0
  58. package/dialog.js.map +1 -0
  59. package/document-title.d.ts +11 -0
  60. package/document-title.d.ts.map +1 -0
  61. package/document-title.js +17 -0
  62. package/document-title.js.map +1 -0
  63. package/drawer.d.ts +63 -0
  64. package/drawer.d.ts.map +1 -0
  65. package/drawer.js +188 -0
  66. package/drawer.js.map +1 -0
  67. package/empty-state.d.ts +10 -0
  68. package/empty-state.d.ts.map +1 -0
  69. package/empty-state.js +7 -0
  70. package/empty-state.js.map +1 -0
  71. package/file-uploader.d.ts +67 -0
  72. package/file-uploader.d.ts.map +1 -0
  73. package/file-uploader.js +156 -0
  74. package/file-uploader.js.map +1 -0
  75. package/filter-chips.d.ts +47 -0
  76. package/filter-chips.d.ts.map +1 -0
  77. package/filter-chips.js +62 -0
  78. package/filter-chips.js.map +1 -0
  79. package/global-search.d.ts +41 -0
  80. package/global-search.d.ts.map +1 -0
  81. package/global-search.js +116 -0
  82. package/global-search.js.map +1 -0
  83. package/index.d.ts +70 -0
  84. package/index.d.ts.map +1 -0
  85. package/index.js +70 -0
  86. package/index.js.map +1 -0
  87. package/input.d.ts +9 -0
  88. package/input.d.ts.map +1 -0
  89. package/input.js +11 -0
  90. package/input.js.map +1 -0
  91. package/label.d.ts +3 -0
  92. package/label.d.ts.map +1 -0
  93. package/label.js +6 -0
  94. package/label.js.map +1 -0
  95. package/line-grid.d.ts +48 -0
  96. package/line-grid.d.ts.map +1 -0
  97. package/line-grid.js +155 -0
  98. package/line-grid.js.map +1 -0
  99. package/link-context.d.ts +44 -0
  100. package/link-context.d.ts.map +1 -0
  101. package/link-context.js +32 -0
  102. package/link-context.js.map +1 -0
  103. package/list-nav.d.ts +28 -0
  104. package/list-nav.d.ts.map +1 -0
  105. package/list-nav.js +41 -0
  106. package/list-nav.js.map +1 -0
  107. package/list-pagination.d.ts +27 -0
  108. package/list-pagination.d.ts.map +1 -0
  109. package/list-pagination.js +43 -0
  110. package/list-pagination.js.map +1 -0
  111. package/list-params.d.ts +41 -0
  112. package/list-params.d.ts.map +1 -0
  113. package/list-params.js +84 -0
  114. package/list-params.js.map +1 -0
  115. package/list-search-input-state.d.ts +13 -0
  116. package/list-search-input-state.d.ts.map +1 -0
  117. package/list-search-input-state.js +27 -0
  118. package/list-search-input-state.js.map +1 -0
  119. package/list-search-input.d.ts +16 -0
  120. package/list-search-input.d.ts.map +1 -0
  121. package/list-search-input.js +51 -0
  122. package/list-search-input.js.map +1 -0
  123. package/mobile-tab-bar.d.ts +14 -0
  124. package/mobile-tab-bar.d.ts.map +1 -0
  125. package/mobile-tab-bar.js +25 -0
  126. package/mobile-tab-bar.js.map +1 -0
  127. package/multi-select.d.ts +32 -0
  128. package/multi-select.d.ts.map +1 -0
  129. package/multi-select.js +52 -0
  130. package/multi-select.js.map +1 -0
  131. package/nav-overflow.d.ts +12 -0
  132. package/nav-overflow.d.ts.map +1 -0
  133. package/nav-overflow.js +30 -0
  134. package/nav-overflow.js.map +1 -0
  135. package/navigation-config-editor.d.ts +26 -0
  136. package/navigation-config-editor.d.ts.map +1 -0
  137. package/navigation-config-editor.js +57 -0
  138. package/navigation-config-editor.js.map +1 -0
  139. package/navigation-config.d.ts +48 -0
  140. package/navigation-config.d.ts.map +1 -0
  141. package/navigation-config.js +75 -0
  142. package/navigation-config.js.map +1 -0
  143. package/navigation-mode.d.ts +16 -0
  144. package/navigation-mode.d.ts.map +1 -0
  145. package/navigation-mode.js +30 -0
  146. package/navigation-mode.js.map +1 -0
  147. package/notifications-bell.d.ts +22 -0
  148. package/notifications-bell.d.ts.map +1 -0
  149. package/notifications-bell.js +30 -0
  150. package/notifications-bell.js.map +1 -0
  151. package/org-chart.d.ts +85 -0
  152. package/org-chart.d.ts.map +1 -0
  153. package/org-chart.js +193 -0
  154. package/org-chart.js.map +1 -0
  155. package/package.json +61 -0
  156. package/page-header.d.ts +32 -0
  157. package/page-header.d.ts.map +1 -0
  158. package/page-header.js +14 -0
  159. package/page-header.js.map +1 -0
  160. package/page-layout.d.ts +29 -0
  161. package/page-layout.d.ts.map +1 -0
  162. package/page-layout.js +23 -0
  163. package/page-layout.js.map +1 -0
  164. package/page-transition.d.ts +13 -0
  165. package/page-transition.d.ts.map +1 -0
  166. package/page-transition.js +14 -0
  167. package/page-transition.js.map +1 -0
  168. package/paged-table.d.ts +54 -0
  169. package/paged-table.d.ts.map +1 -0
  170. package/paged-table.js +74 -0
  171. package/paged-table.js.map +1 -0
  172. package/popover.d.ts +16 -0
  173. package/popover.d.ts.map +1 -0
  174. package/popover.js +99 -0
  175. package/popover.js.map +1 -0
  176. package/progress.d.ts +7 -0
  177. package/progress.d.ts.map +1 -0
  178. package/progress.js +8 -0
  179. package/progress.js.map +1 -0
  180. package/prompt-dialog.d.ts +21 -0
  181. package/prompt-dialog.d.ts.map +1 -0
  182. package/prompt-dialog.js +109 -0
  183. package/prompt-dialog.js.map +1 -0
  184. package/record-list.d.ts +76 -0
  185. package/record-list.d.ts.map +1 -0
  186. package/record-list.js +51 -0
  187. package/record-list.js.map +1 -0
  188. package/search-select.d.ts +42 -0
  189. package/search-select.d.ts.map +1 -0
  190. package/search-select.js +194 -0
  191. package/search-select.js.map +1 -0
  192. package/select-options.d.ts +16 -0
  193. package/select-options.d.ts.map +1 -0
  194. package/select-options.js +119 -0
  195. package/select-options.js.map +1 -0
  196. package/select.d.ts +24 -0
  197. package/select.d.ts.map +1 -0
  198. package/select.js +65 -0
  199. package/select.js.map +1 -0
  200. package/separator.d.ts +6 -0
  201. package/separator.d.ts.map +1 -0
  202. package/separator.js +7 -0
  203. package/separator.js.map +1 -0
  204. package/settings-layout.d.ts +79 -0
  205. package/settings-layout.d.ts.map +1 -0
  206. package/settings-layout.js +74 -0
  207. package/settings-layout.js.map +1 -0
  208. package/sidebar-nav.d.ts +67 -0
  209. package/sidebar-nav.d.ts.map +1 -0
  210. package/sidebar-nav.js +291 -0
  211. package/sidebar-nav.js.map +1 -0
  212. package/signature-pad.d.ts +16 -0
  213. package/signature-pad.d.ts.map +1 -0
  214. package/signature-pad.js +355 -0
  215. package/signature-pad.js.map +1 -0
  216. package/skeleton.d.ts +9 -0
  217. package/skeleton.d.ts.map +1 -0
  218. package/skeleton.js +12 -0
  219. package/skeleton.js.map +1 -0
  220. package/slider.d.ts +13 -0
  221. package/slider.d.ts.map +1 -0
  222. package/slider.js +15 -0
  223. package/slider.js.map +1 -0
  224. package/sortable-th.d.ts +28 -0
  225. package/sortable-th.d.ts.map +1 -0
  226. package/sortable-th.js +35 -0
  227. package/sortable-th.js.map +1 -0
  228. package/sparkline.d.ts +16 -0
  229. package/sparkline.d.ts.map +1 -0
  230. package/sparkline.js +46 -0
  231. package/sparkline.js.map +1 -0
  232. package/spinner.d.ts +11 -0
  233. package/spinner.d.ts.map +1 -0
  234. package/spinner.js +11 -0
  235. package/spinner.js.map +1 -0
  236. package/styles.css +166 -0
  237. package/subtab-nav.d.ts +17 -0
  238. package/subtab-nav.d.ts.map +1 -0
  239. package/subtab-nav.js +11 -0
  240. package/subtab-nav.js.map +1 -0
  241. package/switch.d.ts +9 -0
  242. package/switch.d.ts.map +1 -0
  243. package/switch.js +11 -0
  244. package/switch.js.map +1 -0
  245. package/tab-content.d.ts +24 -0
  246. package/tab-content.d.ts.map +1 -0
  247. package/tab-content.js +26 -0
  248. package/tab-content.js.map +1 -0
  249. package/table.d.ts +28 -0
  250. package/table.d.ts.map +1 -0
  251. package/table.js +21 -0
  252. package/table.js.map +1 -0
  253. package/tabs.d.ts +18 -0
  254. package/tabs.d.ts.map +1 -0
  255. package/tabs.js +18 -0
  256. package/tabs.js.map +1 -0
  257. package/text-context.d.ts +9 -0
  258. package/text-context.d.ts.map +1 -0
  259. package/text-context.js +12 -0
  260. package/text-context.js.map +1 -0
  261. package/textarea.d.ts +4 -0
  262. package/textarea.d.ts.map +1 -0
  263. package/textarea.js +6 -0
  264. package/textarea.js.map +1 -0
  265. package/theme-script.d.ts +3 -0
  266. package/theme-script.d.ts.map +1 -0
  267. package/theme-script.js +6 -0
  268. package/theme-script.js.map +1 -0
  269. package/theme.d.ts +28 -0
  270. package/theme.d.ts.map +1 -0
  271. package/theme.js +92 -0
  272. package/theme.js.map +1 -0
  273. package/toast.d.ts +52 -0
  274. package/toast.d.ts.map +1 -0
  275. package/toast.js +178 -0
  276. package/toast.js.map +1 -0
  277. package/tooltip.d.ts +12 -0
  278. package/tooltip.d.ts.map +1 -0
  279. package/tooltip.js +14 -0
  280. package/tooltip.js.map +1 -0
  281. package/top-nav.d.ts +18 -0
  282. package/top-nav.d.ts.map +1 -0
  283. package/top-nav.js +160 -0
  284. package/top-nav.js.map +1 -0
  285. package/upload-limits.d.ts +4 -0
  286. package/upload-limits.d.ts.map +1 -0
  287. package/upload-limits.js +21 -0
  288. package/upload-limits.js.map +1 -0
  289. package/use-element-size.d.ts +7 -0
  290. package/use-element-size.d.ts.map +1 -0
  291. package/use-element-size.js +21 -0
  292. package/use-element-size.js.map +1 -0
  293. package/utils.d.ts +4 -0
  294. package/utils.d.ts.map +1 -0
  295. package/utils.js +7 -0
  296. package/utils.js.map +1 -0
  297. package/workspace-launcher.d.ts +50 -0
  298. package/workspace-launcher.d.ts.map +1 -0
  299. package/workspace-launcher.js +127 -0
  300. package/workspace-launcher.js.map +1 -0
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+ import { type BadgeProps } from './badge.js';
3
+ export type RecordCellKind = 'text' | 'amount' | 'status' | 'reference' | 'actions' | 'custom';
4
+ export type RecordColumn<Row> = {
5
+ key: string;
6
+ label: string;
7
+ kind?: RecordCellKind;
8
+ align?: 'left' | 'right';
9
+ sortable?: boolean;
10
+ width?: number | string;
11
+ /** Custom / override renderer. */
12
+ render?: (row: Row) => React.ReactNode;
13
+ /** For `reference` cells — the destination href. */
14
+ href?: (row: Row) => string;
15
+ /** For `amount`/`text` cells — format the raw value. */
16
+ format?: (value: unknown, row: Row) => React.ReactNode;
17
+ /** For `status` cells — map a value to a Badge variant. */
18
+ statusVariant?: (value: string) => BadgeProps['variant'];
19
+ };
20
+ export type RecordListLink = (props: {
21
+ href: string;
22
+ children: React.ReactNode;
23
+ className: string;
24
+ }) => React.ReactNode;
25
+ export type RecordListProps<Row> = {
26
+ columns: RecordColumn<Row>[];
27
+ rows: Row[];
28
+ getRowId: (row: Row) => string;
29
+ /** Root layout classes, including full-height flex layouts for workspaces. */
30
+ className?: string;
31
+ /** Classes for the underlying Table's scrolling/card shell. */
32
+ tableContainerClassName?: string;
33
+ /** Classes for the underlying table element. */
34
+ tableClassName?: string;
35
+ search?: {
36
+ value: string;
37
+ onChange: (v: string) => void;
38
+ placeholder?: string;
39
+ };
40
+ /** Filter chips or other toolbar-left content. */
41
+ filters?: React.ReactNode;
42
+ /** Toolbar-right content (e.g. a saved-views switcher). */
43
+ toolbarActions?: React.ReactNode;
44
+ sort?: {
45
+ key: string;
46
+ dir: 'asc' | 'desc';
47
+ };
48
+ onSortChange?: (key: string) => void;
49
+ pagination?: {
50
+ page: number;
51
+ perPage: number;
52
+ total: number;
53
+ onPageChange: (page: number) => void;
54
+ };
55
+ empty?: {
56
+ title: string;
57
+ description?: string;
58
+ action?: React.ReactNode;
59
+ icon?: React.ReactNode;
60
+ };
61
+ /** Supply an app router Link for reference cells (falls back to <a>). */
62
+ linkRender?: RecordListLink;
63
+ onRowClick?: (row: Row) => void;
64
+ /**
65
+ * The row whose record is open, or is being opened.
66
+ *
67
+ * Lists that open a record over themselves need this twice over: once so the
68
+ * list behind the drawer still says which of its rows the drawer belongs to,
69
+ * and once so a click has somewhere to land while the record loads. Without
70
+ * it a row click is silent until the record arrives, which on a slow record
71
+ * reads as a click that did nothing.
72
+ */
73
+ activeRowId?: string | null;
74
+ };
75
+ export declare function RecordList<Row>({ columns, rows, getRowId, className, tableContainerClassName, tableClassName, search, filters, toolbarActions, sort, onSortChange, pagination, empty, linkRender, onRowClick, activeRowId, }: RecordListProps<Row>): React.JSX.Element;
76
+ //# sourceMappingURL=record-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-list.d.ts","sourceRoot":"","sources":["../src/record-list.tsx"],"names":[],"mappings":"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"}
package/record-list.js ADDED
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Search } from 'lucide-react';
5
+ import { Badge } from './badge.js';
6
+ import { Button } from './button.js';
7
+ import { EmptyState } from './empty-state.js';
8
+ import { Input } from './input.js';
9
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from './table.js';
10
+ import { cn } from './utils.js';
11
+ export function RecordList({ columns, rows, getRowId, className, tableContainerClassName, tableClassName, search, filters, toolbarActions, sort, onSortChange, pagination, empty, linkRender, onRowClick, activeRowId, }) {
12
+ const renderLink = linkRender ??
13
+ (({ href, children, className }) => (_jsx("a", { href: href, className: className, children: children })));
14
+ const cell = (row, c) => {
15
+ const v = row[c.key];
16
+ const emptyText = _jsx("span", { className: "text-fg-subtle", children: "\u2014" });
17
+ if (c.render && c.kind !== 'actions')
18
+ return _jsx(TableCell, { children: c.render(row) }, c.key);
19
+ switch (c.kind) {
20
+ case 'reference':
21
+ return (_jsx(TableCell, { className: "font-medium", children: c.href && v != null && v !== ''
22
+ ? renderLink({
23
+ href: c.href(row),
24
+ className: 'text-primary hover:underline',
25
+ children: String(v),
26
+ })
27
+ : emptyText }, c.key));
28
+ case 'amount':
29
+ return (_jsx(TableCell, { className: "text-right tabular-nums", children: v == null || v === '' ? emptyText : (c.format ? c.format(v, row) : String(v)) }, c.key));
30
+ case 'status':
31
+ return (_jsx(TableCell, { children: _jsx(Badge, { variant: c.statusVariant?.(String(v)) ?? 'secondary', children: String(v) }) }, c.key));
32
+ case 'actions':
33
+ return (_jsx(TableCell, { className: "w-px whitespace-nowrap px-2 text-right", children: c.render ? c.render(row) : null }, c.key));
34
+ default:
35
+ return (_jsx(TableCell, { className: cn(c.align === 'right' && 'text-right'), children: v == null || v === '' ? emptyText : c.format ? c.format(v, row) : String(v) }, c.key));
36
+ }
37
+ };
38
+ const totalPages = pagination ? Math.max(1, Math.ceil(pagination.total / pagination.perPage)) : 1;
39
+ const from = pagination ? (pagination.page - 1) * pagination.perPage + 1 : 0;
40
+ const to = pagination ? Math.min(pagination.page * pagination.perPage, pagination.total) : 0;
41
+ return (_jsxs("div", { className: cn('space-y-3', className), children: [(search || filters || toolbarActions) ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [search ? (_jsxs("div", { className: "relative w-full sm:w-64", children: [_jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-fg-subtle" }), _jsx(Input, { value: search.value, onChange: (e) => search.onChange(e.target.value), placeholder: search.placeholder ?? 'Search…', className: "pl-9" })] })) : null, filters, _jsx("div", { className: "ml-auto flex items-center gap-2", children: toolbarActions })] })) : null, rows.length === 0 ? (_jsx(EmptyState, { icon: empty?.icon, title: empty?.title ?? 'Nothing here yet', description: empty?.description, action: empty?.action })) : (_jsxs(_Fragment, { children: [_jsxs(Table, { className: tableClassName, containerClassName: tableContainerClassName, children: [_jsx(TableHeader, { children: _jsx(TableRow, { noAnimate: true, children: columns.map((c) => {
42
+ const active = sort?.key === c.key;
43
+ const alignRight = c.align === 'right' || c.kind === 'amount';
44
+ return (_jsx(TableHead, { style: c.width ? { width: c.width } : undefined, className: cn(alignRight && 'text-right', c.kind === 'actions' && 'text-right'), children: c.sortable && onSortChange ? (_jsxs("button", { type: "button", onClick: () => onSortChange(c.key), className: cn('inline-flex items-center gap-1 uppercase tracking-wide transition-colors hover:text-fg', alignRight && 'flex-row-reverse', active && 'text-fg'), children: [c.label, active ? (sort.dir === 'asc' ? (_jsx(ArrowUp, { className: "size-3" })) : (_jsx(ArrowDown, { className: "size-3" }))) : null] })) : (c.label) }, c.key));
45
+ }) }) }), _jsx(TableBody, { children: rows.map((row) => {
46
+ const rowId = getRowId(row);
47
+ const active = activeRowId != null && activeRowId === rowId;
48
+ return (_jsx(TableRow, { "data-state": active ? 'selected' : undefined, "aria-selected": active || undefined, onClick: onRowClick ? () => onRowClick(row) : undefined, className: onRowClick ? 'cursor-pointer' : undefined, children: columns.map((c) => cell(row, c)) }, rowId));
49
+ }) })] }), pagination && pagination.total > pagination.perPage ? (_jsxs("div", { className: "flex items-center justify-between text-sm text-fg-muted", children: [_jsxs("span", { children: [from, "\u2013", to, " of ", pagination.total] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: pagination.page <= 1, onClick: () => pagination.onPageChange(pagination.page - 1), children: [_jsx(ChevronLeft, { className: "size-4" }), " Prev"] }), _jsxs("span", { className: "tabular-nums", children: [pagination.page, " / ", totalPages] }), _jsxs(Button, { variant: "outline", size: "sm", disabled: pagination.page >= totalPages, onClick: () => pagination.onPageChange(pagination.page + 1), children: ["Next ", _jsx(ChevronRight, { className: "size-4" })] })] })] })) : null] }))] }));
50
+ }
51
+ //# sourceMappingURL=record-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-list.js","sourceRoot":"","sources":["../src/record-list.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Search } from 'lucide-react'\nimport { Badge, type BadgeProps } from './badge'\nimport { Button } from './button'\nimport { EmptyState } from './empty-state'\nimport { Input } from './input'\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from './table'\nimport { cn } from './utils'\n\nexport type RecordCellKind = 'text' | 'amount' | 'status' | 'reference' | 'actions' | 'custom'\n\nexport type RecordColumn<Row> = {\n key: string\n label: string\n kind?: RecordCellKind\n align?: 'left' | 'right'\n sortable?: boolean\n width?: number | string\n /** Custom / override renderer. */\n render?: (row: Row) => React.ReactNode\n /** For `reference` cells — the destination href. */\n href?: (row: Row) => string\n /** For `amount`/`text` cells — format the raw value. */\n format?: (value: unknown, row: Row) => React.ReactNode\n /** For `status` cells — map a value to a Badge variant. */\n statusVariant?: (value: string) => BadgeProps['variant']\n}\n\nexport type RecordListLink = (props: {\n href: string\n children: React.ReactNode\n className: string\n}) => React.ReactNode\n\nexport type RecordListProps<Row> = {\n columns: RecordColumn<Row>[]\n rows: Row[]\n getRowId: (row: Row) => string\n /** Root layout classes, including full-height flex layouts for workspaces. */\n className?: string\n /** Classes for the underlying Table's scrolling/card shell. */\n tableContainerClassName?: string\n /** Classes for the underlying table element. */\n tableClassName?: string\n search?: { value: string; onChange: (v: string) => void; placeholder?: string }\n /** Filter chips or other toolbar-left content. */\n filters?: React.ReactNode\n /** Toolbar-right content (e.g. a saved-views switcher). */\n toolbarActions?: React.ReactNode\n sort?: { key: string; dir: 'asc' | 'desc' }\n onSortChange?: (key: string) => void\n pagination?: {\n page: number\n perPage: number\n total: number\n onPageChange: (page: number) => void\n }\n empty?: { title: string; description?: string; action?: React.ReactNode; icon?: React.ReactNode }\n /** Supply an app router Link for reference cells (falls back to <a>). */\n linkRender?: RecordListLink\n onRowClick?: (row: Row) => void\n /**\n * The row whose record is open, or is being opened.\n *\n * Lists that open a record over themselves need this twice over: once so the\n * list behind the drawer still says which of its rows the drawer belongs to,\n * and once so a click has somewhere to land while the record loads. Without\n * it a row click is silent until the record arrives, which on a slow record\n * reads as a click that did nothing.\n */\n activeRowId?: string | null\n}\n\nexport function RecordList<Row>({\n columns,\n rows,\n getRowId,\n className,\n tableContainerClassName,\n tableClassName,\n search,\n filters,\n toolbarActions,\n sort,\n onSortChange,\n pagination,\n empty,\n linkRender,\n onRowClick,\n activeRowId,\n}: RecordListProps<Row>) {\n const renderLink: RecordListLink =\n linkRender ??\n (({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ))\n\n const cell = (row: Row, c: RecordColumn<Row>) => {\n const v = (row as Record<string, unknown>)[c.key]\n const emptyText = <span className=\"text-fg-subtle\">—</span>\n if (c.render && c.kind !== 'actions')\n return <TableCell key={c.key}>{c.render(row)}</TableCell>\n switch (c.kind) {\n case 'reference':\n return (\n <TableCell key={c.key} className=\"font-medium\">\n {c.href && v != null && v !== ''\n ? renderLink({\n href: c.href(row),\n className: 'text-primary hover:underline',\n children: String(v),\n })\n : emptyText}\n </TableCell>\n )\n case 'amount':\n return (\n <TableCell key={c.key} className=\"text-right tabular-nums\">\n {v == null || v === '' ? emptyText : (c.format ? c.format(v, row) : String(v))}\n </TableCell>\n )\n case 'status':\n return (\n <TableCell key={c.key}>\n <Badge variant={c.statusVariant?.(String(v)) ?? 'secondary'}>{String(v)}</Badge>\n </TableCell>\n )\n case 'actions':\n return (\n <TableCell key={c.key} className=\"w-px whitespace-nowrap px-2 text-right\">\n {c.render ? c.render(row) : null}\n </TableCell>\n )\n default:\n return (\n <TableCell key={c.key} className={cn(c.align === 'right' && 'text-right')}>\n {v == null || v === '' ? emptyText : c.format ? c.format(v, row) : String(v)}\n </TableCell>\n )\n }\n }\n\n const totalPages = pagination ? Math.max(1, Math.ceil(pagination.total / pagination.perPage)) : 1\n const from = pagination ? (pagination.page - 1) * pagination.perPage + 1 : 0\n const to = pagination ? Math.min(pagination.page * pagination.perPage, pagination.total) : 0\n\n return (\n <div className={cn('space-y-3', className)}>\n {(search || filters || toolbarActions) ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n {search ? (\n <div className=\"relative w-full sm:w-64\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-fg-subtle\" />\n <Input\n value={search.value}\n onChange={(e) => search.onChange(e.target.value)}\n placeholder={search.placeholder ?? 'Search…'}\n className=\"pl-9\"\n />\n </div>\n ) : null}\n {filters}\n <div className=\"ml-auto flex items-center gap-2\">{toolbarActions}</div>\n </div>\n ) : null}\n\n {rows.length === 0 ? (\n <EmptyState\n icon={empty?.icon}\n title={empty?.title ?? 'Nothing here yet'}\n description={empty?.description}\n action={empty?.action}\n />\n ) : (\n <>\n <Table className={tableClassName} containerClassName={tableContainerClassName}>\n <TableHeader>\n <TableRow noAnimate>\n {columns.map((c) => {\n const active = sort?.key === c.key\n const alignRight = c.align === 'right' || c.kind === 'amount'\n return (\n <TableHead\n key={c.key}\n style={c.width ? { width: c.width } : undefined}\n className={cn(alignRight && 'text-right', c.kind === 'actions' && 'text-right')}\n >\n {c.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(c.key)}\n className={cn(\n 'inline-flex items-center gap-1 uppercase tracking-wide transition-colors hover:text-fg',\n alignRight && 'flex-row-reverse',\n active && 'text-fg',\n )}\n >\n {c.label}\n {active ? (\n sort!.dir === 'asc' ? (\n <ArrowUp className=\"size-3\" />\n ) : (\n <ArrowDown className=\"size-3\" />\n )\n ) : null}\n </button>\n ) : (\n c.label\n )}\n </TableHead>\n )\n })}\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((row) => {\n const rowId = getRowId(row)\n const active = activeRowId != null && activeRowId === rowId\n return (\n <TableRow\n key={rowId}\n // TableRow already dresses a selected row; this is the same\n // state, not a second look for it.\n data-state={active ? 'selected' : undefined}\n aria-selected={active || undefined}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={onRowClick ? 'cursor-pointer' : undefined}\n >\n {columns.map((c) => cell(row, c))}\n </TableRow>\n )\n })}\n </TableBody>\n </Table>\n\n {pagination && pagination.total > pagination.perPage ? (\n <div className=\"flex items-center justify-between text-sm text-fg-muted\">\n <span>\n {from}–{to} of {pagination.total}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={pagination.page <= 1}\n onClick={() => pagination.onPageChange(pagination.page - 1)}\n >\n <ChevronLeft className=\"size-4\" /> Prev\n </Button>\n <span className=\"tabular-nums\">\n {pagination.page} / {totalPages}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={pagination.page >= totalPages}\n onClick={() => pagination.onPageChange(pagination.page + 1)}\n >\n Next <ChevronRight className=\"size-4\" />\n </Button>\n </div>\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import * as React from 'react';
2
+ export type SelectOption = {
3
+ value: string;
4
+ label: string;
5
+ hint?: string;
6
+ disabled?: boolean;
7
+ /** Group header label; options sharing a group are batched under it. */
8
+ group?: string;
9
+ };
10
+ export type SearchSelectProps = {
11
+ value: string;
12
+ onChange: (value: string) => void;
13
+ options: SelectOption[];
14
+ placeholder?: string;
15
+ searchPlaceholder?: string;
16
+ disabled?: boolean;
17
+ /** Adds a leading empty option (e.g. "None"). */
18
+ clearable?: boolean;
19
+ emptyLabel?: string;
20
+ /** Title at the top of the mobile bottom sheet. */
21
+ sheetTitle?: string;
22
+ ariaLabel?: string;
23
+ className?: string;
24
+ triggerClassName?: string;
25
+ /** Force the search box on/off. Auto: shown when >7 options or any groups. */
26
+ searchable?: boolean;
27
+ /** Preserve server ordering + forward the query instead of local filtering. */
28
+ remote?: boolean;
29
+ loading?: boolean;
30
+ statusMessage?: string;
31
+ statusTone?: 'muted' | 'error';
32
+ onSearchChange?: (query: string) => void;
33
+ invalid?: boolean;
34
+ id?: string;
35
+ noneLabel?: string;
36
+ noMatchesLabel?: string;
37
+ searchingLabel?: string;
38
+ selectLabel?: string;
39
+ closeLabel?: string;
40
+ };
41
+ export declare function SearchSelect({ value, onChange, options, placeholder, searchPlaceholder, disabled, clearable, emptyLabel, sheetTitle, ariaLabel, className, triggerClassName, searchable, remote, loading, statusMessage, statusTone, onSearchChange, invalid, id, noneLabel, noMatchesLabel, searchingLabel, selectLabel, closeLabel, }: SearchSelectProps): React.JSX.Element;
42
+ //# sourceMappingURL=search-select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-select.d.ts","sourceRoot":"","sources":["../src/search-select.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IAC9B,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IAEX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAuB,EACvB,iBAA6B,EAC7B,QAAgB,EAChB,SAAiB,EACjB,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,MAAc,EACd,OAAe,EACf,aAAa,EACb,UAAoB,EACpB,cAAc,EACd,OAAe,EACf,EAAE,EACF,SAAkB,EAClB,cAA6B,EAC7B,cAA6B,EAC7B,WAAsB,EACtB,UAAoB,GACrB,EAAE,iBAAiB,qBAkVnB"}
@@ -0,0 +1,194 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ // SearchSelect — a slick, mobile-first searchable select / typeahead.
4
+ // • Desktop: anchored dropdown (portaled to <body>) with a search box + kbd nav.
5
+ // • Mobile (<lg): an iOS/Android-style bottom sheet with large tap targets.
6
+ // Animated, portal'd sheet, Esc + click-out + scroll-lock, disabled options,
7
+ // optgroup-style headers (via SelectOption.group), hints, clearable, remote mode.
8
+ // Fully tokenized; i18n strings are injectable props with English defaults.
9
+ import * as React from 'react';
10
+ import { createPortal } from 'react-dom';
11
+ import { AnimatePresence, motion } from 'framer-motion';
12
+ import { Check, ChevronDown, Search, X } from 'lucide-react';
13
+ import { cn } from './utils.js';
14
+ export function SearchSelect({ value, onChange, options, placeholder = 'Select…', searchPlaceholder = 'Search…', disabled = false, clearable = false, emptyLabel, sheetTitle, ariaLabel, className, triggerClassName, searchable, remote = false, loading = false, statusMessage, statusTone = 'muted', onSearchChange, invalid = false, id, noneLabel = 'None', noMatchesLabel = 'No matches', searchingLabel = 'Searching…', selectLabel = 'Select', closeLabel = 'Close', }) {
15
+ const [open, setOpen] = React.useState(false);
16
+ const [query, setQuery] = React.useState('');
17
+ const [highlight, setHighlight] = React.useState(0);
18
+ const [isDesktop, setIsDesktop] = React.useState(true);
19
+ const [mounted, setMounted] = React.useState(false);
20
+ const wrapRef = React.useRef(null);
21
+ const searchRef = React.useRef(null);
22
+ const triggerRef = React.useRef(null);
23
+ const dropRef = React.useRef(null);
24
+ // The menu is anchored from the edge that touches the trigger: `top` when it
25
+ // opens downward, `bottom` when it opens upward. Anchoring an upward menu by
26
+ // its top would place it a full maxHeight above the trigger no matter how
27
+ // short the list actually is — a three-option menu would float hundreds of
28
+ // pixels clear of the field it belongs to.
29
+ const [pos, setPos] = React.useState(null);
30
+ function place() {
31
+ const r = triggerRef.current?.getBoundingClientRect();
32
+ if (r) {
33
+ // Collision-aware: open upward when the space below is tight, and clamp
34
+ // the list to the available viewport space either way.
35
+ const margin = 8;
36
+ const below = window.innerHeight - r.bottom - margin;
37
+ const above = r.top - margin;
38
+ const desired = 320;
39
+ if (below >= Math.min(desired, 180) || below >= above) {
40
+ setPos({ edge: 'top', offset: r.bottom + 6, left: r.left, width: r.width, maxHeight: Math.min(desired, below) });
41
+ }
42
+ else {
43
+ setPos({
44
+ edge: 'bottom',
45
+ offset: Math.max(margin, window.innerHeight - r.top + 6),
46
+ left: r.left,
47
+ width: r.width,
48
+ maxHeight: Math.min(desired, above),
49
+ });
50
+ }
51
+ }
52
+ }
53
+ React.useEffect(() => {
54
+ setMounted(true);
55
+ const mq = window.matchMedia('(min-width: 1024px)');
56
+ const update = () => setIsDesktop(mq.matches);
57
+ update();
58
+ mq.addEventListener('change', update);
59
+ return () => mq.removeEventListener('change', update);
60
+ }, []);
61
+ const allOptions = React.useMemo(() => (clearable ? [{ value: '', label: emptyLabel ?? noneLabel }, ...options] : options), [clearable, emptyLabel, noneLabel, options]);
62
+ const selected = options.find((o) => o.value === value);
63
+ const showEmpty = clearable && value === '' && !!emptyLabel;
64
+ const display = selected?.label ?? (showEmpty ? emptyLabel : placeholder);
65
+ const isPlaceholder = !selected && !showEmpty;
66
+ const showSearch = searchable ?? (allOptions.length > 7 || allOptions.some((o) => o.group));
67
+ const filtered = React.useMemo(() => {
68
+ if (remote)
69
+ return allOptions;
70
+ const q = query.trim().toLowerCase();
71
+ if (!q)
72
+ return allOptions;
73
+ return allOptions.filter((o) => o.label.toLowerCase().includes(q) || o.group?.toLowerCase().includes(q));
74
+ }, [allOptions, query, remote]);
75
+ function firstEnabled(items, start) {
76
+ if (start >= 0 && start < items.length && !items[start]?.disabled)
77
+ return start;
78
+ const fwd = items.findIndex((o) => !o.disabled);
79
+ return fwd === -1 ? 0 : fwd;
80
+ }
81
+ function openMenu() {
82
+ if (disabled)
83
+ return;
84
+ setQuery('');
85
+ onSearchChange?.('');
86
+ setHighlight(firstEnabled(allOptions, allOptions.findIndex((o) => o.value === value)));
87
+ place();
88
+ setOpen(true);
89
+ setTimeout(() => searchRef.current?.focus(), 60);
90
+ }
91
+ function choose(v) {
92
+ onChange(v);
93
+ setOpen(false);
94
+ }
95
+ React.useEffect(() => {
96
+ if (!open || !isDesktop)
97
+ return;
98
+ function onDoc(e) {
99
+ const target = e.target;
100
+ if (wrapRef.current?.contains(target) || dropRef.current?.contains(target))
101
+ return;
102
+ setOpen(false);
103
+ }
104
+ document.addEventListener('mousedown', onDoc);
105
+ return () => document.removeEventListener('mousedown', onDoc);
106
+ }, [open, isDesktop]);
107
+ React.useEffect(() => {
108
+ if (!open || !isDesktop)
109
+ return;
110
+ place();
111
+ const onMove = () => place();
112
+ window.addEventListener('scroll', onMove, true);
113
+ window.addEventListener('resize', onMove);
114
+ return () => {
115
+ window.removeEventListener('scroll', onMove, true);
116
+ window.removeEventListener('resize', onMove);
117
+ };
118
+ }, [open, isDesktop]);
119
+ React.useEffect(() => {
120
+ if (!open)
121
+ return;
122
+ const nextEnabled = (from, dir) => {
123
+ let index = from;
124
+ while (true) {
125
+ index += dir;
126
+ if (index < 0 || index >= filtered.length)
127
+ return from;
128
+ if (!filtered[index]?.disabled)
129
+ return index;
130
+ }
131
+ };
132
+ function onKey(e) {
133
+ if (e.key === 'Escape')
134
+ setOpen(false);
135
+ else if (e.key === 'ArrowDown') {
136
+ e.preventDefault();
137
+ setHighlight((h) => nextEnabled(h, 1));
138
+ }
139
+ else if (e.key === 'ArrowUp') {
140
+ e.preventDefault();
141
+ setHighlight((h) => nextEnabled(h, -1));
142
+ }
143
+ else if (e.key === 'Enter') {
144
+ e.preventDefault();
145
+ const o = filtered[highlight];
146
+ if (o && !o.disabled) {
147
+ onChange(o.value);
148
+ setOpen(false);
149
+ }
150
+ }
151
+ }
152
+ document.addEventListener('keydown', onKey);
153
+ let restore;
154
+ if (!isDesktop) {
155
+ const prev = document.body.style.overflow;
156
+ document.body.style.overflow = 'hidden';
157
+ restore = () => {
158
+ document.body.style.overflow = prev;
159
+ };
160
+ }
161
+ return () => {
162
+ document.removeEventListener('keydown', onKey);
163
+ restore?.();
164
+ };
165
+ }, [filtered, highlight, isDesktop, onChange, open]);
166
+ const optionList = (_jsxs(_Fragment, { children: [_jsxs("ul", { role: "listbox", className: "py-1", children: [filtered.length === 0 && !loading ? (_jsx("li", { role: "option", "aria-disabled": "true", className: "px-3 py-8 text-center text-sm text-fg-subtle", children: noMatchesLabel })) : null, filtered.map((o, i) => {
167
+ const active = o.value === value;
168
+ const prevGroup = i > 0 ? filtered[i - 1]?.group : undefined;
169
+ const header = o.group && o.group !== prevGroup ? o.group : null;
170
+ return (_jsxs("li", { role: "presentation", children: [header ? (_jsx("div", { className: "px-4 pb-1 pt-2.5 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase lg:px-3", children: header })) : null, _jsxs("button", { type: "button", role: "option", "aria-selected": active, disabled: o.disabled, onMouseEnter: () => !o.disabled && setHighlight(i), onClick: () => !o.disabled && choose(o.value), className: cn('flex h-12 w-full items-center gap-2.5 px-4 text-left text-[15px] transition-colors lg:h-9 lg:px-3 lg:text-sm', o.disabled
171
+ ? 'cursor-not-allowed text-fg-subtle'
172
+ : i === highlight
173
+ ? 'bg-primary-subtle'
174
+ : 'active:bg-surface-hover', !o.disabled && (active ? 'font-medium text-primary' : 'text-fg')), children: [_jsxs("span", { className: "min-w-0 flex-1 truncate", children: [o.label, o.hint ? _jsx("span", { className: "ml-1.5 text-xs text-fg-subtle", children: o.hint }) : null] }), active ? _jsx(Check, { size: 17, className: "shrink-0 text-primary" }) : null] })] }, o.value || `__opt-${i}`));
175
+ })] }), loading || statusMessage ? (_jsx("div", { role: statusTone === 'error' ? 'alert' : 'status', className: cn('border-t border-border px-3 py-2 text-xs', statusTone === 'error' ? 'text-danger' : 'text-fg-muted'), children: loading ? searchingLabel : statusMessage })) : null] }));
176
+ const searchBox = (largeText) => (_jsxs("div", { className: "relative px-3 pt-3", children: [_jsx(Search, { size: 16, className: "absolute left-6 top-1/2 -translate-y-1/2 text-fg-subtle" }), _jsx("input", { ref: searchRef, value: query, onChange: (e) => {
177
+ const next = e.target.value;
178
+ setQuery(next);
179
+ onSearchChange?.(next);
180
+ setHighlight(0);
181
+ }, placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-busy": loading || undefined, className: cn('w-full rounded-lg border border-border bg-bg-subtle pl-9 pr-3 outline-none transition focus:border-primary focus:bg-surface focus:ring-2 focus:ring-ring/20', largeText ? 'h-11 text-base' : 'h-9 text-base sm:text-sm') })] }));
182
+ return (_jsxs("div", { ref: wrapRef, className: cn('relative', className), children: [_jsxs("button", { ref: triggerRef, type: "button", id: id, onClick: () => (open ? setOpen(false) : openMenu()), disabled: disabled, "aria-label": ariaLabel, "aria-haspopup": "listbox", "aria-expanded": open, "aria-invalid": invalid || undefined, className: cn('flex h-10 w-full items-center gap-2 rounded-lg border border-border bg-surface px-3 text-left text-sm shadow-sm transition', 'focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/25', disabled && 'cursor-not-allowed bg-bg-subtle opacity-70', open && 'border-primary ring-2 ring-ring/25', invalid && 'border-danger focus:border-danger focus:ring-danger/30', triggerClassName), children: [_jsx("span", { className: cn('min-w-0 flex-1 truncate', isPlaceholder ? 'text-fg-subtle' : 'text-fg'), children: display }), _jsx(ChevronDown, { size: 16, className: cn('shrink-0 text-fg-subtle transition-transform', open && 'rotate-180') })] }), mounted && open && isDesktop && pos
183
+ ? createPortal(_jsxs("div", { ref: dropRef, "data-ui-overlay": true, style: {
184
+ position: 'fixed',
185
+ [pos.edge]: pos.offset,
186
+ left: pos.left,
187
+ width: Math.max(pos.width, 208),
188
+ maxHeight: pos.maxHeight,
189
+ }, className: "z-[60] flex flex-col overflow-hidden rounded-xl border border-border bg-elevated shadow-lg", children: [showSearch ? searchBox(false) : null, _jsx("div", { className: cn('min-h-0 flex-1 overflow-y-auto', showSearch && 'mt-1'), children: optionList })] }), document.body)
190
+ : null, mounted && !isDesktop
191
+ ? createPortal(_jsx(AnimatePresence, { children: open ? (_jsxs("div", { "data-ui-overlay": true, className: "fixed inset-0 z-[60]", children: [_jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "absolute inset-0 bg-overlay/40 backdrop-blur-[2px]", onClick: () => setOpen(false) }), _jsxs(motion.div, { initial: { y: '100%' }, animate: { y: 0 }, exit: { y: '100%' }, transition: { type: 'spring', damping: 34, stiffness: 340, mass: 0.8 }, className: "absolute inset-x-0 bottom-0 flex max-h-[82vh] flex-col rounded-t-2xl border-t border-border bg-surface shadow-lg", children: [_jsx("div", { className: "flex items-center justify-center pt-2.5", children: _jsx("span", { className: "h-1.5 w-10 rounded-full bg-border-strong" }) }), _jsxs("div", { className: "flex items-center justify-between px-4 pb-1 pt-2", children: [_jsx("span", { className: "text-base font-semibold text-fg", children: sheetTitle ?? selectLabel }), _jsx("button", { type: "button", onClick: () => setOpen(false), "aria-label": closeLabel, className: "rounded-md p-1.5 text-fg-muted hover:bg-surface-hover", children: _jsx(X, { size: 18 }) })] }), showSearch ? searchBox(true) : null, _jsx("div", { className: "mt-1 min-h-0 flex-1 overflow-y-auto pb-[max(0.75rem,env(safe-area-inset-bottom))]", children: optionList })] })] })) : null }), document.body)
192
+ : null] }));
193
+ }
194
+ //# sourceMappingURL=search-select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-select.js","sourceRoot":"","sources":["../src/search-select.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,sEAAsE;AACtE,mFAAmF;AACnF,8EAA8E;AAC9E,6EAA6E;AAC7E,kFAAkF;AAClF,4EAA4E;AAE5E,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AA4C5B,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAW,GAAG,SAAS,EACvB,iBAAiB,GAAG,SAAS,EAC7B,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,KAAK,EACjB,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,MAAM,GAAG,KAAK,EACd,OAAO,GAAG,KAAK,EACf,aAAa,EACb,UAAU,GAAG,OAAO,EACpB,cAAc,EACd,OAAO,GAAG,KAAK,EACf,EAAE,EACF,SAAS,GAAG,MAAM,EAClB,cAAc,GAAG,YAAY,EAC7B,cAAc,GAAG,YAAY,EAC7B,WAAW,GAAG,QAAQ,EACtB,UAAU,GAAG,OAAO,GACF;IAClB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC5C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;IACnD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACtD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAiB,IAAI,CAAC,CAAA;IAClD,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAmB,IAAI,CAAC,CAAA;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAoB,IAAI,CAAC,CAAA;IACxD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAiB,IAAI,CAAC,CAAA;IAClD,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,2EAA2E;IAC3E,2CAA2C;IAC3C,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAIlC,IAAI,CAAC,CAAA;IACP,SAAS,KAAK;QACZ,MAAM,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAA;QACrD,IAAI,CAAC,EAAE,CAAC;YACN,wEAAwE;YACxE,uDAAuD;YACvD,MAAM,MAAM,GAAG,CAAC,CAAA;YAChB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC,MAAM,GAAG,MAAM,CAAA;YACpD,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,MAAM,CAAA;YAC5B,MAAM,OAAO,GAAG,GAAG,CAAA;YACnB,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;gBACtD,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;YAClH,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC;oBACL,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;oBACxD,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;iBACpC,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAA;QACnD,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,EAAE,CAAA;QACR,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACrC,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACvD,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAC9B,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,IAAI,SAAS,EAAE,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EACzF,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAC5C,CAAA;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAA;IACvD,MAAM,SAAS,GAAG,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,CAAC,UAAU,CAAA;IAC3D,MAAM,OAAO,GAAG,QAAQ,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IACzE,MAAM,aAAa,GAAG,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAA;IAE7C,MAAM,UAAU,GAAG,UAAU,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;IAE3F,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAClC,IAAI,MAAM;YAAE,OAAO,UAAU,CAAA;QAC7B,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACpC,IAAI,CAAC,CAAC;YAAE,OAAO,UAAU,CAAA;QACzB,OAAO,UAAU,CAAC,MAAM,CACtB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC/E,CAAA;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAE/B,SAAS,YAAY,CAAC,KAAqB,EAAE,KAAa;QACxD,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,QAAQ;YAAE,OAAO,KAAK,CAAA;QAC/E,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;QAC/C,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC7B,CAAC;IAED,SAAS,QAAQ;QACf,IAAI,QAAQ;YAAE,OAAM;QACpB,QAAQ,CAAC,EAAE,CAAC,CAAA;QACZ,cAAc,EAAE,CAAC,EAAE,CAAC,CAAA;QACpB,YAAY,CAAC,YAAY,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CAAA;QACtF,KAAK,EAAE,CAAA;QACP,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;IAClD,CAAC;IACD,SAAS,MAAM,CAAC,CAAS;QACvB,QAAQ,CAAC,CAAC,CAAC,CAAA;QACX,OAAO,CAAC,KAAK,CAAC,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QAC/B,SAAS,KAAK,CAAC,CAAa;YAC1B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAc,CAAA;YAC/B,IAAI,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC;gBAAE,OAAM;YAClF,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QAC7C,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;IAC/D,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;IAErB,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QAC/B,KAAK,EAAE,CAAA;QACP,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,EAAE,CAAA;QAC5B,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACzC,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;YAClD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC9C,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;IAErB,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,EAAE;YAChD,IAAI,KAAK,GAAG,IAAI,CAAA;YAChB,OAAO,IAAI,EAAE,CAAC;gBACZ,KAAK,IAAI,GAAG,CAAA;gBACZ,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ,CAAC,MAAM;oBAAE,OAAO,IAAI,CAAA;gBACtD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ;oBAAE,OAAO,KAAK,CAAA;YAC9C,CAAC;QACH,CAAC,CAAA;QACD,SAAS,KAAK,CAAC,CAAgB;YAC7B,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAA;iBACjC,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;gBAC/B,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YACxC,CAAC;iBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;gBAC/B,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YACzC,CAAC;iBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;gBAC7B,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,MAAM,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAA;gBAC7B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;oBACrB,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;oBACjB,OAAO,CAAC,KAAK,CAAC,CAAA;gBAChB,CAAC;YACH,CAAC;QACH,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC3C,IAAI,OAAiC,CAAA;QACrC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;YACzC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;YACvC,OAAO,GAAG,GAAG,EAAE;gBACb,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;YACrC,CAAC,CAAA;QACH,CAAC;QACD,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YAC9C,OAAO,EAAE,EAAE,CAAA;QACb,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAA;IAEpD,MAAM,UAAU,GAAG,CACjB,8BACE,cAAI,IAAI,EAAC,SAAS,EAAC,SAAS,EAAC,MAAM,aAChC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CACnC,aAAI,IAAI,EAAC,QAAQ,mBAAe,MAAM,EAAC,SAAS,EAAC,8CAA8C,YAC5F,cAAc,GACZ,CACN,CAAC,CAAC,CAAC,IAAI,EACP,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;wBACrB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAA;wBAChC,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;wBAC5D,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;wBAChE,OAAO,CACL,cAAkC,IAAI,EAAC,cAAc,aAClD,MAAM,CAAC,CAAC,CAAC,CACR,cAAK,SAAS,EAAC,2FAA2F,YACvG,MAAM,GACH,CACP,CAAC,CAAC,CAAC,IAAI,EACR,kBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,QAAQ,mBACE,MAAM,EACrB,QAAQ,EAAE,CAAC,CAAC,QAAQ,EACpB,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,EAClD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAC7C,SAAS,EAAE,EAAE,CACX,8GAA8G,EAC9G,CAAC,CAAC,QAAQ;wCACR,CAAC,CAAC,mCAAmC;wCACrC,CAAC,CAAC,CAAC,KAAK,SAAS;4CACf,CAAC,CAAC,mBAAmB;4CACrB,CAAC,CAAC,yBAAyB,EAC/B,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,SAAS,CAAC,CACjE,aAED,gBAAM,SAAS,EAAC,yBAAyB,aACtC,CAAC,CAAC,KAAK,EACP,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,+BAA+B,YAAE,CAAC,CAAC,IAAI,GAAQ,CAAC,CAAC,CAAC,IAAI,IAC3E,EACN,MAAM,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,uBAAuB,GAAG,CAAC,CAAC,CAAC,IAAI,IAC/D,KA5BF,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,EAAE,CA6B3B,CACN,CAAA;oBACH,CAAC,CAAC,IACC,EACJ,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,CAC1B,cACE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EACjD,SAAS,EAAE,EAAE,CACX,0CAA0C,EAC1C,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CACzD,YAEA,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,GACrC,CACP,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;IAED,MAAM,SAAS,GAAG,CAAC,SAAkB,EAAE,EAAE,CAAC,CACxC,eAAK,SAAS,EAAC,oBAAoB,aACjC,KAAC,MAAM,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,yDAAyD,GAAG,EACxF,gBACE,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;oBACd,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAA;oBAC3B,QAAQ,CAAC,IAAI,CAAC,CAAA;oBACd,cAAc,EAAE,CAAC,IAAI,CAAC,CAAA;oBACtB,YAAY,CAAC,CAAC,CAAC,CAAA;gBACjB,CAAC,EACD,WAAW,EAAE,iBAAiB,gBAClB,iBAAiB,eAClB,OAAO,IAAI,SAAS,EAC/B,SAAS,EAAE,EAAE,CACX,6JAA6J,EAC7J,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,0BAA0B,CAC1D,GACD,IACE,CACP,CAAA;IAED,OAAO,CACL,eAAK,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,aACrD,kBACE,GAAG,EAAE,UAAU,EACf,IAAI,EAAC,QAAQ,EACb,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,EACnD,QAAQ,EAAE,QAAQ,gBACN,SAAS,mBACP,SAAS,mBACR,IAAI,kBACL,OAAO,IAAI,SAAS,EAClC,SAAS,EAAE,EAAE,CACX,4HAA4H,EAC5H,yEAAyE,EACzE,QAAQ,IAAI,4CAA4C,EACxD,IAAI,IAAI,oCAAoC,EAC5C,OAAO,IAAI,wDAAwD,EACnE,gBAAgB,CACjB,aAED,eAAM,SAAS,EAAE,EAAE,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,YACzF,OAAO,GACH,EACP,KAAC,WAAW,IACV,IAAI,EAAE,EAAE,EACR,SAAS,EAAE,EAAE,CAAC,8CAA8C,EAAE,IAAI,IAAI,YAAY,CAAC,GACnF,IACK,EAER,OAAO,IAAI,IAAI,IAAI,SAAS,IAAI,GAAG;gBAClC,CAAC,CAAC,YAAY,CACV,eACE,GAAG,EAAE,OAAO,2BAEZ,KAAK,EAAE;wBACL,QAAQ,EAAE,OAAO;wBACjB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM;wBACtB,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;wBAC/B,SAAS,EAAE,GAAG,CAAC,SAAS;qBACzB,EACD,SAAS,EAAC,4FAA4F,aAErG,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EACrC,cAAK,SAAS,EAAE,EAAE,CAAC,gCAAgC,EAAE,UAAU,IAAI,MAAM,CAAC,YAAG,UAAU,GAAO,IAC1F,EACN,QAAQ,CAAC,IAAI,CACd;gBACH,CAAC,CAAC,IAAI,EAEP,OAAO,IAAI,CAAC,SAAS;gBACpB,CAAC,CAAC,YAAY,CACV,KAAC,eAAe,cACb,IAAI,CAAC,CAAC,CAAC,CACN,wCAAqB,SAAS,EAAC,sBAAsB,aACnD,KAAC,MAAM,CAAC,GAAG,IACT,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACpB,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAC9B,SAAS,EAAC,oDAAoD,EAC9D,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAC7B,EACF,MAAC,MAAM,CAAC,GAAG,IACT,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,EACtB,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EACjB,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,EACnB,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EACtE,SAAS,EAAC,kHAAkH,aAE5H,cAAK,SAAS,EAAC,yCAAyC,YACtD,eAAM,SAAS,EAAC,0CAA0C,GAAG,GACzD,EACN,eAAK,SAAS,EAAC,kDAAkD,aAC/D,eAAM,SAAS,EAAC,iCAAiC,YAAE,UAAU,IAAI,WAAW,GAAQ,EACpF,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,gBACjB,UAAU,EACtB,SAAS,EAAC,uDAAuD,YAEjE,KAAC,CAAC,IAAC,IAAI,EAAE,EAAE,GAAI,GACR,IACL,EACL,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EACpC,cAAK,SAAS,EAAC,mFAAmF,YAC/F,UAAU,GACP,IACK,IACT,CACP,CAAC,CAAC,CAAC,IAAI,GACQ,EAClB,QAAQ,CAAC,IAAI,CACd;gBACH,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\n// SearchSelect — a slick, mobile-first searchable select / typeahead.\n// • Desktop: anchored dropdown (portaled to <body>) with a search box + kbd nav.\n// • Mobile (<lg): an iOS/Android-style bottom sheet with large tap targets.\n// Animated, portal'd sheet, Esc + click-out + scroll-lock, disabled options,\n// optgroup-style headers (via SelectOption.group), hints, clearable, remote mode.\n// Fully tokenized; i18n strings are injectable props with English defaults.\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport { Check, ChevronDown, Search, X } from 'lucide-react'\nimport { cn } from './utils'\n\nexport type SelectOption = {\n value: string\n label: string\n hint?: string\n disabled?: boolean\n /** Group header label; options sharing a group are batched under it. */\n group?: string\n}\n\nexport type SearchSelectProps = {\n value: string\n onChange: (value: string) => void\n options: SelectOption[]\n placeholder?: string\n searchPlaceholder?: string\n disabled?: boolean\n /** Adds a leading empty option (e.g. \"None\"). */\n clearable?: boolean\n emptyLabel?: string\n /** Title at the top of the mobile bottom sheet. */\n sheetTitle?: string\n ariaLabel?: string\n className?: string\n triggerClassName?: string\n /** Force the search box on/off. Auto: shown when >7 options or any groups. */\n searchable?: boolean\n /** Preserve server ordering + forward the query instead of local filtering. */\n remote?: boolean\n loading?: boolean\n statusMessage?: string\n statusTone?: 'muted' | 'error'\n onSearchChange?: (query: string) => void\n invalid?: boolean\n id?: string\n // Injectable labels (default English).\n noneLabel?: string\n noMatchesLabel?: string\n searchingLabel?: string\n selectLabel?: string\n closeLabel?: string\n}\n\nexport function SearchSelect({\n value,\n onChange,\n options,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n disabled = false,\n clearable = false,\n emptyLabel,\n sheetTitle,\n ariaLabel,\n className,\n triggerClassName,\n searchable,\n remote = false,\n loading = false,\n statusMessage,\n statusTone = 'muted',\n onSearchChange,\n invalid = false,\n id,\n noneLabel = 'None',\n noMatchesLabel = 'No matches',\n searchingLabel = 'Searching…',\n selectLabel = 'Select',\n closeLabel = 'Close',\n}: SearchSelectProps) {\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [highlight, setHighlight] = React.useState(0)\n const [isDesktop, setIsDesktop] = React.useState(true)\n const [mounted, setMounted] = React.useState(false)\n const wrapRef = React.useRef<HTMLDivElement>(null)\n const searchRef = React.useRef<HTMLInputElement>(null)\n const triggerRef = React.useRef<HTMLButtonElement>(null)\n const dropRef = React.useRef<HTMLDivElement>(null)\n // The menu is anchored from the edge that touches the trigger: `top` when it\n // opens downward, `bottom` when it opens upward. Anchoring an upward menu by\n // its top would place it a full maxHeight above the trigger no matter how\n // short the list actually is — a three-option menu would float hundreds of\n // pixels clear of the field it belongs to.\n const [pos, setPos] = React.useState<\n | { edge: 'top'; offset: number; left: number; width: number; maxHeight: number }\n | { edge: 'bottom'; offset: number; left: number; width: number; maxHeight: number }\n | null\n >(null)\n function place() {\n const r = triggerRef.current?.getBoundingClientRect()\n if (r) {\n // Collision-aware: open upward when the space below is tight, and clamp\n // the list to the available viewport space either way.\n const margin = 8\n const below = window.innerHeight - r.bottom - margin\n const above = r.top - margin\n const desired = 320\n if (below >= Math.min(desired, 180) || below >= above) {\n setPos({ edge: 'top', offset: r.bottom + 6, left: r.left, width: r.width, maxHeight: Math.min(desired, below) })\n } else {\n setPos({\n edge: 'bottom',\n offset: Math.max(margin, window.innerHeight - r.top + 6),\n left: r.left,\n width: r.width,\n maxHeight: Math.min(desired, above),\n })\n }\n }\n }\n\n React.useEffect(() => {\n setMounted(true)\n const mq = window.matchMedia('(min-width: 1024px)')\n const update = () => setIsDesktop(mq.matches)\n update()\n mq.addEventListener('change', update)\n return () => mq.removeEventListener('change', update)\n }, [])\n\n const allOptions = React.useMemo(\n () => (clearable ? [{ value: '', label: emptyLabel ?? noneLabel }, ...options] : options),\n [clearable, emptyLabel, noneLabel, options],\n )\n const selected = options.find((o) => o.value === value)\n const showEmpty = clearable && value === '' && !!emptyLabel\n const display = selected?.label ?? (showEmpty ? emptyLabel : placeholder)\n const isPlaceholder = !selected && !showEmpty\n\n const showSearch = searchable ?? (allOptions.length > 7 || allOptions.some((o) => o.group))\n\n const filtered = React.useMemo(() => {\n if (remote) return allOptions\n const q = query.trim().toLowerCase()\n if (!q) return allOptions\n return allOptions.filter(\n (o) => o.label.toLowerCase().includes(q) || o.group?.toLowerCase().includes(q),\n )\n }, [allOptions, query, remote])\n\n function firstEnabled(items: SelectOption[], start: number) {\n if (start >= 0 && start < items.length && !items[start]?.disabled) return start\n const fwd = items.findIndex((o) => !o.disabled)\n return fwd === -1 ? 0 : fwd\n }\n\n function openMenu() {\n if (disabled) return\n setQuery('')\n onSearchChange?.('')\n setHighlight(firstEnabled(allOptions, allOptions.findIndex((o) => o.value === value)))\n place()\n setOpen(true)\n setTimeout(() => searchRef.current?.focus(), 60)\n }\n function choose(v: string) {\n onChange(v)\n setOpen(false)\n }\n\n React.useEffect(() => {\n if (!open || !isDesktop) return\n function onDoc(e: MouseEvent) {\n const target = e.target as Node\n if (wrapRef.current?.contains(target) || dropRef.current?.contains(target)) return\n setOpen(false)\n }\n document.addEventListener('mousedown', onDoc)\n return () => document.removeEventListener('mousedown', onDoc)\n }, [open, isDesktop])\n\n React.useEffect(() => {\n if (!open || !isDesktop) return\n place()\n const onMove = () => place()\n window.addEventListener('scroll', onMove, true)\n window.addEventListener('resize', onMove)\n return () => {\n window.removeEventListener('scroll', onMove, true)\n window.removeEventListener('resize', onMove)\n }\n }, [open, isDesktop])\n\n React.useEffect(() => {\n if (!open) return\n const nextEnabled = (from: number, dir: 1 | -1) => {\n let index = from\n while (true) {\n index += dir\n if (index < 0 || index >= filtered.length) return from\n if (!filtered[index]?.disabled) return index\n }\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false)\n else if (e.key === 'ArrowDown') {\n e.preventDefault()\n setHighlight((h) => nextEnabled(h, 1))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setHighlight((h) => nextEnabled(h, -1))\n } else if (e.key === 'Enter') {\n e.preventDefault()\n const o = filtered[highlight]\n if (o && !o.disabled) {\n onChange(o.value)\n setOpen(false)\n }\n }\n }\n document.addEventListener('keydown', onKey)\n let restore: (() => void) | undefined\n if (!isDesktop) {\n const prev = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n restore = () => {\n document.body.style.overflow = prev\n }\n }\n return () => {\n document.removeEventListener('keydown', onKey)\n restore?.()\n }\n }, [filtered, highlight, isDesktop, onChange, open])\n\n const optionList = (\n <>\n <ul role=\"listbox\" className=\"py-1\">\n {filtered.length === 0 && !loading ? (\n <li role=\"option\" aria-disabled=\"true\" className=\"px-3 py-8 text-center text-sm text-fg-subtle\">\n {noMatchesLabel}\n </li>\n ) : null}\n {filtered.map((o, i) => {\n const active = o.value === value\n const prevGroup = i > 0 ? filtered[i - 1]?.group : undefined\n const header = o.group && o.group !== prevGroup ? o.group : null\n return (\n <li key={o.value || `__opt-${i}`} role=\"presentation\">\n {header ? (\n <div className=\"px-4 pb-1 pt-2.5 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase lg:px-3\">\n {header}\n </div>\n ) : null}\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n disabled={o.disabled}\n onMouseEnter={() => !o.disabled && setHighlight(i)}\n onClick={() => !o.disabled && choose(o.value)}\n className={cn(\n 'flex h-12 w-full items-center gap-2.5 px-4 text-left text-[15px] transition-colors lg:h-9 lg:px-3 lg:text-sm',\n o.disabled\n ? 'cursor-not-allowed text-fg-subtle'\n : i === highlight\n ? 'bg-primary-subtle'\n : 'active:bg-surface-hover',\n !o.disabled && (active ? 'font-medium text-primary' : 'text-fg'),\n )}\n >\n <span className=\"min-w-0 flex-1 truncate\">\n {o.label}\n {o.hint ? <span className=\"ml-1.5 text-xs text-fg-subtle\">{o.hint}</span> : null}\n </span>\n {active ? <Check size={17} className=\"shrink-0 text-primary\" /> : null}\n </button>\n </li>\n )\n })}\n </ul>\n {loading || statusMessage ? (\n <div\n role={statusTone === 'error' ? 'alert' : 'status'}\n className={cn(\n 'border-t border-border px-3 py-2 text-xs',\n statusTone === 'error' ? 'text-danger' : 'text-fg-muted',\n )}\n >\n {loading ? searchingLabel : statusMessage}\n </div>\n ) : null}\n </>\n )\n\n const searchBox = (largeText: boolean) => (\n <div className=\"relative px-3 pt-3\">\n <Search size={16} className=\"absolute left-6 top-1/2 -translate-y-1/2 text-fg-subtle\" />\n <input\n ref={searchRef}\n value={query}\n onChange={(e) => {\n const next = e.target.value\n setQuery(next)\n onSearchChange?.(next)\n setHighlight(0)\n }}\n placeholder={searchPlaceholder}\n aria-label={searchPlaceholder}\n aria-busy={loading || undefined}\n className={cn(\n 'w-full rounded-lg border border-border bg-bg-subtle pl-9 pr-3 outline-none transition focus:border-primary focus:bg-surface focus:ring-2 focus:ring-ring/20',\n largeText ? 'h-11 text-base' : 'h-9 text-base sm:text-sm',\n )}\n />\n </div>\n )\n\n return (\n <div ref={wrapRef} className={cn('relative', className)}>\n <button\n ref={triggerRef}\n type=\"button\"\n id={id}\n onClick={() => (open ? setOpen(false) : openMenu())}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-invalid={invalid || undefined}\n className={cn(\n 'flex h-10 w-full items-center gap-2 rounded-lg border border-border bg-surface px-3 text-left text-sm shadow-sm transition',\n 'focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/25',\n disabled && 'cursor-not-allowed bg-bg-subtle opacity-70',\n open && 'border-primary ring-2 ring-ring/25',\n invalid && 'border-danger focus:border-danger focus:ring-danger/30',\n triggerClassName,\n )}\n >\n <span className={cn('min-w-0 flex-1 truncate', isPlaceholder ? 'text-fg-subtle' : 'text-fg')}>\n {display}\n </span>\n <ChevronDown\n size={16}\n className={cn('shrink-0 text-fg-subtle transition-transform', open && 'rotate-180')}\n />\n </button>\n\n {mounted && open && isDesktop && pos\n ? createPortal(\n <div\n ref={dropRef}\n data-ui-overlay\n style={{\n position: 'fixed',\n [pos.edge]: pos.offset,\n left: pos.left,\n width: Math.max(pos.width, 208),\n maxHeight: pos.maxHeight,\n }}\n className=\"z-[60] flex flex-col overflow-hidden rounded-xl border border-border bg-elevated shadow-lg\"\n >\n {showSearch ? searchBox(false) : null}\n <div className={cn('min-h-0 flex-1 overflow-y-auto', showSearch && 'mt-1')}>{optionList}</div>\n </div>,\n document.body,\n )\n : null}\n\n {mounted && !isDesktop\n ? createPortal(\n <AnimatePresence>\n {open ? (\n <div data-ui-overlay className=\"fixed inset-0 z-[60]\">\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n className=\"absolute inset-0 bg-overlay/40 backdrop-blur-[2px]\"\n onClick={() => setOpen(false)}\n />\n <motion.div\n initial={{ y: '100%' }}\n animate={{ y: 0 }}\n exit={{ y: '100%' }}\n transition={{ type: 'spring', damping: 34, stiffness: 340, mass: 0.8 }}\n className=\"absolute inset-x-0 bottom-0 flex max-h-[82vh] flex-col rounded-t-2xl border-t border-border bg-surface shadow-lg\"\n >\n <div className=\"flex items-center justify-center pt-2.5\">\n <span className=\"h-1.5 w-10 rounded-full bg-border-strong\" />\n </div>\n <div className=\"flex items-center justify-between px-4 pb-1 pt-2\">\n <span className=\"text-base font-semibold text-fg\">{sheetTitle ?? selectLabel}</span>\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n aria-label={closeLabel}\n className=\"rounded-md p-1.5 text-fg-muted hover:bg-surface-hover\"\n >\n <X size={18} />\n </button>\n </div>\n {showSearch ? searchBox(true) : null}\n <div className=\"mt-1 min-h-0 flex-1 overflow-y-auto pb-[max(0.75rem,env(safe-area-inset-bottom))]\">\n {optionList}\n </div>\n </motion.div>\n </div>\n ) : null}\n </AnimatePresence>,\n document.body,\n )\n : null}\n </div>\n )\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import type { SelectOption } from './search-select.js';
3
+ type ParsedSelectChildren = {
4
+ options: SelectOption[];
5
+ placeholder?: string;
6
+ clearable: boolean;
7
+ emptyLabel?: string;
8
+ };
9
+ export declare function selectChildrenEqual(left: ParsedSelectChildren, right: ParsedSelectChildren): boolean;
10
+ /** Read the resolved option tree after React components have rendered inside
11
+ * the hidden native select. This covers option-producing components, which
12
+ * cannot be evaluated safely while parsing the original ReactNode tree. */
13
+ export declare function parseNativeSelect(select: HTMLSelectElement): ParsedSelectChildren;
14
+ export declare function parseSelectChildren(children: React.ReactNode): ParsedSelectChildren;
15
+ export {};
16
+ //# sourceMappingURL=select-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-options.d.ts","sourceRoot":"","sources":["../src/select-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAgBT;AAED;;2EAE2E;AAC3E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,oBAAoB,CA6BjF;AAmBD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,GAAG,oBAAoB,CAuDnF"}