@dealdeploy/skl 0.1.7 → 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/.agents/skills/opentui/SKILL.md +198 -0
- package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.agents/skills/opentui/references/components/code-diff.md +496 -0
- package/.agents/skills/opentui/references/components/containers.md +412 -0
- package/.agents/skills/opentui/references/components/inputs.md +531 -0
- package/.agents/skills/opentui/references/components/text-display.md +384 -0
- package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.agents/skills/opentui/references/core/api.md +506 -0
- package/.agents/skills/opentui/references/core/configuration.md +166 -0
- package/.agents/skills/opentui/references/core/gotchas.md +393 -0
- package/.agents/skills/opentui/references/core/patterns.md +448 -0
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.agents/skills/opentui/references/layout/patterns.md +444 -0
- package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.agents/skills/opentui/references/react/api.md +435 -0
- package/.agents/skills/opentui/references/react/configuration.md +301 -0
- package/.agents/skills/opentui/references/react/gotchas.md +443 -0
- package/.agents/skills/opentui/references/react/patterns.md +501 -0
- package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.agents/skills/opentui/references/solid/api.md +543 -0
- package/.agents/skills/opentui/references/solid/configuration.md +315 -0
- package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
- package/.agents/skills/opentui/references/solid/patterns.md +558 -0
- package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/.claude/skills/opentui/SKILL.md +198 -0
- package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.claude/skills/opentui/references/components/code-diff.md +496 -0
- package/.claude/skills/opentui/references/components/containers.md +412 -0
- package/.claude/skills/opentui/references/components/inputs.md +531 -0
- package/.claude/skills/opentui/references/components/text-display.md +384 -0
- package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.claude/skills/opentui/references/core/api.md +506 -0
- package/.claude/skills/opentui/references/core/configuration.md +166 -0
- package/.claude/skills/opentui/references/core/gotchas.md +393 -0
- package/.claude/skills/opentui/references/core/patterns.md +448 -0
- package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.claude/skills/opentui/references/layout/patterns.md +444 -0
- package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.claude/skills/opentui/references/react/api.md +435 -0
- package/.claude/skills/opentui/references/react/configuration.md +301 -0
- package/.claude/skills/opentui/references/react/gotchas.md +443 -0
- package/.claude/skills/opentui/references/react/patterns.md +501 -0
- package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.claude/skills/opentui/references/solid/api.md +543 -0
- package/.claude/skills/opentui/references/solid/configuration.md +315 -0
- package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
- package/.claude/skills/opentui/references/solid/patterns.md +558 -0
- package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/index.ts +169 -38
- package/package.json +1 -1
- package/update.ts +87 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# React API Reference
|
|
2
|
+
|
|
3
|
+
## Rendering
|
|
4
|
+
|
|
5
|
+
### createRoot(renderer)
|
|
6
|
+
|
|
7
|
+
Creates a React root for rendering.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { createCliRenderer } from "@opentui/core"
|
|
11
|
+
import { createRoot } from "@opentui/react"
|
|
12
|
+
|
|
13
|
+
const renderer = await createCliRenderer({
|
|
14
|
+
exitOnCtrlC: false, // Handle Ctrl+C yourself
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const root = createRoot(renderer)
|
|
18
|
+
root.render(<App />)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Hooks
|
|
22
|
+
|
|
23
|
+
### useRenderer()
|
|
24
|
+
|
|
25
|
+
Access the OpenTUI renderer instance.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { useRenderer } from "@opentui/react"
|
|
29
|
+
import { useEffect } from "react"
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
const renderer = useRenderer()
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
// Access renderer properties
|
|
36
|
+
console.log(`Terminal: ${renderer.width}x${renderer.height}`)
|
|
37
|
+
|
|
38
|
+
// Show debug console
|
|
39
|
+
renderer.console.show()
|
|
40
|
+
|
|
41
|
+
// Access theme mode (dark/light based on terminal settings)
|
|
42
|
+
console.log(`Theme: ${renderer.themeMode}`) // "dark" | "light" | null
|
|
43
|
+
}, [renderer])
|
|
44
|
+
|
|
45
|
+
return <text>Hello</text>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Listen for theme mode changes
|
|
49
|
+
function ThemedApp() {
|
|
50
|
+
const renderer = useRenderer()
|
|
51
|
+
const [theme, setTheme] = useState(renderer.themeMode ?? "dark")
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const handler = (mode: "dark" | "light") => setTheme(mode)
|
|
55
|
+
renderer.on("theme_mode", handler)
|
|
56
|
+
return () => renderer.off("theme_mode", handler)
|
|
57
|
+
}, [renderer])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<box backgroundColor={theme === "dark" ? "#1a1a2e" : "#ffffff"}>
|
|
61
|
+
<text fg={theme === "dark" ? "#fff" : "#000"}>
|
|
62
|
+
Current theme: {theme}
|
|
63
|
+
</text>
|
|
64
|
+
</box>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### useKeyboard(handler, options?)
|
|
70
|
+
|
|
71
|
+
Handle keyboard events.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useKeyboard, useRenderer } from "@opentui/react"
|
|
75
|
+
|
|
76
|
+
function App() {
|
|
77
|
+
const renderer = useRenderer()
|
|
78
|
+
|
|
79
|
+
useKeyboard((key) => {
|
|
80
|
+
if (key.name === "escape") {
|
|
81
|
+
renderer.destroy() // Never use process.exit() directly!
|
|
82
|
+
}
|
|
83
|
+
if (key.ctrl && key.name === "s") {
|
|
84
|
+
saveDocument()
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return <text>Press ESC to exit</text>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// With release events
|
|
92
|
+
function GameControls() {
|
|
93
|
+
const [pressed, setPressed] = useState(new Set<string>())
|
|
94
|
+
|
|
95
|
+
useKeyboard(
|
|
96
|
+
(event) => {
|
|
97
|
+
setPressed(keys => {
|
|
98
|
+
const newKeys = new Set(keys)
|
|
99
|
+
if (event.eventType === "release") {
|
|
100
|
+
newKeys.delete(event.name)
|
|
101
|
+
} else {
|
|
102
|
+
newKeys.add(event.name)
|
|
103
|
+
}
|
|
104
|
+
return newKeys
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
{ release: true } // Include release events
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return <text>Pressed: {Array.from(pressed).join(", ")}</text>
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Options:**
|
|
115
|
+
- `release?: boolean` - Include key release events (default: false)
|
|
116
|
+
|
|
117
|
+
**KeyEvent properties:**
|
|
118
|
+
- `name: string` - Key name ("a", "escape", "f1", etc.)
|
|
119
|
+
- `sequence: string` - Raw escape sequence
|
|
120
|
+
- `ctrl: boolean` - Ctrl modifier
|
|
121
|
+
- `shift: boolean` - Shift modifier
|
|
122
|
+
- `meta: boolean` - Alt modifier
|
|
123
|
+
- `option: boolean` - Option modifier (macOS)
|
|
124
|
+
- `eventType: "press" | "release" | "repeat"`
|
|
125
|
+
- `repeated: boolean` - Key is being held
|
|
126
|
+
|
|
127
|
+
### useOnResize(callback)
|
|
128
|
+
|
|
129
|
+
Handle terminal resize events.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { useOnResize } from "@opentui/react"
|
|
133
|
+
|
|
134
|
+
function App() {
|
|
135
|
+
useOnResize((width, height) => {
|
|
136
|
+
console.log(`Resized to ${width}x${height}`)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
return <text>Resize the terminal</text>
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### useTerminalDimensions()
|
|
144
|
+
|
|
145
|
+
Get reactive terminal dimensions.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { useTerminalDimensions } from "@opentui/react"
|
|
149
|
+
|
|
150
|
+
function ResponsiveLayout() {
|
|
151
|
+
const { width, height } = useTerminalDimensions()
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<box flexDirection={width > 80 ? "row" : "column"}>
|
|
155
|
+
<box flexGrow={1}>
|
|
156
|
+
<text>Width: {width}</text>
|
|
157
|
+
</box>
|
|
158
|
+
<box flexGrow={1}>
|
|
159
|
+
<text>Height: {height}</text>
|
|
160
|
+
</box>
|
|
161
|
+
</box>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### useTimeline(options?)
|
|
167
|
+
|
|
168
|
+
Create animations with the timeline system.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useTimeline } from "@opentui/react"
|
|
172
|
+
import { useEffect, useState } from "react"
|
|
173
|
+
|
|
174
|
+
function AnimatedBox() {
|
|
175
|
+
const [width, setWidth] = useState(0)
|
|
176
|
+
|
|
177
|
+
const timeline = useTimeline({
|
|
178
|
+
duration: 2000,
|
|
179
|
+
loop: false,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
timeline.add(
|
|
184
|
+
{ width: 0 },
|
|
185
|
+
{
|
|
186
|
+
width: 50,
|
|
187
|
+
duration: 2000,
|
|
188
|
+
ease: "easeOutQuad",
|
|
189
|
+
onUpdate: (anim) => {
|
|
190
|
+
setWidth(Math.round(anim.targets[0].width))
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
}, [timeline])
|
|
195
|
+
|
|
196
|
+
return <box style={{ width, height: 3, backgroundColor: "#6a5acd" }} />
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Options:**
|
|
201
|
+
- `duration?: number` - Default duration (ms)
|
|
202
|
+
- `loop?: boolean` - Loop the timeline
|
|
203
|
+
- `autoplay?: boolean` - Auto-start (default: true)
|
|
204
|
+
- `onComplete?: () => void` - Completion callback
|
|
205
|
+
- `onPause?: () => void` - Pause callback
|
|
206
|
+
|
|
207
|
+
**Timeline methods:**
|
|
208
|
+
- `add(target, properties, startTime?)` - Add animation
|
|
209
|
+
- `play()` - Start playback
|
|
210
|
+
- `pause()` - Pause playback
|
|
211
|
+
- `restart()` - Restart from beginning
|
|
212
|
+
|
|
213
|
+
## Components
|
|
214
|
+
|
|
215
|
+
### Text Component
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<text
|
|
219
|
+
content="Hello" // Or use children
|
|
220
|
+
fg="#FFFFFF" // Foreground color
|
|
221
|
+
bg="#000000" // Background color
|
|
222
|
+
selectable={true} // Allow text selection
|
|
223
|
+
>
|
|
224
|
+
{/* Use nested modifier tags for styling */}
|
|
225
|
+
<span fg="red">Red</span>
|
|
226
|
+
<strong>Bold</strong>
|
|
227
|
+
<em>Italic</em>
|
|
228
|
+
<u>Underline</u>
|
|
229
|
+
<br />
|
|
230
|
+
<a href="https://...">Link</a>
|
|
231
|
+
</text>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
> **Note**: Do NOT use `bold`, `italic`, `underline` as props on `<text>`. Use nested modifier tags like `<strong>`, `<em>`, `<u>` instead.
|
|
235
|
+
|
|
236
|
+
### Box Component
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
<box
|
|
240
|
+
// Borders
|
|
241
|
+
border // Enable border
|
|
242
|
+
borderStyle="single" // single | double | rounded | bold
|
|
243
|
+
borderColor="#FFFFFF"
|
|
244
|
+
title="Title"
|
|
245
|
+
titleAlignment="center" // left | center | right
|
|
246
|
+
|
|
247
|
+
// Colors
|
|
248
|
+
backgroundColor="#1a1a2e"
|
|
249
|
+
|
|
250
|
+
// Layout (see layout/REFERENCE.md)
|
|
251
|
+
flexDirection="row"
|
|
252
|
+
justifyContent="center"
|
|
253
|
+
alignItems="center"
|
|
254
|
+
gap={2}
|
|
255
|
+
|
|
256
|
+
// Spacing
|
|
257
|
+
padding={2}
|
|
258
|
+
paddingTop={1}
|
|
259
|
+
paddingX={2} // Horizontal (left + right)
|
|
260
|
+
paddingY={1} // Vertical (top + bottom)
|
|
261
|
+
margin={1}
|
|
262
|
+
marginX={2} // Horizontal (left + right)
|
|
263
|
+
marginY={1} // Vertical (top + bottom)
|
|
264
|
+
|
|
265
|
+
// Dimensions
|
|
266
|
+
width={40}
|
|
267
|
+
height={10}
|
|
268
|
+
flexGrow={1}
|
|
269
|
+
|
|
270
|
+
// Focus
|
|
271
|
+
focusable // Allow box to receive focus
|
|
272
|
+
focused={isFocused} // Controlled focus state
|
|
273
|
+
|
|
274
|
+
// Events
|
|
275
|
+
onMouseDown={(e) => {}}
|
|
276
|
+
onMouseUp={(e) => {}}
|
|
277
|
+
onMouseMove={(e) => {}}
|
|
278
|
+
>
|
|
279
|
+
{children}
|
|
280
|
+
</box>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Scrollbox Component
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
<scrollbox
|
|
287
|
+
focused // Enable keyboard scrolling
|
|
288
|
+
style={{
|
|
289
|
+
rootOptions: { backgroundColor: "#24283b" },
|
|
290
|
+
wrapperOptions: { backgroundColor: "#1f2335" },
|
|
291
|
+
viewportOptions: { backgroundColor: "#1a1b26" },
|
|
292
|
+
contentOptions: { backgroundColor: "#16161e" },
|
|
293
|
+
scrollbarOptions: {
|
|
294
|
+
showArrows: true,
|
|
295
|
+
trackOptions: {
|
|
296
|
+
foregroundColor: "#7aa2f7",
|
|
297
|
+
backgroundColor: "#414868",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
{/* Scrollable content */}
|
|
303
|
+
{items.map((item, i) => (
|
|
304
|
+
<box key={i}>
|
|
305
|
+
<text>{item}</text>
|
|
306
|
+
</box>
|
|
307
|
+
))}
|
|
308
|
+
</scrollbox>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Input Component
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
<input
|
|
315
|
+
value={value}
|
|
316
|
+
onChange={(newValue) => setValue(newValue)}
|
|
317
|
+
placeholder="Enter text..."
|
|
318
|
+
focused // Start focused
|
|
319
|
+
width={30}
|
|
320
|
+
backgroundColor="#1a1a1a"
|
|
321
|
+
textColor="#FFFFFF"
|
|
322
|
+
cursorColor="#00FF00"
|
|
323
|
+
focusedBackgroundColor="#2a2a2a"
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Textarea Component
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
<textarea
|
|
331
|
+
value={text}
|
|
332
|
+
onChange={(newValue) => setText(newValue)}
|
|
333
|
+
placeholder="Enter multiple lines..."
|
|
334
|
+
focused
|
|
335
|
+
width={40}
|
|
336
|
+
height={10}
|
|
337
|
+
showLineNumbers
|
|
338
|
+
wrapText
|
|
339
|
+
/>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Select Component
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
<select
|
|
346
|
+
options={[
|
|
347
|
+
{ name: "Option 1", description: "First option", value: "1" },
|
|
348
|
+
{ name: "Option 2", description: "Second option", value: "2" },
|
|
349
|
+
]}
|
|
350
|
+
onChange={(index, option) => setSelected(option)}
|
|
351
|
+
selectedIndex={0}
|
|
352
|
+
focused
|
|
353
|
+
showScrollIndicator
|
|
354
|
+
height={8}
|
|
355
|
+
/>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Tab Select Component
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
<tab-select
|
|
362
|
+
options={[
|
|
363
|
+
{ name: "Home", description: "Dashboard" },
|
|
364
|
+
{ name: "Settings", description: "Configuration" },
|
|
365
|
+
]}
|
|
366
|
+
onChange={(index, option) => setTab(option)}
|
|
367
|
+
tabWidth={20}
|
|
368
|
+
focused
|
|
369
|
+
/>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### ASCII Font Component
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
<ascii-font
|
|
376
|
+
text="TITLE"
|
|
377
|
+
font="tiny" // tiny | block | slick | shade
|
|
378
|
+
color="#FFFFFF"
|
|
379
|
+
/>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Code Component
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
<code
|
|
386
|
+
code={sourceCode}
|
|
387
|
+
language="typescript"
|
|
388
|
+
showLineNumbers
|
|
389
|
+
highlightLines={[1, 5, 10]}
|
|
390
|
+
/>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Line Number Component
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
<line-number
|
|
397
|
+
code={sourceCode}
|
|
398
|
+
language="typescript"
|
|
399
|
+
startLine={1}
|
|
400
|
+
highlightedLines={[5]}
|
|
401
|
+
diagnostics={[
|
|
402
|
+
{ line: 3, severity: "error", message: "Syntax error" }
|
|
403
|
+
]}
|
|
404
|
+
/>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Diff Component
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
<diff
|
|
411
|
+
oldCode={originalCode}
|
|
412
|
+
newCode={modifiedCode}
|
|
413
|
+
language="typescript"
|
|
414
|
+
mode="unified" // unified | split
|
|
415
|
+
showLineNumbers
|
|
416
|
+
/>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Type Exports
|
|
420
|
+
|
|
421
|
+
```tsx
|
|
422
|
+
import type {
|
|
423
|
+
// Component props
|
|
424
|
+
TextProps,
|
|
425
|
+
BoxProps,
|
|
426
|
+
InputProps,
|
|
427
|
+
SelectProps,
|
|
428
|
+
|
|
429
|
+
// Hook types
|
|
430
|
+
KeyEvent,
|
|
431
|
+
|
|
432
|
+
// From core
|
|
433
|
+
CliRenderer,
|
|
434
|
+
} from "@opentui/react"
|
|
435
|
+
```
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# React Configuration
|
|
2
|
+
|
|
3
|
+
## Project Setup
|
|
4
|
+
|
|
5
|
+
### Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bunx create-tui@latest -t react my-app
|
|
9
|
+
cd my-app && bun install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The CLI creates the `my-app` directory for you - it must **not already exist**.
|
|
13
|
+
|
|
14
|
+
Options: `--no-git` (skip git init), `--no-install` (skip bun install)
|
|
15
|
+
|
|
16
|
+
### Manual Setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mkdir my-tui && cd my-tui
|
|
20
|
+
bun init
|
|
21
|
+
bun install @opentui/react @opentui/core react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## TypeScript Configuration
|
|
25
|
+
|
|
26
|
+
### tsconfig.json
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"compilerOptions": {
|
|
31
|
+
"lib": ["ESNext", "DOM"],
|
|
32
|
+
"target": "ESNext",
|
|
33
|
+
"module": "ESNext",
|
|
34
|
+
"moduleResolution": "bundler",
|
|
35
|
+
|
|
36
|
+
"jsx": "react-jsx",
|
|
37
|
+
"jsxImportSource": "@opentui/react",
|
|
38
|
+
|
|
39
|
+
"strict": true,
|
|
40
|
+
"skipLibCheck": true,
|
|
41
|
+
"noEmit": true,
|
|
42
|
+
"types": ["bun-types"]
|
|
43
|
+
},
|
|
44
|
+
"include": ["src/**/*"]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Critical settings:**
|
|
49
|
+
- `jsx: "react-jsx"` - Use the new JSX transform
|
|
50
|
+
- `jsxImportSource: "@opentui/react"` - Import JSX runtime from OpenTUI
|
|
51
|
+
|
|
52
|
+
### Why DOM lib?
|
|
53
|
+
|
|
54
|
+
The `DOM` lib is needed for React types. OpenTUI's JSX types extend React's.
|
|
55
|
+
|
|
56
|
+
## Package Configuration
|
|
57
|
+
|
|
58
|
+
### package.json
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"name": "my-tui-app",
|
|
63
|
+
"type": "module",
|
|
64
|
+
"scripts": {
|
|
65
|
+
"start": "bun run src/index.tsx",
|
|
66
|
+
"dev": "bun --watch run src/index.tsx",
|
|
67
|
+
"test": "bun test",
|
|
68
|
+
"build": "bun build src/index.tsx --outdir=dist --target=bun"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@opentui/core": "latest",
|
|
72
|
+
"@opentui/react": "latest",
|
|
73
|
+
"react": ">=19.0.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@types/bun": "latest",
|
|
77
|
+
"@types/react": ">=19.0.0",
|
|
78
|
+
"typescript": "latest"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Project Structure
|
|
84
|
+
|
|
85
|
+
Recommended structure:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
my-tui-app/
|
|
89
|
+
├── src/
|
|
90
|
+
│ ├── components/
|
|
91
|
+
│ │ ├── Header.tsx
|
|
92
|
+
│ │ ├── Sidebar.tsx
|
|
93
|
+
│ │ └── MainContent.tsx
|
|
94
|
+
│ ├── hooks/
|
|
95
|
+
│ │ └── useAppState.ts
|
|
96
|
+
│ ├── App.tsx
|
|
97
|
+
│ └── index.tsx
|
|
98
|
+
├── package.json
|
|
99
|
+
└── tsconfig.json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Entry Point (src/index.tsx)
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { createCliRenderer } from "@opentui/core"
|
|
106
|
+
import { createRoot } from "@opentui/react"
|
|
107
|
+
import { App } from "./App"
|
|
108
|
+
|
|
109
|
+
const renderer = await createCliRenderer({
|
|
110
|
+
exitOnCtrlC: true,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
createRoot(renderer).render(<App />)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### App Component (src/App.tsx)
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { Header } from "./components/Header"
|
|
120
|
+
import { Sidebar } from "./components/Sidebar"
|
|
121
|
+
import { MainContent } from "./components/MainContent"
|
|
122
|
+
|
|
123
|
+
export function App() {
|
|
124
|
+
return (
|
|
125
|
+
<box flexDirection="column" width="100%" height="100%">
|
|
126
|
+
<Header />
|
|
127
|
+
<box flexDirection="row" flexGrow={1}>
|
|
128
|
+
<Sidebar />
|
|
129
|
+
<MainContent />
|
|
130
|
+
</box>
|
|
131
|
+
</box>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Renderer Configuration
|
|
137
|
+
|
|
138
|
+
### createCliRenderer Options
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { createCliRenderer, ConsolePosition } from "@opentui/core"
|
|
142
|
+
|
|
143
|
+
const renderer = await createCliRenderer({
|
|
144
|
+
// Rendering
|
|
145
|
+
targetFPS: 60,
|
|
146
|
+
|
|
147
|
+
// Behavior
|
|
148
|
+
exitOnCtrlC: true, // Set false to handle Ctrl+C yourself
|
|
149
|
+
autoFocus: true, // Auto-focus elements on click (default: true)
|
|
150
|
+
useMouse: true, // Enable mouse support (default: true)
|
|
151
|
+
|
|
152
|
+
// Debug console
|
|
153
|
+
consoleOptions: {
|
|
154
|
+
position: ConsolePosition.BOTTOM,
|
|
155
|
+
sizePercent: 30,
|
|
156
|
+
startInDebugMode: false,
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Cleanup
|
|
160
|
+
onDestroy: () => {
|
|
161
|
+
// Cleanup code
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Building for Distribution
|
|
167
|
+
|
|
168
|
+
### Bundling with Bun
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// build.ts
|
|
172
|
+
await Bun.build({
|
|
173
|
+
entrypoints: ["./src/index.tsx"],
|
|
174
|
+
outdir: "./dist",
|
|
175
|
+
target: "bun",
|
|
176
|
+
minify: true,
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Run: `bun run build.ts`
|
|
181
|
+
|
|
182
|
+
### Creating Executables
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// build.ts
|
|
186
|
+
await Bun.build({
|
|
187
|
+
entrypoints: ["./src/index.tsx"],
|
|
188
|
+
outdir: "./dist",
|
|
189
|
+
target: "bun",
|
|
190
|
+
compile: {
|
|
191
|
+
target: "bun-darwin-arm64", // or bun-linux-x64, etc.
|
|
192
|
+
outfile: "my-app",
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Environment Variables
|
|
198
|
+
|
|
199
|
+
Create `.env` for development:
|
|
200
|
+
|
|
201
|
+
```env
|
|
202
|
+
# Debug settings
|
|
203
|
+
OTUI_SHOW_STATS=false
|
|
204
|
+
SHOW_CONSOLE=false
|
|
205
|
+
|
|
206
|
+
# App settings
|
|
207
|
+
API_URL=https://api.example.com
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Bun auto-loads `.env` files. Access via `process.env`:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
const apiUrl = process.env.API_URL
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## React DevTools
|
|
217
|
+
|
|
218
|
+
OpenTUI React supports React DevTools for debugging.
|
|
219
|
+
|
|
220
|
+
### Setup
|
|
221
|
+
|
|
222
|
+
1. Install DevTools as a dev dependency (must use version 7):
|
|
223
|
+
```bash
|
|
224
|
+
bun add react-devtools-core@7 -d
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
2. Run DevTools standalone app:
|
|
228
|
+
```bash
|
|
229
|
+
npx react-devtools@7
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
3. Start your app with `DEV=true` environment variable:
|
|
233
|
+
```bash
|
|
234
|
+
DEV=true bun run src/index.tsx
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Important**: Auto-connect to DevTools ONLY happens when `DEV=true` is set. Without this environment variable, the DevTools connection code is not loaded.
|
|
238
|
+
|
|
239
|
+
### How It Works
|
|
240
|
+
|
|
241
|
+
OpenTUI checks for `process.env["DEV"] === "true"` at startup. When true, it dynamically imports `react-devtools-core` and connects to the standalone DevTools app.
|
|
242
|
+
|
|
243
|
+
## Testing Configuration
|
|
244
|
+
|
|
245
|
+
### Test Setup
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// src/test-utils.tsx
|
|
249
|
+
import { createTestRenderer } from "@opentui/core/testing"
|
|
250
|
+
import { createRoot } from "@opentui/react"
|
|
251
|
+
|
|
252
|
+
export async function renderForTest(
|
|
253
|
+
element: React.ReactElement,
|
|
254
|
+
options = { width: 80, height: 24 }
|
|
255
|
+
) {
|
|
256
|
+
const testSetup = await createTestRenderer(options)
|
|
257
|
+
createRoot(testSetup.renderer).render(element)
|
|
258
|
+
return testSetup
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Test Example
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// src/components/Counter.test.tsx
|
|
266
|
+
import { test, expect } from "bun:test"
|
|
267
|
+
import { renderForTest } from "../test-utils"
|
|
268
|
+
import { Counter } from "./Counter"
|
|
269
|
+
|
|
270
|
+
test("Counter renders initial value", async () => {
|
|
271
|
+
const { snapshot } = await renderForTest(<Counter initialValue={5} />)
|
|
272
|
+
expect(snapshot()).toContain("Count: 5")
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Common Issues
|
|
277
|
+
|
|
278
|
+
### JSX Types Not Working
|
|
279
|
+
|
|
280
|
+
Ensure `jsxImportSource` is set:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"compilerOptions": {
|
|
285
|
+
"jsx": "react-jsx",
|
|
286
|
+
"jsxImportSource": "@opentui/react"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### React Version Mismatch
|
|
292
|
+
|
|
293
|
+
Ensure React 19+:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
bun install react@19 @types/react@19
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Module Resolution Errors
|
|
300
|
+
|
|
301
|
+
Use `moduleResolution: "bundler"` for Bun compatibility.
|