Files
TUIkit/TUIKit.docc/Tutorials/BuildThemableUI.md
T
phranck 2cf72c2b19 feat: Add comprehensive DocC documentation with tutorials
Add complete Apple-style DocC documentation for TUIKit framework:

## Documentation Structure
- Main landing page (TUIKit.md) with quick-start and topic navigation
- 8 guide articles: GettingStarted, ViewHierarchy, StateManagement, Theming, Appearance, Focus, Modifiers, Architecture
- 3 step-by-step tutorials: BuildYourFirstApp, BuildInteractiveMenu, BuildThemableUI
- Documentation catalog with proper Info.plist configuration

## Code Improvements
- Enhanced Box.swift documentation with clear container comparison
- Added comprehensive examples and usage patterns
- Explained appearance integration and sizing behavior

## Infrastructure
- GitHub Actions workflow (.github/workflows/docc.yml) for automatic DocC building
- Configured for deployment to GitHub Pages on push to main
- Uses xcrun docc build with proper artifact handling

## Documentation Includes
- API reference stubs for all major components
- Comprehensive guide articles with code examples
- Interactive tutorials following Apple's documentation style
- Cross-references using DocC syntax (``Type``)
- Best practices and design patterns explained

The documentation is ready for automated building and deployment to GitHub Pages.
2026-01-29 02:42:29 +01:00

13 KiB

Building a Themable UI

Create an application with dynamic theme switching and persistence.

@Intro(title: "Build a Themable App") { Learn how to integrate TUIKit's theming system into your application, allowing users to switch between themes and persist their preference.

You'll work with the theme environment, theme manager, and storage to
create a professional themable application.

}

Overview

In this tutorial, you'll create an app that features:

  • Theme environment integration
  • Dynamic theme switching
  • Persisted theme preference
  • Visual theme selection UI

@Section(title: "Set Up Theme Storage") { @ContentAndMedia { Use @AppStorage to remember the user's theme choice across sessions. }

@Steps {
    @Step {
        Create an app that stores the selected theme:

        ```swift
        import TUIKit

        @main
        struct ThemableApp: App {
            @AppStorage("selectedTheme") var selectedThemeName: String = "green"

            var body: some Scene {
                WindowGroup {
                    ContentView()
                        .environment(\.theme, themeForName(selectedThemeName))
                }
            }

            func themeForName(_ name: String) -> Theme {
                switch name {
                case "amber":
                    return AmberPhosphorTheme()
                case "white":
                    return WhitePhosphorTheme()
                case "red":
                    return RedPhosphorTheme()
                default:
                    return GreenPhosphorTheme()
                }
            }
        }

        struct ContentView: View {
            var body: some View {
                VStack(spacing: 2) {
                    Text("Themable Application")
                        .bold()
                        .foregroundColor(.theme.accent)

                    Spacer()

                    Text("Select a theme to customize the appearance")
                        .foregroundColor(.theme.foregroundSecondary)

                    Spacer()
                }
                .padding()
            }
        }
        ```

        `@AppStorage("selectedTheme")` automatically persists the theme choice.
        When the app restarts, it loads the saved theme.
    }

    @Step {
        Run your app:

        ```bash
        swift run
        ```

        The app launches with the default Green theme.
    }
}

}

@Section(title: "Create Theme Selection UI") { @ContentAndMedia { Display the 4 available themes and let users select one. }

@Steps {
    @Step {
        Add a theme menu:

        ```swift
        @main
        struct ThemableApp: App {
            @AppStorage("selectedTheme") var selectedThemeName: String = "green"

            var body: some Scene {
                WindowGroup {
                    ContentView(selectedThemeName: $selectedThemeName)
                        .environment(\.theme, themeForName(selectedThemeName))
                }
            }

            func themeForName(_ name: String) -> Theme {
                // ... same as before
            }
        }

        struct ContentView: View {
            @Binding var selectedThemeName: String

            let themes = [
                ("green", "🟢 Green (Default)"),
                ("amber", "🟡 Amber"),
                ("white", "⚪ White"),
                ("red", "🔴 Red")
            ]

            var body: some View {
                VStack(spacing: 2) {
                    Text("Themable Application")
                        .bold()
                        .foregroundColor(.theme.accent)

                    Spacer()

                    Text("Select a theme:")
                        .bold()

                    Menu(
                        items: themes.map { id, label in
                            MenuItem(label, id: id)
                        },
                        selection: $selectedThemeName
                    )

                    Spacer()

                    Text("Current: \(themeName(selectedThemeName))")
                        .foregroundColor(.theme.foregroundSecondary)

                    Spacer()
                }
                .padding()
            }

            func themeName(_ id: String) -> String {
                themes.first(where: { $0.0 == id })?.1 ?? id
            }
        }
        ```

        Pass `$selectedThemeName` as a binding to allow the menu to change it.
        The environment automatically updates because it depends on this value.
    }

    @Step {
        Test theme switching:

        ```bash
        swift run
        ```

        Use Tab/Arrow keys to select different themes.
        Notice the colors change immediately!
    }
}

}

@Section(title: "Display Theme Colors") { @ContentAndMedia { Show a visual preview of the current theme's colors. }

@Steps {
    @Step {
        Add a color preview:

        ```swift
        struct ContentView: View {
            @Binding var selectedThemeName: String
            @Environment(\.theme) var theme

            let themes = [
                ("green", "🟢 Green (Default)"),
                ("amber", "🟡 Amber"),
                ("white", "⚪ White"),
                ("red", "🔴 Red")
            ]

            var body: some View {
                VStack(spacing: 2) {
                    Text("Themable Application")
                        .bold()
                        .foregroundColor(.theme.accent)

                    Spacer()

                    Text("Select a theme:").bold()

                    Menu(
                        items: themes.map { id, label in
                            MenuItem(label, id: id)
                        },
                        selection: $selectedThemeName
                    )

                    Spacer()

                    // Color preview box
                    VStack(spacing: 1) {
                        Text("Theme Colors")
                            .bold()

                        HStack(spacing: 1) {
                            Text(" ").background(theme.foreground)
                            Text(" ").background(theme.accent)
                            Text(" ").background(theme.border)
                            Text(" ").background(theme.warning)
                        }

                        Text("Foreground | Accent | Border | Warning")
                            .foregroundColor(.theme.foregroundSecondary)
                    }
                    .padding(1)
                    .border(.rounded)

                    Spacer()
                }
                .padding()
            }

            func themeName(_ id: String) -> String {
                themes.first(where: { $0.0 == id })?.1 ?? id
            }
        }
        ```

        `@Environment(\.theme)` gives access to the current theme.
        Use `theme.foreground`, `theme.accent`, etc. to access colors.
    }

    @Step {
        Run the app:

        ```bash
        swift run
        ```

        Switch themes and see the color preview update.
    }
}

}

@Section(title: "Add Theme Shortcuts and Status Bar") { @ContentAndMedia { Let users cycle themes quickly with keyboard shortcuts. }

@Steps {
    @Step {
        Add quick theme cycling:

        ```swift
        struct ContentView: View {
            @Binding var selectedThemeName: String
            @Environment(\.theme) var theme

            let themes = [
                ("green", "🟢 Green (Default)"),
                ("amber", "🟡 Amber"),
                ("white", "⚪ White"),
                ("red", "🔴 Red")
            ]

            let themeIds = ["green", "amber", "white", "red"]

            var body: some View {
                VStack(spacing: 2) {
                    // ... menu UI as before ...

                    Spacer()
                }
                .padding()
                .onKeyPress { event in
                    if event.key == .character("t") {
                        // Cycle to next theme
                        if let currentIndex = themeIds.firstIndex(of: selectedThemeName) {
                            let nextIndex = (currentIndex + 1) % themeIds.count
                            selectedThemeName = themeIds[nextIndex]
                        }
                        return true
                    }
                    return false
                }
                .statusBarItems {
                    StatusBarItem(
                        label: "Cycle Theme",
                        shortcut: Shortcut.letter("t"),
                        action: { }
                    )

                    StatusBarItem(
                        label: "Help",
                        shortcut: Shortcut.letter("?"),
                        action: { }
                    )

                    StatusBarItem(
                        label: "Quit",
                        shortcut: Shortcut.letter("q"),
                        action: { }
                    )
                }
            }

            func themeName(_ id: String) -> String {
                themes.first(where: { $0.0 == id })?.1 ?? id
            }
        }
        ```

        Now users can press `t` to quickly cycle through themes.
    }

    @Step {
        Test theme cycling:

        ```bash
        swift run
        ```

        Press `t` repeatedly to cycle through all 4 themes.
        The selection updates and the preview colors change.
    }
}

}

@Section(title: "Enhance with Appearance Switching") { @ContentAndMedia { Add appearance style switching alongside theme switching. }

@Steps {
    @Step {
        Add appearance cycling:

        ```swift
        @main
        struct ThemableApp: App {
            @AppStorage("selectedTheme") var selectedThemeName: String = "green"
            @AppStorage("selectedAppearance") var selectedAppearance: String = "rounded"

            var body: some Scene {
                WindowGroup {
                    ContentView(
                        selectedThemeName: $selectedThemeName,
                        selectedAppearance: $selectedAppearance
                    )
                    .environment(\.theme, themeForName(selectedThemeName))
                    .environment(\.appearance, appearanceForName(selectedAppearance))
                }
            }

            func themeForName(_ name: String) -> Theme {
                // ... existing code ...
            }

            func appearanceForName(_ name: String) -> Appearance {
                switch name {
                case "line":
                    return .line
                case "doubled":
                    return .doubleLine
                case "heavy":
                    return .heavy
                case "block":
                    return .block
                default:
                    return .rounded
                }
            }
        }
        ```

        Now users can customize both theme AND appearance style!
    }

    @Step {
        The changes are automatically persisted with `@AppStorage`.
        Run the app:

        ```bash
        swift run
        ```

        Select a theme and appearance, exit with `q`, and run again.
        Your selection is remembered!
    }
}

}

Next Steps

Congratulations! You've built a fully themed, customizable application.