Kacheln und Fenster sprechen dieselbe Sprache

Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund
hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt
Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl.

Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren
deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht
mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz
auf schwarz endet.

Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das
lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient,
bis jede Kachel einmal zu sehen war.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-05 21:05:51 +02:00
co-authored by Claude Opus 5
parent 9b600de9a9
commit edb327cabe
32 changed files with 3677 additions and 968 deletions
+26 -52
View File
@@ -16,7 +16,7 @@ struct FanView: View {
let roomy: Bool
var body: some View {
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: Onyx.Space.row) {
switch control.installState {
case .installed:
if let report = control.report, !report.fans.isEmpty {
@@ -24,10 +24,10 @@ struct FanView: View {
FanRow(control: control, index: index, fan: fan, roomy: roomy)
}
if let hottest = report.hottestCelsius {
Divider().overlay(Onyx.Color.hairline)
HStack(spacing: 5) {
OnyxHairline()
HStack(spacing: Onyx.Space.sm) {
Image(systemName: "thermometer.medium")
.font(.system(size: 10))
.font(.system(size: Onyx.Symbol.small))
Text("\(Int(hottest.rounded())) °C")
.font(Onyx.Font.metricSmall).monospacedDigit()
Spacer(minLength: 0)
@@ -35,14 +35,24 @@ struct FanView: View {
.foregroundStyle(Onyx.Color.textTertiary)
}
} else {
notice("fans.noData", symbol: "fan")
OnyxNotice(symbol: "fan",
text: String(localized: "fans.noData"),
placement: .leading)
}
case .requiresApproval:
notice("fans.approval.needed", symbol: "hand.raised") {
control.openApprovalSettings()
OnyxNotice(symbol: "hand.raised",
text: String(localized: "fans.approval.needed"),
placement: .leading) {
OnyxPillButton(String(localized: "fans.install")) {
control.openApprovalSettings()
}
}
case .notInstalled:
notice("fans.notInstalled", symbol: "fan") { control.install() }
OnyxNotice(symbol: "fan",
text: String(localized: "fans.notInstalled"),
placement: .leading) {
OnyxPillButton(String(localized: "fans.install")) { control.install() }
}
case .failed(let reason):
Text(reason)
.font(Onyx.Font.caption)
@@ -54,29 +64,6 @@ struct FanView: View {
.onAppear { control.refreshInstallState() }
}
@ViewBuilder
private func notice(_ key: LocalizedStringKey, symbol: String,
action: (() -> Void)? = nil) -> some View {
VStack(alignment: .leading, spacing: 6) {
Image(systemName: symbol)
.font(.system(size: 15, weight: .light))
.foregroundStyle(Onyx.Color.textTertiary)
Text(key)
.font(Onyx.Font.caption)
.foregroundStyle(Onyx.Color.textSecondary)
.fixedSize(horizontal: false, vertical: true)
if let action {
Button(action: action) {
Text("fans.install")
.font(Onyx.Font.caption)
.foregroundStyle(Onyx.Color.surface)
.padding(.horizontal, 10).padding(.vertical, 4)
.background(Onyx.Color.accent, in: .capsule)
}
.buttonStyle(.plain)
}
}
}
}
private struct FanRow: View {
@@ -86,8 +73,8 @@ private struct FanRow: View {
let roomy: Bool
var body: some View {
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 6) {
VStack(alignment: .leading, spacing: Onyx.Space.xs) {
HStack(spacing: Onyx.Space.sm) {
Text("fans.fan \(fan.index + 1)")
.font(Onyx.Font.caption)
.foregroundStyle(Onyx.Color.textSecondary)
@@ -98,29 +85,16 @@ private struct FanRow: View {
.foregroundStyle(Onyx.Color.textPrimary)
}
HStack(spacing: 8) {
HStack(spacing: Onyx.Space.md) {
// Automatik ist der Ruhezustand und bleibt einen Klick entfernt:
// wer manuell geregelt hat, muss ohne Nachdenken zurückkönnen.
// Als gefüllte Pille, wenn sie greift — ein blauer Text sieht
// aus wie ein Link und nicht wie ein Zustand.
Button { control.setAutomatic(index: index) } label: {
Text("fans.auto")
.font(Onyx.Font.metricSmall)
.foregroundStyle(fan.isManual ? Onyx.Color.textTertiary
: Onyx.Color.surface)
.padding(.horizontal, 7)
.padding(.vertical, 2)
.background {
Capsule()
.fill(fan.isManual ? Color.clear : Onyx.Color.accent)
.overlay {
Capsule().strokeBorder(
fan.isManual ? Onyx.Color.hairline : .clear,
lineWidth: 1)
}
}
OnyxPillButton(String(localized: "fans.auto"),
isFilled: !fan.isManual,
size: .compact) {
control.setAutomatic(index: index)
}
.buttonStyle(.plain)
.disabled(!fan.isManual)
// Ein Regler über einen leeren Bereich stürzt ab. Meldet der
@@ -184,7 +158,7 @@ final class FanMenuBarModule: MenuBarModule {
func makePopoverView() -> AnyView {
AnyView(FanView(control: control, roomy: true)
.padding(14)
.padding(Onyx.Space.xl)
.frame(width: 260))
}