Initialer Import: HA-Panel, Design-System, Companion-App
Drei zusammengehörige Teile in einem Repository: - homeassistant/ Das fertige, im Einsatz befindliche Home-Assistant-Panel (panel_custom Custom Element + pyscript-Backend). Echte Fahrzeug- und Personendaten (fahrzeugprofil.json, fahrten.jsonl, tankvorgaenge.jsonl, Tankbelege) bleiben per .gitignore außen vor; die anonymisierte Vorlage fahrzeugprofil.example.json ist mit dabei. - design-system/ Eigenständige React-Komponentenbibliothek (@audi-dash/ui), die die visuelle Sprache des Panels nachbildet - ohne Audi-Markenzeichen und ohne die lizenzierte Hausschrift. Dient als Grundlage für Claude Design. War bis hierher ein eigenes Repository und ist in dieses eingeschmolzen worden. - companion-app/ Datenschicht der neuen App DataMetric360 (iOS/Android via Capacitor, zusätzlich als Iframe im HA-Dashboard). Noch ohne Oberfläche: REST- und WebSocket-Zugriff auf Home Assistant plus Warteschlange für Änderungen ohne Netz. Ersetzt das eingespritzte hass-Objekt, das nur innerhalb des HA-Frontends existiert. Dazu die Projektdokumentation: SPECIFICATION.md (Ist-Stand des Panels), COMPANION_APP_ARCHITECTURE.md (Architekturentscheidungen der neuen App), AUDIT_2026-08-10.md, DESIGN_BRIEF_DATAMETRIC360.md und der ursprüngliche Bauauftrag. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface ImagePlaceholderProps {
|
||||
src?: string;
|
||||
alt: string;
|
||||
/** Caption shown on the load-error placeholder. */
|
||||
label: React.ReactNode;
|
||||
/** Small monospace hint under the label, e.g. an expected filename. */
|
||||
hint?: React.ReactNode;
|
||||
/** Placeholder icon override - defaults to a simple image glyph. */
|
||||
icon?: React.ReactNode;
|
||||
size?: "default" | "mini" | "round";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_ICON = (
|
||||
<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M4 5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5Zm2 0v14h12V5H6Zm3 3a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm-1 8 3-3 2 2 3-4 3 5H8Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export function ImagePlaceholder({
|
||||
src,
|
||||
alt,
|
||||
label,
|
||||
hint,
|
||||
icon,
|
||||
size = "default",
|
||||
className,
|
||||
}: ImagePlaceholderProps) {
|
||||
const [failed, setFailed] = React.useState(false);
|
||||
const classes = ["ads-bildbox"];
|
||||
if (size === "mini") classes.push("ads-bildbox--mini");
|
||||
if (size === "round") classes.push("ads-bildbox--round");
|
||||
if (className) classes.push(className);
|
||||
|
||||
const showPlaceholder = !src || failed;
|
||||
|
||||
return (
|
||||
<div className={classes.join(" ")}>
|
||||
{!showPlaceholder && (
|
||||
<img src={src} alt={alt} onError={() => setFailed(true)} />
|
||||
)}
|
||||
{showPlaceholder && (
|
||||
<div className="ads-platzhalter">
|
||||
<span className="ads-platzhalter__icon">{icon ?? DEFAULT_ICON}</span>
|
||||
<span className="ads-platzhalter__label">{label}</span>
|
||||
{hint !== undefined && <span className="ads-platzhalter__hint">{hint}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user