Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
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 [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 className="text-primary">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} 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user