@slexn/codecenter-ui 1.0.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 (472) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +148 -0
  3. package/components.json +20 -0
  4. package/dist/codecenter-ui.cjs +10 -0
  5. package/dist/codecenter-ui.js +17995 -0
  6. package/dist/components/ui/accordion/Accordion.vue.d.ts +28 -0
  7. package/dist/components/ui/accordion/AccordionContent.vue.d.ts +22 -0
  8. package/dist/components/ui/accordion/AccordionItem.vue.d.ts +24 -0
  9. package/dist/components/ui/accordion/AccordionTrigger.vue.d.ts +22 -0
  10. package/dist/components/ui/accordion/index.d.ts +4 -0
  11. package/dist/components/ui/alert/Alert.vue.d.ts +32 -0
  12. package/dist/components/ui/alert/AlertDescription.vue.d.ts +24 -0
  13. package/dist/components/ui/alert/AlertTitle.vue.d.ts +24 -0
  14. package/dist/components/ui/alert/index.d.ts +4 -0
  15. package/dist/components/ui/alert/variants.d.ts +5 -0
  16. package/dist/components/ui/button/Button.vue.d.ts +27 -0
  17. package/dist/components/ui/button/index.d.ts +2 -0
  18. package/dist/components/ui/button/variants.d.ts +6 -0
  19. package/dist/components/ui/button-group/ButtonGroup.vue.d.ts +25 -0
  20. package/dist/components/ui/button-group/index.d.ts +2 -0
  21. package/dist/components/ui/card/Card.vue.d.ts +21 -0
  22. package/dist/components/ui/card/CardContent.vue.d.ts +21 -0
  23. package/dist/components/ui/card/CardDescription.vue.d.ts +21 -0
  24. package/dist/components/ui/card/CardFooter.vue.d.ts +21 -0
  25. package/dist/components/ui/card/CardHeader.vue.d.ts +21 -0
  26. package/dist/components/ui/card/CardTitle.vue.d.ts +21 -0
  27. package/dist/components/ui/card/index.d.ts +6 -0
  28. package/dist/components/ui/chart/Chart.vue.d.ts +92 -0
  29. package/dist/components/ui/chart/index.d.ts +2 -0
  30. package/dist/components/ui/chat/Chat.vue.d.ts +190 -0
  31. package/dist/components/ui/chat/ChatAttachments.vue.d.ts +11 -0
  32. package/dist/components/ui/chat/ChatCodeBlock.vue.d.ts +16 -0
  33. package/dist/components/ui/chat/code-block.d.ts +27 -0
  34. package/dist/components/ui/chat/index.d.ts +6 -0
  35. package/dist/components/ui/chat/types.d.ts +15 -0
  36. package/dist/components/ui/checkbox/Checkbox.vue.d.ts +29 -0
  37. package/dist/components/ui/checkbox/index.d.ts +1 -0
  38. package/dist/components/ui/commit/Commit.vue.d.ts +62 -0
  39. package/dist/components/ui/commit/index.d.ts +2 -0
  40. package/dist/components/ui/contribution-graph/ContributionGraph.vue.d.ts +87 -0
  41. package/dist/components/ui/contribution-graph/index.d.ts +2 -0
  42. package/dist/components/ui/data-table/DataTable.vue.d.ts +109 -0
  43. package/dist/components/ui/data-table/index.d.ts +2 -0
  44. package/dist/components/ui/date-picker/DatePicker.vue.d.ts +37 -0
  45. package/dist/components/ui/date-picker/index.d.ts +2 -0
  46. package/dist/components/ui/dialog/Dialog.vue.d.ts +25 -0
  47. package/dist/components/ui/dialog/DialogClose.vue.d.ts +18 -0
  48. package/dist/components/ui/dialog/DialogContent.vue.d.ts +39 -0
  49. package/dist/components/ui/dialog/DialogDescription.vue.d.ts +22 -0
  50. package/dist/components/ui/dialog/DialogFooter.vue.d.ts +21 -0
  51. package/dist/components/ui/dialog/DialogHeader.vue.d.ts +21 -0
  52. package/dist/components/ui/dialog/DialogOverlay.vue.d.ts +22 -0
  53. package/dist/components/ui/dialog/DialogScrollContent.vue.d.ts +36 -0
  54. package/dist/components/ui/dialog/DialogTitle.vue.d.ts +22 -0
  55. package/dist/components/ui/dialog/DialogTrigger.vue.d.ts +18 -0
  56. package/dist/components/ui/dialog/index.d.ts +10 -0
  57. package/dist/components/ui/diff/DiffTool.vue.d.ts +21 -0
  58. package/dist/components/ui/diff/diff-parser.d.ts +30 -0
  59. package/dist/components/ui/diff/diff-tool.d.ts +36 -0
  60. package/dist/components/ui/diff/index.d.ts +2 -0
  61. package/dist/components/ui/dropdown-menu/DropdownMenu.vue.d.ts +24 -0
  62. package/dist/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue.d.ts +29 -0
  63. package/dist/components/ui/dropdown-menu/DropdownMenuContent.vue.d.ts +36 -0
  64. package/dist/components/ui/dropdown-menu/DropdownMenuGroup.vue.d.ts +18 -0
  65. package/dist/components/ui/dropdown-menu/DropdownMenuItem.vue.d.ts +26 -0
  66. package/dist/components/ui/dropdown-menu/DropdownMenuLabel.vue.d.ts +23 -0
  67. package/dist/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue.d.ts +22 -0
  68. package/dist/components/ui/dropdown-menu/DropdownMenuRadioItem.vue.d.ts +27 -0
  69. package/dist/components/ui/dropdown-menu/DropdownMenuSeparator.vue.d.ts +7 -0
  70. package/dist/components/ui/dropdown-menu/DropdownMenuShortcut.vue.d.ts +21 -0
  71. package/dist/components/ui/dropdown-menu/DropdownMenuSub.vue.d.ts +22 -0
  72. package/dist/components/ui/dropdown-menu/DropdownMenuSubContent.vue.d.ts +38 -0
  73. package/dist/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue.d.ts +23 -0
  74. package/dist/components/ui/dropdown-menu/DropdownMenuTrigger.vue.d.ts +18 -0
  75. package/dist/components/ui/dropdown-menu/index.d.ts +15 -0
  76. package/dist/components/ui/gauge/Gauge.vue.d.ts +62 -0
  77. package/dist/components/ui/gauge/index.d.ts +2 -0
  78. package/dist/components/ui/git-graph/GitGraph.vue.d.ts +59 -0
  79. package/dist/components/ui/git-graph/index.d.ts +2 -0
  80. package/dist/components/ui/incident-timeline/IncidentTimeline.vue.d.ts +50 -0
  81. package/dist/components/ui/incident-timeline/index.d.ts +2 -0
  82. package/dist/components/ui/input/Input.vue.d.ts +14 -0
  83. package/dist/components/ui/input/InputControl.vue.d.ts +14 -0
  84. package/dist/components/ui/input/InputFieldGroup.vue.d.ts +21 -0
  85. package/dist/components/ui/input/index.d.ts +4 -0
  86. package/dist/components/ui/input/types.d.ts +31 -0
  87. package/dist/components/ui/kpi-card/KpiCard.vue.d.ts +46 -0
  88. package/dist/components/ui/kpi-card/index.d.ts +2 -0
  89. package/dist/components/ui/kpi-line-card/KpiLineCard.vue.d.ts +66 -0
  90. package/dist/components/ui/kpi-line-card/index.d.ts +2 -0
  91. package/dist/components/ui/model-selector/ModelSelector.vue.d.ts +41 -0
  92. package/dist/components/ui/model-selector/index.d.ts +2 -0
  93. package/dist/components/ui/model-selector/types.d.ts +12 -0
  94. package/dist/components/ui/network-graph/NetworkGraph.vue.d.ts +75 -0
  95. package/dist/components/ui/network-graph/index.d.ts +2 -0
  96. package/dist/components/ui/pagination/Pagination.vue.d.ts +29 -0
  97. package/dist/components/ui/pagination/PaginationContent.vue.d.ts +29 -0
  98. package/dist/components/ui/pagination/PaginationEllipsis.vue.d.ts +22 -0
  99. package/dist/components/ui/pagination/PaginationFirst.vue.d.ts +26 -0
  100. package/dist/components/ui/pagination/PaginationItem.vue.d.ts +28 -0
  101. package/dist/components/ui/pagination/PaginationLast.vue.d.ts +26 -0
  102. package/dist/components/ui/pagination/PaginationNext.vue.d.ts +26 -0
  103. package/dist/components/ui/pagination/PaginationPrevious.vue.d.ts +26 -0
  104. package/dist/components/ui/pagination/index.d.ts +8 -0
  105. package/dist/components/ui/profile/Profile.vue.d.ts +30 -0
  106. package/dist/components/ui/profile/ProfileGroup.vue.d.ts +37 -0
  107. package/dist/components/ui/profile/index.d.ts +4 -0
  108. package/dist/components/ui/progress/Progress.vue.d.ts +36 -0
  109. package/dist/components/ui/progress/index.d.ts +2 -0
  110. package/dist/components/ui/prompt-input/PromptInput.vue.d.ts +150 -0
  111. package/dist/components/ui/prompt-input/index.d.ts +2 -0
  112. package/dist/components/ui/prompt-input/types.d.ts +61 -0
  113. package/dist/components/ui/radio-group/RadioGroup.vue.d.ts +30 -0
  114. package/dist/components/ui/radio-group/RadioGroupItem.vue.d.ts +12 -0
  115. package/dist/components/ui/radio-group/RadioGroupOption.vue.d.ts +28 -0
  116. package/dist/components/ui/radio-group/index.d.ts +3 -0
  117. package/dist/components/ui/reasoning/Reasoning.vue.d.ts +40 -0
  118. package/dist/components/ui/reasoning/index.d.ts +2 -0
  119. package/dist/components/ui/reasoning/types.d.ts +26 -0
  120. package/dist/components/ui/select/Select.vue.d.ts +28 -0
  121. package/dist/components/ui/select/SelectContent.vue.d.ts +46 -0
  122. package/dist/components/ui/select/SelectGroup.vue.d.ts +18 -0
  123. package/dist/components/ui/select/SelectItem.vue.d.ts +26 -0
  124. package/dist/components/ui/select/SelectItemText.vue.d.ts +18 -0
  125. package/dist/components/ui/select/SelectLabel.vue.d.ts +22 -0
  126. package/dist/components/ui/select/SelectScrollDownButton.vue.d.ts +22 -0
  127. package/dist/components/ui/select/SelectScrollUpButton.vue.d.ts +22 -0
  128. package/dist/components/ui/select/SelectSeparator.vue.d.ts +7 -0
  129. package/dist/components/ui/select/SelectTrigger.vue.d.ts +25 -0
  130. package/dist/components/ui/select/SelectValue.vue.d.ts +18 -0
  131. package/dist/components/ui/select/index.d.ts +11 -0
  132. package/dist/components/ui/select/search.d.ts +18 -0
  133. package/dist/components/ui/separator/Separator.vue.d.ts +6 -0
  134. package/dist/components/ui/separator/index.d.ts +2 -0
  135. package/dist/components/ui/separator/types.d.ts +7 -0
  136. package/dist/components/ui/shimmer/Shimmer.vue.d.ts +37 -0
  137. package/dist/components/ui/shimmer/index.d.ts +2 -0
  138. package/dist/components/ui/sidebar/Sidebar.vue.d.ts +24 -0
  139. package/dist/components/ui/sidebar/SidebarContent.vue.d.ts +21 -0
  140. package/dist/components/ui/sidebar/SidebarFooter.vue.d.ts +21 -0
  141. package/dist/components/ui/sidebar/SidebarGroup.vue.d.ts +21 -0
  142. package/dist/components/ui/sidebar/SidebarGroupAction.vue.d.ts +24 -0
  143. package/dist/components/ui/sidebar/SidebarGroupContent.vue.d.ts +21 -0
  144. package/dist/components/ui/sidebar/SidebarGroupLabel.vue.d.ts +24 -0
  145. package/dist/components/ui/sidebar/SidebarHeader.vue.d.ts +21 -0
  146. package/dist/components/ui/sidebar/SidebarInput.vue.d.ts +6 -0
  147. package/dist/components/ui/sidebar/SidebarInset.vue.d.ts +21 -0
  148. package/dist/components/ui/sidebar/SidebarMenu.vue.d.ts +21 -0
  149. package/dist/components/ui/sidebar/SidebarMenuAction.vue.d.ts +25 -0
  150. package/dist/components/ui/sidebar/SidebarMenuBadge.vue.d.ts +21 -0
  151. package/dist/components/ui/sidebar/SidebarMenuButton.vue.d.ts +25 -0
  152. package/dist/components/ui/sidebar/SidebarMenuButtonChild.vue.d.ts +30 -0
  153. package/dist/components/ui/sidebar/SidebarMenuItem.vue.d.ts +21 -0
  154. package/dist/components/ui/sidebar/SidebarMenuSub.vue.d.ts +21 -0
  155. package/dist/components/ui/sidebar/SidebarMenuSubButton.vue.d.ts +27 -0
  156. package/dist/components/ui/sidebar/SidebarMenuSubItem.vue.d.ts +21 -0
  157. package/dist/components/ui/sidebar/SidebarProvider.vue.d.ts +36 -0
  158. package/dist/components/ui/sidebar/SidebarRail.vue.d.ts +21 -0
  159. package/dist/components/ui/sidebar/SidebarSeparator.vue.d.ts +6 -0
  160. package/dist/components/ui/sidebar/SidebarTrigger.vue.d.ts +6 -0
  161. package/dist/components/ui/sidebar/context.d.ts +19 -0
  162. package/dist/components/ui/sidebar/index.d.ts +26 -0
  163. package/dist/components/ui/sidebar/types.d.ts +11 -0
  164. package/dist/components/ui/sidebar/variants.d.ts +6 -0
  165. package/dist/components/ui/skeleton/Skeleton.vue.d.ts +18 -0
  166. package/dist/components/ui/skeleton/index.d.ts +2 -0
  167. package/dist/components/ui/sonner/Sonner.vue.d.ts +5 -0
  168. package/dist/components/ui/sonner/index.d.ts +2 -0
  169. package/dist/components/ui/spinner/Spinner.vue.d.ts +11 -0
  170. package/dist/components/ui/spinner/index.d.ts +1 -0
  171. package/dist/components/ui/stepper/Stepper.vue.d.ts +38 -0
  172. package/dist/components/ui/stepper/StepperDescription.vue.d.ts +22 -0
  173. package/dist/components/ui/stepper/StepperIndicator.vue.d.ts +30 -0
  174. package/dist/components/ui/stepper/StepperItem.vue.d.ts +24 -0
  175. package/dist/components/ui/stepper/StepperSeparator.vue.d.ts +7 -0
  176. package/dist/components/ui/stepper/StepperTitle.vue.d.ts +22 -0
  177. package/dist/components/ui/stepper/StepperTrigger.vue.d.ts +22 -0
  178. package/dist/components/ui/stepper/index.d.ts +7 -0
  179. package/dist/components/ui/switch/Switch.vue.d.ts +12 -0
  180. package/dist/components/ui/switch/index.d.ts +1 -0
  181. package/dist/components/ui/table/Table.vue.d.ts +22 -0
  182. package/dist/components/ui/table/TableBody.vue.d.ts +21 -0
  183. package/dist/components/ui/table/TableCaption.vue.d.ts +21 -0
  184. package/dist/components/ui/table/TableCell.vue.d.ts +22 -0
  185. package/dist/components/ui/table/TableEmpty.vue.d.ts +24 -0
  186. package/dist/components/ui/table/TableFooter.vue.d.ts +21 -0
  187. package/dist/components/ui/table/TableHead.vue.d.ts +21 -0
  188. package/dist/components/ui/table/TableHeader.vue.d.ts +21 -0
  189. package/dist/components/ui/table/TableRow.vue.d.ts +21 -0
  190. package/dist/components/ui/table/index.d.ts +9 -0
  191. package/dist/components/ui/tabs/Tabs.vue.d.ts +28 -0
  192. package/dist/components/ui/tabs/TabsContent.vue.d.ts +22 -0
  193. package/dist/components/ui/tabs/TabsList.vue.d.ts +22 -0
  194. package/dist/components/ui/tabs/TabsTrigger.vue.d.ts +22 -0
  195. package/dist/components/ui/tabs/index.d.ts +4 -0
  196. package/dist/components/ui/tag/Tag.vue.d.ts +35 -0
  197. package/dist/components/ui/tag/index.d.ts +2 -0
  198. package/dist/components/ui/tag/variants.d.ts +6 -0
  199. package/dist/components/ui/textarea/Textarea.vue.d.ts +14 -0
  200. package/dist/components/ui/textarea/TextareaControl.vue.d.ts +14 -0
  201. package/dist/components/ui/textarea/TextareaFieldGroup.vue.d.ts +21 -0
  202. package/dist/components/ui/textarea/index.d.ts +4 -0
  203. package/dist/components/ui/textarea/types.d.ts +32 -0
  204. package/dist/components/ui/tool/Tool.vue.d.ts +61 -0
  205. package/dist/components/ui/tool/index.d.ts +2 -0
  206. package/dist/components/ui/tooltip/Tooltip.vue.d.ts +24 -0
  207. package/dist/components/ui/tooltip/TooltipContent.vue.d.ts +32 -0
  208. package/dist/components/ui/tooltip/TooltipProvider.vue.d.ts +20 -0
  209. package/dist/components/ui/tooltip/TooltipTrigger.vue.d.ts +18 -0
  210. package/dist/components/ui/tooltip/index.d.ts +4 -0
  211. package/dist/docs/component-docs.d.ts +18 -0
  212. package/dist/docs/markdown.d.ts +27 -0
  213. package/dist/index.d.ts +45 -0
  214. package/dist/lib/code-highlight.d.ts +11 -0
  215. package/dist/lib/utils.d.ts +2 -0
  216. package/dist/styles.css +3 -0
  217. package/package.json +76 -0
  218. package/public/r/accordion.json +52 -0
  219. package/public/r/alert.json +51 -0
  220. package/public/r/button-group.json +31 -0
  221. package/public/r/button.json +39 -0
  222. package/public/r/card.json +61 -0
  223. package/public/r/chart.json +31 -0
  224. package/public/r/chat.json +186 -0
  225. package/public/r/checkbox.json +34 -0
  226. package/public/r/commit.json +32 -0
  227. package/public/r/contribution-graph.json +63 -0
  228. package/public/r/data-table.json +197 -0
  229. package/public/r/date-picker.json +33 -0
  230. package/public/r/dialog.json +88 -0
  231. package/public/r/diff.json +71 -0
  232. package/public/r/dropdown-menu.json +112 -0
  233. package/public/r/gauge.json +31 -0
  234. package/public/r/git-graph.json +32 -0
  235. package/public/r/incident-timeline.json +64 -0
  236. package/public/r/input.json +49 -0
  237. package/public/r/kpi-card.json +32 -0
  238. package/public/r/kpi-line-card.json +32 -0
  239. package/public/r/model-selector.json +148 -0
  240. package/public/r/network-graph.json +33 -0
  241. package/public/r/pagination.json +95 -0
  242. package/public/r/profile.json +37 -0
  243. package/public/r/progress.json +31 -0
  244. package/public/r/prompt-input.json +293 -0
  245. package/public/r/radio-group.json +45 -0
  246. package/public/r/reasoning.json +38 -0
  247. package/public/r/registry.json +2512 -0
  248. package/public/r/select.json +100 -0
  249. package/public/r/separator.json +37 -0
  250. package/public/r/shimmer.json +31 -0
  251. package/public/r/sidebar.json +221 -0
  252. package/public/r/skeleton.json +31 -0
  253. package/public/r/sonner.json +33 -0
  254. package/public/r/spinner.json +31 -0
  255. package/public/r/stepper.json +70 -0
  256. package/public/r/switch.json +33 -0
  257. package/public/r/table.json +79 -0
  258. package/public/r/tabs.json +51 -0
  259. package/public/r/tag.json +39 -0
  260. package/public/r/textarea.json +49 -0
  261. package/public/r/tool.json +32 -0
  262. package/public/r/tooltip.json +51 -0
  263. package/registry.json +2512 -0
  264. package/src/components/docs/MarkdownContent.vue +106 -0
  265. package/src/components/ui/accordion/Accordion.vue +24 -0
  266. package/src/components/ui/accordion/AccordionContent.vue +62 -0
  267. package/src/components/ui/accordion/AccordionItem.vue +23 -0
  268. package/src/components/ui/accordion/AccordionTrigger.vue +38 -0
  269. package/src/components/ui/accordion/index.ts +4 -0
  270. package/src/components/ui/alert/Alert.vue +40 -0
  271. package/src/components/ui/alert/AlertDescription.vue +24 -0
  272. package/src/components/ui/alert/AlertTitle.vue +24 -0
  273. package/src/components/ui/alert/index.ts +4 -0
  274. package/src/components/ui/alert/variants.ts +19 -0
  275. package/src/components/ui/button/Button.vue +27 -0
  276. package/src/components/ui/button/index.ts +2 -0
  277. package/src/components/ui/button/variants.ts +32 -0
  278. package/src/components/ui/button-group/ButtonGroup.vue +31 -0
  279. package/src/components/ui/button-group/index.ts +2 -0
  280. package/src/components/ui/card/Card.vue +17 -0
  281. package/src/components/ui/card/CardContent.vue +14 -0
  282. package/src/components/ui/card/CardDescription.vue +14 -0
  283. package/src/components/ui/card/CardFooter.vue +14 -0
  284. package/src/components/ui/card/CardHeader.vue +17 -0
  285. package/src/components/ui/card/CardTitle.vue +14 -0
  286. package/src/components/ui/card/index.ts +6 -0
  287. package/src/components/ui/chart/Chart.vue +1042 -0
  288. package/src/components/ui/chart/index.ts +13 -0
  289. package/src/components/ui/chat/Chat.vue +1297 -0
  290. package/src/components/ui/chat/ChatAttachments.vue +278 -0
  291. package/src/components/ui/chat/ChatCodeBlock.vue +283 -0
  292. package/src/components/ui/chat/code-block.ts +30 -0
  293. package/src/components/ui/chat/index.ts +24 -0
  294. package/src/components/ui/chat/types.ts +23 -0
  295. package/src/components/ui/checkbox/Checkbox.vue +38 -0
  296. package/src/components/ui/checkbox/index.ts +1 -0
  297. package/src/components/ui/commit/Commit.vue +423 -0
  298. package/src/components/ui/commit/index.ts +9 -0
  299. package/src/components/ui/contribution-graph/ContributionGraph.vue +719 -0
  300. package/src/components/ui/contribution-graph/index.ts +9 -0
  301. package/src/components/ui/data-table/DataTable.vue +534 -0
  302. package/src/components/ui/data-table/index.ts +9 -0
  303. package/src/components/ui/date-picker/DatePicker.vue +649 -0
  304. package/src/components/ui/date-picker/index.ts +7 -0
  305. package/src/components/ui/dialog/Dialog.vue +19 -0
  306. package/src/components/ui/dialog/DialogClose.vue +17 -0
  307. package/src/components/ui/dialog/DialogContent.vue +60 -0
  308. package/src/components/ui/dialog/DialogDescription.vue +23 -0
  309. package/src/components/ui/dialog/DialogFooter.vue +17 -0
  310. package/src/components/ui/dialog/DialogHeader.vue +17 -0
  311. package/src/components/ui/dialog/DialogOverlay.vue +23 -0
  312. package/src/components/ui/dialog/DialogScrollContent.vue +69 -0
  313. package/src/components/ui/dialog/DialogTitle.vue +23 -0
  314. package/src/components/ui/dialog/DialogTrigger.vue +17 -0
  315. package/src/components/ui/dialog/index.ts +10 -0
  316. package/src/components/ui/diff/DiffTool.vue +513 -0
  317. package/src/components/ui/diff/diff-parser.ts +423 -0
  318. package/src/components/ui/diff/diff-tool.ts +39 -0
  319. package/src/components/ui/diff/index.ts +5 -0
  320. package/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  321. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -0
  322. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -0
  323. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -0
  324. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -0
  325. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  326. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  327. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  328. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  329. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  330. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
  331. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
  332. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  333. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  334. package/src/components/ui/dropdown-menu/index.ts +16 -0
  335. package/src/components/ui/gauge/Gauge.vue +725 -0
  336. package/src/components/ui/gauge/index.ts +9 -0
  337. package/src/components/ui/git-graph/GitGraph.vue +715 -0
  338. package/src/components/ui/git-graph/index.ts +9 -0
  339. package/src/components/ui/incident-timeline/IncidentTimeline.vue +360 -0
  340. package/src/components/ui/incident-timeline/index.ts +7 -0
  341. package/src/components/ui/input/Input.vue +159 -0
  342. package/src/components/ui/input/InputControl.vue +135 -0
  343. package/src/components/ui/input/InputFieldGroup.vue +14 -0
  344. package/src/components/ui/input/index.ts +9 -0
  345. package/src/components/ui/input/types.ts +34 -0
  346. package/src/components/ui/kpi-card/KpiCard.vue +268 -0
  347. package/src/components/ui/kpi-card/index.ts +9 -0
  348. package/src/components/ui/kpi-line-card/KpiLineCard.vue +622 -0
  349. package/src/components/ui/kpi-line-card/index.ts +11 -0
  350. package/src/components/ui/model-selector/ModelSelector.vue +328 -0
  351. package/src/components/ui/model-selector/index.ts +6 -0
  352. package/src/components/ui/model-selector/types.ts +15 -0
  353. package/src/components/ui/network-graph/NetworkGraph.vue +902 -0
  354. package/src/components/ui/network-graph/index.ts +7 -0
  355. package/src/components/ui/pagination/Pagination.vue +26 -0
  356. package/src/components/ui/pagination/PaginationContent.vue +24 -0
  357. package/src/components/ui/pagination/PaginationEllipsis.vue +27 -0
  358. package/src/components/ui/pagination/PaginationFirst.vue +33 -0
  359. package/src/components/ui/pagination/PaginationItem.vue +39 -0
  360. package/src/components/ui/pagination/PaginationLast.vue +33 -0
  361. package/src/components/ui/pagination/PaginationNext.vue +33 -0
  362. package/src/components/ui/pagination/PaginationPrevious.vue +33 -0
  363. package/src/components/ui/pagination/index.ts +8 -0
  364. package/src/components/ui/profile/Profile.vue +226 -0
  365. package/src/components/ui/profile/ProfileGroup.vue +96 -0
  366. package/src/components/ui/profile/index.ts +8 -0
  367. package/src/components/ui/progress/Progress.vue +271 -0
  368. package/src/components/ui/progress/index.ts +9 -0
  369. package/src/components/ui/prompt-input/PromptInput.vue +1094 -0
  370. package/src/components/ui/prompt-input/index.ts +14 -0
  371. package/src/components/ui/prompt-input/types.ts +78 -0
  372. package/src/components/ui/radio-group/RadioGroup.vue +36 -0
  373. package/src/components/ui/radio-group/RadioGroupItem.vue +45 -0
  374. package/src/components/ui/radio-group/RadioGroupOption.vue +80 -0
  375. package/src/components/ui/radio-group/index.ts +3 -0
  376. package/src/components/ui/reasoning/Reasoning.vue +278 -0
  377. package/src/components/ui/reasoning/index.ts +8 -0
  378. package/src/components/ui/reasoning/types.ts +29 -0
  379. package/src/components/ui/select/Select.vue +19 -0
  380. package/src/components/ui/select/SelectContent.vue +166 -0
  381. package/src/components/ui/select/SelectGroup.vue +23 -0
  382. package/src/components/ui/select/SelectItem.vue +97 -0
  383. package/src/components/ui/select/SelectItemText.vue +15 -0
  384. package/src/components/ui/select/SelectLabel.vue +17 -0
  385. package/src/components/ui/select/SelectScrollDownButton.vue +26 -0
  386. package/src/components/ui/select/SelectScrollUpButton.vue +26 -0
  387. package/src/components/ui/select/SelectSeparator.vue +19 -0
  388. package/src/components/ui/select/SelectTrigger.vue +33 -0
  389. package/src/components/ui/select/SelectValue.vue +15 -0
  390. package/src/components/ui/select/index.ts +11 -0
  391. package/src/components/ui/select/search.ts +26 -0
  392. package/src/components/ui/separator/Separator.vue +30 -0
  393. package/src/components/ui/separator/index.ts +5 -0
  394. package/src/components/ui/separator/types.ts +9 -0
  395. package/src/components/ui/shimmer/Shimmer.vue +110 -0
  396. package/src/components/ui/shimmer/index.ts +5 -0
  397. package/src/components/ui/sidebar/Sidebar.vue +142 -0
  398. package/src/components/ui/sidebar/SidebarContent.vue +18 -0
  399. package/src/components/ui/sidebar/SidebarFooter.vue +18 -0
  400. package/src/components/ui/sidebar/SidebarGroup.vue +18 -0
  401. package/src/components/ui/sidebar/SidebarGroupAction.vue +31 -0
  402. package/src/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  403. package/src/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
  404. package/src/components/ui/sidebar/SidebarHeader.vue +18 -0
  405. package/src/components/ui/sidebar/SidebarInput.vue +26 -0
  406. package/src/components/ui/sidebar/SidebarInset.vue +23 -0
  407. package/src/components/ui/sidebar/SidebarMenu.vue +18 -0
  408. package/src/components/ui/sidebar/SidebarMenuAction.vue +34 -0
  409. package/src/components/ui/sidebar/SidebarMenuBadge.vue +25 -0
  410. package/src/components/ui/sidebar/SidebarMenuButton.vue +37 -0
  411. package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +38 -0
  412. package/src/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  413. package/src/components/ui/sidebar/SidebarMenuSub.vue +18 -0
  414. package/src/components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
  415. package/src/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  416. package/src/components/ui/sidebar/SidebarProvider.vue +119 -0
  417. package/src/components/ui/sidebar/SidebarRail.vue +35 -0
  418. package/src/components/ui/sidebar/SidebarSeparator.vue +18 -0
  419. package/src/components/ui/sidebar/SidebarTrigger.vue +28 -0
  420. package/src/components/ui/sidebar/context.ts +39 -0
  421. package/src/components/ui/sidebar/index.ts +43 -0
  422. package/src/components/ui/sidebar/types.ts +13 -0
  423. package/src/components/ui/sidebar/variants.ts +25 -0
  424. package/src/components/ui/skeleton/Skeleton.vue +53 -0
  425. package/src/components/ui/skeleton/index.ts +5 -0
  426. package/src/components/ui/sonner/Sonner.vue +69 -0
  427. package/src/components/ui/sonner/index.ts +12 -0
  428. package/src/components/ui/spinner/Spinner.vue +33 -0
  429. package/src/components/ui/spinner/index.ts +1 -0
  430. package/src/components/ui/stepper/Stepper.vue +29 -0
  431. package/src/components/ui/stepper/StepperDescription.vue +30 -0
  432. package/src/components/ui/stepper/StepperIndicator.vue +50 -0
  433. package/src/components/ui/stepper/StepperItem.vue +28 -0
  434. package/src/components/ui/stepper/StepperSeparator.vue +25 -0
  435. package/src/components/ui/stepper/StepperTitle.vue +27 -0
  436. package/src/components/ui/stepper/StepperTrigger.vue +27 -0
  437. package/src/components/ui/stepper/index.ts +7 -0
  438. package/src/components/ui/switch/Switch.vue +41 -0
  439. package/src/components/ui/switch/index.ts +1 -0
  440. package/src/components/ui/table/Table.vue +23 -0
  441. package/src/components/ui/table/TableBody.vue +17 -0
  442. package/src/components/ui/table/TableCaption.vue +17 -0
  443. package/src/components/ui/table/TableCell.vue +24 -0
  444. package/src/components/ui/table/TableEmpty.vue +31 -0
  445. package/src/components/ui/table/TableFooter.vue +17 -0
  446. package/src/components/ui/table/TableHead.vue +22 -0
  447. package/src/components/ui/table/TableHeader.vue +17 -0
  448. package/src/components/ui/table/TableRow.vue +22 -0
  449. package/src/components/ui/table/index.ts +9 -0
  450. package/src/components/ui/tabs/Tabs.vue +24 -0
  451. package/src/components/ui/tabs/TabsContent.vue +22 -0
  452. package/src/components/ui/tabs/TabsList.vue +27 -0
  453. package/src/components/ui/tabs/TabsTrigger.vue +27 -0
  454. package/src/components/ui/tabs/index.ts +4 -0
  455. package/src/components/ui/tag/Tag.vue +55 -0
  456. package/src/components/ui/tag/index.ts +2 -0
  457. package/src/components/ui/tag/variants.ts +29 -0
  458. package/src/components/ui/textarea/Textarea.vue +159 -0
  459. package/src/components/ui/textarea/TextareaControl.vue +120 -0
  460. package/src/components/ui/textarea/TextareaFieldGroup.vue +14 -0
  461. package/src/components/ui/textarea/index.ts +10 -0
  462. package/src/components/ui/textarea/types.ts +35 -0
  463. package/src/components/ui/tool/Tool.vue +304 -0
  464. package/src/components/ui/tool/index.ts +7 -0
  465. package/src/components/ui/tooltip/Tooltip.vue +19 -0
  466. package/src/components/ui/tooltip/TooltipContent.vue +44 -0
  467. package/src/components/ui/tooltip/TooltipProvider.vue +14 -0
  468. package/src/components/ui/tooltip/TooltipTrigger.vue +15 -0
  469. package/src/components/ui/tooltip/index.ts +4 -0
  470. package/src/lib/code-highlight.ts +220 -0
  471. package/src/lib/utils.ts +6 -0
  472. package/src/styles.css +684 -0
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://shadcn-vue.com/schema/registry-item.json",
3
+ "name": "commit",
4
+ "title": "Commit",
5
+ "description": "A collapsible commit summary card with changed files, status letters, line stats, and copy action.",
6
+ "dependencies": [
7
+ "@lucide/vue",
8
+ "clsx",
9
+ "tailwind-merge"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "src/components/ui/commit/Commit.vue",
14
+ "content": "<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref } from \"vue\"\nimport type { HTMLAttributes } from \"vue\"\nimport {\n CheckIcon,\n ChevronDownIcon,\n CopyIcon,\n FileIcon,\n GitCommitHorizontalIcon,\n} from \"@lucide/vue\"\nimport { cn } from \"@/lib/utils\"\n\nexport type CommitFileStatus = \"added\" | \"modified\" | \"deleted\" | \"renamed\"\nexport type CommitSize = \"sm\" | \"default\" | \"lg\"\n\nexport interface CommitFile {\n additions?: number\n deletions?: number\n path: string\n status?: CommitFileStatus\n}\n\nexport interface CommitCopyPayload {\n value: string\n}\n\nexport interface CommitFileSelectPayload {\n file: CommitFile\n index: number\n}\n\nexport interface CommitTogglePayload {\n open: boolean\n}\n\ninterface CommitProps {\n authorInitials?: string\n authorName?: string\n avatarLabel?: string\n class?: HTMLAttributes[\"class\"]\n collapsible?: boolean\n copiedLabel?: string\n copyLabel?: string\n copyValue?: string\n date?: string\n defaultOpen?: boolean\n emptyLabel?: string\n files?: CommitFile[]\n hash?: string\n message?: string\n open?: boolean\n showCopy?: boolean\n showToggle?: boolean\n size?: CommitSize\n}\n\nconst props = withDefaults(defineProps<CommitProps>(), {\n authorInitials: \"CC\",\n collapsible: true,\n copiedLabel: \"Copied\",\n copyLabel: \"Copy commit hash\",\n defaultOpen: true,\n emptyLabel: \"No changed files.\",\n message: \"Commit message\",\n showCopy: true,\n showToggle: true,\n size: \"default\",\n})\n\nconst emit = defineEmits<{\n \"copy\": [payload: CommitCopyPayload]\n \"file-select\": [payload: CommitFileSelectPayload]\n \"toggle\": [payload: CommitTogglePayload]\n \"update:open\": [value: boolean]\n}>()\n\nconst copied = ref(false)\nconst localOpen = ref(props.defaultOpen)\nlet copiedTimeout: ReturnType<typeof setTimeout> | undefined\n\nconst commitFiles = computed(() => props.files ?? [])\nconst copyText = computed(() => props.copyValue ?? props.hash ?? props.message)\nconst copyButtonLabel = computed(() => copied.value ? props.copiedLabel : props.copyLabel)\nconst isOpen = computed(() => {\n if (!props.collapsible) return true\n\n return props.open ?? localOpen.value\n})\nconst sizeClasses = computed(() => {\n if (props.size === \"sm\") {\n return {\n avatar: \"size-7\",\n avatarIcon: \"size-3.5\",\n copyButton: \"size-7\",\n copyIcon: \"size-3.5\",\n empty: \"px-3 py-2.5 text-xs\",\n fileIcon: \"size-3.5\",\n files: \"py-1.5\",\n header: \"gap-2.5 px-3 py-2.5\",\n headerBorder: isOpen.value ? \"border-b\" : \"\",\n innerGap: \"gap-2.5\",\n meta: \"mt-0.5 gap-1.5 text-xs\",\n path: \"text-xs\",\n row: \"grid-cols-[0.75rem_0.875rem_minmax(0,1fr)_auto] gap-2 px-3 py-1\",\n stats: \"gap-1.5 text-xs\",\n status: \"text-xs\",\n title: \"text-xs\",\n toggleButton: \"size-7\",\n toggleIcon: \"size-3.5\",\n }\n }\n\n if (props.size === \"lg\") {\n return {\n avatar: \"size-10\",\n avatarIcon: \"size-5\",\n copyButton: \"size-9\",\n copyIcon: \"size-4\",\n empty: \"px-5 py-4 text-xs\",\n fileIcon: \"size-4\",\n files: \"py-3\",\n header: \"gap-3 px-5 py-4\",\n headerBorder: isOpen.value ? \"border-b\" : \"\",\n innerGap: \"gap-3\",\n meta: \"mt-1 gap-2 text-xs\",\n path: \"text-xs\",\n row: \"grid-cols-[0.875rem_1rem_minmax(0,1fr)_auto] gap-2.5 px-5 py-2\",\n stats: \"gap-2 text-xs\",\n status: \"text-xs\",\n title: \"text-sm\",\n toggleButton: \"size-9\",\n toggleIcon: \"size-4\",\n }\n }\n\n return {\n avatar: \"size-8\",\n avatarIcon: \"size-4\",\n copyButton: \"size-8\",\n copyIcon: \"size-4\",\n empty: \"px-4 py-3 text-xs\",\n fileIcon: \"size-3.5\",\n files: \"py-2\",\n header: \"gap-2.5 px-4 py-3\",\n headerBorder: isOpen.value ? \"border-b\" : \"\",\n innerGap: \"gap-2.5\",\n meta: \"mt-0.5 gap-1.5 text-xs\",\n path: \"text-xs\",\n row: \"grid-cols-[0.75rem_0.875rem_minmax(0,1fr)_auto] gap-2 px-4 py-1.5\",\n stats: \"gap-1.5 text-xs\",\n status: \"text-xs\",\n title: \"text-[13px]\",\n toggleButton: \"size-8\",\n toggleIcon: \"size-4\",\n }\n})\n\nonBeforeUnmount(() => {\n if (copiedTimeout) clearTimeout(copiedTimeout)\n})\n\nfunction statusLabel(status: CommitFileStatus = \"modified\") {\n if (status === \"added\") return \"A\"\n if (status === \"deleted\") return \"D\"\n if (status === \"renamed\") return \"R\"\n\n return \"M\"\n}\n\nfunction statusClass(status: CommitFileStatus = \"modified\") {\n if (status === \"added\") return \"text-emerald-600\"\n if (status === \"deleted\") return \"text-destructive\"\n if (status === \"renamed\") return \"text-sky-600\"\n\n return \"text-amber-600\"\n}\n\nfunction formatAddition(value?: number) {\n if (!value) return \"\"\n\n return `+${value.toLocaleString()}`\n}\n\nfunction formatDeletion(value?: number) {\n if (!value) return \"\"\n\n return `-${value.toLocaleString()}`\n}\n\nasync function handleCopy() {\n const value = copyText.value\n\n emit(\"copy\", { value })\n\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard) {\n await navigator.clipboard.writeText(value)\n }\n } catch {\n // Clipboard writes can be blocked by the runtime; keep the UI response and event.\n }\n\n copied.value = true\n\n if (copiedTimeout) clearTimeout(copiedTimeout)\n copiedTimeout = setTimeout(() => {\n copied.value = false\n }, 1600)\n}\n\nfunction setOpen(nextOpen: boolean) {\n if (!props.collapsible) return\n\n const previousOpen = isOpen.value\n\n if (props.open === undefined) {\n localOpen.value = nextOpen\n }\n\n emit(\"update:open\", nextOpen)\n\n if (previousOpen !== nextOpen) {\n emit(\"toggle\", { open: nextOpen })\n }\n}\n\nfunction toggleOpen() {\n setOpen(!isOpen.value)\n}\n\nfunction handleFileSelect(file: CommitFile, index: number) {\n emit(\"file-select\", {\n file,\n index,\n })\n}\n</script>\n\n<template>\n <article\n data-slot=\"commit\"\n :data-open=\"isOpen\"\n :data-size=\"props.size\"\n :class=\"cn('overflow-hidden rounded-xl border bg-card text-card-foreground shadow-xs', props.class)\"\n >\n <header\n data-slot=\"commit-header\"\n :class=\"cn('flex items-center transition-colors', sizeClasses.header, sizeClasses.headerBorder)\"\n >\n <component\n :is=\"props.collapsible ? 'button' : 'div'\"\n data-slot=\"commit-toggle\"\n :type=\"props.collapsible ? 'button' : undefined\"\n :aria-expanded=\"props.collapsible ? isOpen : undefined\"\n :class=\"cn('flex min-w-0 flex-1 items-center text-left outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50', sizeClasses.innerGap)\"\n @click=\"toggleOpen\"\n >\n <span\n data-slot=\"commit-avatar\"\n :aria-label=\"props.avatarLabel ?? props.authorName ?? 'Commit'\"\n :class=\"cn('flex shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground', sizeClasses.avatar)\"\n >\n <GitCommitHorizontalIcon\n data-slot=\"commit-avatar-icon\"\n aria-hidden=\"true\"\n :class=\"sizeClasses.avatarIcon\"\n />\n </span>\n\n <span data-slot=\"commit-title-group\" class=\"min-w-0 flex-1 pt-0.5\">\n <span\n data-slot=\"commit-message\"\n :class=\"cn('block truncate font-semibold leading-tight tracking-normal', sizeClasses.title)\"\n >\n {{ props.message }}\n </span>\n\n <span\n data-slot=\"commit-meta\"\n :class=\"cn('flex min-w-0 flex-wrap items-center leading-tight text-muted-foreground', sizeClasses.meta)\"\n >\n <span\n v-if=\"props.hash\"\n data-slot=\"commit-hash\"\n class=\"inline-flex min-w-0 items-center font-mono\"\n >\n <span class=\"truncate\">{{ props.hash }}</span>\n </span>\n\n <span\n v-if=\"props.hash && props.date\"\n data-slot=\"commit-meta-separator\"\n aria-hidden=\"true\"\n >\n •\n </span>\n\n <span v-if=\"props.date\" data-slot=\"commit-date\">\n {{ props.date }}\n </span>\n </span>\n </span>\n </component>\n\n <button\n v-if=\"props.showCopy\"\n data-slot=\"commit-copy\"\n type=\"button\"\n :aria-label=\"copyButtonLabel\"\n :class=\"\n cn(\n 'inline-flex shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',\n sizeClasses.copyButton,\n copied && 'text-emerald-600',\n )\n \"\n @click=\"handleCopy\"\n >\n <CheckIcon\n v-if=\"copied\"\n data-slot=\"commit-copy-icon\"\n aria-hidden=\"true\"\n :class=\"sizeClasses.copyIcon\"\n />\n <CopyIcon\n v-else\n data-slot=\"commit-copy-icon\"\n aria-hidden=\"true\"\n :class=\"sizeClasses.copyIcon\"\n />\n </button>\n\n <button\n v-if=\"props.collapsible && props.showToggle\"\n data-slot=\"commit-toggle-icon\"\n type=\"button\"\n :aria-label=\"isOpen ? 'Collapse commit' : 'Expand commit'\"\n :aria-expanded=\"isOpen\"\n :class=\"\n cn(\n 'inline-flex shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',\n sizeClasses.toggleButton,\n )\n \"\n @click=\"toggleOpen\"\n >\n <ChevronDownIcon\n aria-hidden=\"true\"\n :class=\"cn('transition-transform', sizeClasses.toggleIcon, !isOpen && '-rotate-90')\"\n />\n </button>\n </header>\n\n <ul\n v-if=\"isOpen && commitFiles.length\"\n data-slot=\"commit-files\"\n :class=\"cn('flex flex-col', sizeClasses.files)\"\n >\n <li\n v-for=\"(file, index) in commitFiles\"\n :key=\"`${file.status ?? 'modified'}-${file.path}-${index}`\"\n data-slot=\"commit-file-item\"\n >\n <button\n data-slot=\"commit-file\"\n type=\"button\"\n :data-status=\"file.status ?? 'modified'\"\n :class=\"cn('grid w-full items-center text-left outline-none transition-colors hover:bg-muted/50 focus-visible:bg-muted/70', sizeClasses.row)\"\n @click=\"handleFileSelect(file, index)\"\n >\n <span\n data-slot=\"commit-file-status\"\n :class=\"cn('font-mono font-semibold leading-none', sizeClasses.status, statusClass(file.status))\"\n >\n {{ statusLabel(file.status) }}\n </span>\n\n <FileIcon\n data-slot=\"commit-file-icon\"\n aria-hidden=\"true\"\n :class=\"cn('text-muted-foreground', sizeClasses.fileIcon)\"\n />\n\n <span\n data-slot=\"commit-file-path\"\n :class=\"cn('min-w-0 truncate font-mono leading-tight text-foreground', sizeClasses.path)\"\n >\n {{ file.path }}\n </span>\n\n <span\n data-slot=\"commit-file-stats\"\n :class=\"cn('flex shrink-0 items-center justify-end font-mono leading-tight', sizeClasses.stats)\"\n >\n <span\n v-if=\"formatAddition(file.additions)\"\n data-slot=\"commit-file-additions\"\n class=\"text-emerald-600\"\n >\n {{ formatAddition(file.additions) }}\n </span>\n\n <span\n v-if=\"formatDeletion(file.deletions)\"\n data-slot=\"commit-file-deletions\"\n class=\"text-destructive\"\n >\n {{ formatDeletion(file.deletions) }}\n </span>\n </span>\n </button>\n </li>\n </ul>\n\n <div\n v-else-if=\"isOpen\"\n data-slot=\"commit-empty\"\n :class=\"cn('text-muted-foreground', sizeClasses.empty)\"\n >\n {{ props.emptyLabel }}\n </div>\n </article>\n</template>\n",
15
+ "type": "registry:ui",
16
+ "target": "components/ui/commit/Commit.vue"
17
+ },
18
+ {
19
+ "path": "src/components/ui/commit/index.ts",
20
+ "content": "export { default as Commit } from \"./Commit.vue\"\nexport type {\n CommitCopyPayload,\n CommitFile,\n CommitFileSelectPayload,\n CommitFileStatus,\n CommitSize,\n CommitTogglePayload,\n} from \"./Commit.vue\"\n",
21
+ "type": "registry:ui",
22
+ "target": "components/ui/commit/index.ts"
23
+ },
24
+ {
25
+ "path": "src/lib/utils.ts",
26
+ "content": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
27
+ "type": "registry:lib",
28
+ "target": "lib/utils.ts"
29
+ }
30
+ ],
31
+ "type": "registry:ui"
32
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://shadcn-vue.com/schema/registry-item.json",
3
+ "name": "contribution-graph",
4
+ "title": "Contribution Graph",
5
+ "description": "A calendar heatmap for contribution activity with month labels, weekday labels, tooltips, selection, and intensity legend.",
6
+ "dependencies": [
7
+ "@vueuse/core",
8
+ "clsx",
9
+ "reka-ui",
10
+ "tailwind-merge"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "src/components/ui/contribution-graph/ContributionGraph.vue",
15
+ "content": "<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from \"vue\"\nimport type { CSSProperties, HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"../tooltip\"\n\nexport type ContributionGraphSize = \"default\" | \"lg\" | \"sm\"\nexport type ContributionGraphWeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6\n\nexport interface ContributionGraphItem {\n count: number\n date: Date | string\n label?: string\n}\n\nexport interface ContributionGraphDay {\n count: number\n date: string\n day: number\n label: string\n level: number\n outside: boolean\n row: number\n weekday: number\n}\n\nexport interface ContributionGraphSelectPayload extends ContributionGraphDay {\n item?: ContributionGraphItem\n}\n\nexport interface ContributionGraphYearSelectPayload {\n endDate: string\n startDate: string\n year: number\n}\n\ninterface NormalizedContributionItem extends ContributionGraphItem {\n count: number\n date: string\n}\n\ninterface ContributionGraphProps {\n activeLabel?: string\n class?: HTMLAttributes[\"class\"]\n description?: string\n emptyLabel?: string\n endDate?: Date | string\n fitWidth?: boolean\n gridClass?: HTMLAttributes[\"class\"]\n items?: ContributionGraphItem[]\n legendLabel?: string\n lessLabel?: string\n locale?: string\n maxCount?: number\n modelValue?: string\n moreLabel?: string\n showLegend?: boolean\n showMonthLabels?: boolean\n showSummary?: boolean\n showWeekdayLabels?: boolean\n showYearSelector?: boolean\n size?: ContributionGraphSize\n startDate?: Date | string\n summary?: string\n title?: string\n totalLabel?: string\n weekStart?: ContributionGraphWeekStart\n weeks?: number\n year?: number | string\n yearOptions?: Array<number | string>\n yearSelectorLabel?: string\n}\n\nconst props = withDefaults(defineProps<ContributionGraphProps>(), {\n activeLabel: \"active days\",\n emptyLabel: \"No contributions\",\n fitWidth: true,\n legendLabel: \"Contribution intensity\",\n lessLabel: \"Less\",\n moreLabel: \"More\",\n showLegend: true,\n showMonthLabels: true,\n showSummary: true,\n showWeekdayLabels: false,\n showYearSelector: true,\n size: \"default\",\n totalLabel: \"contributions\",\n weekStart: 0,\n yearSelectorLabel: \"Contribution years\",\n})\n\nconst emit = defineEmits<{\n \"select\": [day: ContributionGraphSelectPayload]\n \"update:modelValue\": [date: string]\n \"update:year\": [year: number]\n \"year-change\": [payload: ContributionGraphYearSelectPayload]\n}>()\n\nconst dayMs = 24 * 60 * 60 * 1000\nconst graphViewport = ref<HTMLElement | null>(null)\nconst graphWidth = ref<number | null>(null)\nconst internalYear = ref<number | null>(null)\nlet resizeObserver: ResizeObserver | undefined\n\nonMounted(() => {\n const element = graphViewport.value\n\n if (!element || typeof ResizeObserver === \"undefined\") return\n\n graphWidth.value = element.clientWidth\n resizeObserver = new ResizeObserver((entries) => {\n graphWidth.value = Math.round(entries[0]?.contentRect.width ?? element.clientWidth)\n })\n resizeObserver.observe(element)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n})\n\nconst dateFormatter = computed(() =>\n new Intl.DateTimeFormat(props.locale, {\n day: \"numeric\",\n month: \"short\",\n year: \"numeric\",\n }),\n)\nconst monthFormatter = computed(() =>\n new Intl.DateTimeFormat(props.locale, {\n month: \"short\",\n }),\n)\nconst weekdayFormatter = computed(() =>\n new Intl.DateTimeFormat(props.locale, {\n weekday: \"short\",\n }),\n)\nconst numberFormatter = computed(() =>\n new Intl.NumberFormat(props.locale, {\n maximumFractionDigits: 0,\n }),\n)\nconst baseEndDate = computed(() => stripTime(parseDate(props.endDate) ?? new Date()))\nconst selectedYear = computed(() =>\n normalizeYear(props.year) ?? internalYear.value ?? baseEndDate.value.getFullYear(),\n)\n\nconst range = computed(() => {\n if (props.showYearSelector) {\n return getContributionYearRange(selectedYear.value)\n }\n\n const rawEnd = baseEndDate.value\n const defaultStart = props.weeks\n ? addDays(rawEnd, -(Math.max(1, Math.trunc(props.weeks)) * 7) + 1)\n : getOneYearStart(rawEnd)\n const parsedStart = parseDate(props.startDate)\n const rawStart = stripTime(parsedStart ?? defaultStart)\n\n if (rawStart <= rawEnd) {\n return {\n end: rawEnd,\n start: rawStart,\n }\n }\n\n return {\n end: rawStart,\n start: rawEnd,\n }\n})\n\nconst normalizedItems = computed(() => {\n const items = new Map<string, NormalizedContributionItem>()\n\n for (const item of props.items ?? []) {\n const date = parseDate(item.date)\n\n if (!date) continue\n\n const key = formatLocalDate(date)\n const existing = items.get(key)\n const count = normalizeCount(item.count)\n\n items.set(key, {\n ...item,\n count: (existing?.count ?? 0) + count,\n date: key,\n label: item.label ?? existing?.label,\n })\n }\n\n return items\n})\n\nconst maxCount = computed(() => {\n if (props.maxCount !== undefined) return Math.max(normalizeCount(props.maxCount), 0)\n\n return Math.max(\n 0,\n ...Array.from(normalizedItems.value.values(), (item) => item.count),\n )\n})\n\nconst days = computed<ContributionGraphDay[]>(() => {\n const totalDays = differenceInDays(range.value.start, range.value.end) + 1\n\n return Array.from({ length: totalDays }, (_, index) => {\n const date = addDays(range.value.start, index)\n const key = formatLocalDate(date)\n const item = normalizedItems.value.get(key)\n const count = item?.count ?? 0\n const level = getContributionLevel(count)\n\n return {\n count,\n date: key,\n day: date.getDate(),\n label: item?.label ?? formatCellLabel(date, count),\n level,\n outside: false,\n row: getWeekdayRow(date, props.weekStart),\n weekday: date.getDay(),\n }\n })\n})\n\nconst calendarWeeks = computed(() => {\n const grouped: ContributionGraphDay[][] = []\n const weekMap = new Map<string, ContributionGraphDay[]>()\n\n for (const day of days.value) {\n const date = parseDate(day.date)\n\n if (!date) continue\n\n const key = formatLocalDate(startOfWeek(date, props.weekStart))\n let week = weekMap.get(key)\n\n if (!week) {\n week = []\n weekMap.set(key, week)\n grouped.push(week)\n }\n\n week.push(day)\n }\n\n return grouped\n})\n\nconst weekCount = computed(() => calendarWeeks.value.length)\nconst totalCount = computed(() =>\n days.value.reduce((total, day) => total + day.count, 0),\n)\nconst activeDays = computed(() =>\n days.value.filter((day) => day.count > 0).length,\n)\nconst availableYears = computed(() => {\n const configuredYears = props.yearOptions\n ?.map((year) => normalizeYear(year))\n .filter((year): year is number => year !== null)\n\n if (configuredYears?.length) {\n const years = new Set(configuredYears)\n years.add(selectedYear.value)\n\n return Array.from(years).sort((a, b) => b - a)\n }\n\n const years = new Set<number>([\n baseEndDate.value.getFullYear(),\n selectedYear.value,\n ])\n\n for (const item of normalizedItems.value.values()) {\n const year = normalizeYear(item.date.slice(0, 4))\n\n if (year !== null) years.add(year)\n }\n\n return Array.from(years).sort((a, b) => b - a)\n})\nconst hasYearSelector = computed(() =>\n props.showYearSelector && availableYears.value.length > 0,\n)\nconst formattedSummary = computed(() => {\n if (props.summary) return props.summary\n\n return `${numberFormatter.value.format(totalCount.value)} ${props.totalLabel} · ${numberFormatter.value.format(activeDays.value)} ${props.activeLabel}`\n})\nconst hasHeader = computed(() =>\n Boolean(props.title || props.description || props.showSummary),\n)\n\nconst monthLabelInterval = computed(() => {\n if (!props.fitWidth || graphWidth.value === null) return 1\n if (graphWidth.value < 360) return 4\n if (graphWidth.value < 520) return 3\n if (graphWidth.value < 680) return 2\n\n return 1\n})\n\nconst monthLabels = computed(() => {\n const labels: Array<{ column: number, key: string, label: string }> = []\n let lastKey = \"\"\n\n calendarWeeks.value.forEach((week, index) => {\n const firstMonthDay =\n week.find((day) => !day.outside && parseDate(day.date)?.getDate() === 1) ??\n (index === 0 ? week.find((day) => !day.outside) : undefined)\n\n if (!firstMonthDay) return\n\n const date = parseDate(firstMonthDay.date)\n\n if (!date) return\n\n const key = `${date.getFullYear()}-${date.getMonth()}`\n\n if (key === lastKey) return\n\n labels.push({\n column: index + 1,\n key,\n label: monthFormatter.value.format(date),\n })\n lastKey = key\n })\n\n return labels.map((label, index) => ({\n ...label,\n align: index === labels.length - 1 ? \"end\" : \"start\",\n span: (labels[index + 1]?.column ?? weekCount.value + 1) - label.column,\n visible:\n monthLabelInterval.value <= 1 ||\n labels.length <= 6 ||\n index === 0 ||\n index === labels.length - 1 ||\n index % monthLabelInterval.value === 0,\n }))\n})\n\nconst weekdayLabels = computed(() =>\n Array.from({ length: 7 }, (_, index) => {\n const weekday = (props.weekStart + index) % 7\n const labelDate = new Date(2024, 0, 7 + weekday)\n const shouldShow = weekday === 1 || weekday === 3 || weekday === 5\n\n return {\n key: weekday,\n label: shouldShow ? weekdayFormatter.value.format(labelDate) : \"\",\n }\n }),\n)\n\nconst graphStyle = computed<CSSProperties>(() => ({\n gridTemplateColumns: props.fitWidth\n ? `repeat(${weekCount.value}, minmax(0, 1fr))`\n : `repeat(${weekCount.value}, var(--contribution-graph-cell-size))`,\n}))\n\nconst rootStyle = computed<CSSProperties>(() => ({\n \"--contribution-graph-cell-size\": getCellSize(props.size),\n \"--contribution-graph-gap\": props.fitWidth ? \"0.125rem\" : \"0.25rem\",\n} as CSSProperties))\n\nfunction normalizeCount(value: number) {\n if (!Number.isFinite(value)) return 0\n\n return Math.max(0, Math.round(value))\n}\n\nfunction normalizeYear(value?: number | string | null) {\n const year = typeof value === \"string\" ? Number(value) : value\n\n if (typeof year !== \"number\" || !Number.isFinite(year)) return null\n\n return Math.trunc(year)\n}\n\nfunction getContributionLevel(count: number) {\n if (count <= 0 || maxCount.value <= 0) return 0\n\n return Math.max(1, Math.min(4, Math.ceil((count / maxCount.value) * 4)))\n}\n\nfunction getCellSize(size: ContributionGraphSize) {\n if (size === \"sm\") return \"0.625rem\"\n if (size === \"lg\") return \"0.875rem\"\n\n return \"0.75rem\"\n}\n\nfunction getOneYearStart(endDate: Date) {\n const start = new Date(endDate)\n start.setFullYear(start.getFullYear() - 1)\n\n return addDays(start, 1)\n}\n\nfunction getContributionYearRange(year: number) {\n const end = year === baseEndDate.value.getFullYear()\n ? baseEndDate.value\n : new Date(year, 11, 31)\n\n return {\n end,\n start: getOneYearStart(end),\n }\n}\n\nfunction parseDate(value?: Date | string | null) {\n if (!value) return null\n\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null\n\n return stripTime(value)\n }\n\n const match = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})/)\n\n if (!match) return null\n\n const [, year, month, day] = match\n const date = new Date(Number(year), Number(month) - 1, Number(day))\n\n if (Number.isNaN(date.getTime())) return null\n\n return stripTime(date)\n}\n\nfunction stripTime(date: Date) {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate())\n}\n\nfunction addDays(date: Date, amount: number) {\n const nextDate = new Date(date)\n nextDate.setDate(nextDate.getDate() + amount)\n\n return nextDate\n}\n\nfunction startOfWeek(date: Date, weekStart: ContributionGraphWeekStart) {\n const day = date.getDay()\n const diff = (day - weekStart + 7) % 7\n\n return addDays(date, -diff)\n}\n\nfunction getWeekdayRow(date: Date, weekStart: ContributionGraphWeekStart) {\n return ((date.getDay() - weekStart + 7) % 7) + 1\n}\n\nfunction differenceInDays(start: Date, end: Date) {\n return Math.round((stripTime(end).getTime() - stripTime(start).getTime()) / dayMs)\n}\n\nfunction isDateInRange(date: Date, start: Date, end: Date) {\n return date >= start && date <= end\n}\n\nfunction formatLocalDate(date: Date) {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, \"0\")\n const day = String(date.getDate()).padStart(2, \"0\")\n\n return `${year}-${month}-${day}`\n}\n\nfunction formatCellLabel(date: Date, count: number) {\n const formattedDate = dateFormatter.value.format(date)\n\n if (count <= 0) return `${props.emptyLabel} on ${formattedDate}`\n\n return `${numberFormatter.value.format(count)} ${props.totalLabel} on ${formattedDate}`\n}\n\nfunction handleSelect(day: ContributionGraphDay) {\n if (day.outside) return\n\n const item = normalizedItems.value.get(day.date)\n\n emit(\"update:modelValue\", day.date)\n emit(\"select\", {\n ...day,\n item,\n })\n}\n\nfunction handleYearSelect(year: number) {\n if (year === selectedYear.value) return\n\n internalYear.value = year\n\n const nextRange = getContributionYearRange(year)\n const selectedDate = parseDate(props.modelValue)\n\n emit(\"update:year\", year)\n emit(\"year-change\", {\n endDate: formatLocalDate(nextRange.end),\n startDate: formatLocalDate(nextRange.start),\n year,\n })\n\n if (!selectedDate || !isDateInRange(selectedDate, nextRange.start, nextRange.end)) {\n emit(\"update:modelValue\", formatLocalDate(nextRange.end))\n }\n}\n</script>\n\n<template>\n <div\n data-slot=\"contribution-graph\"\n :class=\"cn('flex min-w-0 flex-col gap-4 text-sm', props.class)\"\n :style=\"rootStyle\"\n >\n <div\n v-if=\"hasHeader\"\n data-slot=\"contribution-graph-header\"\n class=\"flex flex-wrap items-end justify-between gap-3\"\n >\n <div v-if=\"props.title || props.description\" class=\"flex min-w-0 flex-col gap-1\">\n <h3\n v-if=\"props.title\"\n data-slot=\"contribution-graph-title\"\n class=\"truncate text-base font-semibold leading-none\"\n >\n {{ props.title }}\n </h3>\n <p\n v-if=\"props.description\"\n data-slot=\"contribution-graph-description\"\n class=\"text-sm text-muted-foreground\"\n >\n {{ props.description }}\n </p>\n </div>\n\n <p\n v-if=\"props.showSummary\"\n data-slot=\"contribution-graph-summary\"\n class=\"shrink-0 text-sm font-medium text-muted-foreground\"\n >\n {{ formattedSummary }}\n </p>\n </div>\n\n <div\n data-slot=\"contribution-graph-body\"\n :class=\"cn('flex min-w-0 flex-col gap-4', hasYearSelector && 'md:grid md:grid-cols-[minmax(0,1fr)_4.5rem] md:items-start')\"\n >\n <div data-slot=\"contribution-graph-main\" class=\"flex min-w-0 flex-col gap-4\">\n <TooltipProvider>\n <div\n data-slot=\"contribution-graph-scroll\"\n ref=\"graphViewport\"\n :class=\"cn('min-w-0 pb-1', props.fitWidth ? 'overflow-visible' : 'overflow-x-auto')\"\n >\n <div\n data-slot=\"contribution-graph-layout\"\n :class=\"\n cn(\n 'gap-x-2 gap-y-1',\n props.fitWidth ? 'grid w-full min-w-0' : 'inline-grid min-w-max',\n props.showWeekdayLabels ? 'grid-cols-[2rem_minmax(0,1fr)]' : 'grid-cols-1',\n props.gridClass,\n )\n \"\n >\n <div\n v-if=\"props.showWeekdayLabels && props.showMonthLabels\"\n aria-hidden=\"true\"\n />\n\n <div\n v-if=\"props.showMonthLabels\"\n data-slot=\"contribution-graph-months\"\n class=\"grid min-h-4 gap-[var(--contribution-graph-gap)] overflow-visible text-xs text-muted-foreground\"\n :style=\"graphStyle\"\n >\n <span\n v-for=\"month in monthLabels\"\n :key=\"month.key\"\n v-show=\"month.visible\"\n class=\"whitespace-nowrap leading-none\"\n :style=\"{\n gridColumn: `${month.column} / span ${month.span}`,\n justifySelf: month.align,\n }\"\n >\n {{ month.label }}\n </span>\n </div>\n\n <div\n v-if=\"props.showWeekdayLabels\"\n data-slot=\"contribution-graph-weekdays\"\n class=\"grid grid-rows-7 gap-1 py-px text-right text-xs leading-none text-muted-foreground\"\n >\n <span\n v-for=\"weekday in weekdayLabels\"\n :key=\"weekday.key\"\n class=\"flex h-[var(--contribution-graph-cell-size)] items-center justify-end\"\n >\n {{ weekday.label }}\n </span>\n </div>\n\n <div\n data-slot=\"contribution-graph-grid\"\n :class=\"\n cn(\n 'grid gap-[var(--contribution-graph-gap)]',\n props.fitWidth ? 'w-full min-w-0' : 'min-w-max',\n )\n \"\n role=\"grid\"\n :aria-label=\"props.title ?? 'Contribution graph'\"\n :style=\"graphStyle\"\n >\n <div\n v-for=\"(week, weekIndex) in calendarWeeks\"\n :key=\"weekIndex\"\n data-slot=\"contribution-graph-week\"\n class=\"grid min-w-0 grid-rows-7 gap-[var(--contribution-graph-gap)]\"\n role=\"row\"\n >\n <template\n v-for=\"contributionDay in week\"\n :key=\"contributionDay.date\"\n >\n <Tooltip>\n <TooltipTrigger as-child>\n <button\n data-slot=\"contribution-graph-cell\"\n type=\"button\"\n role=\"gridcell\"\n :aria-label=\"contributionDay.label\"\n :aria-selected=\"props.modelValue === contributionDay.date\"\n :data-level=\"contributionDay.level\"\n :data-outside=\"contributionDay.outside ? '' : undefined\"\n :disabled=\"contributionDay.outside\"\n :style=\"{ gridRow: contributionDay.row }\"\n :class=\"\n cn(\n 'border border-border/60 bg-[var(--contribution-graph-level-0)] outline-none transition-[background-color,border-color,box-shadow,opacity,transform] hover:border-foreground/30 hover:ring-2 hover:ring-ring/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none data-[outside]:opacity-30 data-[level=1]:bg-[var(--contribution-graph-level-1)] data-[level=2]:bg-[var(--contribution-graph-level-2)] data-[level=3]:bg-[var(--contribution-graph-level-3)] data-[level=4]:bg-[var(--contribution-graph-level-4)] aria-selected:border-foreground aria-selected:ring-2 aria-selected:ring-ring/40',\n props.fitWidth\n ? 'aspect-square w-full min-w-0 rounded-[2px]'\n : 'size-[var(--contribution-graph-cell-size)] rounded-[3px]',\n contributionDay.count > 0 && 'hover:-translate-y-px',\n )\n \"\n @click=\"handleSelect(contributionDay)\"\n />\n </TooltipTrigger>\n <TooltipContent>\n {{ contributionDay.label }}\n </TooltipContent>\n </Tooltip>\n </template>\n </div>\n </div>\n </div>\n </div>\n </TooltipProvider>\n\n <div\n v-if=\"props.showLegend\"\n data-slot=\"contribution-graph-legend\"\n class=\"flex flex-wrap items-center justify-end gap-2 text-xs text-muted-foreground\"\n :aria-label=\"props.legendLabel\"\n >\n <span>{{ props.lessLabel }}</span>\n <span\n v-for=\"level in [0, 1, 2, 3, 4]\"\n :key=\"level\"\n data-slot=\"contribution-graph-legend-cell\"\n :data-level=\"level\"\n class=\"size-[var(--contribution-graph-cell-size)] rounded-[3px] border border-border/60 bg-[var(--contribution-graph-level-0)] data-[level=1]:bg-[var(--contribution-graph-level-1)] data-[level=2]:bg-[var(--contribution-graph-level-2)] data-[level=3]:bg-[var(--contribution-graph-level-3)] data-[level=4]:bg-[var(--contribution-graph-level-4)]\"\n />\n <span>{{ props.moreLabel }}</span>\n </div>\n </div>\n\n <nav\n v-if=\"hasYearSelector\"\n data-slot=\"contribution-graph-years\"\n class=\"order-first flex min-w-0 gap-1 overflow-x-auto pb-1 md:order-none md:flex-col md:overflow-visible md:pb-0\"\n :aria-label=\"props.yearSelectorLabel\"\n >\n <button\n v-for=\"year in availableYears\"\n :key=\"year\"\n data-slot=\"contribution-graph-year\"\n type=\"button\"\n :aria-current=\"year === selectedYear ? 'date' : undefined\"\n :aria-pressed=\"year === selectedYear\"\n :data-state=\"year === selectedYear ? 'active' : 'inactive'\"\n :class=\"\n cn(\n 'inline-flex h-8 shrink-0 items-center justify-center rounded-md px-3 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 data-[state=active]:bg-primary data-[state=active]:text-primary-foreground md:w-full',\n )\n \"\n @click=\"handleYearSelect(year)\"\n >\n {{ year }}\n </button>\n </nav>\n </div>\n </div>\n</template>\n",
16
+ "type": "registry:ui",
17
+ "target": "components/ui/contribution-graph/ContributionGraph.vue"
18
+ },
19
+ {
20
+ "path": "src/components/ui/contribution-graph/index.ts",
21
+ "content": "export { default as ContributionGraph } from \"./ContributionGraph.vue\"\nexport type {\n ContributionGraphDay,\n ContributionGraphItem,\n ContributionGraphSelectPayload,\n ContributionGraphSize,\n ContributionGraphWeekStart,\n ContributionGraphYearSelectPayload,\n} from \"./ContributionGraph.vue\"\n",
22
+ "type": "registry:ui",
23
+ "target": "components/ui/contribution-graph/index.ts"
24
+ },
25
+ {
26
+ "path": "src/components/ui/tooltip/Tooltip.vue",
27
+ "content": "<script setup lang=\"ts\">\nimport type { TooltipRootEmits, TooltipRootProps } from \"reka-ui\"\nimport { TooltipRoot, useForwardPropsEmits } from \"reka-ui\"\n\nconst props = defineProps<TooltipRootProps>()\nconst emits = defineEmits<TooltipRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <TooltipRoot\n v-slot=\"slotProps\"\n data-slot=\"tooltip\"\n v-bind=\"forwarded\"\n >\n <slot v-bind=\"slotProps\" />\n </TooltipRoot>\n</template>\n",
28
+ "type": "registry:ui",
29
+ "target": "components/ui/tooltip/Tooltip.vue"
30
+ },
31
+ {
32
+ "path": "src/components/ui/tooltip/TooltipTrigger.vue",
33
+ "content": "<script setup lang=\"ts\">\nimport type { TooltipTriggerProps } from \"reka-ui\"\nimport { TooltipTrigger } from \"reka-ui\"\n\nconst props = defineProps<TooltipTriggerProps>()\n</script>\n\n<template>\n <TooltipTrigger\n data-slot=\"tooltip-trigger\"\n v-bind=\"props\"\n >\n <slot />\n </TooltipTrigger>\n</template>\n",
34
+ "type": "registry:ui",
35
+ "target": "components/ui/tooltip/TooltipTrigger.vue"
36
+ },
37
+ {
38
+ "path": "src/components/ui/tooltip/TooltipContent.vue",
39
+ "content": "<script setup lang=\"ts\">\nimport type { TooltipContentEmits, TooltipContentProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { TooltipArrow, TooltipContent, TooltipPortal, useForwardPropsEmits } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(\n defineProps<TooltipContentProps & {\n arrow?: boolean\n class?: HTMLAttributes[\"class\"]\n }>(),\n {\n arrow: true,\n sideOffset: 4,\n },\n)\n\nconst emits = defineEmits<TooltipContentEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"arrow\", \"class\")\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <TooltipPortal>\n <TooltipContent\n data-slot=\"tooltip-content\"\n v-bind=\"{ ...forwarded, ...$attrs }\"\n :class=\"cn('bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance', props.class)\"\n >\n <slot />\n\n <TooltipArrow\n v-if=\"props.arrow\"\n class=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\"\n />\n </TooltipContent>\n </TooltipPortal>\n</template>\n",
40
+ "type": "registry:ui",
41
+ "target": "components/ui/tooltip/TooltipContent.vue"
42
+ },
43
+ {
44
+ "path": "src/components/ui/tooltip/TooltipProvider.vue",
45
+ "content": "<script setup lang=\"ts\">\nimport type { TooltipProviderProps } from \"reka-ui\"\nimport { TooltipProvider } from \"reka-ui\"\n\nconst props = withDefaults(defineProps<TooltipProviderProps>(), {\n delayDuration: 0,\n})\n</script>\n\n<template>\n <TooltipProvider v-bind=\"props\">\n <slot />\n </TooltipProvider>\n</template>\n",
46
+ "type": "registry:ui",
47
+ "target": "components/ui/tooltip/TooltipProvider.vue"
48
+ },
49
+ {
50
+ "path": "src/components/ui/tooltip/index.ts",
51
+ "content": "export { default as Tooltip } from \"./Tooltip.vue\"\nexport { default as TooltipContent } from \"./TooltipContent.vue\"\nexport { default as TooltipProvider } from \"./TooltipProvider.vue\"\nexport { default as TooltipTrigger } from \"./TooltipTrigger.vue\"\n",
52
+ "type": "registry:ui",
53
+ "target": "components/ui/tooltip/index.ts"
54
+ },
55
+ {
56
+ "path": "src/lib/utils.ts",
57
+ "content": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
58
+ "type": "registry:lib",
59
+ "target": "lib/utils.ts"
60
+ }
61
+ ],
62
+ "type": "registry:ui"
63
+ }
@@ -0,0 +1,197 @@
1
+ {
2
+ "$schema": "https://shadcn-vue.com/schema/registry-item.json",
3
+ "name": "data-table",
4
+ "title": "Data Table",
5
+ "description": "A searchable, sortable, selectable, and paginated data table composed from table primitives.",
6
+ "dependencies": [
7
+ "@lucide/vue",
8
+ "@vueuse/core",
9
+ "class-variance-authority",
10
+ "clsx",
11
+ "reka-ui",
12
+ "tailwind-merge"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "src/components/ui/data-table/DataTable.vue",
17
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { computed, ref, useSlots, watch } from \"vue\"\nimport {\n ArrowDownIcon,\n ArrowUpDownIcon,\n ArrowUpIcon,\n CheckIcon,\n MinusIcon,\n SearchIcon,\n} from \"@lucide/vue\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n Pagination,\n PaginationContent,\n PaginationEllipsis,\n PaginationFirst,\n PaginationItem,\n PaginationLast,\n PaginationNext,\n PaginationPrevious,\n} from \"@/components/ui/pagination\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableEmpty,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\"\nimport { cn } from \"@/lib/utils\"\n\nexport type DataTableRow = Record<string, unknown>\nexport type DataTableRowKey = number | string\nexport type DataTableSortDirection = \"asc\" | \"desc\" | null\n\nexport interface DataTableColumn {\n accessor?: (row: DataTableRow) => unknown\n align?: \"center\" | \"left\" | \"right\"\n cellClass?: HTMLAttributes[\"class\"]\n class?: HTMLAttributes[\"class\"]\n format?: (value: unknown, row: DataTableRow) => number | string\n headerClass?: HTMLAttributes[\"class\"]\n key: string\n label: string\n searchable?: boolean\n sortable?: boolean\n}\n\nexport interface DataTableSortPayload {\n direction: DataTableSortDirection\n key: string\n}\n\nexport interface DataTableSelectionPayload {\n keys: DataTableRowKey[]\n rows: DataTableRow[]\n}\n\ninterface IndexedRow {\n index: number\n key: DataTableRowKey\n row: DataTableRow\n}\n\nconst props = withDefaults(\n defineProps<{\n caption?: string\n class?: HTMLAttributes[\"class\"]\n columns?: DataTableColumn[]\n containerClass?: HTMLAttributes[\"class\"]\n emptyText?: string\n loading?: boolean\n loadingText?: string\n modelValue?: DataTableRowKey[]\n noResultsText?: string\n page?: number\n pageSize?: number\n paginationClass?: HTMLAttributes[\"class\"]\n rowKey?: ((row: DataTableRow, index: number) => DataTableRowKey) | string\n rows?: DataTableRow[]\n searchPlaceholder?: string\n searchValue?: string\n searchable?: boolean\n selectable?: boolean\n showPagination?: boolean\n showRowCount?: boolean\n sortDirection?: DataTableSortDirection\n sortKey?: string\n tableClass?: HTMLAttributes[\"class\"]\n toolbarClass?: HTMLAttributes[\"class\"]\n }>(),\n {\n columns: () => [],\n emptyText: \"No rows found.\",\n loading: false,\n loadingText: \"Loading rows...\",\n noResultsText: \"No results found.\",\n page: undefined,\n pageSize: 10,\n rowKey: \"id\",\n rows: () => [],\n searchPlaceholder: \"Search rows\",\n searchValue: undefined,\n searchable: true,\n selectable: false,\n showPagination: true,\n showRowCount: true,\n sortDirection: null,\n sortKey: \"\",\n },\n)\n\nconst emit = defineEmits<{\n \"select\": [payload: DataTableSelectionPayload]\n \"sort\": [payload: DataTableSortPayload]\n \"update:modelValue\": [keys: DataTableRowKey[]]\n \"update:page\": [page: number]\n \"update:searchValue\": [value: string]\n \"update:sortDirection\": [direction: DataTableSortDirection]\n \"update:sortKey\": [key: string]\n}>()\n\nconst slots = useSlots()\nconst internalPage = ref(1)\nconst internalSearchValue = ref(\"\")\nconst internalSelectedKeys = ref<DataTableRowKey[]>([])\nconst internalSortDirection = ref<DataTableSortDirection>(props.sortDirection)\nconst internalSortKey = ref(props.sortKey)\n\nconst pageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst columnCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\nconst hasToolbar = computed(() => props.searchable || Boolean(slots.toolbar))\nconst searchValue = computed({\n get: () => props.searchValue ?? internalSearchValue.value,\n set: (value: string) => {\n internalSearchValue.value = value\n emit(\"update:searchValue\", value)\n },\n})\nconst currentPage = computed({\n get: () => props.page ?? internalPage.value,\n set: (value: number) => {\n internalPage.value = value\n emit(\"update:page\", value)\n },\n})\nconst selectedKeys = computed({\n get: () => props.modelValue ?? internalSelectedKeys.value,\n set: (keys: DataTableRowKey[]) => {\n internalSelectedKeys.value = keys\n emit(\"update:modelValue\", keys)\n emit(\"select\", {\n keys,\n rows: indexedRows.value\n .filter((item) => keys.includes(item.key))\n .map((item) => item.row),\n })\n },\n})\nconst activeSortKey = computed({\n get: () => props.sortKey || internalSortKey.value,\n set: (key: string) => {\n internalSortKey.value = key\n emit(\"update:sortKey\", key)\n },\n})\nconst activeSortDirection = computed({\n get: () => props.sortDirection ?? internalSortDirection.value,\n set: (direction: DataTableSortDirection) => {\n internalSortDirection.value = direction\n emit(\"update:sortDirection\", direction)\n },\n})\n\nconst indexedRows = computed<IndexedRow[]>(() =>\n props.rows.map((row, index) => ({\n index,\n key: getRowKey(row, index),\n row,\n })),\n)\nconst filteredRows = computed(() => {\n const query = normalizeSearchText(searchValue.value)\n\n if (!query) return indexedRows.value\n\n const searchableColumns = props.columns.filter((column) => column.searchable !== false)\n\n return indexedRows.value.filter((item) =>\n searchableColumns.some((column) =>\n normalizeSearchText(String(getCellValue(item.row, column) ?? \"\")).includes(query),\n ),\n )\n})\nconst sortedRows = computed(() => {\n if (!activeSortKey.value || !activeSortDirection.value) return filteredRows.value\n\n const column = props.columns.find((item) => item.key === activeSortKey.value)\n\n if (!column) return filteredRows.value\n\n const direction = activeSortDirection.value === \"asc\" ? 1 : -1\n\n return [...filteredRows.value].sort((first, second) =>\n compareCellValues(\n getCellValue(first.row, column),\n getCellValue(second.row, column),\n ) * direction,\n )\n})\nconst pageCount = computed(() =>\n Math.max(1, Math.ceil(sortedRows.value.length / pageSize.value)),\n)\nconst visibleRows = computed(() => {\n if (!props.showPagination) return sortedRows.value\n\n const start = (currentPage.value - 1) * pageSize.value\n\n return sortedRows.value.slice(start, start + pageSize.value)\n})\nconst rowCountLabel = computed(() => {\n if (sortedRows.value.length === 0) return \"0 rows\"\n\n if (!props.showPagination) return `${sortedRows.value.length} rows`\n\n const start = (currentPage.value - 1) * pageSize.value + 1\n const end = Math.min(currentPage.value * pageSize.value, sortedRows.value.length)\n\n return `${start}-${end} of ${sortedRows.value.length} rows`\n})\nconst hasRows = computed(() => visibleRows.value.length > 0)\nconst emptyStateText = computed(() =>\n props.loading\n ? props.loadingText\n : searchValue.value\n ? props.noResultsText\n : props.emptyText,\n)\nconst headerSelectionState = computed(() => {\n const keys = visibleRows.value.map((item) => item.key)\n\n if (keys.length === 0) return false\n\n const selectedCount = keys.filter((key) => selectedKeys.value.includes(key)).length\n\n if (selectedCount === 0) return false\n if (selectedCount === keys.length) return true\n\n return \"indeterminate\"\n})\n\nwatch([pageSize, () => sortedRows.value.length], () => {\n if (currentPage.value > pageCount.value) currentPage.value = pageCount.value\n if (currentPage.value < 1) currentPage.value = 1\n})\n\nwatch(searchValue, () => {\n currentPage.value = 1\n})\n\nfunction getRowKey(row: DataTableRow, index: number) {\n if (typeof props.rowKey === \"function\") return props.rowKey(row, index)\n\n const value = row[props.rowKey]\n\n return typeof value === \"number\" || typeof value === \"string\" ? value : index\n}\n\nfunction getCellValue(row: DataTableRow, column: DataTableColumn) {\n return column.accessor ? column.accessor(row) : row[column.key]\n}\n\nfunction getCellText(row: DataTableRow, column: DataTableColumn) {\n const value = getCellValue(row, column)\n\n if (column.format) return column.format(value, row)\n if (value === null || value === undefined) return \"\"\n\n return String(value)\n}\n\nfunction normalizeSearchText(value: string) {\n return value.trim().toLowerCase().replace(/\\s+/g, \" \")\n}\n\nfunction compareCellValues(first: unknown, second: unknown) {\n if (first === second) return 0\n if (first === null || first === undefined) return 1\n if (second === null || second === undefined) return -1\n\n if (typeof first === \"number\" && typeof second === \"number\") {\n return first - second\n }\n\n return String(first).localeCompare(String(second), undefined, {\n numeric: true,\n sensitivity: \"base\",\n })\n}\n\nfunction isColumnSortable(column: DataTableColumn) {\n return column.sortable !== false\n}\n\nfunction getSortIcon(column: DataTableColumn) {\n if (activeSortKey.value !== column.key) return ArrowUpDownIcon\n if (activeSortDirection.value === \"asc\") return ArrowUpIcon\n if (activeSortDirection.value === \"desc\") return ArrowDownIcon\n\n return ArrowUpDownIcon\n}\n\nfunction toggleSort(column: DataTableColumn) {\n if (!isColumnSortable(column)) return\n\n let nextDirection: DataTableSortDirection = \"asc\"\n\n if (activeSortKey.value === column.key) {\n nextDirection =\n activeSortDirection.value === \"asc\"\n ? \"desc\"\n : activeSortDirection.value === \"desc\"\n ? null\n : \"asc\"\n }\n\n activeSortKey.value = nextDirection ? column.key : \"\"\n activeSortDirection.value = nextDirection\n emit(\"sort\", {\n direction: nextDirection,\n key: nextDirection ? column.key : \"\",\n })\n}\n\nfunction alignClass(align?: DataTableColumn[\"align\"]) {\n if (align === \"right\") return \"text-right\"\n if (align === \"center\") return \"text-center\"\n\n return \"text-left\"\n}\n\nfunction isRowSelected(key: DataTableRowKey) {\n return selectedKeys.value.includes(key)\n}\n\nfunction toggleRowSelection(item: IndexedRow, value: boolean | \"indeterminate\") {\n const keys = new Set(selectedKeys.value)\n\n if (value === true) keys.add(item.key)\n else keys.delete(item.key)\n\n selectedKeys.value = Array.from(keys)\n}\n\nfunction toggleVisibleSelection(value: boolean | \"indeterminate\") {\n const keys = new Set(selectedKeys.value)\n const visibleKeys = visibleRows.value.map((item) => item.key)\n\n if (value === true) {\n visibleKeys.forEach((key) => keys.add(key))\n } else {\n visibleKeys.forEach((key) => keys.delete(key))\n }\n\n selectedKeys.value = Array.from(keys)\n}\n</script>\n\n<template>\n <div data-slot=\"data-table\" :class=\"cn('flex min-w-0 flex-col gap-4', props.class)\">\n <div\n v-if=\"hasToolbar\"\n data-slot=\"data-table-toolbar\"\n :class=\"cn('flex flex-wrap items-center justify-between gap-3', props.toolbarClass)\"\n >\n <slot name=\"toolbar\">\n <Input\n v-if=\"props.searchable\"\n v-model=\"searchValue\"\n :icon=\"SearchIcon\"\n class=\"w-full sm:w-72\"\n :placeholder=\"props.searchPlaceholder\"\n />\n </slot>\n\n <p\n v-if=\"props.showRowCount\"\n data-slot=\"data-table-row-count\"\n class=\"shrink-0 text-sm text-muted-foreground\"\n >\n {{ rowCountLabel }}\n </p>\n </div>\n\n <Table\n :class=\"cn('min-w-[640px]', props.tableClass)\"\n :container-class=\"cn('rounded-md border', props.containerClass)\"\n >\n <caption v-if=\"props.caption\" class=\"sr-only\">\n {{ props.caption }}\n </caption>\n\n <TableHeader>\n <TableRow>\n <TableHead v-if=\"props.selectable\" class=\"w-10\">\n <Checkbox\n :model-value=\"headerSelectionState\"\n aria-label=\"Select visible rows\"\n @update:model-value=\"toggleVisibleSelection\"\n >\n <MinusIcon v-if=\"headerSelectionState === 'indeterminate'\" class=\"size-3.5\" />\n <CheckIcon v-else class=\"size-3.5\" />\n </Checkbox>\n </TableHead>\n\n <TableHead\n v-for=\"column in props.columns\"\n :key=\"column.key\"\n :class=\"cn(alignClass(column.align), column.headerClass, column.class)\"\n >\n <Button\n v-if=\"isColumnSortable(column)\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n :aria-label=\"`Sort by ${column.label}`\"\n :class=\"\n cn(\n '-mx-2 h-8 px-2',\n column.align === 'right' && 'ml-auto',\n column.align === 'center' && 'mx-auto',\n )\n \"\n @click=\"toggleSort(column)\"\n >\n <span class=\"truncate\">{{ column.label }}</span>\n <component :is=\"getSortIcon(column)\" data-icon=\"inline-end\" />\n </Button>\n\n <span v-else class=\"truncate\">\n {{ column.label }}\n </span>\n </TableHead>\n </TableRow>\n </TableHeader>\n\n <TableBody>\n <TableRow\n v-for=\"item in visibleRows\"\n :key=\"item.key\"\n :data-state=\"isRowSelected(item.key) ? 'selected' : undefined\"\n >\n <TableCell v-if=\"props.selectable\" class=\"w-10\">\n <Checkbox\n :model-value=\"isRowSelected(item.key)\"\n :aria-label=\"`Select row ${item.index + 1}`\"\n @update:model-value=\"toggleRowSelection(item, $event)\"\n />\n </TableCell>\n\n <TableCell\n v-for=\"column in props.columns\"\n :key=\"column.key\"\n :class=\"cn(alignClass(column.align), column.cellClass, column.class)\"\n >\n <slot\n :name=\"`cell-${column.key}`\"\n :column=\"column\"\n :index=\"item.index\"\n :row=\"item.row\"\n :value=\"getCellValue(item.row, column)\"\n >\n {{ getCellText(item.row, column) }}\n </slot>\n </TableCell>\n </TableRow>\n\n <TableEmpty v-if=\"!hasRows\" :colspan=\"Math.max(columnCount, 1)\">\n <slot name=\"empty\" :search=\"searchValue\">\n {{ emptyStateText }}\n </slot>\n </TableEmpty>\n </TableBody>\n </Table>\n\n <div\n v-if=\"props.showPagination && sortedRows.length > pageSize\"\n data-slot=\"data-table-pagination\"\n :class=\"cn('flex flex-wrap items-center justify-between gap-3', props.paginationClass)\"\n >\n <p\n v-if=\"props.showRowCount && !hasToolbar\"\n class=\"text-sm text-muted-foreground\"\n >\n {{ rowCountLabel }}\n </p>\n\n <Pagination\n v-slot=\"{ page }\"\n v-model:page=\"currentPage\"\n :items-per-page=\"pageSize\"\n :total=\"sortedRows.length\"\n :sibling-count=\"1\"\n show-edges\n class=\"mx-0 justify-end\"\n >\n <PaginationContent v-slot=\"{ items }\">\n <PaginationFirst size=\"sm\" />\n <PaginationPrevious size=\"sm\" />\n <template\n v-for=\"(paginationItem, index) in items\"\n :key=\"`${paginationItem.type}-${index}`\"\n >\n <PaginationItem\n v-if=\"paginationItem.type === 'page'\"\n :value=\"paginationItem.value\"\n :is-active=\"paginationItem.value === page\"\n size=\"icon-sm\"\n >\n {{ paginationItem.value }}\n </PaginationItem>\n <PaginationEllipsis v-else class=\"size-8\" />\n </template>\n <PaginationNext size=\"sm\" />\n <PaginationLast size=\"sm\" />\n </PaginationContent>\n </Pagination>\n </div>\n </div>\n</template>\n",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/data-table/DataTable.vue"
20
+ },
21
+ {
22
+ "path": "src/components/ui/data-table/index.ts",
23
+ "content": "export { default as DataTable } from \"./DataTable.vue\"\nexport type {\n DataTableColumn,\n DataTableRow,\n DataTableRowKey,\n DataTableSelectionPayload,\n DataTableSortDirection,\n DataTableSortPayload,\n} from \"./DataTable.vue\"\n",
24
+ "type": "registry:ui",
25
+ "target": "components/ui/data-table/index.ts"
26
+ },
27
+ {
28
+ "path": "src/components/ui/button/Button.vue",
29
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { Primitive, type PrimitiveProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants, type ButtonVariants } from \"./variants\"\n\ninterface ButtonProps extends PrimitiveProps {\n variant?: ButtonVariants[\"variant\"]\n size?: ButtonVariants[\"size\"]\n class?: HTMLAttributes[\"class\"]\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n as: \"button\",\n})\n</script>\n\n<template>\n <Primitive\n data-slot=\"button\"\n :as=\"props.as\"\n :as-child=\"props.asChild\"\n :class=\"cn(buttonVariants({ variant: props.variant, size: props.size }), props.class)\"\n >\n <slot />\n </Primitive>\n</template>\n",
30
+ "type": "registry:ui",
31
+ "target": "components/ui/button/Button.vue"
32
+ },
33
+ {
34
+ "path": "src/components/ui/button/variants.ts",
35
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\"\n\nexport const buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/30\",\n outline:\n \"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground\",\n secondary: \"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-6\",\n icon: \"size-9\",\n \"icon-sm\": \"size-8\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n)\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>\n",
36
+ "type": "registry:ui",
37
+ "target": "components/ui/button/variants.ts"
38
+ },
39
+ {
40
+ "path": "src/components/ui/button/index.ts",
41
+ "content": "export { default as Button } from \"./Button.vue\"\nexport { buttonVariants, type ButtonVariants } from \"./variants\"\n",
42
+ "type": "registry:ui",
43
+ "target": "components/ui/button/index.ts"
44
+ },
45
+ {
46
+ "path": "src/components/ui/checkbox/Checkbox.vue",
47
+ "content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { Check } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<CheckboxRootProps & { class?: HTMLAttributes[\"class\"] }>()\nconst emits = defineEmits<CheckboxRootEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <CheckboxRoot\n v-slot=\"slotProps\"\n data-slot=\"checkbox\"\n v-bind=\"forwarded\"\n :class=\"\n cn(\n 'peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <CheckboxIndicator\n data-slot=\"checkbox-indicator\"\n class=\"grid place-content-center text-current transition-none\"\n >\n <slot v-bind=\"slotProps\">\n <Check class=\"size-3.5\" />\n </slot>\n </CheckboxIndicator>\n </CheckboxRoot>\n</template>\n",
48
+ "type": "registry:ui",
49
+ "target": "components/ui/checkbox/Checkbox.vue"
50
+ },
51
+ {
52
+ "path": "src/components/ui/checkbox/index.ts",
53
+ "content": "export { default as Checkbox } from \"./Checkbox.vue\"\n",
54
+ "type": "registry:ui",
55
+ "target": "components/ui/checkbox/index.ts"
56
+ },
57
+ {
58
+ "path": "src/components/ui/input/Input.vue",
59
+ "content": "<script setup lang=\"ts\">\nimport { computed, useAttrs, useId } from \"vue\"\nimport { cn } from \"@/lib/utils\"\nimport InputControl from \"./InputControl.vue\"\nimport type { InputModelValue, InputProps } from \"./types\"\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<InputProps>(), {\n iconPosition: \"start\",\n type: \"text\",\n})\n\nconst emit = defineEmits<{\n \"update:modelValue\": [value: InputModelValue]\n input: [event: Event]\n change: [event: Event]\n}>()\n\nconst attrs = useAttrs()\nconst generatedId = useId()\n\nconst controlId = computed(() => props.id ?? generatedId)\nconst hasMessage = computed(() => Boolean(props.message || props.error))\nconst hasError = computed(() => Boolean(props.error))\nconst shouldRenderField = computed(() =>\n Boolean(\n props.field\n || props.fieldGroup\n || props.label\n || props.description\n || hasMessage.value,\n ),\n)\nconst descriptionId = computed(() =>\n props.description ? `${controlId.value}-description` : undefined,\n)\nconst messageId = computed(() =>\n hasMessage.value ? `${controlId.value}-message` : undefined,\n)\nconst describedBy = computed(() => {\n const value = [\n attrs[\"aria-describedby\"],\n descriptionId.value,\n messageId.value,\n ].filter(Boolean).join(\" \")\n\n return value || undefined\n})\nconst resolvedMessage = computed(() =>\n typeof props.error === \"string\" ? props.error : props.message,\n)\n\nfunction handleUpdate(value: InputModelValue) {\n emit(\"update:modelValue\", value)\n}\n</script>\n\n<template>\n <InputControl\n v-if=\"!shouldRenderField\"\n v-bind=\"attrs\"\n :id=\"controlId\"\n :class=\"props.class\"\n :default-value=\"props.defaultValue\"\n :disabled=\"props.disabled\"\n :icon=\"props.icon\"\n :icon-class=\"props.iconClass\"\n :icon-position=\"props.iconPosition\"\n :input-class=\"props.inputClass\"\n :invalid=\"props.invalid || hasError\"\n :model-value=\"props.modelValue\"\n :placeholder=\"props.placeholder\"\n :readonly=\"props.readonly\"\n :type=\"props.type\"\n @change=\"emit('change', $event)\"\n @input=\"emit('input', $event)\"\n @update:model-value=\"handleUpdate\"\n />\n\n <div\n v-else\n :data-slot=\"props.fieldGroup ? 'input-field-group' : 'input-field'\"\n :class=\"\n cn(\n props.fieldGroup ? 'grid gap-4' : 'grid gap-2',\n props.groupClass,\n )\n \"\n >\n <div\n data-slot=\"input-field\"\n :data-disabled=\"props.disabled ? '' : undefined\"\n :data-invalid=\"hasError ? '' : undefined\"\n :class=\"cn('grid gap-2', props.fieldClass)\"\n >\n <label\n v-if=\"props.label\"\n data-slot=\"input-label\"\n :for=\"controlId\"\n :class=\"\n cn(\n 'text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70 data-[disabled=true]:opacity-50',\n props.labelClass,\n )\n \"\n >\n {{ props.label }}\n </label>\n\n <InputControl\n v-bind=\"attrs\"\n :id=\"controlId\"\n :aria-describedby=\"describedBy\"\n :class=\"props.class\"\n :default-value=\"props.defaultValue\"\n :disabled=\"props.disabled\"\n :icon=\"props.icon\"\n :icon-class=\"props.iconClass\"\n :icon-position=\"props.iconPosition\"\n :input-class=\"props.inputClass\"\n :invalid=\"props.invalid || hasError\"\n :model-value=\"props.modelValue\"\n :placeholder=\"props.placeholder\"\n :readonly=\"props.readonly\"\n :type=\"props.type\"\n @change=\"emit('change', $event)\"\n @input=\"emit('input', $event)\"\n @update:model-value=\"handleUpdate\"\n />\n\n <p\n v-if=\"props.description\"\n :id=\"descriptionId\"\n data-slot=\"input-description\"\n :class=\"cn('text-xs leading-relaxed text-muted-foreground', props.descriptionClass)\"\n >\n {{ props.description }}\n </p>\n\n <p\n v-if=\"resolvedMessage\"\n :id=\"messageId\"\n data-slot=\"input-message\"\n :class=\"\n cn(\n 'text-xs font-medium leading-relaxed',\n hasError ? 'text-destructive' : 'text-muted-foreground',\n props.messageClass,\n )\n \"\n >\n {{ resolvedMessage }}\n </p>\n </div>\n </div>\n</template>\n",
60
+ "type": "registry:ui",
61
+ "target": "components/ui/input/Input.vue"
62
+ },
63
+ {
64
+ "path": "src/components/ui/input/InputControl.vue",
65
+ "content": "<script setup lang=\"ts\">\nimport { computed, useAttrs } from \"vue\"\nimport { cn } from \"@/lib/utils\"\nimport type { InputControlProps, InputModelValue } from \"./types\"\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<InputControlProps>(), {\n iconPosition: \"start\",\n type: \"text\",\n})\n\nconst emit = defineEmits<{\n \"update:modelValue\": [value: InputModelValue]\n input: [event: Event]\n change: [event: Event]\n}>()\n\nconst attrs = useAttrs()\n\nconst isFileInput = computed(() => props.type === \"file\")\nconst hasIcon = computed(() => Boolean(props.icon))\nconst inputValue = computed(() => {\n if (isFileInput.value) return undefined\n\n return props.modelValue ?? props.defaultValue ?? \"\"\n})\nconst valueBinding = computed(() =>\n isFileInput.value ? {} : { value: inputValue.value },\n)\nconst controlAttrs = computed(() => ({\n ...attrs,\n ...valueBinding.value,\n}))\n\nconst inputClasses = computed(() =>\n cn(\n \"border-input bg-background text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:mr-3 file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20\",\n isFileInput.value && \"cursor-pointer\",\n hasIcon.value\n && \"border-0 bg-transparent px-0 shadow-none focus-visible:border-transparent focus-visible:ring-0\",\n props.inputClass,\n !hasIcon.value && props.class,\n ),\n)\n\nconst wrapperClasses = computed(() =>\n cn(\n \"border-input bg-background text-foreground focus-within:border-ring focus-within:ring-ring/50 flex h-9 w-full min-w-0 items-center gap-2 rounded-md border px-3 shadow-xs transition-[color,box-shadow] focus-within:ring-[3px] has-disabled:cursor-not-allowed has-disabled:opacity-50 has-aria-invalid:border-destructive has-aria-invalid:ring-destructive/20\",\n props.class,\n ),\n)\n\nfunction getInputModelValue(target: HTMLInputElement) {\n if (isFileInput.value) return target.files\n if (props.type === \"number\") return target.value === \"\" ? \"\" : target.valueAsNumber\n\n return target.value\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n\n if (!isFileInput.value) {\n emit(\"update:modelValue\", getInputModelValue(target))\n }\n\n emit(\"input\", event)\n}\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLInputElement\n\n if (isFileInput.value) {\n emit(\"update:modelValue\", getInputModelValue(target))\n }\n\n emit(\"change\", event)\n}\n</script>\n\n<template>\n <div\n v-if=\"hasIcon\"\n data-slot=\"input-wrapper\"\n :class=\"wrapperClasses\"\n >\n <component\n :is=\"props.icon\"\n v-if=\"props.iconPosition === 'start'\"\n aria-hidden=\"true\"\n data-slot=\"input-icon\"\n :class=\"cn('size-4 shrink-0 text-muted-foreground', props.iconClass)\"\n />\n\n <input\n v-bind=\"controlAttrs\"\n :id=\"props.id\"\n data-slot=\"input\"\n :aria-invalid=\"props.invalid ? true : undefined\"\n :class=\"inputClasses\"\n :disabled=\"props.disabled\"\n :placeholder=\"props.placeholder\"\n :readonly=\"props.readonly\"\n :type=\"props.type\"\n @change=\"handleChange\"\n @input=\"handleInput\"\n >\n\n <component\n :is=\"props.icon\"\n v-if=\"props.iconPosition === 'end'\"\n aria-hidden=\"true\"\n data-slot=\"input-icon\"\n :class=\"cn('size-4 shrink-0 text-muted-foreground', props.iconClass)\"\n />\n </div>\n\n <input\n v-else\n v-bind=\"controlAttrs\"\n :id=\"props.id\"\n data-slot=\"input\"\n :aria-invalid=\"props.invalid ? true : undefined\"\n :class=\"inputClasses\"\n :disabled=\"props.disabled\"\n :placeholder=\"props.placeholder\"\n :readonly=\"props.readonly\"\n :type=\"props.type\"\n @change=\"handleChange\"\n @input=\"handleInput\"\n >\n</template>\n",
66
+ "type": "registry:ui",
67
+ "target": "components/ui/input/InputControl.vue"
68
+ },
69
+ {
70
+ "path": "src/components/ui/input/InputFieldGroup.vue",
71
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n}>()\n</script>\n\n<template>\n <div data-slot=\"input-field-group\" :class=\"cn('grid gap-4', props.class)\">\n <slot />\n </div>\n</template>\n",
72
+ "type": "registry:ui",
73
+ "target": "components/ui/input/InputFieldGroup.vue"
74
+ },
75
+ {
76
+ "path": "src/components/ui/input/types.ts",
77
+ "content": "import type { Component, HTMLAttributes, InputHTMLAttributes } from \"vue\"\n\nexport type InputModelValue = string | number | FileList | null\nexport type InputIconPosition = \"start\" | \"end\"\n\nexport interface InputControlProps {\n class?: HTMLAttributes[\"class\"]\n defaultValue?: string | number\n disabled?: boolean\n icon?: Component\n iconClass?: HTMLAttributes[\"class\"]\n iconPosition?: InputIconPosition\n id?: string\n inputClass?: HTMLAttributes[\"class\"]\n invalid?: boolean\n modelValue?: InputModelValue\n placeholder?: string\n readonly?: boolean\n type?: InputHTMLAttributes[\"type\"]\n}\n\nexport interface InputProps extends InputControlProps {\n description?: string\n descriptionClass?: HTMLAttributes[\"class\"]\n error?: string | boolean\n field?: boolean\n fieldClass?: HTMLAttributes[\"class\"]\n fieldGroup?: boolean\n groupClass?: HTMLAttributes[\"class\"]\n label?: string\n labelClass?: HTMLAttributes[\"class\"]\n message?: string\n messageClass?: HTMLAttributes[\"class\"]\n}\n",
78
+ "type": "registry:ui",
79
+ "target": "components/ui/input/types.ts"
80
+ },
81
+ {
82
+ "path": "src/components/ui/input/index.ts",
83
+ "content": "export { default as Input } from \"./Input.vue\"\nexport { default as InputControl } from \"./InputControl.vue\"\nexport { default as InputFieldGroup } from \"./InputFieldGroup.vue\"\nexport type {\n InputControlProps,\n InputIconPosition,\n InputModelValue,\n InputProps,\n} from \"./types\"\n",
84
+ "type": "registry:ui",
85
+ "target": "components/ui/input/index.ts"
86
+ },
87
+ {
88
+ "path": "src/components/ui/pagination/Pagination.vue",
89
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationRootEmits, PaginationRootProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationRoot, useForwardPropsEmits } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<PaginationRootProps & {\n class?: HTMLAttributes[\"class\"]\n}>()\nconst emits = defineEmits<PaginationRootEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <PaginationRoot\n v-slot=\"slotProps\"\n data-slot=\"pagination\"\n v-bind=\"forwarded\"\n :class=\"cn('mx-auto flex w-full justify-center', props.class)\"\n >\n <slot v-bind=\"slotProps\" />\n </PaginationRoot>\n</template>\n",
90
+ "type": "registry:ui",
91
+ "target": "components/ui/pagination/Pagination.vue"
92
+ },
93
+ {
94
+ "path": "src/components/ui/pagination/PaginationContent.vue",
95
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationListProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationList } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<PaginationListProps & {\n class?: HTMLAttributes[\"class\"]\n}>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n</script>\n\n<template>\n <PaginationList\n v-slot=\"slotProps\"\n data-slot=\"pagination-content\"\n v-bind=\"delegatedProps\"\n :class=\"cn('flex flex-row items-center gap-1', props.class)\"\n >\n <slot v-bind=\"slotProps\" />\n </PaginationList>\n</template>\n",
96
+ "type": "registry:ui",
97
+ "target": "components/ui/pagination/PaginationContent.vue"
98
+ },
99
+ {
100
+ "path": "src/components/ui/pagination/PaginationEllipsis.vue",
101
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationEllipsisProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { MoreHorizontalIcon } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationEllipsis } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<PaginationEllipsisProps & {\n class?: HTMLAttributes[\"class\"]\n}>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n</script>\n\n<template>\n <PaginationEllipsis\n data-slot=\"pagination-ellipsis\"\n v-bind=\"delegatedProps\"\n :class=\"cn('flex size-9 items-center justify-center', props.class)\"\n >\n <slot>\n <MoreHorizontalIcon class=\"size-4\" />\n <span class=\"sr-only\">More pages</span>\n </slot>\n </PaginationEllipsis>\n</template>\n",
102
+ "type": "registry:ui",
103
+ "target": "components/ui/pagination/PaginationEllipsis.vue"
104
+ },
105
+ {
106
+ "path": "src/components/ui/pagination/PaginationFirst.vue",
107
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationFirstProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { ButtonVariants } from \"@/components/ui/button\"\nimport { ChevronsLeftIcon } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationFirst, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst props = withDefaults(defineProps<PaginationFirstProps & {\n class?: HTMLAttributes[\"class\"]\n size?: ButtonVariants[\"size\"]\n}>(), {\n size: \"default\",\n})\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"size\")\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <PaginationFirst\n data-slot=\"pagination-first\"\n v-bind=\"forwarded\"\n :class=\"cn(buttonVariants({ variant: 'ghost', size: props.size }), 'gap-1 px-2.5', props.class)\"\n >\n <slot>\n <ChevronsLeftIcon />\n <span class=\"hidden sm:block\">First</span>\n </slot>\n </PaginationFirst>\n</template>\n",
108
+ "type": "registry:ui",
109
+ "target": "components/ui/pagination/PaginationFirst.vue"
110
+ },
111
+ {
112
+ "path": "src/components/ui/pagination/PaginationItem.vue",
113
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationListItemProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { ButtonVariants } from \"@/components/ui/button\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationListItem } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst props = withDefaults(defineProps<PaginationListItemProps & {\n class?: HTMLAttributes[\"class\"]\n isActive?: boolean\n size?: ButtonVariants[\"size\"]\n}>(), {\n isActive: false,\n size: \"icon\",\n})\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"isActive\", \"size\")\n</script>\n\n<template>\n <PaginationListItem\n data-slot=\"pagination-item\"\n v-bind=\"delegatedProps\"\n :class=\"\n cn(\n buttonVariants({\n variant: props.isActive ? 'outline' : 'ghost',\n size: props.size,\n }),\n !props.isActive && 'data-[selected=true]:border data-[selected=true]:border-input data-[selected=true]:bg-background data-[selected=true]:shadow-xs',\n props.class,\n )\n \"\n >\n <slot />\n </PaginationListItem>\n</template>\n",
114
+ "type": "registry:ui",
115
+ "target": "components/ui/pagination/PaginationItem.vue"
116
+ },
117
+ {
118
+ "path": "src/components/ui/pagination/PaginationLast.vue",
119
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationLastProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { ButtonVariants } from \"@/components/ui/button\"\nimport { ChevronsRightIcon } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationLast, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst props = withDefaults(defineProps<PaginationLastProps & {\n class?: HTMLAttributes[\"class\"]\n size?: ButtonVariants[\"size\"]\n}>(), {\n size: \"default\",\n})\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"size\")\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <PaginationLast\n data-slot=\"pagination-last\"\n v-bind=\"forwarded\"\n :class=\"cn(buttonVariants({ variant: 'ghost', size: props.size }), 'gap-1 px-2.5', props.class)\"\n >\n <slot>\n <span class=\"hidden sm:block\">Last</span>\n <ChevronsRightIcon />\n </slot>\n </PaginationLast>\n</template>\n",
120
+ "type": "registry:ui",
121
+ "target": "components/ui/pagination/PaginationLast.vue"
122
+ },
123
+ {
124
+ "path": "src/components/ui/pagination/PaginationNext.vue",
125
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationNextProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { ButtonVariants } from \"@/components/ui/button\"\nimport { ChevronRightIcon } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationNext, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst props = withDefaults(defineProps<PaginationNextProps & {\n class?: HTMLAttributes[\"class\"]\n size?: ButtonVariants[\"size\"]\n}>(), {\n size: \"default\",\n})\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"size\")\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <PaginationNext\n data-slot=\"pagination-next\"\n v-bind=\"forwarded\"\n :class=\"cn(buttonVariants({ variant: 'ghost', size: props.size }), 'gap-1 px-2.5', props.class)\"\n >\n <slot>\n <span class=\"hidden sm:block\">Next</span>\n <ChevronRightIcon />\n </slot>\n </PaginationNext>\n</template>\n",
126
+ "type": "registry:ui",
127
+ "target": "components/ui/pagination/PaginationNext.vue"
128
+ },
129
+ {
130
+ "path": "src/components/ui/pagination/PaginationPrevious.vue",
131
+ "content": "<script setup lang=\"ts\">\nimport type { PaginationPrevProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { ButtonVariants } from \"@/components/ui/button\"\nimport { ChevronLeftIcon } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { PaginationPrev, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst props = withDefaults(defineProps<PaginationPrevProps & {\n class?: HTMLAttributes[\"class\"]\n size?: ButtonVariants[\"size\"]\n}>(), {\n size: \"default\",\n})\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"size\")\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <PaginationPrev\n data-slot=\"pagination-previous\"\n v-bind=\"forwarded\"\n :class=\"cn(buttonVariants({ variant: 'ghost', size: props.size }), 'gap-1 px-2.5', props.class)\"\n >\n <slot>\n <ChevronLeftIcon />\n <span class=\"hidden sm:block\">Previous</span>\n </slot>\n </PaginationPrev>\n</template>\n",
132
+ "type": "registry:ui",
133
+ "target": "components/ui/pagination/PaginationPrevious.vue"
134
+ },
135
+ {
136
+ "path": "src/components/ui/pagination/index.ts",
137
+ "content": "export { default as Pagination } from \"./Pagination.vue\"\nexport { default as PaginationContent } from \"./PaginationContent.vue\"\nexport { default as PaginationEllipsis } from \"./PaginationEllipsis.vue\"\nexport { default as PaginationFirst } from \"./PaginationFirst.vue\"\nexport { default as PaginationItem } from \"./PaginationItem.vue\"\nexport { default as PaginationLast } from \"./PaginationLast.vue\"\nexport { default as PaginationNext } from \"./PaginationNext.vue\"\nexport { default as PaginationPrevious } from \"./PaginationPrevious.vue\"\n",
138
+ "type": "registry:ui",
139
+ "target": "components/ui/pagination/index.ts"
140
+ },
141
+ {
142
+ "path": "src/components/ui/table/Table.vue",
143
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n containerClass?: HTMLAttributes[\"class\"]\n}>()\n</script>\n\n<template>\n <div\n data-slot=\"table-container\"\n :class=\"cn('relative w-full min-w-0 overflow-auto', props.containerClass)\"\n >\n <table\n data-slot=\"table\"\n :class=\"cn('w-full caption-bottom text-sm', props.class)\"\n >\n <slot />\n </table>\n </div>\n</template>\n",
144
+ "type": "registry:ui",
145
+ "target": "components/ui/table/Table.vue"
146
+ },
147
+ {
148
+ "path": "src/components/ui/table/TableBody.vue",
149
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n}>()\n</script>\n\n<template>\n <tbody\n data-slot=\"table-body\"\n :class=\"cn('[&_tr:last-child]:border-0', props.class)\"\n >\n <slot />\n </tbody>\n</template>\n",
150
+ "type": "registry:ui",
151
+ "target": "components/ui/table/TableBody.vue"
152
+ },
153
+ {
154
+ "path": "src/components/ui/table/TableCell.vue",
155
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n colspan?: number | string\n}>()\n</script>\n\n<template>\n <td\n data-slot=\"table-cell\"\n :colspan=\"props.colspan\"\n :class=\"\n cn(\n 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n props.class,\n )\n \"\n >\n <slot />\n </td>\n</template>\n",
156
+ "type": "registry:ui",
157
+ "target": "components/ui/table/TableCell.vue"
158
+ },
159
+ {
160
+ "path": "src/components/ui/table/TableEmpty.vue",
161
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\nimport TableCell from \"./TableCell.vue\"\nimport TableRow from \"./TableRow.vue\"\n\nconst props = withDefaults(defineProps<{\n class?: HTMLAttributes[\"class\"]\n colspan?: number | string\n}>(), {\n colspan: 1,\n})\n</script>\n\n<template>\n <TableRow>\n <TableCell\n :colspan=\"props.colspan\"\n :class=\"\n cn(\n 'p-4 text-sm text-foreground',\n props.class,\n )\n \"\n >\n <div class=\"flex items-center justify-center py-10\">\n <slot />\n </div>\n </TableCell>\n </TableRow>\n</template>\n",
162
+ "type": "registry:ui",
163
+ "target": "components/ui/table/TableEmpty.vue"
164
+ },
165
+ {
166
+ "path": "src/components/ui/table/TableHead.vue",
167
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n}>()\n</script>\n\n<template>\n <th\n data-slot=\"table-head\"\n :class=\"\n cn(\n 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n props.class,\n )\n \"\n >\n <slot />\n </th>\n</template>\n",
168
+ "type": "registry:ui",
169
+ "target": "components/ui/table/TableHead.vue"
170
+ },
171
+ {
172
+ "path": "src/components/ui/table/TableHeader.vue",
173
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n}>()\n</script>\n\n<template>\n <thead\n data-slot=\"table-header\"\n :class=\"cn('[&_tr]:border-b', props.class)\"\n >\n <slot />\n </thead>\n</template>\n",
174
+ "type": "registry:ui",
175
+ "target": "components/ui/table/TableHeader.vue"
176
+ },
177
+ {
178
+ "path": "src/components/ui/table/TableRow.vue",
179
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<{\n class?: HTMLAttributes[\"class\"]\n}>()\n</script>\n\n<template>\n <tr\n data-slot=\"table-row\"\n :class=\"\n cn(\n 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',\n props.class,\n )\n \"\n >\n <slot />\n </tr>\n</template>\n",
180
+ "type": "registry:ui",
181
+ "target": "components/ui/table/TableRow.vue"
182
+ },
183
+ {
184
+ "path": "src/components/ui/table/index.ts",
185
+ "content": "export { default as Table } from \"./Table.vue\"\nexport { default as TableBody } from \"./TableBody.vue\"\nexport { default as TableCaption } from \"./TableCaption.vue\"\nexport { default as TableCell } from \"./TableCell.vue\"\nexport { default as TableEmpty } from \"./TableEmpty.vue\"\nexport { default as TableFooter } from \"./TableFooter.vue\"\nexport { default as TableHead } from \"./TableHead.vue\"\nexport { default as TableHeader } from \"./TableHeader.vue\"\nexport { default as TableRow } from \"./TableRow.vue\"\n",
186
+ "type": "registry:ui",
187
+ "target": "components/ui/table/index.ts"
188
+ },
189
+ {
190
+ "path": "src/lib/utils.ts",
191
+ "content": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
192
+ "type": "registry:lib",
193
+ "target": "lib/utils.ts"
194
+ }
195
+ ],
196
+ "type": "registry:ui"
197
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://shadcn-vue.com/schema/registry-item.json",
3
+ "name": "date-picker",
4
+ "title": "Date Picker",
5
+ "description": "A date picker control that can switch between single date, range, and time picker modes with props.",
6
+ "dependencies": [
7
+ "@lucide/vue",
8
+ "clsx",
9
+ "reka-ui",
10
+ "tailwind-merge"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "src/components/ui/date-picker/DatePicker.vue",
15
+ "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { computed, ref, watch } from \"vue\"\nimport {\n CalendarIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n ClockIcon,\n} from \"@lucide/vue\"\nimport {\n PopoverContent,\n PopoverPortal,\n PopoverRoot,\n PopoverTrigger,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nexport type DatePickerMode = \"single\" | \"range\" | \"time\"\n\nexport interface DatePickerSingleObjectValue {\n date?: string\n}\n\nexport interface DatePickerRangeObjectValue {\n start?: string\n end?: string\n}\n\nexport type DatePickerValue =\n | string\n | DatePickerSingleObjectValue\n | DatePickerRangeObjectValue\n | null\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(\n defineProps<{\n class?: HTMLAttributes[\"class\"]\n contentClass?: HTMLAttributes[\"class\"]\n disabled?: boolean\n max?: string\n min?: string\n mode?: DatePickerMode\n modelValue?: DatePickerValue\n placeholder?: string\n range?: boolean\n timePlaceholder?: string\n }>(),\n {\n mode: \"single\",\n placeholder: \"Pick a date\",\n range: false,\n timePlaceholder: \"Pick time\",\n },\n)\n\nconst emit = defineEmits<{\n \"update:modelValue\": [value: DatePickerValue]\n change: [value: DatePickerValue]\n}>()\n\nconst open = ref(false)\nconst dateValue = ref(\"\")\nconst startValue = ref(\"\")\nconst endValue = ref(\"\")\nconst hoveredDate = ref(\"\")\nconst timeHour = ref(\"\")\nconst timeMinute = ref(\"\")\nconst timeMinuteInput = ref<HTMLInputElement | null>(null)\nconst visibleMonth = ref(getMonthDate(new Date()))\n\nconst isRange = computed(() => props.range || props.mode === \"range\")\nconst isTimeOnly = computed(() => props.mode === \"time\")\n\nconst weekDays = [\"Su\", \"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\"]\n\nconst calendarTitle = computed(() =>\n new Intl.DateTimeFormat(undefined, {\n month: \"long\",\n year: \"numeric\",\n }).format(visibleMonth.value),\n)\n\nconst calendarDays = computed(() => {\n const year = visibleMonth.value.getFullYear()\n const month = visibleMonth.value.getMonth()\n const firstDay = new Date(year, month, 1)\n const gridStart = new Date(year, month, 1 - firstDay.getDay())\n const rangeBounds = getActiveRangeBounds()\n const today = formatLocalDate(new Date())\n\n return Array.from({ length: 42 }, (_, index) => {\n const day = new Date(\n gridStart.getFullYear(),\n gridStart.getMonth(),\n gridStart.getDate() + index,\n )\n const value = formatLocalDate(day)\n const selected = isDateSelected(value)\n const rangeActive = selected || isDateInRange(value) || isDateInPreviewRange(value)\n\n return {\n currentMonth: day.getMonth() === month,\n date: value,\n day: day.getDate(),\n disabled: isDateDisabled(value),\n rangeActive,\n rangeEnd: rangeActive && rangeBounds?.end === value,\n rangeStart: rangeActive && rangeBounds?.start === value,\n selected,\n today: value === today,\n }\n })\n})\n\nconst displayValue = computed(() => {\n if (isRange.value) {\n const rangeLabel = [\n formatDateLabel(startValue.value),\n formatDateLabel(endValue.value),\n ].filter(Boolean).join(\" - \")\n\n return rangeLabel\n }\n\n return formatDateLabel(dateValue.value)\n})\n\nconst hasDisplayValue = computed(() => displayValue.value.length > 0)\n\nfunction extractDatePart(value: string) {\n const match = value.match(/\\d{4}-\\d{2}-\\d{2}/)\n\n return match?.[0] ?? \"\"\n}\n\nfunction extractTimePart(value: string) {\n const match = value.match(/(?:T|\\s)?(\\d{2}:\\d{2})/)\n\n return match?.[1] ?? \"\"\n}\n\nfunction formatLocalDate(date: Date) {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, \"0\")\n const day = String(date.getDate()).padStart(2, \"0\")\n\n return `${year}-${month}-${day}`\n}\n\nfunction parseLocalDate(value: string) {\n const [year, month, day] = value.split(\"-\").map(Number)\n\n if (!year || !month || !day) return null\n\n return new Date(year, month - 1, day)\n}\n\nfunction getMonthDate(date: Date) {\n return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\nfunction getTimeParts(value: string) {\n const [hour = \"\", minute = \"\"] = value.split(\":\")\n\n return {\n hour,\n minute,\n }\n}\n\nfunction sanitizeTimeInput(value: string) {\n return value.replace(/\\D/g, \"\").slice(0, 2)\n}\n\nfunction normalizeTimeInput(value: string, max: number) {\n if (!value) return \"\"\n\n const number = Number(value)\n\n if (Number.isNaN(number)) return \"\"\n\n return String(Math.min(number, max)).padStart(2, \"0\")\n}\n\nfunction getCompleteTimeValue() {\n if (!timeHour.value || !timeMinute.value) return \"\"\n\n const hour = normalizeTimeInput(timeHour.value, 23)\n const minute = normalizeTimeInput(timeMinute.value, 59)\n\n if (!hour || !minute) return \"\"\n\n return `${hour}:${minute}`\n}\n\nfunction setTimeModelValue(value: string | null) {\n emit(\"update:modelValue\", value)\n emit(\"change\", value)\n}\n\nfunction emitCompleteTimeValue() {\n const value = getCompleteTimeValue()\n\n if (!value || timeHour.value.length < 2 || timeMinute.value.length < 2) return\n\n const [hour, minute] = value.split(\":\")\n\n timeHour.value = hour\n timeMinute.value = minute\n setTimeModelValue(value)\n}\n\nfunction handleTimeInput(part: \"hour\" | \"minute\", event: Event) {\n if (props.disabled) return\n\n const value = sanitizeTimeInput((event.target as HTMLInputElement).value)\n\n if (part === \"hour\") {\n timeHour.value = value\n\n if (value.length === 2) {\n timeMinuteInput.value?.focus()\n }\n } else {\n timeMinute.value = value\n }\n\n emitCompleteTimeValue()\n}\n\nfunction commitTimeInput(part: \"hour\" | \"minute\") {\n if (props.disabled) return\n\n if (part === \"hour\") {\n timeHour.value = normalizeTimeInput(timeHour.value, 23)\n } else {\n timeMinute.value = normalizeTimeInput(timeMinute.value, 59)\n }\n\n const value = getCompleteTimeValue()\n\n if (value) {\n const [hour, minute] = value.split(\":\")\n\n timeHour.value = hour\n timeMinute.value = minute\n setTimeModelValue(value)\n return\n }\n\n if (!timeHour.value && !timeMinute.value) {\n setTimeModelValue(null)\n }\n}\n\nfunction selectInputText(event: FocusEvent) {\n const input = event.target as HTMLInputElement\n\n input.select()\n}\n\nfunction formatDateLabel(value: string) {\n if (!value) return \"\"\n\n const [year, month, day] = value.split(\"-\").map(Number)\n\n if (!year || !month || !day) return value\n\n return new Intl.DateTimeFormat(undefined, {\n day: \"numeric\",\n month: \"short\",\n year: \"numeric\",\n }).format(new Date(year, month - 1, day))\n}\n\nfunction setVisibleMonthFromValue(value: string) {\n const date = parseLocalDate(value)\n\n if (!date) return\n\n visibleMonth.value = getMonthDate(date)\n}\n\nfunction isDateDisabled(value: string) {\n return Boolean(\n (props.min && value < props.min)\n || (props.max && value > props.max),\n )\n}\n\nfunction isDateSelected(value: string) {\n if (isRange.value) {\n return value === startValue.value || value === endValue.value\n }\n\n return value === dateValue.value\n}\n\nfunction isDateInRange(value: string) {\n if (!isRange.value || !startValue.value || !endValue.value) return false\n\n return value > startValue.value && value < endValue.value\n}\n\nfunction isDateInPreviewRange(value: string) {\n if (\n !isRange.value\n || !startValue.value\n || endValue.value\n || !hoveredDate.value\n ) {\n return false\n }\n\n const start = startValue.value < hoveredDate.value\n ? startValue.value\n : hoveredDate.value\n const end = startValue.value < hoveredDate.value\n ? hoveredDate.value\n : startValue.value\n\n return value >= start && value <= end\n}\n\nfunction getActiveRangeBounds() {\n if (!isRange.value || !startValue.value) return null\n\n const boundary = endValue.value || hoveredDate.value || startValue.value\n\n return startValue.value <= boundary\n ? { start: startValue.value, end: boundary }\n : { start: boundary, end: startValue.value }\n}\n\nfunction moveMonth(months: number) {\n visibleMonth.value = new Date(\n visibleMonth.value.getFullYear(),\n visibleMonth.value.getMonth() + months,\n 1,\n )\n}\n\nfunction selectCalendarDate(value: string) {\n if (props.disabled || isDateDisabled(value)) return\n\n let shouldClose = false\n hoveredDate.value = \"\"\n\n if (isRange.value) {\n if (!startValue.value || endValue.value) {\n startValue.value = value\n endValue.value = \"\"\n } else if (value < startValue.value) {\n endValue.value = startValue.value\n startValue.value = value\n shouldClose = true\n } else {\n endValue.value = value\n shouldClose = true\n }\n } else {\n dateValue.value = value\n shouldClose = true\n }\n\n setVisibleMonthFromValue(value)\n emitValue()\n\n if (shouldClose) {\n open.value = false\n }\n}\n\nfunction setRangeHoveredDate(value: string) {\n if (\n !isRange.value\n || !startValue.value\n || endValue.value\n || isDateDisabled(value)\n ) {\n return\n }\n\n hoveredDate.value = value\n}\n\nfunction clearRangeHoveredDate() {\n hoveredDate.value = \"\"\n}\n\nfunction syncFromModelValue(value: DatePickerValue | undefined) {\n dateValue.value = \"\"\n startValue.value = \"\"\n endValue.value = \"\"\n hoveredDate.value = \"\"\n timeHour.value = \"\"\n timeMinute.value = \"\"\n\n if (!value) return\n\n if (typeof value === \"string\") {\n if (isTimeOnly.value) {\n const timeParts = getTimeParts(extractTimePart(value))\n\n timeHour.value = timeParts.hour\n timeMinute.value = timeParts.minute\n return\n }\n\n if (isRange.value) {\n startValue.value = extractDatePart(value)\n return\n }\n\n dateValue.value = extractDatePart(value)\n if (dateValue.value) setVisibleMonthFromValue(dateValue.value)\n return\n }\n\n if (isRange.value) {\n const rangeValue = value as DatePickerRangeObjectValue\n\n startValue.value = rangeValue.start ? extractDatePart(rangeValue.start) : \"\"\n endValue.value = rangeValue.end ? extractDatePart(rangeValue.end) : \"\"\n if (startValue.value || endValue.value) {\n setVisibleMonthFromValue(startValue.value || endValue.value)\n }\n return\n }\n\n const singleValue = value as DatePickerSingleObjectValue\n\n dateValue.value = singleValue.date ? extractDatePart(singleValue.date) : \"\"\n if (dateValue.value) setVisibleMonthFromValue(dateValue.value)\n}\n\nfunction createModelValue(): DatePickerValue {\n if (isTimeOnly.value) return getCompleteTimeValue() || null\n\n if (isRange.value) {\n const next: DatePickerRangeObjectValue = {}\n\n if (startValue.value) next.start = startValue.value\n if (endValue.value) next.end = endValue.value\n\n return Object.keys(next).length ? next : null\n }\n\n return dateValue.value || null\n}\n\nfunction emitValue() {\n const value = createModelValue()\n\n emit(\"update:modelValue\", value)\n emit(\"change\", value)\n}\n\nwatch(\n () => [props.modelValue, props.mode, props.range] as const,\n ([value]) => syncFromModelValue(value),\n { deep: true, immediate: true },\n)\n</script>\n\n<template>\n <div\n v-if=\"isTimeOnly\"\n v-bind=\"$attrs\"\n data-slot=\"date-picker-time-field\"\n role=\"group\"\n :aria-disabled=\"props.disabled ? 'true' : undefined\"\n :aria-label=\"props.timePlaceholder\"\n :class=\"\n cn(\n 'border-input bg-background text-foreground focus-within:border-ring focus-within:ring-ring/50 flex h-9 w-full items-center gap-2 rounded-md border px-3 py-1 shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]',\n props.disabled && 'cursor-not-allowed opacity-50',\n props.class,\n )\n \"\n >\n <ClockIcon aria-hidden=\"true\" class=\"size-4 shrink-0 text-muted-foreground\" />\n\n <input\n data-slot=\"date-picker-hour-input\"\n :value=\"timeHour\"\n :disabled=\"props.disabled\"\n aria-label=\"Hour\"\n autocomplete=\"off\"\n class=\"h-7 w-8 bg-transparent text-center text-sm font-medium tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n inputmode=\"numeric\"\n maxlength=\"2\"\n pattern=\"[0-9]*\"\n placeholder=\"HH\"\n type=\"text\"\n @blur=\"commitTimeInput('hour')\"\n @focus=\"selectInputText\"\n @input=\"handleTimeInput('hour', $event)\"\n />\n\n <span aria-hidden=\"true\" class=\"text-sm font-medium text-muted-foreground\">\n :\n </span>\n\n <input\n ref=\"timeMinuteInput\"\n data-slot=\"date-picker-minute-input\"\n :value=\"timeMinute\"\n :disabled=\"props.disabled\"\n aria-label=\"Minute\"\n autocomplete=\"off\"\n class=\"h-7 w-8 bg-transparent text-center text-sm font-medium tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n inputmode=\"numeric\"\n maxlength=\"2\"\n pattern=\"[0-9]*\"\n placeholder=\"MM\"\n type=\"text\"\n @blur=\"commitTimeInput('minute')\"\n @focus=\"selectInputText\"\n @input=\"handleTimeInput('minute', $event)\"\n />\n </div>\n\n <PopoverRoot v-else v-model:open=\"open\">\n <PopoverTrigger as-child>\n <button\n v-bind=\"$attrs\"\n data-slot=\"date-picker-trigger\"\n type=\"button\"\n :disabled=\"props.disabled\"\n :class=\"\n cn(\n 'border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex h-9 w-full items-center justify-start gap-2 rounded-md border px-3 py-2 text-left text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n !hasDisplayValue && 'text-muted-foreground',\n props.class,\n )\n \"\n >\n <CalendarIcon aria-hidden=\"true\" class=\"size-4 shrink-0\" />\n <span class=\"min-w-0 flex-1 truncate\">\n {{ hasDisplayValue ? displayValue : props.placeholder }}\n </span>\n </button>\n </PopoverTrigger>\n\n <PopoverPortal>\n <PopoverContent\n data-slot=\"date-picker-content\"\n align=\"start\"\n :side-offset=\"4\"\n :class=\"\n cn(\n 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-md border p-3 shadow-md outline-none',\n props.contentClass,\n )\n \"\n >\n <div data-slot=\"date-picker-panel\" class=\"flex flex-col gap-3\">\n <div data-slot=\"date-picker-calendar\" class=\"grid gap-3\">\n <div class=\"flex items-center justify-between\">\n <button\n data-slot=\"date-picker-prev-month\"\n type=\"button\"\n aria-label=\"Previous month\"\n class=\"hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring inline-flex size-8 items-center justify-center rounded-md text-foreground transition-colors outline-none focus-visible:ring-2\"\n @click=\"moveMonth(-1)\"\n >\n <ChevronLeftIcon aria-hidden=\"true\" class=\"size-4\" />\n </button>\n\n <div\n data-slot=\"date-picker-calendar-heading\"\n class=\"text-base font-semibold\"\n >\n {{ calendarTitle }}\n </div>\n\n <button\n data-slot=\"date-picker-next-month\"\n type=\"button\"\n aria-label=\"Next month\"\n class=\"hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring inline-flex size-8 items-center justify-center rounded-md text-foreground transition-colors outline-none focus-visible:ring-2\"\n @click=\"moveMonth(1)\"\n >\n <ChevronRightIcon aria-hidden=\"true\" class=\"size-4\" />\n </button>\n </div>\n\n <div\n data-slot=\"date-picker-weekdays\"\n class=\"grid grid-cols-7 gap-1 text-center text-xs font-medium text-muted-foreground\"\n >\n <span v-for=\"day in weekDays\" :key=\"day\">\n {{ day }}\n </span>\n </div>\n\n <div\n data-slot=\"date-picker-days\"\n :class=\"cn('grid grid-cols-7', isRange ? 'gap-0' : 'gap-1')\"\n @mouseleave=\"clearRangeHoveredDate\"\n >\n <button\n v-for=\"day in calendarDays\"\n :key=\"day.date\"\n data-slot=\"date-picker-day\"\n type=\"button\"\n :aria-label=\"formatDateLabel(day.date)\"\n :aria-current=\"day.today ? 'date' : undefined\"\n :aria-pressed=\"day.selected\"\n :disabled=\"day.disabled\"\n :class=\"\n cn(\n 'hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring relative flex h-8 w-full items-center justify-center rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-40',\n !day.currentMonth && !day.rangeActive && 'text-muted-foreground/60',\n day.rangeActive && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground',\n isRange && day.rangeActive && 'rounded-none',\n isRange && day.rangeStart && !day.rangeEnd && 'rounded-l-md',\n isRange && day.rangeEnd && !day.rangeStart && 'rounded-r-md',\n isRange && day.rangeStart && day.rangeEnd && 'rounded-md',\n )\n \"\n @click=\"selectCalendarDate(day.date)\"\n @focus=\"setRangeHoveredDate(day.date)\"\n @mouseenter=\"setRangeHoveredDate(day.date)\"\n >\n <span>{{ day.day }}</span>\n <span\n v-if=\"day.today\"\n aria-hidden=\"true\"\n :class=\"\n cn(\n 'absolute bottom-0.5 left-1/2 size-1 -translate-x-1/2 rounded-full',\n day.rangeActive ? 'bg-primary-foreground' : 'bg-primary',\n )\n \"\n />\n </button>\n </div>\n </div>\n </div>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n",
16
+ "type": "registry:ui",
17
+ "target": "components/ui/date-picker/DatePicker.vue"
18
+ },
19
+ {
20
+ "path": "src/components/ui/date-picker/index.ts",
21
+ "content": "export { default as DatePicker } from \"./DatePicker.vue\"\nexport type {\n DatePickerMode,\n DatePickerRangeObjectValue,\n DatePickerSingleObjectValue,\n DatePickerValue,\n} from \"./DatePicker.vue\"\n",
22
+ "type": "registry:ui",
23
+ "target": "components/ui/date-picker/index.ts"
24
+ },
25
+ {
26
+ "path": "src/lib/utils.ts",
27
+ "content": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
28
+ "type": "registry:lib",
29
+ "target": "lib/utils.ts"
30
+ }
31
+ ],
32
+ "type": "registry:ui"
33
+ }