467 Stellen. Nicht durch einen anderen Strich ersetzt, sondern durch das
Satzzeichen, das an der jeweiligen Stelle richtig ist: Doppelpunkt, wo eine
Erlaeuterung folgt; Komma, wo der Satz weiterlaeuft; Punkt, wo zwei
Hauptsaetze aneinanderstiessen. Ein Halbgeviertstrich waere nur derselbe
Griff mit kuerzerem Strich gewesen.
Regelbasiert ersetzt und danach nachgeprueft, weil eine Regel allein nicht
reicht. Gefunden und einzeln korrigiert:
- Eingeschobene Saetze mit Strichpaar. Nur der oeffnende Strich passte ins
Muster, der schliessende blieb als "—," stehen. Drei Stellen, jetzt mit
Klammern oder als eigener Satz.
- Doppelte Doppelpunkte, wo beide Striche eines Paars zu Doppelpunkten wurden
("Globale Regeln: Schrift, Groessen, Abstaende: kommen aus HiveGrid").
Sieben Stellen in Doku und Oberflaeche, jetzt in Klammern.
- Pfade, denen ein Komma statt eines Doppelpunkts folgte.
- Ausgerichtete Kommentartabellen, in denen der Strich eine Spalte war.
Nicht angefasst: drei Geviertstriche, die keine Prosa sind, sondern
Tabellenplatzhalter fuer "kein Wert" (Trichter, Auftraege, Kunden). Dort ist
der Strich die Angabe selbst; ihn zu entfernen liesse leere Zellen zurueck.
tsc, lint, build, verify:carbon und verify:layout laufen sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
150 lines
5.4 KiB
TypeScript
150 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import {
|
|
FeatherDownload,
|
|
FeatherEyeOff,
|
|
FeatherInfo,
|
|
FeatherMoreHorizontal,
|
|
} from "@subframe/core";
|
|
import { useWidgets } from "../../context/WidgetContext";
|
|
import { useToast } from "../../context/ToastContext";
|
|
|
|
/**
|
|
* Rahmen für jede Visualisierung (COMP-09).
|
|
*
|
|
* Enthält die einzige "echte" Interaktionslogik des Prototyps:
|
|
* Über das ⋯-Menü lässt sich die Karte ausblenden; sie taucht dann in der
|
|
* Filterleiste zum Wiedereinblenden auf. Der Zustand ist persistiert.
|
|
*/
|
|
export function ChartCard({
|
|
id,
|
|
titel,
|
|
untertitel,
|
|
hinweis,
|
|
aktion,
|
|
legende,
|
|
children,
|
|
className = "",
|
|
}: {
|
|
/** Stabile ID: Schlüssel für Ausblenden/Einblenden. */
|
|
id: string;
|
|
titel: string;
|
|
untertitel?: string;
|
|
/** Erklärt die Kennzahl; erscheint als Tooltip am (i). */
|
|
hinweis?: string;
|
|
aktion?: React.ReactNode;
|
|
legende?: React.ReactNode;
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}) {
|
|
const { istSichtbar, verstecke } = useWidgets();
|
|
const { zeigeToast } = useToast();
|
|
const [menuOffen, setMenuOffen] = React.useState(false);
|
|
const menu = React.useRef<HTMLDivElement>(null);
|
|
const ausloeser = React.useRef<HTMLButtonElement>(null);
|
|
|
|
React.useEffect(() => {
|
|
if (!menuOffen) return;
|
|
const schliesse = (event: MouseEvent) => {
|
|
if (!menu.current?.contains(event.target as Node)) setMenuOffen(false);
|
|
};
|
|
// Escape schliesst und gibt den Fokus an den Ausloeser zurueck, sonst
|
|
// landet die Tastaturbedienung nach dem Schliessen am Seitenanfang.
|
|
const taste = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") {
|
|
setMenuOffen(false);
|
|
ausloeser.current?.focus();
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", schliesse);
|
|
document.addEventListener("keydown", taste);
|
|
return () => {
|
|
document.removeEventListener("mousedown", schliesse);
|
|
document.removeEventListener("keydown", taste);
|
|
};
|
|
}, [menuOffen]);
|
|
|
|
if (!istSichtbar(id)) return null;
|
|
|
|
return (
|
|
<section
|
|
className={`flex flex-col overflow-hidden rounded-lg border border-solid border-border-default bg-white shadow-sm ${className}`}
|
|
>
|
|
<header className="flex flex-none items-start justify-between gap-3 px-4 pb-3 pt-4">
|
|
<div className="flex min-w-0 flex-col">
|
|
<div className="flex items-center gap-1.5">
|
|
<h2 className="min-w-0 break-words text-[14px] font-semibold leading-tight text-text-neutral-primary">
|
|
{titel}
|
|
</h2>
|
|
{hinweis ? (
|
|
<span
|
|
title={hinweis}
|
|
className="flex h-4 w-4 flex-none cursor-help items-center justify-center text-text-neutral-subtle hover:text-text-neutral-secondary"
|
|
>
|
|
<FeatherInfo className="text-[13px]" />
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
{untertitel ? (
|
|
<p className="line-clamp-2 break-words text-[12px] leading-snug text-text-neutral-tertiary">
|
|
{untertitel}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="dv-no-print relative flex flex-none items-center gap-1" ref={menu}>
|
|
{aktion}
|
|
<button
|
|
ref={ausloeser}
|
|
onClick={() => setMenuOffen((wert) => !wert)}
|
|
aria-label={`Optionen für ${titel}`}
|
|
aria-haspopup="menu"
|
|
aria-expanded={menuOffen}
|
|
className="flex h-8 w-8 items-center justify-center rounded-md text-text-neutral-tertiary hover:bg-neutral-100 hover:text-text-neutral-primary"
|
|
>
|
|
<FeatherMoreHorizontal className="text-[16px]" />
|
|
</button>
|
|
|
|
{menuOffen ? (
|
|
<div
|
|
role="menu"
|
|
className="absolute right-0 top-9 z-40 flex w-52 flex-col rounded-lg border border-solid border-border-default bg-white py-1 shadow-lg"
|
|
>
|
|
<button
|
|
onClick={() => {
|
|
verstecke(id, titel);
|
|
setMenuOffen(false);
|
|
}}
|
|
className="flex items-center gap-2 px-3 py-2 text-left text-[13px] text-text-neutral-primary hover:bg-neutral-50"
|
|
>
|
|
<FeatherEyeOff className="text-[14px] text-text-neutral-tertiary" />
|
|
Widget ausblenden
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
zeigeToast("Als Bild vorgemerkt", `${titel}: Export folgt in der Umsetzung`);
|
|
setMenuOffen(false);
|
|
}}
|
|
className="flex items-center gap-2 px-3 py-2 text-left text-[13px] text-text-neutral-primary hover:bg-neutral-50"
|
|
>
|
|
<FeatherDownload className="text-[14px] text-text-neutral-tertiary" />
|
|
Als Bild exportieren
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</header>
|
|
|
|
{legende ? <div className="flex-none px-4 pb-2">{legende}</div> : null}
|
|
|
|
{/* justify-center: Karten einer Zeile sind gleich hoch, aber nicht gleich
|
|
voll. Eine Karte mit einem Balken neben einer mit einem Diagramm über
|
|
400 px bekommt den Überschuss oben und unten je zur Hälfte, statt ihn
|
|
unten abzuladen. Der Inhalt behält seine natürliche Höhe, die Karte
|
|
wächst weiter mit, wenn mehr hineinkommt. */}
|
|
<div className="flex min-h-0 grow flex-col justify-center px-4 pb-4">{children}</div>
|
|
</section>
|
|
);
|
|
}
|