@oussemasahbeni/keycloakify-login-shadcn 250004.0.8 → 250004.0.10
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.
- package/README.md +317 -0
- package/keycloak-theme/components/ui/alert.tsx +69 -61
- package/keycloak-theme/components/ui/button.tsx +44 -38
- package/keycloak-theme/components/ui/card.tsx +60 -45
- package/keycloak-theme/components/ui/checkbox.tsx +24 -22
- package/keycloak-theme/components/ui/dropdown-menu.tsx +231 -176
- package/keycloak-theme/components/ui/field.tsx +51 -48
- package/keycloak-theme/components/ui/input-otp.tsx +56 -49
- package/keycloak-theme/components/ui/input.tsx +18 -21
- package/keycloak-theme/components/ui/label.tsx +19 -20
- package/keycloak-theme/components/ui/radio-group.tsx +27 -25
- package/keycloak-theme/components/ui/select.tsx +160 -121
- package/keycloak-theme/components/ui/separator.tsx +23 -23
- package/keycloak-theme/components/ui/tooltip.tsx +54 -24
- package/keycloak-theme/login/KcPage.tsx +0 -1
- package/keycloak-theme/login/components/Template/Template.tsx +2 -2
- package/keycloak-theme/login/components/Template/useInitializeTemplate.ts +3 -19
- package/keycloak-theme/login/index.css +3 -20
- package/keycloak-theme/login/pages/login/Form.tsx +49 -51
- package/keycloak-theme/login/pages/login/SocialProviders.tsx +9 -4
- package/keycloak-theme/login/pages/login/providers/github.svg +4 -3
- package/keycloak-theme/login/pages/login/providers/x.svg +4 -3
- package/keycloak-theme/login/pages/login/useProviderLogos.tsx +2 -3
- package/keycloak-theme/login/styleLevelCustomization.tsx +1 -0
- package/keycloak-theme/public/keycloak-theme/login/js/authChecker.js +95 -0
- package/keycloak-theme/public/keycloak-theme/login/js/passkeysConditionalAuth.js +86 -0
- package/keycloak-theme/public/keycloak-theme/login/js/rfc4648.js +185 -0
- package/keycloak-theme/public/keycloak-theme/login/js/webauthnAuthenticate.js +113 -0
- package/keycloak-theme/public/keycloak-theme/login/js/webauthnRegister.js +153 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Keycloakify Shadcn Starter
|
|
2
|
+
|
|
3
|
+
A modern, production-ready Keycloak login theme built with React, TypeScript, Tailwind CSS v4, shadcn/ui, and Keycloakify v11.
|
|
4
|
+
|
|
5
|
+
**npm Package:** [@oussemasahbeni/keycloakify-login-shadcn](https://www.npmjs.com/package/@oussemasahbeni/keycloakify-login-shadcn)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- 🎨 **Modern UI** - Beautiful, responsive design using Tailwind CSS v4 and shadcn/ui components
|
|
12
|
+
- 🌙 **Dark Mode** - Built-in dark/light/system theme toggle with persistent preferences
|
|
13
|
+
- 🌍 **Multi-language Support** - i18n ready with English, French, and Arabic translations (RTL supported)
|
|
14
|
+
- 📧 **Custom Email Templates** - Styled email templates using jsx-email for all Keycloak events
|
|
15
|
+
- 🔐 **Complete Login Flow** - All 35+ Keycloak login pages fully customized
|
|
16
|
+
- 🎭 **Social Login Providers** - Pre-styled icons for 16+ OAuth providers (Google, GitHub, Microsoft, etc.)
|
|
17
|
+
- 📖 **Storybook Integration** - Visual testing and documentation for all components
|
|
18
|
+
- ⚡ **Vite Powered** - Fast development with HMR and optimized builds
|
|
19
|
+
- 🔧 **Type-Safe** - Full TypeScript support throughout the codebase
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🚀 Quick Start with npm
|
|
24
|
+
|
|
25
|
+
Get started quickly by using the published npm package in your own project.
|
|
26
|
+
|
|
27
|
+
### Step 1: Create a new Vite + React + TypeScript project
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm create vite
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
When prompted:
|
|
34
|
+
|
|
35
|
+
- **Project name:** `keycloak-theme` (or your preferred name)
|
|
36
|
+
- **Select a framework:** Choose **React**
|
|
37
|
+
- **Select a variant:** Choose **TypeScript**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd keycloak-theme
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Step 2: Install dependencies
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add keycloakify @oussemasahbeni/keycloakify-login-shadcn
|
|
47
|
+
pnpm install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Step 3: Initialize Keycloakify
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx keycloakify init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
When prompted:
|
|
57
|
+
|
|
58
|
+
- **Which theme type would you like to initialize?** Select **(x) login**
|
|
59
|
+
- **Do you want to install the Stories?** Select **(x) Yes (Recommended)**
|
|
60
|
+
|
|
61
|
+
### Step 4: Configure Vite
|
|
62
|
+
|
|
63
|
+
Update your `vite.config.ts` to include Tailwind CSS, path aliases, and the Keycloakify plugin:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import react from "@vitejs/plugin-react";
|
|
67
|
+
import { keycloakify } from "keycloakify/vite-plugin";
|
|
68
|
+
import { defineConfig } from "vite";
|
|
69
|
+
import path from "node:path";
|
|
70
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
71
|
+
|
|
72
|
+
// https://vite.dev/config/
|
|
73
|
+
export default defineConfig({
|
|
74
|
+
plugins: [
|
|
75
|
+
react(),
|
|
76
|
+
tailwindcss(),
|
|
77
|
+
keycloakify({
|
|
78
|
+
accountThemeImplementation: "none"
|
|
79
|
+
})
|
|
80
|
+
],
|
|
81
|
+
resolve: {
|
|
82
|
+
alias: {
|
|
83
|
+
"@": path.resolve(__dirname, "./src")
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Step 5: Configure TypeScript paths
|
|
90
|
+
|
|
91
|
+
Add the path alias to your `tsconfig.app.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"compilerOptions": {
|
|
96
|
+
"paths": {
|
|
97
|
+
"@/*": ["./src/*"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 6: Run Storybook and build
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Run Storybook for component development and testing
|
|
107
|
+
pnpm storybook
|
|
108
|
+
|
|
109
|
+
# Build the Keycloak theme JAR file
|
|
110
|
+
pnpm build-keycloak-theme
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
That's it! You now have a fully functional Keycloak login theme using the published package.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 🛠️ Development (for contributors)
|
|
118
|
+
|
|
119
|
+
If you want to clone this repository and develop/customize the theme locally:
|
|
120
|
+
|
|
121
|
+
### Prerequisites
|
|
122
|
+
|
|
123
|
+
- Node.js 18+
|
|
124
|
+
- pnpm (or npm/yarn)
|
|
125
|
+
- [Maven](https://maven.apache.org/) (for building the theme JAR)
|
|
126
|
+
|
|
127
|
+
### Clone and Install
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Clone the repository
|
|
131
|
+
git clone https://github.com/Oussemasahbeni/keycloakify-shadcn-starter.git
|
|
132
|
+
cd keycloakify-shadcn-starter
|
|
133
|
+
|
|
134
|
+
# Install dependencies
|
|
135
|
+
pnpm install
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Development Commands
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Start development server with hot reload
|
|
142
|
+
pnpm dev
|
|
143
|
+
|
|
144
|
+
# Run Storybook for component development
|
|
145
|
+
pnpm storybook
|
|
146
|
+
|
|
147
|
+
# Preview email templates
|
|
148
|
+
pnpm emails:preview
|
|
149
|
+
|
|
150
|
+
# Build the Keycloak theme JAR
|
|
151
|
+
pnpm build-keycloak-theme
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🖼️ Supported Pages
|
|
157
|
+
|
|
158
|
+
This theme includes custom implementations for all Keycloak login pages:
|
|
159
|
+
|
|
160
|
+
| Authentication | Account Management | Security |
|
|
161
|
+
| ------------------- | ------------------- | --------------------- |
|
|
162
|
+
| Login | Register | WebAuthn Authenticate |
|
|
163
|
+
| Login with Username | Update Profile | WebAuthn Register |
|
|
164
|
+
| Login with Password | Update Email | Configure TOTP |
|
|
165
|
+
| Login OTP | Delete Account | Recovery Codes |
|
|
166
|
+
| Login with Passkeys | Logout Confirm | Reset OTP |
|
|
167
|
+
| OAuth Grant | Terms & Conditions | X509 Info |
|
|
168
|
+
| Device Verification | Select Organization | Delete Credential |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### Branding
|
|
173
|
+
|
|
174
|
+
1. **Logo**: Replace `src/login/assets/img/auth-logo.svg` with your company logo
|
|
175
|
+
2. **Colors**: Modify CSS variables in `src/login/index.css`
|
|
176
|
+
3. **Fonts**: Update font imports in `src/login/assets/fonts/`
|
|
177
|
+
|
|
178
|
+
### Internationalization
|
|
179
|
+
|
|
180
|
+
Add or modify translations in `src/login/i18n.ts`:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
.withCustomTranslations({
|
|
184
|
+
en: {
|
|
185
|
+
welcomeMessage: "Welcome to Your App",
|
|
186
|
+
loginAccountTitle: "Login to your account",
|
|
187
|
+
// ... more translations
|
|
188
|
+
},
|
|
189
|
+
fr: { /* French translations */ },
|
|
190
|
+
ar: { /* Arabic translations */ }
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### UI Components
|
|
195
|
+
|
|
196
|
+
The theme uses shadcn/ui components located in `src/components/ui/`:
|
|
197
|
+
|
|
198
|
+
- `alert.tsx` - Alert messages
|
|
199
|
+
- `button.tsx` - Buttons with variants
|
|
200
|
+
- `card.tsx` - Card containers
|
|
201
|
+
- `checkbox.tsx` - Checkbox inputs
|
|
202
|
+
- `input.tsx` - Text inputs
|
|
203
|
+
- `label.tsx` - Form labels
|
|
204
|
+
- `dropdown-menu.tsx` - Dropdown menus
|
|
205
|
+
- `radio-group.tsx` - Radio button groups
|
|
206
|
+
- `tooltip.tsx` - Tooltips
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 📧 Email Templates
|
|
211
|
+
|
|
212
|
+
Custom email templates are built with [jsx-email](https://jsx.email/) and support multiple languages.
|
|
213
|
+
|
|
214
|
+
### Available Templates
|
|
215
|
+
|
|
216
|
+
| Template | Description |
|
|
217
|
+
| ---------------------------- | ------------------------------- |
|
|
218
|
+
| `email-verification.tsx` | Email verification |
|
|
219
|
+
| `password-reset.tsx` | Password reset link |
|
|
220
|
+
| `executeActions.tsx` | Required actions |
|
|
221
|
+
| `identity-provider-link.tsx` | IDP linking |
|
|
222
|
+
| `org-invite.tsx` | Organization invitation |
|
|
223
|
+
| `event-login_error.tsx` | Login error notification |
|
|
224
|
+
| `event-update_password.tsx` | Password change notification |
|
|
225
|
+
| `event-update_totp.tsx` | TOTP configuration notification |
|
|
226
|
+
| And more... | |
|
|
227
|
+
|
|
228
|
+
### Preview Emails Locally
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pnpm emails:preview
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Email Locales
|
|
235
|
+
|
|
236
|
+
Translations are in `src/email/locales/{locale}/translation.json`:
|
|
237
|
+
|
|
238
|
+
- `en/` - English
|
|
239
|
+
- `fr/` - French
|
|
240
|
+
- `ar/` - Arabic
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 🔨 Building for Production
|
|
245
|
+
|
|
246
|
+
### Install Maven
|
|
247
|
+
|
|
248
|
+
Required for building the Keycloak theme JAR file.
|
|
249
|
+
|
|
250
|
+
- **macOS**: `brew install maven`
|
|
251
|
+
- **Ubuntu/Debian**: `sudo apt-get install maven`
|
|
252
|
+
- **Windows**: `choco install openjdk && choco install maven`
|
|
253
|
+
|
|
254
|
+
### Build the Theme
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pnpm build-keycloak-theme
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The built theme will be output as a `.jar` file in the `dist_keycloak/` directory.
|
|
261
|
+
|
|
262
|
+
### Deploy to Keycloak
|
|
263
|
+
|
|
264
|
+
1. Copy the `.jar` file to your Keycloak's `providers/` directory
|
|
265
|
+
2. Restart Keycloak
|
|
266
|
+
3. Go to Keycloak Admin Console → **Realm Settings** → **Themes**
|
|
267
|
+
4. Select your custom theme from the dropdown
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 🧪 Testing
|
|
272
|
+
|
|
273
|
+
### Storybook
|
|
274
|
+
|
|
275
|
+
Run Storybook for visual testing and component documentation:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
pnpm storybook
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Local Keycloak Testing
|
|
282
|
+
|
|
283
|
+
For local testing with a Keycloak instance, see the [Keycloakify documentation](https://docs.keycloakify.dev/testing-your-theme).
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 🤝 Contributing
|
|
288
|
+
|
|
289
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
290
|
+
|
|
291
|
+
1. Fork the repository
|
|
292
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
293
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
294
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
295
|
+
5. Open a Pull Request
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 📄 License
|
|
300
|
+
|
|
301
|
+
This project is licensed under the MIT License.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 🙏 Acknowledgments
|
|
306
|
+
|
|
307
|
+
- [Keycloakify](https://keycloakify.dev) - For making Keycloak theming with React possible
|
|
308
|
+
- [shadcn/ui](https://ui.shadcn.com) - For the beautiful UI components
|
|
309
|
+
- [Tailwind CSS](https://tailwindcss.com) - For the utility-first CSS framework
|
|
310
|
+
- [jsx-email](https://jsx.email) - For React email templates
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 📦 Package Information
|
|
315
|
+
|
|
316
|
+
**npm:** [@oussemasahbeni/keycloakify-login-shadcn](https://www.npmjs.com/package/@oussemasahbeni/keycloakify-login-shadcn)
|
|
317
|
+
**GitHub:** [Oussemasahbeni/keycloakify-shadcn-starter](https://github.com/Oussemasahbeni/keycloakify-shadcn-starter)
|
|
@@ -1,81 +1,89 @@
|
|
|
1
|
+
import { cn } from "@/components/lib/utils";
|
|
1
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
3
|
import { AlertTriangle, Info, XCircle } from "lucide-react";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { MdCheckCircle } from "react-icons/md";
|
|
5
6
|
|
|
6
|
-
import { cn } from "@/components/lib/utils";
|
|
7
|
-
|
|
8
7
|
const alertVariants = cva(
|
|
9
|
-
"relative w-full rounded-lg border
|
|
8
|
+
"relative w-full rounded-lg border px-5 py-4 text-sm flex items-center gap-4 transition-all",
|
|
10
9
|
{
|
|
11
10
|
variants: {
|
|
12
11
|
variant: {
|
|
13
12
|
info: "bg-card text-card-foreground",
|
|
14
|
-
|
|
15
|
-
warning:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/50 dark:text-green-300 [&>svg]:text-green-800 dark:[&>svg]:text-green-300"
|
|
19
|
-
}
|
|
13
|
+
success: "bg-emerald-50/50 border-emerald-200 text-emerald-900 dark:bg-emerald-500/5 dark:border-emerald-500/20 dark:text-emerald-400",
|
|
14
|
+
warning: "bg-amber-50/50 border-amber-200 text-amber-900 dark:bg-amber-500/5 dark:border-amber-500/20 dark:text-amber-400",
|
|
15
|
+
error: "bg-destructive/5 border-destructive/20 text-destructive",
|
|
16
|
+
},
|
|
20
17
|
},
|
|
21
18
|
defaultVariants: {
|
|
22
|
-
variant: "info"
|
|
23
|
-
}
|
|
19
|
+
variant: "info",
|
|
20
|
+
},
|
|
24
21
|
}
|
|
25
22
|
);
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
24
|
+
interface AlertProps
|
|
25
|
+
extends React.ComponentProps<"div">,
|
|
26
|
+
VariantProps<typeof alertVariants> {
|
|
27
|
+
showIcon?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Alert({
|
|
31
|
+
className,
|
|
32
|
+
variant = "info",
|
|
33
|
+
showIcon = true,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: AlertProps) {
|
|
37
|
+
const Icon = {
|
|
38
|
+
info: Info,
|
|
39
|
+
error: XCircle,
|
|
40
|
+
warning: AlertTriangle,
|
|
41
|
+
success: MdCheckCircle,
|
|
42
|
+
}[variant as string] || Info;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
data-slot="alert"
|
|
47
|
+
role="alert"
|
|
48
|
+
className={cn(alertVariants({ variant }), className)}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
{showIcon && <Icon className="size-5 shrink-0" data-slot="alert-icon" />}
|
|
52
|
+
{/* We use a div wrapper for children to ensure Title and Description stack vertically */}
|
|
53
|
+
<div className="flex flex-col gap-0.5 flex-1">
|
|
54
|
+
{children}
|
|
55
|
+
</div>
|
|
52
56
|
</div>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Alert.displayName = "Alert";
|
|
57
|
+
);
|
|
58
|
+
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
data-slot="alert-title"
|
|
64
|
+
className={cn(
|
|
65
|
+
"font-medium leading-none tracking-tight",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
function AlertDescription({
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}: React.ComponentProps<"div">) {
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
data-slot="alert-description"
|
|
80
|
+
className={cn(
|
|
81
|
+
"text-muted-foreground text-sm [&_p]:leading-relaxed",
|
|
82
|
+
className
|
|
83
|
+
)}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
80
88
|
|
|
81
89
|
export { Alert, AlertDescription, AlertTitle };
|
|
@@ -1,56 +1,62 @@
|
|
|
1
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
import * as React from "react"
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from '../lib/utils'
|
|
4
5
|
|
|
5
|
-
import { cn } from "@/components/lib/utils";
|
|
6
6
|
|
|
7
7
|
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-
|
|
8
|
+
"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",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
default: "bg-primary text-primary-foreground
|
|
12
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
13
13
|
destructive:
|
|
14
|
-
"bg-destructive text-destructive-
|
|
14
|
+
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
15
15
|
outline:
|
|
16
|
-
"border
|
|
16
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
17
17
|
secondary:
|
|
18
|
-
"bg-secondary text-secondary-foreground
|
|
19
|
-
ghost:
|
|
20
|
-
|
|
18
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
21
22
|
},
|
|
22
23
|
size: {
|
|
23
|
-
default: "h-9 px-4 py-2",
|
|
24
|
-
sm: "h-8 rounded-md px-3
|
|
25
|
-
lg: "h-10 rounded-md px-
|
|
26
|
-
icon: "
|
|
27
|
-
|
|
24
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
25
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
26
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
27
|
+
icon: "size-9",
|
|
28
|
+
"icon-sm": "size-8",
|
|
29
|
+
"icon-lg": "size-10",
|
|
30
|
+
},
|
|
28
31
|
},
|
|
29
32
|
defaultVariants: {
|
|
30
33
|
variant: "default",
|
|
31
|
-
size: "default"
|
|
32
|
-
}
|
|
34
|
+
size: "default",
|
|
35
|
+
},
|
|
33
36
|
}
|
|
34
|
-
)
|
|
37
|
+
)
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
function Button({
|
|
40
|
+
className,
|
|
41
|
+
variant = "default",
|
|
42
|
+
size = "default",
|
|
43
|
+
asChild = false,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<"button"> &
|
|
46
|
+
VariantProps<typeof buttonVariants> & {
|
|
47
|
+
asChild?: boolean
|
|
48
|
+
}) {
|
|
49
|
+
const Comp = asChild ? Slot : "button"
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
Button.displayName = "Button";
|
|
51
|
+
return (
|
|
52
|
+
<Comp
|
|
53
|
+
data-slot="button"
|
|
54
|
+
data-variant={variant}
|
|
55
|
+
data-size={size}
|
|
56
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
55
61
|
|
|
56
|
-
export { Button, buttonVariants }
|
|
62
|
+
export { Button, buttonVariants }
|