Compare commits
2 Commits
ce905431ed
...
4d84e2ec14
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d84e2ec14 | |||
| bd69d8fc0a |
@ -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") { }
|
||||||
|
|||||||
@ -190,36 +190,24 @@ public enum Design {
|
|||||||
|
|
||||||
/// Standard icon sizes for SF Symbols and custom icons.
|
/// Standard icon sizes for SF Symbols and custom icons.
|
||||||
public enum IconSize {
|
public enum IconSize {
|
||||||
|
/// Extra small icon (10pt) - tiny indicators.
|
||||||
public static let xSmall: CGFloat = 10
|
public static let xSmall: CGFloat = 10
|
||||||
|
/// Small icon (12pt) - inline with caption text.
|
||||||
public static let small: CGFloat = 12
|
public static let small: CGFloat = 12
|
||||||
|
/// Medium icon (16pt) - inline with body text.
|
||||||
public static let medium: CGFloat = 16
|
public static let medium: CGFloat = 16
|
||||||
|
/// Large icon (22pt) - standalone icons.
|
||||||
public static let large: CGFloat = 22
|
public static let large: CGFloat = 22
|
||||||
public static let xLarge: CGFloat = 32
|
/// Extra large icon (28pt) - row icons, list items.
|
||||||
public static let xxLarge: CGFloat = 48
|
public static let xLarge: CGFloat = 28
|
||||||
public static let xxxLarge: CGFloat = 64
|
/// Double extra large icon (36pt) - card icons, buttons.
|
||||||
}
|
public static let xxLarge: CGFloat = 36
|
||||||
|
/// Triple extra large icon (48pt) - feature icons.
|
||||||
// MARK: - Font Sizes (Base values for @ScaledMetric)
|
public static let xxxLarge: CGFloat = 48
|
||||||
|
/// Display icon (64pt) - section headers, prominent features.
|
||||||
/// Base font sizes to use with @ScaledMetric for Dynamic Type support.
|
public static let display: CGFloat = 64
|
||||||
public enum BaseFontSize {
|
/// Hero icon (80pt) - empty states, splash screens.
|
||||||
public static let xxSmall: CGFloat = 7
|
public static let hero: CGFloat = 80
|
||||||
public static let xSmall: CGFloat = 9
|
|
||||||
public static let small: CGFloat = 10
|
|
||||||
public static let caption: CGFloat = 11
|
|
||||||
public static let body: CGFloat = 12
|
|
||||||
public static let callout: CGFloat = 13
|
|
||||||
public static let medium: CGFloat = 14
|
|
||||||
public static let subheadline: CGFloat = 15
|
|
||||||
public static let large: CGFloat = 16
|
|
||||||
public static let xLarge: CGFloat = 18
|
|
||||||
public static let xxLarge: CGFloat = 20
|
|
||||||
public static let title3: CGFloat = 22
|
|
||||||
public static let title2: CGFloat = 26
|
|
||||||
public static let title: CGFloat = 32
|
|
||||||
public static let largeTitle: CGFloat = 36
|
|
||||||
public static let display: CGFloat = 48
|
|
||||||
public static let hero: CGFloat = 64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Scale
|
// MARK: - Scale
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user