diff options
| author | Christian Cleberg <[email protected]> | 2026-07-21 20:47:34 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-21 21:40:44 -0500 |
| commit | c220e26bb496e455a5bdb6a8dc22abf09ecfed5f (patch) | |
| tree | 1709346960c291f9f7c304724c89432e7194affa /DomainDig/DashboardView.swift | |
| parent | dc479a3ba0d27fe86509d0eb2d27b01132c2b37f (diff) | |
| download | domain-dig-c220e26bb496e455a5bdb6a8dc22abf09ecfed5f.tar.gz domain-dig-c220e26bb496e455a5bdb6a8dc22abf09ecfed5f.tar.bz2 domain-dig-c220e26bb496e455a5bdb6a8dc22abf09ecfed5f.zip | |
feat(a11y): color independence, reduce motion, reduce transparency (#21 phase 5)
Audit unchanged at 11 dark / 14 light — expected, as none of these
settings are exercised by performAccessibilityAudit, and simctl can
toggle only Increase Contrast, not Differentiate Without Color, Reduce
Motion, or Reduce Transparency. Correct by construction and build-clean;
runtime behaviour is verified in the Phase 6 manual pass.
Color independence:
- Widget status is now an SF Symbol (checkmark.circle.fill /
exclamationmark.triangle.fill / exclamationmark.octagon.fill), the same
vocabulary as the in-app badges, replacing a silent colour-only dot on
both the domain rows and the small-view count pills. Status now survives
greyscale and reads consistently across surfaces.
- Under accessibilityDifferentiateWithoutColor: the Dashboard
summary-card dot becomes a per-filter symbol, the selected quick-filter
chip gains a checkmark and a border (selection was fill-colour only, and
also gains the .isSelected trait), and LabeledValueRow prefixes a
warning/failure symbol. All gated on the setting so the default UI stays
uncluttered.
Reduce motion: all five withAnimation/.animation sites now pass nil under
accessibilityReduceMotion — AppCopyButton's check cross-fade,
CollapsibleSectionView's expand/collapse, TimelineDiffView's scroll, and
WatchlistView's list reorder.
Reduce transparency: the single .thinMaterial capsule falls back to an
opaque AppSurfaceElevated fill under accessibilityReduceTransparency.
The SweepActivityController item from the plan is dropped: it is pure
ActivityKit lifecycle with no animation, confirmed back in the issue
triage.
Diffstat (limited to 'DomainDig/DashboardView.swift')
| -rw-r--r-- | DomainDig/DashboardView.swift | 61 |
1 files changed, 50 insertions, 11 deletions
diff --git a/DomainDig/DashboardView.swift b/DomainDig/DashboardView.swift index 24955bb..d86e591 100644 --- a/DomainDig/DashboardView.swift +++ b/DomainDig/DashboardView.swift @@ -2,6 +2,7 @@ import SwiftUI struct DashboardView: View { @Environment(\.appDensity) private var appDensity + @Environment(\.accessibilityDifferentiateWithoutColor) private var differentiateWithoutColor @Bindable var viewModel: DomainViewModel @State private var collapsedGroups = Set<String>() @@ -44,15 +45,33 @@ struct DashboardView: View { Button { viewModel.dashboardFilter = filter } label: { - Text(filter.title) - .font(appDensity.font(.caption, weight: .semibold)) - .foregroundStyle(viewModel.dashboardFilter == filter ? Color(.appOnAccent) : Color.primary) - .padding(.horizontal, 12) - .padding(.vertical, 8) - .background(viewModel.dashboardFilter == filter ? Color(.statusInfo) : Color(.appSurfaceElevated)) - .clipShape(Capsule()) + let isSelected = viewModel.dashboardFilter == filter + HStack(spacing: 4) { + // Selection is a fill-colour change; under + // Differentiate Without Color add a + // checkmark + border so it does not depend + // on hue alone. + if isSelected, differentiateWithoutColor { + Image(systemName: "checkmark") + .font(.caption2.weight(.bold)) + } + Text(filter.title) + .font(appDensity.font(.caption, weight: .semibold)) + } + .foregroundStyle(isSelected ? Color(.appOnAccent) : Color.primary) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(isSelected ? Color(.statusInfo) : Color(.appSurfaceElevated)) + .overlay( + Capsule().strokeBorder( + Color.primary, + lineWidth: isSelected && differentiateWithoutColor ? 1.5 : 0 + ) + ) + .clipShape(Capsule()) } .buttonStyle(.plain) + .accessibilityAddTraits(viewModel.dashboardFilter == filter ? .isSelected : []) } } .padding(.vertical, 4) @@ -198,10 +217,18 @@ struct DashboardView: View { // Was a fixed 28pt, which ignored Dynamic Type entirely. .font(.system(.title, design: .rounded, weight: .bold)) .foregroundStyle(.primary) - HStack { - Circle() - .fill(tint) - .frame(width: 8, height: 8) + HStack(spacing: 5) { + // A symbol under Differentiate Without Color (where a bare + // colour dot conveys nothing), a plain dot otherwise. + if differentiateWithoutColor { + Image(systemName: symbol(for: filter)) + .font(.caption2) + .foregroundStyle(tint) + } else { + Circle() + .fill(tint) + .frame(width: 8, height: 8) + } Text(filter.title) .font(appDensity.font(.caption2, design: .default, weight: .semibold)) .foregroundStyle(tint) @@ -215,6 +242,18 @@ struct DashboardView: View { .buttonStyle(.plain) } + private func symbol(for filter: PortfolioFilterOption) -> String { + switch filter { + case .all: return "square.grid.2x2.fill" + case .healthy: return "checkmark.circle.fill" + case .warning: return "exclamationmark.triangle.fill" + case .critical: return "exclamationmark.octagon.fill" + case .changed: return "arrow.triangle.2.circlepath" + case .expiring: return "clock.badge.exclamationmark.fill" + case .unreachable: return "wifi.slash" + } + } + private func cardBackground(for filter: PortfolioFilterOption) -> some ShapeStyle { if viewModel.dashboardFilter == filter { // Uses the authored info surface rather than a translucent wash of |
