import SwiftUI /// # Typography — the SF type ramp (FROZEN public surface) /// /// All font choices come from `DS.Typography`. The ramp maps to Apple's /// semantic text styles so everything scales with Dynamic Type (the a11y /// audit's "keep it scalable" point). Numbers/dimensions/cost/`cols×rows`/ /// timestamps use `mono(_:)` — SF Mono with tabular figures — so columns line /// up and digits don't jitter as values change (matches the existing /// `TelemetryChips`/`Timeline` convention, now centralized). extension DS { enum Typography { // ── Proportional ramp (Dynamic-Type scaling, semantic styles) ─────── /// Screen hero title. static let largeTitle = Font.largeTitle /// Section / prominent title. static let title = Font.title2 /// Emphasis / card heading. static let headline = Font.headline /// Default body text. static let body = Font.body /// Slightly smaller body (secondary actions). static let callout = Font.callout /// Meta / caption text. static let caption = Font.caption // ── Monospaced (numbers, dimensions, timestamps) ──────────────────── /// SF Mono + tabular figures at the given text style (default `.body`). /// Use for anything numeric that must align or not jump: `cols×rows`, /// device/client counts, `$cost`, context %, relative timestamps. static func mono(_ style: Font.TextStyle = .body) -> Font { .system(style, design: .monospaced).monospacedDigit() } /// The canonical meta-number font: caption-sized mono + tabular. static let metaMono = mono(.caption) // ── Dynamic Type clamp for dense numerics (T-iOS-34) ──────────────── /// Upper bound applied to **numeric / meta** text only (telemetry chips, /// `dsMetaText` rows: `ctx 92% · $0.1234 · 161×50`). /// /// Prose scales all the way to `.accessibility5` — that is the point of /// Dynamic Type and nothing here caps it. Tabular numerics are different: /// they live in one-line rows next to a status badge, and at AX4/AX5 a /// single chip row becomes three wrapped lines that push the row's real /// content off screen. Capping them at `.accessibility2` (already ~2× /// the default size) keeps the meta line legible AND keeps the row's /// primary text — the session name — visible. /// /// Pinned in `DynamicTypeLayoutTests` both as a policy value and /// behaviorally (AX2 and AX5 must measure the same height). static let numericClamp: DynamicTypeSize = .accessibility2 } } // MARK: - MetaText style /// Secondary + monospaced-tabular styling for a row's numeric meta line /// ("N 台设备在看 · 161×50"). Apply via `.dsMetaText()`. /// /// T-iOS-34 · carries the `numericClamp` so every meta line in the app gets the /// same Dynamic Type ceiling from ONE place (per-screen clamps would drift). struct MetaText: ViewModifier { func body(content: Content) -> some View { content .font(DS.Typography.metaMono) .foregroundStyle(DS.Palette.textSecondary) .dynamicTypeSize(...DS.Typography.numericClamp) } } extension View { /// Style a meta/number line as secondary monospaced-tabular text. func dsMetaText() -> some View { modifier(MetaText()) } }