@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,23 @@
1
+ {
2
+ "name": "scheduler",
3
+ "type": "ui",
4
+ "description": "`src/components/ui/scheduler.tsx` is a compositional, data-agnostic calendar.",
5
+ "dependencies": {
6
+ "date-fns": "^4.4.0",
7
+ "lucide-react": "^1.41.0"
8
+ },
9
+ "registryDependencies": [
10
+ "button",
11
+ "controllable",
12
+ "live-region",
13
+ "segment-group",
14
+ "utils"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "components/ui/scheduler.tsx",
19
+ "content": "import * as React from \"react\"\nimport {\n addDays,\n addMinutes,\n addMonths,\n addWeeks,\n differenceInCalendarDays,\n differenceInMinutes,\n eachDayOfInterval,\n endOfMonth,\n endOfWeek,\n format,\n isSameDay,\n isSameMonth,\n isToday,\n setHours,\n setMinutes,\n startOfDay,\n startOfMonth,\n startOfWeek,\n} from \"date-fns\"\nimport { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\nimport { Button } from \"@/components/ui/button\"\nimport { SegmentGroup, SegmentGroupIndicator, SegmentGroupItem } from \"@/components/ui/segment-group\"\nimport { cn } from \"@/lib/utils\"\nimport { useControllable } from \"@/lib/controllable\"\nimport { LiveRegion, useLiveRegion } from \"@/components/ui/live-region\"\n\n/**\n * Scheduler — a compositional, data-agnostic calendar. The consumer owns the\n * events and renders them with the parts; the root owns the anchor `date`,\n * the `view` (day/week/month), the time-grid geometry and the interactions:\n * drag to move, drag the bottom edge to resize, drag on empty time to create,\n * keyboard moves. Everything is reported as intents (`onEventChange`,\n * `onCreate`) snapped to `slotMinutes`.\n *\n * Anatomy:\n * Scheduler { date, view, onEventChange, onCreate }\n * SchedulerToolbar > SchedulerPrevTrigger / SchedulerTodayTrigger / SchedulerNextTrigger / SchedulerTitle / SchedulerViewSelect\n * SchedulerTimeGrid (day, week)\n * SchedulerTimeGridHeader > SchedulerDayHeading date=… (+ SchedulerAllDayRow)\n * SchedulerTimeGridBody > SchedulerTimeGutter + SchedulerDayColumn date= events=\n * children: (event) => <SchedulerEvent value= start= end=> … <SchedulerEventResizeHandle/> </SchedulerEvent>\n * SchedulerNowIndicator, SchedulerCreatePreview\n * SchedulerMonthGrid > SchedulerMonthHeader + SchedulerMonthBody > SchedulerMonthCell date= > SchedulerMonthEvent value=…\n *\n * Keyboard on an event: Space/Enter picks up, ↑/↓ move by a slot, ←/→ move a\n * day, Shift+↑/↓ change the end, Escape cancels.\n */\n\ntype SchedulerView = \"day\" | \"week\" | \"month\"\ntype EventLike = { id: string; start: Date; end: Date; allDay?: boolean }\ntype SchedulerEventChange = { id: string; start: Date; end: Date }\ntype Interaction = { id: string; type: \"move\" | \"resize\"; start: Date; end: Date }\ntype CreateState = { start: Date; end: Date } | null\n\ntype SchedulerContextValue = {\n date: Date\n setDate: (date: Date) => void\n view: SchedulerView\n setView: (view: SchedulerView) => void\n range: { start: Date; end: Date }\n days: Date[]\n weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6\n hourHeight: number\n slotMinutes: number\n minHour: number\n maxHour: number\n editable: boolean\n interaction: Interaction | null\n creating: CreateState\n grabbed: string | null\n timeToY: (date: Date) => number\n yToMinutes: (y: number) => number\n bodyRef: React.RefObject<HTMLDivElement | null>\n startMove: (event: EventLike, pointer: React.PointerEvent, columnDates: Date[]) => void\n startResize: (event: EventLike, pointer: React.PointerEvent) => void\n startCreate: (day: Date, pointer: React.PointerEvent, columnDates: Date[]) => void\n startMonthMove: (event: EventLike, pointer: React.PointerEvent) => void\n grab: (id: string | null) => void\n cancelGrab: () => void\n nudge: (event: EventLike, deltaMinutes: number, deltaDays: number, part: \"both\" | \"end\") => void\n announce: (message: string) => void\n}\n\nconst SchedulerContext = React.createContext<SchedulerContextValue | null>(null)\nfunction useScheduler() {\n const ctx = React.useContext(SchedulerContext)\n if (!ctx) throw new Error(\"Scheduler parts must be used within <Scheduler>\")\n return ctx\n}\n\nconst clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v))\nconst minutesOfDay = (d: Date) => d.getHours() * 60 + d.getMinutes()\nconst withMinutes = (day: Date, minutes: number) => setMinutes(setHours(startOfDay(day), 0), minutes)\n\nfunction visibleRange(date: Date, view: SchedulerView, weekStartsOn: SchedulerContextValue[\"weekStartsOn\"]) {\n if (view === \"day\") return { start: startOfDay(date), end: startOfDay(date) }\n if (view === \"week\") return { start: startOfWeek(date, { weekStartsOn }), end: endOfWeek(date, { weekStartsOn }) }\n return {\n start: startOfWeek(startOfMonth(date), { weekStartsOn }),\n end: endOfWeek(endOfMonth(date), { weekStartsOn }),\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Root\n * ------------------------------------------------------------------------- */\n\nfunction Scheduler({\n date: dateProp,\n defaultDate,\n onDateChange,\n view: viewProp,\n defaultView = \"week\",\n onViewChange,\n weekStartsOn = 1,\n hourHeight = 56,\n slotMinutes = 15,\n minHour = 0,\n maxHour = 24,\n editable = true,\n onEventChange,\n onCreate,\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"onChange\"> & {\n date?: Date\n defaultDate?: Date\n onDateChange?: (date: Date) => void\n view?: SchedulerView\n defaultView?: SchedulerView\n onViewChange?: (view: SchedulerView) => void\n weekStartsOn?: SchedulerContextValue[\"weekStartsOn\"]\n hourHeight?: number\n slotMinutes?: number\n minHour?: number\n maxHour?: number\n editable?: boolean\n onEventChange?: (change: SchedulerEventChange) => void\n /** Drag on empty time (or a month cell click) proposes a new event. */\n onCreate?: (range: { start: Date; end: Date; allDay?: boolean }) => void\n}) {\n const [initialDate] = React.useState(() => startOfDay(defaultDate ?? new Date()))\n const [date, setDate] = useControllable(dateProp, initialDate, onDateChange)\n const [view, setView] = useControllable<SchedulerView>(viewProp, defaultView, onViewChange)\n\n const range = React.useMemo(() => visibleRange(date, view, weekStartsOn), [date, view, weekStartsOn])\n const days = React.useMemo(() => eachDayOfInterval(range), [range])\n\n const bodyRef = React.useRef<HTMLDivElement>(null)\n const minuteHeight = hourHeight / 60\n const timeToY = React.useCallback(\n (d: Date) => (minutesOfDay(d) - minHour * 60) * minuteHeight,\n [minHour, minuteHeight]\n )\n const yToMinutes = React.useCallback(\n (y: number) =>\n clamp(Math.round((y / minuteHeight + minHour * 60) / slotMinutes) * slotMinutes, minHour * 60, maxHour * 60),\n [minuteHeight, minHour, maxHour, slotMinutes]\n )\n\n const [interaction, setInteraction] = React.useState<Interaction | null>(null)\n const [creating, setCreating] = React.useState<CreateState>(null)\n const [grabbed, setGrabbed] = React.useState<string | null>(null)\n const { message: announcement, announce } = useLiveRegion()\n const onEventChangeRef = React.useRef(onEventChange)\n const onCreateRef = React.useRef(onCreate)\n React.useEffect(() => {\n onEventChangeRef.current = onEventChange\n onCreateRef.current = onCreate\n }, [onEventChange, onCreate])\n const grabOrigin = React.useRef<SchedulerEventChange | null>(null)\n\n const track = React.useCallback((onMove: (e: PointerEvent) => void, onUp: (e: PointerEvent) => void) => {\n const up = (e: PointerEvent) => {\n window.removeEventListener(\"pointermove\", onMove)\n window.removeEventListener(\"pointerup\", up)\n window.removeEventListener(\"pointercancel\", up)\n onUp(e)\n }\n window.addEventListener(\"pointermove\", onMove)\n window.addEventListener(\"pointerup\", up)\n window.addEventListener(\"pointercancel\", up)\n }, [])\n\n /** Which day column a client x falls in, from the column elements' rects. */\n const dayAtX = React.useCallback((x: number, columnDates: Date[]) => {\n const columns = Array.from(bodyRef.current?.querySelectorAll<HTMLElement>(\"[data-slot=scheduler-day-column]\") ?? [])\n for (const [i, col] of columns.entries()) {\n const r = col.getBoundingClientRect()\n if (x >= r.left && x < r.right) return columnDates[i] ?? null\n }\n return null\n }, [])\n\n const startMove = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent, columnDates: Date[]) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originY = pointer.clientY\n const originDay = startOfDay(event.start)\n let current: Interaction = { id: event.id, type: \"move\", start: event.start, end: event.end }\n let moved = false\n setInteraction(current)\n track(\n (e) => {\n const dy = e.clientY - originY\n const deltaMinutes = Math.round(dy / minuteHeight / slotMinutes) * slotMinutes\n const day = dayAtX(e.clientX, columnDates) ?? originDay\n const dayDelta = differenceInCalendarDays(day, originDay)\n const duration = differenceInMinutes(event.end, event.start)\n const startMin = clamp(minutesOfDay(event.start) + deltaMinutes, minHour * 60, maxHour * 60 - duration)\n const start = addDays(withMinutes(originDay, startMin), dayDelta)\n current = { id: event.id, type: \"move\", start, end: addMinutes(start, duration) }\n if (Math.abs(dy) > 3 || dayDelta !== 0) moved = true\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (\n moved &&\n (current.start.getTime() !== event.start.getTime() || current.end.getTime() !== event.end.getTime())\n ) {\n onEventChangeRef.current?.({ id: event.id, start: current.start, end: current.end })\n }\n }\n )\n },\n [editable, track, dayAtX, minuteHeight, slotMinutes, minHour, maxHour]\n )\n\n const startResize = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originY = pointer.clientY\n let current: Interaction = { id: event.id, type: \"resize\", start: event.start, end: event.end }\n setInteraction(current)\n track(\n (e) => {\n const deltaMinutes = Math.round((e.clientY - originY) / minuteHeight / slotMinutes) * slotMinutes\n const endMin = clamp(\n minutesOfDay(event.end) + deltaMinutes,\n minutesOfDay(event.start) + slotMinutes,\n maxHour * 60\n )\n current = { ...current, end: withMinutes(event.start, endMin) }\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (current.end.getTime() !== event.end.getTime()) {\n onEventChangeRef.current?.({ id: event.id, start: event.start, end: current.end })\n }\n }\n )\n },\n [editable, track, minuteHeight, slotMinutes, maxHour]\n )\n\n const startCreate = React.useCallback(\n (day: Date, pointer: React.PointerEvent, columnDates: Date[]) => {\n if (!editable || !onCreateRef.current) return\n const column = (pointer.currentTarget as HTMLElement).getBoundingClientRect()\n const originMin = yToMinutes(pointer.clientY - column.top)\n let current: NonNullable<CreateState> = {\n start: withMinutes(day, originMin),\n end: withMinutes(day, originMin + slotMinutes),\n }\n let dragged = false\n setCreating(current)\n track(\n (e) => {\n const min = yToMinutes(e.clientY - column.top)\n const targetDay = dayAtX(e.clientX, columnDates) ?? day\n const a = Math.min(originMin, min)\n const b = Math.max(originMin, min, a + slotMinutes)\n current = { start: withMinutes(targetDay, a), end: withMinutes(targetDay, b) }\n dragged = true\n setCreating(current)\n },\n () => {\n setCreating(null)\n onCreateRef.current?.(dragged ? current : { start: current.start, end: addMinutes(current.start, 60) })\n }\n )\n },\n [editable, track, yToMinutes, slotMinutes, dayAtX]\n )\n\n /** Month view: drag a chip onto another cell to shift the event by whole days. */\n const startMonthMove = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originDay = startOfDay(event.start)\n let current: Interaction = { id: event.id, type: \"move\", start: event.start, end: event.end }\n let moved = false\n setInteraction(current)\n const cellAt = (x: number, y: number) =>\n document.elementFromPoint(x, y)?.closest<HTMLElement>(\"[data-slot=scheduler-month-cell]\")?.dataset.date\n track(\n (e) => {\n const target = cellAt(e.clientX, e.clientY)\n if (!target) return\n const delta = differenceInCalendarDays(new Date(target), originDay)\n current = { ...current, start: addDays(event.start, delta), end: addDays(event.end, delta) }\n moved = true\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (moved && current.start.getTime() !== event.start.getTime()) {\n onEventChangeRef.current?.({ id: event.id, start: current.start, end: current.end })\n }\n }\n )\n },\n [editable, track]\n )\n\n const grab = React.useCallback(\n (id: string | null) => {\n if (id) {\n setGrabbed(id)\n announce(\"Picked up. Arrows move; Shift+Up/Down change the end. Space drops, Escape cancels.\")\n } else {\n setGrabbed(null)\n grabOrigin.current = null\n announce(\"Dropped.\")\n }\n },\n [announce]\n )\n const cancelGrab = React.useCallback(() => {\n if (grabOrigin.current) onEventChangeRef.current?.(grabOrigin.current)\n setGrabbed(null)\n grabOrigin.current = null\n announce(\"Cancelled.\")\n }, [announce])\n\n const nudge = React.useCallback(\n (event: EventLike, deltaMinutes: number, deltaDays: number, part: \"both\" | \"end\") => {\n if (!grabOrigin.current || grabOrigin.current.id !== event.id) {\n grabOrigin.current = { id: event.id, start: event.start, end: event.end }\n }\n const next =\n part === \"end\"\n ? {\n start: event.start,\n end: addMinutes(\n event.end,\n Math.max(deltaMinutes, slotMinutes - differenceInMinutes(event.end, event.start))\n ),\n }\n : {\n start: addDays(addMinutes(event.start, deltaMinutes), deltaDays),\n end: addDays(addMinutes(event.end, deltaMinutes), deltaDays),\n }\n onEventChangeRef.current?.({ id: event.id, ...next })\n announce(`${format(next.start, \"EEE d, p\")} to ${format(next.end, \"p\")}.`)\n },\n [announce, slotMinutes]\n )\n\n const ctx = React.useMemo<SchedulerContextValue>(\n () => ({\n date,\n setDate,\n view,\n setView,\n range,\n days,\n weekStartsOn,\n hourHeight,\n slotMinutes,\n minHour,\n maxHour,\n editable,\n interaction,\n creating,\n grabbed,\n timeToY,\n yToMinutes,\n bodyRef,\n startMove,\n startResize,\n startCreate,\n startMonthMove,\n grab,\n cancelGrab,\n nudge,\n announce,\n }),\n [\n date,\n setDate,\n view,\n setView,\n range,\n days,\n weekStartsOn,\n hourHeight,\n slotMinutes,\n minHour,\n maxHour,\n editable,\n interaction,\n creating,\n grabbed,\n timeToY,\n yToMinutes,\n startMove,\n startResize,\n startCreate,\n startMonthMove,\n grab,\n cancelGrab,\n nudge,\n announce,\n ]\n )\n\n return (\n <SchedulerContext.Provider value={ctx}>\n <div\n data-slot=\"scheduler\"\n data-view={view}\n data-interacting={interaction?.type ?? (creating ? \"create\" : undefined)}\n className={cn(\"flex min-h-0 flex-1 flex-col gap-3\", className)}\n {...props}\n >\n {children}\n <LiveRegion data-slot=\"scheduler-live-region\" message={announcement} />\n </div>\n </SchedulerContext.Provider>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toolbar\n * ------------------------------------------------------------------------- */\n\nfunction SchedulerToolbar({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"scheduler-toolbar\" className={cn(\"flex flex-wrap items-center gap-2\", className)} {...props} />\n}\n\nfunction shift(date: Date, view: SchedulerView, direction: 1 | -1) {\n if (view === \"day\") return addDays(date, direction)\n if (view === \"week\") return addWeeks(date, direction)\n return addMonths(date, direction)\n}\n\nfunction SchedulerPrevTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { date, view, setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-prev-trigger\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={`Previous ${view}`}\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(shift(date, view, -1))\n }}\n {...props}\n >\n {asChild ? children : (children ?? <ChevronLeftIcon />)}\n </Button>\n )\n}\n\nfunction SchedulerNextTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { date, view, setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-next-trigger\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={`Next ${view}`}\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(shift(date, view, 1))\n }}\n {...props}\n >\n {asChild ? children : (children ?? <ChevronRightIcon />)}\n </Button>\n )\n}\n\nfunction SchedulerTodayTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-today-trigger\"\n variant=\"outline\"\n size=\"sm\"\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(startOfDay(new Date()))\n }}\n {...props}\n >\n {asChild\n ? children\n : (children ?? (\n <>\n <CalendarIcon /> Today\n </>\n ))}\n </Button>\n )\n}\n\n/** \"September 2026\", \"Sep 7 – 13, 2026\" or \"Mon, Sep 7\". */\nfunction SchedulerTitle({ className, children, ...props }: React.ComponentProps<\"h2\">) {\n const { date, view, range } = useScheduler()\n const text =\n view === \"month\"\n ? format(date, \"MMMM yyyy\")\n : view === \"day\"\n ? format(date, \"EEEE, MMM d, yyyy\")\n : isSameMonth(range.start, range.end)\n ? `${format(range.start, \"MMM d\")} – ${format(range.end, \"d, yyyy\")}`\n : `${format(range.start, \"MMM d\")} – ${format(range.end, \"MMM d, yyyy\")}`\n return (\n <h2 data-slot=\"scheduler-title\" className={cn(\"text-base font-semibold tabular-nums\", className)} {...props}>\n {children ?? text}\n </h2>\n )\n}\n\nfunction SchedulerViewSelect({\n className,\n ...props\n}: Omit<React.ComponentProps<typeof SegmentGroup>, \"value\" | \"onValueChange\">) {\n const { view, setView } = useScheduler()\n return (\n <SegmentGroup\n data-slot=\"scheduler-view-select\"\n value={view}\n onValueChange={({ value }) => value && setView(value as SchedulerView)}\n className={className}\n {...props}\n >\n <SegmentGroupIndicator />\n <SegmentGroupItem value=\"day\">Day</SegmentGroupItem>\n <SegmentGroupItem value=\"week\">Week</SegmentGroupItem>\n <SegmentGroupItem value=\"month\">Month</SegmentGroupItem>\n </SegmentGroup>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Time grid (day / week)\n * ------------------------------------------------------------------------- */\n\nconst GUTTER = \"3.5rem\"\n\nfunction SchedulerTimeGrid({ className, style, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-time-grid\"\n className={cn(\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border bg-background\", className)}\n style={{ [\"--scheduler-gutter\" as string]: GUTTER, ...style }}\n {...props}\n />\n )\n}\n\nfunction SchedulerTimeGridHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-time-grid-header\"\n className={cn(\"grid shrink-0 border-b bg-muted/50\", className)}\n style={{ gridTemplateColumns: \"var(--scheduler-gutter) 1fr\" }}\n {...props}\n />\n )\n}\n\n/** Day headings across the top, one per visible day. */\nfunction SchedulerDayHeadings({\n className,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & { children?: (day: Date) => React.ReactNode }) {\n const { days, setDate, setView } = useScheduler()\n return (\n <>\n <div aria-hidden className=\"border-e\" />\n <div\n data-slot=\"scheduler-day-headings\"\n className={cn(\"grid\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n {days.map((day) => (\n <button\n key={day.toISOString()}\n type=\"button\"\n data-slot=\"scheduler-day-heading\"\n data-today={isToday(day) ? \"\" : undefined}\n className=\"group/heading flex flex-col items-center gap-0.5 border-e py-2 text-xs text-muted-foreground outline-none last:border-e-0 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset\"\n onClick={() => {\n setDate(day)\n setView(\"day\")\n }}\n >\n {children ? (\n children(day)\n ) : (\n <>\n <span>{format(day, \"EEE\")}</span>\n <span className=\"flex size-7 items-center justify-center rounded-full text-base font-semibold text-foreground group-data-today/heading:bg-primary group-data-today/heading:text-primary-foreground\">\n {format(day, \"d\")}\n </span>\n </>\n )}\n </button>\n ))}\n </div>\n </>\n )\n}\n\n/** Strip under the headings for all-day events; render chips inside per day. */\nfunction SchedulerAllDayRow({\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & { children: (day: Date) => React.ReactNode }) {\n const { days } = useScheduler()\n return (\n <>\n <div className=\"flex items-start justify-end border-e border-t px-1.5 py-1 text-[10px] text-muted-foreground uppercase\">\n all day\n </div>\n <div\n data-slot=\"scheduler-all-day-row\"\n className={cn(\"grid border-t\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n {days.map((day) => (\n <div\n key={day.toISOString()}\n data-slot=\"scheduler-all-day-cell\"\n className=\"flex min-h-7 flex-col gap-0.5 border-e p-0.5 last:border-e-0\"\n >\n {children(day)}\n </div>\n ))}\n </div>\n </>\n )\n}\n\nfunction SchedulerTimeGridBody({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { bodyRef, hourHeight, minHour, maxHour } = useScheduler()\n // Scroll to a sensible hour on mount.\n React.useEffect(() => {\n const el = bodyRef.current\n if (el) el.scrollTop = Math.max(0, (8 - minHour) * hourHeight - 8)\n }, [bodyRef, hourHeight, minHour])\n return (\n <div\n ref={bodyRef}\n data-slot=\"scheduler-time-grid-body\"\n className={cn(\"relative min-h-0 flex-1 overflow-y-auto\", className)}\n {...props}\n >\n <div\n className=\"grid\"\n style={{ gridTemplateColumns: \"var(--scheduler-gutter) 1fr\", height: (maxHour - minHour) * hourHeight }}\n >\n {children}\n </div>\n </div>\n )\n}\n\nfunction SchedulerTimeGutter({ className, ...props }: React.ComponentProps<\"div\">) {\n const { hourHeight, minHour, maxHour } = useScheduler()\n const hours = Array.from({ length: maxHour - minHour }, (_, i) => minHour + i)\n return (\n <div data-slot=\"scheduler-time-gutter\" className={cn(\"relative border-e\", className)} {...props}>\n {hours.map((h) => (\n <div\n key={h}\n className=\"absolute inset-e-1.5 -translate-y-1/2 text-[11px] text-muted-foreground tabular-nums\"\n style={{ top: (h - minHour) * hourHeight }}\n >\n {h > minHour ? format(setHours(new Date(), h), \"h a\") : \"\"}\n </div>\n ))}\n </div>\n )\n}\n\n/** The columns container; hour lines are drawn behind. */\nfunction SchedulerDayColumns({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { days, hourHeight, minHour, maxHour } = useScheduler()\n const hours = Array.from({ length: maxHour - minHour }, (_, i) => i)\n return (\n <div\n data-slot=\"scheduler-day-columns\"\n className={cn(\"relative grid\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n <div aria-hidden className=\"pointer-events-none absolute inset-0\">\n {hours.map((i) => (\n <div key={i} className=\"absolute inset-x-0 border-t border-border/70\" style={{ top: i * hourHeight }} />\n ))}\n </div>\n {children}\n </div>\n )\n}\n\n/** Side-by-side lanes for overlapping events. */\nfunction layoutLanes(events: EventLike[]) {\n const sorted = [...events]\n .filter((e) => !e.allDay)\n .sort((a, b) => a.start.getTime() - b.start.getTime() || b.end.getTime() - a.end.getTime())\n const result = new Map<string, { lane: number; lanes: number }>()\n let cluster: EventLike[] = []\n let clusterEnd = 0\n const flush = () => {\n const laneEnds: number[] = []\n const assigned: { id: string; lane: number }[] = []\n for (const e of cluster) {\n let lane = laneEnds.findIndex((end) => end <= e.start.getTime())\n if (lane === -1) lane = laneEnds.length\n laneEnds[lane] = e.end.getTime()\n assigned.push({ id: e.id, lane })\n }\n for (const a of assigned) result.set(a.id, { lane: a.lane, lanes: laneEnds.length })\n cluster = []\n clusterEnd = 0\n }\n for (const e of sorted) {\n if (cluster.length && e.start.getTime() >= clusterEnd) flush()\n cluster.push(e)\n clusterEnd = Math.max(clusterEnd, e.end.getTime())\n }\n if (cluster.length) flush()\n return result\n}\n\nconst ColumnContext = React.createContext<{ day: Date; lanes: Map<string, { lane: number; lanes: number }> } | null>(\n null\n)\n\n/** One day. `events` are the ones on this day (for overlap layout); `children` renders each. */\nfunction SchedulerDayColumn<T extends EventLike>({\n date,\n events,\n className,\n children,\n onPointerDown,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & {\n date: Date\n events: T[]\n children: (event: T) => React.ReactNode\n}) {\n const scheduler = useScheduler()\n const lanes = React.useMemo(() => layoutLanes(events), [events])\n const ctx = React.useMemo(() => ({ day: date, lanes }), [date, lanes])\n return (\n <ColumnContext.Provider value={ctx}>\n <div\n data-slot=\"scheduler-day-column\"\n data-date={date.toISOString()}\n data-today={isToday(date) ? \"\" : undefined}\n className={cn(\"relative border-e last:border-e-0 data-today:bg-primary/3\", className)}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-event]\")) return\n scheduler.startCreate(date, event, scheduler.days)\n }}\n {...props}\n >\n {events.map((event) => (\n <React.Fragment key={event.id}>{children(event)}</React.Fragment>\n ))}\n </div>\n </ColumnContext.Provider>\n )\n}\n\nconst EventContext = React.createContext<EventLike | null>(null)\n\nfunction SchedulerEvent({\n value,\n start,\n end,\n className,\n style,\n children,\n onPointerDown,\n onKeyDown,\n onBlur,\n ...props\n}: React.ComponentProps<\"div\"> & { value: string; start: Date; end: Date }) {\n const s = useScheduler()\n const column = React.useContext(ColumnContext)\n const live = s.interaction?.id === value ? s.interaction : null\n const shownStart = live ? live.start : start\n const shownEnd = live ? live.end : end\n // While a move crosses days the event stays in its original column but shifts visually.\n const dayOffset = column ? differenceInCalendarDays(startOfDay(shownStart), column.day) : 0\n const lane = column?.lanes.get(value) ?? { lane: 0, lanes: 1 }\n const top = s.timeToY(shownStart)\n const height = Math.max(s.hourHeight / 4, (differenceInMinutes(shownEnd, shownStart) * s.hourHeight) / 60)\n const isGrabbed = s.grabbed === value\n const eventLike = React.useMemo(() => ({ id: value, start, end }), [value, start, end])\n return (\n <EventContext.Provider value={eventLike}>\n <div\n {...props}\n data-slot=\"scheduler-event\"\n data-value={value}\n data-dragging={live?.type}\n data-grabbed={isGrabbed ? \"\" : undefined}\n tabIndex={s.editable ? 0 : undefined}\n aria-pressed={s.editable ? isGrabbed : undefined}\n aria-label={`${format(shownStart, \"EEE d, p\")} to ${format(shownEnd, \"p\")}`}\n className={cn(\n \"group/event absolute flex flex-col overflow-hidden rounded-md border border-primary/30 bg-primary/15 px-1.5 py-1 text-xs text-foreground outline-none select-none\",\n s.editable && \"cursor-grab active:cursor-grabbing\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring/50 data-dragging:z-20 data-dragging:shadow-lg data-grabbed:ring-2 data-grabbed:ring-primary\",\n className\n )}\n style={{\n top,\n height,\n left: `calc(${(lane.lane / lane.lanes) * 100}% + 2px)`,\n width: `calc(${100 / lane.lanes}% - 4px)`,\n transform: dayOffset ? `translateX(calc(${dayOffset * 100}% + ${dayOffset * 1}px))` : undefined,\n ...style,\n }}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-event-resize-handle], button, a\")) return\n s.startMove(eventLike, event, s.days)\n }}\n onKeyDown={(event) => {\n onKeyDown?.(event)\n if (event.defaultPrevented || !s.editable || event.target !== event.currentTarget) return\n if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault()\n s.grab(isGrabbed ? null : value)\n return\n }\n if (!isGrabbed) return\n const step = s.slotMinutes\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n event.preventDefault()\n const delta = event.key === \"ArrowUp\" ? -step : step\n s.nudge(eventLike, delta, 0, event.shiftKey ? \"end\" : \"both\")\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n event.preventDefault()\n s.nudge(eventLike, 0, event.key === \"ArrowLeft\" ? -1 : 1, \"both\")\n } else if (event.key === \"Escape\") {\n event.preventDefault()\n s.cancelGrab()\n }\n }}\n onBlur={(event) => {\n onBlur?.(event)\n if (isGrabbed && !event.currentTarget.contains(event.relatedTarget as Node | null)) s.grab(null)\n }}\n >\n {children}\n </div>\n </EventContext.Provider>\n )\n}\n\nfunction SchedulerEventTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"scheduler-event-title\" className={cn(\"truncate font-medium\", className)} {...props} />\n}\n\n/** \"9:00 – 9:45 AM\"; reflects the live drag. */\nfunction SchedulerEventTime({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const e = React.useContext(EventContext)\n if (!e) return null\n const live = s.interaction?.id === e.id ? s.interaction : null\n const start = live ? live.start : e.start\n const end = live ? live.end : e.end\n return (\n <div\n data-slot=\"scheduler-event-time\"\n className={cn(\"truncate text-[11px] tabular-nums opacity-80\", className)}\n {...props}\n >\n {children ?? `${format(start, \"h:mm\")} – ${format(end, \"h:mm a\")}`}\n </div>\n )\n}\n\nfunction SchedulerEventResizeHandle({ className, onPointerDown, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const e = React.useContext(EventContext)\n if (!s.editable || !e) return null\n return (\n <div\n data-slot=\"scheduler-event-resize-handle\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn(\n \"absolute inset-x-0 bottom-0 h-2 cursor-ns-resize touch-none after:absolute after:inset-x-1/2 after:bottom-0.5 after:h-0.5 after:w-6 after:-translate-x-1/2 after:rounded-full after:bg-primary/50 after:opacity-0 group-hover/event:after:opacity-100\",\n className\n )}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n s.startResize(e, event)\n }}\n {...props}\n />\n )\n}\n\n/** Outline of the event being created by dragging; renders nothing when idle. */\nfunction SchedulerCreatePreview({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n if (!s.creating) return null\n const index = s.days.findIndex((d) => isSameDay(d, s.creating!.start))\n if (index === -1) return null\n const top = s.timeToY(s.creating.start)\n const height = (differenceInMinutes(s.creating.end, s.creating.start) * s.hourHeight) / 60\n return (\n <div\n data-slot=\"scheduler-create-preview\"\n aria-hidden\n className={cn(\n \"pointer-events-none absolute z-20 rounded-md border-2 border-dashed border-primary/60 bg-primary/10 px-1.5 py-1 text-[11px] text-primary tabular-nums\",\n className\n )}\n style={{\n top,\n height,\n left: `calc(${(index / s.days.length) * 100}% + 2px)`,\n width: `calc(${100 / s.days.length}% - 4px)`,\n }}\n {...props}\n >\n {children ?? `${format(s.creating.start, \"h:mm\")} – ${format(s.creating.end, \"h:mm a\")}`}\n </div>\n )\n}\n\n/** Red line at the current time in today's column. */\nfunction SchedulerNowIndicator({ className, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const [now, setNow] = React.useState(() => new Date())\n React.useEffect(() => {\n const timer = setInterval(() => setNow(new Date()), 60_000)\n return () => clearInterval(timer)\n }, [])\n const index = s.days.findIndex((d) => isToday(d))\n if (index === -1) return null\n return (\n <div\n data-slot=\"scheduler-now-indicator\"\n aria-hidden\n className={cn(\"pointer-events-none absolute z-10 h-px bg-destructive\", className)}\n style={{ top: s.timeToY(now), left: `${(index / s.days.length) * 100}%`, width: `${100 / s.days.length}%` }}\n {...props}\n >\n <span className=\"absolute -top-1 -left-1 size-2 rounded-full bg-destructive\" />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Month grid\n * ------------------------------------------------------------------------- */\n\nfunction SchedulerMonthGrid({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-month-grid\"\n className={cn(\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border bg-background\", className)}\n {...props}\n />\n )\n}\n\nfunction SchedulerMonthHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n const { days } = useScheduler()\n return (\n <div\n data-slot=\"scheduler-month-header\"\n className={cn(\"grid grid-cols-7 border-b bg-muted/50 text-xs text-muted-foreground\", className)}\n {...props}\n >\n {days.slice(0, 7).map((d) => (\n <div key={d.toISOString()} className=\"border-e px-2 py-1.5 text-center font-medium last:border-e-0\">\n {format(d, \"EEE\")}\n </div>\n ))}\n </div>\n )\n}\n\nfunction SchedulerMonthBody({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { days } = useScheduler()\n return (\n <div\n data-slot=\"scheduler-month-body\"\n className={cn(\"grid min-h-0 flex-1 grid-cols-7 overflow-y-auto\", className)}\n style={{ gridTemplateRows: `repeat(${Math.ceil(days.length / 7)}, minmax(6rem, 1fr))` }}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction SchedulerMonthCell({\n date,\n className,\n children,\n onClick,\n ...props\n}: React.ComponentProps<\"div\"> & { date: Date }) {\n const s = useScheduler()\n const outside = !isSameMonth(date, s.date)\n return (\n <div\n data-slot=\"scheduler-month-cell\"\n data-date={date.toISOString()}\n data-today={isToday(date) ? \"\" : undefined}\n data-outside={outside ? \"\" : undefined}\n className={cn(\n \"group/cell flex min-h-0 flex-col gap-0.5 border-e border-b p-1 text-xs data-outside:bg-muted/30 data-outside:text-muted-foreground nth-[7n]:border-e-0\",\n className\n )}\n onClick={(event) => {\n onClick?.(event)\n if (event.defaultPrevented) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-month-event], button, a\")) return\n s.setDate(date)\n }}\n {...props}\n >\n <button\n type=\"button\"\n data-slot=\"scheduler-month-day\"\n className=\"mb-0.5 flex size-6 items-center justify-center self-start rounded-full text-xs font-medium outline-none group-data-today/cell:bg-primary group-data-today/cell:text-primary-foreground hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50\"\n onClick={(event) => {\n event.stopPropagation()\n s.setDate(date)\n s.setView(\"day\")\n }}\n >\n {format(date, \"d\")}\n </button>\n <div className=\"flex min-h-0 flex-1 flex-col gap-0.5 overflow-hidden\">{children}</div>\n </div>\n )\n}\n\n/** Compact chip for the month view; draggable between cells. */\nfunction SchedulerMonthEvent({\n value,\n start,\n end,\n className,\n children,\n onPointerDown,\n ...props\n}: React.ComponentProps<\"div\"> & { value: string; start: Date; end: Date }) {\n const s = useScheduler()\n const live = s.interaction?.id === value ? s.interaction : null\n const eventLike = React.useMemo(() => ({ id: value, start, end }), [value, start, end])\n return (\n <div\n {...props}\n data-slot=\"scheduler-month-event\"\n data-value={value}\n data-dragging={live ? \"\" : undefined}\n tabIndex={s.editable ? 0 : undefined}\n aria-label={`${format(start, \"EEE d, p\")}`}\n className={cn(\n \"flex cursor-grab items-center gap-1 truncate rounded-sm border border-primary/30 bg-primary/15 px-1.5 py-px text-[11px]/4 outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/50 active:cursor-grabbing data-dragging:opacity-50\",\n className\n )}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n s.startMonthMove(eventLike, event)\n }}\n >\n {children}\n </div>\n )\n}\n\nfunction SchedulerMonthMore({ className, ...props }: React.ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n data-slot=\"scheduler-month-more\"\n className={cn(\n \"self-start rounded-sm px-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n Scheduler,\n SchedulerToolbar,\n SchedulerPrevTrigger,\n SchedulerNextTrigger,\n SchedulerTodayTrigger,\n SchedulerTitle,\n SchedulerViewSelect,\n SchedulerTimeGrid,\n SchedulerTimeGridHeader,\n SchedulerDayHeadings,\n SchedulerAllDayRow,\n SchedulerTimeGridBody,\n SchedulerTimeGutter,\n SchedulerDayColumns,\n SchedulerDayColumn,\n SchedulerEvent,\n SchedulerEventTitle,\n SchedulerEventTime,\n SchedulerEventResizeHandle,\n SchedulerCreatePreview,\n SchedulerNowIndicator,\n SchedulerMonthGrid,\n SchedulerMonthHeader,\n SchedulerMonthBody,\n SchedulerMonthCell,\n SchedulerMonthEvent,\n SchedulerMonthMore,\n useScheduler,\n layoutLanes,\n visibleRange,\n type SchedulerView,\n type SchedulerEventChange,\n}\n"
20
+ }
21
+ ],
22
+ "docs": "## Scheduler (calendar)\n`src/components/ui/scheduler.tsx` is a compositional, data-agnostic calendar. The consumer owns\nevents and renders them with the parts; the root owns the anchor `date` and `view`\n(day|week|month, both controllable), the time-grid geometry (`hourHeight`, `slotMinutes`,\n`minHour`/`maxHour`, `weekStartsOn`), the interactions, and reports `onEventChange({ id, start,\nend })` and `onCreate({ start, end })` snapped to `slotMinutes`. Parts: `SchedulerToolbar` >\n`Prev/Next/TodayTrigger`, `SchedulerTitle`, `SchedulerViewSelect` (SegmentGroup);\n`SchedulerTimeGrid` > `SchedulerTimeGridHeader` > `SchedulerDayHeadings` (click a day → day view)\n+ `SchedulerAllDayRow` (render prop per day) and `SchedulerTimeGridBody` (scrolls to 8am on mount)\n> `SchedulerTimeGutter` + `SchedulerDayColumns` > `SchedulerDayColumn date= events=` (render-prop\nchildren per event; computes side-by-side lanes for overlaps via `layoutLanes`; pointer-down on\nempty time starts drag-to-create) > `SchedulerEvent value= start= end=` (absolute by time;\ndraggable to another time/day, `data-dragging` move|resize, `data-grabbed`) > `SchedulerEventTitle`,\n`SchedulerEventTime` (reflects the live drag), `SchedulerEventResizeHandle`; `SchedulerNowIndicator`,\n`SchedulerCreatePreview`. `SchedulerMonthGrid` > `SchedulerMonthHeader` + `SchedulerMonthBody` >\n`SchedulerMonthCell date=` (`data-today`/`data-outside`, day number → day view) > `SchedulerMonthEvent`\n(chip draggable between cells; shifts by whole days) + `SchedulerMonthMore`. Keyboard on an event:\nSpace/Enter picks up, ↑/↓ move by a slot, ←/→ move a day, Shift+↑/↓ change the end, Escape cancels;\na live region announces. Event parts spread `props` first so an `asChild` wrapper (e.g.\n`PopoverTrigger`) cannot override their `data-slot` or pointer handlers. `useScheduler()` exposes\n`days`, `range`, `timeToY`, `setDate`, `setView`. `src/demo/calendar/` is the reference\n(`/projects/calendar`): categories with colors, all-day strip, overlapping meetings in lanes, event popover\n(title, category, delete), new-event dialog from drag-to-create or the toolbar, undo/redo."
23
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "scroll-area",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1"
7
+ },
8
+ "registryDependencies": [
9
+ "utils"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/scroll-area.tsx",
14
+ "content": "import * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"@ark-ui/react\"\n\nfunction ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {\n return (\n <ScrollAreaPrimitive.Root data-slot=\"scroll-area\" className={cn(\"relative\", className)} {...props}>\n <ScrollAreaViewport>\n <ScrollAreaContent>{children}</ScrollAreaContent>\n </ScrollAreaViewport>\n <ScrollBar />\n <ScrollAreaCorner />\n </ScrollAreaPrimitive.Root>\n )\n}\n\nfunction ScrollAreaContext({ ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Context>) {\n return <ScrollAreaPrimitive.Context {...props} />\n}\n\nfunction ScrollAreaViewport({ className, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Viewport>) {\n return (\n <ScrollAreaPrimitive.Viewport\n data-slot=\"scroll-area-viewport\"\n className={cn(\n \"no-scrollbar size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ScrollAreaContent({ className, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Content>) {\n return (\n <ScrollAreaPrimitive.Content data-slot=\"scroll-area-content\" className={cn(\"min-w-full\", className)} {...props} />\n )\n}\n\nfunction ScrollBar({\n className,\n orientation = \"vertical\",\n ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.Scrollbar>) {\n return (\n <ScrollAreaPrimitive.Scrollbar\n data-slot=\"scroll-area-scrollbar\"\n orientation={orientation}\n className={cn(\n \"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-horizontal:not-data-overflow-x:hidden data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent data-vertical:not-data-overflow-y:hidden\",\n className\n )}\n {...props}\n >\n <ScrollAreaThumb />\n </ScrollAreaPrimitive.Scrollbar>\n )\n}\n\nfunction ScrollAreaThumb({ className, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Thumb>) {\n return (\n <ScrollAreaPrimitive.Thumb\n data-slot=\"scroll-area-thumb\"\n className={cn(\"relative flex-1 rounded-full bg-border\", className)}\n {...props}\n />\n )\n}\n\nfunction ScrollAreaCorner({ ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Corner>) {\n return <ScrollAreaPrimitive.Corner data-slot=\"scroll-area-corner\" {...props} />\n}\n\nexport {\n ScrollArea,\n ScrollAreaContent,\n ScrollAreaContext,\n ScrollAreaCorner,\n ScrollAreaThumb,\n ScrollAreaViewport,\n ScrollBar,\n}\n"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "segment-group",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1"
7
+ },
8
+ "registryDependencies": [
9
+ "utils"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/segment-group.tsx",
14
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { SegmentGroup as SegmentGroupPrimitive } from \"@ark-ui/react\"\n\nfunction SegmentGroup({\n className,\n orientation = \"horizontal\",\n ...props\n}: React.ComponentProps<typeof SegmentGroupPrimitive.Root>) {\n return (\n <SegmentGroupPrimitive.Root\n data-slot=\"segment-group\"\n orientation={orientation}\n className={cn(\n \"relative inline-flex h-8 w-fit items-center gap-0.5 rounded-lg bg-muted p-0.75 text-muted-foreground data-vertical:h-auto data-vertical:flex-col data-vertical:items-stretch\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SegmentGroupContext({ ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.Context>) {\n return <SegmentGroupPrimitive.Context {...props} />\n}\n\nfunction SegmentGroupLabel({ className, ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.Label>) {\n return (\n <SegmentGroupPrimitive.Label\n data-slot=\"segment-group-label\"\n className={cn(\"text-sm font-medium select-none\", className)}\n {...props}\n />\n )\n}\n\nfunction SegmentGroupIndicator({ className, ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.Indicator>) {\n return (\n <SegmentGroupPrimitive.Indicator\n data-slot=\"segment-group-indicator\"\n className={cn(\n \"top-(--top) left-(--left) z-0 h-(--height) w-(--width) rounded-md bg-background shadow-sm transition-[left,top,width,height] duration-200 dark:border dark:border-input dark:bg-input/30\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SegmentGroupItem({ className, children, ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.Item>) {\n return (\n <SegmentGroupPrimitive.Item\n data-slot=\"segment-group-item\"\n className={cn(\n \"relative z-10 inline-flex h-full cursor-pointer items-center justify-center gap-1.5 rounded-md px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-colors select-none hover:text-foreground data-focus-visible:ring-3 data-focus-visible:ring-ring/50 data-checked:text-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <SegmentGroupItemText>{children}</SegmentGroupItemText>\n <SegmentGroupItemControl />\n <SegmentGroupItemHiddenInput />\n </SegmentGroupPrimitive.Item>\n )\n}\n\nfunction SegmentGroupItemContext({ ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.ItemContext>) {\n return <SegmentGroupPrimitive.ItemContext {...props} />\n}\n\nfunction SegmentGroupItemText({ ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.ItemText>) {\n return <SegmentGroupPrimitive.ItemText data-slot=\"segment-group-item-text\" {...props} />\n}\n\nfunction SegmentGroupItemControl({ ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.ItemControl>) {\n return <SegmentGroupPrimitive.ItemControl data-slot=\"segment-group-item-control\" {...props} />\n}\n\nfunction SegmentGroupItemHiddenInput({ ...props }: React.ComponentProps<typeof SegmentGroupPrimitive.ItemHiddenInput>) {\n return <SegmentGroupPrimitive.ItemHiddenInput {...props} />\n}\n\nexport {\n SegmentGroup,\n SegmentGroupContext,\n SegmentGroupIndicator,\n SegmentGroupItem,\n SegmentGroupItemContext,\n SegmentGroupItemControl,\n SegmentGroupItemHiddenInput,\n SegmentGroupItemText,\n SegmentGroupLabel,\n}\n"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "select",
3
+ "type": "ui",
4
+ "description": "`src/lib/history.ts` exports `useHistory(initial, limit)` → `{ present, set(next, { commit }), checkpoint, undo, redo, reset, canUndo, canRedo }`; the node graph and form builder use it. Never add a local copy.",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1",
7
+ "lucide-react": "^1.41.0"
8
+ },
9
+ "registryDependencies": [
10
+ "utils"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "components/ui/select.tsx",
15
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport {\n Select as SelectPrimitive,\n Portal as PortalPrimitive,\n createListCollection,\n type CollectionItem,\n type ListCollection,\n} from \"@ark-ui/react\"\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\"\n\nfunction Select<T extends CollectionItem>({\n positioning,\n lazyMount = true,\n unmountOnExit = true,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root<T>>) {\n return (\n <SelectPrimitive.Root\n data-slot=\"select\"\n positioning={{\n placement: \"bottom\",\n gutter: 4,\n sameWidth: true,\n ...positioning,\n }}\n lazyMount={lazyMount}\n unmountOnExit={unmountOnExit}\n {...props}\n />\n )\n}\n\nfunction SelectContext({ ...props }: React.ComponentProps<typeof SelectPrimitive.Context>) {\n return <SelectPrimitive.Context {...props} />\n}\n\nfunction SelectHiddenSelect({ ...props }: React.ComponentProps<typeof SelectPrimitive.HiddenSelect>) {\n return <SelectPrimitive.HiddenSelect {...props} />\n}\n\nfunction SelectRootLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label data-slot=\"select-root-label\" className={cn(\"text-sm font-medium\", className)} {...props} />\n )\n}\n\nfunction SelectControl({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Control>) {\n return (\n <SelectPrimitive.Control\n data-slot=\"select-control\"\n className={cn(\"flex items-center gap-1\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectGroup({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ItemGroup>) {\n return <SelectPrimitive.ItemGroup data-slot=\"select-group\" className={cn(\"scroll-my-1 p-1\", className)} {...props} />\n}\n\nfunction SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.ValueText>) {\n return <SelectPrimitive.ValueText data-slot=\"select-value\" {...props} />\n}\n\nfunction SelectIndicator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Indicator>) {\n return (\n <SelectPrimitive.Indicator\n data-slot=\"select-indicator\"\n className={cn(\"pointer-events-none text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectClearTrigger({ ...props }: React.ComponentProps<typeof SelectPrimitive.ClearTrigger>) {\n return <SelectPrimitive.ClearTrigger data-slot=\"select-clear-trigger\" {...props} />\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\n/**\n * `variant=\"unstyled\"` keeps only the layout (flex, gap, icon sizing) so a host\n * can supply its own chrome, e.g. an editable grid cell that already draws the\n * focus ring.\n */\nfunction SelectTrigger({\n className,\n size = \"default\",\n variant = \"default\",\n children,\n ...props\n}: Omit<React.ComponentProps<typeof SelectPrimitive.Trigger>, \"id\"> & {\n size?: \"sm\" | \"default\"\n variant?: \"default\" | \"unstyled\"\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n data-variant={variant}\n className={cn(\n \"flex items-center justify-between gap-1.5 text-sm whitespace-nowrap outline-none select-none disabled:cursor-not-allowed disabled:opacity-50 data-placeholder-shown:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n variant === \"default\" &&\n \"w-fit rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 data-invalid:border-destructive data-invalid:ring-3 data-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] dark:bg-input/30 dark:hover:bg-input/50 dark:data-invalid:border-destructive/50 dark:data-invalid:ring-destructive/40\",\n className\n )}\n {...props}\n >\n {children}\n <SelectIndicator asChild>\n <ChevronDownIcon className=\"size-4\" />\n </SelectIndicator>\n </SelectPrimitive.Trigger>\n )\n}\n\nfunction SelectPositioner({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Positioner>) {\n return (\n <SelectPrimitive.Positioner data-slot=\"select-positioner\" className={cn(\"[--z-index:50]\", className)} {...props} />\n )\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction SelectContent({\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<typeof SelectPrimitive.Content>, \"id\">) {\n return (\n <PortalPrimitive>\n <SelectPositioner>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n \"relative z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto 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-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\",\n className\n )}\n {...props}\n >\n <SelectScrollUpButton />\n {children}\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPositioner>\n </PortalPrimitive>\n )\n}\n\nfunction SelectList({ ...props }: React.ComponentProps<typeof SelectPrimitive.List>) {\n return <SelectPrimitive.List data-slot=\"select-list\" {...props} />\n}\n\nfunction SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ItemGroupLabel>) {\n return (\n <SelectPrimitive.ItemGroupLabel\n data-slot=\"select-label\"\n className={cn(\"px-1.5 py-1 text-xs text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectItem({\n className,\n children,\n item,\n value,\n ...props\n}: Omit<React.ComponentProps<typeof SelectPrimitive.Item>, \"item\"> & {\n /** The collection item. Falls back to `value` for string collections. */\n item?: CollectionItem\n value?: string\n}) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n item={item ?? value}\n className={cn(\n \"relative flex w-full 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 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 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <SelectItemIndicator>\n <CheckIcon className=\"pointer-events-none\" />\n </SelectItemIndicator>\n <SelectPrimitive.ItemText data-slot=\"select-item-text\">{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\nfunction SelectItemText({ ...props }: React.ComponentProps<typeof SelectPrimitive.ItemText>) {\n return <SelectPrimitive.ItemText data-slot=\"select-item-text\" {...props} />\n}\n\nfunction SelectItemIndicator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ItemIndicator>) {\n return (\n <SelectPrimitive.ItemIndicator\n data-slot=\"select-item-indicator\"\n className={cn(\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectSeparator({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"horizontal\"\n data-slot=\"select-separator\"\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n )\n}\n\n/**\n * Ark's Select content is its own scroll container and has no scroll buttons.\n * These reproduce Radix's: shown only when the content can scroll in that\n * direction, and they scroll while hovered.\n */\nfunction useSelectScrollButton(direction: \"up\" | \"down\") {\n const ref = React.useRef<HTMLDivElement>(null)\n const [visible, setVisible] = React.useState(false)\n const timer = React.useRef<number | null>(null)\n\n const getContent = React.useCallback(\n () => ref.current?.closest<HTMLElement>('[data-slot=\"select-content\"]') ?? null,\n []\n )\n\n React.useEffect(() => {\n const content = getContent()\n if (!content) return\n const update = () => {\n const canUp = content.scrollTop > 0\n const canDown = content.scrollTop + content.clientHeight < content.scrollHeight - 1\n setVisible(direction === \"up\" ? canUp : canDown)\n }\n update()\n content.addEventListener(\"scroll\", update)\n const observer = new ResizeObserver(update)\n observer.observe(content)\n return () => {\n content.removeEventListener(\"scroll\", update)\n observer.disconnect()\n }\n }, [direction, getContent])\n\n const stop = React.useCallback(() => {\n if (timer.current !== null) window.clearInterval(timer.current)\n timer.current = null\n }, [])\n\n const start = React.useCallback(() => {\n stop()\n timer.current = window.setInterval(() => {\n getContent()?.scrollBy({ top: direction === \"up\" ? -8 : 8 })\n }, 40)\n }, [direction, getContent, stop])\n\n React.useEffect(() => stop, [stop])\n\n return { ref, visible, start, stop }\n}\n\nfunction SelectScrollUpButton({ className, onPointerEnter, onPointerLeave, ...props }: React.ComponentProps<\"div\">) {\n const { ref, visible, start, stop } = useSelectScrollButton(\"up\")\n return (\n <div\n ref={ref}\n aria-hidden\n hidden={!visible}\n data-slot=\"select-scroll-up-button\"\n className={cn(\n \"sticky top-0 z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n onPointerEnter={(event) => {\n onPointerEnter?.(event)\n start()\n }}\n onPointerLeave={(event) => {\n onPointerLeave?.(event)\n stop()\n }}\n {...props}\n >\n <ChevronUpIcon />\n </div>\n )\n}\n\nfunction SelectScrollDownButton({ className, onPointerEnter, onPointerLeave, ...props }: React.ComponentProps<\"div\">) {\n const { ref, visible, start, stop } = useSelectScrollButton(\"down\")\n return (\n <div\n ref={ref}\n aria-hidden\n hidden={!visible}\n data-slot=\"select-scroll-down-button\"\n className={cn(\n \"sticky bottom-0 z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n onPointerEnter={(event) => {\n onPointerEnter?.(event)\n start()\n }}\n onPointerLeave={(event) => {\n onPointerLeave?.(event)\n stop()\n }}\n {...props}\n >\n <ChevronDownIcon />\n </div>\n )\n}\n\n/** Render-prop access to one item's state (`{ selected, highlighted, disabled, ... }`). */\nfunction SelectItemContext({ ...props }: React.ComponentProps<typeof SelectPrimitive.ItemContext>) {\n return <SelectPrimitive.ItemContext {...props} />\n}\n\nexport {\n Select,\n SelectClearTrigger,\n SelectContent,\n SelectContext,\n SelectControl,\n SelectGroup,\n SelectHiddenSelect,\n SelectIndicator,\n SelectItem,\n SelectItemIndicator,\n SelectItemText,\n SelectLabel,\n SelectList,\n SelectPositioner,\n SelectRootLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n createListCollection,\n type CollectionItem,\n type ListCollection,\n SelectItemContext,\n}\n"
16
+ }
17
+ ],
18
+ "docs": "## Shared hooks\n`src/lib/history.ts` exports `useHistory(initial, limit)` → `{ present, set(next, { commit }),\ncheckpoint, undo, redo, reset, canUndo, canRedo }`; the node graph and form builder use it. Never\nadd a local copy. There is no native `<select>` component any more (`native-select.tsx` was\nremoved); always use `Select`."
19
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "separator",
3
+ "type": "ui",
4
+ "description": "Ark UI has no Separator primitive. This mirrors Radix's: a `div` with `role=\"separator\"` (or `role=\"none\"` when decorative) and `data-orientation`.",
5
+ "dependencies": {},
6
+ "registryDependencies": [
7
+ "utils"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "components/ui/separator.tsx",
12
+ "content": "import * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Ark UI has no Separator primitive. This mirrors Radix's: a `div` with\n * `role=\"separator\"` (or `role=\"none\"` when decorative) and `data-orientation`.\n */\nfunction Separator({\n className,\n orientation = \"horizontal\",\n decorative = true,\n ...props\n}: React.ComponentProps<\"div\"> & {\n orientation?: \"horizontal\" | \"vertical\"\n decorative?: boolean\n}) {\n return (\n <div\n data-slot=\"separator\"\n data-orientation={orientation}\n role={decorative ? \"none\" : \"separator\"}\n aria-orientation={!decorative && orientation === \"vertical\" ? \"vertical\" : undefined}\n className={cn(\n \"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport { Separator }\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "sheet",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1",
7
+ "lucide-react": "^1.41.0"
8
+ },
9
+ "registryDependencies": [
10
+ "button",
11
+ "utils"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "components/ui/sheet.tsx",
16
+ "content": "import * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Dialog as SheetPrimitive, Portal as PortalPrimitive } from \"@ark-ui/react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { XIcon } from \"lucide-react\"\n\nfunction Sheet({ lazyMount = true, unmountOnExit = true, ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {\n return <SheetPrimitive.Root lazyMount={lazyMount} unmountOnExit={unmountOnExit} {...props} />\n}\n\nfunction SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />\n}\n\nfunction SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.CloseTrigger>) {\n return <SheetPrimitive.CloseTrigger data-slot=\"sheet-close\" {...props} />\n}\n\nfunction SheetPortal({ ...props }: React.ComponentProps<typeof PortalPrimitive>) {\n return <PortalPrimitive {...props} />\n}\n\nfunction SheetContext({ ...props }: React.ComponentProps<typeof SheetPrimitive.Context>) {\n return <SheetPrimitive.Context {...props} />\n}\n\nfunction SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Backdrop>) {\n return (\n <SheetPrimitive.Backdrop\n data-slot=\"sheet-overlay\"\n className={cn(\n \"fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SheetPositioner({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Positioner>) {\n return (\n <SheetPrimitive.Positioner\n data-slot=\"sheet-positioner\"\n className={cn(\"fixed inset-0 z-50\", className)}\n {...props}\n />\n )\n}\n\nfunction SheetContent({\n className,\n children,\n side = \"right\",\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n side?: \"top\" | \"right\" | \"bottom\" | \"left\"\n showCloseButton?: boolean\n}) {\n return (\n <SheetPortal>\n <SheetOverlay />\n <SheetPositioner data-side={side}>\n <SheetPrimitive.Content\n data-slot=\"sheet-content\"\n data-side={side}\n className={cn(\n \"absolute flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10\",\n className\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <SheetPrimitive.CloseTrigger data-slot=\"sheet-close\" asChild>\n <Button variant=\"ghost\" className=\"absolute top-3 right-3\" size=\"icon-sm\">\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </Button>\n </SheetPrimitive.CloseTrigger>\n )}\n </SheetPrimitive.Content>\n </SheetPositioner>\n </SheetPortal>\n )\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"sheet-header\" className={cn(\"flex flex-col gap-0.5 p-4\", className)} {...props} />\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"sheet-footer\" className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />\n}\n\nfunction SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {\n return (\n <SheetPrimitive.Title\n data-slot=\"sheet-title\"\n className={cn(\"font-heading text-base font-medium text-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {\n return (\n <SheetPrimitive.Description\n data-slot=\"sheet-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetContext,\n SheetHeader,\n SheetFooter,\n SheetOverlay,\n SheetPortal,\n SheetPositioner,\n SheetTitle,\n SheetDescription,\n}\n"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "sidebar",
3
+ "type": "ui",
4
+ "description": "`src/components/ui/app-shell.tsx` is a compositional, router-agnostic shell built on `sidebar.tsx` (shadcn-admin layout): `AppShell` (context: sidebar variant/collapsible, search open state, `searchShortcut` for cmd/ctrl+K), `AppShellSideba",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1",
7
+ "class-variance-authority": "^0.7.1",
8
+ "lucide-react": "^1.41.0"
9
+ },
10
+ "registryDependencies": [
11
+ "button",
12
+ "input",
13
+ "separator",
14
+ "sheet",
15
+ "skeleton",
16
+ "tooltip",
17
+ "use-mobile",
18
+ "utils"
19
+ ],
20
+ "files": [
21
+ {
22
+ "path": "components/ui/sidebar.tsx",
23
+ "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\nimport { ark } from \"@ark-ui/react\"\n\nimport { useIsMobile } from \"@/hooks/use-mobile\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from \"@/components/ui/sheet\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\"\nimport { PanelLeftIcon } from \"lucide-react\"\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\"\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\"\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\"\n open: boolean\n setOpen: (open: boolean) => void\n openMobile: boolean\n setOpenMobile: (open: boolean) => void\n isMobile: boolean\n toggleSidebar: () => void\n}\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null)\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext)\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\")\n }\n\n return context\n}\n\nfunction SidebarProvider({\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean\n open?: boolean\n onOpenChange?: (open: boolean) => void\n}) {\n const isMobile = useIsMobile()\n const [openMobile, setOpenMobile] = React.useState(false)\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen)\n const open = openProp ?? _open\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value\n if (setOpenProp) {\n setOpenProp(openState)\n } else {\n _setOpen(openState)\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n },\n [setOpenProp, open]\n )\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)\n }, [isMobile, setOpen, setOpenMobile])\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n toggleSidebar()\n }\n }\n\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [toggleSidebar])\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\"\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n )\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <div\n data-slot=\"sidebar-wrapper\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar\", className)}\n {...props}\n >\n {children}\n </div>\n </SidebarContext.Provider>\n )\n}\n\nfunction Sidebar({\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n dir,\n ...props\n}: React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\"\n variant?: \"sidebar\" | \"floating\" | \"inset\"\n collapsible?: \"offcanvas\" | \"icon\" | \"none\"\n}) {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar()\n\n if (collapsible === \"none\") {\n return (\n <div\n data-slot=\"sidebar\"\n className={cn(\"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\", className)}\n {...props}\n >\n {children}\n </div>\n )\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={({ open }) => setOpenMobile(open)}>\n <SheetContent\n {...props}\n dir={dir}\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex size-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <div\n className=\"group peer hidden text-sidebar-foreground md:block\"\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n data-slot=\"sidebar\"\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n data-slot=\"sidebar-gap\"\n className={cn(\n \"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\"\n )}\n />\n <div\n data-slot=\"sidebar-container\"\n data-side={side}\n className={cn(\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:-left-(--sidebar-width) data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:-right-(--sidebar-width) md:flex\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\" || variant === \"inset\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l\",\n className\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar-inner\"\n className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n >\n {children}\n </div>\n </div>\n </div>\n )\n}\n\nfunction SidebarTrigger({ className, onClick, asChild, children, ...props }: React.ComponentProps<typeof Button>) {\n const { toggleSidebar } = useSidebar()\n\n return (\n <Button\n data-sidebar=\"trigger\"\n data-slot=\"sidebar-trigger\"\n variant=\"ghost\"\n size=\"icon-sm\"\n asChild={asChild}\n className={cn(className)}\n onClick={(event) => {\n onClick?.(event)\n toggleSidebar()\n }}\n {...props}\n >\n {asChild\n ? children\n : (children ?? (\n <>\n <PanelLeftIcon />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </>\n ))}\n </Button>\n )\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n const { toggleSidebar } = useSidebar()\n\n return (\n <button\n data-sidebar=\"rail\"\n data-slot=\"sidebar-rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(\n \"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:inset-s-1/2 after:w-0.5 hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2\",\n \"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize\",\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n return (\n <main\n data-slot=\"sidebar-inset\"\n className={cn(\n \"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarInput({ className, ...props }: React.ComponentProps<typeof Input>) {\n return (\n <Input\n data-slot=\"sidebar-input\"\n data-sidebar=\"input\"\n className={cn(\"h-8 w-full bg-background shadow-none\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-header\"\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-footer\"\n data-sidebar=\"footer\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot=\"sidebar-separator\"\n data-sidebar=\"separator\"\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-content\"\n data-sidebar=\"content\"\n className={cn(\n \"no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group\"\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarGroupLabel({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n const Comp = ark.div\n\n return (\n <Comp\n asChild={asChild}\n data-slot=\"sidebar-group-label\"\n data-sidebar=\"group-label\"\n className={cn(\n \"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarGroupAction({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> & { asChild?: boolean }) {\n const Comp = ark.button\n\n return (\n <Comp\n asChild={asChild}\n data-slot=\"sidebar-group-action\"\n data-sidebar=\"group-action\"\n className={cn(\n \"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarGroupContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group-content\"\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu\"\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-0\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-item\"\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n )\n}\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate\",\n {\n variants: {\n variant: {\n default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction SidebarMenuButton({\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n}: React.ComponentProps<\"button\"> & {\n asChild?: boolean\n isActive?: boolean\n tooltip?: string | React.ComponentProps<typeof TooltipContent>\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n const Comp = ark.button\n const { isMobile, state } = useSidebar()\n\n const button = (\n <Comp\n asChild={asChild}\n data-slot=\"sidebar-menu-button\"\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n )\n\n if (!tooltip) {\n return button\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n }\n }\n\n return (\n <Tooltip positioning={{ placement: \"right\" }}>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent hidden={state !== \"collapsed\" || isMobile} {...tooltip} />\n </Tooltip>\n )\n}\n\nfunction SidebarMenuAction({\n className,\n asChild = false,\n showOnHover = false,\n ...props\n}: React.ComponentProps<\"button\"> & {\n asChild?: boolean\n showOnHover?: boolean\n}) {\n const Comp = ark.button\n\n return (\n <Comp\n asChild={asChild}\n data-slot=\"sidebar-menu-action\"\n data-sidebar=\"menu-action\"\n className={cn(\n \"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\n showOnHover &&\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarMenuBadge({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-menu-badge\"\n data-sidebar=\"menu-badge\"\n className={cn(\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarMenuSkeleton({\n className,\n showIcon = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showIcon?: boolean\n}) {\n // Random width between 50 to 90%.\n const [width] = React.useState(() => {\n return `${Math.floor(Math.random() * 40) + 50}%`\n })\n\n return (\n <div\n data-slot=\"sidebar-menu-skeleton\"\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n <Skeleton\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n )\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu-sub\"\n data-sidebar=\"menu-sub\"\n className={cn(\n \"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SidebarMenuSubItem({ className, ...props }: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-sub-item\"\n data-sidebar=\"menu-sub-item\"\n className={cn(\"group/menu-sub-item relative\", className)}\n {...props}\n />\n )\n}\n\nfunction SidebarMenuSubButton({\n asChild = false,\n size = \"md\",\n isActive = false,\n className,\n ...props\n}: React.ComponentProps<\"a\"> & {\n asChild?: boolean\n size?: \"sm\" | \"md\"\n isActive?: boolean\n}) {\n const Comp = ark.a\n\n return (\n <Comp\n asChild={asChild}\n data-slot=\"sidebar-menu-sub-button\"\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n}\n"
24
+ }
25
+ ],
26
+ "docs": "## App shell\n`src/components/ui/app-shell.tsx` is a compositional, router-agnostic shell built on\n`sidebar.tsx` (shadcn-admin layout): `AppShell` (context: sidebar variant/collapsible,\nsearch open state, `searchShortcut` for cmd/ctrl+K), `AppShellSidebar` + `AppShellBrand*`,\n`AppShellNav*` (groups, links with `active`, badges, `AppShellNavCollapsible` that becomes\na dropdown flyout when the sidebar is icon-collapsed), `AppShellUser*`, `AppShellContent`,\n`AppShellHeader` (fixed/sticky with scroll shadow, includes the sidebar trigger),\n`AppShellHeaderActions`, `AppShellMain` (`fixed` = viewport-height, `fluid` = full width),\n`AppShellTopNav*`, `AppShellSearch` + `AppShellCommandDialog` (bound to shell search\nstate; nest a `Command` inside), `AppShellNotifications*` (Popover-based bell with a `count`\nbadge on the trigger; header/title/action, list, `AppShellNotificationItem` with `unread` +\n`asChild` surface, icon/content/title/description/time/indicator, empty, footer; data and read\nstate are the consumer's), and `AppShellThemeToggle` (`theme`/`onThemeChange`, no theme\nlibrary assumed). Header icon buttons use `size=\"icon-lg\"` (size-9, `scale-95 rounded-full`)\nand `AppShellHeaderActions` uses the header's `gap-3 sm:gap-4` to match shadcn-admin. Navigation is plain anchors with `asChild` so any router works.\n`src/demo/shell/` is the reference (nav data, hash router, team/user menus, command menu)\nand `src/app.tsx` wraps every demo route in it. `AppShellDemo` takes `headerStart` for\npage-specific header content (the dashboard passes its `AppShellTopNav` + `AppShellTopNavMenu`)."
27
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "signature-pad",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1",
7
+ "lucide-react": "^1.41.0"
8
+ },
9
+ "registryDependencies": [
10
+ "button",
11
+ "utils"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "components/ui/signature-pad.tsx",
16
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { SignaturePad as SignaturePadPrimitive } from \"@ark-ui/react\"\nimport { XIcon } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\n\nfunction SignaturePad({ className, ...props }: React.ComponentProps<typeof SignaturePadPrimitive.Root>) {\n return (\n <SignaturePadPrimitive.Root\n data-slot=\"signature-pad\"\n className={cn(\"flex w-full flex-col gap-1.5\", className)}\n {...props}\n />\n )\n}\n\nfunction SignaturePadContext({ ...props }: React.ComponentProps<typeof SignaturePadPrimitive.Context>) {\n return <SignaturePadPrimitive.Context {...props} />\n}\n\nfunction SignaturePadLabel({ className, ...props }: React.ComponentProps<typeof SignaturePadPrimitive.Label>) {\n return (\n <SignaturePadPrimitive.Label\n data-slot=\"signature-pad-label\"\n className={cn(\"text-sm font-medium select-none\", className)}\n {...props}\n />\n )\n}\n\nfunction SignaturePadControl({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SignaturePadPrimitive.Control>) {\n return (\n <SignaturePadPrimitive.Control\n data-slot=\"signature-pad-control\"\n className={cn(\n \"relative h-40 w-full touch-none overflow-hidden rounded-xl border border-input bg-transparent transition-colors has-focus-visible:border-ring has-focus-visible:ring-3 has-focus-visible:ring-ring/50 dark:bg-input/30 data-disabled:pointer-events-none data-disabled:opacity-50\",\n className\n )}\n {...props}\n >\n {children ?? (\n <>\n <SignaturePadSegment />\n <SignaturePadGuide />\n <SignaturePadClearTrigger />\n </>\n )}\n </SignaturePadPrimitive.Control>\n )\n}\n\nfunction SignaturePadSegment({ className, ...props }: React.ComponentProps<typeof SignaturePadPrimitive.Segment>) {\n return (\n <SignaturePadPrimitive.Segment\n data-slot=\"signature-pad-segment\"\n className={cn(\"fill-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SignaturePadGuide({ className, ...props }: React.ComponentProps<typeof SignaturePadPrimitive.Guide>) {\n return (\n <SignaturePadPrimitive.Guide\n data-slot=\"signature-pad-guide\"\n className={cn(\"pointer-events-none absolute inset-x-4 bottom-8 border-b border-dashed border-border\", className)}\n {...props}\n />\n )\n}\n\nfunction SignaturePadClearTrigger({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SignaturePadPrimitive.ClearTrigger>) {\n return (\n <SignaturePadPrimitive.ClearTrigger\n data-slot=\"signature-pad-clear-trigger\"\n className={cn(\"absolute top-2 right-2\", className)}\n asChild\n {...props}\n >\n <Button variant=\"ghost\" size=\"icon-sm\">\n {children ?? <XIcon />}\n <span className=\"sr-only\">Clear</span>\n </Button>\n </SignaturePadPrimitive.ClearTrigger>\n )\n}\n\nfunction SignaturePadHiddenInput({ ...props }: React.ComponentProps<typeof SignaturePadPrimitive.HiddenInput>) {\n return <SignaturePadPrimitive.HiddenInput {...props} />\n}\n\nexport {\n SignaturePad,\n SignaturePadClearTrigger,\n SignaturePadContext,\n SignaturePadControl,\n SignaturePadGuide,\n SignaturePadHiddenInput,\n SignaturePadLabel,\n SignaturePadSegment,\n}\n"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "skeleton",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {},
6
+ "registryDependencies": [
7
+ "utils"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "components/ui/skeleton.tsx",
12
+ "content": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"skeleton\" className={cn(\"animate-pulse rounded-md bg-muted\", className)} {...props} />\n}\n\nexport { Skeleton }\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "slider",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1"
7
+ },
8
+ "registryDependencies": [
9
+ "utils"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/slider.tsx",
14
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Slider as SliderPrimitive } from \"@ark-ui/react\"\n\nfunction Slider({\n className,\n children,\n defaultValue,\n value,\n min = 0,\n max = 100,\n ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n )\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n \"relative flex w-full touch-none flex-col gap-2 select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto\",\n className\n )}\n {...props}\n >\n {children}\n <SliderControl>\n <SliderTrack>\n <SliderRange />\n </SliderTrack>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderThumb key={index} index={index}>\n <SliderHiddenInput />\n </SliderThumb>\n ))}\n </SliderControl>\n </SliderPrimitive.Root>\n )\n}\n\nfunction SliderRoot({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.Root>) {\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n className={cn(\n \"relative flex w-full touch-none flex-col gap-2 select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SliderContext({ ...props }: React.ComponentProps<typeof SliderPrimitive.Context>) {\n return <SliderPrimitive.Context {...props} />\n}\n\nfunction SliderLabel({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.Label>) {\n return <SliderPrimitive.Label data-slot=\"slider-label\" className={cn(\"text-sm font-medium\", className)} {...props} />\n}\n\nfunction SliderValueText({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.ValueText>) {\n return (\n <SliderPrimitive.ValueText\n data-slot=\"slider-value-text\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SliderControl({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.Control>) {\n return (\n <SliderPrimitive.Control\n data-slot=\"slider-control\"\n className={cn(\"relative flex items-center data-vertical:h-full data-vertical:flex-col\", className)}\n {...props}\n />\n )\n}\n\nfunction SliderTrack({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.Track>) {\n return (\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n \"relative grow overflow-hidden rounded-full bg-muted data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SliderRange({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.Range>) {\n return (\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className={cn(\"absolute bg-primary select-none data-horizontal:h-full data-vertical:w-full\", className)}\n {...props}\n />\n )\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction SliderThumb({ className, ...props }: Omit<React.ComponentProps<typeof SliderPrimitive.Thumb>, \"id\">) {\n return (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n className={cn(\n \"relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden data-dragging:ring-3 data-disabled:pointer-events-none data-disabled:opacity-50\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction SliderHiddenInput({ ...props }: Omit<React.ComponentProps<typeof SliderPrimitive.HiddenInput>, \"id\">) {\n return <SliderPrimitive.HiddenInput {...props} />\n}\n\nfunction SliderMarkerGroup({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.MarkerGroup>) {\n return (\n <SliderPrimitive.MarkerGroup\n data-slot=\"slider-marker-group\"\n className={cn(\"relative h-4 text-xs text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SliderMarker({ className, ...props }: React.ComponentProps<typeof SliderPrimitive.Marker>) {\n return (\n <SliderPrimitive.Marker\n data-slot=\"slider-marker\"\n className={cn(\"absolute -translate-x-1/2\", className)}\n {...props}\n />\n )\n}\n\n/** Value bubble shown above a thumb while it is dragged. Place it inside `SliderThumb`. */\nfunction SliderDraggingIndicator({\n className,\n ...props\n}: React.ComponentProps<typeof SliderPrimitive.DraggingIndicator>) {\n return (\n <SliderPrimitive.DraggingIndicator\n data-slot=\"slider-dragging-indicator\"\n className={cn(\n \"pointer-events-none absolute bottom-full left-1/2 mb-2 -translate-x-1/2 rounded-md bg-primary px-1.5 py-0.5 text-xs font-medium text-primary-foreground tabular-nums\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n Slider,\n SliderContext,\n SliderControl,\n SliderHiddenInput,\n SliderLabel,\n SliderMarker,\n SliderMarkerGroup,\n SliderRange,\n SliderRoot,\n SliderThumb,\n SliderTrack,\n SliderValueText,\n SliderDraggingIndicator,\n}\n"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "sonner",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "lucide-react": "^1.41.0",
7
+ "next-themes": "^0.4.6",
8
+ "sonner": "^2.0.8"
9
+ },
10
+ "registryDependencies": [],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/sonner.tsx",
14
+ "content": "import { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\nimport { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from \"lucide-react\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme()\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"group\"\n icons={{\n success: <CircleCheckIcon className=\"size-4\" />,\n info: <InfoIcon className=\"size-4\" />,\n warning: <TriangleAlertIcon className=\"size-4\" />,\n error: <OctagonXIcon className=\"size-4\" />,\n loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n }}\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\",\n \"--border-radius\": \"var(--radius)\",\n } as React.CSSProperties\n }\n toastOptions={{\n classNames: {\n toast: \"cn-toast\",\n },\n }}\n {...props}\n />\n )\n}\n\nexport { Toaster }\n"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "spinner",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "lucide-react": "^1.41.0"
7
+ },
8
+ "registryDependencies": [
9
+ "utils"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/spinner.tsx",
14
+ "content": "import { cn } from \"@/lib/utils\"\nimport { Loader2Icon } from \"lucide-react\"\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n return (\n <Loader2Icon\n data-slot=\"spinner\"\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\"size-4 animate-spin\", className)}\n {...props}\n />\n )\n}\n\nexport { Spinner }\n"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "steps",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1",
7
+ "lucide-react": "^1.41.0"
8
+ },
9
+ "registryDependencies": [
10
+ "button",
11
+ "utils"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "components/ui/steps.tsx",
16
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Steps as StepsPrimitive } from \"@ark-ui/react\"\nimport { CheckIcon } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\n\nfunction Steps({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.Root>) {\n return (\n <StepsPrimitive.Root\n data-slot=\"steps\"\n className={cn(\"flex w-full flex-col gap-4 data-vertical:flex-row\", className)}\n {...props}\n />\n )\n}\n\nfunction StepsContext({ ...props }: React.ComponentProps<typeof StepsPrimitive.Context>) {\n return <StepsPrimitive.Context {...props} />\n}\n\nfunction StepsList({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.List>) {\n return (\n <StepsPrimitive.List\n data-slot=\"steps-list\"\n className={cn(\"flex items-center gap-2 data-vertical:flex-col data-vertical:items-stretch\", className)}\n {...props}\n />\n )\n}\n\nfunction StepsItem({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.Item>) {\n return (\n <StepsPrimitive.Item\n data-slot=\"steps-item\"\n className={cn(\n \"group/steps-item flex flex-1 items-center gap-2 last:flex-none data-vertical:flex-col data-vertical:items-start\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction StepsItemContext({ ...props }: React.ComponentProps<typeof StepsPrimitive.ItemContext>) {\n return <StepsPrimitive.ItemContext {...props} />\n}\n\nfunction StepsTrigger({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.Trigger>) {\n return (\n <StepsPrimitive.Trigger\n data-slot=\"steps-trigger\"\n className={cn(\n \"flex items-center gap-2 rounded-md text-left text-sm font-medium outline-none focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-incomplete:text-muted-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction StepsIndicator({ className, children, ...props }: React.ComponentProps<typeof StepsPrimitive.Indicator>) {\n return (\n <StepsPrimitive.Indicator\n data-slot=\"steps-indicator\"\n className={cn(\n \"flex size-7 shrink-0 items-center justify-center rounded-full border border-input text-xs font-medium tabular-nums transition-colors data-complete:border-primary data-complete:bg-primary data-complete:text-primary-foreground data-current:border-primary data-current:bg-primary data-current:text-primary-foreground [&_svg]:size-3.5\",\n className\n )}\n {...props}\n >\n {children ?? (\n <StepsPrimitive.ItemContext>\n {({ completed, index }) => (completed ? <CheckIcon /> : index + 1)}\n </StepsPrimitive.ItemContext>\n )}\n </StepsPrimitive.Indicator>\n )\n}\n\nfunction StepsSeparator({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.Separator>) {\n return (\n <StepsPrimitive.Separator\n data-slot=\"steps-separator\"\n className={cn(\n \"h-px flex-1 bg-border transition-colors data-complete:bg-primary data-vertical:ml-3.5 data-vertical:h-6 data-vertical:w-px data-vertical:flex-none\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction StepsContent({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.Content>) {\n return <StepsPrimitive.Content data-slot=\"steps-content\" className={cn(\"text-sm\", className)} {...props} />\n}\n\nfunction StepsCompletedContent({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.CompletedContent>) {\n return (\n <StepsPrimitive.CompletedContent\n data-slot=\"steps-completed-content\"\n className={cn(\"text-sm\", className)}\n {...props}\n />\n )\n}\n\nfunction StepsProgress({ className, ...props }: React.ComponentProps<typeof StepsPrimitive.Progress>) {\n return (\n <StepsPrimitive.Progress\n data-slot=\"steps-progress\"\n className={cn(\"h-1 w-full rounded-full bg-muted\", className)}\n {...props}\n />\n )\n}\n\nfunction StepsPrevTrigger({ className, children, ...props }: React.ComponentProps<typeof StepsPrimitive.PrevTrigger>) {\n return (\n <StepsPrimitive.PrevTrigger data-slot=\"steps-prev-trigger\" className={cn(className)} asChild {...props}>\n <Button variant=\"outline\" size=\"sm\">\n {children}\n </Button>\n </StepsPrimitive.PrevTrigger>\n )\n}\n\nfunction StepsNextTrigger({ className, children, ...props }: React.ComponentProps<typeof StepsPrimitive.NextTrigger>) {\n return (\n <StepsPrimitive.NextTrigger data-slot=\"steps-next-trigger\" className={cn(className)} asChild {...props}>\n <Button size=\"sm\">{children}</Button>\n </StepsPrimitive.NextTrigger>\n )\n}\n\nexport {\n Steps,\n StepsCompletedContent,\n StepsContent,\n StepsContext,\n StepsIndicator,\n StepsItem,\n StepsItemContext,\n StepsList,\n StepsNextTrigger,\n StepsPrevTrigger,\n StepsProgress,\n StepsSeparator,\n StepsTrigger,\n}\n"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "swap",
3
+ "type": "ui",
4
+ "description": "",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1"
7
+ },
8
+ "registryDependencies": [
9
+ "utils"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/swap.tsx",
14
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Swap as SwapPrimitive } from \"@ark-ui/react\"\n\nfunction Swap({ className, ...props }: React.ComponentProps<typeof SwapPrimitive.Root>) {\n return (\n <SwapPrimitive.Root\n data-slot=\"swap\"\n className={cn(\n \"relative inline-grid cursor-pointer place-items-center select-none *:col-start-1 *:row-start-1\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SwapIndicator({ className, ...props }: React.ComponentProps<typeof SwapPrimitive.Indicator>) {\n return (\n <SwapPrimitive.Indicator\n data-slot=\"swap-indicator\"\n className={cn(\n \"transition-[opacity,transform] duration-200 data-[state=closed]:scale-75 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport { Swap, SwapIndicator }\n"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "switch",
3
+ "type": "ui",
4
+ "description": "`id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working.",
5
+ "dependencies": {
6
+ "@ark-ui/react": "^5.39.1"
7
+ },
8
+ "registryDependencies": [
9
+ "utils"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "components/ui/switch.tsx",
14
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Switch as SwitchPrimitive } from \"@ark-ui/react\"\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction Switch({\n className,\n size = \"default\",\n children,\n ...props\n}: Omit<React.ComponentProps<typeof SwitchPrimitive.Root>, \"id\"> & {\n size?: \"sm\" | \"default\"\n}) {\n return (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n data-size={size}\n className=\"group/switch peer inline-flex items-center gap-2\"\n {...props}\n >\n <SwitchControl className={className}>\n <SwitchThumb />\n </SwitchControl>\n {children && <SwitchLabel>{children}</SwitchLabel>}\n <SwitchHiddenInput />\n </SwitchPrimitive.Root>\n )\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction SwitchRoot({ ...props }: Omit<React.ComponentProps<typeof SwitchPrimitive.Root>, \"id\">) {\n return <SwitchPrimitive.Root data-slot=\"switch\" {...props} />\n}\n\nfunction SwitchContext({ ...props }: React.ComponentProps<typeof SwitchPrimitive.Context>) {\n return <SwitchPrimitive.Context {...props} />\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction SwitchControl({ className, ...props }: Omit<React.ComponentProps<typeof SwitchPrimitive.Control>, \"id\">) {\n return (\n <SwitchPrimitive.Control\n data-slot=\"switch-control\"\n className={cn(\n \"relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none group-has-focus-visible/field-label:border-transparent group-has-focus-visible/field-label:ring-0 group-data-[size=default]/switch:h-[18.4px] group-data-[size=default]/switch:w-8 group-data-[size=sm]/switch:h-3.5 group-data-[size=sm]/switch:w-6 after:absolute after:-inset-x-3 after:-inset-y-2 data-focus-visible:border-ring data-focus-visible:ring-3 data-focus-visible:ring-ring/50 data-invalid:border-destructive data-invalid:ring-3 data-invalid:ring-destructive/20 dark:data-invalid:border-destructive/50 dark:data-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SwitchThumb({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Thumb>) {\n return (\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className={cn(\n \"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction SwitchLabel({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Label>) {\n return (\n <SwitchPrimitive.Label\n data-slot=\"switch-label\"\n className={cn(\"text-sm leading-none select-none data-disabled:opacity-50\", className)}\n {...props}\n />\n )\n}\n\n/** `id` is omitted: set part ids through the root `ids` prop so Ark's internal lookups keep working. */\nfunction SwitchHiddenInput({ ...props }: Omit<React.ComponentProps<typeof SwitchPrimitive.HiddenInput>, \"id\">) {\n return <SwitchPrimitive.HiddenInput {...props} />\n}\n\nexport { Switch, SwitchContext, SwitchControl, SwitchHiddenInput, SwitchLabel, SwitchRoot, SwitchThumb }\n"
15
+ }
16
+ ]
17
+ }