@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,20 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
4
+ return (
5
+ <kbd
6
+ data-slot="kbd"
7
+ className={cn(
8
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
9
+ className
10
+ )}
11
+ {...props}
12
+ />
13
+ )
14
+ }
15
+
16
+ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
17
+ return <kbd data-slot="kbd-group" className={cn("inline-flex items-center gap-1", className)} {...props} />
18
+ }
19
+
20
+ export { Kbd, KbdGroup }
@@ -0,0 +1,30 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ /**
5
+ * Ark UI has no standalone Label primitive (its labels live inside each
6
+ * component's anatomy). This mirrors Radix's: a native `label` that ignores
7
+ * double-click text selection.
8
+ */
9
+ function Label({ className, onMouseDown, ...props }: React.ComponentProps<"label">) {
10
+ return (
11
+ <label
12
+ data-slot="label"
13
+ className={cn(
14
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
15
+ className
16
+ )}
17
+ onMouseDown={(event) => {
18
+ // Only prevent text selection if clicking inside the label itself
19
+ const target = event.target as HTMLElement
20
+ if (target.closest("button, input, select, textarea")) return
21
+ onMouseDown?.(event)
22
+ // Prevent text selection when double clicking label
23
+ if (!event.defaultPrevented && event.detail > 1) event.preventDefault()
24
+ }}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+
30
+ export { Label }
@@ -0,0 +1,54 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ /**
7
+ * Shared screen-reader announcer. `announce` re-fires even when the same text is
8
+ * announced twice, and `clearAfter` (ms) empties the region so stale text is not
9
+ * re-read when a user lands on it.
10
+ */
11
+ function useLiveRegion(options: { clearAfter?: number } = {}) {
12
+ const { clearAfter } = options
13
+ const [message, setMessage] = React.useState("")
14
+ const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null)
15
+ const announce = React.useCallback(
16
+ (text: string) => {
17
+ setMessage("")
18
+ queueMicrotask(() => setMessage(text))
19
+ if (timer.current) clearTimeout(timer.current)
20
+ if (clearAfter) timer.current = setTimeout(() => setMessage(""), clearAfter)
21
+ },
22
+ [clearAfter]
23
+ )
24
+ React.useEffect(
25
+ () => () => {
26
+ if (timer.current) clearTimeout(timer.current)
27
+ },
28
+ []
29
+ )
30
+ return { message, announce }
31
+ }
32
+
33
+ /** Visually hidden `role="status"` region. Pair with `useLiveRegion`. */
34
+ function LiveRegion({
35
+ message,
36
+ assertive = false,
37
+ className,
38
+ ...props
39
+ }: React.ComponentProps<"div"> & { message: string; assertive?: boolean }) {
40
+ return (
41
+ <div
42
+ data-slot="live-region"
43
+ role={assertive ? "alert" : "status"}
44
+ aria-live={assertive ? "assertive" : "polite"}
45
+ aria-atomic="true"
46
+ className={cn("sr-only", className)}
47
+ {...props}
48
+ >
49
+ {message}
50
+ </div>
51
+ )
52
+ }
53
+
54
+ export { LiveRegion, useLiveRegion }
@@ -0,0 +1,66 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+ import { ark } from "@ark-ui/react"
5
+
6
+ const markerVariants = cva(
7
+ "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "",
12
+ separator:
13
+ "before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-border after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-border",
14
+ border: "border-b border-border pb-2",
15
+ },
16
+ },
17
+ }
18
+ )
19
+
20
+ function Marker({
21
+ className,
22
+ variant = "default",
23
+ asChild = false,
24
+ ...props
25
+ }: React.ComponentProps<"div"> &
26
+ VariantProps<typeof markerVariants> & {
27
+ asChild?: boolean
28
+ }) {
29
+ const Comp = ark.div
30
+
31
+ return (
32
+ <Comp
33
+ asChild={asChild}
34
+ data-slot="marker"
35
+ data-variant={variant}
36
+ className={cn(markerVariants({ variant, className }))}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
43
+ return (
44
+ <span
45
+ data-slot="marker-icon"
46
+ aria-hidden="true"
47
+ className={cn("size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4", className)}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
54
+ return (
55
+ <span
56
+ data-slot="marker-content"
57
+ className={cn(
58
+ "min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Marker, MarkerIcon, MarkerContent, markerVariants }
@@ -0,0 +1,61 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+ import { Marquee as MarqueePrimitive } from "@ark-ui/react"
6
+
7
+ function Marquee({ className, ...props }: React.ComponentProps<typeof MarqueePrimitive.Root>) {
8
+ return (
9
+ <MarqueePrimitive.Root
10
+ data-slot="marquee"
11
+ className={cn("group/marquee relative w-full overflow-hidden", className)}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ function MarqueeContext({ ...props }: React.ComponentProps<typeof MarqueePrimitive.Context>) {
18
+ return <MarqueePrimitive.Context {...props} />
19
+ }
20
+
21
+ function MarqueeViewport({ className, ...props }: React.ComponentProps<typeof MarqueePrimitive.Viewport>) {
22
+ return (
23
+ <MarqueePrimitive.Viewport
24
+ data-slot="marquee-viewport"
25
+ className={cn("flex w-full overflow-hidden", className)}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function MarqueeContent({ className, ...props }: React.ComponentProps<typeof MarqueePrimitive.Content>) {
32
+ return (
33
+ <MarqueePrimitive.Content
34
+ data-slot="marquee-content"
35
+ className={cn(
36
+ "flex shrink-0 items-center group-data-paused/marquee:paused data-reverse:shimmer-reverse data-horizontal:animate-marquee-x data-vertical:animate-marquee-y",
37
+ className
38
+ )}
39
+ {...props}
40
+ />
41
+ )
42
+ }
43
+
44
+ function MarqueeItem({ className, ...props }: React.ComponentProps<typeof MarqueePrimitive.Item>) {
45
+ return <MarqueePrimitive.Item data-slot="marquee-item" className={cn("shrink-0", className)} {...props} />
46
+ }
47
+
48
+ function MarqueeEdge({ className, ...props }: React.ComponentProps<typeof MarqueePrimitive.Edge>) {
49
+ return (
50
+ <MarqueePrimitive.Edge
51
+ data-slot="marquee-edge"
52
+ className={cn(
53
+ "pointer-events-none absolute inset-y-0 z-10 w-12 from-background to-transparent data-[side=end]:right-0 data-[side=end]:bg-linear-to-l data-[side=start]:left-0 data-[side=start]:bg-linear-to-r",
54
+ className
55
+ )}
56
+ {...props}
57
+ />
58
+ )
59
+ }
60
+
61
+ export { Marquee, MarqueeContent, MarqueeContext, MarqueeEdge, MarqueeItem, MarqueeViewport }
@@ -0,0 +1,456 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+ import { useControllable } from "@/lib/controllable"
6
+ import { Menu as MenubarPrimitive, Portal as PortalPrimitive } from "@ark-ui/react"
7
+ import { CheckIcon, ChevronRightIcon } from "lucide-react"
8
+
9
+ /**
10
+ * Ark UI has no Menubar primitive. `Menubar` composes Ark `Menu` roots and adds
11
+ * the menubar behaviours Radix provides: one open menu at a time, hover
12
+ * switching between triggers once a menu is open, roving tabindex across
13
+ * triggers, and ArrowLeft/ArrowRight moving between menus.
14
+ */
15
+
16
+ type MenubarContextValue = {
17
+ value: string | null
18
+ setValue: (value: string | null) => void
19
+ loop: boolean
20
+ register: (id: string) => () => void
21
+ order: string[]
22
+ rootRef: React.RefObject<HTMLDivElement | null>
23
+ }
24
+
25
+ const MenubarContext = React.createContext<MenubarContextValue | null>(null)
26
+ const MenubarMenuContext = React.createContext<string | null>(null)
27
+
28
+ function useMenubar(component: string) {
29
+ const ctx = React.useContext(MenubarContext)
30
+ if (!ctx) throw new Error(`${component} must be used within <Menubar>`)
31
+ return ctx
32
+ }
33
+
34
+ function useMenubarMenu(component: string) {
35
+ const id = React.useContext(MenubarMenuContext)
36
+ if (!id) throw new Error(`${component} must be used within <MenubarMenu>`)
37
+ return id
38
+ }
39
+
40
+ function Menubar({
41
+ className,
42
+ value: valueProp,
43
+ defaultValue = null,
44
+ onValueChange,
45
+ loop = false,
46
+ ...props
47
+ }: Omit<React.ComponentProps<"div">, "value" | "defaultValue"> & {
48
+ /** The id of the open menu (controlled). */
49
+ value?: string | null
50
+ defaultValue?: string | null
51
+ onValueChange?: (value: string | null) => void
52
+ /** Whether ArrowLeft/ArrowRight wrap around at the ends. */
53
+ loop?: boolean
54
+ }) {
55
+ const [value, setValue] = useControllable(valueProp, defaultValue, onValueChange)
56
+ const [order, setOrder] = React.useState<string[]>([])
57
+ const register = React.useCallback((id: string) => {
58
+ setOrder((prev) => (prev.includes(id) ? prev : [...prev, id]))
59
+ return () => setOrder((prev) => prev.filter((x) => x !== id))
60
+ }, [])
61
+ const rootRef = React.useRef<HTMLDivElement>(null)
62
+ const ctx = React.useMemo(
63
+ () => ({ value, setValue, loop, register, order, rootRef }),
64
+ [value, setValue, loop, register, order]
65
+ )
66
+ return (
67
+ <MenubarContext.Provider value={ctx}>
68
+ <div
69
+ ref={rootRef}
70
+ role="menubar"
71
+ data-slot="menubar"
72
+ className={cn("flex h-8 items-center gap-0.5 rounded-lg border p-0.75", className)}
73
+ {...props}
74
+ />
75
+ </MenubarContext.Provider>
76
+ )
77
+ }
78
+
79
+ function useMenubarNavigation() {
80
+ const ctx = useMenubar("MenubarTrigger")
81
+ return React.useCallback(
82
+ (fromId: string, direction: 1 | -1, open: boolean) => {
83
+ const { order, loop, rootRef, setValue } = ctx
84
+ const index = order.indexOf(fromId)
85
+ let next = index + direction
86
+ if (next < 0) next = loop ? order.length - 1 : 0
87
+ if (next >= order.length) next = loop ? 0 : order.length - 1
88
+ const nextId = order[next]
89
+ if (!nextId || nextId === fromId) return
90
+ const trigger = rootRef.current?.querySelector<HTMLElement>(
91
+ `[data-slot="menubar-trigger"][data-menubar-value="${nextId}"]`
92
+ )
93
+ trigger?.focus()
94
+ setValue(open ? nextId : null)
95
+ },
96
+ [ctx]
97
+ )
98
+ }
99
+
100
+ function MenubarMenu({
101
+ value: valueProp,
102
+ positioning,
103
+ lazyMount = true,
104
+ unmountOnExit = true,
105
+ onOpenChange,
106
+ ...props
107
+ }: Omit<React.ComponentProps<typeof MenubarPrimitive.Root>, "open" | "defaultOpen"> & {
108
+ /** Identifies this menu within the menubar. Auto-generated when omitted. */
109
+ value?: string
110
+ }) {
111
+ const ctx = useMenubar("MenubarMenu")
112
+ const generated = React.useId()
113
+ const id = valueProp ?? generated
114
+ const { register } = ctx
115
+ React.useEffect(() => register(id), [register, id])
116
+ return (
117
+ <MenubarMenuContext.Provider value={id}>
118
+ <MenubarPrimitive.Root
119
+ open={ctx.value === id}
120
+ onOpenChange={(details) => {
121
+ ctx.setValue(details.open ? id : ctx.value === id ? null : ctx.value)
122
+ onOpenChange?.(details)
123
+ }}
124
+ positioning={{
125
+ placement: "bottom-start",
126
+ gutter: 8,
127
+ offset: { crossAxis: -4 },
128
+ ...positioning,
129
+ }}
130
+ lazyMount={lazyMount}
131
+ unmountOnExit={unmountOnExit}
132
+ {...props}
133
+ />
134
+ </MenubarMenuContext.Provider>
135
+ )
136
+ }
137
+
138
+ function MenubarTrigger({
139
+ className,
140
+ onPointerEnter,
141
+ onKeyDown,
142
+ ...props
143
+ }: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
144
+ const ctx = useMenubar("MenubarTrigger")
145
+ const id = useMenubarMenu("MenubarTrigger")
146
+ const navigate = useMenubarNavigation()
147
+ const isOpen = ctx.value === id
148
+ const isFirst = ctx.order[0] === id
149
+ return (
150
+ <MenubarPrimitive.Trigger
151
+ data-slot="menubar-trigger"
152
+ data-menubar-value={id}
153
+ tabIndex={isOpen || (ctx.value === null && isFirst) ? 0 : -1}
154
+ className={cn(
155
+ "flex items-center rounded-sm px-1.5 py-0.5 text-sm font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted",
156
+ className
157
+ )}
158
+ onPointerEnter={(event) => {
159
+ onPointerEnter?.(event)
160
+ if (ctx.value !== null && ctx.value !== id) ctx.setValue(id)
161
+ }}
162
+ onKeyDown={(event) => {
163
+ onKeyDown?.(event)
164
+ if (event.defaultPrevented) return
165
+ if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
166
+ event.preventDefault()
167
+ navigate(id, event.key === "ArrowRight" ? 1 : -1, isOpen)
168
+ }
169
+ }}
170
+ {...props}
171
+ />
172
+ )
173
+ }
174
+
175
+ function MenubarPortal({ ...props }: React.ComponentProps<typeof PortalPrimitive>) {
176
+ return <PortalPrimitive {...props} />
177
+ }
178
+
179
+ function MenubarMenuContext_({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Context>) {
180
+ return <MenubarPrimitive.Context {...props} />
181
+ }
182
+
183
+ function MenubarPositioner({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Positioner>) {
184
+ return (
185
+ <MenubarPrimitive.Positioner
186
+ data-slot="menubar-positioner"
187
+ className={cn("[--z-index:50]", className)}
188
+ {...props}
189
+ />
190
+ )
191
+ }
192
+
193
+ function MenubarContent({ className, onKeyDown, ...props }: React.ComponentProps<typeof MenubarPrimitive.Content>) {
194
+ const id = useMenubarMenu("MenubarContent")
195
+ const navigate = useMenubarNavigation()
196
+ return (
197
+ <MenubarPortal>
198
+ <MenubarPositioner>
199
+ <MenubarPrimitive.Content
200
+ data-slot="menubar-content"
201
+ className={cn(
202
+ "z-50 min-w-36 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover p-1 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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95",
203
+ className
204
+ )}
205
+ onKeyDown={(event) => {
206
+ onKeyDown?.(event)
207
+ if (event.defaultPrevented) return
208
+ if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return
209
+ const target = event.target as HTMLElement
210
+ // Inside a submenu, Ark owns ArrowLeft (close) and ArrowRight.
211
+ if (target.closest('[data-slot="menubar-sub-content"]')) return
212
+ // On a highlighted sub trigger, ArrowRight opens the submenu.
213
+ if (
214
+ event.key === "ArrowRight" &&
215
+ event.currentTarget.querySelector('[data-slot="menubar-sub-trigger"][data-highlighted]')
216
+ )
217
+ return
218
+ event.preventDefault()
219
+ navigate(id, event.key === "ArrowRight" ? 1 : -1, true)
220
+ }}
221
+ {...props}
222
+ />
223
+ </MenubarPositioner>
224
+ </MenubarPortal>
225
+ )
226
+ }
227
+
228
+ function MenubarArrow({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Arrow>) {
229
+ return (
230
+ <MenubarPrimitive.Arrow
231
+ data-slot="menubar-arrow"
232
+ className={cn("[--arrow-background:var(--color-popover)] [--arrow-size:0.625rem]", className)}
233
+ {...props}
234
+ >
235
+ <MenubarPrimitive.ArrowTip data-slot="menubar-arrow-tip" className="border-t border-l border-foreground/10" />
236
+ </MenubarPrimitive.Arrow>
237
+ )
238
+ }
239
+
240
+ function MenubarGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.ItemGroup>) {
241
+ return <MenubarPrimitive.ItemGroup data-slot="menubar-group" {...props} />
242
+ }
243
+
244
+ function MenubarItem({
245
+ className,
246
+ inset,
247
+ variant = "default",
248
+ value,
249
+ ...props
250
+ }: Omit<React.ComponentProps<typeof MenubarPrimitive.Item>, "value"> & {
251
+ value?: string
252
+ inset?: boolean
253
+ variant?: "default" | "destructive"
254
+ }) {
255
+ const id = React.useId()
256
+ return (
257
+ <MenubarPrimitive.Item
258
+ data-slot="menubar-item"
259
+ data-inset={inset}
260
+ data-variant={variant}
261
+ value={value ?? id}
262
+ className={cn(
263
+ "group/menubar-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 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-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive",
264
+ className
265
+ )}
266
+ {...props}
267
+ />
268
+ )
269
+ }
270
+
271
+ function MenubarItemText({ ...props }: React.ComponentProps<typeof MenubarPrimitive.ItemText>) {
272
+ return <MenubarPrimitive.ItemText data-slot="menubar-item-text" {...props} />
273
+ }
274
+
275
+ function MenubarItemIndicator({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.ItemIndicator>) {
276
+ return (
277
+ <MenubarPrimitive.ItemIndicator
278
+ data-slot="menubar-item-indicator"
279
+ className={cn("pointer-events-none absolute right-2 flex items-center justify-center", className)}
280
+ {...props}
281
+ />
282
+ )
283
+ }
284
+
285
+ const optionItemClassName =
286
+ "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
287
+
288
+ function MenubarCheckboxItem({
289
+ className,
290
+ children,
291
+ checked = false,
292
+ inset,
293
+ value,
294
+ ...props
295
+ }: Omit<React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>, "checked" | "value"> & {
296
+ checked?: boolean
297
+ value?: string
298
+ inset?: boolean
299
+ }) {
300
+ const id = React.useId()
301
+ return (
302
+ <MenubarPrimitive.CheckboxItem
303
+ data-slot="menubar-checkbox-item"
304
+ data-inset={inset}
305
+ className={cn(optionItemClassName, className)}
306
+ checked={checked}
307
+ value={value ?? id}
308
+ {...props}
309
+ >
310
+ <MenubarItemIndicator data-slot="menubar-checkbox-item-indicator">
311
+ <CheckIcon />
312
+ </MenubarItemIndicator>
313
+ {children}
314
+ </MenubarPrimitive.CheckboxItem>
315
+ )
316
+ }
317
+
318
+ function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioItemGroup>) {
319
+ return <MenubarPrimitive.RadioItemGroup data-slot="menubar-radio-group" {...props} />
320
+ }
321
+
322
+ function MenubarRadioItem({
323
+ className,
324
+ children,
325
+ inset,
326
+ ...props
327
+ }: React.ComponentProps<typeof MenubarPrimitive.RadioItem> & {
328
+ inset?: boolean
329
+ }) {
330
+ return (
331
+ <MenubarPrimitive.RadioItem
332
+ data-slot="menubar-radio-item"
333
+ data-inset={inset}
334
+ className={cn(optionItemClassName, className)}
335
+ {...props}
336
+ >
337
+ <MenubarItemIndicator data-slot="menubar-radio-item-indicator">
338
+ <CheckIcon />
339
+ </MenubarItemIndicator>
340
+ {children}
341
+ </MenubarPrimitive.RadioItem>
342
+ )
343
+ }
344
+
345
+ function MenubarLabel({
346
+ className,
347
+ inset,
348
+ ...props
349
+ }: React.ComponentProps<typeof MenubarPrimitive.ItemGroupLabel> & {
350
+ inset?: boolean
351
+ }) {
352
+ return (
353
+ <MenubarPrimitive.ItemGroupLabel
354
+ data-slot="menubar-label"
355
+ data-inset={inset}
356
+ className={cn("px-1.5 py-1 text-sm font-medium data-inset:pl-7", className)}
357
+ {...props}
358
+ />
359
+ )
360
+ }
361
+
362
+ function MenubarSeparator({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
363
+ return (
364
+ <MenubarPrimitive.Separator
365
+ data-slot="menubar-separator"
366
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
367
+ {...props}
368
+ />
369
+ )
370
+ }
371
+
372
+ function MenubarShortcut({ className, ...props }: React.ComponentProps<"span">) {
373
+ return (
374
+ <span
375
+ data-slot="menubar-shortcut"
376
+ className={cn(
377
+ "ml-auto text-xs tracking-widest text-muted-foreground group-data-highlighted/menubar-item:text-accent-foreground",
378
+ className
379
+ )}
380
+ {...props}
381
+ />
382
+ )
383
+ }
384
+
385
+ function MenubarSub({
386
+ lazyMount = true,
387
+ unmountOnExit = true,
388
+ ...props
389
+ }: React.ComponentProps<typeof MenubarPrimitive.Root>) {
390
+ return <MenubarPrimitive.Root lazyMount={lazyMount} unmountOnExit={unmountOnExit} {...props} />
391
+ }
392
+
393
+ function MenubarSubTrigger({
394
+ className,
395
+ inset,
396
+ children,
397
+ ...props
398
+ }: React.ComponentProps<typeof MenubarPrimitive.TriggerItem> & {
399
+ inset?: boolean
400
+ }) {
401
+ return (
402
+ <MenubarPrimitive.TriggerItem
403
+ data-slot="menubar-sub-trigger"
404
+ data-inset={inset}
405
+ className={cn(
406
+ "flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
407
+ className
408
+ )}
409
+ {...props}
410
+ >
411
+ {children}
412
+ <ChevronRightIcon className="ml-auto" />
413
+ </MenubarPrimitive.TriggerItem>
414
+ )
415
+ }
416
+
417
+ function MenubarSubContent({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Content>) {
418
+ return (
419
+ <MenubarPortal>
420
+ <MenubarPositioner>
421
+ <MenubarPrimitive.Content
422
+ data-slot="menubar-sub-content"
423
+ className={cn(
424
+ "z-50 min-w-32 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg 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-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",
425
+ className
426
+ )}
427
+ {...props}
428
+ />
429
+ </MenubarPositioner>
430
+ </MenubarPortal>
431
+ )
432
+ }
433
+
434
+ export {
435
+ Menubar,
436
+ MenubarArrow,
437
+ MenubarCheckboxItem,
438
+ MenubarContent,
439
+ MenubarMenuContext_ as MenubarContext,
440
+ MenubarGroup,
441
+ MenubarItem,
442
+ MenubarItemIndicator,
443
+ MenubarItemText,
444
+ MenubarLabel,
445
+ MenubarMenu,
446
+ MenubarPortal,
447
+ MenubarPositioner,
448
+ MenubarRadioGroup,
449
+ MenubarRadioItem,
450
+ MenubarSeparator,
451
+ MenubarShortcut,
452
+ MenubarSub,
453
+ MenubarSubContent,
454
+ MenubarSubTrigger,
455
+ MenubarTrigger,
456
+ }