diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4816602..f3175b6 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -18,5 +18,6 @@
"typescript",
"typescriptreact",
"vue"
- ]
+ ],
+ "css.lint.unknownAtRules": "ignore"
}
diff --git a/app/assets/css/tailwind.css b/app/assets/css/tailwind.css
index a461c50..6ed1557 100644
--- a/app/assets/css/tailwind.css
+++ b/app/assets/css/tailwind.css
@@ -1 +1,127 @@
-@import "tailwindcss";
\ No newline at end of file
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --font-sans: 'Inter Variable', sans-serif;
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --color-foreground: var(--foreground);
+ --color-background: var(--background);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --radius-2xl: calc(var(--radius) + 8px);
+ --radius-3xl: calc(var(--radius) + 12px);
+ --radius-4xl: calc(var(--radius) + 16px);
+}
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.141 0.005 285.823);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.141 0.005 285.823);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.141 0.005 285.823);
+ --primary: oklch(0.60 0.10 185);
+ --primary-foreground: oklch(0.98 0.01 181);
+ --secondary: oklch(0.967 0.001 286.375);
+ --secondary-foreground: oklch(0.21 0.006 285.885);
+ --muted: oklch(0.967 0.001 286.375);
+ --muted-foreground: oklch(0.552 0.016 285.938);
+ --accent: oklch(0.967 0.001 286.375);
+ --accent-foreground: oklch(0.21 0.006 285.885);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.92 0.004 286.32);
+ --input: oklch(0.92 0.004 286.32);
+ --ring: oklch(0.705 0.015 286.067);
+ --chart-1: oklch(0.85 0.13 181);
+ --chart-2: oklch(0.78 0.13 182);
+ --chart-3: oklch(0.70 0.12 183);
+ --chart-4: oklch(0.60 0.10 185);
+ --chart-5: oklch(0.51 0.09 186);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
+ --sidebar-primary: oklch(0.60 0.10 185);
+ --sidebar-primary-foreground: oklch(0.98 0.01 181);
+ --sidebar-accent: oklch(0.967 0.001 286.375);
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
+ --sidebar-border: oklch(0.92 0.004 286.32);
+ --sidebar-ring: oklch(0.705 0.015 286.067);
+}
+
+.dark {
+ --background: oklch(0.141 0.005 285.823);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.21 0.006 285.885);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.21 0.006 285.885);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.70 0.12 183);
+ --primary-foreground: oklch(0.28 0.04 193);
+ --secondary: oklch(0.274 0.006 286.033);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.274 0.006 286.033);
+ --muted-foreground: oklch(0.705 0.015 286.067);
+ --accent: oklch(0.274 0.006 286.033);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.552 0.016 285.938);
+ --chart-1: oklch(0.85 0.13 181);
+ --chart-2: oklch(0.78 0.13 182);
+ --chart-3: oklch(0.70 0.12 183);
+ --chart-4: oklch(0.60 0.10 185);
+ --chart-5: oklch(0.51 0.09 186);
+ --sidebar: oklch(0.21 0.006 285.885);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.78 0.13 182);
+ --sidebar-primary-foreground: oklch(0.28 0.04 193);
+ --sidebar-accent: oklch(0.274 0.006 286.033);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.552 0.016 285.938);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply font-sans bg-background text-foreground;
+ }
+ html {
+ @apply font-sans;
+ }
+}
\ No newline at end of file
diff --git a/app/components/ui/button/Button.vue b/app/components/ui/button/Button.vue
new file mode 100644
index 0000000..bd3754e
--- /dev/null
+++ b/app/components/ui/button/Button.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/button/index.ts b/app/components/ui/button/index.ts
new file mode 100644
index 0000000..b2e66e0
--- /dev/null
+++ b/app/components/ui/button/index.ts
@@ -0,0 +1,35 @@
+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-[3px] 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",
+ 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-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+export type ButtonVariants = VariantProps;
diff --git a/app/lib/utils.ts b/app/lib/utils.ts
new file mode 100644
index 0000000..88283f0
--- /dev/null
+++ b/app/lib/utils.ts
@@ -0,0 +1,7 @@
+import type { ClassValue } from "clsx";
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/app/pages/index.vue b/app/pages/index.vue
index ecb8ea1..2282efc 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1,3 +1,10 @@
+
+
- Dashboard
+ Dashboard
+
+
+
diff --git a/app/plugins/50-ssr-width.ts b/app/plugins/50-ssr-width.ts
new file mode 100644
index 0000000..cfdaa60
--- /dev/null
+++ b/app/plugins/50-ssr-width.ts
@@ -0,0 +1,5 @@
+import { provideSSRWidth } from "@vueuse/core";
+
+export default defineNuxtPlugin((nuxtApp) => {
+ provideSSRWidth(1024, nuxtApp.vueApp);
+});
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..4ec9aa7
--- /dev/null
+++ b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://shadcn-vue.com/schema.json",
+ "style": "new-york",
+ "typescript": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/assets/css/tailwind.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "composables": "@/composables"
+ },
+ "registries": {}
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index b801e08..5669e32 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -7,6 +7,7 @@ export default withNuxt(eslintPluginPrettierRecommended, {
rules: {
// Vue rules
"vue/no-multiple-template-root": "off",
+ "vue/require-default-prop": "off",
// Typescript rules
"@typescript-eslint/no-explicit-any": "off",
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 113325b..494864b 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -3,6 +3,13 @@ import tailwindcss from "@tailwindcss/vite";
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
+ app: {
+ head: {
+ htmlAttrs: {
+ class: "dark",
+ },
+ },
+ },
devtools: { enabled: true },
router: {
options: {
@@ -13,6 +20,11 @@ export default defineNuxtConfig({
css: ["~/assets/css/tailwind.css", "~/assets/scss/styles.scss"],
vite: {
plugins: [tailwindcss()],
+ build: { sourcemap: false },
+ },
+ modules: ["@nuxt/eslint", "shadcn-nuxt", "@vueuse/nuxt"],
+ shadcn: {
+ prefix: "",
+ componentDir: "~/components/ui",
},
- modules: ["@nuxt/eslint"],
});
diff --git a/package-lock.json b/package-lock.json
index 8b04adf..ac5b050 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,25 +9,35 @@
"version": "1.0.0",
"hasInstallScript": true,
"dependencies": {
- "@tailwindcss/vite": "^4.1.18",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-vue-next": "^0.562.0",
"nuxt": "^4.1.3",
- "tailwindcss": "^4.1.18",
+ "reka-ui": "^2.6.1",
+ "shadcn-nuxt": "^2.4.3",
+ "tailwind-merge": "^3.4.0",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@nuxt/eslint": "^1.9.0",
+ "@tailwindcss/vite": "^4.1.18",
"@vitejs/plugin-vue": "^6.0.1",
"@vitest/coverage-v8": "^4.0.1",
"@vue/test-utils": "^2.4.6",
+ "@vueuse/core": "^14.1.0",
+ "@vueuse/nuxt": "^14.1.0",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"happy-dom": "^20.0.8",
"prettier": "^3.6.2",
"sass-embedded": "^1.93.3",
+ "tailwindcss": "^4.1.18",
+ "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
- "vitest": "^4.0.1"
+ "vitest": "^4.0.1",
+ "vue-tsc": "^3.1.8"
}
},
"node_modules/@antfu/install-pkg": {
@@ -1344,6 +1354,68 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@floating-ui/vue": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz",
+ "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4",
+ "@floating-ui/utils": "^0.2.10",
+ "vue-demi": ">=0.13.0"
+ }
+ },
+ "node_modules/@floating-ui/vue/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1396,6 +1468,24 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@internationalized/date": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.1.tgz",
+ "integrity": "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/number": {
+ "version": "3.6.5",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz",
+ "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
"node_modules/@ioredis/commands": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz",
@@ -3799,10 +3889,20 @@
"eslint": ">=9.0.0"
}
},
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/@tailwindcss/node": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
"integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
@@ -3818,6 +3918,7 @@
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
"integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
@@ -3844,6 +3945,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3860,6 +3962,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3876,6 +3979,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3892,6 +3996,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3908,6 +4013,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3924,6 +4030,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3940,6 +4047,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3956,6 +4064,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3972,6 +4081,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3996,6 +4106,7 @@
"cpu": [
"wasm32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -4017,6 +4128,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -4033,6 +4145,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -4046,6 +4159,7 @@
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
"integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.1.18",
@@ -4056,6 +4170,32 @@
"vite": "^5.2.0 || ^6 || ^7"
}
},
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.13.13",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.13.tgz",
+ "integrity": "sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/vue-virtual": {
+ "version": "3.13.13",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.13.tgz",
+ "integrity": "sha512-Cf2xIEE8nWAfsX0N5nihkPYMeQRT+pHt4NEkuP8rNCn6lVnLDiV8rC8IeIxbKmQC0yPnj4SIBLwXYVf86xxKTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.13.13"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "vue": "^2.7.0 || ^3.0.0"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -4119,6 +4259,12 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"license": "MIT"
},
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "license": "MIT"
+ },
"node_modules/@types/whatwg-mimetype": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
@@ -4883,6 +5029,18 @@
"integrity": "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==",
"license": "MIT"
},
+ "node_modules/@volar/typescript": {
+ "version": "2.4.26",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.26.tgz",
+ "integrity": "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.26",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
"node_modules/@vue-macros/common": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.1.tgz",
@@ -5157,6 +5315,67 @@
"vue-component-type-helpers": "^2.0.0"
}
},
+ "node_modules/@vueuse/core": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.1.0.tgz",
+ "integrity": "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "14.1.0",
+ "@vueuse/shared": "14.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.1.0.tgz",
+ "integrity": "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/nuxt": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/nuxt/-/nuxt-14.1.0.tgz",
+ "integrity": "sha512-zw8WSgRrdtsA1daqlFl5ojoTJnvWad/IbMIcHw4EN8Wci09koeFfh5/oKbkKeIQ3gzihvr9x0bu8BVz8Z2auSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nuxt/kit": "^4.1.3",
+ "@vueuse/core": "14.1.0",
+ "@vueuse/metadata": "14.1.0",
+ "local-pkg": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "nuxt": "^3.0.0 || ^4.0.0-0",
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.1.0.tgz",
+ "integrity": "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
"node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
@@ -5368,6 +5587,18 @@
"devOptional": true,
"license": "Python-2.0"
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -5873,6 +6104,18 @@
"consola": "^3.2.3"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/clean-regexp": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
@@ -5985,6 +6228,15 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@ -6740,6 +6992,7 @@
"version": "5.18.4",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
"integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -8788,6 +9041,7 @@
"version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "devOptional": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -9170,6 +9424,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-vue-next": {
+ "version": "0.562.0",
+ "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.562.0.tgz",
+ "integrity": "sha512-LN0BLGKMFulv0lnfK29r14DcngRUhIqdcaL0zXTt2o0oS9odlrjCGaU3/X9hIihOjjN8l8e+Y9G/famcNYaI7Q==",
+ "license": "ISC",
+ "peerDependencies": {
+ "vue": ">=3.0.1"
+ }
+ },
"node_modules/magic-regexp": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz",
@@ -11575,6 +11838,63 @@
"regjsparser": "bin/parser"
}
},
+ "node_modules/reka-ui": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.6.1.tgz",
+ "integrity": "sha512-XK7cJDQoNuGXfCNzBBo/81Yg/OgjPwvbabnlzXG2VsdSgNsT6iIkuPBPr+C0Shs+3bb0x0lbPvgQAhMSCKm5Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.6.13",
+ "@floating-ui/vue": "^1.1.6",
+ "@internationalized/date": "^3.5.0",
+ "@internationalized/number": "^3.5.0",
+ "@tanstack/vue-virtual": "^3.12.0",
+ "@vueuse/core": "^12.5.0",
+ "@vueuse/shared": "^12.5.0",
+ "aria-hidden": "^1.2.4",
+ "defu": "^6.1.4",
+ "ohash": "^2.0.11"
+ },
+ "peerDependencies": {
+ "vue": ">= 3.2.0"
+ }
+ },
+ "node_modules/reka-ui/node_modules/@vueuse/core": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz",
+ "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "12.8.2",
+ "@vueuse/shared": "12.8.2",
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/reka-ui/node_modules/@vueuse/metadata": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz",
+ "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/reka-ui/node_modules/@vueuse/shared": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz",
+ "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==",
+ "license": "MIT",
+ "dependencies": {
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -12310,6 +12630,48 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/shadcn-nuxt": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/shadcn-nuxt/-/shadcn-nuxt-2.4.3.tgz",
+ "integrity": "sha512-OY1vV3aQCmbgTn/DFIyaSOxvIosUeWKT98eLBabgYc8K6V99IdTRJ5zlbFj0olwTHHwXTs8pGvDK9CGkn/pY2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@nuxt/kit": "^3.17.4",
+ "oxc-parser": "^0.102.0"
+ }
+ },
+ "node_modules/shadcn-nuxt/node_modules/@nuxt/kit": {
+ "version": "3.20.2",
+ "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.20.2.tgz",
+ "integrity": "sha512-laqfmMcWWNV1FsVmm1+RQUoGY8NIJvCRl0z0K8ikqPukoEry0LXMqlQ+xaf8xJRvoH2/78OhZmsEEsUBTXipcw==",
+ "license": "MIT",
+ "dependencies": {
+ "c12": "^3.3.2",
+ "consola": "^3.4.2",
+ "defu": "^6.1.4",
+ "destr": "^2.0.5",
+ "errx": "^0.1.0",
+ "exsolve": "^1.0.8",
+ "ignore": "^7.0.5",
+ "jiti": "^2.6.1",
+ "klona": "^2.0.6",
+ "knitwork": "^1.3.0",
+ "mlly": "^1.8.0",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "pkg-types": "^2.3.0",
+ "rc9": "^2.1.2",
+ "scule": "^1.3.0",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ufo": "^1.6.1",
+ "unctx": "^2.4.1",
+ "untyped": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -12858,16 +13220,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -13064,9 +13438,18 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "devOptional": true,
"license": "0BSD"
},
+ "node_modules/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -14127,6 +14510,23 @@
"vue": "^3.5.0"
}
},
+ "node_modules/vue-tsc": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.8.tgz",
+ "integrity": "sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/typescript": "2.4.26",
+ "@vue/language-core": "3.1.8"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/package.json b/package.json
index e3bd3fa..1b78c64 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"type": "module",
"private": true,
"scripts": {
+ "prebuild": "vue-tsc --noEmit",
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
@@ -11,29 +12,40 @@
"postinstall": "nuxt prepare",
"lint": "eslint .",
"lint-files": "eslint --ext .js,.ts,.vue",
+ "type-check": "vue-tsc --noEmit",
"vitest": "vitest",
"test": "vitest run",
"coverage": "vitest run --coverage"
},
"dependencies": {
- "@tailwindcss/vite": "^4.1.18",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-vue-next": "^0.562.0",
"nuxt": "^4.1.3",
- "tailwindcss": "^4.1.18",
+ "reka-ui": "^2.6.1",
+ "shadcn-nuxt": "^2.4.3",
+ "tailwind-merge": "^3.4.0",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@nuxt/eslint": "^1.9.0",
+ "@tailwindcss/vite": "^4.1.18",
"@vitejs/plugin-vue": "^6.0.1",
"@vitest/coverage-v8": "^4.0.1",
"@vue/test-utils": "^2.4.6",
+ "@vueuse/core": "^14.1.0",
+ "@vueuse/nuxt": "^14.1.0",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"happy-dom": "^20.0.8",
"prettier": "^3.6.2",
"sass-embedded": "^1.93.3",
+ "tailwindcss": "^4.1.18",
+ "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
- "vitest": "^4.0.1"
+ "vitest": "^4.0.1",
+ "vue-tsc": "^3.1.8"
}
}
diff --git a/tests/.keep b/tests/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/plugins/50-ssr-width.test.ts b/tests/plugins/50-ssr-width.test.ts
new file mode 100644
index 0000000..2a70a70
--- /dev/null
+++ b/tests/plugins/50-ssr-width.test.ts
@@ -0,0 +1,31 @@
+import { describe, it, expect, vi } from "vitest";
+import { provideSSRWidth } from "@vueuse/core";
+
+// Mock @vueuse/core
+vi.mock("@vueuse/core", () => ({
+ provideSSRWidth: vi.fn(),
+}));
+
+describe("SSR Width Plugin", () => {
+ it("should call provideSSRWidth with 1024 and vueApp", async () => {
+ // Mock global defineNuxtPlugin
+ vi.stubGlobal("defineNuxtPlugin", (plugin: any) => plugin);
+
+ // Dynamic import to ensure global is set first
+ const { default: plugin } = await import("~/plugins/50-ssr-width");
+
+ const mockVueApp = {};
+ const mockNuxtApp = {
+ vueApp: mockVueApp,
+ };
+
+ // The plugin default export is the function passed to defineNuxtPlugin
+ // because of our mock above.
+ plugin(mockNuxtApp);
+
+ expect(provideSSRWidth).toHaveBeenCalledTimes(1);
+ expect(provideSSRWidth).toHaveBeenCalledWith(1024, mockVueApp);
+
+ vi.unstubAllGlobals();
+ });
+});
diff --git a/tsconfig.test.json b/tsconfig.test.json
index 8eda9c9..6356beb 100644
--- a/tsconfig.test.json
+++ b/tsconfig.test.json
@@ -1,5 +1,10 @@
{
"compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
"paths": {
"~": ["./app"],
"~/*": ["./app/*"],