mirror of
https://github.com/phranck/TUIkit.git
synced 2026-05-21 09:50:35 +00:00
Feat: Add Table component with column support
- TableColumn: Column definition with title, alignment, width modes - Key path and closure-based value extraction - Width modes: .fixed(Int), .flexible, .ratio(Double) - Alignment: .leading, .center, .trailing - Chainable modifiers: .alignment(), .width() - Table: SwiftUI-compatible table with column headers - Single selection: Table(data, selection: Binding<ID?>) - Multi-selection: Table(data, selection: Binding<Set<ID>>) - Reuses ItemListHandler for navigation/selection - ANSI-aware column alignment - Header row with column titles - Space-only separators (no vertical lines) - Scroll indicators when content overflows - Empty state placeholder - .disabled() modifier support - TableColumnBuilder: Result builder for column DSL - TablePage: Example with file browser style demo - 21 new tests for Table and TableColumn Completes Phase 2 of List & Table plan.
This commit is contained in:
@@ -21,6 +21,7 @@ enum DemoPage: Int, CaseIterable {
|
||||
case radioButtons
|
||||
case spinners
|
||||
case lists
|
||||
case tables
|
||||
}
|
||||
|
||||
// MARK: - Content View (Page Router)
|
||||
@@ -62,6 +63,10 @@ struct ContentView: View {
|
||||
// Quick jump to Lists
|
||||
currentPage = .lists
|
||||
return true
|
||||
case .character("-"):
|
||||
// Quick jump to Tables
|
||||
currentPage = .tables
|
||||
return true
|
||||
default:
|
||||
return false // Let other handlers process
|
||||
}
|
||||
@@ -107,6 +112,9 @@ struct ContentView: View {
|
||||
case .lists:
|
||||
ListPage()
|
||||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||||
case .tables:
|
||||
TablePage()
|
||||
.statusBarItems(subPageItems(pageSetter: pageSetter))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@ private struct FileItem: Identifiable {
|
||||
let size: String
|
||||
let icon: String
|
||||
|
||||
static let sampleFiles: [FileItem] = [
|
||||
FileItem(id: "1", name: "README.md", size: "4.2 KB", icon: "📄"),
|
||||
FileItem(id: "2", name: "Package.swift", size: "1.8 KB", icon: "📦"),
|
||||
FileItem(id: "3", name: "Sources", size: "128 KB", icon: "📁"),
|
||||
FileItem(id: "4", name: "Tests", size: "64 KB", icon: "📁"),
|
||||
FileItem(id: "5", name: ".gitignore", size: "0.5 KB", icon: "📄"),
|
||||
FileItem(id: "6", name: "LICENSE", size: "1.1 KB", icon: "📄"),
|
||||
FileItem(id: "7", name: "docs", size: "256 KB", icon: "📁"),
|
||||
FileItem(id: "8", name: "plans", size: "32 KB", icon: "📁"),
|
||||
FileItem(id: "9", name: ".swiftlint.yml", size: "1.2 KB", icon: "⚙️"),
|
||||
FileItem(id: "10", name: ".github", size: "8 KB", icon: "📁"),
|
||||
FileItem(id: "11", name: "Makefile", size: "0.8 KB", icon: "📄"),
|
||||
FileItem(id: "12", name: ".claude", size: "16 KB", icon: "📁"),
|
||||
static let sampleFiles: [Self] = [
|
||||
Self(id: "1", name: "README.md", size: "4.2 KB", icon: "📄"),
|
||||
Self(id: "2", name: "Package.swift", size: "1.8 KB", icon: "📦"),
|
||||
Self(id: "3", name: "Sources", size: "128 KB", icon: "📁"),
|
||||
Self(id: "4", name: "Tests", size: "64 KB", icon: "📁"),
|
||||
Self(id: "5", name: ".gitignore", size: "0.5 KB", icon: "📄"),
|
||||
Self(id: "6", name: "LICENSE", size: "1.1 KB", icon: "📄"),
|
||||
Self(id: "7", name: "docs", size: "256 KB", icon: "📁"),
|
||||
Self(id: "8", name: "plans", size: "32 KB", icon: "📁"),
|
||||
Self(id: "9", name: ".swiftlint.yml", size: "1.2 KB", icon: "⚙️"),
|
||||
Self(id: "10", name: ".github", size: "8 KB", icon: "📁"),
|
||||
Self(id: "11", name: "Makefile", size: "0.8 KB", icon: "📄"),
|
||||
Self(id: "12", name: ".claude", size: "16 KB", icon: "📁"),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ struct MainMenuPage: View {
|
||||
MenuItem(label: "Radio Buttons", shortcut: "8"),
|
||||
MenuItem(label: "Spinners", shortcut: "9"),
|
||||
MenuItem(label: "Lists", shortcut: "0"),
|
||||
MenuItem(label: "Tables", shortcut: "-"),
|
||||
],
|
||||
selection: $menuSelection,
|
||||
onSelect: { index in
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// 🖥️ TUIKit — Terminal UI Kit for Swift
|
||||
// TablePage.swift
|
||||
//
|
||||
// Created by LAYERED.work
|
||||
// License: MIT
|
||||
|
||||
import TUIkit
|
||||
|
||||
// MARK: - Demo Data
|
||||
|
||||
/// A file entry for the table demo.
|
||||
private struct FileEntry: Identifiable, Sendable {
|
||||
let id: String
|
||||
let name: String
|
||||
let size: String
|
||||
let modified: String
|
||||
let type: String
|
||||
|
||||
static let sampleFiles: [Self] = [
|
||||
Self(id: "1", name: "README.md", size: "4.2 KB", modified: "2026-02-07", type: "Markdown"),
|
||||
Self(id: "2", name: "Package.swift", size: "1.8 KB", modified: "2026-02-06", type: "Swift"),
|
||||
Self(id: "3", name: "Sources/", size: "128 KB", modified: "2026-02-07", type: "Directory"),
|
||||
Self(id: "4", name: "Tests/", size: "64 KB", modified: "2026-02-05", type: "Directory"),
|
||||
Self(id: "5", name: ".gitignore", size: "0.5 KB", modified: "2026-01-15", type: "Config"),
|
||||
Self(id: "6", name: "LICENSE", size: "1.1 KB", modified: "2026-01-01", type: "Text"),
|
||||
Self(id: "7", name: "docs/", size: "256 KB", modified: "2026-02-04", type: "Directory"),
|
||||
Self(id: "8", name: "plans/", size: "32 KB", modified: "2026-02-07", type: "Directory"),
|
||||
Self(id: "9", name: ".swiftlint.yml", size: "1.2 KB", modified: "2026-02-02", type: "YAML"),
|
||||
Self(id: "10", name: ".github/", size: "8 KB", modified: "2026-01-20", type: "Directory"),
|
||||
Self(id: "11", name: "Makefile", size: "0.8 KB", modified: "2026-02-01", type: "Makefile"),
|
||||
Self(id: "12", name: ".claude/", size: "16 KB", modified: "2026-02-07", type: "Directory"),
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Table Page
|
||||
|
||||
/// Table component demo page.
|
||||
///
|
||||
/// Shows interactive table features including:
|
||||
/// - Column definitions with key paths
|
||||
/// - Column alignment (leading, center, trailing)
|
||||
/// - Column width modes (fixed, flexible, ratio)
|
||||
/// - Single and multi-selection
|
||||
/// - Keyboard navigation
|
||||
/// - Scroll indicators
|
||||
struct TablePage: View {
|
||||
@State var singleSelection: String?
|
||||
@State var multiSelection: Set<String> = []
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 1) {
|
||||
|
||||
DemoSection("File Browser (Single Selection)") {
|
||||
Table(
|
||||
FileEntry.sampleFiles,
|
||||
selection: $singleSelection,
|
||||
maxVisibleRows: 6
|
||||
) {
|
||||
TableColumn("Name", value: \FileEntry.name)
|
||||
TableColumn("Size", value: \FileEntry.size)
|
||||
.width(.fixed(10))
|
||||
.alignment(.trailing)
|
||||
TableColumn("Modified", value: \FileEntry.modified)
|
||||
.width(.fixed(12))
|
||||
TableColumn("Type", value: \FileEntry.type)
|
||||
.width(.fixed(10))
|
||||
}
|
||||
}
|
||||
|
||||
DemoSection("Multi-Selection Table") {
|
||||
Table(
|
||||
FileEntry.sampleFiles,
|
||||
selection: $multiSelection,
|
||||
maxVisibleRows: 4
|
||||
) {
|
||||
TableColumn("Name", value: \FileEntry.name)
|
||||
TableColumn("Type", value: \FileEntry.type)
|
||||
.width(.fixed(12))
|
||||
}
|
||||
}
|
||||
|
||||
DemoSection("Current Selections") {
|
||||
VStack(spacing: 1) {
|
||||
HStack(spacing: 1) {
|
||||
Text("Single:").foregroundColor(.palette.foregroundSecondary)
|
||||
Text(singleSelection ?? "(none)")
|
||||
.bold()
|
||||
.foregroundColor(.palette.accent)
|
||||
}
|
||||
HStack(spacing: 1) {
|
||||
Text("Multi:").foregroundColor(.palette.foregroundSecondary)
|
||||
Text(multiSelection.isEmpty ? "(none)" : multiSelection.sorted().joined(separator: ", "))
|
||||
.bold()
|
||||
.foregroundColor(.palette.accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DemoSection("Navigation") {
|
||||
VStack {
|
||||
Text("Use [Up/Down] to navigate rows").dim()
|
||||
Text("Use [Home/End] to jump to first/last").dim()
|
||||
Text("Use [PageUp/PageDown] for fast scrolling").dim()
|
||||
Text("Use [Enter/Space] to select/deselect").dim()
|
||||
Text("Use [Tab] to switch between tables").dim()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.appHeader {
|
||||
HStack {
|
||||
Text("Table Demo").bold().foregroundColor(.palette.accent)
|
||||
Spacer()
|
||||
Text("TUIkit v\(tuiKitVersion)").foregroundColor(.palette.foregroundTertiary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user