@opentui/react 0.1.24 → 0.1.26
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 +135 -0
- package/index.js +57 -10
- package/package.json +2 -2
- package/src/components/error-boundary.d.ts +16 -0
- package/src/hooks/index.d.ts +5 -0
- package/src/hooks/use-keyboard.d.ts +2 -2
- package/src/hooks/use-timeline.d.ts +2 -0
- package/src/index.d.ts +1 -4
- package/src/reconciler/renderer.js +33 -4
package/README.md
CHANGED
|
@@ -188,6 +188,58 @@ function App() {
|
|
|
188
188
|
|
|
189
189
|
**Returns:** An object with `width` and `height` properties representing the current terminal dimensions.
|
|
190
190
|
|
|
191
|
+
#### `useTimeline(options?)`
|
|
192
|
+
|
|
193
|
+
Create and manage animations using OpenTUI's timeline system. This hook automatically registers and unregisters the timeline with the animation engine.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { render, useTimeline } from "@opentui/react"
|
|
197
|
+
import { useEffect, useState } from "react"
|
|
198
|
+
|
|
199
|
+
function App() {
|
|
200
|
+
const [width, setWidth] = useState(0)
|
|
201
|
+
|
|
202
|
+
const timeline = useTimeline({
|
|
203
|
+
duration: 2000,
|
|
204
|
+
loop: false,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
timeline.add(
|
|
209
|
+
{
|
|
210
|
+
width,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
width: 50,
|
|
214
|
+
duration: 2000,
|
|
215
|
+
ease: "linear",
|
|
216
|
+
onUpdate: (animation) => {
|
|
217
|
+
setWidth(animation.targets[0].width)
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
}, [])
|
|
222
|
+
|
|
223
|
+
return <box style={{ width, backgroundColor: "#6a5acd" }} />
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Parameters:**
|
|
228
|
+
|
|
229
|
+
- `options?`: Optional `TimelineOptions` object with properties:
|
|
230
|
+
- `duration?`: Animation duration in milliseconds (default: 1000)
|
|
231
|
+
- `loop?`: Whether the timeline should loop (default: false)
|
|
232
|
+
- `autoplay?`: Whether to automatically start the timeline (default: true)
|
|
233
|
+
- `onComplete?`: Callback when timeline completes
|
|
234
|
+
- `onPause?`: Callback when timeline is paused
|
|
235
|
+
|
|
236
|
+
**Returns:** A `Timeline` instance with methods:
|
|
237
|
+
|
|
238
|
+
- `add(target, properties, startTime)`: Add animation to timeline
|
|
239
|
+
- `play()`: Start the timeline
|
|
240
|
+
- `pause()`: Pause the timeline
|
|
241
|
+
- `restart()`: Restart the timeline from beginning
|
|
242
|
+
|
|
191
243
|
## Components
|
|
192
244
|
|
|
193
245
|
### Text Component
|
|
@@ -504,6 +556,89 @@ function App() {
|
|
|
504
556
|
render(<App />)
|
|
505
557
|
```
|
|
506
558
|
|
|
559
|
+
### System Monitor Animation
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
import { TextAttributes } from "@opentui/core"
|
|
563
|
+
import { render, useTimeline } from "@opentui/react"
|
|
564
|
+
import { useEffect, useState } from "react"
|
|
565
|
+
|
|
566
|
+
type Stats = {
|
|
567
|
+
cpu: number
|
|
568
|
+
memory: number
|
|
569
|
+
network: number
|
|
570
|
+
disk: number
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export const App = () => {
|
|
574
|
+
const [stats, setAnimatedStats] = useState<Stats>({
|
|
575
|
+
cpu: 0,
|
|
576
|
+
memory: 0,
|
|
577
|
+
network: 0,
|
|
578
|
+
disk: 0,
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
const timeline = useTimeline({
|
|
582
|
+
duration: 3000,
|
|
583
|
+
loop: false,
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
timeline.add(
|
|
588
|
+
stats,
|
|
589
|
+
{
|
|
590
|
+
cpu: 85,
|
|
591
|
+
memory: 70,
|
|
592
|
+
network: 95,
|
|
593
|
+
disk: 60,
|
|
594
|
+
duration: 3000,
|
|
595
|
+
ease: "linear",
|
|
596
|
+
onUpdate: (values) => {
|
|
597
|
+
setAnimatedStats({ ...values.targets[0] })
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
0,
|
|
601
|
+
)
|
|
602
|
+
}, [])
|
|
603
|
+
|
|
604
|
+
const statsMap = [
|
|
605
|
+
{ name: "CPU", key: "cpu", color: "#6a5acd" },
|
|
606
|
+
{ name: "Memory", key: "memory", color: "#4682b4" },
|
|
607
|
+
{ name: "Network", key: "network", color: "#20b2aa" },
|
|
608
|
+
{ name: "Disk", key: "disk", color: "#daa520" },
|
|
609
|
+
]
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<box
|
|
613
|
+
title="System Monitor"
|
|
614
|
+
style={{
|
|
615
|
+
margin: 1,
|
|
616
|
+
padding: 1,
|
|
617
|
+
border: true,
|
|
618
|
+
marginLeft: 2,
|
|
619
|
+
marginRight: 2,
|
|
620
|
+
borderStyle: "single",
|
|
621
|
+
borderColor: "#4a4a4a",
|
|
622
|
+
}}
|
|
623
|
+
>
|
|
624
|
+
{statsMap.map((stat) => (
|
|
625
|
+
<box key={stat.key}>
|
|
626
|
+
<box flexDirection="row" justifyContent="space-between">
|
|
627
|
+
<text>{stat.name}</text>
|
|
628
|
+
<text attributes={TextAttributes.DIM}>{Math.round(stats[stat.key as keyof Stats])}%</text>
|
|
629
|
+
</box>
|
|
630
|
+
<box style={{ backgroundColor: "#333333" }}>
|
|
631
|
+
<box style={{ width: `${stats[stat.key as keyof Stats]}%`, height: 1, backgroundColor: stat.color }} />
|
|
632
|
+
</box>
|
|
633
|
+
</box>
|
|
634
|
+
))}
|
|
635
|
+
</box>
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
render(<App />)
|
|
640
|
+
```
|
|
641
|
+
|
|
507
642
|
### Styled Text Showcase
|
|
508
643
|
|
|
509
644
|
```tsx
|
package/index.js
CHANGED
|
@@ -97,10 +97,10 @@ var AppContext = createContext({
|
|
|
97
97
|
var useAppContext = () => {
|
|
98
98
|
return useContext(AppContext);
|
|
99
99
|
};
|
|
100
|
-
// src/hooks/use-keyboard.
|
|
100
|
+
// src/hooks/use-keyboard.ts
|
|
101
101
|
import { useEffect } from "react";
|
|
102
102
|
|
|
103
|
-
// src/hooks/use-event.
|
|
103
|
+
// src/hooks/use-event.ts
|
|
104
104
|
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
105
105
|
function useEvent(handler) {
|
|
106
106
|
const handlerRef = useRef(handler);
|
|
@@ -113,7 +113,7 @@ function useEvent(handler) {
|
|
|
113
113
|
}, []);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// src/hooks/use-keyboard.
|
|
116
|
+
// src/hooks/use-keyboard.ts
|
|
117
117
|
var useKeyboard = (handler) => {
|
|
118
118
|
const { keyHandler } = useAppContext();
|
|
119
119
|
const stableHandler = useEvent(handler);
|
|
@@ -124,7 +124,7 @@ var useKeyboard = (handler) => {
|
|
|
124
124
|
};
|
|
125
125
|
}, [keyHandler, stableHandler]);
|
|
126
126
|
};
|
|
127
|
-
// src/hooks/use-renderer.
|
|
127
|
+
// src/hooks/use-renderer.ts
|
|
128
128
|
var useRenderer = () => {
|
|
129
129
|
const { renderer } = useAppContext();
|
|
130
130
|
if (!renderer) {
|
|
@@ -132,7 +132,7 @@ var useRenderer = () => {
|
|
|
132
132
|
}
|
|
133
133
|
return renderer;
|
|
134
134
|
};
|
|
135
|
-
// src/hooks/use-resize.
|
|
135
|
+
// src/hooks/use-resize.ts
|
|
136
136
|
import { useEffect as useEffect2 } from "react";
|
|
137
137
|
var useOnResize = (callback) => {
|
|
138
138
|
const renderer = useRenderer();
|
|
@@ -144,7 +144,7 @@ var useOnResize = (callback) => {
|
|
|
144
144
|
}, [renderer, callback]);
|
|
145
145
|
return renderer;
|
|
146
146
|
};
|
|
147
|
-
// src/hooks/use-terminal-dimensions.
|
|
147
|
+
// src/hooks/use-terminal-dimensions.ts
|
|
148
148
|
import { useState } from "react";
|
|
149
149
|
var useTerminalDimensions = () => {
|
|
150
150
|
const renderer = useRenderer();
|
|
@@ -158,9 +158,26 @@ var useTerminalDimensions = () => {
|
|
|
158
158
|
useOnResize(cb);
|
|
159
159
|
return dimensions;
|
|
160
160
|
};
|
|
161
|
+
// src/hooks/use-timeline.ts
|
|
162
|
+
import { engine, Timeline } from "@opentui/core";
|
|
163
|
+
import { useEffect as useEffect3 } from "react";
|
|
164
|
+
var useTimeline = (options = {}) => {
|
|
165
|
+
const timeline = new Timeline(options);
|
|
166
|
+
useEffect3(() => {
|
|
167
|
+
if (!options.autoplay) {
|
|
168
|
+
timeline.play();
|
|
169
|
+
}
|
|
170
|
+
engine.register(timeline);
|
|
171
|
+
return () => {
|
|
172
|
+
timeline.pause();
|
|
173
|
+
engine.unregister(timeline);
|
|
174
|
+
};
|
|
175
|
+
}, []);
|
|
176
|
+
return timeline;
|
|
177
|
+
};
|
|
161
178
|
// src/reconciler/renderer.ts
|
|
162
|
-
import { createCliRenderer,
|
|
163
|
-
import
|
|
179
|
+
import { createCliRenderer, engine as engine2 } from "@opentui/core";
|
|
180
|
+
import React2 from "react";
|
|
164
181
|
|
|
165
182
|
// src/reconciler/reconciler.ts
|
|
166
183
|
import ReactReconciler from "react-reconciler";
|
|
@@ -455,13 +472,43 @@ function _render(element, root) {
|
|
|
455
472
|
reconciler.updateContainer(element, container, null, () => {});
|
|
456
473
|
}
|
|
457
474
|
|
|
475
|
+
// src/components/error-boundary.tsx
|
|
476
|
+
import React from "react";
|
|
477
|
+
|
|
478
|
+
// jsx-dev-runtime.js
|
|
479
|
+
import { Fragment, jsxDEV } from "react/jsx-dev-runtime";
|
|
480
|
+
|
|
481
|
+
// src/components/error-boundary.tsx
|
|
482
|
+
class ErrorBoundary extends React.Component {
|
|
483
|
+
constructor(props) {
|
|
484
|
+
super(props);
|
|
485
|
+
this.state = { hasError: false, error: null };
|
|
486
|
+
}
|
|
487
|
+
static getDerivedStateFromError(error) {
|
|
488
|
+
return { hasError: true, error };
|
|
489
|
+
}
|
|
490
|
+
render() {
|
|
491
|
+
if (this.state.hasError && this.state.error) {
|
|
492
|
+
return /* @__PURE__ */ jsxDEV("box", {
|
|
493
|
+
style: { flexDirection: "column", padding: 2 },
|
|
494
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
495
|
+
fg: "red",
|
|
496
|
+
children: this.state.error.stack || this.state.error.message
|
|
497
|
+
}, undefined, false, undefined, this)
|
|
498
|
+
}, undefined, false, undefined, this);
|
|
499
|
+
}
|
|
500
|
+
return this.props.children;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
458
504
|
// src/reconciler/renderer.ts
|
|
459
|
-
var keyHandler = getKeyHandler();
|
|
460
505
|
async function render(node, rendererConfig = {}) {
|
|
461
506
|
const renderer = await createCliRenderer(rendererConfig);
|
|
462
|
-
|
|
507
|
+
engine2.attach(renderer);
|
|
508
|
+
_render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
463
509
|
}
|
|
464
510
|
export {
|
|
511
|
+
useTimeline,
|
|
465
512
|
useTerminalDimensions,
|
|
466
513
|
useRenderer,
|
|
467
514
|
useOnResize,
|
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.26",
|
|
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.26",
|
|
39
39
|
"react-reconciler": "^0.32.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export declare class ErrorBoundary extends React.Component<{
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
}, {
|
|
5
|
+
hasError: boolean;
|
|
6
|
+
error: Error | null;
|
|
7
|
+
}> {
|
|
8
|
+
constructor(props: {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
});
|
|
11
|
+
static getDerivedStateFromError(error: Error): {
|
|
12
|
+
hasError: boolean;
|
|
13
|
+
error: Error;
|
|
14
|
+
};
|
|
15
|
+
render(): any;
|
|
16
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const useKeyboard: (handler: (key:
|
|
1
|
+
import type { KeyEvent } from "@opentui/core";
|
|
2
|
+
export declare const useKeyboard: (handler: (key: KeyEvent) => void) => void;
|
package/src/index.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
export * from "./components";
|
|
2
2
|
export * from "./components/app";
|
|
3
|
-
export * from "./hooks
|
|
4
|
-
export * from "./hooks/use-renderer";
|
|
5
|
-
export * from "./hooks/use-resize";
|
|
6
|
-
export * from "./hooks/use-terminal-dimensions";
|
|
3
|
+
export * from "./hooks";
|
|
7
4
|
export * from "./reconciler/renderer";
|
|
8
5
|
export * from "./types/components";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/reconciler/renderer.ts
|
|
3
|
-
import { createCliRenderer,
|
|
4
|
-
import
|
|
3
|
+
import { createCliRenderer, engine } from "@opentui/core";
|
|
4
|
+
import React2 from "react";
|
|
5
5
|
|
|
6
6
|
// src/components/app.tsx
|
|
7
7
|
import { createContext, useContext } from "react";
|
|
@@ -390,11 +390,40 @@ function _render(element, root) {
|
|
|
390
390
|
reconciler.updateContainer(element, container, null, () => {});
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
// src/components/error-boundary.tsx
|
|
394
|
+
import React from "react";
|
|
395
|
+
|
|
396
|
+
// jsx-dev-runtime.js
|
|
397
|
+
import { Fragment, jsxDEV } from "react/jsx-dev-runtime";
|
|
398
|
+
|
|
399
|
+
// src/components/error-boundary.tsx
|
|
400
|
+
class ErrorBoundary extends React.Component {
|
|
401
|
+
constructor(props) {
|
|
402
|
+
super(props);
|
|
403
|
+
this.state = { hasError: false, error: null };
|
|
404
|
+
}
|
|
405
|
+
static getDerivedStateFromError(error) {
|
|
406
|
+
return { hasError: true, error };
|
|
407
|
+
}
|
|
408
|
+
render() {
|
|
409
|
+
if (this.state.hasError && this.state.error) {
|
|
410
|
+
return /* @__PURE__ */ jsxDEV("box", {
|
|
411
|
+
style: { flexDirection: "column", padding: 2 },
|
|
412
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
413
|
+
fg: "red",
|
|
414
|
+
children: this.state.error.stack || this.state.error.message
|
|
415
|
+
}, undefined, false, undefined, this)
|
|
416
|
+
}, undefined, false, undefined, this);
|
|
417
|
+
}
|
|
418
|
+
return this.props.children;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
393
422
|
// src/reconciler/renderer.ts
|
|
394
|
-
var keyHandler = getKeyHandler();
|
|
395
423
|
async function render(node, rendererConfig = {}) {
|
|
396
424
|
const renderer = await createCliRenderer(rendererConfig);
|
|
397
|
-
|
|
425
|
+
engine.attach(renderer);
|
|
426
|
+
_render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
398
427
|
}
|
|
399
428
|
export {
|
|
400
429
|
render
|