refactor: Rename package from SwiftTUI to TUIKit

BREAKING CHANGE: Package name changed due to name collision with
existing rensbreur/SwiftTUI package.

Changes:
- Rename package from SwiftTUI to TUIKit in Package.swift
- Rename Sources/SwiftTUI to Sources/TUIKit
- Rename Sources/SwiftTUIExample to Sources/TUIKitExample
- Rename Tests/SwiftTUITests to Tests/TUIKitTests
- Rename SwiftTUI.swift to TUIKit.swift
- Update all imports: import SwiftTUI -> import TUIKit
- Update all code references: SwiftTUI.renderToBuffer -> TUIKit.renderToBuffer
- Update documentation comments
- Rename swiftTUIVersion to tuiKitVersion

All 181 tests passing.
This commit is contained in:
phranck
2026-01-28 19:32:09 +01:00
parent 44d9d4490e
commit 45e73faafb
65 changed files with 140 additions and 140 deletions
+56
View File
@@ -0,0 +1,56 @@
//
// AppState.swift
// TUIKitExample
//
// Global state management for the example app.
//
import TUIKit
// MARK: - Demo Page Enum
/// The available demo pages in the example app.
enum DemoPage: Int, CaseIterable {
case menu = 0
case textStyles = 1
case colors = 2
case containers = 3
case overlays = 4
case layout = 5
case buttons = 6
}
// MARK: - App State
/// Global state for the example app.
///
/// This class manages the current page and menu selection.
/// Changes trigger automatic re-renders via `AppState`.
///
/// Status bar items are now managed declaratively via the
/// `.statusBarItems()` modifier in `ContentView`.
final class ExampleAppState: @unchecked Sendable {
static let shared = ExampleAppState()
/// The current page being displayed.
var currentPage: DemoPage = .menu {
didSet {
AppState.shared.setNeedsRender()
}
}
/// The selected menu index.
var menuSelection: Int = 0 {
didSet { AppState.shared.setNeedsRender() }
}
/// Binding for menu selection.
var menuSelectionBinding: Binding<Int> {
Binding(
get: { self.menuSelection },
set: { self.menuSelection = $0 }
)
}
private init() {}
}
@@ -0,0 +1,40 @@
//
// DemoSection.swift
// TUIKitExample
//
// A reusable section component for organizing demo content.
//
import TUIKit
/// A section with a styled title and content.
///
/// Used to group related demo content with a yellow underlined title.
///
/// # Example
///
/// ```swift
/// DemoSection("Basic Features") {
/// Text("Feature 1")
/// Text("Feature 2")
/// }
/// ```
struct DemoSection<Content: View>: View {
let title: String
let content: Content
init(_ title: String, @ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
VStack(alignment: .leading) {
Text(title)
.bold()
.underline()
.foregroundColor(.yellow)
content
}
}
}
@@ -0,0 +1,50 @@
//
// HeaderView.swift
// TUIKitExample
//
// A reusable header component for demo pages.
//
import TUIKit
/// A styled header with title on the left and version on the right.
///
/// Used at the top of each demo page to provide consistent branding
/// and optional subtitle.
///
/// # Example
///
/// ```swift
/// HeaderView(
/// title: "My Demo",
/// subtitle: "An optional description"
/// )
/// ```
struct HeaderView: View {
let title: String
let subtitle: String?
init(title: String, subtitle: String? = nil) {
self.title = title
self.subtitle = subtitle
}
var body: some View {
VStack {
HStack {
Text(title)
.bold()
.foregroundColor(.cyan)
Spacer()
Text("TUIKit v\(tuiKitVersion)")
.dim()
}
if let sub = subtitle {
Text(sub)
.dim()
.italic()
}
Divider(character: "")
}
}
}
+81
View File
@@ -0,0 +1,81 @@
//
// ContentView.swift
// TUIKitExample
//
// The main content view that routes between demo pages.
//
import TUIKit
// MARK: - Content View (Page Router)
/// The main content view that switches between pages.
///
/// This view acts as a router, displaying the appropriate demo page
/// based on the current state. It uses the `.statusBarItems()` modifier
/// to declaratively set context-sensitive status bar items.
struct ContentView: View {
var body: some View {
let state = ExampleAppState.shared
// Show current page based on state
pageContent(for: state.currentPage)
.onKeyPress { event in
switch event.key {
case .escape:
// ESC goes back to menu (or exits if already on menu)
if state.currentPage != .menu {
state.currentPage = .menu
return true // Consumed
}
return false // Let default handler exit the app
default:
return false // Let other handlers process
}
}
}
@ViewBuilder
private func pageContent(for page: DemoPage) -> some View {
switch page {
case .menu:
MainMenuPage()
.statusBarItems {
StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "nav")
StatusBarItem(shortcut: Shortcut.enter, label: "select", key: .enter)
StatusBarItem(shortcut: Shortcut.range("1", "6"), label: "jump")
StatusBarItem(shortcut: Shortcut.quit, label: "quit")
}
case .textStyles:
TextStylesPage()
.statusBarItems(subPageItems)
case .colors:
ColorsPage()
.statusBarItems(subPageItems)
case .containers:
ContainersPage()
.statusBarItems(subPageItems)
case .overlays:
OverlaysPage()
.statusBarItems(subPageItems)
case .layout:
LayoutPage()
.statusBarItems(subPageItems)
case .buttons:
ButtonsPage()
.statusBarItems(subPageItems)
}
}
/// Common status bar items for sub-pages.
private var subPageItems: [any StatusBarItemProtocol] {
[
StatusBarItem(shortcut: Shortcut.escape, label: "back") {
ExampleAppState.shared.currentPage = .menu
},
StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"),
StatusBarItem(shortcut: Shortcut.quit, label: "quit")
]
}
}
@@ -0,0 +1,73 @@
//
// ButtonsPage.swift
// TUIKitExample
//
// Demonstrates button and focus system capabilities.
//
import TUIKit
/// Buttons and focus demo page.
///
/// Shows interactive button features including:
/// - Different button styles (default, primary, success, destructive)
/// - Disabled buttons
/// - Plain style (no border)
/// - ButtonRow for horizontal groups
/// - Focus navigation with Tab
struct ButtonsPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Buttons & Focus Demo")
DemoSection("Button Styles") {
HStack(spacing: 2) {
Button("Default") {
// Default style button action
}
Button("Primary", style: .primary) {
// Primary button action
}
Button("Success", style: .success) {
// Success button action
}
Button("Destructive", style: .destructive) {
// Destructive button action
}
}
}
DemoSection("Disabled Button") {
HStack(spacing: 2) {
Button("Enabled") { }
Button("Disabled") { }.disabled()
}
}
DemoSection("Plain Style (No Border)") {
HStack(spacing: 2) {
Button("Link 1", style: .plain) { }
Button("Link 2", style: .plain) { }
}
}
DemoSection("ButtonRow (Horizontal Group)") {
ButtonRow(spacing: 3) {
Button("Cancel") { }
Button("Save", style: .primary) { }
}
}
DemoSection("Focus Navigation") {
VStack {
Text("Use [Tab] to move focus between buttons")
.dim()
Text("Use [Enter] or [Space] to press the focused button")
.dim()
}
}
Spacer()
}
}
}
@@ -0,0 +1,67 @@
//
// ColorsPage.swift
// TUIKitExample
//
// Demonstrates color capabilities.
//
import TUIKit
/// Colors demo page.
///
/// Shows various color options including:
/// - Standard ANSI colors (8 colors)
/// - Bright colors (8 colors)
/// - RGB colors (24-bit true color)
/// - Semantic colors (primary, success, warning, error)
struct ColorsPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Colors Demo")
DemoSection("Standard ANSI Colors") {
HStack(spacing: 2) {
Text("Black").foregroundColor(.black).background(.white)
Text("Red").foregroundColor(.red)
Text("Green").foregroundColor(.green)
Text("Yellow").foregroundColor(.yellow)
}
HStack(spacing: 2) {
Text("Blue").foregroundColor(.blue)
Text("Magenta").foregroundColor(.magenta)
Text("Cyan").foregroundColor(.cyan)
Text("White").foregroundColor(.white)
}
}
DemoSection("Bright Colors") {
HStack(spacing: 2) {
Text("Bright Red").foregroundColor(.brightRed)
Text("Bright Green").foregroundColor(.brightGreen)
Text("Bright Yellow").foregroundColor(.brightYellow)
Text("Bright Blue").foregroundColor(.brightBlue)
}
}
DemoSection("RGB Colors (24-bit)") {
HStack(spacing: 2) {
Text("Orange").foregroundColor(.rgb(255, 128, 0))
Text("Pink").foregroundColor(.rgb(255, 105, 180))
Text("Teal").foregroundColor(.rgb(0, 128, 128))
Text("Purple").foregroundColor(.rgb(128, 0, 128))
}
}
DemoSection("Semantic Colors") {
HStack(spacing: 2) {
Text("Primary").foregroundColor(.primary)
Text("Success").foregroundColor(.success)
Text("Warning").foregroundColor(.warning)
Text("Error").foregroundColor(.error)
}
}
Spacer()
}
}
}
@@ -0,0 +1,62 @@
//
// ContainersPage.swift
// TUIKitExample
//
// Demonstrates container view capabilities.
//
import TUIKit
/// Container views demo page.
///
/// Shows various container views including:
/// - Card (bordered container with padding)
/// - Box (simple bordered container)
/// - Panel (container with title in border)
/// - All available border styles
struct ContainersPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Container Views Demo")
HStack(spacing: 2) {
// Card example
VStack(alignment: .leading) {
Text("Card").bold().foregroundColor(.yellow)
Card(borderStyle: .rounded, borderColor: .cyan) {
Text("A Card view")
Text("with padding").dim()
}
}
// Box example
VStack(alignment: .leading) {
Text("Box").bold().foregroundColor(.yellow)
Box(.doubleLine, color: .green) {
Text("Simple Box")
}
}
// Panel example
VStack(alignment: .leading) {
Text("Panel").bold().foregroundColor(.yellow)
Panel("Info", borderStyle: .line, titleColor: .magenta) {
Text("Title in border")
}
}
}
DemoSection("Border Styles") {
HStack(spacing: 1) {
Box(.line) { Text("line") }
Box(.rounded) { Text("rounded") }
Box(.doubleLine) { Text("double") }
Box(.heavy) { Text("heavy") }
Box(.block) { Text("block") }
}
}
Spacer()
}
}
}
@@ -0,0 +1,72 @@
//
// LayoutPage.swift
// TUIKitExample
//
// Demonstrates layout system capabilities.
//
import TUIKit
/// Layout system demo page.
///
/// Shows various layout options including:
/// - VStack (vertical stacking)
/// - HStack (horizontal stacking)
/// - Spacer (flexible space)
/// - Padding and frame modifiers
struct LayoutPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Layout System Demo")
DemoSection("VStack (Vertical)") {
Box(.rounded, color: .brightBlack) {
VStack(spacing: 0) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}
}
DemoSection("HStack (Horizontal)") {
Box(.rounded, color: .brightBlack) {
HStack(spacing: 2) {
Text("Left")
Text("Center")
Text("Right")
}
}
}
DemoSection("Spacer") {
Box(.rounded, color: .brightBlack) {
HStack {
Text("Start")
Spacer()
Text("End")
}
}
}
DemoSection("Padding & Frame") {
HStack(spacing: 2) {
VStack {
Text(".padding()").dim()
Text("Padded")
.padding(EdgeInsets(all: 1))
.border(.line)
}
VStack {
Text(".frame()").dim()
Text("Framed")
.frame(width: 15, alignment: .center)
.border(.line)
}
}
}
Spacer()
}
}
}
@@ -0,0 +1,81 @@
//
// MainMenuPage.swift
// TUIKitExample
//
// The main menu page with navigation to all demos.
//
import TUIKit
/// The main menu page.
///
/// Displays a centered menu with all available demos and
/// feature highlight boxes at the bottom.
struct MainMenuPage: View {
var body: some View {
let state = ExampleAppState.shared
VStack(spacing: 1) {
HeaderView(
title: "TUIKit Example App",
subtitle: "A SwiftUI-like framework for Terminal User Interfaces"
)
Spacer(minLength: 1)
HStack {
Spacer()
Menu(
title: "Select a Demo",
items: [
MenuItem(label: "Text Styles", shortcut: "1"),
MenuItem(label: "Colors", shortcut: "2"),
MenuItem(label: "Container Views", shortcut: "3"),
MenuItem(label: "Overlays & Modals", shortcut: "4"),
MenuItem(label: "Layout System", shortcut: "5"),
MenuItem(label: "Buttons & Focus", shortcut: "6")
],
selection: state.menuSelectionBinding,
onSelect: { index in
// Navigate to the selected page
if let page = DemoPage(rawValue: index + 1) {
state.currentPage = page
}
},
selectedColor: .cyan,
borderStyle: .rounded,
borderColor: .brightBlack
)
Spacer()
}
Spacer(minLength: 1)
// Feature highlights (centered)
HStack {
Spacer()
HStack(spacing: 3) {
featureBox("Pure Swift", "No ncurses")
featureBox("Declarative", "SwiftUI-like")
featureBox("Composable", "View protocol")
}
Spacer()
}
Spacer()
}
}
/// Creates a small feature highlight box.
private func featureBox(_ title: String, _ subtitle: String) -> some View {
VStack {
Text(title)
.bold()
.foregroundColor(.green)
Text(subtitle)
.dim()
}
.padding(EdgeInsets(horizontal: 2, vertical: 1))
.border(.rounded, color: .brightBlack)
}
}
@@ -0,0 +1,57 @@
//
// OverlaysPage.swift
// TUIKitExample
//
// Demonstrates overlay and modal capabilities.
//
import TUIKit
/// Overlays and modals demo page.
///
/// Shows the overlay system including:
/// - `.overlay()` modifier
/// - `.dimmed()` modifier
/// - `.modal()` helper
/// - Note: The status bar is NOT dimmed by modals!
struct OverlaysPage: View {
var body: some View {
// Background content with modal overlay
backgroundContent
.modal {
Alert(
title: "Modal Alert",
message: "This alert overlays dimmed content!",
borderStyle: .rounded,
borderColor: .yellow,
titleColor: .yellow
) {
HStack(spacing: 3) {
Text("[OK]").bold().foregroundColor(.green)
Text("[Cancel]").foregroundColor(.red)
}
}
}
}
var backgroundContent: some View {
VStack(spacing: 1) {
HeaderView(title: "Overlays & Modals Demo")
DemoSection("Overlay System Features") {
Text("• .overlay() modifier - layer content on top")
Text("• .dimmed() modifier - reduce visual emphasis")
Text("• .modal() helper - combines dimmed + centered overlay")
}
DemoSection("This page demonstrates a modal overlay") {
Text("The content behind is dimmed automatically")
Text("Note: The status bar is NOT dimmed!")
.bold()
.foregroundColor(.green)
}
Spacer()
}
}
}
@@ -0,0 +1,46 @@
//
// TextStylesPage.swift
// TUIKitExample
//
// Demonstrates text styling capabilities.
//
import TUIKit
/// Text styles demo page.
///
/// Shows various text styling options including:
/// - Basic styles (bold, italic, underline, etc.)
/// - Combined styles
/// - Special effects (blink, inverted)
struct TextStylesPage: View {
var body: some View {
VStack(spacing: 1) {
HeaderView(title: "Text Styles Demo")
DemoSection("Basic Styles") {
Text("Normal text - no styling applied")
Text("Bold text").bold()
Text("Italic text").italic()
Text("Underlined text").underline()
Text("Strikethrough text").strikethrough()
Text("Dimmed text").dim()
}
DemoSection("Combined Styles") {
Text("Bold + Italic").bold().italic()
Text("Bold + Underline").bold().underline()
Text("Bold + Color").bold().foregroundColor(.cyan)
Text("Italic + Dim").italic().dim()
Text("All combined").bold().italic().underline().foregroundColor(.magenta)
}
DemoSection("Special Effects") {
Text("Blinking text (if terminal supports)").blink()
Text("Inverted colors").inverted()
}
Spacer()
}
}
}
+25
View File
@@ -0,0 +1,25 @@
//
// main.swift
// TUIKitExample
//
// Entry point for the TUIKit example application.
//
// This app demonstrates TUIKit capabilities through various demo pages.
// Use the menu to navigate between demos.
//
import TUIKit
// MARK: - Main App
/// The main example application.
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// Run the app
ExampleApp.main()