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.
This commit is contained in:
phranck
2026-01-29 02:42:29 +01:00
parent 2232fefc44
commit 2cf72c2b19
15 changed files with 3362 additions and 8 deletions
@@ -0,0 +1,375 @@
# Building an Interactive Menu
Create a navigation menu with keyboard shortcuts and status bar hints.
@Intro(title: "Build an Interactive Menu App") {
Learn how to create a functional menu system with keyboard navigation,
selection handling, and status bar integration.
You'll build a simple settings menu that demonstrates focus management,
keyboard events, and status bar items.
}
## Overview
In this tutorial, you'll create a menu-driven application featuring:
- `Menu` component for selection
- Keyboard shortcut handling
- Status bar with hints
- Page navigation patterns
@Section(title: "Create the Menu Component") {
@ContentAndMedia {
Start with a basic menu that displays options and tracks selection.
}
@Steps {
@Step {
Create your app with a `Menu`:
```swift
import TUIKit
@main
struct MenuApp: App {
@State private var selectedOption: String = "home"
var body: some Scene {
WindowGroup {
VStack(spacing: 2) {
Text("Main Menu")
.bold()
.foregroundColor(.theme.accent)
Spacer()
Menu(
items: [
MenuItem("📄 View Profile", id: "profile"),
MenuItem("⚙️ Settings", id: "settings"),
MenuItem("💾 Save Data", id: "save"),
MenuItem("❌ Exit", id: "exit")
],
selection: $selectedOption
)
Spacer()
Text("Selected: \(selectedOption)")
.foregroundColor(.theme.foregroundSecondary)
}
.padding()
}
}
}
```
- `Menu` creates an interactive selectable list
- `$selectedOption` creates a two-way binding
- Use Tab/Arrow keys to navigate
- Press Enter to confirm selection
}
@Step {
Run the app:
```bash
swift run
```
Test navigating with Tab, arrow keys, and Enter.
The "Selected" text updates as you navigate.
}
}
}
@Section(title: "Add Page Navigation") {
@ContentAndMedia {
Create different pages that show based on menu selection.
}
@Steps {
@Step {
Add conditional pages:
```swift
@main
struct MenuApp: App {
@State private var selectedOption: String = "home"
var body: some Scene {
WindowGroup {
if selectedOption == "exit" {
VStack {
Text("Exiting...")
}
} else {
VStack(spacing: 2) {
Text("Main Menu")
.bold()
.foregroundColor(.theme.accent)
Spacer()
Menu(
items: [
MenuItem("📄 View Profile", id: "profile"),
MenuItem("⚙️ Settings", id: "settings"),
MenuItem("💾 Save Data", id: "save"),
MenuItem("❌ Exit", id: "exit")
],
selection: $selectedOption
)
Spacer()
currentPageContent
}
.padding()
}
}
}
@ViewBuilder
var currentPageContent: some View {
switch selectedOption {
case "profile":
VStack(spacing: 1) {
Text("📄 Profile Information").bold()
Text("Name: John Doe")
Text("Role: Developer")
}
case "settings":
VStack(spacing: 1) {
Text("⚙️ Application Settings").bold()
Text("Theme: Green")
Text("Language: English")
}
case "save":
Text("✓ Data saved successfully!")
.foregroundColor(.theme.success)
default:
Text("Select an option above")
}
}
}
```
The `currentPageContent` computed property shows different content
based on the selected menu item.
}
@Step {
Run and test navigation:
```bash
swift run
```
Navigate to each menu item and see the page content change.
}
}
}
@Section(title: "Add Status Bar Hints") {
@ContentAndMedia {
Add a status bar at the bottom with helpful keyboard hints.
}
@Steps {
@Step {
Add status bar items:
```swift
@main
struct MenuApp: App {
@State private var selectedOption: String = "home"
var body: some Scene {
WindowGroup {
if selectedOption == "exit" {
VStack {
Text("Exiting...")
}
} else {
VStack(spacing: 2) {
Text("Main Menu")
.bold()
.foregroundColor(.theme.accent)
Spacer()
Menu(
items: [
MenuItem("📄 View Profile", id: "profile"),
MenuItem("⚙️ Settings", id: "settings"),
MenuItem("💾 Save Data", id: "save"),
MenuItem("❌ Exit", id: "exit")
],
selection: $selectedOption
)
Spacer()
currentPageContent
}
.padding()
}
.statusBarItems {
StatusBarItem(
label: "Select",
shortcut: Shortcut.enter,
action: { }
)
StatusBarItem(
label: "Theme",
shortcut: Shortcut.letter("t"),
action: { }
)
StatusBarItem(
label: "Help",
shortcut: Shortcut.letter("?"),
action: { }
)
StatusBarItem(
label: "Quit",
shortcut: Shortcut.letter("q"),
action: { }
)
}
}
}
@ViewBuilder
var currentPageContent: some View {
switch selectedOption {
case "profile":
VStack(spacing: 1) {
Text("📄 Profile Information").bold()
Text("Name: John Doe")
Text("Role: Developer")
}
case "settings":
VStack(spacing: 1) {
Text("⚙️ Application Settings").bold()
Text("Theme: Green")
Text("Language: English")
}
case "save":
Text("✓ Data saved successfully!")
.foregroundColor(.theme.success)
default:
Text("Select an option above")
}
}
}
```
`statusBarItems` adds a bottom status bar with keyboard shortcuts.
}
@Step {
Run the app:
```bash
swift run
```
The status bar appears at the bottom showing available shortcuts.
}
}
}
@Section(title: "Add Keyboard Shortcuts") {
@ContentAndMedia {
Handle custom keyboard shortcuts to navigate directly to menu items.
}
@Steps {
@Step {
Add keyboard event handling:
```swift
.onKeyPress { event in
switch event.key {
case .character("1"):
selectedOption = "profile"
return true
case .character("2"):
selectedOption = "settings"
return true
case .character("3"):
selectedOption = "save"
return true
case .character("e"):
selectedOption = "exit"
return true
default:
return false
}
}
```
Add this modifier to your main `VStack`. Now users can:
- Press `1` to go to Profile
- Press `2` to go to Settings
- Press `3` to Save
- Press `e` to Exit
}
@Step {
Update the status bar to show these shortcuts:
```swift
.statusBarItems {
StatusBarItem(
label: "Profile",
shortcut: Shortcut.digit("1"),
action: { }
)
StatusBarItem(
label: "Settings",
shortcut: Shortcut.digit("2"),
action: { }
)
StatusBarItem(
label: "Save",
shortcut: Shortcut.digit("3"),
action: { }
)
StatusBarItem(
label: "Exit",
shortcut: Shortcut.letter("e"),
action: { }
)
}
```
}
@Step {
Test your shortcuts:
```bash
swift run
```
Press number keys to jump directly to menu items.
}
}
}
## Next Steps
You've created an interactive menu-driven application!
- Learn about <doc:Focus> for advanced focus management
- Explore <doc:StateManagement> for more complex state patterns
- Try building a <doc:BuildThemableUI> with theme switching
- Check out <doc:Appearance> for custom styling
+421
View File
@@ -0,0 +1,421 @@
# 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.
- Learn more about <doc:Theming> for creating custom themes
- Explore <doc:Appearance> for structural styles
- See <doc:StateManagement> for advanced state patterns
- Check the <doc:Architecture> overview for deeper understanding
+270
View File
@@ -0,0 +1,270 @@
# Building Your First App
Create a simple counter application to learn TUIKit basics.
@Intro(title: "Create Your First TUIKit App") {
Learn the fundamentals of building a terminal user interface with TUIKit
by creating a simple counter application.
You'll learn how to set up an `@main` app, use `@State` for local state,
add interactive buttons, and use basic layout with `VStack` and `HStack`.
}
## Overview
In this tutorial, you'll build a working counter app with increment/decrement buttons.
This will teach you:
- Creating an app with the `@main` attribute
- Using `VStack` and `HStack` for layout
- Handling button taps with `@State`
- Running and testing your app
@Section(title: "Create the App Entry Point") {
@ContentAndMedia {
Every TUIKit app needs an entry point decorated with `@main`.
This tells Swift to use your app as the entry point for the program.
}
@Steps {
@Step {
Create a new file called `main.swift` in your project:
```swift
import TUIKit
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
Text("Hello, TUIKit!")
}
}
}
```
The `@main` attribute marks this as the app entry point.
`WindowGroup` represents the main window of your terminal app.
}
@Step {
Run your app with:
```bash
swift run
```
You should see "Hello, TUIKit!" displayed in the terminal.
}
}
}
@Section(title: "Add Layout and Text") {
@ContentAndMedia {
Now let's organize content with `VStack` (vertical layout).
We'll add a title and multiple text lines.
}
@Steps {
@Step {
Modify the `body` to use `VStack`:
```swift
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
VStack(spacing: 1) {
Text("Counter App")
.bold()
Text("Simple counter to learn TUIKit")
.foregroundColor(.theme.foregroundSecondary)
Spacer()
Text("Current count: 0")
Spacer()
}
.padding()
}
}
}
```
- `VStack(spacing: 1)` arranges content vertically with 1-unit spacing
- `.bold()` makes the title bold
- `.foregroundColor()` colors text
- `Spacer()` creates flexible vertical space
- `.padding()` adds space around all edges
}
@Step {
Run the app again:
```bash
swift run
```
Now you should see a nicely formatted display with title,
description, and spacing.
}
}
}
@Section(title: "Add Interactive State") {
@ContentAndMedia {
Use `@State` to track the counter value and re-render when it changes.
This makes your app interactive!
}
@Steps {
@Step {
Add a `@State` property for the counter:
```swift
@main
struct CounterApp: App {
@State private var count: Int = 0
var body: some Scene {
WindowGroup {
VStack(spacing: 1) {
Text("Counter App")
.bold()
Text("Simple counter to learn TUIKit")
.foregroundColor(.theme.foregroundSecondary)
Spacer()
Text("Current count: \(count)")
Spacer()
}
.padding()
}
}
}
```
The `@State` property wrapper stores local state. When `count` changes,
the view automatically re-renders.
}
}
}
@Section(title: "Add Buttons") {
@ContentAndMedia {
Now add buttons to increment and decrement the counter.
Use `HStack` to arrange them horizontally.
}
@Steps {
@Step {
Add buttons using `HStack`:
```swift
@main
struct CounterApp: App {
@State private var count: Int = 0
var body: some Scene {
WindowGroup {
VStack(spacing: 1) {
Text("Counter App")
.bold()
Text("Simple counter to learn TUIKit")
.foregroundColor(.theme.foregroundSecondary)
Spacer()
Text("Current count: \(count)")
HStack(spacing: 2) {
Button("Decrement") { count -= 1 }
Button("Increment") { count += 1 }
}
Spacer()
Text("Press 'q' to quit")
.foregroundColor(.theme.foregroundTertiary)
}
.padding()
}
}
}
```
- `HStack(spacing: 2)` arranges buttons horizontally
- `Button(label) { action }` creates a clickable button
- The closure after the label runs when the button is pressed
}
@Step {
Run your app:
```bash
swift run
```
Now use Tab to navigate to the buttons and press Enter to increment/decrement.
The counter value updates in real-time!
}
}
}
@Section(title: "Test and Iterate") {
@ContentAndMedia {
Test your counter app and try making improvements.
}
@Steps {
@Step {
Test the following interactions:
- Press `Tab` to move focus to the next button
- Press `Shift+Tab` to move focus backward
- Press `Enter` when a button is focused to activate it
- Watch the counter update
- Press `q` to quit the app
- Press `t` to cycle through themes
- Press `a` to cycle through appearance styles
}
@Step {
Try these enhancements:
Add a reset button:
```swift
HStack(spacing: 2) {
Button("Decrement") { count = max(0, count - 1) }
Button("Reset") { count = 0 }
Button("Increment") { count += 1 }
}
```
Or add a border around the counter:
```swift
Text("Current count: \(count)")
.bold()
.padding(1)
.border(.rounded)
```
}
}
}
## Next Steps
Congratulations! You've built your first TUIKit app.
- Learn more about <doc:StateManagement> for complex state scenarios
- Explore <doc:Theming> to customize colors
- Try building an interactive <doc:BuildInteractiveMenu>
- Check out <doc:Modifiers> for more styling options