@liedsonc/core-auth-kit 0.1.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.
- package/LICENSE +21 -0
- package/README.md +759 -0
- package/auth-ui/components/auth-card.tsx +49 -0
- package/auth-ui/components/auth-form.tsx +53 -0
- package/auth-ui/components/error-message.tsx +24 -0
- package/auth-ui/components/form-field.tsx +39 -0
- package/auth-ui/components/index.ts +8 -0
- package/auth-ui/components/loading-spinner.tsx +19 -0
- package/auth-ui/components/oauth-buttons.tsx +49 -0
- package/auth-ui/components/password-input.tsx +93 -0
- package/auth-ui/components/success-message.tsx +24 -0
- package/auth-ui/components/ui/button.tsx +52 -0
- package/auth-ui/components/ui/card.tsx +75 -0
- package/auth-ui/components/ui/input.tsx +21 -0
- package/auth-ui/components/ui/label.tsx +25 -0
- package/auth-ui/context.tsx +26 -0
- package/auth-ui/hooks/use-auth.ts +131 -0
- package/auth-ui/hooks/use-oauth.ts +25 -0
- package/auth-ui/index.ts +28 -0
- package/auth-ui/package.json +55 -0
- package/auth-ui/pages/forgot-password/index.tsx +83 -0
- package/auth-ui/pages/index.ts +5 -0
- package/auth-ui/pages/login/index.tsx +119 -0
- package/auth-ui/pages/register/index.tsx +149 -0
- package/auth-ui/pages/reset-password/index.tsx +133 -0
- package/auth-ui/pages/verify-email/index.tsx +143 -0
- package/auth-ui/styles/index.css +7 -0
- package/auth-ui/tsconfig.json +33 -0
- package/auth-ui/types/index.ts +34 -0
- package/auth-ui/utils.ts +6 -0
- package/package.json +32 -0
- package/postcss.config.mjs +9 -0
- package/tailwind.config.ts +41 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardFooter,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from "./ui/card";
|
|
11
|
+
import { useAuthUIConfig } from "../context";
|
|
12
|
+
import { cn } from "../utils";
|
|
13
|
+
import "../styles/index.css";
|
|
14
|
+
|
|
15
|
+
export interface AuthCardProps {
|
|
16
|
+
title: string;
|
|
17
|
+
subtitle?: string;
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
footer?: React.ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AuthCard({
|
|
24
|
+
title,
|
|
25
|
+
subtitle,
|
|
26
|
+
children,
|
|
27
|
+
footer,
|
|
28
|
+
className,
|
|
29
|
+
}: AuthCardProps) {
|
|
30
|
+
const config = useAuthUIConfig();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Card className={cn("auth-ui-card", className)}>
|
|
34
|
+
<CardHeader className="space-y-1">
|
|
35
|
+
{config.logo && (
|
|
36
|
+
<div className="flex justify-center mb-2" aria-hidden="true">
|
|
37
|
+
{config.logo}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
<CardTitle className="text-center">{title}</CardTitle>
|
|
41
|
+
{subtitle && (
|
|
42
|
+
<CardDescription className="text-center">{subtitle}</CardDescription>
|
|
43
|
+
)}
|
|
44
|
+
</CardHeader>
|
|
45
|
+
<CardContent>{children}</CardContent>
|
|
46
|
+
{footer && <CardFooter className="flex flex-col gap-2">{footer}</CardFooter>}
|
|
47
|
+
</Card>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
|
|
6
|
+
export interface AuthFormProps
|
|
7
|
+
extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
|
|
8
|
+
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AuthForm({
|
|
16
|
+
onSubmit,
|
|
17
|
+
loading = false,
|
|
18
|
+
error,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
...props
|
|
22
|
+
}: AuthFormProps) {
|
|
23
|
+
const [submitting, setSubmitting] = React.useState(false);
|
|
24
|
+
const isDisabled = loading || submitting;
|
|
25
|
+
|
|
26
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
if (isDisabled) return;
|
|
29
|
+
setSubmitting(true);
|
|
30
|
+
try {
|
|
31
|
+
await onSubmit(e);
|
|
32
|
+
} finally {
|
|
33
|
+
setSubmitting(false);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<form
|
|
39
|
+
onSubmit={handleSubmit}
|
|
40
|
+
noValidate
|
|
41
|
+
className={cn("space-y-4", className)}
|
|
42
|
+
aria-busy={isDisabled}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{error && (
|
|
46
|
+
<p role="alert" className="text-sm text-destructive">
|
|
47
|
+
{error}
|
|
48
|
+
</p>
|
|
49
|
+
)}
|
|
50
|
+
{children}
|
|
51
|
+
</form>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utils";
|
|
4
|
+
|
|
5
|
+
export function ErrorMessage({
|
|
6
|
+
children,
|
|
7
|
+
className,
|
|
8
|
+
id,
|
|
9
|
+
}: {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
}) {
|
|
14
|
+
if (!children) return null;
|
|
15
|
+
return (
|
|
16
|
+
<p
|
|
17
|
+
id={id}
|
|
18
|
+
role="alert"
|
|
19
|
+
className={cn("text-sm text-destructive", className)}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</p>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Label } from "./ui/label";
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
|
|
6
|
+
export function FormField({
|
|
7
|
+
label,
|
|
8
|
+
htmlFor,
|
|
9
|
+
error,
|
|
10
|
+
children,
|
|
11
|
+
required,
|
|
12
|
+
className,
|
|
13
|
+
}: {
|
|
14
|
+
label: string;
|
|
15
|
+
htmlFor: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
}) {
|
|
21
|
+
return (
|
|
22
|
+
<div className={cn("space-y-2", className)}>
|
|
23
|
+
<Label htmlFor={htmlFor}>
|
|
24
|
+
{label}
|
|
25
|
+
{required && (
|
|
26
|
+
<span className="text-destructive ml-0.5" aria-hidden="true">
|
|
27
|
+
*
|
|
28
|
+
</span>
|
|
29
|
+
)}
|
|
30
|
+
</Label>
|
|
31
|
+
{children}
|
|
32
|
+
{error && (
|
|
33
|
+
<p id={`${htmlFor}-error`} role="alert" className="text-sm text-destructive">
|
|
34
|
+
{error}
|
|
35
|
+
</p>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AuthCard } from "./auth-card";
|
|
2
|
+
export { AuthForm } from "./auth-form";
|
|
3
|
+
export { ErrorMessage } from "./error-message";
|
|
4
|
+
export { FormField } from "./form-field";
|
|
5
|
+
export { LoadingSpinner } from "./loading-spinner";
|
|
6
|
+
export { OAuthButtons } from "./oauth-buttons";
|
|
7
|
+
export { PasswordInput } from "./password-input";
|
|
8
|
+
export { SuccessMessage } from "./success-message";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utils";
|
|
4
|
+
|
|
5
|
+
export function LoadingSpinner({
|
|
6
|
+
className,
|
|
7
|
+
"aria-label": ariaLabel = "Loading",
|
|
8
|
+
}: {
|
|
9
|
+
className?: string;
|
|
10
|
+
"aria-label"?: string;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
role="status"
|
|
15
|
+
aria-label={ariaLabel}
|
|
16
|
+
className={cn("inline-block size-5 animate-spin rounded-full border-2 border-current border-t-transparent", className)}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "./ui/button";
|
|
4
|
+
import { useOAuth } from "../hooks/use-oauth";
|
|
5
|
+
import { cn } from "../utils";
|
|
6
|
+
import type { OAuthProvider } from "../types";
|
|
7
|
+
|
|
8
|
+
const providerLabels: Record<OAuthProvider, string> = {
|
|
9
|
+
google: "Continue with Google",
|
|
10
|
+
apple: "Continue with Apple",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function OAuthButtons({
|
|
14
|
+
providers,
|
|
15
|
+
loadingProvider,
|
|
16
|
+
onSignIn,
|
|
17
|
+
className,
|
|
18
|
+
}: {
|
|
19
|
+
providers: { provider: OAuthProvider; enabled: boolean }[];
|
|
20
|
+
loadingProvider: OAuthProvider | null;
|
|
21
|
+
onSignIn: (provider: OAuthProvider) => void | Promise<void>;
|
|
22
|
+
className?: string;
|
|
23
|
+
}) {
|
|
24
|
+
const enabled = providers.filter((p) => p.enabled);
|
|
25
|
+
|
|
26
|
+
if (enabled.length === 0) return null;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={cn("space-y-2", className)}>
|
|
30
|
+
{enabled.map(({ provider }) => (
|
|
31
|
+
<Button
|
|
32
|
+
key={provider}
|
|
33
|
+
type="button"
|
|
34
|
+
variant="outline"
|
|
35
|
+
className="w-full"
|
|
36
|
+
disabled={!!loadingProvider}
|
|
37
|
+
onClick={() => onSignIn(provider)}
|
|
38
|
+
aria-busy={loadingProvider === provider}
|
|
39
|
+
>
|
|
40
|
+
{loadingProvider === provider ? (
|
|
41
|
+
<span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
42
|
+
) : (
|
|
43
|
+
providerLabels[provider]
|
|
44
|
+
)}
|
|
45
|
+
</Button>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Input } from "./ui/input";
|
|
5
|
+
import { Button } from "./ui/button";
|
|
6
|
+
import { cn } from "../utils";
|
|
7
|
+
|
|
8
|
+
function getStrength(value: string): 0 | 1 | 2 | 3 {
|
|
9
|
+
if (!value) return 0;
|
|
10
|
+
let s = 0;
|
|
11
|
+
if (value.length >= 8) s++;
|
|
12
|
+
if (value.length >= 12) s++;
|
|
13
|
+
if (/[A-Z]/.test(value) && /[a-z]/.test(value) && /\d/.test(value)) s++;
|
|
14
|
+
return Math.min(s, 3) as 0 | 1 | 2 | 3;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PasswordInputProps
|
|
18
|
+
extends Omit<React.ComponentProps<typeof Input>, "type"> {
|
|
19
|
+
showStrength?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
|
|
23
|
+
({ className, showStrength = false, ...props }, ref) => {
|
|
24
|
+
const [show, setShow] = React.useState(false);
|
|
25
|
+
const [value, setValue] = React.useState("");
|
|
26
|
+
const id = React.useId();
|
|
27
|
+
const strength = showStrength ? getStrength(value) : 0;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-1.5">
|
|
31
|
+
<div className="relative">
|
|
32
|
+
<Input
|
|
33
|
+
ref={ref}
|
|
34
|
+
type={show ? "text" : "password"}
|
|
35
|
+
autoComplete="current-password"
|
|
36
|
+
aria-describedby={showStrength ? `${id}-strength` : undefined}
|
|
37
|
+
className={cn("pr-10", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
value={props.value ?? value}
|
|
40
|
+
onChange={(e) => {
|
|
41
|
+
setValue(e.target.value);
|
|
42
|
+
props.onChange?.(e);
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
<Button
|
|
46
|
+
type="button"
|
|
47
|
+
variant="ghost"
|
|
48
|
+
size="icon"
|
|
49
|
+
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
|
50
|
+
aria-label={show ? "Hide password" : "Show password"}
|
|
51
|
+
aria-pressed={show}
|
|
52
|
+
onClick={() => setShow((p) => !p)}
|
|
53
|
+
tabIndex={-1}
|
|
54
|
+
>
|
|
55
|
+
<span className="text-xs font-medium" aria-hidden="true">
|
|
56
|
+
{show ? "Hide" : "Show"}
|
|
57
|
+
</span>
|
|
58
|
+
</Button>
|
|
59
|
+
</div>
|
|
60
|
+
{showStrength && value && (
|
|
61
|
+
<div
|
|
62
|
+
id={`${id}-strength`}
|
|
63
|
+
role="progressbar"
|
|
64
|
+
aria-valuenow={strength}
|
|
65
|
+
aria-valuemin={0}
|
|
66
|
+
aria-valuemax={3}
|
|
67
|
+
aria-label="Password strength"
|
|
68
|
+
className="flex gap-1"
|
|
69
|
+
>
|
|
70
|
+
{[1, 2, 3].map((i) => (
|
|
71
|
+
<span
|
|
72
|
+
key={i}
|
|
73
|
+
className={cn(
|
|
74
|
+
"h-1 flex-1 rounded-full",
|
|
75
|
+
i <= strength
|
|
76
|
+
? strength === 1
|
|
77
|
+
? "bg-destructive"
|
|
78
|
+
: strength === 2
|
|
79
|
+
? "bg-amber-500"
|
|
80
|
+
: "bg-green-500"
|
|
81
|
+
: "bg-muted"
|
|
82
|
+
)}
|
|
83
|
+
/>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
PasswordInput.displayName = "PasswordInput";
|
|
92
|
+
|
|
93
|
+
export { PasswordInput };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utils";
|
|
4
|
+
|
|
5
|
+
export function SuccessMessage({
|
|
6
|
+
children,
|
|
7
|
+
className,
|
|
8
|
+
id,
|
|
9
|
+
}: {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
}) {
|
|
14
|
+
if (!children) return null;
|
|
15
|
+
return (
|
|
16
|
+
<p
|
|
17
|
+
id={id}
|
|
18
|
+
role="status"
|
|
19
|
+
className={cn("text-sm text-green-600 dark:text-green-400", className)}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</p>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 "../../utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
13
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
14
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
15
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
default: "h-10 px-4 py-2",
|
|
20
|
+
sm: "h-9 rounded-md px-3",
|
|
21
|
+
lg: "h-11 rounded-md px-8",
|
|
22
|
+
icon: "h-10 w-10",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "default",
|
|
27
|
+
size: "default",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface ButtonProps
|
|
33
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
34
|
+
VariantProps<typeof buttonVariants> {
|
|
35
|
+
asChild?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
39
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
40
|
+
const Comp = asChild ? Slot : "button";
|
|
41
|
+
return (
|
|
42
|
+
<Comp
|
|
43
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
44
|
+
ref={ref}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
Button.displayName = "Button";
|
|
51
|
+
|
|
52
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../utils";
|
|
3
|
+
|
|
4
|
+
const Card = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn(
|
|
11
|
+
"rounded-lg border border-border bg-card text-card-foreground shadow-sm",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
));
|
|
17
|
+
Card.displayName = "Card";
|
|
18
|
+
|
|
19
|
+
const CardHeader = React.forwardRef<
|
|
20
|
+
HTMLDivElement,
|
|
21
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
CardHeader.displayName = "CardHeader";
|
|
30
|
+
|
|
31
|
+
const CardTitle = React.forwardRef<
|
|
32
|
+
HTMLParagraphElement,
|
|
33
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<h3
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
));
|
|
41
|
+
CardTitle.displayName = "CardTitle";
|
|
42
|
+
|
|
43
|
+
const CardDescription = React.forwardRef<
|
|
44
|
+
HTMLParagraphElement,
|
|
45
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<p
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
));
|
|
53
|
+
CardDescription.displayName = "CardDescription";
|
|
54
|
+
|
|
55
|
+
const CardContent = React.forwardRef<
|
|
56
|
+
HTMLDivElement,
|
|
57
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
60
|
+
));
|
|
61
|
+
CardContent.displayName = "CardContent";
|
|
62
|
+
|
|
63
|
+
const CardFooter = React.forwardRef<
|
|
64
|
+
HTMLDivElement,
|
|
65
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
66
|
+
>(({ className, ...props }, ref) => (
|
|
67
|
+
<div
|
|
68
|
+
ref={ref}
|
|
69
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
));
|
|
73
|
+
CardFooter.displayName = "CardFooter";
|
|
74
|
+
|
|
75
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../utils";
|
|
3
|
+
|
|
4
|
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
5
|
+
({ className, type, ...props }, ref) => {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background 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-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
ref={ref}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
Input.displayName = "Input";
|
|
20
|
+
|
|
21
|
+
export { Input };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
import { cn } from "../../utils";
|
|
7
|
+
|
|
8
|
+
const labelVariants = cva(
|
|
9
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const Label = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
15
|
+
VariantProps<typeof labelVariants>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<LabelPrimitive.Root
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn(labelVariants(), className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
24
|
+
|
|
25
|
+
export { Label };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import type { AuthUIConfig } from "./types";
|
|
5
|
+
|
|
6
|
+
const AuthUIConfigContext = React.createContext<AuthUIConfig | null>(null);
|
|
7
|
+
|
|
8
|
+
export function AuthUIProvider({
|
|
9
|
+
config,
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
config: AuthUIConfig;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<AuthUIConfigContext.Provider value={config}>
|
|
17
|
+
{children}
|
|
18
|
+
</AuthUIConfigContext.Provider>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useAuthUIConfig(): AuthUIConfig {
|
|
23
|
+
const ctx = React.useContext(AuthUIConfigContext);
|
|
24
|
+
if (!ctx) throw new Error("useAuthUIConfig must be used within AuthUIProvider");
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|