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 <[email protected]>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user