@teaui/react
Version:
React Reconciler and renderer for TeaUI
202 lines (149 loc) • 8.54 kB
Markdown
# @teaui/react
React custom renderer for [TeaUI](https://github.com/colinta/teaui). Write fullscreen terminal UIs with React components, hooks, and JSX.
## Install
```bash
pnpm install @teaui/core @teaui/react react
# TypeScript users:
pnpm install -D @types/react
```
## Usage
```tsx
import React, {useReducer} from 'react'
import {interceptConsoleLog} from '@teaui/core'
import {Box, Button, Stack, run} from '@teaui/react'
interceptConsoleLog()
function App() {
const [bang, addBang] = useReducer(s => s + '!', '')
return (
<Box border="single">
<Stack.down>
Hello TeaUI{bang}
<Button onClick={addBang}>Click me</Button>
</Stack.down>
</Box>
)
}
run(<App />)
```
Compile and run:
```bash
pnpm tsc && node .dist/index.js
```
## API
### `run(element, options?)`
Creates a `Window` and `Screen`, renders the React element tree, and enters fullscreen mode.
```ts
const [screen, window, component, unmount] = await run(<App />)
```
Returns `[Screen, Window, ReactNode, unmount]`. Call `unmount()` to tear down the React tree.
### `render(screen, window, element)`
Lower-level alternative — mount a React element into an existing `Screen` and `Window`.
```ts
import {Screen, Window} from '@teaui/core'
import {render} from '@teaui/react'
const window = new Window()
const [screen] = await Screen.start(window)
const unmount = render(screen, window, <App />)
```
Returns an `unmount()` function.
## Components
All components are typed wrappers around TeaUI core views. They accept the same props as the core constructors, with `children` mapped to React children.
### Views (leaf nodes)
| Component | Element | Description |
| --------------------- | ------------------------ | ---------------------------------------- |
| `<Br />` | `<tui-br>` | Line break in text |
| `<Checkbox />` | `<tui-checkbox>` | Toggle checkbox |
| `<CollapsibleText />` | `<tui-collapsible-text>` | Text that truncates with expand/collapse |
| `<ConsoleLog />` | `<tui-console>` | Displays intercepted `console.*` output |
| `<Digits />` | `<tui-digits>` | Large-font digit display |
| `<H1 />`–`<H6 />` | `<tui-h1>`–`<tui-h6>` | Header text |
| `<Input />` | `<tui-input>` | Text input field |
| `<Separator />` | `<tui-separator>` | Horizontal or vertical line |
| `<Slider />` | `<tui-slider>` | Value slider |
| `<Space />` | `<tui-space>` | Empty spacer |
| `<ToggleGroup />` | `<tui-toggle-group>` | Group of toggle options |
`Separator` has `.horizontal` and `.vertical` variants. `Slider` has `.horizontal` and `.vertical` variants.
### Containers
| Component | Element | Description |
| ----------------- | ------------------- | ------------------------------------------------- |
| `<Box />` | `<tui-box>` | Box with optional border and padding |
| `<Button />` | `<tui-button>` | Clickable button |
| `<Collapsible />` | `<tui-collapsible>` | Toggle between `collapsed` and `expanded` content |
| `<Scrollable />` | `<tui-scrollable>` | Scrollable content region |
| `<Stack />` | `<tui-stack>` | Linear layout |
| `<Text />` | `<tui-text>` | Text container (sets font, alignment, wrap) |
| `<Style />` | `<tui-style>` | Inline text styles (bold, italic, etc.) |
`Stack` has `.down`, `.up`, `.left`, and `.right` variants.
### Complex Containers
| Component | Element | Description |
| ----------------------- | ------------------------- | --------------------------------- |
| `<Accordion />` | `<tui-accordion>` | Expandable section group |
| `<Accordion.Section />` | `<tui-accordion-section>` | Section within an accordion |
| `<Drawer />` | `<tui-drawer>` | Panel that slides in from an edge |
| `<Tabs />` | `<tui-tabs>` | Tabbed container |
| `<Tabs.Section />` | `<tui-tabs-section>` | Tab within tabs |
| `<Tree />` | `<tui-tree>` | Tree view with expandable nodes |
`Drawer` has `.top`, `.right`, `.bottom`, and `.left` variants. Each accepts `content` and `drawer` props for the two panes.
### Intrinsic Elements
You can also use the `tui-` prefixed JSX elements directly:
```tsx
<tui-stack direction="down">
<tui-box border="single" width={20}>
<tui-text alignment="center">Hello</tui-text>
</tui-box>
</tui-stack>
```
## Text Handling
React string literals are rendered as `TextLiteral` nodes, which are automatically grouped into `TextContainer`s for layout:
```tsx
<Stack.down>
hello {/* TextLiteral → TextContainer #1 */}
<Br /> {/* TextLiteral → TextContainer #1 */}
<Box /> {/* Box breaks the text group */}
goodbye {/* TextLiteral → TextContainer #2 */}
</Stack.down>
```
Use `<Text>` to control font, alignment, and word wrap. Use `<Style>` for inline formatting (bold, italic, etc.):
```tsx
<Text alignment="center" wrap>
This is <Style bold>important</Style> text.
</Text>
```
## React Features
| Feature | Status |
| --------------------------------------------------- | ------------------------------------------------ |
| Hooks (`useState`, `useReducer`, `useEffect`, etc.) | ✅ Works |
| Context (`useContext`, providers) | ✅ Works |
| Refs (`useRef`, callback refs) | ✅ Works |
| Suspense | ✅ Supported (timeouts delegate to `setTimeout`) |
| Portals | ⚠️ No-op (doesn't crash, but no portal behavior) |
| Error Boundaries | Not yet supported |
## Tests
```bash
pnpm test # run once
pnpm test:watch # watch mode
```
Tests use [vitest](https://vitest.dev/) and cover:
- **`isSame.test.ts`** — Deep equality comparisons (primitives, arrays, Sets, Maps, Dates, objects, React fiber nodes)
- **`reconciler.test.ts`** — Rendering, child manipulation, text node grouping, updates, refs, unmount
- **`components.test.ts`** — All component wrappers render the correct view types; Drawer variants pass `content`/`drawer` props
## Architecture
```
@teaui/react
├── lib/
│ ├── index.ts # Re-exports reconciler + components
│ ├── reconciler.ts # react-reconciler host config, render(), run()
│ ├── isSame.ts # Deep equality for prepareUpdate
│ ├── components.tsx # Typed React wrappers + JSX IntrinsicElements
│ └── components/
│ └── TextReact.ts # TextLiteral, TextContainer, TextProvider, TextStyle
├── tests/
│ ├── isSame.test.ts
│ ├── reconciler.test.tsx
│ └── components.test.tsx
└── vitest.config.ts
```
**`reconciler.ts`** implements the `react-reconciler` host config. It maps JSX element types to TeaUI view constructors in `createInstance`, manages the view tree via `appendChild`/`removeChild`/`insertBefore`, and applies prop updates via `commitUpdate` (which calls `view.update()`). Text string children become `TextLiteral` instances. The `prepareUpdate` function uses `isSame` to diff old and new props — if anything changed, `commitUpdate` passes the full new props to the view.
**`components/TextReact.ts`** defines the text rendering architecture. Adjacent `TextLiteral` nodes are grouped into a `TextContainer`, which handles layout. `TextProvider` (`<Text>`) sets text properties (font, alignment, wrap) for descendant text. `TextStyle` (`<Style>`) applies inline SGR styles without affecting layout properties.
## License
MIT