Files
audi-app/design-system/src/components/Sheet/ActionSheet.tsx
T
tobias b1411625aa Teilen-Blatt, HIG-Blaetter, geteilte Ansichtseinstellungen (2026.8.31.1)
Share-Erweiterung: ein PDF aus Mail landet ueber Teilen -> DataMetric360 als
Tankbeleg. Alles Native liegt versioniert unter companion-app/native/, weil
ios/ gitignored ist und npx cap add ios es sonst wieder frisst;
scripts/ios-teilen-einrichten.mjs haengt es bei jedem Bau ins Xcode-Projekt
und ist in ios-signieren.sh eingehaengt. Der pbxproj-Teil ist auf keinem Mac
erprobt - er bricht vor dem Schreiben ab, und docs/SHARE_EXTENSION.md
beschreibt dieselben Handgriffe fuer Xcode.

"In den Kalender uebernehmen" ging in der App nie: WKWebView ignoriert das
download-Attribut, der Klick lief ins Leere. Auf dem Geraet jetzt
@capacitor/filesystem plus @capacitor/share.

useTheme/useBoden legten je Aufrufer eigenen Zustand an - fuenf Kopien. Der
Schalter aenderte nur seine eigene, data-theme in App.tsx blieb stehen, die
Tag/Nacht-Umschaltung wirkte erst nach einem Neustart. Jetzt ein Speicher je
Einstellung ueber useSyncExternalStore. Zwei Regressionstests, gegen den
alten Stand als fehlschlagend nachgewiesen.

Alle Popups nach Apple HIG: neue Bausteine Sheet und ActionSheet, blickdicht
und mit abgedunkeltem Schleier, in beiden Codebasen. Popup portalierte nach
document.body - ausserhalb von [data-theme] - und war deshalb im Tagmodus
fast unsichtbar; Portal zielt jetzt auf .ads-root.

ImagePlaceholder merkte sich "fehlgeschlagen" ohne die Adresse: nach einem
leeren Galerieplatz zeigten auch die mit Foto nur noch den Platzhalter.
Vier Regressionstests, ebenfalls gegen den alten Stand geprueft.

Bildadressen tragen jetzt einen Cache-Brecher - HA liefert /local/ mit 31
Tagen Cache-Vorgabe aus, im Panel eingefuegte Radfotos erschienen in der App
deshalb nicht.

uebersichtsbild: Panel speichert den Namen, die App verglich gegen den
Dateinamen - der Vergleich traf nie zu. Kanonisch ist der Name.

Weiter: Navigationspfeil statt gleichschenkligem Dreieck auf der Streckenlinie
(die Kerbe unterscheidet Kopf und Ende), CI-Symbol tour-s als Fahrten-Icon,
Datumsfelder zeigen ihren Wert ohne erstes Antippen, feste Beispielnamen im
Setup, Kopf-Gegengewicht fuer mittige Titel, Standort-Blatt mit festem
Fussabstand, NSLocationWhenInUseUsageDescription, watchPosition fuer die
Live-Ortung, Art-Pille wieder als Knopf, Zwischenablage fuer Tankbelege.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 14:24:52 +02:00

99 lines
3.0 KiB
TypeScript

import * as React from "react";
import { createPortal } from "react-dom";
import { sheetPortalTarget, useEscape } from "./Sheet";
/**
* Action sheet - the iOS pattern for a short set of choices the user asked for.
*
* Two groups, exactly as Apple describes them: the choices in one rounded
* card, "Cancel" alone in a second one below it with a gap between. The gap is
* not decoration - it is what makes Cancel unmistakably the way out rather
* than one more option in the list.
*
* Deliberately identical in geometry to `.sheet` in the panel's
* audi-dashboard.css, which already implemented this pattern: 10px side
* margins, 16px corners, 52px minimum row height, one hairline between rows,
* opaque surface. Two codebases, one look.
*
* Use `Sheet` instead when the content is a form rather than a list of
* choices.
*/
export interface ActionSheetAktion {
label: React.ReactNode;
onClick: () => void;
/** Red, for anything that removes or overwrites something. */
destructive?: boolean;
disabled?: boolean;
}
export interface ActionSheetProps {
open: boolean;
onClose: () => void;
/** Optional heading above the choices. */
title?: React.ReactNode;
/** One explanatory line under the title. */
description?: React.ReactNode;
actions: ActionSheetAktion[];
/** Label of the separated bottom button. Pass null to leave it out. */
cancelLabel?: string | null;
/** Rendered between the heading and the choices - an error line, a hint. */
children?: React.ReactNode;
className?: string;
portalTarget?: Element;
}
export function ActionSheet({
open,
onClose,
title,
description,
actions,
cancelLabel = "Abbrechen",
children,
className,
portalTarget,
}: ActionSheetProps) {
useEscape(open, onClose);
const target = sheetPortalTarget(portalTarget);
if (!open || !target) return null;
const classes = ["ads-aktionsblatt"];
if (className) classes.push(className);
return createPortal(
<>
<div className="ads-sheet-scrim" onClick={onClose} />
<div className={classes.join(" ")} role="dialog" aria-modal="true">
<div className="ads-aktionsblatt__gruppe">
{(title || description) && (
<div className="ads-aktionsblatt__kopf">
{title && <strong>{title}</strong>}
{description && <span>{description}</span>}
</div>
)}
{children && <div className="ads-aktionsblatt__zusatz">{children}</div>}
{actions.map((aktion, i) => (
<button
key={i}
type="button"
className={aktion.destructive ? "ads-aktionsblatt__loeschen" : undefined}
disabled={aktion.disabled}
onClick={aktion.onClick}
>
{aktion.label}
</button>
))}
</div>
{cancelLabel !== null && (
<button type="button" className="ads-aktionsblatt__abbrechen" onClick={onClose}>
{cancelLabel}
</button>
)}
</div>
</>,
target,
);
}