@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.
- package/README.md +129 -0
- package/dist/index.js +221 -0
- package/package.json +20 -15
- package/templates/counter/README.md +88 -0
- package/templates/counter/package.json +18 -0
- package/templates/counter/src/App.ts +65 -0
- package/templates/counter/src/components/Counter.ts +78 -0
- package/templates/counter/src/components/CounterPanel.ts +74 -0
- package/templates/counter/src/components/Header.ts +55 -0
- package/templates/counter/src/components/HistoryPanel.ts +96 -0
- package/templates/counter/src/components/KeyBindings.ts +60 -0
- package/templates/counter/src/components/StatsPanel.ts +101 -0
- package/templates/counter/src/main.ts +87 -0
- package/templates/counter/src/state/counters.ts +121 -0
- package/templates/counter/tsconfig.json +16 -0
- package/templates/dashboard/README.md +95 -0
- package/templates/dashboard/package.json +18 -0
- package/templates/dashboard/src/App.ts +72 -0
- package/templates/dashboard/src/components/Footer.ts +102 -0
- package/templates/dashboard/src/components/Header.ts +108 -0
- package/templates/dashboard/src/components/LogsPanel.ts +98 -0
- package/templates/dashboard/src/components/MetricsPanel.ts +145 -0
- package/templates/dashboard/src/components/Sidebar.ts +162 -0
- package/templates/dashboard/src/components/TrafficPanel.ts +129 -0
- package/templates/dashboard/src/main.ts +66 -0
- package/templates/dashboard/src/state/logs.ts +42 -0
- package/templates/dashboard/src/state/metrics.ts +129 -0
- package/templates/dashboard/src/state/theme.ts +20 -0
- package/templates/dashboard/tsconfig.json +16 -0
- package/templates/minimal/README.md +98 -0
- package/templates/minimal/package.json +18 -0
- package/templates/minimal/src/App.ts +108 -0
- package/templates/minimal/src/components/Header.ts +52 -0
- package/templates/minimal/src/main.ts +24 -0
- package/templates/minimal/tsconfig.json +16 -0
- package/src/commands/create.ts +0 -300
- package/src/index.ts +0 -75
- package/src/utils/colors.ts +0 -132
- package/src/utils/prompts.ts +0 -273
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @rlabs-inc/create-tui
|
|
2
|
+
|
|
3
|
+
Scaffold professional TUI Framework applications with a single command.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Create a new project interactively
|
|
9
|
+
bunx @rlabs-inc/create-tui my-app
|
|
10
|
+
|
|
11
|
+
# Or use a specific template
|
|
12
|
+
bunx @rlabs-inc/create-tui my-app --template counter
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Templates
|
|
16
|
+
|
|
17
|
+
### minimal
|
|
18
|
+
|
|
19
|
+
A clean starting point with essential structure.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
my-app/
|
|
23
|
+
├── src/
|
|
24
|
+
│ └── main.ts
|
|
25
|
+
├── package.json
|
|
26
|
+
└── tsconfig.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Features:**
|
|
30
|
+
- Basic fullscreen app with theme support
|
|
31
|
+
- Keyboard handling (quit with q/Escape)
|
|
32
|
+
- Ready for your components
|
|
33
|
+
|
|
34
|
+
### counter
|
|
35
|
+
|
|
36
|
+
Interactive counter showcase demonstrating reactive state management.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
my-app/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── main.ts
|
|
42
|
+
│ ├── App.ts
|
|
43
|
+
│ ├── components/
|
|
44
|
+
│ │ ├── Counter.ts
|
|
45
|
+
│ │ ├── CounterPanel.ts
|
|
46
|
+
│ │ ├── Header.ts
|
|
47
|
+
│ │ └── HistoryPanel.ts
|
|
48
|
+
│ └── state/
|
|
49
|
+
│ └── counters.ts
|
|
50
|
+
├── package.json
|
|
51
|
+
└── tsconfig.json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Features:**
|
|
55
|
+
- Fine-grained reactivity with signals
|
|
56
|
+
- Template primitives (`each`, `show`)
|
|
57
|
+
- Focus management with Tab navigation
|
|
58
|
+
- Auto-scroll in history panel
|
|
59
|
+
- Theme switching (12+ themes)
|
|
60
|
+
- Variant styling
|
|
61
|
+
|
|
62
|
+
### dashboard
|
|
63
|
+
|
|
64
|
+
Full system monitor-style dashboard with multiple panels.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
my-app/
|
|
68
|
+
├── src/
|
|
69
|
+
│ ├── main.ts
|
|
70
|
+
│ ├── App.ts
|
|
71
|
+
│ ├── components/
|
|
72
|
+
│ │ ├── Header.ts
|
|
73
|
+
│ │ ├── Sidebar.ts
|
|
74
|
+
│ │ ├── MetricsPanel.ts
|
|
75
|
+
│ │ ├── TrafficPanel.ts
|
|
76
|
+
│ │ ├── LogsPanel.ts
|
|
77
|
+
│ │ └── Footer.ts
|
|
78
|
+
│ └── state/
|
|
79
|
+
│ ├── metrics.ts
|
|
80
|
+
│ ├── logs.ts
|
|
81
|
+
│ └── theme.ts
|
|
82
|
+
├── package.json
|
|
83
|
+
└── tsconfig.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Features:**
|
|
87
|
+
- Complex multi-panel layout
|
|
88
|
+
- Real-time metric simulation
|
|
89
|
+
- Activity logging with levels (info, warn, error, success)
|
|
90
|
+
- Auto-scrolling log panel
|
|
91
|
+
- Flexbox layout with grow/shrink
|
|
92
|
+
- Theme switching
|
|
93
|
+
|
|
94
|
+
## CLI Options
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Usage: create-tui <project-name> [options]
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
-t, --template <name> Template to use (minimal, counter, dashboard)
|
|
101
|
+
--skip-install Skip running bun install
|
|
102
|
+
-h, --help Show help
|
|
103
|
+
-v, --version Show version
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## After Scaffolding
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
cd my-app
|
|
110
|
+
bun run dev # Start development
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Framework Features Showcased
|
|
114
|
+
|
|
115
|
+
| Feature | minimal | counter | dashboard |
|
|
116
|
+
|---------|---------|---------|-----------|
|
|
117
|
+
| Signals & Reactivity | ✓ | ✓ | ✓ |
|
|
118
|
+
| Theme System | ✓ | ✓ | ✓ |
|
|
119
|
+
| Keyboard Handling | ✓ | ✓ | ✓ |
|
|
120
|
+
| Focus Management | - | ✓ | ✓ |
|
|
121
|
+
| `each()` Primitive | - | ✓ | ✓ |
|
|
122
|
+
| `show()` Primitive | - | ✓ | - |
|
|
123
|
+
| Auto-scroll | - | ✓ | ✓ |
|
|
124
|
+
| Variant Styling | - | ✓ | ✓ |
|
|
125
|
+
| Multi-panel Layout | - | ✓ | ✓ |
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
function parseArgs(args) {
|
|
6
|
+
const result = {
|
|
7
|
+
help: false,
|
|
8
|
+
version: false
|
|
9
|
+
};
|
|
10
|
+
let i = 0;
|
|
11
|
+
while (i < args.length) {
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (arg === "-h" || arg === "--help") {
|
|
14
|
+
result.help = true;
|
|
15
|
+
} else if (arg === "-v" || arg === "--version") {
|
|
16
|
+
result.version = true;
|
|
17
|
+
} else if (arg === "-t" || arg === "--template") {
|
|
18
|
+
result.template = args[++i];
|
|
19
|
+
} else if (arg === "--skip-install") {
|
|
20
|
+
result.skipInstall = true;
|
|
21
|
+
} else if (!arg.startsWith("-") && !result.projectName) {
|
|
22
|
+
result.projectName = arg;
|
|
23
|
+
}
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/scaffold.ts
|
|
30
|
+
import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
|
|
31
|
+
import { join, dirname } from "path";
|
|
32
|
+
var {spawn } = globalThis.Bun;
|
|
33
|
+
async function scaffold(options) {
|
|
34
|
+
const { projectName, template, skipInstall } = options;
|
|
35
|
+
const targetDir = join(process.cwd(), projectName);
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(` Creating project: ${projectName}`);
|
|
38
|
+
console.log(` Template: ${template}`);
|
|
39
|
+
console.log();
|
|
40
|
+
if (existsSync(targetDir)) {
|
|
41
|
+
const files = readdirSync(targetDir);
|
|
42
|
+
if (files.length > 0) {
|
|
43
|
+
throw new Error(`Directory "${projectName}" already exists and is not empty`);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
mkdirSync(targetDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
const templatesDir = findTemplatesDir();
|
|
49
|
+
const templateDir = join(templatesDir, template);
|
|
50
|
+
if (!existsSync(templateDir)) {
|
|
51
|
+
throw new Error(`Template "${template}" not found at ${templateDir}`);
|
|
52
|
+
}
|
|
53
|
+
console.log(" Copying template files...");
|
|
54
|
+
copyDir(templateDir, targetDir, projectName);
|
|
55
|
+
if (!skipInstall) {
|
|
56
|
+
console.log(" Installing dependencies...");
|
|
57
|
+
await runCommand("bun", ["install"], targetDir);
|
|
58
|
+
}
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(` Done! Your project is ready.`);
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(` Next steps:`);
|
|
63
|
+
console.log(` cd ${projectName}`);
|
|
64
|
+
if (skipInstall) {
|
|
65
|
+
console.log(` bun install`);
|
|
66
|
+
}
|
|
67
|
+
console.log(` bun run dev`);
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
function findTemplatesDir() {
|
|
71
|
+
const devPath = join(dirname(import.meta.dir), "templates");
|
|
72
|
+
if (existsSync(devPath)) {
|
|
73
|
+
return devPath;
|
|
74
|
+
}
|
|
75
|
+
const pkgPath = join(dirname(dirname(import.meta.dir)), "templates");
|
|
76
|
+
if (existsSync(pkgPath)) {
|
|
77
|
+
return pkgPath;
|
|
78
|
+
}
|
|
79
|
+
throw new Error("Could not find templates directory");
|
|
80
|
+
}
|
|
81
|
+
function copyDir(src, dest, projectName) {
|
|
82
|
+
const entries = readdirSync(src);
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const srcPath = join(src, entry);
|
|
85
|
+
const destPath = join(dest, entry);
|
|
86
|
+
const stat = statSync(srcPath);
|
|
87
|
+
if (stat.isDirectory()) {
|
|
88
|
+
mkdirSync(destPath, { recursive: true });
|
|
89
|
+
copyDir(srcPath, destPath, projectName);
|
|
90
|
+
} else {
|
|
91
|
+
let content = readFileSync(srcPath, "utf-8");
|
|
92
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
93
|
+
writeFileSync(destPath, content);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function runCommand(command, args, cwd) {
|
|
98
|
+
const proc = spawn({
|
|
99
|
+
cmd: [command, ...args],
|
|
100
|
+
cwd,
|
|
101
|
+
stdout: "inherit",
|
|
102
|
+
stderr: "inherit"
|
|
103
|
+
});
|
|
104
|
+
const exitCode = await proc.exited;
|
|
105
|
+
if (exitCode !== 0) {
|
|
106
|
+
throw new Error(`Command "${command} ${args.join(" ")}" failed with exit code ${exitCode}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/interactive.ts
|
|
111
|
+
import * as readline from "readline";
|
|
112
|
+
var TEMPLATES = ["minimal", "counter", "dashboard"];
|
|
113
|
+
async function runInteractive(existingName, existingTemplate) {
|
|
114
|
+
const rl = readline.createInterface({
|
|
115
|
+
input: process.stdin,
|
|
116
|
+
output: process.stdout
|
|
117
|
+
});
|
|
118
|
+
const question = (prompt) => {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
rl.question(prompt, (answer) => {
|
|
121
|
+
resolve(answer.trim());
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
console.log();
|
|
126
|
+
console.log(" @rlabs-inc/create-tui");
|
|
127
|
+
console.log(" Create TUI Framework applications");
|
|
128
|
+
console.log();
|
|
129
|
+
let projectName = existingName;
|
|
130
|
+
if (!projectName) {
|
|
131
|
+
projectName = await question(" Project name: ");
|
|
132
|
+
if (!projectName) {
|
|
133
|
+
projectName = "my-tui-app";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
let template = existingTemplate;
|
|
137
|
+
if (!template) {
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(" Available templates:");
|
|
140
|
+
console.log(" 1. minimal - Bare bones project structure");
|
|
141
|
+
console.log(" 2. counter - Classic reactive counter example");
|
|
142
|
+
console.log(" 3. dashboard - Multi-component layout showcase");
|
|
143
|
+
console.log();
|
|
144
|
+
const choice = await question(" Select template (1-3) [1]: ");
|
|
145
|
+
const index = parseInt(choice, 10) - 1;
|
|
146
|
+
if (index >= 0 && index < TEMPLATES.length) {
|
|
147
|
+
template = TEMPLATES[index];
|
|
148
|
+
} else {
|
|
149
|
+
template = "minimal";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
rl.close();
|
|
153
|
+
return {
|
|
154
|
+
projectName,
|
|
155
|
+
template,
|
|
156
|
+
skipInstall: false
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/index.ts
|
|
161
|
+
var TEMPLATES2 = ["minimal", "counter", "dashboard"];
|
|
162
|
+
async function main() {
|
|
163
|
+
const args = parseArgs(process.argv.slice(2));
|
|
164
|
+
if (args.help) {
|
|
165
|
+
printHelp();
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
if (args.version) {
|
|
169
|
+
console.log("0.2.0");
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
let options;
|
|
173
|
+
if (!args.projectName || !args.template) {
|
|
174
|
+
options = await runInteractive(args.projectName, args.template);
|
|
175
|
+
} else {
|
|
176
|
+
if (!TEMPLATES2.includes(args.template)) {
|
|
177
|
+
console.error(`
|
|
178
|
+
Error: Unknown template "${args.template}"`);
|
|
179
|
+
console.error(`Available templates: ${TEMPLATES2.join(", ")}
|
|
180
|
+
`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
options = {
|
|
184
|
+
projectName: args.projectName,
|
|
185
|
+
template: args.template,
|
|
186
|
+
skipInstall: args.skipInstall ?? false
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
await scaffold(options);
|
|
190
|
+
}
|
|
191
|
+
function printHelp() {
|
|
192
|
+
console.log(`
|
|
193
|
+
@rlabs-inc/create-tui
|
|
194
|
+
|
|
195
|
+
Create TUI Framework applications with professional project structure.
|
|
196
|
+
|
|
197
|
+
Usage:
|
|
198
|
+
bunx @rlabs-inc/create-tui [project-name] [options]
|
|
199
|
+
|
|
200
|
+
Options:
|
|
201
|
+
-t, --template <name> Template to use (minimal, counter, dashboard)
|
|
202
|
+
--skip-install Skip dependency installation
|
|
203
|
+
-h, --help Show this help message
|
|
204
|
+
-v, --version Show version number
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
bunx @rlabs-inc/create-tui my-app
|
|
208
|
+
bunx @rlabs-inc/create-tui my-app --template counter
|
|
209
|
+
bunx @rlabs-inc/create-tui my-app -t dashboard --skip-install
|
|
210
|
+
|
|
211
|
+
Templates:
|
|
212
|
+
minimal Bare bones project structure
|
|
213
|
+
counter Classic reactive counter example
|
|
214
|
+
dashboard Multi-component layout showcase
|
|
215
|
+
`);
|
|
216
|
+
}
|
|
217
|
+
main().catch((err) => {
|
|
218
|
+
console.error(`
|
|
219
|
+
Error:`, err.message);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
package/package.json
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/create-tui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Create TUI Framework applications
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Create TUI Framework applications with a single command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-tui": "./
|
|
7
|
+
"create-tui": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
|
-
"dev": "bun run src/index.ts"
|
|
14
|
+
"dev": "bun run src/index.ts",
|
|
15
|
+
"build": "bun build src/index.ts --outfile dist/index.js --target bun",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"prepublishOnly": "bun run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@rlabs-inc/tui": "latest",
|
|
21
|
+
"@rlabs-inc/signals": "latest"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/bun": "latest",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
14
26
|
},
|
|
15
27
|
"keywords": [
|
|
16
28
|
"tui",
|
|
@@ -23,18 +35,11 @@
|
|
|
23
35
|
"typescript",
|
|
24
36
|
"reactive"
|
|
25
37
|
],
|
|
26
|
-
"author": "
|
|
38
|
+
"author": "RLabs Inc",
|
|
27
39
|
"license": "MIT",
|
|
28
40
|
"repository": {
|
|
29
41
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/rlabs-inc/tui
|
|
31
|
-
"directory": "packages/tui-cli"
|
|
32
|
-
},
|
|
33
|
-
"homepage": "https://github.com/rlabs-inc/tui",
|
|
34
|
-
"engines": {
|
|
35
|
-
"node": ">=18"
|
|
42
|
+
"url": "https://github.com/rlabs-inc/create-tui"
|
|
36
43
|
},
|
|
37
|
-
"
|
|
38
|
-
"access": "public"
|
|
39
|
-
}
|
|
44
|
+
"homepage": "https://github.com/rlabs-inc/tui"
|
|
40
45
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A reactive counter showcase demonstrating TUI Framework's fine-grained reactivity.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Multiple independent counters with different variants
|
|
8
|
+
- Theme switching between 12+ built-in themes
|
|
9
|
+
- Keyboard navigation and focus management
|
|
10
|
+
- Reactive derived state (totals, averages)
|
|
11
|
+
- Component composition patterns
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install dependencies
|
|
17
|
+
bun install
|
|
18
|
+
|
|
19
|
+
# Run the application
|
|
20
|
+
bun run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Controls
|
|
24
|
+
|
|
25
|
+
| Key | Action |
|
|
26
|
+
|-----|--------|
|
|
27
|
+
| `Tab` | Focus next counter |
|
|
28
|
+
| `Shift+Tab` | Focus previous counter |
|
|
29
|
+
| `+` / `=` | Increment focused counter |
|
|
30
|
+
| `-` | Decrement focused counter |
|
|
31
|
+
| `r` | Reset focused counter |
|
|
32
|
+
| `R` | Reset all counters |
|
|
33
|
+
| `t` | Cycle through themes |
|
|
34
|
+
| `q` / `Escape` | Quit |
|
|
35
|
+
|
|
36
|
+
## Project Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
{{PROJECT_NAME}}/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── main.ts # Entry point
|
|
42
|
+
│ ├── App.ts # Root component with layout
|
|
43
|
+
│ ├── state/
|
|
44
|
+
│ │ └── counters.ts # Counter state management
|
|
45
|
+
│ └── components/
|
|
46
|
+
│ ├── Counter.ts # Individual counter component
|
|
47
|
+
│ ├── CounterPanel.ts # Panel with multiple counters
|
|
48
|
+
│ ├── StatsPanel.ts # Statistics display
|
|
49
|
+
│ ├── Header.ts # App header
|
|
50
|
+
│ └── KeyBindings.ts # Help panel
|
|
51
|
+
├── package.json
|
|
52
|
+
└── README.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Key Concepts Demonstrated
|
|
56
|
+
|
|
57
|
+
### Signals & Derived State
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const counter = signal(0)
|
|
61
|
+
const doubled = derived(() => counter.value * 2)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Theme Variants
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
box({
|
|
68
|
+
variant: 'primary', // Uses theme's primary color
|
|
69
|
+
children: () => { ... }
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Focus Management
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { focusManager } from '@rlabs-inc/tui'
|
|
77
|
+
|
|
78
|
+
// Register focusable component
|
|
79
|
+
input({ ... }) // Auto-registers
|
|
80
|
+
|
|
81
|
+
// Navigate focus
|
|
82
|
+
keyboard.onKey('Tab', () => focusManager.next())
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Learn More
|
|
86
|
+
|
|
87
|
+
- [TUI Framework](https://github.com/rlabs-inc/tui)
|
|
88
|
+
- [Signals Library](https://github.com/rlabs-inc/signals)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bun run src/main.ts",
|
|
7
|
+
"build": "bun build src/main.ts --outfile dist/main.js --target bun",
|
|
8
|
+
"typecheck": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@rlabs-inc/tui": "latest",
|
|
12
|
+
"@rlabs-inc/signals": "latest"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "latest",
|
|
16
|
+
"typescript": "^5.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root Application Component
|
|
3
|
+
*
|
|
4
|
+
* Layout structure:
|
|
5
|
+
* - Header (app title + theme indicator)
|
|
6
|
+
* - Main area:
|
|
7
|
+
* - Left: Counter panel with focusable counters
|
|
8
|
+
* - Right: Stats panel + scrollable history
|
|
9
|
+
* - Footer (key bindings)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { box, text, t, BorderStyle } from '@rlabs-inc/tui'
|
|
13
|
+
import { Header } from './components/Header'
|
|
14
|
+
import { CounterPanel } from './components/CounterPanel'
|
|
15
|
+
import { StatsPanel } from './components/StatsPanel'
|
|
16
|
+
import { HistoryPanel } from './components/HistoryPanel'
|
|
17
|
+
import { KeyBindings } from './components/KeyBindings'
|
|
18
|
+
|
|
19
|
+
export function App() {
|
|
20
|
+
box({
|
|
21
|
+
width: '100%',
|
|
22
|
+
height: '100%',
|
|
23
|
+
flexDirection: 'column',
|
|
24
|
+
children: () => {
|
|
25
|
+
// Header
|
|
26
|
+
Header()
|
|
27
|
+
|
|
28
|
+
// Main content area
|
|
29
|
+
box({
|
|
30
|
+
grow: 1,
|
|
31
|
+
flexDirection: 'row',
|
|
32
|
+
padding: 1,
|
|
33
|
+
gap: 1,
|
|
34
|
+
children: () => {
|
|
35
|
+
// Left panel: Counters
|
|
36
|
+
box({
|
|
37
|
+
width: '50%',
|
|
38
|
+
flexDirection: 'column',
|
|
39
|
+
gap: 1,
|
|
40
|
+
children: () => {
|
|
41
|
+
CounterPanel()
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Right panel: Stats + History
|
|
46
|
+
box({
|
|
47
|
+
grow: 1,
|
|
48
|
+
flexDirection: 'column',
|
|
49
|
+
gap: 1,
|
|
50
|
+
children: () => {
|
|
51
|
+
// Statistics
|
|
52
|
+
StatsPanel()
|
|
53
|
+
|
|
54
|
+
// History with auto-scroll
|
|
55
|
+
HistoryPanel()
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Footer: Key bindings
|
|
62
|
+
KeyBindings()
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counter Component
|
|
3
|
+
*
|
|
4
|
+
* Individual counter with focus support and variant styling.
|
|
5
|
+
* Demonstrates:
|
|
6
|
+
* - Focus management integration
|
|
7
|
+
* - Variant-based theming
|
|
8
|
+
* - Reactive value display
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { derived } from '@rlabs-inc/signals'
|
|
12
|
+
import { box, text, t, BorderStyle, Attr, getVariantStyle } from '@rlabs-inc/tui'
|
|
13
|
+
import type { Counter as CounterState } from '../state/counters'
|
|
14
|
+
|
|
15
|
+
interface CounterProps {
|
|
16
|
+
counter: CounterState
|
|
17
|
+
focused: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Counter({ counter, focused }: CounterProps) {
|
|
21
|
+
// Get variant colors
|
|
22
|
+
const variantStyle = getVariantStyle(counter.variant)
|
|
23
|
+
|
|
24
|
+
box({
|
|
25
|
+
border: focused ? BorderStyle.DOUBLE : BorderStyle.SINGLE,
|
|
26
|
+
borderColor: focused ? t.primary : t.border,
|
|
27
|
+
padding: 1,
|
|
28
|
+
bg: focused ? t.surface : undefined,
|
|
29
|
+
children: () => {
|
|
30
|
+
// Counter name with variant indicator
|
|
31
|
+
box({
|
|
32
|
+
flexDirection: 'row',
|
|
33
|
+
justifyContent: 'space-between',
|
|
34
|
+
children: () => {
|
|
35
|
+
text({
|
|
36
|
+
content: counter.name,
|
|
37
|
+
fg: focused ? t.primary : t.text,
|
|
38
|
+
attrs: focused ? Attr.BOLD : 0,
|
|
39
|
+
})
|
|
40
|
+
box({
|
|
41
|
+
width: 2,
|
|
42
|
+
height: 1,
|
|
43
|
+
bg: variantStyle.bg,
|
|
44
|
+
})
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Value display - large and centered
|
|
49
|
+
box({
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
padding: 1,
|
|
52
|
+
children: () => {
|
|
53
|
+
text({
|
|
54
|
+
content: derived(() => {
|
|
55
|
+
const val = counter.value.value
|
|
56
|
+
return val >= 0 ? `+${val}` : `${val}`
|
|
57
|
+
}),
|
|
58
|
+
fg: derived(() =>
|
|
59
|
+
counter.value.value >= 0 ? t.success.value : t.error.value
|
|
60
|
+
),
|
|
61
|
+
attrs: Attr.BOLD,
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Focus indicator
|
|
67
|
+
box({
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
children: () => {
|
|
70
|
+
text({
|
|
71
|
+
content: focused ? '[+/-] to change' : '',
|
|
72
|
+
fg: t.textDim,
|
|
73
|
+
})
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|