From c6f2feed148280083c0ba3b051803bb5582d77dc Mon Sep 17 00:00:00 2001 From: xiaomai Date: Wed, 17 Sep 2025 23:32:29 +0800 Subject: [PATCH] feat(ui): integrate Shadcn UI This commit integrates the Shadcn UI component library to provide a foundational set of components for the application's user interface. Key changes include: - Added necessary dependencies like `class-variance-authority`, `clsx`, and `tailwind-merge`. - Initialized Shadcn via its CLI, creating the `components.json` configuration file. - Configured TypeScript path aliases in `tsconfig.json` and `tsconfig.app.json` to support `@/*` imports. - Implemented CSS variables for theming, supporting both light and dark modes, in `main.css`. - Added the standard `cn` utility function for merging Tailwind CSS classes. - Updated `README.md` with detailed setup instructions for Shadcn UI. --- README.md | 63 ++++++++++++++++++++++- components.json | 22 ++++++++ package.json | 5 ++ pnpm-lock.yaml | 53 ++++++++++++++++++++ src/lib/utils.ts | 6 +++ src/styles/main.css | 119 ++++++++++++++++++++++++++++++++++++++++++++ tsconfig.app.json | 1 + tsconfig.json | 8 ++- 8 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 components.json create mode 100644 src/lib/utils.ts diff --git a/README.md b/README.md index e3631fd..b87967c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This is a learning project that combines multiple frameworks to create a demo ap - **Base framework:** Vue 3 + Vite 7 - **CSS framework:** Tailwind CSS 4 +- **UI framework:** Shadcn UI - **Package manager:** pnpm --- @@ -114,7 +115,7 @@ Now you can use Tailwind CSS atomic classes in your project. --- -## Example Usage +#### Example Usage for Tailwind CSS v4 ```vue @@ -144,3 +145,63 @@ Now you can use Tailwind CSS atomic classes in your project. ``` + +### Shadcn UI + +> Installation guide adapted from the [official Shadcn UI Vite documentation](https://ui.shadcn.com/docs/installation/vite). + +#### 1. Configure TypeScript Paths + +Add the `baseUrl` and `paths` properties to the `compilerOptions` section in both `tsconfig.json` and `tsconfig.app.json` to enable absolute imports: + +**tsconfig.json** + +```json +{ + "files": [], + "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.app.json" }], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +**tsconfig.app.json** + +```json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +This allows you to import files from `src` using `@/` instead of relative paths like `../../`. + +--- + +#### 2. Install Node Type Definitions + +Shadcn UI requires Node type definitions for TypeScript. Install them as a development dependency: + +```sh +pnpm add -D @types/node +``` + +--- + +#### 3. Initialize Shadcn CLI + +Run the Shadcn CLI to set up the UI components and folder structure: + +```sh +pnpm dlx shadcn@latest init +``` + +Follow the prompts to choose which components to include. After initialization, you can start using Shadcn UI components in your Vue project. diff --git a/components.json b/components.json new file mode 100644 index 0000000..92abb59 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles/main.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/package.json b/package.json index 7b3f988..d5892e1 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,11 @@ }, "dependencies": { "@tailwindcss/vite": "^4.1.13", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.544.0", "pinia": "^3.0.3", + "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.13", "vue": "^3.5.18", "vue-router": "^4.5.1" @@ -35,6 +39,7 @@ "jiti": "^2.4.2", "npm-run-all2": "^8.0.4", "prettier": "3.6.2", + "tw-animate-css": "^1.3.8", "typescript": "~5.8.0", "vite": "^7.0.6", "vite-plugin-vue-devtools": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d86122..9c2e648 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,21 @@ importers: '@tailwindcss/vite': specifier: ^4.1.13 version: 4.1.13(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.544.0 + version: 0.544.0(react@19.1.1) pinia: specifier: ^3.0.3 version: 3.0.3(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 tailwindcss: specifier: ^4.1.13 version: 4.1.13 @@ -60,6 +72,9 @@ importers: prettier: specifier: 3.6.2 version: 3.6.2 + tw-animate-css: + specifier: ^1.3.8 + version: 1.3.8 typescript: specifier: ~5.8.0 version: 5.8.3 @@ -951,6 +966,13 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1398,6 +1420,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.544.0: + resolution: {integrity: sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -1586,6 +1613,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + engines: {node: '>=0.10.0'} + read-package-json-fast@4.0.0: resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -1670,6 +1701,9 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + tailwindcss@4.1.13: resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} @@ -1699,6 +1733,9 @@ packages: peerDependencies: typescript: '>=4.8.4' + tw-animate-css@1.3.8: + resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2757,6 +2794,12 @@ snapshots: chownr@3.0.0: {} + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3163,6 +3206,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.544.0(react@19.1.1): + dependencies: + react: 19.1.1 + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3318,6 +3365,8 @@ snapshots: queue-microtask@1.2.3: {} + react@19.1.1: {} + read-package-json-fast@4.0.0: dependencies: json-parse-even-better-errors: 4.0.0 @@ -3402,6 +3451,8 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tailwind-merge@3.3.1: {} + tailwindcss@4.1.13: {} tapable@2.2.3: {} @@ -3430,6 +3481,8 @@ snapshots: dependencies: typescript: 5.8.3 + tw-animate-css@1.3.8: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/src/styles/main.css b/src/styles/main.css index 01f5ae2..43b2aeb 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1,4 +1,7 @@ @import 'tailwindcss'; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); /* 定义主题变量 */ @theme { @@ -42,3 +45,119 @@ @apply bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent; } */ + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --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.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/tsconfig.app.json b/tsconfig.app.json index 913b8f2..0c46d59 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -5,6 +5,7 @@ "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "baseUrl": ".", "paths": { "@/*": ["./src/*"] } diff --git a/tsconfig.json b/tsconfig.json index 66b5e57..bfe39bf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,11 @@ { "path": "./tsconfig.app.json" } - ] + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } }