diff --git a/app/app.vue b/app/app.vue
index 09f935b..87beb38 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -2,5 +2,10 @@
+
+
+
diff --git a/app/components/ui/button/Button.vue b/app/components/ui/button/Button.vue
new file mode 100644
index 0000000..3763470
--- /dev/null
+++ b/app/components/ui/button/Button.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/button/index.ts b/app/components/ui/button/index.ts
new file mode 100644
index 0000000..0a62178
--- /dev/null
+++ b/app/components/ui/button/index.ts
@@ -0,0 +1,40 @@
+import type { VariantProps } from "class-variance-authority"
+import { cva } from "class-variance-authority"
+
+export { default as Button } from "./Button.vue"
+
+export const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ "default": "h-9 px-4 py-2 has-[>svg]:px-3",
+ "xs": "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
+ "sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ "lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
+ "icon": "size-9",
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+export type ButtonVariants = VariantProps
diff --git a/app/components/ui/card/Card.vue b/app/components/ui/card/Card.vue
new file mode 100644
index 0000000..f5a0707
--- /dev/null
+++ b/app/components/ui/card/Card.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/CardAction.vue b/app/components/ui/card/CardAction.vue
new file mode 100644
index 0000000..c91638b
--- /dev/null
+++ b/app/components/ui/card/CardAction.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/CardContent.vue b/app/components/ui/card/CardContent.vue
new file mode 100644
index 0000000..dfbc552
--- /dev/null
+++ b/app/components/ui/card/CardContent.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/CardDescription.vue b/app/components/ui/card/CardDescription.vue
new file mode 100644
index 0000000..71c1b8d
--- /dev/null
+++ b/app/components/ui/card/CardDescription.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/CardFooter.vue b/app/components/ui/card/CardFooter.vue
new file mode 100644
index 0000000..9e3739e
--- /dev/null
+++ b/app/components/ui/card/CardFooter.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/CardHeader.vue b/app/components/ui/card/CardHeader.vue
new file mode 100644
index 0000000..4fe4da4
--- /dev/null
+++ b/app/components/ui/card/CardHeader.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/CardTitle.vue b/app/components/ui/card/CardTitle.vue
new file mode 100644
index 0000000..5f479e7
--- /dev/null
+++ b/app/components/ui/card/CardTitle.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/card/index.ts b/app/components/ui/card/index.ts
new file mode 100644
index 0000000..1627758
--- /dev/null
+++ b/app/components/ui/card/index.ts
@@ -0,0 +1,7 @@
+export { default as Card } from "./Card.vue"
+export { default as CardAction } from "./CardAction.vue"
+export { default as CardContent } from "./CardContent.vue"
+export { default as CardDescription } from "./CardDescription.vue"
+export { default as CardFooter } from "./CardFooter.vue"
+export { default as CardHeader } from "./CardHeader.vue"
+export { default as CardTitle } from "./CardTitle.vue"
diff --git a/app/components/ui/field/Field.vue b/app/components/ui/field/Field.vue
new file mode 100644
index 0000000..5519d37
--- /dev/null
+++ b/app/components/ui/field/Field.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/field/FieldContent.vue b/app/components/ui/field/FieldContent.vue
new file mode 100644
index 0000000..d9a23fd
--- /dev/null
+++ b/app/components/ui/field/FieldContent.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/field/FieldDescription.vue b/app/components/ui/field/FieldDescription.vue
new file mode 100644
index 0000000..7240a83
--- /dev/null
+++ b/app/components/ui/field/FieldDescription.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/field/FieldError.vue b/app/components/ui/field/FieldError.vue
new file mode 100644
index 0000000..8a0a63f
--- /dev/null
+++ b/app/components/ui/field/FieldError.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
diff --git a/app/components/ui/field/FieldGroup.vue b/app/components/ui/field/FieldGroup.vue
new file mode 100644
index 0000000..834d8ce
--- /dev/null
+++ b/app/components/ui/field/FieldGroup.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/field/FieldLabel.vue b/app/components/ui/field/FieldLabel.vue
new file mode 100644
index 0000000..ce6c498
--- /dev/null
+++ b/app/components/ui/field/FieldLabel.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/app/components/ui/field/FieldLegend.vue b/app/components/ui/field/FieldLegend.vue
new file mode 100644
index 0000000..c620fed
--- /dev/null
+++ b/app/components/ui/field/FieldLegend.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/components/ui/field/FieldSeparator.vue b/app/components/ui/field/FieldSeparator.vue
new file mode 100644
index 0000000..97d0efa
--- /dev/null
+++ b/app/components/ui/field/FieldSeparator.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/ui/field/FieldSet.vue b/app/components/ui/field/FieldSet.vue
new file mode 100644
index 0000000..7be4dc9
--- /dev/null
+++ b/app/components/ui/field/FieldSet.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/app/components/ui/field/FieldTitle.vue b/app/components/ui/field/FieldTitle.vue
new file mode 100644
index 0000000..f564b8b
--- /dev/null
+++ b/app/components/ui/field/FieldTitle.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/field/index.ts b/app/components/ui/field/index.ts
new file mode 100644
index 0000000..162ba14
--- /dev/null
+++ b/app/components/ui/field/index.ts
@@ -0,0 +1,39 @@
+import type { VariantProps } from "class-variance-authority"
+import { cva } from "class-variance-authority"
+
+export const fieldVariants = cva(
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
+ {
+ variants: {
+ orientation: {
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
+ horizontal: [
+ "flex-row items-center",
+ "[&>[data-slot=field-label]]:flex-auto",
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ ],
+ responsive: [
+ "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ ],
+ },
+ },
+ defaultVariants: {
+ orientation: "vertical",
+ },
+ },
+)
+
+export type FieldVariants = VariantProps
+
+export { default as Field } from "./Field.vue"
+export { default as FieldContent } from "./FieldContent.vue"
+export { default as FieldDescription } from "./FieldDescription.vue"
+export { default as FieldError } from "./FieldError.vue"
+export { default as FieldGroup } from "./FieldGroup.vue"
+export { default as FieldLabel } from "./FieldLabel.vue"
+export { default as FieldLegend } from "./FieldLegend.vue"
+export { default as FieldSeparator } from "./FieldSeparator.vue"
+export { default as FieldSet } from "./FieldSet.vue"
+export { default as FieldTitle } from "./FieldTitle.vue"
diff --git a/app/components/ui/input/Input.vue b/app/components/ui/input/Input.vue
new file mode 100644
index 0000000..9db9e85
--- /dev/null
+++ b/app/components/ui/input/Input.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
diff --git a/app/components/ui/input/index.ts b/app/components/ui/input/index.ts
new file mode 100644
index 0000000..9976b86
--- /dev/null
+++ b/app/components/ui/input/index.ts
@@ -0,0 +1 @@
+export { default as Input } from "./Input.vue"
diff --git a/app/components/ui/label/Label.vue b/app/components/ui/label/Label.vue
new file mode 100644
index 0000000..ee63970
--- /dev/null
+++ b/app/components/ui/label/Label.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/app/components/ui/label/index.ts b/app/components/ui/label/index.ts
new file mode 100644
index 0000000..036e35c
--- /dev/null
+++ b/app/components/ui/label/index.ts
@@ -0,0 +1 @@
+export { default as Label } from "./Label.vue"
diff --git a/app/components/ui/separator/Separator.vue b/app/components/ui/separator/Separator.vue
new file mode 100644
index 0000000..78d60ec
--- /dev/null
+++ b/app/components/ui/separator/Separator.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
diff --git a/app/components/ui/separator/index.ts b/app/components/ui/separator/index.ts
new file mode 100644
index 0000000..4407287
--- /dev/null
+++ b/app/components/ui/separator/index.ts
@@ -0,0 +1 @@
+export { default as Separator } from "./Separator.vue"
diff --git a/app/components/ui/sonner/Sonner.vue b/app/components/ui/sonner/Sonner.vue
new file mode 100644
index 0000000..45389fc
--- /dev/null
+++ b/app/components/ui/sonner/Sonner.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/ui/sonner/index.ts b/app/components/ui/sonner/index.ts
new file mode 100644
index 0000000..6673112
--- /dev/null
+++ b/app/components/ui/sonner/index.ts
@@ -0,0 +1 @@
+export { default as Toaster } from "./Sonner.vue"
diff --git a/bun.lock b/bun.lock
index 4445132..7a0b181 100644
--- a/bun.lock
+++ b/bun.lock
@@ -6,13 +6,16 @@
"name": "web2",
"dependencies": {
"@lucide/vue": "^1.26.0",
+ "@vueuse/core": "^14.3.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"nuxt": "^4.5.0",
+ "reka-ui": "^2.10.1",
"shadcn-nuxt": "2.8.0",
"tailwind-merge": "^3.6.0",
"vue": "^3.5.40",
"vue-router": "^5.2.0",
+ "vue-sonner": "^2.0.9",
},
"devDependencies": {
"@nuxtjs/tailwindcss": "7.0.0-beta.1",
@@ -1675,6 +1678,8 @@
"vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="],
+ "vue-sonner": ["vue-sonner@2.0.9", "", { "peerDependencies": { "@nuxt/kit": "^4.0.3", "@nuxt/schema": "^4.0.3", "nuxt": "^4.0.3" }, "optionalPeers": ["@nuxt/kit", "@nuxt/schema", "nuxt"] }, "sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw=="],
+
"web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
diff --git a/package.json b/package.json
index 32171d4..1ef9e65 100644
--- a/package.json
+++ b/package.json
@@ -11,10 +11,13 @@
},
"dependencies": {
"@lucide/vue": "^1.26.0",
+ "@vueuse/core": "^14.3.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"nuxt": "^4.5.0",
+ "reka-ui": "^2.10.1",
"shadcn-nuxt": "2.8.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"vue": "^3.5.40",
"vue-router": "^5.2.0"