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.
This commit is contained in:
xiaomai
2025-09-17 23:32:29 +08:00
parent 1458b04b17
commit c6f2feed14
8 changed files with 275 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ This is a learning project that combines multiple frameworks to create a demo ap
- **Base framework:** Vue 3 + Vite 7 - **Base framework:** Vue 3 + Vite 7
- **CSS framework:** Tailwind CSS 4 - **CSS framework:** Tailwind CSS 4
- **UI framework:** Shadcn UI
- **Package manager:** pnpm - **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 ```vue
<script setup lang="ts"></script> <script setup lang="ts"></script>
@@ -144,3 +145,63 @@ Now you can use Tailwind CSS atomic classes in your project.
</div> </div>
</template> </template>
``` ```
### 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.

22
components.json Normal file
View File

@@ -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": {}
}

View File

@@ -17,7 +17,11 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.13", "@tailwindcss/vite": "^4.1.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.544.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
"vue": "^3.5.18", "vue": "^3.5.18",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
@@ -35,6 +39,7 @@
"jiti": "^2.4.2", "jiti": "^2.4.2",
"npm-run-all2": "^8.0.4", "npm-run-all2": "^8.0.4",
"prettier": "3.6.2", "prettier": "3.6.2",
"tw-animate-css": "^1.3.8",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"vite": "^7.0.6", "vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0", "vite-plugin-vue-devtools": "^8.0.0",

53
pnpm-lock.yaml generated
View File

@@ -11,9 +11,21 @@ importers:
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.13 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)) 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: pinia:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3(typescript@5.8.3)(vue@3.5.21(typescript@5.8.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: tailwindcss:
specifier: ^4.1.13 specifier: ^4.1.13
version: 4.1.13 version: 4.1.13
@@ -60,6 +72,9 @@ importers:
prettier: prettier:
specifier: 3.6.2 specifier: 3.6.2
version: 3.6.2 version: 3.6.2
tw-animate-css:
specifier: ^1.3.8
version: 1.3.8
typescript: typescript:
specifier: ~5.8.0 specifier: ~5.8.0
version: 5.8.3 version: 5.8.3
@@ -951,6 +966,13 @@ packages:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'} 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: color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
@@ -1398,6 +1420,11 @@ packages:
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 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: magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
@@ -1586,6 +1613,10 @@ packages:
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 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: read-package-json-fast@4.0.0:
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
engines: {node: ^18.17.0 || >=20.5.0} engines: {node: ^18.17.0 || >=20.5.0}
@@ -1670,6 +1701,9 @@ packages:
resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
engines: {node: ^14.18.0 || >=16.0.0} 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: tailwindcss@4.1.13:
resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==}
@@ -1699,6 +1733,9 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.8.4' typescript: '>=4.8.4'
tw-animate-css@1.3.8:
resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==}
type-check@0.4.0: type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -2757,6 +2794,12 @@ snapshots:
chownr@3.0.0: {} chownr@3.0.0: {}
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
clsx@2.1.1: {}
color-convert@2.0.1: color-convert@2.0.1:
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
@@ -3163,6 +3206,10 @@ snapshots:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
lucide-react@0.544.0(react@19.1.1):
dependencies:
react: 19.1.1
magic-string@0.30.19: magic-string@0.30.19:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
@@ -3318,6 +3365,8 @@ snapshots:
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
react@19.1.1: {}
read-package-json-fast@4.0.0: read-package-json-fast@4.0.0:
dependencies: dependencies:
json-parse-even-better-errors: 4.0.0 json-parse-even-better-errors: 4.0.0
@@ -3402,6 +3451,8 @@ snapshots:
dependencies: dependencies:
'@pkgr/core': 0.2.9 '@pkgr/core': 0.2.9
tailwind-merge@3.3.1: {}
tailwindcss@4.1.13: {} tailwindcss@4.1.13: {}
tapable@2.2.3: {} tapable@2.2.3: {}
@@ -3430,6 +3481,8 @@ snapshots:
dependencies: dependencies:
typescript: 5.8.3 typescript: 5.8.3
tw-animate-css@1.3.8: {}
type-check@0.4.0: type-check@0.4.0:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -1,4 +1,7 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
/* 定义主题变量 */ /* 定义主题变量 */
@theme { @theme {
@@ -42,3 +45,119 @@
@apply bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent; @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;
}
}

View File

@@ -5,6 +5,7 @@
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }

View File

@@ -7,5 +7,11 @@
{ {
"path": "./tsconfig.app.json" "path": "./tsconfig.app.json"
} }
] ],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
} }