diff --git a/frontend/src/components/LanguageSwitcher.tsx b/frontend/src/components/LanguageSwitcher.tsx
index 51bb511c..e20db88b 100644
--- a/frontend/src/components/LanguageSwitcher.tsx
+++ b/frontend/src/components/LanguageSwitcher.tsx
@@ -4,6 +4,7 @@ import { LANGUAGES, type LangCode } from "../i18n";
import { PopoverItem, PopoverSurface } from "./ui/AnchoredPopover";
import { useAnchoredPopover } from "./ui/anchoredPopoverHooks";
import { Pressable } from "./ui/Pressable";
+import { RAIL_GLYPH, RAIL_ROW } from "./ui/NavRail";
import { SURFACE } from "./ui/surfaces";
// Compact language picker (globe + current code). Presentational: the parent decides what
@@ -52,20 +53,23 @@ export default function LanguageSwitcher({
layer
className={
rail
- ? "relative p-2 rounded-lg text-muted hover:text-fg transition touch-target"
+ ? `${RAIL_ROW} h-9 text-muted hover:text-fg`
: "flex h-7 items-center gap-1.5 px-2.5 rounded-lg text-sm text-muted hover:text-fg transition touch-target"
}
>
-
{rail ? (
- /* Current-language badge (HU/EN) so the active language reads at a glance without
- opening the menu. The ring matches the page background to detach it from the icon. */
-
- {current.code.toUpperCase()}
+ /* A rail row (F32), with the current-language badge (HU/EN) on the glyph's corner so the
+ active language reads at a glance. The ring matches the page to detach it from the icon. */
+
+
+
+ {current.code.toUpperCase()}
+
) : (
- {current.code}
+
)}
+ {rail ? null : {current.code}}
diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx
index 97288c0e..e0967a64 100644
--- a/frontend/src/components/NavSidebar.tsx
+++ b/frontend/src/components/NavSidebar.tsx
@@ -20,14 +20,20 @@ import {
} from "../lib/modules";
import { useScrollFade } from "../lib/useScrollFade";
import { type LangCode } from "../i18n";
-import { IconButton } from "./ui/IconButton";
import AvatarImg from "./ui/Avatar";
import LanguageSwitcher from "./LanguageSwitcher";
import { PopoverSurface } from "./ui/AnchoredPopover";
import { useAnchoredPopover } from "./ui/anchoredPopoverHooks";
import { Pressable } from "./ui/Pressable";
import { SURFACE } from "./ui/surfaces";
-import { NavRail, NavRailDivider, NavRailItem, NavRailList, RAIL_ROW } from "./ui/NavRail";
+import {
+ NavRail,
+ NavRailAction,
+ NavRailDivider,
+ NavRailItem,
+ NavRailList,
+ RAIL_ROW,
+} from "./ui/NavRail";
import { usePrefs } from "./prefsContext";
// Primary app navigation: a thin icon-only left rail. The modules used to hide under the avatar
@@ -196,24 +202,10 @@ export default function NavSidebar({
-
+
-
-
-
-
-
-
+
+
({ hintsEnabled: () => true, subscribeHints: () => () => {} }));
@@ -45,3 +47,20 @@ describe("NavRailItem", () => {
expect(row.getAttribute("aria-current")).toBeNull();
});
});
+
+// F32: the rail's footer (language, About, Sign out) drew 32 and 36 squares with 16 and 20px glyphs
+// beside the 36-tall full-width rows, so its spacing and glyphs broke the column. An action is a row too.
+describe("NavRailAction", () => {
+ it("is a named rail row, not a toolbar icon button", () => {
+ render( {}} />);
+ const row = screen.getByRole("button", { name: "About" });
+ expect(row.className).toContain("h-9");
+ expect(row.className).toContain("w-full");
+ expect(row.querySelector("svg")?.getAttribute("class")).toContain("size-4.5");
+ });
+
+ it("the nav rail holds no toolbar icon button", () => {
+ const src = readFileSync(resolve(__dirname, "../NavSidebar.tsx"), "utf8");
+ expect(src).not.toMatch(/]/);
+ });
+});
diff --git a/frontend/src/components/ui/NavRail.tsx b/frontend/src/components/ui/NavRail.tsx
index 97fef293..f3c4c67b 100644
--- a/frontend/src/components/ui/NavRail.tsx
+++ b/frontend/src/components/ui/NavRail.tsx
@@ -62,6 +62,9 @@ export function NavRailDivider() {
return ;
}
+/** The glyph of every rail row: a page, an action, the language switcher. */
+export const RAIL_GLYPH = "size-4.5";
+
/** The shape of one rail row, for a row that is not a page (the account button). */
export const RAIL_ROW =
"relative flex w-full flex-col items-center justify-center gap-0.5 rounded-lg transition touch-target";
@@ -112,7 +115,7 @@ export function NavRailItem({
/>
)}
-
+
{shown && (
{badge > 9 ? "9+" : badge}
@@ -128,3 +131,28 @@ export function NavRailItem({
);
}
+
+/** A rail row that acts instead of navigating (About, Sign out): the page rows' geometry and glyph, so
+ * the column keeps one rhythm (F32: toolbar icon buttons here drew 32 squares with 16px glyphs). */
+export function NavRailAction({
+ icon: Icon,
+ label,
+ onClick,
+}: {
+ icon: LucideIcon;
+ /** The tooltip and the accessible name. */
+ label: string;
+ onClick: () => void;
+}) {
+ return (
+
+
+
+
+
+ );
+}