From bd47e755d58925a521835eb39fde7f191f8bc9a4 Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sat, 25 May 2024 08:09:30 +0800 Subject: [PATCH] feat: init --- .editorconfig | 13 + .env.example | 10 + .gitignore | 27 + .node-version | 1 + .npmrc | 1 + .vscode/extensions.json | 5 + .vscode/settings.json | 4 + LICENSE | 661 + README.md | 78 + app.config.ts | 10 + app.vue | 35 + assets/css/tailwind.css | 91 + assets/images/404.svg | 1 + assets/images/cloudflare.png | Bin 0 -> 21687 bytes assets/images/hero.svg | 1 + assets/images/nuxtjs.png | Bin 0 -> 16413 bytes assets/location/world-topo.json | 1 + components.json | 16 + components/dashboard/Breadcrumb.vue | 37 + components/dashboard/Counters.vue | 99 + components/dashboard/DatePicker.vue | 152 + components/dashboard/Index.vue | 41 + components/dashboard/Logout.vue | 32 + components/dashboard/Nav.vue | 28 + components/dashboard/Views.vue | 73 + components/dashboard/links/Delete.vue | 45 + components/dashboard/links/Editor.vue | 193 + components/dashboard/links/Index.vue | 63 + components/dashboard/links/Link.vue | 186 + components/dashboard/links/QRCode.vue | 81 + components/dashboard/metrics/Group.vue | 46 + components/dashboard/metrics/Index.vue | 25 + components/dashboard/metrics/List.vue | 75 + components/dashboard/metrics/Locations.vue | 93 + components/dashboard/metrics/Metric.vue | 113 + components/dashboard/metrics/name/Icon.vue | 101 + components/dashboard/metrics/name/Index.vue | 66 + components/dashboard/metrics/name/Referer.vue | 30 + components/dashboard/metrics/name/Slug.vue | 14 + components/home/Cta.vue | 20 + components/home/Features.vue | 78 + components/home/Hero.vue | 56 + components/home/Link.vue | 52 + components/home/Logos.vue | 19 + components/home/Twitter.vue | 20 + components/layouts/Footer.vue | 99 + components/layouts/Header.vue | 93 + components/login/index.vue | 65 + components/ui/accordion/Accordion.vue | 19 + components/ui/accordion/AccordionContent.vue | 24 + components/ui/accordion/AccordionItem.vue | 24 + components/ui/accordion/AccordionTrigger.vue | 39 + components/ui/accordion/index.ts | 4 + components/ui/alert-dialog/AlertDialog.vue | 14 + .../ui/alert-dialog/AlertDialogAction.vue | 20 + .../ui/alert-dialog/AlertDialogCancel.vue | 20 + .../ui/alert-dialog/AlertDialogContent.vue | 42 + .../alert-dialog/AlertDialogDescription.vue | 25 + .../ui/alert-dialog/AlertDialogFooter.vue | 21 + .../ui/alert-dialog/AlertDialogHeader.vue | 16 + .../ui/alert-dialog/AlertDialogTitle.vue | 22 + .../ui/alert-dialog/AlertDialogTrigger.vue | 11 + components/ui/alert-dialog/index.ts | 9 + components/ui/alert/Alert.vue | 16 + components/ui/alert/AlertDescription.vue | 14 + components/ui/alert/AlertTitle.vue | 14 + components/ui/alert/index.ts | 23 + components/ui/aspect-ratio/AspectRatio.vue | 11 + components/ui/aspect-ratio/index.ts | 1 + components/ui/auto-form/AutoForm.vue | 105 + components/ui/auto-form/AutoFormField.vue | 45 + .../ui/auto-form/AutoFormFieldArray.vue | 110 + .../ui/auto-form/AutoFormFieldBoolean.vue | 41 + components/ui/auto-form/AutoFormFieldDate.vue | 57 + components/ui/auto-form/AutoFormFieldEnum.vue | 49 + components/ui/auto-form/AutoFormFieldFile.vue | 74 + .../ui/auto-form/AutoFormFieldInput.vue | 36 + .../ui/auto-form/AutoFormFieldNumber.vue | 32 + .../ui/auto-form/AutoFormFieldObject.vue | 78 + components/ui/auto-form/AutoFormLabel.vue | 14 + components/ui/auto-form/constant.ts | 39 + components/ui/auto-form/dependencies.ts | 92 + components/ui/auto-form/index.ts | 15 + components/ui/auto-form/interface.ts | 81 + components/ui/auto-form/utils.ts | 171 + components/ui/avatar/Avatar.vue | 21 + components/ui/avatar/AvatarFallback.vue | 11 + components/ui/avatar/AvatarImage.vue | 9 + components/ui/avatar/index.ts | 24 + components/ui/breadcrumb/Breadcrumb.vue | 13 + .../ui/breadcrumb/BreadcrumbEllipsis.vue | 22 + components/ui/breadcrumb/BreadcrumbItem.vue | 16 + components/ui/breadcrumb/BreadcrumbLink.vue | 19 + components/ui/breadcrumb/BreadcrumbList.vue | 16 + components/ui/breadcrumb/BreadcrumbPage.vue | 19 + .../ui/breadcrumb/BreadcrumbSeparator.vue | 21 + components/ui/breadcrumb/index.ts | 7 + components/ui/button/Button.vue | 26 + components/ui/button/index.ts | 35 + components/ui/calendar/Calendar.vue | 60 + components/ui/calendar/CalendarCell.vue | 24 + .../ui/calendar/CalendarCellTrigger.vue | 38 + components/ui/calendar/CalendarGrid.vue | 24 + components/ui/calendar/CalendarGridBody.vue | 11 + components/ui/calendar/CalendarGridHead.vue | 11 + components/ui/calendar/CalendarGridRow.vue | 21 + components/ui/calendar/CalendarHeadCell.vue | 21 + components/ui/calendar/CalendarHeader.vue | 21 + components/ui/calendar/CalendarHeading.vue | 27 + components/ui/calendar/CalendarNextButton.vue | 32 + components/ui/calendar/CalendarPrevButton.vue | 32 + components/ui/calendar/index.ts | 12 + components/ui/card/Card.vue | 21 + components/ui/card/CardContent.vue | 14 + components/ui/card/CardDescription.vue | 14 + components/ui/card/CardFooter.vue | 14 + components/ui/card/CardHeader.vue | 14 + components/ui/card/CardTitle.vue | 18 + components/ui/card/index.ts | 6 + components/ui/chart-area/AreaChart.vue | 135 + components/ui/chart-area/index.ts | 1 + components/ui/chart-bar/BarChart.vue | 114 + components/ui/chart-bar/index.ts | 1 + components/ui/chart/ChartCrosshair.vue | 50 + components/ui/chart/ChartLegend.vue | 53 + components/ui/chart/ChartSingleTooltip.vue | 65 + components/ui/chart/ChartTooltip.vue | 51 + components/ui/chart/index.ts | 18 + components/ui/chart/interface.ts | 64 + components/ui/checkbox/Checkbox.vue | 33 + components/ui/checkbox/index.ts | 1 + components/ui/dialog/Dialog.vue | 14 + components/ui/dialog/DialogClose.vue | 11 + components/ui/dialog/DialogContent.vue | 50 + components/ui/dialog/DialogDescription.vue | 24 + components/ui/dialog/DialogFooter.vue | 19 + components/ui/dialog/DialogHeader.vue | 16 + components/ui/dialog/DialogScrollContent.vue | 59 + components/ui/dialog/DialogTitle.vue | 29 + components/ui/dialog/DialogTrigger.vue | 11 + components/ui/dialog/index.ts | 9 + components/ui/form/FormControl.vue | 16 + components/ui/form/FormDescription.vue | 20 + components/ui/form/FormItem.vue | 25 + components/ui/form/FormLabel.vue | 23 + components/ui/form/FormMessage.vue | 16 + components/ui/form/index.ts | 6 + components/ui/form/useFormField.ts | 30 + components/ui/hover-card/HoverCard.vue | 14 + components/ui/hover-card/HoverCardContent.vue | 41 + components/ui/hover-card/HoverCardTrigger.vue | 11 + components/ui/hover-card/index.ts | 3 + components/ui/input/Input.vue | 24 + components/ui/input/index.ts | 1 + components/ui/label/Label.vue | 27 + components/ui/label/index.ts | 1 + components/ui/menubar/Menubar.vue | 35 + components/ui/menubar/MenubarCheckboxItem.vue | 40 + components/ui/menubar/MenubarContent.vue | 43 + components/ui/menubar/MenubarGroup.vue | 11 + components/ui/menubar/MenubarItem.vue | 35 + components/ui/menubar/MenubarLabel.vue | 13 + components/ui/menubar/MenubarMenu.vue | 11 + components/ui/menubar/MenubarRadioGroup.vue | 20 + components/ui/menubar/MenubarRadioItem.vue | 40 + components/ui/menubar/MenubarSeparator.vue | 19 + components/ui/menubar/MenubarShortcut.vue | 14 + components/ui/menubar/MenubarSub.vue | 19 + components/ui/menubar/MenubarSubContent.vue | 39 + components/ui/menubar/MenubarSubTrigger.vue | 30 + components/ui/menubar/MenubarTrigger.vue | 29 + components/ui/menubar/index.ts | 15 + .../ui/navigation-menu/NavigationMenu.vue | 33 + .../navigation-menu/NavigationMenuContent.vue | 34 + .../NavigationMenuIndicator.vue | 24 + .../ui/navigation-menu/NavigationMenuItem.vue | 11 + .../ui/navigation-menu/NavigationMenuLink.vue | 19 + .../ui/navigation-menu/NavigationMenuList.vue | 29 + .../navigation-menu/NavigationMenuTrigger.vue | 34 + .../NavigationMenuViewport.vue | 33 + components/ui/navigation-menu/index.ts | 12 + components/ui/popover/Popover.vue | 15 + components/ui/popover/PopoverContent.vue | 48 + components/ui/popover/PopoverTrigger.vue | 11 + components/ui/popover/index.ts | 3 + components/ui/progress/Progress.vue | 39 + components/ui/progress/index.ts | 1 + components/ui/radio-group/RadioGroup.vue | 25 + components/ui/radio-group/RadioGroupItem.vue | 39 + components/ui/radio-group/index.ts | 2 + .../ui/range-calendar/RangeCalendar.vue | 60 + .../ui/range-calendar/RangeCalendarCell.vue | 24 + .../RangeCalendarCellTrigger.vue | 40 + .../ui/range-calendar/RangeCalendarGrid.vue | 24 + .../range-calendar/RangeCalendarGridBody.vue | 11 + .../range-calendar/RangeCalendarGridHead.vue | 11 + .../range-calendar/RangeCalendarGridRow.vue | 21 + .../range-calendar/RangeCalendarHeadCell.vue | 21 + .../ui/range-calendar/RangeCalendarHeader.vue | 21 + .../range-calendar/RangeCalendarHeading.vue | 27 + .../RangeCalendarNextButton.vue | 32 + .../RangeCalendarPrevButton.vue | 32 + components/ui/range-calendar/index.ts | 12 + components/ui/select/Select.vue | 15 + components/ui/select/SelectContent.vue | 53 + components/ui/select/SelectGroup.vue | 19 + components/ui/select/SelectItem.vue | 44 + components/ui/select/SelectItemText.vue | 11 + components/ui/select/SelectLabel.vue | 13 + .../ui/select/SelectScrollDownButton.vue | 24 + components/ui/select/SelectScrollUpButton.vue | 24 + components/ui/select/SelectSeparator.vue | 17 + components/ui/select/SelectTrigger.vue | 31 + components/ui/select/SelectValue.vue | 11 + components/ui/select/index.ts | 11 + components/ui/separator/Separator.vue | 20 + components/ui/separator/index.ts | 1 + components/ui/skeleton/Skeleton.vue | 14 + components/ui/skeleton/index.ts | 1 + components/ui/sonner/Sonner.vue | 22 + components/ui/sonner/index.ts | 1 + components/ui/switch/Switch.vue | 37 + components/ui/switch/index.ts | 1 + components/ui/table/Table.vue | 16 + components/ui/table/TableBody.vue | 14 + components/ui/table/TableCaption.vue | 14 + components/ui/table/TableCell.vue | 21 + components/ui/table/TableEmpty.vue | 37 + components/ui/table/TableFooter.vue | 14 + components/ui/table/TableHead.vue | 14 + components/ui/table/TableHeader.vue | 14 + components/ui/table/TableRow.vue | 14 + components/ui/table/index.ts | 8 + components/ui/tabs/Tabs.vue | 15 + components/ui/tabs/TabsContent.vue | 22 + components/ui/tabs/TabsList.vue | 25 + components/ui/tabs/TabsTrigger.vue | 27 + components/ui/tabs/index.ts | 4 + components/ui/textarea/Textarea.vue | 24 + components/ui/textarea/index.ts | 1 + components/ui/tooltip/Tooltip.vue | 14 + components/ui/tooltip/TooltipContent.vue | 31 + components/ui/tooltip/TooltipProvider.vue | 11 + components/ui/tooltip/TooltipTrigger.vue | 11 + components/ui/tooltip/index.ts | 4 + docs/configuration.md | 37 + error.vue | 13 + eslint.config.mjs | 17 + layouts/default.vue | 11 + middleware/auth.global.ts | 19 + nuxt.config.ts | 51 + package.json | 64 + pages/dashboard/index.vue | 10 + pages/dashboard/link.vue | 44 + pages/dashboard/links.vue | 6 + pages/dashboard/login.vue | 5 + pages/index.vue | 9 + pnpm-lock.yaml | 12777 ++++++++++++++++ public/android-chrome-192x192.png | Bin 0 -> 1403 bytes public/android-chrome-512x512.png | Bin 0 -> 3836 bytes public/apple-touch-icon.png | Bin 0 -> 599 bytes public/banner.png | Bin 0 -> 130126 bytes public/favicon.ico | Bin 0 -> 5238 bytes public/icon-192-maskable.png | Bin 0 -> 689 bytes public/image.png | Bin 0 -> 109582 bytes public/sink-1024.png | Bin 0 -> 2852 bytes public/sink.png | Bin 0 -> 599 bytes public/site.webmanifest | 19 + renovate.json | 5 + schemas/link.ts | 24 + schemas/query.ts | 25 + server/api/link/ai.get.ts | 40 + server/api/link/create.post.ts | 27 + server/api/link/delete.post.ts | 15 + server/api/link/edit.put.ts | 35 + server/api/link/list.get.ts | 29 + server/api/link/query.get.ts | 18 + server/api/stats/counters.get.ts | 19 + server/api/stats/metrics.get.ts | 25 + server/api/stats/views.get.ts | 29 + server/api/verify.ts | 6 + server/middleware/1.redirect.ts | 28 + server/middleware/2.auth.ts | 15 + server/tsconfig.json | 3 + server/utils/access-log.ts | 109 + server/utils/cloudflare.ts | 15 + server/utils/query-filter.ts | 31 + server/utils/sql-bricks.ts | 2 + server/utils/time.ts | 13 + tailwind.config.js | 86 + tsconfig.json | 4 + utils/api.ts | 15 + utils/color.ts | 3 + utils/flag.ts | 8 + utils/index.ts | 6 + utils/number.ts | 6 + utils/time.ts | 41 + wrangler.toml | 7 + 298 files changed, 21915 insertions(+) create mode 100755 .editorconfig create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .node-version create mode 100644 .npmrc create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app.config.ts create mode 100644 app.vue create mode 100644 assets/css/tailwind.css create mode 100644 assets/images/404.svg create mode 100644 assets/images/cloudflare.png create mode 100644 assets/images/hero.svg create mode 100644 assets/images/nuxtjs.png create mode 100644 assets/location/world-topo.json create mode 100644 components.json create mode 100644 components/dashboard/Breadcrumb.vue create mode 100644 components/dashboard/Counters.vue create mode 100644 components/dashboard/DatePicker.vue create mode 100644 components/dashboard/Index.vue create mode 100644 components/dashboard/Logout.vue create mode 100644 components/dashboard/Nav.vue create mode 100644 components/dashboard/Views.vue create mode 100644 components/dashboard/links/Delete.vue create mode 100644 components/dashboard/links/Editor.vue create mode 100644 components/dashboard/links/Index.vue create mode 100644 components/dashboard/links/Link.vue create mode 100644 components/dashboard/links/QRCode.vue create mode 100644 components/dashboard/metrics/Group.vue create mode 100644 components/dashboard/metrics/Index.vue create mode 100644 components/dashboard/metrics/List.vue create mode 100644 components/dashboard/metrics/Locations.vue create mode 100644 components/dashboard/metrics/Metric.vue create mode 100644 components/dashboard/metrics/name/Icon.vue create mode 100644 components/dashboard/metrics/name/Index.vue create mode 100644 components/dashboard/metrics/name/Referer.vue create mode 100644 components/dashboard/metrics/name/Slug.vue create mode 100644 components/home/Cta.vue create mode 100644 components/home/Features.vue create mode 100644 components/home/Hero.vue create mode 100644 components/home/Link.vue create mode 100644 components/home/Logos.vue create mode 100644 components/home/Twitter.vue create mode 100644 components/layouts/Footer.vue create mode 100644 components/layouts/Header.vue create mode 100644 components/login/index.vue create mode 100644 components/ui/accordion/Accordion.vue create mode 100644 components/ui/accordion/AccordionContent.vue create mode 100644 components/ui/accordion/AccordionItem.vue create mode 100644 components/ui/accordion/AccordionTrigger.vue create mode 100644 components/ui/accordion/index.ts create mode 100644 components/ui/alert-dialog/AlertDialog.vue create mode 100644 components/ui/alert-dialog/AlertDialogAction.vue create mode 100644 components/ui/alert-dialog/AlertDialogCancel.vue create mode 100644 components/ui/alert-dialog/AlertDialogContent.vue create mode 100644 components/ui/alert-dialog/AlertDialogDescription.vue create mode 100644 components/ui/alert-dialog/AlertDialogFooter.vue create mode 100644 components/ui/alert-dialog/AlertDialogHeader.vue create mode 100644 components/ui/alert-dialog/AlertDialogTitle.vue create mode 100644 components/ui/alert-dialog/AlertDialogTrigger.vue create mode 100644 components/ui/alert-dialog/index.ts create mode 100644 components/ui/alert/Alert.vue create mode 100644 components/ui/alert/AlertDescription.vue create mode 100644 components/ui/alert/AlertTitle.vue create mode 100644 components/ui/alert/index.ts create mode 100644 components/ui/aspect-ratio/AspectRatio.vue create mode 100644 components/ui/aspect-ratio/index.ts create mode 100644 components/ui/auto-form/AutoForm.vue create mode 100644 components/ui/auto-form/AutoFormField.vue create mode 100644 components/ui/auto-form/AutoFormFieldArray.vue create mode 100644 components/ui/auto-form/AutoFormFieldBoolean.vue create mode 100644 components/ui/auto-form/AutoFormFieldDate.vue create mode 100644 components/ui/auto-form/AutoFormFieldEnum.vue create mode 100644 components/ui/auto-form/AutoFormFieldFile.vue create mode 100644 components/ui/auto-form/AutoFormFieldInput.vue create mode 100644 components/ui/auto-form/AutoFormFieldNumber.vue create mode 100644 components/ui/auto-form/AutoFormFieldObject.vue create mode 100644 components/ui/auto-form/AutoFormLabel.vue create mode 100644 components/ui/auto-form/constant.ts create mode 100644 components/ui/auto-form/dependencies.ts create mode 100644 components/ui/auto-form/index.ts create mode 100644 components/ui/auto-form/interface.ts create mode 100644 components/ui/auto-form/utils.ts create mode 100644 components/ui/avatar/Avatar.vue create mode 100644 components/ui/avatar/AvatarFallback.vue create mode 100644 components/ui/avatar/AvatarImage.vue create mode 100644 components/ui/avatar/index.ts create mode 100644 components/ui/breadcrumb/Breadcrumb.vue create mode 100644 components/ui/breadcrumb/BreadcrumbEllipsis.vue create mode 100644 components/ui/breadcrumb/BreadcrumbItem.vue create mode 100644 components/ui/breadcrumb/BreadcrumbLink.vue create mode 100644 components/ui/breadcrumb/BreadcrumbList.vue create mode 100644 components/ui/breadcrumb/BreadcrumbPage.vue create mode 100644 components/ui/breadcrumb/BreadcrumbSeparator.vue create mode 100644 components/ui/breadcrumb/index.ts create mode 100644 components/ui/button/Button.vue create mode 100644 components/ui/button/index.ts create mode 100644 components/ui/calendar/Calendar.vue create mode 100644 components/ui/calendar/CalendarCell.vue create mode 100644 components/ui/calendar/CalendarCellTrigger.vue create mode 100644 components/ui/calendar/CalendarGrid.vue create mode 100644 components/ui/calendar/CalendarGridBody.vue create mode 100644 components/ui/calendar/CalendarGridHead.vue create mode 100644 components/ui/calendar/CalendarGridRow.vue create mode 100644 components/ui/calendar/CalendarHeadCell.vue create mode 100644 components/ui/calendar/CalendarHeader.vue create mode 100644 components/ui/calendar/CalendarHeading.vue create mode 100644 components/ui/calendar/CalendarNextButton.vue create mode 100644 components/ui/calendar/CalendarPrevButton.vue create mode 100644 components/ui/calendar/index.ts create mode 100644 components/ui/card/Card.vue create mode 100644 components/ui/card/CardContent.vue create mode 100644 components/ui/card/CardDescription.vue create mode 100644 components/ui/card/CardFooter.vue create mode 100644 components/ui/card/CardHeader.vue create mode 100644 components/ui/card/CardTitle.vue create mode 100644 components/ui/card/index.ts create mode 100644 components/ui/chart-area/AreaChart.vue create mode 100644 components/ui/chart-area/index.ts create mode 100644 components/ui/chart-bar/BarChart.vue create mode 100644 components/ui/chart-bar/index.ts create mode 100644 components/ui/chart/ChartCrosshair.vue create mode 100644 components/ui/chart/ChartLegend.vue create mode 100644 components/ui/chart/ChartSingleTooltip.vue create mode 100644 components/ui/chart/ChartTooltip.vue create mode 100644 components/ui/chart/index.ts create mode 100644 components/ui/chart/interface.ts create mode 100644 components/ui/checkbox/Checkbox.vue create mode 100644 components/ui/checkbox/index.ts create mode 100644 components/ui/dialog/Dialog.vue create mode 100644 components/ui/dialog/DialogClose.vue create mode 100644 components/ui/dialog/DialogContent.vue create mode 100644 components/ui/dialog/DialogDescription.vue create mode 100644 components/ui/dialog/DialogFooter.vue create mode 100644 components/ui/dialog/DialogHeader.vue create mode 100644 components/ui/dialog/DialogScrollContent.vue create mode 100644 components/ui/dialog/DialogTitle.vue create mode 100644 components/ui/dialog/DialogTrigger.vue create mode 100644 components/ui/dialog/index.ts create mode 100644 components/ui/form/FormControl.vue create mode 100644 components/ui/form/FormDescription.vue create mode 100644 components/ui/form/FormItem.vue create mode 100644 components/ui/form/FormLabel.vue create mode 100644 components/ui/form/FormMessage.vue create mode 100644 components/ui/form/index.ts create mode 100644 components/ui/form/useFormField.ts create mode 100644 components/ui/hover-card/HoverCard.vue create mode 100644 components/ui/hover-card/HoverCardContent.vue create mode 100644 components/ui/hover-card/HoverCardTrigger.vue create mode 100644 components/ui/hover-card/index.ts create mode 100644 components/ui/input/Input.vue create mode 100644 components/ui/input/index.ts create mode 100644 components/ui/label/Label.vue create mode 100644 components/ui/label/index.ts create mode 100644 components/ui/menubar/Menubar.vue create mode 100644 components/ui/menubar/MenubarCheckboxItem.vue create mode 100644 components/ui/menubar/MenubarContent.vue create mode 100644 components/ui/menubar/MenubarGroup.vue create mode 100644 components/ui/menubar/MenubarItem.vue create mode 100644 components/ui/menubar/MenubarLabel.vue create mode 100644 components/ui/menubar/MenubarMenu.vue create mode 100644 components/ui/menubar/MenubarRadioGroup.vue create mode 100644 components/ui/menubar/MenubarRadioItem.vue create mode 100644 components/ui/menubar/MenubarSeparator.vue create mode 100644 components/ui/menubar/MenubarShortcut.vue create mode 100644 components/ui/menubar/MenubarSub.vue create mode 100644 components/ui/menubar/MenubarSubContent.vue create mode 100644 components/ui/menubar/MenubarSubTrigger.vue create mode 100644 components/ui/menubar/MenubarTrigger.vue create mode 100644 components/ui/menubar/index.ts create mode 100644 components/ui/navigation-menu/NavigationMenu.vue create mode 100644 components/ui/navigation-menu/NavigationMenuContent.vue create mode 100644 components/ui/navigation-menu/NavigationMenuIndicator.vue create mode 100644 components/ui/navigation-menu/NavigationMenuItem.vue create mode 100644 components/ui/navigation-menu/NavigationMenuLink.vue create mode 100644 components/ui/navigation-menu/NavigationMenuList.vue create mode 100644 components/ui/navigation-menu/NavigationMenuTrigger.vue create mode 100644 components/ui/navigation-menu/NavigationMenuViewport.vue create mode 100644 components/ui/navigation-menu/index.ts create mode 100644 components/ui/popover/Popover.vue create mode 100644 components/ui/popover/PopoverContent.vue create mode 100644 components/ui/popover/PopoverTrigger.vue create mode 100644 components/ui/popover/index.ts create mode 100644 components/ui/progress/Progress.vue create mode 100644 components/ui/progress/index.ts create mode 100644 components/ui/radio-group/RadioGroup.vue create mode 100644 components/ui/radio-group/RadioGroupItem.vue create mode 100644 components/ui/radio-group/index.ts create mode 100644 components/ui/range-calendar/RangeCalendar.vue create mode 100644 components/ui/range-calendar/RangeCalendarCell.vue create mode 100644 components/ui/range-calendar/RangeCalendarCellTrigger.vue create mode 100644 components/ui/range-calendar/RangeCalendarGrid.vue create mode 100644 components/ui/range-calendar/RangeCalendarGridBody.vue create mode 100644 components/ui/range-calendar/RangeCalendarGridHead.vue create mode 100644 components/ui/range-calendar/RangeCalendarGridRow.vue create mode 100644 components/ui/range-calendar/RangeCalendarHeadCell.vue create mode 100644 components/ui/range-calendar/RangeCalendarHeader.vue create mode 100644 components/ui/range-calendar/RangeCalendarHeading.vue create mode 100644 components/ui/range-calendar/RangeCalendarNextButton.vue create mode 100644 components/ui/range-calendar/RangeCalendarPrevButton.vue create mode 100644 components/ui/range-calendar/index.ts create mode 100644 components/ui/select/Select.vue create mode 100644 components/ui/select/SelectContent.vue create mode 100644 components/ui/select/SelectGroup.vue create mode 100644 components/ui/select/SelectItem.vue create mode 100644 components/ui/select/SelectItemText.vue create mode 100644 components/ui/select/SelectLabel.vue create mode 100644 components/ui/select/SelectScrollDownButton.vue create mode 100644 components/ui/select/SelectScrollUpButton.vue create mode 100644 components/ui/select/SelectSeparator.vue create mode 100644 components/ui/select/SelectTrigger.vue create mode 100644 components/ui/select/SelectValue.vue create mode 100644 components/ui/select/index.ts create mode 100644 components/ui/separator/Separator.vue create mode 100644 components/ui/separator/index.ts create mode 100644 components/ui/skeleton/Skeleton.vue create mode 100644 components/ui/skeleton/index.ts create mode 100644 components/ui/sonner/Sonner.vue create mode 100644 components/ui/sonner/index.ts create mode 100644 components/ui/switch/Switch.vue create mode 100644 components/ui/switch/index.ts create mode 100644 components/ui/table/Table.vue create mode 100644 components/ui/table/TableBody.vue create mode 100644 components/ui/table/TableCaption.vue create mode 100644 components/ui/table/TableCell.vue create mode 100644 components/ui/table/TableEmpty.vue create mode 100644 components/ui/table/TableFooter.vue create mode 100644 components/ui/table/TableHead.vue create mode 100644 components/ui/table/TableHeader.vue create mode 100644 components/ui/table/TableRow.vue create mode 100644 components/ui/table/index.ts create mode 100644 components/ui/tabs/Tabs.vue create mode 100644 components/ui/tabs/TabsContent.vue create mode 100644 components/ui/tabs/TabsList.vue create mode 100644 components/ui/tabs/TabsTrigger.vue create mode 100644 components/ui/tabs/index.ts create mode 100644 components/ui/textarea/Textarea.vue create mode 100644 components/ui/textarea/index.ts create mode 100644 components/ui/tooltip/Tooltip.vue create mode 100644 components/ui/tooltip/TooltipContent.vue create mode 100644 components/ui/tooltip/TooltipProvider.vue create mode 100644 components/ui/tooltip/TooltipTrigger.vue create mode 100644 components/ui/tooltip/index.ts create mode 100644 docs/configuration.md create mode 100644 error.vue create mode 100644 eslint.config.mjs create mode 100644 layouts/default.vue create mode 100644 middleware/auth.global.ts create mode 100644 nuxt.config.ts create mode 100644 package.json create mode 100644 pages/dashboard/index.vue create mode 100644 pages/dashboard/link.vue create mode 100644 pages/dashboard/links.vue create mode 100644 pages/dashboard/login.vue create mode 100644 pages/index.vue create mode 100644 pnpm-lock.yaml create mode 100644 public/android-chrome-192x192.png create mode 100644 public/android-chrome-512x512.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/banner.png create mode 100644 public/favicon.ico create mode 100644 public/icon-192-maskable.png create mode 100644 public/image.png create mode 100644 public/sink-1024.png create mode 100644 public/sink.png create mode 100644 public/site.webmanifest create mode 100644 renovate.json create mode 100644 schemas/link.ts create mode 100644 schemas/query.ts create mode 100644 server/api/link/ai.get.ts create mode 100644 server/api/link/create.post.ts create mode 100644 server/api/link/delete.post.ts create mode 100644 server/api/link/edit.put.ts create mode 100644 server/api/link/list.get.ts create mode 100644 server/api/link/query.get.ts create mode 100644 server/api/stats/counters.get.ts create mode 100644 server/api/stats/metrics.get.ts create mode 100644 server/api/stats/views.get.ts create mode 100644 server/api/verify.ts create mode 100644 server/middleware/1.redirect.ts create mode 100644 server/middleware/2.auth.ts create mode 100644 server/tsconfig.json create mode 100644 server/utils/access-log.ts create mode 100644 server/utils/cloudflare.ts create mode 100644 server/utils/query-filter.ts create mode 100644 server/utils/sql-bricks.ts create mode 100644 server/utils/time.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 utils/api.ts create mode 100644 utils/color.ts create mode 100644 utils/flag.ts create mode 100644 utils/index.ts create mode 100644 utils/number.ts create mode 100644 utils/time.ts create mode 100644 wrangler.toml diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 00000000..91422397 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_size = 2 +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..bdc351be --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +NUXT_PUBLIC_PREVIEW_MODE=true +NUXT_PUBLIC_SLUG_DEFAULT_LENGTH=5 +NUXT_SITE_TOKEN=SinkCool +NUXT_REDIRECT_STATUS_CODE=308 +NUXT_HOME_URL="https://sink.cool" +NUXT_CF_ACCOUNT_ID=123456 +NUXT_CF_API_TOKEN=CloudflareAPIToken +NUXT_DATASET=sink_v0 +NUXT_AI_MODEL="@cf/meta/llama-3-8b-instruct" +NUXT_AI_PROMPT="You are a URL shortening assistant......" diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..40335292 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example +.wrangler +site +cache diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..9a2a0e21 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v20 diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..c483022c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +shamefully-hoist=true \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..3cbc28fc --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "vue.volar" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..898ec90f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + // Enable ESlint flat config support + "eslint.experimental.useFlatConfig": true +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2602aa8e --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# ⚡ Sink + +**A Simple / Speedy / Secrue Link Shortener with Analytics, 100% run on Cloudflare.** + +![Hero](./public/image.png) + +---- + +## ✨ Features + +- **URL Shortening:** Compress your URLs to their minimal length. +- **Analytics:** Monitor link analytics and gather insightful statistics. +- **Serverless:** Deploy without the need for traditional servers. +- **Customizable Slug:** Support for personalized slugs. +- **🪄 AI Slug:** Leverage AI to generate slugs. +- **Link Expiration:** Set expiration dates for your links. + +## 🪧 Demo + +Experience the demo at [Sink.Cool](https://sink.cool/dashboard). Log in using the Site Token below: + +```txt +Site Token: SinkCool +``` + +## 🧱 Technologies Used + +- **Framework**: [Nuxt](https://nuxt.com/) +- **Database**: [Cloudflare Workers KV](https://developers.cloudflare.com/kv/) +- **Analytics Engine**: [Cloudflare Workers Analytics Engine](https://developers.cloudflare.com/analytics/) +- **UI Components**: [Shadcn-vue](https://www.shadcn-vue.com/) +- **Styling:** [Tailwind CSS](https://tailwindcss.com/) +- **Deployment**: [Cloudflare](https://www.cloudflare.com/) + +## 🚗 Roadmap [WIP] + +We welcome your contributions and PRs. + +- [ ] Browser Extension +- [ ] Raycast Extension +- [ ] Apple Shortcuts +- [ ] Enhanced Link Management (with Cloudflare D1) +- [ ] Analytics Enhancements (Support for merging filter conditions) +- [ ] Dashboard Performance Optimization (Infinite loading) +- [ ] Units Test +- [ ] Support for Other Deployment Platforms + +## 🏗️ Deployment + +1. [Fork](https://github.com/ccbikai/Sink/fork) the repository to your GitHub account. +2. Create a [Cloudflare Pages](https://developers.cloudflare.com/pages/) project. +3. Select the `Sink` repository and the `Nuxt.js` preset. +4. Configure environment variables. + 1. `NUXT_SITE_TOKEN` length must exceed **8**. + 2. `NUXT_CF_ACCOUNT_ID` [find your account ID](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/). + 3. `NUXT_CF_API_TOKEN` Create a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). This token requires `Account.Account Analytics` permissions at the very least. [Reference.](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/#authentication). + +5. Save and deploy. +6. Cancel the deployment, navigate to `Settings` -> `Functions`. + 1. KV namespace bindings. Bind the variable name `KV` to a KV namespace. + 2. Workers AI Bindings. Bind the variable name `AI` to the Workers AI Catalog. _Optional_ + 3. Analytics Engine bindings. Bind the variable name `ANALYTICS` to the `sink` dataset, and enable [Cloudflare Analytics Engine beta](https://developers.cloudflare.com/analytics/analytics-engine/get-started/) for your account. +7. Redeploy. + +## ⚒️ Configuration + +[Configuration Docs](./docs/configuration.md) + +## 💖 Credits + +1. [**Cloudflare**](https://www.cloudflare.com/) +2. [**NuxtHub**](https://hub.nuxt.com/) +3. [**Astroship**](https://astroship.web3templates.com/) + +## ☕ Sponsor + +1. [Follow Me on X(Twitter)](https://x.com/ccbikai). +2. [Become a sponsor to on GitHub](https://github.com/sponsors/ccbikai). diff --git a/app.config.ts b/app.config.ts new file mode 100644 index 00000000..343033f2 --- /dev/null +++ b/app.config.ts @@ -0,0 +1,10 @@ +export default defineAppConfig({ + title: 'Sink', + description: 'A Simple / Speedy / Secrue Link Shortener with Analytics, 100% run on Cloudflare.', + image: 'https://sink.cool/banner.png', + previewTTL: 24 * 3600, // 24h + slugRegex: /^[a-z0-9]+(?:-[a-z0-9]+)*$/i, + reserveSlug: [ + 'dashboard', + ], +}) diff --git a/app.vue b/app.vue new file mode 100644 index 00000000..e804b964 --- /dev/null +++ b/app.vue @@ -0,0 +1,35 @@ + + + diff --git a/assets/css/tailwind.css b/assets/css/tailwind.css new file mode 100644 index 00000000..280d20f2 --- /dev/null +++ b/assets/css/tailwind.css @@ -0,0 +1,91 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + + --ring: 240 10% 3.9%; + + --radius: 0.5rem; + + --vis-tooltip-background-color: none !important; + --vis-tooltip-border-color: none !important; + --vis-tooltip-text-color: none !important; + --vis-tooltip-shadow-color: none !important; + --vis-tooltip-backdrop-filter: none !important; + --vis-tooltip-padding: none !important; + + --vis-primary-color: 198 93% 60%; + --vis-secondary-color: 158 64% 52%; + /* --vis-secondary-color: 160 81% 40%; */ + /* --vis-secondary-color: var(--primary); */ + --vis-text-color: var(--muted-foreground); + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + + --ring: 240 4.9% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/assets/images/404.svg b/assets/images/404.svg new file mode 100644 index 00000000..5e461162 --- /dev/null +++ b/assets/images/404.svg @@ -0,0 +1 @@ + diff --git a/assets/images/cloudflare.png b/assets/images/cloudflare.png new file mode 100644 index 0000000000000000000000000000000000000000..c947d9dfaa128d222ecb1f56186dc535fddd361f GIT binary patch literal 21687 zcmdSB^;a9;^FNL|w765;y~TpJxV3nZpaqH-5~R4h6xTrTVgZUKSg}$p1a~NfLIQ;X zeIi9ayx!-0|A_DXVR!HDotZncGmm@EoY_r$W1vY+!bF0Fg+;EdrDlYMg@?w%!U-h8 zeY}G<7cPH1;kl^lt72ibq?6v-<3IMXK}MP{v0z9R%wtFTjh>0R<_EpIM#+bV2X}Y( z$KzwWip1XD-hS-5xw-wjy1JdrzUy_r{k?Rz*>PR+s;;i?>Lb=<)b!vZ2NJN6fic!2 zO_uJ%NxLz?C}^1v??o{cGQ4Q%?r4W_ivF#!{!RPg0Fr_#q4aQ`fef;Z8s!>SXX+XEF|LcPN7B-fRQky?$FRKDp?1XNS|8lbEIocneGxg>_O~+>sXA zcj(hr`0=X|x_i(3I=>j~ovXY7Kq#kI7oSv*NJ@|$(m@�@j{c6k{UNlZ#YpghCu< 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 diff --git a/assets/images/nuxtjs.png b/assets/images/nuxtjs.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c9f7fd65b94713c636f87b97629da420a336fd GIT binary patch literal 16413 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 +import { NuxtLink } from '#components' + +defineProps({ + title: { + type: String, + required: true, + }, +}) + + + diff --git a/components/dashboard/Counters.vue b/components/dashboard/Counters.vue new file mode 100644 index 00000000..d3924a87 --- /dev/null +++ b/components/dashboard/Counters.vue @@ -0,0 +1,99 @@ + + + diff --git a/components/dashboard/DatePicker.vue b/components/dashboard/DatePicker.vue new file mode 100644 index 00000000..33799229 --- /dev/null +++ b/components/dashboard/DatePicker.vue @@ -0,0 +1,152 @@ + + + diff --git a/components/dashboard/Index.vue b/components/dashboard/Index.vue new file mode 100644 index 00000000..9b2583d9 --- /dev/null +++ b/components/dashboard/Index.vue @@ -0,0 +1,41 @@ + + + diff --git a/components/dashboard/Logout.vue b/components/dashboard/Logout.vue new file mode 100644 index 00000000..8f4a3eab --- /dev/null +++ b/components/dashboard/Logout.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/dashboard/Nav.vue b/components/dashboard/Nav.vue new file mode 100644 index 00000000..f3d418d2 --- /dev/null +++ b/components/dashboard/Nav.vue @@ -0,0 +1,28 @@ + + + diff --git a/components/dashboard/Views.vue b/components/dashboard/Views.vue new file mode 100644 index 00000000..61c67a7a --- /dev/null +++ b/components/dashboard/Views.vue @@ -0,0 +1,73 @@ + + + diff --git a/components/dashboard/links/Delete.vue b/components/dashboard/links/Delete.vue new file mode 100644 index 00000000..c4648076 --- /dev/null +++ b/components/dashboard/links/Delete.vue @@ -0,0 +1,45 @@ + + + diff --git a/components/dashboard/links/Editor.vue b/components/dashboard/links/Editor.vue new file mode 100644 index 00000000..356d74fa --- /dev/null +++ b/components/dashboard/links/Editor.vue @@ -0,0 +1,193 @@ + + + diff --git a/components/dashboard/links/Index.vue b/components/dashboard/links/Index.vue new file mode 100644 index 00000000..a1c076a2 --- /dev/null +++ b/components/dashboard/links/Index.vue @@ -0,0 +1,63 @@ + + + diff --git a/components/dashboard/links/Link.vue b/components/dashboard/links/Link.vue new file mode 100644 index 00000000..13d20cd2 --- /dev/null +++ b/components/dashboard/links/Link.vue @@ -0,0 +1,186 @@ + + + diff --git a/components/dashboard/links/QRCode.vue b/components/dashboard/links/QRCode.vue new file mode 100644 index 00000000..d782c99a --- /dev/null +++ b/components/dashboard/links/QRCode.vue @@ -0,0 +1,81 @@ + + + diff --git a/components/dashboard/metrics/Group.vue b/components/dashboard/metrics/Group.vue new file mode 100644 index 00000000..317454ec --- /dev/null +++ b/components/dashboard/metrics/Group.vue @@ -0,0 +1,46 @@ + + + diff --git a/components/dashboard/metrics/Index.vue b/components/dashboard/metrics/Index.vue new file mode 100644 index 00000000..79374157 --- /dev/null +++ b/components/dashboard/metrics/Index.vue @@ -0,0 +1,25 @@ + diff --git a/components/dashboard/metrics/List.vue b/components/dashboard/metrics/List.vue new file mode 100644 index 00000000..144cb270 --- /dev/null +++ b/components/dashboard/metrics/List.vue @@ -0,0 +1,75 @@ + + + diff --git a/components/dashboard/metrics/Locations.vue b/components/dashboard/metrics/Locations.vue new file mode 100644 index 00000000..70b1637f --- /dev/null +++ b/components/dashboard/metrics/Locations.vue @@ -0,0 +1,93 @@ + + + diff --git a/components/dashboard/metrics/Metric.vue b/components/dashboard/metrics/Metric.vue new file mode 100644 index 00000000..71101276 --- /dev/null +++ b/components/dashboard/metrics/Metric.vue @@ -0,0 +1,113 @@ + + + diff --git a/components/dashboard/metrics/name/Icon.vue b/components/dashboard/metrics/name/Icon.vue new file mode 100644 index 00000000..7a6daad3 --- /dev/null +++ b/components/dashboard/metrics/name/Icon.vue @@ -0,0 +1,101 @@ + + + diff --git a/components/dashboard/metrics/name/Index.vue b/components/dashboard/metrics/name/Index.vue new file mode 100644 index 00000000..44395ca2 --- /dev/null +++ b/components/dashboard/metrics/name/Index.vue @@ -0,0 +1,66 @@ + + + diff --git a/components/dashboard/metrics/name/Referer.vue b/components/dashboard/metrics/name/Referer.vue new file mode 100644 index 00000000..9ca5474d --- /dev/null +++ b/components/dashboard/metrics/name/Referer.vue @@ -0,0 +1,30 @@ + + + diff --git a/components/dashboard/metrics/name/Slug.vue b/components/dashboard/metrics/name/Slug.vue new file mode 100644 index 00000000..1cb9504b --- /dev/null +++ b/components/dashboard/metrics/name/Slug.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/home/Cta.vue b/components/home/Cta.vue new file mode 100644 index 00000000..234d61db --- /dev/null +++ b/components/home/Cta.vue @@ -0,0 +1,20 @@ + diff --git a/components/home/Features.vue b/components/home/Features.vue new file mode 100644 index 00000000..cf37f982 --- /dev/null +++ b/components/home/Features.vue @@ -0,0 +1,78 @@ + + + diff --git a/components/home/Hero.vue b/components/home/Hero.vue new file mode 100644 index 00000000..5e7385cb --- /dev/null +++ b/components/home/Hero.vue @@ -0,0 +1,56 @@ + + + diff --git a/components/home/Link.vue b/components/home/Link.vue new file mode 100644 index 00000000..678a920c --- /dev/null +++ b/components/home/Link.vue @@ -0,0 +1,52 @@ + + + diff --git a/components/home/Logos.vue b/components/home/Logos.vue new file mode 100644 index 00000000..8fd8f082 --- /dev/null +++ b/components/home/Logos.vue @@ -0,0 +1,19 @@ + diff --git a/components/home/Twitter.vue b/components/home/Twitter.vue new file mode 100644 index 00000000..e3557dbd --- /dev/null +++ b/components/home/Twitter.vue @@ -0,0 +1,20 @@ + + + diff --git a/components/layouts/Footer.vue b/components/layouts/Footer.vue new file mode 100644 index 00000000..8fa9ff15 --- /dev/null +++ b/components/layouts/Footer.vue @@ -0,0 +1,99 @@ + + + diff --git a/components/layouts/Header.vue b/components/layouts/Header.vue new file mode 100644 index 00000000..cc36c7d0 --- /dev/null +++ b/components/layouts/Header.vue @@ -0,0 +1,93 @@ + + + diff --git a/components/login/index.vue b/components/login/index.vue new file mode 100644 index 00000000..ef590e26 --- /dev/null +++ b/components/login/index.vue @@ -0,0 +1,65 @@ + + + diff --git a/components/ui/accordion/Accordion.vue b/components/ui/accordion/Accordion.vue new file mode 100644 index 00000000..8ce8571b --- /dev/null +++ b/components/ui/accordion/Accordion.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/accordion/AccordionContent.vue b/components/ui/accordion/AccordionContent.vue new file mode 100644 index 00000000..ffda75a1 --- /dev/null +++ b/components/ui/accordion/AccordionContent.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/accordion/AccordionItem.vue b/components/ui/accordion/AccordionItem.vue new file mode 100644 index 00000000..2894812e --- /dev/null +++ b/components/ui/accordion/AccordionItem.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/accordion/AccordionTrigger.vue b/components/ui/accordion/AccordionTrigger.vue new file mode 100644 index 00000000..4dbf37e7 --- /dev/null +++ b/components/ui/accordion/AccordionTrigger.vue @@ -0,0 +1,39 @@ + + + diff --git a/components/ui/accordion/index.ts b/components/ui/accordion/index.ts new file mode 100644 index 00000000..9340ac06 --- /dev/null +++ b/components/ui/accordion/index.ts @@ -0,0 +1,4 @@ +export { default as Accordion } from './Accordion.vue' +export { default as AccordionContent } from './AccordionContent.vue' +export { default as AccordionItem } from './AccordionItem.vue' +export { default as AccordionTrigger } from './AccordionTrigger.vue' diff --git a/components/ui/alert-dialog/AlertDialog.vue b/components/ui/alert-dialog/AlertDialog.vue new file mode 100644 index 00000000..8fb30de8 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialog.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogAction.vue b/components/ui/alert-dialog/AlertDialogAction.vue new file mode 100644 index 00000000..a442e8a5 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogAction.vue @@ -0,0 +1,20 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogCancel.vue b/components/ui/alert-dialog/AlertDialogCancel.vue new file mode 100644 index 00000000..648c20bb --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogCancel.vue @@ -0,0 +1,20 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogContent.vue b/components/ui/alert-dialog/AlertDialogContent.vue new file mode 100644 index 00000000..bb9166c8 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogContent.vue @@ -0,0 +1,42 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogDescription.vue b/components/ui/alert-dialog/AlertDialogDescription.vue new file mode 100644 index 00000000..f8458a11 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogDescription.vue @@ -0,0 +1,25 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogFooter.vue b/components/ui/alert-dialog/AlertDialogFooter.vue new file mode 100644 index 00000000..7a7a5b1d --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogFooter.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogHeader.vue b/components/ui/alert-dialog/AlertDialogHeader.vue new file mode 100644 index 00000000..ee9dfbf1 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogHeader.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogTitle.vue b/components/ui/alert-dialog/AlertDialogTitle.vue new file mode 100644 index 00000000..07dd65f9 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogTitle.vue @@ -0,0 +1,22 @@ + + + diff --git a/components/ui/alert-dialog/AlertDialogTrigger.vue b/components/ui/alert-dialog/AlertDialogTrigger.vue new file mode 100644 index 00000000..4f5e2fd0 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialogTrigger.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/alert-dialog/index.ts b/components/ui/alert-dialog/index.ts new file mode 100644 index 00000000..91d138ae --- /dev/null +++ b/components/ui/alert-dialog/index.ts @@ -0,0 +1,9 @@ +export { default as AlertDialog } from './AlertDialog.vue' +export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue' +export { default as AlertDialogContent } from './AlertDialogContent.vue' +export { default as AlertDialogHeader } from './AlertDialogHeader.vue' +export { default as AlertDialogTitle } from './AlertDialogTitle.vue' +export { default as AlertDialogDescription } from './AlertDialogDescription.vue' +export { default as AlertDialogFooter } from './AlertDialogFooter.vue' +export { default as AlertDialogAction } from './AlertDialogAction.vue' +export { default as AlertDialogCancel } from './AlertDialogCancel.vue' diff --git a/components/ui/alert/Alert.vue b/components/ui/alert/Alert.vue new file mode 100644 index 00000000..fe4074ac --- /dev/null +++ b/components/ui/alert/Alert.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/alert/AlertDescription.vue b/components/ui/alert/AlertDescription.vue new file mode 100644 index 00000000..a19cc209 --- /dev/null +++ b/components/ui/alert/AlertDescription.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/alert/AlertTitle.vue b/components/ui/alert/AlertTitle.vue new file mode 100644 index 00000000..1d5fd418 --- /dev/null +++ b/components/ui/alert/AlertTitle.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/alert/index.ts b/components/ui/alert/index.ts new file mode 100644 index 00000000..765f704d --- /dev/null +++ b/components/ui/alert/index.ts @@ -0,0 +1,23 @@ +import { type VariantProps, cva } from 'class-variance-authority' + +export { default as Alert } from './Alert.vue' +export { default as AlertTitle } from './AlertTitle.vue' +export { default as AlertDescription } from './AlertDescription.vue' + +export const alertVariants = cva( + 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +export type AlertVariants = VariantProps diff --git a/components/ui/aspect-ratio/AspectRatio.vue b/components/ui/aspect-ratio/AspectRatio.vue new file mode 100644 index 00000000..65291818 --- /dev/null +++ b/components/ui/aspect-ratio/AspectRatio.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/aspect-ratio/index.ts b/components/ui/aspect-ratio/index.ts new file mode 100644 index 00000000..3faf121c --- /dev/null +++ b/components/ui/aspect-ratio/index.ts @@ -0,0 +1 @@ +export { default as AspectRatio } from './AspectRatio.vue' diff --git a/components/ui/auto-form/AutoForm.vue b/components/ui/auto-form/AutoForm.vue new file mode 100644 index 00000000..3500e450 --- /dev/null +++ b/components/ui/auto-form/AutoForm.vue @@ -0,0 +1,105 @@ + + + diff --git a/components/ui/auto-form/AutoFormField.vue b/components/ui/auto-form/AutoFormField.vue new file mode 100644 index 00000000..5c31d8b4 --- /dev/null +++ b/components/ui/auto-form/AutoFormField.vue @@ -0,0 +1,45 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldArray.vue b/components/ui/auto-form/AutoFormFieldArray.vue new file mode 100644 index 00000000..666dccf3 --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldArray.vue @@ -0,0 +1,110 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldBoolean.vue b/components/ui/auto-form/AutoFormFieldBoolean.vue new file mode 100644 index 00000000..03aee45a --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldBoolean.vue @@ -0,0 +1,41 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldDate.vue b/components/ui/auto-form/AutoFormFieldDate.vue new file mode 100644 index 00000000..b1cae240 --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldDate.vue @@ -0,0 +1,57 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldEnum.vue b/components/ui/auto-form/AutoFormFieldEnum.vue new file mode 100644 index 00000000..aa5f2b32 --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldEnum.vue @@ -0,0 +1,49 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldFile.vue b/components/ui/auto-form/AutoFormFieldFile.vue new file mode 100644 index 00000000..13cc0903 --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldFile.vue @@ -0,0 +1,74 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldInput.vue b/components/ui/auto-form/AutoFormFieldInput.vue new file mode 100644 index 00000000..8bcff6ca --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldInput.vue @@ -0,0 +1,36 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldNumber.vue b/components/ui/auto-form/AutoFormFieldNumber.vue new file mode 100644 index 00000000..d494a5b5 --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldNumber.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/ui/auto-form/AutoFormFieldObject.vue b/components/ui/auto-form/AutoFormFieldObject.vue new file mode 100644 index 00000000..c4892336 --- /dev/null +++ b/components/ui/auto-form/AutoFormFieldObject.vue @@ -0,0 +1,78 @@ + + + diff --git a/components/ui/auto-form/AutoFormLabel.vue b/components/ui/auto-form/AutoFormLabel.vue new file mode 100644 index 00000000..121b5c0a --- /dev/null +++ b/components/ui/auto-form/AutoFormLabel.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/auto-form/constant.ts b/components/ui/auto-form/constant.ts new file mode 100644 index 00000000..33fee687 --- /dev/null +++ b/components/ui/auto-form/constant.ts @@ -0,0 +1,39 @@ +import AutoFormFieldArray from './AutoFormFieldArray.vue' +import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue' +import AutoFormFieldDate from './AutoFormFieldDate.vue' +import AutoFormFieldEnum from './AutoFormFieldEnum.vue' +import AutoFormFieldFile from './AutoFormFieldFile.vue' +import AutoFormFieldInput from './AutoFormFieldInput.vue' +import AutoFormFieldNumber from './AutoFormFieldNumber.vue' +import AutoFormFieldObject from './AutoFormFieldObject.vue' + +export const INPUT_COMPONENTS = { + date: AutoFormFieldDate, + select: AutoFormFieldEnum, + radio: AutoFormFieldEnum, + checkbox: AutoFormFieldBoolean, + switch: AutoFormFieldBoolean, + textarea: AutoFormFieldInput, + number: AutoFormFieldNumber, + string: AutoFormFieldInput, + file: AutoFormFieldFile, + array: AutoFormFieldArray, + object: AutoFormFieldObject, +} + +/** + * Define handlers for specific Zod types. + * You can expand this object to support more types. + */ +export const DEFAULT_ZOD_HANDLERS: { + [key: string]: keyof typeof INPUT_COMPONENTS +} = { + ZodString: 'string', + ZodBoolean: 'checkbox', + ZodDate: 'date', + ZodEnum: 'select', + ZodNativeEnum: 'select', + ZodNumber: 'number', + ZodArray: 'array', + ZodObject: 'object', +} diff --git a/components/ui/auto-form/dependencies.ts b/components/ui/auto-form/dependencies.ts new file mode 100644 index 00000000..ea761633 --- /dev/null +++ b/components/ui/auto-form/dependencies.ts @@ -0,0 +1,92 @@ +import type * as z from 'zod' +import type { Ref } from 'vue' +import { computed, ref, watch } from 'vue' +import { useFieldValue, useFormValues } from 'vee-validate' +import { createContext } from 'radix-vue' +import { type Dependency, DependencyType, type EnumValues } from './interface' +import { getFromPath, getIndexIfArray } from './utils' + +export const [injectDependencies, provideDependencies] = createContext>>[] | undefined>>('AutoFormDependencies') + +export default function useDependencies( + fieldName: string, +) { + const form = useFormValues() + // parsed test[0].age => test.age + const currentFieldName = fieldName.replace(/\[\d+\]/g, '') + const currentFieldValue = useFieldValue(fieldName) + + if (!form) + throw new Error('useDependencies should be used within ') + + const dependencies = injectDependencies() + const isDisabled = ref(false) + const isHidden = ref(false) + const isRequired = ref(false) + const overrideOptions = ref() + + const currentFieldDependencies = computed(() => dependencies.value?.filter( + dependency => dependency.targetField === currentFieldName, + )) + + function getSourceValue(dep: Dependency) { + const source = dep.sourceField as string + const index = getIndexIfArray(fieldName) ?? -1 + const [sourceLast, ...sourceInitial] = source.split('.').toReversed() + const [_targetLast, ...targetInitial] = (dep.targetField as string).split('.').toReversed() + + if (index >= 0 && sourceInitial.join(',') === targetInitial.join(',')) { + const [_currentLast, ...currentInitial] = fieldName.split('.').toReversed() + return getFromPath(form.value, currentInitial.join('.') + sourceLast) + } + + return getFromPath(form.value, source) + } + + const sourceFieldValues = computed(() => currentFieldDependencies.value?.map(dep => getSourceValue(dep))) + + const resetConditionState = () => { + isDisabled.value = false + isHidden.value = false + isRequired.value = false + overrideOptions.value = undefined + } + + watch([sourceFieldValues, dependencies], () => { + resetConditionState() + currentFieldDependencies.value?.forEach((dep) => { + const sourceValue = getSourceValue(dep) + const conditionMet = dep.when(sourceValue, currentFieldValue.value) + + switch (dep.type) { + case DependencyType.DISABLES: + if (conditionMet) + isDisabled.value = true + + break + case DependencyType.REQUIRES: + if (conditionMet) + isRequired.value = true + + break + case DependencyType.HIDES: + if (conditionMet) + isHidden.value = true + + break + case DependencyType.SETS_OPTIONS: + if (conditionMet) + overrideOptions.value = dep.options + + break + } + }) + }, { immediate: true, deep: true }) + + return { + isDisabled, + isHidden, + isRequired, + overrideOptions, + } +} diff --git a/components/ui/auto-form/index.ts b/components/ui/auto-form/index.ts new file mode 100644 index 00000000..0fb84384 --- /dev/null +++ b/components/ui/auto-form/index.ts @@ -0,0 +1,15 @@ +export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils' +export type { Config, ConfigItem, FieldProps } from './interface' + +export { default as AutoForm } from './AutoForm.vue' +export { default as AutoFormField } from './AutoFormField.vue' +export { default as AutoFormLabel } from './AutoFormLabel.vue' + +export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue' +export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue' +export { default as AutoFormFieldDate } from './AutoFormFieldDate.vue' +export { default as AutoFormFieldEnum } from './AutoFormFieldEnum.vue' +export { default as AutoFormFieldFile } from './AutoFormFieldFile.vue' +export { default as AutoFormFieldInput } from './AutoFormFieldInput.vue' +export { default as AutoFormFieldNumber } from './AutoFormFieldNumber.vue' +export { default as AutoFormFieldObject } from './AutoFormFieldObject.vue' diff --git a/components/ui/auto-form/interface.ts b/components/ui/auto-form/interface.ts new file mode 100644 index 00000000..4dd8b121 --- /dev/null +++ b/components/ui/auto-form/interface.ts @@ -0,0 +1,81 @@ +import type { Component, InputHTMLAttributes } from 'vue' +import type { ZodAny, z } from 'zod' +import type { INPUT_COMPONENTS } from './constant' + +export interface FieldProps { + fieldName: string + label?: string + required?: boolean + config?: ConfigItem + disabled?: boolean +} + +export interface Shape { + type: string + default?: any + required?: boolean + options?: string[] + schema?: ZodAny +} + +export interface ConfigItem { + /** Value for the `FormLabel` */ + label?: string + /** Value for the `FormDescription` */ + description?: string + /** Pick which component to be rendered. */ + component?: keyof typeof INPUT_COMPONENTS | Component + /** Hide `FormLabel`. */ + hideLabel?: boolean + inputProps?: InputHTMLAttributes +} + +// Define a type to unwrap an array +type UnwrapArray = T extends (infer U)[] ? U : never + +export type Config = { + // If SchemaType.key is an object, create a nested Config, otherwise ConfigItem + [Key in keyof SchemaType]?: + SchemaType[Key] extends any[] + ? UnwrapArray> + : SchemaType[Key] extends object + ? Config + : ConfigItem; +} + +export enum DependencyType { + DISABLES, + REQUIRES, + HIDES, + SETS_OPTIONS, +} + +interface BaseDependency>> { + sourceField: keyof SchemaType + type: DependencyType + targetField: keyof SchemaType + when: (sourceFieldValue: any, targetFieldValue: any) => boolean +} + +export type ValueDependency>> = + BaseDependency & { + type: + | DependencyType.DISABLES + | DependencyType.REQUIRES + | DependencyType.HIDES + } + +export type EnumValues = readonly [string, ...string[]] + +export type OptionsDependency< + SchemaType extends z.infer>, +> = BaseDependency & { + type: DependencyType.SETS_OPTIONS + + // Partial array of values from sourceField that will trigger the dependency + options: EnumValues +} + +export type Dependency>> = + | ValueDependency + | OptionsDependency diff --git a/components/ui/auto-form/utils.ts b/components/ui/auto-form/utils.ts new file mode 100644 index 00000000..da3d33f2 --- /dev/null +++ b/components/ui/auto-form/utils.ts @@ -0,0 +1,171 @@ +import type { z } from 'zod' + +// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions. +export type ZodObjectOrWrapped = + | z.ZodObject + | z.ZodEffects> + +/** + * Beautify a camelCase string. + * e.g. "myString" -> "My String" + */ +export function beautifyObjectName(string: string) { + // Remove bracketed indices + // if numbers only return the string + let output = string.replace(/\[\d+\]/g, '').replace(/([A-Z])/g, ' $1') + output = output.charAt(0).toUpperCase() + output.slice(1) + return output +} + +/** + * Parse string and extract the index + * @param string + * @returns index or undefined + */ +export function getIndexIfArray(string: string) { + const indexRegex = /\[(\d+)\]/ + // Match the index + const match = string.match(indexRegex) + // Extract the index (number) + const index = match ? Number.parseInt(match[1]) : undefined + return index +} + +/** + * Get the lowest level Zod type. + * This will unpack optionals, refinements, etc. + */ +export function getBaseSchema< + ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny, +>(schema: ChildType | z.ZodEffects): ChildType | null { + if (!schema) + return null + if ('innerType' in schema._def) + return getBaseSchema(schema._def.innerType as ChildType) + + if ('schema' in schema._def) + return getBaseSchema(schema._def.schema as ChildType) + + return schema as ChildType +} + +/** + * Get the type name of the lowest level Zod type. + * This will unpack optionals, refinements, etc. + */ +export function getBaseType(schema: z.ZodAny) { + const baseSchema = getBaseSchema(schema) + return baseSchema ? baseSchema._def.typeName : '' +} + +/** + * Search for a "ZodDefault" in the Zod stack and return its value. + */ +export function getDefaultValueInZodStack(schema: z.ZodAny): any { + const typedSchema = schema as unknown as z.ZodDefault< + z.ZodNumber | z.ZodString + > + + if (typedSchema._def.typeName === 'ZodDefault') + return typedSchema._def.defaultValue() + + if ('innerType' in typedSchema._def) { + return getDefaultValueInZodStack( + typedSchema._def.innerType as unknown as z.ZodAny, + ) + } + if ('schema' in typedSchema._def) { + return getDefaultValueInZodStack( + (typedSchema._def as any).schema as z.ZodAny, + ) + } + + return undefined +} + +export function getObjectFormSchema( + schema: ZodObjectOrWrapped, +): z.ZodObject { + if (schema?._def.typeName === 'ZodEffects') { + const typedSchema = schema as z.ZodEffects> + return getObjectFormSchema(typedSchema._def.schema) + } + return schema as z.ZodObject +} + +function isIndex(value: unknown): value is number { + return Number(value) >= 0 +} +/** + * Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax + */ +export function normalizeFormPath(path: string): string { + const pathArr = path.split('.') + if (!pathArr.length) + return '' + + let fullPath = String(pathArr[0]) + for (let i = 1; i < pathArr.length; i++) { + if (isIndex(pathArr[i])) { + fullPath += `[${pathArr[i]}]` + continue + } + + fullPath += `.${pathArr[i]}` + } + + return fullPath +} + +type NestedRecord = Record | { [k: string]: NestedRecord } +/** + * Checks if the path opted out of nested fields using `[fieldName]` syntax + */ +export function isNotNestedPath(path: string) { + return /^\[.+\]$/i.test(path) +} +function isObject(obj: unknown): obj is Record { + return obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj) +} +function isContainerValue(value: unknown): value is Record { + return isObject(value) || Array.isArray(value) +} +function cleanupNonNestedPath(path: string) { + if (isNotNestedPath(path)) + return path.replace(/\[|\]/gi, '') + + return path +} + +/** + * Gets a nested property value from an object + */ +export function getFromPath(object: NestedRecord | undefined, path: string): TValue | undefined +export function getFromPath( + object: NestedRecord | undefined, + path: string, + fallback?: TFallback, +): TValue | TFallback +export function getFromPath( + object: NestedRecord | undefined, + path: string, + fallback?: TFallback, +): TValue | TFallback | undefined { + if (!object) + return fallback + + if (isNotNestedPath(path)) + return object[cleanupNonNestedPath(path)] as TValue | undefined + + const resolvedValue = (path || '') + .split(/\.|\[(\d+)\]/) + .filter(Boolean) + .reduce((acc, propKey) => { + if (isContainerValue(acc) && propKey in acc) + return acc[propKey] + + return fallback + }, object as unknown) + + return resolvedValue as TValue | undefined +} diff --git a/components/ui/avatar/Avatar.vue b/components/ui/avatar/Avatar.vue new file mode 100644 index 00000000..254b8107 --- /dev/null +++ b/components/ui/avatar/Avatar.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/avatar/AvatarFallback.vue b/components/ui/avatar/AvatarFallback.vue new file mode 100644 index 00000000..a671a219 --- /dev/null +++ b/components/ui/avatar/AvatarFallback.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/avatar/AvatarImage.vue b/components/ui/avatar/AvatarImage.vue new file mode 100644 index 00000000..43499fa2 --- /dev/null +++ b/components/ui/avatar/AvatarImage.vue @@ -0,0 +1,9 @@ + + + diff --git a/components/ui/avatar/index.ts b/components/ui/avatar/index.ts new file mode 100644 index 00000000..c4af1a66 --- /dev/null +++ b/components/ui/avatar/index.ts @@ -0,0 +1,24 @@ +import { type VariantProps, cva } from 'class-variance-authority' + +export { default as Avatar } from './Avatar.vue' +export { default as AvatarImage } from './AvatarImage.vue' +export { default as AvatarFallback } from './AvatarFallback.vue' + +export const avatarVariant = cva( + 'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden', + { + variants: { + size: { + sm: 'h-10 w-10 text-xs', + base: 'h-16 w-16 text-2xl', + lg: 'h-32 w-32 text-5xl', + }, + shape: { + circle: 'rounded-full', + square: 'rounded-md', + }, + }, + }, +) + +export type AvatarVariants = VariantProps diff --git a/components/ui/breadcrumb/Breadcrumb.vue b/components/ui/breadcrumb/Breadcrumb.vue new file mode 100644 index 00000000..72ca1437 --- /dev/null +++ b/components/ui/breadcrumb/Breadcrumb.vue @@ -0,0 +1,13 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbEllipsis.vue b/components/ui/breadcrumb/BreadcrumbEllipsis.vue new file mode 100644 index 00000000..d01e15ca --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbEllipsis.vue @@ -0,0 +1,22 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbItem.vue b/components/ui/breadcrumb/BreadcrumbItem.vue new file mode 100644 index 00000000..4e071d1d --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbItem.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbLink.vue b/components/ui/breadcrumb/BreadcrumbLink.vue new file mode 100644 index 00000000..cfbb0b1c --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbLink.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbList.vue b/components/ui/breadcrumb/BreadcrumbList.vue new file mode 100644 index 00000000..b7ef968b --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbList.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbPage.vue b/components/ui/breadcrumb/BreadcrumbPage.vue new file mode 100644 index 00000000..be12591f --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbPage.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbSeparator.vue b/components/ui/breadcrumb/BreadcrumbSeparator.vue new file mode 100644 index 00000000..8d76cca0 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbSeparator.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/breadcrumb/index.ts b/components/ui/breadcrumb/index.ts new file mode 100644 index 00000000..05909832 --- /dev/null +++ b/components/ui/breadcrumb/index.ts @@ -0,0 +1,7 @@ +export { default as Breadcrumb } from './Breadcrumb.vue' +export { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue' +export { default as BreadcrumbItem } from './BreadcrumbItem.vue' +export { default as BreadcrumbLink } from './BreadcrumbLink.vue' +export { default as BreadcrumbList } from './BreadcrumbList.vue' +export { default as BreadcrumbPage } from './BreadcrumbPage.vue' +export { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue' diff --git a/components/ui/button/Button.vue b/components/ui/button/Button.vue new file mode 100644 index 00000000..8d15abf4 --- /dev/null +++ b/components/ui/button/Button.vue @@ -0,0 +1,26 @@ + + + diff --git a/components/ui/button/index.ts b/components/ui/button/index.ts new file mode 100644 index 00000000..1b00c326 --- /dev/null +++ b/components/ui/button/index.ts @@ -0,0 +1,35 @@ +import { type VariantProps, cva } from 'class-variance-authority' + +export { default as Button } from './Button.vue' + +export const buttonVariants = cva( + 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: + 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-10 px-4 py-2', + xs: 'h-7 rounded px-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +) + +export type ButtonVariants = VariantProps diff --git a/components/ui/calendar/Calendar.vue b/components/ui/calendar/Calendar.vue new file mode 100644 index 00000000..26eee9d7 --- /dev/null +++ b/components/ui/calendar/Calendar.vue @@ -0,0 +1,60 @@ + + + diff --git a/components/ui/calendar/CalendarCell.vue b/components/ui/calendar/CalendarCell.vue new file mode 100644 index 00000000..120dc902 --- /dev/null +++ b/components/ui/calendar/CalendarCell.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/calendar/CalendarCellTrigger.vue b/components/ui/calendar/CalendarCellTrigger.vue new file mode 100644 index 00000000..dc3e61f8 --- /dev/null +++ b/components/ui/calendar/CalendarCellTrigger.vue @@ -0,0 +1,38 @@ + + + diff --git a/components/ui/calendar/CalendarGrid.vue b/components/ui/calendar/CalendarGrid.vue new file mode 100644 index 00000000..d3c50088 --- /dev/null +++ b/components/ui/calendar/CalendarGrid.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/calendar/CalendarGridBody.vue b/components/ui/calendar/CalendarGridBody.vue new file mode 100644 index 00000000..23d71ced --- /dev/null +++ b/components/ui/calendar/CalendarGridBody.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/calendar/CalendarGridHead.vue b/components/ui/calendar/CalendarGridHead.vue new file mode 100644 index 00000000..f8101a3d --- /dev/null +++ b/components/ui/calendar/CalendarGridHead.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/calendar/CalendarGridRow.vue b/components/ui/calendar/CalendarGridRow.vue new file mode 100644 index 00000000..b5866c0a --- /dev/null +++ b/components/ui/calendar/CalendarGridRow.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/calendar/CalendarHeadCell.vue b/components/ui/calendar/CalendarHeadCell.vue new file mode 100644 index 00000000..6fd5d36c --- /dev/null +++ b/components/ui/calendar/CalendarHeadCell.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/calendar/CalendarHeader.vue b/components/ui/calendar/CalendarHeader.vue new file mode 100644 index 00000000..1b98e0b1 --- /dev/null +++ b/components/ui/calendar/CalendarHeader.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/calendar/CalendarHeading.vue b/components/ui/calendar/CalendarHeading.vue new file mode 100644 index 00000000..097d86fe --- /dev/null +++ b/components/ui/calendar/CalendarHeading.vue @@ -0,0 +1,27 @@ + + + diff --git a/components/ui/calendar/CalendarNextButton.vue b/components/ui/calendar/CalendarNextButton.vue new file mode 100644 index 00000000..767dd787 --- /dev/null +++ b/components/ui/calendar/CalendarNextButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/ui/calendar/CalendarPrevButton.vue b/components/ui/calendar/CalendarPrevButton.vue new file mode 100644 index 00000000..b4914771 --- /dev/null +++ b/components/ui/calendar/CalendarPrevButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/ui/calendar/index.ts b/components/ui/calendar/index.ts new file mode 100644 index 00000000..5239a1bd --- /dev/null +++ b/components/ui/calendar/index.ts @@ -0,0 +1,12 @@ +export { default as Calendar } from './Calendar.vue' +export { default as CalendarCell } from './CalendarCell.vue' +export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue' +export { default as CalendarGrid } from './CalendarGrid.vue' +export { default as CalendarGridBody } from './CalendarGridBody.vue' +export { default as CalendarGridHead } from './CalendarGridHead.vue' +export { default as CalendarGridRow } from './CalendarGridRow.vue' +export { default as CalendarHeadCell } from './CalendarHeadCell.vue' +export { default as CalendarHeader } from './CalendarHeader.vue' +export { default as CalendarHeading } from './CalendarHeading.vue' +export { default as CalendarNextButton } from './CalendarNextButton.vue' +export { default as CalendarPrevButton } from './CalendarPrevButton.vue' diff --git a/components/ui/card/Card.vue b/components/ui/card/Card.vue new file mode 100644 index 00000000..6d784dca --- /dev/null +++ b/components/ui/card/Card.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/card/CardContent.vue b/components/ui/card/CardContent.vue new file mode 100644 index 00000000..a6e1d1b0 --- /dev/null +++ b/components/ui/card/CardContent.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/card/CardDescription.vue b/components/ui/card/CardDescription.vue new file mode 100644 index 00000000..cde6c216 --- /dev/null +++ b/components/ui/card/CardDescription.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/card/CardFooter.vue b/components/ui/card/CardFooter.vue new file mode 100644 index 00000000..125e6356 --- /dev/null +++ b/components/ui/card/CardFooter.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/card/CardHeader.vue b/components/ui/card/CardHeader.vue new file mode 100644 index 00000000..f13e49a9 --- /dev/null +++ b/components/ui/card/CardHeader.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/card/CardTitle.vue b/components/ui/card/CardTitle.vue new file mode 100644 index 00000000..58a3ce47 --- /dev/null +++ b/components/ui/card/CardTitle.vue @@ -0,0 +1,18 @@ + + + diff --git a/components/ui/card/index.ts b/components/ui/card/index.ts new file mode 100644 index 00000000..8170483c --- /dev/null +++ b/components/ui/card/index.ts @@ -0,0 +1,6 @@ +export { default as Card } from './Card.vue' +export { default as CardHeader } from './CardHeader.vue' +export { default as CardTitle } from './CardTitle.vue' +export { default as CardDescription } from './CardDescription.vue' +export { default as CardContent } from './CardContent.vue' +export { default as CardFooter } from './CardFooter.vue' diff --git a/components/ui/chart-area/AreaChart.vue b/components/ui/chart-area/AreaChart.vue new file mode 100644 index 00000000..bad98776 --- /dev/null +++ b/components/ui/chart-area/AreaChart.vue @@ -0,0 +1,135 @@ + + + diff --git a/components/ui/chart-area/index.ts b/components/ui/chart-area/index.ts new file mode 100644 index 00000000..81345fd8 --- /dev/null +++ b/components/ui/chart-area/index.ts @@ -0,0 +1 @@ +export { default as AreaChart } from './AreaChart.vue' diff --git a/components/ui/chart-bar/BarChart.vue b/components/ui/chart-bar/BarChart.vue new file mode 100644 index 00000000..933fbb93 --- /dev/null +++ b/components/ui/chart-bar/BarChart.vue @@ -0,0 +1,114 @@ + + + diff --git a/components/ui/chart-bar/index.ts b/components/ui/chart-bar/index.ts new file mode 100644 index 00000000..805149a5 --- /dev/null +++ b/components/ui/chart-bar/index.ts @@ -0,0 +1 @@ +export { default as BarChart } from './BarChart.vue' diff --git a/components/ui/chart/ChartCrosshair.vue b/components/ui/chart/ChartCrosshair.vue new file mode 100644 index 00000000..ff7f354a --- /dev/null +++ b/components/ui/chart/ChartCrosshair.vue @@ -0,0 +1,50 @@ + + + diff --git a/components/ui/chart/ChartLegend.vue b/components/ui/chart/ChartLegend.vue new file mode 100644 index 00000000..4d58b215 --- /dev/null +++ b/components/ui/chart/ChartLegend.vue @@ -0,0 +1,53 @@ + + + diff --git a/components/ui/chart/ChartSingleTooltip.vue b/components/ui/chart/ChartSingleTooltip.vue new file mode 100644 index 00000000..ee35dd95 --- /dev/null +++ b/components/ui/chart/ChartSingleTooltip.vue @@ -0,0 +1,65 @@ + + + diff --git a/components/ui/chart/ChartTooltip.vue b/components/ui/chart/ChartTooltip.vue new file mode 100644 index 00000000..7bdb96ea --- /dev/null +++ b/components/ui/chart/ChartTooltip.vue @@ -0,0 +1,51 @@ + + + diff --git a/components/ui/chart/index.ts b/components/ui/chart/index.ts new file mode 100644 index 00000000..e2d64c17 --- /dev/null +++ b/components/ui/chart/index.ts @@ -0,0 +1,18 @@ +export { default as ChartTooltip } from './ChartTooltip.vue' +export { default as ChartSingleTooltip } from './ChartSingleTooltip.vue' +export { default as ChartLegend } from './ChartLegend.vue' +export { default as ChartCrosshair } from './ChartCrosshair.vue' + +export function defaultColors(count: number = 3) { + const quotient = Math.floor(count / 2) + const remainder = count % 2 + + const primaryCount = quotient + remainder + const secondaryCount = quotient + return [ + ...Array.from(Array(primaryCount).keys()).map(i => `hsl(var(--vis-primary-color) / ${1 - (1 / primaryCount) * i})`), + ...Array.from(Array(secondaryCount).keys()).map(i => `hsl(var(--vis-secondary-color) / ${1 - (1 / secondaryCount) * i})`), + ] +} + +export * from './interface' diff --git a/components/ui/chart/interface.ts b/components/ui/chart/interface.ts new file mode 100644 index 00000000..c3838afc --- /dev/null +++ b/components/ui/chart/interface.ts @@ -0,0 +1,64 @@ +import type { Spacing } from '@unovis/ts' + +type KeyOf> = Extract + +export interface BaseChartProps> { + /** + * The source data, in which each entry is a dictionary. + */ + data: T[] + /** + * Select the categories from your data. Used to populate the legend and toolip. + */ + categories: KeyOf[] + /** + * Sets the key to map the data to the axis. + */ + index: KeyOf + /** + * Change the default colors. + */ + colors?: string[] + /** + * Margin of each the container + */ + margin?: Spacing + /** + * Change the opacity of the non-selected field + * @default 0.2 + */ + filterOpacity?: number + /** + * Function to format X label + */ + xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string + /** + * Function to format Y label + */ + yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string + /** + * Controls the visibility of the X axis. + * @default true + */ + showXAxis?: boolean + /** + * Controls the visibility of the Y axis. + * @default true + */ + showYAxis?: boolean + /** + * Controls the visibility of tooltip. + * @default true + */ + showTooltip?: boolean + /** + * Controls the visibility of legend. + * @default true + */ + showLegend?: boolean + /** + * Controls the visibility of gridline. + * @default true + */ + showGridLine?: boolean +} diff --git a/components/ui/checkbox/Checkbox.vue b/components/ui/checkbox/Checkbox.vue new file mode 100644 index 00000000..d484d911 --- /dev/null +++ b/components/ui/checkbox/Checkbox.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/ui/checkbox/index.ts b/components/ui/checkbox/index.ts new file mode 100644 index 00000000..8c28c286 --- /dev/null +++ b/components/ui/checkbox/index.ts @@ -0,0 +1 @@ +export { default as Checkbox } from './Checkbox.vue' diff --git a/components/ui/dialog/Dialog.vue b/components/ui/dialog/Dialog.vue new file mode 100644 index 00000000..a04c0262 --- /dev/null +++ b/components/ui/dialog/Dialog.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/dialog/DialogClose.vue b/components/ui/dialog/DialogClose.vue new file mode 100644 index 00000000..a64703e5 --- /dev/null +++ b/components/ui/dialog/DialogClose.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/dialog/DialogContent.vue b/components/ui/dialog/DialogContent.vue new file mode 100644 index 00000000..6f2d2304 --- /dev/null +++ b/components/ui/dialog/DialogContent.vue @@ -0,0 +1,50 @@ + + + diff --git a/components/ui/dialog/DialogDescription.vue b/components/ui/dialog/DialogDescription.vue new file mode 100644 index 00000000..86f5b1c7 --- /dev/null +++ b/components/ui/dialog/DialogDescription.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/dialog/DialogFooter.vue b/components/ui/dialog/DialogFooter.vue new file mode 100644 index 00000000..b633458e --- /dev/null +++ b/components/ui/dialog/DialogFooter.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/dialog/DialogHeader.vue b/components/ui/dialog/DialogHeader.vue new file mode 100644 index 00000000..dee30d9a --- /dev/null +++ b/components/ui/dialog/DialogHeader.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/dialog/DialogScrollContent.vue b/components/ui/dialog/DialogScrollContent.vue new file mode 100644 index 00000000..554e6111 --- /dev/null +++ b/components/ui/dialog/DialogScrollContent.vue @@ -0,0 +1,59 @@ + + + diff --git a/components/ui/dialog/DialogTitle.vue b/components/ui/dialog/DialogTitle.vue new file mode 100644 index 00000000..32954cd1 --- /dev/null +++ b/components/ui/dialog/DialogTitle.vue @@ -0,0 +1,29 @@ + + + diff --git a/components/ui/dialog/DialogTrigger.vue b/components/ui/dialog/DialogTrigger.vue new file mode 100644 index 00000000..ee0c12ff --- /dev/null +++ b/components/ui/dialog/DialogTrigger.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/dialog/index.ts b/components/ui/dialog/index.ts new file mode 100644 index 00000000..847e999f --- /dev/null +++ b/components/ui/dialog/index.ts @@ -0,0 +1,9 @@ +export { default as Dialog } from './Dialog.vue' +export { default as DialogClose } from './DialogClose.vue' +export { default as DialogTrigger } from './DialogTrigger.vue' +export { default as DialogHeader } from './DialogHeader.vue' +export { default as DialogTitle } from './DialogTitle.vue' +export { default as DialogDescription } from './DialogDescription.vue' +export { default as DialogContent } from './DialogContent.vue' +export { default as DialogScrollContent } from './DialogScrollContent.vue' +export { default as DialogFooter } from './DialogFooter.vue' diff --git a/components/ui/form/FormControl.vue b/components/ui/form/FormControl.vue new file mode 100644 index 00000000..8459cab8 --- /dev/null +++ b/components/ui/form/FormControl.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/form/FormDescription.vue b/components/ui/form/FormDescription.vue new file mode 100644 index 00000000..055b050f --- /dev/null +++ b/components/ui/form/FormDescription.vue @@ -0,0 +1,20 @@ + + + diff --git a/components/ui/form/FormItem.vue b/components/ui/form/FormItem.vue new file mode 100644 index 00000000..3b202392 --- /dev/null +++ b/components/ui/form/FormItem.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/components/ui/form/FormLabel.vue b/components/ui/form/FormLabel.vue new file mode 100644 index 00000000..1aa2956d --- /dev/null +++ b/components/ui/form/FormLabel.vue @@ -0,0 +1,23 @@ + + + diff --git a/components/ui/form/FormMessage.vue b/components/ui/form/FormMessage.vue new file mode 100644 index 00000000..308755e4 --- /dev/null +++ b/components/ui/form/FormMessage.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/form/index.ts b/components/ui/form/index.ts new file mode 100644 index 00000000..30a30a6c --- /dev/null +++ b/components/ui/form/index.ts @@ -0,0 +1,6 @@ +export { Form, Field as FormField } from 'vee-validate' +export { default as FormItem } from './FormItem.vue' +export { default as FormLabel } from './FormLabel.vue' +export { default as FormControl } from './FormControl.vue' +export { default as FormMessage } from './FormMessage.vue' +export { default as FormDescription } from './FormDescription.vue' diff --git a/components/ui/form/useFormField.ts b/components/ui/form/useFormField.ts new file mode 100644 index 00000000..73eeee3e --- /dev/null +++ b/components/ui/form/useFormField.ts @@ -0,0 +1,30 @@ +import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate' +import { inject } from 'vue' +import { FORM_ITEM_INJECTION_KEY } from './FormItem.vue' + +export function useFormField() { + const fieldContext = inject(FieldContextKey) + const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY) + + const fieldState = { + valid: useIsFieldValid(), + isDirty: useIsFieldDirty(), + isTouched: useIsFieldTouched(), + error: useFieldError(), + } + + if (!fieldContext) + throw new Error('useFormField should be used within ') + + const { name } = fieldContext + const id = fieldItemContext + + return { + id, + name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} diff --git a/components/ui/hover-card/HoverCard.vue b/components/ui/hover-card/HoverCard.vue new file mode 100644 index 00000000..f17c9d1f --- /dev/null +++ b/components/ui/hover-card/HoverCard.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/hover-card/HoverCardContent.vue b/components/ui/hover-card/HoverCardContent.vue new file mode 100644 index 00000000..12e3ff23 --- /dev/null +++ b/components/ui/hover-card/HoverCardContent.vue @@ -0,0 +1,41 @@ + + + diff --git a/components/ui/hover-card/HoverCardTrigger.vue b/components/ui/hover-card/HoverCardTrigger.vue new file mode 100644 index 00000000..3e300b95 --- /dev/null +++ b/components/ui/hover-card/HoverCardTrigger.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/hover-card/index.ts b/components/ui/hover-card/index.ts new file mode 100644 index 00000000..c7bfd32d --- /dev/null +++ b/components/ui/hover-card/index.ts @@ -0,0 +1,3 @@ +export { default as HoverCard } from './HoverCard.vue' +export { default as HoverCardTrigger } from './HoverCardTrigger.vue' +export { default as HoverCardContent } from './HoverCardContent.vue' diff --git a/components/ui/input/Input.vue b/components/ui/input/Input.vue new file mode 100644 index 00000000..8a1919df --- /dev/null +++ b/components/ui/input/Input.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/input/index.ts b/components/ui/input/index.ts new file mode 100644 index 00000000..a691dd6c --- /dev/null +++ b/components/ui/input/index.ts @@ -0,0 +1 @@ +export { default as Input } from './Input.vue' diff --git a/components/ui/label/Label.vue b/components/ui/label/Label.vue new file mode 100644 index 00000000..31a5c691 --- /dev/null +++ b/components/ui/label/Label.vue @@ -0,0 +1,27 @@ + + + diff --git a/components/ui/label/index.ts b/components/ui/label/index.ts new file mode 100644 index 00000000..572c2f01 --- /dev/null +++ b/components/ui/label/index.ts @@ -0,0 +1 @@ +export { default as Label } from './Label.vue' diff --git a/components/ui/menubar/Menubar.vue b/components/ui/menubar/Menubar.vue new file mode 100644 index 00000000..b1f14076 --- /dev/null +++ b/components/ui/menubar/Menubar.vue @@ -0,0 +1,35 @@ + + + diff --git a/components/ui/menubar/MenubarCheckboxItem.vue b/components/ui/menubar/MenubarCheckboxItem.vue new file mode 100644 index 00000000..ec265eb2 --- /dev/null +++ b/components/ui/menubar/MenubarCheckboxItem.vue @@ -0,0 +1,40 @@ + + + diff --git a/components/ui/menubar/MenubarContent.vue b/components/ui/menubar/MenubarContent.vue new file mode 100644 index 00000000..625614c7 --- /dev/null +++ b/components/ui/menubar/MenubarContent.vue @@ -0,0 +1,43 @@ + + + diff --git a/components/ui/menubar/MenubarGroup.vue b/components/ui/menubar/MenubarGroup.vue new file mode 100644 index 00000000..853976b5 --- /dev/null +++ b/components/ui/menubar/MenubarGroup.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/menubar/MenubarItem.vue b/components/ui/menubar/MenubarItem.vue new file mode 100644 index 00000000..05bb60d8 --- /dev/null +++ b/components/ui/menubar/MenubarItem.vue @@ -0,0 +1,35 @@ + + + diff --git a/components/ui/menubar/MenubarLabel.vue b/components/ui/menubar/MenubarLabel.vue new file mode 100644 index 00000000..9a88b760 --- /dev/null +++ b/components/ui/menubar/MenubarLabel.vue @@ -0,0 +1,13 @@ + + + diff --git a/components/ui/menubar/MenubarMenu.vue b/components/ui/menubar/MenubarMenu.vue new file mode 100644 index 00000000..fec5ee55 --- /dev/null +++ b/components/ui/menubar/MenubarMenu.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/menubar/MenubarRadioGroup.vue b/components/ui/menubar/MenubarRadioGroup.vue new file mode 100644 index 00000000..60a8cd1d --- /dev/null +++ b/components/ui/menubar/MenubarRadioGroup.vue @@ -0,0 +1,20 @@ + + + diff --git a/components/ui/menubar/MenubarRadioItem.vue b/components/ui/menubar/MenubarRadioItem.vue new file mode 100644 index 00000000..fa63ad68 --- /dev/null +++ b/components/ui/menubar/MenubarRadioItem.vue @@ -0,0 +1,40 @@ + + + diff --git a/components/ui/menubar/MenubarSeparator.vue b/components/ui/menubar/MenubarSeparator.vue new file mode 100644 index 00000000..7f01a30c --- /dev/null +++ b/components/ui/menubar/MenubarSeparator.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/menubar/MenubarShortcut.vue b/components/ui/menubar/MenubarShortcut.vue new file mode 100644 index 00000000..ab793093 --- /dev/null +++ b/components/ui/menubar/MenubarShortcut.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/menubar/MenubarSub.vue b/components/ui/menubar/MenubarSub.vue new file mode 100644 index 00000000..6b76cd3e --- /dev/null +++ b/components/ui/menubar/MenubarSub.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/menubar/MenubarSubContent.vue b/components/ui/menubar/MenubarSubContent.vue new file mode 100644 index 00000000..bd23a26b --- /dev/null +++ b/components/ui/menubar/MenubarSubContent.vue @@ -0,0 +1,39 @@ + + + diff --git a/components/ui/menubar/MenubarSubTrigger.vue b/components/ui/menubar/MenubarSubTrigger.vue new file mode 100644 index 00000000..a57ae6ff --- /dev/null +++ b/components/ui/menubar/MenubarSubTrigger.vue @@ -0,0 +1,30 @@ + + + diff --git a/components/ui/menubar/MenubarTrigger.vue b/components/ui/menubar/MenubarTrigger.vue new file mode 100644 index 00000000..97127022 --- /dev/null +++ b/components/ui/menubar/MenubarTrigger.vue @@ -0,0 +1,29 @@ + + + diff --git a/components/ui/menubar/index.ts b/components/ui/menubar/index.ts new file mode 100644 index 00000000..808ec4d3 --- /dev/null +++ b/components/ui/menubar/index.ts @@ -0,0 +1,15 @@ +export { default as Menubar } from './Menubar.vue' +export { default as MenubarItem } from './MenubarItem.vue' +export { default as MenubarContent } from './MenubarContent.vue' +export { default as MenubarGroup } from './MenubarGroup.vue' +export { default as MenubarMenu } from './MenubarMenu.vue' +export { default as MenubarRadioGroup } from './MenubarRadioGroup.vue' +export { default as MenubarRadioItem } from './MenubarRadioItem.vue' +export { default as MenubarCheckboxItem } from './MenubarCheckboxItem.vue' +export { default as MenubarSeparator } from './MenubarSeparator.vue' +export { default as MenubarSub } from './MenubarSub.vue' +export { default as MenubarSubContent } from './MenubarSubContent.vue' +export { default as MenubarSubTrigger } from './MenubarSubTrigger.vue' +export { default as MenubarTrigger } from './MenubarTrigger.vue' +export { default as MenubarShortcut } from './MenubarShortcut.vue' +export { default as MenubarLabel } from './MenubarLabel.vue' diff --git a/components/ui/navigation-menu/NavigationMenu.vue b/components/ui/navigation-menu/NavigationMenu.vue new file mode 100644 index 00000000..534084f5 --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenu.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuContent.vue b/components/ui/navigation-menu/NavigationMenuContent.vue new file mode 100644 index 00000000..45a9aff8 --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuContent.vue @@ -0,0 +1,34 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuIndicator.vue b/components/ui/navigation-menu/NavigationMenuIndicator.vue new file mode 100644 index 00000000..435b2bd6 --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuIndicator.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuItem.vue b/components/ui/navigation-menu/NavigationMenuItem.vue new file mode 100644 index 00000000..50e1565f --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuItem.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuLink.vue b/components/ui/navigation-menu/NavigationMenuLink.vue new file mode 100644 index 00000000..30c91c61 --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuLink.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuList.vue b/components/ui/navigation-menu/NavigationMenuList.vue new file mode 100644 index 00000000..d02ebc45 --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuList.vue @@ -0,0 +1,29 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuTrigger.vue b/components/ui/navigation-menu/NavigationMenuTrigger.vue new file mode 100644 index 00000000..d61badd4 --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuTrigger.vue @@ -0,0 +1,34 @@ + + + diff --git a/components/ui/navigation-menu/NavigationMenuViewport.vue b/components/ui/navigation-menu/NavigationMenuViewport.vue new file mode 100644 index 00000000..858417ae --- /dev/null +++ b/components/ui/navigation-menu/NavigationMenuViewport.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/ui/navigation-menu/index.ts b/components/ui/navigation-menu/index.ts new file mode 100644 index 00000000..1aca4227 --- /dev/null +++ b/components/ui/navigation-menu/index.ts @@ -0,0 +1,12 @@ +import { cva } from 'class-variance-authority' + +export { default as NavigationMenu } from './NavigationMenu.vue' +export { default as NavigationMenuList } from './NavigationMenuList.vue' +export { default as NavigationMenuItem } from './NavigationMenuItem.vue' +export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue' +export { default as NavigationMenuContent } from './NavigationMenuContent.vue' +export { default as NavigationMenuLink } from './NavigationMenuLink.vue' + +export const navigationMenuTriggerStyle = cva( + 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', +) diff --git a/components/ui/popover/Popover.vue b/components/ui/popover/Popover.vue new file mode 100644 index 00000000..1a5873a3 --- /dev/null +++ b/components/ui/popover/Popover.vue @@ -0,0 +1,15 @@ + + + diff --git a/components/ui/popover/PopoverContent.vue b/components/ui/popover/PopoverContent.vue new file mode 100644 index 00000000..a3348491 --- /dev/null +++ b/components/ui/popover/PopoverContent.vue @@ -0,0 +1,48 @@ + + + diff --git a/components/ui/popover/PopoverTrigger.vue b/components/ui/popover/PopoverTrigger.vue new file mode 100644 index 00000000..22f4772a --- /dev/null +++ b/components/ui/popover/PopoverTrigger.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/popover/index.ts b/components/ui/popover/index.ts new file mode 100644 index 00000000..495d55a8 --- /dev/null +++ b/components/ui/popover/index.ts @@ -0,0 +1,3 @@ +export { default as Popover } from './Popover.vue' +export { default as PopoverTrigger } from './PopoverTrigger.vue' +export { default as PopoverContent } from './PopoverContent.vue' diff --git a/components/ui/progress/Progress.vue b/components/ui/progress/Progress.vue new file mode 100644 index 00000000..5e6e7632 --- /dev/null +++ b/components/ui/progress/Progress.vue @@ -0,0 +1,39 @@ + + + diff --git a/components/ui/progress/index.ts b/components/ui/progress/index.ts new file mode 100644 index 00000000..eace9893 --- /dev/null +++ b/components/ui/progress/index.ts @@ -0,0 +1 @@ +export { default as Progress } from './Progress.vue' diff --git a/components/ui/radio-group/RadioGroup.vue b/components/ui/radio-group/RadioGroup.vue new file mode 100644 index 00000000..584ab408 --- /dev/null +++ b/components/ui/radio-group/RadioGroup.vue @@ -0,0 +1,25 @@ + + + diff --git a/components/ui/radio-group/RadioGroupItem.vue b/components/ui/radio-group/RadioGroupItem.vue new file mode 100644 index 00000000..508d2747 --- /dev/null +++ b/components/ui/radio-group/RadioGroupItem.vue @@ -0,0 +1,39 @@ + + + diff --git a/components/ui/radio-group/index.ts b/components/ui/radio-group/index.ts new file mode 100644 index 00000000..fa1da9c2 --- /dev/null +++ b/components/ui/radio-group/index.ts @@ -0,0 +1,2 @@ +export { default as RadioGroup } from './RadioGroup.vue' +export { default as RadioGroupItem } from './RadioGroupItem.vue' diff --git a/components/ui/range-calendar/RangeCalendar.vue b/components/ui/range-calendar/RangeCalendar.vue new file mode 100644 index 00000000..06bbc461 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendar.vue @@ -0,0 +1,60 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarCell.vue b/components/ui/range-calendar/RangeCalendarCell.vue new file mode 100644 index 00000000..54407009 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarCell.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarCellTrigger.vue b/components/ui/range-calendar/RangeCalendarCellTrigger.vue new file mode 100644 index 00000000..910e0c62 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarCellTrigger.vue @@ -0,0 +1,40 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarGrid.vue b/components/ui/range-calendar/RangeCalendarGrid.vue new file mode 100644 index 00000000..24a6e78e --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarGrid.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarGridBody.vue b/components/ui/range-calendar/RangeCalendarGridBody.vue new file mode 100644 index 00000000..cae15cc7 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarGridBody.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarGridHead.vue b/components/ui/range-calendar/RangeCalendarGridHead.vue new file mode 100644 index 00000000..c11ad365 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarGridHead.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarGridRow.vue b/components/ui/range-calendar/RangeCalendarGridRow.vue new file mode 100644 index 00000000..fde0cc4b --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarGridRow.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarHeadCell.vue b/components/ui/range-calendar/RangeCalendarHeadCell.vue new file mode 100644 index 00000000..5544ade7 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarHeadCell.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarHeader.vue b/components/ui/range-calendar/RangeCalendarHeader.vue new file mode 100644 index 00000000..ee0f840a --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarHeader.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarHeading.vue b/components/ui/range-calendar/RangeCalendarHeading.vue new file mode 100644 index 00000000..5dfb7009 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarHeading.vue @@ -0,0 +1,27 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarNextButton.vue b/components/ui/range-calendar/RangeCalendarNextButton.vue new file mode 100644 index 00000000..4ed90145 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarNextButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/ui/range-calendar/RangeCalendarPrevButton.vue b/components/ui/range-calendar/RangeCalendarPrevButton.vue new file mode 100644 index 00000000..c74beca4 --- /dev/null +++ b/components/ui/range-calendar/RangeCalendarPrevButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/ui/range-calendar/index.ts b/components/ui/range-calendar/index.ts new file mode 100644 index 00000000..7ba1637e --- /dev/null +++ b/components/ui/range-calendar/index.ts @@ -0,0 +1,12 @@ +export { default as RangeCalendar } from './RangeCalendar.vue' +export { default as RangeCalendarCell } from './RangeCalendarCell.vue' +export { default as RangeCalendarCellTrigger } from './RangeCalendarCellTrigger.vue' +export { default as RangeCalendarGrid } from './RangeCalendarGrid.vue' +export { default as RangeCalendarGridBody } from './RangeCalendarGridBody.vue' +export { default as RangeCalendarGridHead } from './RangeCalendarGridHead.vue' +export { default as RangeCalendarGridRow } from './RangeCalendarGridRow.vue' +export { default as RangeCalendarHeadCell } from './RangeCalendarHeadCell.vue' +export { default as RangeCalendarHeader } from './RangeCalendarHeader.vue' +export { default as RangeCalendarHeading } from './RangeCalendarHeading.vue' +export { default as RangeCalendarNextButton } from './RangeCalendarNextButton.vue' +export { default as RangeCalendarPrevButton } from './RangeCalendarPrevButton.vue' diff --git a/components/ui/select/Select.vue b/components/ui/select/Select.vue new file mode 100644 index 00000000..adc42fdf --- /dev/null +++ b/components/ui/select/Select.vue @@ -0,0 +1,15 @@ + + + diff --git a/components/ui/select/SelectContent.vue b/components/ui/select/SelectContent.vue new file mode 100644 index 00000000..f252c713 --- /dev/null +++ b/components/ui/select/SelectContent.vue @@ -0,0 +1,53 @@ + + + diff --git a/components/ui/select/SelectGroup.vue b/components/ui/select/SelectGroup.vue new file mode 100644 index 00000000..fa2f8827 --- /dev/null +++ b/components/ui/select/SelectGroup.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/select/SelectItem.vue b/components/ui/select/SelectItem.vue new file mode 100644 index 00000000..47e10d4e --- /dev/null +++ b/components/ui/select/SelectItem.vue @@ -0,0 +1,44 @@ + + + diff --git a/components/ui/select/SelectItemText.vue b/components/ui/select/SelectItemText.vue new file mode 100644 index 00000000..a0bb5c24 --- /dev/null +++ b/components/ui/select/SelectItemText.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/select/SelectLabel.vue b/components/ui/select/SelectLabel.vue new file mode 100644 index 00000000..08547d19 --- /dev/null +++ b/components/ui/select/SelectLabel.vue @@ -0,0 +1,13 @@ + + + diff --git a/components/ui/select/SelectScrollDownButton.vue b/components/ui/select/SelectScrollDownButton.vue new file mode 100644 index 00000000..12d6c297 --- /dev/null +++ b/components/ui/select/SelectScrollDownButton.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/select/SelectScrollUpButton.vue b/components/ui/select/SelectScrollUpButton.vue new file mode 100644 index 00000000..7628cb5c --- /dev/null +++ b/components/ui/select/SelectScrollUpButton.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/ui/select/SelectSeparator.vue b/components/ui/select/SelectSeparator.vue new file mode 100644 index 00000000..ef139ae9 --- /dev/null +++ b/components/ui/select/SelectSeparator.vue @@ -0,0 +1,17 @@ + + + diff --git a/components/ui/select/SelectTrigger.vue b/components/ui/select/SelectTrigger.vue new file mode 100644 index 00000000..3121f49a --- /dev/null +++ b/components/ui/select/SelectTrigger.vue @@ -0,0 +1,31 @@ + + + diff --git a/components/ui/select/SelectValue.vue b/components/ui/select/SelectValue.vue new file mode 100644 index 00000000..4bc37dd8 --- /dev/null +++ b/components/ui/select/SelectValue.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/select/index.ts b/components/ui/select/index.ts new file mode 100644 index 00000000..b1d89eed --- /dev/null +++ b/components/ui/select/index.ts @@ -0,0 +1,11 @@ +export { default as Select } from './Select.vue' +export { default as SelectValue } from './SelectValue.vue' +export { default as SelectTrigger } from './SelectTrigger.vue' +export { default as SelectContent } from './SelectContent.vue' +export { default as SelectGroup } from './SelectGroup.vue' +export { default as SelectItem } from './SelectItem.vue' +export { default as SelectItemText } from './SelectItemText.vue' +export { default as SelectLabel } from './SelectLabel.vue' +export { default as SelectSeparator } from './SelectSeparator.vue' +export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue' +export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue' diff --git a/components/ui/separator/Separator.vue b/components/ui/separator/Separator.vue new file mode 100644 index 00000000..8523000c --- /dev/null +++ b/components/ui/separator/Separator.vue @@ -0,0 +1,20 @@ + + + diff --git a/components/ui/separator/index.ts b/components/ui/separator/index.ts new file mode 100644 index 00000000..2287bcb9 --- /dev/null +++ b/components/ui/separator/index.ts @@ -0,0 +1 @@ +export { default as Separator } from './Separator.vue' diff --git a/components/ui/skeleton/Skeleton.vue b/components/ui/skeleton/Skeleton.vue new file mode 100644 index 00000000..52625122 --- /dev/null +++ b/components/ui/skeleton/Skeleton.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/skeleton/index.ts b/components/ui/skeleton/index.ts new file mode 100644 index 00000000..be21fad3 --- /dev/null +++ b/components/ui/skeleton/index.ts @@ -0,0 +1 @@ +export { default as Skeleton } from './Skeleton.vue' diff --git a/components/ui/sonner/Sonner.vue b/components/ui/sonner/Sonner.vue new file mode 100644 index 00000000..b82b29e2 --- /dev/null +++ b/components/ui/sonner/Sonner.vue @@ -0,0 +1,22 @@ + + + diff --git a/components/ui/sonner/index.ts b/components/ui/sonner/index.ts new file mode 100644 index 00000000..0d4a6423 --- /dev/null +++ b/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from './Sonner.vue' diff --git a/components/ui/switch/Switch.vue b/components/ui/switch/Switch.vue new file mode 100644 index 00000000..d0162eaa --- /dev/null +++ b/components/ui/switch/Switch.vue @@ -0,0 +1,37 @@ + + + diff --git a/components/ui/switch/index.ts b/components/ui/switch/index.ts new file mode 100644 index 00000000..87b4b17d --- /dev/null +++ b/components/ui/switch/index.ts @@ -0,0 +1 @@ +export { default as Switch } from './Switch.vue' diff --git a/components/ui/table/Table.vue b/components/ui/table/Table.vue new file mode 100644 index 00000000..96fd1da8 --- /dev/null +++ b/components/ui/table/Table.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/table/TableBody.vue b/components/ui/table/TableBody.vue new file mode 100644 index 00000000..be95025b --- /dev/null +++ b/components/ui/table/TableBody.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/table/TableCaption.vue b/components/ui/table/TableCaption.vue new file mode 100644 index 00000000..97d6f481 --- /dev/null +++ b/components/ui/table/TableCaption.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/table/TableCell.vue b/components/ui/table/TableCell.vue new file mode 100644 index 00000000..491a794b --- /dev/null +++ b/components/ui/table/TableCell.vue @@ -0,0 +1,21 @@ + + + diff --git a/components/ui/table/TableEmpty.vue b/components/ui/table/TableEmpty.vue new file mode 100644 index 00000000..2f06baa6 --- /dev/null +++ b/components/ui/table/TableEmpty.vue @@ -0,0 +1,37 @@ + + + diff --git a/components/ui/table/TableFooter.vue b/components/ui/table/TableFooter.vue new file mode 100644 index 00000000..1e4cb678 --- /dev/null +++ b/components/ui/table/TableFooter.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/table/TableHead.vue b/components/ui/table/TableHead.vue new file mode 100644 index 00000000..66a8c43a --- /dev/null +++ b/components/ui/table/TableHead.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/table/TableHeader.vue b/components/ui/table/TableHeader.vue new file mode 100644 index 00000000..909813b5 --- /dev/null +++ b/components/ui/table/TableHeader.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/table/TableRow.vue b/components/ui/table/TableRow.vue new file mode 100644 index 00000000..4a4bd918 --- /dev/null +++ b/components/ui/table/TableRow.vue @@ -0,0 +1,14 @@ + + + diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts new file mode 100644 index 00000000..44582d35 --- /dev/null +++ b/components/ui/table/index.ts @@ -0,0 +1,8 @@ +export { default as Table } from './Table.vue' +export { default as TableBody } from './TableBody.vue' +export { default as TableCell } from './TableCell.vue' +export { default as TableHead } from './TableHead.vue' +export { default as TableHeader } from './TableHeader.vue' +export { default as TableRow } from './TableRow.vue' +export { default as TableCaption } from './TableCaption.vue' +export { default as TableEmpty } from './TableEmpty.vue' diff --git a/components/ui/tabs/Tabs.vue b/components/ui/tabs/Tabs.vue new file mode 100644 index 00000000..2fa0971f --- /dev/null +++ b/components/ui/tabs/Tabs.vue @@ -0,0 +1,15 @@ + + + diff --git a/components/ui/tabs/TabsContent.vue b/components/ui/tabs/TabsContent.vue new file mode 100644 index 00000000..508f00d0 --- /dev/null +++ b/components/ui/tabs/TabsContent.vue @@ -0,0 +1,22 @@ + + + diff --git a/components/ui/tabs/TabsList.vue b/components/ui/tabs/TabsList.vue new file mode 100644 index 00000000..8d012075 --- /dev/null +++ b/components/ui/tabs/TabsList.vue @@ -0,0 +1,25 @@ + + + diff --git a/components/ui/tabs/TabsTrigger.vue b/components/ui/tabs/TabsTrigger.vue new file mode 100644 index 00000000..fc0f4146 --- /dev/null +++ b/components/ui/tabs/TabsTrigger.vue @@ -0,0 +1,27 @@ + + + diff --git a/components/ui/tabs/index.ts b/components/ui/tabs/index.ts new file mode 100644 index 00000000..fbea0c68 --- /dev/null +++ b/components/ui/tabs/index.ts @@ -0,0 +1,4 @@ +export { default as Tabs } from './Tabs.vue' +export { default as TabsTrigger } from './TabsTrigger.vue' +export { default as TabsList } from './TabsList.vue' +export { default as TabsContent } from './TabsContent.vue' diff --git a/components/ui/textarea/Textarea.vue b/components/ui/textarea/Textarea.vue new file mode 100644 index 00000000..8edf162f --- /dev/null +++ b/components/ui/textarea/Textarea.vue @@ -0,0 +1,24 @@ + + +