@rlabs-inc/create-tui 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +129 -0
  2. package/dist/index.js +221 -0
  3. package/package.json +20 -15
  4. package/templates/counter/README.md +88 -0
  5. package/templates/counter/package.json +18 -0
  6. package/templates/counter/src/App.ts +65 -0
  7. package/templates/counter/src/components/Counter.ts +78 -0
  8. package/templates/counter/src/components/CounterPanel.ts +74 -0
  9. package/templates/counter/src/components/Header.ts +55 -0
  10. package/templates/counter/src/components/HistoryPanel.ts +96 -0
  11. package/templates/counter/src/components/KeyBindings.ts +60 -0
  12. package/templates/counter/src/components/StatsPanel.ts +101 -0
  13. package/templates/counter/src/main.ts +87 -0
  14. package/templates/counter/src/state/counters.ts +121 -0
  15. package/templates/counter/tsconfig.json +16 -0
  16. package/templates/dashboard/README.md +95 -0
  17. package/templates/dashboard/package.json +18 -0
  18. package/templates/dashboard/src/App.ts +72 -0
  19. package/templates/dashboard/src/components/Footer.ts +102 -0
  20. package/templates/dashboard/src/components/Header.ts +108 -0
  21. package/templates/dashboard/src/components/LogsPanel.ts +98 -0
  22. package/templates/dashboard/src/components/MetricsPanel.ts +145 -0
  23. package/templates/dashboard/src/components/Sidebar.ts +162 -0
  24. package/templates/dashboard/src/components/TrafficPanel.ts +129 -0
  25. package/templates/dashboard/src/main.ts +66 -0
  26. package/templates/dashboard/src/state/logs.ts +42 -0
  27. package/templates/dashboard/src/state/metrics.ts +129 -0
  28. package/templates/dashboard/src/state/theme.ts +20 -0
  29. package/templates/dashboard/tsconfig.json +16 -0
  30. package/templates/minimal/README.md +98 -0
  31. package/templates/minimal/package.json +18 -0
  32. package/templates/minimal/src/App.ts +108 -0
  33. package/templates/minimal/src/components/Header.ts +52 -0
  34. package/templates/minimal/src/main.ts +24 -0
  35. package/templates/minimal/tsconfig.json +16 -0
  36. package/src/commands/create.ts +0 -300
  37. package/src/index.ts +0 -75
  38. package/src/utils/colors.ts +0 -132
  39. package/src/utils/prompts.ts +0 -273
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Counter Panel Component
3
+ *
4
+ * Displays all counters using the `each` template primitive.
5
+ * Demonstrates:
6
+ * - Reactive list rendering with each()
7
+ * - Focus tracking per item
8
+ */
9
+
10
+ import { derived } from '@rlabs-inc/signals'
11
+ import { box, text, each, t, BorderStyle, Attr, focusManager } from '@rlabs-inc/tui'
12
+ import { counters } from '../state/counters'
13
+ import { Counter } from './Counter'
14
+
15
+ export function CounterPanel() {
16
+ box({
17
+ border: BorderStyle.SINGLE,
18
+ borderColor: t.border,
19
+ flexDirection: 'column',
20
+ grow: 1,
21
+ children: () => {
22
+ // Panel header
23
+ box({
24
+ padding: 1,
25
+ borderBottom: BorderStyle.SINGLE,
26
+ borderColor: t.border,
27
+ children: () => {
28
+ text({
29
+ content: 'COUNTERS',
30
+ fg: t.textBright,
31
+ attrs: Attr.BOLD,
32
+ })
33
+ text({
34
+ content: 'Tab to navigate, +/- to change value',
35
+ fg: t.textDim,
36
+ })
37
+ },
38
+ })
39
+
40
+ // Counter grid using each() primitive
41
+ box({
42
+ padding: 1,
43
+ gap: 1,
44
+ flexDirection: 'row',
45
+ flexWrap: 'wrap',
46
+ children: () => {
47
+ each(
48
+ () => counters.value,
49
+ (getCounter, key) => {
50
+ const counter = getCounter()
51
+ // Find index for focus tracking
52
+ const counterIndex = counters.value.findIndex(c => String(c.id) === key)
53
+ const isFocused = derived(
54
+ () => focusManager.focusedIndex === counterIndex
55
+ )
56
+
57
+ return box({
58
+ width: '48%',
59
+ minWidth: 20,
60
+ children: () => {
61
+ Counter({
62
+ counter,
63
+ focused: isFocused.value,
64
+ })
65
+ },
66
+ })
67
+ },
68
+ { key: (c) => String(c.id) }
69
+ )
70
+ },
71
+ })
72
+ },
73
+ })
74
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Header Component
3
+ *
4
+ * Shows app title and current theme.
5
+ */
6
+
7
+ import { box, text, t, BorderStyle, Attr } from '@rlabs-inc/tui'
8
+ import { currentThemeName } from '../state/counters'
9
+
10
+ export function Header() {
11
+ box({
12
+ height: 3,
13
+ width: '100%',
14
+ border: BorderStyle.SINGLE,
15
+ borderColor: t.primary,
16
+ paddingLeft: 1,
17
+ paddingRight: 1,
18
+ flexDirection: 'row',
19
+ justifyContent: 'space-between',
20
+ alignItems: 'center',
21
+ children: () => {
22
+ // Left: Title
23
+ box({
24
+ flexDirection: 'row',
25
+ gap: 2,
26
+ alignItems: 'center',
27
+ children: () => {
28
+ text({
29
+ content: '{{PROJECT_NAME}}',
30
+ fg: t.primary,
31
+ attrs: Attr.BOLD,
32
+ })
33
+ text({ content: '|', fg: t.textDim })
34
+ text({ content: 'Reactive Counter Showcase', fg: t.textMuted })
35
+ },
36
+ })
37
+
38
+ // Right: Theme indicator
39
+ box({
40
+ flexDirection: 'row',
41
+ gap: 1,
42
+ alignItems: 'center',
43
+ children: () => {
44
+ text({ content: 'Theme:', fg: t.textDim })
45
+ text({
46
+ content: currentThemeName,
47
+ fg: t.accent,
48
+ attrs: Attr.BOLD,
49
+ })
50
+ text({ content: '[t]', fg: t.textDim })
51
+ },
52
+ })
53
+ },
54
+ })
55
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * History Panel Component
3
+ *
4
+ * Scrollable history log demonstrating auto-scroll detection.
5
+ * When content exceeds the container height, TUI automatically
6
+ * enables scrolling.
7
+ *
8
+ * Demonstrates:
9
+ * - Auto-scroll when content overflows
10
+ * - each() for dynamic list rendering
11
+ * - show() for conditional empty state
12
+ */
13
+
14
+ import { derived } from '@rlabs-inc/signals'
15
+ import { box, text, each, show, t, BorderStyle, Attr } from '@rlabs-inc/tui'
16
+ import { history } from '../state/counters'
17
+
18
+ export function HistoryPanel() {
19
+ box({
20
+ border: BorderStyle.SINGLE,
21
+ borderColor: t.border,
22
+ grow: 1,
23
+ flexDirection: 'column',
24
+ children: () => {
25
+ // Header
26
+ box({
27
+ padding: 1,
28
+ borderBottom: BorderStyle.SINGLE,
29
+ borderColor: t.border,
30
+ flexDirection: 'row',
31
+ justifyContent: 'space-between',
32
+ children: () => {
33
+ text({
34
+ content: 'HISTORY',
35
+ fg: t.textBright,
36
+ attrs: Attr.BOLD,
37
+ })
38
+ text({
39
+ content: derived(() => `${history.value.length} entries`),
40
+ fg: t.textDim,
41
+ })
42
+ },
43
+ })
44
+
45
+ // Scrollable content area
46
+ box({
47
+ grow: 1,
48
+ padding: 1,
49
+ overflow: 'scroll', // Enable scrolling
50
+ children: () => {
51
+ // Empty state using show()
52
+ show(
53
+ () => history.value.length === 0,
54
+ () => {
55
+ return box({
56
+ alignItems: 'center',
57
+ padding: 2,
58
+ children: () => {
59
+ text({ content: 'No history yet', fg: t.textDim })
60
+ text({
61
+ content: 'Actions will appear here',
62
+ fg: t.textDim,
63
+ })
64
+ },
65
+ })
66
+ }
67
+ )
68
+
69
+ // History entries using each()
70
+ each(
71
+ () => history.value,
72
+ (getEntry) => {
73
+ const entry = getEntry()
74
+ return box({
75
+ flexDirection: 'row',
76
+ gap: 1,
77
+ children: () => {
78
+ text({
79
+ content: entry.timestamp,
80
+ fg: t.textDim,
81
+ width: 10,
82
+ })
83
+ text({
84
+ content: entry.message,
85
+ fg: t.text,
86
+ })
87
+ },
88
+ })
89
+ },
90
+ { key: (e) => String(e.id) }
91
+ )
92
+ },
93
+ })
94
+ },
95
+ })
96
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Key Bindings Component
3
+ *
4
+ * Footer showing available keyboard shortcuts.
5
+ */
6
+
7
+ import { box, text, t, BorderStyle } from '@rlabs-inc/tui'
8
+
9
+ export function KeyBindings() {
10
+ box({
11
+ height: 3,
12
+ width: '100%',
13
+ border: BorderStyle.SINGLE,
14
+ borderColor: t.border,
15
+ paddingLeft: 1,
16
+ paddingRight: 1,
17
+ flexDirection: 'row',
18
+ justifyContent: 'space-between',
19
+ alignItems: 'center',
20
+ children: () => {
21
+ // Navigation keys
22
+ box({
23
+ flexDirection: 'row',
24
+ gap: 2,
25
+ children: () => {
26
+ KeyHint({ key: 'Tab', action: 'Focus' })
27
+ KeyHint({ key: '+/-', action: 'Change' })
28
+ KeyHint({ key: 'r', action: 'Reset' })
29
+ },
30
+ })
31
+
32
+ // Action keys
33
+ box({
34
+ flexDirection: 'row',
35
+ gap: 2,
36
+ children: () => {
37
+ KeyHint({ key: 'R', action: 'Reset All' })
38
+ KeyHint({ key: 't', action: 'Theme' })
39
+ KeyHint({ key: 'q', action: 'Quit' })
40
+ },
41
+ })
42
+ },
43
+ })
44
+ }
45
+
46
+ interface KeyHintProps {
47
+ key: string
48
+ action: string
49
+ }
50
+
51
+ function KeyHint({ key, action }: KeyHintProps) {
52
+ box({
53
+ flexDirection: 'row',
54
+ gap: 1,
55
+ children: () => {
56
+ text({ content: `[${key}]`, fg: t.info })
57
+ text({ content: action, fg: t.textDim })
58
+ },
59
+ })
60
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Stats Panel Component
3
+ *
4
+ * Displays derived statistics computed from counter values.
5
+ * Demonstrates reactive derived state.
6
+ */
7
+
8
+ import { derived } from '@rlabs-inc/signals'
9
+ import { box, text, t, BorderStyle, Attr } from '@rlabs-inc/tui'
10
+ import { total, average, min, max, counters } from '../state/counters'
11
+
12
+ export function StatsPanel() {
13
+ box({
14
+ border: BorderStyle.SINGLE,
15
+ borderColor: t.border,
16
+ children: () => {
17
+ // Header
18
+ box({
19
+ padding: 1,
20
+ borderBottom: BorderStyle.SINGLE,
21
+ borderColor: t.border,
22
+ children: () => {
23
+ text({
24
+ content: 'STATISTICS',
25
+ fg: t.textBright,
26
+ attrs: Attr.BOLD,
27
+ })
28
+ },
29
+ })
30
+
31
+ // Stats grid
32
+ box({
33
+ padding: 1,
34
+ gap: 1,
35
+ children: () => {
36
+ // Row 1: Total and Average
37
+ box({
38
+ flexDirection: 'row',
39
+ gap: 2,
40
+ children: () => {
41
+ StatItem({ label: 'Total', value: total, color: t.primary })
42
+ StatItem({
43
+ label: 'Average',
44
+ value: derived(() => average.value.toFixed(1)),
45
+ color: t.secondary,
46
+ })
47
+ },
48
+ })
49
+
50
+ // Row 2: Min and Max
51
+ box({
52
+ flexDirection: 'row',
53
+ gap: 2,
54
+ children: () => {
55
+ StatItem({ label: 'Min', value: min, color: t.error })
56
+ StatItem({ label: 'Max', value: max, color: t.success })
57
+ },
58
+ })
59
+
60
+ // Row 3: Count
61
+ box({
62
+ flexDirection: 'row',
63
+ gap: 2,
64
+ children: () => {
65
+ StatItem({
66
+ label: 'Counters',
67
+ value: derived(() => counters.value.length),
68
+ color: t.info,
69
+ })
70
+ },
71
+ })
72
+ },
73
+ })
74
+ },
75
+ })
76
+ }
77
+
78
+ interface StatItemProps {
79
+ label: string
80
+ value: { value: number | string } | (() => number | string)
81
+ color: { value: import('@rlabs-inc/tui').RGBA }
82
+ }
83
+
84
+ function StatItem({ label, value, color }: StatItemProps) {
85
+ box({
86
+ flexDirection: 'row',
87
+ gap: 1,
88
+ width: '45%',
89
+ children: () => {
90
+ text({ content: `${label}:`, fg: t.textDim, width: 10 })
91
+ text({
92
+ content: derived(() => {
93
+ const v = typeof value === 'function' ? value() : value.value
94
+ return String(v)
95
+ }),
96
+ fg: color,
97
+ attrs: Attr.BOLD,
98
+ })
99
+ },
100
+ })
101
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * {{PROJECT_NAME}} - Reactive Counter Showcase
3
+ *
4
+ * Demonstrates TUI Framework's key features:
5
+ * - Fine-grained reactivity with signals
6
+ * - Template primitives (each, show)
7
+ * - Focus management
8
+ * - Auto-scroll detection
9
+ * - Theme switching
10
+ */
11
+
12
+ import { mount, keyboard, focusManager } from '@rlabs-inc/tui'
13
+ import { App } from './App'
14
+ import { counters, resetAll, addHistoryEntry, cycleTheme } from './state/counters'
15
+
16
+ async function main() {
17
+ const cleanup = await mount(
18
+ () => App(),
19
+ { mode: 'fullscreen', mouse: true }
20
+ )
21
+
22
+ // ==========================================================================
23
+ // KEYBOARD HANDLERS
24
+ // ==========================================================================
25
+
26
+ // Counter controls (apply to focused counter)
27
+ keyboard.onKey(['+', '='], () => {
28
+ const idx = focusManager.focusedIndex
29
+ if (idx >= 0 && idx < counters.value.length) {
30
+ const counter = counters.value[idx]
31
+ counter.value.value++
32
+ addHistoryEntry(`Counter ${idx + 1}: +1 = ${counter.value.value}`)
33
+ }
34
+ return true
35
+ })
36
+
37
+ keyboard.onKey('-', () => {
38
+ const idx = focusManager.focusedIndex
39
+ if (idx >= 0 && idx < counters.value.length) {
40
+ const counter = counters.value[idx]
41
+ counter.value.value--
42
+ addHistoryEntry(`Counter ${idx + 1}: -1 = ${counter.value.value}`)
43
+ }
44
+ return true
45
+ })
46
+
47
+ keyboard.onKey('r', () => {
48
+ const idx = focusManager.focusedIndex
49
+ if (idx >= 0 && idx < counters.value.length) {
50
+ const counter = counters.value[idx]
51
+ counter.value.value = 0
52
+ addHistoryEntry(`Counter ${idx + 1}: reset to 0`)
53
+ }
54
+ return true
55
+ })
56
+
57
+ keyboard.onKey('R', () => {
58
+ resetAll()
59
+ addHistoryEntry('All counters reset')
60
+ return true
61
+ })
62
+
63
+ // Theme cycling
64
+ keyboard.onKey('t', () => {
65
+ const themeName = cycleTheme()
66
+ addHistoryEntry(`Theme: ${themeName}`)
67
+ return true
68
+ })
69
+
70
+ // Focus navigation
71
+ keyboard.onKey('Tab', () => {
72
+ focusManager.focusNext()
73
+ return true
74
+ })
75
+
76
+ keyboard.onKey('Shift+Tab', () => {
77
+ focusManager.focusPrevious()
78
+ return true
79
+ })
80
+
81
+ // Quit
82
+ keyboard.onKey(['q', 'Q', 'Escape'], () => {
83
+ cleanup()
84
+ })
85
+ }
86
+
87
+ main().catch(console.error)
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Counter State Management
3
+ *
4
+ * Centralized state using signals with derived computations.
5
+ */
6
+
7
+ import { signal, derived, type WritableSignal } from '@rlabs-inc/signals'
8
+ import { setTheme, themes } from '@rlabs-inc/tui'
9
+
10
+ // =============================================================================
11
+ // COUNTER STATE
12
+ // =============================================================================
13
+
14
+ export interface Counter {
15
+ id: number
16
+ name: string
17
+ value: WritableSignal<number>
18
+ variant: 'primary' | 'secondary' | 'success' | 'warning' | 'info'
19
+ }
20
+
21
+ let nextId = 1
22
+
23
+ function createCounter(
24
+ name: string,
25
+ variant: Counter['variant'],
26
+ initial = 0
27
+ ): Counter {
28
+ return {
29
+ id: nextId++,
30
+ name,
31
+ value: signal(initial),
32
+ variant,
33
+ }
34
+ }
35
+
36
+ // Initial counters with different variants
37
+ export const counters = signal<Counter[]>([
38
+ createCounter('Alpha', 'primary', 0),
39
+ createCounter('Beta', 'secondary', 10),
40
+ createCounter('Gamma', 'success', 5),
41
+ createCounter('Delta', 'warning', -3),
42
+ ])
43
+
44
+ // =============================================================================
45
+ // DERIVED STATE
46
+ // =============================================================================
47
+
48
+ /** Total of all counter values */
49
+ export const total = derived(() =>
50
+ counters.value.reduce((sum, c) => sum + c.value.value, 0)
51
+ )
52
+
53
+ /** Average of all counter values */
54
+ export const average = derived(() => {
55
+ const list = counters.value
56
+ if (list.length === 0) return 0
57
+ return Math.round(total.value / list.length * 10) / 10
58
+ })
59
+
60
+ /** Minimum counter value */
61
+ export const min = derived(() => {
62
+ const values = counters.value.map(c => c.value.value)
63
+ return values.length > 0 ? Math.min(...values) : 0
64
+ })
65
+
66
+ /** Maximum counter value */
67
+ export const max = derived(() => {
68
+ const values = counters.value.map(c => c.value.value)
69
+ return values.length > 0 ? Math.max(...values) : 0
70
+ })
71
+
72
+ // =============================================================================
73
+ // HISTORY (demonstrates auto-scroll)
74
+ // =============================================================================
75
+
76
+ export interface HistoryEntry {
77
+ id: number
78
+ timestamp: string
79
+ message: string
80
+ }
81
+
82
+ let historyId = 1
83
+ export const history = signal<HistoryEntry[]>([])
84
+
85
+ export function addHistoryEntry(message: string) {
86
+ const entry: HistoryEntry = {
87
+ id: historyId++,
88
+ timestamp: new Date().toLocaleTimeString(),
89
+ message,
90
+ }
91
+ // Keep last 50 entries
92
+ history.value = [...history.value.slice(-49), entry]
93
+ }
94
+
95
+ // =============================================================================
96
+ // ACTIONS
97
+ // =============================================================================
98
+
99
+ export function resetAll() {
100
+ counters.value.forEach(c => {
101
+ c.value.value = 0
102
+ })
103
+ }
104
+
105
+ // =============================================================================
106
+ // THEME MANAGEMENT
107
+ // =============================================================================
108
+
109
+ const themeNames = Object.keys(themes)
110
+ export const currentThemeIndex = signal(0)
111
+ export const currentThemeName = derived(() => themeNames[currentThemeIndex.value])
112
+
113
+ export function cycleTheme(): string {
114
+ currentThemeIndex.value = (currentThemeIndex.value + 1) % themeNames.length
115
+ const themeName = themeNames[currentThemeIndex.value]
116
+ setTheme(themeName as any)
117
+ return themeName
118
+ }
119
+
120
+ // Initialize with first entry
121
+ addHistoryEntry('Application started')
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "types": ["bun-types"]
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }
@@ -0,0 +1,95 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A comprehensive system dashboard demonstrating all TUI Framework features.
4
+
5
+ ## Features
6
+
7
+ - **Multi-panel layout** with header, sidebar, main content, and footer
8
+ - **Real-time reactive updates** with simulated system metrics
9
+ - **Theme system** with 12+ built-in themes and live switching
10
+ - **Auto-scrolling logs** that track activity
11
+ - **Progress bars** and status indicators
12
+ - **Variant-based styling** for semantic colors
13
+ - **Keyboard navigation** and shortcuts
14
+ - **Component composition** patterns
15
+
16
+ ## Getting Started
17
+
18
+ ```bash
19
+ # Install dependencies
20
+ bun install
21
+
22
+ # Run the application
23
+ bun run dev
24
+ ```
25
+
26
+ ## Controls
27
+
28
+ | Key | Action |
29
+ |-----|--------|
30
+ | `1-4` | Switch between panels |
31
+ | `t` | Cycle through themes |
32
+ | `r` | Refresh metrics |
33
+ | `p` | Pause/Resume updates |
34
+ | `q` / `Escape` | Quit |
35
+
36
+ ## Project Structure
37
+
38
+ ```
39
+ {{PROJECT_NAME}}/
40
+ ├── src/
41
+ │ ├── main.ts # Entry point and keyboard handling
42
+ │ ├── App.ts # Root layout component
43
+ │ ├── state/
44
+ │ │ ├── metrics.ts # System metrics state
45
+ │ │ ├── theme.ts # Theme management
46
+ │ │ └── logs.ts # Activity log state
47
+ │ └── components/
48
+ │ ├── Header.ts # Top bar with status
49
+ │ ├── Sidebar.ts # Navigation + quick stats
50
+ │ ├── MetricsPanel.ts # CPU, Memory, Disk meters
51
+ │ ├── TrafficPanel.ts # Network and request stats
52
+ │ ├── ServicesPanel.ts # Service status indicators
53
+ │ ├── LogsPanel.ts # Scrollable activity logs
54
+ │ └── Footer.ts # Bottom bar with shortcuts
55
+ ├── package.json
56
+ └── README.md
57
+ ```
58
+
59
+ ## Architecture Highlights
60
+
61
+ ### Reactive State
62
+ All metrics use signals that automatically propagate changes:
63
+
64
+ ```typescript
65
+ const cpuUsage = signal(45)
66
+ const cpuBar = derived(() => progressBar(cpuUsage.value, 20))
67
+ ```
68
+
69
+ ### Component Composition
70
+ Panels are composed from smaller, reusable components:
71
+
72
+ ```typescript
73
+ function Sidebar() {
74
+ box({ children: () => {
75
+ QuickStats()
76
+ Navigation()
77
+ }})
78
+ }
79
+ ```
80
+
81
+ ### Theme Integration
82
+ Components use semantic theme colors for consistent styling:
83
+
84
+ ```typescript
85
+ box({
86
+ variant: 'primary',
87
+ borderColor: t.primary,
88
+ fg: t.text,
89
+ })
90
+ ```
91
+
92
+ ## Learn More
93
+
94
+ - [TUI Framework](https://github.com/rlabs-inc/tui)
95
+ - [Signals Library](https://github.com/rlabs-inc/signals)