@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,16 @@
1
+ {
2
+ "name": "virtual-list",
3
+ "type": "ui",
4
+ "description": "-------------------------------- context --------------------------------",
5
+ "dependencies": {},
6
+ "registryDependencies": [
7
+ "utils"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "components/ui/virtual-list.tsx",
12
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/* -------------------------------- context -------------------------------- */\n\ntype VirtualItem = {\n index: number\n key: React.Key\n /** Offset from the top of the content, in px. */\n start: number\n size: number\n end: number\n /** False until the row's real height has been measured. */\n measured: boolean\n}\n\ntype ScrollAlign = \"auto\" | \"start\" | \"center\" | \"end\"\n\ntype VirtualListContextValue = {\n count: number\n items: VirtualItem[]\n totalSize: number\n /** First and last rendered index (overscan included), or null when empty. */\n range: { start: number; end: number } | null\n measure: (index: number, size: number) => void\n scrollToIndex: (index: number, options?: { align?: ScrollAlign; behavior?: ScrollBehavior }) => void\n scrollToOffset: (offset: number, behavior?: ScrollBehavior) => void\n viewportRef: React.RefObject<HTMLDivElement | null>\n getItemKey: (index: number) => React.Key\n}\n\nconst VirtualListContext = React.createContext<VirtualListContextValue | null>(null)\n\nfunction useVirtualList() {\n const ctx = React.useContext(VirtualListContext)\n if (!ctx) throw new Error(\"VirtualList parts must be used inside <VirtualList>\")\n return ctx\n}\n\n/* -------------------------------- helpers -------------------------------- */\n\n/** Largest index whose start is <= offset. */\nfunction findIndex(starts: Float64Array, count: number, offset: number) {\n let lo = 0\n let hi = count - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (starts[mid] <= offset) lo = mid\n else hi = mid - 1\n }\n return lo\n}\n\n/* ---------------------------------- root --------------------------------- */\n\ntype VirtualListProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n count: number\n /** Row height before measurement (a number, or per index). */\n estimateSize?: number | ((index: number) => number)\n /** Rows rendered beyond the visible window on each side. */\n overscan?: number\n gap?: number\n paddingStart?: number\n paddingEnd?: number\n getItemKey?: (index: number) => React.Key\n /** Fires when the rendered window changes. */\n onRangeChange?: (range: { start: number; end: number } | null) => void\n children: React.ReactNode\n}\n\nfunction VirtualList({\n count,\n estimateSize = 36,\n overscan = 6,\n gap = 0,\n paddingStart = 0,\n paddingEnd = 0,\n getItemKey = (index) => index,\n onRangeChange,\n className,\n children,\n ...props\n}: VirtualListProps) {\n const viewportRef = React.useRef<HTMLDivElement | null>(null)\n const sizes = React.useRef(new Map<number, number>())\n const [version, setVersion] = React.useState(0)\n const [scrollTop, setScrollTop] = React.useState(0)\n const [viewportSize, setViewportSize] = React.useState(0)\n const estimate = React.useCallback(\n (index: number) => (typeof estimateSize === \"function\" ? estimateSize(index) : estimateSize),\n [estimateSize]\n )\n\n // Prefix sums of row starts; recomputed when a measurement or the count changes.\n const layout = React.useMemo(() => {\n const starts = new Float64Array(count)\n let offset = paddingStart\n for (let i = 0; i < count; i++) {\n starts[i] = offset\n offset += (sizes.current.get(i) ?? estimate(i)) + (i < count - 1 ? gap : 0)\n }\n return { starts, totalSize: offset + paddingEnd }\n // `version` is the measurement tick.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [count, estimate, gap, paddingStart, paddingEnd, version])\n\n const range = React.useMemo(() => {\n if (count === 0 || viewportSize === 0) return count === 0 ? null : { start: 0, end: Math.min(count - 1, overscan) }\n const first = findIndex(layout.starts, count, scrollTop)\n const last = findIndex(layout.starts, count, scrollTop + viewportSize)\n return { start: Math.max(0, first - overscan), end: Math.min(count - 1, last + overscan) }\n }, [count, viewportSize, scrollTop, layout, overscan])\n\n const items = React.useMemo(() => {\n if (!range) return []\n const out: VirtualItem[] = []\n for (let i = range.start; i <= range.end; i++) {\n const measured = sizes.current.has(i)\n const size = sizes.current.get(i) ?? estimate(i)\n out.push({ index: i, key: getItemKey(i), start: layout.starts[i], size, end: layout.starts[i] + size, measured })\n }\n return out\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [range, layout, estimate, getItemKey, version])\n\n const onRangeRef = React.useRef(onRangeChange)\n onRangeRef.current = onRangeChange\n const lastRange = React.useRef<string>(\"\")\n React.useEffect(() => {\n const key = range ? `${range.start}-${range.end}` : \"none\"\n if (key === lastRange.current) return\n lastRange.current = key\n onRangeRef.current?.(range)\n }, [range])\n\n // Drop stale measurements when the list shrinks.\n React.useEffect(() => {\n let dirty = false\n for (const index of sizes.current.keys()) {\n if (index >= count) {\n sizes.current.delete(index)\n dirty = true\n }\n }\n if (dirty) setVersion((v) => v + 1)\n }, [count])\n\n const measure = React.useCallback((index: number, size: number) => {\n const prev = sizes.current.get(index)\n if (prev !== undefined && Math.abs(prev - size) < 0.5) return\n sizes.current.set(index, size)\n setVersion((v) => v + 1)\n }, [])\n\n React.useLayoutEffect(() => {\n const el = viewportRef.current\n if (!el) return\n const update = () => setViewportSize(el.clientHeight)\n update()\n const ro = new ResizeObserver(update)\n ro.observe(el)\n return () => ro.disconnect()\n }, [])\n\n const scrollToOffset = React.useCallback((offset: number, behavior: ScrollBehavior = \"auto\") => {\n viewportRef.current?.scrollTo({ top: offset, behavior })\n }, [])\n\n const pending = React.useRef<{ index: number; align: ScrollAlign; tries: number } | null>(null)\n const offsetFor = React.useCallback(\n (index: number, align: ScrollAlign) => {\n const el = viewportRef.current\n if (!el) return 0\n const start = layout.starts[index] ?? 0\n const size = sizes.current.get(index) ?? estimate(index)\n const height = el.clientHeight\n let resolved = align\n if (align === \"auto\") {\n if (start < el.scrollTop) resolved = \"start\"\n else if (start + size > el.scrollTop + height) resolved = \"end\"\n else return el.scrollTop\n }\n if (resolved === \"start\") return start\n if (resolved === \"end\") return start + size - height\n return start + size / 2 - height / 2\n },\n [layout, estimate]\n )\n\n const scrollToIndex = React.useCallback(\n (index: number, options?: { align?: ScrollAlign; behavior?: ScrollBehavior }) => {\n const clamped = Math.max(0, Math.min(count - 1, index))\n const align = options?.align ?? \"auto\"\n pending.current = { index: clamped, align, tries: 0 }\n scrollToOffset(offsetFor(clamped, align), options?.behavior)\n },\n [count, offsetFor, scrollToOffset]\n )\n\n // Estimated rows shift as they get measured: re-aim at the target a few times.\n React.useLayoutEffect(() => {\n const p = pending.current\n const el = viewportRef.current\n if (!p || !el) return\n if (p.tries >= 4 || !sizes.current.has(p.index)) {\n if (p.tries >= 4) pending.current = null\n return\n }\n const target = offsetFor(p.index, p.align === \"auto\" ? \"start\" : p.align)\n if (Math.abs(el.scrollTop - target) > 1) {\n p.tries += 1\n el.scrollTop = target\n } else pending.current = null\n })\n\n const ctx: VirtualListContextValue = {\n count,\n items,\n totalSize: layout.totalSize,\n range,\n measure,\n scrollToIndex,\n scrollToOffset,\n viewportRef,\n getItemKey,\n }\n\n return (\n <VirtualListContext.Provider value={ctx}>\n <VirtualListScroll onScroll={setScrollTop} />\n <div data-slot=\"virtual-list\" className={cn(\"flex min-h-0 flex-col\", className)} {...props}>\n {children}\n </div>\n </VirtualListContext.Provider>\n )\n}\n\n/** Attaches the scroll listener once the viewport mounts (kept out of the root's render). */\nfunction VirtualListScroll({ onScroll }: { onScroll: (top: number) => void }) {\n const ctx = useVirtualList()\n React.useEffect(() => {\n const el = ctx.viewportRef.current\n if (!el) return\n const handler = () => onScroll(el.scrollTop)\n el.addEventListener(\"scroll\", handler, { passive: true })\n return () => el.removeEventListener(\"scroll\", handler)\n }, [ctx.viewportRef, onScroll])\n return null\n}\n\n/* --------------------------------- parts --------------------------------- */\n\nfunction VirtualListViewport({ className, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n return (\n <div\n ref={ctx.viewportRef}\n data-slot=\"virtual-list-viewport\"\n className={cn(\"relative min-h-0 overflow-y-auto overscroll-contain\", className)}\n {...props}\n />\n )\n}\n\n/** Sized to the whole list so the scrollbar is right; rows position inside it. */\nfunction VirtualListContent({ className, style, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n return (\n <div\n data-slot=\"virtual-list-content\"\n className={cn(\"relative w-full\", className)}\n style={{ height: ctx.totalSize, ...style }}\n {...props}\n />\n )\n}\n\n/** Renders the visible window. The child renders one row and must return a `VirtualListItem`. */\nfunction VirtualListItems({ children }: { children: (item: VirtualItem) => React.ReactNode }) {\n const ctx = useVirtualList()\n return (\n <>\n {ctx.items.map((item) => (\n <React.Fragment key={item.key}>{children(item)}</React.Fragment>\n ))}\n </>\n )\n}\n\nfunction VirtualListItem({ index, className, style, ...props }: React.ComponentProps<\"div\"> & { index: number }) {\n const { items, measure } = useVirtualList()\n const item = items.find((i) => i.index === index)\n const ref = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el) return\n const run = () => measure(index, el.getBoundingClientRect().height)\n run()\n const ro = new ResizeObserver(run)\n ro.observe(el)\n // React 19 ref cleanup\n return () => ro.disconnect()\n },\n [measure, index]\n )\n return (\n <div\n ref={ref}\n data-slot=\"virtual-list-item\"\n data-index={index}\n className={cn(\"absolute inset-x-0 top-0\", className)}\n style={{ transform: `translateY(${item?.start ?? 0}px)`, ...style }}\n {...props}\n />\n )\n}\n\nfunction VirtualListEmpty({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n if (ctx.count > 0) return null\n return (\n <div\n data-slot=\"virtual-list-empty\"\n className={cn(\"flex flex-1 items-center justify-center py-8 text-sm text-muted-foreground\", className)}\n {...props}\n >\n {children ?? \"Nothing to show\"}\n </div>\n )\n}\n\nexport {\n VirtualList,\n VirtualListContent,\n VirtualListEmpty,\n VirtualListItem,\n VirtualListItems,\n VirtualListViewport,\n useVirtualList,\n type VirtualItem,\n type VirtualListProps,\n}\n"
13
+ }
14
+ ],
15
+ "docs": "## Virtual list\n`src/components/ui/virtual-list.tsx` is a dependency-free windowed list. `VirtualList { count,\nestimateSize (number or per index), overscan, gap, paddingStart/End, getItemKey, onRangeChange }`\nowns scroll offset, viewport size, and measured row heights (prefix sums + binary search; rows\nmeasure themselves with a ResizeObserver and estimates are replaced as rows appear). Parts:\n`VirtualListViewport` (the scroll container; **the consumer gives it a height**, it must never be\n`flex-1` in an auto-height column or it grows to the whole content and renders every row) >\n`VirtualListContent` (sized to `totalSize`) > `VirtualListItems` (render-prop child gets\n`{ index, key, start, size, measured }` and returns a `VirtualListItem index=`, absolutely\npositioned via translateY, `data-index`), `VirtualListEmpty`. `useVirtualList()` exposes `items`,\n`range`, `totalSize`, `scrollToIndex(index, { align: auto|start|center|end })` (re-aims a few times\nwhile estimated rows get measured) and `scrollToOffset`. Keyboard is the consumer's: keep focus on\nthe viewport with `aria-activedescendant` and call `scrollToIndex` for the active row, as the\ncontacts listbox in `src/demo/virtual-list/` does (`/audit-log`, \"Audit log\": a 100k-line log with mixed\nheights and go-to-line, and a 10k-contact listbox with search)."
16
+ }
@@ -0,0 +1,111 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+ import { Accordion as AccordionPrimitive } from "@ark-ui/react"
4
+ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
5
+
6
+ function Accordion({
7
+ className,
8
+ lazyMount = true,
9
+ unmountOnExit = true,
10
+ ...props
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
12
+ return (
13
+ <AccordionPrimitive.Root
14
+ data-slot="accordion"
15
+ lazyMount={lazyMount}
16
+ unmountOnExit={unmountOnExit}
17
+ className={cn("flex w-full flex-col", className)}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function AccordionContext({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Context>) {
24
+ return <AccordionPrimitive.Context {...props} />
25
+ }
26
+
27
+ function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
28
+ return (
29
+ <AccordionPrimitive.Item data-slot="accordion-item" className={cn("not-last:border-b", className)} {...props} />
30
+ )
31
+ }
32
+
33
+ function AccordionItemContext({ ...props }: React.ComponentProps<typeof AccordionPrimitive.ItemContext>) {
34
+ return <AccordionPrimitive.ItemContext {...props} />
35
+ }
36
+
37
+ function AccordionTrigger({
38
+ className,
39
+ children,
40
+ ...props
41
+ }: React.ComponentProps<typeof AccordionPrimitive.ItemTrigger>) {
42
+ return (
43
+ <AccordionPrimitive.ItemTrigger
44
+ data-slot="accordion-trigger"
45
+ className={cn(
46
+ "group/accordion-trigger relative flex w-full flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
47
+ className
48
+ )}
49
+ {...props}
50
+ >
51
+ {children}
52
+ <ChevronDownIcon
53
+ data-slot="accordion-trigger-icon"
54
+ className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
55
+ />
56
+ <ChevronUpIcon
57
+ data-slot="accordion-trigger-icon"
58
+ className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
59
+ />
60
+ </AccordionPrimitive.ItemTrigger>
61
+ )
62
+ }
63
+
64
+ function AccordionItemIndicator({
65
+ className,
66
+ ...props
67
+ }: React.ComponentProps<typeof AccordionPrimitive.ItemIndicator>) {
68
+ return (
69
+ <AccordionPrimitive.ItemIndicator
70
+ data-slot="accordion-item-indicator"
71
+ className={cn(
72
+ "ml-auto size-4 shrink-0 text-muted-foreground transition-transform data-open:rotate-180",
73
+ className
74
+ )}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ function AccordionContent({
81
+ className,
82
+ children,
83
+ ...props
84
+ }: React.ComponentProps<typeof AccordionPrimitive.ItemContent>) {
85
+ return (
86
+ <AccordionPrimitive.ItemContent
87
+ data-slot="accordion-content"
88
+ className="overflow-hidden text-sm [--accordion-panel-height:var(--height)] data-open:animate-accordion-down data-closed:animate-accordion-up"
89
+ {...props}
90
+ >
91
+ <div
92
+ className={cn(
93
+ "pt-0 pb-2.5 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
94
+ className
95
+ )}
96
+ >
97
+ {children}
98
+ </div>
99
+ </AccordionPrimitive.ItemContent>
100
+ )
101
+ }
102
+
103
+ export {
104
+ Accordion,
105
+ AccordionContent,
106
+ AccordionContext,
107
+ AccordionItem,
108
+ AccordionItemContext,
109
+ AccordionItemIndicator,
110
+ AccordionTrigger,
111
+ }
@@ -0,0 +1,228 @@
1
+ import * as React from "react"
2
+ import { Button } from "@/components/ui/button"
3
+ import { formatRelativeTime } from "@/lib/time"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ /**
7
+ * ActivityFeed — a compositional, data-agnostic timeline of events. The
8
+ * consumer maps its events to items; the feed adds the connector line, the
9
+ * marker column, relative times and the WAI-ARIA feed pattern (articles are
10
+ * focusable; PageUp/PageDown move between them, Ctrl+Home/End jump).
11
+ *
12
+ * Anatomy:
13
+ * ActivityFeed
14
+ * ActivityFeedGroup > ActivityFeedGroupLabel + ActivityFeedItems
15
+ * ActivityFeedItem value=…
16
+ * ActivityFeedItemMarker dot / icon / avatar on the line
17
+ * ActivityFeedItemContent
18
+ * ActivityFeedItemHeader > ActivityFeedItemActor … ActivityFeedItemTime date=…
19
+ * ActivityFeedItemBody
20
+ * ActivityFeedItemActions
21
+ * ActivityFeedLoadMoreTrigger / ActivityFeedEmpty
22
+ */
23
+
24
+ function ActivityFeed({ className, busy, onKeyDown, ...props }: React.ComponentProps<"div"> & { busy?: boolean }) {
25
+ const ref = React.useRef<HTMLDivElement>(null)
26
+ return (
27
+ <div
28
+ ref={ref}
29
+ data-slot="activity-feed"
30
+ role="feed"
31
+ aria-busy={busy || undefined}
32
+ className={cn("flex flex-col gap-6", className)}
33
+ onKeyDown={(event) => {
34
+ onKeyDown?.(event)
35
+ if (event.defaultPrevented) return
36
+ const articles = Array.from(ref.current?.querySelectorAll<HTMLElement>("[data-slot=activity-feed-item]") ?? [])
37
+ const current = (event.target as HTMLElement).closest<HTMLElement>("[data-slot=activity-feed-item]")
38
+ const index = current ? articles.indexOf(current) : -1
39
+ const meta = event.metaKey || event.ctrlKey
40
+ let next: number | null = null
41
+ if (event.key === "PageDown") next = Math.min(articles.length - 1, index + 1)
42
+ else if (event.key === "PageUp") next = Math.max(0, index - 1)
43
+ else if (event.key === "Home" && meta) next = 0
44
+ else if (event.key === "End" && meta) next = articles.length - 1
45
+ if (next === null || !articles[next]) return
46
+ event.preventDefault()
47
+ articles[next].focus()
48
+ }}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ function ActivityFeedGroup({ className, ...props }: React.ComponentProps<"section">) {
55
+ return <section data-slot="activity-feed-group" className={cn("flex flex-col gap-3", className)} {...props} />
56
+ }
57
+
58
+ function ActivityFeedGroupLabel({ className, ...props }: React.ComponentProps<"h3">) {
59
+ return (
60
+ <h3
61
+ data-slot="activity-feed-group-label"
62
+ className={cn("text-xs font-medium tracking-wide text-muted-foreground uppercase", className)}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ /** The list; draws the vertical connector behind the markers. */
69
+ function ActivityFeedItems({ className, ...props }: React.ComponentProps<"ol">) {
70
+ return (
71
+ <ol
72
+ data-slot="activity-feed-items"
73
+ className={cn(
74
+ "relative flex flex-col gap-5 before:absolute before:inset-y-2 before:inset-s-4 before:w-px before:-translate-x-1/2 before:bg-border",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ function ActivityFeedItem({ value, className, ...props }: React.ComponentProps<"li"> & { value: string }) {
83
+ return (
84
+ <li
85
+ data-slot="activity-feed-item"
86
+ data-value={value}
87
+ role="article"
88
+ tabIndex={0}
89
+ className={cn(
90
+ "group/activity relative flex gap-3 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring/50",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ )
96
+ }
97
+
98
+ /** Sits on the line: pass an icon, an Avatar, or nothing for a dot. */
99
+ function ActivityFeedItemMarker({ className, children, ...props }: React.ComponentProps<"div">) {
100
+ return (
101
+ <div
102
+ data-slot="activity-feed-item-marker"
103
+ className={cn(
104
+ "relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full bg-background ring-4 ring-background",
105
+ "[&>svg]:size-4 [&>svg]:text-muted-foreground",
106
+ !children && "after:size-2 after:rounded-full after:bg-muted-foreground/60",
107
+ children && "border bg-muted",
108
+ className
109
+ )}
110
+ {...props}
111
+ >
112
+ {children}
113
+ </div>
114
+ )
115
+ }
116
+
117
+ function ActivityFeedItemContent({ className, ...props }: React.ComponentProps<"div">) {
118
+ return (
119
+ <div
120
+ data-slot="activity-feed-item-content"
121
+ className={cn("flex min-w-0 flex-1 flex-col gap-1.5 pt-1.5", className)}
122
+ {...props}
123
+ />
124
+ )
125
+ }
126
+
127
+ function ActivityFeedItemHeader({ className, ...props }: React.ComponentProps<"div">) {
128
+ return (
129
+ <div
130
+ data-slot="activity-feed-item-header"
131
+ className={cn("flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5 text-sm text-muted-foreground", className)}
132
+ {...props}
133
+ />
134
+ )
135
+ }
136
+
137
+ function ActivityFeedItemActor({ className, ...props }: React.ComponentProps<"span">) {
138
+ return (
139
+ <span data-slot="activity-feed-item-actor" className={cn("font-medium text-foreground", className)} {...props} />
140
+ )
141
+ }
142
+
143
+ /** `<time>` with a relative label by default; the full date is in the title. */
144
+ function ActivityFeedItemTime({
145
+ date,
146
+ now,
147
+ className,
148
+ children,
149
+ ...props
150
+ }: Omit<React.ComponentProps<"time">, "dateTime"> & { date: Date; now?: Date }) {
151
+ return (
152
+ <time
153
+ data-slot="activity-feed-item-time"
154
+ dateTime={date.toISOString()}
155
+ title={date.toLocaleString()}
156
+ className={cn("text-xs whitespace-nowrap text-muted-foreground/80", className)}
157
+ {...props}
158
+ >
159
+ {children ?? formatRelativeTime(date, { now })}
160
+ </time>
161
+ )
162
+ }
163
+
164
+ /** Optional detail block: a quote, a diff, a preview card. */
165
+ function ActivityFeedItemBody({ className, ...props }: React.ComponentProps<"div">) {
166
+ return (
167
+ <div
168
+ data-slot="activity-feed-item-body"
169
+ className={cn("rounded-lg border bg-muted/40 px-3 py-2 text-sm", className)}
170
+ {...props}
171
+ />
172
+ )
173
+ }
174
+
175
+ function ActivityFeedItemActions({ className, ...props }: React.ComponentProps<"div">) {
176
+ return (
177
+ <div
178
+ data-slot="activity-feed-item-actions"
179
+ className={cn("flex items-center gap-1 text-xs", className)}
180
+ {...props}
181
+ />
182
+ )
183
+ }
184
+
185
+ function ActivityFeedLoadMoreTrigger({ asChild, children, className, ...props }: React.ComponentProps<typeof Button>) {
186
+ return (
187
+ <Button
188
+ data-slot="activity-feed-load-more-trigger"
189
+ variant="outline"
190
+ size="sm"
191
+ asChild={asChild}
192
+ className={cn("self-center", className)}
193
+ {...props}
194
+ >
195
+ {asChild ? children : (children ?? "Load older activity")}
196
+ </Button>
197
+ )
198
+ }
199
+
200
+ function ActivityFeedEmpty({ className, ...props }: React.ComponentProps<"div">) {
201
+ return (
202
+ <div
203
+ data-slot="activity-feed-empty"
204
+ className={cn(
205
+ "flex h-32 flex-col items-center justify-center gap-1 rounded-lg border border-dashed text-sm text-muted-foreground",
206
+ className
207
+ )}
208
+ {...props}
209
+ />
210
+ )
211
+ }
212
+
213
+ export {
214
+ ActivityFeed,
215
+ ActivityFeedGroup,
216
+ ActivityFeedGroupLabel,
217
+ ActivityFeedItems,
218
+ ActivityFeedItem,
219
+ ActivityFeedItemMarker,
220
+ ActivityFeedItemContent,
221
+ ActivityFeedItemHeader,
222
+ ActivityFeedItemActor,
223
+ ActivityFeedItemTime,
224
+ ActivityFeedItemBody,
225
+ ActivityFeedItemActions,
226
+ ActivityFeedLoadMoreTrigger,
227
+ ActivityFeedEmpty,
228
+ }
@@ -0,0 +1,204 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+ import { Dialog as AlertDialogPrimitive, Portal as PortalPrimitive } from "@ark-ui/react"
4
+
5
+ import { Button } from "@/components/ui/button"
6
+
7
+ function getDefaultInitialFocusEl() {
8
+ return document.querySelector<HTMLElement>(
9
+ '[data-slot="alert-dialog-content"][data-state="open"] [data-slot="alert-dialog-cancel"]'
10
+ )
11
+ }
12
+
13
+ function AlertDialog({
14
+ role = "alertdialog",
15
+ closeOnInteractOutside = false,
16
+ initialFocusEl = getDefaultInitialFocusEl,
17
+ lazyMount = true,
18
+ unmountOnExit = true,
19
+ ...props
20
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
21
+ return (
22
+ <AlertDialogPrimitive.Root
23
+ role={role}
24
+ closeOnInteractOutside={closeOnInteractOutside}
25
+ initialFocusEl={initialFocusEl}
26
+ lazyMount={lazyMount}
27
+ unmountOnExit={unmountOnExit}
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ function AlertDialogTrigger({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
34
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
35
+ }
36
+
37
+ function AlertDialogPortal({ ...props }: React.ComponentProps<typeof PortalPrimitive>) {
38
+ return <PortalPrimitive {...props} />
39
+ }
40
+
41
+ function AlertDialogContext({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Context>) {
42
+ return <AlertDialogPrimitive.Context {...props} />
43
+ }
44
+
45
+ function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Backdrop>) {
46
+ return (
47
+ <AlertDialogPrimitive.Backdrop
48
+ data-slot="alert-dialog-overlay"
49
+ className={cn(
50
+ "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",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ function AlertDialogPositioner({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Positioner>) {
59
+ return (
60
+ <AlertDialogPrimitive.Positioner
61
+ data-slot="alert-dialog-positioner"
62
+ className={cn("fixed inset-0 z-50 flex items-center justify-center", className)}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ function AlertDialogContent({
69
+ className,
70
+ size = "default",
71
+ ...props
72
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
73
+ size?: "default" | "sm"
74
+ }) {
75
+ return (
76
+ <AlertDialogPortal>
77
+ <AlertDialogOverlay />
78
+ <AlertDialogPositioner>
79
+ <AlertDialogPrimitive.Content
80
+ data-slot="alert-dialog-content"
81
+ data-size={size}
82
+ className={cn(
83
+ "group/alert-dialog-content relative grid w-full gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm 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",
84
+ className
85
+ )}
86
+ {...props}
87
+ />
88
+ </AlertDialogPositioner>
89
+ </AlertDialogPortal>
90
+ )
91
+ }
92
+
93
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
94
+ return (
95
+ <div
96
+ data-slot="alert-dialog-header"
97
+ className={cn(
98
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
107
+ return (
108
+ <div
109
+ data-slot="alert-dialog-footer"
110
+ className={cn(
111
+ "-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
112
+ className
113
+ )}
114
+ {...props}
115
+ />
116
+ )
117
+ }
118
+
119
+ function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) {
120
+ return (
121
+ <div
122
+ data-slot="alert-dialog-media"
123
+ className={cn(
124
+ "mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
125
+ className
126
+ )}
127
+ {...props}
128
+ />
129
+ )
130
+ }
131
+
132
+ function AlertDialogTitle({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
133
+ return (
134
+ <AlertDialogPrimitive.Title
135
+ data-slot="alert-dialog-title"
136
+ className={cn(
137
+ "font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
138
+ className
139
+ )}
140
+ {...props}
141
+ />
142
+ )
143
+ }
144
+
145
+ function AlertDialogDescription({
146
+ className,
147
+ ...props
148
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
149
+ return (
150
+ <AlertDialogPrimitive.Description
151
+ data-slot="alert-dialog-description"
152
+ className={cn(
153
+ "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
154
+ className
155
+ )}
156
+ {...props}
157
+ />
158
+ )
159
+ }
160
+
161
+ function AlertDialogAction({
162
+ className,
163
+ variant = "default",
164
+ size = "default",
165
+ ...props
166
+ }: React.ComponentProps<typeof AlertDialogPrimitive.CloseTrigger> &
167
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
168
+ return (
169
+ <Button variant={variant} size={size} asChild>
170
+ <AlertDialogPrimitive.CloseTrigger data-slot="alert-dialog-action" className={cn(className)} {...props} />
171
+ </Button>
172
+ )
173
+ }
174
+
175
+ function AlertDialogCancel({
176
+ className,
177
+ variant = "outline",
178
+ size = "default",
179
+ ...props
180
+ }: React.ComponentProps<typeof AlertDialogPrimitive.CloseTrigger> &
181
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
182
+ return (
183
+ <Button variant={variant} size={size} asChild>
184
+ <AlertDialogPrimitive.CloseTrigger data-slot="alert-dialog-cancel" className={cn(className)} {...props} />
185
+ </Button>
186
+ )
187
+ }
188
+
189
+ export {
190
+ AlertDialog,
191
+ AlertDialogAction,
192
+ AlertDialogCancel,
193
+ AlertDialogContent,
194
+ AlertDialogContext,
195
+ AlertDialogDescription,
196
+ AlertDialogFooter,
197
+ AlertDialogHeader,
198
+ AlertDialogMedia,
199
+ AlertDialogOverlay,
200
+ AlertDialogPortal,
201
+ AlertDialogPositioner,
202
+ AlertDialogTitle,
203
+ AlertDialogTrigger,
204
+ }
@@ -0,0 +1,55 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const alertVariants = cva(
6
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-card text-card-foreground",
11
+ destructive:
12
+ "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ }
19
+ )
20
+
21
+ function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
22
+ return <div data-slot="alert" role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
23
+ }
24
+
25
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
26
+ return (
27
+ <div
28
+ data-slot="alert-title"
29
+ className={cn(
30
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
39
+ return (
40
+ <div
41
+ data-slot="alert-description"
42
+ className={cn(
43
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
44
+ className
45
+ )}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return <div data-slot="alert-action" className={cn("absolute top-2 right-2", className)} {...props} />
53
+ }
54
+
55
+ export { Alert, AlertTitle, AlertDescription, AlertAction }