design v5: Übersicht als Vorschau, Grundbausteine im neuen Look
Tiefblauer Grund mit Lichtflecken in den Bereichsfarben, Glasflächen mit Lichtkante (flaeche), ruhigere Schrift (Überschriften normal geschrieben, Großbuchstaben nur als kleines Etikett) und dezente Bewegung. - Startseite neu: Geräte-Ring mit Gesamtlage, drei Eckwerte, Instrumente (8 Messringe), Bereichskarten mit Kacheln je Modell, Baustein und Gerät (Symbol, Zustandspunkt, Update direkt an der Kachel), Speicher/NAS, Zeitleisten „Als Nächstes“ und „Zuletzt eingespielt“. - Panel, Schildchen, Kennzahl, Verlaufslinie, Seitenleiste und Kopf im neuen Look — wirkt schon auf allen Seiten. - Symbole je Gerät (lib/geraetebild): Farbe des Bereichs, Zustände nur als Punkt. Vorbilder: Heimdall, Dashy, Homepage, umbrelOS, Pulse. Noch nicht ausgerollt (kein dist): erst nach dem Ja des Users. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b816fe6d32
commit
948e55e4f2
@@ -0,0 +1,82 @@
|
||||
import type { CSSProperties } from "react"
|
||||
import { cn } from "cn"
|
||||
import type { Bereich } from "@/lib/navigation"
|
||||
import { type Einheit, wertText } from "@/lib/messung"
|
||||
import type { Messpunkt } from "@/lib/typen"
|
||||
import { Sparkline } from "./Sparkline"
|
||||
|
||||
// Ein Messwert als Ring (Design v5, 25.09.2026): der Bogen füllt sich bis zum Wert, im Normalfall in der Farbe des
|
||||
// Bereichs, ab der gelben Grenze bernstein, ab der roten rot — dazu das Wort („hoch“, „kritisch“), damit es nicht
|
||||
// nur an der Farbe hängt. Zwei feine Striche am Rand zeigen, wo die Grenzen liegen; darunter die Linie der Stunde.
|
||||
|
||||
const ZUSTAND = {
|
||||
ruhig: { wert: "text-foreground", marke: null },
|
||||
gelb: { wert: "text-bernstein", marke: "hoch" },
|
||||
rot: { wert: "text-rot-text", marke: "kritisch" },
|
||||
} as const
|
||||
|
||||
const MITTE = 48
|
||||
const RADIUS = 37
|
||||
const UMFANG = 2 * Math.PI * RADIUS
|
||||
const BOGEN = UMFANG * 0.75
|
||||
|
||||
/** Punkt auf dem Rand für einen Anteil 0…1 des 270°-Bogens (Beginn unten links, im Uhrzeigersinn). */
|
||||
function randpunkt(anteil: number, r: number) {
|
||||
const winkel = ((135 + 270 * anteil) * Math.PI) / 180
|
||||
return { x: MITTE + r * Math.cos(winkel), y: MITTE + r * Math.sin(winkel) }
|
||||
}
|
||||
|
||||
export function Messring({ label, wert, einheit, punkte, zustand = "ruhig", bereich, grenzen, max = 100, zeitraumText, i = 0 }: {
|
||||
label: string
|
||||
wert: number | null | undefined
|
||||
einheit: Einheit
|
||||
punkte?: Messpunkt[]
|
||||
zustand?: keyof typeof ZUSTAND
|
||||
bereich: Bereich
|
||||
/** Gelbe und rote Grenze in der Einheit des Werts. */
|
||||
grenzen: [number, number]
|
||||
max?: number
|
||||
zeitraumText: string
|
||||
/** Reihenfolge für das Auftauchen. */
|
||||
i?: number
|
||||
}) {
|
||||
const z = ZUSTAND[zustand]
|
||||
const farbe = zustand === "rot" ? "var(--rot)" : zustand === "gelb" ? "var(--bernstein)" : bereich === "box" ? "var(--box)" : "var(--lab)"
|
||||
const anteil = wert == null ? 0 : Math.min(Math.max(wert / max, 0), 1)
|
||||
const [zahl, einheitText] = wertText(wert, einheit).split(" ")
|
||||
return (
|
||||
<div className="flaeche-innen flex min-w-0 flex-col items-center gap-1 rounded-2xl px-2 pt-2.5 pb-2">
|
||||
<div className="relative size-[88px]">
|
||||
<svg viewBox="0 0 96 96" className="size-full" aria-hidden>
|
||||
<circle cx={MITTE} cy={MITTE} r={RADIUS} fill="none" stroke="rgb(255 255 255 / 0.07)" strokeWidth={7}
|
||||
strokeLinecap="round" strokeDasharray={`${BOGEN} ${UMFANG}`} transform={`rotate(135 ${MITTE} ${MITTE})`} />
|
||||
{grenzen.map((g, n) => {
|
||||
const a = randpunkt(Math.min(g / max, 1), RADIUS + 6)
|
||||
const b = randpunkt(Math.min(g / max, 1), RADIUS + 9.5)
|
||||
return <line key={g} x1={a.x} y1={a.y} x2={b.x} y2={b.y} strokeWidth={1.5} strokeLinecap="round"
|
||||
stroke={n === 0 ? "var(--bernstein)" : "var(--rot)"} opacity={0.55} />
|
||||
})}
|
||||
{wert != null && anteil > 0 && (
|
||||
<circle cx={MITTE} cy={MITTE} r={RADIUS} fill="none" stroke={farbe} strokeWidth={7} strokeLinecap="round"
|
||||
strokeDasharray={`${Math.max(BOGEN * anteil, 0.5)} ${UMFANG}`} transform={`rotate(135 ${MITTE} ${MITTE})`}
|
||||
className="ring-fuellt transition-[stroke-dasharray] duration-700"
|
||||
style={{ "--i": i, filter: `drop-shadow(0 0 5px color-mix(in srgb, ${farbe} 65%, transparent))` } as CSSProperties} />
|
||||
)}
|
||||
</svg>
|
||||
<span className="absolute inset-0 flex items-center justify-center pt-1">
|
||||
<span className={cn("font-anzeige text-[26px] leading-none font-semibold tabular-nums", z.wert)}>{zahl}</span>
|
||||
{einheitText && <span className="ml-0.5 self-center pt-1.5 text-[12px] font-medium text-text-3">{einheitText}</span>}
|
||||
</span>
|
||||
</div>
|
||||
<span className="etikett -mt-1 flex items-center gap-1.5 text-[10.5px] text-text-3">
|
||||
{label}
|
||||
{z.marke && <span className={z.wert}>· {z.marke}</span>}
|
||||
</span>
|
||||
<div className="w-full px-1">
|
||||
{punkte && punkte.length > 1
|
||||
? <Sparkline punkte={punkte} max={max} farbe={farbe} hoehe={20} beschriftung={`${label}, ${zeitraumText}`} />
|
||||
: <span className="block h-5" aria-hidden />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user