create-vite-redux 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +98 -0
  2. package/index.js +169 -0
  3. package/package.json +30 -0
  4. package/templates/react-redux-tw/.env.example +1 -0
  5. package/templates/react-redux-tw/.prettierignore +2 -0
  6. package/templates/react-redux-tw/.prettierrc +8 -0
  7. package/templates/react-redux-tw/README.md +81 -0
  8. package/templates/react-redux-tw/_gitignore +31 -0
  9. package/templates/react-redux-tw/_package.json +49 -0
  10. package/templates/react-redux-tw/eslint.config.js +25 -0
  11. package/templates/react-redux-tw/index.html +13 -0
  12. package/templates/react-redux-tw/src/App.tsx +15 -0
  13. package/templates/react-redux-tw/src/components/layout/Footer.tsx +11 -0
  14. package/templates/react-redux-tw/src/components/layout/Header.tsx +16 -0
  15. package/templates/react-redux-tw/src/components/ui/button.tsx +54 -0
  16. package/templates/react-redux-tw/src/components/ui/card.tsx +73 -0
  17. package/templates/react-redux-tw/src/components/ui/dialog.tsx +118 -0
  18. package/templates/react-redux-tw/src/components/ui/form.tsx +138 -0
  19. package/templates/react-redux-tw/src/components/ui/input.tsx +18 -0
  20. package/templates/react-redux-tw/src/components/ui/label.tsx +18 -0
  21. package/templates/react-redux-tw/src/components/ui/sonner.tsx +19 -0
  22. package/templates/react-redux-tw/src/features/auth/LoginForm.tsx +92 -0
  23. package/templates/react-redux-tw/src/features/auth/authApi.ts +40 -0
  24. package/templates/react-redux-tw/src/features/auth/authSchema.ts +19 -0
  25. package/templates/react-redux-tw/src/features/auth/authSlice.ts +31 -0
  26. package/templates/react-redux-tw/src/features/counter/Counter.tsx +34 -0
  27. package/templates/react-redux-tw/src/features/counter/counterSlice.ts +32 -0
  28. package/templates/react-redux-tw/src/hooks/redux.ts +5 -0
  29. package/templates/react-redux-tw/src/index.css +123 -0
  30. package/templates/react-redux-tw/src/lib/utils.ts +6 -0
  31. package/templates/react-redux-tw/src/main.tsx +17 -0
  32. package/templates/react-redux-tw/src/router/index.tsx +26 -0
  33. package/templates/react-redux-tw/src/services/api.ts +10 -0
  34. package/templates/react-redux-tw/src/store/index.ts +16 -0
  35. package/templates/react-redux-tw/tsconfig.app.json +32 -0
  36. package/templates/react-redux-tw/tsconfig.json +7 -0
  37. package/templates/react-redux-tw/tsconfig.node.json +24 -0
  38. package/templates/react-redux-tw/vite.config.ts +17 -0
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # create-vite-redux
2
+
3
+ > Scaffold a production-ready React app in seconds.
4
+
5
+ ```bash
6
+ npm create vite-redux my-app
7
+ ```
8
+
9
+ ## What you get
10
+
11
+ | Tool | Role |
12
+ |------|------|
13
+ | React 19 + Vite 6 | UI & bundler |
14
+ | TypeScript 5 | Static types |
15
+ | Redux Toolkit 2 | Global state |
16
+ | RTK Query | Data fetching & caching |
17
+ | React Router v7 | Client-side routing |
18
+ | Tailwind CSS v4 | Utility-first styling |
19
+ | shadcn/ui (new-york) | Pre-built accessible components |
20
+ | Zod 3 | Runtime validation + type inference |
21
+ | React Hook Form 7 | Form state management |
22
+ | ESLint v9 + Prettier | Linting & formatting |
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ # npm
28
+ npm create vite-redux my-app
29
+
30
+ # pnpm
31
+ pnpm create vite-redux my-app
32
+
33
+ # yarn
34
+ yarn create vite-redux my-app
35
+ ```
36
+
37
+ You'll be prompted for one option:
38
+
39
+ ```
40
+ ✔ Include example auth feature? › Yes / No
41
+ ```
42
+
43
+ Everything else — TypeScript, Redux, RTK Query, Tailwind, shadcn, Zod, React Router, ESLint, Prettier — is always included.
44
+
45
+ ## Get started
46
+
47
+ ```bash
48
+ cd my-app
49
+ npm install
50
+ npm run dev
51
+ ```
52
+
53
+ ## Project structure
54
+
55
+ ```
56
+ src/
57
+ ├── components/
58
+ │ ├── ui/ ← pre-baked shadcn components
59
+ │ └── layout/ ← Header, Footer
60
+ ├── features/
61
+ │ ├── counter/ ← Redux slice demo
62
+ │ └── auth/ ← optional (LoginForm + Zod + RTK Query)
63
+ ├── hooks/
64
+ │ └── redux.ts ← useAppDispatch, useAppSelector
65
+ ├── router/
66
+ │ └── index.tsx ← React Router config
67
+ ├── services/
68
+ │ └── api.ts ← RTK Query base API
69
+ ├── store/
70
+ │ └── index.ts ← Redux store
71
+ ├── lib/
72
+ │ └── utils.ts ← cn() utility
73
+ ├── App.tsx
74
+ ├── main.tsx
75
+ └── index.css ← Tailwind v4 + shadcn OKLCH tokens
76
+ ```
77
+
78
+ ## Environment variables
79
+
80
+ Copy `.env.example` to `.env` and set your API URL:
81
+
82
+ ```
83
+ VITE_API_URL=http://localhost:3000/api
84
+ ```
85
+
86
+ ## Scripts
87
+
88
+ | Script | Description |
89
+ |--------|-------------|
90
+ | `npm run dev` | Start dev server |
91
+ | `npm run build` | Production build |
92
+ | `npm run lint` | Run ESLint |
93
+ | `npm run format` | Run Prettier |
94
+ | `npm run preview` | Preview production build |
95
+
96
+ ## License
97
+
98
+ MIT
package/index.js ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs-extra'
4
+ import path from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import prompts from 'prompts'
7
+ import pc from 'picocolors'
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
+
11
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
12
+
13
+ function toValidPackageName(name) {
14
+ return name
15
+ .trim()
16
+ .toLowerCase()
17
+ .replace(/\s+/g, '-')
18
+ .replace(/[^a-z0-9-]/g, '')
19
+ }
20
+
21
+ function replaceInFile(filePath, replacements) {
22
+ if (!fs.existsSync(filePath)) return
23
+ let content = fs.readFileSync(filePath, 'utf-8')
24
+ for (const [from, to] of replacements) {
25
+ content = content.replaceAll(from, to)
26
+ }
27
+ fs.writeFileSync(filePath, content, 'utf-8')
28
+ }
29
+
30
+ // Remove lines that contain a specific string (used to strip auth imports/reducers)
31
+ function removeLinesContaining(filePath, ...patterns) {
32
+ if (!fs.existsSync(filePath)) return
33
+ const lines = fs.readFileSync(filePath, 'utf-8').split('\n')
34
+ const filtered = lines.filter((line) => !patterns.some((p) => line.includes(p)))
35
+ fs.writeFileSync(filePath, filtered.join('\n'), 'utf-8')
36
+ }
37
+
38
+ // ─── Banner ───────────────────────────────────────────────────────────────────
39
+
40
+ console.log()
41
+ console.log(pc.bold(pc.cyan('┌─────────────────────────────────────┐')))
42
+ console.log(pc.bold(pc.cyan('│') + pc.bold(' create-vite-redux v1.0.0 ') + pc.bold(pc.cyan('│'))))
43
+ console.log(pc.bold(pc.cyan('└─────────────────────────────────────┘')))
44
+ console.log()
45
+
46
+ // ─── Get project name from argv or prompt ─────────────────────────────────────
47
+
48
+ let targetDir = process.argv[2]
49
+
50
+ const response = await prompts(
51
+ [
52
+ {
53
+ type: targetDir ? null : 'text',
54
+ name: 'projectName',
55
+ message: 'Project name:',
56
+ initial: 'my-app',
57
+ validate: (v) => (v.trim().length > 0 ? true : 'Project name cannot be empty'),
58
+ },
59
+ {
60
+ type: 'confirm',
61
+ name: 'includeAuth',
62
+ message: 'Include example auth feature? (LoginForm + Zod + RTK Query)',
63
+ initial: true,
64
+ },
65
+ ],
66
+ {
67
+ onCancel() {
68
+ console.log(pc.red('\n✖ Cancelled'))
69
+ process.exit(1)
70
+ },
71
+ }
72
+ )
73
+
74
+ const projectName = targetDir ?? response.projectName
75
+ const packageName = toValidPackageName(projectName)
76
+ const { includeAuth } = response
77
+
78
+ // ─── Resolve paths ────────────────────────────────────────────────────────────
79
+
80
+ const cwd = process.cwd()
81
+ const dest = path.resolve(cwd, projectName)
82
+ const templateDir = path.resolve(__dirname, 'templates', 'react-redux-tw')
83
+
84
+ // ─── Safety check ─────────────────────────────────────────────────────────────
85
+
86
+ if (fs.existsSync(dest)) {
87
+ const { overwrite } = await prompts({
88
+ type: 'confirm',
89
+ name: 'overwrite',
90
+ message: `Target directory ${pc.yellow(projectName)} already exists. Overwrite?`,
91
+ initial: false,
92
+ })
93
+ if (!overwrite) {
94
+ console.log(pc.red('\n✖ Aborted'))
95
+ process.exit(1)
96
+ }
97
+ fs.removeSync(dest)
98
+ }
99
+
100
+ // ─── Copy template ────────────────────────────────────────────────────────────
101
+
102
+ console.log()
103
+ console.log(pc.dim(' Setting up project…'))
104
+
105
+ await fs.copy(templateDir, dest, {
106
+ // preserve dotfiles (.gitignore, .env.example, etc.)
107
+ filter: () => true,
108
+ })
109
+
110
+ // ─── Rename _package.json → package.json ─────────────────────────────────────
111
+
112
+ const pkgSrc = path.join(dest, '_package.json')
113
+ const pkgDest = path.join(dest, 'package.json')
114
+ fs.moveSync(pkgSrc, pkgDest)
115
+
116
+ // ─── Rename _gitignore → .gitignore ──────────────────────────────────────────
117
+ // npm strips .gitignore from tarballs, so it's stored as _gitignore in the template
118
+
119
+ const gitignoreSrc = path.join(dest, '_gitignore')
120
+ const gitignoreDest = path.join(dest, '.gitignore')
121
+ if (fs.existsSync(gitignoreSrc)) {
122
+ fs.moveSync(gitignoreSrc, gitignoreDest)
123
+ }
124
+
125
+ // ─── Inject project name ──────────────────────────────────────────────────────
126
+
127
+ const nameReplacements = [['{{PROJECT_NAME}}', projectName]]
128
+
129
+ replaceInFile(pkgDest, [['{{PROJECT_NAME}}', packageName]])
130
+ replaceInFile(path.join(dest, 'index.html'), nameReplacements)
131
+ replaceInFile(path.join(dest, 'README.md'), nameReplacements)
132
+ replaceInFile(path.join(dest, 'src', 'router', 'index.tsx'), nameReplacements)
133
+ replaceInFile(path.join(dest, 'src', 'components', 'layout', 'Header.tsx'), nameReplacements)
134
+
135
+ // ─── Remove auth feature if not included ──────────────────────────────────────
136
+
137
+ if (!includeAuth) {
138
+ fs.removeSync(path.join(dest, 'src', 'features', 'auth'))
139
+
140
+ // Remove auth import and reducer from store/index.ts
141
+ removeLinesContaining(
142
+ path.join(dest, 'src', 'store', 'index.ts'),
143
+ "import authReducer from '@/features/auth/authSlice'",
144
+ 'auth: authReducer,'
145
+ )
146
+ }
147
+
148
+ // ─── Done ─────────────────────────────────────────────────────────────────────
149
+
150
+ console.log()
151
+ console.log(' ' + pc.green('✔') + pc.bold(' Done! Your project is ready.'))
152
+ console.log()
153
+ console.log(' ' + pc.dim('Included:'))
154
+ console.log(' ' + pc.dim(' ·') + ' React 19 + Vite 6 + TypeScript')
155
+ console.log(' ' + pc.dim(' ·') + ' Redux Toolkit + RTK Query')
156
+ console.log(' ' + pc.dim(' ·') + ' React Router v7')
157
+ console.log(' ' + pc.dim(' ·') + ' Tailwind CSS v4 + shadcn/ui (new-york)')
158
+ console.log(' ' + pc.dim(' ·') + ' Zod + React Hook Form')
159
+ console.log(' ' + pc.dim(' ·') + ' ESLint v9 + Prettier')
160
+ if (includeAuth) {
161
+ console.log(' ' + pc.dim(' ·') + ' Auth feature (LoginForm + authSlice + authApi)')
162
+ }
163
+ console.log()
164
+ console.log(' ' + pc.bold('Get started:'))
165
+ console.log()
166
+ console.log(' ' + pc.cyan(`cd ${projectName}`))
167
+ console.log(' ' + pc.cyan('npm install'))
168
+ console.log(' ' + pc.cyan('npm run dev'))
169
+ console.log()
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "create-vite-redux",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a React 19 + Vite 6 + TypeScript + Redux Toolkit + RTK Query + Tailwind v4 + shadcn/ui + Zod + RHF project",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-vite-redux": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "templates"
12
+ ],
13
+ "keywords": [
14
+ "react",
15
+ "vite",
16
+ "redux",
17
+ "tailwind",
18
+ "shadcn",
19
+ "template",
20
+ "starter",
21
+ "scaffold"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "fs-extra": "^11",
27
+ "picocolors": "^1",
28
+ "prompts": "^2"
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ VITE_API_URL=http://localhost:3000/api
@@ -0,0 +1,2 @@
1
+ dist/
2
+ node_modules/
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": false,
3
+ "singleQuote": true,
4
+ "tabWidth": 2,
5
+ "trailingComma": "es5",
6
+ "printWidth": 100,
7
+ "plugins": ["prettier-plugin-tailwindcss"]
8
+ }
@@ -0,0 +1,81 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A React 19 app scaffolded with [create-vite-redux](https://www.npmjs.com/package/create-vite-redux).
4
+
5
+ ## Stack
6
+
7
+ | Tool | Role |
8
+ |------|------|
9
+ | React 19 + Vite 6 | UI & bundler |
10
+ | TypeScript 5 | Static types |
11
+ | Redux Toolkit 2 + RTK Query | State & data fetching |
12
+ | React Router v7 | Routing |
13
+ | Tailwind CSS v4 + shadcn/ui | Styling & components |
14
+ | Zod + React Hook Form | Validation & forms |
15
+
16
+ ## Getting started
17
+
18
+ ```bash
19
+ npm install
20
+ npm run dev
21
+ ```
22
+
23
+ ## Project structure
24
+
25
+ ```
26
+ src/
27
+ ├── components/
28
+ │ ├── ui/ ← shadcn/ui components (button, card, form, dialog, sonner…)
29
+ │ └── layout/ ← Header, Footer
30
+ ├── features/
31
+ │ ├── counter/ ← Redux slice demo — start here
32
+ │ └── auth/ ← Auth feature (if included at scaffold time)
33
+ ├── hooks/
34
+ │ └── redux.ts ← useAppDispatch, useAppSelector
35
+ ├── router/
36
+ │ └── index.tsx ← Add new routes here
37
+ ├── services/
38
+ │ └── api.ts ← RTK Query base API — inject endpoints into this
39
+ ├── store/
40
+ │ └── index.ts ← Redux store
41
+ ├── lib/
42
+ │ └── utils.ts ← cn() helper
43
+ ├── App.tsx ← Root layout (Header + Footer + <Outlet />)
44
+ ├── main.tsx ← Entry point
45
+ └── index.css ← Tailwind v4 + OKLCH design tokens
46
+ ```
47
+
48
+ ## Environment variables
49
+
50
+ ```bash
51
+ cp .env.example .env
52
+ ```
53
+
54
+ | Variable | Description |
55
+ |----------|-------------|
56
+ | `VITE_API_URL` | Base URL for RTK Query (`/api` by default) |
57
+
58
+ ## Adding a new feature
59
+
60
+ 1. Create `src/features/my-feature/mySlice.ts` — define your slice
61
+ 2. Register the reducer in `src/store/index.ts`
62
+ 3. Create `src/features/my-feature/myApi.ts` — inject RTK Query endpoints into `api`
63
+ 4. Add a route in `src/router/index.tsx`
64
+
65
+ ## Adding shadcn components
66
+
67
+ Although components are pre-baked, you can add more via the shadcn CLI:
68
+
69
+ ```bash
70
+ npx shadcn@latest add <component>
71
+ ```
72
+
73
+ ## Scripts
74
+
75
+ | Script | Description |
76
+ |--------|-------------|
77
+ | `npm run dev` | Start dev server |
78
+ | `npm run build` | Production build |
79
+ | `npm run lint` | Run ESLint |
80
+ | `npm run format` | Run Prettier |
81
+ | `npm run preview` | Preview production build |
@@ -0,0 +1,31 @@
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
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+
26
+ # Environment files
27
+ .env
28
+ .env.local
29
+ .env.development.local
30
+ .env.test.local
31
+ .env.production.local
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "format": "prettier --write .",
11
+ "preview": "vite preview"
12
+ },
13
+ "dependencies": {
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0",
16
+ "react-router-dom": "^7.0.0",
17
+ "@reduxjs/toolkit": "^2.0.0",
18
+ "react-redux": "^9.0.0",
19
+ "react-hook-form": "^7.0.0",
20
+ "@hookform/resolvers": "^3.0.0",
21
+ "zod": "^3.0.0",
22
+ "clsx": "^2.0.0",
23
+ "tailwind-merge": "^2.0.0",
24
+ "class-variance-authority": "^0.7.0",
25
+ "lucide-react": "latest",
26
+ "sonner": "^1.0.0",
27
+ "@radix-ui/react-label": "^2.0.0",
28
+ "@radix-ui/react-slot": "^1.0.0",
29
+ "@radix-ui/react-dialog": "^1.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "vite": "^6.0.0",
33
+ "@vitejs/plugin-react": "^4.0.0",
34
+ "@tailwindcss/vite": "^4.0.0",
35
+ "tailwindcss": "^4.0.0",
36
+ "tw-animate-css": "^1.0.0",
37
+ "typescript": "^5.0.0",
38
+ "@types/react": "^19.0.0",
39
+ "@types/react-dom": "^19.0.0",
40
+ "eslint": "^9.0.0",
41
+ "@eslint/js": "^9.0.0",
42
+ "globals": "^15.0.0",
43
+ "typescript-eslint": "^8.0.0",
44
+ "eslint-plugin-react-hooks": "^5.0.0",
45
+ "eslint-plugin-react-refresh": "^0.4.0",
46
+ "prettier": "^3.0.0",
47
+ "prettier-plugin-tailwindcss": "^0.6.0"
48
+ }
49
+ }
@@ -0,0 +1,25 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+
7
+ export default tseslint.config(
8
+ { ignores: ['dist'] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ['**/*.{ts,tsx}'],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ 'react-hooks': reactHooks,
18
+ 'react-refresh': reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
23
+ },
24
+ }
25
+ )
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{PROJECT_NAME}}</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,15 @@
1
+ import { Outlet } from 'react-router-dom'
2
+ import { Header } from '@/components/layout/Header'
3
+ import { Footer } from '@/components/layout/Footer'
4
+
5
+ export default function App() {
6
+ return (
7
+ <div className="min-h-screen flex flex-col">
8
+ <Header />
9
+ <main className="flex-1 flex flex-col items-center justify-center gap-8 p-8">
10
+ <Outlet />
11
+ </main>
12
+ <Footer />
13
+ </div>
14
+ )
15
+ }
@@ -0,0 +1,11 @@
1
+ export function Footer() {
2
+ return (
3
+ <footer className="border-t border-border mt-auto">
4
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-center">
5
+ <p className="text-sm text-muted-foreground">
6
+ Built with create-my-stack
7
+ </p>
8
+ </div>
9
+ </footer>
10
+ )
11
+ }
@@ -0,0 +1,16 @@
1
+ import { Link } from 'react-router-dom'
2
+
3
+ export function Header() {
4
+ return (
5
+ <header className="border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50">
6
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between">
7
+ <Link to="/" className="font-semibold text-foreground hover:text-foreground/80 transition-colors">
8
+ {{PROJECT_NAME}}
9
+ </Link>
10
+ <nav className="flex items-center gap-4 text-sm text-muted-foreground">
11
+ <Link to="/" className="hover:text-foreground transition-colors">Home</Link>
12
+ </nav>
13
+ </div>
14
+ </header>
15
+ )
16
+ }
@@ -0,0 +1,54 @@
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 { cn } from '@/lib/utils'
5
+
6
+ const buttonVariants = cva(
7
+ '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 aria-invalid:border-destructive cursor-pointer',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
12
+ destructive:
13
+ 'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20',
14
+ outline:
15
+ 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
16
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs 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 has-[>svg]:px-3',
22
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
23
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
24
+ icon: 'size-9',
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: 'default',
29
+ size: 'default',
30
+ },
31
+ }
32
+ )
33
+
34
+ function Button({
35
+ className,
36
+ variant,
37
+ size,
38
+ asChild = false,
39
+ ...props
40
+ }: React.ComponentProps<'button'> &
41
+ VariantProps<typeof buttonVariants> & {
42
+ asChild?: boolean
43
+ }) {
44
+ const Comp = asChild ? Slot : 'button'
45
+ return (
46
+ <Comp
47
+ data-slot="button"
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ export { Button, buttonVariants }