Symmetri i huvudet och i knapparna

Navigationen trycktes ihop mot högerkanten med ett tomrum efter
ordmärket. På smal skärm ligger den nu på egen rad och fördelas över
hela måttet — samma fördelning som sidfoten redan använder. Raden får
sin balans av marginalerna i stället för av att posterna klumpas ihop.
Mätt: noll pixlar kvar på bägge sidor. Portalens sex länkar blir på 360
px en tvåkolumnsindex, vilket läser sig som avsikt och inte som spill.

Knapparna var innehållsbreda, så REQUEST ACCOUNT blev dubbelt så bred
som LOGIN. Två knappar bredvid varandra där den ena är dubbelt så bred
läser sig som en huvudåtgärd och en eftertanke, oavsett vad de heter —
och det är inte förhållandet mellan dem. Rutnätet ger dem samma bredd;
vikten skiljs av fyllning mot kontur, som resten av systemet gör.

Lika bredd räckte inte: REQUEST ACCOUNT bryts till två rader och
knapparna blev då olika HÖGA, vilket är samma obalans en rad ned. Bägge
fyller nu sin cell. Mätt: 163x57 mot 163x57.

---- Och ett test som slutade mäta -------------------------------------

Namntestet från förra ändringen matchade markup-formen, inte
invarianten. När <Link> fick en className hittade mönstret noll knappar
— testet slutade mäta i stället för att falla. Det enda som avslöjade
det var kravet att det ska hitta NÅGOT, och det är därför kravet står
där i stället för att tas för givet.

Mutationstestat igen efter rättelsen: bägge testen faller när
"Customer login" läggs tillbaka.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-06 13:54:37 +00:00
parent 0cc4f1578c
commit 6790b60182
4 changed files with 47 additions and 17 deletions
+15 -1
View File
@@ -303,12 +303,21 @@ export function Knapp({
onClick, onClick,
variant = "primar", variant = "primar",
disabled, disabled,
bred = false,
type = "button", type = "button",
}: { }: {
children: ReactNode; children: ReactNode;
onClick?: () => void; onClick?: () => void;
variant?: "primar" | "sekundar"; variant?: "primar" | "sekundar";
disabled?: boolean; disabled?: boolean;
/**
* Fyller sin behållare i stället för att vara innehållsbred.
*
* För knappar som står bredvid varandra: två olika breda knappar läser
* sig som en huvudåtgärd och en eftertanke, oavsett vad de heter. Med
* lika bredd skiljs vikten av fyllning mot kontur i stället.
*/
bred?: boolean;
type?: "button" | "submit"; type?: "button" | "submit";
}) { }) {
const primär = variant === "primar"; const primär = variant === "primar";
@@ -317,7 +326,12 @@ export function Knapp({
type={type} type={type}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
className="border px-4 py-2 text-[13px] font-semibold uppercase tracking-[0.08em] disabled:opacity-40" // `h-full` och inte bara `w-full`: lika breda knappar där den ena
// texten bryts till två rader blir olika HÖGA, vilket är samma
// obalans en rad ned. Bägge fyller sin cell.
className={`border px-4 py-2 text-[13px] font-semibold uppercase tracking-[0.08em] disabled:opacity-40${
bred ? " h-full w-full" : ""
}`}
style={{ style={{
background: primär ? FARG.graphite : FARG.white, background: primär ? FARG.graphite : FARG.white,
color: primär ? FARG.white : FARG.graphite, color: primär ? FARG.white : FARG.graphite,
@@ -416,7 +416,13 @@ describe("inloggningen heter samma sak överallt", () => {
const namn = ram.match(/\{ till: "\/alva\/logga-in", text: "([^"]+)" \}/)?.[1]; const namn = ram.match(/\{ till: "\/alva\/logga-in", text: "([^"]+)" \}/)?.[1];
expect(namn).toBeTruthy(); expect(namn).toBeTruthy();
// Varje länk till inloggningen på startsidan bär samma text. // Varje länk till inloggningen på startsidan bär samma text.
const knappar = [...start.matchAll(/to="\/alva\/logga-in">\s*<Knapp[^>]*>([^<]+)<\/Knapp>/g)]; //
// Mönstret tål attribut på länken. Första versionen gjorde inte det,
// och när <Link> fick en className matchade den noll knappar — testet
// slutade mäta i stället för att falla. Det enda som avslöjade det var
// kravet nedan på att den ska hitta NÅGOT, vilket är därför det står
// där och inte som en självklarhet.
const knappar = [...start.matchAll(/to="\/alva\/logga-in"[^>]*>\s*<Knapp[^>]*>([^<]+)<\/Knapp>/g)];
expect(knappar.length).toBeGreaterThan(0); expect(knappar.length).toBeGreaterThan(0);
for (const [, text] of knappar) expect(text.trim()).toBe(namn); for (const [, text] of knappar) expect(text.trim()).toBe(namn);
}); });
+10 -5
View File
@@ -49,11 +49,16 @@ export function Ram({ children, portal = false }: { children: ReactNode; portal?
</span> </span>
</Link> </Link>
{/* Portalens fem länkar fick inte plats på en telefon och sköt {/* Portalens länkar fick inte plats på en telefon och sköt ut
ut dokumentet i sidled i stället för att brytas. Raden bryts dokumentet i sidled i stället för att brytas. Raden bryts nu.
nu, med tätare mellanrum på smal skärm. */}
<nav aria-label={portal ? "Portal" : "Site"}> På smal skärm ligger navigationen dessutom på EGEN rad och
<ul className="flex flex-wrap gap-2 sm:gap-6"> fördelas över hela måttet, i stället för att tryckas ihop mot
höger kant med ett tomrum efter ordmärket. Det är samma
fördelning som sidfoten redan använder — raden får sin balans
av marginalerna, inte av att posterna klumpas ihop. */}
<nav aria-label={portal ? "Portal" : "Site"} className="w-full sm:w-auto">
<ul className="flex flex-wrap justify-between gap-x-4 gap-y-2 sm:justify-end sm:gap-6">
{lankar.map((l) => { {lankar.map((l) => {
const aktiv = plats.pathname === l.till; const aktiv = plats.pathname === l.till;
return ( return (
+15 -10
View File
@@ -51,12 +51,17 @@ export default function Start() {
{ALVA.definition} {ALVA.definition}
</p> </p>
<div className="mt-10 flex gap-4"> {/* Lika breda, inte innehållsbreda. Två knappar bredvid varandra
<Link to="/alva/ansokan"> där den ena är dubbelt så bred som den andra läser sig som en
<Knapp>Request account</Knapp> huvudåtgärd och en eftertanke — och det är inte förhållandet
mellan dem. Rutnätet ger dem samma bredd; vikten skiljs av
fyllning mot kontur, som resten av systemet gör. */}
<div className="mt-10 grid max-w-[420px] grid-cols-2 gap-4">
<Link to="/alva/ansokan" className="block h-full">
<Knapp bred>Request account</Knapp>
</Link> </Link>
<Link to="/alva/logga-in"> <Link to="/alva/logga-in" className="block h-full">
<Knapp variant="sekundar">Login</Knapp> <Knapp bred variant="sekundar">Login</Knapp>
</Link> </Link>
</div> </div>
</div> </div>
@@ -278,12 +283,12 @@ export default function Start() {
<div className="font-mono text-[11px]" style={{ color: FARG.steel }}> <div className="font-mono text-[11px]" style={{ color: FARG.steel }}>
{PLATTFORMSVERSION} {PLATTFORMSVERSION}
</div> </div>
<div className="flex gap-4"> <div className="grid max-w-[420px] grid-cols-2 gap-4">
<Link to="/alva/ansokan"> <Link to="/alva/ansokan" className="block h-full">
<Knapp>Request account</Knapp> <Knapp bred>Request account</Knapp>
</Link> </Link>
<Link to="/alva/logga-in"> <Link to="/alva/logga-in" className="block h-full">
<Knapp variant="sekundar">Login</Knapp> <Knapp bred variant="sekundar">Login</Knapp>
</Link> </Link>
</div> </div>
</div> </div>