Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
166 lines
6.1 KiB
TypeScript
166 lines
6.1 KiB
TypeScript
import type { HTMLAttributes, ReactElement, ReactNode } from "react";
|
|
|
|
function joinClassNames(...classNames: (string | undefined)[]): string {
|
|
return classNames.filter(Boolean).join(" ");
|
|
}
|
|
|
|
export interface AuthShellProps extends HTMLAttributes<HTMLElement> {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function AuthShell({ children, className, ...props }: AuthShellProps): ReactElement {
|
|
return (
|
|
<main
|
|
className={joinClassNames(
|
|
"relative isolate flex min-h-screen items-center justify-center overflow-hidden bg-[#f0f4fc] px-4 py-8 text-[#0f141d] sm:px-8 dark:bg-[#080b12] dark:text-[#eef3ff]",
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
|
|
<div
|
|
className="absolute left-1/2 top-1/2 h-[52rem] w-[52rem] -translate-x-1/2 -translate-y-1/2 rounded-full opacity-20 blur-[2px] animate-spin"
|
|
style={{
|
|
background:
|
|
"conic-gradient(from 0deg, #2f80ff, #8b5cf6, #ec4899, #e5484d, #f59e0b, #14b8a6, #2f80ff)",
|
|
animationDuration: "30s",
|
|
}}
|
|
/>
|
|
<div
|
|
className="absolute left-1/2 top-1/2 h-[36rem] w-[36rem] -translate-x-1/2 -translate-y-1/2 rounded-full opacity-20 blur-[1px] animate-spin"
|
|
style={{
|
|
background:
|
|
"conic-gradient(from 120deg, #14b8a6, #06b6d4, #2f80ff, #6366f1, #8b5cf6, #14b8a6)",
|
|
animationDuration: "20s",
|
|
animationDirection: "reverse",
|
|
}}
|
|
/>
|
|
<div
|
|
className="absolute left-1/2 top-1/2 h-[22rem] w-[22rem] -translate-x-1/2 -translate-y-1/2 rounded-full opacity-25 animate-spin"
|
|
style={{
|
|
background:
|
|
"conic-gradient(from 240deg, #f59e0b, #f97316, #e5484d, #ec4899, #8b5cf6, #f59e0b)",
|
|
animationDuration: "14s",
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(248,250,255,0.58)_0%,rgba(15,20,29,0.72)_62%,rgba(15,20,29,0.9)_100%)] dark:bg-[radial-gradient(ellipse_at_center,rgba(8,11,18,0.32)_0%,rgba(8,11,18,0.78)_62%,rgba(8,11,18,0.96)_100%)]" />
|
|
</div>
|
|
<div className="relative z-10 w-full max-w-[27rem]">{children}</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
export interface AuthCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function AuthCard({ children, className, ...props }: AuthCardProps): ReactElement {
|
|
return (
|
|
<div
|
|
className={joinClassNames(
|
|
"relative w-full overflow-hidden rounded-b-2xl border border-[#b8c4de] bg-[#dde4f2]/90 p-6 shadow-[0_30px_70px_rgba(15,20,29,0.24)] backdrop-blur-sm sm:p-10 dark:border-[#2f3b52] dark:bg-[#1b2331]/[0.92] dark:shadow-[0_32px_80px_rgba(0,0,0,0.52)]",
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-x-0 top-0 h-0.5 bg-[linear-gradient(90deg,#2f80ff,#8b5cf6,#14b8a6,#f59e0b)]"
|
|
/>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export interface AuthBrandProps {
|
|
title?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function AuthBrand({ title = "Mosaic Stack", className }: AuthBrandProps): ReactElement {
|
|
return (
|
|
<div className={joinClassNames("flex items-center justify-center gap-3", className)}>
|
|
<div className="relative h-9 w-9 animate-spin" style={{ animationDuration: "20s" }}>
|
|
<span className="absolute left-0 top-0 h-[0.88rem] w-[0.88rem] rounded-[3px] bg-[#2f80ff]" />
|
|
<span className="absolute right-0 top-0 h-[0.88rem] w-[0.88rem] rounded-[3px] bg-[#8b5cf6]" />
|
|
<span className="absolute bottom-0 right-0 h-[0.88rem] w-[0.88rem] rounded-[3px] bg-[#14b8a6]" />
|
|
<span className="absolute bottom-0 left-0 h-[0.88rem] w-[0.88rem] rounded-[3px] bg-[#f59e0b]" />
|
|
<span className="absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#ec4899]" />
|
|
</div>
|
|
<span className="bg-[linear-gradient(135deg,#56a0ff,#8b5cf6,#14b8a6)] bg-clip-text text-xl font-extrabold tracking-tight text-transparent">
|
|
{title}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export type AuthStatusTone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
|
|
export interface AuthStatusPillProps {
|
|
label: string;
|
|
tone?: AuthStatusTone;
|
|
className?: string;
|
|
}
|
|
|
|
export function AuthStatusPill({
|
|
label,
|
|
tone = "neutral",
|
|
className,
|
|
}: AuthStatusPillProps): ReactElement {
|
|
const toneStyles: Record<AuthStatusTone, string> = {
|
|
neutral:
|
|
"border-[#b8c4de] bg-[#f8faff] text-[#2f3b52] dark:border-[#2f3b52] dark:bg-[#0f141d]/70 dark:text-[#c5d0e6]",
|
|
info: "border-sky-400/50 bg-sky-500/15 text-sky-900 dark:text-sky-200",
|
|
success: "border-emerald-400/55 bg-emerald-500/15 text-emerald-900 dark:text-emerald-200",
|
|
warning: "border-amber-400/60 bg-amber-500/15 text-amber-900 dark:text-amber-200",
|
|
danger: "border-rose-400/55 bg-rose-500/15 text-rose-900 dark:text-rose-200",
|
|
};
|
|
|
|
const dotStyles: Record<AuthStatusTone, string> = {
|
|
neutral: "bg-[#5a6a87] dark:bg-[#8f9db7]",
|
|
info: "bg-sky-500",
|
|
success: "bg-emerald-500",
|
|
warning: "bg-amber-500",
|
|
danger: "bg-rose-500",
|
|
};
|
|
|
|
return (
|
|
<span
|
|
className={joinClassNames(
|
|
"inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[0.67rem] font-semibold uppercase tracking-[0.08em]",
|
|
toneStyles[tone],
|
|
className
|
|
)}
|
|
>
|
|
<span
|
|
aria-hidden="true"
|
|
className={joinClassNames("h-1.5 w-1.5 rounded-full", dotStyles[tone])}
|
|
/>
|
|
<span>{label}</span>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export interface AuthDividerProps {
|
|
text?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function AuthDivider({
|
|
text = "or continue with",
|
|
className,
|
|
}: AuthDividerProps): ReactElement {
|
|
return (
|
|
<div
|
|
className={joinClassNames(
|
|
"py-8 flex items-center gap-3 text-[0.67rem] font-semibold uppercase tracking-[0.08em] text-[#5a6a87] dark:text-[#8f9db7]",
|
|
className
|
|
)}
|
|
>
|
|
<span aria-hidden="true" className="h-px flex-1 bg-[#b8c4de] dark:bg-[#2f3b52]" />
|
|
<span>{text}</span>
|
|
<span aria-hidden="true" className="h-px flex-1 bg-[#b8c4de] dark:bg-[#2f3b52]" />
|
|
</div>
|
|
);
|
|
}
|