Files
ThothII/frontend/src/shell/host/FullHeader.tsx
T

77 lines
4.0 KiB
TypeScript

import { useState } from "react";
import { Menu } from "@base-ui/react/menu";
import { ChevronDown, Maximize, Minimize, Moon, Sun } from "lucide-react";
import { Button, buttonVariants } from "../../components/ui/button";
import { useAuthUser } from "../../auth/authState";
import { availableLocales, useI18n } from "../../i18n";
import { useShell } from "./ShellProvider";
export function FullHeader({ onLogout }: { onLogout?: () => Promise<void> }) {
const shell = useShell();
const { t } = useI18n();
const user = useAuthUser();
const [fullscreenPending, setFullscreenPending] = useState(false);
const [logoutPending, setLogoutPending] = useState(false);
const [languagePointerFocus, setLanguagePointerFocus] = useState(false);
const [error, setError] = useState<string>();
if (shell?.mode !== "full") return null;
const fullscreenLabel = shell.fullscreen ? t("Exit fullscreen") : t("Enter fullscreen");
async function toggleFullscreen() {
setFullscreenPending(true);
setError(undefined);
try {
if (document.fullscreenElement) await document.exitFullscreen();
else await document.documentElement.requestFullscreen();
} catch {
setError("Fullscreen could not be changed. Try again or check your browser permissions.");
} finally { setFullscreenPending(false); }
}
async function logout() {
if (!onLogout || logoutPending) return;
setLogoutPending(true);
setError(undefined);
try { await onLogout(); }
catch { setError("Logout could not be completed. Please try again."); }
finally { setLogoutPending(false); }
}
return (
<header className="thot-full-header" aria-label={t("Application header")}>
<span className="thot-full-header__brand">Thoth<span>II</span></span>
<div className="thot-full-header__controls">
<label className="sr-only" htmlFor="thot-ui-language">{t("Interface language")}</label>
<select id="thot-ui-language" className="thot-shell-select" value={shell.locale}
data-pointer-focus={languagePointerFocus || undefined}
onPointerDown={() => setLanguagePointerFocus(true)}
onKeyDown={event => { if (event.key !== "Escape") setLanguagePointerFocus(false); }}
onBlur={() => setLanguagePointerFocus(false)}
onChange={event => shell.changeLocale(event.target.value)}>
{availableLocales.map(({ code, name }) => <option key={code} value={code} lang={code}>{name}</option>)}
</select>
<Button variant="ghost" size="icon" aria-label={shell.theme === "light" ? t("Use dark theme") : t("Use light theme")}
title={shell.theme === "light" ? t("Use dark theme") : t("Use light theme")}
onClick={() => shell.changeTheme(shell.theme === "light" ? "dark" : "light")}>
{shell.theme === "light" ? <Moon aria-hidden="true" /> : <Sun aria-hidden="true" />}
</Button>
<Button variant="ghost" size="icon" aria-label={fullscreenLabel} title={fullscreenLabel} aria-pressed={shell.fullscreen}
disabled={fullscreenPending} onClick={() => { void toggleFullscreen(); }}>
{shell.fullscreen ? <Minimize aria-hidden="true" /> : <Maximize aria-hidden="true" />}
</Button>
{user && (onLogout ? <Menu.Root>
<Menu.Trigger className={`${buttonVariants({ variant: "ghost" })} thot-full-header__user`} disabled={logoutPending}>
<span className="truncate">{user.displayName ?? user.subject}</span><ChevronDown aria-hidden="true" className="size-4" />
</Menu.Trigger>
<Menu.Portal><Menu.Positioner side="bottom" align="end" sideOffset={4}>
<Menu.Popup className="thot-user-menu">
<Menu.Item className="thot-user-menu__item" onClick={() => { void logout(); }}>{t("Log out")}</Menu.Item>
</Menu.Popup>
</Menu.Positioner></Menu.Portal>
</Menu.Root> : <span className="thot-full-header__user truncate">{user.displayName ?? user.subject}</span>)}
</div>
{error && <p role="alert" className="thot-full-header__error">{t(error)}</p>}
</header>
);
}