fix(ui): simplify login and suppress pointer focus ring on locale select
This commit is contained in:
@@ -7,6 +7,7 @@ import { LoginPage } from "./LoginPage";
|
||||
import { clearAuthState, setAuthState } from "./authState";
|
||||
import * as authApi from "../api/auth";
|
||||
import { server } from "../test/msw";
|
||||
import { setLocale } from "../i18n";
|
||||
|
||||
const localConfig = { mode: "local", localLogin: true, oidcLogin: false } as const;
|
||||
const oidcConfig = { mode: "oidc", localLogin: false, oidcLogin: true } as const;
|
||||
@@ -26,9 +27,16 @@ const authenticated = {
|
||||
};
|
||||
|
||||
beforeEach(() => clearAuthState());
|
||||
afterEach(() => clearAuthState());
|
||||
afterEach(() => { clearAuthState(); setLocale("en"); });
|
||||
|
||||
describe("LoginPage", () => {
|
||||
test.each(["en", "it"])("keeps only the main sign-in title in %s", locale => {
|
||||
setLocale(locale);
|
||||
render(<LoginPage config={localConfig} onAuthenticated={vi.fn()} />);
|
||||
expect(screen.getByRole("heading", { name: locale === "it" ? "Accedi a ThothII" : "Sign in to ThothII" })).toBeInTheDocument();
|
||||
expect(screen.queryByText(locale === "it" ? "Accesso a ThothII" : "ThothII access")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(locale === "it" ? "Usa l’account della tua installazione per continuare." : "Use your installation account to continue.")).not.toBeInTheDocument();
|
||||
});
|
||||
test("shows an unchecked local Remember me control and clears the password after failure", async () => {
|
||||
let submittedPassword = "";
|
||||
server.use(http.post("/api/auth/local/login", async ({ request }) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useI18n } from "../i18n";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { FormEvent } from "react";
|
||||
import { AlertTriangle, ArrowRight, Eye, EyeOff, LockKeyhole } from "lucide-react";
|
||||
import { AlertTriangle, ArrowRight, Eye, EyeOff } from "lucide-react";
|
||||
import { ApiError } from "../api/client";
|
||||
import { beginOidcLogin, loginLocal } from "../api/auth";
|
||||
import type { AuthenticatedUser, AuthPublicConfig } from "../api/types";
|
||||
@@ -85,12 +85,7 @@ export function LoginPage({ config, onAuthenticated, onRetry }: LoginPageProps)
|
||||
|
||||
<section className="mx-auto w-full max-w-md rounded-2xl border border-border/80 bg-card p-6 shadow-md sm:p-8">
|
||||
<div className="mb-7">
|
||||
<div className="flex items-center gap-2 text-primary" aria-hidden="true">
|
||||
<LockKeyhole className="size-4" />
|
||||
<span className="thot-label text-primary">{t("ThothII access")}</span>
|
||||
</div>
|
||||
<h2 className="mt-3 font-heading text-3xl font-semibold tracking-tight">{t("Sign in to ThothII")}</h2>
|
||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">{t("Use your installation account to continue.")}</p>
|
||||
<h2 className="font-heading text-3xl font-semibold tracking-tight">{t("Sign in to ThothII")}</h2>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -267,6 +267,12 @@ body.thot-full-document { margin: 0; }
|
||||
color: oklch(var(--foreground));
|
||||
background: oklch(var(--card));
|
||||
}
|
||||
/* Native selects can match :focus-visible after a pointer click as well.
|
||||
Suppress only that outer ring; keyboard focus retains its visible outline. */
|
||||
.thot-full-header .thot-shell-select[data-pointer-focus="true"]:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.thot-shell-select {
|
||||
min-height: 2rem;
|
||||
max-width: 9rem;
|
||||
|
||||
@@ -12,6 +12,7 @@ export function FullHeader({ onLogout }: { onLogout?: () => Promise<void> }) {
|
||||
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");
|
||||
@@ -41,7 +42,12 @@ export function FullHeader({ onLogout }: { onLogout?: () => Promise<void> }) {
|
||||
<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} onChange={event => shell.changeLocale(event.target.value)}>
|
||||
<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")}
|
||||
|
||||
Reference in New Issue
Block a user