From 44895b59cc9566b430b4b37a4bfefd9abee322f2 Mon Sep 17 00:00:00 2001 From: phranck Date: Tue, 3 Feb 2026 02:19:12 +0100 Subject: [PATCH 1/5] Feat: Redesign OverlaysPage with interactive demo menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the static single-alert demo with an interactive page featuring: - Menu with 8 overlay variants (5 Alert presets, 2 Dialog styles, 1 custom modal) - Description panel showing details and API usage for the selected variant - Enter key triggers the selected overlay with dimmed background - Dismiss button closes any overlay Variants demonstrated: 1. Alert (Standard) — default theme colors 2. Alert (Warning) — yellow preset 3. Alert (Error) — red preset 4. Alert (Info) — cyan preset 5. Alert (Success) — green preset 6. Dialog — content-only, no footer separator 7. Dialog with Footer — footer section with separator line 8. Modal (Custom) — arbitrary view content with .modal(isPresented:) --- .../TUIkitExample/Pages/OverlaysPage.swift | 282 +++++++++++++++--- 1 file changed, 234 insertions(+), 48 deletions(-) diff --git a/Sources/TUIkitExample/Pages/OverlaysPage.swift b/Sources/TUIkitExample/Pages/OverlaysPage.swift index 6dce563..1154556 100644 --- a/Sources/TUIkitExample/Pages/OverlaysPage.swift +++ b/Sources/TUIkitExample/Pages/OverlaysPage.swift @@ -2,78 +2,264 @@ // OverlaysPage.swift // TUIkitExample // -// Demonstrates overlay and modal capabilities. +// Demonstrates overlay and modal capabilities with an interactive menu. // import TUIkit -/// Overlays and modals demo page. +// MARK: - Overlay Demo Variants + +/// Available overlay demo variants. +private enum OverlayDemo: Int, CaseIterable { + case alertStandard + case alertWarning + case alertError + case alertInfo + case alertSuccess + case dialog + case dialogWithFooter + case modalCustom + + /// Display label for the menu. + var label: String { + switch self { + case .alertStandard: "Alert (Standard)" + case .alertWarning: "Alert (Warning)" + case .alertError: "Alert (Error)" + case .alertInfo: "Alert (Info)" + case .alertSuccess: "Alert (Success)" + case .dialog: "Dialog" + case .dialogWithFooter: "Dialog with Footer" + case .modalCustom: "Modal (Custom)" + } + } + + /// Description text for the detail panel. + var description: String { + switch self { + case .alertStandard: + "A standard alert with default theme colors. Uses .alert(isPresented:) modifier." + case .alertWarning: + "A warning-style alert with yellow border and title. Uses Alert.warning() preset." + case .alertError: + "An error-style alert with red border and title. Uses Alert.error() preset." + case .alertInfo: + "An info-style alert with cyan border and title. Uses Alert.info() preset." + case .alertSuccess: + "A success-style alert with green border and title. Uses Alert.success() preset." + case .dialog: + "A Dialog view with custom content. More flexible than Alert — accepts any views." + case .dialogWithFooter: + "A Dialog with a footer section for action buttons, separated by a divider line." + case .modalCustom: + "A custom modal overlay using .modal(isPresented:). Accepts any view as content." + } + } + + /// API usage example for the detail panel. + var apiUsage: String { + switch self { + case .alertStandard: + ".alert(\"Title\", isPresented: $show) { actions } message: { Text(\"...\") }" + case .alertWarning: + ".modal(isPresented: $show) { Alert.warning(message: \"...\") { actions } }" + case .alertError: + ".modal(isPresented: $show) { Alert.error(message: \"...\") { actions } }" + case .alertInfo: + ".modal(isPresented: $show) { Alert.info(message: \"...\") { actions } }" + case .alertSuccess: + ".modal(isPresented: $show) { Alert.success(message: \"...\") { actions } }" + case .dialog: + ".modal(isPresented: $show) { Dialog(title: \"...\") { content } }" + case .dialogWithFooter: + ".modal(isPresented: $show) { Dialog(title: \"...\") { content } footer: { buttons } }" + case .modalCustom: + ".modal(isPresented: $show) { VStack { ... } }" + } + } +} + +// MARK: - Overlays Page + +/// Interactive overlays and modals demo page. /// -/// Shows the overlay system including: -/// - `.alert(isPresented:)` modifier - SwiftUI-style alert presentation -/// - `.modal(isPresented:)` modifier - SwiftUI-style modal presentation -/// - `.dimmed()` modifier - visual de-emphasis -/// - Note: The status bar is NOT dimmed by modals! +/// Displays a menu of overlay variants on the left and a description +/// panel on the right. Pressing Enter shows the selected overlay +/// with dimmed background content. struct OverlaysPage: View { - @State var showModal: Bool = true + @State var menuSelection: Int = 0 + @State var showOverlay: Bool = false + + /// The currently selected demo variant. + private var selectedDemo: OverlayDemo { + OverlayDemo.allCases[menuSelection] + } var body: some View { backgroundContent - .alert( - "Alert Demo", - isPresented: $showModal, - actions: { - Button("Dismiss", style: .primary) { - showModal = false - } - }, - message: { - Text("This alert uses the new .alert(isPresented:) API!") - }, - borderColor: .palette.border, - titleColor: .palette.accent - ) + .modal(isPresented: $showOverlay) { + overlayContent(for: selectedDemo) + } } - var backgroundContent: some View { + // MARK: - Background Content + + /// The main background content with menu and description. + private var backgroundContent: some View { VStack(spacing: 1) { HeaderView(title: "Overlays & Modals Demo") - DemoSection("Presentation APIs (SwiftUI-style)") { - Text(" .alert(isPresented:) - declarative alert presentation") - Text(" .modal(isPresented:) - declarative modal presentation") - Text(" .overlay() - layer content on top") - Text(" .dimmed() - reduce visual emphasis") - } + HStack(spacing: 3) { + // Left: Demo menu + Menu( + title: "Select a Demo", + items: OverlayDemo.allCases.map { demo in + MenuItem(label: demo.label, shortcut: nil) + }, + selection: $menuSelection, + onSelect: { _ in + showOverlay = true + }, + selectedColor: .palette.accent, + borderColor: .palette.border + ) - DemoSection("Modal Toggle (@State)") { - HStack(spacing: 2) { - if showModal { - Text("Modal is visible") - .foregroundColor(.palette.accent) - } else { - Text("Modal dismissed") - .foregroundColor(.palette.foregroundSecondary) - Button("Show Again", style: .primary) { - showModal = true - } - } - } + // Right: Description of selected demo + descriptionPanel } DemoSection("How It Works") { - Text("Uses .alert(isPresented: $showModal) { ... }") + Text("All overlays use the SwiftUI-style presentation API:") .foregroundColor(.palette.foregroundSecondary) - Text("No manual if/else needed - SwiftUI-style API!") + Text(" .alert(isPresented:) — for Alert views") + .foregroundColor(.palette.foregroundSecondary) + Text(" .modal(isPresented:) — for Dialog, custom content") + .foregroundColor(.palette.foregroundSecondary) + Text("The background is automatically dimmed. Status bar stays visible.") .bold() .foregroundColor(.palette.accent) - Text("Pressing 'Dismiss' sets showModal = false") - .foregroundColor(.palette.foregroundSecondary) - Text("Status bar is NOT dimmed (separate render layer)") - .foregroundColor(.palette.foregroundSecondary) } Spacer() } } + + // MARK: - Description Panel + + /// Detail panel showing the selected demo's description and API usage. + private var descriptionPanel: some View { + Panel(selectedDemo.label, titleColor: .palette.accent) { + VStack(alignment: .leading, spacing: 1) { + Text(selectedDemo.description) + .foregroundColor(.palette.foreground) + + Text("") + + Text("API:") + .bold() + .foregroundColor(.palette.accent) + Text(" \(selectedDemo.apiUsage)") + .foregroundColor(.palette.foregroundSecondary) + } + } + .frame(width: 55) + } + + // MARK: - Overlay Content + + /// Builds the overlay content for the selected demo variant. + @ViewBuilder + private func overlayContent(for demo: OverlayDemo) -> some View { + switch demo { + case .alertStandard: + Alert( + title: "Standard Alert", + message: "This is a standard alert with default theme colors.", + borderColor: .palette.border, + titleColor: .palette.accent + ) { + dismissButton + } + .frame(width: 50) + + case .alertWarning: + Alert