Compare commits

...

4 Commits

18 changed files with 650 additions and 624 deletions

View File

@ -263,72 +263,6 @@ public enum Design {
public static let badge: CGFloat = IconSize.small public static let badge: CGFloat = IconSize.small
} }
// MARK: - Typography
/// Semantic font styles for consistent text appearance.
///
/// Use these semantic styles instead of inline `.font()` modifiers.
/// All fonts use SwiftUI system fonts that scale with Dynamic Type.
///
/// ## Example
///
/// ```swift
/// Text("Settings")
/// .font(Design.Typography.settingsTitle)
/// ```
public enum Typography {
// MARK: - Display
/// Large display text for splash screens and heroes.
public static let displayLarge = Font.largeTitle.weight(.bold)
/// Medium display text for major headings.
public static let displayMedium = Font.title.weight(.bold)
/// Small display text for section titles.
public static let displaySmall = Font.title2.weight(.bold)
// MARK: - Headlines
/// Standard headline for card titles and section headers.
public static let headline = Font.headline
/// Bold headline for emphasized titles.
public static let headlineBold = Font.headline.weight(.bold)
// MARK: - Body
/// Large body text.
public static let bodyLarge = Font.body
/// Standard body text for most content.
public static let body = Font.subheadline
/// Medium weight body text for row titles.
public static let bodyMedium = Font.subheadline.weight(.medium)
/// Bold body text for emphasis.
public static let bodyBold = Font.subheadline.weight(.bold)
// MARK: - Caption
/// Standard caption for metadata and descriptions.
public static let caption = Font.caption
/// Medium weight caption for labels.
public static let captionMedium = Font.caption.weight(.medium)
/// Bold caption for section headers and badges.
public static let captionBold = Font.caption.weight(.semibold)
/// Small caption for fine print.
public static let captionSmall = Font.caption2
// MARK: - Semantic Styles
/// Settings row title text.
public static let settingsTitle = Font.subheadline.weight(.medium)
/// Uppercase section header text.
public static let sectionHeader = Font.caption.weight(.semibold)
/// Badge and pill text with rounded design.
public static let badge = Font.subheadline.weight(.bold)
/// Callout text for emphasized rows.
public static let callout = Font.callout
/// Semibold callout for selectable row titles.
public static let calloutBold = Font.callout.weight(.semibold)
}
// MARK: - Scale // MARK: - Scale
/// Scale factors for pressed states, selections, and animations. /// Scale factors for pressed states, selections, and animations.

View File

@ -1,378 +1,270 @@
# Typography and Iconography Guide # Typography System Guide
This guide documents the typography and iconography systems in Bedrock for building consistent UI components. This guide documents the typography and text styling system in Bedrock.
## Quick Reference ## Overview
| What you need | Use this | The typography system consists of:
|--------------|----------|
| Font for text | `Design.Typography.*` |
| Icon sizing | `SymbolIcon` component or `Design.SymbolSize.*` |
| Icon container background | `Design.Size.iconContainerSmall/Medium/Large` |
| Avatar/profile image | `Design.Size.avatarSmall/Medium/Large` |
| Badge container | `Design.Size.badgeSmall/Medium/Large` |
**Deprecated:** `Design.IconSize.*` - use `SymbolSize` instead (kept for backwards compatibility). 1. **`Theme`** - Global theme registration for all color providers
2. **`Typography` enum** - All font definitions with explicit naming
3. **`TextEmphasis` enum** - Semantic text colors
4. **`StyledLabel`** - Simple text component combining typography + emphasis
5. **`IconLabel`** - Icon + text component
6. **`.typography()` modifier** - View extension for applying fonts
--- ---
## Typography ## App Setup
All fonts are defined in `Design.Typography` and scale automatically with Dynamic Type. Register your app's theme once at launch:
### Semantic Font Styles
Use these semantic styles instead of inline `.font()` modifiers:
| Style | Usage | SwiftUI Equivalent |
|-------|-------|-------------------|
| `displayLarge` | Splash screens, heroes | `.largeTitle.weight(.bold)` |
| `displayMedium` | Major headings | `.title.weight(.bold)` |
| `displaySmall` | Section titles | `.title2.weight(.bold)` |
| `headline` | Card titles, section headers | `.headline` |
| `headlineBold` | Emphasized titles | `.headline.weight(.bold)` |
| `bodyLarge` | Large body text | `.body` |
| `body` | Standard content | `.subheadline` |
| `bodyMedium` | Row titles | `.subheadline.weight(.medium)` |
| `bodyBold` | Emphasized text | `.subheadline.weight(.bold)` |
| `caption` | Metadata, descriptions | `.caption` |
| `captionMedium` | Labels | `.caption.weight(.medium)` |
| `captionBold` | Section headers, badges | `.caption.weight(.semibold)` |
| `captionSmall` | Fine print | `.caption2` |
| `settingsTitle` | Settings row titles | `.subheadline.weight(.medium)` |
| `sectionHeader` | Uppercase section headers | `.caption.weight(.semibold)` |
| `badge` | Badge/pill text | `.subheadline.weight(.bold)` |
| `callout` | Callout text | `.callout` |
| `calloutBold` | Selectable row titles | `.callout.weight(.semibold)` |
### Two Approaches
There are two ways to apply typography. Choose based on your needs:
| Approach | Best For |
|----------|----------|
| `*Label` components | Simple text with default styling |
| `Text().font(Design.Typography.*)` | Complex layouts, additional modifiers |
### Preferred: StyledText Components
**Use these for most cases.** They combine font + color with sensible defaults:
```swift ```swift
// Titles // In App.init()
TitleLabel("Settings", style: .settings) Theme.register(
TitleLabel("Morning Ritual", style: .headline) text: AppTextColors.self,
TitleLabel("Welcome", style: .displayLarge) surface: AppSurface.self,
accent: AppAccent.self,
// Body text status: AppStatus.self
BodyLabel("Description text", emphasis: .secondary) )
BodyLabel("Important note", emphasis: .primary, weight: .medium) Theme.register(border: AppBorder.self) // Optional
// Captions
CaptionLabel("Day 3 of 28")
CaptionLabel("PRO", style: .badge)
// Section headers
SectionHeader("General")
SectionHeader("Notifications", icon: "bell.fill")
``` ```
### Fallback: Direct Typography Usage This enables Bedrock components to use your app's colors automatically.
**Use `Design.Typography.*` when you need more control:** ### Using Theme Colors
After registration, use `Theme.*` to access your registered colors:
```swift ```swift
// Need .multilineTextAlignment or .lineLimit // In Bedrock components (automatic via TextEmphasis)
Text("A longer description that might wrap") StyledLabel("Title", .heading) // Uses Theme.Text.primary
.font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center)
.lineLimit(3)
// Localized strings with interpolation // Direct access when needed
Text("Day \(dayNumber) of \(totalDays)") let bgColor = Theme.Surface.card
.font(Design.Typography.caption) let accentColor = Theme.Accent.primary
.foregroundStyle(AppTextColors.tertiary) let successColor = Theme.Status.success
// Custom colors not in the component
Text("Warning message")
.font(Design.Typography.bodyMedium)
.foregroundStyle(AppStatus.warning)
// Animated text
Text(isExpanded ? "Collapse" : "Expand")
.font(Design.Typography.callout)
.animation(.easeInOut, value: isExpanded)
```
### Migration Examples
```swift
// Before (inconsistent)
Text("Settings")
.font(.subheadline.weight(.medium))
.foregroundStyle(.primary)
// After (using component)
TitleLabel("Settings", style: .settings)
// After (using direct typography, if more control needed)
Text("Settings")
.font(Design.Typography.settingsTitle)
.foregroundStyle(.primary)
``` ```
--- ---
## Iconography ## Typography Enum
All SF Symbol sizing is defined in `Design.SymbolSize` with semantic names. All fonts are defined in the `Typography` enum. Each case name explicitly describes the font:
### Semantic Icon Sizes - Base name = regular weight (e.g., `.title2` = Font.title2)
- `*Bold` = bold weight (e.g., `.title2Bold` = Font.title2.bold())
- `*Emphasis` = semibold weight (e.g., `.bodyEmphasis` = Font.body.weight(.semibold))
| Size | Points | Usage | ### Available Styles
|------|--------|-------|
| `inline` | 16pt | Inline with body text |
| `row` | 22pt | List row icons |
| `rowContainer` | 28pt | Row icons with background |
| `card` | 36pt | Card/button icons |
| `feature` | 48pt | Feature callout icons |
| `section` | 64pt | Section header icons |
| `hero` | 80pt | Empty state/hero icons |
| `chevron` | 12pt | Navigation chevrons |
| `indicator` | 16pt | Status indicators |
| `badge` | 12pt | Badge icons |
### Using SymbolIcon | Category | Style | Font |
|----------|-------|------|
Use `SymbolIcon` for consistent SF Symbol styling: | **Hero/Titles** | `.hero` | largeTitle |
| | `.heroBold` | largeTitle.bold() |
```swift | | `.title` | title |
// Before (inconsistent) | | `.titleBold` | title.bold() |
Image(systemName: "star.fill") | | `.title2` | title2 |
.font(.subheadline) | | `.title2Bold` | title2.bold() |
.foregroundStyle(.secondary) | | `.title3` | title3 |
| | `.title3Bold` | title3.bold() |
// After (consistent) | **Headings** | `.heading` | headline |
SymbolIcon("star.fill", size: .row, color: .secondary) | | `.headingEmphasis` | headline.weight(.semibold) |
``` | | `.headingBold` | headline.bold() |
| | `.subheading` | subheadline |
### Size Examples | | `.subheadingEmphasis` | subheadline.weight(.semibold) |
| **Body** | `.body` | body |
```swift | | `.bodyEmphasis` | body.weight(.semibold) |
// Inline with text | | `.callout` | callout |
SymbolIcon("checkmark", size: .inline, color: .green) | | `.calloutEmphasis` | callout.weight(.semibold) |
| **Micro** | `.footnote` | footnote |
// Row icon in a list | | `.footnoteEmphasis` | footnote.weight(.semibold) |
SymbolIcon("star.fill", size: .row, color: .yellow) | | `.caption` | caption |
| | `.captionEmphasis` | caption.weight(.semibold) |
// Card icon | | `.caption2` | caption2 |
SymbolIcon("sparkles", size: .card, color: .accent) | | `.caption2Emphasis` | caption2.weight(.semibold) |
// Hero/empty state icon
SymbolIcon("moon.stars.fill", size: .hero, color: .accent)
// Navigation chevron
SymbolIcon.chevron() // Convenience method
// Checkmark indicator
SymbolIcon.checkmark(color: .green) // Convenience method
```
### Icon + Container Pattern
For icons with backgrounds, combine `SymbolIcon` with `Design.Size.iconContainer*`:
```swift
SymbolIcon("star.fill", size: .inline, color: .white)
.frame(width: Design.Size.iconContainerSmall, height: Design.Size.iconContainerSmall)
.background(AppAccent.primary.opacity(Design.Opacity.heavy))
.clipShape(.rect(cornerRadius: Design.CornerRadius.xSmall))
```
Container sizes:
- `iconContainerSmall`: 28pt
- `iconContainerMedium`: 40pt
- `iconContainerLarge`: 56pt
--- ---
## When to Use Fallback Approach ## TextEmphasis Enum
The `*Label` components work for ~80% of cases. Use `Text().font(Design.Typography.*)` for these exceptions: Semantic text colors resolved from the registered `TextTheme`:
### 1. String Interpolation | Emphasis | Description |
|----------|-------------|
| `.primary` | Main text color (default) |
| `.secondary` | Supporting text |
| `.tertiary` | Subtle/hint text |
| `.disabled` | Disabled state |
| `.inverse` | Contrasting backgrounds |
| `.custom(Color)` | Custom color override |
### When to Use `.custom()`
Only use `.custom()` for colors that aren't text colors:
```swift ```swift
// Label components only accept String, not interpolated strings // ✅ Semantic text colors - use the emphasis value directly
Text(String(localized: "Arc \(arcNumber) Complete")) StyledLabel("Title", .heading) // .primary is default
.font(Design.Typography.body) StyledLabel("Subtitle", .subheading, emphasis: .secondary)
.foregroundStyle(AppTextColors.secondary) StyledLabel("Hint", .caption, emphasis: .tertiary)
// ✅ Use .custom() for non-text colors
StyledLabel("Success!", .heading, emphasis: .custom(AppStatus.success))
StyledLabel("Arc 3", .caption, emphasis: .custom(AppAccent.primary))
// ✅ Use .custom() for conditional colors
StyledLabel(text, .body, emphasis: .custom(isActive ? AppStatus.success : AppTextColors.tertiary))
``` ```
### 2. Additional Modifiers ---
## StyledLabel Component
A single component for all styled text:
```swift ```swift
// Need .multilineTextAlignment(), .lineLimit(), or .frame() StyledLabel(
Text(description) _ text: String,
.font(Design.Typography.caption) _ typography: Typography = .body,
.foregroundStyle(AppTextColors.secondary) emphasis: TextEmphasis = .primary,
.multilineTextAlignment(.center) alignment: TextAlignment? = nil,
.lineLimit(2) lineLimit: Int? = nil
)
``` ```
### 3. Font Design Variants ### Examples
```swift ```swift
// Need .fontDesign(.rounded) or similar // Simple - uses registered theme's primary color
StyledLabel("Morning Ritual", .heading)
StyledLabel("Day 6 of 28", .caption, emphasis: .secondary)
// With alignment and line limit
StyledLabel("Centered text", .body, alignment: .center)
StyledLabel("One line", .caption, emphasis: .tertiary, lineLimit: 1)
// In buttons
Button(action: onContinue) {
StyledLabel("Continue", .heading, emphasis: .inverse)
.frame(maxWidth: .infinity)
.frame(height: 50)
.background(AppAccent.primary)
}
```
---
## IconLabel Component
For icon + text combinations:
```swift
IconLabel(_ icon: String, _ text: String, _ typography: Typography = .body, emphasis: TextEmphasis = .primary)
```
### Examples
```swift
IconLabel("bell.fill", "Notifications", .subheading)
IconLabel("star.fill", "Favorites", .body, emphasis: .secondary)
IconLabel("flame.fill", "5-day streak", .caption, emphasis: .custom(AppStatus.success))
```
---
## When to Use StyledLabel vs Text()
### Use StyledLabel (Preferred)
Use `StyledLabel` for 95% of text:
```swift
StyledLabel("Settings", .subheadingEmphasis)
StyledLabel("Description", .subheading, emphasis: .secondary)
StyledLabel("Centered", .body, alignment: .center)
```
### Use Text() with .typography() (Rare Exceptions)
Only when you need modifiers `StyledLabel` doesn't support:
```swift
// Font design variants
Text(formattedValue) Text(formattedValue)
.font(Design.Typography.settingsTitle) .typography(.subheadingEmphasis)
.fontDesign(.rounded) .fontDesign(.rounded)
.foregroundStyle(.secondary)
// TextField styling
TextField("Placeholder", text: $value)
.typography(.heading)
// Inside special view builders (like Charts AxisValueLabel)
AxisValueLabel {
Text("\(value)%")
.font(Typography.caption2.font)
.foregroundStyle(color)
}
``` ```
### 4. Conditional Colors ### Never Use Raw `.font()` with System Fonts
```swift ```swift
// Color changes based on state // ❌ BAD
Text(title) Text("Title").font(.headline)
.font(Design.Typography.headline)
.foregroundStyle(isActive ? AppTextColors.primary : AppTextColors.tertiary)
```
### 5. Button Labels with Layout // ✅ GOOD
StyledLabel("Title", .heading)
```swift
// Need .frame() for button sizing
Text(actionTitle)
.font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary)
.frame(maxWidth: .infinity)
.frame(height: 50)
```
### 6. Custom Theme Colors (Bedrock)
```swift
// Bedrock uses .white for dark backgrounds, not semantic colors
Text(title)
.font(Design.Typography.settingsTitle)
.foregroundStyle(.white)
``` ```
--- ---
## Migration Guide ## Migration from Old System
### From Inline Fonts ### Old vs New
Replace inline font modifiers with `Design.Typography`:
```swift ```swift
// Old // OLD: Design.Typography
.font(.subheadline.weight(.medium)) Text("Title").font(Design.Typography.headline)
// New
.font(Design.Typography.settingsTitle)
// Old // NEW: StyledLabel
.font(.caption.weight(.semibold)) StyledLabel("Title", .heading)
// New
.font(Design.Typography.sectionHeader)
// Old // OLD: Multiple Label components
.font(.headline) TitleLabel("Title", style: .headline, color: .white)
// New BodyLabel("Body", emphasis: .secondary)
.font(Design.Typography.headline)
// NEW: Single StyledLabel
StyledLabel("Title", .heading, emphasis: .inverse)
StyledLabel("Body", .subheading, emphasis: .secondary)
``` ```
### From Inline Icons ### Mapping Table
Replace inline Image styling with `SymbolIcon`: | Old (Design.Typography) | New (Typography) |
|------------------------|------------------|
| `.displayLarge` | `.heroBold` |
| `.displayMedium` | `.titleBold` |
| `.displaySmall` | `.title2Bold` |
| `.headline` | `.heading` |
| `.headlineBold` | `.headingBold` |
| `.body` | `.subheading` |
| `.bodyMedium` | `.subheadingEmphasis` |
| `.bodyLarge` | `.body` |
| `.caption` | `.caption` |
| `.captionMedium` | `.captionEmphasis` |
| `.captionSmall` | `.caption2` |
---
## SymbolIcon (Unchanged)
The `SymbolIcon` component remains the same for consistent icon styling:
```swift ```swift
// Old
Image(systemName: "star.fill")
.font(.body)
.foregroundStyle(.accent)
// New
SymbolIcon("star.fill", size: .row, color: .accent) SymbolIcon("star.fill", size: .row, color: .accent)
```
### From Hero Icons
Replace hero icon patterns with semantic sizes:
```swift
// Old
Image(systemName: "sparkles")
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppAccent.primary)
// New
SymbolIcon("sparkles", size: .hero, color: AppAccent.primary) SymbolIcon("sparkles", size: .hero, color: AppAccent.primary)
SymbolIcon.chevron()
``` ```
--- See the existing documentation for `SymbolIcon` sizes and usage.
## Best Practices
1. **Always use semantic styles** - Use `Design.Typography.settingsTitle` instead of `.subheadline.weight(.medium)`
2. **Consistent icon sizing** - Use `SymbolIcon` sizes that match the context (`.row` for lists, `.hero` for empty states)
3. **Preserve Dynamic Type** - All typography scales automatically; avoid fixed sizes
4. **Color consistency** - Pair typography with semantic colors (`AppTextColors.primary`, `.secondary`, `.tertiary`)
5. **Container sizing** - Use `Design.Size.iconContainer*` for icon backgrounds, `Design.SymbolSize.*` for the icon itself
6. **Audit regularly** - Search for inline `.font()` and `Image(systemName:)` to find inconsistencies
---
## What NOT to Use
These patterns are deprecated or should be avoided:
| Avoid | Use Instead |
|-------|-------------|
| `Design.IconSize.*` | `SymbolIcon` or `Design.SymbolSize.*` |
| `.font(.subheadline.weight(.medium))` | `Design.Typography.settingsTitle` |
| `.font(.system(size: 80))` | `SymbolIcon("icon", size: .hero)` |
| Hardcoded frame sizes like `44x44` | `Design.Size.actionRowMinHeight` or `Design.Size.iconContainer*` |
---
## Size Reference
### Design.Size (UI Elements)
| Constant | Value | Use For |
|----------|-------|---------|
| `actionRowMinHeight` | 44pt | Minimum tappable row height |
| `buttonHeight` | 50pt | Standard button height |
| `iconContainerSmall` | 28pt | Small icon backgrounds |
| `iconContainerMedium` | 40pt | Medium icon backgrounds |
| `iconContainerLarge` | 56pt | Large icon backgrounds |
| `avatarSmall` | 32pt | Small profile images |
| `avatarMedium` | 44pt | Medium profile images |
| `avatarLarge` | 64pt | Large profile images |
| `badgeSmall` | 20pt | Small badge containers |
| `badgeMedium` | 26pt | Medium badge containers |
| `badgeLarge` | 32pt | Large badge containers |
### Design.SymbolSize (Icon Point Sizes)
| Constant | Value | Use For |
|----------|-------|---------|
| `inline` | 16pt | Inline with body text |
| `row` | 22pt | List row icons |
| `rowContainer` | 28pt | Row icons with background |
| `card` | 36pt | Card/button icons |
| `feature` | 48pt | Feature callouts |
| `section` | 64pt | Section headers |
| `hero` | 80pt | Empty states |
| `chevron` | 12pt | Navigation chevrons |
| `indicator` | 16pt | Status indicators |
| `badge` | 12pt | Badge icons |

View File

@ -0,0 +1,64 @@
//
// TextEmphasis.swift
// Bedrock
//
// Semantic text color emphasis levels.
//
import SwiftUI
// MARK: - Text Emphasis
/// Semantic text color emphasis levels.
///
/// Use `TextEmphasis` to specify text color semantically. Colors are resolved
/// from the globally registered `Theme`.
///
/// ## Example
///
/// ```swift
/// // Register your theme once at app launch
/// Theme.register(
/// text: AppTextColors.self,
/// surface: AppSurface.self,
/// accent: AppAccent.self,
/// status: AppStatus.self
/// )
///
/// // Then use emphasis levels directly
/// StyledLabel("Primary text", .body) // uses .primary
/// StyledLabel("Secondary text", .caption, emphasis: .secondary)
/// StyledLabel("Custom color", .heading, emphasis: .custom(.red))
/// ```
public enum TextEmphasis: Sendable {
/// Primary text color (highest emphasis). Default.
case primary
/// Secondary text color (medium emphasis).
case secondary
/// Tertiary text color (low emphasis).
case tertiary
/// Disabled text color.
case disabled
/// Inverse text color (for contrasting backgrounds).
case inverse
/// Custom color override for one-off cases.
case custom(Color)
/// Resolves the color from the globally registered Theme.
public var color: Color {
switch self {
case .primary: Theme.Text.primary
case .secondary: Theme.Text.secondary
case .tertiary: Theme.Text.tertiary
case .disabled: Theme.Text.disabled
case .inverse: Theme.Text.inverse
case .custom(let color): color
}
}
}
// MARK: - Deprecated TextTheme
/// Deprecated: Use `Theme` instead.
@available(*, deprecated, renamed: "Theme", message: "Use Theme.register() instead")
public typealias TextTheme = Theme

View File

@ -0,0 +1,124 @@
//
// Theme.swift
// Bedrock
//
// Global theme registration for Bedrock components.
//
import SwiftUI
// MARK: - Global Theme Registration
/// Global theme provider for Bedrock components.
///
/// Register your app's theme once at launch to enable semantic colors
/// in `StyledLabel`, `IconLabel`, and other Bedrock components.
///
/// ## Usage
///
/// ```swift
/// // In App.init()
/// Theme.register(
/// text: AppTextColors.self,
/// surface: AppSurface.self,
/// accent: AppAccent.self,
/// status: AppStatus.self
/// )
///
/// // Then use semantic emphasis directly
/// StyledLabel("Title", .heading) // uses Theme.Text.primary
/// StyledLabel("Subtitle", .subheading, emphasis: .secondary) // uses Theme.Text.secondary
/// ```
public enum Theme {
// MARK: - Registered Providers
// Set once at app launch, read-only thereafter
nonisolated(unsafe) private static var _text: any TextColorProvider.Type = DefaultTextColors.self
nonisolated(unsafe) private static var _surface: any SurfaceColorProvider.Type = DefaultSurfaceColors.self
nonisolated(unsafe) private static var _accent: any AccentColorProvider.Type = DefaultAccentColors.self
nonisolated(unsafe) private static var _status: any StatusColorProvider.Type = DefaultStatusColors.self
nonisolated(unsafe) private static var _border: any BorderColorProvider.Type = DefaultBorderColors.self
// MARK: - Registration
/// Registers custom color providers for the app.
///
/// Call this once at app launch:
/// ```swift
/// Theme.register(
/// text: AppTextColors.self,
/// surface: AppSurface.self,
/// accent: AppAccent.self,
/// status: AppStatus.self
/// )
/// ```
public static func register<T: TextColorProvider, S: SurfaceColorProvider, A: AccentColorProvider, St: StatusColorProvider>(
text: T.Type,
surface: S.Type,
accent: A.Type,
status: St.Type
) {
_text = text
_surface = surface
_accent = accent
_status = status
}
/// Registers a border color provider (optional).
public static func register<B: BorderColorProvider>(border: B.Type) {
_border = border
}
// MARK: - Text Colors
/// Registered text color provider.
public enum Text {
public static var primary: Color { _text.primary }
public static var secondary: Color { _text.secondary }
public static var tertiary: Color { _text.tertiary }
public static var disabled: Color { _text.disabled }
public static var inverse: Color { _text.inverse }
}
// MARK: - Surface Colors
/// Registered surface color provider.
public enum Surface {
public static var primary: Color { _surface.primary }
public static var secondary: Color { _surface.secondary }
public static var tertiary: Color { _surface.tertiary }
public static var card: Color { _surface.card }
public static var overlay: Color { _surface.overlay }
}
// MARK: - Accent Colors
/// Registered accent color provider.
public enum Accent {
public static var primary: Color { _accent.primary }
public static var light: Color { _accent.light }
public static var dark: Color { _accent.dark }
public static var secondary: Color { _accent.secondary }
}
// MARK: - Status Colors
/// Registered status color provider.
public enum Status {
public static var success: Color { _status.success }
public static var warning: Color { _status.warning }
public static var error: Color { _status.error }
public static var info: Color { _status.info }
}
// MARK: - Border Colors
/// Registered border color provider.
public enum Border {
public static var subtle: Color { _border.subtle }
public static var standard: Color { _border.standard }
public static var emphasized: Color { _border.emphasized }
}
}
// Note: Default providers are defined in Colors.swift

View File

@ -0,0 +1,138 @@
//
// Typography.swift
// Bedrock
//
// Semantic typography system with explicit, self-describing font names.
//
import SwiftUI
/// Semantic typography with explicit naming.
///
/// Each case name describes exactly what font you get:
/// - Base name = regular weight (e.g., `.title2` = Font.title2)
/// - `*Bold` = bold weight (e.g., `.title2Bold` = Font.title2.bold())
/// - `*Emphasis` = semibold weight (e.g., `.bodyEmphasis` = Font.body.weight(.semibold))
///
/// ## Example
///
/// ```swift
/// Text("Hello")
/// .font(Typography.heading.font)
///
/// // Or with the View extension:
/// Text("Hello")
/// .typography(.heading)
/// ```
public enum Typography: CaseIterable, Sendable {
// MARK: - Hero / Large Titles
/// Large title (regular weight).
case hero
/// Large title (bold weight).
case heroBold
/// Title (regular weight).
case title
/// Title (bold weight).
case titleBold
/// Title 2 (regular weight).
case title2
/// Title 2 (bold weight).
case title2Bold
/// Title 3 (regular weight).
case title3
/// Title 3 (bold weight).
case title3Bold
// MARK: - Headings
/// Headline (regular weight).
case heading
/// Headline (semibold weight).
case headingEmphasis
/// Headline (bold weight).
case headingBold
/// Subheadline (regular weight).
case subheading
/// Subheadline (semibold weight).
case subheadingEmphasis
// MARK: - Body
/// Body text (regular weight).
case body
/// Body text (semibold weight).
case bodyEmphasis
/// Callout text (regular weight).
case callout
/// Callout text (semibold weight).
case calloutEmphasis
// MARK: - Micro
/// Footnote text (regular weight).
case footnote
/// Footnote text (semibold weight).
case footnoteEmphasis
/// Caption text (regular weight).
case caption
/// Caption text (semibold weight).
case captionEmphasis
/// Caption 2 text (regular weight).
case caption2
/// Caption 2 text (semibold weight).
case caption2Emphasis
// MARK: - Font
/// The SwiftUI Font for this typography style.
public var font: Font {
switch self {
case .hero: .largeTitle
case .heroBold: .largeTitle.bold()
case .title: .title
case .titleBold: .title.bold()
case .title2: .title2
case .title2Bold: .title2.bold()
case .title3: .title3
case .title3Bold: .title3.bold()
case .heading: .headline
case .headingEmphasis: .headline.weight(.semibold)
case .headingBold: .headline.bold()
case .subheading: .subheadline
case .subheadingEmphasis: .subheadline.weight(.semibold)
case .body: .body
case .bodyEmphasis: .body.weight(.semibold)
case .callout: .callout
case .calloutEmphasis: .callout.weight(.semibold)
case .footnote: .footnote
case .footnoteEmphasis: .footnote.weight(.semibold)
case .caption: .caption
case .captionEmphasis: .caption.weight(.semibold)
case .caption2: .caption2
case .caption2Emphasis: .caption2.weight(.semibold)
}
}
}
// MARK: - View Extension
extension View {
/// Applies a typography style to the view.
///
/// ```swift
/// Text("Hello")
/// .typography(.heading)
/// ```
public func typography(_ style: Typography) -> some View {
self.font(style.font)
}
}

View File

@ -73,9 +73,7 @@ public extension View {
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) .clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
.pulsing(isActive: true) .pulsing(isActive: true)
Text("Pulsing highlights interactive areas") StyledLabel("Pulsing highlights interactive areas", .caption, emphasis: .secondary)
.foregroundStyle(Color.TextColors.secondary)
.font(.caption)
} }
} }
} }

View File

@ -37,7 +37,7 @@ public struct BadgePill: View {
public var body: some View { public var body: some View {
Text(text) Text(text)
.font(Design.Typography.badge) .typography(.subheadingEmphasis)
.fontDesign(.rounded) .fontDesign(.rounded)
.foregroundStyle(isSelected ? .black : accentColor) .foregroundStyle(isSelected ? .black : accentColor)
.padding(.horizontal, Design.Spacing.small) .padding(.horizontal, Design.Spacing.small)

View File

@ -75,26 +75,18 @@ public struct LicensesView: View {
private func licenseCard(_ license: License) -> some View { private func licenseCard(_ license: License) -> some 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) StyledLabel(license.name, .bodyEmphasis, emphasis: .inverse)
.font(Design.Typography.bodyBold)
.foregroundStyle(.white)
Text(license.description) StyledLabel(license.description, .caption, emphasis: .secondary)
.font(Design.Typography.caption)
.foregroundStyle(.white.opacity(Design.Opacity.strong))
HStack { HStack {
Label(license.licenseType, systemImage: "doc.text") IconLabel("doc.text", license.licenseType, .caption2, emphasis: .custom(accentColor))
.font(Design.Typography.captionSmall)
.foregroundStyle(accentColor)
Spacer() Spacer()
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") IconLabel("arrow.up.right.square", String(localized: "View on GitHub"), .caption2, emphasis: .custom(accentColor))
.font(Design.Typography.captionSmall)
.foregroundStyle(accentColor)
} }
} }
} }

View File

@ -51,9 +51,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) StyledLabel(title, .subheadingEmphasis, emphasis: .inverse)
.font(Design.Typography.settingsTitle)
.foregroundStyle(.white)
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
ForEach(options.indices, id: \.self) { index in ForEach(options.indices, id: \.self) { index in
@ -61,9 +59,7 @@ public struct SegmentedPicker<T: Equatable>: View {
Button { Button {
selection = option.1 selection = option.1
} label: { } label: {
Text(option.0) StyledLabel(option.0, .subheadingEmphasis, emphasis: .custom(selection == option.1 ? .black : .white.opacity(Design.Opacity.strong)))
.font(Design.Typography.settingsTitle)
.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)
.background( .background(

View File

@ -67,13 +67,9 @@ public struct SelectableRow<Badge: View>: View {
Button(action: action) { Button(action: action) {
HStack { HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text(title) StyledLabel(title, .calloutEmphasis, emphasis: .inverse)
.font(Design.Typography.calloutBold)
.foregroundStyle(.white)
Text(subtitle) StyledLabel(subtitle, .subheading, emphasis: .tertiary)
.font(Design.Typography.body)
.foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
Spacer() Spacer()

View File

@ -67,10 +67,10 @@ public struct SettingsNavigationRow<Destination: View>: View {
} label: { } label: {
HStack { HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
TitleLabel(title, style: .settings) StyledLabel(title, .subheadingEmphasis)
if let subtitle { if let subtitle {
CaptionLabel(subtitle) StyledLabel(subtitle, .caption, emphasis: .secondary)
} }
} }

View File

@ -59,12 +59,12 @@ public struct SettingsRow<Accessory: View>: View {
.background(iconColor.opacity(Design.Opacity.heavy)) .background(iconColor.opacity(Design.Opacity.heavy))
.clipShape(.rect(cornerRadius: Design.CornerRadius.xSmall)) .clipShape(.rect(cornerRadius: Design.CornerRadius.xSmall))
BodyLabel(title) StyledLabel(title, .subheading)
Spacer() Spacer()
if let value { if let value {
BodyLabel(value, emphasis: .secondary) StyledLabel(value, .subheading, emphasis: .secondary)
} }
if let accessory { if let accessory {

View File

@ -40,8 +40,8 @@ public struct SettingsSectionHeader: View {
} }
Text(title) Text(title)
.font(Design.Typography.sectionHeader) .font(Typography.captionEmphasis.font)
.foregroundStyle(.secondary) .foregroundStyle(Theme.Text.secondary)
.textCase(.uppercase) .textCase(.uppercase)
.tracking(0.5) .tracking(0.5)

View File

@ -79,16 +79,16 @@ public struct SettingsSegmentedPicker<T: Equatable, Accessory: View>: View {
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
// Title row with optional accessory // Title row with optional accessory
HStack(spacing: Design.Spacing.xSmall) { HStack(spacing: Design.Spacing.xSmall) {
TitleLabel(title, style: .settings) StyledLabel(title, .subheadingEmphasis)
if let titleAccessory { if let titleAccessory {
titleAccessory titleAccessory
.font(Design.Typography.captionSmall) .font(Typography.caption2.font)
} }
} }
// Subtitle // Subtitle
CaptionLabel(subtitle) StyledLabel(subtitle, .caption, emphasis: .secondary)
// Segmented buttons // Segmented buttons
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
@ -97,9 +97,7 @@ public struct SettingsSegmentedPicker<T: Equatable, Accessory: View>: View {
Button { Button {
selection = option.1 selection = option.1
} label: { } label: {
Text(option.0) StyledLabel(option.0, .subheadingEmphasis, emphasis: .custom(selection == option.1 ? Color.white : Theme.Text.primary))
.font(Design.Typography.settingsTitle)
.foregroundStyle(selection == option.1 ? Color.white : .primary)
.padding(.vertical, Design.Spacing.small) .padding(.vertical, Design.Spacing.small)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background( .background(

View File

@ -110,18 +110,18 @@ public struct SettingsSlider<Value: BinaryFloatingPoint & Sendable>: View where
public var body: some View { public var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
HStack { HStack {
TitleLabel(title, style: .settings) StyledLabel(title, .subheadingEmphasis)
Spacer() Spacer()
// Exception: Needs .fontDesign(.rounded) modifier // Exception: Needs .fontDesign(.rounded) modifier
Text(format(value)) Text(format(value))
.font(Design.Typography.settingsTitle) .font(Typography.subheadingEmphasis.font)
.fontDesign(.rounded) .fontDesign(.rounded)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
CaptionLabel(subtitle) StyledLabel(subtitle, .caption, emphasis: .secondary)
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
if let leadingIcon { if let leadingIcon {

View File

@ -78,15 +78,15 @@ public struct SettingsToggle<Accessory: View>: View {
Toggle(isOn: $isOn) { 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) {
TitleLabel(title, style: .settings) StyledLabel(title, .subheadingEmphasis)
if let titleAccessory { if let titleAccessory {
titleAccessory titleAccessory
.font(Design.Typography.captionSmall) .font(Typography.caption2.font)
} }
} }
BodyLabel(subtitle, emphasis: .secondary) StyledLabel(subtitle, .subheading, emphasis: .secondary)
} }
} }
.tint(accentColor) .tint(accentColor)

View File

@ -117,18 +117,14 @@ public struct iCloudSyncSettingsView<ViewModel: CloudSyncable>: View {
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
SymbolIcon(syncStatusIcon, size: .inline, color: syncStatusColor) SymbolIcon(syncStatusIcon, size: .inline, color: syncStatusColor)
Text(syncStatusText) StyledLabel(syncStatusText, .caption, emphasis: .tertiary)
.font(Design.Typography.caption)
.foregroundStyle(.white.opacity(Design.Opacity.medium))
Spacer() Spacer()
Button { Button {
viewModel.forceSync() viewModel.forceSync()
} label: { } label: {
Text(String(localized: "Sync Now")) StyledLabel(String(localized: "Sync Now"), .captionEmphasis, emphasis: .custom(accentColor))
.font(Design.Typography.captionMedium)
.foregroundStyle(accentColor)
} }
} }
.padding(.top, Design.Spacing.xSmall) .padding(.top, Design.Spacing.xSmall)

View File

@ -7,209 +7,103 @@
import SwiftUI import SwiftUI
// MARK: - Title Label // MARK: - Styled Label
/// A title label with semantic styling. /// A text label with semantic typography and color.
/// ///
/// Use `TitleLabel` for card titles, section headers, and row titles /// Use `StyledLabel` for all text that follows the typography system.
/// to ensure consistent typography across your app. /// It combines a `Typography` style with a `TextEmphasis` color.
/// ///
/// ## Example /// ## Example
/// ///
/// ```swift /// ```swift
/// TitleLabel("Settings", style: .settings) /// StyledLabel("Morning Ritual", .heading)
/// TitleLabel("Morning Ritual", style: .card) /// StyledLabel("Day 6 of 28", .caption, emphasis: .secondary)
/// StyledLabel("Warning", .bodyEmphasis, emphasis: .custom(.red))
/// ``` /// ```
public struct TitleLabel: View { public struct StyledLabel: View {
private let text: String private let text: String
private let style: Style private let typography: Typography
private let color: Color? private let emphasis: TextEmphasis
private let alignment: TextAlignment?
private let lineLimit: Int?
/// Title label styles. /// Creates a styled label.
public enum Style {
/// Large display title for splash screens.
case displayLarge
/// Medium display title for major headings.
case displayMedium
/// Small display title for section titles.
case displaySmall
/// Standard headline for cards and sections.
case headline
/// Settings row title.
case settings
/// Callout style for selectable rows.
case callout
var font: Font {
switch self {
case .displayLarge: Design.Typography.displayLarge
case .displayMedium: Design.Typography.displayMedium
case .displaySmall: Design.Typography.displaySmall
case .headline: Design.Typography.headline
case .settings: Design.Typography.settingsTitle
case .callout: Design.Typography.calloutBold
}
}
}
/// Creates a title label.
/// - Parameters: /// - Parameters:
/// - text: The text to display. /// - text: The text to display.
/// - style: The title style (default: `.headline`). /// - typography: The typography style (default: `.body`).
/// - color: Optional color override (default: `.primary`). /// - emphasis: The text emphasis/color (default: `.primary`).
public init(_ text: String, style: Style = .headline, color: Color? = nil) { /// - alignment: Optional multiline text alignment.
self.text = text /// - lineLimit: Optional line limit.
self.style = style public init(
self.color = color _ text: String,
} _ typography: Typography = .body,
emphasis: TextEmphasis = .primary,
public var body: some View { alignment: TextAlignment? = nil,
Text(text) lineLimit: Int? = nil
.font(style.font) ) {
.foregroundStyle(color ?? .primary)
}
}
// MARK: - Body Label
/// A body text label with emphasis levels.
///
/// Use `BodyLabel` for descriptive text, row subtitles, and content
/// to ensure consistent typography across your app.
///
/// ## Example
///
/// ```swift
/// BodyLabel("Your daily rituals", emphasis: .secondary)
/// BodyLabel("Important note", emphasis: .primary, weight: .medium)
/// BodyLabel("Custom color", color: AppTextColors.primary)
/// ```
public struct BodyLabel: View {
private let text: String
private let emphasis: Emphasis
private let weight: Weight
private let color: Color?
/// Body text emphasis levels.
public enum Emphasis {
/// Primary text color.
case primary
/// Secondary text color.
case secondary
var color: Color {
switch self {
case .primary: .primary
case .secondary: .secondary
}
}
}
/// Body text weight.
public enum Weight {
case regular
case medium
case bold
var font: Font {
switch self {
case .regular: Design.Typography.body
case .medium: Design.Typography.bodyMedium
case .bold: Design.Typography.bodyBold
}
}
}
/// Creates a body label.
/// - Parameters:
/// - text: The text to display.
/// - emphasis: The emphasis level (default: `.primary`).
/// - weight: The font weight (default: `.regular`)one /// - color: Optional color override (takes precedence over emphasis).
public init(_ text: String, emphasis: Emphasis = .primary, weight: Weight = .regular, color: Color? = nil) {
self.text = text self.text = text
self.typography = typography
self.emphasis = emphasis self.emphasis = emphasis
self.weight = weight self.alignment = alignment
self.color = color self.lineLimit = lineLimit
} }
public var body: some View { public var body: some View {
Text(text) Text(text)
.font(weight.font) .font(typography.font)
.foregroundStyle(color ?? emphasis.color) .foregroundStyle(emphasis.color)
.multilineTextAlignment(alignment ?? .leading)
.lineLimit(lineLimit)
} }
} }
// MARK: - Caption Label // MARK: - Icon Label
/// A caption label for metadata and small text. /// A label with an icon and text, styled with semantic typography.
/// ///
/// Use `CaptionLabel` for timestamps, metadata, badges, and fine print /// Use `IconLabel` for icon + text combinations like menu items or list rows.
/// to ensure consistent typography across your app.
/// ///
/// ## Example /// ## Example
/// ///
/// ```swift /// ```swift
/// CaptionLabel("Day 6 of 28", style: .badge) /// IconLabel("bell.fill", "Notifications", .subheading)
/// CaptionLabel("Last updated 2 hours ago", style: .standard) /// IconLabel("star.fill", "Favorites", .body, emphasis: .secondary)
/// ``` /// ```
public struct CaptionLabel: View { public struct IconLabel: View {
private let icon: String
private let text: String private let text: String
private let style: Style private let typography: Typography
private let color: Color? private let emphasis: TextEmphasis
/// Caption label styles. /// Creates an icon label.
public enum Style {
/// Standard caption for metadata.
case standard
/// Medium weight caption for labels.
case medium
/// Bold caption for section headers.
case bold
/// Small caption for fine print.
case small
/// Badge text with bold weight.
case badge
/// Uppercase section header.
case sectionHeader
var font: Font {
switch self {
case .standard: Design.Typography.caption
case .medium: Design.Typography.captionMedium
case .bold: Design.Typography.captionBold
case .small: Design.Typography.captionSmall
case .badge: Design.Typography.badge
case .sectionHeader: Design.Typography.sectionHeader
}
}
var isUppercase: Bool {
self == .sectionHeader
}
}
/// Creates a caption label.
/// - Parameters: /// - Parameters:
/// - icon: The SF Symbol name.
/// - text: The text to display. /// - text: The text to display.
/// - style: The caption style (default: `.standard`). /// - typography: The typography style (default: `.body`).
/// - color: Optional color override (default: `.secondary`). /// - emphasis: The text emphasis/color (default: `.primary`).
public init(_ text: String, style: Style = .standard, color: Color? = nil) { public init(
_ icon: String,
_ text: String,
_ typography: Typography = .body,
emphasis: TextEmphasis = .primary
) {
self.icon = icon
self.text = text self.text = text
self.style = style self.typography = typography
self.color = color self.emphasis = emphasis
} }
public var body: some View { public var body: some View {
Text(text) Label(text, systemImage: icon)
.font(style.font) .font(typography.font)
.foregroundStyle(color ?? .secondary) .foregroundStyle(emphasis.color)
.textCase(style.isUppercase ? .uppercase : nil)
} }
} }
// MARK: - Section Header // MARK: - Section Header
/// A section header label with uppercase styling. /// A section header with uppercase styling.
/// ///
/// Use `SectionHeader` for grouped list section titles /// Use `SectionHeader` for grouped list section titles
/// to match iOS Settings app conventions. /// to match iOS Settings app conventions.
@ -217,7 +111,7 @@ public struct CaptionLabel: View {
/// ## Example /// ## Example
/// ///
/// ```swift /// ```swift
/// SectionHeader("GENERAL") /// SectionHeader("General")
/// SectionHeader("Notifications", icon: "bell.fill") /// SectionHeader("Notifications", icon: "bell.fill")
/// ``` /// ```
public struct SectionHeader: View { public struct SectionHeader: View {
@ -243,47 +137,51 @@ public struct SectionHeader: View {
} }
Text(title) Text(title)
.font(Design.Typography.sectionHeader) .font(Typography.caption.font)
.foregroundStyle(.secondary) .foregroundStyle(Theme.Text.secondary)
.textCase(.uppercase) .textCase(.uppercase)
.tracking(0.5) .tracking(0.5)
} }
} }
} }
// MARK: - Preview // MARK: - Previews
#Preview("Title Labels") { #Preview("Styled Labels") {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
TitleLabel("Display Large", style: .displayLarge) Group {
TitleLabel("Display Medium", style: .displayMedium) StyledLabel("Hero Bold", .heroBold)
TitleLabel("Display Small", style: .displaySmall) StyledLabel("Title Bold", .titleBold)
TitleLabel("Headline", style: .headline) StyledLabel("Title 2", .title2)
TitleLabel("Settings Title", style: .settings) StyledLabel("Heading", .heading)
TitleLabel("Callout", style: .callout) StyledLabel("Heading Emphasis", .headingEmphasis)
}
Divider()
Group {
StyledLabel("Body", .body)
StyledLabel("Body Emphasis", .bodyEmphasis)
StyledLabel("Subheading", .subheading, emphasis: .secondary)
StyledLabel("Caption", .caption, emphasis: .secondary)
StyledLabel("Caption 2", .caption2, emphasis: .tertiary)
}
Divider()
Group {
StyledLabel("Custom Color", .body, emphasis: .custom(.orange))
StyledLabel("Disabled", .body, emphasis: .disabled)
}
} }
.padding() .padding()
} }
#Preview("Body Labels") { #Preview("Icon Labels") {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
BodyLabel("Primary body text") IconLabel("bell.fill", "Notifications", .subheading)
BodyLabel("Secondary body text", emphasis: .secondary) IconLabel("star.fill", "Favorites", .body, emphasis: .secondary)
BodyLabel("Custom color body", color: .gray) IconLabel("gear", "Settings", .caption)
BodyLabel("Medium weight body", weight: .medium)
BodyLabel("Bold body text", weight: .bold)
}
.padding()
}
#Preview("Caption Labels") {
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
CaptionLabel("Standard caption")
CaptionLabel("Medium caption", style: .medium)
CaptionLabel("Bold caption", style: .bold)
CaptionLabel("Small caption", style: .small)
CaptionLabel("Badge text", style: .badge)
CaptionLabel("Section Header", style: .sectionHeader)
} }
.padding() .padding()
} }