Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-27 08:29:20 -06:00
parent ce905431ed
commit bd69d8fc0a
14 changed files with 55 additions and 52 deletions

View File

@ -107,7 +107,7 @@ struct MyView: View {
var body: some View { var body: some View {
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
Text("Hello") Text("Hello")
.font(.system(size: Design.BaseFontSize.title)) .font(.title)
.padding(Design.Spacing.large) .padding(Design.Spacing.large)
Button("Tap Me") { } Button("Tap Me") { }

View File

@ -37,7 +37,8 @@ public struct BadgePill: View {
public var body: some View { public var body: some View {
Text(text) Text(text)
.font(.system(size: Design.BaseFontSize.body, weight: .bold, design: .rounded)) .font(.subheadline.weight(.bold))
.fontDesign(.rounded)
.foregroundStyle(isSelected ? .black : accentColor) .foregroundStyle(isSelected ? .black : accentColor)
.padding(.horizontal, Design.Spacing.small) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, Design.Spacing.xSmall) .padding(.vertical, Design.Spacing.xSmall)

View File

@ -76,16 +76,16 @@ public struct LicensesView: View {
SettingsCard(backgroundColor: cardBackgroundColor, borderColor: cardBorderColor) { SettingsCard(backgroundColor: cardBackgroundColor, borderColor: cardBorderColor) {
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text(license.name) Text(license.name)
.font(.system(size: Design.BaseFontSize.medium, weight: .bold)) .font(.subheadline.weight(.bold))
.foregroundStyle(.white) .foregroundStyle(.white)
Text(license.description) Text(license.description)
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.white.opacity(Design.Opacity.strong)) .foregroundStyle(.white.opacity(Design.Opacity.strong))
HStack { HStack {
Label(license.licenseType, systemImage: "doc.text") Label(license.licenseType, systemImage: "doc.text")
.font(.system(size: Design.BaseFontSize.xSmall)) .font(.caption2)
.foregroundStyle(accentColor) .foregroundStyle(accentColor)
Spacer() Spacer()
@ -93,7 +93,7 @@ public struct LicensesView: View {
if let linkURL = URL(string: license.url) { if let linkURL = URL(string: license.url) {
Link(destination: linkURL) { Link(destination: linkURL) {
Label(String(localized: "View on GitHub"), systemImage: "arrow.up.right.square") Label(String(localized: "View on GitHub"), systemImage: "arrow.up.right.square")
.font(.system(size: Design.BaseFontSize.xSmall)) .font(.caption2)
.foregroundStyle(accentColor) .foregroundStyle(accentColor)
} }
} }

View File

@ -316,16 +316,16 @@ Toggle(isOn: $isOn) {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
HStack(spacing: Design.Spacing.xSmall) { HStack(spacing: Design.Spacing.xSmall) {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.white) .foregroundStyle(.white)
Image(systemName: "crown.fill") Image(systemName: "crown.fill")
.font(.system(size: Design.BaseFontSize.small)) .font(.caption2)
.foregroundStyle(AppStatus.warning) .foregroundStyle(AppStatus.warning)
} }
Text(subtitle) Text(subtitle)
.font(.system(size: Design.BaseFontSize.body)) .font(.subheadline)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
} }
@ -391,28 +391,29 @@ SettingsSlider(
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
HStack { HStack {
Text("Ring Size") Text("Ring Size")
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.white) .foregroundStyle(.white)
Spacer() Spacer()
Text("\(Int(viewModel.ringSize))pt") Text("\(Int(viewModel.ringSize))pt")
.font(.system(size: Design.BaseFontSize.body, weight: .medium, design: .rounded)) .font(.subheadline.weight(.medium))
.fontDesign(.rounded)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
Text("Adjusts the size of the ring") Text("Adjusts the size of the ring")
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
Image(systemName: "circle") Image(systemName: "circle")
.font(.system(size: Design.BaseFontSize.small)) .font(.caption2)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
Slider(value: $viewModel.ringSize, in: 20...100, step: 5) Slider(value: $viewModel.ringSize, in: 20...100, step: 5)
.tint(AppAccent.primary) .tint(AppAccent.primary)
Image(systemName: "circle") Image(systemName: "circle")
.font(.system(size: Design.BaseFontSize.large)) .font(.callout)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
} }
@ -458,18 +459,18 @@ NavigationLink {
HStack { HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("Open Source Licenses") Text("Open Source Licenses")
.font(.system(size: Design.BaseFontSize.body, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.white) .foregroundStyle(.white)
Text("Third-party libraries used in this app") Text("Third-party libraries used in this app")
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
.padding(Design.Spacing.medium) .padding(Design.Spacing.medium)
@ -576,11 +577,11 @@ SettingsSegmentedPicker(
// ❌ BEFORE: Inline title/subtitle with SegmentedPicker // ❌ BEFORE: Inline title/subtitle with SegmentedPicker
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("Camera") Text("Camera")
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.white) .foregroundStyle(.white)
Text("Choose between front and back camera lenses") Text("Choose between front and back camera lenses")
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
SegmentedPicker( SegmentedPicker(
@ -923,7 +924,7 @@ SettingsView()
4. **Section-specific accents**: Use `AppStatus.warning` for premium sections, `AppStatus.error` for debug. 4. **Section-specific accents**: Use `AppStatus.warning` for premium sections, `AppStatus.error` for debug.
5. **Test with Dynamic Type**: Bedrock uses `Design.BaseFontSize` values that scale properly. 5. **Test with Dynamic Type**: Bedrock uses iOS semantic fonts (`.body`, `.caption`, etc.) that scale properly with Dynamic Type.
6. **Avoid `Color.` typealiases**: Use `App`-prefixed typealiases to prevent conflicts with Bedrock's defaults. 6. **Avoid `Color.` typealiases**: Use `App`-prefixed typealiases to prevent conflicts with Bedrock's defaults.

View File

@ -52,7 +52,7 @@ public struct SegmentedPicker<T: Equatable>: View {
public var body: some View { public var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.white) .foregroundStyle(.white)
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
@ -62,7 +62,7 @@ public struct SegmentedPicker<T: Equatable>: View {
selection = option.1 selection = option.1
} label: { } label: {
Text(option.0) Text(option.0)
.font(.system(size: Design.BaseFontSize.body, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(selection == option.1 ? .black : .white.opacity(Design.Opacity.strong)) .foregroundStyle(selection == option.1 ? .black : .white.opacity(Design.Opacity.strong))
.padding(.vertical, Design.Spacing.small) .padding(.vertical, Design.Spacing.small)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

View File

@ -68,11 +68,11 @@ public struct SelectableRow<Badge: View>: View {
HStack { HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.large, weight: .semibold)) .font(.callout.weight(.semibold))
.foregroundStyle(.white) .foregroundStyle(.white)
Text(subtitle) Text(subtitle)
.font(.system(size: Design.BaseFontSize.body)) .font(.subheadline)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }

View File

@ -68,12 +68,12 @@ public struct SettingsNavigationRow<Destination: View>: View {
HStack { HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.body, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.primary) .foregroundStyle(.primary)
if let subtitle { if let subtitle {
Text(subtitle) Text(subtitle)
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} }
@ -81,7 +81,7 @@ public struct SettingsNavigationRow<Destination: View>: View {
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.padding(Design.Spacing.medium) .padding(Design.Spacing.medium)

View File

@ -55,21 +55,21 @@ public struct SettingsRow<Accessory: View>: View {
Button(action: action) { Button(action: action) {
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
Image(systemName: systemImage) Image(systemName: systemImage)
.font(.system(size: Design.BaseFontSize.medium)) .font(.subheadline)
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: Design.Size.iconContainerSmall, height: Design.Size.iconContainerSmall) .frame(width: Design.Size.iconContainerSmall, height: Design.Size.iconContainerSmall)
.background(iconColor.opacity(Design.Opacity.heavy)) .background(iconColor.opacity(Design.Opacity.heavy))
.clipShape(.rect(cornerRadius: Design.CornerRadius.xSmall)) .clipShape(.rect(cornerRadius: Design.CornerRadius.xSmall))
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.medium)) .font(.subheadline)
.foregroundStyle(.primary) .foregroundStyle(.primary)
Spacer() Spacer()
if let value { if let value {
Text(value) Text(value)
.font(.system(size: Design.BaseFontSize.body)) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
@ -77,7 +77,7 @@ public struct SettingsRow<Accessory: View>: View {
accessory accessory
} else { } else {
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: Design.BaseFontSize.body, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.tertiary) .foregroundStyle(.tertiary)
} }
} }

View File

@ -37,12 +37,12 @@ public struct SettingsSectionHeader: View {
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
if let systemImage { if let systemImage {
Image(systemName: systemImage) Image(systemName: systemImage)
.font(.system(size: Design.BaseFontSize.medium)) .font(.subheadline)
.foregroundStyle(accentColor.opacity(Design.Opacity.strong)) .foregroundStyle(accentColor.opacity(Design.Opacity.strong))
} }
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.caption, weight: .semibold)) .font(.caption.weight(.semibold))
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.textCase(.uppercase) .textCase(.uppercase)
.tracking(0.5) .tracking(0.5)

View File

@ -80,18 +80,18 @@ public struct SettingsSegmentedPicker<T: Equatable, Accessory: View>: View {
// Title row with optional accessory // Title row with optional accessory
HStack(spacing: Design.Spacing.xSmall) { HStack(spacing: Design.Spacing.xSmall) {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.primary) .foregroundStyle(.primary)
if let titleAccessory { if let titleAccessory {
titleAccessory titleAccessory
.font(.system(size: Design.BaseFontSize.small)) .font(.caption2)
} }
} }
// Subtitle // Subtitle
Text(subtitle) Text(subtitle)
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
// Segmented buttons // Segmented buttons
@ -102,7 +102,7 @@ public struct SettingsSegmentedPicker<T: Equatable, Accessory: View>: View {
selection = option.1 selection = option.1
} label: { } label: {
Text(option.0) Text(option.0)
.font(.system(size: Design.BaseFontSize.body, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(selection == option.1 ? Color.white : .primary) .foregroundStyle(selection == option.1 ? Color.white : .primary)
.padding(.vertical, Design.Spacing.small) .padding(.vertical, Design.Spacing.small)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

View File

@ -111,24 +111,25 @@ public struct SettingsSlider<Value: BinaryFloatingPoint & Sendable>: View where
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
HStack { HStack {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.primary) .foregroundStyle(.primary)
Spacer() Spacer()
Text(format(value)) Text(format(value))
.font(.system(size: Design.BaseFontSize.body, weight: .medium, design: .rounded)) .font(.subheadline.weight(.medium))
.fontDesign(.rounded)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
Text(subtitle) Text(subtitle)
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
if let leadingIcon { if let leadingIcon {
leadingIcon leadingIcon
.font(.system(size: Design.BaseFontSize.small)) .font(.caption2)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
@ -137,7 +138,7 @@ public struct SettingsSlider<Value: BinaryFloatingPoint & Sendable>: View where
if let trailingIcon { if let trailingIcon {
trailingIcon trailingIcon
.font(.system(size: Design.BaseFontSize.large)) .font(.callout)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} }

View File

@ -79,17 +79,17 @@ public struct SettingsToggle<Accessory: View>: View {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
HStack(spacing: Design.Spacing.xSmall) { HStack(spacing: Design.Spacing.xSmall) {
Text(title) Text(title)
.font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundStyle(.primary) .foregroundStyle(.primary)
if let titleAccessory { if let titleAccessory {
titleAccessory titleAccessory
.font(.system(size: Design.BaseFontSize.small)) .font(.caption2)
} }
} }
Text(subtitle) Text(subtitle)
.font(.system(size: Design.BaseFontSize.body)) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} }

View File

@ -116,11 +116,11 @@ public struct iCloudSyncSettingsView<ViewModel: CloudSyncable>: View {
if viewModel.iCloudEnabled && viewModel.iCloudAvailable { if viewModel.iCloudEnabled && viewModel.iCloudAvailable {
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
Image(systemName: syncStatusIcon) Image(systemName: syncStatusIcon)
.font(.system(size: Design.BaseFontSize.body)) .font(.subheadline)
.foregroundStyle(syncStatusColor) .foregroundStyle(syncStatusColor)
Text(syncStatusText) Text(syncStatusText)
.font(.system(size: Design.BaseFontSize.caption)) .font(.caption)
.foregroundStyle(.white.opacity(Design.Opacity.medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
Spacer() Spacer()
@ -129,7 +129,7 @@ public struct iCloudSyncSettingsView<ViewModel: CloudSyncable>: View {
viewModel.forceSync() viewModel.forceSync()
} label: { } label: {
Text(String(localized: "Sync Now")) Text(String(localized: "Sync Now"))
.font(.system(size: Design.BaseFontSize.caption, weight: .medium)) .font(.caption.weight(.medium))
.foregroundStyle(accentColor) .foregroundStyle(accentColor)
} }
} }

View File

@ -45,10 +45,10 @@ final class BedrockTests: XCTestCase {
XCTAssertGreaterThan(Design.Animation.springDuration, 0) XCTAssertGreaterThan(Design.Animation.springDuration, 0)
} }
func testFontSizesArePositive() { func testIconSizesArePositive() {
XCTAssertGreaterThan(Design.BaseFontSize.small, 0) XCTAssertGreaterThan(Design.IconSize.small, 0)
XCTAssertGreaterThan(Design.BaseFontSize.body, 0) XCTAssertGreaterThan(Design.IconSize.medium, 0)
XCTAssertGreaterThan(Design.BaseFontSize.title, 0) XCTAssertGreaterThan(Design.IconSize.large, 0)
} }
func testMinimumTouchTargetMeetsHIG() { func testMinimumTouchTargetMeetsHIG() {