@multicomma/arkcn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +78 -0
  2. package/bin/arkcn.mjs +78 -0
  3. package/cli/commands.mjs +251 -0
  4. package/cli/project.mjs +168 -0
  5. package/cli/registry.mjs +50 -0
  6. package/cli/ui.mjs +46 -0
  7. package/mcp/server.mjs +149 -0
  8. package/package.json +201 -0
  9. package/registry/base.css +680 -0
  10. package/registry/index.json +1833 -0
  11. package/registry/items/accordion.json +18 -0
  12. package/registry/items/activity-feed.json +18 -0
  13. package/registry/items/alert-dialog.json +18 -0
  14. package/registry/items/alert.json +17 -0
  15. package/registry/items/angle-slider.json +17 -0
  16. package/registry/items/app-shell.json +28 -0
  17. package/registry/items/aspect-ratio.json +13 -0
  18. package/registry/items/attachment.json +19 -0
  19. package/registry/items/avatar.json +17 -0
  20. package/registry/items/badge.json +18 -0
  21. package/registry/items/breadcrumb.json +18 -0
  22. package/registry/items/bubble.json +18 -0
  23. package/registry/items/button-group.json +19 -0
  24. package/registry/items/button.json +18 -0
  25. package/registry/items/calendar.json +19 -0
  26. package/registry/items/canvas.json +24 -0
  27. package/registry/items/card.json +15 -0
  28. package/registry/items/carousel.json +19 -0
  29. package/registry/items/cascader.json +23 -0
  30. package/registry/items/chart.json +17 -0
  31. package/registry/items/chat.json +23 -0
  32. package/registry/items/checkbox.json +18 -0
  33. package/registry/items/clipboard.json +19 -0
  34. package/registry/items/collapsible.json +15 -0
  35. package/registry/items/color-picker.json +19 -0
  36. package/registry/items/combobox.json +20 -0
  37. package/registry/items/command.json +21 -0
  38. package/registry/items/comment-thread.json +25 -0
  39. package/registry/items/context-menu.json +18 -0
  40. package/registry/items/controllable.json +14 -0
  41. package/registry/items/data-grid.json +24 -0
  42. package/registry/items/data-table-adapter.json +14 -0
  43. package/registry/items/data-table.json +32 -0
  44. package/registry/items/date-input.json +17 -0
  45. package/registry/items/dialog.json +19 -0
  46. package/registry/items/direction.json +13 -0
  47. package/registry/items/drag-preview.json +16 -0
  48. package/registry/items/drawer.json +18 -0
  49. package/registry/items/dropdown-menu.json +18 -0
  50. package/registry/items/editable.json +17 -0
  51. package/registry/items/empty.json +17 -0
  52. package/registry/items/field.json +19 -0
  53. package/registry/items/file-upload.json +19 -0
  54. package/registry/items/floating-panel.json +19 -0
  55. package/registry/items/floating-toolbar.json +16 -0
  56. package/registry/items/form.json +14 -0
  57. package/registry/items/format.json +15 -0
  58. package/registry/items/gantt.json +22 -0
  59. package/registry/items/history.json +14 -0
  60. package/registry/items/hotkeys.json +18 -0
  61. package/registry/items/hover-card.json +17 -0
  62. package/registry/items/image-cropper.json +17 -0
  63. package/registry/items/input-group.json +20 -0
  64. package/registry/items/input-otp.json +18 -0
  65. package/registry/items/input.json +15 -0
  66. package/registry/items/item.json +19 -0
  67. package/registry/items/json-tree-view.json +17 -0
  68. package/registry/items/kanban.json +26 -0
  69. package/registry/items/kbd.json +15 -0
  70. package/registry/items/label.json +15 -0
  71. package/registry/items/live-region.json +16 -0
  72. package/registry/items/marker.json +18 -0
  73. package/registry/items/marquee.json +17 -0
  74. package/registry/items/menubar.json +19 -0
  75. package/registry/items/message.json +15 -0
  76. package/registry/items/navigation-menu.json +19 -0
  77. package/registry/items/node-graph.json +22 -0
  78. package/registry/items/number-input.json +18 -0
  79. package/registry/items/pagination.json +18 -0
  80. package/registry/items/password-input.json +18 -0
  81. package/registry/items/popconfirm.json +20 -0
  82. package/registry/items/popover.json +18 -0
  83. package/registry/items/progress.json +17 -0
  84. package/registry/items/qr-code.json +17 -0
  85. package/registry/items/query-builder.json +23 -0
  86. package/registry/items/radio-group.json +17 -0
  87. package/registry/items/rating-group.json +18 -0
  88. package/registry/items/resizable.json +17 -0
  89. package/registry/items/rich-text-editor.json +37 -0
  90. package/registry/items/row-layout.json +15 -0
  91. package/registry/items/scheduler.json +23 -0
  92. package/registry/items/scroll-area.json +17 -0
  93. package/registry/items/segment-group.json +17 -0
  94. package/registry/items/select.json +19 -0
  95. package/registry/items/separator.json +15 -0
  96. package/registry/items/sheet.json +19 -0
  97. package/registry/items/sidebar.json +27 -0
  98. package/registry/items/signature-pad.json +19 -0
  99. package/registry/items/skeleton.json +15 -0
  100. package/registry/items/slider.json +17 -0
  101. package/registry/items/sonner.json +17 -0
  102. package/registry/items/spinner.json +17 -0
  103. package/registry/items/steps.json +19 -0
  104. package/registry/items/swap.json +17 -0
  105. package/registry/items/switch.json +17 -0
  106. package/registry/items/table.json +16 -0
  107. package/registry/items/tabs.json +18 -0
  108. package/registry/items/tags-input.json +18 -0
  109. package/registry/items/textarea.json +15 -0
  110. package/registry/items/time.json +14 -0
  111. package/registry/items/timer.json +18 -0
  112. package/registry/items/toc.json +17 -0
  113. package/registry/items/toggle-group.json +19 -0
  114. package/registry/items/toggle.json +18 -0
  115. package/registry/items/tooltip.json +17 -0
  116. package/registry/items/tour.json +19 -0
  117. package/registry/items/transfer-list.json +24 -0
  118. package/registry/items/tree-select.json +24 -0
  119. package/registry/items/tree-view.json +18 -0
  120. package/registry/items/use-mobile.json +13 -0
  121. package/registry/items/utils.json +17 -0
  122. package/registry/items/virtual-list.json +16 -0
  123. package/src/components/ui/accordion.tsx +111 -0
  124. package/src/components/ui/activity-feed.tsx +228 -0
  125. package/src/components/ui/alert-dialog.tsx +204 -0
  126. package/src/components/ui/alert.tsx +55 -0
  127. package/src/components/ui/angle-slider.tsx +117 -0
  128. package/src/components/ui/app-shell.tsx +985 -0
  129. package/src/components/ui/aspect-ratio.tsx +35 -0
  130. package/src/components/ui/attachment.tsx +193 -0
  131. package/src/components/ui/avatar.tsx +94 -0
  132. package/src/components/ui/badge.tsx +45 -0
  133. package/src/components/ui/breadcrumb.tsx +93 -0
  134. package/src/components/ui/bubble.tsx +119 -0
  135. package/src/components/ui/button-group.tsx +79 -0
  136. package/src/components/ui/button.tsx +65 -0
  137. package/src/components/ui/calendar.tsx +172 -0
  138. package/src/components/ui/canvas.tsx +723 -0
  139. package/src/components/ui/card.tsx +69 -0
  140. package/src/components/ui/carousel.tsx +228 -0
  141. package/src/components/ui/cascader.tsx +754 -0
  142. package/src/components/ui/chart.tsx +303 -0
  143. package/src/components/ui/chat.tsx +544 -0
  144. package/src/components/ui/checkbox.tsx +94 -0
  145. package/src/components/ui/clipboard.tsx +94 -0
  146. package/src/components/ui/collapsible.tsx +32 -0
  147. package/src/components/ui/color-picker.tsx +441 -0
  148. package/src/components/ui/combobox.tsx +400 -0
  149. package/src/components/ui/command.tsx +292 -0
  150. package/src/components/ui/comment-thread.tsx +923 -0
  151. package/src/components/ui/context-menu.tsx +331 -0
  152. package/src/components/ui/data-grid.tsx +1227 -0
  153. package/src/components/ui/data-table.tsx +993 -0
  154. package/src/components/ui/date-input.tsx +103 -0
  155. package/src/components/ui/dialog.tsx +159 -0
  156. package/src/components/ui/direction.tsx +32 -0
  157. package/src/components/ui/drawer.tsx +200 -0
  158. package/src/components/ui/dropdown-menu.tsx +329 -0
  159. package/src/components/ui/editable.tsx +90 -0
  160. package/src/components/ui/empty.tsx +86 -0
  161. package/src/components/ui/field.tsx +218 -0
  162. package/src/components/ui/file-upload.tsx +204 -0
  163. package/src/components/ui/floating-panel.tsx +199 -0
  164. package/src/components/ui/floating-toolbar.tsx +79 -0
  165. package/src/components/ui/format.tsx +22 -0
  166. package/src/components/ui/gantt.tsx +950 -0
  167. package/src/components/ui/hotkeys.tsx +340 -0
  168. package/src/components/ui/hover-card.tsx +83 -0
  169. package/src/components/ui/image-cropper.tsx +100 -0
  170. package/src/components/ui/input-group.tsx +133 -0
  171. package/src/components/ui/input-otp.tsx +82 -0
  172. package/src/components/ui/input.tsx +18 -0
  173. package/src/components/ui/item.tsx +174 -0
  174. package/src/components/ui/json-tree-view.tsx +37 -0
  175. package/src/components/ui/kanban.tsx +884 -0
  176. package/src/components/ui/kbd.tsx +20 -0
  177. package/src/components/ui/label.tsx +30 -0
  178. package/src/components/ui/live-region.tsx +54 -0
  179. package/src/components/ui/marker.tsx +66 -0
  180. package/src/components/ui/marquee.tsx +61 -0
  181. package/src/components/ui/menubar.tsx +456 -0
  182. package/src/components/ui/message.tsx +74 -0
  183. package/src/components/ui/navigation-menu.tsx +202 -0
  184. package/src/components/ui/node-graph.tsx +1723 -0
  185. package/src/components/ui/number-input.tsx +148 -0
  186. package/src/components/ui/pagination.tsx +88 -0
  187. package/src/components/ui/password-input.tsx +104 -0
  188. package/src/components/ui/popconfirm.tsx +149 -0
  189. package/src/components/ui/popover.tsx +126 -0
  190. package/src/components/ui/progress.tsx +114 -0
  191. package/src/components/ui/qr-code.tsx +51 -0
  192. package/src/components/ui/query-builder.tsx +938 -0
  193. package/src/components/ui/radio-group.tsx +108 -0
  194. package/src/components/ui/rating-group.tsx +91 -0
  195. package/src/components/ui/resizable.tsx +41 -0
  196. package/src/components/ui/rich-text-editor.tsx +845 -0
  197. package/src/components/ui/scheduler.tsx +1164 -0
  198. package/src/components/ui/scroll-area.tsx +82 -0
  199. package/src/components/ui/segment-group.tsx +95 -0
  200. package/src/components/ui/select.tsx +354 -0
  201. package/src/components/ui/separator.tsx +32 -0
  202. package/src/components/ui/sheet.tsx +130 -0
  203. package/src/components/ui/sidebar.tsx +665 -0
  204. package/src/components/ui/signature-pad.tsx +112 -0
  205. package/src/components/ui/skeleton.tsx +7 -0
  206. package/src/components/ui/slider.tsx +183 -0
  207. package/src/components/ui/sonner.tsx +37 -0
  208. package/src/components/ui/spinner.tsx +16 -0
  209. package/src/components/ui/steps.tsx +152 -0
  210. package/src/components/ui/swap.tsx +33 -0
  211. package/src/components/ui/switch.tsx +83 -0
  212. package/src/components/ui/table.tsx +72 -0
  213. package/src/components/ui/tabs.tsx +100 -0
  214. package/src/components/ui/tags-input.tsx +153 -0
  215. package/src/components/ui/textarea.tsx +17 -0
  216. package/src/components/ui/timer.tsx +67 -0
  217. package/src/components/ui/toc.tsx +64 -0
  218. package/src/components/ui/toggle-group.tsx +89 -0
  219. package/src/components/ui/toggle.tsx +46 -0
  220. package/src/components/ui/tooltip.tsx +113 -0
  221. package/src/components/ui/tour.tsx +168 -0
  222. package/src/components/ui/transfer-list.tsx +700 -0
  223. package/src/components/ui/tree-select.tsx +638 -0
  224. package/src/components/ui/tree-view.tsx +217 -0
  225. package/src/components/ui/virtual-list.tsx +343 -0
  226. package/src/hooks/use-mobile.ts +19 -0
  227. package/src/lib/controllable.ts +31 -0
  228. package/src/lib/data-table-adapter.ts +67 -0
  229. package/src/lib/drag-preview.ts +39 -0
  230. package/src/lib/form.ts +39 -0
  231. package/src/lib/history.ts +55 -0
  232. package/src/lib/row-layout.ts +81 -0
  233. package/src/lib/table.ts +483 -0
  234. package/src/lib/time.ts +37 -0
  235. package/src/lib/utils.ts +6 -0
  236. package/src/styles/arkcn.css +764 -0
  237. package/src/styles/index.css +3 -0
@@ -0,0 +1,400 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ Combobox as ComboboxPrimitive,
6
+ Portal as PortalPrimitive,
7
+ createListCollection,
8
+ useComboboxContext,
9
+ useFilter,
10
+ useListCollection,
11
+ type CollectionItem,
12
+ type ListCollection,
13
+ } from "@ark-ui/react"
14
+ import { cn } from "@/lib/utils"
15
+
16
+ import { Button } from "@/components/ui/button"
17
+ import { InputGroup, InputGroupAddon, InputGroupTrigger, InputGroupInput } from "@/components/ui/input-group"
18
+ import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
19
+
20
+ function Combobox<T extends CollectionItem>({
21
+ positioning,
22
+ anchor,
23
+ lazyMount = true,
24
+ unmountOnExit = true,
25
+ ...props
26
+ }: React.ComponentProps<typeof ComboboxPrimitive.Root<T>> & {
27
+ /** Anchor the popup to another element, e.g. a `ComboboxChips` container. */
28
+ anchor?: React.RefObject<HTMLElement | null>
29
+ }) {
30
+ return (
31
+ <ComboboxPrimitive.Root
32
+ data-slot="combobox"
33
+ positioning={{
34
+ placement: "bottom-start",
35
+ gutter: 6,
36
+ sameWidth: true,
37
+ ...(anchor ? { getAnchorElement: () => anchor.current } : {}),
38
+ ...positioning,
39
+ }}
40
+ lazyMount={lazyMount}
41
+ unmountOnExit={unmountOnExit}
42
+ {...props}
43
+ />
44
+ )
45
+ }
46
+
47
+ function ComboboxContext({ ...props }: React.ComponentProps<typeof ComboboxPrimitive.Context>) {
48
+ return <ComboboxPrimitive.Context {...props} />
49
+ }
50
+
51
+ function ComboboxRootLabel({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.Label>) {
52
+ return (
53
+ <ComboboxPrimitive.Label
54
+ data-slot="combobox-root-label"
55
+ className={cn("text-sm font-medium", className)}
56
+ {...props}
57
+ />
58
+ )
59
+ }
60
+
61
+ function ComboboxControl({ ...props }: React.ComponentProps<typeof ComboboxPrimitive.Control>) {
62
+ return <ComboboxPrimitive.Control data-slot="combobox-control" {...props} />
63
+ }
64
+
65
+ /**
66
+ * Ark has no value-text part for Combobox. Renders the selected items as
67
+ * text, or `placeholder` when nothing is selected.
68
+ */
69
+ function ComboboxValue({
70
+ placeholder,
71
+ children,
72
+ ...props
73
+ }: React.ComponentProps<"span"> & { placeholder?: React.ReactNode }) {
74
+ const combobox = useComboboxContext()
75
+ return (
76
+ <span data-slot="combobox-value" data-placeholder-shown={combobox.hasSelectedItems ? undefined : ""} {...props}>
77
+ {children ?? (combobox.hasSelectedItems ? combobox.valueAsString : placeholder)}
78
+ </span>
79
+ )
80
+ }
81
+
82
+ /** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */
83
+ function ComboboxTrigger({
84
+ className,
85
+ children,
86
+ ...props
87
+ }: Omit<React.ComponentProps<typeof ComboboxPrimitive.Trigger>, "id">) {
88
+ return (
89
+ <ComboboxPrimitive.Trigger
90
+ data-slot="combobox-trigger"
91
+ className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
92
+ {...props}
93
+ >
94
+ {children}
95
+ <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
96
+ </ComboboxPrimitive.Trigger>
97
+ )
98
+ }
99
+
100
+ function ComboboxClear({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.ClearTrigger>) {
101
+ const combobox = useComboboxContext()
102
+ // Match Base UI: unmount when there is nothing to clear, so the trigger
103
+ // button (hidden via `has-data-[slot=combobox-clear]`) shows again.
104
+ if (!combobox.hasSelectedItems && !combobox.inputValue) return null
105
+ return (
106
+ <ComboboxPrimitive.ClearTrigger data-slot="combobox-clear" className={cn(className)} asChild {...props}>
107
+ <InputGroupTrigger variant="ghost" size="icon-xs">
108
+ <XIcon className="pointer-events-none" />
109
+ </InputGroupTrigger>
110
+ </ComboboxPrimitive.ClearTrigger>
111
+ )
112
+ }
113
+
114
+ /** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */
115
+ function ComboboxInput({
116
+ className,
117
+ children,
118
+ disabled = false,
119
+ showTrigger = true,
120
+ showClear = false,
121
+ ...props
122
+ }: Omit<React.ComponentProps<typeof ComboboxPrimitive.Input>, "id"> & {
123
+ showTrigger?: boolean
124
+ showClear?: boolean
125
+ }) {
126
+ return (
127
+ <ComboboxPrimitive.Control asChild>
128
+ <InputGroup className={cn("w-auto", className)}>
129
+ <ComboboxPrimitive.Input asChild disabled={disabled} {...props}>
130
+ <InputGroupInput />
131
+ </ComboboxPrimitive.Input>
132
+ <InputGroupAddon align="inline-end">
133
+ {showTrigger && (
134
+ <InputGroupTrigger
135
+ size="icon-xs"
136
+ variant="ghost"
137
+ asChild
138
+ data-slot="input-group-trigger"
139
+ className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
140
+ disabled={disabled}
141
+ >
142
+ <ComboboxTrigger />
143
+ </InputGroupTrigger>
144
+ )}
145
+ {showClear && <ComboboxClear disabled={disabled} />}
146
+ </InputGroupAddon>
147
+ {children}
148
+ </InputGroup>
149
+ </ComboboxPrimitive.Control>
150
+ )
151
+ }
152
+
153
+ function ComboboxPositioner({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.Positioner>) {
154
+ return (
155
+ <ComboboxPrimitive.Positioner
156
+ data-slot="combobox-positioner"
157
+ className={cn("isolate [--z-index:50]", className)}
158
+ {...props}
159
+ />
160
+ )
161
+ }
162
+
163
+ /** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */
164
+ function ComboboxContent({ className, ...props }: Omit<React.ComponentProps<typeof ComboboxPrimitive.Content>, "id">) {
165
+ return (
166
+ <PortalPrimitive>
167
+ <ComboboxPositioner>
168
+ <ComboboxPrimitive.Content
169
+ data-slot="combobox-content"
170
+ className={cn(
171
+ "group/combobox-content relative z-50 max-h-(--available-height) w-(--reference-width) max-w-(--available-width) origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 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 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
172
+ className
173
+ )}
174
+ {...props}
175
+ />
176
+ </ComboboxPositioner>
177
+ </PortalPrimitive>
178
+ )
179
+ }
180
+
181
+ function ComboboxList({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.List>) {
182
+ return (
183
+ <ComboboxPrimitive.List
184
+ data-slot="combobox-list"
185
+ className={cn(
186
+ "no-scrollbar max-h-[min(calc(--spacing(72)-(--spacing(9))),calc(var(--available-height)-(--spacing(9))))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0",
187
+ className
188
+ )}
189
+ {...props}
190
+ />
191
+ )
192
+ }
193
+
194
+ function ComboboxItem({
195
+ className,
196
+ children,
197
+ item,
198
+ value,
199
+ ...props
200
+ }: Omit<React.ComponentProps<typeof ComboboxPrimitive.Item>, "item"> & {
201
+ /** The collection item. Falls back to `value` for string collections. */
202
+ item?: CollectionItem
203
+ value?: string
204
+ }) {
205
+ return (
206
+ <ComboboxPrimitive.Item
207
+ data-slot="combobox-item"
208
+ item={item ?? value}
209
+ className={cn(
210
+ "relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
211
+ className
212
+ )}
213
+ {...props}
214
+ >
215
+ {children}
216
+ <ComboboxItemIndicator>
217
+ <CheckIcon className="pointer-events-none" />
218
+ </ComboboxItemIndicator>
219
+ </ComboboxPrimitive.Item>
220
+ )
221
+ }
222
+
223
+ function ComboboxItemText({ ...props }: React.ComponentProps<typeof ComboboxPrimitive.ItemText>) {
224
+ return <ComboboxPrimitive.ItemText data-slot="combobox-item-text" {...props} />
225
+ }
226
+
227
+ function ComboboxItemIndicator({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.ItemIndicator>) {
228
+ return (
229
+ <ComboboxPrimitive.ItemIndicator
230
+ data-slot="combobox-item-indicator"
231
+ className={cn("pointer-events-none absolute right-2 flex size-4 items-center justify-center", className)}
232
+ {...props}
233
+ />
234
+ )
235
+ }
236
+
237
+ function ComboboxGroup({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.ItemGroup>) {
238
+ return <ComboboxPrimitive.ItemGroup data-slot="combobox-group" className={cn(className)} {...props} />
239
+ }
240
+
241
+ function ComboboxLabel({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.ItemGroupLabel>) {
242
+ return (
243
+ <ComboboxPrimitive.ItemGroupLabel
244
+ data-slot="combobox-label"
245
+ className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
246
+ {...props}
247
+ />
248
+ )
249
+ }
250
+
251
+ /**
252
+ * Renders every item in the root's collection through a render function.
253
+ * Ark has no Collection part; this reads the collection from context.
254
+ */
255
+ function ComboboxCollection<T extends CollectionItem = CollectionItem>({
256
+ children,
257
+ }: {
258
+ children: (item: T, index: number) => React.ReactNode
259
+ }) {
260
+ const combobox = useComboboxContext()
261
+ const items = combobox.collection.items as T[]
262
+ return (
263
+ <React.Fragment>
264
+ {items.map((item, index) => (
265
+ <React.Fragment key={combobox.collection.getItemValue(item) ?? index}>{children(item, index)}</React.Fragment>
266
+ ))}
267
+ </React.Fragment>
268
+ )
269
+ }
270
+
271
+ function ComboboxEmpty({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.Empty>) {
272
+ return (
273
+ <ComboboxPrimitive.Empty
274
+ data-slot="combobox-empty"
275
+ className={cn("flex w-full justify-center py-2 text-center text-sm text-muted-foreground", className)}
276
+ {...props}
277
+ />
278
+ )
279
+ }
280
+
281
+ function ComboboxSeparator({ className, ...props }: React.ComponentProps<"div">) {
282
+ return (
283
+ <div
284
+ role="separator"
285
+ aria-orientation="horizontal"
286
+ data-slot="combobox-separator"
287
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
288
+ {...props}
289
+ />
290
+ )
291
+ }
292
+
293
+ /**
294
+ * Chips for `multiple` comboboxes. Ark has no chip parts; these read the
295
+ * selected values from context and remove them via `clearValue`.
296
+ */
297
+ function ComboboxChips({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.Control>) {
298
+ return (
299
+ <ComboboxPrimitive.Control
300
+ data-slot="combobox-chips"
301
+ className={cn(
302
+ "flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors has-focus-visible:border-ring has-focus-visible:ring-3 has-focus-visible:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
303
+ className
304
+ )}
305
+ {...props}
306
+ />
307
+ )
308
+ }
309
+
310
+ function ComboboxChip({
311
+ className,
312
+ children,
313
+ value,
314
+ showRemove = true,
315
+ ...props
316
+ }: React.ComponentProps<"div"> & {
317
+ /** The selected value this chip represents. */
318
+ value: string
319
+ showRemove?: boolean
320
+ }) {
321
+ const combobox = useComboboxContext()
322
+ return (
323
+ <div
324
+ data-slot="combobox-chip"
325
+ data-value={value}
326
+ className={cn(
327
+ "flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
328
+ className
329
+ )}
330
+ {...props}
331
+ >
332
+ {children}
333
+ {showRemove && (
334
+ <Button
335
+ type="button"
336
+ variant="ghost"
337
+ size="icon-xs"
338
+ data-slot="combobox-chip-remove"
339
+ aria-label={`Remove ${value}`}
340
+ disabled={combobox.disabled}
341
+ className="-ml-1 opacity-50 hover:opacity-100"
342
+ onClick={() => combobox.clearValue(value)}
343
+ >
344
+ <XIcon className="pointer-events-none" />
345
+ </Button>
346
+ )}
347
+ </div>
348
+ )
349
+ }
350
+
351
+ function ComboboxChipsInput({ className, ...props }: React.ComponentProps<typeof ComboboxPrimitive.Input>) {
352
+ return (
353
+ <ComboboxPrimitive.Input
354
+ data-slot="combobox-chip-input"
355
+ className={cn("min-w-16 flex-1 outline-none", className)}
356
+ {...props}
357
+ />
358
+ )
359
+ }
360
+
361
+ function useComboboxAnchor() {
362
+ return React.useRef<HTMLDivElement | null>(null)
363
+ }
364
+
365
+ /** Render-prop access to one item's state (`{ selected, highlighted, disabled, ... }`). */
366
+ function ComboboxItemContext({ ...props }: React.ComponentProps<typeof ComboboxPrimitive.ItemContext>) {
367
+ return <ComboboxPrimitive.ItemContext {...props} />
368
+ }
369
+
370
+ export {
371
+ Combobox,
372
+ ComboboxChip,
373
+ ComboboxChips,
374
+ ComboboxChipsInput,
375
+ ComboboxClear,
376
+ ComboboxCollection,
377
+ ComboboxContent,
378
+ ComboboxContext,
379
+ ComboboxControl,
380
+ ComboboxEmpty,
381
+ ComboboxGroup,
382
+ ComboboxInput,
383
+ ComboboxItem,
384
+ ComboboxItemIndicator,
385
+ ComboboxItemText,
386
+ ComboboxLabel,
387
+ ComboboxList,
388
+ ComboboxPositioner,
389
+ ComboboxRootLabel,
390
+ ComboboxSeparator,
391
+ ComboboxTrigger,
392
+ ComboboxValue,
393
+ createListCollection,
394
+ useComboboxAnchor,
395
+ useFilter,
396
+ useListCollection,
397
+ type CollectionItem,
398
+ type ListCollection,
399
+ ComboboxItemContext,
400
+ }
@@ -0,0 +1,292 @@
1
+ import * as React from "react"
2
+ import {
3
+ Listbox as CommandPrimitive,
4
+ createListCollection,
5
+ useFilter,
6
+ useListCollection,
7
+ type CollectionItem,
8
+ type ListCollection,
9
+ } from "@ark-ui/react"
10
+ import { cn } from "@/lib/utils"
11
+
12
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
13
+ import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
14
+ import { SearchIcon, CheckIcon } from "lucide-react"
15
+
16
+ /**
17
+ * Backed by Ark UI's Listbox. Ark has no built-in fuzzy filter: pass a
18
+ * `collection` and filter it yourself (see `useListCollection` and
19
+ * `useFilter`, re-exported below), wiring `CommandInput`'s `onValueChange`
20
+ * to the collection's `filter`.
21
+ */
22
+
23
+ type SelectHandler = (value: string) => void
24
+
25
+ const CommandSelectContext = React.createContext<{
26
+ register: (value: string, handler: SelectHandler) => () => void
27
+ } | null>(null)
28
+
29
+ function Command<T extends CollectionItem>({
30
+ className,
31
+ selectionMode = "single",
32
+ onSelect,
33
+ defaultHighlightedValue,
34
+ ...props
35
+ }: React.ComponentProps<typeof CommandPrimitive.Root<T>>) {
36
+ // Like cmdk, highlight the first item on mount so Enter works immediately.
37
+ const initialHighlight = defaultHighlightedValue ?? props.collection.firstValue
38
+ const handlers = React.useRef(new Map<string, SelectHandler>())
39
+ const register = React.useCallback((value: string, handler: SelectHandler) => {
40
+ handlers.current.set(value, handler)
41
+ return () => {
42
+ handlers.current.delete(value)
43
+ }
44
+ }, [])
45
+ const ctx = React.useMemo(() => ({ register }), [register])
46
+ return (
47
+ <CommandSelectContext.Provider value={ctx}>
48
+ <CommandPrimitive.Root
49
+ data-slot="command"
50
+ selectionMode={selectionMode}
51
+ defaultHighlightedValue={initialHighlight}
52
+ onSelect={(details) => {
53
+ handlers.current.get(details.value)?.(details.value)
54
+ onSelect?.(details)
55
+ }}
56
+ className={cn(
57
+ "flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground",
58
+ className
59
+ )}
60
+ {...props}
61
+ />
62
+ </CommandSelectContext.Provider>
63
+ )
64
+ }
65
+
66
+ function CommandContext({ ...props }: React.ComponentProps<typeof CommandPrimitive.Context>) {
67
+ return <CommandPrimitive.Context {...props} />
68
+ }
69
+
70
+ function CommandDialog({
71
+ title = "Command Palette",
72
+ description = "Search for a command to run...",
73
+ children,
74
+ className,
75
+ showCloseButton = false,
76
+ ...props
77
+ }: React.ComponentProps<typeof Dialog> & {
78
+ title?: string
79
+ description?: string
80
+ className?: string
81
+ showCloseButton?: boolean
82
+ }) {
83
+ return (
84
+ <Dialog {...props}>
85
+ <DialogContent className={cn("overflow-hidden rounded-xl! p-0", className)} showCloseButton={showCloseButton}>
86
+ <DialogHeader className="sr-only">
87
+ <DialogTitle>{title}</DialogTitle>
88
+ <DialogDescription>{description}</DialogDescription>
89
+ </DialogHeader>
90
+ {children}
91
+ </DialogContent>
92
+ </Dialog>
93
+ )
94
+ }
95
+
96
+ function CommandInput({
97
+ className,
98
+ autoHighlight = true,
99
+ onValueChange,
100
+ onChange,
101
+ onFocus,
102
+ ...props
103
+ }: React.ComponentProps<typeof CommandPrimitive.Input> & {
104
+ /** Called with the input text on every change. Wire it to your collection's `filter`. */
105
+ onValueChange?: (value: string) => void
106
+ }) {
107
+ const settled = React.useRef(false)
108
+ return (
109
+ <div data-slot="command-input-wrapper" className="p-1 pb-0">
110
+ <InputGroup className="h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!">
111
+ <CommandPrimitive.Input
112
+ data-slot="command-input"
113
+ autoHighlight={autoHighlight}
114
+ onFocus={(event) => {
115
+ onFocus?.(event)
116
+ // When a Dialog focuses this input in the same tick it mounts, the
117
+ // listbox machine has not entered its ready state yet and drops the
118
+ // focus event, leaving the highlighted item invisible. Re-issue the
119
+ // focus once, on the next tick, so the listbox registers it.
120
+ if (settled.current) return
121
+ settled.current = true
122
+ const el = event.currentTarget
123
+ setTimeout(() => {
124
+ if (document.activeElement !== el) return
125
+ el.blur()
126
+ el.focus({ preventScroll: true })
127
+ }, 0)
128
+ }}
129
+ className={cn("w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50", className)}
130
+ onChange={(event) => {
131
+ onChange?.(event)
132
+ onValueChange?.(event.currentTarget.value)
133
+ }}
134
+ {...props}
135
+ />
136
+ <InputGroupAddon>
137
+ <SearchIcon className="size-4 shrink-0 opacity-50" />
138
+ </InputGroupAddon>
139
+ </InputGroup>
140
+ </div>
141
+ )
142
+ }
143
+
144
+ function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Content>) {
145
+ return (
146
+ <CommandPrimitive.Content
147
+ data-slot="command-list"
148
+ className={cn("no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none", className)}
149
+ {...props}
150
+ />
151
+ )
152
+ }
153
+
154
+ function CommandEmpty({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
155
+ return (
156
+ <CommandPrimitive.Empty
157
+ data-slot="command-empty"
158
+ className={cn("py-6 text-center text-sm", className)}
159
+ {...props}
160
+ />
161
+ )
162
+ }
163
+
164
+ function CommandGroup({
165
+ className,
166
+ heading,
167
+ children,
168
+ ...props
169
+ }: React.ComponentProps<typeof CommandPrimitive.ItemGroup> & {
170
+ heading?: React.ReactNode
171
+ }) {
172
+ return (
173
+ <CommandPrimitive.ItemGroup
174
+ data-slot="command-group"
175
+ className={cn("overflow-hidden p-1 text-foreground", className)}
176
+ {...props}
177
+ >
178
+ {heading != null && <CommandGroupLabel>{heading}</CommandGroupLabel>}
179
+ {children}
180
+ </CommandPrimitive.ItemGroup>
181
+ )
182
+ }
183
+
184
+ function CommandGroupLabel({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.ItemGroupLabel>) {
185
+ return (
186
+ <CommandPrimitive.ItemGroupLabel
187
+ data-slot="command-group-label"
188
+ className={cn("px-2 py-1.5 text-xs font-medium text-muted-foreground", className)}
189
+ {...props}
190
+ />
191
+ )
192
+ }
193
+
194
+ function CommandSeparator({ className, ...props }: React.ComponentProps<"div">) {
195
+ return (
196
+ <div
197
+ role="separator"
198
+ aria-orientation="horizontal"
199
+ data-slot="command-separator"
200
+ className={cn("-mx-1 h-px bg-border", className)}
201
+ {...props}
202
+ />
203
+ )
204
+ }
205
+
206
+ function CommandItem({
207
+ className,
208
+ children,
209
+ item,
210
+ value,
211
+ onSelect,
212
+ ...props
213
+ }: Omit<React.ComponentProps<typeof CommandPrimitive.Item>, "item" | "onSelect"> & {
214
+ /** The collection item. Falls back to `value` for string collections. */
215
+ item?: CollectionItem
216
+ value?: string
217
+ /** Called with the item's value when it is selected by click or Enter. */
218
+ onSelect?: SelectHandler
219
+ }) {
220
+ const ctx = React.useContext(CommandSelectContext)
221
+ const resolvedItem = item ?? value
222
+ const itemValue =
223
+ typeof resolvedItem === "string" ? resolvedItem : ((resolvedItem as { value?: string } | undefined)?.value ?? value)
224
+ React.useEffect(() => {
225
+ if (!ctx || !onSelect || itemValue == null) return
226
+ return ctx.register(itemValue, onSelect)
227
+ }, [ctx, onSelect, itemValue])
228
+ return (
229
+ <CommandPrimitive.Item
230
+ data-slot="command-item"
231
+ item={resolvedItem}
232
+ className={cn(
233
+ "group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-highlighted:bg-muted data-highlighted:text-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-highlighted:*:[svg]:text-foreground",
234
+ className
235
+ )}
236
+ {...props}
237
+ >
238
+ {children}
239
+ <CommandItemIndicator>
240
+ <CheckIcon />
241
+ </CommandItemIndicator>
242
+ </CommandPrimitive.Item>
243
+ )
244
+ }
245
+
246
+ function CommandItemText({ ...props }: React.ComponentProps<typeof CommandPrimitive.ItemText>) {
247
+ return <CommandPrimitive.ItemText data-slot="command-item-text" {...props} />
248
+ }
249
+
250
+ function CommandItemIndicator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.ItemIndicator>) {
251
+ return (
252
+ <CommandPrimitive.ItemIndicator
253
+ data-slot="command-item-indicator"
254
+ className={cn("ml-auto group-has-data-[slot=command-shortcut]/command-item:hidden", className)}
255
+ {...props}
256
+ />
257
+ )
258
+ }
259
+
260
+ function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
261
+ return (
262
+ <span
263
+ data-slot="command-shortcut"
264
+ className={cn(
265
+ "ml-auto text-xs tracking-widest text-muted-foreground group-data-highlighted/command-item:text-foreground",
266
+ className
267
+ )}
268
+ {...props}
269
+ />
270
+ )
271
+ }
272
+
273
+ export {
274
+ Command,
275
+ CommandContext,
276
+ CommandDialog,
277
+ CommandEmpty,
278
+ CommandGroup,
279
+ CommandGroupLabel,
280
+ CommandInput,
281
+ CommandItem,
282
+ CommandItemIndicator,
283
+ CommandItemText,
284
+ CommandList,
285
+ CommandSeparator,
286
+ CommandShortcut,
287
+ createListCollection,
288
+ useFilter,
289
+ useListCollection,
290
+ type CollectionItem,
291
+ type ListCollection,
292
+ }