Lovable commited on
Commit
1d7851a
·
0 Parent(s):

template: tanstack_start_ts

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +32 -0
  2. .prettierignore +7 -0
  3. .prettierrc +6 -0
  4. bunfig.toml +2 -0
  5. components.json +22 -0
  6. eslint.config.js +28 -0
  7. package-lock.json +0 -0
  8. package.json +87 -0
  9. src/components/ui/accordion.tsx +51 -0
  10. src/components/ui/alert-dialog.tsx +115 -0
  11. src/components/ui/alert.tsx +49 -0
  12. src/components/ui/aspect-ratio.tsx +5 -0
  13. src/components/ui/avatar.tsx +47 -0
  14. src/components/ui/badge.tsx +32 -0
  15. src/components/ui/breadcrumb.tsx +101 -0
  16. src/components/ui/button.tsx +49 -0
  17. src/components/ui/calendar.tsx +177 -0
  18. src/components/ui/card.tsx +55 -0
  19. src/components/ui/carousel.tsx +240 -0
  20. src/components/ui/chart.tsx +331 -0
  21. src/components/ui/checkbox.tsx +26 -0
  22. src/components/ui/collapsible.tsx +11 -0
  23. src/components/ui/command.tsx +143 -0
  24. src/components/ui/context-menu.tsx +187 -0
  25. src/components/ui/dialog.tsx +104 -0
  26. src/components/ui/drawer.tsx +98 -0
  27. src/components/ui/dropdown-menu.tsx +188 -0
  28. src/components/ui/form.tsx +171 -0
  29. src/components/ui/hover-card.tsx +27 -0
  30. src/components/ui/input-otp.tsx +69 -0
  31. src/components/ui/input.tsx +22 -0
  32. src/components/ui/label.tsx +21 -0
  33. src/components/ui/menubar.tsx +229 -0
  34. src/components/ui/navigation-menu.tsx +120 -0
  35. src/components/ui/pagination.tsx +98 -0
  36. src/components/ui/popover.tsx +31 -0
  37. src/components/ui/progress.tsx +25 -0
  38. src/components/ui/radio-group.tsx +36 -0
  39. src/components/ui/resizable.tsx +37 -0
  40. src/components/ui/scroll-area.tsx +44 -0
  41. src/components/ui/select.tsx +152 -0
  42. src/components/ui/separator.tsx +24 -0
  43. src/components/ui/sheet.tsx +122 -0
  44. src/components/ui/sidebar.tsx +744 -0
  45. src/components/ui/skeleton.tsx +7 -0
  46. src/components/ui/slider.tsx +23 -0
  47. src/components/ui/sonner.tsx +23 -0
  48. src/components/ui/switch.tsx +27 -0
  49. src/components/ui/table.tsx +94 -0
  50. src/components/ui/tabs.tsx +53 -0
.gitignore ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ .output
14
+ .vinxi
15
+ .tanstack/**
16
+ .nitro
17
+ *.local
18
+
19
+ # Wrangler / Cloudflare
20
+ .wrangler/
21
+ .dev.vars
22
+
23
+ # Editor directories and files
24
+ .vscode/*
25
+ !.vscode/extensions.json
26
+ .idea
27
+ .DS_Store
28
+ *.suo
29
+ *.ntvs*
30
+ *.njsproj
31
+ *.sln
32
+ *.sw?
.prettierignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ .output
4
+ .vinxi
5
+ pnpm-lock.yaml
6
+ package-lock.json
7
+ routeTree.gen.ts
.prettierrc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "printWidth": 100,
3
+ "semi": true,
4
+ "singleQuote": false,
5
+ "trailingComma": "all"
6
+ }
bunfig.toml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [install]
2
+ saveTextLockfile = false
components.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "css": "src/styles.css",
8
+ "baseColor": "slate",
9
+ "cssVariables": true,
10
+ "prefix": ""
11
+ },
12
+ "iconLibrary": "lucide",
13
+ "rtl": false,
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ },
21
+ "registries": {}
22
+ }
eslint.config.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from "@eslint/js";
2
+ import eslintPluginPrettier from "eslint-plugin-prettier/recommended";
3
+ import globals from "globals";
4
+ import reactHooks from "eslint-plugin-react-hooks";
5
+ import reactRefresh from "eslint-plugin-react-refresh";
6
+ import tseslint from "typescript-eslint";
7
+
8
+ export default tseslint.config(
9
+ { ignores: ["dist", ".output", ".vinxi"] },
10
+ {
11
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
12
+ files: ["**/*.{ts,tsx}"],
13
+ languageOptions: {
14
+ ecmaVersion: 2020,
15
+ globals: globals.browser,
16
+ },
17
+ plugins: {
18
+ "react-hooks": reactHooks,
19
+ "react-refresh": reactRefresh,
20
+ },
21
+ rules: {
22
+ ...reactHooks.configs.recommended.rules,
23
+ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
24
+ "@typescript-eslint/no-unused-vars": "off",
25
+ },
26
+ },
27
+ eslintPluginPrettier,
28
+ );
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "tanstack_start_ts",
3
+ "private": true,
4
+ "sideEffects": false,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite dev",
8
+ "build": "vite build",
9
+ "build:dev": "vite build --mode development",
10
+ "preview": "vite preview",
11
+ "lint": "eslint .",
12
+ "format": "prettier --write ."
13
+ },
14
+ "dependencies": {
15
+ "@cloudflare/vite-plugin": "^1.25.5",
16
+ "@hookform/resolvers": "^5.2.2",
17
+ "@radix-ui/react-accordion": "^1.2.12",
18
+ "@radix-ui/react-alert-dialog": "^1.1.15",
19
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
20
+ "@radix-ui/react-avatar": "^1.1.11",
21
+ "@radix-ui/react-checkbox": "^1.3.3",
22
+ "@radix-ui/react-collapsible": "^1.1.12",
23
+ "@radix-ui/react-context-menu": "^2.2.16",
24
+ "@radix-ui/react-dialog": "^1.1.15",
25
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
26
+ "@radix-ui/react-hover-card": "^1.1.15",
27
+ "@radix-ui/react-label": "^2.1.8",
28
+ "@radix-ui/react-menubar": "^1.1.16",
29
+ "@radix-ui/react-navigation-menu": "^1.2.14",
30
+ "@radix-ui/react-popover": "^1.1.15",
31
+ "@radix-ui/react-progress": "^1.1.8",
32
+ "@radix-ui/react-radio-group": "^1.3.8",
33
+ "@radix-ui/react-scroll-area": "^1.2.10",
34
+ "@radix-ui/react-select": "^2.2.6",
35
+ "@radix-ui/react-separator": "^1.1.8",
36
+ "@radix-ui/react-slider": "^1.3.6",
37
+ "@radix-ui/react-slot": "^1.2.4",
38
+ "@radix-ui/react-switch": "^1.2.6",
39
+ "@radix-ui/react-tabs": "^1.1.13",
40
+ "@radix-ui/react-toggle": "^1.1.10",
41
+ "@radix-ui/react-toggle-group": "^1.1.11",
42
+ "@radix-ui/react-tooltip": "^1.2.8",
43
+ "@tailwindcss/vite": "^4.2.1",
44
+ "@tanstack/react-query": "^5.83.0",
45
+ "@tanstack/react-router": "^1.168.0",
46
+ "@tanstack/react-start": "^1.167.14",
47
+ "@tanstack/router-plugin": "^1.167.10",
48
+ "class-variance-authority": "^0.7.1",
49
+ "clsx": "^2.1.1",
50
+ "cmdk": "^1.1.1",
51
+ "date-fns": "^4.1.0",
52
+ "embla-carousel-react": "^8.6.0",
53
+ "input-otp": "^1.4.2",
54
+ "lucide-react": "^0.575.0",
55
+ "react": "^19.2.0",
56
+ "react-day-picker": "^9.14.0",
57
+ "react-dom": "^19.2.0",
58
+ "react-hook-form": "^7.71.2",
59
+ "react-resizable-panels": "^4.6.5",
60
+ "recharts": "^2.15.4",
61
+ "sonner": "^2.0.7",
62
+ "tailwind-merge": "^3.5.0",
63
+ "tailwindcss": "^4.2.1",
64
+ "tw-animate-css": "^1.3.4",
65
+ "vaul": "^1.1.2",
66
+ "vite-tsconfig-paths": "^6.0.2",
67
+ "zod": "^3.24.2"
68
+ },
69
+ "devDependencies": {
70
+ "@eslint/js": "^9.32.0",
71
+ "@lovable.dev/vite-tanstack-config": "^1.4.0",
72
+ "@types/node": "^22.16.5",
73
+ "@types/react": "^19.2.0",
74
+ "@types/react-dom": "^19.2.0",
75
+ "@vitejs/plugin-react": "^5.0.4",
76
+ "eslint": "^9.32.0",
77
+ "eslint-config-prettier": "^10.1.1",
78
+ "eslint-plugin-prettier": "^5.2.6",
79
+ "eslint-plugin-react-hooks": "^5.2.0",
80
+ "eslint-plugin-react-refresh": "^0.4.20",
81
+ "globals": "^15.15.0",
82
+ "prettier": "^3.7.3",
83
+ "typescript": "^5.8.3",
84
+ "typescript-eslint": "^8.56.1",
85
+ "vite": "^7.3.1"
86
+ }
87
+ }
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
3
+ import { ChevronDown } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const Accordion = AccordionPrimitive.Root;
8
+
9
+ const AccordionItem = React.forwardRef<
10
+ React.ElementRef<typeof AccordionPrimitive.Item>,
11
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
+ >(({ className, ...props }, ref) => (
13
+ <AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
14
+ ));
15
+ AccordionItem.displayName = "AccordionItem";
16
+
17
+ const AccordionTrigger = React.forwardRef<
18
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
19
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
20
+ >(({ className, children, ...props }, ref) => (
21
+ <AccordionPrimitive.Header className="flex">
22
+ <AccordionPrimitive.Trigger
23
+ ref={ref}
24
+ className={cn(
25
+ "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
26
+ className,
27
+ )}
28
+ {...props}
29
+ >
30
+ {children}
31
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
32
+ </AccordionPrimitive.Trigger>
33
+ </AccordionPrimitive.Header>
34
+ ));
35
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
36
+
37
+ const AccordionContent = React.forwardRef<
38
+ React.ElementRef<typeof AccordionPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
40
+ >(({ className, children, ...props }, ref) => (
41
+ <AccordionPrimitive.Content
42
+ ref={ref}
43
+ className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
44
+ {...props}
45
+ >
46
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
47
+ </AccordionPrimitive.Content>
48
+ ));
49
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName;
50
+
51
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
3
+
4
+ import { cn } from "@/lib/utils";
5
+ import { buttonVariants } from "@/components/ui/button";
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root;
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal;
12
+
13
+ const AlertDialogOverlay = React.forwardRef<
14
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <AlertDialogPrimitive.Overlay
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
+ className,
21
+ )}
22
+ {...props}
23
+ ref={ref}
24
+ />
25
+ ));
26
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
27
+
28
+ const AlertDialogContent = React.forwardRef<
29
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
+ >(({ className, ...props }, ref) => (
32
+ <AlertDialogPortal>
33
+ <AlertDialogOverlay />
34
+ <AlertDialogPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
38
+ className,
39
+ )}
40
+ {...props}
41
+ />
42
+ </AlertDialogPortal>
43
+ ));
44
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
45
+
46
+ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
47
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
48
+ );
49
+ AlertDialogHeader.displayName = "AlertDialogHeader";
50
+
51
+ const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
52
+ <div
53
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
54
+ {...props}
55
+ />
56
+ );
57
+ AlertDialogFooter.displayName = "AlertDialogFooter";
58
+
59
+ const AlertDialogTitle = React.forwardRef<
60
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
61
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
62
+ >(({ className, ...props }, ref) => (
63
+ <AlertDialogPrimitive.Title
64
+ ref={ref}
65
+ className={cn("text-lg font-semibold", className)}
66
+ {...props}
67
+ />
68
+ ));
69
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
70
+
71
+ const AlertDialogDescription = React.forwardRef<
72
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
73
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
74
+ >(({ className, ...props }, ref) => (
75
+ <AlertDialogPrimitive.Description
76
+ ref={ref}
77
+ className={cn("text-sm text-muted-foreground", className)}
78
+ {...props}
79
+ />
80
+ ));
81
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
82
+
83
+ const AlertDialogAction = React.forwardRef<
84
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
85
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
86
+ >(({ className, ...props }, ref) => (
87
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
88
+ ));
89
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
90
+
91
+ const AlertDialogCancel = React.forwardRef<
92
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
93
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
94
+ >(({ className, ...props }, ref) => (
95
+ <AlertDialogPrimitive.Cancel
96
+ ref={ref}
97
+ className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
98
+ {...props}
99
+ />
100
+ ));
101
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
102
+
103
+ export {
104
+ AlertDialog,
105
+ AlertDialogPortal,
106
+ AlertDialogOverlay,
107
+ AlertDialogTrigger,
108
+ AlertDialogContent,
109
+ AlertDialogHeader,
110
+ AlertDialogFooter,
111
+ AlertDialogTitle,
112
+ AlertDialogDescription,
113
+ AlertDialogAction,
114
+ AlertDialogCancel,
115
+ };
src/components/ui/alert.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ },
20
+ );
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
27
+ ));
28
+ Alert.displayName = "Alert";
29
+
30
+ const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
31
+ ({ className, ...props }, ref) => (
32
+ <h5
33
+ ref={ref}
34
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
35
+ {...props}
36
+ />
37
+ ),
38
+ );
39
+ AlertTitle.displayName = "AlertTitle";
40
+
41
+ const AlertDescription = React.forwardRef<
42
+ HTMLParagraphElement,
43
+ React.HTMLAttributes<HTMLParagraphElement>
44
+ >(({ className, ...props }, ref) => (
45
+ <div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
46
+ ));
47
+ AlertDescription.displayName = "AlertDescription";
48
+
49
+ export { Alert, AlertTitle, AlertDescription };
src/components/ui/aspect-ratio.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root;
4
+
5
+ export { AspectRatio };
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
15
+ {...props}
16
+ />
17
+ ));
18
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
19
+
20
+ const AvatarImage = React.forwardRef<
21
+ React.ElementRef<typeof AvatarPrimitive.Image>,
22
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
23
+ >(({ className, ...props }, ref) => (
24
+ <AvatarPrimitive.Image
25
+ ref={ref}
26
+ className={cn("aspect-square h-full w-full", className)}
27
+ {...props}
28
+ />
29
+ ));
30
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
31
+
32
+ const AvatarFallback = React.forwardRef<
33
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
34
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
35
+ >(({ className, ...props }, ref) => (
36
+ <AvatarPrimitive.Fallback
37
+ ref={ref}
38
+ className={cn(
39
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ ));
45
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
46
+
47
+ export { Avatar, AvatarImage, AvatarFallback };
src/components/ui/badge.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary:
13
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ destructive:
15
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
16
+ outline: "text-foreground",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "default",
21
+ },
22
+ },
23
+ );
24
+
25
+ export interface BadgeProps
26
+ extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
27
+
28
+ function Badge({ className, variant, ...props }: BadgeProps) {
29
+ return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
30
+ }
31
+
32
+ export { Badge, badgeVariants };
src/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode;
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
13
+ Breadcrumb.displayName = "Breadcrumb";
14
+
15
+ const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(
16
+ ({ className, ...props }, ref) => (
17
+ <ol
18
+ ref={ref}
19
+ className={cn(
20
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ ),
26
+ );
27
+ BreadcrumbList.displayName = "BreadcrumbList";
28
+
29
+ const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
30
+ ({ className, ...props }, ref) => (
31
+ <li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
32
+ ),
33
+ );
34
+ BreadcrumbItem.displayName = "BreadcrumbItem";
35
+
36
+ const BreadcrumbLink = React.forwardRef<
37
+ HTMLAnchorElement,
38
+ React.ComponentPropsWithoutRef<"a"> & {
39
+ asChild?: boolean;
40
+ }
41
+ >(({ asChild, className, ...props }, ref) => {
42
+ const Comp = asChild ? Slot : "a";
43
+
44
+ return (
45
+ <Comp
46
+ ref={ref}
47
+ className={cn("transition-colors hover:text-foreground", className)}
48
+ {...props}
49
+ />
50
+ );
51
+ });
52
+ BreadcrumbLink.displayName = "BreadcrumbLink";
53
+
54
+ const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
55
+ ({ className, ...props }, ref) => (
56
+ <span
57
+ ref={ref}
58
+ role="link"
59
+ aria-disabled="true"
60
+ aria-current="page"
61
+ className={cn("font-normal text-foreground", className)}
62
+ {...props}
63
+ />
64
+ ),
65
+ );
66
+ BreadcrumbPage.displayName = "BreadcrumbPage";
67
+
68
+ const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
69
+ <li
70
+ role="presentation"
71
+ aria-hidden="true"
72
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
73
+ {...props}
74
+ >
75
+ {children ?? <ChevronRight />}
76
+ </li>
77
+ );
78
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
79
+
80
+ const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
81
+ <span
82
+ role="presentation"
83
+ aria-hidden="true"
84
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
85
+ {...props}
86
+ >
87
+ <MoreHorizontal className="h-4 w-4" />
88
+ <span className="sr-only">More</span>
89
+ </span>
90
+ );
91
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
92
+
93
+ export {
94
+ Breadcrumb,
95
+ BreadcrumbList,
96
+ BreadcrumbItem,
97
+ BreadcrumbLink,
98
+ BreadcrumbPage,
99
+ BreadcrumbSeparator,
100
+ BreadcrumbEllipsis,
101
+ };
src/components/ui/button.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
13
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
14
+ outline:
15
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
16
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
17
+ ghost: "hover:bg-accent hover:text-accent-foreground",
18
+ link: "text-primary underline-offset-4 hover:underline",
19
+ },
20
+ size: {
21
+ default: "h-9 px-4 py-2",
22
+ sm: "h-8 rounded-md px-3 text-xs",
23
+ lg: "h-10 rounded-md px-8",
24
+ icon: "h-9 w-9",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: "default",
29
+ size: "default",
30
+ },
31
+ },
32
+ );
33
+
34
+ export interface ButtonProps
35
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
36
+ asChild?: boolean;
37
+ }
38
+
39
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
40
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
41
+ const Comp = asChild ? Slot : "button";
42
+ return (
43
+ <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
44
+ );
45
+ },
46
+ );
47
+ Button.displayName = "Button";
48
+
49
+ export { Button, buttonVariants };
src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
5
+ import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
6
+
7
+ import { cn } from "@/lib/utils";
8
+ import { Button, buttonVariants } from "@/components/ui/button";
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ captionLayout = "label",
15
+ buttonVariant = "ghost",
16
+ formatters,
17
+ components,
18
+ ...props
19
+ }: React.ComponentProps<typeof DayPicker> & {
20
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"];
21
+ }) {
22
+ const defaultClassNames = getDefaultClassNames();
23
+
24
+ return (
25
+ <DayPicker
26
+ showOutsideDays={showOutsideDays}
27
+ className={cn(
28
+ "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
29
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
30
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
31
+ className,
32
+ )}
33
+ captionLayout={captionLayout}
34
+ formatters={{
35
+ formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
36
+ ...formatters,
37
+ }}
38
+ classNames={{
39
+ root: cn("w-fit", defaultClassNames.root),
40
+ months: cn("relative flex flex-col gap-4 md:flex-row", defaultClassNames.months),
41
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
42
+ nav: cn(
43
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
44
+ defaultClassNames.nav,
45
+ ),
46
+ button_previous: cn(
47
+ buttonVariants({ variant: buttonVariant }),
48
+ "h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50",
49
+ defaultClassNames.button_previous,
50
+ ),
51
+ button_next: cn(
52
+ buttonVariants({ variant: buttonVariant }),
53
+ "h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50",
54
+ defaultClassNames.button_next,
55
+ ),
56
+ month_caption: cn(
57
+ "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
58
+ defaultClassNames.month_caption,
59
+ ),
60
+ dropdowns: cn(
61
+ "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
62
+ defaultClassNames.dropdowns,
63
+ ),
64
+ dropdown_root: cn(
65
+ "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
66
+ defaultClassNames.dropdown_root,
67
+ ),
68
+ dropdown: cn("bg-popover absolute inset-0 opacity-0", defaultClassNames.dropdown),
69
+ caption_label: cn(
70
+ "select-none font-medium",
71
+ captionLayout === "label"
72
+ ? "text-sm"
73
+ : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
74
+ defaultClassNames.caption_label,
75
+ ),
76
+ table: "w-full border-collapse",
77
+ weekdays: cn("flex", defaultClassNames.weekdays),
78
+ weekday: cn(
79
+ "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
80
+ defaultClassNames.weekday,
81
+ ),
82
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
83
+ week_number_header: cn("w-(--cell-size) select-none", defaultClassNames.week_number_header),
84
+ week_number: cn(
85
+ "text-muted-foreground select-none text-[0.8rem]",
86
+ defaultClassNames.week_number,
87
+ ),
88
+ day: cn(
89
+ "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
90
+ defaultClassNames.day,
91
+ ),
92
+ range_start: cn("bg-accent rounded-l-md", defaultClassNames.range_start),
93
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
94
+ range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
95
+ today: cn(
96
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
97
+ defaultClassNames.today,
98
+ ),
99
+ outside: cn(
100
+ "text-muted-foreground aria-selected:text-muted-foreground",
101
+ defaultClassNames.outside,
102
+ ),
103
+ disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
104
+ hidden: cn("invisible", defaultClassNames.hidden),
105
+ ...classNames,
106
+ }}
107
+ components={{
108
+ Root: ({ className, rootRef, ...props }) => {
109
+ return <div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />;
110
+ },
111
+ Chevron: ({ className, orientation, ...props }) => {
112
+ if (orientation === "left") {
113
+ return <ChevronLeftIcon className={cn("size-4", className)} {...props} />;
114
+ }
115
+
116
+ if (orientation === "right") {
117
+ return <ChevronRightIcon className={cn("size-4", className)} {...props} />;
118
+ }
119
+
120
+ return <ChevronDownIcon className={cn("size-4", className)} {...props} />;
121
+ },
122
+ DayButton: CalendarDayButton,
123
+ WeekNumber: ({ children, ...props }) => {
124
+ return (
125
+ <td {...props}>
126
+ <div className="flex size-(--cell-size) items-center justify-center text-center">
127
+ {children}
128
+ </div>
129
+ </td>
130
+ );
131
+ },
132
+ ...components,
133
+ }}
134
+ {...props}
135
+ />
136
+ );
137
+ }
138
+
139
+ function CalendarDayButton({
140
+ className,
141
+ day,
142
+ modifiers,
143
+ ...props
144
+ }: React.ComponentProps<typeof DayButton>) {
145
+ const defaultClassNames = getDefaultClassNames();
146
+
147
+ const ref = React.useRef<HTMLButtonElement>(null);
148
+ React.useEffect(() => {
149
+ if (modifiers.focused) ref.current?.focus();
150
+ }, [modifiers.focused]);
151
+
152
+ return (
153
+ <Button
154
+ ref={ref}
155
+ variant="ghost"
156
+ size="icon"
157
+ data-day={day.date.toLocaleDateString()}
158
+ data-selected-single={
159
+ modifiers.selected &&
160
+ !modifiers.range_start &&
161
+ !modifiers.range_end &&
162
+ !modifiers.range_middle
163
+ }
164
+ data-range-start={modifiers.range_start}
165
+ data-range-end={modifiers.range_end}
166
+ data-range-middle={modifiers.range_middle}
167
+ className={cn(
168
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
169
+ defaultClassNames.day,
170
+ className,
171
+ )}
172
+ {...props}
173
+ />
174
+ );
175
+ }
176
+
177
+ export { Calendar, CalendarDayButton };
src/components/ui/card.tsx ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div
8
+ ref={ref}
9
+ className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
10
+ {...props}
11
+ />
12
+ ),
13
+ );
14
+ Card.displayName = "Card";
15
+
16
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
17
+ ({ className, ...props }, ref) => (
18
+ <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
19
+ ),
20
+ );
21
+ CardHeader.displayName = "CardHeader";
22
+
23
+ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
24
+ ({ className, ...props }, ref) => (
25
+ <div
26
+ ref={ref}
27
+ className={cn("font-semibold leading-none tracking-tight", className)}
28
+ {...props}
29
+ />
30
+ ),
31
+ );
32
+ CardTitle.displayName = "CardTitle";
33
+
34
+ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
35
+ ({ className, ...props }, ref) => (
36
+ <div ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
37
+ ),
38
+ );
39
+ CardDescription.displayName = "CardDescription";
40
+
41
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
42
+ ({ className, ...props }, ref) => (
43
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
44
+ ),
45
+ );
46
+ CardContent.displayName = "CardContent";
47
+
48
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
49
+ ({ className, ...props }, ref) => (
50
+ <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
51
+ ),
52
+ );
53
+ CardFooter.displayName = "CardFooter";
54
+
55
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
src/components/ui/carousel.tsx ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
3
+ import { ArrowLeft, ArrowRight } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ type CarouselApi = UseEmblaCarouselType[1];
9
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
10
+ type CarouselOptions = UseCarouselParameters[0];
11
+ type CarouselPlugin = UseCarouselParameters[1];
12
+
13
+ type CarouselProps = {
14
+ opts?: CarouselOptions;
15
+ plugins?: CarouselPlugin;
16
+ orientation?: "horizontal" | "vertical";
17
+ setApi?: (api: CarouselApi) => void;
18
+ };
19
+
20
+ type CarouselContextProps = {
21
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
22
+ api: ReturnType<typeof useEmblaCarousel>[1];
23
+ scrollPrev: () => void;
24
+ scrollNext: () => void;
25
+ canScrollPrev: boolean;
26
+ canScrollNext: boolean;
27
+ } & CarouselProps;
28
+
29
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null);
30
+
31
+ function useCarousel() {
32
+ const context = React.useContext(CarouselContext);
33
+
34
+ if (!context) {
35
+ throw new Error("useCarousel must be used within a <Carousel />");
36
+ }
37
+
38
+ return context;
39
+ }
40
+
41
+ const Carousel = React.forwardRef<
42
+ HTMLDivElement,
43
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
44
+ >(({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
45
+ const [carouselRef, api] = useEmblaCarousel(
46
+ {
47
+ ...opts,
48
+ axis: orientation === "horizontal" ? "x" : "y",
49
+ },
50
+ plugins,
51
+ );
52
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
53
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
54
+
55
+ const onSelect = React.useCallback((api: CarouselApi) => {
56
+ if (!api) {
57
+ return;
58
+ }
59
+
60
+ setCanScrollPrev(api.canScrollPrev());
61
+ setCanScrollNext(api.canScrollNext());
62
+ }, []);
63
+
64
+ const scrollPrev = React.useCallback(() => {
65
+ api?.scrollPrev();
66
+ }, [api]);
67
+
68
+ const scrollNext = React.useCallback(() => {
69
+ api?.scrollNext();
70
+ }, [api]);
71
+
72
+ const handleKeyDown = React.useCallback(
73
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
74
+ if (event.key === "ArrowLeft") {
75
+ event.preventDefault();
76
+ scrollPrev();
77
+ } else if (event.key === "ArrowRight") {
78
+ event.preventDefault();
79
+ scrollNext();
80
+ }
81
+ },
82
+ [scrollPrev, scrollNext],
83
+ );
84
+
85
+ React.useEffect(() => {
86
+ if (!api || !setApi) {
87
+ return;
88
+ }
89
+
90
+ setApi(api);
91
+ }, [api, setApi]);
92
+
93
+ React.useEffect(() => {
94
+ if (!api) {
95
+ return;
96
+ }
97
+
98
+ onSelect(api);
99
+ api.on("reInit", onSelect);
100
+ api.on("select", onSelect);
101
+
102
+ return () => {
103
+ api?.off("select", onSelect);
104
+ };
105
+ }, [api, onSelect]);
106
+
107
+ return (
108
+ <CarouselContext.Provider
109
+ value={{
110
+ carouselRef,
111
+ api: api,
112
+ opts,
113
+ orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
114
+ scrollPrev,
115
+ scrollNext,
116
+ canScrollPrev,
117
+ canScrollNext,
118
+ }}
119
+ >
120
+ <div
121
+ ref={ref}
122
+ onKeyDownCapture={handleKeyDown}
123
+ className={cn("relative", className)}
124
+ role="region"
125
+ aria-roledescription="carousel"
126
+ {...props}
127
+ >
128
+ {children}
129
+ </div>
130
+ </CarouselContext.Provider>
131
+ );
132
+ });
133
+ Carousel.displayName = "Carousel";
134
+
135
+ const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
136
+ ({ className, ...props }, ref) => {
137
+ const { carouselRef, orientation } = useCarousel();
138
+
139
+ return (
140
+ <div ref={carouselRef} className="overflow-hidden">
141
+ <div
142
+ ref={ref}
143
+ className={cn(
144
+ "flex",
145
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
146
+ className,
147
+ )}
148
+ {...props}
149
+ />
150
+ </div>
151
+ );
152
+ },
153
+ );
154
+ CarouselContent.displayName = "CarouselContent";
155
+
156
+ const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
157
+ ({ className, ...props }, ref) => {
158
+ const { orientation } = useCarousel();
159
+
160
+ return (
161
+ <div
162
+ ref={ref}
163
+ role="group"
164
+ aria-roledescription="slide"
165
+ className={cn(
166
+ "min-w-0 shrink-0 grow-0 basis-full",
167
+ orientation === "horizontal" ? "pl-4" : "pt-4",
168
+ className,
169
+ )}
170
+ {...props}
171
+ />
172
+ );
173
+ },
174
+ );
175
+ CarouselItem.displayName = "CarouselItem";
176
+
177
+ const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
178
+ ({ className, variant = "outline", size = "icon", ...props }, ref) => {
179
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
180
+
181
+ return (
182
+ <Button
183
+ ref={ref}
184
+ variant={variant}
185
+ size={size}
186
+ className={cn(
187
+ "absolute h-8 w-8 rounded-full",
188
+ orientation === "horizontal"
189
+ ? "-left-12 top-1/2 -translate-y-1/2"
190
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
191
+ className,
192
+ )}
193
+ disabled={!canScrollPrev}
194
+ onClick={scrollPrev}
195
+ {...props}
196
+ >
197
+ <ArrowLeft className="h-4 w-4" />
198
+ <span className="sr-only">Previous slide</span>
199
+ </Button>
200
+ );
201
+ },
202
+ );
203
+ CarouselPrevious.displayName = "CarouselPrevious";
204
+
205
+ const CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
206
+ ({ className, variant = "outline", size = "icon", ...props }, ref) => {
207
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
208
+
209
+ return (
210
+ <Button
211
+ ref={ref}
212
+ variant={variant}
213
+ size={size}
214
+ className={cn(
215
+ "absolute h-8 w-8 rounded-full",
216
+ orientation === "horizontal"
217
+ ? "-right-12 top-1/2 -translate-y-1/2"
218
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
219
+ className,
220
+ )}
221
+ disabled={!canScrollNext}
222
+ onClick={scrollNext}
223
+ {...props}
224
+ >
225
+ <ArrowRight className="h-4 w-4" />
226
+ <span className="sr-only">Next slide</span>
227
+ </Button>
228
+ );
229
+ },
230
+ );
231
+ CarouselNext.displayName = "CarouselNext";
232
+
233
+ export {
234
+ type CarouselApi,
235
+ Carousel,
236
+ CarouselContent,
237
+ CarouselItem,
238
+ CarouselPrevious,
239
+ CarouselNext,
240
+ };
src/components/ui/chart.tsx ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as RechartsPrimitive from "recharts";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ // Format: { THEME_NAME: CSS_SELECTOR }
7
+ const THEMES = { light: "", dark: ".dark" } as const;
8
+
9
+ export type ChartConfig = {
10
+ [k in string]: {
11
+ label?: React.ReactNode;
12
+ icon?: React.ComponentType;
13
+ } & (
14
+ | { color?: string; theme?: never }
15
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
16
+ );
17
+ };
18
+
19
+ type ChartContextProps = {
20
+ config: ChartConfig;
21
+ };
22
+
23
+ const ChartContext = React.createContext<ChartContextProps | null>(null);
24
+
25
+ function useChart() {
26
+ const context = React.useContext(ChartContext);
27
+
28
+ if (!context) {
29
+ throw new Error("useChart must be used within a <ChartContainer />");
30
+ }
31
+
32
+ return context;
33
+ }
34
+
35
+ const ChartContainer = React.forwardRef<
36
+ HTMLDivElement,
37
+ React.ComponentProps<"div"> & {
38
+ config: ChartConfig;
39
+ children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
40
+ }
41
+ >(({ id, className, children, config, ...props }, ref) => {
42
+ const uniqueId = React.useId();
43
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
44
+
45
+ return (
46
+ <ChartContext.Provider value={{ config }}>
47
+ <div
48
+ data-chart={chartId}
49
+ ref={ref}
50
+ className={cn(
51
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
52
+ className,
53
+ )}
54
+ {...props}
55
+ >
56
+ <ChartStyle id={chartId} config={config} />
57
+ <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
58
+ </div>
59
+ </ChartContext.Provider>
60
+ );
61
+ });
62
+ ChartContainer.displayName = "Chart";
63
+
64
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
65
+ const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color);
66
+
67
+ if (!colorConfig.length) {
68
+ return null;
69
+ }
70
+
71
+ return (
72
+ <style
73
+ dangerouslySetInnerHTML={{
74
+ __html: Object.entries(THEMES)
75
+ .map(
76
+ ([theme, prefix]) => `
77
+ ${prefix} [data-chart=${id}] {
78
+ ${colorConfig
79
+ .map(([key, itemConfig]) => {
80
+ const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;
81
+ return color ? ` --color-${key}: ${color};` : null;
82
+ })
83
+ .join("\n")}
84
+ }
85
+ `,
86
+ )
87
+ .join("\n"),
88
+ }}
89
+ />
90
+ );
91
+ };
92
+
93
+ const ChartTooltip = RechartsPrimitive.Tooltip;
94
+
95
+ const ChartTooltipContent = React.forwardRef<
96
+ HTMLDivElement,
97
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
98
+ React.ComponentProps<"div"> & {
99
+ hideLabel?: boolean;
100
+ hideIndicator?: boolean;
101
+ indicator?: "line" | "dot" | "dashed";
102
+ nameKey?: string;
103
+ labelKey?: string;
104
+ }
105
+ >(
106
+ (
107
+ {
108
+ active,
109
+ payload,
110
+ className,
111
+ indicator = "dot",
112
+ hideLabel = false,
113
+ hideIndicator = false,
114
+ label,
115
+ labelFormatter,
116
+ labelClassName,
117
+ formatter,
118
+ color,
119
+ nameKey,
120
+ labelKey,
121
+ },
122
+ ref,
123
+ ) => {
124
+ const { config } = useChart();
125
+
126
+ const tooltipLabel = React.useMemo(() => {
127
+ if (hideLabel || !payload?.length) {
128
+ return null;
129
+ }
130
+
131
+ const [item] = payload;
132
+ const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
133
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
134
+ const value =
135
+ !labelKey && typeof label === "string"
136
+ ? config[label as keyof typeof config]?.label || label
137
+ : itemConfig?.label;
138
+
139
+ if (labelFormatter) {
140
+ return (
141
+ <div className={cn("font-medium", labelClassName)}>{labelFormatter(value, payload)}</div>
142
+ );
143
+ }
144
+
145
+ if (!value) {
146
+ return null;
147
+ }
148
+
149
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>;
150
+ }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
151
+
152
+ if (!active || !payload?.length) {
153
+ return null;
154
+ }
155
+
156
+ const nestLabel = payload.length === 1 && indicator !== "dot";
157
+
158
+ return (
159
+ <div
160
+ ref={ref}
161
+ className={cn(
162
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
163
+ className,
164
+ )}
165
+ >
166
+ {!nestLabel ? tooltipLabel : null}
167
+ <div className="grid gap-1.5">
168
+ {payload
169
+ .filter((item) => item.type !== "none")
170
+ .map((item, index) => {
171
+ const key = `${nameKey || item.name || item.dataKey || "value"}`;
172
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
173
+ const indicatorColor = color || item.payload.fill || item.color;
174
+
175
+ return (
176
+ <div
177
+ key={item.dataKey}
178
+ className={cn(
179
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
180
+ indicator === "dot" && "items-center",
181
+ )}
182
+ >
183
+ {formatter && item?.value !== undefined && item.name ? (
184
+ formatter(item.value, item.name, item, index, item.payload)
185
+ ) : (
186
+ <>
187
+ {itemConfig?.icon ? (
188
+ <itemConfig.icon />
189
+ ) : (
190
+ !hideIndicator && (
191
+ <div
192
+ className={cn(
193
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
194
+ {
195
+ "h-2.5 w-2.5": indicator === "dot",
196
+ "w-1": indicator === "line",
197
+ "w-0 border-[1.5px] border-dashed bg-transparent":
198
+ indicator === "dashed",
199
+ "my-0.5": nestLabel && indicator === "dashed",
200
+ },
201
+ )}
202
+ style={
203
+ {
204
+ "--color-bg": indicatorColor,
205
+ "--color-border": indicatorColor,
206
+ } as React.CSSProperties
207
+ }
208
+ />
209
+ )
210
+ )}
211
+ <div
212
+ className={cn(
213
+ "flex flex-1 justify-between leading-none",
214
+ nestLabel ? "items-end" : "items-center",
215
+ )}
216
+ >
217
+ <div className="grid gap-1.5">
218
+ {nestLabel ? tooltipLabel : null}
219
+ <span className="text-muted-foreground">
220
+ {itemConfig?.label || item.name}
221
+ </span>
222
+ </div>
223
+ {item.value && (
224
+ <span className="font-mono font-medium tabular-nums text-foreground">
225
+ {item.value.toLocaleString()}
226
+ </span>
227
+ )}
228
+ </div>
229
+ </>
230
+ )}
231
+ </div>
232
+ );
233
+ })}
234
+ </div>
235
+ </div>
236
+ );
237
+ },
238
+ );
239
+ ChartTooltipContent.displayName = "ChartTooltip";
240
+
241
+ const ChartLegend = RechartsPrimitive.Legend;
242
+
243
+ const ChartLegendContent = React.forwardRef<
244
+ HTMLDivElement,
245
+ React.ComponentProps<"div"> &
246
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
247
+ hideIcon?: boolean;
248
+ nameKey?: string;
249
+ }
250
+ >(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
251
+ const { config } = useChart();
252
+
253
+ if (!payload?.length) {
254
+ return null;
255
+ }
256
+
257
+ return (
258
+ <div
259
+ ref={ref}
260
+ className={cn(
261
+ "flex items-center justify-center gap-4",
262
+ verticalAlign === "top" ? "pb-3" : "pt-3",
263
+ className,
264
+ )}
265
+ >
266
+ {payload
267
+ .filter((item) => item.type !== "none")
268
+ .map((item) => {
269
+ const key = `${nameKey || item.dataKey || "value"}`;
270
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
271
+
272
+ return (
273
+ <div
274
+ key={item.value}
275
+ className={cn(
276
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
277
+ )}
278
+ >
279
+ {itemConfig?.icon && !hideIcon ? (
280
+ <itemConfig.icon />
281
+ ) : (
282
+ <div
283
+ className="h-2 w-2 shrink-0 rounded-[2px]"
284
+ style={{
285
+ backgroundColor: item.color,
286
+ }}
287
+ />
288
+ )}
289
+ {itemConfig?.label}
290
+ </div>
291
+ );
292
+ })}
293
+ </div>
294
+ );
295
+ });
296
+ ChartLegendContent.displayName = "ChartLegend";
297
+
298
+ // Helper to extract item config from a payload.
299
+ function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
300
+ if (typeof payload !== "object" || payload === null) {
301
+ return undefined;
302
+ }
303
+
304
+ const payloadPayload =
305
+ "payload" in payload && typeof payload.payload === "object" && payload.payload !== null
306
+ ? payload.payload
307
+ : undefined;
308
+
309
+ let configLabelKey: string = key;
310
+
311
+ if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
312
+ configLabelKey = payload[key as keyof typeof payload] as string;
313
+ } else if (
314
+ payloadPayload &&
315
+ key in payloadPayload &&
316
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
317
+ ) {
318
+ configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
319
+ }
320
+
321
+ return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
322
+ }
323
+
324
+ export {
325
+ ChartContainer,
326
+ ChartTooltip,
327
+ ChartTooltipContent,
328
+ ChartLegend,
329
+ ChartLegendContent,
330
+ ChartStyle,
331
+ };
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
3
+ import { Check } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
15
+ className,
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator className={cn("grid place-content-center text-current")}>
20
+ <Check className="h-4 w-4" />
21
+ </CheckboxPrimitive.Indicator>
22
+ </CheckboxPrimitive.Root>
23
+ ));
24
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
25
+
26
+ export { Checkbox };
src/components/ui/collapsible.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root;
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
src/components/ui/command.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { type DialogProps } from "@radix-ui/react-dialog";
5
+ import { Command as CommandPrimitive } from "cmdk";
6
+ import { Search } from "lucide-react";
7
+
8
+ import { cn } from "@/lib/utils";
9
+ import { Dialog, DialogContent } from "@/components/ui/dialog";
10
+
11
+ const Command = React.forwardRef<
12
+ React.ElementRef<typeof CommandPrimitive>,
13
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
14
+ >(({ className, ...props }, ref) => (
15
+ <CommandPrimitive
16
+ ref={ref}
17
+ className={cn(
18
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ ));
24
+ Command.displayName = CommandPrimitive.displayName;
25
+
26
+ const CommandDialog = ({ children, ...props }: DialogProps) => {
27
+ return (
28
+ <Dialog {...props}>
29
+ <DialogContent className="overflow-hidden p-0">
30
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
31
+ {children}
32
+ </Command>
33
+ </DialogContent>
34
+ </Dialog>
35
+ );
36
+ };
37
+
38
+ const CommandInput = React.forwardRef<
39
+ React.ElementRef<typeof CommandPrimitive.Input>,
40
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
41
+ >(({ className, ...props }, ref) => (
42
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
43
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
44
+ <CommandPrimitive.Input
45
+ ref={ref}
46
+ className={cn(
47
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ </div>
53
+ ));
54
+
55
+ CommandInput.displayName = CommandPrimitive.Input.displayName;
56
+
57
+ const CommandList = React.forwardRef<
58
+ React.ElementRef<typeof CommandPrimitive.List>,
59
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
60
+ >(({ className, ...props }, ref) => (
61
+ <CommandPrimitive.List
62
+ ref={ref}
63
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
64
+ {...props}
65
+ />
66
+ ));
67
+
68
+ CommandList.displayName = CommandPrimitive.List.displayName;
69
+
70
+ const CommandEmpty = React.forwardRef<
71
+ React.ElementRef<typeof CommandPrimitive.Empty>,
72
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
73
+ >((props, ref) => (
74
+ <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
75
+ ));
76
+
77
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
78
+
79
+ const CommandGroup = React.forwardRef<
80
+ React.ElementRef<typeof CommandPrimitive.Group>,
81
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
82
+ >(({ className, ...props }, ref) => (
83
+ <CommandPrimitive.Group
84
+ ref={ref}
85
+ className={cn(
86
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ ));
92
+
93
+ CommandGroup.displayName = CommandPrimitive.Group.displayName;
94
+
95
+ const CommandSeparator = React.forwardRef<
96
+ React.ElementRef<typeof CommandPrimitive.Separator>,
97
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
98
+ >(({ className, ...props }, ref) => (
99
+ <CommandPrimitive.Separator
100
+ ref={ref}
101
+ className={cn("-mx-1 h-px bg-border", className)}
102
+ {...props}
103
+ />
104
+ ));
105
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
106
+
107
+ const CommandItem = React.forwardRef<
108
+ React.ElementRef<typeof CommandPrimitive.Item>,
109
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
110
+ >(({ className, ...props }, ref) => (
111
+ <CommandPrimitive.Item
112
+ ref={ref}
113
+ className={cn(
114
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
115
+ className,
116
+ )}
117
+ {...props}
118
+ />
119
+ ));
120
+
121
+ CommandItem.displayName = CommandPrimitive.Item.displayName;
122
+
123
+ const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
124
+ return (
125
+ <span
126
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
127
+ {...props}
128
+ />
129
+ );
130
+ };
131
+ CommandShortcut.displayName = "CommandShortcut";
132
+
133
+ export {
134
+ Command,
135
+ CommandDialog,
136
+ CommandInput,
137
+ CommandList,
138
+ CommandEmpty,
139
+ CommandGroup,
140
+ CommandItem,
141
+ CommandShortcut,
142
+ CommandSeparator,
143
+ };
src/components/ui/context-menu.tsx ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
3
+ import { Check, ChevronRight, Circle } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const ContextMenu = ContextMenuPrimitive.Root;
8
+
9
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
10
+
11
+ const ContextMenuGroup = ContextMenuPrimitive.Group;
12
+
13
+ const ContextMenuPortal = ContextMenuPrimitive.Portal;
14
+
15
+ const ContextMenuSub = ContextMenuPrimitive.Sub;
16
+
17
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
18
+
19
+ const ContextMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean;
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <ContextMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
29
+ inset && "pl-8",
30
+ className,
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </ContextMenuPrimitive.SubTrigger>
37
+ ));
38
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
39
+
40
+ const ContextMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <ContextMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin)",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ ));
53
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
54
+
55
+ const ContextMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
58
+ >(({ className, ...props }, ref) => (
59
+ <ContextMenuPrimitive.Portal>
60
+ <ContextMenuPrimitive.Content
61
+ ref={ref}
62
+ className={cn(
63
+ "z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin)",
64
+ className,
65
+ )}
66
+ {...props}
67
+ />
68
+ </ContextMenuPrimitive.Portal>
69
+ ));
70
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
71
+
72
+ const ContextMenuItem = React.forwardRef<
73
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
74
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
75
+ inset?: boolean;
76
+ }
77
+ >(({ className, inset, ...props }, ref) => (
78
+ <ContextMenuPrimitive.Item
79
+ ref={ref}
80
+ className={cn(
81
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
82
+ inset && "pl-8",
83
+ className,
84
+ )}
85
+ {...props}
86
+ />
87
+ ));
88
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
89
+
90
+ const ContextMenuCheckboxItem = React.forwardRef<
91
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
92
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
93
+ >(({ className, children, checked, ...props }, ref) => (
94
+ <ContextMenuPrimitive.CheckboxItem
95
+ ref={ref}
96
+ className={cn(
97
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
98
+ className,
99
+ )}
100
+ checked={checked}
101
+ {...props}
102
+ >
103
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
104
+ <ContextMenuPrimitive.ItemIndicator>
105
+ <Check className="h-4 w-4" />
106
+ </ContextMenuPrimitive.ItemIndicator>
107
+ </span>
108
+ {children}
109
+ </ContextMenuPrimitive.CheckboxItem>
110
+ ));
111
+ ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
112
+
113
+ const ContextMenuRadioItem = React.forwardRef<
114
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
115
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
116
+ >(({ className, children, ...props }, ref) => (
117
+ <ContextMenuPrimitive.RadioItem
118
+ ref={ref}
119
+ className={cn(
120
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
121
+ className,
122
+ )}
123
+ {...props}
124
+ >
125
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
126
+ <ContextMenuPrimitive.ItemIndicator>
127
+ <Circle className="h-4 w-4 fill-current" />
128
+ </ContextMenuPrimitive.ItemIndicator>
129
+ </span>
130
+ {children}
131
+ </ContextMenuPrimitive.RadioItem>
132
+ ));
133
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
134
+
135
+ const ContextMenuLabel = React.forwardRef<
136
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
137
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
138
+ inset?: boolean;
139
+ }
140
+ >(({ className, inset, ...props }, ref) => (
141
+ <ContextMenuPrimitive.Label
142
+ ref={ref}
143
+ className={cn("px-2 py-1.5 text-sm font-semibold text-foreground", inset && "pl-8", className)}
144
+ {...props}
145
+ />
146
+ ));
147
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
148
+
149
+ const ContextMenuSeparator = React.forwardRef<
150
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
151
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
152
+ >(({ className, ...props }, ref) => (
153
+ <ContextMenuPrimitive.Separator
154
+ ref={ref}
155
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
156
+ {...props}
157
+ />
158
+ ));
159
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
160
+
161
+ const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
162
+ return (
163
+ <span
164
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
165
+ {...props}
166
+ />
167
+ );
168
+ };
169
+ ContextMenuShortcut.displayName = "ContextMenuShortcut";
170
+
171
+ export {
172
+ ContextMenu,
173
+ ContextMenuTrigger,
174
+ ContextMenuContent,
175
+ ContextMenuItem,
176
+ ContextMenuCheckboxItem,
177
+ ContextMenuRadioItem,
178
+ ContextMenuLabel,
179
+ ContextMenuSeparator,
180
+ ContextMenuShortcut,
181
+ ContextMenuGroup,
182
+ ContextMenuPortal,
183
+ ContextMenuSub,
184
+ ContextMenuSubContent,
185
+ ContextMenuSubTrigger,
186
+ ContextMenuRadioGroup,
187
+ };
src/components/ui/dialog.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { X } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const Dialog = DialogPrimitive.Root;
10
+
11
+ const DialogTrigger = DialogPrimitive.Trigger;
12
+
13
+ const DialogPortal = DialogPrimitive.Portal;
14
+
15
+ const DialogClose = DialogPrimitive.Close;
16
+
17
+ const DialogOverlay = React.forwardRef<
18
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
19
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
20
+ >(({ className, ...props }, ref) => (
21
+ <DialogPrimitive.Overlay
22
+ ref={ref}
23
+ className={cn(
24
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ ));
30
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
31
+
32
+ const DialogContent = React.forwardRef<
33
+ React.ElementRef<typeof DialogPrimitive.Content>,
34
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
35
+ >(({ className, children, ...props }, ref) => (
36
+ <DialogPortal>
37
+ <DialogOverlay />
38
+ <DialogPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
42
+ className,
43
+ )}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
48
+ <X className="h-4 w-4" />
49
+ <span className="sr-only">Close</span>
50
+ </DialogPrimitive.Close>
51
+ </DialogPrimitive.Content>
52
+ </DialogPortal>
53
+ ));
54
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
55
+
56
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
57
+ <div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
58
+ );
59
+ DialogHeader.displayName = "DialogHeader";
60
+
61
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
62
+ <div
63
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
64
+ {...props}
65
+ />
66
+ );
67
+ DialogFooter.displayName = "DialogFooter";
68
+
69
+ const DialogTitle = React.forwardRef<
70
+ React.ElementRef<typeof DialogPrimitive.Title>,
71
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
72
+ >(({ className, ...props }, ref) => (
73
+ <DialogPrimitive.Title
74
+ ref={ref}
75
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
76
+ {...props}
77
+ />
78
+ ));
79
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
80
+
81
+ const DialogDescription = React.forwardRef<
82
+ React.ElementRef<typeof DialogPrimitive.Description>,
83
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
84
+ >(({ className, ...props }, ref) => (
85
+ <DialogPrimitive.Description
86
+ ref={ref}
87
+ className={cn("text-sm text-muted-foreground", className)}
88
+ {...props}
89
+ />
90
+ ));
91
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
92
+
93
+ export {
94
+ Dialog,
95
+ DialogPortal,
96
+ DialogOverlay,
97
+ DialogTrigger,
98
+ DialogClose,
99
+ DialogContent,
100
+ DialogHeader,
101
+ DialogFooter,
102
+ DialogTitle,
103
+ DialogDescription,
104
+ };
src/components/ui/drawer.tsx ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Drawer as DrawerPrimitive } from "vaul";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const Drawer = ({
7
+ shouldScaleBackground = true,
8
+ ...props
9
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
10
+ <DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
11
+ );
12
+ Drawer.displayName = "Drawer";
13
+
14
+ const DrawerTrigger = DrawerPrimitive.Trigger;
15
+
16
+ const DrawerPortal = DrawerPrimitive.Portal;
17
+
18
+ const DrawerClose = DrawerPrimitive.Close;
19
+
20
+ const DrawerOverlay = React.forwardRef<
21
+ React.ElementRef<typeof DrawerPrimitive.Overlay>,
22
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
23
+ >(({ className, ...props }, ref) => (
24
+ <DrawerPrimitive.Overlay
25
+ ref={ref}
26
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
27
+ {...props}
28
+ />
29
+ ));
30
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
31
+
32
+ const DrawerContent = React.forwardRef<
33
+ React.ElementRef<typeof DrawerPrimitive.Content>,
34
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
35
+ >(({ className, children, ...props }, ref) => (
36
+ <DrawerPortal>
37
+ <DrawerOverlay />
38
+ <DrawerPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
42
+ className,
43
+ )}
44
+ {...props}
45
+ >
46
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
47
+ {children}
48
+ </DrawerPrimitive.Content>
49
+ </DrawerPortal>
50
+ ));
51
+ DrawerContent.displayName = "DrawerContent";
52
+
53
+ const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
54
+ <div className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} />
55
+ );
56
+ DrawerHeader.displayName = "DrawerHeader";
57
+
58
+ const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
59
+ <div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
60
+ );
61
+ DrawerFooter.displayName = "DrawerFooter";
62
+
63
+ const DrawerTitle = React.forwardRef<
64
+ React.ElementRef<typeof DrawerPrimitive.Title>,
65
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
66
+ >(({ className, ...props }, ref) => (
67
+ <DrawerPrimitive.Title
68
+ ref={ref}
69
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
70
+ {...props}
71
+ />
72
+ ));
73
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
74
+
75
+ const DrawerDescription = React.forwardRef<
76
+ React.ElementRef<typeof DrawerPrimitive.Description>,
77
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
78
+ >(({ className, ...props }, ref) => (
79
+ <DrawerPrimitive.Description
80
+ ref={ref}
81
+ className={cn("text-sm text-muted-foreground", className)}
82
+ {...props}
83
+ />
84
+ ));
85
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
86
+
87
+ export {
88
+ Drawer,
89
+ DrawerPortal,
90
+ DrawerOverlay,
91
+ DrawerTrigger,
92
+ DrawerClose,
93
+ DrawerContent,
94
+ DrawerHeader,
95
+ DrawerFooter,
96
+ DrawerTitle,
97
+ DrawerDescription,
98
+ };
src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { Check, ChevronRight, Circle } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root;
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean;
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
31
+ inset && "pl-8",
32
+ className,
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRight className="ml-auto" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ));
40
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
41
+
42
+ const DropdownMenuSubContent = React.forwardRef<
43
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
44
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
45
+ >(({ className, ...props }, ref) => (
46
+ <DropdownMenuPrimitive.SubContent
47
+ ref={ref}
48
+ className={cn(
49
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",
50
+ className,
51
+ )}
52
+ {...props}
53
+ />
54
+ ));
55
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
56
+
57
+ const DropdownMenuContent = React.forwardRef<
58
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
59
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
60
+ >(({ className, sideOffset = 4, ...props }, ref) => (
61
+ <DropdownMenuPrimitive.Portal>
62
+ <DropdownMenuPrimitive.Content
63
+ ref={ref}
64
+ sideOffset={sideOffset}
65
+ className={cn(
66
+ "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
67
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",
68
+ className,
69
+ )}
70
+ {...props}
71
+ />
72
+ </DropdownMenuPrimitive.Portal>
73
+ ));
74
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
75
+
76
+ const DropdownMenuItem = React.forwardRef<
77
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
78
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
79
+ inset?: boolean;
80
+ }
81
+ >(({ className, inset, ...props }, ref) => (
82
+ <DropdownMenuPrimitive.Item
83
+ ref={ref}
84
+ className={cn(
85
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
86
+ inset && "pl-8",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ ));
92
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
93
+
94
+ const DropdownMenuCheckboxItem = React.forwardRef<
95
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
96
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
97
+ >(({ className, children, checked, ...props }, ref) => (
98
+ <DropdownMenuPrimitive.CheckboxItem
99
+ ref={ref}
100
+ className={cn(
101
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
102
+ className,
103
+ )}
104
+ checked={checked}
105
+ {...props}
106
+ >
107
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
108
+ <DropdownMenuPrimitive.ItemIndicator>
109
+ <Check className="h-4 w-4" />
110
+ </DropdownMenuPrimitive.ItemIndicator>
111
+ </span>
112
+ {children}
113
+ </DropdownMenuPrimitive.CheckboxItem>
114
+ ));
115
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
116
+
117
+ const DropdownMenuRadioItem = React.forwardRef<
118
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
119
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
120
+ >(({ className, children, ...props }, ref) => (
121
+ <DropdownMenuPrimitive.RadioItem
122
+ ref={ref}
123
+ className={cn(
124
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
125
+ className,
126
+ )}
127
+ {...props}
128
+ >
129
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
130
+ <DropdownMenuPrimitive.ItemIndicator>
131
+ <Circle className="h-2 w-2 fill-current" />
132
+ </DropdownMenuPrimitive.ItemIndicator>
133
+ </span>
134
+ {children}
135
+ </DropdownMenuPrimitive.RadioItem>
136
+ ));
137
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
138
+
139
+ const DropdownMenuLabel = React.forwardRef<
140
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
141
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
142
+ inset?: boolean;
143
+ }
144
+ >(({ className, inset, ...props }, ref) => (
145
+ <DropdownMenuPrimitive.Label
146
+ ref={ref}
147
+ className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
148
+ {...props}
149
+ />
150
+ ));
151
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
152
+
153
+ const DropdownMenuSeparator = React.forwardRef<
154
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
155
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
156
+ >(({ className, ...props }, ref) => (
157
+ <DropdownMenuPrimitive.Separator
158
+ ref={ref}
159
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
160
+ {...props}
161
+ />
162
+ ));
163
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
164
+
165
+ const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
166
+ return (
167
+ <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
168
+ );
169
+ };
170
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
171
+
172
+ export {
173
+ DropdownMenu,
174
+ DropdownMenuTrigger,
175
+ DropdownMenuContent,
176
+ DropdownMenuItem,
177
+ DropdownMenuCheckboxItem,
178
+ DropdownMenuRadioItem,
179
+ DropdownMenuLabel,
180
+ DropdownMenuSeparator,
181
+ DropdownMenuShortcut,
182
+ DropdownMenuGroup,
183
+ DropdownMenuPortal,
184
+ DropdownMenuSub,
185
+ DropdownMenuSubContent,
186
+ DropdownMenuSubTrigger,
187
+ DropdownMenuRadioGroup,
188
+ };
src/components/ui/form.tsx ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as LabelPrimitive from "@radix-ui/react-label";
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import {
5
+ Controller,
6
+ FormProvider,
7
+ useFormContext,
8
+ type ControllerProps,
9
+ type FieldPath,
10
+ type FieldValues,
11
+ } from "react-hook-form";
12
+
13
+ import { cn } from "@/lib/utils";
14
+ import { Label } from "@/components/ui/label";
15
+
16
+ const Form = FormProvider;
17
+
18
+ type FormFieldContextValue<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
21
+ > = {
22
+ name: TName;
23
+ };
24
+
25
+ const FormFieldContext = React.createContext<FormFieldContextValue | null>(null);
26
+
27
+ const FormField = <
28
+ TFieldValues extends FieldValues = FieldValues,
29
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
30
+ >({
31
+ ...props
32
+ }: ControllerProps<TFieldValues, TName>) => {
33
+ return (
34
+ <FormFieldContext.Provider value={{ name: props.name }}>
35
+ <Controller {...props} />
36
+ </FormFieldContext.Provider>
37
+ );
38
+ };
39
+
40
+ const useFormField = () => {
41
+ const fieldContext = React.useContext(FormFieldContext);
42
+ const itemContext = React.useContext(FormItemContext);
43
+ const { getFieldState, formState } = useFormContext();
44
+
45
+ if (!fieldContext) {
46
+ throw new Error("useFormField should be used within <FormField>");
47
+ }
48
+
49
+ if (!itemContext) {
50
+ throw new Error("useFormField should be used within <FormItem>");
51
+ }
52
+
53
+ const fieldState = getFieldState(fieldContext.name, formState);
54
+
55
+ const { id } = itemContext;
56
+
57
+ return {
58
+ id,
59
+ name: fieldContext.name,
60
+ formItemId: `${id}-form-item`,
61
+ formDescriptionId: `${id}-form-item-description`,
62
+ formMessageId: `${id}-form-item-message`,
63
+ ...fieldState,
64
+ };
65
+ };
66
+
67
+ type FormItemContextValue = {
68
+ id: string;
69
+ };
70
+
71
+ const FormItemContext = React.createContext<FormItemContextValue | null>(null);
72
+
73
+ const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
74
+ ({ className, ...props }, ref) => {
75
+ const id = React.useId();
76
+
77
+ return (
78
+ <FormItemContext.Provider value={{ id }}>
79
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
80
+ </FormItemContext.Provider>
81
+ );
82
+ },
83
+ );
84
+ FormItem.displayName = "FormItem";
85
+
86
+ const FormLabel = React.forwardRef<
87
+ React.ElementRef<typeof LabelPrimitive.Root>,
88
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
89
+ >(({ className, ...props }, ref) => {
90
+ const { error, formItemId } = useFormField();
91
+
92
+ return (
93
+ <Label
94
+ ref={ref}
95
+ className={cn(error && "text-destructive", className)}
96
+ htmlFor={formItemId}
97
+ {...props}
98
+ />
99
+ );
100
+ });
101
+ FormLabel.displayName = "FormLabel";
102
+
103
+ const FormControl = React.forwardRef<
104
+ React.ElementRef<typeof Slot>,
105
+ React.ComponentPropsWithoutRef<typeof Slot>
106
+ >(({ ...props }, ref) => {
107
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
108
+
109
+ return (
110
+ <Slot
111
+ ref={ref}
112
+ id={formItemId}
113
+ aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
114
+ aria-invalid={!!error}
115
+ {...props}
116
+ />
117
+ );
118
+ });
119
+ FormControl.displayName = "FormControl";
120
+
121
+ const FormDescription = React.forwardRef<
122
+ HTMLParagraphElement,
123
+ React.HTMLAttributes<HTMLParagraphElement>
124
+ >(({ className, ...props }, ref) => {
125
+ const { formDescriptionId } = useFormField();
126
+
127
+ return (
128
+ <p
129
+ ref={ref}
130
+ id={formDescriptionId}
131
+ className={cn("text-[0.8rem] text-muted-foreground", className)}
132
+ {...props}
133
+ />
134
+ );
135
+ });
136
+ FormDescription.displayName = "FormDescription";
137
+
138
+ const FormMessage = React.forwardRef<
139
+ HTMLParagraphElement,
140
+ React.HTMLAttributes<HTMLParagraphElement>
141
+ >(({ className, children, ...props }, ref) => {
142
+ const { error, formMessageId } = useFormField();
143
+ const body = error ? String(error?.message ?? "") : children;
144
+
145
+ if (!body) {
146
+ return null;
147
+ }
148
+
149
+ return (
150
+ <p
151
+ ref={ref}
152
+ id={formMessageId}
153
+ className={cn("text-[0.8rem] font-medium text-destructive", className)}
154
+ {...props}
155
+ >
156
+ {body}
157
+ </p>
158
+ );
159
+ });
160
+ FormMessage.displayName = "FormMessage";
161
+
162
+ export {
163
+ useFormField,
164
+ Form,
165
+ FormItem,
166
+ FormLabel,
167
+ FormControl,
168
+ FormDescription,
169
+ FormMessage,
170
+ FormField,
171
+ };
src/components/ui/hover-card.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const HoverCard = HoverCardPrimitive.Root;
7
+
8
+ const HoverCardTrigger = HoverCardPrimitive.Trigger;
9
+
10
+ const HoverCardContent = React.forwardRef<
11
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
12
+ React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
13
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14
+ <HoverCardPrimitive.Content
15
+ ref={ref}
16
+ align={align}
17
+ sideOffset={sideOffset}
18
+ className={cn(
19
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-hover-card-content-transform-origin)",
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ ));
25
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
26
+
27
+ export { HoverCard, HoverCardTrigger, HoverCardContent };
src/components/ui/input-otp.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { OTPInput, OTPInputContext } from "input-otp";
3
+ import { Minus } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const InputOTP = React.forwardRef<
8
+ React.ElementRef<typeof OTPInput>,
9
+ React.ComponentPropsWithoutRef<typeof OTPInput>
10
+ >(({ className, containerClassName, ...props }, ref) => (
11
+ <OTPInput
12
+ ref={ref}
13
+ containerClassName={cn(
14
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
15
+ containerClassName,
16
+ )}
17
+ className={cn("disabled:cursor-not-allowed", className)}
18
+ {...props}
19
+ />
20
+ ));
21
+ InputOTP.displayName = "InputOTP";
22
+
23
+ const InputOTPGroup = React.forwardRef<
24
+ React.ElementRef<"div">,
25
+ React.ComponentPropsWithoutRef<"div">
26
+ >(({ className, ...props }, ref) => (
27
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
28
+ ));
29
+ InputOTPGroup.displayName = "InputOTPGroup";
30
+
31
+ const InputOTPSlot = React.forwardRef<
32
+ React.ElementRef<"div">,
33
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
34
+ >(({ index, className, ...props }, ref) => {
35
+ const inputOTPContext = React.useContext(OTPInputContext);
36
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
37
+
38
+ return (
39
+ <div
40
+ ref={ref}
41
+ className={cn(
42
+ "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
43
+ isActive && "z-10 ring-1 ring-ring",
44
+ className,
45
+ )}
46
+ {...props}
47
+ >
48
+ {char}
49
+ {hasFakeCaret && (
50
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
51
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
52
+ </div>
53
+ )}
54
+ </div>
55
+ );
56
+ });
57
+ InputOTPSlot.displayName = "InputOTPSlot";
58
+
59
+ const InputOTPSeparator = React.forwardRef<
60
+ React.ElementRef<"div">,
61
+ React.ComponentPropsWithoutRef<"div">
62
+ >(({ ...props }, ref) => (
63
+ <div ref={ref} role="separator" {...props}>
64
+ <Minus />
65
+ </div>
66
+ ));
67
+ InputOTPSeparator.displayName = "InputOTPSeparator";
68
+
69
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
src/components/ui/input.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className,
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ );
18
+ },
19
+ );
20
+ Input.displayName = "Input";
21
+
22
+ export { Input };
src/components/ui/label.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as LabelPrimitive from "@radix-ui/react-label";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11
+ );
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
16
+ >(({ className, ...props }, ref) => (
17
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
18
+ ));
19
+ Label.displayName = LabelPrimitive.Root.displayName;
20
+
21
+ export { Label };
src/components/ui/menubar.tsx ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as MenubarPrimitive from "@radix-ui/react-menubar";
3
+ import { Check, ChevronRight, Circle } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function MenubarMenu({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
8
+ return <MenubarPrimitive.Menu {...props} />;
9
+ }
10
+
11
+ function MenubarGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
12
+ return <MenubarPrimitive.Group {...props} />;
13
+ }
14
+
15
+ function MenubarPortal({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
16
+ return <MenubarPrimitive.Portal {...props} />;
17
+ }
18
+
19
+ function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
20
+ return <MenubarPrimitive.RadioGroup {...props} />;
21
+ }
22
+
23
+ function MenubarSub({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
24
+ return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
25
+ }
26
+
27
+ const Menubar = React.forwardRef<
28
+ React.ElementRef<typeof MenubarPrimitive.Root>,
29
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
30
+ >(({ className, ...props }, ref) => (
31
+ <MenubarPrimitive.Root
32
+ ref={ref}
33
+ className={cn(
34
+ "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
35
+ className,
36
+ )}
37
+ {...props}
38
+ />
39
+ ));
40
+ Menubar.displayName = MenubarPrimitive.Root.displayName;
41
+
42
+ const MenubarTrigger = React.forwardRef<
43
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
44
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
45
+ >(({ className, ...props }, ref) => (
46
+ <MenubarPrimitive.Trigger
47
+ ref={ref}
48
+ className={cn(
49
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
50
+ className,
51
+ )}
52
+ {...props}
53
+ />
54
+ ));
55
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
56
+
57
+ const MenubarSubTrigger = React.forwardRef<
58
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
59
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
60
+ inset?: boolean;
61
+ }
62
+ >(({ className, inset, children, ...props }, ref) => (
63
+ <MenubarPrimitive.SubTrigger
64
+ ref={ref}
65
+ className={cn(
66
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
67
+ inset && "pl-8",
68
+ className,
69
+ )}
70
+ {...props}
71
+ >
72
+ {children}
73
+ <ChevronRight className="ml-auto h-4 w-4" />
74
+ </MenubarPrimitive.SubTrigger>
75
+ ));
76
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
77
+
78
+ const MenubarSubContent = React.forwardRef<
79
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
80
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
81
+ >(({ className, ...props }, ref) => (
82
+ <MenubarPrimitive.SubContent
83
+ ref={ref}
84
+ className={cn(
85
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-menubar-content-transform-origin)",
86
+ className,
87
+ )}
88
+ {...props}
89
+ />
90
+ ));
91
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
92
+
93
+ const MenubarContent = React.forwardRef<
94
+ React.ElementRef<typeof MenubarPrimitive.Content>,
95
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
96
+ >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
97
+ <MenubarPrimitive.Portal>
98
+ <MenubarPrimitive.Content
99
+ ref={ref}
100
+ align={align}
101
+ alignOffset={alignOffset}
102
+ sideOffset={sideOffset}
103
+ className={cn(
104
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-menubar-content-transform-origin)",
105
+ className,
106
+ )}
107
+ {...props}
108
+ />
109
+ </MenubarPrimitive.Portal>
110
+ ));
111
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName;
112
+
113
+ const MenubarItem = React.forwardRef<
114
+ React.ElementRef<typeof MenubarPrimitive.Item>,
115
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
116
+ inset?: boolean;
117
+ }
118
+ >(({ className, inset, ...props }, ref) => (
119
+ <MenubarPrimitive.Item
120
+ ref={ref}
121
+ className={cn(
122
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
123
+ inset && "pl-8",
124
+ className,
125
+ )}
126
+ {...props}
127
+ />
128
+ ));
129
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName;
130
+
131
+ const MenubarCheckboxItem = React.forwardRef<
132
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
133
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
134
+ >(({ className, children, checked, ...props }, ref) => (
135
+ <MenubarPrimitive.CheckboxItem
136
+ ref={ref}
137
+ className={cn(
138
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
139
+ className,
140
+ )}
141
+ checked={checked}
142
+ {...props}
143
+ >
144
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
145
+ <MenubarPrimitive.ItemIndicator>
146
+ <Check className="h-4 w-4" />
147
+ </MenubarPrimitive.ItemIndicator>
148
+ </span>
149
+ {children}
150
+ </MenubarPrimitive.CheckboxItem>
151
+ ));
152
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
153
+
154
+ const MenubarRadioItem = React.forwardRef<
155
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
156
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
157
+ >(({ className, children, ...props }, ref) => (
158
+ <MenubarPrimitive.RadioItem
159
+ ref={ref}
160
+ className={cn(
161
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
162
+ className,
163
+ )}
164
+ {...props}
165
+ >
166
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
167
+ <MenubarPrimitive.ItemIndicator>
168
+ <Circle className="h-4 w-4 fill-current" />
169
+ </MenubarPrimitive.ItemIndicator>
170
+ </span>
171
+ {children}
172
+ </MenubarPrimitive.RadioItem>
173
+ ));
174
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
175
+
176
+ const MenubarLabel = React.forwardRef<
177
+ React.ElementRef<typeof MenubarPrimitive.Label>,
178
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
179
+ inset?: boolean;
180
+ }
181
+ >(({ className, inset, ...props }, ref) => (
182
+ <MenubarPrimitive.Label
183
+ ref={ref}
184
+ className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
185
+ {...props}
186
+ />
187
+ ));
188
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
189
+
190
+ const MenubarSeparator = React.forwardRef<
191
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
192
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
193
+ >(({ className, ...props }, ref) => (
194
+ <MenubarPrimitive.Separator
195
+ ref={ref}
196
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
197
+ {...props}
198
+ />
199
+ ));
200
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
201
+
202
+ const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
203
+ return (
204
+ <span
205
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
206
+ {...props}
207
+ />
208
+ );
209
+ };
210
+ MenubarShortcut.displayname = "MenubarShortcut";
211
+
212
+ export {
213
+ Menubar,
214
+ MenubarMenu,
215
+ MenubarTrigger,
216
+ MenubarContent,
217
+ MenubarItem,
218
+ MenubarSeparator,
219
+ MenubarLabel,
220
+ MenubarCheckboxItem,
221
+ MenubarRadioGroup,
222
+ MenubarRadioItem,
223
+ MenubarPortal,
224
+ MenubarSubContent,
225
+ MenubarSubTrigger,
226
+ MenubarGroup,
227
+ MenubarSub,
228
+ MenubarShortcut,
229
+ };
src/components/ui/navigation-menu.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
3
+ import { cva } from "class-variance-authority";
4
+ import { ChevronDown } from "lucide-react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const NavigationMenu = React.forwardRef<
9
+ React.ElementRef<typeof NavigationMenuPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
11
+ >(({ className, children, ...props }, ref) => (
12
+ <NavigationMenuPrimitive.Root
13
+ ref={ref}
14
+ className={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", className)}
15
+ {...props}
16
+ >
17
+ {children}
18
+ <NavigationMenuViewport />
19
+ </NavigationMenuPrimitive.Root>
20
+ ));
21
+ NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
22
+
23
+ const NavigationMenuList = React.forwardRef<
24
+ React.ElementRef<typeof NavigationMenuPrimitive.List>,
25
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
26
+ >(({ className, ...props }, ref) => (
27
+ <NavigationMenuPrimitive.List
28
+ ref={ref}
29
+ className={cn("group flex flex-1 list-none items-center justify-center space-x-1", className)}
30
+ {...props}
31
+ />
32
+ ));
33
+ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
34
+
35
+ const NavigationMenuItem = NavigationMenuPrimitive.Item;
36
+
37
+ const navigationMenuTriggerStyle = cva(
38
+ "group inline-flex h-9 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-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent",
39
+ );
40
+
41
+ const NavigationMenuTrigger = React.forwardRef<
42
+ React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
43
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <NavigationMenuPrimitive.Trigger
46
+ ref={ref}
47
+ className={cn(navigationMenuTriggerStyle(), "group", className)}
48
+ {...props}
49
+ >
50
+ {children}{" "}
51
+ <ChevronDown
52
+ className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
53
+ aria-hidden="true"
54
+ />
55
+ </NavigationMenuPrimitive.Trigger>
56
+ ));
57
+ NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
58
+
59
+ const NavigationMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof NavigationMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
62
+ >(({ className, ...props }, ref) => (
63
+ <NavigationMenuPrimitive.Content
64
+ ref={ref}
65
+ className={cn(
66
+ "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
67
+ className,
68
+ )}
69
+ {...props}
70
+ />
71
+ ));
72
+ NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
73
+
74
+ const NavigationMenuLink = NavigationMenuPrimitive.Link;
75
+
76
+ const NavigationMenuViewport = React.forwardRef<
77
+ React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
78
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
79
+ >(({ className, ...props }, ref) => (
80
+ <div className={cn("absolute left-0 top-full flex justify-center")}>
81
+ <NavigationMenuPrimitive.Viewport
82
+ className={cn(
83
+ "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
84
+ className,
85
+ )}
86
+ ref={ref}
87
+ {...props}
88
+ />
89
+ </div>
90
+ ));
91
+ NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
92
+
93
+ const NavigationMenuIndicator = React.forwardRef<
94
+ React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
95
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
96
+ >(({ className, ...props }, ref) => (
97
+ <NavigationMenuPrimitive.Indicator
98
+ ref={ref}
99
+ className={cn(
100
+ "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
101
+ className,
102
+ )}
103
+ {...props}
104
+ >
105
+ <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
106
+ </NavigationMenuPrimitive.Indicator>
107
+ ));
108
+ NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
109
+
110
+ export {
111
+ navigationMenuTriggerStyle,
112
+ NavigationMenu,
113
+ NavigationMenuList,
114
+ NavigationMenuItem,
115
+ NavigationMenuContent,
116
+ NavigationMenuTrigger,
117
+ NavigationMenuLink,
118
+ NavigationMenuIndicator,
119
+ NavigationMenuViewport,
120
+ };
src/components/ui/pagination.tsx ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+ import { ButtonProps, buttonVariants } from "@/components/ui/button";
6
+
7
+ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
8
+ <nav
9
+ role="navigation"
10
+ aria-label="pagination"
11
+ className={cn("mx-auto flex w-full justify-center", className)}
12
+ {...props}
13
+ />
14
+ );
15
+ Pagination.displayName = "Pagination";
16
+
17
+ const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
18
+ ({ className, ...props }, ref) => (
19
+ <ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
20
+ ),
21
+ );
22
+ PaginationContent.displayName = "PaginationContent";
23
+
24
+ const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
25
+ ({ className, ...props }, ref) => <li ref={ref} className={cn("", className)} {...props} />,
26
+ );
27
+ PaginationItem.displayName = "PaginationItem";
28
+
29
+ type PaginationLinkProps = {
30
+ isActive?: boolean;
31
+ } & Pick<ButtonProps, "size"> &
32
+ React.ComponentProps<"a">;
33
+
34
+ const PaginationLink = ({ className, isActive, size = "icon", ...props }: PaginationLinkProps) => (
35
+ <a
36
+ aria-current={isActive ? "page" : undefined}
37
+ className={cn(
38
+ buttonVariants({
39
+ variant: isActive ? "outline" : "ghost",
40
+ size,
41
+ }),
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ );
47
+ PaginationLink.displayName = "PaginationLink";
48
+
49
+ const PaginationPrevious = ({
50
+ className,
51
+ ...props
52
+ }: React.ComponentProps<typeof PaginationLink>) => (
53
+ <PaginationLink
54
+ aria-label="Go to previous page"
55
+ size="default"
56
+ className={cn("gap-1 pl-2.5", className)}
57
+ {...props}
58
+ >
59
+ <ChevronLeft className="h-4 w-4" />
60
+ <span>Previous</span>
61
+ </PaginationLink>
62
+ );
63
+ PaginationPrevious.displayName = "PaginationPrevious";
64
+
65
+ const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
66
+ <PaginationLink
67
+ aria-label="Go to next page"
68
+ size="default"
69
+ className={cn("gap-1 pr-2.5", className)}
70
+ {...props}
71
+ >
72
+ <span>Next</span>
73
+ <ChevronRight className="h-4 w-4" />
74
+ </PaginationLink>
75
+ );
76
+ PaginationNext.displayName = "PaginationNext";
77
+
78
+ const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
79
+ <span
80
+ aria-hidden
81
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
82
+ {...props}
83
+ >
84
+ <MoreHorizontal className="h-4 w-4" />
85
+ <span className="sr-only">More pages</span>
86
+ </span>
87
+ );
88
+ PaginationEllipsis.displayName = "PaginationEllipsis";
89
+
90
+ export {
91
+ Pagination,
92
+ PaginationContent,
93
+ PaginationLink,
94
+ PaginationItem,
95
+ PaginationPrevious,
96
+ PaginationNext,
97
+ PaginationEllipsis,
98
+ };
src/components/ui/popover.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const Popover = PopoverPrimitive.Root;
7
+
8
+ const PopoverTrigger = PopoverPrimitive.Trigger;
9
+
10
+ const PopoverAnchor = PopoverPrimitive.Anchor;
11
+
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-popover-content-transform-origin)",
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ ));
29
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
src/components/ui/progress.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as ProgressPrimitive from "@radix-ui/react-progress";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const Progress = React.forwardRef<
9
+ React.ElementRef<typeof ProgressPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
11
+ >(({ className, value, ...props }, ref) => (
12
+ <ProgressPrimitive.Root
13
+ ref={ref}
14
+ className={cn("relative h-2 w-full overflow-hidden rounded-full bg-primary/20", className)}
15
+ {...props}
16
+ >
17
+ <ProgressPrimitive.Indicator
18
+ className="h-full w-full flex-1 bg-primary transition-all"
19
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
20
+ />
21
+ </ProgressPrimitive.Root>
22
+ ));
23
+ Progress.displayName = ProgressPrimitive.Root.displayName;
24
+
25
+ export { Progress };
src/components/ui/radio-group.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
3
+ import { Circle } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const RadioGroup = React.forwardRef<
8
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
10
+ >(({ className, ...props }, ref) => {
11
+ return <RadioGroupPrimitive.Root className={cn("grid gap-2", className)} {...props} ref={ref} />;
12
+ });
13
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
14
+
15
+ const RadioGroupItem = React.forwardRef<
16
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
17
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
18
+ >(({ className, ...props }, ref) => {
19
+ return (
20
+ <RadioGroupPrimitive.Item
21
+ ref={ref}
22
+ className={cn(
23
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
24
+ className,
25
+ )}
26
+ {...props}
27
+ >
28
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
29
+ <Circle className="h-3.5 w-3.5 fill-primary" />
30
+ </RadioGroupPrimitive.Indicator>
31
+ </RadioGroupPrimitive.Item>
32
+ );
33
+ });
34
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
35
+
36
+ export { RadioGroup, RadioGroupItem };
src/components/ui/resizable.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { GripVertical } from "lucide-react";
2
+ import { Group, Panel, Separator } from "react-resizable-panels";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps<typeof Group>) => (
7
+ <Group
8
+ className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)}
9
+ {...props}
10
+ />
11
+ );
12
+
13
+ const ResizablePanel = Panel;
14
+
15
+ const ResizableHandle = ({
16
+ withHandle,
17
+ className,
18
+ ...props
19
+ }: React.ComponentProps<typeof Separator> & {
20
+ withHandle?: boolean;
21
+ }) => (
22
+ <Separator
23
+ className={cn(
24
+ "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
25
+ className,
26
+ )}
27
+ {...props}
28
+ >
29
+ {withHandle && (
30
+ <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
31
+ <GripVertical className="h-2.5 w-2.5" />
32
+ </div>
33
+ )}
34
+ </Separator>
35
+ );
36
+
37
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
src/components/ui/scroll-area.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const ScrollArea = React.forwardRef<
7
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9
+ >(({ className, children, ...props }, ref) => (
10
+ <ScrollAreaPrimitive.Root
11
+ ref={ref}
12
+ className={cn("relative overflow-hidden", className)}
13
+ {...props}
14
+ >
15
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
16
+ {children}
17
+ </ScrollAreaPrimitive.Viewport>
18
+ <ScrollBar />
19
+ <ScrollAreaPrimitive.Corner />
20
+ </ScrollAreaPrimitive.Root>
21
+ ));
22
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
23
+
24
+ const ScrollBar = React.forwardRef<
25
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
26
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
27
+ >(({ className, orientation = "vertical", ...props }, ref) => (
28
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
29
+ ref={ref}
30
+ orientation={orientation}
31
+ className={cn(
32
+ "flex touch-none select-none transition-colors",
33
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
34
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
35
+ className,
36
+ )}
37
+ {...props}
38
+ >
39
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
40
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
41
+ ));
42
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
43
+
44
+ export { ScrollArea, ScrollBar };
src/components/ui/select.tsx ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SelectPrimitive from "@radix-ui/react-select";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const Select = SelectPrimitive.Root;
10
+
11
+ const SelectGroup = SelectPrimitive.Group;
12
+
13
+ const SelectValue = SelectPrimitive.Value;
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
+ className,
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ));
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
42
+ {...props}
43
+ >
44
+ <ChevronUp className="h-4 w-4" />
45
+ </SelectPrimitive.ScrollUpButton>
46
+ ));
47
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
48
+
49
+ const SelectScrollDownButton = React.forwardRef<
50
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
51
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
52
+ >(({ className, ...props }, ref) => (
53
+ <SelectPrimitive.ScrollDownButton
54
+ ref={ref}
55
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
56
+ {...props}
57
+ >
58
+ <ChevronDown className="h-4 w-4" />
59
+ </SelectPrimitive.ScrollDownButton>
60
+ ));
61
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
62
+
63
+ const SelectContent = React.forwardRef<
64
+ React.ElementRef<typeof SelectPrimitive.Content>,
65
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
66
+ >(({ className, children, position = "popper", ...props }, ref) => (
67
+ <SelectPrimitive.Portal>
68
+ <SelectPrimitive.Content
69
+ ref={ref}
70
+ className={cn(
71
+ "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-select-content-transform-origin)",
72
+ position === "popper" &&
73
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
74
+ className,
75
+ )}
76
+ position={position}
77
+ {...props}
78
+ >
79
+ <SelectScrollUpButton />
80
+ <SelectPrimitive.Viewport
81
+ className={cn(
82
+ "p-1",
83
+ position === "popper" &&
84
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
85
+ )}
86
+ >
87
+ {children}
88
+ </SelectPrimitive.Viewport>
89
+ <SelectScrollDownButton />
90
+ </SelectPrimitive.Content>
91
+ </SelectPrimitive.Portal>
92
+ ));
93
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
94
+
95
+ const SelectLabel = React.forwardRef<
96
+ React.ElementRef<typeof SelectPrimitive.Label>,
97
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
98
+ >(({ className, ...props }, ref) => (
99
+ <SelectPrimitive.Label
100
+ ref={ref}
101
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
102
+ {...props}
103
+ />
104
+ ));
105
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
106
+
107
+ const SelectItem = React.forwardRef<
108
+ React.ElementRef<typeof SelectPrimitive.Item>,
109
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
110
+ >(({ className, children, ...props }, ref) => (
111
+ <SelectPrimitive.Item
112
+ ref={ref}
113
+ className={cn(
114
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
115
+ className,
116
+ )}
117
+ {...props}
118
+ >
119
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
120
+ <SelectPrimitive.ItemIndicator>
121
+ <Check className="h-4 w-4" />
122
+ </SelectPrimitive.ItemIndicator>
123
+ </span>
124
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
125
+ </SelectPrimitive.Item>
126
+ ));
127
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
128
+
129
+ const SelectSeparator = React.forwardRef<
130
+ React.ElementRef<typeof SelectPrimitive.Separator>,
131
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
132
+ >(({ className, ...props }, ref) => (
133
+ <SelectPrimitive.Separator
134
+ ref={ref}
135
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
136
+ {...props}
137
+ />
138
+ ));
139
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
140
+
141
+ export {
142
+ Select,
143
+ SelectGroup,
144
+ SelectValue,
145
+ SelectTrigger,
146
+ SelectContent,
147
+ SelectLabel,
148
+ SelectItem,
149
+ SelectSeparator,
150
+ SelectScrollUpButton,
151
+ SelectScrollDownButton,
152
+ };
src/components/ui/separator.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const Separator = React.forwardRef<
7
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
9
+ >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
10
+ <SeparatorPrimitive.Root
11
+ ref={ref}
12
+ decorative={decorative}
13
+ orientation={orientation}
14
+ className={cn(
15
+ "shrink-0 bg-border",
16
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ ));
22
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
23
+
24
+ export { Separator };
src/components/ui/sheet.tsx ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SheetPrimitive from "@radix-ui/react-dialog";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { X } from "lucide-react";
7
+
8
+ import { cn } from "@/lib/utils";
9
+
10
+ const Sheet = SheetPrimitive.Root;
11
+
12
+ const SheetTrigger = SheetPrimitive.Trigger;
13
+
14
+ const SheetClose = SheetPrimitive.Close;
15
+
16
+ const SheetPortal = SheetPrimitive.Portal;
17
+
18
+ const SheetOverlay = React.forwardRef<
19
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
20
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
21
+ >(({ className, ...props }, ref) => (
22
+ <SheetPrimitive.Overlay
23
+ className={cn(
24
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
25
+ className,
26
+ )}
27
+ {...props}
28
+ ref={ref}
29
+ />
30
+ ));
31
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
32
+
33
+ const sheetVariants = cva(
34
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
35
+ {
36
+ variants: {
37
+ side: {
38
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39
+ bottom:
40
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42
+ right:
43
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ side: "right",
48
+ },
49
+ },
50
+ );
51
+
52
+ interface SheetContentProps
53
+ extends
54
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
55
+ VariantProps<typeof sheetVariants> {}
56
+
57
+ const SheetContent = React.forwardRef<
58
+ React.ElementRef<typeof SheetPrimitive.Content>,
59
+ SheetContentProps
60
+ >(({ side = "right", className, children, ...props }, ref) => (
61
+ <SheetPortal>
62
+ <SheetOverlay />
63
+ <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
64
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
65
+ <X className="h-4 w-4" />
66
+ <span className="sr-only">Close</span>
67
+ </SheetPrimitive.Close>
68
+ {children}
69
+ </SheetPrimitive.Content>
70
+ </SheetPortal>
71
+ ));
72
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
73
+
74
+ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
75
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
76
+ );
77
+ SheetHeader.displayName = "SheetHeader";
78
+
79
+ const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
80
+ <div
81
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
82
+ {...props}
83
+ />
84
+ );
85
+ SheetFooter.displayName = "SheetFooter";
86
+
87
+ const SheetTitle = React.forwardRef<
88
+ React.ElementRef<typeof SheetPrimitive.Title>,
89
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
90
+ >(({ className, ...props }, ref) => (
91
+ <SheetPrimitive.Title
92
+ ref={ref}
93
+ className={cn("text-lg font-semibold text-foreground", className)}
94
+ {...props}
95
+ />
96
+ ));
97
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
98
+
99
+ const SheetDescription = React.forwardRef<
100
+ React.ElementRef<typeof SheetPrimitive.Description>,
101
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
102
+ >(({ className, ...props }, ref) => (
103
+ <SheetPrimitive.Description
104
+ ref={ref}
105
+ className={cn("text-sm text-muted-foreground", className)}
106
+ {...props}
107
+ />
108
+ ));
109
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
110
+
111
+ export {
112
+ Sheet,
113
+ SheetPortal,
114
+ SheetOverlay,
115
+ SheetTrigger,
116
+ SheetClose,
117
+ SheetContent,
118
+ SheetHeader,
119
+ SheetFooter,
120
+ SheetTitle,
121
+ SheetDescription,
122
+ };
src/components/ui/sidebar.tsx ADDED
@@ -0,0 +1,744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { PanelLeft } from "lucide-react";
5
+
6
+ import { useIsMobile } from "@/hooks/use-mobile";
7
+ import { cn } from "@/lib/utils";
8
+ import { Button } from "@/components/ui/button";
9
+ import { Input } from "@/components/ui/input";
10
+ import { Separator } from "@/components/ui/separator";
11
+ import {
12
+ Sheet,
13
+ SheetContent,
14
+ SheetDescription,
15
+ SheetHeader,
16
+ SheetTitle,
17
+ } from "@/components/ui/sheet";
18
+ import { Skeleton } from "@/components/ui/skeleton";
19
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
20
+
21
+ const SIDEBAR_COOKIE_NAME = "sidebar_state";
22
+ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
23
+ const SIDEBAR_WIDTH = "16rem";
24
+ const SIDEBAR_WIDTH_MOBILE = "18rem";
25
+ const SIDEBAR_WIDTH_ICON = "3rem";
26
+ const SIDEBAR_KEYBOARD_SHORTCUT = "b";
27
+
28
+ type SidebarContextProps = {
29
+ state: "expanded" | "collapsed";
30
+ open: boolean;
31
+ setOpen: (open: boolean) => void;
32
+ openMobile: boolean;
33
+ setOpenMobile: (open: boolean) => void;
34
+ isMobile: boolean;
35
+ toggleSidebar: () => void;
36
+ };
37
+
38
+ const SidebarContext = React.createContext<SidebarContextProps | null>(null);
39
+
40
+ function useSidebar() {
41
+ const context = React.useContext(SidebarContext);
42
+ if (!context) {
43
+ throw new Error("useSidebar must be used within a SidebarProvider.");
44
+ }
45
+
46
+ return context;
47
+ }
48
+
49
+ const SidebarProvider = React.forwardRef<
50
+ HTMLDivElement,
51
+ React.ComponentProps<"div"> & {
52
+ defaultOpen?: boolean;
53
+ open?: boolean;
54
+ onOpenChange?: (open: boolean) => void;
55
+ }
56
+ >(
57
+ (
58
+ {
59
+ defaultOpen = true,
60
+ open: openProp,
61
+ onOpenChange: setOpenProp,
62
+ className,
63
+ style,
64
+ children,
65
+ ...props
66
+ },
67
+ ref,
68
+ ) => {
69
+ const isMobile = useIsMobile();
70
+ const [openMobile, setOpenMobile] = React.useState(false);
71
+
72
+ // This is the internal state of the sidebar.
73
+ // We use openProp and setOpenProp for control from outside the component.
74
+ const [_open, _setOpen] = React.useState(defaultOpen);
75
+ const open = openProp ?? _open;
76
+ const setOpen = React.useCallback(
77
+ (value: boolean | ((value: boolean) => boolean)) => {
78
+ const openState = typeof value === "function" ? value(open) : value;
79
+ if (setOpenProp) {
80
+ setOpenProp(openState);
81
+ } else {
82
+ _setOpen(openState);
83
+ }
84
+
85
+ // This sets the cookie to keep the sidebar state.
86
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
87
+ },
88
+ [setOpenProp, open],
89
+ );
90
+
91
+ // Helper to toggle the sidebar.
92
+ const toggleSidebar = React.useCallback(() => {
93
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
94
+ }, [isMobile, setOpen, setOpenMobile]);
95
+
96
+ // Adds a keyboard shortcut to toggle the sidebar.
97
+ React.useEffect(() => {
98
+ const handleKeyDown = (event: KeyboardEvent) => {
99
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
100
+ event.preventDefault();
101
+ toggleSidebar();
102
+ }
103
+ };
104
+
105
+ window.addEventListener("keydown", handleKeyDown);
106
+ return () => window.removeEventListener("keydown", handleKeyDown);
107
+ }, [toggleSidebar]);
108
+
109
+ // We add a state so that we can do data-state="expanded" or "collapsed".
110
+ // This makes it easier to style the sidebar with Tailwind classes.
111
+ const state = open ? "expanded" : "collapsed";
112
+
113
+ const contextValue = React.useMemo<SidebarContextProps>(
114
+ () => ({
115
+ state,
116
+ open,
117
+ setOpen,
118
+ isMobile,
119
+ openMobile,
120
+ setOpenMobile,
121
+ toggleSidebar,
122
+ }),
123
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
124
+ );
125
+
126
+ return (
127
+ <SidebarContext.Provider value={contextValue}>
128
+ <TooltipProvider delayDuration={0}>
129
+ <div
130
+ style={
131
+ {
132
+ "--sidebar-width": SIDEBAR_WIDTH,
133
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
134
+ ...style,
135
+ } as React.CSSProperties
136
+ }
137
+ className={cn(
138
+ "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
139
+ className,
140
+ )}
141
+ ref={ref}
142
+ {...props}
143
+ >
144
+ {children}
145
+ </div>
146
+ </TooltipProvider>
147
+ </SidebarContext.Provider>
148
+ );
149
+ },
150
+ );
151
+ SidebarProvider.displayName = "SidebarProvider";
152
+
153
+ const Sidebar = React.forwardRef<
154
+ HTMLDivElement,
155
+ React.ComponentProps<"div"> & {
156
+ side?: "left" | "right";
157
+ variant?: "sidebar" | "floating" | "inset";
158
+ collapsible?: "offcanvas" | "icon" | "none";
159
+ }
160
+ >(
161
+ (
162
+ {
163
+ side = "left",
164
+ variant = "sidebar",
165
+ collapsible = "offcanvas",
166
+ className,
167
+ children,
168
+ ...props
169
+ },
170
+ ref,
171
+ ) => {
172
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
173
+
174
+ if (collapsible === "none") {
175
+ return (
176
+ <div
177
+ className={cn(
178
+ "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
179
+ className,
180
+ )}
181
+ ref={ref}
182
+ {...props}
183
+ >
184
+ {children}
185
+ </div>
186
+ );
187
+ }
188
+
189
+ if (isMobile) {
190
+ return (
191
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
192
+ <SheetContent
193
+ data-sidebar="sidebar"
194
+ data-mobile="true"
195
+ className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
196
+ style={
197
+ {
198
+ "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
199
+ } as React.CSSProperties
200
+ }
201
+ side={side}
202
+ >
203
+ <SheetHeader className="sr-only">
204
+ <SheetTitle>Sidebar</SheetTitle>
205
+ <SheetDescription>Displays the mobile sidebar.</SheetDescription>
206
+ </SheetHeader>
207
+ <div className="flex h-full w-full flex-col">{children}</div>
208
+ </SheetContent>
209
+ </Sheet>
210
+ );
211
+ }
212
+
213
+ return (
214
+ <div
215
+ ref={ref}
216
+ className="group peer hidden text-sidebar-foreground md:block"
217
+ data-state={state}
218
+ data-collapsible={state === "collapsed" ? collapsible : ""}
219
+ data-variant={variant}
220
+ data-side={side}
221
+ >
222
+ {/* This is what handles the sidebar gap on desktop */}
223
+ <div
224
+ className={cn(
225
+ "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
226
+ "group-data-[collapsible=offcanvas]:w-0",
227
+ "group-data-[side=right]:rotate-180",
228
+ variant === "floating" || variant === "inset"
229
+ ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
230
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
231
+ )}
232
+ />
233
+ <div
234
+ className={cn(
235
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
236
+ side === "left"
237
+ ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
238
+ : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
239
+ // Adjust the padding for floating and inset variants.
240
+ variant === "floating" || variant === "inset"
241
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
242
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
243
+ className,
244
+ )}
245
+ {...props}
246
+ >
247
+ <div
248
+ data-sidebar="sidebar"
249
+ className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
250
+ >
251
+ {children}
252
+ </div>
253
+ </div>
254
+ </div>
255
+ );
256
+ },
257
+ );
258
+ Sidebar.displayName = "Sidebar";
259
+
260
+ const SidebarTrigger = React.forwardRef<
261
+ React.ElementRef<typeof Button>,
262
+ React.ComponentProps<typeof Button>
263
+ >(({ className, onClick, ...props }, ref) => {
264
+ const { toggleSidebar } = useSidebar();
265
+
266
+ return (
267
+ <Button
268
+ ref={ref}
269
+ data-sidebar="trigger"
270
+ variant="ghost"
271
+ size="icon"
272
+ className={cn("h-7 w-7", className)}
273
+ onClick={(event) => {
274
+ onClick?.(event);
275
+ toggleSidebar();
276
+ }}
277
+ {...props}
278
+ >
279
+ <PanelLeft />
280
+ <span className="sr-only">Toggle Sidebar</span>
281
+ </Button>
282
+ );
283
+ });
284
+ SidebarTrigger.displayName = "SidebarTrigger";
285
+
286
+ const SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<"button">>(
287
+ ({ className, ...props }, ref) => {
288
+ const { toggleSidebar } = useSidebar();
289
+
290
+ return (
291
+ <button
292
+ ref={ref}
293
+ data-sidebar="rail"
294
+ aria-label="Toggle Sidebar"
295
+ tabIndex={-1}
296
+ onClick={toggleSidebar}
297
+ title="Toggle Sidebar"
298
+ className={cn(
299
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
300
+ "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
301
+ "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
302
+ "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
303
+ "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
304
+ "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
305
+ className,
306
+ )}
307
+ {...props}
308
+ />
309
+ );
310
+ },
311
+ );
312
+ SidebarRail.displayName = "SidebarRail";
313
+
314
+ const SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<"main">>(
315
+ ({ className, ...props }, ref) => {
316
+ return (
317
+ <main
318
+ ref={ref}
319
+ className={cn(
320
+ "relative flex w-full flex-1 flex-col bg-background",
321
+ "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
322
+ className,
323
+ )}
324
+ {...props}
325
+ />
326
+ );
327
+ },
328
+ );
329
+ SidebarInset.displayName = "SidebarInset";
330
+
331
+ const SidebarInput = React.forwardRef<
332
+ React.ElementRef<typeof Input>,
333
+ React.ComponentProps<typeof Input>
334
+ >(({ className, ...props }, ref) => {
335
+ return (
336
+ <Input
337
+ ref={ref}
338
+ data-sidebar="input"
339
+ className={cn(
340
+ "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
341
+ className,
342
+ )}
343
+ {...props}
344
+ />
345
+ );
346
+ });
347
+ SidebarInput.displayName = "SidebarInput";
348
+
349
+ const SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
350
+ ({ className, ...props }, ref) => {
351
+ return (
352
+ <div
353
+ ref={ref}
354
+ data-sidebar="header"
355
+ className={cn("flex flex-col gap-2 p-2", className)}
356
+ {...props}
357
+ />
358
+ );
359
+ },
360
+ );
361
+ SidebarHeader.displayName = "SidebarHeader";
362
+
363
+ const SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
364
+ ({ className, ...props }, ref) => {
365
+ return (
366
+ <div
367
+ ref={ref}
368
+ data-sidebar="footer"
369
+ className={cn("flex flex-col gap-2 p-2", className)}
370
+ {...props}
371
+ />
372
+ );
373
+ },
374
+ );
375
+ SidebarFooter.displayName = "SidebarFooter";
376
+
377
+ const SidebarSeparator = React.forwardRef<
378
+ React.ElementRef<typeof Separator>,
379
+ React.ComponentProps<typeof Separator>
380
+ >(({ className, ...props }, ref) => {
381
+ return (
382
+ <Separator
383
+ ref={ref}
384
+ data-sidebar="separator"
385
+ className={cn("mx-2 w-auto bg-sidebar-border", className)}
386
+ {...props}
387
+ />
388
+ );
389
+ });
390
+ SidebarSeparator.displayName = "SidebarSeparator";
391
+
392
+ const SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
393
+ ({ className, ...props }, ref) => {
394
+ return (
395
+ <div
396
+ ref={ref}
397
+ data-sidebar="content"
398
+ className={cn(
399
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
400
+ className,
401
+ )}
402
+ {...props}
403
+ />
404
+ );
405
+ },
406
+ );
407
+ SidebarContent.displayName = "SidebarContent";
408
+
409
+ const SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
410
+ ({ className, ...props }, ref) => {
411
+ return (
412
+ <div
413
+ ref={ref}
414
+ data-sidebar="group"
415
+ className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
416
+ {...props}
417
+ />
418
+ );
419
+ },
420
+ );
421
+ SidebarGroup.displayName = "SidebarGroup";
422
+
423
+ const SidebarGroupLabel = React.forwardRef<
424
+ HTMLDivElement,
425
+ React.ComponentProps<"div"> & { asChild?: boolean }
426
+ >(({ className, asChild = false, ...props }, ref) => {
427
+ const Comp = asChild ? Slot : "div";
428
+
429
+ return (
430
+ <Comp
431
+ ref={ref}
432
+ data-sidebar="group-label"
433
+ className={cn(
434
+ "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
435
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
436
+ className,
437
+ )}
438
+ {...props}
439
+ />
440
+ );
441
+ });
442
+ SidebarGroupLabel.displayName = "SidebarGroupLabel";
443
+
444
+ const SidebarGroupAction = React.forwardRef<
445
+ HTMLButtonElement,
446
+ React.ComponentProps<"button"> & { asChild?: boolean }
447
+ >(({ className, asChild = false, ...props }, ref) => {
448
+ const Comp = asChild ? Slot : "button";
449
+
450
+ return (
451
+ <Comp
452
+ ref={ref}
453
+ data-sidebar="group-action"
454
+ className={cn(
455
+ "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
456
+ // Increases the hit area of the button on mobile.
457
+ "after:absolute after:-inset-2 after:md:hidden",
458
+ "group-data-[collapsible=icon]:hidden",
459
+ className,
460
+ )}
461
+ {...props}
462
+ />
463
+ );
464
+ });
465
+ SidebarGroupAction.displayName = "SidebarGroupAction";
466
+
467
+ const SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
468
+ ({ className, ...props }, ref) => (
469
+ <div
470
+ ref={ref}
471
+ data-sidebar="group-content"
472
+ className={cn("w-full text-sm", className)}
473
+ {...props}
474
+ />
475
+ ),
476
+ );
477
+ SidebarGroupContent.displayName = "SidebarGroupContent";
478
+
479
+ const SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
480
+ ({ className, ...props }, ref) => (
481
+ <ul
482
+ ref={ref}
483
+ data-sidebar="menu"
484
+ className={cn("flex w-full min-w-0 flex-col gap-1", className)}
485
+ {...props}
486
+ />
487
+ ),
488
+ );
489
+ SidebarMenu.displayName = "SidebarMenu";
490
+
491
+ const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
492
+ ({ className, ...props }, ref) => (
493
+ <li
494
+ ref={ref}
495
+ data-sidebar="menu-item"
496
+ className={cn("group/menu-item relative", className)}
497
+ {...props}
498
+ />
499
+ ),
500
+ );
501
+ SidebarMenuItem.displayName = "SidebarMenuItem";
502
+
503
+ const sidebarMenuButtonVariants = cva(
504
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
505
+ {
506
+ variants: {
507
+ variant: {
508
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
509
+ outline:
510
+ "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
511
+ },
512
+ size: {
513
+ default: "h-8 text-sm",
514
+ sm: "h-7 text-xs",
515
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
516
+ },
517
+ },
518
+ defaultVariants: {
519
+ variant: "default",
520
+ size: "default",
521
+ },
522
+ },
523
+ );
524
+
525
+ const SidebarMenuButton = React.forwardRef<
526
+ HTMLButtonElement,
527
+ React.ComponentProps<"button"> & {
528
+ asChild?: boolean;
529
+ isActive?: boolean;
530
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>;
531
+ } & VariantProps<typeof sidebarMenuButtonVariants>
532
+ >(
533
+ (
534
+ {
535
+ asChild = false,
536
+ isActive = false,
537
+ variant = "default",
538
+ size = "default",
539
+ tooltip,
540
+ className,
541
+ ...props
542
+ },
543
+ ref,
544
+ ) => {
545
+ const Comp = asChild ? Slot : "button";
546
+ const { isMobile, state } = useSidebar();
547
+
548
+ const button = (
549
+ <Comp
550
+ ref={ref}
551
+ data-sidebar="menu-button"
552
+ data-size={size}
553
+ data-active={isActive}
554
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
555
+ {...props}
556
+ />
557
+ );
558
+
559
+ if (!tooltip) {
560
+ return button;
561
+ }
562
+
563
+ if (typeof tooltip === "string") {
564
+ tooltip = {
565
+ children: tooltip,
566
+ };
567
+ }
568
+
569
+ return (
570
+ <Tooltip>
571
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
572
+ <TooltipContent
573
+ side="right"
574
+ align="center"
575
+ hidden={state !== "collapsed" || isMobile}
576
+ {...tooltip}
577
+ />
578
+ </Tooltip>
579
+ );
580
+ },
581
+ );
582
+ SidebarMenuButton.displayName = "SidebarMenuButton";
583
+
584
+ const SidebarMenuAction = React.forwardRef<
585
+ HTMLButtonElement,
586
+ React.ComponentProps<"button"> & {
587
+ asChild?: boolean;
588
+ showOnHover?: boolean;
589
+ }
590
+ >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
591
+ const Comp = asChild ? Slot : "button";
592
+
593
+ return (
594
+ <Comp
595
+ ref={ref}
596
+ data-sidebar="menu-action"
597
+ className={cn(
598
+ "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
599
+ // Increases the hit area of the button on mobile.
600
+ "after:absolute after:-inset-2 after:md:hidden",
601
+ "peer-data-[size=sm]/menu-button:top-1",
602
+ "peer-data-[size=default]/menu-button:top-1.5",
603
+ "peer-data-[size=lg]/menu-button:top-2.5",
604
+ "group-data-[collapsible=icon]:hidden",
605
+ showOnHover &&
606
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
607
+ className,
608
+ )}
609
+ {...props}
610
+ />
611
+ );
612
+ });
613
+ SidebarMenuAction.displayName = "SidebarMenuAction";
614
+
615
+ const SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
616
+ ({ className, ...props }, ref) => (
617
+ <div
618
+ ref={ref}
619
+ data-sidebar="menu-badge"
620
+ className={cn(
621
+ "pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground",
622
+ "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
623
+ "peer-data-[size=sm]/menu-button:top-1",
624
+ "peer-data-[size=default]/menu-button:top-1.5",
625
+ "peer-data-[size=lg]/menu-button:top-2.5",
626
+ "group-data-[collapsible=icon]:hidden",
627
+ className,
628
+ )}
629
+ {...props}
630
+ />
631
+ ),
632
+ );
633
+ SidebarMenuBadge.displayName = "SidebarMenuBadge";
634
+
635
+ const SidebarMenuSkeleton = React.forwardRef<
636
+ HTMLDivElement,
637
+ React.ComponentProps<"div"> & {
638
+ showIcon?: boolean;
639
+ }
640
+ >(({ className, showIcon = false, ...props }, ref) => {
641
+ // Random width between 50 to 90%.
642
+ const width = React.useMemo(() => {
643
+ return `${Math.floor(Math.random() * 40) + 50}%`;
644
+ }, []);
645
+
646
+ return (
647
+ <div
648
+ ref={ref}
649
+ data-sidebar="menu-skeleton"
650
+ className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
651
+ {...props}
652
+ >
653
+ {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
654
+ <Skeleton
655
+ className="h-4 max-w-(--skeleton-width) flex-1"
656
+ data-sidebar="menu-skeleton-text"
657
+ style={
658
+ {
659
+ "--skeleton-width": width,
660
+ } as React.CSSProperties
661
+ }
662
+ />
663
+ </div>
664
+ );
665
+ });
666
+ SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
667
+
668
+ const SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
669
+ ({ className, ...props }, ref) => (
670
+ <ul
671
+ ref={ref}
672
+ data-sidebar="menu-sub"
673
+ className={cn(
674
+ "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
675
+ "group-data-[collapsible=icon]:hidden",
676
+ className,
677
+ )}
678
+ {...props}
679
+ />
680
+ ),
681
+ );
682
+ SidebarMenuSub.displayName = "SidebarMenuSub";
683
+
684
+ const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
685
+ ({ ...props }, ref) => <li ref={ref} {...props} />,
686
+ );
687
+ SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
688
+
689
+ const SidebarMenuSubButton = React.forwardRef<
690
+ HTMLAnchorElement,
691
+ React.ComponentProps<"a"> & {
692
+ asChild?: boolean;
693
+ size?: "sm" | "md";
694
+ isActive?: boolean;
695
+ }
696
+ >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
697
+ const Comp = asChild ? Slot : "a";
698
+
699
+ return (
700
+ <Comp
701
+ ref={ref}
702
+ data-sidebar="menu-sub-button"
703
+ data-size={size}
704
+ data-active={isActive}
705
+ className={cn(
706
+ "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
707
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
708
+ size === "sm" && "text-xs",
709
+ size === "md" && "text-sm",
710
+ "group-data-[collapsible=icon]:hidden",
711
+ className,
712
+ )}
713
+ {...props}
714
+ />
715
+ );
716
+ });
717
+ SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
718
+
719
+ export {
720
+ Sidebar,
721
+ SidebarContent,
722
+ SidebarFooter,
723
+ SidebarGroup,
724
+ SidebarGroupAction,
725
+ SidebarGroupContent,
726
+ SidebarGroupLabel,
727
+ SidebarHeader,
728
+ SidebarInput,
729
+ SidebarInset,
730
+ SidebarMenu,
731
+ SidebarMenuAction,
732
+ SidebarMenuBadge,
733
+ SidebarMenuButton,
734
+ SidebarMenuItem,
735
+ SidebarMenuSkeleton,
736
+ SidebarMenuSub,
737
+ SidebarMenuSubButton,
738
+ SidebarMenuSubItem,
739
+ SidebarProvider,
740
+ SidebarRail,
741
+ SidebarSeparator,
742
+ SidebarTrigger,
743
+ useSidebar,
744
+ };
src/components/ui/skeleton.tsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils";
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return <div className={cn("animate-pulse rounded-md bg-primary/10", className)} {...props} />;
5
+ }
6
+
7
+ export { Skeleton };
src/components/ui/slider.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as SliderPrimitive from "@radix-ui/react-slider";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const Slider = React.forwardRef<
7
+ React.ElementRef<typeof SliderPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <SliderPrimitive.Root
11
+ ref={ref}
12
+ className={cn("relative flex w-full touch-none select-none items-center", className)}
13
+ {...props}
14
+ >
15
+ <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
16
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
17
+ </SliderPrimitive.Track>
18
+ <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
19
+ </SliderPrimitive.Root>
20
+ ));
21
+ Slider.displayName = SliderPrimitive.Root.displayName;
22
+
23
+ export { Slider };
src/components/ui/sonner.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Toaster as Sonner } from "sonner";
2
+
3
+ type ToasterProps = React.ComponentProps<typeof Sonner>;
4
+
5
+ const Toaster = ({ ...props }: ToasterProps) => {
6
+ return (
7
+ <Sonner
8
+ className="toaster group"
9
+ toastOptions={{
10
+ classNames: {
11
+ toast:
12
+ "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
13
+ description: "group-[.toast]:text-muted-foreground",
14
+ actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
15
+ cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
16
+ },
17
+ }}
18
+ {...props}
19
+ />
20
+ );
21
+ };
22
+
23
+ export { Toaster };
src/components/ui/switch.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const Switch = React.forwardRef<
7
+ React.ElementRef<typeof SwitchPrimitives.Root>,
8
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <SwitchPrimitives.Root
11
+ className={cn(
12
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
13
+ className,
14
+ )}
15
+ {...props}
16
+ ref={ref}
17
+ >
18
+ <SwitchPrimitives.Thumb
19
+ className={cn(
20
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
21
+ )}
22
+ />
23
+ </SwitchPrimitives.Root>
24
+ ));
25
+ Switch.displayName = SwitchPrimitives.Root.displayName;
26
+
27
+ export { Switch };
src/components/ui/table.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div className="relative w-full overflow-auto">
8
+ <table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
9
+ </div>
10
+ ),
11
+ );
12
+ Table.displayName = "Table";
13
+
14
+ const TableHeader = React.forwardRef<
15
+ HTMLTableSectionElement,
16
+ React.HTMLAttributes<HTMLTableSectionElement>
17
+ >(({ className, ...props }, ref) => (
18
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
19
+ ));
20
+ TableHeader.displayName = "TableHeader";
21
+
22
+ const TableBody = React.forwardRef<
23
+ HTMLTableSectionElement,
24
+ React.HTMLAttributes<HTMLTableSectionElement>
25
+ >(({ className, ...props }, ref) => (
26
+ <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
27
+ ));
28
+ TableBody.displayName = "TableBody";
29
+
30
+ const TableFooter = React.forwardRef<
31
+ HTMLTableSectionElement,
32
+ React.HTMLAttributes<HTMLTableSectionElement>
33
+ >(({ className, ...props }, ref) => (
34
+ <tfoot
35
+ ref={ref}
36
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
37
+ {...props}
38
+ />
39
+ ));
40
+ TableFooter.displayName = "TableFooter";
41
+
42
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
43
+ ({ className, ...props }, ref) => (
44
+ <tr
45
+ ref={ref}
46
+ className={cn(
47
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ ),
53
+ );
54
+ TableRow.displayName = "TableRow";
55
+
56
+ const TableHead = React.forwardRef<
57
+ HTMLTableCellElement,
58
+ React.ThHTMLAttributes<HTMLTableCellElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <th
61
+ ref={ref}
62
+ className={cn(
63
+ "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
64
+ className,
65
+ )}
66
+ {...props}
67
+ />
68
+ ));
69
+ TableHead.displayName = "TableHead";
70
+
71
+ const TableCell = React.forwardRef<
72
+ HTMLTableCellElement,
73
+ React.TdHTMLAttributes<HTMLTableCellElement>
74
+ >(({ className, ...props }, ref) => (
75
+ <td
76
+ ref={ref}
77
+ className={cn(
78
+ "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
79
+ className,
80
+ )}
81
+ {...props}
82
+ />
83
+ ));
84
+ TableCell.displayName = "TableCell";
85
+
86
+ const TableCaption = React.forwardRef<
87
+ HTMLTableCaptionElement,
88
+ React.HTMLAttributes<HTMLTableCaptionElement>
89
+ >(({ className, ...props }, ref) => (
90
+ <caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
91
+ ));
92
+ TableCaption.displayName = "TableCaption";
93
+
94
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
src/components/ui/tabs.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const Tabs = TabsPrimitive.Root;
7
+
8
+ const TabsList = React.forwardRef<
9
+ React.ElementRef<typeof TabsPrimitive.List>,
10
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
11
+ >(({ className, ...props }, ref) => (
12
+ <TabsPrimitive.List
13
+ ref={ref}
14
+ className={cn(
15
+ "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ ));
21
+ TabsList.displayName = TabsPrimitive.List.displayName;
22
+
23
+ const TabsTrigger = React.forwardRef<
24
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
26
+ >(({ className, ...props }, ref) => (
27
+ <TabsPrimitive.Trigger
28
+ ref={ref}
29
+ className={cn(
30
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
31
+ className,
32
+ )}
33
+ {...props}
34
+ />
35
+ ));
36
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
37
+
38
+ const TabsContent = React.forwardRef<
39
+ React.ElementRef<typeof TabsPrimitive.Content>,
40
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
41
+ >(({ className, ...props }, ref) => (
42
+ <TabsPrimitive.Content
43
+ ref={ref}
44
+ className={cn(
45
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ ));
51
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
52
+
53
+ export { Tabs, TabsList, TabsTrigger, TabsContent };