@milktop/react-ui 0.0.0-stage → 0.1.2

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 (250) hide show
  1. package/README.md +173 -2
  2. package/dist/accordion/Accordion.d.ts +65 -0
  3. package/dist/accordion/Accordion.js +61 -0
  4. package/dist/accordion/accordion.css +17 -0
  5. package/dist/action-bar/ActionBar.d.ts +33 -0
  6. package/dist/action-bar/ActionBar.js +48 -0
  7. package/dist/action-bar/action-bar.css +22 -0
  8. package/dist/alert/Alert.d.ts +28 -0
  9. package/dist/alert/Alert.js +12 -0
  10. package/dist/alert/alert.css +32 -0
  11. package/dist/app-shell/AppShell.d.ts +38 -0
  12. package/dist/app-shell/AppShell.js +92 -0
  13. package/dist/app-shell/Topbar.d.ts +20 -0
  14. package/dist/app-shell/Topbar.js +13 -0
  15. package/dist/app-shell/app-shell.css +41 -0
  16. package/dist/app-shell/shell.d.ts +9 -0
  17. package/dist/app-shell/shell.js +3 -0
  18. package/dist/attachments/Attachments.d.ts +84 -0
  19. package/dist/attachments/Attachments.js +235 -0
  20. package/dist/attachments/attachments.css +86 -0
  21. package/dist/avatar/Avatar.d.ts +31 -0
  22. package/dist/avatar/Avatar.js +41 -0
  23. package/dist/avatar/AvatarGroup.d.ts +41 -0
  24. package/dist/avatar/AvatarGroup.js +17 -0
  25. package/dist/avatar/avatar.css +73 -0
  26. package/dist/badge/Badge.d.ts +26 -0
  27. package/dist/badge/Badge.js +10 -0
  28. package/dist/badge/badge.css +24 -0
  29. package/dist/banner/Banner.d.ts +33 -0
  30. package/dist/banner/Banner.js +14 -0
  31. package/dist/banner/banner.css +25 -0
  32. package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
  33. package/dist/breadcrumbs/Breadcrumbs.js +28 -0
  34. package/dist/breadcrumbs/breadcrumbs.css +18 -0
  35. package/dist/button/Button.d.ts +40 -0
  36. package/dist/button/Button.js +19 -0
  37. package/dist/button/button.css +63 -0
  38. package/dist/card/Card.d.ts +355 -0
  39. package/dist/card/Card.js +56 -0
  40. package/dist/card/card.css +43 -0
  41. package/dist/chart/AreaChart.d.ts +19 -0
  42. package/dist/chart/AreaChart.js +61 -0
  43. package/dist/chart/BarChart.d.ts +3 -0
  44. package/dist/chart/BarChart.js +60 -0
  45. package/dist/chart/ChartFrame.d.ts +25 -0
  46. package/dist/chart/ChartFrame.js +22 -0
  47. package/dist/chart/DonutChart.d.ts +20 -0
  48. package/dist/chart/DonutChart.js +41 -0
  49. package/dist/chart/Sparkline.d.ts +26 -0
  50. package/dist/chart/Sparkline.js +36 -0
  51. package/dist/chart/chart.css +51 -0
  52. package/dist/chart/chart.d.ts +23 -0
  53. package/dist/chart/chart.js +19 -0
  54. package/dist/checkbox/Checkbox.d.ts +50 -0
  55. package/dist/checkbox/Checkbox.js +19 -0
  56. package/dist/checkbox/CheckboxGroup.d.ts +44 -0
  57. package/dist/checkbox/CheckboxGroup.js +80 -0
  58. package/dist/checkbox/checkbox.css +49 -0
  59. package/dist/collapsible/Collapsible.d.ts +41 -0
  60. package/dist/collapsible/Collapsible.js +15 -0
  61. package/dist/collapsible/collapsible.css +20 -0
  62. package/dist/color-scheme.d.ts +18 -0
  63. package/dist/color-scheme.js +84 -0
  64. package/dist/command/Command.d.ts +64 -0
  65. package/dist/command/Command.js +162 -0
  66. package/dist/command/command.css +47 -0
  67. package/dist/confirm/confirm.css +5 -0
  68. package/dist/confirm/confirm.d.ts +42 -0
  69. package/dist/confirm/confirm.js +115 -0
  70. package/dist/copy-button/CopyButton.d.ts +30 -0
  71. package/dist/copy-button/CopyButton.js +23 -0
  72. package/dist/copy-button/copy-button.css +12 -0
  73. package/dist/css.d.ts +2 -0
  74. package/dist/data-list/DataList.d.ts +55 -0
  75. package/dist/data-list/DataList.js +43 -0
  76. package/dist/data-list/data-list.css +19 -0
  77. package/dist/date-picker/DatePicker.d.ts +95 -0
  78. package/dist/date-picker/DatePicker.js +237 -0
  79. package/dist/date-picker/date-picker.css +53 -0
  80. package/dist/dialog/Dialog.d.ts +98 -0
  81. package/dist/dialog/Dialog.js +92 -0
  82. package/dist/dialog/dialog.css +31 -0
  83. package/dist/divider/Divider.d.ts +27 -0
  84. package/dist/divider/Divider.js +8 -0
  85. package/dist/divider/divider.css +12 -0
  86. package/dist/editable/Editable.d.ts +47 -0
  87. package/dist/editable/Editable.js +51 -0
  88. package/dist/editable/editable.css +25 -0
  89. package/dist/editor/Editor.d.ts +59 -0
  90. package/dist/editor/Editor.js +220 -0
  91. package/dist/editor/editor.css +42 -0
  92. package/dist/editor/image.d.ts +1 -0
  93. package/dist/editor/image.js +23 -0
  94. package/dist/empty-state/EmptyState.d.ts +22 -0
  95. package/dist/empty-state/EmptyState.js +9 -0
  96. package/dist/empty-state/empty-state.css +14 -0
  97. package/dist/event-calendar/EventCalendar.d.ts +213 -0
  98. package/dist/event-calendar/EventCalendar.js +686 -0
  99. package/dist/event-calendar/dates.d.ts +8 -0
  100. package/dist/event-calendar/dates.js +21 -0
  101. package/dist/event-calendar/event-calendar.css +157 -0
  102. package/dist/field/Field.d.ts +66 -0
  103. package/dist/field/Field.js +23 -0
  104. package/dist/field/field.css +26 -0
  105. package/dist/file-upload/FileUpload.d.ts +60 -0
  106. package/dist/file-upload/FileUpload.js +102 -0
  107. package/dist/file-upload/file-upload.css +32 -0
  108. package/dist/format/FormatNumber.d.ts +42 -0
  109. package/dist/format/FormatNumber.js +59 -0
  110. package/dist/format/LocaleProvider.d.ts +33 -0
  111. package/dist/format/LocaleProvider.js +48 -0
  112. package/dist/format/format.d.ts +37 -0
  113. package/dist/format/format.js +74 -0
  114. package/dist/format/messages.d.ts +211 -0
  115. package/dist/format/messages.js +202 -0
  116. package/dist/highlight/Highlight.d.ts +11 -0
  117. package/dist/highlight/Highlight.js +14 -0
  118. package/dist/highlight/highlight.css +17 -0
  119. package/dist/hover-card/HoverCard.d.ts +47 -0
  120. package/dist/hover-card/HoverCard.js +18 -0
  121. package/dist/hover-card/hover-card.css +19 -0
  122. package/dist/index.d.ts +84 -0
  123. package/dist/index.js +85 -0
  124. package/dist/input/Input.d.ts +41 -0
  125. package/dist/input/Input.js +48 -0
  126. package/dist/input/input.css +25 -0
  127. package/dist/kbd/Kbd.d.ts +19 -0
  128. package/dist/kbd/Kbd.js +20 -0
  129. package/dist/kbd/kbd.css +8 -0
  130. package/dist/menu/Menu.d.ts +223 -0
  131. package/dist/menu/Menu.js +275 -0
  132. package/dist/menu/menu.css +38 -0
  133. package/dist/native-select/NativeSelect.d.ts +36 -0
  134. package/dist/native-select/NativeSelect.js +51 -0
  135. package/dist/native-select/native-select.css +16 -0
  136. package/dist/number-input/NumberInput.d.ts +56 -0
  137. package/dist/number-input/NumberInput.js +67 -0
  138. package/dist/number-input/number-input.css +21 -0
  139. package/dist/page/Page.d.ts +30 -0
  140. package/dist/page/Page.js +9 -0
  141. package/dist/page/page.css +17 -0
  142. package/dist/pagination/Pagination.d.ts +31 -0
  143. package/dist/pagination/Pagination.js +58 -0
  144. package/dist/pagination/pagination.css +15 -0
  145. package/dist/password-input/PasswordInput.d.ts +47 -0
  146. package/dist/password-input/PasswordInput.js +35 -0
  147. package/dist/password-input/password-input.css +18 -0
  148. package/dist/pin-input/PinInput.d.ts +45 -0
  149. package/dist/pin-input/PinInput.js +22 -0
  150. package/dist/pin-input/pin-input.css +13 -0
  151. package/dist/popover/Popover.d.ts +65 -0
  152. package/dist/popover/Popover.js +26 -0
  153. package/dist/popover/popover.css +16 -0
  154. package/dist/progress/Progress.d.ts +56 -0
  155. package/dist/progress/Progress.js +28 -0
  156. package/dist/progress/progress.css +24 -0
  157. package/dist/prose/Prose.d.ts +23 -0
  158. package/dist/prose/Prose.js +10 -0
  159. package/dist/prose/prose.css +29 -0
  160. package/dist/radio-group/RadioGroup.d.ts +59 -0
  161. package/dist/radio-group/RadioGroup.js +37 -0
  162. package/dist/radio-group/radio-group.css +30 -0
  163. package/dist/rolling-number/RollingNumber.d.ts +27 -0
  164. package/dist/rolling-number/RollingNumber.js +18 -0
  165. package/dist/rolling-number/rolling-number.css +10 -0
  166. package/dist/segment-group/Segmented.d.ts +68 -0
  167. package/dist/segment-group/Segmented.js +79 -0
  168. package/dist/segment-group/segment-group.css +25 -0
  169. package/dist/select/ListSelect.d.ts +5 -0
  170. package/dist/select/ListSelect.js +82 -0
  171. package/dist/select/SearchSelect.d.ts +43 -0
  172. package/dist/select/SearchSelect.js +219 -0
  173. package/dist/select/Select.d.ts +147 -0
  174. package/dist/select/Select.js +32 -0
  175. package/dist/select/items.d.ts +17 -0
  176. package/dist/select/items.js +14 -0
  177. package/dist/select/search-select.css +45 -0
  178. package/dist/select/select.css +52 -0
  179. package/dist/sheet/Sheet.d.ts +88 -0
  180. package/dist/sheet/Sheet.js +66 -0
  181. package/dist/sheet/sheet.css +44 -0
  182. package/dist/sidebar/NavGroup.d.ts +29 -0
  183. package/dist/sidebar/NavGroup.js +78 -0
  184. package/dist/sidebar/NavItem.d.ts +33 -0
  185. package/dist/sidebar/NavItem.js +50 -0
  186. package/dist/sidebar/NavSection.d.ts +20 -0
  187. package/dist/sidebar/NavSection.js +44 -0
  188. package/dist/sidebar/Sidebar.d.ts +65 -0
  189. package/dist/sidebar/Sidebar.js +59 -0
  190. package/dist/sidebar/SidebarUser.d.ts +31 -0
  191. package/dist/sidebar/SidebarUser.js +14 -0
  192. package/dist/sidebar/context.d.ts +13 -0
  193. package/dist/sidebar/context.js +6 -0
  194. package/dist/sidebar/sidebar.css +109 -0
  195. package/dist/skeleton/Skeleton.d.ts +25 -0
  196. package/dist/skeleton/Skeleton.js +8 -0
  197. package/dist/skeleton/skeleton.css +13 -0
  198. package/dist/slider/Slider.d.ts +70 -0
  199. package/dist/slider/Slider.js +65 -0
  200. package/dist/slider/slider.css +17 -0
  201. package/dist/sortable-list/SortableList.d.ts +46 -0
  202. package/dist/sortable-list/SortableList.js +85 -0
  203. package/dist/sortable-list/sortable-list.css +25 -0
  204. package/dist/spinner/Spinner.d.ts +18 -0
  205. package/dist/spinner/Spinner.js +11 -0
  206. package/dist/spinner/spinner.css +9 -0
  207. package/dist/stat/Stat.d.ts +42 -0
  208. package/dist/stat/Stat.js +21 -0
  209. package/dist/stat/Stats.d.ts +23 -0
  210. package/dist/stat/Stats.js +8 -0
  211. package/dist/stat/stat.css +34 -0
  212. package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
  213. package/dist/swatch-picker/SwatchPicker.js +54 -0
  214. package/dist/swatch-picker/swatch-picker.css +38 -0
  215. package/dist/switch/Switch.d.ts +39 -0
  216. package/dist/switch/Switch.js +17 -0
  217. package/dist/switch/switch.css +13 -0
  218. package/dist/table/Table.d.ts +90 -0
  219. package/dist/table/Table.js +145 -0
  220. package/dist/table/table.css +44 -0
  221. package/dist/tabs/Tabs.d.ts +87 -0
  222. package/dist/tabs/Tabs.js +90 -0
  223. package/dist/tabs/tabs.css +53 -0
  224. package/dist/tags-input/TagsInput.d.ts +69 -0
  225. package/dist/tags-input/TagsInput.js +95 -0
  226. package/dist/tags-input/tags-input.css +37 -0
  227. package/dist/textarea/Textarea.d.ts +24 -0
  228. package/dist/textarea/Textarea.js +51 -0
  229. package/dist/textarea/textarea.css +14 -0
  230. package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
  231. package/dist/theme-toggle/ThemeToggle.js +71 -0
  232. package/dist/theme-toggle/theme-toggle.css +23 -0
  233. package/dist/theme.css +121 -0
  234. package/dist/time-input/TimeInput.d.ts +25 -0
  235. package/dist/time-input/TimeInput.js +20 -0
  236. package/dist/timeline/Timeline.d.ts +50 -0
  237. package/dist/timeline/Timeline.js +15 -0
  238. package/dist/timeline/timeline.css +29 -0
  239. package/dist/toast/Toaster.d.ts +36 -0
  240. package/dist/toast/Toaster.js +25 -0
  241. package/dist/toast/toast.css +33 -0
  242. package/dist/tooltip/Tooltip.d.ts +55 -0
  243. package/dist/tooltip/Tooltip.js +48 -0
  244. package/dist/tooltip/tooltip.css +18 -0
  245. package/dist/tree-view/TreeView.d.ts +86 -0
  246. package/dist/tree-view/TreeView.js +74 -0
  247. package/dist/tree-view/tree-view.css +29 -0
  248. package/dist/utils.d.ts +19 -0
  249. package/dist/utils.js +59 -0
  250. package/package.json +99 -4
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Select without search: a button that opens the list. Used by Select.tsx,
3
+ // which documents the props.
4
+ import { Fragment, useMemo, useRef } from 'react';
5
+ import { Select as Ark, createListCollection, useSelect } from '@ark-ui/react/select';
6
+ import { useFieldContext } from '@ark-ui/react/field';
7
+ import 'iconify-icon';
8
+ import { cx, partsOf, slot } from '../utils.js';
9
+ import { useMessages } from '../format/LocaleProvider.js';
10
+ import { useItems } from './items.js';
11
+ import '../theme.css';
12
+ import './select.css';
13
+ export function ListSelect({ label = null, items = [], placeholder: placeholderProp, labelKey, valueKey, disabledKey, multiple = false, tags = false, variant = 'subtle', hideSelected = false, deselectable = false, closeOnSelect, clearable = false, emptyText: emptyTextProp, empty, name, size = 'md', placement = 'bottom-start', disabled = false, value, defaultValue, onValueChange, renderItem, renderValue, start,
14
+ // Search-only props, not for the div.
15
+ searchable: _searchable, load: _load, debounce: _debounce, loadingText: _loadingText, onCreate: _onCreate, renderCreate: _renderCreate, className, classes, ...rest }) {
16
+ const t = useMessages();
17
+ const placeholder = placeholderProp ?? t.select.placeholder;
18
+ const emptyText = emptyTextProp ?? t.select.empty;
19
+ const field = useFieldContext();
20
+ const part = partsOf('select', classes);
21
+ const { labelOf, valueOf, keyOf, isDisabled, setCurrent, keys } = useItems({ labelKey, valueKey, disabledKey, multiple, value, defaultValue, onValueChange });
22
+ // With hideSelected the picked items aren't rendered, but stay in Zag's
23
+ // collection (for their labels), disabled so the keys skip them.
24
+ const hidden = (item) => hideSelected && multiple && keys.includes(keyOf(item));
25
+ const listed = items.filter((item) => !hidden(item));
26
+ const collection = useMemo(() => createListCollection({
27
+ items: [...items],
28
+ itemToString: labelOf,
29
+ itemToValue: keyOf,
30
+ isItemDisabled: (item) => hidden(item) || isDisabled(item),
31
+ }), [items, labelKey, valueKey, disabledKey, hideSelected, multiple, keys.join('\u0000')]); // eslint-disable-line react-hooks/exhaustive-deps
32
+ // The items' own values for Zag's keys.
33
+ const valuesOf = (list) => list.map((key) => items.find((item) => keyOf(item) === key)).filter((item) => item !== undefined).map(valueOf);
34
+ function change(details) {
35
+ const chosen = valuesOf(details.value);
36
+ if (multiple)
37
+ keepPlace(details.value);
38
+ setCurrent(multiple ? chosen : chosen[0] ?? null);
39
+ }
40
+ // Opening highlights the pick, which hideSelected hides (and Enter would
41
+ // then untick unseen): highlight the first item shown instead.
42
+ function openChange({ open }) {
43
+ if (open && hideSelected && multiple && listed.length)
44
+ setTimeout(() => api.current.setHighlightValue(keyOf(listed[0])), 0);
45
+ }
46
+ const select = useSelect({
47
+ collection, value: keys, onValueChange: change, onOpenChange: openChange, multiple, deselectable, closeOnSelect: closeOnSelect ?? !multiple, disabled,
48
+ positioning: { placement, sameWidth: true }, ...(name && { name }),
49
+ ...(t.zag.select && { translations: t.zag.select }),
50
+ });
51
+ // The latest api, for the timers below.
52
+ const api = useRef(select);
53
+ api.current = select;
54
+ // Picking several: the highlight stays where it was (on the next item, when
55
+ // the picked one leaves the list), so Enter can pick a run of them.
56
+ function keepPlace(next) {
57
+ const key = next.find((k) => !keys.includes(k)) ?? keys.find((k) => !next.includes(k));
58
+ const at = listed.findIndex((item) => keyOf(item) === key);
59
+ if (at < 0)
60
+ return;
61
+ setTimeout(() => {
62
+ const shown = items.filter((item) => !(hideSelected && next.includes(keyOf(item))));
63
+ const item = shown[Math.min(at, shown.length - 1)];
64
+ if (item && api.current.open)
65
+ api.current.setHighlightValue(keyOf(item));
66
+ }, 30);
67
+ }
68
+ // Backspace on the closed button removes the last pick. Set here, not by
69
+ // Zag, which won't clear an item hideSelected has disabled.
70
+ function removeLast(e) {
71
+ if (e.key !== 'Backspace' || !multiple || select.open || !keys.length)
72
+ return;
73
+ setCurrent(valuesOf(keys.slice(0, -1)));
74
+ }
75
+ return (_jsxs(Ark.RootProvider, { value: select, ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, "data-variant": variant, children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsxs(Ark.Control, { ...part('control'), children: [_jsxs(Ark.Trigger, { ...part('trigger'), onKeyDown: removeLast, children: [start != null && _jsx("span", { ...part('start'), children: typeof start === 'function' ? start(select.selectedItems[0] ?? null) : start }), multiple && tags && select.hasSelectedItems
76
+ // Chips, not buttons (a button can't hold buttons): untick an item to remove it.
77
+ ? _jsx("span", { ...part('tags'), children: select.selectedItems.map((item) => (_jsx("span", { ...part('tag'), children: _jsx("span", { ...part('tag-text'), children: renderValue ? renderValue(item) : labelOf(item) }) }, keyOf(item)))) })
78
+ : renderValue && select.hasSelectedItems
79
+ // Your rendering of the chosen item(s), in place of Ark's text.
80
+ ? _jsx("span", { ...part('value-text'), children: select.selectedItems.map((item, i) => _jsxs(Fragment, { children: [i > 0 && ', ', renderValue(item)] }, keyOf(item))) })
81
+ : _jsx(Ark.ValueText, { ...part('value-text'), placeholder: placeholder }), _jsx(Ark.Indicator, { ...part('indicator'), children: _jsx("iconify-icon", { icon: "lucide:chevron-down" }) })] }), clearable && _jsx(Ark.ClearTrigger, { ...part('clear-trigger'), children: _jsx("iconify-icon", { icon: "lucide:x" }) })] }), _jsx(Ark.Positioner, { ...part('positioner'), children: _jsxs(Ark.Content, { ...part('content'), children: [listed.map((item) => (_jsxs(Ark.Item, { item: item, ...part('item'), children: [_jsx(Ark.ItemText, { ...part('item-text'), children: renderItem ? renderItem(item, { selected: keys.includes(keyOf(item)) }) : labelOf(item) }), _jsx(Ark.ItemIndicator, { ...part('item-indicator'), children: _jsx("iconify-icon", { icon: "lucide:check" }) })] }, keyOf(item)))), !listed.length && _jsx("div", { ...part('empty'), children: slot(empty, emptyText, { query: '' }) })] }) }), _jsx(Ark.HiddenSelect, {})] }));
82
+ }
@@ -0,0 +1,43 @@
1
+ import { Combobox as Ark } from '@ark-ui/react/combobox';
2
+ import 'iconify-icon';
3
+ import type { InnerProps } from './Select.js';
4
+ import '../theme.css';
5
+ import './search-select.css';
6
+ export declare function SearchSelect<T>({ label, items, placeholder: placeholderProp, labelKey, valueKey, disabledKey, multiple, variant, hideSelected, closeOnSelect, clearable, load, debounce, loadingText: loadingTextProp, onCreate, emptyText: emptyTextProp, empty, name, size, placement, disabled, value, defaultValue, onValueChange, renderItem, renderValue, start, renderCreate, searchable: _searchable, tags: _tags, deselectable: _deselectable, className, classes, ...rest }: InnerProps<T>): import("react").JSX.Element;
7
+ /**
8
+ * Combobox's parts, for a searchable select built by hand (Select's own
9
+ * search mode is built from them):
10
+ *
11
+ * <Combobox.Root collection={collection} onInputValueChange={…}>
12
+ * <Combobox.Label>Fruit</Combobox.Label>
13
+ * <Combobox.Control><Combobox.Input /><Combobox.Trigger>▾</Combobox.Trigger></Combobox.Control>
14
+ * <Combobox.Positioner><Combobox.Content>…items…</Combobox.Content></Combobox.Positioner>
15
+ * </Combobox.Root>
16
+ */
17
+ export declare const Combobox: {
18
+ Root: Ark.RootComponent<{}>;
19
+ RootProvider: Ark.RootProviderComponent<{}>;
20
+ Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
21
+ Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
22
+ Input: import("react").ForwardRefExoticComponent<Ark.InputProps & import("react").RefAttributes<HTMLInputElement>>;
23
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
24
+ ClearTrigger: import("react").ForwardRefExoticComponent<Ark.ClearTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
25
+ Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
26
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
27
+ List: import("react").ForwardRefExoticComponent<Ark.ListProps & import("react").RefAttributes<HTMLDivElement>>;
28
+ Item: import("react").ForwardRefExoticComponent<Ark.ItemProps & import("react").RefAttributes<HTMLDivElement>>;
29
+ ItemText: import("react").ForwardRefExoticComponent<Ark.ItemTextProps & import("react").RefAttributes<HTMLDivElement>>;
30
+ ItemIndicator: import("react").ForwardRefExoticComponent<Ark.ItemIndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
31
+ ItemGroup: import("react").ForwardRefExoticComponent<Ark.ItemGroupProps & import("react").RefAttributes<HTMLDivElement>>;
32
+ ItemGroupLabel: import("react").ForwardRefExoticComponent<Ark.ItemGroupLabelProps & import("react").RefAttributes<HTMLDivElement>>;
33
+ Empty: import("react").ForwardRefExoticComponent<Ark.EmptyProps & import("react").RefAttributes<HTMLDivElement>>;
34
+ Context: <T extends unknown>(props: Ark.ContextProps<T>) => import("react").ReactNode;
35
+ Tags: {
36
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
37
+ displayName: string;
38
+ };
39
+ Tag: {
40
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
41
+ displayName: string;
42
+ };
43
+ };
@@ -0,0 +1,219 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // Select with search: an input that filters the list, loads it, or creates
3
+ // items. Used by Select.tsx, which documents the props.
4
+ import { useEffect, useMemo, useRef, useState } from 'react';
5
+ import { Combobox as Ark, createListCollection, useCombobox, } from '@ark-ui/react/combobox';
6
+ import { useFieldContext } from '@ark-ui/react/field';
7
+ import 'iconify-icon';
8
+ import { cx, partOf, partsOf, slot } from '../utils.js';
9
+ import { useMessages } from '../format/LocaleProvider.js';
10
+ import { useItems } from './items.js';
11
+ import '../theme.css';
12
+ import './search-select.css';
13
+ // The "Create …" option, in the list beside the items.
14
+ const CREATE = '\u0000create';
15
+ const isCreating = (entry) => typeof entry === 'object' && entry !== null && CREATE in entry;
16
+ export function SearchSelect({ label = null, items = [], placeholder: placeholderProp, labelKey, valueKey, disabledKey, multiple = false, variant = 'subtle', hideSelected = false, closeOnSelect, clearable = false, load, debounce = 200, loadingText: loadingTextProp, onCreate, emptyText: emptyTextProp, empty, name, size = 'md', placement = 'bottom-start', disabled = false, value, defaultValue, onValueChange, renderItem, renderValue, start, renderCreate,
17
+ // ListSelect-only props, not for the div.
18
+ searchable: _searchable, tags: _tags, deselectable: _deselectable, className, classes, ...rest }) {
19
+ const t = useMessages();
20
+ const placeholder = placeholderProp ?? t.select.searchPlaceholder;
21
+ const loadingText = loadingTextProp ?? t.select.loading;
22
+ const emptyText = emptyTextProp ?? t.select.noMatches;
23
+ const field = useFieldContext();
24
+ // Ark stamps data-scope="combobox" on its parts; ours match, for search-select.css.
25
+ const part = partsOf('combobox', classes);
26
+ const { labelOf, valueOf, keyOf, isDisabled, setCurrent, keys } = useItems({ labelKey, valueKey, disabledKey, multiple, value, defaultValue, onValueChange });
27
+ // Items found by `load` or made by `onCreate` are remembered, so picked ones keep their labels.
28
+ const [seen, setSeen] = useState([]);
29
+ const [created, setCreated] = useState([]);
30
+ const pool = [...(load ? seen : items), ...created];
31
+ const byKey = (key) => pool.find((item) => keyOf(item) === key);
32
+ // What's been typed (not the picked item's label), filtering the list:
33
+ // labels starting with it first, then those containing it.
34
+ const [query, setQuery] = useState('');
35
+ const [loaded, setLoaded] = useState([]);
36
+ const [loading, setLoading] = useState(false);
37
+ const loader = useRef(load);
38
+ loader.current = load;
39
+ // With `load`, the server filters: ask it (debounced), keeping only the latest answer.
40
+ useEffect(() => {
41
+ if (!loader.current)
42
+ return;
43
+ let current = true;
44
+ setLoading(true);
45
+ const timer = setTimeout(async () => {
46
+ const list = [...await loader.current(query)];
47
+ if (!current)
48
+ return;
49
+ setLoaded(list);
50
+ setSeen((seen) => [...seen.filter((item) => !list.some((it) => keyOf(it) === keyOf(item))), ...list]);
51
+ setLoading(false);
52
+ // Zag highlighted the first item as they typed, when the list was empty
53
+ // (loading); do it now the results are in, so Enter picks it.
54
+ if (query && list.length)
55
+ setTimeout(() => {
56
+ if (api.current.open)
57
+ input.current?.dispatchEvent(new globalThis.KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
58
+ }, 30);
59
+ }, debounce);
60
+ return () => { current = false; clearTimeout(timer); };
61
+ }, [query, !!load, debounce]); // eslint-disable-line react-hooks/exhaustive-deps
62
+ const shown = useMemo(() => {
63
+ if (load)
64
+ return loading ? [] : loaded;
65
+ const q = query.toLowerCase();
66
+ if (!q)
67
+ return [...items];
68
+ const text = (item) => labelOf(item).toLowerCase();
69
+ return [...items.filter((item) => text(item).startsWith(q)), ...items.filter((item) => !text(item).startsWith(q) && text(item).includes(q))];
70
+ }, [items, query, load, loading, loaded, labelKey]); // eslint-disable-line react-hooks/exhaustive-deps
71
+ const picked = multiple ? keys.map(byKey).filter((item) => item !== undefined) : [];
72
+ // The list as shown, and the selected items it doesn't show (left out by a
73
+ // search or `hideSelected`). Zag still needs those to know their labels, so
74
+ // they stay in its collection, disabled so the keys skip them, but unrendered.
75
+ const listed = hideSelected && multiple ? shown.filter((item) => !keys.includes(keyOf(item))) : shown;
76
+ const offList = keys.filter((key) => !listed.some((item) => keyOf(item) === key)).map(byKey).filter((item) => item !== undefined);
77
+ // The "Create …" option: for typed text that matches no item.
78
+ const typed = query.trim();
79
+ const creating = onCreate && typed && !loading && !pool.some((item) => labelOf(item).toLowerCase() === typed.toLowerCase())
80
+ ? { [CREATE]: true, label: typed } : null;
81
+ const collection = useMemo(() => createListCollection({
82
+ items: [...listed, ...(creating ? [creating] : []), ...offList],
83
+ itemToString: (entry) => (isCreating(entry) ? entry.label : labelOf(entry)),
84
+ itemToValue: (entry) => (isCreating(entry) ? CREATE : keyOf(entry)),
85
+ isItemDisabled: (entry) => !isCreating(entry) && (offList.includes(entry) || isDisabled(entry)),
86
+ }), [listed.map(keyOf).join('\u0000'), creating?.label, offList.map(keyOf).join('\u0000'), labelKey, valueKey, disabledKey]); // eslint-disable-line react-hooks/exhaustive-deps
87
+ async function createFrom(text) {
88
+ setQuery('');
89
+ const made = await onCreate(text);
90
+ const combobox = api.current;
91
+ if (made != null) {
92
+ setCreated((created) => [...created, made]);
93
+ setCurrent(multiple ? [...keysOf(keys), valueOf(made)] : valueOf(made));
94
+ combobox.setInputValue(multiple ? '' : labelOf(made));
95
+ }
96
+ else
97
+ combobox.setInputValue(multiple ? '' : keys.length ? labelOf(byKey(keys[0]) ?? '') : '');
98
+ }
99
+ // The current value's items' own values, from their keys.
100
+ const keysOf = (list) => list.map(byKey).filter((item) => item !== undefined).map(valueOf);
101
+ function inputChange(details) {
102
+ // Emptying the input clears a single pick; partial edits revert on blur.
103
+ if (details.reason === 'input-change' && !details.inputValue && !multiple && keys.length)
104
+ queueMicrotask(() => api.current.clearValue());
105
+ if (details.reason === 'input-change')
106
+ setQuery(details.inputValue);
107
+ else if (!load || details.reason === 'clear-trigger')
108
+ setQuery('');
109
+ }
110
+ // Zag's values are strings; map them back to the items' own.
111
+ function change(details) {
112
+ if (details.value.includes(CREATE))
113
+ return void createFrom(typed);
114
+ const chosen = keysOf(details.value);
115
+ if (multiple)
116
+ keepPlace(details.value);
117
+ setCurrent(multiple ? chosen : chosen[0] ?? null);
118
+ }
119
+ const combobox = useCombobox({
120
+ collection, value: keys, onValueChange: change, onInputValueChange: inputChange,
121
+ multiple, disabled, placeholder, openOnClick: true, inputBehavior: 'autohighlight',
122
+ ...(t.zag.combobox && { translations: t.zag.combobox }),
123
+ closeOnSelect: closeOnSelect ?? !multiple, selectionBehavior: multiple ? 'clear' : 'replace',
124
+ positioning: { placement, sameWidth: true }, ...(name && { name }),
125
+ });
126
+ // The latest api, for the timers and promises here.
127
+ const api = useRef(combobox);
128
+ api.current = combobox;
129
+ const input = useRef(null);
130
+ // Picking several: the highlight stays where it was (on the next item, when
131
+ // the picked one leaves the list), so Enter can pick a run of them. Not
132
+ // after a search, which the pick clears. Zag only acts on Enter once the
133
+ // keys have moved the highlight, so it moves there the way they would.
134
+ function keepPlace(next) {
135
+ const key = next.find((k) => !keys.includes(k)) ?? keys.find((k) => !next.includes(k));
136
+ const at = listed.findIndex((item) => keyOf(item) === key);
137
+ if (at < 0 || query)
138
+ return;
139
+ setTimeout(() => {
140
+ const n = Math.min(at, listed.length - 1);
141
+ if (n < 0 || !api.current.open)
142
+ return;
143
+ if (n > 0)
144
+ api.current.setHighlightValue(keyOf(listed[n - 1]));
145
+ // In React the highlight lands on the next render: move on from it after that.
146
+ setTimeout(() => input.current?.dispatchEvent(new globalThis.KeyboardEvent('keydown', { key: n > 0 ? 'ArrowDown' : 'Home', bubbles: true })), 30);
147
+ }, 30);
148
+ }
149
+ // Removes picks. Set here, not by Zag, which won't clear an item that's
150
+ // off the list (disabled in its collection).
151
+ const unpick = (key) => setCurrent(keysOf(keys.filter((k) => k !== key)));
152
+ // Backspace in an empty input removes the last pick.
153
+ function removeLast(e) {
154
+ if (e.key !== 'Backspace' || !multiple || e.currentTarget.value || !keys.length)
155
+ return;
156
+ unpick(keys.at(-1));
157
+ }
158
+ // A click on the box (around the tags, not on a button) types into the input.
159
+ function focusInput(e) {
160
+ if (e.target.closest('button, input'))
161
+ return;
162
+ e.preventDefault();
163
+ input.current?.focus();
164
+ }
165
+ // Zag reverts stray text only while the list is open; when focus leaves
166
+ // the whole combobox, put the selected item's label back (or nothing). An
167
+ // emptied input has already cleared the pick (see inputChange).
168
+ function revert(e) {
169
+ if (e.currentTarget.contains(e.relatedTarget))
170
+ return;
171
+ const text = multiple ? '' : keys.length ? labelOf(byKey(keys[0]) ?? '') : '';
172
+ const root = e.currentTarget;
173
+ // After Zag has handled the blur (closing the list, if a search left it open).
174
+ setTimeout(() => {
175
+ if (root.contains(document.activeElement))
176
+ return;
177
+ // Tabbing away leaves a multiple select's list open, and its layer then
178
+ // swallows the next field's typing.
179
+ if (api.current.open)
180
+ api.current.setOpen(false);
181
+ if (api.current.inputValue !== text)
182
+ api.current.setInputValue(text);
183
+ setQuery('');
184
+ }, 0);
185
+ }
186
+ return (_jsxs(Ark.RootProvider, { value: combobox, ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, "data-variant": variant, onBlur: revert, children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsxs(Ark.Control, { ...part('control'), onMouseDown: focusInput, children: [start != null && _jsx("span", { ...part('start'), children: typeof start === 'function' ? start((keys[0] !== undefined ? byKey(keys[0]) : undefined) ?? null) : start }), _jsxs("div", { ...part('tags'), children: [picked.map((item) => (_jsxs("span", { ...part('tag'), children: [_jsx("span", { ...part('tag-text'), children: renderValue ? renderValue(item) : labelOf(item) }), _jsx("button", { ...part('tag-remove'), type: "button", tabIndex: -1, "aria-label": t.common.remove(labelOf(item)), onClick: (e) => { e.stopPropagation(); unpick(keyOf(item)); }, children: _jsx("iconify-icon", { icon: "lucide:x" }) })] }, keyOf(item)))), _jsx(Ark.Input, { ref: input, ...part('input'), placeholder: picked.length ? '' : placeholder, onKeyDown: removeLast })] }), clearable && _jsx(Ark.ClearTrigger, { ...part('clear-trigger'), children: _jsx("iconify-icon", { icon: "lucide:x" }) }), _jsx(Ark.Trigger, { ...part('trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevron-down" }) })] }), _jsx(Ark.Positioner, { ...part('positioner'), children: _jsxs(Ark.Content, { ...part('content'), children: [listed.map((item) => (_jsxs(Ark.Item, { item: item, ...part('item'), children: [_jsx(Ark.ItemText, { ...part('item-text'), children: renderItem ? renderItem(item, { selected: keys.includes(keyOf(item)) }) : labelOf(item) }), _jsx(Ark.ItemIndicator, { ...part('item-indicator'), children: _jsx("iconify-icon", { icon: "lucide:check" }) })] }, keyOf(item)))), !listed.length && !creating && (_jsx("div", { ...part('empty'), children: loading ? loadingText : slot(empty, emptyText, { query: typed }) })), creating && (_jsx(Ark.Item, { item: creating, ...part('create'), children: renderCreate ? renderCreate(creating.label)
187
+ : _jsxs(_Fragment, { children: [_jsx("iconify-icon", { icon: "lucide:plus", "aria-hidden": "true" }), _jsx("span", { children: t.select.create(creating.label) })] }) }))] }) })] }));
188
+ }
189
+ /**
190
+ * Combobox's parts, for a searchable select built by hand (Select's own
191
+ * search mode is built from them):
192
+ *
193
+ * <Combobox.Root collection={collection} onInputValueChange={…}>
194
+ * <Combobox.Label>Fruit</Combobox.Label>
195
+ * <Combobox.Control><Combobox.Input /><Combobox.Trigger>▾</Combobox.Trigger></Combobox.Control>
196
+ * <Combobox.Positioner><Combobox.Content>…items…</Combobox.Content></Combobox.Positioner>
197
+ * </Combobox.Root>
198
+ */
199
+ export const Combobox = {
200
+ Root: Ark.Root,
201
+ RootProvider: Ark.RootProvider,
202
+ Label: Ark.Label,
203
+ Control: Ark.Control,
204
+ Input: Ark.Input,
205
+ Trigger: Ark.Trigger,
206
+ ClearTrigger: Ark.ClearTrigger,
207
+ Positioner: Ark.Positioner,
208
+ Content: Ark.Content,
209
+ List: Ark.List,
210
+ Item: Ark.Item,
211
+ ItemText: Ark.ItemText,
212
+ ItemIndicator: Ark.ItemIndicator,
213
+ ItemGroup: Ark.ItemGroup,
214
+ ItemGroupLabel: Ark.ItemGroupLabel,
215
+ Empty: Ark.Empty,
216
+ Context: Ark.Context,
217
+ Tags: partOf('combobox', 'tags'),
218
+ Tag: partOf('combobox', 'tag', 'span'),
219
+ };
@@ -0,0 +1,147 @@
1
+ /**
2
+ * A select: pick from a list. A button that opens it, or, with `searchable`,
3
+ * `load` or `onCreate`, an input you type into to filter it.
4
+ *
5
+ * <Select label="Level" items={['GCSE', 'A Level']} value={level} onValueChange={setLevel} />
6
+ * <Select label="Student" items={students} labelKey="name" valueKey="id" searchable defaultValue={2} />
7
+ * <Select label="City" load={searchCities} labelKey="name" valueKey="id" />
8
+ * <Select label="Topics" items={topics} multiple onCreate={addTopic} value={picked} onValueChange={setPicked} />
9
+ *
10
+ * - `items`: strings, or objects (see `labelKey`, `valueKey`, `disabledKey`)
11
+ * - `value`, `defaultValue`: the item's own value, or an array of them with `multiple`
12
+ * - `onValueChange`: called with the new value (items' own values, not strings)
13
+ * - `multiple`: pick several; picked items show ticked in the list
14
+ * - `tags`: with `multiple`, show the picks as tags in the box (always, when
15
+ * searchable); `variant` sets their look: 'subtle', 'accent' or 'outline'
16
+ * - `hideSelected`: with `multiple`, picked items leave the list
17
+ * - `searchable`: type to filter the list (labels starting with the text first)
18
+ * - `load`: async function(query) returning items, for server search; implies
19
+ * searchable (debounced by `debounce` ms, `loadingText` meanwhile)
20
+ * - `onCreate`: function(query) called when someone picks "Create …" for text
21
+ * that matches no item; implies searchable. Return the new item (or a
22
+ * promise of it) to select it, or handle it yourself
23
+ * - `deselectable`: clicking the picked item again clears it (without search)
24
+ * - `closeOnSelect`: close the list after a pick (on by default, except with `multiple`)
25
+ * - `clearable`: a button to clear it
26
+ * - `placeholder`, `emptyText` (when nothing matches)
27
+ * - `name`: for plain form posts
28
+ * - `size`: 'sm' or 'md'; `placement`: where the list opens ('bottom-start')
29
+ * - `renderItem`: an option's content, (item, { selected }) => node
30
+ * - `renderValue`: the chosen item's content in the closed select (and each
31
+ * tag's, picking several), (item) => node; with search, the box holds
32
+ * typed text, so use `start` there
33
+ * - `start`: before the value (or the search box): a node, or (item) => node
34
+ * given the chosen item (the first, picking several; null if none), e.g.
35
+ * a colour's dot
36
+ * - `renderCreate`: the create option's content, (query) => node
37
+ * - `empty`: shown when nothing matches, a node or ({ query }) => node
38
+ *
39
+ * Picking several, the highlight stays put (on the next item, when the
40
+ * picked one leaves the list), so Enter picks a run of them; Backspace in
41
+ * an empty box removes the last pick.
42
+ *
43
+ * By hand, with Ark's parts (and `createListCollection` for the items):
44
+ *
45
+ * <Select.Root collection={collection}>
46
+ * <Select.Label>Level</Select.Label>
47
+ * <Select.Control>
48
+ * <Select.Trigger><Select.ValueText placeholder="Pick one" /><Select.Indicator>▾</Select.Indicator></Select.Trigger>
49
+ * </Select.Control>
50
+ * <Select.Positioner>
51
+ * <Select.Content>
52
+ * {collection.items.map((item) => (
53
+ * <Select.Item key={item.value} item={item}><Select.ItemText>{item.label}</Select.ItemText></Select.Item>
54
+ * ))}
55
+ * </Select.Content>
56
+ * </Select.Positioner>
57
+ * <Select.HiddenSelect />
58
+ * </Select.Root>
59
+ *
60
+ * With search, the same with Combobox's parts (Combobox.Input in the control).
61
+ */
62
+ import type { ComponentProps, ReactNode } from 'react';
63
+ import { Select as Ark, createListCollection } from '@ark-ui/react/select';
64
+ import { type Classes, type Slot } from '../utils.js';
65
+ type Primitive = string | number;
66
+ export type ItemValue<T, K extends string> = T extends Primitive ? T : K extends keyof T ? T[K] : unknown;
67
+ type One<V> = {
68
+ multiple?: false;
69
+ value?: V | null;
70
+ defaultValue?: V | null;
71
+ onValueChange?: (value: V | null) => void;
72
+ };
73
+ type Several<V> = {
74
+ multiple: true;
75
+ value?: V[];
76
+ defaultValue?: V[];
77
+ onValueChange?: (value: V[]) => void;
78
+ };
79
+ export type SelectPart = 'root' | 'label' | 'control' | 'trigger' | 'valueText' | 'input' | 'tags' | 'tag' | 'tagText' | 'tagRemove' | 'indicator' | 'clearTrigger' | 'positioner' | 'content' | 'item' | 'itemText' | 'itemIndicator' | 'empty' | 'create' | 'start';
80
+ export type SelectProps<T, K extends string = 'value'> = Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange' | 'children'> & {
81
+ items?: readonly T[];
82
+ label?: ReactNode;
83
+ labelKey?: string;
84
+ valueKey?: K;
85
+ disabledKey?: string;
86
+ placeholder?: string;
87
+ tags?: boolean;
88
+ variant?: 'subtle' | 'accent' | 'outline';
89
+ hideSelected?: boolean;
90
+ searchable?: boolean;
91
+ load?: (query: string) => Promise<readonly T[]> | readonly T[];
92
+ debounce?: number;
93
+ loadingText?: ReactNode;
94
+ onCreate?: (query: string) => T | null | undefined | void | Promise<T | null | undefined | void>;
95
+ deselectable?: boolean;
96
+ closeOnSelect?: boolean;
97
+ clearable?: boolean;
98
+ emptyText?: ReactNode;
99
+ empty?: Slot<{
100
+ query: string;
101
+ }>;
102
+ name?: string;
103
+ size?: 'sm' | 'md';
104
+ placement?: NonNullable<Ark.RootProps<T>['positioning']>['placement'];
105
+ disabled?: boolean;
106
+ renderItem?: (item: T, state: {
107
+ selected: boolean;
108
+ }) => ReactNode;
109
+ renderValue?: (item: T) => ReactNode;
110
+ start?: ReactNode | ((item: T | null) => ReactNode);
111
+ renderCreate?: (query: string) => ReactNode;
112
+ classes?: Classes<SelectPart>;
113
+ } & (One<ItemValue<T, K>> | Several<ItemValue<T, K>>);
114
+ export type InnerProps<T> = Omit<SelectProps<T>, 'multiple' | 'value' | 'defaultValue' | 'onValueChange' | 'valueKey'> & {
115
+ valueKey?: string;
116
+ multiple?: boolean;
117
+ value?: unknown;
118
+ defaultValue?: unknown;
119
+ onValueChange?: (value: unknown) => void;
120
+ };
121
+ declare function SelectShortcut<T, K extends string = 'value'>(props: SelectProps<T, K>): import("react").JSX.Element;
122
+ export declare const Select: typeof SelectShortcut & {
123
+ Root: Ark.RootComponent<{}>;
124
+ RootProvider: Ark.RootProviderComponent<{}>;
125
+ Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
126
+ Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
127
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
128
+ ValueText: import("react").ForwardRefExoticComponent<Ark.ValueTextProps & import("react").RefAttributes<HTMLSpanElement>>;
129
+ Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
130
+ ClearTrigger: import("react").ForwardRefExoticComponent<Ark.ClearTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
131
+ Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
132
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
133
+ List: import("react").ForwardRefExoticComponent<Ark.ListProps & import("react").RefAttributes<HTMLDivElement>>;
134
+ Item: import("react").ForwardRefExoticComponent<Ark.ItemProps & import("react").RefAttributes<HTMLDivElement>>;
135
+ ItemText: import("react").ForwardRefExoticComponent<Ark.ItemTextProps & import("react").RefAttributes<HTMLDivElement>>;
136
+ ItemIndicator: import("react").ForwardRefExoticComponent<Ark.ItemIndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
137
+ ItemGroup: import("react").ForwardRefExoticComponent<Ark.ItemGroupProps & import("react").RefAttributes<HTMLDivElement>>;
138
+ ItemGroupLabel: import("react").ForwardRefExoticComponent<Ark.ItemGroupLabelProps & import("react").RefAttributes<HTMLDivElement>>;
139
+ HiddenSelect: import("react").ForwardRefExoticComponent<Ark.HiddenSelectProps & import("react").RefAttributes<HTMLSelectElement>>;
140
+ Context: <T extends unknown>(props: Ark.ContextProps<T>) => ReactNode;
141
+ Empty: {
142
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
143
+ displayName: string;
144
+ };
145
+ };
146
+ export { createListCollection };
147
+ export { Combobox } from './SearchSelect.js';
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Select as Ark, createListCollection } from '@ark-ui/react/select';
3
+ import { ListSelect } from './ListSelect.js';
4
+ import { SearchSelect } from './SearchSelect.js';
5
+ import { partOf, withParts } from '../utils.js';
6
+ function SelectShortcut(props) {
7
+ const inner = props;
8
+ return inner.searchable || inner.load || inner.onCreate ? _jsx(SearchSelect, { ...inner }) : _jsx(ListSelect, { ...inner });
9
+ }
10
+ export const Select = withParts(SelectShortcut, {
11
+ Root: Ark.Root,
12
+ RootProvider: Ark.RootProvider,
13
+ Label: Ark.Label,
14
+ Control: Ark.Control,
15
+ Trigger: Ark.Trigger,
16
+ ValueText: Ark.ValueText,
17
+ Indicator: Ark.Indicator,
18
+ ClearTrigger: Ark.ClearTrigger,
19
+ Positioner: Ark.Positioner,
20
+ Content: Ark.Content,
21
+ List: Ark.List,
22
+ Item: Ark.Item,
23
+ ItemText: Ark.ItemText,
24
+ ItemIndicator: Ark.ItemIndicator,
25
+ ItemGroup: Ark.ItemGroup,
26
+ ItemGroupLabel: Ark.ItemGroupLabel,
27
+ HiddenSelect: Ark.HiddenSelect,
28
+ Context: Ark.Context,
29
+ Empty: partOf('select', 'empty'),
30
+ });
31
+ export { createListCollection };
32
+ export { Combobox } from './SearchSelect.js';
@@ -0,0 +1,17 @@
1
+ export declare function useItems<T>({ labelKey, valueKey, disabledKey, multiple, value, defaultValue, onValueChange }: {
2
+ labelKey?: string;
3
+ valueKey?: string;
4
+ disabledKey?: string;
5
+ multiple?: boolean;
6
+ value?: unknown;
7
+ defaultValue?: unknown;
8
+ onValueChange?: (value: unknown) => void;
9
+ }): {
10
+ labelOf: (item: T) => string;
11
+ valueOf: (item: T) => unknown;
12
+ keyOf: (item: T) => string;
13
+ isDisabled: (item: T) => boolean;
14
+ current: unknown;
15
+ setCurrent: (next: unknown) => void;
16
+ keys: string[];
17
+ };
@@ -0,0 +1,14 @@
1
+ import { useControllable } from '../utils.js';
2
+ // Items are strings, numbers or objects; Zag's values are strings. These map
3
+ // between them, and keep the value (the items' own values) controlled or not.
4
+ export function useItems({ labelKey = 'label', valueKey = 'value', disabledKey = 'disabled', multiple = false, value, defaultValue, onValueChange }) {
5
+ const field = (item, key) => item[key];
6
+ const isObject = (item) => typeof item === 'object' && item !== null;
7
+ const labelOf = (item) => String(isObject(item) ? field(item, labelKey) : item);
8
+ const valueOf = (item) => (isObject(item) ? field(item, valueKey) : item);
9
+ const keyOf = (item) => String(valueOf(item));
10
+ const isDisabled = (item) => isObject(item) && !!field(item, disabledKey);
11
+ const [current, setCurrent] = useControllable(value, defaultValue ?? (multiple ? [] : null), onValueChange);
12
+ const keys = multiple ? (current ?? []).map(String) : current == null ? [] : [String(current)];
13
+ return { labelOf, valueOf, keyOf, isDisabled, current, setCurrent, keys };
14
+ }
@@ -0,0 +1,45 @@
1
+ @layer ui {
2
+ [data-scope=combobox][data-part=root] { display: inline-block; position: relative; min-width: 15rem; color: var(--ui-text); font-family: var(--ui-font);
3
+ --tag-bg: var(--ui-hover); --tag-text: var(--ui-text); --tag-border: transparent;
4
+ &[data-variant=accent] { --tag-bg: var(--ui-accent-soft); --tag-text: var(--ui-accent-soft-text); }
5
+ &[data-variant=outline] { --tag-bg: var(--ui-surface); --tag-border: var(--ui-border); } }
6
+ [data-scope=combobox][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
7
+ [data-scope=combobox][data-part=control] { display: flex; align-items: center; gap: 0.25rem; min-height: var(--ui-control-height);
8
+ padding: 0.125rem 0.25rem 0.125rem 0.5rem; box-sizing: border-box; background: var(--ui-surface);
9
+ border: 1px solid var(--ui-border); border-radius: var(--ui-radius);
10
+ &:focus-within { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
11
+ &[data-disabled] { opacity: 0.5; }
12
+ &[data-invalid], &:has([aria-invalid=true]) { border-color: var(--ui-danger); } }
13
+ [data-scope=combobox][data-part=tags] { display: flex; flex-wrap: wrap; align-items: center; flex: 1; min-width: 0; gap: 0.375rem; padding: 0.125rem 0; }
14
+ [data-scope=combobox][data-part=input] { flex: 1; min-width: 4rem; height: 1.625rem; padding: 0; border: none; background: transparent;
15
+ outline: none; color: inherit; font-family: inherit; font-size: 14px;
16
+ &::placeholder { color: var(--ui-muted); } }
17
+ [data-scope=combobox][data-part=tag] { display: flex; align-items: center; gap: 0.25rem; height: 1.625rem; padding: 0 0.25rem 0 0.625rem;
18
+ box-sizing: border-box; border-radius: calc(var(--ui-radius) - 2px); background: var(--tag-bg); color: var(--tag-text);
19
+ border: 1px solid var(--tag-border); font-size: 13px; font-weight: 500; line-height: 1; }
20
+ [data-scope=combobox][data-part=tag-text] { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 12rem; }
21
+ [data-scope=combobox][data-part=tag-remove] { display: grid; place-items: center; width: 1.125rem; height: 1.125rem; padding: 0; border: none;
22
+ background: transparent; border-radius: 4px; color: inherit; opacity: 0.6; cursor: pointer;
23
+ &:hover { opacity: 1; background: rgba(0, 0, 0, 0.08); } }
24
+ [data-scope=combobox]:is([data-part=clear-trigger], [data-part=trigger]) { display: grid; place-items: center; flex-shrink: 0;
25
+ width: 1.75rem; height: 1.75rem; border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer;
26
+ &:hover { background: var(--ui-hover); color: var(--ui-text); } }
27
+ [data-scope=combobox][data-part=trigger][data-state=open] { rotate: 180deg; }
28
+
29
+ /* Zag copies the content's z-index onto its positioner. */
30
+ [data-scope=combobox][data-part=content] { z-index: 50; margin: 0; padding: 0.25rem; list-style: none; box-sizing: border-box; outline: none;
31
+ max-height: min(300px, var(--available-height)); overflow-y: auto; font-weight: 400;
32
+ background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow); }
33
+ [data-scope=combobox][data-part=empty] { padding: 0.5rem; font-size: 14px; color: var(--ui-muted); }
34
+ [data-scope=combobox][data-part=item] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
35
+ padding: 0.375rem 0.5rem; border-radius: 4px; font-size: 14px; cursor: pointer;
36
+ &[data-highlighted] { background: var(--ui-hover); }
37
+ &[data-state=checked] { font-weight: 500; }
38
+ &[data-disabled] { opacity: 0.4; cursor: not-allowed; } }
39
+ /* The create option (Zag gives it data-part="item"; ours is "create"). */
40
+ [data-scope=combobox][data-part=create] { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.5rem;
41
+ border-radius: 4px; font-size: 14px; color: var(--ui-accent); cursor: pointer;
42
+ &[data-highlighted] { background: var(--ui-hover); } }
43
+ [data-scope=combobox][data-part=item-indicator] { display: none; color: var(--ui-accent);
44
+ &[data-state=checked] { display: inline-flex; } }
45
+ }