77 lines
4.0 KiB
TypeScript
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>
|
|
);
|
|
}
|