@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
@@ -1,9 +1,9 @@
1
- import { cva, type VariantProps } from "class-variance-authority";
2
- import { useMemo } from "react";
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import { useMemo } from "react"
3
3
 
4
- import { cn } from "@/components/lib/utils";
5
- import { Label } from "@/components/ui/label";
6
- import { Separator } from "@/components/ui/separator";
4
+ import { Label } from "@/components/ui/label"
5
+ import { Separator } from "@/components/ui/separator"
6
+ import { cn } from '../lib/utils'
7
7
 
8
8
  function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
9
9
  return (
@@ -16,7 +16,7 @@ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
16
16
  )}
17
17
  {...props}
18
18
  />
19
- );
19
+ )
20
20
  }
21
21
 
22
22
  function FieldLegend({
@@ -36,7 +36,7 @@ function FieldLegend({
36
36
  )}
37
37
  {...props}
38
38
  />
39
- );
39
+ )
40
40
  }
41
41
 
42
42
  function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
@@ -49,11 +49,11 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
49
49
  )}
50
50
  {...props}
51
51
  />
52
- );
52
+ )
53
53
  }
54
54
 
55
55
  const fieldVariants = cva(
56
- "group/field data-[invalid=true]:text-destructive flex w-full gap-2",
56
+ "group/field flex w-full gap-2 ",
57
57
  {
58
58
  variants: {
59
59
  orientation: {
@@ -61,20 +61,20 @@ const fieldVariants = cva(
61
61
  horizontal: [
62
62
  "flex-row items-center",
63
63
  "[&>[data-slot=field-label]]:flex-auto",
64
- "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start"
64
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
65
65
  ],
66
66
  responsive: [
67
- "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
67
+ "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
68
68
  "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
69
- "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
70
- ]
71
- }
69
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
70
+ ],
71
+ },
72
72
  },
73
73
  defaultVariants: {
74
- orientation: "vertical"
75
- }
74
+ orientation: "vertical",
75
+ },
76
76
  }
77
- );
77
+ )
78
78
 
79
79
  function Field({
80
80
  className,
@@ -89,7 +89,7 @@ function Field({
89
89
  className={cn(fieldVariants({ orientation }), className)}
90
90
  {...props}
91
91
  />
92
- );
92
+ )
93
93
  }
94
94
 
95
95
  function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
@@ -102,10 +102,13 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
102
102
  )}
103
103
  {...props}
104
104
  />
105
- );
105
+ )
106
106
  }
107
107
 
108
- function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
108
+ function FieldLabel({
109
+ className,
110
+ ...props
111
+ }: React.ComponentProps<typeof Label>) {
109
112
  return (
110
113
  <Label
111
114
  data-slot="field-label"
@@ -117,7 +120,7 @@ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>)
117
120
  )}
118
121
  {...props}
119
122
  />
120
- );
123
+ )
121
124
  }
122
125
 
123
126
  function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -125,12 +128,12 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
125
128
  <div
126
129
  data-slot="field-label"
127
130
  className={cn(
128
- "flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50",
131
+ "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
129
132
  className
130
133
  )}
131
134
  {...props}
132
135
  />
133
- );
136
+ )
134
137
  }
135
138
 
136
139
  function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
@@ -138,14 +141,14 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
138
141
  <p
139
142
  data-slot="field-description"
140
143
  className={cn(
141
- "text-muted-foreground text-sm font-normal leading-normal group-has-data-[orientation=horizontal]/field:text-balance",
142
- "nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
144
+ "text-muted-foreground text-sm leading-normal font-normal group-has-data-[orientation=horizontal]/field:text-balance",
145
+ "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
143
146
  "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
144
147
  className
145
148
  )}
146
149
  {...props}
147
150
  />
148
- );
151
+ )
149
152
  }
150
153
 
151
154
  function FieldSeparator({
@@ -153,7 +156,7 @@ function FieldSeparator({
153
156
  className,
154
157
  ...props
155
158
  }: React.ComponentProps<"div"> & {
156
- children?: React.ReactNode;
159
+ children?: React.ReactNode
157
160
  }) {
158
161
  return (
159
162
  <div
@@ -175,7 +178,7 @@ function FieldSeparator({
175
178
  </span>
176
179
  )}
177
180
  </div>
178
- );
181
+ )
179
182
  }
180
183
 
181
184
  function FieldError({
@@ -184,33 +187,37 @@ function FieldError({
184
187
  errors,
185
188
  ...props
186
189
  }: React.ComponentProps<"div"> & {
187
- errors?: Array<{ message?: string } | undefined>;
190
+ errors?: Array<{ message?: string } | undefined>
188
191
  }) {
189
192
  const content = useMemo(() => {
190
193
  if (children) {
191
- return children;
194
+ return children
192
195
  }
193
196
 
194
- if (!errors) {
195
- return null;
197
+ if (!errors?.length) {
198
+ return null
196
199
  }
197
200
 
198
- if (errors?.length === 1 && errors[0]?.message) {
199
- return errors[0].message;
201
+ const uniqueErrors = [
202
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
203
+ ]
204
+
205
+ if (uniqueErrors?.length == 1) {
206
+ return uniqueErrors[0]?.message
200
207
  }
201
208
 
202
209
  return (
203
210
  <ul className="ml-4 flex list-disc flex-col gap-1">
204
- {errors.map(
211
+ {uniqueErrors.map(
205
212
  (error, index) =>
206
213
  error?.message && <li key={index}>{error.message}</li>
207
214
  )}
208
215
  </ul>
209
- );
210
- }, [children, errors]);
216
+ )
217
+ }, [children, errors])
211
218
 
212
219
  if (!content) {
213
- return null;
220
+ return null
214
221
  }
215
222
 
216
223
  return (
@@ -222,18 +229,14 @@ function FieldError({
222
229
  >
223
230
  {content}
224
231
  </div>
225
- );
232
+ )
226
233
  }
227
234
 
228
235
  export {
229
- Field,
230
- FieldContent,
231
- FieldDescription,
236
+ Field, FieldContent, FieldDescription,
232
237
  FieldError,
233
- FieldGroup,
234
- FieldLabel,
235
- FieldLegend,
238
+ FieldGroup, FieldLabel, FieldLegend,
236
239
  FieldSeparator,
237
- FieldSet,
238
- FieldTitle
239
- };
240
+ FieldSet, FieldTitle
241
+ }
242
+
@@ -1,46 +1,55 @@
1
- import { OTPInput, OTPInputContext } from "input-otp";
2
- import { Minus } from "lucide-react";
3
- import * as React from "react";
1
+ import { OTPInput, OTPInputContext } from "input-otp"
2
+ import { MinusIcon } from "lucide-react"
3
+ import * as React from "react"
4
4
 
5
- import { cn } from "@/components/lib/utils";
5
+ import { cn } from '../lib/utils'
6
6
 
7
- const InputOTP = React.forwardRef<
8
- React.ElementRef<typeof OTPInput>,
9
- React.ComponentPropsWithoutRef<typeof OTPInput>
10
- >(({ className, containerClassName, ...props }, ref) => (
11
- <OTPInput
12
- ref={ref}
13
- containerClassName={cn(
14
- "flex items-center gap-2 has-[:disabled]:opacity-50",
15
- containerClassName
16
- )}
17
- className={cn("disabled:cursor-not-allowed", className)}
18
- {...props}
19
- />
20
- ));
21
- InputOTP.displayName = "InputOTP";
7
+ function InputOTP({
8
+ className,
9
+ containerClassName,
10
+ ...props
11
+ }: React.ComponentProps<typeof OTPInput> & {
12
+ containerClassName?: string
13
+ }) {
14
+ return (
15
+ <OTPInput
16
+ data-slot="input-otp"
17
+ containerClassName={cn(
18
+ "flex items-center gap-2 has-disabled:opacity-50",
19
+ containerClassName
20
+ )}
21
+ className={cn("disabled:cursor-not-allowed", className)}
22
+ {...props}
23
+ />
24
+ )
25
+ }
22
26
 
23
- const InputOTPGroup = React.forwardRef<
24
- React.ElementRef<"div">,
25
- React.ComponentPropsWithoutRef<"div">
26
- >(({ className, ...props }, ref) => (
27
- <div ref={ref} className={cn("flex items-center", className)} {...props} />
28
- ));
29
- InputOTPGroup.displayName = "InputOTPGroup";
27
+ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
28
+ return (
29
+ <div
30
+ data-slot="input-otp-group"
31
+ className={cn("flex items-center", className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
30
36
 
31
- const InputOTPSlot = React.forwardRef<
32
- React.ElementRef<"div">,
33
- React.ComponentPropsWithoutRef<"div"> & { index: number }
34
- >(({ index, className, ...props }, ref) => {
35
- const inputOTPContext = React.useContext(OTPInputContext);
36
- const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
37
+ function InputOTPSlot({
38
+ index,
39
+ className,
40
+ ...props
41
+ }: React.ComponentProps<"div"> & {
42
+ index: number
43
+ }) {
44
+ const inputOTPContext = React.useContext(OTPInputContext)
45
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
37
46
 
38
47
  return (
39
48
  <div
40
- ref={ref}
49
+ data-slot="input-otp-slot"
50
+ data-active={isActive}
41
51
  className={cn(
42
- "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
43
- isActive && "z-10 ring-1 ring-ring",
52
+ "data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
44
53
  className
45
54
  )}
46
55
  {...props}
@@ -48,22 +57,20 @@ const InputOTPSlot = React.forwardRef<
48
57
  {char}
49
58
  {hasFakeCaret && (
50
59
  <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
51
- <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
60
+ <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
52
61
  </div>
53
62
  )}
54
63
  </div>
55
- );
56
- });
57
- InputOTPSlot.displayName = "InputOTPSlot";
64
+ )
65
+ }
66
+
67
+ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
68
+ return (
69
+ <div data-slot="input-otp-separator" role="separator" {...props}>
70
+ <MinusIcon />
71
+ </div>
72
+ )
73
+ }
58
74
 
59
- const InputOTPSeparator = React.forwardRef<
60
- React.ElementRef<"div">,
61
- React.ComponentPropsWithoutRef<"div">
62
- >(({ ...props }, ref) => (
63
- <div ref={ref} role="separator" {...props}>
64
- <Minus />
65
- </div>
66
- ));
67
- InputOTPSeparator.displayName = "InputOTPSeparator";
75
+ export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot }
68
76
 
69
- export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };
@@ -1,24 +1,21 @@
1
- import * as React from "react";
1
+ import * as React from "react"
2
2
 
3
- import { cn } from "@/components/lib/utils";
3
+ import { cn } from '../lib/utils'
4
4
 
5
- const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
- ({ className, type, ...props }, ref) => {
7
- return (
8
- <input
9
- type={type}
10
- className={cn(
11
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
- "group-data-[invalid=true]/field:border-destructive group-data-[invalid=true]/field:focus-visible:ring-destructive",
13
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
- className
15
- )}
16
- ref={ref}
17
- {...props}
18
- />
19
- );
20
- }
21
- );
22
- Input.displayName = "Input";
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
23
20
 
24
- export { Input };
21
+ export { Input }
@@ -1,24 +1,23 @@
1
- import * as LabelPrimitive from "@radix-ui/react-label";
2
- import { cva, type VariantProps } from "class-variance-authority";
3
- import * as React from "react";
1
+ import * as LabelPrimitive from "@radix-ui/react-label"
2
+ import * as React from "react"
4
3
 
5
- import { cn } from "@/components/lib/utils";
4
+ import { cn } from '../lib/utils'
6
5
 
7
- const labelVariants = cva(
8
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
- );
10
6
 
11
- const Label = React.forwardRef<
12
- React.ElementRef<typeof LabelPrimitive.Root>,
13
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
- VariantProps<typeof labelVariants>
15
- >(({ className, ...props }, ref) => (
16
- <LabelPrimitive.Root
17
- ref={ref}
18
- className={cn(labelVariants(), className)}
19
- {...props}
20
- />
21
- ));
22
- Label.displayName = LabelPrimitive.Root.displayName;
7
+ function Label({
8
+ className,
9
+ ...props
10
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
11
+ return (
12
+ <LabelPrimitive.Root
13
+ data-slot="label"
14
+ className={cn(
15
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
23
22
 
24
- export { Label };
23
+ export { Label }
@@ -1,42 +1,44 @@
1
- import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
2
- import { Circle } from "lucide-react";
3
- import * as React from "react";
1
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
2
+ import { CircleIcon } from "lucide-react"
3
+ import * as React from "react"
4
4
 
5
- import { cn } from "@/components/lib/utils";
5
+ import { cn } from '../lib/utils'
6
6
 
7
- const RadioGroup = React.forwardRef<
8
- React.ElementRef<typeof RadioGroupPrimitive.Root>,
9
- React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
10
- >(({ className, ...props }, ref) => {
7
+
8
+ function RadioGroup({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
11
12
  return (
12
13
  <RadioGroupPrimitive.Root
13
- className={cn("grid gap-2", className)}
14
+ data-slot="radio-group"
15
+ className={cn("grid gap-3", className)}
14
16
  {...props}
15
- ref={ref}
16
17
  />
17
- );
18
- });
19
- RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
18
+ )
19
+ }
20
20
 
21
- const RadioGroupItem = React.forwardRef<
22
- React.ElementRef<typeof RadioGroupPrimitive.Item>,
23
- React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
24
- >(({ className, ...props }, ref) => {
21
+ function RadioGroupItem({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
25
25
  return (
26
26
  <RadioGroupPrimitive.Item
27
- ref={ref}
27
+ data-slot="radio-group-item"
28
28
  className={cn(
29
- "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
29
+ "border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
30
30
  className
31
31
  )}
32
32
  {...props}
33
33
  >
34
- <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
35
- <Circle className="h-3.5 w-3.5 fill-primary" />
34
+ <RadioGroupPrimitive.Indicator
35
+ data-slot="radio-group-indicator"
36
+ className="relative flex items-center justify-center"
37
+ >
38
+ <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
36
39
  </RadioGroupPrimitive.Indicator>
37
40
  </RadioGroupPrimitive.Item>
38
- );
39
- });
40
- RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
41
+ )
42
+ }
41
43
 
42
- export { RadioGroup, RadioGroupItem };
44
+ export { RadioGroup, RadioGroupItem }