@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.
Files changed (30) hide show
  1. package/README.md +317 -0
  2. package/keycloak-theme/components/ui/alert.tsx +69 -61
  3. package/keycloak-theme/components/ui/button.tsx +44 -38
  4. package/keycloak-theme/components/ui/card.tsx +60 -45
  5. package/keycloak-theme/components/ui/checkbox.tsx +24 -22
  6. package/keycloak-theme/components/ui/dropdown-menu.tsx +231 -176
  7. package/keycloak-theme/components/ui/field.tsx +51 -48
  8. package/keycloak-theme/components/ui/input-otp.tsx +56 -49
  9. package/keycloak-theme/components/ui/input.tsx +18 -21
  10. package/keycloak-theme/components/ui/label.tsx +19 -20
  11. package/keycloak-theme/components/ui/radio-group.tsx +27 -25
  12. package/keycloak-theme/components/ui/select.tsx +160 -121
  13. package/keycloak-theme/components/ui/separator.tsx +23 -23
  14. package/keycloak-theme/components/ui/tooltip.tsx +54 -24
  15. package/keycloak-theme/login/KcPage.tsx +0 -1
  16. package/keycloak-theme/login/components/Template/Template.tsx +2 -2
  17. package/keycloak-theme/login/components/Template/useInitializeTemplate.ts +3 -19
  18. package/keycloak-theme/login/index.css +3 -20
  19. package/keycloak-theme/login/pages/login/Form.tsx +49 -51
  20. package/keycloak-theme/login/pages/login/SocialProviders.tsx +9 -4
  21. package/keycloak-theme/login/pages/login/providers/github.svg +4 -3
  22. package/keycloak-theme/login/pages/login/providers/x.svg +4 -3
  23. package/keycloak-theme/login/pages/login/useProviderLogos.tsx +2 -3
  24. package/keycloak-theme/login/styleLevelCustomization.tsx +1 -0
  25. package/keycloak-theme/public/keycloak-theme/login/js/authChecker.js +95 -0
  26. package/keycloak-theme/public/keycloak-theme/login/js/passkeysConditionalAuth.js +86 -0
  27. package/keycloak-theme/public/keycloak-theme/login/js/rfc4648.js +185 -0
  28. package/keycloak-theme/public/keycloak-theme/login/js/webauthnAuthenticate.js +113 -0
  29. package/keycloak-theme/public/keycloak-theme/login/js/webauthnRegister.js +153 -0
  30. 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 p-4 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7",
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
- error: "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/50 dark:text-red-300 [&>svg]:text-red-800 dark:[&>svg]:text-red-300",
15
- warning:
16
- "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/50 dark:text-amber-300 [&>svg]:text-amber-800 dark:[&>svg]:text-amber-300",
17
- success:
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
- const Alert = React.forwardRef<
28
- HTMLDivElement,
29
- React.HTMLAttributes<HTMLDivElement> &
30
- VariantProps<typeof alertVariants> & { showIcon?: boolean }
31
- >(({ className, showIcon = true, variant, ...props }, ref) => (
32
- <div
33
- ref={ref}
34
- role="alert"
35
- className={cn(alertVariants({ variant }), className)}
36
- {...props}
37
- >
38
- <div className="flex items-start gap-3">
39
- {showIcon && (
40
- <>
41
- {variant === "info" && <Info className="h-5 w-5 shrink-0" />}
42
- {variant === "error" && <XCircle className="h-5 w-5 shrink-0" />}
43
- {variant === "warning" && (
44
- <AlertTriangle className="h-5 w-5 shrink-0" />
45
- )}
46
- {variant === "success" && (
47
- <MdCheckCircle className="h-5 w-5 shrink-0" />
48
- )}
49
- </>
50
- )}
51
- {props.children}
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
- </div>
54
- ));
55
- Alert.displayName = "Alert";
57
+ );
58
+ }
56
59
 
57
- const AlertTitle = React.forwardRef<
58
- HTMLParagraphElement,
59
- React.HTMLAttributes<HTMLHeadingElement>
60
- >(({ className, ...props }, ref) => (
61
- <h5
62
- ref={ref}
63
- className={cn("mb-1 font-medium leading-none tracking-tight", className)}
64
- {...props}
65
- />
66
- ));
67
- AlertTitle.displayName = "AlertTitle";
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
- const AlertDescription = React.forwardRef<
70
- HTMLParagraphElement,
71
- React.HTMLAttributes<HTMLParagraphElement>
72
- >(({ className, ...props }, ref) => (
73
- <div
74
- ref={ref}
75
- className={cn("text-sm [&_p]:leading-relaxed", className)}
76
- {...props}
77
- />
78
- ));
79
- AlertDescription.displayName = "AlertDescription";
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-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
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 shadow hover:bg-primary/90",
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
13
  destructive:
14
- "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
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 border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
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 shadow-sm hover:bg-secondary/80",
19
- ghost: "hover:bg-accent hover:text-accent-foreground",
20
- link: "text-primary underline-offset-4 hover:underline"
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 text-xs",
25
- lg: "h-10 rounded-md px-8",
26
- icon: "h-9 w-9"
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
- export interface ButtonProps
37
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
- VariantProps<typeof buttonVariants> {
39
- asChild?: boolean;
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
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
- ({ className, variant, size, asChild = false, ...props }, ref) => {
44
- const Comp = asChild ? Slot : "button";
45
- return (
46
- <Comp
47
- className={cn(buttonVariants({ variant, size, className }))}
48
- ref={ref}
49
- {...props}
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 }