@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 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.tsx
100
+ // src/hooks/use-keyboard.ts
101
101
  import { useEffect } from "react";
102
102
 
103
- // src/hooks/use-event.tsx
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.tsx
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.tsx
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.tsx
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.tsx
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, getKeyHandler } from "@opentui/core";
163
- import React from "react";
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
- _render(React.createElement(AppContext.Provider, { value: { keyHandler, renderer } }, node), renderer.root);
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.24",
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.24",
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
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./use-keyboard";
2
+ export * from "./use-renderer";
3
+ export * from "./use-resize";
4
+ export * from "./use-terminal-dimensions";
5
+ export * from "./use-timeline";
@@ -1,2 +1,2 @@
1
- import type { ParsedKey } from "@opentui/core";
2
- export declare const useKeyboard: (handler: (key: ParsedKey) => void) => void;
1
+ import type { KeyEvent } from "@opentui/core";
2
+ export declare const useKeyboard: (handler: (key: KeyEvent) => void) => void;
@@ -0,0 +1,2 @@
1
+ import { Timeline, type TimelineOptions } from "@opentui/core";
2
+ export declare const useTimeline: (options?: TimelineOptions) => Timeline;
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/use-keyboard";
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, getKeyHandler } from "@opentui/core";
4
- import React from "react";
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
- _render(React.createElement(AppContext.Provider, { value: { keyHandler, renderer } }, node), renderer.root);
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