Ein Raster fuer alle acht Kacheln, nicht drei nebeneinandergestellte
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m24s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m8s

Gleich breit und gleich weit auseinander werden Kacheln erst, wenn sie
demselben Raster angehoeren. Die beiden Anlaeufe davor stellten drei eigene
Raster nebeneinander: zuerst mit gleichem Anteil je Gruppe (fuenf Kacheln
gequetscht, eine mit demselben Platz), dann mit einem Anteil nach
Kachelzahl. Da stimmten die Breiten fast — aber eben nur fast: eine Gruppe
mit fuenf Kacheln hat vier Abstaende in sich, eine mit einer keinen, und
dieser Unterschied verteilt sich auf die Breiten.

Jetzt acht gleiche Spalten und ein einziger Abstandswert. Die Ueberschriften
sitzen in Zeile 1 und ueberspannen die Spalten ihrer Gruppe (5, 2, 1), die
Kacheln in Zeile 2. Beide Zeilen entstehen aus derselben DOM-Reihenfolge —
Ueberschrift, ihre Kacheln, naechste Ueberschrift —, die fuer Vorlesegeraete
die richtige ist; die ausdrueckliche Zeilenangabe sortiert sie fuers Auge.

Die Spannweite steht ausgeschrieben in einer Tabelle und nicht als
span var(--kacheln): Tailwind erzeugt nur Klassen, die als Zeichenkette im
Quelltext stehen, und eine berechnete Spannweite waere zur Bauzeit nicht zu
sehen.

Unterhalb der grossen Breite keine Zeilenangabe: dann fliesst alles der
Reihe nach, Ueberschrift ueber die volle Breite, ihre Kacheln zu zweit
darunter.
This commit is contained in:
2026-09-16 22:54:07 +02:00
parent c46f18c37a
commit bf7f59a959

View File

@@ -1,6 +1,6 @@
import { ChevronRight } from "lucide-react"; import { ChevronRight } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { Suspense, type CSSProperties } from "react"; import { Fragment, Suspense } from "react";
import { AnstehendFilter } from "@/components/dashboard/AnstehendFilter"; import { AnstehendFilter } from "@/components/dashboard/AnstehendFilter";
import { AnstehendListe, type AnstehendEintrag } from "@/components/dashboard/AnstehendListe"; import { AnstehendListe, type AnstehendEintrag } from "@/components/dashboard/AnstehendListe";
import { DraftsCard } from "@/components/dashboard/DraftsCard"; import { DraftsCard } from "@/components/dashboard/DraftsCard";
@@ -31,6 +31,28 @@ const TONE: Record<string, { text: string; bar: string }> = {
/** Eine Kennzahl auf der Übersicht. */ /** Eine Kennzahl auf der Übersicht. */
type Kachel = { label: string; value: string | number; tone: keyof typeof TONE; href: string }; type Kachel = { label: string; value: string | number; tone: keyof typeof TONE; href: string };
/**
* Wie viele Spalten die Überschrift einer Gruppe überspannt — nach der Zahl
* ihrer Kacheln.
*
* Ausgeschrieben und nicht als `span var(--kacheln)`: Tailwind erzeugt nur
* Klassen, die als Zeichenkette im Quelltext stehen, und eine aus einer
* Variablen berechnete Spannweite wäre zur Bauzeit nicht zu sehen. Die
* Tabelle deckt jede mögliche Gruppengrösse bis zur Rasterbreite ab; ein
* Zugriff daneben gäbe `undefined` und damit eine Überschrift über einer
* einzigen Spalte — auffällig genug, um bemerkt zu werden.
*/
const SPALTEN_SPAN: Record<number, string> = {
1: "xl:col-span-1",
2: "xl:col-span-2",
3: "xl:col-span-3",
4: "xl:col-span-4",
5: "xl:col-span-5",
6: "xl:col-span-6",
7: "xl:col-span-7",
8: "xl:col-span-8",
};
// Die Tabelle, die hier stand, ist nach lib/colors.ts gewandert: Punkt und // Die Tabelle, die hier stand, ist nach lib/colors.ts gewandert: Punkt und
// Chip derselben Zeile kamen aus zwei getrennten Verzeichnissen, und eines // Chip derselben Zeile kamen aus zwei getrennten Verzeichnissen, und eines
// davon war das falsche. Jetzt leiten beide aus EVENT_CATEGORY ab und koennen // davon war das falsche. Jetzt leiten beide aus EVENT_CATEGORY ab und koennen
@@ -224,47 +246,56 @@ export default async function DashboardPage({
return ( return (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{drafts.length > 0 && <DraftsCard drafts={drafts} />} {drafts.length > 0 && <DraftsCard drafts={drafts} />}
{/* ── Eine Reihe, drei Gruppen ────────────────────────────────── {/* ── Ein Raster, nicht drei nebeneinandergestellte ──────────────
Ab der grossen Breite stehen alle acht Kacheln nebeneinander, die Alle acht Kacheln liegen in **einem** Raster mit acht gleichen
Überschrift jeweils über den ihren. Spalten und einem einzigen Abstandswert. Nur so sind sie gleich
breit und gleich weit auseinander — unabhängig davon, zu welcher
Gruppe sie gehören.
Die Breite einer Gruppe folgt der **Zahl ihrer Kacheln**: fünf, zwei, Die beiden Anläufe davor stellten drei eigene Raster nebeneinander.
eine. Der erste Versuch gab jeder Gruppe denselben Anteil (`flex-1`) Das ging zweimal schief: erst bekam jede Gruppe ein Drittel der
— dann quetschten sich fünf Kacheln in ein Drittel und brachen um, Breite (fünf Kacheln gequetscht, eine mit demselben Platz), dann
während die einzelne rechts dasselbe Drittel für sich hatte. einen Anteil nach Kachelzahl — da stimmten die Breiten fast, aber
eben nur fast: eine Gruppe mit fünf Kacheln hat vier Abstände in
sich, eine mit einer keinen, und dieser Unterschied verteilt sich
auf die Kachelbreiten. Gleichmässig wird es erst, wenn alle Kacheln
demselben Raster angehören.
Die Zahl geht als CSS-Variable in beide Regeln, statt als Die Überschriften sitzen in Zeile 1 und überspannen die Spalten
ausgeschriebene Klasse je Gruppe. Käme eine neunte Kachel dazu, ihrer Gruppe, die Kacheln in Zeile 2. Beide Zeilen entstehen aus
stimmte die Aufteilung von selbst; mit `xl:flex-[5]` von Hand müsste derselben DOM-Reihenfolge (Überschrift, ihre Kacheln, nächste
jemand daran denken, und es fiele nicht auf, wenn er es nicht tut. Überschrift …) — die ist für Vorlesegeräte die richtige, und die
ausdrückliche Zeilenangabe sortiert sie fürs Auge.
Darunter untereinander, je Gruppe zwei Kacheln nebeneinander — in Unterhalb der grossen Breite gibt es keine Zeilenangabe: dann fliesst
einer Reihe wären acht Kacheln auf einem Laptop unlesbar schmal. */} alles der Reihe nach, die Überschrift über die volle Breite und ihre
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:gap-5"> Kacheln zu zweit darunter. Acht nebeneinander wären auf einem Laptop
unlesbar schmal. */}
<div className="grid grid-cols-2 gap-x-3 gap-y-3 xl:grid-cols-8 xl:gap-y-1.5">
{kpiGruppen.map((gruppe) => ( {kpiGruppen.map((gruppe) => (
<section <Fragment key={gruppe.titel}>
key={gruppe.titel} <h2
className="min-w-0 xl:[flex:var(--kacheln)_1_0%]" className={`mt-2 self-end text-[11px] font-bold uppercase tracking-wider text-ink-muted
style={{ "--kacheln": gruppe.kacheln.length } as CSSProperties} col-span-2 xl:mt-0 xl:[grid-row:1] ${SPALTEN_SPAN[gruppe.kacheln.length]}`}
> >
<h2 className="mb-1.5 text-[11px] font-bold uppercase tracking-wider text-ink-muted">{gruppe.titel}</h2> {gruppe.titel}
<div className="grid grid-cols-2 gap-3 xl:[grid-template-columns:repeat(var(--kacheln),minmax(0,1fr))]"> </h2>
{gruppe.kacheln.map((kpi) => ( {gruppe.kacheln.map((kpi) => (
<Link <Link
key={kpi.label} key={kpi.label}
href={kpi.href} href={kpi.href}
className={`${CARD_CLASS} group relative overflow-hidden p-4 pl-5 transition-shadow hover:shadow-[var(--shadow-card-hover)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500`} className={`${CARD_CLASS} group relative overflow-hidden p-4 pl-5 transition-shadow xl:[grid-row:2] hover:shadow-[var(--shadow-card-hover)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500`}
> >
<span className={`absolute inset-y-0 left-0 w-1 ${TONE[kpi.tone].bar}`} aria-hidden /> <span className={`absolute inset-y-0 left-0 w-1 ${TONE[kpi.tone].bar}`} aria-hidden />
{/* Number first in the visual order: it is what the eye is {/* Number first in the visual order: it is what the eye is
looking for, and the label only qualifies it. */} looking for, and the label only qualifies it.
{/* Kleiner, sobald alle acht in einer Reihe stehen: dort
bleiben je Kachel gut 110 px, und „748.4" braucht in Kleiner, sobald alle acht in einer Reihe stehen: dort
30 px Schriftgrad mehr davon, als nach dem Innenabstand bleiben je Kachel gut 110 px, und „748.4" braucht in 30 px
übrig ist. Die Kachel hat `overflow-hidden` — die Zahl Schriftgrad mehr davon, als nach dem Innenabstand übrig
wäre also nicht zu breit, sondern abgeschnitten, und ist. Die Kachel hat `overflow-hidden` — die Zahl wäre also
eine abgeschnittene Kennzahl ist schlimmer als eine nicht zu breit, sondern abgeschnitten, und eine
kleine. Darunter stehen zwei je Reihe, da ist Platz. */} abgeschnittene Kennzahl ist schlimmer als eine kleine. */}
<div className={`text-3xl font-extrabold leading-none tabular-nums xl:text-2xl ${TONE[kpi.tone].text}`}> <div className={`text-3xl font-extrabold leading-none tabular-nums xl:text-2xl ${TONE[kpi.tone].text}`}>
{kpi.value} {kpi.value}
</div> </div>
@@ -274,8 +305,7 @@ export default async function DashboardPage({
</div> </div>
</Link> </Link>
))} ))}
</div> </Fragment>
</section>
))} ))}
</div> </div>