(() => {
try {
const saved = localStorage.getItem(THEME_KEY);
if (saved) return saved === "dark";
} catch {
/* 무시 */
}
return (
typeof window !== "undefined" &&
window.matchMedia?.("(prefers-color-scheme: dark)").matches
);
});
useEffect(() => {
const root = document.documentElement;
if (dark) root.setAttribute("data-theme", "dark");
else root.removeAttribute("data-theme");
try {
localStorage.setItem(THEME_KEY, dark ? "dark" : "light");
} catch {
/* 무시 */
}
}, [dark]);
return [dark, () => setDark((d) => !d)];
}
function initials(name?: string | null): string {
const trimmed = (name ?? "").trim();
if (!trimmed) return "·";
// 한글이면 첫 글자, 영문이면 첫 글자 대문자
return trimmed.slice(0, 1);
}
export interface TopbarProps {
/** 역할 컨텍스트 라벨 오버라이드 (없으면 user.role 라벨) */
contextLabel?: string;
}
/** 공통 톱바 (56px). 브랜드 + 역할 라벨 + 테마/사용자/로그아웃. §6.3 */
export function Topbar({ contextLabel }: TopbarProps) {
const { user, logout } = useAuth();
const navigate = useNavigate();
const [dark, toggleTheme] = useTheme();
const label = contextLabel ?? (user ? roleLabel(user.role) : null);
const onLogout = async () => {
await logout();
navigate("/login", { replace: true });
};
return (
);
}