@nick-skriabin/glyph 0.1.0 → 0.1.3
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 +545 -0
- package/package.json +11 -3
package/README.md
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://em-content.zobj.net/source/apple/391/crystal-ball_1f52e.png" width="120" height="120" alt="Glyph">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Glyph</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>React renderer for terminal UIs</strong><br>
|
|
9
|
+
<em>Flexbox layout. Keyboard-driven. Zero compromises.</em>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="#quick-start">Quick Start</a> •
|
|
14
|
+
<a href="#components">Components</a> •
|
|
15
|
+
<a href="#hooks">Hooks</a> •
|
|
16
|
+
<a href="#styling">Styling</a> •
|
|
17
|
+
<a href="#examples">Examples</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="https://img.shields.io/badge/React-18%2B-61dafb?logo=react&logoColor=white" alt="React 18+">
|
|
22
|
+
<img src="https://img.shields.io/badge/Yoga-Flexbox-mediumpurple?logo=meta&logoColor=white" alt="Yoga Flexbox">
|
|
23
|
+
<img src="https://img.shields.io/badge/TypeScript-First-3178c6?logo=typescript&logoColor=white" alt="TypeScript">
|
|
24
|
+
<img src="https://img.shields.io/badge/License-MIT-blue" alt="MIT License">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
Build real terminal applications with React. Glyph provides a full component model with flexbox layout (powered by Yoga), focus management, keyboard input, and efficient diff-based rendering. Write TUIs the same way you write web apps.
|
|
30
|
+
|
|
31
|
+
| | | |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
|  |  | |
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
|
|
38
|
+
| | |
|
|
39
|
+
|---|---|
|
|
40
|
+
| **Flexbox Layout** | Full CSS-like flexbox via Yoga — rows, columns, wrapping, alignment, gaps, padding |
|
|
41
|
+
| **Rich Components** | Box, Text, Input, Button, Checkbox, Radio, Select, ScrollView, List, Menu, Progress, Spinner, Toasts, Portal |
|
|
42
|
+
| **Focus System** | Tab navigation, focus scopes, focus trapping for modals |
|
|
43
|
+
| **Keyboard Input** | `useInput` hook, declarative `<Keybind>` component, vim-style bindings |
|
|
44
|
+
| **Smart Rendering** | Double-buffered framebuffer with character-level diffing — only changed cells are written |
|
|
45
|
+
| **True Colors** | Named colors, hex, RGB, 256-palette. Auto-contrast text on colored backgrounds |
|
|
46
|
+
| **Borders** | Single, double, rounded, and ASCII border styles |
|
|
47
|
+
| **TypeScript** | Full type coverage. Every prop, style, and hook is typed |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# npm
|
|
55
|
+
npm install @nick-skriabin/glyph react
|
|
56
|
+
|
|
57
|
+
# pnpm
|
|
58
|
+
pnpm add @nick-skriabin/glyph react
|
|
59
|
+
|
|
60
|
+
# bun
|
|
61
|
+
bun add @nick-skriabin/glyph react
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import React from "react";
|
|
70
|
+
import { render, Box, Text, Keybind, useApp } from "@nick-skriabin/glyph";
|
|
71
|
+
|
|
72
|
+
function App() {
|
|
73
|
+
const { exit } = useApp();
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Box style={{ border: "round", borderColor: "cyan", padding: 1 }}>
|
|
77
|
+
<Text style={{ bold: true, color: "green" }}>Hello, Glyph!</Text>
|
|
78
|
+
<Keybind keypress="q" onPress={() => exit()} />
|
|
79
|
+
</Box>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
render(<App />);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Run it:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx tsx app.tsx
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Components
|
|
95
|
+
|
|
96
|
+
### `<Box>`
|
|
97
|
+
|
|
98
|
+
Flexbox container. The fundamental building block.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<Box style={{ flexDirection: "row", gap: 2, border: "single", padding: 1 }}>
|
|
102
|
+
<Box style={{ flexGrow: 1, bg: "blue" }}>
|
|
103
|
+
<Text>Left</Text>
|
|
104
|
+
</Box>
|
|
105
|
+
<Box style={{ flexGrow: 1, bg: "red" }}>
|
|
106
|
+
<Text>Right</Text>
|
|
107
|
+
</Box>
|
|
108
|
+
</Box>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `<Text>`
|
|
112
|
+
|
|
113
|
+
Styled text content. Supports wrapping, alignment, bold, dim, italic, underline.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
<Text style={{ color: "yellowBright", bold: true, textAlign: "center" }}>
|
|
117
|
+
Warning: something happened
|
|
118
|
+
</Text>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `<Input>`
|
|
122
|
+
|
|
123
|
+
Text input field with cursor and placeholder support.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<Input
|
|
127
|
+
value={text}
|
|
128
|
+
onChange={setText}
|
|
129
|
+
placeholder="Type here..."
|
|
130
|
+
style={{ bg: "blackBright", paddingX: 1 }}
|
|
131
|
+
focusedStyle={{ bg: "white", color: "black" }}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Supports `multiline` for multi-line editing. The cursor is always visible when focused, with inverted colors for clarity.
|
|
136
|
+
|
|
137
|
+
### `<Button>`
|
|
138
|
+
|
|
139
|
+
Focusable button with press handling and visual feedback.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<Button
|
|
143
|
+
onPress={() => console.log("clicked")}
|
|
144
|
+
style={{ border: "single", borderColor: "cyan", paddingX: 2 }}
|
|
145
|
+
focusedStyle={{ borderColor: "yellowBright", bold: true }}
|
|
146
|
+
>
|
|
147
|
+
<Text>Submit</Text>
|
|
148
|
+
</Button>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Buttons participate in the focus system automatically. Press `Enter` or `Space` to activate.
|
|
152
|
+
|
|
153
|
+
### `<Checkbox>`
|
|
154
|
+
|
|
155
|
+
Toggle checkbox with label support.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
const [agreed, setAgreed] = useState(false);
|
|
159
|
+
|
|
160
|
+
<Checkbox
|
|
161
|
+
checked={agreed}
|
|
162
|
+
onChange={setAgreed}
|
|
163
|
+
label="I agree to the terms"
|
|
164
|
+
focusedStyle={{ color: "cyan" }}
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Focusable. Press `Enter` or `Space` to toggle. Supports custom `checkedChar` and `uncheckedChar` props.
|
|
169
|
+
|
|
170
|
+
### `<Radio>`
|
|
171
|
+
|
|
172
|
+
Radio button group for single selection from multiple options.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
const [theme, setTheme] = useState<string>("dark");
|
|
176
|
+
|
|
177
|
+
<Radio
|
|
178
|
+
items={[
|
|
179
|
+
{ label: "Light", value: "light" },
|
|
180
|
+
{ label: "Dark", value: "dark" },
|
|
181
|
+
{ label: "System", value: "system" },
|
|
182
|
+
]}
|
|
183
|
+
value={theme}
|
|
184
|
+
onChange={setTheme}
|
|
185
|
+
focusedItemStyle={{ color: "cyan" }}
|
|
186
|
+
selectedItemStyle={{ bold: true }}
|
|
187
|
+
/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Focusable. Navigate with `Up`/`Down`/`Left`/`Right`/`Tab`/`Shift+Tab`, select with `Enter`/`Space`. Supports `direction` prop (`"column"` or `"row"`), custom `selectedChar` and `unselectedChar`.
|
|
191
|
+
|
|
192
|
+
### `<ScrollView>`
|
|
193
|
+
|
|
194
|
+
Scrollable container with keyboard navigation and clipping.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<ScrollView style={{ flexGrow: 1, border: "single" }}>
|
|
198
|
+
{items.map((item, i) => (
|
|
199
|
+
<Box key={i}>
|
|
200
|
+
<Text>{item}</Text>
|
|
201
|
+
</Box>
|
|
202
|
+
))}
|
|
203
|
+
</ScrollView>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Keyboard:** `PageUp`/`PageDown`, `Ctrl+d`/`Ctrl+u` (half-page), `Ctrl+f`/`Ctrl+b` (full page).
|
|
207
|
+
|
|
208
|
+
Shows a scrollbar when content exceeds viewport (disable with `showScrollbar={false}`). Supports controlled mode with `scrollOffset` and `onScroll` props.
|
|
209
|
+
|
|
210
|
+
### `<List>`
|
|
211
|
+
|
|
212
|
+
Keyboard-navigable selection list with a render callback.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<List
|
|
216
|
+
count={items.length}
|
|
217
|
+
onSelect={(index) => handleSelect(items[index])}
|
|
218
|
+
disabledIndices={new Set([2, 5])}
|
|
219
|
+
renderItem={({ index, selected, focused }) => (
|
|
220
|
+
<Box style={selected && focused ? { bg: "cyan" } : {}}>
|
|
221
|
+
<Text style={selected ? { bold: true } : {}}>
|
|
222
|
+
{selected ? "> " : " "}{items[index]}
|
|
223
|
+
</Text>
|
|
224
|
+
</Box>
|
|
225
|
+
)}
|
|
226
|
+
/>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Focusable. `Up`/`Down`/`j`/`k` to navigate, `G` to jump to bottom, `gg` to jump to top, `Enter` to select. Disabled indices are skipped.
|
|
230
|
+
|
|
231
|
+
### `<Menu>`
|
|
232
|
+
|
|
233
|
+
Styled menu built on `<List>`. Accepts structured items with labels, values, and disabled state.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
<Menu
|
|
237
|
+
items={[
|
|
238
|
+
{ label: "New File", value: "new" },
|
|
239
|
+
{ label: "Open File", value: "open" },
|
|
240
|
+
{ label: "Export", value: "export", disabled: true },
|
|
241
|
+
{ label: "Quit", value: "quit" },
|
|
242
|
+
]}
|
|
243
|
+
onSelect={(value) => handleAction(value)}
|
|
244
|
+
highlightColor="yellow"
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `<Select>`
|
|
249
|
+
|
|
250
|
+
Dropdown select with keyboard navigation and type-to-filter search.
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
const [lang, setLang] = useState<string | undefined>();
|
|
254
|
+
|
|
255
|
+
<Select
|
|
256
|
+
items={[
|
|
257
|
+
{ label: "TypeScript", value: "ts" },
|
|
258
|
+
{ label: "JavaScript", value: "js" },
|
|
259
|
+
{ label: "Rust", value: "rust" },
|
|
260
|
+
{ label: "Go", value: "go" },
|
|
261
|
+
{ label: "COBOL", value: "cobol", disabled: true },
|
|
262
|
+
]}
|
|
263
|
+
value={lang}
|
|
264
|
+
onChange={setLang}
|
|
265
|
+
placeholder="Pick a language..."
|
|
266
|
+
maxVisible={6}
|
|
267
|
+
highlightColor="yellow"
|
|
268
|
+
/>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Focusable. `Enter`/`Space`/`Down` to open, `Up`/`Down` to navigate, `Enter` to confirm, `Escape` to close. Type characters to filter items when open. Disabled items are skipped.
|
|
272
|
+
|
|
273
|
+
Props: `items`, `value`, `onChange`, `placeholder`, `maxVisible`, `highlightColor`, `searchable`, `style`, `focusedStyle`, `dropdownStyle`, `disabled`.
|
|
274
|
+
|
|
275
|
+
### `<FocusScope>`
|
|
276
|
+
|
|
277
|
+
Focus trapping for modals and overlays.
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
<FocusScope trap>
|
|
281
|
+
<Input value={v} onChange={setV} />
|
|
282
|
+
<Button onPress={submit}>
|
|
283
|
+
<Text>OK</Text>
|
|
284
|
+
</Button>
|
|
285
|
+
</FocusScope>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `<Portal>`
|
|
289
|
+
|
|
290
|
+
Renders children in a fullscreen absolute overlay. Useful for modals and dialogs.
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
<Portal>
|
|
294
|
+
<Box style={{ width: "100%", height: "100%", justifyContent: "center", alignItems: "center" }}>
|
|
295
|
+
<Box style={{ width: 40, border: "double", bg: "black", padding: 1 }}>
|
|
296
|
+
<Text>Modal content</Text>
|
|
297
|
+
</Box>
|
|
298
|
+
</Box>
|
|
299
|
+
</Portal>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `<Keybind>`
|
|
303
|
+
|
|
304
|
+
Declarative keyboard shortcut. Renders nothing.
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<Keybind keypress="ctrl+s" onPress={save} />
|
|
308
|
+
<Keybind keypress="escape" onPress={close} />
|
|
309
|
+
<Keybind keypress="q" onPress={() => exit()} />
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `<Progress>`
|
|
313
|
+
|
|
314
|
+
Determinate or indeterminate progress bar. Uses `useLayout` to measure actual width and renders block characters.
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
<Progress value={0.65} showPercent />
|
|
318
|
+
<Progress indeterminate label="Loading" />
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Props: `value` (0..1), `indeterminate`, `width`, `label`, `showPercent`, `filled`/`empty` (characters).
|
|
322
|
+
|
|
323
|
+
### `<Spinner>`
|
|
324
|
+
|
|
325
|
+
Animated spinner with configurable frames. Cleans up timers on unmount.
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
<Spinner label="Loading..." style={{ color: "green" }} />
|
|
329
|
+
<Spinner frames={["|", "/", "-", "\\"]} intervalMs={100} />
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### `<ToastHost>` + `useToast()`
|
|
333
|
+
|
|
334
|
+
Lightweight toast notifications rendered via Portal. Wrap your app in `<ToastHost>`, then push toasts from anywhere with `useToast()`.
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
function App() {
|
|
338
|
+
const toast = useToast();
|
|
339
|
+
return <Keybind keypress="t" onPress={() =>
|
|
340
|
+
toast({ message: "Saved!", variant: "success" })
|
|
341
|
+
} />;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
render(<ToastHost position="top-right"><App /></ToastHost>);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Variants: `"info"`, `"success"`, `"warning"`, `"error"`. Auto-dismiss after `durationMs` (default 3000).
|
|
348
|
+
|
|
349
|
+
### `<Spacer>`
|
|
350
|
+
|
|
351
|
+
Flexible space filler. Pushes siblings apart.
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
<Box style={{ flexDirection: "row" }}>
|
|
355
|
+
<Text>Left</Text>
|
|
356
|
+
<Spacer />
|
|
357
|
+
<Text>Right</Text>
|
|
358
|
+
</Box>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Hooks
|
|
364
|
+
|
|
365
|
+
### `useInput(handler)`
|
|
366
|
+
|
|
367
|
+
Listen for all keyboard events.
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
useInput((key) => {
|
|
371
|
+
if (key.name === "escape") close();
|
|
372
|
+
if (key.ctrl && key.name === "s") save();
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### `useFocus(nodeRef)`
|
|
377
|
+
|
|
378
|
+
Get focus state for a node.
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
const ref = useRef(null);
|
|
382
|
+
const { focused, focus } = useFocus(ref);
|
|
383
|
+
|
|
384
|
+
<Box ref={ref} focusable>
|
|
385
|
+
<Text style={focused ? { color: "cyan" } : {}}>
|
|
386
|
+
{focused ? "* focused *" : "not focused"}
|
|
387
|
+
</Text>
|
|
388
|
+
</Box>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### `useLayout(nodeRef)`
|
|
392
|
+
|
|
393
|
+
Subscribe to a node's computed layout.
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
const ref = useRef(null);
|
|
397
|
+
const layout = useLayout(ref);
|
|
398
|
+
|
|
399
|
+
// layout: { x, y, width, height, innerX, innerY, innerWidth, innerHeight }
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### `useApp()`
|
|
403
|
+
|
|
404
|
+
Access app-level utilities.
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
const { exit, columns, rows } = useApp();
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Styling
|
|
413
|
+
|
|
414
|
+
All components accept a `style` prop. Glyph uses Yoga for flexbox layout, so the model is familiar if you've used CSS flexbox or React Native.
|
|
415
|
+
|
|
416
|
+
### Layout
|
|
417
|
+
|
|
418
|
+
| Property | Type | Description |
|
|
419
|
+
|----------|------|-------------|
|
|
420
|
+
| `width`, `height` | `number \| "${n}%"` | Dimensions |
|
|
421
|
+
| `minWidth`, `minHeight` | `number` | Minimum dimensions |
|
|
422
|
+
| `maxWidth`, `maxHeight` | `number` | Maximum dimensions |
|
|
423
|
+
| `padding` | `number` | Padding on all sides |
|
|
424
|
+
| `paddingX`, `paddingY` | `number` | Horizontal / vertical padding |
|
|
425
|
+
| `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft` | `number` | Individual sides |
|
|
426
|
+
| `gap` | `number` | Gap between flex children |
|
|
427
|
+
|
|
428
|
+
### Flexbox
|
|
429
|
+
|
|
430
|
+
| Property | Type | Default |
|
|
431
|
+
|----------|------|---------|
|
|
432
|
+
| `flexDirection` | `"row" \| "column"` | `"column"` |
|
|
433
|
+
| `flexWrap` | `"nowrap" \| "wrap"` | `"nowrap"` |
|
|
434
|
+
| `justifyContent` | `"flex-start" \| "center" \| "flex-end" \| "space-between" \| "space-around"` | `"flex-start"` |
|
|
435
|
+
| `alignItems` | `"flex-start" \| "center" \| "flex-end" \| "stretch"` | `"stretch"` |
|
|
436
|
+
| `flexGrow` | `number` | `0` |
|
|
437
|
+
| `flexShrink` | `number` | `0` |
|
|
438
|
+
|
|
439
|
+
### Positioning
|
|
440
|
+
|
|
441
|
+
| Property | Type | Description |
|
|
442
|
+
|----------|------|-------------|
|
|
443
|
+
| `position` | `"relative" \| "absolute"` | Positioning mode |
|
|
444
|
+
| `top`, `right`, `bottom`, `left` | `number \| "${n}%"` | Offsets |
|
|
445
|
+
| `inset` | `number \| "${n}%"` | Shorthand for all four edges |
|
|
446
|
+
| `zIndex` | `number` | Stacking order |
|
|
447
|
+
|
|
448
|
+
### Visual
|
|
449
|
+
|
|
450
|
+
| Property | Type | Description |
|
|
451
|
+
|----------|------|-------------|
|
|
452
|
+
| `bg` | `Color` | Background color |
|
|
453
|
+
| `border` | `"none" \| "single" \| "double" \| "round" \| "ascii"` | Border style |
|
|
454
|
+
| `borderColor` | `Color` | Border color |
|
|
455
|
+
| `clip` | `boolean` | Clip overflowing children |
|
|
456
|
+
|
|
457
|
+
### Text
|
|
458
|
+
|
|
459
|
+
| Property | Type | Description |
|
|
460
|
+
|----------|------|-------------|
|
|
461
|
+
| `color` | `Color` | Text color |
|
|
462
|
+
| `bold` | `boolean` | Bold text |
|
|
463
|
+
| `dim` | `boolean` | Dimmed text |
|
|
464
|
+
| `italic` | `boolean` | Italic text |
|
|
465
|
+
| `underline` | `boolean` | Underlined text |
|
|
466
|
+
| `wrap` | `"wrap" \| "truncate" \| "ellipsis" \| "none"` | Text wrapping mode |
|
|
467
|
+
| `textAlign` | `"left" \| "center" \| "right"` | Text alignment |
|
|
468
|
+
|
|
469
|
+
### Colors
|
|
470
|
+
|
|
471
|
+
Colors can be specified as:
|
|
472
|
+
|
|
473
|
+
- **Named:** `"red"`, `"green"`, `"blueBright"`, `"whiteBright"`, etc.
|
|
474
|
+
- **Hex:** `"#ff0000"`, `"#1a1a2e"`
|
|
475
|
+
- **RGB:** `{ r: 255, g: 0, b: 0 }`
|
|
476
|
+
- **256-palette:** `0`–`255`
|
|
477
|
+
|
|
478
|
+
Text on colored backgrounds automatically picks black or white for contrast when no explicit color is set.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## `render(element, options?)`
|
|
483
|
+
|
|
484
|
+
Mount a React element to the terminal.
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
const app = render(<App />, {
|
|
488
|
+
stdout: process.stdout,
|
|
489
|
+
stdin: process.stdin,
|
|
490
|
+
debug: false,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
app.unmount(); // Tear down
|
|
494
|
+
app.exit(); // Unmount and exit process
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Examples
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
# Clone and install
|
|
503
|
+
git clone <repo-url> && cd glyph
|
|
504
|
+
pnpm install && pnpm build
|
|
505
|
+
|
|
506
|
+
# Run examples
|
|
507
|
+
pnpm --filter basic-layout dev # Flexbox layout demo
|
|
508
|
+
pnpm --filter modal-input dev # Modal, input, focus trapping
|
|
509
|
+
pnpm --filter scrollview-demo dev # Scrollable content
|
|
510
|
+
pnpm --filter list-demo dev # Keyboard-navigable list
|
|
511
|
+
pnpm --filter menu-demo dev # Styled menu
|
|
512
|
+
pnpm --filter select-demo dev # Dropdown select with search
|
|
513
|
+
pnpm --filter forms-demo dev # Checkbox and Radio inputs
|
|
514
|
+
pnpm --filter dashboard dev # Full task manager (all components)
|
|
515
|
+
pnpm --filter showcase dev # Progress, Spinner, Toasts
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Architecture
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
src/
|
|
524
|
+
├── reconciler/ React reconciler (host config + GlyphNode tree)
|
|
525
|
+
├── layout/ Yoga-based flexbox + text measurement
|
|
526
|
+
├── paint/ Framebuffer, character diffing, borders, colors
|
|
527
|
+
├── runtime/ Terminal raw mode, key parsing, OSC handling
|
|
528
|
+
├── components/ Box, Text, Input, Button, ScrollView, List, Menu, ...
|
|
529
|
+
├── hooks/ useInput, useFocus, useLayout, useApp
|
|
530
|
+
└── render.ts Entry point tying it all together
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Render pipeline:** React reconciler builds a GlyphNode tree → Yoga computes flexbox layout → painter rasterizes to a framebuffer → diff engine writes only changed cells to stdout.
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## License
|
|
538
|
+
|
|
539
|
+
MIT
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
<p align="center">
|
|
544
|
+
<sub>Built with React • Yoga • a lot of ANSI escape codes</sub>
|
|
545
|
+
</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nick-skriabin/glyph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A React renderer for terminal UIs with flexbox layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"files": [
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
22
24
|
"scripts": {
|
|
23
25
|
"build": "tsup",
|
|
24
26
|
"dev": "tsup --watch",
|
|
@@ -40,7 +42,13 @@
|
|
|
40
42
|
"tsup": "^8",
|
|
41
43
|
"typescript": "^5.5"
|
|
42
44
|
},
|
|
43
|
-
"keywords": [
|
|
45
|
+
"keywords": [
|
|
46
|
+
"react",
|
|
47
|
+
"terminal",
|
|
48
|
+
"tui",
|
|
49
|
+
"cli",
|
|
50
|
+
"flexbox"
|
|
51
|
+
],
|
|
44
52
|
"license": "MIT",
|
|
45
53
|
"repository": {
|
|
46
54
|
"type": "git",
|