@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.
- package/LICENSE +34 -0
- package/README.md +148 -0
- package/components.json +20 -0
- package/dist/codecenter-ui.cjs +10 -0
- package/dist/codecenter-ui.js +17995 -0
- package/dist/components/ui/accordion/Accordion.vue.d.ts +28 -0
- package/dist/components/ui/accordion/AccordionContent.vue.d.ts +22 -0
- package/dist/components/ui/accordion/AccordionItem.vue.d.ts +24 -0
- package/dist/components/ui/accordion/AccordionTrigger.vue.d.ts +22 -0
- package/dist/components/ui/accordion/index.d.ts +4 -0
- package/dist/components/ui/alert/Alert.vue.d.ts +32 -0
- package/dist/components/ui/alert/AlertDescription.vue.d.ts +24 -0
- package/dist/components/ui/alert/AlertTitle.vue.d.ts +24 -0
- package/dist/components/ui/alert/index.d.ts +4 -0
- package/dist/components/ui/alert/variants.d.ts +5 -0
- package/dist/components/ui/button/Button.vue.d.ts +27 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/variants.d.ts +6 -0
- package/dist/components/ui/button-group/ButtonGroup.vue.d.ts +25 -0
- package/dist/components/ui/button-group/index.d.ts +2 -0
- package/dist/components/ui/card/Card.vue.d.ts +21 -0
- package/dist/components/ui/card/CardContent.vue.d.ts +21 -0
- package/dist/components/ui/card/CardDescription.vue.d.ts +21 -0
- package/dist/components/ui/card/CardFooter.vue.d.ts +21 -0
- package/dist/components/ui/card/CardHeader.vue.d.ts +21 -0
- package/dist/components/ui/card/CardTitle.vue.d.ts +21 -0
- package/dist/components/ui/card/index.d.ts +6 -0
- package/dist/components/ui/chart/Chart.vue.d.ts +92 -0
- package/dist/components/ui/chart/index.d.ts +2 -0
- package/dist/components/ui/chat/Chat.vue.d.ts +190 -0
- package/dist/components/ui/chat/ChatAttachments.vue.d.ts +11 -0
- package/dist/components/ui/chat/ChatCodeBlock.vue.d.ts +16 -0
- package/dist/components/ui/chat/code-block.d.ts +27 -0
- package/dist/components/ui/chat/index.d.ts +6 -0
- package/dist/components/ui/chat/types.d.ts +15 -0
- package/dist/components/ui/checkbox/Checkbox.vue.d.ts +29 -0
- package/dist/components/ui/checkbox/index.d.ts +1 -0
- package/dist/components/ui/commit/Commit.vue.d.ts +62 -0
- package/dist/components/ui/commit/index.d.ts +2 -0
- package/dist/components/ui/contribution-graph/ContributionGraph.vue.d.ts +87 -0
- package/dist/components/ui/contribution-graph/index.d.ts +2 -0
- package/dist/components/ui/data-table/DataTable.vue.d.ts +109 -0
- package/dist/components/ui/data-table/index.d.ts +2 -0
- package/dist/components/ui/date-picker/DatePicker.vue.d.ts +37 -0
- package/dist/components/ui/date-picker/index.d.ts +2 -0
- package/dist/components/ui/dialog/Dialog.vue.d.ts +25 -0
- package/dist/components/ui/dialog/DialogClose.vue.d.ts +18 -0
- package/dist/components/ui/dialog/DialogContent.vue.d.ts +39 -0
- package/dist/components/ui/dialog/DialogDescription.vue.d.ts +22 -0
- package/dist/components/ui/dialog/DialogFooter.vue.d.ts +21 -0
- package/dist/components/ui/dialog/DialogHeader.vue.d.ts +21 -0
- package/dist/components/ui/dialog/DialogOverlay.vue.d.ts +22 -0
- package/dist/components/ui/dialog/DialogScrollContent.vue.d.ts +36 -0
- package/dist/components/ui/dialog/DialogTitle.vue.d.ts +22 -0
- package/dist/components/ui/dialog/DialogTrigger.vue.d.ts +18 -0
- package/dist/components/ui/dialog/index.d.ts +10 -0
- package/dist/components/ui/diff/DiffTool.vue.d.ts +21 -0
- package/dist/components/ui/diff/diff-parser.d.ts +30 -0
- package/dist/components/ui/diff/diff-tool.d.ts +36 -0
- package/dist/components/ui/diff/index.d.ts +2 -0
- package/dist/components/ui/dropdown-menu/DropdownMenu.vue.d.ts +24 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue.d.ts +29 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuContent.vue.d.ts +36 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuGroup.vue.d.ts +18 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuItem.vue.d.ts +26 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuLabel.vue.d.ts +23 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue.d.ts +22 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuRadioItem.vue.d.ts +27 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSeparator.vue.d.ts +7 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuShortcut.vue.d.ts +21 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSub.vue.d.ts +22 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSubContent.vue.d.ts +38 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue.d.ts +23 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuTrigger.vue.d.ts +18 -0
- package/dist/components/ui/dropdown-menu/index.d.ts +15 -0
- package/dist/components/ui/gauge/Gauge.vue.d.ts +62 -0
- package/dist/components/ui/gauge/index.d.ts +2 -0
- package/dist/components/ui/git-graph/GitGraph.vue.d.ts +59 -0
- package/dist/components/ui/git-graph/index.d.ts +2 -0
- package/dist/components/ui/incident-timeline/IncidentTimeline.vue.d.ts +50 -0
- package/dist/components/ui/incident-timeline/index.d.ts +2 -0
- package/dist/components/ui/input/Input.vue.d.ts +14 -0
- package/dist/components/ui/input/InputControl.vue.d.ts +14 -0
- package/dist/components/ui/input/InputFieldGroup.vue.d.ts +21 -0
- package/dist/components/ui/input/index.d.ts +4 -0
- package/dist/components/ui/input/types.d.ts +31 -0
- package/dist/components/ui/kpi-card/KpiCard.vue.d.ts +46 -0
- package/dist/components/ui/kpi-card/index.d.ts +2 -0
- package/dist/components/ui/kpi-line-card/KpiLineCard.vue.d.ts +66 -0
- package/dist/components/ui/kpi-line-card/index.d.ts +2 -0
- package/dist/components/ui/model-selector/ModelSelector.vue.d.ts +41 -0
- package/dist/components/ui/model-selector/index.d.ts +2 -0
- package/dist/components/ui/model-selector/types.d.ts +12 -0
- package/dist/components/ui/network-graph/NetworkGraph.vue.d.ts +75 -0
- package/dist/components/ui/network-graph/index.d.ts +2 -0
- package/dist/components/ui/pagination/Pagination.vue.d.ts +29 -0
- package/dist/components/ui/pagination/PaginationContent.vue.d.ts +29 -0
- package/dist/components/ui/pagination/PaginationEllipsis.vue.d.ts +22 -0
- package/dist/components/ui/pagination/PaginationFirst.vue.d.ts +26 -0
- package/dist/components/ui/pagination/PaginationItem.vue.d.ts +28 -0
- package/dist/components/ui/pagination/PaginationLast.vue.d.ts +26 -0
- package/dist/components/ui/pagination/PaginationNext.vue.d.ts +26 -0
- package/dist/components/ui/pagination/PaginationPrevious.vue.d.ts +26 -0
- package/dist/components/ui/pagination/index.d.ts +8 -0
- package/dist/components/ui/profile/Profile.vue.d.ts +30 -0
- package/dist/components/ui/profile/ProfileGroup.vue.d.ts +37 -0
- package/dist/components/ui/profile/index.d.ts +4 -0
- package/dist/components/ui/progress/Progress.vue.d.ts +36 -0
- package/dist/components/ui/progress/index.d.ts +2 -0
- package/dist/components/ui/prompt-input/PromptInput.vue.d.ts +150 -0
- package/dist/components/ui/prompt-input/index.d.ts +2 -0
- package/dist/components/ui/prompt-input/types.d.ts +61 -0
- package/dist/components/ui/radio-group/RadioGroup.vue.d.ts +30 -0
- package/dist/components/ui/radio-group/RadioGroupItem.vue.d.ts +12 -0
- package/dist/components/ui/radio-group/RadioGroupOption.vue.d.ts +28 -0
- package/dist/components/ui/radio-group/index.d.ts +3 -0
- package/dist/components/ui/reasoning/Reasoning.vue.d.ts +40 -0
- package/dist/components/ui/reasoning/index.d.ts +2 -0
- package/dist/components/ui/reasoning/types.d.ts +26 -0
- package/dist/components/ui/select/Select.vue.d.ts +28 -0
- package/dist/components/ui/select/SelectContent.vue.d.ts +46 -0
- package/dist/components/ui/select/SelectGroup.vue.d.ts +18 -0
- package/dist/components/ui/select/SelectItem.vue.d.ts +26 -0
- package/dist/components/ui/select/SelectItemText.vue.d.ts +18 -0
- package/dist/components/ui/select/SelectLabel.vue.d.ts +22 -0
- package/dist/components/ui/select/SelectScrollDownButton.vue.d.ts +22 -0
- package/dist/components/ui/select/SelectScrollUpButton.vue.d.ts +22 -0
- package/dist/components/ui/select/SelectSeparator.vue.d.ts +7 -0
- package/dist/components/ui/select/SelectTrigger.vue.d.ts +25 -0
- package/dist/components/ui/select/SelectValue.vue.d.ts +18 -0
- package/dist/components/ui/select/index.d.ts +11 -0
- package/dist/components/ui/select/search.d.ts +18 -0
- package/dist/components/ui/separator/Separator.vue.d.ts +6 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/types.d.ts +7 -0
- package/dist/components/ui/shimmer/Shimmer.vue.d.ts +37 -0
- package/dist/components/ui/shimmer/index.d.ts +2 -0
- package/dist/components/ui/sidebar/Sidebar.vue.d.ts +24 -0
- package/dist/components/ui/sidebar/SidebarContent.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarFooter.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarGroup.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarGroupAction.vue.d.ts +24 -0
- package/dist/components/ui/sidebar/SidebarGroupContent.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarGroupLabel.vue.d.ts +24 -0
- package/dist/components/ui/sidebar/SidebarHeader.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarInput.vue.d.ts +6 -0
- package/dist/components/ui/sidebar/SidebarInset.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarMenu.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarMenuAction.vue.d.ts +25 -0
- package/dist/components/ui/sidebar/SidebarMenuBadge.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarMenuButton.vue.d.ts +25 -0
- package/dist/components/ui/sidebar/SidebarMenuButtonChild.vue.d.ts +30 -0
- package/dist/components/ui/sidebar/SidebarMenuItem.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarMenuSub.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarMenuSubButton.vue.d.ts +27 -0
- package/dist/components/ui/sidebar/SidebarMenuSubItem.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarProvider.vue.d.ts +36 -0
- package/dist/components/ui/sidebar/SidebarRail.vue.d.ts +21 -0
- package/dist/components/ui/sidebar/SidebarSeparator.vue.d.ts +6 -0
- package/dist/components/ui/sidebar/SidebarTrigger.vue.d.ts +6 -0
- package/dist/components/ui/sidebar/context.d.ts +19 -0
- package/dist/components/ui/sidebar/index.d.ts +26 -0
- package/dist/components/ui/sidebar/types.d.ts +11 -0
- package/dist/components/ui/sidebar/variants.d.ts +6 -0
- package/dist/components/ui/skeleton/Skeleton.vue.d.ts +18 -0
- package/dist/components/ui/skeleton/index.d.ts +2 -0
- package/dist/components/ui/sonner/Sonner.vue.d.ts +5 -0
- package/dist/components/ui/sonner/index.d.ts +2 -0
- package/dist/components/ui/spinner/Spinner.vue.d.ts +11 -0
- package/dist/components/ui/spinner/index.d.ts +1 -0
- package/dist/components/ui/stepper/Stepper.vue.d.ts +38 -0
- package/dist/components/ui/stepper/StepperDescription.vue.d.ts +22 -0
- package/dist/components/ui/stepper/StepperIndicator.vue.d.ts +30 -0
- package/dist/components/ui/stepper/StepperItem.vue.d.ts +24 -0
- package/dist/components/ui/stepper/StepperSeparator.vue.d.ts +7 -0
- package/dist/components/ui/stepper/StepperTitle.vue.d.ts +22 -0
- package/dist/components/ui/stepper/StepperTrigger.vue.d.ts +22 -0
- package/dist/components/ui/stepper/index.d.ts +7 -0
- package/dist/components/ui/switch/Switch.vue.d.ts +12 -0
- package/dist/components/ui/switch/index.d.ts +1 -0
- package/dist/components/ui/table/Table.vue.d.ts +22 -0
- package/dist/components/ui/table/TableBody.vue.d.ts +21 -0
- package/dist/components/ui/table/TableCaption.vue.d.ts +21 -0
- package/dist/components/ui/table/TableCell.vue.d.ts +22 -0
- package/dist/components/ui/table/TableEmpty.vue.d.ts +24 -0
- package/dist/components/ui/table/TableFooter.vue.d.ts +21 -0
- package/dist/components/ui/table/TableHead.vue.d.ts +21 -0
- package/dist/components/ui/table/TableHeader.vue.d.ts +21 -0
- package/dist/components/ui/table/TableRow.vue.d.ts +21 -0
- package/dist/components/ui/table/index.d.ts +9 -0
- package/dist/components/ui/tabs/Tabs.vue.d.ts +28 -0
- package/dist/components/ui/tabs/TabsContent.vue.d.ts +22 -0
- package/dist/components/ui/tabs/TabsList.vue.d.ts +22 -0
- package/dist/components/ui/tabs/TabsTrigger.vue.d.ts +22 -0
- package/dist/components/ui/tabs/index.d.ts +4 -0
- package/dist/components/ui/tag/Tag.vue.d.ts +35 -0
- package/dist/components/ui/tag/index.d.ts +2 -0
- package/dist/components/ui/tag/variants.d.ts +6 -0
- package/dist/components/ui/textarea/Textarea.vue.d.ts +14 -0
- package/dist/components/ui/textarea/TextareaControl.vue.d.ts +14 -0
- package/dist/components/ui/textarea/TextareaFieldGroup.vue.d.ts +21 -0
- package/dist/components/ui/textarea/index.d.ts +4 -0
- package/dist/components/ui/textarea/types.d.ts +32 -0
- package/dist/components/ui/tool/Tool.vue.d.ts +61 -0
- package/dist/components/ui/tool/index.d.ts +2 -0
- package/dist/components/ui/tooltip/Tooltip.vue.d.ts +24 -0
- package/dist/components/ui/tooltip/TooltipContent.vue.d.ts +32 -0
- package/dist/components/ui/tooltip/TooltipProvider.vue.d.ts +20 -0
- package/dist/components/ui/tooltip/TooltipTrigger.vue.d.ts +18 -0
- package/dist/components/ui/tooltip/index.d.ts +4 -0
- package/dist/docs/component-docs.d.ts +18 -0
- package/dist/docs/markdown.d.ts +27 -0
- package/dist/index.d.ts +45 -0
- package/dist/lib/code-highlight.d.ts +11 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/styles.css +3 -0
- package/package.json +76 -0
- package/public/r/accordion.json +52 -0
- package/public/r/alert.json +51 -0
- package/public/r/button-group.json +31 -0
- package/public/r/button.json +39 -0
- package/public/r/card.json +61 -0
- package/public/r/chart.json +31 -0
- package/public/r/chat.json +186 -0
- package/public/r/checkbox.json +34 -0
- package/public/r/commit.json +32 -0
- package/public/r/contribution-graph.json +63 -0
- package/public/r/data-table.json +197 -0
- package/public/r/date-picker.json +33 -0
- package/public/r/dialog.json +88 -0
- package/public/r/diff.json +71 -0
- package/public/r/dropdown-menu.json +112 -0
- package/public/r/gauge.json +31 -0
- package/public/r/git-graph.json +32 -0
- package/public/r/incident-timeline.json +64 -0
- package/public/r/input.json +49 -0
- package/public/r/kpi-card.json +32 -0
- package/public/r/kpi-line-card.json +32 -0
- package/public/r/model-selector.json +148 -0
- package/public/r/network-graph.json +33 -0
- package/public/r/pagination.json +95 -0
- package/public/r/profile.json +37 -0
- package/public/r/progress.json +31 -0
- package/public/r/prompt-input.json +293 -0
- package/public/r/radio-group.json +45 -0
- package/public/r/reasoning.json +38 -0
- package/public/r/registry.json +2512 -0
- package/public/r/select.json +100 -0
- package/public/r/separator.json +37 -0
- package/public/r/shimmer.json +31 -0
- package/public/r/sidebar.json +221 -0
- package/public/r/skeleton.json +31 -0
- package/public/r/sonner.json +33 -0
- package/public/r/spinner.json +31 -0
- package/public/r/stepper.json +70 -0
- package/public/r/switch.json +33 -0
- package/public/r/table.json +79 -0
- package/public/r/tabs.json +51 -0
- package/public/r/tag.json +39 -0
- package/public/r/textarea.json +49 -0
- package/public/r/tool.json +32 -0
- package/public/r/tooltip.json +51 -0
- package/registry.json +2512 -0
- package/src/components/docs/MarkdownContent.vue +106 -0
- package/src/components/ui/accordion/Accordion.vue +24 -0
- package/src/components/ui/accordion/AccordionContent.vue +62 -0
- package/src/components/ui/accordion/AccordionItem.vue +23 -0
- package/src/components/ui/accordion/AccordionTrigger.vue +38 -0
- package/src/components/ui/accordion/index.ts +4 -0
- package/src/components/ui/alert/Alert.vue +40 -0
- package/src/components/ui/alert/AlertDescription.vue +24 -0
- package/src/components/ui/alert/AlertTitle.vue +24 -0
- package/src/components/ui/alert/index.ts +4 -0
- package/src/components/ui/alert/variants.ts +19 -0
- package/src/components/ui/button/Button.vue +27 -0
- package/src/components/ui/button/index.ts +2 -0
- package/src/components/ui/button/variants.ts +32 -0
- package/src/components/ui/button-group/ButtonGroup.vue +31 -0
- package/src/components/ui/button-group/index.ts +2 -0
- package/src/components/ui/card/Card.vue +17 -0
- package/src/components/ui/card/CardContent.vue +14 -0
- package/src/components/ui/card/CardDescription.vue +14 -0
- package/src/components/ui/card/CardFooter.vue +14 -0
- package/src/components/ui/card/CardHeader.vue +17 -0
- package/src/components/ui/card/CardTitle.vue +14 -0
- package/src/components/ui/card/index.ts +6 -0
- package/src/components/ui/chart/Chart.vue +1042 -0
- package/src/components/ui/chart/index.ts +13 -0
- package/src/components/ui/chat/Chat.vue +1297 -0
- package/src/components/ui/chat/ChatAttachments.vue +278 -0
- package/src/components/ui/chat/ChatCodeBlock.vue +283 -0
- package/src/components/ui/chat/code-block.ts +30 -0
- package/src/components/ui/chat/index.ts +24 -0
- package/src/components/ui/chat/types.ts +23 -0
- package/src/components/ui/checkbox/Checkbox.vue +38 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/commit/Commit.vue +423 -0
- package/src/components/ui/commit/index.ts +9 -0
- package/src/components/ui/contribution-graph/ContributionGraph.vue +719 -0
- package/src/components/ui/contribution-graph/index.ts +9 -0
- package/src/components/ui/data-table/DataTable.vue +534 -0
- package/src/components/ui/data-table/index.ts +9 -0
- package/src/components/ui/date-picker/DatePicker.vue +649 -0
- package/src/components/ui/date-picker/index.ts +7 -0
- package/src/components/ui/dialog/Dialog.vue +19 -0
- package/src/components/ui/dialog/DialogClose.vue +17 -0
- package/src/components/ui/dialog/DialogContent.vue +60 -0
- package/src/components/ui/dialog/DialogDescription.vue +23 -0
- package/src/components/ui/dialog/DialogFooter.vue +17 -0
- package/src/components/ui/dialog/DialogHeader.vue +17 -0
- package/src/components/ui/dialog/DialogOverlay.vue +23 -0
- package/src/components/ui/dialog/DialogScrollContent.vue +69 -0
- package/src/components/ui/dialog/DialogTitle.vue +23 -0
- package/src/components/ui/dialog/DialogTrigger.vue +17 -0
- package/src/components/ui/dialog/index.ts +10 -0
- package/src/components/ui/diff/DiffTool.vue +513 -0
- package/src/components/ui/diff/diff-parser.ts +423 -0
- package/src/components/ui/diff/diff-tool.ts +39 -0
- package/src/components/ui/diff/index.ts +5 -0
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
- package/src/components/ui/dropdown-menu/index.ts +16 -0
- package/src/components/ui/gauge/Gauge.vue +725 -0
- package/src/components/ui/gauge/index.ts +9 -0
- package/src/components/ui/git-graph/GitGraph.vue +715 -0
- package/src/components/ui/git-graph/index.ts +9 -0
- package/src/components/ui/incident-timeline/IncidentTimeline.vue +360 -0
- package/src/components/ui/incident-timeline/index.ts +7 -0
- package/src/components/ui/input/Input.vue +159 -0
- package/src/components/ui/input/InputControl.vue +135 -0
- package/src/components/ui/input/InputFieldGroup.vue +14 -0
- package/src/components/ui/input/index.ts +9 -0
- package/src/components/ui/input/types.ts +34 -0
- package/src/components/ui/kpi-card/KpiCard.vue +268 -0
- package/src/components/ui/kpi-card/index.ts +9 -0
- package/src/components/ui/kpi-line-card/KpiLineCard.vue +622 -0
- package/src/components/ui/kpi-line-card/index.ts +11 -0
- package/src/components/ui/model-selector/ModelSelector.vue +328 -0
- package/src/components/ui/model-selector/index.ts +6 -0
- package/src/components/ui/model-selector/types.ts +15 -0
- package/src/components/ui/network-graph/NetworkGraph.vue +902 -0
- package/src/components/ui/network-graph/index.ts +7 -0
- package/src/components/ui/pagination/Pagination.vue +26 -0
- package/src/components/ui/pagination/PaginationContent.vue +24 -0
- package/src/components/ui/pagination/PaginationEllipsis.vue +27 -0
- package/src/components/ui/pagination/PaginationFirst.vue +33 -0
- package/src/components/ui/pagination/PaginationItem.vue +39 -0
- package/src/components/ui/pagination/PaginationLast.vue +33 -0
- package/src/components/ui/pagination/PaginationNext.vue +33 -0
- package/src/components/ui/pagination/PaginationPrevious.vue +33 -0
- package/src/components/ui/pagination/index.ts +8 -0
- package/src/components/ui/profile/Profile.vue +226 -0
- package/src/components/ui/profile/ProfileGroup.vue +96 -0
- package/src/components/ui/profile/index.ts +8 -0
- package/src/components/ui/progress/Progress.vue +271 -0
- package/src/components/ui/progress/index.ts +9 -0
- package/src/components/ui/prompt-input/PromptInput.vue +1094 -0
- package/src/components/ui/prompt-input/index.ts +14 -0
- package/src/components/ui/prompt-input/types.ts +78 -0
- package/src/components/ui/radio-group/RadioGroup.vue +36 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +45 -0
- package/src/components/ui/radio-group/RadioGroupOption.vue +80 -0
- package/src/components/ui/radio-group/index.ts +3 -0
- package/src/components/ui/reasoning/Reasoning.vue +278 -0
- package/src/components/ui/reasoning/index.ts +8 -0
- package/src/components/ui/reasoning/types.ts +29 -0
- package/src/components/ui/select/Select.vue +19 -0
- package/src/components/ui/select/SelectContent.vue +166 -0
- package/src/components/ui/select/SelectGroup.vue +23 -0
- package/src/components/ui/select/SelectItem.vue +97 -0
- package/src/components/ui/select/SelectItemText.vue +15 -0
- package/src/components/ui/select/SelectLabel.vue +17 -0
- package/src/components/ui/select/SelectScrollDownButton.vue +26 -0
- package/src/components/ui/select/SelectScrollUpButton.vue +26 -0
- package/src/components/ui/select/SelectSeparator.vue +19 -0
- package/src/components/ui/select/SelectTrigger.vue +33 -0
- package/src/components/ui/select/SelectValue.vue +15 -0
- package/src/components/ui/select/index.ts +11 -0
- package/src/components/ui/select/search.ts +26 -0
- package/src/components/ui/separator/Separator.vue +30 -0
- package/src/components/ui/separator/index.ts +5 -0
- package/src/components/ui/separator/types.ts +9 -0
- package/src/components/ui/shimmer/Shimmer.vue +110 -0
- package/src/components/ui/shimmer/index.ts +5 -0
- package/src/components/ui/sidebar/Sidebar.vue +142 -0
- package/src/components/ui/sidebar/SidebarContent.vue +18 -0
- package/src/components/ui/sidebar/SidebarFooter.vue +18 -0
- package/src/components/ui/sidebar/SidebarGroup.vue +18 -0
- package/src/components/ui/sidebar/SidebarGroupAction.vue +31 -0
- package/src/components/ui/sidebar/SidebarGroupContent.vue +18 -0
- package/src/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
- package/src/components/ui/sidebar/SidebarHeader.vue +18 -0
- package/src/components/ui/sidebar/SidebarInput.vue +26 -0
- package/src/components/ui/sidebar/SidebarInset.vue +23 -0
- package/src/components/ui/sidebar/SidebarMenu.vue +18 -0
- package/src/components/ui/sidebar/SidebarMenuAction.vue +34 -0
- package/src/components/ui/sidebar/SidebarMenuBadge.vue +25 -0
- package/src/components/ui/sidebar/SidebarMenuButton.vue +37 -0
- package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +38 -0
- package/src/components/ui/sidebar/SidebarMenuItem.vue +18 -0
- package/src/components/ui/sidebar/SidebarMenuSub.vue +18 -0
- package/src/components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
- package/src/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
- package/src/components/ui/sidebar/SidebarProvider.vue +119 -0
- package/src/components/ui/sidebar/SidebarRail.vue +35 -0
- package/src/components/ui/sidebar/SidebarSeparator.vue +18 -0
- package/src/components/ui/sidebar/SidebarTrigger.vue +28 -0
- package/src/components/ui/sidebar/context.ts +39 -0
- package/src/components/ui/sidebar/index.ts +43 -0
- package/src/components/ui/sidebar/types.ts +13 -0
- package/src/components/ui/sidebar/variants.ts +25 -0
- package/src/components/ui/skeleton/Skeleton.vue +53 -0
- package/src/components/ui/skeleton/index.ts +5 -0
- package/src/components/ui/sonner/Sonner.vue +69 -0
- package/src/components/ui/sonner/index.ts +12 -0
- package/src/components/ui/spinner/Spinner.vue +33 -0
- package/src/components/ui/spinner/index.ts +1 -0
- package/src/components/ui/stepper/Stepper.vue +29 -0
- package/src/components/ui/stepper/StepperDescription.vue +30 -0
- package/src/components/ui/stepper/StepperIndicator.vue +50 -0
- package/src/components/ui/stepper/StepperItem.vue +28 -0
- package/src/components/ui/stepper/StepperSeparator.vue +25 -0
- package/src/components/ui/stepper/StepperTitle.vue +27 -0
- package/src/components/ui/stepper/StepperTrigger.vue +27 -0
- package/src/components/ui/stepper/index.ts +7 -0
- package/src/components/ui/switch/Switch.vue +41 -0
- package/src/components/ui/switch/index.ts +1 -0
- package/src/components/ui/table/Table.vue +23 -0
- package/src/components/ui/table/TableBody.vue +17 -0
- package/src/components/ui/table/TableCaption.vue +17 -0
- package/src/components/ui/table/TableCell.vue +24 -0
- package/src/components/ui/table/TableEmpty.vue +31 -0
- package/src/components/ui/table/TableFooter.vue +17 -0
- package/src/components/ui/table/TableHead.vue +22 -0
- package/src/components/ui/table/TableHeader.vue +17 -0
- package/src/components/ui/table/TableRow.vue +22 -0
- package/src/components/ui/table/index.ts +9 -0
- package/src/components/ui/tabs/Tabs.vue +24 -0
- package/src/components/ui/tabs/TabsContent.vue +22 -0
- package/src/components/ui/tabs/TabsList.vue +27 -0
- package/src/components/ui/tabs/TabsTrigger.vue +27 -0
- package/src/components/ui/tabs/index.ts +4 -0
- package/src/components/ui/tag/Tag.vue +55 -0
- package/src/components/ui/tag/index.ts +2 -0
- package/src/components/ui/tag/variants.ts +29 -0
- package/src/components/ui/textarea/Textarea.vue +159 -0
- package/src/components/ui/textarea/TextareaControl.vue +120 -0
- package/src/components/ui/textarea/TextareaFieldGroup.vue +14 -0
- package/src/components/ui/textarea/index.ts +10 -0
- package/src/components/ui/textarea/types.ts +35 -0
- package/src/components/ui/tool/Tool.vue +304 -0
- package/src/components/ui/tool/index.ts +7 -0
- package/src/components/ui/tooltip/Tooltip.vue +19 -0
- package/src/components/ui/tooltip/TooltipContent.vue +44 -0
- package/src/components/ui/tooltip/TooltipProvider.vue +14 -0
- package/src/components/ui/tooltip/TooltipTrigger.vue +15 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/lib/code-highlight.ts +220 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +684 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://shadcn-vue.com/schema/registry-item.json",
|
|
3
|
+
"name": "dialog",
|
|
4
|
+
"title": "Dialog",
|
|
5
|
+
"description": "A modal window for focused tasks with overlay, title, description, footer actions, and close controls.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@lucide/vue",
|
|
8
|
+
"@vueuse/core",
|
|
9
|
+
"clsx",
|
|
10
|
+
"reka-ui",
|
|
11
|
+
"tailwind-merge"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "src/components/ui/dialog/Dialog.vue",
|
|
16
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogRootEmits, DialogRootProps } from \"reka-ui\"\nimport { DialogRoot, useForwardPropsEmits } from \"reka-ui\"\n\nconst props = defineProps<DialogRootProps>()\nconst emits = defineEmits<DialogRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DialogRoot\n v-slot=\"slotProps\"\n data-slot=\"dialog\"\n v-bind=\"forwarded\"\n >\n <slot v-bind=\"slotProps\" />\n </DialogRoot>\n</template>\n",
|
|
17
|
+
"type": "registry:ui",
|
|
18
|
+
"target": "components/ui/dialog/Dialog.vue"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "src/components/ui/dialog/DialogTrigger.vue",
|
|
22
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogTriggerProps } from \"reka-ui\"\nimport { DialogTrigger, useForwardProps } from \"reka-ui\"\n\nconst props = defineProps<DialogTriggerProps>()\n\nconst forwardedProps = useForwardProps(props)\n</script>\n\n<template>\n <DialogTrigger\n data-slot=\"dialog-trigger\"\n v-bind=\"forwardedProps\"\n >\n <slot />\n </DialogTrigger>\n</template>\n",
|
|
23
|
+
"type": "registry:ui",
|
|
24
|
+
"target": "components/ui/dialog/DialogTrigger.vue"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "src/components/ui/dialog/DialogContent.vue",
|
|
28
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogContentEmits, DialogContentProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { X } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DialogClose,\n DialogContent,\n DialogPortal,\n useForwardPropsEmits,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\nimport DialogOverlay from \"./DialogOverlay.vue\"\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(\n defineProps<DialogContentProps & {\n class?: HTMLAttributes[\"class\"]\n showCloseButton?: boolean\n }>(),\n {\n showCloseButton: true,\n },\n)\nconst emits = defineEmits<DialogContentEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"showCloseButton\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DialogPortal>\n <DialogOverlay />\n <DialogContent\n data-slot=\"dialog-content\"\n v-bind=\"{ ...$attrs, ...forwarded }\"\n :class=\"\n cn(\n 'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg',\n props.class,\n )\n \"\n >\n <slot />\n\n <DialogClose\n v-if=\"props.showCloseButton\"\n data-slot=\"dialog-close\"\n class=\"absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background focus:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n >\n <X aria-hidden=\"true\" />\n <span class=\"sr-only\">Close</span>\n </DialogClose>\n </DialogContent>\n </DialogPortal>\n</template>\n",
|
|
29
|
+
"type": "registry:ui",
|
|
30
|
+
"target": "components/ui/dialog/DialogContent.vue"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "src/components/ui/dialog/DialogScrollContent.vue",
|
|
34
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogContentEmits, DialogContentProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { X } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DialogClose,\n DialogContent,\n DialogOverlay,\n DialogPortal,\n useForwardPropsEmits,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<DialogContentProps & { class?: HTMLAttributes[\"class\"] }>()\nconst emits = defineEmits<DialogContentEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nfunction handlePointerDownOutside(event: CustomEvent) {\n const originalEvent = event.detail?.originalEvent as PointerEvent | undefined\n const target = originalEvent?.target\n\n if (!(target instanceof HTMLElement) || originalEvent === undefined) return\n\n if (\n originalEvent.offsetX > target.clientWidth\n || originalEvent.offsetY > target.clientHeight\n ) {\n event.preventDefault()\n }\n}\n</script>\n\n<template>\n <DialogPortal>\n <DialogOverlay\n class=\"fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-[var(--overlay)] backdrop-blur-[4px] [-webkit-backdrop-filter:blur(4px)] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\"\n >\n <DialogContent\n data-slot=\"dialog-scroll-content\"\n v-bind=\"{ ...$attrs, ...forwarded }\"\n :class=\"\n cn(\n 'relative z-50 my-8 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg duration-200 sm:max-w-lg',\n props.class,\n )\n \"\n @pointer-down-outside=\"handlePointerDownOutside\"\n >\n <slot />\n\n <DialogClose\n data-slot=\"dialog-close\"\n class=\"absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background focus:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n >\n <X aria-hidden=\"true\" />\n <span class=\"sr-only\">Close</span>\n </DialogClose>\n </DialogContent>\n </DialogOverlay>\n </DialogPortal>\n</template>\n",
|
|
35
|
+
"type": "registry:ui",
|
|
36
|
+
"target": "components/ui/dialog/DialogScrollContent.vue"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "src/components/ui/dialog/DialogOverlay.vue",
|
|
40
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogOverlayProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { DialogOverlay, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DialogOverlayProps & { class?: HTMLAttributes[\"class\"] }>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <DialogOverlay\n data-slot=\"dialog-overlay\"\n v-bind=\"forwardedProps\"\n :class=\"cn('fixed inset-0 z-50 bg-[var(--overlay)] backdrop-blur-[4px] [-webkit-backdrop-filter:blur(4px)] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0', props.class)\"\n >\n <slot />\n </DialogOverlay>\n</template>\n",
|
|
41
|
+
"type": "registry:ui",
|
|
42
|
+
"target": "components/ui/dialog/DialogOverlay.vue"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "src/components/ui/dialog/DialogHeader.vue",
|
|
46
|
+
"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\n data-slot=\"dialog-header\"\n :class=\"cn('flex flex-col gap-2 text-center sm:text-left', props.class)\"\n >\n <slot />\n </div>\n</template>\n",
|
|
47
|
+
"type": "registry:ui",
|
|
48
|
+
"target": "components/ui/dialog/DialogHeader.vue"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "src/components/ui/dialog/DialogFooter.vue",
|
|
52
|
+
"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\n data-slot=\"dialog-footer\"\n :class=\"cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)\"\n >\n <slot />\n </div>\n</template>\n",
|
|
53
|
+
"type": "registry:ui",
|
|
54
|
+
"target": "components/ui/dialog/DialogFooter.vue"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"path": "src/components/ui/dialog/DialogTitle.vue",
|
|
58
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogTitleProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { DialogTitle, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DialogTitleProps & { class?: HTMLAttributes[\"class\"] }>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <DialogTitle\n data-slot=\"dialog-title\"\n v-bind=\"forwardedProps\"\n :class=\"cn('text-lg font-semibold leading-none', props.class)\"\n >\n <slot />\n </DialogTitle>\n</template>\n",
|
|
59
|
+
"type": "registry:ui",
|
|
60
|
+
"target": "components/ui/dialog/DialogTitle.vue"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "src/components/ui/dialog/DialogDescription.vue",
|
|
64
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogDescriptionProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { DialogDescription, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes[\"class\"] }>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <DialogDescription\n data-slot=\"dialog-description\"\n v-bind=\"forwardedProps\"\n :class=\"cn('text-sm text-muted-foreground', props.class)\"\n >\n <slot />\n </DialogDescription>\n</template>\n",
|
|
65
|
+
"type": "registry:ui",
|
|
66
|
+
"target": "components/ui/dialog/DialogDescription.vue"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"path": "src/components/ui/dialog/DialogClose.vue",
|
|
70
|
+
"content": "<script setup lang=\"ts\">\nimport type { DialogCloseProps } from \"reka-ui\"\nimport { DialogClose, useForwardProps } from \"reka-ui\"\n\nconst props = defineProps<DialogCloseProps>()\n\nconst forwardedProps = useForwardProps(props)\n</script>\n\n<template>\n <DialogClose\n data-slot=\"dialog-close\"\n v-bind=\"forwardedProps\"\n >\n <slot />\n </DialogClose>\n</template>\n",
|
|
71
|
+
"type": "registry:ui",
|
|
72
|
+
"target": "components/ui/dialog/DialogClose.vue"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"path": "src/components/ui/dialog/index.ts",
|
|
76
|
+
"content": "export { default as Dialog } from \"./Dialog.vue\"\nexport { default as DialogClose } from \"./DialogClose.vue\"\nexport { default as DialogContent } from \"./DialogContent.vue\"\nexport { default as DialogDescription } from \"./DialogDescription.vue\"\nexport { default as DialogFooter } from \"./DialogFooter.vue\"\nexport { default as DialogHeader } from \"./DialogHeader.vue\"\nexport { default as DialogOverlay } from \"./DialogOverlay.vue\"\nexport { default as DialogScrollContent } from \"./DialogScrollContent.vue\"\nexport { default as DialogTitle } from \"./DialogTitle.vue\"\nexport { default as DialogTrigger } from \"./DialogTrigger.vue\"\n",
|
|
77
|
+
"type": "registry:ui",
|
|
78
|
+
"target": "components/ui/dialog/index.ts"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"path": "src/lib/utils.ts",
|
|
82
|
+
"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",
|
|
83
|
+
"type": "registry:lib",
|
|
84
|
+
"target": "lib/utils.ts"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"type": "registry:ui"
|
|
88
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://shadcn-vue.com/schema/registry-item.json",
|
|
3
|
+
"name": "diff",
|
|
4
|
+
"title": "Diff Tool",
|
|
5
|
+
"description": "A standalone split diff viewer with previous and next panes, line numbers, syntax highlighting, copy, and expand controls.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@lucide/vue",
|
|
8
|
+
"class-variance-authority",
|
|
9
|
+
"clsx",
|
|
10
|
+
"highlight.js",
|
|
11
|
+
"reka-ui",
|
|
12
|
+
"tailwind-merge"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "src/components/ui/button/Button.vue",
|
|
17
|
+
"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",
|
|
18
|
+
"type": "registry:ui",
|
|
19
|
+
"target": "components/ui/button/Button.vue"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "src/components/ui/button/variants.ts",
|
|
23
|
+
"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",
|
|
24
|
+
"type": "registry:ui",
|
|
25
|
+
"target": "components/ui/button/variants.ts"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "src/components/ui/button/index.ts",
|
|
29
|
+
"content": "export { default as Button } from \"./Button.vue\"\nexport { buttonVariants, type ButtonVariants } from \"./variants\"\n",
|
|
30
|
+
"type": "registry:ui",
|
|
31
|
+
"target": "components/ui/button/index.ts"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "src/components/ui/diff/DiffTool.vue",
|
|
35
|
+
"content": "<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from \"vue\"\nimport { CheckIcon, CopyIcon, Maximize2Icon, Minimize2Icon } from \"@lucide/vue\"\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nimport {\n diffToolDefaults,\n type DiffToolEmits,\n type DiffToolProps,\n} from \"./diff-tool\"\nimport { createSplitDiffModel, splitDiffCopyText } from \"./diff-parser\"\n\nconst props = withDefaults(\n defineProps<DiffToolProps>(),\n diffToolDefaults,\n)\n\nconst emit = defineEmits<DiffToolEmits>()\n\nconst copied = ref(false)\nconst expanded = ref(false)\nlet copiedTimer: ReturnType<typeof setTimeout> | undefined\nlet previousBodyOverflow: string | undefined\nlet syncingPaneScroll = false\n\nconst diffModel = computed(() => createSplitDiffModel({\n code: props.code,\n highlight: props.highlight,\n language: props.language,\n leftCode: props.leftCode,\n leftLabel: props.leftLabel,\n rightCode: props.rightCode,\n rightLabel: props.rightLabel,\n}))\nconst visibleLineCount = computed(() =>\n Math.max(1, Math.trunc(props.maxVisibleLines)),\n)\nconst shouldScroll = computed(() => diffModel.value.rows.length > visibleLineCount.value)\nconst lineStyle = computed(() => ({\n gridTemplateColumns: props.lineNumbers\n ? `${diffModel.value.lineNumberWidth} max-content`\n : \"max-content\",\n}))\nconst collapsedScrollStyle = computed(() => shouldScroll.value\n ? {\n maxHeight: `calc(${visibleLineCount.value} * 1.5rem + 2.5rem)`,\n }\n : undefined)\nconst languageLabel = computed(() => diffModel.value.language || props.language || props.label)\nconst copyText = computed(() => splitDiffCopyText(diffModel.value))\n\nasync function copyDiff() {\n try {\n if (typeof navigator !== \"undefined\") {\n await navigator.clipboard?.writeText(copyText.value)\n }\n } catch {\n // Clipboard permissions vary by browser; the component still reports intent.\n }\n\n emit(\"copy\", copyText.value)\n copied.value = true\n\n if (copiedTimer) clearTimeout(copiedTimer)\n copiedTimer = setTimeout(() => {\n copied.value = false\n }, 1200)\n}\n\nfunction openExpanded() {\n expanded.value = true\n}\n\nfunction closeExpanded() {\n expanded.value = false\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (event.key === \"Escape\") closeExpanded()\n}\n\nfunction syncPaneScroll(event: Event) {\n if (syncingPaneScroll || !(event.currentTarget instanceof HTMLElement)) return\n\n const sourcePane = event.currentTarget\n const scrollContainer = sourcePane.closest('[data-slot=\"diff-tool-scroll\"]')\n const panes = scrollContainer?.querySelectorAll<HTMLElement>('[data-slot=\"diff-tool-pane\"]')\n\n if (!panes) return\n\n syncingPaneScroll = true\n\n panes.forEach((pane) => {\n if (pane !== sourcePane) {\n pane.scrollLeft = sourcePane.scrollLeft\n }\n })\n\n requestAnimationFrame(() => {\n syncingPaneScroll = false\n })\n}\n\nwatch(expanded, (isExpanded) => {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") return\n\n if (isExpanded) {\n previousBodyOverflow = document.body.style.overflow\n document.body.style.overflow = \"hidden\"\n window.addEventListener(\"keydown\", handleKeydown)\n return\n }\n\n document.body.style.overflow = previousBodyOverflow ?? \"\"\n previousBodyOverflow = undefined\n window.removeEventListener(\"keydown\", handleKeydown)\n})\n\nonBeforeUnmount(() => {\n if (copiedTimer) clearTimeout(copiedTimer)\n\n if (typeof document !== \"undefined\") {\n document.body.style.overflow = previousBodyOverflow ?? \"\"\n }\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"keydown\", handleKeydown)\n }\n})\n</script>\n\n<template>\n <div\n data-slot=\"diff-tool\"\n :data-scrollable=\"shouldScroll ? '' : undefined\"\n :class=\"\n cn(\n 'relative w-full min-w-0 overflow-hidden rounded-xl border font-mono text-[13px] leading-6 shadow-sm',\n props.class,\n )\n \"\n >\n <div\n data-slot=\"diff-tool-toolbar\"\n class=\"flex h-11 items-center justify-between gap-3 border-b px-3\"\n >\n <div class=\"flex min-w-0 items-center gap-2\">\n <span\n data-slot=\"diff-tool-label\"\n class=\"truncate text-xs font-medium\"\n >\n {{ props.label }}\n </span>\n <span\n data-slot=\"diff-tool-language\"\n class=\"shrink-0 text-xs\"\n >\n {{ languageLabel }}\n </span>\n </div>\n <div class=\"flex shrink-0 items-center gap-1\">\n <Button\n data-slot=\"diff-tool-copy\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n class=\"size-7 rounded-md\"\n :aria-label=\"copied ? props.copiedLabel : props.copyLabel\"\n :title=\"copied ? props.copiedLabel : props.copyLabel\"\n @click=\"copyDiff\"\n >\n <CheckIcon v-if=\"copied\" />\n <CopyIcon v-else />\n </Button>\n <Button\n data-slot=\"diff-tool-expand-trigger\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n class=\"size-7 rounded-md\"\n :aria-label=\"props.expandLabel\"\n :title=\"props.expandLabel\"\n @click=\"openExpanded\"\n >\n <Maximize2Icon />\n </Button>\n </div>\n </div>\n\n <div\n data-slot=\"diff-tool-scroll\"\n :data-scrollable=\"shouldScroll ? '' : undefined\"\n class=\"min-w-0 max-w-full overflow-y-auto overflow-x-hidden [tab-size:2]\"\n :style=\"collapsedScrollStyle\"\n >\n <div\n data-slot=\"diff-tool-header-row\"\n class=\"sticky top-0 z-10 grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)]\"\n >\n <div\n data-slot=\"diff-tool-side-header\"\n class=\"min-w-0 border-b px-3 py-2 text-left text-xs font-medium\"\n >\n {{ diffModel.leftLabel }}\n </div>\n <div\n data-slot=\"diff-tool-side-header\"\n data-side=\"right\"\n class=\"min-w-0 border-b border-l px-3 py-2 text-left text-xs font-medium\"\n >\n {{ diffModel.rightLabel }}\n </div>\n </div>\n <div\n data-slot=\"diff-tool-panes\"\n class=\"grid min-w-0 grid-cols-[minmax(0,1fr)_minmax(0,1fr)]\"\n >\n <div\n data-slot=\"diff-tool-pane\"\n class=\"min-w-0 overflow-x-auto overflow-y-hidden\"\n @scroll=\"syncPaneScroll\"\n >\n <div\n data-slot=\"diff-tool-pane-content\"\n class=\"inline-block min-w-full\"\n >\n <div\n v-for=\"row in diffModel.rows\"\n :key=\"`${row.key}-left`\"\n data-slot=\"diff-tool-row\"\n data-side=\"left\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-cell\"\n :data-diff=\"row.left.kind\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-line\"\n class=\"grid w-max min-w-full\"\n :style=\"lineStyle\"\n >\n <span\n v-if=\"props.lineNumbers\"\n data-slot=\"diff-tool-line-number\"\n aria-hidden=\"true\"\n class=\"select-none border-r pl-2 pr-3 text-right tabular-nums\"\n >\n {{ row.left.lineNumber }}\n </span>\n <code\n data-slot=\"diff-tool-line-content\"\n class=\"block min-h-[1lh] min-w-max pl-3 pr-4 whitespace-pre\"\n ><span\n v-if=\"row.left.isEmpty\"\n aria-hidden=\"true\"\n > </span><span\n v-else\n data-slot=\"diff-tool-token\"\n v-html=\"row.left.html\"\n /></code>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div\n data-slot=\"diff-tool-pane\"\n data-side=\"right\"\n class=\"min-w-0 overflow-x-auto overflow-y-hidden border-l\"\n @scroll=\"syncPaneScroll\"\n >\n <div\n data-slot=\"diff-tool-pane-content\"\n class=\"inline-block min-w-full\"\n >\n <div\n v-for=\"row in diffModel.rows\"\n :key=\"`${row.key}-right`\"\n data-slot=\"diff-tool-row\"\n data-side=\"right\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-cell\"\n data-side=\"right\"\n :data-diff=\"row.right.kind\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-line\"\n class=\"grid w-max min-w-full\"\n :style=\"lineStyle\"\n >\n <span\n v-if=\"props.lineNumbers\"\n data-slot=\"diff-tool-line-number\"\n aria-hidden=\"true\"\n class=\"select-none border-r pl-2 pr-3 text-right tabular-nums\"\n >\n {{ row.right.lineNumber }}\n </span>\n <code\n data-slot=\"diff-tool-line-content\"\n class=\"block min-h-[1lh] min-w-max pl-3 pr-4 whitespace-pre\"\n ><span\n v-if=\"row.right.isEmpty\"\n aria-hidden=\"true\"\n > </span><span\n v-else\n data-slot=\"diff-tool-token\"\n v-html=\"row.right.html\"\n /></code>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <Teleport to=\"body\">\n <div\n v-if=\"expanded\"\n data-slot=\"diff-tool-dialog-overlay\"\n class=\"fixed inset-0 z-50 flex items-center justify-center p-4\"\n @click.self=\"closeExpanded\"\n >\n <section\n data-slot=\"diff-tool-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"props.expandLabel\"\n class=\"grid w-full max-w-7xl grid-rows-[auto_minmax(0,1fr)] overflow-hidden rounded-2xl border shadow-2xl\"\n >\n <h2 class=\"sr-only\">\n {{ props.expandLabel }}\n </h2>\n <div\n data-slot=\"diff-tool-dialog-header\"\n class=\"flex h-11 items-center justify-between gap-3 border-b px-4\"\n >\n <span\n data-slot=\"diff-tool-label\"\n class=\"min-w-0 truncate text-xs font-medium\"\n >\n {{ props.label }}\n </span>\n <div class=\"flex shrink-0 items-center gap-1\">\n <Button\n data-slot=\"diff-tool-copy\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n class=\"size-8 rounded-md\"\n :aria-label=\"copied ? props.copiedLabel : props.copyLabel\"\n :title=\"copied ? props.copiedLabel : props.copyLabel\"\n @click=\"copyDiff\"\n >\n <CheckIcon v-if=\"copied\" />\n <CopyIcon v-else />\n </Button>\n <Button\n data-slot=\"diff-tool-collapse-trigger\"\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n class=\"size-8 rounded-md\"\n :aria-label=\"props.closeLabel\"\n :title=\"props.closeLabel\"\n @click=\"closeExpanded\"\n >\n <Minimize2Icon />\n </Button>\n </div>\n </div>\n\n <div\n data-slot=\"diff-tool-scroll\"\n data-expanded\n class=\"min-w-0 max-w-full max-h-[calc(90vh-4rem)] overflow-y-auto overflow-x-hidden [tab-size:2]\"\n >\n <div\n data-slot=\"diff-tool-header-row\"\n class=\"sticky top-0 z-10 grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)]\"\n >\n <div\n data-slot=\"diff-tool-side-header\"\n class=\"min-w-0 border-b px-3 py-2 text-left text-xs font-medium\"\n >\n {{ diffModel.leftLabel }}\n </div>\n <div\n data-slot=\"diff-tool-side-header\"\n data-side=\"right\"\n class=\"min-w-0 border-b border-l px-3 py-2 text-left text-xs font-medium\"\n >\n {{ diffModel.rightLabel }}\n </div>\n </div>\n <div\n data-slot=\"diff-tool-panes\"\n class=\"grid min-w-0 grid-cols-[minmax(0,1fr)_minmax(0,1fr)]\"\n >\n <div\n data-slot=\"diff-tool-pane\"\n class=\"min-w-0 overflow-x-auto overflow-y-hidden\"\n @scroll=\"syncPaneScroll\"\n >\n <div\n data-slot=\"diff-tool-pane-content\"\n class=\"inline-block min-w-full\"\n >\n <div\n v-for=\"row in diffModel.rows\"\n :key=\"`${row.key}-dialog-left`\"\n data-slot=\"diff-tool-row\"\n data-side=\"left\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-cell\"\n :data-diff=\"row.left.kind\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-line\"\n class=\"grid w-max min-w-full\"\n :style=\"lineStyle\"\n >\n <span\n v-if=\"props.lineNumbers\"\n data-slot=\"diff-tool-line-number\"\n aria-hidden=\"true\"\n class=\"select-none border-r pl-2 pr-3 text-right tabular-nums\"\n >\n {{ row.left.lineNumber }}\n </span>\n <code\n data-slot=\"diff-tool-line-content\"\n class=\"block min-h-[1lh] min-w-max pl-3 pr-4 whitespace-pre\"\n ><span\n v-if=\"row.left.isEmpty\"\n aria-hidden=\"true\"\n > </span><span\n v-else\n data-slot=\"diff-tool-token\"\n v-html=\"row.left.html\"\n /></code>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div\n data-slot=\"diff-tool-pane\"\n data-side=\"right\"\n class=\"min-w-0 overflow-x-auto overflow-y-hidden border-l\"\n @scroll=\"syncPaneScroll\"\n >\n <div\n data-slot=\"diff-tool-pane-content\"\n class=\"inline-block min-w-full\"\n >\n <div\n v-for=\"row in diffModel.rows\"\n :key=\"`${row.key}-dialog-right`\"\n data-slot=\"diff-tool-row\"\n data-side=\"right\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-cell\"\n data-side=\"right\"\n :data-diff=\"row.right.kind\"\n class=\"w-max min-w-full\"\n >\n <div\n data-slot=\"diff-tool-line\"\n class=\"grid w-max min-w-full\"\n :style=\"lineStyle\"\n >\n <span\n v-if=\"props.lineNumbers\"\n data-slot=\"diff-tool-line-number\"\n aria-hidden=\"true\"\n class=\"select-none border-r pl-2 pr-3 text-right tabular-nums\"\n >\n {{ row.right.lineNumber }}\n </span>\n <code\n data-slot=\"diff-tool-line-content\"\n class=\"block min-h-[1lh] min-w-max pl-3 pr-4 whitespace-pre\"\n ><span\n v-if=\"row.right.isEmpty\"\n aria-hidden=\"true\"\n > </span><span\n v-else\n data-slot=\"diff-tool-token\"\n v-html=\"row.right.html\"\n /></code>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </section>\n </div>\n </Teleport>\n</template>\n",
|
|
36
|
+
"type": "registry:ui",
|
|
37
|
+
"target": "components/ui/diff/DiffTool.vue"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"path": "src/components/ui/diff/diff-tool.ts",
|
|
41
|
+
"content": "import type { HTMLAttributes } from \"vue\"\n\nexport interface DiffToolProps {\n class?: HTMLAttributes[\"class\"]\n closeLabel?: string\n code?: string\n copiedLabel?: string\n copyLabel?: string\n expandLabel?: string\n highlight?: boolean\n label?: string\n language?: string\n leftCode?: string\n leftLabel?: string\n lineNumbers?: boolean\n maxVisibleLines?: number\n rightCode?: string\n rightLabel?: string\n}\n\nexport type DiffToolEmits = {\n copy: [code: string]\n}\n\nexport const diffToolDefaults = {\n closeLabel: \"닫기\",\n code: \"\",\n copiedLabel: \"복사됨\",\n copyLabel: \"diff 복사\",\n expandLabel: \"크게 보기\",\n highlight: true,\n label: \"Diff\",\n leftCode: \"\",\n leftLabel: \"원본\",\n lineNumbers: true,\n maxVisibleLines: 20,\n rightCode: \"\",\n rightLabel: \"변경본\",\n} satisfies Partial<DiffToolProps>\n",
|
|
42
|
+
"type": "registry:ui",
|
|
43
|
+
"target": "components/ui/diff/diff-tool.ts"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"path": "src/components/ui/diff/diff-parser.ts",
|
|
47
|
+
"content": "import {\n codeLineNumberWidth,\n createCodeLines,\n normalizeCode,\n normalizeCodeLanguage,\n splitTextLines,\n} from \"@/lib/code-highlight\"\n\nexport type SplitDiffKind = \"addition\" | \"context\" | \"deletion\" | \"empty\"\n\nexport type SplitDiffSide = {\n content: string\n html: string\n isEmpty: boolean\n kind: SplitDiffKind\n lineNumber?: number\n}\n\nexport type SplitDiffRow = {\n key: string\n left: SplitDiffSide\n right: SplitDiffSide\n}\n\nexport type SplitDiffModel = {\n language?: string\n leftLabel: string\n lineNumberWidth: string\n rightLabel: string\n rows: SplitDiffRow[]\n}\n\ntype PendingSide = Omit<SplitDiffSide, \"html\" | \"isEmpty\">\n\nconst hunkPattern = /^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/\nconst languageByExtension: Record<string, string> = {\n cjs: \"javascript\",\n css: \"css\",\n htm: \"html\",\n html: \"html\",\n js: \"javascript\",\n json: \"json\",\n jsonc: \"json\",\n jsx: \"javascript\",\n md: \"markdown\",\n mjs: \"javascript\",\n py: \"python\",\n rb: \"ruby\",\n scss: \"scss\",\n sh: \"bash\",\n ts: \"typescript\",\n tsx: \"typescript\",\n vue: \"vue\",\n xml: \"xml\",\n yaml: \"yaml\",\n yml: \"yaml\",\n}\n\nexport function createSplitDiffModel({\n code,\n highlight = true,\n language,\n leftCode,\n leftLabel = \"원본\",\n rightCode,\n rightLabel = \"변경본\",\n}: {\n code?: string\n highlight?: boolean\n language?: string\n leftCode?: string\n leftLabel?: string\n rightCode?: string\n rightLabel?: string\n}): SplitDiffModel {\n const normalizedCode = normalizeCode(code ?? \"\").trim()\n const normalizedLeftCode = normalizeCode(leftCode ?? \"\")\n const normalizedRightCode = normalizeCode(rightCode ?? \"\")\n const parsed = normalizedCode\n ? parseUnifiedDiff(normalizedCode, leftLabel, rightLabel)\n : createPairedDiff(normalizedLeftCode, normalizedRightCode, leftLabel, rightLabel)\n const normalizedLanguage = normalizeCodeLanguage(language) ??\n inferCodeLanguage(leftLabel, rightLabel)\n\n return applyHighlight({\n ...parsed,\n language: normalizedLanguage,\n rows: parsed.rows.length > 0 ? parsed.rows : createEmptyRows(),\n }, highlight)\n}\n\nexport function splitDiffCopyText(model: SplitDiffModel) {\n return [\n model.leftLabel,\n sideText(model.rows, \"left\"),\n \"\",\n model.rightLabel,\n sideText(model.rows, \"right\"),\n ].join(\"\\n\")\n}\n\nfunction parseUnifiedDiff(\n code: string,\n leftLabel: string,\n rightLabel: string,\n): Omit<SplitDiffModel, \"language\" | \"lineNumberWidth\"> {\n const rows: SplitDiffRow[] = []\n const lines = splitTextLines(code)\n const pendingDeletions: PendingSide[] = []\n let leftLineNumber = 0\n let rightLineNumber = 0\n let inHunk = false\n\n for (const line of lines) {\n if (line.startsWith(\"diff \") || line.startsWith(\"index \")) {\n flushPendingDeletions(rows, pendingDeletions)\n inHunk = false\n continue\n }\n\n if (!inHunk && (line.startsWith(\"--- \") || line.startsWith(\"+++ \"))) {\n continue\n }\n\n const hunkMatch = line.match(hunkPattern)\n\n if (hunkMatch) {\n flushPendingDeletions(rows, pendingDeletions)\n leftLineNumber = Number(hunkMatch[1])\n rightLineNumber = Number(hunkMatch[2])\n inHunk = true\n continue\n }\n\n if (!inHunk || line.startsWith(\"\\\\ No newline\")) {\n continue\n }\n\n if (line.startsWith(\"-\")) {\n pendingDeletions.push({\n content: line.slice(1),\n kind: \"deletion\",\n lineNumber: leftLineNumber,\n })\n leftLineNumber += 1\n continue\n }\n\n if (line.startsWith(\"+\")) {\n const leftSide = pendingDeletions.shift()\n rows.push(createCodeRow(\n rows.length,\n leftSide,\n {\n content: line.slice(1),\n kind: \"addition\",\n lineNumber: rightLineNumber,\n },\n ))\n rightLineNumber += 1\n continue\n }\n\n flushPendingDeletions(rows, pendingDeletions)\n\n const content = line.startsWith(\" \") ? line.slice(1) : line\n rows.push(createCodeRow(\n rows.length,\n {\n content,\n kind: \"context\",\n lineNumber: leftLineNumber,\n },\n {\n content,\n kind: \"context\",\n lineNumber: rightLineNumber,\n },\n ))\n leftLineNumber += 1\n rightLineNumber += 1\n }\n\n flushPendingDeletions(rows, pendingDeletions)\n\n return {\n leftLabel,\n rightLabel,\n rows,\n }\n}\n\nfunction createPairedDiff(\n leftCode: string,\n rightCode: string,\n leftLabel: string,\n rightLabel: string,\n): Omit<SplitDiffModel, \"language\" | \"lineNumberWidth\"> {\n const operations = createLineOperations(splitTextLines(leftCode), splitTextLines(rightCode))\n const rows: SplitDiffRow[] = []\n const pendingDeletions: PendingSide[] = []\n\n for (const operation of operations) {\n if (operation.type === \"deletion\") {\n pendingDeletions.push({\n content: operation.content,\n kind: \"deletion\",\n lineNumber: operation.leftLineNumber,\n })\n continue\n }\n\n if (operation.type === \"addition\") {\n const leftSide = pendingDeletions.shift()\n rows.push(createCodeRow(\n rows.length,\n leftSide,\n {\n content: operation.content,\n kind: \"addition\",\n lineNumber: operation.rightLineNumber,\n },\n ))\n continue\n }\n\n flushPendingDeletions(rows, pendingDeletions)\n rows.push(createCodeRow(\n rows.length,\n {\n content: operation.content,\n kind: \"context\",\n lineNumber: operation.leftLineNumber,\n },\n {\n content: operation.content,\n kind: \"context\",\n lineNumber: operation.rightLineNumber,\n },\n ))\n }\n\n flushPendingDeletions(rows, pendingDeletions)\n\n return {\n leftLabel,\n rightLabel,\n rows,\n }\n}\n\nfunction createLineOperations(leftLines: string[], rightLines: string[]) {\n const distances = Array.from(\n { length: leftLines.length + 1 },\n () => Array<number>(rightLines.length + 1).fill(0),\n )\n\n for (let leftIndex = leftLines.length - 1; leftIndex >= 0; leftIndex -= 1) {\n for (let rightIndex = rightLines.length - 1; rightIndex >= 0; rightIndex -= 1) {\n distances[leftIndex][rightIndex] = leftLines[leftIndex] === rightLines[rightIndex]\n ? distances[leftIndex + 1][rightIndex + 1] + 1\n : Math.max(\n distances[leftIndex + 1][rightIndex],\n distances[leftIndex][rightIndex + 1],\n )\n }\n }\n\n const operations: Array<{\n content: string\n leftLineNumber?: number\n rightLineNumber?: number\n type: \"addition\" | \"context\" | \"deletion\"\n }> = []\n let leftIndex = 0\n let rightIndex = 0\n\n while (leftIndex < leftLines.length && rightIndex < rightLines.length) {\n if (leftLines[leftIndex] === rightLines[rightIndex]) {\n operations.push({\n content: leftLines[leftIndex],\n leftLineNumber: leftIndex + 1,\n rightLineNumber: rightIndex + 1,\n type: \"context\",\n })\n leftIndex += 1\n rightIndex += 1\n continue\n }\n\n if (distances[leftIndex + 1][rightIndex] >= distances[leftIndex][rightIndex + 1]) {\n operations.push({\n content: leftLines[leftIndex],\n leftLineNumber: leftIndex + 1,\n type: \"deletion\",\n })\n leftIndex += 1\n continue\n }\n\n operations.push({\n content: rightLines[rightIndex],\n rightLineNumber: rightIndex + 1,\n type: \"addition\",\n })\n rightIndex += 1\n }\n\n for (; leftIndex < leftLines.length; leftIndex += 1) {\n operations.push({\n content: leftLines[leftIndex],\n leftLineNumber: leftIndex + 1,\n type: \"deletion\",\n })\n }\n\n for (; rightIndex < rightLines.length; rightIndex += 1) {\n operations.push({\n content: rightLines[rightIndex],\n rightLineNumber: rightIndex + 1,\n type: \"addition\",\n })\n }\n\n return operations\n}\n\nfunction applyHighlight(\n model: Omit<SplitDiffModel, \"lineNumberWidth\">,\n highlight: boolean,\n): SplitDiffModel {\n const leftCode = model.rows.map((row) => row.left.content).join(\"\\n\")\n const rightCode = model.rows.map((row) => row.right.content).join(\"\\n\")\n const leftLines = createCodeLines(leftCode, model.language, highlight)\n const rightLines = createCodeLines(rightCode, model.language, highlight)\n const maxLineNumber = model.rows.reduce((current, row) => Math.max(\n current,\n row.left.lineNumber ?? 0,\n row.right.lineNumber ?? 0,\n ), 1)\n\n return {\n ...model,\n lineNumberWidth: codeLineNumberWidth(maxLineNumber),\n rows: model.rows.map((row, index) => ({\n ...row,\n left: {\n ...row.left,\n html: leftLines[index]?.html ?? \"\",\n isEmpty: row.left.content.length === 0,\n },\n right: {\n ...row.right,\n html: rightLines[index]?.html ?? \"\",\n isEmpty: row.right.content.length === 0,\n },\n })),\n }\n}\n\nfunction createCodeRow(\n index: number,\n left?: PendingSide,\n right?: PendingSide,\n): SplitDiffRow {\n return {\n key: `code-${index}`,\n left: createSide(left),\n right: createSide(right),\n }\n}\n\nfunction createEmptyRows() {\n return [\n createCodeRow(0, {\n content: \"\",\n kind: \"context\",\n lineNumber: 1,\n }, {\n content: \"\",\n kind: \"context\",\n lineNumber: 1,\n }),\n ]\n}\n\nfunction createSide(side?: PendingSide): SplitDiffSide {\n if (!side) {\n return {\n content: \"\",\n html: \"\",\n isEmpty: true,\n kind: \"empty\",\n }\n }\n\n return {\n ...side,\n html: \"\",\n isEmpty: side.content.length === 0,\n }\n}\n\nfunction flushPendingDeletions(rows: SplitDiffRow[], pendingDeletions: PendingSide[]) {\n while (pendingDeletions.length > 0) {\n rows.push(createCodeRow(rows.length, pendingDeletions.shift()))\n }\n}\n\nfunction inferCodeLanguage(leftLabel: string, rightLabel: string) {\n const extension = [rightLabel, leftLabel]\n .map((label) => label.match(/\\.([a-z0-9]+)(?:\\s|$)/i)?.[1]?.toLowerCase())\n .find(Boolean)\n\n return extension ? languageByExtension[extension] : undefined\n}\n\nfunction sideText(rows: SplitDiffRow[], side: \"left\" | \"right\") {\n return rows\n .map((row) => row[side].kind === \"empty\" ? \"\" : row[side].content)\n .join(\"\\n\")\n .replace(/\\n+$/g, \"\")\n}\n",
|
|
48
|
+
"type": "registry:ui",
|
|
49
|
+
"target": "components/ui/diff/diff-parser.ts"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"path": "src/components/ui/diff/index.ts",
|
|
53
|
+
"content": "export { default as DiffTool } from \"./DiffTool.vue\"\nexport type {\n DiffToolEmits,\n DiffToolProps,\n} from \"./diff-tool\"\n",
|
|
54
|
+
"type": "registry:ui",
|
|
55
|
+
"target": "components/ui/diff/index.ts"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"path": "src/lib/code-highlight.ts",
|
|
59
|
+
"content": "import type { LanguageFn } from \"highlight.js\"\nimport hljs from \"highlight.js/lib/core\"\nimport bash from \"highlight.js/lib/languages/bash\"\nimport css from \"highlight.js/lib/languages/css\"\nimport diff from \"highlight.js/lib/languages/diff\"\nimport javascript from \"highlight.js/lib/languages/javascript\"\nimport json from \"highlight.js/lib/languages/json\"\nimport markdown from \"highlight.js/lib/languages/markdown\"\nimport python from \"highlight.js/lib/languages/python\"\nimport ruby from \"highlight.js/lib/languages/ruby\"\nimport scss from \"highlight.js/lib/languages/scss\"\nimport shell from \"highlight.js/lib/languages/shell\"\nimport typescript from \"highlight.js/lib/languages/typescript\"\nimport xml from \"highlight.js/lib/languages/xml\"\nimport yaml from \"highlight.js/lib/languages/yaml\"\n\nexport type CodeLine = {\n diffType?: \"addition\" | \"deletion\" | \"metadata\"\n html: string\n isEmpty: boolean\n number: number\n}\n\nconst highlighter = hljs.newInstance()\nconst registeredLanguages = [\n \"bash\",\n \"css\",\n \"diff\",\n \"javascript\",\n \"json\",\n \"markdown\",\n \"python\",\n \"ruby\",\n \"scss\",\n \"shell\",\n \"typescript\",\n \"xml\",\n \"yaml\",\n]\nconst languageAliases: Record<string, string> = {\n bash: \"bash\",\n cjs: \"javascript\",\n diff: \"diff\",\n patch: \"diff\",\n htm: \"xml\",\n html: \"xml\",\n js: \"javascript\",\n jsonc: \"json\",\n jsx: \"javascript\",\n md: \"markdown\",\n mjs: \"javascript\",\n py: \"python\",\n rb: \"ruby\",\n sh: \"bash\",\n shell: \"shell\",\n ts: \"typescript\",\n tsx: \"typescript\",\n vue: \"xml\",\n xml: \"xml\",\n yml: \"yaml\",\n}\n\nregisterLanguage(\"bash\", bash)\nregisterLanguage(\"css\", css)\nregisterLanguage(\"diff\", diff)\nregisterLanguage(\"javascript\", javascript)\nregisterLanguage(\"json\", json)\nregisterLanguage(\"markdown\", markdown)\nregisterLanguage(\"python\", python)\nregisterLanguage(\"ruby\", ruby)\nregisterLanguage(\"scss\", scss)\nregisterLanguage(\"shell\", shell)\nregisterLanguage(\"typescript\", typescript)\nregisterLanguage(\"xml\", xml)\nregisterLanguage(\"yaml\", yaml)\n\nhighlighter.registerAliases([\"htm\", \"html\", \"vue\"], { languageName: \"xml\" })\nhighlighter.registerAliases([\"patch\"], { languageName: \"diff\" })\nhighlighter.registerAliases([\"js\", \"jsx\", \"mjs\", \"cjs\"], { languageName: \"javascript\" })\nhighlighter.registerAliases([\"ts\", \"tsx\"], { languageName: \"typescript\" })\nhighlighter.registerAliases([\"md\"], { languageName: \"markdown\" })\nhighlighter.registerAliases([\"py\"], { languageName: \"python\" })\nhighlighter.registerAliases([\"rb\"], { languageName: \"ruby\" })\nhighlighter.registerAliases([\"sh\"], { languageName: \"bash\" })\nhighlighter.registerAliases([\"yml\"], { languageName: \"yaml\" })\n\nexport function createCodeLines(\n code: string,\n language?: string,\n highlight = true,\n): CodeLine[] {\n const normalizedCode = normalizeCode(code)\n const normalizedLanguage = normalizeCodeLanguage(language)\n const sourceLines = splitTextLines(normalizedCode)\n const highlightedLines = splitHighlightedHtmlLines(\n highlight ? highlightCode(normalizedCode, normalizedLanguage) : escapeHtml(normalizedCode),\n )\n\n return sourceLines.map((line, index) => ({\n diffType: normalizedLanguage === \"diff\" ? diffLineType(line) : undefined,\n html: highlightedLines[index] ?? \"\",\n isEmpty: line.length === 0,\n number: index + 1,\n }))\n}\n\nexport function codeLineNumberWidth(lineCount: number) {\n const digitCount = Math.max(2, String(Math.max(1, lineCount)).length)\n\n return `calc(${digitCount}ch + 1.5rem)`\n}\n\nfunction registerLanguage(language: string, defineLanguage: LanguageFn) {\n highlighter.registerLanguage(language, defineLanguage)\n}\n\nfunction highlightCode(code: string, language?: string) {\n const normalizedLanguage = normalizeCodeLanguage(language)\n\n try {\n if (normalizedLanguage && highlighter.getLanguage(normalizedLanguage)) {\n return highlighter.highlight(code, {\n ignoreIllegals: true,\n language: normalizedLanguage,\n }).value\n }\n\n return highlighter.highlightAuto(code, registeredLanguages).value\n } catch {\n return escapeHtml(code)\n }\n}\n\nexport function normalizeCodeLanguage(language?: string) {\n const normalized = language?.trim().toLowerCase()\n\n if (!normalized) return undefined\n\n return languageAliases[normalized] ?? normalized\n}\n\nfunction diffLineType(line: string): CodeLine[\"diffType\"] {\n if (line.startsWith(\"+\") && !line.startsWith(\"+++\")) return \"addition\"\n if (line.startsWith(\"-\") && !line.startsWith(\"---\")) return \"deletion\"\n if (\n line.startsWith(\"@@\") ||\n line.startsWith(\"diff \") ||\n line.startsWith(\"index \") ||\n line.startsWith(\"---\") ||\n line.startsWith(\"+++\")\n ) {\n return \"metadata\"\n }\n\n return undefined\n}\n\nexport function normalizeCode(code: string) {\n return code.replace(/\\r\\n?/g, \"\\n\")\n}\n\nexport function splitTextLines(text: string) {\n return text.length > 0 ? text.split(\"\\n\") : [\"\"]\n}\n\nfunction splitHighlightedHtmlLines(html: string) {\n const lines = [\"\"]\n const openTags: string[] = []\n const spanTagPattern = /<\\/?span\\b[^>]*>/gi\n let index = 0\n\n for (const match of html.matchAll(spanTagPattern)) {\n appendHtmlText(lines, openTags, html.slice(index, match.index))\n appendSpanTag(lines, openTags, match[0])\n index = match.index + match[0].length\n }\n\n appendHtmlText(lines, openTags, html.slice(index))\n\n return lines\n}\n\nfunction appendHtmlText(lines: string[], openTags: string[], text: string) {\n const parts = text.split(\"\\n\")\n\n for (const [index, part] of parts.entries()) {\n if (index > 0) {\n closeOpenTags(lines, openTags)\n lines.push(openTags.join(\"\"))\n }\n\n lines[lines.length - 1] += part\n }\n}\n\nfunction appendSpanTag(lines: string[], openTags: string[], tag: string) {\n lines[lines.length - 1] += tag\n\n if (tag.startsWith(\"</\")) {\n openTags.pop()\n return\n }\n\n openTags.push(tag)\n}\n\nfunction closeOpenTags(lines: string[], openTags: string[]) {\n for (let index = openTags.length - 1; index >= 0; index -= 1) {\n lines[lines.length - 1] += \"</span>\"\n }\n}\n\nfunction escapeHtml(text: string) {\n return text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n}\n",
|
|
60
|
+
"type": "registry:lib",
|
|
61
|
+
"target": "lib/code-highlight.ts"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"path": "src/lib/utils.ts",
|
|
65
|
+
"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",
|
|
66
|
+
"type": "registry:lib",
|
|
67
|
+
"target": "lib/utils.ts"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"type": "registry:ui"
|
|
71
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://shadcn-vue.com/schema/registry-item.json",
|
|
3
|
+
"name": "dropdown-menu",
|
|
4
|
+
"title": "Dropdown Menu",
|
|
5
|
+
"description": "A menu primitive for grouped actions, checkbox items, radio items, shortcuts, and submenus.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@lucide/vue",
|
|
8
|
+
"@vueuse/core",
|
|
9
|
+
"clsx",
|
|
10
|
+
"reka-ui",
|
|
11
|
+
"tailwind-merge"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenu.vue",
|
|
16
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuRootEmits, DropdownMenuRootProps } from \"reka-ui\"\nimport { DropdownMenuRoot, useForwardPropsEmits } from \"reka-ui\"\n\nconst props = defineProps<DropdownMenuRootProps>()\nconst emits = defineEmits<DropdownMenuRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DropdownMenuRoot\n v-slot=\"slotProps\"\n data-slot=\"dropdown-menu\"\n v-bind=\"forwarded\"\n >\n <slot v-bind=\"slotProps\" />\n </DropdownMenuRoot>\n</template>\n",
|
|
17
|
+
"type": "registry:ui",
|
|
18
|
+
"target": "components/ui/dropdown-menu/DropdownMenu.vue"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuTrigger.vue",
|
|
22
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuTriggerProps } from \"reka-ui\"\nimport { DropdownMenuTrigger, useForwardProps } from \"reka-ui\"\n\nconst props = defineProps<DropdownMenuTriggerProps>()\n\nconst forwardedProps = useForwardProps(props)\n</script>\n\n<template>\n <DropdownMenuTrigger\n data-slot=\"dropdown-menu-trigger\"\n v-bind=\"forwardedProps\"\n >\n <slot />\n </DropdownMenuTrigger>\n</template>\n",
|
|
23
|
+
"type": "registry:ui",
|
|
24
|
+
"target": "components/ui/dropdown-menu/DropdownMenuTrigger.vue"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuContent.vue",
|
|
28
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuContentEmits, DropdownMenuContentProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DropdownMenuContent,\n DropdownMenuPortal,\n useForwardPropsEmits,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(\n defineProps<DropdownMenuContentProps & { class?: HTMLAttributes[\"class\"] }>(),\n {\n sideOffset: 4,\n },\n)\nconst emits = defineEmits<DropdownMenuContentEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DropdownMenuPortal>\n <DropdownMenuContent\n data-slot=\"dropdown-menu-content\"\n v-bind=\"{ ...$attrs, ...forwarded }\"\n :class=\"cn('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 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', props.class)\"\n >\n <slot />\n </DropdownMenuContent>\n </DropdownMenuPortal>\n</template>\n",
|
|
29
|
+
"type": "registry:ui",
|
|
30
|
+
"target": "components/ui/dropdown-menu/DropdownMenuContent.vue"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuGroup.vue",
|
|
34
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuGroupProps } from \"reka-ui\"\nimport { DropdownMenuGroup } from \"reka-ui\"\n\nconst props = defineProps<DropdownMenuGroupProps>()\n</script>\n\n<template>\n <DropdownMenuGroup\n data-slot=\"dropdown-menu-group\"\n v-bind=\"props\"\n >\n <slot />\n </DropdownMenuGroup>\n</template>\n",
|
|
35
|
+
"type": "registry:ui",
|
|
36
|
+
"target": "components/ui/dropdown-menu/DropdownMenuGroup.vue"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuItem.vue",
|
|
40
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuItemProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { DropdownMenuItem, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = withDefaults(defineProps<DropdownMenuItemProps & {\n class?: HTMLAttributes[\"class\"]\n inset?: boolean\n variant?: \"default\" | \"destructive\"\n}>(), {\n variant: \"default\",\n})\n\nconst delegatedProps = reactiveOmit(props, \"inset\", \"variant\", \"class\")\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <DropdownMenuItem\n data-slot=\"dropdown-menu-item\"\n :data-inset=\"inset ? '' : undefined\"\n :data-variant=\"variant\"\n v-bind=\"forwardedProps\"\n :class=\"cn('focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=text-])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4', props.class)\"\n >\n <slot />\n </DropdownMenuItem>\n</template>\n",
|
|
41
|
+
"type": "registry:ui",
|
|
42
|
+
"target": "components/ui/dropdown-menu/DropdownMenuItem.vue"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue",
|
|
46
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { Check } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DropdownMenuCheckboxItem,\n DropdownMenuItemIndicator,\n useForwardPropsEmits,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes[\"class\"] }>()\nconst emits = defineEmits<DropdownMenuCheckboxItemEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DropdownMenuCheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n v-bind=\"forwarded\"\n :class=\"cn(\n 'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',\n props.class,\n )\"\n >\n <span class=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuItemIndicator>\n <slot name=\"indicator-icon\">\n <Check class=\"size-4\" />\n </slot>\n </DropdownMenuItemIndicator>\n </span>\n <slot />\n </DropdownMenuCheckboxItem>\n</template>\n",
|
|
47
|
+
"type": "registry:ui",
|
|
48
|
+
"target": "components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue",
|
|
52
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from \"reka-ui\"\nimport {\n DropdownMenuRadioGroup,\n useForwardPropsEmits,\n} from \"reka-ui\"\n\nconst props = defineProps<DropdownMenuRadioGroupProps>()\nconst emits = defineEmits<DropdownMenuRadioGroupEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DropdownMenuRadioGroup\n data-slot=\"dropdown-menu-radio-group\"\n v-bind=\"forwarded\"\n >\n <slot />\n </DropdownMenuRadioGroup>\n</template>\n",
|
|
53
|
+
"type": "registry:ui",
|
|
54
|
+
"target": "components/ui/dropdown-menu/DropdownMenuRadioGroup.vue"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue",
|
|
58
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { Circle } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DropdownMenuItemIndicator,\n DropdownMenuRadioItem,\n useForwardPropsEmits,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes[\"class\"] }>()\n\nconst emits = defineEmits<DropdownMenuRadioItemEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DropdownMenuRadioItem\n data-slot=\"dropdown-menu-radio-item\"\n v-bind=\"forwarded\"\n :class=\"cn(\n 'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',\n props.class,\n )\"\n >\n <span class=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuItemIndicator>\n <slot name=\"indicator-icon\">\n <Circle class=\"size-2 fill-current\" />\n </slot>\n </DropdownMenuItemIndicator>\n </span>\n <slot />\n </DropdownMenuRadioItem>\n</template>\n",
|
|
59
|
+
"type": "registry:ui",
|
|
60
|
+
"target": "components/ui/dropdown-menu/DropdownMenuRadioItem.vue"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuLabel.vue",
|
|
64
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuLabelProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport { DropdownMenuLabel, useForwardProps } from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes[\"class\"], inset?: boolean }>()\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"inset\")\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <DropdownMenuLabel\n data-slot=\"dropdown-menu-label\"\n :data-inset=\"inset ? '' : undefined\"\n v-bind=\"forwardedProps\"\n :class=\"cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', props.class)\"\n >\n <slot />\n </DropdownMenuLabel>\n</template>\n",
|
|
65
|
+
"type": "registry:ui",
|
|
66
|
+
"target": "components/ui/dropdown-menu/DropdownMenuLabel.vue"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuSeparator.vue",
|
|
70
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSeparatorProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DropdownMenuSeparator,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DropdownMenuSeparatorProps & {\n class?: HTMLAttributes[\"class\"]\n}>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n</script>\n\n<template>\n <DropdownMenuSeparator\n data-slot=\"dropdown-menu-separator\"\n v-bind=\"delegatedProps\"\n :class=\"cn('bg-border -mx-1 my-1 h-px', props.class)\"\n />\n</template>\n",
|
|
71
|
+
"type": "registry:ui",
|
|
72
|
+
"target": "components/ui/dropdown-menu/DropdownMenuSeparator.vue"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuShortcut.vue",
|
|
76
|
+
"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 <span\n data-slot=\"dropdown-menu-shortcut\"\n :class=\"cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)\"\n >\n <slot />\n </span>\n</template>\n",
|
|
77
|
+
"type": "registry:ui",
|
|
78
|
+
"target": "components/ui/dropdown-menu/DropdownMenuShortcut.vue"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuSub.vue",
|
|
82
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSubEmits, DropdownMenuSubProps } from \"reka-ui\"\nimport {\n DropdownMenuSub,\n useForwardPropsEmits,\n} from \"reka-ui\"\n\nconst props = defineProps<DropdownMenuSubProps>()\nconst emits = defineEmits<DropdownMenuSubEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <DropdownMenuSub data-slot=\"dropdown-menu-sub\" v-bind=\"forwarded\">\n <slot />\n </DropdownMenuSub>\n</template>\n",
|
|
83
|
+
"type": "registry:ui",
|
|
84
|
+
"target": "components/ui/dropdown-menu/DropdownMenuSub.vue"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue",
|
|
88
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSubTriggerProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { ChevronRight } from \"@lucide/vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DropdownMenuSubTrigger,\n useForwardProps,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes[\"class\"], inset?: boolean }>()\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"inset\")\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <DropdownMenuSubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n v-bind=\"forwardedProps\"\n :data-inset=\"inset ? '' : undefined\"\n :class=\"cn(\n 'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4 data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=text-])]:text-muted-foreground',\n props.class,\n )\"\n >\n <slot />\n <ChevronRight class=\"ml-auto size-4\" />\n </DropdownMenuSubTrigger>\n</template>\n",
|
|
89
|
+
"type": "registry:ui",
|
|
90
|
+
"target": "components/ui/dropdown-menu/DropdownMenuSubTrigger.vue"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"path": "src/components/ui/dropdown-menu/DropdownMenuSubContent.vue",
|
|
94
|
+
"content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport { reactiveOmit } from \"@vueuse/core\"\nimport {\n DropdownMenuSubContent,\n useForwardPropsEmits,\n} from \"reka-ui\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes[\"class\"] }>()\nconst emits = defineEmits<DropdownMenuSubContentEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\")\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DropdownMenuSubContent\n data-slot=\"dropdown-menu-sub-content\"\n v-bind=\"forwarded\"\n :class=\"cn('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 min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg', props.class)\"\n >\n <slot />\n </DropdownMenuSubContent>\n</template>\n",
|
|
95
|
+
"type": "registry:ui",
|
|
96
|
+
"target": "components/ui/dropdown-menu/DropdownMenuSubContent.vue"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"path": "src/components/ui/dropdown-menu/index.ts",
|
|
100
|
+
"content": "export { default as DropdownMenu } from \"./DropdownMenu.vue\"\n\nexport { default as DropdownMenuCheckboxItem } from \"./DropdownMenuCheckboxItem.vue\"\nexport { default as DropdownMenuContent } from \"./DropdownMenuContent.vue\"\nexport { default as DropdownMenuGroup } from \"./DropdownMenuGroup.vue\"\nexport { default as DropdownMenuItem } from \"./DropdownMenuItem.vue\"\nexport { default as DropdownMenuLabel } from \"./DropdownMenuLabel.vue\"\nexport { default as DropdownMenuRadioGroup } from \"./DropdownMenuRadioGroup.vue\"\nexport { default as DropdownMenuRadioItem } from \"./DropdownMenuRadioItem.vue\"\nexport { default as DropdownMenuSeparator } from \"./DropdownMenuSeparator.vue\"\nexport { default as DropdownMenuShortcut } from \"./DropdownMenuShortcut.vue\"\nexport { default as DropdownMenuSub } from \"./DropdownMenuSub.vue\"\nexport { default as DropdownMenuSubContent } from \"./DropdownMenuSubContent.vue\"\nexport { default as DropdownMenuSubTrigger } from \"./DropdownMenuSubTrigger.vue\"\nexport { default as DropdownMenuTrigger } from \"./DropdownMenuTrigger.vue\"\nexport { DropdownMenuPortal } from \"reka-ui\"\n",
|
|
101
|
+
"type": "registry:ui",
|
|
102
|
+
"target": "components/ui/dropdown-menu/index.ts"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"path": "src/lib/utils.ts",
|
|
106
|
+
"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",
|
|
107
|
+
"type": "registry:lib",
|
|
108
|
+
"target": "lib/utils.ts"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"type": "registry:ui"
|
|
112
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://shadcn-vue.com/schema/registry-item.json",
|
|
3
|
+
"name": "gauge",
|
|
4
|
+
"title": "Gauge",
|
|
5
|
+
"description": "A semi-circular gauge and linear meter chart for single KPI values with markers, labels, and formatted values.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/components/ui/gauge/Gauge.vue",
|
|
13
|
+
"content": "<script setup lang=\"ts\">\nimport { computed } from \"vue\"\nimport type { CSSProperties, HTMLAttributes } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nexport type GaugeType = \"gauge\" | \"meter\"\nexport type GaugeSize = \"sm\" | \"default\" | \"lg\"\nexport type GaugeValue = number | string | null | undefined\n\nexport interface GaugeSegment {\n color?: string\n label?: string\n max?: number\n min?: number\n}\n\nexport interface GaugeValueFormatterContext {\n markerValue?: number | null\n max: number\n min: number\n percent: number\n type: GaugeType\n}\n\nexport type GaugeValueFormatter = (\n value: number,\n context?: GaugeValueFormatterContext,\n) => number | string\n\ninterface GaugeProps {\n class?: HTMLAttributes[\"class\"]\n color?: string\n description?: string\n emptyLabel?: string\n framed?: boolean\n label?: string\n markerColor?: string\n markerLabel?: string\n markerValue?: GaugeValue\n max?: number\n maxLabel?: string\n min?: number\n minLabel?: string\n segments?: GaugeSegment[]\n showLabels?: boolean\n showMarker?: boolean\n showNeedle?: boolean\n showValue?: boolean\n size?: GaugeSize\n summary?: string\n title?: string\n trackColor?: string\n type?: GaugeType\n unit?: string\n value?: GaugeValue\n valueFormatter?: GaugeValueFormatter\n}\n\ninterface NormalizedGaugeSegment {\n color: string\n label?: string\n max: number\n min: number\n}\n\ninterface RadialPoint {\n x: number\n y: number\n}\n\nconst props = withDefaults(defineProps<GaugeProps>(), {\n color: \"oklch(0.3 0.11 255)\",\n emptyLabel: \"No gauge value.\",\n framed: true,\n markerColor: \"oklch(0.58 0 0)\",\n max: 100,\n min: 0,\n showLabels: true,\n showMarker: true,\n showNeedle: false,\n showValue: true,\n size: \"default\",\n trackColor: \"oklch(0.88 0 0)\",\n type: \"gauge\",\n})\n\nconst gaugeCenter = {\n x: 130,\n y: 128,\n}\nconst gaugeRadius = 100\nconst radialViewBox = \"0 0 286 162\"\n\nconst numberFormatter = computed(() =>\n new Intl.NumberFormat(undefined, {\n maximumFractionDigits: 2,\n }),\n)\n\nconst sizeClasses = computed(() => {\n if (props.size === \"sm\") {\n return {\n body: \"gap-3 p-3\",\n description: \"text-xs\",\n header: \"gap-2 px-3 py-2.5\",\n labelClass: \"text-[11px]\",\n markerFontSize: 11,\n meter: \"h-2\",\n radial: \"max-w-[15rem]\",\n scaleFontSize: 12,\n strokeWidth: 18,\n summary: \"text-xs\",\n title: \"text-xs\",\n valueFontSize: 28,\n }\n }\n\n if (props.size === \"lg\") {\n return {\n body: \"gap-5 p-5\",\n description: \"text-sm\",\n header: \"gap-3 px-5 py-4\",\n labelClass: \"text-sm\",\n markerFontSize: 13,\n meter: \"h-3\",\n radial: \"max-w-[22rem]\",\n scaleFontSize: 13,\n strokeWidth: 24,\n summary: \"text-sm\",\n title: \"text-sm\",\n valueFontSize: 40,\n }\n }\n\n return {\n body: \"gap-4 p-4\",\n description: \"text-sm\",\n header: \"gap-2.5 px-4 py-3\",\n labelClass: \"text-xs\",\n markerFontSize: 12,\n meter: \"h-2.5\",\n radial: \"max-w-[18rem]\",\n scaleFontSize: 12,\n strokeWidth: 22,\n summary: \"text-xs\",\n title: \"text-[13px]\",\n valueFontSize: 34,\n }\n})\n\nconst rootClass = computed(() =>\n cn(\n \"text-card-foreground\",\n props.framed\n ? \"overflow-hidden rounded-lg border bg-card shadow-xs\"\n : \"w-full\",\n props.class,\n ),\n)\n\nconst hasHeader = computed(() =>\n Boolean(props.title || props.description || props.summary),\n)\n\nconst domain = computed(() => {\n let min = normalizeBoundary(props.min, 0)\n let max = normalizeBoundary(props.max, 100)\n\n if (min > max) {\n const nextMin = max\n max = min\n min = nextMin\n }\n\n if (min === max) {\n max = min + 1\n }\n\n return {\n max,\n min,\n }\n})\n\nconst numericValue = computed(() => toFiniteNumber(props.value))\nconst numericMarkerValue = computed(() => toFiniteNumber(props.markerValue))\n\nconst clampedValue = computed(() => {\n const value = numericValue.value\n const { max, min } = domain.value\n\n if (value === null) return null\n\n return Math.min(max, Math.max(min, value))\n})\n\nconst clampedMarkerValue = computed(() => {\n const value = numericMarkerValue.value\n const { max, min } = domain.value\n\n if (value === null) return null\n\n return Math.min(max, Math.max(min, value))\n})\n\nconst percent = computed(() => {\n const value = clampedValue.value\n\n if (value === null) return 0\n\n return valueToPercent(value)\n})\n\nconst markerPercent = computed(() => {\n const value = clampedMarkerValue.value\n\n if (value === null) return null\n\n return valueToPercent(value)\n})\n\nconst normalizedSegments = computed<NormalizedGaugeSegment[]>(() => {\n const segments = props.segments ?? []\n const { max, min } = domain.value\n const colors = [\n \"oklch(0.63 0.16 150)\",\n \"oklch(0.74 0.17 80)\",\n \"oklch(0.62 0.2 28)\",\n ]\n\n return segments\n .map((segment, index) => {\n const rawMin = segment.min ?? (index === 0\n ? min\n : segments[index - 1]?.max ?? min)\n const rawMax = segment.max ?? max\n const segmentMin = Math.min(max, Math.max(min, rawMin))\n const segmentMax = Math.min(max, Math.max(min, rawMax))\n const normalizedMin = Math.min(segmentMin, segmentMax)\n const normalizedMax = Math.max(segmentMin, segmentMax)\n\n return {\n color: segment.color ?? colors[index % colors.length] ?? props.color,\n label: segment.label,\n max: normalizedMax,\n min: normalizedMin,\n }\n })\n .filter((segment) => segment.max > segment.min)\n})\n\nconst activeColor = computed(() => {\n const value = clampedValue.value\n\n if (value !== null) {\n const segment = normalizedSegments.value.find((item) =>\n value >= item.min && value <= item.max,\n )\n\n if (segment) return segment.color\n }\n\n return props.color\n})\n\nconst formattedValue = computed(() => {\n const value = clampedValue.value\n\n if (value === null) return \"\"\n\n return formatGaugeValue(value)\n})\n\nconst minLabel = computed(() =>\n props.minLabel ?? formatGaugeValue(domain.value.min),\n)\nconst maxLabel = computed(() =>\n props.maxLabel ?? formatGaugeValue(domain.value.max),\n)\n\nconst markerLabel = computed(() => {\n const value = clampedMarkerValue.value\n\n if (value === null) return \"\"\n\n return props.markerLabel ?? formatGaugeValue(value)\n})\n\nconst gaugeTrackPath = computed(() => buildArcPath(0, 1))\nconst gaugeValuePath = computed(() => buildArcPath(0, percent.value))\n\nconst radialMarker = computed(() => {\n if (!props.showMarker || markerPercent.value === null) return null\n\n const marker = markerPercent.value\n const strokeOffset = sizeClasses.value.strokeWidth / 2\n const inner = radialPoint(marker, gaugeRadius - strokeOffset - 5)\n const outer = radialPoint(marker, gaugeRadius + strokeOffset + 9)\n const label = radialPoint(marker, gaugeRadius + strokeOffset + 31)\n const labelX = clampCoordinate(label.x, 20, 266)\n const labelY = clampCoordinate(label.y, 16, 136)\n\n return {\n anchor: \"middle\",\n labelX,\n labelY,\n x1: inner.x,\n x2: outer.x,\n y1: inner.y,\n y2: outer.y,\n }\n})\n\nconst needle = computed(() => {\n const point = radialPoint(percent.value, gaugeRadius - 30)\n\n return {\n x1: gaugeCenter.x,\n x2: point.x,\n y1: gaugeCenter.y,\n y2: point.y,\n }\n})\n\nconst meterFillStyle = computed<CSSProperties>(() => ({\n backgroundColor: activeColor.value,\n width: `${roundPercent(percent.value)}%`,\n}))\n\nconst meterTrackStyle = computed<CSSProperties>(() => ({\n backgroundColor: props.trackColor,\n}))\n\nconst meterMarkerStyle = computed<CSSProperties>(() => ({\n backgroundColor: props.markerColor,\n left: markerPercent.value === null ? \"0%\" : `${roundPercent(markerPercent.value)}%`,\n}))\n\nconst valueTextStyle = computed<CSSProperties>(() => ({\n fontSize: `${sizeClasses.value.valueFontSize}px`,\n fontWeight: \"400\",\n letterSpacing: \"0\",\n}))\n\nconst labelTextStyle = computed<CSSProperties>(() => ({\n fontSize: `${sizeClasses.value.scaleFontSize + 4}px`,\n letterSpacing: \"0\",\n}))\n\nconst scaleTextStyle = computed<CSSProperties>(() => ({\n fontSize: `${sizeClasses.value.scaleFontSize}px`,\n letterSpacing: \"0\",\n}))\n\nconst scaleLabelPosition = computed(() => ({\n maxX: roundCoordinate(radialPoint(1, gaugeRadius).x),\n minX: roundCoordinate(radialPoint(0, gaugeRadius).x),\n y: 150,\n}))\n\nconst markerTextStyle = computed<CSSProperties>(() => ({\n fontSize: `${sizeClasses.value.markerFontSize}px`,\n fontWeight: \"600\",\n letterSpacing: \"0\",\n}))\n\nconst ariaValueText = computed(() =>\n formattedValue.value || props.emptyLabel,\n)\n\nfunction normalizeBoundary(value: number | undefined, fallback: number) {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback\n}\n\nfunction toFiniteNumber(value: unknown) {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null\n }\n\n if (typeof value === \"string\" && value.trim().length > 0) {\n const parsed = Number(value)\n\n return Number.isFinite(parsed) ? parsed : null\n }\n\n return null\n}\n\nfunction valueToPercent(value: number) {\n const { max, min } = domain.value\n\n return Math.min(1, Math.max(0, (value - min) / (max - min)))\n}\n\nfunction formatGaugeValue(value: number) {\n const valuePercent = valueToPercent(value)\n const formatted = props.valueFormatter?.(value, {\n markerValue: clampedMarkerValue.value,\n max: domain.value.max,\n min: domain.value.min,\n percent: valuePercent,\n type: props.type,\n })\n\n return formatted === undefined\n ? `${numberFormatter.value.format(value)}${props.unit ?? \"\"}`\n : String(formatted)\n}\n\nfunction buildArcPath(startPercent: number, endPercent: number) {\n const start = Math.min(1, Math.max(0, startPercent))\n const end = Math.min(1, Math.max(0, endPercent))\n\n if (end <= start) return \"\"\n\n const startPoint = radialPoint(start, gaugeRadius)\n const endPoint = radialPoint(end, gaugeRadius)\n\n return [\n `M ${roundCoordinate(startPoint.x)} ${roundCoordinate(startPoint.y)}`,\n `A ${gaugeRadius} ${gaugeRadius} 0 0 1 ${roundCoordinate(endPoint.x)} ${roundCoordinate(endPoint.y)}`,\n ].join(\" \")\n}\n\nfunction radialPoint(pointPercent: number, radius: number): RadialPoint {\n const angle = Math.PI - (Math.PI * pointPercent)\n\n return {\n x: gaugeCenter.x + (Math.cos(angle) * radius),\n y: gaugeCenter.y - (Math.sin(angle) * radius),\n }\n}\n\nfunction roundCoordinate(value: number) {\n return Math.round(value * 100) / 100\n}\n\nfunction roundPercent(value: number) {\n return Math.round(value * 10000) / 100\n}\n\nfunction clampCoordinate(value: number, min: number, max: number) {\n return Math.min(max, Math.max(min, value))\n}\n</script>\n\n<template>\n <article\n data-slot=\"gauge\"\n :data-size=\"props.size\"\n :data-type=\"props.type\"\n :class=\"rootClass\"\n >\n <header\n v-if=\"hasHeader\"\n data-slot=\"gauge-header\"\n :class=\"cn('flex flex-wrap items-start justify-between border-b', sizeClasses.header)\"\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=\"gauge-title\"\n :class=\"cn('truncate font-semibold leading-tight tracking-normal', sizeClasses.title)\"\n >\n {{ props.title }}\n </h3>\n <p\n v-if=\"props.description\"\n data-slot=\"gauge-description\"\n :class=\"cn('text-muted-foreground', sizeClasses.description)\"\n >\n {{ props.description }}\n </p>\n </div>\n\n <p\n v-if=\"props.summary\"\n data-slot=\"gauge-summary\"\n :class=\"cn('shrink-0 font-medium text-muted-foreground', sizeClasses.summary)\"\n >\n {{ props.summary }}\n </p>\n </header>\n\n <div\n data-slot=\"gauge-body\"\n :class=\"cn('flex min-w-0 flex-col', sizeClasses.body)\"\n >\n <div\n v-if=\"clampedValue !== null\"\n data-slot=\"gauge-visual\"\n class=\"min-w-0\"\n role=\"meter\"\n :aria-label=\"props.title ?? props.label ?? 'Gauge'\"\n :aria-valuemax=\"domain.max\"\n :aria-valuemin=\"domain.min\"\n :aria-valuenow=\"clampedValue\"\n :aria-valuetext=\"ariaValueText\"\n >\n <div\n v-if=\"props.type === 'gauge'\"\n data-slot=\"gauge-radial\"\n :class=\"cn('relative mx-auto w-full', sizeClasses.radial)\"\n >\n <svg\n data-slot=\"gauge-svg\"\n class=\"block h-auto w-full overflow-visible\"\n role=\"presentation\"\n :viewBox=\"radialViewBox\"\n >\n <path\n data-slot=\"gauge-track\"\n :d=\"gaugeTrackPath\"\n fill=\"none\"\n :stroke=\"props.trackColor\"\n stroke-linecap=\"round\"\n :stroke-width=\"sizeClasses.strokeWidth\"\n />\n\n <path\n data-slot=\"gauge-value-arc\"\n :d=\"gaugeValuePath\"\n fill=\"none\"\n :stroke=\"activeColor\"\n stroke-linecap=\"round\"\n :stroke-width=\"sizeClasses.strokeWidth\"\n />\n\n <g\n v-if=\"radialMarker\"\n data-slot=\"gauge-marker\"\n class=\"text-muted-foreground\"\n >\n <line\n data-slot=\"gauge-marker-line\"\n :x1=\"radialMarker.x1\"\n :x2=\"radialMarker.x2\"\n :y1=\"radialMarker.y1\"\n :y2=\"radialMarker.y2\"\n :stroke=\"props.markerColor\"\n stroke-linecap=\"square\"\n stroke-width=\"4\"\n />\n <text\n v-if=\"markerLabel\"\n data-slot=\"gauge-marker-label\"\n class=\"fill-current\"\n dominant-baseline=\"middle\"\n :style=\"markerTextStyle\"\n :text-anchor=\"radialMarker.anchor\"\n :x=\"radialMarker.labelX\"\n :y=\"radialMarker.labelY\"\n >\n {{ markerLabel }}\n </text>\n </g>\n\n <g\n v-if=\"props.showNeedle\"\n data-slot=\"gauge-needle\"\n >\n <line\n :x1=\"needle.x1\"\n :x2=\"needle.x2\"\n :y1=\"needle.y1\"\n :y2=\"needle.y2\"\n :stroke=\"activeColor\"\n stroke-linecap=\"round\"\n stroke-width=\"3\"\n />\n <circle\n :cx=\"gaugeCenter.x\"\n :cy=\"gaugeCenter.y\"\n fill=\"var(--card)\"\n :stroke=\"activeColor\"\n stroke-width=\"3\"\n r=\"6\"\n />\n </g>\n\n <text\n v-if=\"props.showValue\"\n data-slot=\"gauge-value\"\n class=\"fill-current text-foreground\"\n dominant-baseline=\"middle\"\n text-anchor=\"middle\"\n :style=\"valueTextStyle\"\n :x=\"gaugeCenter.x\"\n y=\"90\"\n >\n {{ formattedValue }}\n </text>\n <text\n v-if=\"props.label\"\n data-slot=\"gauge-label\"\n class=\"fill-current text-muted-foreground\"\n dominant-baseline=\"middle\"\n text-anchor=\"middle\"\n :style=\"labelTextStyle\"\n :x=\"gaugeCenter.x\"\n y=\"118\"\n >\n {{ props.label }}\n </text>\n\n <g\n v-if=\"props.showLabels\"\n data-slot=\"gauge-scale\"\n class=\"fill-current text-muted-foreground\"\n :style=\"scaleTextStyle\"\n >\n <text\n data-slot=\"gauge-min-label\"\n dominant-baseline=\"middle\"\n text-anchor=\"middle\"\n :x=\"scaleLabelPosition.minX\"\n :y=\"scaleLabelPosition.y\"\n >\n {{ minLabel }}\n </text>\n <text\n data-slot=\"gauge-max-label\"\n dominant-baseline=\"middle\"\n text-anchor=\"middle\"\n :x=\"scaleLabelPosition.maxX\"\n :y=\"scaleLabelPosition.y\"\n >\n {{ maxLabel }}\n </text>\n </g>\n </svg>\n </div>\n\n <div\n v-else\n data-slot=\"gauge-meter\"\n class=\"flex min-w-0 flex-col gap-3\"\n >\n <div class=\"flex min-w-0 items-end justify-between gap-4\">\n <div class=\"min-w-0\">\n <p\n v-if=\"props.label\"\n data-slot=\"gauge-label\"\n :class=\"cn('truncate leading-none text-muted-foreground', sizeClasses.labelClass)\"\n >\n {{ props.label }}\n </p>\n <p\n v-if=\"props.showValue\"\n data-slot=\"gauge-value\"\n class=\"mt-1 font-semibold leading-none tracking-normal text-foreground\"\n >\n {{ formattedValue }}\n </p>\n </div>\n <p\n data-slot=\"gauge-percent\"\n class=\"shrink-0 text-xs font-medium text-muted-foreground\"\n >\n {{ Math.round(percent * 100) }}%\n </p>\n </div>\n\n <div\n data-slot=\"gauge-meter-track\"\n :class=\"cn('relative rounded-full', sizeClasses.meter)\"\n :style=\"meterTrackStyle\"\n >\n <div\n data-slot=\"gauge-meter-fill\"\n class=\"h-full rounded-full transition-[width]\"\n :style=\"meterFillStyle\"\n />\n <span\n v-if=\"props.showMarker && markerPercent !== null\"\n data-slot=\"gauge-meter-marker\"\n class=\"absolute top-1/2 h-[calc(100%_+_0.5rem)] w-1 -translate-x-1/2 -translate-y-1/2 rounded-full\"\n :style=\"meterMarkerStyle\"\n />\n </div>\n\n <div\n v-if=\"props.showLabels\"\n data-slot=\"gauge-scale\"\n :class=\"cn('flex items-center justify-between text-muted-foreground', sizeClasses.labelClass)\"\n >\n <span>{{ minLabel }}</span>\n <span v-if=\"markerLabel\">{{ markerLabel }}</span>\n <span>{{ maxLabel }}</span>\n </div>\n </div>\n </div>\n\n <p\n v-else\n data-slot=\"gauge-empty\"\n :class=\"cn('text-muted-foreground', sizeClasses.labelClass)\"\n >\n {{ props.emptyLabel }}\n </p>\n\n <div\n v-if=\"normalizedSegments.length\"\n data-slot=\"gauge-legend\"\n :class=\"cn('flex flex-wrap items-center justify-end gap-2 text-muted-foreground', sizeClasses.labelClass)\"\n >\n <span\n v-for=\"segment in normalizedSegments\"\n :key=\"`legend-${segment.min}-${segment.max}`\"\n data-slot=\"gauge-legend-item\"\n class=\"inline-flex min-w-0 items-center gap-1.5\"\n >\n <span\n data-slot=\"gauge-legend-swatch\"\n class=\"size-2 shrink-0 rounded-full\"\n :style=\"{ backgroundColor: segment.color }\"\n />\n <span class=\"truncate\">\n {{ segment.label ?? `${numberFormatter.format(segment.min)}-${numberFormatter.format(segment.max)}` }}\n </span>\n </span>\n </div>\n </div>\n </article>\n</template>\n",
|
|
14
|
+
"type": "registry:ui",
|
|
15
|
+
"target": "components/ui/gauge/Gauge.vue"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "src/components/ui/gauge/index.ts",
|
|
19
|
+
"content": "export { default as Gauge } from \"./Gauge.vue\"\nexport type {\n GaugeSegment,\n GaugeSize,\n GaugeType,\n GaugeValue,\n GaugeValueFormatter,\n GaugeValueFormatterContext,\n} from \"./Gauge.vue\"\n",
|
|
20
|
+
"type": "registry:ui",
|
|
21
|
+
"target": "components/ui/gauge/index.ts"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "src/lib/utils.ts",
|
|
25
|
+
"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",
|
|
26
|
+
"type": "registry:lib",
|
|
27
|
+
"target": "lib/utils.ts"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"type": "registry:ui"
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://shadcn-vue.com/schema/registry-item.json",
|
|
3
|
+
"name": "git-graph",
|
|
4
|
+
"title": "Git Graph",
|
|
5
|
+
"description": "A compact commit graph timeline with lanes, merge edges, refs, metadata, and selectable rows.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@lucide/vue",
|
|
8
|
+
"clsx",
|
|
9
|
+
"tailwind-merge"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "src/components/ui/git-graph/GitGraph.vue",
|
|
14
|
+
"content": "<script setup lang=\"ts\">\nimport { computed } from \"vue\"\nimport type { CSSProperties, HTMLAttributes } from \"vue\"\nimport { GitCommitHorizontalIcon, GitForkIcon, TagIcon } from \"@lucide/vue\"\nimport { cn } from \"@/lib/utils\"\n\nexport type GitGraphSize = \"sm\" | \"default\" | \"lg\"\nexport type GitGraphRefType = \"branch\" | \"head\" | \"remote\" | \"tag\"\n\nexport interface GitGraphRef {\n name: string\n type?: GitGraphRefType\n}\n\nexport interface GitGraphCommit {\n author?: string\n date?: string\n hash?: string\n id: string\n lane?: number\n message: string\n parents?: string[]\n refs?: Array<string | GitGraphRef>\n}\n\nexport interface GitGraphSelectPayload {\n commit: GitGraphCommit\n index: number\n}\n\ninterface NormalizedGitGraphRef extends GitGraphRef {\n type: GitGraphRefType\n}\n\ninterface GitGraphRow {\n commit: GitGraphCommit\n index: number\n lane: number\n parentIds: string[]\n refs: NormalizedGitGraphRef[]\n}\n\ntype GitGraphEdgeKind = \"direct\" | \"branch\" | \"merge\"\n\ninterface GitGraphEdge {\n color: string\n d: string\n fromIndex: number\n kind: GitGraphEdgeKind\n lane: number\n parentId: string\n toIndex: number\n}\n\ninterface GitGraphMetaItem {\n class?: HTMLAttributes[\"class\"]\n slot: \"git-graph-author\" | \"git-graph-date\" | \"git-graph-hash\"\n value: string\n}\n\nexport interface GitGraphProps {\n class?: HTMLAttributes[\"class\"]\n commits?: GitGraphCommit[]\n description?: string\n emptyLabel?: string\n laneColors?: string[]\n modelValue?: string\n selectable?: boolean\n showAuthor?: boolean\n showCommitIcon?: boolean\n showDate?: boolean\n showGraph?: boolean\n showHash?: boolean\n showMeta?: boolean\n showRefs?: boolean\n size?: GitGraphSize\n summary?: string\n title?: string\n}\n\nconst props = withDefaults(defineProps<GitGraphProps>(), {\n emptyLabel: \"No commits.\",\n selectable: true,\n showAuthor: true,\n showCommitIcon: true,\n showDate: true,\n showGraph: true,\n showHash: true,\n showMeta: true,\n showRefs: true,\n size: \"default\",\n})\n\nconst emit = defineEmits<{\n \"select\": [payload: GitGraphSelectPayload]\n \"update:modelValue\": [value: string]\n}>()\n\nconst defaultLaneColors = [\n \"oklch(62.7% 0.194 149.214)\",\n \"oklch(55.8% 0.288 302.321)\",\n \"oklch(63.7% 0.237 25.331)\",\n \"oklch(68.1% 0.162 75.834)\",\n \"oklch(58.8% 0.158 241.966)\",\n \"oklch(61.7% 0.19 204.36)\",\n]\n\nconst commits = computed(() => props.commits ?? [])\nconst rows = computed<GitGraphRow[]>(() => resolveRows(commits.value))\nconst rowByCommitId = computed(() => {\n const rowMap = new Map<string, GitGraphRow>()\n\n for (const row of rows.value) {\n rowMap.set(row.commit.id, row)\n\n if (row.commit.hash) {\n rowMap.set(row.commit.hash, row)\n }\n }\n\n return rowMap\n})\nconst laneCount = computed(() => {\n const lanes = rows.value.map((row) => row.lane)\n\n return Math.max(1, Math.max(...lanes, 0) + 1)\n})\nconst sizeClasses = computed(() => {\n if (props.size === \"sm\") {\n return {\n content: \"text-xs\",\n description: \"text-xs\",\n graphIcon: \"size-3.5\",\n hash: \"text-[11px]\",\n header: \"gap-2.5 px-3 py-2.5\",\n message: \"text-xs\",\n meta: \"gap-1.5 text-[11px]\",\n node: 7,\n ref: \"h-5.5 gap-1 px-1.5 text-[11px]\",\n refIcon: \"size-3\",\n row: \"h-11 px-3\",\n rowHeight: 44,\n laneGap: 18,\n lanePadding: 10,\n rowPadding: 12,\n title: \"text-xs\",\n }\n }\n\n if (props.size === \"lg\") {\n return {\n content: \"text-sm\",\n description: \"text-sm\",\n graphIcon: \"size-4.5\",\n hash: \"text-xs\",\n header: \"gap-3 px-5 py-4\",\n message: \"text-sm\",\n meta: \"gap-2 text-xs\",\n node: 10,\n ref: \"h-6.5 gap-1.5 px-2 text-xs\",\n refIcon: \"size-3.5\",\n row: \"h-15 px-5\",\n rowHeight: 60,\n laneGap: 24,\n lanePadding: 14,\n rowPadding: 20,\n title: \"text-sm\",\n }\n }\n\n return {\n content: \"text-[13px]\",\n description: \"text-sm\",\n graphIcon: \"size-4\",\n hash: \"text-xs\",\n header: \"gap-2.5 px-4 py-3\",\n message: \"text-[13px]\",\n meta: \"gap-1.5 text-xs\",\n node: 9,\n ref: \"h-6 gap-1.5 px-2 text-xs\",\n refIcon: \"size-3.5\",\n row: \"h-13 px-4\",\n rowHeight: 52,\n laneGap: 20,\n lanePadding: 12,\n rowPadding: 16,\n title: \"text-[13px]\",\n }\n})\nconst hasHeader = computed(() => Boolean(props.title || props.description || props.summary))\nconst graphAreaWidth = computed(() =>\n props.showGraph\n ? (sizeClasses.value.lanePadding * 2) + ((laneCount.value - 1) * sizeClasses.value.laneGap) + sizeClasses.value.node\n : 0,\n)\nconst graphHeight = computed(() => Math.max(rows.value.length, 1) * sizeClasses.value.rowHeight)\nconst graphStyle = computed<CSSProperties>(() => ({\n minWidth: props.showGraph && graphAreaWidth.value > 180 ? `${graphAreaWidth.value + 240}px` : undefined,\n}))\nconst svgStyle = computed<CSSProperties>(() => ({\n height: `${graphHeight.value}px`,\n left: `${sizeClasses.value.rowPadding}px`,\n width: `${graphAreaWidth.value}px`,\n}))\nconst edges = computed<GitGraphEdge[]>(() => {\n const nextEdges: GitGraphEdge[] = []\n\n for (const row of rows.value) {\n for (const [parentIndex, parentId] of row.parentIds.entries()) {\n const parentRow = rowByCommitId.value.get(parentId)\n\n if (!parentRow || parentRow.index <= row.index) continue\n const kind = edgeKind(row, parentRow, parentIndex)\n const colorLane = parentIndex > 0 ? parentRow.lane : row.lane\n\n nextEdges.push({\n color: laneColor(colorLane),\n d: edgePath(row, parentRow, parentIndex),\n fromIndex: row.index,\n kind,\n lane: colorLane,\n parentId,\n toIndex: parentRow.index,\n })\n }\n }\n\n return nextEdges.sort((a, b) => edgeSortWeight(a.kind) - edgeSortWeight(b.kind))\n})\n\nfunction normalizeLane(value: number) {\n if (!Number.isFinite(value)) return 0\n\n return Math.max(0, Math.trunc(value))\n}\n\nfunction resolveRows(items: GitGraphCommit[]): GitGraphRow[] {\n const aliasMap = buildAliasMap(items)\n const activeLanes: Array<string | undefined> = []\n const nextRows: GitGraphRow[] = []\n\n for (const [index, commit] of items.entries()) {\n const parentIds = normalizeParentIds(commit.parents)\n const activeLane = findActiveLane(commit, activeLanes, aliasMap)\n const hintedLane = typeof commit.lane === \"number\" ? normalizeLane(commit.lane) : undefined\n const lane = activeLane ?? hintedLane ?? firstAvailableLane(activeLanes)\n\n ensureLane(activeLanes, lane)\n activeLanes[lane] = commit.id\n removeDuplicateActiveRefs(activeLanes, commit, lane, aliasMap)\n\n nextRows.push({\n commit,\n index,\n lane,\n parentIds,\n refs: normalizeRefs(commit.refs),\n })\n\n if (parentIds[0]) {\n activeLanes[lane] = parentIds[0]\n } else {\n activeLanes[lane] = undefined\n }\n\n for (const parentId of parentIds.slice(1)) {\n if (findActiveRef(activeLanes, parentId, aliasMap) !== undefined) continue\n\n const parentLane = firstAvailableLane(activeLanes, lane + 1)\n ensureLane(activeLanes, parentLane)\n activeLanes[parentLane] = parentId\n }\n\n trimInactiveLanes(activeLanes)\n }\n\n return nextRows\n}\n\nfunction normalizeParentIds(parents?: string[]) {\n return (parents ?? []).filter((parentId) => parentId.trim().length > 0)\n}\n\nfunction buildAliasMap(items: GitGraphCommit[]) {\n const aliasMap = new Map<string, Set<string>>()\n\n for (const commit of items) {\n const aliases = commitAliases(commit)\n const aliasSet = new Set(aliases)\n\n for (const alias of aliases) {\n aliasMap.set(alias, aliasSet)\n }\n }\n\n return aliasMap\n}\n\nfunction commitAliases(commit: GitGraphCommit) {\n return [commit.id, commit.hash].filter((alias): alias is string => Boolean(alias))\n}\n\nfunction refsMatch(left: string, right: string, aliasMap: Map<string, Set<string>>) {\n if (left === right) return true\n\n return (aliasMap.get(left)?.has(right) ?? false) || (aliasMap.get(right)?.has(left) ?? false)\n}\n\nfunction findActiveLane(\n commit: GitGraphCommit,\n activeLanes: Array<string | undefined>,\n aliasMap: Map<string, Set<string>>,\n) {\n const aliases = commitAliases(commit)\n const lane = activeLanes.findIndex((activeRef) =>\n Boolean(activeRef && aliases.some((alias) => refsMatch(activeRef, alias, aliasMap))),\n )\n\n return lane >= 0 ? lane : undefined\n}\n\nfunction findActiveRef(\n activeLanes: Array<string | undefined>,\n ref: string,\n aliasMap: Map<string, Set<string>>,\n) {\n const lane = activeLanes.findIndex((activeRef) => Boolean(activeRef && refsMatch(activeRef, ref, aliasMap)))\n\n return lane >= 0 ? lane : undefined\n}\n\nfunction firstAvailableLane(activeLanes: Array<string | undefined>, startAt = 0) {\n for (let lane = Math.max(0, startAt); lane < activeLanes.length; lane += 1) {\n if (!activeLanes[lane]) return lane\n }\n\n return activeLanes.length\n}\n\nfunction ensureLane(activeLanes: Array<string | undefined>, lane: number) {\n while (activeLanes.length <= lane) {\n activeLanes.push(undefined)\n }\n}\n\nfunction removeDuplicateActiveRefs(\n activeLanes: Array<string | undefined>,\n commit: GitGraphCommit,\n currentLane: number,\n aliasMap: Map<string, Set<string>>,\n) {\n const aliases = commitAliases(commit)\n\n for (const [lane, activeRef] of activeLanes.entries()) {\n if (lane === currentLane || !activeRef) continue\n\n if (aliases.some((alias) => refsMatch(activeRef, alias, aliasMap))) {\n activeLanes[lane] = undefined\n }\n }\n}\n\nfunction trimInactiveLanes(activeLanes: Array<string | undefined>) {\n while (activeLanes.length && !activeLanes[activeLanes.length - 1]) {\n activeLanes.pop()\n }\n}\n\nfunction normalizeRefs(refs?: Array<string | GitGraphRef>): NormalizedGitGraphRef[] {\n return (refs ?? []).map((ref) => {\n if (typeof ref === \"string\") {\n return {\n name: ref,\n type: inferRefType(ref),\n }\n }\n\n return {\n ...ref,\n type: ref.type ?? inferRefType(ref.name),\n }\n })\n}\n\nfunction inferRefType(name: string): GitGraphRefType {\n if (name === \"HEAD\") return \"head\"\n if (name.startsWith(\"origin/\")) return \"remote\"\n if (/^v?\\d+\\.\\d+/.test(name)) return \"tag\"\n\n return \"branch\"\n}\n\nfunction laneColor(lane: number) {\n const colors = props.laneColors?.length ? props.laneColors : defaultLaneColors\n\n return colors[lane % colors.length] ?? colors[0]\n}\n\nfunction laneX(lane: number) {\n return sizeClasses.value.lanePadding + (lane * sizeClasses.value.laneGap) + (sizeClasses.value.node / 2)\n}\n\nfunction rowY(index: number) {\n return (index * sizeClasses.value.rowHeight) + (sizeClasses.value.rowHeight / 2)\n}\n\nfunction edgeKind(fromRow: GitGraphRow, toRow: GitGraphRow, parentIndex: number): GitGraphEdgeKind {\n if (fromRow.lane === toRow.lane) return \"direct\"\n if (parentIndex > 0) return \"merge\"\n\n return \"branch\"\n}\n\nfunction edgeSortWeight(kind: GitGraphEdgeKind) {\n if (kind === \"direct\") return 0\n if (kind === \"branch\") return 1\n\n return 2\n}\n\nfunction edgePath(fromRow: GitGraphRow, toRow: GitGraphRow, parentIndex: number) {\n const fromX = laneX(fromRow.lane)\n const fromY = rowY(fromRow.index)\n const toX = laneX(toRow.lane)\n const toY = rowY(toRow.index)\n\n if (fromX === toX) return `M ${fromX} ${fromY} L ${toX} ${toY}`\n\n const spanY = Math.max(sizeClasses.value.rowHeight, toY - fromY)\n const lead = Math.max(10, Math.min(sizeClasses.value.rowHeight * 0.38, spanY * 0.42))\n const curveStartY = parentIndex > 0\n ? fromY + (lead * 0.55)\n : Math.max(fromY + lead, toY - (lead * 1.35))\n const curveEndY = parentIndex > 0\n ? Math.min(toY - (lead * 0.35), curveStartY + (lead * 1.15))\n : toY - (lead * 0.25)\n const controlOffset = Math.max(6, Math.min(18, lead * 0.55))\n\n return [\n `M ${fromX} ${fromY}`,\n `L ${fromX} ${curveStartY}`,\n `C ${fromX} ${curveStartY + controlOffset}, ${toX} ${curveEndY - controlOffset}, ${toX} ${curveEndY}`,\n `L ${toX} ${toY}`,\n ].join(\" \")\n}\n\nfunction refClass(type: GitGraphRefType) {\n if (type === \"head\") return \"border-primary/30 bg-primary text-primary-foreground\"\n if (type === \"remote\") return \"border-sky-600/20 bg-sky-600/10 text-sky-600\"\n if (type === \"tag\") return \"border-amber-600/20 bg-amber-600/10 text-amber-600\"\n\n return \"border-emerald-600/20 bg-emerald-600/10 text-emerald-600\"\n}\n\nfunction refIcon(type: GitGraphRefType) {\n if (type === \"tag\") return TagIcon\n\n return GitForkIcon\n}\n\nfunction isSelected(commit: GitGraphCommit) {\n return props.modelValue === commit.id || props.modelValue === commit.hash\n}\n\nfunction rowMetaItems(row: GitGraphRow): GitGraphMetaItem[] {\n if (!props.showMeta) return []\n\n const items: GitGraphMetaItem[] = []\n\n if (props.showHash && row.commit.hash) {\n items.push({\n class: cn(\"font-mono\", sizeClasses.value.hash),\n slot: \"git-graph-hash\",\n value: row.commit.hash,\n })\n }\n\n if (props.showAuthor && row.commit.author) {\n items.push({\n slot: \"git-graph-author\",\n value: row.commit.author,\n })\n }\n\n if (props.showDate && row.commit.date) {\n items.push({\n slot: \"git-graph-date\",\n value: row.commit.date,\n })\n }\n\n return items\n}\n\nfunction rowLabel(row: GitGraphRow) {\n const meta = rowMetaItems(row).map((item) => item.value).join(\", \")\n\n return meta ? `${row.commit.message}, ${meta}` : row.commit.message\n}\n\nfunction handleSelect(row: GitGraphRow) {\n if (!props.selectable) return\n\n emit(\"update:modelValue\", row.commit.id)\n emit(\"select\", {\n commit: row.commit,\n index: row.index,\n })\n}\n</script>\n\n<template>\n <article\n data-slot=\"git-graph\"\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 v-if=\"hasHeader\"\n data-slot=\"git-graph-header\"\n :class=\"cn('flex flex-wrap items-start justify-between border-b', sizeClasses.header)\"\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=\"git-graph-title\"\n :class=\"cn('truncate font-semibold leading-tight tracking-normal', sizeClasses.title)\"\n >\n {{ props.title }}\n </h3>\n <p\n v-if=\"props.description\"\n data-slot=\"git-graph-description\"\n :class=\"cn('text-muted-foreground', sizeClasses.description)\"\n >\n {{ props.description }}\n </p>\n </div>\n\n <p\n v-if=\"props.summary\"\n data-slot=\"git-graph-summary\"\n class=\"shrink-0 text-xs font-medium text-muted-foreground\"\n >\n {{ props.summary }}\n </p>\n </header>\n\n <div\n v-if=\"rows.length\"\n data-slot=\"git-graph-scroll\"\n class=\"min-w-0 overflow-x-auto\"\n >\n <div\n data-slot=\"git-graph-content\"\n class=\"relative min-w-full\"\n :style=\"graphStyle\"\n >\n <svg\n v-if=\"props.showGraph\"\n data-slot=\"git-graph-edges\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute top-0 z-0 overflow-visible\"\n :height=\"graphHeight\"\n :width=\"graphAreaWidth\"\n :viewBox=\"`0 0 ${graphAreaWidth} ${graphHeight}`\"\n :style=\"svgStyle\"\n >\n <path\n v-for=\"edge in edges\"\n :key=\"`${edge.fromIndex}-${edge.toIndex}-${edge.parentId}`\"\n data-slot=\"git-graph-edge\"\n :data-kind=\"edge.kind\"\n :data-lane=\"edge.lane\"\n fill=\"none\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n :d=\"edge.d\"\n :stroke=\"edge.color\"\n :stroke-width=\"2\"\n :opacity=\"edge.kind === 'direct' ? 0.64 : 0.78\"\n />\n </svg>\n\n <ul data-slot=\"git-graph-rows\" class=\"relative z-10 flex min-w-full flex-col\">\n <li\n v-for=\"row in rows\"\n :key=\"row.commit.id\"\n data-slot=\"git-graph-row-item\"\n :data-selected=\"isSelected(row.commit) ? '' : undefined\"\n class=\"relative\"\n >\n <span\n v-if=\"row.index > 0\"\n data-slot=\"git-graph-row-separator\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute inset-x-0 top-0 z-20 h-px bg-border\"\n />\n\n <component\n :is=\"props.selectable ? 'button' : 'div'\"\n data-slot=\"git-graph-row\"\n :type=\"props.selectable ? 'button' : undefined\"\n :aria-label=\"props.selectable ? rowLabel(row) : undefined\"\n :aria-selected=\"props.selectable ? isSelected(row.commit) : undefined\"\n :class=\"\n cn(\n 'grid w-full min-w-0 grid-cols-[var(--git-graph-width)_minmax(0,1fr)] items-center text-left outline-none transition-colors',\n props.selectable && 'hover:bg-muted/50 focus-visible:bg-muted/70 focus-visible:ring-[3px] focus-visible:ring-ring/50',\n isSelected(row.commit) && 'bg-muted/60',\n sizeClasses.row,\n )\n \"\n :style=\"{ '--git-graph-width': `${graphAreaWidth}px` }\"\n @click=\"handleSelect(row)\"\n >\n <span\n v-if=\"props.showGraph\"\n data-slot=\"git-graph-lanes\"\n class=\"relative block h-full\"\n >\n <span\n data-slot=\"git-graph-node\"\n :style=\"{\n backgroundColor: laneColor(row.lane),\n height: `${sizeClasses.node}px`,\n left: `${laneX(row.lane) - (sizeClasses.node / 2)}px`,\n top: `calc(50% - ${sizeClasses.node / 2}px)`,\n width: `${sizeClasses.node}px`,\n }\"\n class=\"absolute rounded-full ring-2 ring-card\"\n />\n </span>\n\n <span data-slot=\"git-graph-commit\" class=\"flex min-w-0 items-center gap-2\">\n <span class=\"flex min-w-0 flex-1 items-center gap-2.5\">\n <span data-slot=\"git-graph-text\" class=\"flex min-w-0 flex-col gap-1\">\n <span class=\"flex min-w-0 items-center\">\n <span\n data-slot=\"git-graph-message\"\n :class=\"cn('min-w-0 truncate font-semibold leading-tight tracking-normal', sizeClasses.message)\"\n >\n {{ row.commit.message }}\n </span>\n </span>\n\n <span\n v-if=\"rowMetaItems(row).length\"\n data-slot=\"git-graph-meta\"\n :class=\"cn('flex min-w-0 flex-wrap items-center leading-tight text-muted-foreground', sizeClasses.meta)\"\n >\n <template\n v-for=\"(meta, metaIndex) in rowMetaItems(row)\"\n :key=\"meta.slot\"\n >\n <span\n v-if=\"metaIndex > 0\"\n aria-hidden=\"true\"\n >\n ·\n </span>\n <span\n :data-slot=\"meta.slot\"\n :class=\"meta.class\"\n >\n {{ meta.value }}\n </span>\n </template>\n </span>\n </span>\n\n <span\n v-if=\"props.showRefs && row.refs.length\"\n data-slot=\"git-graph-refs\"\n class=\"hidden min-w-0 shrink-0 items-center gap-2 self-center sm:flex\"\n >\n <span\n v-for=\"ref in row.refs\"\n :key=\"`${row.commit.id}-${ref.name}`\"\n data-slot=\"git-graph-ref\"\n :data-type=\"ref.type\"\n :class=\"cn('inline-flex max-w-36 shrink-0 items-center rounded-full border font-medium leading-tight', sizeClasses.ref, refClass(ref.type))\"\n >\n <component\n :is=\"refIcon(ref.type)\"\n aria-hidden=\"true\"\n :class=\"sizeClasses.refIcon\"\n />\n <span class=\"truncate\">{{ ref.name }}</span>\n </span>\n </span>\n </span>\n\n <GitCommitHorizontalIcon\n v-if=\"props.showCommitIcon\"\n data-slot=\"git-graph-commit-icon\"\n aria-hidden=\"true\"\n :class=\"cn('hidden shrink-0 text-muted-foreground sm:block', sizeClasses.graphIcon)\"\n />\n </span>\n </component>\n </li>\n </ul>\n </div>\n </div>\n\n <div\n v-else\n data-slot=\"git-graph-empty\"\n :class=\"cn('text-muted-foreground', sizeClasses.header, sizeClasses.content)\"\n >\n {{ props.emptyLabel }}\n </div>\n </article>\n</template>\n",
|
|
15
|
+
"type": "registry:ui",
|
|
16
|
+
"target": "components/ui/git-graph/GitGraph.vue"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "src/components/ui/git-graph/index.ts",
|
|
20
|
+
"content": "export { default as GitGraph } from \"./GitGraph.vue\"\nexport type {\n GitGraphCommit,\n GitGraphProps,\n GitGraphRef,\n GitGraphRefType,\n GitGraphSelectPayload,\n GitGraphSize,\n} from \"./GitGraph.vue\"\n",
|
|
21
|
+
"type": "registry:ui",
|
|
22
|
+
"target": "components/ui/git-graph/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
|
+
}
|