From cc650c68d428b506a5ad56eef890125722d574c9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 4 Jan 2026 16:54:04 -0600 Subject: [PATCH] Add settings theming guide and fix Color typealias conflicts - Add SETTINGS_GUIDE.md with comprehensive documentation for creating branded settings screens - Rename Color.Text to Color.TextColors to avoid conflict with SwiftUI Text view - Rename Color.Button to Color.ButtonColors to avoid conflict with SwiftUI Button view - Add accentColor parameter to SettingsSectionHeader for customizable section icons - Update README and PulsingModifier to use new TextColors typealias --- README.md | 4 +- Sources/Bedrock/Theme/Colors.swift | 9 +- .../Views/Effects/PulsingModifier.swift | 2 +- .../Bedrock/Views/Settings/SETTINGS_GUIDE.md | 391 ++++++++++++++++++ .../Views/Settings/SettingsComponents.swift | 9 +- 5 files changed, 408 insertions(+), 7 deletions(-) create mode 100644 Sources/Bedrock/Views/Settings/SETTINGS_GUIDE.md diff --git a/README.md b/README.md index c6d576c..fe39692 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,10 @@ Bedrock includes neutral default colors that work out of the box: ```swift Text("Primary Text") - .foregroundStyle(Color.Text.primary) + .foregroundStyle(Color.TextColors.primary) Text("Secondary Text") - .foregroundStyle(Color.Text.secondary) + .foregroundStyle(Color.TextColors.secondary) VStack { } .background(Color.Surface.primary) diff --git a/Sources/Bedrock/Theme/Colors.swift b/Sources/Bedrock/Theme/Colors.swift index ad46475..3df1d00 100644 --- a/Sources/Bedrock/Theme/Colors.swift +++ b/Sources/Bedrock/Theme/Colors.swift @@ -113,18 +113,23 @@ public enum DefaultTheme: AppColorTheme { /// These provide the familiar `Color.Surface.primary` syntax using the /// default theme. Apps using custom themes should access colors through /// their theme type directly (e.g., `CasinoTheme.Surface.primary`). +/// +/// Note: `TextColors` and `ButtonColors` are used instead of `Text` and `Button` +/// to avoid conflicts with SwiftUI's `Text` and `Button` views. public extension Color { /// Default surface colors. typealias Surface = DefaultSurfaceColors /// Default text colors. - typealias Text = DefaultTextColors + /// Note: Named `TextColors` to avoid conflict with SwiftUI's `Text` view. + typealias TextColors = DefaultTextColors /// Default accent colors. typealias Accent = DefaultAccentColors /// Default button colors. - typealias Button = DefaultButtonColors + /// Note: Named `ButtonColors` to avoid conflict with SwiftUI's `Button` view. + typealias ButtonColors = DefaultButtonColors /// Default status colors. typealias Status = DefaultStatusColors diff --git a/Sources/Bedrock/Views/Effects/PulsingModifier.swift b/Sources/Bedrock/Views/Effects/PulsingModifier.swift index fde8feb..871b3bc 100644 --- a/Sources/Bedrock/Views/Effects/PulsingModifier.swift +++ b/Sources/Bedrock/Views/Effects/PulsingModifier.swift @@ -74,7 +74,7 @@ public extension View { .pulsing(isActive: true) Text("Pulsing highlights interactive areas") - .foregroundStyle(Color.Text.secondary) + .foregroundStyle(Color.TextColors.secondary) .font(.caption) } } diff --git a/Sources/Bedrock/Views/Settings/SETTINGS_GUIDE.md b/Sources/Bedrock/Views/Settings/SETTINGS_GUIDE.md new file mode 100644 index 0000000..aee238c --- /dev/null +++ b/Sources/Bedrock/Views/Settings/SETTINGS_GUIDE.md @@ -0,0 +1,391 @@ +# Settings View Setup Guide + +This guide explains how to create a branded settings screen using Bedrock's theming system and settings components. + +## Overview + +Bedrock provides: +1. **Color protocols** for consistent theming (`SurfaceColorProvider`, `AccentColorProvider`, etc.) +2. **Reusable settings components** (`SettingsToggle`, `SettingsCard`, `SegmentedPicker`, etc.) +3. **Design constants** for spacing, typography, and animations + +By creating a custom theme, your app gets a unique visual identity while reusing all the settings UI components. + +--- + +## Step 1: Create Your App's Theme + +Create a file called `[AppName]Theme.swift` in your app's `Shared/` folder. + +### Define Surface Colors + +Surface colors create visual depth and separation. Use a subtle tint that matches your brand: + +```swift +import SwiftUI +import Bedrock + +/// Surface colors with a subtle [brand]-tint. +public enum MyAppSurfaceColors: SurfaceColorProvider { + /// Primary background - darkest level + public static let primary = Color(red: 0.08, green: 0.06, blue: 0.10) + + /// Secondary/elevated surface + public static let secondary = Color(red: 0.12, green: 0.08, blue: 0.14) + + /// Tertiary/card surface - most elevated + public static let tertiary = Color(red: 0.16, green: 0.11, blue: 0.18) + + /// Overlay background (for sheets/modals) + public static let overlay = Color(red: 0.10, green: 0.07, blue: 0.12) + + /// Card/grouped element background + public static let card = Color(red: 0.14, green: 0.10, blue: 0.16) + + /// Subtle fill for grouped content sections + public static let groupedFill = Color(red: 0.12, green: 0.09, blue: 0.14) + + /// Section fill for list sections + public static let sectionFill = Color(red: 0.16, green: 0.12, blue: 0.18) +} +``` + +### Define Accent Colors + +These are your brand's primary interactive colors: + +```swift +public enum MyAppAccentColors: AccentColorProvider { + /// Primary accent - your main brand color + public static let primary = Color(red: 0.85, green: 0.25, blue: 0.45) + + /// Light variant + public static let light = Color(red: 0.95, green: 0.45, blue: 0.60) + + /// Dark variant + public static let dark = Color(red: 0.65, green: 0.18, blue: 0.35) + + /// Secondary accent for contrast + public static let secondary = Color(red: 1.0, green: 0.95, blue: 0.90) +} +``` + +### Define Other Color Providers + +Complete the theme with text, button, status, border, and interactive colors: + +```swift +public enum MyAppTextColors: TextColorProvider { + public static let primary = Color.white + public static let secondary = Color.white.opacity(Design.Opacity.accent) + public static let tertiary = Color.white.opacity(Design.Opacity.medium) + public static let disabled = Color.white.opacity(Design.Opacity.light) + public static let placeholder = Color.white.opacity(Design.Opacity.overlay) + public static let inverse = Color.black +} + +public enum MyAppButtonColors: ButtonColorProvider { + public static let primaryLight = Color(red: 0.95, green: 0.40, blue: 0.55) + public static let primaryDark = Color(red: 0.75, green: 0.20, blue: 0.40) + public static let secondary = Color.white.opacity(Design.Opacity.subtle) + public static let destructive = Color.red.opacity(Design.Opacity.heavy) + public static let cancelText = Color.white.opacity(Design.Opacity.strong) +} + +public enum MyAppStatusColors: StatusColorProvider { + public static let success = Color(red: 0.2, green: 0.8, blue: 0.4) + public static let warning = Color(red: 1.0, green: 0.75, blue: 0.2) + public static let error = Color(red: 0.9, green: 0.3, blue: 0.3) + public static let info = Color(red: 0.5, green: 0.7, blue: 0.95) +} + +public enum MyAppBorderColors: BorderColorProvider { + public static let subtle = Color.white.opacity(Design.Opacity.subtle) + public static let standard = Color.white.opacity(Design.Opacity.hint) + public static let emphasized = Color.white.opacity(Design.Opacity.light) + public static let selected = MyAppAccentColors.primary.opacity(Design.Opacity.medium) +} + +public enum MyAppInteractiveColors: InteractiveColorProvider { + public static let selected = MyAppAccentColors.primary.opacity(Design.Opacity.selection) + public static let hover = Color.white.opacity(Design.Opacity.subtle) + public static let pressed = Color.white.opacity(Design.Opacity.hint) + public static let focus = MyAppAccentColors.light +} +``` + +### Combine Into a Theme + +```swift +public enum MyAppTheme: AppColorTheme { + public typealias Surface = MyAppSurfaceColors + public typealias Text = MyAppTextColors + public typealias Accent = MyAppAccentColors + public typealias Button = MyAppButtonColors + public typealias Status = MyAppStatusColors + public typealias Border = MyAppBorderColors + public typealias Interactive = MyAppInteractiveColors +} +``` + +### Add Convenience Typealiases + +Create top-level typealiases with an `App` prefix to avoid conflicts with Bedrock's defaults: + +```swift +/// Short typealiases for cleaner usage throughout the app. +/// These avoid conflicts with Bedrock's default typealiases by using unique names. +/// +/// Usage: +/// ```swift +/// .background(AppSurface.primary) +/// .foregroundStyle(AppAccent.primary) +/// ``` +typealias AppSurface = MyAppSurfaceColors +typealias AppTextColors = MyAppTextColors +typealias AppAccent = MyAppAccentColors +typealias AppButtonColors = MyAppButtonColors +typealias AppStatus = MyAppStatusColors +typealias AppBorder = MyAppBorderColors +typealias AppInteractive = MyAppInteractiveColors +``` + +> **Important**: Do NOT add typealiases inside `extension Color { }` as they will conflict with Bedrock's defaults and cause "ambiguous use" compiler errors. Use top-level `App`-prefixed typealiases instead. + +--- + +## Step 2: Create a Settings Card Container + +Add a reusable card container for grouping related settings: + +```swift +/// A card container that provides visual grouping for settings sections. +struct SettingsCard: View { + @ViewBuilder let content: Content + + var body: some View { + VStack(alignment: .leading, spacing: Design.Spacing.medium) { + content + } + .padding(Design.Spacing.medium) + .background( + RoundedRectangle(cornerRadius: Design.CornerRadius.large) + .fill(AppSurface.card) + ) + .overlay( + RoundedRectangle(cornerRadius: Design.CornerRadius.large) + .strokeBorder(AppBorder.subtle, lineWidth: Design.LineWidth.thin) + ) + } +} +``` + +--- + +## Step 3: Build Your Settings View + +Use Bedrock's components with your theme colors: + +```swift +import SwiftUI +import Bedrock + +struct SettingsView: View { + @Bindable var viewModel: SettingsViewModel + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + ScrollView { + VStack(spacing: Design.Spacing.medium) { + + // Section with header and card + SettingsSectionHeader( + title: "Appearance", + systemImage: "paintbrush", + accentColor: AppAccent.primary + ) + + SettingsCard { + SettingsToggle( + title: "Dark Mode", + subtitle: "Use dark appearance", + isOn: $viewModel.darkMode, + accentColor: AppAccent.primary + ) + + SegmentedPicker( + title: "Theme", + options: [("Light", "light"), ("Dark", "dark"), ("System", "system")], + selection: $viewModel.theme, + accentColor: AppAccent.primary + ) + } + + // Another section + SettingsSectionHeader( + title: "Notifications", + systemImage: "bell", + accentColor: AppAccent.primary + ) + + SettingsCard { + SettingsToggle( + title: "Push Notifications", + subtitle: "Receive alerts and updates", + isOn: $viewModel.pushEnabled, + accentColor: AppAccent.primary + ) + } + + Spacer(minLength: Design.Spacing.xxxLarge) + } + .padding(.horizontal, Design.Spacing.large) + } + .background(AppSurface.primary) + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { dismiss() } + .foregroundStyle(AppAccent.primary) + } + } + } + } +} +``` + +--- + +## Available Components + +### SettingsSectionHeader +A section header with optional icon and accent color. + +```swift +SettingsSectionHeader( + title: "Account", + systemImage: "person.circle", + accentColor: Color.Accent.primary +) +``` + +### SettingsToggle +A toggle row with title and subtitle. + +```swift +SettingsToggle( + title: "Sound Effects", + subtitle: "Play sounds for events", + isOn: $viewModel.soundEnabled, + accentColor: Color.Accent.primary +) +``` + +### SegmentedPicker +A horizontal capsule-style picker. + +```swift +SegmentedPicker( + title: "Quality", + options: [("Low", 0), ("Medium", 1), ("High", 2)], + selection: $viewModel.quality, + accentColor: Color.Accent.primary +) +``` + +### SelectableRow +A card-like row for option selection. + +```swift +SelectableRow( + title: "Premium", + subtitle: "Unlock all features", + isSelected: plan == .premium, + accentColor: Color.Accent.primary, + badge: { BadgePill(text: "$9.99") } +) { + plan = .premium +} +``` + +### VolumePicker +A slider with speaker icons for volume/percentage values. + +```swift +VolumePicker( + label: "Volume", + volume: $viewModel.volume, + accentColor: Color.Accent.primary +) +``` + +### SettingsRow +A navigation-style row with icon and chevron. + +```swift +SettingsRow( + systemImage: "star.fill", + title: "Rate App", + iconColor: Color.Status.warning +) { + openAppStore() +} +``` + +### BadgePill +A capsule badge for values or tags. + +```swift +BadgePill( + text: "$4.99", + isSelected: isCurrentPlan, + accentColor: Color.Accent.primary +) +``` + +--- + +## Color Relationship Guide + +| Surface Level | Use Case | Visual Depth | +|---------------|----------|--------------| +| `AppSurface.primary` | Main background | Darkest | +| `AppSurface.overlay` | Sheet/modal backgrounds | Slightly elevated | +| `AppSurface.card` | Settings cards | Distinct from background | +| `AppSurface.sectionFill` | Section containers | Most elevated | + +| Accent Purpose | Color | +|----------------|-------| +| Interactive elements | `AppAccent.primary` | +| Highlights | `AppAccent.light` | +| Pressed states | `AppAccent.dark` | +| Pro/Premium sections | `AppStatus.warning` | +| Debug/Error sections | `AppStatus.error` | + +--- + +## Tips + +1. **Derive surface colors from your brand**: Add a subtle RGB tint (e.g., if brand is pink, surfaces should have a rose undertone). + +2. **Use consistent accent colors**: Pass `accentColor: AppAccent.primary` to all Bedrock components. + +3. **Group related settings**: Wrap related toggles/pickers in a `SettingsCard` for visual hierarchy. + +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. + +6. **Avoid `Color.` typealiases**: Use `App`-prefixed typealiases to prevent conflicts with Bedrock's defaults. + +--- + +## Example Apps + +- **SelfieCam**: Rose/magenta theme with camera-focused settings +- **SelfieRingLight**: Similar structure with ring light controls +- **CameraTester**: Neutral theme for testing/debugging + +See each app's `[AppName]Theme.swift` for implementation examples. diff --git a/Sources/Bedrock/Views/Settings/SettingsComponents.swift b/Sources/Bedrock/Views/Settings/SettingsComponents.swift index 60192e1..0476d46 100644 --- a/Sources/Bedrock/Views/Settings/SettingsComponents.swift +++ b/Sources/Bedrock/Views/Settings/SettingsComponents.swift @@ -412,13 +412,18 @@ public struct SettingsSectionHeader: View { /// Optional system image name. public let systemImage: String? + /// The accent color for the header. + public let accentColor: Color + /// Creates a section header. /// - Parameters: /// - title: The section title. /// - systemImage: Optional SF Symbol name. - public init(title: String, systemImage: String? = nil) { + /// - accentColor: The accent color (default: primary accent). + public init(title: String, systemImage: String? = nil, accentColor: Color = .Accent.primary) { self.title = title self.systemImage = systemImage + self.accentColor = accentColor } public var body: some View { @@ -426,7 +431,7 @@ public struct SettingsSectionHeader: View { if let systemImage { Image(systemName: systemImage) .font(.system(size: Design.BaseFontSize.medium)) - .foregroundStyle(.white.opacity(Design.Opacity.accent)) + .foregroundStyle(accentColor.opacity(Design.Opacity.strong)) } Text(title)