@opentui/react 0.1.19-snapshot5-5ed8b651 → 0.1.20
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 +106 -52
- package/index.js +2 -4
- package/package.json +2 -2
- package/src/reconciler/renderer.js +2 -4
package/README.md
CHANGED
|
@@ -4,6 +4,14 @@ A React renderer for building terminal user interfaces using [OpenTUI core](http
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
Quick start with [bun](https://bun.sh) and [create-tui](https://github.com/msmps/create-tui):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun create tui --template react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Manual installation:
|
|
14
|
+
|
|
7
15
|
```bash
|
|
8
16
|
bun install @opentui/react @opentui/core react
|
|
9
17
|
```
|
|
@@ -14,14 +22,7 @@ bun install @opentui/react @opentui/core react
|
|
|
14
22
|
import { render } from "@opentui/react"
|
|
15
23
|
|
|
16
24
|
function App() {
|
|
17
|
-
return
|
|
18
|
-
<box>
|
|
19
|
-
<text fg="#00FF00">Hello, Terminal!</text>
|
|
20
|
-
<box title="Welcome" padding={2}>
|
|
21
|
-
<text>Welcome to OpenTUI with React!</text>
|
|
22
|
-
</box>
|
|
23
|
-
</box>
|
|
24
|
-
)
|
|
25
|
+
return <text>Hello, world!</text>
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
render(<App />)
|
|
@@ -56,6 +57,7 @@ OpenTUI React provides several built-in components that map to OpenTUI core rend
|
|
|
56
57
|
- **`<box>`** - Container with borders and layout
|
|
57
58
|
- **`<input>`** - Text input field
|
|
58
59
|
- **`<select>`** - Selection dropdown
|
|
60
|
+
- **`<scrollbox>`** - A scrollable box
|
|
59
61
|
- **`<tab-select>`** - Tab-based selection
|
|
60
62
|
- **`<ascii-font>`** - Display ASCII art text with different font styles
|
|
61
63
|
|
|
@@ -65,11 +67,13 @@ Components can be styled using props or the `style` prop:
|
|
|
65
67
|
|
|
66
68
|
```tsx
|
|
67
69
|
// Direct props
|
|
68
|
-
<
|
|
70
|
+
<box backgroundColor="blue" padding={2}>
|
|
71
|
+
<text>Hello, world!</text>
|
|
72
|
+
</box>
|
|
69
73
|
|
|
70
74
|
// Style prop
|
|
71
75
|
<box style={{ backgroundColor: "blue", padding: 2 }}>
|
|
72
|
-
<text>
|
|
76
|
+
<text>Hello, world!</text>
|
|
73
77
|
</box>
|
|
74
78
|
```
|
|
75
79
|
|
|
@@ -102,14 +106,15 @@ Access the OpenTUI renderer instance.
|
|
|
102
106
|
```tsx
|
|
103
107
|
import { useRenderer } from "@opentui/react"
|
|
104
108
|
|
|
105
|
-
function
|
|
109
|
+
function App() {
|
|
106
110
|
const renderer = useRenderer()
|
|
107
111
|
|
|
108
112
|
useEffect(() => {
|
|
109
|
-
renderer.
|
|
113
|
+
renderer.console.show()
|
|
114
|
+
console.log("Hello, from the console!")
|
|
110
115
|
}, [])
|
|
111
116
|
|
|
112
|
-
return <
|
|
117
|
+
return <box />
|
|
113
118
|
}
|
|
114
119
|
```
|
|
115
120
|
|
|
@@ -120,7 +125,7 @@ Handle keyboard events.
|
|
|
120
125
|
```tsx
|
|
121
126
|
import { useKeyboard } from "@opentui/react"
|
|
122
127
|
|
|
123
|
-
function
|
|
128
|
+
function App() {
|
|
124
129
|
useKeyboard((key) => {
|
|
125
130
|
if (key.name === "escape") {
|
|
126
131
|
process.exit(0)
|
|
@@ -139,7 +144,7 @@ Handle terminal resize events.
|
|
|
139
144
|
import { useOnResize, useRenderer } from "@opentui/react"
|
|
140
145
|
import { useEffect } from "react"
|
|
141
146
|
|
|
142
|
-
function
|
|
147
|
+
function App() {
|
|
143
148
|
const renderer = useRenderer()
|
|
144
149
|
|
|
145
150
|
useEffect(() => {
|
|
@@ -161,7 +166,7 @@ Get current terminal dimensions and automatically update when the terminal is re
|
|
|
161
166
|
```tsx
|
|
162
167
|
import { useTerminalDimensions } from "@opentui/react"
|
|
163
168
|
|
|
164
|
-
function
|
|
169
|
+
function App() {
|
|
165
170
|
const { width, height } = useTerminalDimensions()
|
|
166
171
|
|
|
167
172
|
return (
|
|
@@ -188,7 +193,7 @@ Display text with rich formatting.
|
|
|
188
193
|
```tsx
|
|
189
194
|
import { bold, fg, t } from "@opentui/core"
|
|
190
195
|
|
|
191
|
-
function
|
|
196
|
+
function App() {
|
|
192
197
|
return (
|
|
193
198
|
<box>
|
|
194
199
|
{/* Simple text */}
|
|
@@ -209,22 +214,23 @@ function TextExample() {
|
|
|
209
214
|
Container with borders and layout capabilities.
|
|
210
215
|
|
|
211
216
|
```tsx
|
|
212
|
-
function
|
|
217
|
+
function App() {
|
|
213
218
|
return (
|
|
214
219
|
<box flexDirection="column">
|
|
215
220
|
{/* Basic box */}
|
|
216
|
-
<box>
|
|
221
|
+
<box border>
|
|
217
222
|
<text>Simple box</text>
|
|
218
223
|
</box>
|
|
219
224
|
|
|
220
225
|
{/* Box with title and styling */}
|
|
221
|
-
<box title="Settings" borderStyle="double" padding={2} backgroundColor="blue">
|
|
226
|
+
<box title="Settings" border borderStyle="double" padding={2} backgroundColor="blue">
|
|
222
227
|
<text>Box content</text>
|
|
223
228
|
</box>
|
|
224
229
|
|
|
225
230
|
{/* Styled box */}
|
|
226
231
|
<box
|
|
227
232
|
style={{
|
|
233
|
+
border: true,
|
|
228
234
|
width: 40,
|
|
229
235
|
height: 10,
|
|
230
236
|
margin: 1,
|
|
@@ -246,20 +252,16 @@ Text input field with event handling.
|
|
|
246
252
|
```tsx
|
|
247
253
|
import { useState } from "react"
|
|
248
254
|
|
|
249
|
-
function
|
|
255
|
+
function App() {
|
|
250
256
|
const [value, setValue] = useState("")
|
|
251
|
-
const [focused, setFocused] = useState(true)
|
|
252
257
|
|
|
253
258
|
return (
|
|
254
|
-
<box title="Enter your name" style={{ height: 3 }}>
|
|
259
|
+
<box title="Enter your name" style={{ border: true, height: 3 }}>
|
|
255
260
|
<input
|
|
256
261
|
placeholder="Type here..."
|
|
257
|
-
focused
|
|
262
|
+
focused
|
|
258
263
|
onInput={setValue}
|
|
259
264
|
onSubmit={(value) => console.log("Submitted:", value)}
|
|
260
|
-
style={{
|
|
261
|
-
focusedBackgroundColor: "#333333",
|
|
262
|
-
}}
|
|
263
265
|
/>
|
|
264
266
|
</box>
|
|
265
267
|
)
|
|
@@ -274,7 +276,7 @@ Dropdown selection component.
|
|
|
274
276
|
import type { SelectOption } from "@opentui/core"
|
|
275
277
|
import { useState } from "react"
|
|
276
278
|
|
|
277
|
-
function
|
|
279
|
+
function App() {
|
|
278
280
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
279
281
|
|
|
280
282
|
const options: SelectOption[] = [
|
|
@@ -284,7 +286,7 @@ function SelectExample() {
|
|
|
284
286
|
]
|
|
285
287
|
|
|
286
288
|
return (
|
|
287
|
-
<box style={{ height: 24 }}>
|
|
289
|
+
<box style={{ border: true, height: 24 }}>
|
|
288
290
|
<select
|
|
289
291
|
style={{ height: 22 }}
|
|
290
292
|
options={options}
|
|
@@ -299,6 +301,50 @@ function SelectExample() {
|
|
|
299
301
|
}
|
|
300
302
|
```
|
|
301
303
|
|
|
304
|
+
### Scrollbox Component
|
|
305
|
+
|
|
306
|
+
A scrollable box.
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
function App() {
|
|
310
|
+
return (
|
|
311
|
+
<scrollbox
|
|
312
|
+
style={{
|
|
313
|
+
rootOptions: {
|
|
314
|
+
backgroundColor: "#24283b",
|
|
315
|
+
},
|
|
316
|
+
wrapperOptions: {
|
|
317
|
+
backgroundColor: "#1f2335",
|
|
318
|
+
},
|
|
319
|
+
viewportOptions: {
|
|
320
|
+
backgroundColor: "#1a1b26",
|
|
321
|
+
},
|
|
322
|
+
contentOptions: {
|
|
323
|
+
backgroundColor: "#16161e",
|
|
324
|
+
},
|
|
325
|
+
scrollbarOptions: {
|
|
326
|
+
showArrows: true,
|
|
327
|
+
trackOptions: {
|
|
328
|
+
foregroundColor: "#7aa2f7",
|
|
329
|
+
backgroundColor: "#414868",
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
}}
|
|
333
|
+
focused
|
|
334
|
+
>
|
|
335
|
+
{Array.from({ length: 1000 }).map((_, i) => (
|
|
336
|
+
<box
|
|
337
|
+
key={i}
|
|
338
|
+
style={{ width: "100%", padding: 1, marginBottom: 1, backgroundColor: i % 2 === 0 ? "#292e42" : "#2f3449" }}
|
|
339
|
+
>
|
|
340
|
+
<text content={`Box ${i}`} />
|
|
341
|
+
</box>
|
|
342
|
+
))}
|
|
343
|
+
</scrollbox>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
302
348
|
### ASCII Font Component
|
|
303
349
|
|
|
304
350
|
Display ASCII art text with different font styles.
|
|
@@ -307,7 +353,7 @@ Display ASCII art text with different font styles.
|
|
|
307
353
|
import { measureText } from "@opentui/core"
|
|
308
354
|
import { useState } from "react"
|
|
309
355
|
|
|
310
|
-
function
|
|
356
|
+
function App() {
|
|
311
357
|
const text = "ASCII"
|
|
312
358
|
const [font, setFont] = useState<"block" | "shade" | "slick" | "tiny">("tiny")
|
|
313
359
|
|
|
@@ -317,10 +363,11 @@ function ASCIIFontExample() {
|
|
|
317
363
|
})
|
|
318
364
|
|
|
319
365
|
return (
|
|
320
|
-
<box style={{ paddingLeft: 1, paddingRight: 1 }}>
|
|
366
|
+
<box style={{ border: true, paddingLeft: 1, paddingRight: 1 }}>
|
|
321
367
|
<box
|
|
322
368
|
style={{
|
|
323
369
|
height: 8,
|
|
370
|
+
border: true,
|
|
324
371
|
marginBottom: 1,
|
|
325
372
|
}}
|
|
326
373
|
>
|
|
@@ -365,10 +412,10 @@ function ASCIIFontExample() {
|
|
|
365
412
|
### Login Form
|
|
366
413
|
|
|
367
414
|
```tsx
|
|
368
|
-
import { useState, useCallback } from "react"
|
|
369
415
|
import { render, useKeyboard } from "@opentui/react"
|
|
416
|
+
import { useCallback, useState } from "react"
|
|
370
417
|
|
|
371
|
-
function
|
|
418
|
+
function App() {
|
|
372
419
|
const [username, setUsername] = useState("")
|
|
373
420
|
const [password, setPassword] = useState("")
|
|
374
421
|
const [focused, setFocused] = useState<"username" | "password">("username")
|
|
@@ -389,10 +436,10 @@ function LoginForm() {
|
|
|
389
436
|
}, [username, password])
|
|
390
437
|
|
|
391
438
|
return (
|
|
392
|
-
<box style={{ padding: 2, flexDirection: "column" }}>
|
|
439
|
+
<box style={{ border: true, padding: 2, flexDirection: "column", gap: 1 }}>
|
|
393
440
|
<text fg="#FFFF00">Login Form</text>
|
|
394
441
|
|
|
395
|
-
<box title="Username" style={{ width: 40, height: 3
|
|
442
|
+
<box title="Username" style={{ border: true, width: 40, height: 3 }}>
|
|
396
443
|
<input
|
|
397
444
|
placeholder="Enter username..."
|
|
398
445
|
onInput={setUsername}
|
|
@@ -401,7 +448,7 @@ function LoginForm() {
|
|
|
401
448
|
/>
|
|
402
449
|
</box>
|
|
403
450
|
|
|
404
|
-
<box title="Password" style={{ width: 40, height: 3
|
|
451
|
+
<box title="Password" style={{ border: true, width: 40, height: 3 }}>
|
|
405
452
|
<input
|
|
406
453
|
placeholder="Enter password..."
|
|
407
454
|
onInput={setPassword}
|
|
@@ -421,16 +468,16 @@ function LoginForm() {
|
|
|
421
468
|
)
|
|
422
469
|
}
|
|
423
470
|
|
|
424
|
-
render(<
|
|
471
|
+
render(<App />)
|
|
425
472
|
```
|
|
426
473
|
|
|
427
474
|
### Counter with Timer
|
|
428
475
|
|
|
429
476
|
```tsx
|
|
430
|
-
import { useState, useEffect } from "react"
|
|
431
477
|
import { render } from "@opentui/react"
|
|
478
|
+
import { useEffect, useState } from "react"
|
|
432
479
|
|
|
433
|
-
function
|
|
480
|
+
function App() {
|
|
434
481
|
const [count, setCount] = useState(0)
|
|
435
482
|
|
|
436
483
|
useEffect(() => {
|
|
@@ -448,7 +495,7 @@ function Counter() {
|
|
|
448
495
|
)
|
|
449
496
|
}
|
|
450
497
|
|
|
451
|
-
render(<
|
|
498
|
+
render(<App />)
|
|
452
499
|
```
|
|
453
500
|
|
|
454
501
|
### Styled Text Showcase
|
|
@@ -457,7 +504,7 @@ render(<Counter />)
|
|
|
457
504
|
import { blue, bold, red, t, underline } from "@opentui/core"
|
|
458
505
|
import { render } from "@opentui/react"
|
|
459
506
|
|
|
460
|
-
function
|
|
507
|
+
function App() {
|
|
461
508
|
return (
|
|
462
509
|
<box style={{ flexDirection: "column" }}>
|
|
463
510
|
<text>Simple text</text>
|
|
@@ -471,25 +518,32 @@ function StyledTextShowcase() {
|
|
|
471
518
|
)
|
|
472
519
|
}
|
|
473
520
|
|
|
474
|
-
render(<
|
|
521
|
+
render(<App />)
|
|
475
522
|
```
|
|
476
523
|
|
|
477
524
|
## Component Extension
|
|
478
525
|
|
|
479
|
-
You can create custom components by extending
|
|
526
|
+
You can create custom components by extending OpenTUIs base renderables:
|
|
480
527
|
|
|
481
528
|
```tsx
|
|
482
|
-
import { BoxRenderable, OptimizedBuffer, RGBA } from "@opentui/core"
|
|
529
|
+
import { BoxRenderable, OptimizedBuffer, RGBA, type BoxOptions, type RenderContext } from "@opentui/core"
|
|
483
530
|
import { extend, render } from "@opentui/react"
|
|
484
531
|
|
|
485
532
|
// Create custom component class
|
|
486
533
|
class ButtonRenderable extends BoxRenderable {
|
|
487
534
|
private _label: string = "Button"
|
|
488
535
|
|
|
489
|
-
constructor(
|
|
490
|
-
super(
|
|
491
|
-
|
|
492
|
-
|
|
536
|
+
constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) {
|
|
537
|
+
super(ctx, {
|
|
538
|
+
border: true,
|
|
539
|
+
borderStyle: "single",
|
|
540
|
+
minHeight: 3,
|
|
541
|
+
...options,
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
if (options.label) {
|
|
545
|
+
this._label = options.label
|
|
546
|
+
}
|
|
493
547
|
}
|
|
494
548
|
|
|
495
549
|
protected renderSelf(buffer: OptimizedBuffer): void {
|
|
@@ -510,19 +564,19 @@ class ButtonRenderable extends BoxRenderable {
|
|
|
510
564
|
// Add TypeScript support
|
|
511
565
|
declare module "@opentui/react" {
|
|
512
566
|
interface OpenTUIComponents {
|
|
513
|
-
|
|
567
|
+
consoleButton: typeof ButtonRenderable
|
|
514
568
|
}
|
|
515
569
|
}
|
|
516
570
|
|
|
517
571
|
// Register the component
|
|
518
|
-
extend({
|
|
572
|
+
extend({ consoleButton: ButtonRenderable })
|
|
519
573
|
|
|
520
574
|
// Use in JSX
|
|
521
575
|
function App() {
|
|
522
576
|
return (
|
|
523
577
|
<box>
|
|
524
|
-
<
|
|
525
|
-
<
|
|
578
|
+
<consoleButton label="Click me!" style={{ backgroundColor: "blue" }} />
|
|
579
|
+
<consoleButton label="Another button" style={{ backgroundColor: "green" }} />
|
|
526
580
|
</box>
|
|
527
581
|
)
|
|
528
582
|
}
|
package/index.js
CHANGED
|
@@ -149,8 +149,7 @@ function handleTextChildren(textInstance, children) {
|
|
|
149
149
|
if (typeof child === "string") {
|
|
150
150
|
chunks.push({
|
|
151
151
|
__isChunk: true,
|
|
152
|
-
text:
|
|
153
|
-
plainText: child
|
|
152
|
+
text: child
|
|
154
153
|
});
|
|
155
154
|
} else if (child && typeof child === "object" && "__isChunk" in child) {
|
|
156
155
|
chunks.push(child);
|
|
@@ -160,8 +159,7 @@ function handleTextChildren(textInstance, children) {
|
|
|
160
159
|
const stringValue = String(child);
|
|
161
160
|
chunks.push({
|
|
162
161
|
__isChunk: true,
|
|
163
|
-
text:
|
|
164
|
-
plainText: stringValue
|
|
162
|
+
text: stringValue
|
|
165
163
|
});
|
|
166
164
|
}
|
|
167
165
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.20",
|
|
8
8
|
"description": "React renderer for building terminal user interfaces using OpenTUI core",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@opentui/core": "0.1.
|
|
38
|
+
"@opentui/core": "0.1.20",
|
|
39
39
|
"react-reconciler": "^0.32.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
@@ -84,8 +84,7 @@ function handleTextChildren(textInstance, children) {
|
|
|
84
84
|
if (typeof child === "string") {
|
|
85
85
|
chunks.push({
|
|
86
86
|
__isChunk: true,
|
|
87
|
-
text:
|
|
88
|
-
plainText: child
|
|
87
|
+
text: child
|
|
89
88
|
});
|
|
90
89
|
} else if (child && typeof child === "object" && "__isChunk" in child) {
|
|
91
90
|
chunks.push(child);
|
|
@@ -95,8 +94,7 @@ function handleTextChildren(textInstance, children) {
|
|
|
95
94
|
const stringValue = String(child);
|
|
96
95
|
chunks.push({
|
|
97
96
|
__isChunk: true,
|
|
98
|
-
text:
|
|
99
|
-
plainText: stringValue
|
|
97
|
+
text: stringValue
|
|
100
98
|
});
|
|
101
99
|
}
|
|
102
100
|
}
|