import { useLayoutEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ChevronLeft, ChevronRight } from "lucide-react"; // The floating module-name pill in the top header. The label area is sized to the WIDEST // reachable module title (measured live, in the current language only — so dropping a locale or // gaining/losing a module just changes the input, nothing is hard-coded), which keeps the label // column a constant width: the name never shifts and the arrows stay put when paging between // modules. Carries the back/forward arrows that step cyclically through the active modules, plus // the accent dot (the former PageTitle "eyebrow" look, folded in here). export default function ModuleName({ label, labels, onPrev, onNext, canPrev, canNext, }: { label: string; // Every reachable module's title in the current language — drives the fixed label width. labels: string[]; onPrev: () => void; onNext: () => void; canPrev: boolean; canNext: boolean; }) { const { t } = useTranslation(); const labelRef = useRef(null); const canvasRef = useRef(null); const [labelW, setLabelW] = useState(); // Measure the widest label using the label span's own rendered font (respects font-scale, // tracking and the uppercase transform). Re-runs whenever the label set changes — i.e. on a // language switch or an account switch that changes which modules are reachable. useLayoutEffect(() => { const el = labelRef.current; if (!el || labels.length === 0) return; const cs = getComputedStyle(el); const canvas = canvasRef.current ?? (canvasRef.current = document.createElement("canvas")); const ctx = canvas.getContext("2d"); if (!ctx) return; ctx.font = `${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`; const ls = parseFloat(cs.letterSpacing) || 0; const upper = cs.textTransform === "uppercase"; let max = 0; for (const s of labels) { const txt = upper ? s.toUpperCase() : s; const w = ctx.measureText(txt).width + ls * txt.length; if (w > max) max = w; } setLabelW(Math.ceil(max) + 2); // eslint-disable-next-line react-hooks/exhaustive-deps }, [labels.join("")]); const arrow = "p-1 rounded-lg text-muted transition hover:text-fg hover:bg-card disabled:opacity-25 disabled:pointer-events-none"; return (
{label}
); }