import type { ReactNode } from "react"; import { IconButton, Tooltip } from "../../ui/primitives"; import { Wordmark } from "../../ui/brand"; import { Monitor, Moon, Sun } from "lucide-react"; import { useAppStore, type ThemeMode } from "../../store"; import { t } from "../../i18n"; import s from "./AuthShell.module.css"; export interface AuthShellProps { title: ReactNode; subtitle?: ReactNode; children: ReactNode; footer?: ReactNode; /** 卡内品牌字标。默认不渲染——顶栏已有 logo,卡内再放会一屏两 logo; * 需要时显式传入一个节点即可。 */ cardBrand?: ReactNode | null; /** 隐藏顶栏 logo。过渡型认证屏(OAuth 回调 / 首次设置)用:它们会 SPA * 跳转到主界面,跳转帧里顶栏 logo 会与主界面 header 的 logo 重叠成“两个 * logo”,隐藏即可消除。登录页是整页跳 IdP、无此重叠,保留 logo。 */ hideBrand?: boolean; } // 全屏外壳:登录、首次设置、OAuth 回调共用。带轻量品牌 + 主题切换, // 让用户在登录前也能调主题。 export function AuthShell(props: AuthShellProps) { const { title, subtitle, children, footer, cardBrand, hideBrand } = props; const theme = useAppStore((s) => s.theme); const setTheme = useAppStore((s) => s.setTheme); const next: ThemeMode = theme === "system" ? "light" : theme === "light" ? "dark" : "system"; const icon = theme === "light" ? : theme === "dark" ? : ; // 默认不在卡内重复 logo(顶栏已有),避免一屏两 logo;需要时显式传入。 const brandNode = cardBrand === undefined ? null : cardBrand; return (
{/* 空 span 占位保持 space-between,主题切换仍靠右。 */} {hideBrand ? : } setTheme(next)}> {icon}
{brandNode &&
{brandNode}
}

{title}

{subtitle &&

{subtitle}

}
{children}
{footer &&
{footer}
}
); } // 公共 footer 链接组件:登录页用,setup / oauth 不一定要。 export function AuthFooterLinks(props: { items: { label: string; href: string }[] }) { const { items } = props; return (
{items.map((it, i) => ( {i > 0 && ·} {it.label} ))}
); }