@shadow-js/ui 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +598 -2
  4. package/dist/cli.mjs +2281 -0
  5. package/dist/dev/cli/schema.d.ts +71 -0
  6. package/dist/dev/index.d.ts +6 -0
  7. package/dist/dev/index.js +5 -0
  8. package/dist/dev/locales/ar.d.ts +139 -0
  9. package/dist/dev/locales/ar.js +127 -0
  10. package/dist/dev/locales/ar.js.map +1 -0
  11. package/dist/dev/locales/en.d.ts +139 -0
  12. package/dist/dev/locales/en.js +127 -0
  13. package/dist/dev/locales/en.js.map +1 -0
  14. package/dist/dev/registry/utils/utils.d.ts +27 -0
  15. package/dist/dev/registry/utils/utils.js +26 -0
  16. package/dist/dev/registry/utils/utils.js.map +1 -0
  17. package/dist/dev/theme/theme-logic.d.ts +185 -0
  18. package/dist/dev/theme/theme-logic.js +183 -0
  19. package/dist/dev/theme/theme-logic.js.map +1 -0
  20. package/dist/dev/theme/theme-provider.d.ts +88 -0
  21. package/dist/dev/theme/theme-provider.js +149 -0
  22. package/dist/dev/theme/theme-provider.js.map +1 -0
  23. package/dist/prod/index.js +1 -0
  24. package/dist/prod/locales/ar.js +2 -0
  25. package/dist/prod/locales/ar.js.map +1 -0
  26. package/dist/prod/locales/en.js +2 -0
  27. package/dist/prod/locales/en.js.map +1 -0
  28. package/dist/prod/registry/utils/utils.js +2 -0
  29. package/dist/prod/registry/utils/utils.js.map +1 -0
  30. package/dist/prod/theme/theme-logic.js +2 -0
  31. package/dist/prod/theme/theme-logic.js.map +1 -0
  32. package/dist/prod/theme/theme-provider.js +2 -0
  33. package/dist/prod/theme/theme-provider.js.map +1 -0
  34. package/package.json +102 -4
  35. package/registry/accordion/accordion.tsx +210 -0
  36. package/registry/accordion/item.json +4 -0
  37. package/registry/alert/alert.tsx +200 -0
  38. package/registry/alert/item.json +4 -0
  39. package/registry/alert-dialog/alert-dialog.tsx +530 -0
  40. package/registry/alert-dialog/item.json +4 -0
  41. package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
  42. package/registry/aspect-ratio/item.json +4 -0
  43. package/registry/attachment/attachment.tsx +459 -0
  44. package/registry/attachment/item.json +4 -0
  45. package/registry/avatar/avatar.tsx +385 -0
  46. package/registry/avatar/item.json +4 -0
  47. package/registry/badge/badge.tsx +108 -0
  48. package/registry/badge/item.json +4 -0
  49. package/registry/breadcrumb/breadcrumb.tsx +354 -0
  50. package/registry/breadcrumb/item.json +4 -0
  51. package/registry/bubble/bubble.tsx +260 -0
  52. package/registry/bubble/item.json +4 -0
  53. package/registry/button/button.tsx +125 -0
  54. package/registry/button/item.json +4 -0
  55. package/registry/button-group/button-group.tsx +189 -0
  56. package/registry/button-group/item.json +4 -0
  57. package/registry/calendar/calendar.tsx +348 -0
  58. package/registry/calendar/item.json +4 -0
  59. package/registry/card/card.tsx +311 -0
  60. package/registry/card/item.json +4 -0
  61. package/registry/carousel/carousel.tsx +851 -0
  62. package/registry/carousel/item.json +4 -0
  63. package/registry/chart/chart.tsx +1956 -0
  64. package/registry/chart/item.json +4 -0
  65. package/registry/chart-scale/chart-scale.ts +670 -0
  66. package/registry/chart-scale/item.json +5 -0
  67. package/registry/checkbox/checkbox.tsx +79 -0
  68. package/registry/checkbox/item.json +4 -0
  69. package/registry/code-block/code-block.tsx +470 -0
  70. package/registry/code-block/item.json +4 -0
  71. package/registry/collapsible/collapsible.tsx +117 -0
  72. package/registry/collapsible/item.json +4 -0
  73. package/registry/combobox/combobox.tsx +977 -0
  74. package/registry/combobox/item.json +4 -0
  75. package/registry/command/command.tsx +757 -0
  76. package/registry/command/item.json +4 -0
  77. package/registry/context-menu/context-menu.tsx +697 -0
  78. package/registry/context-menu/item.json +4 -0
  79. package/registry/data-table/data-table.tsx +437 -0
  80. package/registry/data-table/item.json +4 -0
  81. package/registry/date-picker/date-picker.tsx +349 -0
  82. package/registry/date-picker/item.json +4 -0
  83. package/registry/dialog/dialog.tsx +412 -0
  84. package/registry/dialog/item.json +4 -0
  85. package/registry/direction/direction.tsx +88 -0
  86. package/registry/direction/item.json +4 -0
  87. package/registry/drawer/drawer.tsx +787 -0
  88. package/registry/drawer/item.json +4 -0
  89. package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
  90. package/registry/dropdown-menu/item.json +4 -0
  91. package/registry/empty/empty.tsx +240 -0
  92. package/registry/empty/item.json +4 -0
  93. package/registry/field/field.tsx +624 -0
  94. package/registry/field/item.json +4 -0
  95. package/registry/form/form.tsx +1112 -0
  96. package/registry/form/item.json +4 -0
  97. package/registry/hover-card/hover-card.tsx +144 -0
  98. package/registry/hover-card/item.json +4 -0
  99. package/registry/icons/icons.tsx +1691 -0
  100. package/registry/icons/item.json +4 -0
  101. package/registry/input/input.tsx +43 -0
  102. package/registry/input/item.json +4 -0
  103. package/registry/input-group/input-group.tsx +315 -0
  104. package/registry/input-group/item.json +4 -0
  105. package/registry/input-otp/input-otp.tsx +329 -0
  106. package/registry/input-otp/item.json +4 -0
  107. package/registry/item/item.json +4 -0
  108. package/registry/item/item.tsx +439 -0
  109. package/registry/kbd/item.json +4 -0
  110. package/registry/kbd/kbd.tsx +65 -0
  111. package/registry/label/item.json +4 -0
  112. package/registry/label/label.tsx +60 -0
  113. package/registry/marker/item.json +4 -0
  114. package/registry/marker/marker.tsx +175 -0
  115. package/registry/menubar/item.json +4 -0
  116. package/registry/menubar/menubar.tsx +694 -0
  117. package/registry/message/item.json +4 -0
  118. package/registry/message/message.tsx +280 -0
  119. package/registry/message-scroller/item.json +4 -0
  120. package/registry/message-scroller/message-scroller.tsx +262 -0
  121. package/registry/native-select/item.json +4 -0
  122. package/registry/native-select/native-select.tsx +113 -0
  123. package/registry/navigation-menu/item.json +4 -0
  124. package/registry/navigation-menu/navigation-menu.tsx +362 -0
  125. package/registry/pagination/item.json +4 -0
  126. package/registry/pagination/pagination.tsx +460 -0
  127. package/registry/password-input/item.json +5 -0
  128. package/registry/password-input/password-input.tsx +494 -0
  129. package/registry/popover/item.json +4 -0
  130. package/registry/popover/popover.tsx +284 -0
  131. package/registry/progress/item.json +4 -0
  132. package/registry/progress/progress.tsx +190 -0
  133. package/registry/questionnaire/item.json +4 -0
  134. package/registry/questionnaire/questionnaire.tsx +569 -0
  135. package/registry/radio-group/item.json +4 -0
  136. package/registry/radio-group/radio-group.tsx +101 -0
  137. package/registry/resizable/item.json +4 -0
  138. package/registry/resizable/resizable.tsx +455 -0
  139. package/registry/scroll-area/item.json +4 -0
  140. package/registry/scroll-area/scroll-area.tsx +149 -0
  141. package/registry/select/item.json +4 -0
  142. package/registry/select/select.tsx +571 -0
  143. package/registry/separator/item.json +4 -0
  144. package/registry/separator/separator.tsx +52 -0
  145. package/registry/sheet/item.json +4 -0
  146. package/registry/sheet/sheet.tsx +476 -0
  147. package/registry/sidebar/item.json +4 -0
  148. package/registry/sidebar/sidebar.tsx +1870 -0
  149. package/registry/skeleton/item.json +4 -0
  150. package/registry/skeleton/skeleton.tsx +47 -0
  151. package/registry/slider/item.json +4 -0
  152. package/registry/slider/slider.tsx +149 -0
  153. package/registry/spinner/item.json +4 -0
  154. package/registry/spinner/spinner.tsx +51 -0
  155. package/registry/switch/item.json +4 -0
  156. package/registry/switch/switch.tsx +93 -0
  157. package/registry/table/item.json +4 -0
  158. package/registry/table/table.tsx +373 -0
  159. package/registry/tabs/item.json +4 -0
  160. package/registry/tabs/tabs.tsx +209 -0
  161. package/registry/textarea/item.json +4 -0
  162. package/registry/textarea/textarea.tsx +51 -0
  163. package/registry/toast/item.json +4 -0
  164. package/registry/toast/toast.tsx +949 -0
  165. package/registry/toggle/item.json +4 -0
  166. package/registry/toggle/toggle.tsx +110 -0
  167. package/registry/toggle-group/item.json +4 -0
  168. package/registry/toggle-group/toggle-group.tsx +189 -0
  169. package/registry/tooltip/item.json +4 -0
  170. package/registry/tooltip/tooltip.tsx +169 -0
  171. package/registry/trace/item.json +4 -0
  172. package/registry/trace/trace.tsx +356 -0
  173. package/registry/ui-strings/item.json +5 -0
  174. package/registry/ui-strings/ui-strings.tsx +454 -0
  175. package/registry/use-highlighted-code/item.json +5 -0
  176. package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
  177. package/registry/use-mobile/item.json +5 -0
  178. package/registry/use-mobile/use-mobile.ts +46 -0
  179. package/registry/utils/item.json +5 -0
  180. package/registry/utils/utils.ts +38 -0
  181. package/registry.json +3274 -0
  182. package/styles/animations.css +321 -0
  183. package/styles/theme-core.css +55 -0
  184. package/styles/theme.css +12 -0
  185. package/styles/themes/gray.css +69 -0
  186. package/styles/themes/neutral.css +69 -0
  187. package/styles/themes/slate.css +69 -0
  188. package/styles/themes/stone.css +69 -0
  189. package/styles/themes/zinc.css +69 -0
  190. package/styles/typeset.css +251 -0
@@ -0,0 +1,977 @@
1
+ import { createContext, splitProps, useContext } from "@shadow-js/core";
2
+ import {
3
+ type ComboboxChipProps as ComboboxChipPrimitiveProps,
4
+ type ComboboxChipRemoveProps,
5
+ type ComboboxClearProps,
6
+ type ComboboxContentProps as ComboboxContentPrimitiveProps,
7
+ type ComboboxEmptyProps,
8
+ type ComboboxGroupProps,
9
+ type ComboboxInputProps as ComboboxInputPrimitiveProps,
10
+ type ComboboxItemProps as ComboboxItemPrimitiveProps,
11
+ type ComboboxLabelProps,
12
+ type ComboboxLoadingProps,
13
+ Combobox as ComboboxPrimitive,
14
+ type ComboboxProps,
15
+ type ComboboxSeparatorProps,
16
+ type ComboboxTriggerProps,
17
+ type ComboboxChipsProps as ComboboxValueProps,
18
+ type PolymorphicTarget,
19
+ } from "@shadow-js/primitives";
20
+ import { type ComponentProps, For, type JSX } from "@shadow-js/web";
21
+ import { CheckIcon, ChevronDownIcon, XIcon } from "@/components/ui/icons";
22
+ import { useUiStrings } from "@/lib/ui-strings";
23
+ import { type ClassValue, cn } from "@/lib/utils";
24
+
25
+ /** The items a combobox lists, read by {@link ComboboxList}. */
26
+ const itemsContext = createContext<() => readonly unknown[] | undefined>(() => undefined, {
27
+ name: "ComboboxItems",
28
+ });
29
+ /** The items of the group around a {@link ComboboxCollection}. */
30
+ const groupItemsContext = createContext<() => readonly unknown[] | undefined>(() => undefined, {
31
+ name: "ComboboxGroupItems",
32
+ });
33
+
34
+ /**
35
+ * The `items` prop {@link Combobox} adds to the primitive's props: the data that a
36
+ * {@link ComboboxList} with a render function turns into {@link ComboboxItem} parts.
37
+ * @since 1.0.0
38
+ */
39
+ export interface ComboboxItemsProps<T = unknown> {
40
+ /**
41
+ * The data behind the options. A `ComboboxList` whose children are a function renders it once
42
+ * per item. The options still filter themselves from the typed text.
43
+ * @default none
44
+ */
45
+ readonly items?: readonly T[] | undefined;
46
+ }
47
+
48
+ /** The props as the implementation sees them. */
49
+ type Loose = {
50
+ readonly class?: ClassValue;
51
+ readonly children?: JSX.Element;
52
+ readonly [prop: string]: unknown;
53
+ };
54
+
55
+ /** The look shared by the single-line field and the chips field. */
56
+ const FIELD =
57
+ "relative flex w-full min-w-0 rounded-md border border-input outline-none bg-transparent shadow-xs transition-[color,box-shadow] motion-reduce:transition-none dark:bg-input/30 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-[3px] has-[input:focus-visible]:ring-ring/50 has-[input:focus-visible]:outline-solid has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-transparent has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50 has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/20 dark:has-[input[aria-invalid=true]]:ring-destructive/40";
58
+
59
+ /** The look of the small icon buttons at the end of a field. */
60
+ const ICON_BUTTON =
61
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground opacity-50 outline-none hover:opacity-100 disabled:pointer-events-none disabled:opacity-30 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4";
62
+
63
+ /**
64
+ * A text field with a filtered list of options. The user types to narrow the options and picks one;
65
+ * with `multiple`, the picks show as removable chips. Use it for a searchable choice, such as a
66
+ * framework or tag picker.
67
+ *
68
+ * It is controlled with `value` / `onValueChange`, `inputValue` / `onInputValueChange` and `open` /
69
+ * `onOpenChange`, or uncontrolled with the `default*` props. With a `name` it takes part in native
70
+ * forms. Set `shouldFilter={false}` to filter on the server and render exactly the items you want
71
+ * shown. While the list is closed its items are read once without being shown, so a `defaultValue`
72
+ * already shows its label; pass `itemToString` for server rendering and for items that arrive later.
73
+ *
74
+ * Keyboard and ARIA: the input keeps focus and is the only tab stop. Typing opens and filters the
75
+ * list. ArrowDown opens it and highlights the first option, or moves to the next one; ArrowUp does
76
+ * the same from the end. Alt+ArrowDown opens without moving the highlight and Alt+ArrowUp closes.
77
+ * Home and End jump to the first and last option while one is highlighted. Enter picks the
78
+ * highlighted option and never submits a surrounding form while the list is open. Escape closes the
79
+ * list, and a second Escape clears the field. Tab closes the list. With `multiple`, Backspace in an
80
+ * empty input removes the last chip, and ArrowLeft with the caret at the start moves focus to the
81
+ * chips. The input is `role="combobox"` with `aria-autocomplete="list"`, `aria-haspopup="listbox"`,
82
+ * `aria-expanded`, `aria-controls` (while the list is open) and `aria-activedescendant` (the
83
+ * highlighted option). The list is a `role="listbox"` of `role="option"` items with `aria-selected`.
84
+ * Name the input with a `ComboboxLabel` placed outside a group, or with `aria-label`.
85
+ *
86
+ * shadcn/ui differences: this is one set of parts (`ComboboxInput`, `ComboboxContent`,
87
+ * `ComboboxItem`, ...) rather than a `Popover` wrapped around a `Command`. `as` replaces `asChild` on the parts that accept it, and every part has a `data-slot`. The prop is `class`, not `className`.
88
+ *
89
+ * @example
90
+ * import {
91
+ * Combobox,
92
+ * ComboboxContent,
93
+ * ComboboxEmpty,
94
+ * ComboboxInput,
95
+ * ComboboxItem,
96
+ * } from "@/components/ui/combobox";
97
+ *
98
+ * export function FrameworkPicker() {
99
+ * return (
100
+ * <Combobox>
101
+ * <ComboboxInput placeholder="Select a framework" />
102
+ * <ComboboxContent>
103
+ * <ComboboxEmpty>No framework found.</ComboboxEmpty>
104
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
105
+ * <ComboboxItem value="astro">Astro</ComboboxItem>
106
+ * </ComboboxContent>
107
+ * </Combobox>
108
+ * );
109
+ * }
110
+ * @see {@link ComboboxInput}
111
+ * @see {@link ComboboxContent}
112
+ * @since 1.0.0
113
+ */
114
+ function Combobox<T = unknown>(props: ComboboxProps & ComboboxItemsProps<T>): JSX.Element {
115
+ const [local, rest] = splitProps(props as ComboboxProps & ComboboxItemsProps<T>, ["items"]);
116
+ return (
117
+ <itemsContext.Provider value={() => local.items}>
118
+ <ComboboxPrimitive {...(rest as ComboboxProps)} />
119
+ </itemsContext.Provider>
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Own props of {@link ComboboxInput}: the parts the field adds around the `input`.
125
+ * @see {@link ComboboxInput}
126
+ * @since 1.0.0
127
+ */
128
+ export interface ComboboxInputExtraProps {
129
+ /** Shows the chevron button that toggles the list. @default true */
130
+ readonly showTrigger?: boolean | undefined;
131
+ /** Shows the button that clears the text and the selection. @default false */
132
+ readonly showClear?: boolean | undefined;
133
+ /** Rendered after the buttons, inside the field: an icon, or a button of your own. @default none */
134
+ readonly children?: JSX.Element | undefined;
135
+ }
136
+
137
+ /**
138
+ * Props of {@link ComboboxInput}: the attributes of the `input` and {@link ComboboxInputExtraProps}.
139
+ * `class` goes to the field around the input, every other attribute to the `input`.
140
+ * @see {@link ComboboxInput}
141
+ * @since 1.0.0
142
+ */
143
+ export type ComboboxInputProps = ComboboxInputPrimitiveProps & ComboboxInputExtraProps;
144
+
145
+ /**
146
+ * The text field of a combobox: the input that keeps focus, the chevron that toggles the list and,
147
+ * with `showClear`, a clear button. The list is placed
148
+ * against the whole field. Name the input with a `ComboboxLabel` or an `aria-label`. Set
149
+ * `aria-invalid="true"` on the input to draw the field with the destructive border.
150
+ *
151
+ * @param props The input props: see {@link ComboboxInputProps}.
152
+ * @returns The field, with the input inside it.
153
+ * @throws Error when rendered outside a {@link Combobox}.
154
+ * @example
155
+ * import { Combobox, ComboboxContent, ComboboxInput, ComboboxItem } from "@/components/ui/combobox";
156
+ *
157
+ * export function Search() {
158
+ * return (
159
+ * <Combobox>
160
+ * <ComboboxInput placeholder="Search..." showClear aria-label="Search" />
161
+ * <ComboboxContent>
162
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
163
+ * </ComboboxContent>
164
+ * </Combobox>
165
+ * );
166
+ * }
167
+ * @see {@link Combobox}
168
+ * @see {@link ComboboxInputProps}
169
+ * @since 1.0.0
170
+ */
171
+ function ComboboxInput(props: ComboboxInputProps): JSX.Element {
172
+ const [local, rest] = splitProps(props as unknown as Loose & ComboboxInputExtraProps, [
173
+ "class",
174
+ "children",
175
+ "showTrigger",
176
+ "showClear",
177
+ ]);
178
+ return (
179
+ <ComboboxPrimitive.Anchor
180
+ data-slot="combobox-input-group"
181
+ class={cn(FIELD, "h-9 items-center", local.class)}
182
+ >
183
+ <ComboboxPrimitive.Input
184
+ data-slot="combobox-input"
185
+ class="h-full min-w-0 flex-1 bg-transparent px-3 py-1 text-base outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm"
186
+ {...rest}
187
+ />
188
+ {local.showClear === true && <ComboboxClear />}
189
+ {local.showTrigger !== false && <ComboboxTrigger />}
190
+ {local.children}
191
+ </ComboboxPrimitive.Anchor>
192
+ );
193
+ }
194
+
195
+ /**
196
+ * The chevron button that toggles the list. It is not a tab stop (the input is) and pressing it keeps
197
+ * focus in the input. Its default `aria-label` is `"Toggle suggestions"`; pass your own to translate
198
+ * it. `children` replaces the chevron icon.
199
+ *
200
+ * @param props The button props: the attributes of the rendered element, and `children`.
201
+ * @returns The rendered trigger button.
202
+ * @throws Error when rendered outside a {@link Combobox}.
203
+ * @example
204
+ * import { Combobox, ComboboxContent, ComboboxInput, ComboboxItem, ComboboxTrigger } from "@/components/ui/combobox";
205
+ *
206
+ * export function FruitPicker() {
207
+ * return (
208
+ * <Combobox>
209
+ * <ComboboxInput showTrigger={false} aria-label="Fruit" />
210
+ * <ComboboxTrigger aria-label="Show fruits" />
211
+ * <ComboboxContent>
212
+ * <ComboboxItem value="apple">Apple</ComboboxItem>
213
+ * </ComboboxContent>
214
+ * </Combobox>
215
+ * );
216
+ * }
217
+ * @see {@link ComboboxInput}
218
+ * @since 1.0.0
219
+ */
220
+ function ComboboxTrigger<T extends PolymorphicTarget = "button">(
221
+ props: ComboboxTriggerProps<T>,
222
+ ): JSX.Element {
223
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "children"]);
224
+ const t = useUiStrings();
225
+ return (
226
+ <ComboboxPrimitive.Trigger
227
+ data-slot="combobox-trigger"
228
+ aria-label={t("combobox.toggle", "Toggle suggestions")}
229
+ class={cn(ICON_BUTTON, "me-0.5", local.class)}
230
+ {...rest}
231
+ >
232
+ {local.children ?? <ChevronDownIcon />}
233
+ </ComboboxPrimitive.Trigger>
234
+ );
235
+ }
236
+
237
+ /**
238
+ * The button that empties the text and the selection and keeps focus in the input. It is hidden
239
+ * while there is nothing to clear. Its default `aria-label` is `"Clear"`; pass your own to translate
240
+ * it. `children` replaces the X icon.
241
+ *
242
+ * @param props The button props: the attributes of the rendered element, and `children`.
243
+ * @returns The rendered clear button, or nothing while it is hidden.
244
+ * @throws Error when rendered outside a {@link Combobox}.
245
+ * @example
246
+ * import { Combobox, ComboboxClear, ComboboxInput } from "@/components/ui/combobox";
247
+ *
248
+ * export function Cleared() {
249
+ * return (
250
+ * <Combobox>
251
+ * <ComboboxInput showTrigger={false} aria-label="Fruit" />
252
+ * <ComboboxClear aria-label="Clear selection" />
253
+ * </Combobox>
254
+ * );
255
+ * }
256
+ * @see {@link ComboboxInput}
257
+ * @since 1.0.0
258
+ */
259
+ function ComboboxClear<T extends PolymorphicTarget = "button">(
260
+ props: ComboboxClearProps<T>,
261
+ ): JSX.Element {
262
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "children"]);
263
+ const t = useUiStrings();
264
+ return (
265
+ <ComboboxPrimitive.Clear
266
+ data-slot="combobox-clear"
267
+ aria-label={t("combobox.clear", "Clear")}
268
+ class={cn(ICON_BUTTON, local.class)}
269
+ {...rest}
270
+ >
271
+ {local.children ?? <XIcon />}
272
+ </ComboboxPrimitive.Clear>
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Props of {@link ComboboxContent}: the placement options of the popup (such as `side`, `align` and
278
+ * `sideOffset`) and the attributes of the rendered `div`.
279
+ * @see {@link ComboboxContent}
280
+ * @since 1.0.0
281
+ */
282
+ export type ComboboxContentProps<T extends PolymorphicTarget = "div"> =
283
+ ComboboxContentPrimitiveProps<T>;
284
+
285
+ /**
286
+ * The popup list of options. It is portaled to `document.body`, placed against the field and as wide
287
+ * as the field, with a 4 px gap unless you pass `sideOffset`. It never takes focus: the input keeps
288
+ * it and points at the highlighted option with `aria-activedescendant`. Pressing or focusing outside
289
+ * the list, or pressing Escape, closes it.
290
+ *
291
+ * Keyboard and ARIA: the list is a `role="listbox"`, named by the `ComboboxLabel` or else by the
292
+ * input. It is `aria-busy` while {@link ComboboxLoading} is shown and carries `data-empty` when no
293
+ * option matches. Put {@link ComboboxItem}, {@link ComboboxGroup} and {@link ComboboxEmpty} inside it.
294
+ *
295
+ * @param props The list props: see {@link ComboboxContentProps}.
296
+ * @returns The popup list, rendered in a portal while it is open.
297
+ * @throws Error when rendered outside a {@link Combobox}.
298
+ * @example
299
+ * import { Combobox, ComboboxContent, ComboboxInput, ComboboxItem } from "@/components/ui/combobox";
300
+ *
301
+ * export function FruitList() {
302
+ * return (
303
+ * <Combobox>
304
+ * <ComboboxInput aria-label="Fruit" />
305
+ * <ComboboxContent>
306
+ * <ComboboxItem value="apple">Apple</ComboboxItem>
307
+ * </ComboboxContent>
308
+ * </Combobox>
309
+ * );
310
+ * }
311
+ * @see {@link ComboboxItem}
312
+ * @since 1.0.0
313
+ */
314
+ function ComboboxContent<T extends PolymorphicTarget = "div">(
315
+ props: ComboboxContentProps<T>,
316
+ ): JSX.Element {
317
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "children"]);
318
+ return (
319
+ <ComboboxPrimitive.Portal>
320
+ <ComboboxPrimitive.Content
321
+ data-slot="combobox-content"
322
+ sideOffset={4}
323
+ class={cn(
324
+ "relative z-50 max-h-[min(24rem,var(--primitives-available-height))] w-(--primitives-anchor-width) origin-(--primitives-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
325
+ local.class,
326
+ )}
327
+ {...rest}
328
+ >
329
+ {local.children}
330
+ </ComboboxPrimitive.Content>
331
+ </ComboboxPrimitive.Portal>
332
+ );
333
+ }
334
+
335
+ /**
336
+ * Props of {@link ComboboxItem}: `value` (required, unique in the list), `label`, `disabled` and the
337
+ * attributes of the rendered `div`.
338
+ * @see {@link ComboboxItem}
339
+ * @since 1.0.0
340
+ */
341
+ export type ComboboxItemProps<T extends PolymorphicTarget = "div"> = ComboboxItemPrimitiveProps<T>;
342
+
343
+ /**
344
+ * One option of the list. Its text is what the filter matches and what the field shows once the
345
+ * option is picked; give `label` when the children are not plain text. A check marks the picked
346
+ * options. A disabled option cannot be highlighted or picked. Filtered-out options are not rendered.
347
+ *
348
+ * Keyboard and ARIA: the option is a `role="option"` with `aria-selected`, and `aria-disabled` when
349
+ * disabled. The input keeps focus and highlights it with the arrow keys; Enter picks the highlighted
350
+ * option. Picking a single-select option closes the list and shows its label in the field; picking
351
+ * an option of a `multiple` combobox toggles it and keeps the list open.
352
+ *
353
+ * @param props The option props: see {@link ComboboxItemProps}.
354
+ * @returns The option, or nothing while the filter rejects it.
355
+ * @throws Error when rendered outside a {@link Combobox}.
356
+ * @example
357
+ * import { Combobox, ComboboxContent, ComboboxInput, ComboboxItem } from "@/components/ui/combobox";
358
+ *
359
+ * export function Frameworks() {
360
+ * return (
361
+ * <Combobox>
362
+ * <ComboboxInput aria-label="Framework" />
363
+ * <ComboboxContent>
364
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
365
+ * <ComboboxItem value="astro" label="Astro">
366
+ * Astro
367
+ * </ComboboxItem>
368
+ * </ComboboxContent>
369
+ * </Combobox>
370
+ * );
371
+ * }
372
+ * @see {@link ComboboxContent}
373
+ * @since 1.0.0
374
+ */
375
+ function ComboboxItem<T extends PolymorphicTarget = "div">(
376
+ props: ComboboxItemProps<T>,
377
+ ): JSX.Element {
378
+ const [local, rest] = splitProps(props as unknown as Loose & { readonly value: string }, [
379
+ "class",
380
+ "children",
381
+ "value",
382
+ ]);
383
+ return (
384
+ <ComboboxPrimitive.Item
385
+ data-slot="combobox-item"
386
+ value={local.value}
387
+ class={cn(
388
+ "group/combobox-item relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 ps-2 pe-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground forced-colors:data-[highlighted]:bg-[Highlight] forced-colors:data-[highlighted]:text-[HighlightText] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
389
+ local.class,
390
+ )}
391
+ {...rest}
392
+ >
393
+ {local.children}
394
+ <span class="pointer-events-none absolute end-2 flex size-3.5 items-center justify-center">
395
+ <CheckIcon class="hidden size-4 group-data-[state=checked]/combobox-item:block" />
396
+ </span>
397
+ </ComboboxPrimitive.Item>
398
+ );
399
+ }
400
+
401
+ /**
402
+ * The message shown inside the list while it is open and no option matches. It is hidden while
403
+ * {@link ComboboxLoading} is shown. The list itself announces the result count to screen readers.
404
+ *
405
+ * @param props The message props: the attributes of the rendered `div`.
406
+ * @returns The message, or nothing while the list has matches.
407
+ * @throws Error when rendered outside a {@link Combobox}.
408
+ * @example
409
+ * import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput } from "@/components/ui/combobox";
410
+ *
411
+ * export function Empty() {
412
+ * return (
413
+ * <Combobox>
414
+ * <ComboboxInput aria-label="Framework" />
415
+ * <ComboboxContent>
416
+ * <ComboboxEmpty>No framework found.</ComboboxEmpty>
417
+ * </ComboboxContent>
418
+ * </Combobox>
419
+ * );
420
+ * }
421
+ * @see {@link ComboboxContent}
422
+ * @since 1.0.0
423
+ */
424
+ function ComboboxEmpty<T extends PolymorphicTarget = "div">(
425
+ props: ComboboxEmptyProps<T>,
426
+ ): JSX.Element {
427
+ const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
428
+ return (
429
+ <ComboboxPrimitive.Empty
430
+ data-slot="combobox-empty"
431
+ class={cn("py-6 text-center text-sm", local.class)}
432
+ {...rest}
433
+ />
434
+ );
435
+ }
436
+
437
+ /**
438
+ * The status row shown while your results are loading. The combobox does not know about loading, so
439
+ * render it from your own state (for example with `Show`). While it is mounted the list is
440
+ * `aria-busy`.
441
+ *
442
+ * @param props The row props: the attributes of the rendered `div`.
443
+ * @returns The status row.
444
+ * @throws Error when rendered outside a {@link Combobox}.
445
+ * @example
446
+ * import { useSignal } from "@shadow-js/core";
447
+ * import { Show } from "@shadow-js/web";
448
+ * import { Combobox, ComboboxContent, ComboboxInput, ComboboxLoading } from "@/components/ui/combobox";
449
+ *
450
+ * export function Results() {
451
+ * const [loading] = useSignal(true);
452
+ * return (
453
+ * <Combobox>
454
+ * <ComboboxInput aria-label="Search" />
455
+ * <ComboboxContent>
456
+ * <Show when={loading()}>
457
+ * <ComboboxLoading>Searching...</ComboboxLoading>
458
+ * </Show>
459
+ * </ComboboxContent>
460
+ * </Combobox>
461
+ * );
462
+ * }
463
+ * @see {@link ComboboxContent}
464
+ * @since 1.0.0
465
+ */
466
+ function ComboboxLoading<T extends PolymorphicTarget = "div">(
467
+ props: ComboboxLoadingProps<T>,
468
+ ): JSX.Element {
469
+ const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
470
+ return (
471
+ <ComboboxPrimitive.Loading
472
+ data-slot="combobox-loading"
473
+ class={cn("py-6 text-center text-sm text-muted-foreground", local.class)}
474
+ {...rest}
475
+ />
476
+ );
477
+ }
478
+
479
+ /**
480
+ * A group of options under a {@link ComboboxLabel} heading, which names the group. The group is
481
+ * hidden while the filter rejects all of its options.
482
+ *
483
+ * @param props The group props: the attributes of the rendered `div`.
484
+ * @returns The group of options.
485
+ * @throws Error when rendered outside a {@link Combobox}.
486
+ * @example
487
+ * import { Combobox, ComboboxContent, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel } from "@/components/ui/combobox";
488
+ *
489
+ * export function Tools() {
490
+ * return (
491
+ * <Combobox>
492
+ * <ComboboxInput aria-label="Tool" />
493
+ * <ComboboxContent>
494
+ * <ComboboxGroup>
495
+ * <ComboboxLabel>Frameworks</ComboboxLabel>
496
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
497
+ * </ComboboxGroup>
498
+ * </ComboboxContent>
499
+ * </Combobox>
500
+ * );
501
+ * }
502
+ * @see {@link ComboboxLabel}
503
+ * @since 1.0.0
504
+ */
505
+ function ComboboxGroup<T extends PolymorphicTarget = "div">(
506
+ props: ComboboxGroupProps<T> & ComboboxItemsProps,
507
+ ): JSX.Element {
508
+ const [local, rest] = splitProps(
509
+ props as unknown as ComboboxItemsProps & Record<string, unknown>,
510
+ ["items"],
511
+ );
512
+ return (
513
+ <groupItemsContext.Provider value={() => local.items}>
514
+ <ComboboxPrimitive.Group
515
+ data-slot="combobox-group"
516
+ {...(rest as unknown as ComboboxGroupProps<T>)}
517
+ />
518
+ </groupItemsContext.Provider>
519
+ );
520
+ }
521
+
522
+ /**
523
+ * A heading. Inside a {@link ComboboxGroup} it names the group; outside one it is the label of the
524
+ * input and names the list.
525
+ *
526
+ * @param props The label props: the attributes of the rendered element.
527
+ * @returns The heading element.
528
+ * @throws Error when rendered outside a {@link Combobox}.
529
+ * @example
530
+ * import { Combobox, ComboboxInput, ComboboxLabel } from "@/components/ui/combobox";
531
+ *
532
+ * export function FrameworkField() {
533
+ * return (
534
+ * <Combobox>
535
+ * <ComboboxLabel>Framework</ComboboxLabel>
536
+ * <ComboboxInput />
537
+ * </Combobox>
538
+ * );
539
+ * }
540
+ * @see {@link ComboboxGroup}
541
+ * @since 1.0.0
542
+ */
543
+ function ComboboxLabel<T extends PolymorphicTarget = "label">(
544
+ props: ComboboxLabelProps<T>,
545
+ ): JSX.Element {
546
+ const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
547
+ return (
548
+ <ComboboxPrimitive.Label
549
+ data-slot="combobox-label"
550
+ class={cn("px-2 py-1.5 text-xs text-muted-foreground", local.class)}
551
+ {...rest}
552
+ />
553
+ );
554
+ }
555
+
556
+ /**
557
+ * A divider between groups of options. It is decorative (`role="presentation"`), and it is hidden
558
+ * while the filter narrows the list, because the groups it divides may then be incomplete.
559
+ *
560
+ * @param props The divider props: the attributes of the rendered `div`.
561
+ * @returns The divider.
562
+ * @throws Error when rendered outside a {@link Combobox}.
563
+ * @example
564
+ * import { Combobox, ComboboxContent, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxSeparator } from "@/components/ui/combobox";
565
+ *
566
+ * export function Sections() {
567
+ * return (
568
+ * <Combobox>
569
+ * <ComboboxInput aria-label="Tool" />
570
+ * <ComboboxContent>
571
+ * <ComboboxGroup>
572
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
573
+ * </ComboboxGroup>
574
+ * <ComboboxSeparator />
575
+ * <ComboboxGroup>
576
+ * <ComboboxItem value="vite">Vite</ComboboxItem>
577
+ * </ComboboxGroup>
578
+ * </ComboboxContent>
579
+ * </Combobox>
580
+ * );
581
+ * }
582
+ * @see {@link ComboboxGroup}
583
+ * @since 1.0.0
584
+ */
585
+ function ComboboxSeparator<T extends PolymorphicTarget = "div">(
586
+ props: ComboboxSeparatorProps<T>,
587
+ ): JSX.Element {
588
+ const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
589
+ return (
590
+ <ComboboxPrimitive.Separator
591
+ data-slot="combobox-separator"
592
+ class={cn("pointer-events-none -mx-1 my-1 h-px bg-border", local.class)}
593
+ {...rest}
594
+ />
595
+ );
596
+ }
597
+
598
+ /**
599
+ * The field of a `multiple` combobox: the chips and the input wrap inside one bordered box that the
600
+ * list is placed against. Put {@link ComboboxValue} (the chips) and {@link ComboboxChipsInput} inside
601
+ * it.
602
+ *
603
+ * @param props The attributes of the rendered `div`. `class` is merged with the field classes.
604
+ * @returns The field.
605
+ * @throws Error when rendered outside a {@link Combobox}.
606
+ * @example
607
+ * import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxItem, ComboboxValue } from "@/components/ui/combobox";
608
+ *
609
+ * export function Tags() {
610
+ * return (
611
+ * <Combobox multiple defaultValue={["next"]}>
612
+ * <ComboboxChips>
613
+ * <ComboboxValue>
614
+ * {(chip) => <ComboboxChip value={chip.value}>{chip.label()}</ComboboxChip>}
615
+ * </ComboboxValue>
616
+ * <ComboboxChipsInput aria-label="Frameworks" placeholder="Add a framework" />
617
+ * </ComboboxChips>
618
+ * <ComboboxContent>
619
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
620
+ * <ComboboxItem value="astro">Astro</ComboboxItem>
621
+ * </ComboboxContent>
622
+ * </Combobox>
623
+ * );
624
+ * }
625
+ * @see {@link ComboboxValue}
626
+ * @since 1.0.0
627
+ */
628
+ function ComboboxChips(props: ComponentProps<"div">): JSX.Element {
629
+ const [local, rest] = splitProps(props, ["class"]);
630
+ return (
631
+ <ComboboxPrimitive.Anchor
632
+ data-slot="combobox-chips"
633
+ class={cn(FIELD, "min-h-9 flex-wrap items-center gap-1.5 px-2.5 py-1.5 text-sm", local.class)}
634
+ {...rest}
635
+ />
636
+ );
637
+ }
638
+
639
+ /**
640
+ * Renders one {@link ComboboxChip} per selected value, in selection order. Its child is a function
641
+ * that receives the chip's `value` and `label()` and returns the chip. The wrapper has
642
+ * `display: contents`, so the chips flow inside {@link ComboboxChips}.
643
+ *
644
+ * @param props The attributes of the wrapper, and `children`: a function from the chip data to a
645
+ * {@link ComboboxChip}.
646
+ * @returns The wrapper with one chip per selected value.
647
+ * @throws Error when rendered outside a {@link Combobox}.
648
+ * @example
649
+ * import { Combobox, ComboboxChip, ComboboxChips, ComboboxValue } from "@/components/ui/combobox";
650
+ *
651
+ * export function Selected() {
652
+ * return (
653
+ * <Combobox multiple defaultValue={["next"]}>
654
+ * <ComboboxChips>
655
+ * <ComboboxValue>
656
+ * {(chip) => <ComboboxChip value={chip.value}>{chip.label()}</ComboboxChip>}
657
+ * </ComboboxValue>
658
+ * </ComboboxChips>
659
+ * </Combobox>
660
+ * );
661
+ * }
662
+ * @see {@link ComboboxChips}
663
+ * @since 1.0.0
664
+ */
665
+ function ComboboxValue<T extends PolymorphicTarget = "div">(
666
+ props: ComboboxValueProps<T>,
667
+ ): JSX.Element {
668
+ const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
669
+ return (
670
+ <ComboboxPrimitive.Chips
671
+ data-slot="combobox-value"
672
+ class={cn("contents", local.class)}
673
+ {...(rest as unknown as ComboboxValueProps)}
674
+ />
675
+ );
676
+ }
677
+
678
+ /**
679
+ * Own props of {@link ComboboxChip}: the extra parts the chip adds.
680
+ * @see {@link ComboboxChip}
681
+ * @since 1.0.0
682
+ */
683
+ export interface ComboboxChipExtraProps {
684
+ /** Shows the button that removes the chip. @default true */
685
+ readonly showRemove?: boolean | undefined;
686
+ }
687
+
688
+ /**
689
+ * Props of {@link ComboboxChip}: `value` (the selected value it stands for), `showRemove` and the
690
+ * attributes of the rendered `div`.
691
+ * @see {@link ComboboxChip}
692
+ * @since 1.0.0
693
+ */
694
+ export type ComboboxChipProps<T extends PolymorphicTarget = "div"> = ComboboxChipPrimitiveProps<T> &
695
+ ComboboxChipExtraProps;
696
+
697
+ /**
698
+ * A selected value of a `multiple` combobox, shown with a remove button. From the input, ArrowLeft
699
+ * with the caret at the start moves focus to the last chip. Inside the chips, ArrowLeft and
700
+ * ArrowRight move between chips, and Backspace or Delete removes the focused chip.
701
+ *
702
+ * @param props The chip props: see {@link ComboboxChipProps}.
703
+ * @returns The chip.
704
+ * @throws Error when rendered outside a {@link Combobox}.
705
+ * @example
706
+ * import { Combobox, ComboboxChip, ComboboxChips, ComboboxValue } from "@/components/ui/combobox";
707
+ *
708
+ * export function Tag() {
709
+ * return (
710
+ * <Combobox multiple defaultValue={["next"]}>
711
+ * <ComboboxChips>
712
+ * <ComboboxValue>
713
+ * {(chip) => (
714
+ * <ComboboxChip value={chip.value} showRemove={false}>
715
+ * {chip.label()}
716
+ * </ComboboxChip>
717
+ * )}
718
+ * </ComboboxValue>
719
+ * </ComboboxChips>
720
+ * </Combobox>
721
+ * );
722
+ * }
723
+ * @see {@link ComboboxValue}
724
+ * @since 1.0.0
725
+ */
726
+ function ComboboxChip<T extends PolymorphicTarget = "div">(
727
+ props: ComboboxChipProps<T>,
728
+ ): JSX.Element {
729
+ const [local, rest] = splitProps(
730
+ props as unknown as Loose & ComboboxChipExtraProps & { readonly value: string },
731
+ ["class", "children", "showRemove", "value"],
732
+ );
733
+ return (
734
+ <ComboboxPrimitive.Chip
735
+ data-slot="combobox-chip"
736
+ value={local.value}
737
+ class={cn(
738
+ "flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 forced-colors:border forced-colors:border-[ButtonText] focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
739
+ local.class,
740
+ )}
741
+ {...rest}
742
+ >
743
+ {local.children}
744
+ {local.showRemove !== false && <ComboboxChipRemove />}
745
+ </ComboboxPrimitive.Chip>
746
+ );
747
+ }
748
+
749
+ /**
750
+ * The button that removes its chip. It keeps focus in the input and is not a tab stop; the chip
751
+ * itself is removable with the keyboard. Its default `aria-label` is `Remove` followed by the chip's
752
+ * label; pass your own to translate it. `children` replaces the X icon.
753
+ *
754
+ * @param props The button props: the attributes of the rendered element, and `children`.
755
+ * @returns The remove button.
756
+ * @throws Error when rendered outside a {@link ComboboxChip}.
757
+ * @example
758
+ * import { Combobox, ComboboxChip, ComboboxChipRemove, ComboboxChips, ComboboxValue } from "@/components/ui/combobox";
759
+ *
760
+ * export function RemovableTag() {
761
+ * return (
762
+ * <Combobox multiple defaultValue={["next"]}>
763
+ * <ComboboxChips>
764
+ * <ComboboxValue>
765
+ * {(chip) => (
766
+ * <ComboboxChip value={chip.value} showRemove={false}>
767
+ * {chip.label()}
768
+ * <ComboboxChipRemove aria-label={`Remove ${chip.label()}`} />
769
+ * </ComboboxChip>
770
+ * )}
771
+ * </ComboboxValue>
772
+ * </ComboboxChips>
773
+ * </Combobox>
774
+ * );
775
+ * }
776
+ * @see {@link ComboboxChip}
777
+ * @since 1.0.0
778
+ */
779
+ function ComboboxChipRemove<T extends PolymorphicTarget = "button">(
780
+ props: ComboboxChipRemoveProps<T>,
781
+ ): JSX.Element {
782
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "children"]);
783
+ return (
784
+ <ComboboxPrimitive.ChipRemove
785
+ data-slot="combobox-chip-remove"
786
+ class={cn(
787
+ "-ms-1 inline-flex size-4 items-center justify-center rounded-sm opacity-50 hover:opacity-100 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-3.5",
788
+ local.class,
789
+ )}
790
+ {...rest}
791
+ >
792
+ {local.children ?? <XIcon />}
793
+ </ComboboxPrimitive.ChipRemove>
794
+ );
795
+ }
796
+
797
+ /**
798
+ * The text input inside {@link ComboboxChips}. It has no box of its own and fills the room the chips
799
+ * leave. Name it with an `aria-label` or a {@link ComboboxLabel}. Its keys are the ones listed on
800
+ * {@link Combobox}, including Backspace in an empty input, which removes the last chip.
801
+ *
802
+ * @param props The input props: the attributes of the rendered `input`. `class` is merged with the
803
+ * input's classes.
804
+ * @returns The input.
805
+ * @throws Error when rendered outside a {@link Combobox}.
806
+ * @example
807
+ * import { Combobox, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxItem } from "@/components/ui/combobox";
808
+ *
809
+ * export function Adder() {
810
+ * return (
811
+ * <Combobox multiple>
812
+ * <ComboboxChips>
813
+ * <ComboboxChipsInput aria-label="Frameworks" placeholder="Add a framework" />
814
+ * </ComboboxChips>
815
+ * <ComboboxContent>
816
+ * <ComboboxItem value="next">Next.js</ComboboxItem>
817
+ * </ComboboxContent>
818
+ * </Combobox>
819
+ * );
820
+ * }
821
+ * @see {@link ComboboxChips}
822
+ * @since 1.0.0
823
+ */
824
+ function ComboboxChipsInput(props: ComboboxInputPrimitiveProps): JSX.Element {
825
+ const [local, rest] = splitProps(props as unknown as Loose, ["class"]);
826
+ return (
827
+ <ComboboxPrimitive.Input
828
+ data-slot="combobox-chips-input"
829
+ class={cn(
830
+ "min-w-16 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
831
+ local.class,
832
+ )}
833
+ {...rest}
834
+ />
835
+ );
836
+ }
837
+
838
+ /**
839
+ * True for a render function `(item, index) => node`. A child that is a node, or the zero-argument
840
+ * accessor the runtime makes of a lazy child, is not one.
841
+ */
842
+ function isRenderFunction(
843
+ children: unknown,
844
+ ): children is (item: unknown, index: number) => JSX.Element {
845
+ return typeof children === "function" && children.length > 0;
846
+ }
847
+
848
+ /**
849
+ * Props of {@link ComboboxList}: the attributes of the `div`, with children that are either nodes or
850
+ * a render function `(item, index) => node` over the `items` of the {@link Combobox}.
851
+ * @since 1.0.0
852
+ */
853
+ export type ComboboxListProps<T = unknown> = Omit<ComponentProps<"div">, "children"> & {
854
+ readonly children?: JSX.Element | ((item: T, index: number) => JSX.Element) | undefined;
855
+ };
856
+
857
+ /**
858
+ * The scrolling list inside a {@link ComboboxContent}, as in shadcn/ui. With nodes as children it is
859
+ * a layout wrapper. With a function as children it renders that function once per item of the
860
+ * `items` prop of the {@link Combobox} (the index is the position when the row is created). The
861
+ * options still filter themselves from the typed text, so the function returns a
862
+ * {@link ComboboxItem} (or a group) for every item. The listbox role stays on the content.
863
+ *
864
+ * @param props The `div` attributes and the children, nodes or a render function.
865
+ * @returns The list `div`.
866
+ * @throws Error when rendered outside a {@link Combobox}.
867
+ * @example
868
+ * import { Combobox, ComboboxContent, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox";
869
+ *
870
+ * const frameworks = ["Next.js", "Astro", "Remix"];
871
+ *
872
+ * export function FrameworkPicker() {
873
+ * return (
874
+ * <Combobox items={frameworks}>
875
+ * <ComboboxInput aria-label="Framework" />
876
+ * <ComboboxContent>
877
+ * <ComboboxList>
878
+ * {(name: string) => <ComboboxItem value={name}>{name}</ComboboxItem>}
879
+ * </ComboboxList>
880
+ * </ComboboxContent>
881
+ * </Combobox>
882
+ * );
883
+ * }
884
+ * @see {@link ComboboxCollection}
885
+ * @since 1.0.0
886
+ */
887
+ function ComboboxList<T = unknown>(props: ComboboxListProps<T>): JSX.Element {
888
+ const [local, rest] = splitProps(props as ComboboxListProps<T> & { readonly class?: unknown }, [
889
+ "class",
890
+ "children",
891
+ ]);
892
+ const items = useContext(itemsContext);
893
+ return (
894
+ <div
895
+ data-slot="combobox-list"
896
+ class={cn("max-h-[inherit] overflow-y-auto overscroll-contain", local.class as ClassValue)}
897
+ {...rest}
898
+ >
899
+ {isRenderFunction(local.children) ? (
900
+ <For each={items() ?? []}>
901
+ {(item, index) =>
902
+ (local.children as (item: T, index: number) => JSX.Element)(item as T, index())
903
+ }
904
+ </For>
905
+ ) : (
906
+ (local.children as JSX.Element)
907
+ )}
908
+ </div>
909
+ );
910
+ }
911
+
912
+ /**
913
+ * Props of {@link ComboboxCollection}.
914
+ * @since 1.0.0
915
+ */
916
+ export interface ComboboxCollectionProps<T = unknown> {
917
+ /**
918
+ * The items to render. Without it the items of the surrounding {@link ComboboxGroup} are used,
919
+ * and without a group those of the {@link Combobox}.
920
+ * @default the group's items, else the root's
921
+ */
922
+ readonly items?: readonly T[] | undefined;
923
+ /** Renders one item. */
924
+ readonly children: (item: T, index: number) => JSX.Element;
925
+ }
926
+
927
+ /**
928
+ * Renders a render function once per item, inside a {@link ComboboxGroup} that was given `items`
929
+ * (or with its own `items`). It adds no element of its own, so the group keeps hiding itself while
930
+ * the filter rejects all of its options.
931
+ *
932
+ * @param props The items (optional) and the render function.
933
+ * @returns One node per item.
934
+ * @throws Error when rendered outside a {@link Combobox}.
935
+ * @example
936
+ * import { ComboboxCollection, ComboboxGroup, ComboboxItem, ComboboxLabel } from "@/components/ui/combobox";
937
+ *
938
+ * export function Fruits() {
939
+ * return (
940
+ * <ComboboxGroup items={["Apple", "Banana"]}>
941
+ * <ComboboxLabel>Fruits</ComboboxLabel>
942
+ * <ComboboxCollection>
943
+ * {(name: string) => <ComboboxItem value={name}>{name}</ComboboxItem>}
944
+ * </ComboboxCollection>
945
+ * </ComboboxGroup>
946
+ * );
947
+ * }
948
+ * @see {@link ComboboxList}
949
+ * @since 1.0.0
950
+ */
951
+ function ComboboxCollection<T = unknown>(props: ComboboxCollectionProps<T>): JSX.Element {
952
+ const group = useContext(groupItemsContext);
953
+ const root = useContext(itemsContext);
954
+ const items = (): readonly unknown[] => props.items ?? group() ?? root() ?? [];
955
+ return <For each={items()}>{(item, index) => props.children(item as T, index())}</For>;
956
+ }
957
+
958
+ export {
959
+ Combobox,
960
+ ComboboxChip,
961
+ ComboboxChipRemove,
962
+ ComboboxChips,
963
+ ComboboxChipsInput,
964
+ ComboboxClear,
965
+ ComboboxCollection,
966
+ ComboboxContent,
967
+ ComboboxEmpty,
968
+ ComboboxGroup,
969
+ ComboboxInput,
970
+ ComboboxItem,
971
+ ComboboxLabel,
972
+ ComboboxList,
973
+ ComboboxLoading,
974
+ ComboboxSeparator,
975
+ ComboboxTrigger,
976
+ ComboboxValue,
977
+ };