Der Link im Zell-Dialog der Zuweisungsmatrix fuehrte nur auf den Kopf der Schluesselseite; die Dry-Run-Karte liegt weit unten, und wer aus der Matrix kommt, will genau dorthin. Die Karte traegt jetzt id="dry-run" (Karte nimmt dafuer eine optionale id an), der Link zeigt auf /schluessel/<id>#dry-run. scroll-mt haelt die Karte unter der festen Kopfleiste der schmalen Ansicht. E2E: der Matrix-Test klickt den Link und prueft Adresse und Sichtbarkeit der Karte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
2.1 KiB
TypeScript
81 lines
2.1 KiB
TypeScript
import { clsx } from "clsx";
|
||
import type { ReactNode } from "react";
|
||
|
||
/**
|
||
* Karte: --surface, 1 px Rahmen, 16 px Radius, 22–24 px Innenabstand (#002).
|
||
* Mit `titel` entsteht ein Kopf mit optionalen Aktionen rechts.
|
||
*/
|
||
export function Karte({
|
||
titel,
|
||
untertitel,
|
||
aktionen,
|
||
children,
|
||
className,
|
||
innen = "normal",
|
||
as: Element = "section",
|
||
id,
|
||
}: {
|
||
titel?: ReactNode;
|
||
untertitel?: ReactNode;
|
||
aktionen?: ReactNode;
|
||
children?: ReactNode;
|
||
className?: string;
|
||
/** „ohne“ für Inhalte, die bis an den Rand reichen (z. B. Tabellen mit eigenem Abstand). */
|
||
innen?: "normal" | "ohne";
|
||
as?: "section" | "div" | "article";
|
||
/** Sprungziel für Links von anderen Seiten (z. B. `#dry-run`, #032). */
|
||
id?: string;
|
||
}) {
|
||
return (
|
||
<Element
|
||
id={id}
|
||
className={clsx(
|
||
"border-border bg-surface min-w-0 rounded-karte border",
|
||
innen === "normal" && "p-5 sm:p-6",
|
||
className,
|
||
)}
|
||
>
|
||
{(titel || aktionen) && (
|
||
<KartenKopf titel={titel} untertitel={untertitel} aktionen={aktionen} rand={innen === "ohne"} />
|
||
)}
|
||
{children}
|
||
</Element>
|
||
);
|
||
}
|
||
|
||
function KartenKopf({
|
||
titel,
|
||
untertitel,
|
||
aktionen,
|
||
rand,
|
||
}: {
|
||
titel?: ReactNode;
|
||
untertitel?: ReactNode;
|
||
aktionen?: ReactNode;
|
||
rand: boolean;
|
||
}) {
|
||
return (
|
||
<header
|
||
className={clsx(
|
||
"mb-4 flex flex-wrap items-center justify-between gap-x-4 gap-y-2",
|
||
rand && "px-5 pt-5 sm:px-6 sm:pt-6",
|
||
)}
|
||
>
|
||
<div className="min-w-0">
|
||
{titel && <h2 className="text-text text-[17px] leading-snug font-semibold">{titel}</h2>}
|
||
{untertitel && <p className="text-muted mt-0.5 text-sm">{untertitel}</p>}
|
||
</div>
|
||
{aktionen && <div className="flex shrink-0 flex-wrap items-center gap-2">{aktionen}</div>}
|
||
</header>
|
||
);
|
||
}
|
||
|
||
/** Innere Box (--sidebar, 12 px Radius) – für Einträge innerhalb einer Karte. */
|
||
export function InnereBox({ children, className }: { children: ReactNode; className?: string }) {
|
||
return (
|
||
<div className={clsx("border-border bg-sidebar rounded-box min-w-0 border p-4", className)}>
|
||
{children}
|
||
</div>
|
||
);
|
||
}
|