127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
|
import { Button } from "@xtablo/ui/components/button";
|
|
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
|
|
import type { CSSProperties, MouseEventHandler, ReactNode } from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
type AuthCardShellProps = {
|
|
title: string;
|
|
description?: ReactNode;
|
|
children: ReactNode;
|
|
background?: ReactNode;
|
|
topLeft?: ReactNode;
|
|
showThemeToggle?: boolean;
|
|
onBackdropClick?: () => void;
|
|
wrapperClassName?: string;
|
|
wrapperStyle?: CSSProperties;
|
|
onWrapperMouseMove?: MouseEventHandler<HTMLDivElement>;
|
|
onWrapperMouseLeave?: () => void;
|
|
isHovered?: boolean;
|
|
cardClassName?: string;
|
|
};
|
|
|
|
const XTABLO_ASSETS_BASE_URL = "https://assets.xtablo.com";
|
|
|
|
export function AuthCardShell({
|
|
title,
|
|
description,
|
|
children,
|
|
background,
|
|
topLeft,
|
|
showThemeToggle = false,
|
|
onBackdropClick,
|
|
wrapperClassName,
|
|
wrapperStyle,
|
|
onWrapperMouseMove,
|
|
onWrapperMouseLeave,
|
|
isHovered = false,
|
|
cardClassName,
|
|
}: AuthCardShellProps) {
|
|
const { theme, setTheme } = useTheme();
|
|
|
|
const toggleTheme = () => {
|
|
if (theme === "light") {
|
|
setTheme("dark");
|
|
} else if (theme === "dark") {
|
|
setTheme("system");
|
|
} else {
|
|
setTheme("light");
|
|
}
|
|
};
|
|
|
|
const themeIcon =
|
|
theme === "light" ? (
|
|
<SunIcon className="h-5 w-5" />
|
|
) : theme === "dark" ? (
|
|
<MoonIcon className="h-5 w-5" />
|
|
) : (
|
|
<MonitorIcon className="h-5 w-5" />
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className="min-h-screen flex items-center justify-center bg-linear-to-br from-primary/10 via-background to-secondary/5 relative overflow-hidden px-4 py-8 sm:px-6"
|
|
onClick={onBackdropClick}
|
|
>
|
|
{background}
|
|
|
|
<div
|
|
className={twMerge("w-full max-w-lg rounded-2xl relative", wrapperClassName)}
|
|
style={wrapperStyle}
|
|
onMouseMove={onWrapperMouseMove}
|
|
onMouseLeave={onWrapperMouseLeave}
|
|
onClick={(event) => event.stopPropagation()}
|
|
>
|
|
<div className="absolute inset-0 rounded-2xl bg-linear-to-br from-primary/10 via-primary/5 to-secondary/10 blur-xl -z-10" />
|
|
|
|
<div
|
|
data-testid="auth-card-shell"
|
|
className={twMerge(
|
|
"relative w-full h-full p-5 sm:p-8 bg-card/80 backdrop-blur-md rounded-2xl border border-border z-10 transition-shadow duration-200",
|
|
isHovered
|
|
? "shadow-[0_15px_35px_rgba(0,0,0,0.15)] dark:shadow-[0_15px_35px_rgba(0,0,0,0.3)]"
|
|
: "shadow-xl shadow-black/10 dark:shadow-black/25",
|
|
cardClassName
|
|
)}
|
|
>
|
|
{topLeft || showThemeToggle ? (
|
|
<div className="mb-6 flex items-center justify-between">
|
|
<div>{topLeft}</div>
|
|
{showThemeToggle ? (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={toggleTheme}
|
|
className="text-muted-foreground hover:text-foreground p-2"
|
|
aria-label={`change theme (${theme})`}
|
|
>
|
|
{themeIcon}
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="mb-6 flex justify-center">
|
|
<img
|
|
src={`${XTABLO_ASSETS_BASE_URL}/logo_dark.png`}
|
|
alt="Xtablo"
|
|
className="w-16 h-16 object-contain block dark:hidden"
|
|
/>
|
|
<img
|
|
src={`${XTABLO_ASSETS_BASE_URL}/logo_white.png`}
|
|
alt="Xtablo"
|
|
className="w-16 h-16 object-contain hidden dark:block"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2 text-center mb-6">
|
|
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">{title}</h1>
|
|
{description ? <div className="text-muted-foreground">{description}</div> : null}
|
|
</div>
|
|
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|