style(widget): adopt system type styles and control conventions

Replace fixed point sizes with semantic footnote/caption styles, dim
unselected mode labels into a secondary tier, drop the mode track fill
and capsule borders in favor of one capsule language, and reshape the
toggles as borderless system-style pills. Section spacing unifies at
8pt and the error row gains a leading warning glyph. No behavior
change.
This commit is contained in:
Tunglies
2026-09-14 18:50:16 +08:00
parent a544990f9f
commit 8ad95657bd
@@ -21,6 +21,12 @@ struct RoutingView: View {
private var renderingMode: WidgetRenderingMode { systemRenderingMode } private var renderingMode: WidgetRenderingMode { systemRenderingMode }
#endif #endif
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.colorSchemeContrast) private var systemContrast
#if DEBUG
private var contrast: ColorSchemeContrast { previewIncreaseContrast ? .increased : systemContrast }
#else
private var contrast: ColorSchemeContrast { systemContrast }
#endif
@Environment(\.accessibilityReduceMotion) private var systemReduceMotion @Environment(\.accessibilityReduceMotion) private var systemReduceMotion
@Environment(\.accessibilityReduceTransparency) private var systemReduceTransparency @Environment(\.accessibilityReduceTransparency) private var systemReduceTransparency
#if DEBUG #if DEBUG
@@ -35,11 +41,11 @@ struct RoutingView: View {
private func text(_ key: String) -> String { localized(key, language: entry.status.language) } private func text(_ key: String) -> String { localized(key, language: entry.status.language) }
private let modes = ["rule", "global", "direct"] private let modes = ["rule", "global", "direct"]
private var ink: Color { renderingMode == .fullColor ? (colorScheme == .dark ? .white : .black) : .primary } private var ink: Color { renderingMode == .fullColor ? (colorScheme == .dark ? .white : .black) : .primary }
private var secondaryInk: Color { ink.opacity(contrast == .increased ? 0.8 : 0.6) }
@ViewBuilder private var selectionBackground: some View { @ViewBuilder private var selectionBackground: some View {
#if DEBUG #if DEBUG
if previewMaterial == "thin" && renderingMode == .fullColor && !reduceTransparency { if previewMaterial == "thin" && renderingMode == .fullColor && !reduceTransparency {
Capsule().fill(.thinMaterial) Capsule().fill(.thinMaterial)
.overlay(Capsule().strokeBorder(ink.opacity(0.15), lineWidth: 1))
} else { } else {
automaticSelectionBackground automaticSelectionBackground
} }
@@ -57,11 +63,12 @@ struct RoutingView: View {
.glassEffect(.regular, in: Capsule()) .glassEffect(.regular, in: Capsule())
} else { } else {
Capsule().fill(.thinMaterial) Capsule().fill(.thinMaterial)
.overlay(Capsule().strokeBorder(ink.opacity(0.15), lineWidth: 1))
} }
} }
private func modeInk(selected: Bool) -> Color { private func modeInk(selected: Bool) -> Color {
guard selected && reduceTransparency && renderingMode == .fullColor else { return ink } guard selected && reduceTransparency && renderingMode == .fullColor else {
return selected ? ink : secondaryInk
}
return colorScheme == .dark ? .black : .white return colorScheme == .dark ? .black : .white
} }
private var locale: Locale { private var locale: Locale {
@@ -76,18 +83,17 @@ struct RoutingView: View {
return Button(intent: SetRouting(action, value == true ? "false" : "true")) { return Button(intent: SetRouting(action, value == true ? "false" : "true")) {
let label = HStack(spacing: 4) { let label = HStack(spacing: 4) {
if !compact { Image(systemName: icon).font(.caption2) } if !compact { Image(systemName: icon).font(.caption2) }
Text(title).font(.system(size: compact ? 11 : 12, weight: .medium)) Text(title).font(.caption)
.lineLimit(compact ? 2 : 1) .lineLimit(compact ? 2 : 1)
.frame(height: compact ? 28 : nil, alignment: .topLeading) .frame(height: compact ? 30 : nil, alignment: .topLeading)
} }
let toggle = ZStack(alignment: .leading) { let toggle = ZStack(alignment: .leading) {
Capsule().strokeBorder(ink.opacity(0.5), lineWidth: 1) Capsule().fill(ink.opacity(value == true ? 0.28 : 0.08))
.background(ink.opacity(value == true ? 0.22 : 0.06), in: Capsule())
Circle().fill(ink) Circle().fill(ink)
.frame(width: 12, height: 12) .frame(width: 13, height: 13)
.offset(x: value.map { $0 ? 18.0 : 4.0 } ?? 11) .offset(x: value.map { $0 ? 19.0 : 4.0 } ?? 11.5)
} }
.frame(width: 34, height: 20) .frame(width: 36, height: 22)
.environment(\.layoutDirection, .leftToRight) .environment(\.layoutDirection, .leftToRight)
if compact { if compact {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
@@ -102,7 +108,7 @@ struct RoutingView: View {
} }
} }
} }
.foregroundStyle(ink) .foregroundStyle(secondaryInk)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(8) .padding(8)
.disabled(value == nil) .disabled(value == nil)
@@ -126,12 +132,6 @@ struct RoutingView: View {
content.padding(16) content.padding(16)
.frame(width: family == .systemSmall ? 170 : 360, height: 170) .frame(width: family == .systemSmall ? 170 : 360, height: 170)
.background { containerMaterial } .background { containerMaterial }
.overlay {
if previewIncreaseContrast {
RoundedRectangle(cornerRadius: 24).strokeBorder(ink, lineWidth: 2)
}
}
.contrast(previewIncreaseContrast ? 1.25 : 1)
.clipShape(RoundedRectangle(cornerRadius: 24)) .clipShape(RoundedRectangle(cornerRadius: 24))
} else { } else {
content.containerBackground(for: .widget) { containerMaterial } content.containerBackground(for: .widget) { containerMaterial }
@@ -142,7 +142,7 @@ struct RoutingView: View {
} }
private var content: some View { private var content: some View {
VStack(alignment: .leading, spacing: family == .systemSmall ? 10 : 12) { VStack(alignment: .leading, spacing: 8) {
GeometryReader { geometry in GeometryReader { geometry in
let width = (geometry.size.width - 4) / 3 let width = (geometry.size.width - 4) / 3
let orderedModes = rtl ? Array(modes.reversed()) : modes let orderedModes = rtl ? Array(modes.reversed()) : modes
@@ -157,10 +157,7 @@ struct RoutingView: View {
ForEach(orderedModes, id: \.self) { mode in ForEach(orderedModes, id: \.self) { mode in
let selected = entry.status.mode == mode let selected = entry.status.mode == mode
let label = Text(text("tray." + mode)) let label = Text(text("tray." + mode))
.font(.system( .font(selected ? .footnote.weight(.semibold) : .footnote)
size: family == .systemSmall ? 12 : 13,
weight: selected ? .semibold : .regular
))
.lineLimit(1) .lineLimit(1)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 30) .frame(height: 30)
@@ -175,7 +172,6 @@ struct RoutingView: View {
.padding(.horizontal, 2) .padding(.horizontal, 2)
.zIndex(1) .zIndex(1)
} }
.background(ink.opacity(0.04), in: Capsule())
.invalidatableContent() .invalidatableContent()
.environment(\.layoutDirection, .leftToRight) .environment(\.layoutDirection, .leftToRight)
} }
@@ -188,7 +184,12 @@ struct RoutingView: View {
) )
} }
if let error = entry.status.error { if let error = entry.status.error {
Text(text(error)).font(.caption2).foregroundStyle(ink.opacity(0.8)).lineLimit(2) Label {
Text(text(error)).font(.caption2).lineLimit(2)
} icon: {
Image(systemName: "exclamationmark.triangle.fill").font(.caption2)
}
.foregroundStyle(secondaryInk)
} }
} }
.environment(\.locale, locale) .environment(\.locale, locale)