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

This commit is contained in:
Matt Bruce 2026-01-27 11:55:51 -06:00
parent 7859b22927
commit 492e088cef
15 changed files with 189 additions and 202 deletions

View File

@ -9,9 +9,7 @@ The typography system consists of:
1. **`Theme`** - Global theme registration for all color providers 1. **`Theme`** - Global theme registration for all color providers
2. **`Typography` enum** - All font definitions with explicit naming 2. **`Typography` enum** - All font definitions with explicit naming
3. **`TextEmphasis` enum** - Semantic text colors 3. **`TextEmphasis` enum** - Semantic text colors
4. **`StyledLabel`** - Simple text component combining typography + emphasis 4. **`Text().styled()` / `Label().styled()`** - View extensions for semantic styling
5. **`IconLabel`** - Icon + text component
6. **`.typography()` modifier** - View extension for applying fonts
--- ---
@ -37,8 +35,8 @@ This enables Bedrock components to use your app's colors automatically.
After registration, use `Theme.*` to access your registered colors: After registration, use `Theme.*` to access your registered colors:
```swift ```swift
// In Bedrock components (automatic via TextEmphasis) // In views (automatic via TextEmphasis)
StyledLabel("Title", .heading) // Uses Theme.Text.primary Text("Title").styled(.heading) // Uses Theme.Text.primary
// Direct access when needed // Direct access when needed
let bgColor = Theme.Surface.card let bgColor = Theme.Surface.card
@ -88,7 +86,7 @@ All fonts are defined in the `Typography` enum. Each case name explicitly descri
## TextEmphasis Enum ## TextEmphasis Enum
Semantic text colors resolved from the registered `TextTheme`: Semantic text colors resolved from the registered Theme:
| Emphasis | Description | | Emphasis | Description |
|----------|-------------| |----------|-------------|
@ -101,52 +99,67 @@ Semantic text colors resolved from the registered `TextTheme`:
### When to Use `.custom()` ### When to Use `.custom()`
Only use `.custom()` for colors that aren't text colors: Only use `.custom()` for colors that aren't semantic text colors:
```swift ```swift
// ✅ Semantic text colors - use the emphasis value directly // ✅ Semantic text colors - use the emphasis value directly
StyledLabel("Title", .heading) // .primary is default Text("Title").styled(.heading) // .primary is default
StyledLabel("Subtitle", .subheading, emphasis: .secondary) Text("Subtitle").styled(.subheading, emphasis: .secondary)
StyledLabel("Hint", .caption, emphasis: .tertiary) Text("Hint").styled(.caption, emphasis: .tertiary)
// ✅ Use .custom() for non-text colors // ✅ Use .custom() for non-text colors
StyledLabel("Success!", .heading, emphasis: .custom(AppStatus.success)) Text("Success!").styled(.heading, emphasis: .custom(AppStatus.success))
StyledLabel("Arc 3", .caption, emphasis: .custom(AppAccent.primary)) Text("Arc 3").styled(.caption, emphasis: .custom(AppAccent.primary))
// ✅ Use .custom() for conditional colors // ✅ Use .custom() for conditional colors
StyledLabel(text, .body, emphasis: .custom(isActive ? AppStatus.success : AppTextColors.tertiary)) Text(text).styled(.body, emphasis: .custom(isActive ? AppStatus.success : AppTextColors.tertiary))
``` ```
--- ---
## StyledLabel Component ## Text().styled() Extension
A single component for all styled text: The primary way to style text:
```swift ```swift
StyledLabel( Text(_ text: String)
_ text: String, .styled(_ typography: Typography = .body, emphasis: TextEmphasis = .primary)
_ typography: Typography = .body,
emphasis: TextEmphasis = .primary,
alignment: TextAlignment? = nil,
lineLimit: Int? = nil
)
``` ```
### Examples ### Examples
```swift ```swift
// Simple - uses registered theme's primary color // Simple - uses registered theme's primary color
StyledLabel("Morning Ritual", .heading) Text("Morning Ritual").styled(.heading)
StyledLabel("Day 6 of 28", .caption, emphasis: .secondary) Text("Day 6 of 28").styled(.caption, emphasis: .secondary)
// With alignment and line limit // String interpolation
StyledLabel("Centered text", .body, alignment: .center) Text("Count: \(count)").styled(.bodyEmphasis)
StyledLabel("One line", .caption, emphasis: .tertiary, lineLimit: 1)
// With additional modifiers
Text("Centered text")
.styled(.body)
.multilineTextAlignment(.center)
Text("One line")
.styled(.caption, emphasis: .tertiary)
.lineLimit(1)
// Font design variants
Text("$9.99")
.styled(.subheadingEmphasis)
.fontDesign(.rounded)
// Uppercase headers
Text("SECTION")
.styled(.captionEmphasis, emphasis: .secondary)
.textCase(.uppercase)
.tracking(0.5)
// In buttons // In buttons
Button(action: onContinue) { Button(action: onContinue) {
StyledLabel("Continue", .heading, emphasis: .inverse) Text("Continue")
.styled(.heading, emphasis: .inverse)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 50) .frame(height: 50)
.background(AppAccent.primary) .background(AppAccent.primary)
@ -155,66 +168,48 @@ Button(action: onContinue) {
--- ---
## IconLabel Component ## Label().styled() Extension
For icon + text combinations: For icon + text combinations:
```swift ```swift
IconLabel(_ icon: String, _ text: String, _ typography: Typography = .body, emphasis: TextEmphasis = .primary) Label(_ text: String, systemImage: String)
.styled(_ typography: Typography = .body, emphasis: TextEmphasis = .primary)
``` ```
### Examples ### Examples
```swift ```swift
IconLabel("bell.fill", "Notifications", .subheading) Label("Notifications", systemImage: "bell.fill")
IconLabel("star.fill", "Favorites", .body, emphasis: .secondary) .styled(.subheading)
IconLabel("flame.fill", "5-day streak", .caption, emphasis: .custom(AppStatus.success))
Label("Favorites", systemImage: "star.fill")
.styled(.body, emphasis: .secondary)
Label("5-day streak", systemImage: "flame.fill")
.styled(.caption, emphasis: .custom(AppStatus.success))
// In buttons
Button(action: addItem) {
Label("Add to Rituals", systemImage: "plus.circle.fill")
.styled(.heading, emphasis: .inverse)
.frame(maxWidth: .infinity)
.background(AppAccent.primary)
}
``` ```
--- ---
## When to Use StyledLabel vs Text() ## Never Use Raw `.font()` with System Fonts
### 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)
.typography(.subheadingEmphasis)
.fontDesign(.rounded)
// TextField styling
TextField("Placeholder", text: $value)
.typography(.heading)
// Inside special view builders (like Charts AxisValueLabel)
AxisValueLabel {
Text("\(value)%")
.font(Typography.caption2.font)
.foregroundStyle(color)
}
```
### Never Use Raw `.font()` with System Fonts
```swift ```swift
// ❌ BAD // ❌ BAD
Text("Title").font(.headline) Text("Title").font(.headline)
Text("Body").font(.body).foregroundStyle(.white)
// ✅ GOOD // ✅ GOOD
StyledLabel("Title", .heading) Text("Title").styled(.heading)
Text("Body").styled(.body, emphasis: .inverse)
``` ```
--- ---
@ -227,16 +222,21 @@ StyledLabel("Title", .heading)
// OLD: Design.Typography // OLD: Design.Typography
Text("Title").font(Design.Typography.headline) Text("Title").font(Design.Typography.headline)
// NEW: StyledLabel // NEW: Text().styled()
StyledLabel("Title", .heading) Text("Title").styled(.heading)
// OLD: Multiple Label components // OLD: StyledLabel component
TitleLabel("Title", style: .headline, color: .white)
BodyLabel("Body", emphasis: .secondary)
// NEW: Single StyledLabel
StyledLabel("Title", .heading, emphasis: .inverse) StyledLabel("Title", .heading, emphasis: .inverse)
StyledLabel("Body", .subheading, emphasis: .secondary)
// NEW: Text().styled()
Text("Title").styled(.heading, emphasis: .inverse)
// OLD: IconLabel component
IconLabel("bell.fill", "Notifications", .subheading)
// NEW: Label().styled()
Label("Notifications", systemImage: "bell.fill")
.styled(.subheading)
``` ```
### Mapping Table ### Mapping Table

View File

@ -26,9 +26,9 @@ import SwiftUI
/// ) /// )
/// ///
/// // Then use emphasis levels directly /// // Then use emphasis levels directly
/// StyledLabel("Primary text", .body) // uses .primary /// Text("Primary text").styled(.body) // uses .primary
/// StyledLabel("Secondary text", .caption, emphasis: .secondary) /// Text("Secondary text").styled(.caption, emphasis: .secondary)
/// StyledLabel("Custom color", .heading, emphasis: .custom(.red)) /// Text("Custom color").styled(.heading, emphasis: .custom(.red))
/// ``` /// ```
public enum TextEmphasis: Sendable { public enum TextEmphasis: Sendable {
/// Primary text color (highest emphasis). Default. /// Primary text color (highest emphasis). Default.

View File

@ -12,7 +12,7 @@ import SwiftUI
/// Global theme provider for Bedrock components. /// Global theme provider for Bedrock components.
/// ///
/// Register your app's theme once at launch to enable semantic colors /// Register your app's theme once at launch to enable semantic colors
/// in `StyledLabel`, `IconLabel`, and other Bedrock components. /// in `Text().styled()`, `Label().styled()`, and other Bedrock components.
/// ///
/// ## Usage /// ## Usage
/// ///
@ -26,8 +26,8 @@ import SwiftUI
/// ) /// )
/// ///
/// // Then use semantic emphasis directly /// // Then use semantic emphasis directly
/// StyledLabel("Title", .heading) // uses Theme.Text.primary /// Text("Title").styled(.heading) // uses Theme.Text.primary
/// StyledLabel("Subtitle", .subheading, emphasis: .secondary) // uses Theme.Text.secondary /// Text("Subtitle").styled(.subheading, emphasis: .secondary) // uses Theme.Text.secondary
/// ``` /// ```
public enum Theme { public enum Theme {
// MARK: - Registered Providers // MARK: - Registered Providers

View File

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

View File

@ -75,18 +75,20 @@ 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) {
StyledLabel(license.name, .bodyEmphasis, emphasis: .inverse) Text(license.name).styled(.bodyEmphasis, emphasis: .inverse)
StyledLabel(license.description, .caption, emphasis: .secondary) Text(license.description).styled(.caption, emphasis: .secondary)
HStack { HStack {
IconLabel("doc.text", license.licenseType, .caption2, emphasis: .custom(accentColor)) Label(license.licenseType, systemImage: "doc.text")
.styled(.caption2, emphasis: .custom(accentColor))
Spacer() Spacer()
if let linkURL = URL(string: license.url) { if let linkURL = URL(string: license.url) {
Link(destination: linkURL) { Link(destination: linkURL) {
IconLabel("arrow.up.right.square", String(localized: "View on GitHub"), .caption2, emphasis: .custom(accentColor)) Label(String(localized: "View on GitHub"), systemImage: "arrow.up.right.square")
.styled(.caption2, emphasis: .custom(accentColor))
} }
} }
} }

View File

@ -51,7 +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) {
StyledLabel(title, .subheadingEmphasis, emphasis: .inverse) Text(title).styled(.subheadingEmphasis, emphasis: .inverse)
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
ForEach(options.indices, id: \.self) { index in ForEach(options.indices, id: \.self) { index in
@ -59,7 +59,8 @@ public struct SegmentedPicker<T: Equatable>: View {
Button { Button {
selection = option.1 selection = option.1
} label: { } label: {
StyledLabel(option.0, .subheadingEmphasis, emphasis: .custom(selection == option.1 ? .black : .white.opacity(Design.Opacity.strong))) Text(option.0)
.styled(.subheadingEmphasis, emphasis: .custom(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,9 +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) {
StyledLabel(title, .calloutEmphasis, emphasis: .inverse) Text(title).styled(.calloutEmphasis, emphasis: .inverse)
StyledLabel(subtitle, .subheading, emphasis: .tertiary) Text(subtitle).styled(.subheading, emphasis: .tertiary)
} }
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) {
StyledLabel(title, .subheadingEmphasis) Text(title).styled(.subheadingEmphasis)
if let subtitle { if let subtitle {
StyledLabel(subtitle, .caption, emphasis: .secondary) Text(subtitle).styled(.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))
StyledLabel(title, .subheading) Text(title).styled(.subheading)
Spacer() Spacer()
if let value { if let value {
StyledLabel(value, .subheading, emphasis: .secondary) Text(value).styled(.subheading, emphasis: .secondary)
} }
if let accessory { if let accessory {

View File

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

View File

@ -79,7 +79,7 @@ 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) {
StyledLabel(title, .subheadingEmphasis) Text(title).styled(.subheadingEmphasis)
if let titleAccessory { if let titleAccessory {
titleAccessory titleAccessory
@ -88,7 +88,7 @@ public struct SettingsSegmentedPicker<T: Equatable, Accessory: View>: View {
} }
// Subtitle // Subtitle
StyledLabel(subtitle, .caption, emphasis: .secondary) Text(subtitle).styled(.caption, emphasis: .secondary)
// Segmented buttons // Segmented buttons
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
@ -97,7 +97,8 @@ public struct SettingsSegmentedPicker<T: Equatable, Accessory: View>: View {
Button { Button {
selection = option.1 selection = option.1
} label: { } label: {
StyledLabel(option.0, .subheadingEmphasis, emphasis: .custom(selection == option.1 ? Color.white : Theme.Text.primary)) Text(option.0)
.styled(.subheadingEmphasis, emphasis: .custom(selection == option.1 ? Color.white : Theme.Text.primary))
.padding(.vertical, Design.Spacing.small) .padding(.vertical, Design.Spacing.small)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background( .background(

View File

@ -110,18 +110,17 @@ 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 {
StyledLabel(title, .subheadingEmphasis) Text(title).styled(.subheadingEmphasis)
Spacer() Spacer()
// Exception: Needs .fontDesign(.rounded) modifier // Exception: Needs .fontDesign(.rounded) modifier
Text(format(value)) Text(format(value))
.font(Typography.subheadingEmphasis.font) .styled(.subheadingEmphasis, emphasis: .secondary)
.fontDesign(.rounded) .fontDesign(.rounded)
.foregroundStyle(.secondary)
} }
StyledLabel(subtitle, .caption, emphasis: .secondary) Text(subtitle).styled(.caption, emphasis: .secondary)
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
if let leadingIcon { if let leadingIcon {

View File

@ -78,7 +78,7 @@ 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) {
StyledLabel(title, .subheadingEmphasis) Text(title).styled(.subheadingEmphasis)
if let titleAccessory { if let titleAccessory {
titleAccessory titleAccessory
@ -86,7 +86,7 @@ public struct SettingsToggle<Accessory: View>: View {
} }
} }
StyledLabel(subtitle, .subheading, emphasis: .secondary) Text(subtitle).styled(.subheading, emphasis: .secondary)
} }
} }
.tint(accentColor) .tint(accentColor)

View File

@ -117,14 +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)
StyledLabel(syncStatusText, .caption, emphasis: .tertiary) Text(syncStatusText).styled(.caption, emphasis: .tertiary)
Spacer() Spacer()
Button { Button {
viewModel.forceSync() viewModel.forceSync()
} label: { } label: {
StyledLabel(String(localized: "Sync Now"), .captionEmphasis, emphasis: .custom(accentColor)) Text(String(localized: "Sync Now")).styled(.captionEmphasis, emphasis: .custom(accentColor))
} }
} }
.padding(.top, Design.Spacing.xSmall) .padding(.top, Design.Spacing.xSmall)

View File

@ -2,100 +2,70 @@
// StyledText.swift // StyledText.swift
// Bedrock // Bedrock
// //
// Semantic text components for consistent typography. // Semantic text styling extensions for consistent typography.
// //
import SwiftUI import SwiftUI
// MARK: - Styled Label // MARK: - Text Extension
/// A text label with semantic typography and color. public extension Text {
/// /// Applies semantic typography and emphasis styling to a Text view.
/// Use `StyledLabel` for all text that follows the typography system. ///
/// It combines a `Typography` style with a `TextEmphasis` color. /// ## Example
/// ///
/// ## Example /// ```swift
/// /// Text("Hello, \(name)!")
/// ```swift /// .styled(.bodyEmphasis, emphasis: .secondary)
/// StyledLabel("Morning Ritual", .heading) ///
/// StyledLabel("Day 6 of 28", .caption, emphasis: .secondary) /// Text(attributedString)
/// StyledLabel("Warning", .bodyEmphasis, emphasis: .custom(.red)) /// .styled(.caption)
/// ``` ///
public struct StyledLabel: View { /// // With additional modifiers
private let text: String /// Text("HEADER")
private let typography: Typography /// .styled(.captionEmphasis, emphasis: .secondary)
private let emphasis: TextEmphasis /// .textCase(.uppercase)
private let alignment: TextAlignment? /// .tracking(0.5)
private let lineLimit: Int? /// ```
///
/// Creates a styled label.
/// - Parameters: /// - Parameters:
/// - text: The text to display.
/// - typography: The typography style (default: `.body`). /// - typography: The typography style (default: `.body`).
/// - emphasis: The text emphasis/color (default: `.primary`). /// - emphasis: The text emphasis/color (default: `.primary`).
/// - alignment: Optional multiline text alignment. /// - Returns: A styled Text view.
/// - lineLimit: Optional line limit. func styled(
public init(
_ text: String,
_ typography: Typography = .body, _ typography: Typography = .body,
emphasis: TextEmphasis = .primary, emphasis: TextEmphasis = .primary
alignment: TextAlignment? = nil, ) -> some View {
lineLimit: Int? = nil self
) {
self.text = text
self.typography = typography
self.emphasis = emphasis
self.alignment = alignment
self.lineLimit = lineLimit
}
public var body: some View {
Text(text)
.font(typography.font) .font(typography.font)
.foregroundStyle(emphasis.color) .foregroundStyle(emphasis.color)
.multilineTextAlignment(alignment ?? .leading)
.lineLimit(lineLimit)
} }
} }
// MARK: - Icon Label // MARK: - Label Extension
/// A label with an icon and text, styled with semantic typography. public extension Label where Title == Text, Icon == Image {
/// /// Applies semantic typography and emphasis styling to a Label view.
/// Use `IconLabel` for icon + text combinations like menu items or list rows. ///
/// /// ## Example
/// ## Example ///
/// /// ```swift
/// ```swift /// Label("Notifications", systemImage: "bell.fill")
/// IconLabel("bell.fill", "Notifications", .subheading) /// .styled(.subheading, emphasis: .secondary)
/// IconLabel("star.fill", "Favorites", .body, emphasis: .secondary) ///
/// ``` /// Label("Settings", systemImage: "gear")
public struct IconLabel: View { /// .styled(.body)
private let icon: String /// ```
private let text: String ///
private let typography: Typography
private let emphasis: TextEmphasis
/// Creates an icon label.
/// - Parameters: /// - Parameters:
/// - icon: The SF Symbol name.
/// - text: The text to display.
/// - typography: The typography style (default: `.body`). /// - typography: The typography style (default: `.body`).
/// - emphasis: The text emphasis/color (default: `.primary`). /// - emphasis: The text emphasis/color (default: `.primary`).
public init( /// - Returns: A styled Label view.
_ icon: String, func styled(
_ text: String,
_ typography: Typography = .body, _ typography: Typography = .body,
emphasis: TextEmphasis = .primary emphasis: TextEmphasis = .primary
) { ) -> some View {
self.icon = icon self
self.text = text
self.typography = typography
self.emphasis = emphasis
}
public var body: some View {
Label(text, systemImage: icon)
.font(typography.font) .font(typography.font)
.foregroundStyle(emphasis.color) .foregroundStyle(emphasis.color)
} }
@ -137,8 +107,7 @@ public struct SectionHeader: View {
} }
Text(title) Text(title)
.font(Typography.caption.font) .styled(.caption, emphasis: .secondary)
.foregroundStyle(Theme.Text.secondary)
.textCase(.uppercase) .textCase(.uppercase)
.tracking(0.5) .tracking(0.5)
} }
@ -147,41 +116,57 @@ public struct SectionHeader: View {
// MARK: - Previews // MARK: - Previews
#Preview("Styled Labels") { #Preview("Text.styled()") {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Group { Group {
StyledLabel("Hero Bold", .heroBold) Text("Hero Bold").styled(.heroBold)
StyledLabel("Title Bold", .titleBold) Text("Title Bold").styled(.titleBold)
StyledLabel("Title 2", .title2) Text("Title 2").styled(.title2)
StyledLabel("Heading", .heading) Text("Heading").styled(.heading)
StyledLabel("Heading Emphasis", .headingEmphasis) Text("Heading Emphasis").styled(.headingEmphasis)
} }
Divider() Divider()
Group { Group {
StyledLabel("Body", .body) Text("Body").styled(.body)
StyledLabel("Body Emphasis", .bodyEmphasis) Text("Body Emphasis").styled(.bodyEmphasis)
StyledLabel("Subheading", .subheading, emphasis: .secondary) Text("Subheading").styled(.subheading, emphasis: .secondary)
StyledLabel("Caption", .caption, emphasis: .secondary) Text("Caption").styled(.caption, emphasis: .secondary)
StyledLabel("Caption 2", .caption2, emphasis: .tertiary) Text("Caption 2").styled(.caption2, emphasis: .tertiary)
} }
Divider() Divider()
Group { Group {
StyledLabel("Custom Color", .body, emphasis: .custom(.orange)) Text("Custom Color").styled(.body, emphasis: .custom(.orange))
StyledLabel("Disabled", .body, emphasis: .disabled) Text("Disabled").styled(.body, emphasis: .disabled)
}
Divider()
Group {
Text("Count: \(42)").styled(.heading)
Text("UPPERCASE")
.styled(.captionEmphasis, emphasis: .secondary)
.textCase(.uppercase)
.tracking(0.5)
Text("$9.99")
.styled(.subheadingEmphasis)
.fontDesign(.rounded)
} }
} }
.padding() .padding()
} }
#Preview("Icon Labels") { #Preview("Label.styled()") {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
IconLabel("bell.fill", "Notifications", .subheading) Label("Notifications", systemImage: "bell.fill")
IconLabel("star.fill", "Favorites", .body, emphasis: .secondary) .styled(.subheading)
IconLabel("gear", "Settings", .caption) Label("Favorites", systemImage: "star.fill")
.styled(.body, emphasis: .secondary)
Label("Settings", systemImage: "gear")
.styled(.caption)
} }
.padding() .padding()
} }