1ch 0.1.0 → 0.3.0
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 +87 -185
- package/dist/index.d.ts +280 -15
- package/dist/index.js +650 -83
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
**Terminal UI components for React.** For those who'd rather be in the terminal.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The smallest unit is one character cell. A button is `[ OK ]`. A progress bar is `████░░░░`. A table border is `│` and `─` and `┼`. The whole thing is a grid.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install 1ch react react-dom
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What it looks like
|
|
8
14
|
|
|
9
15
|
```tsx
|
|
10
16
|
import { TermUI, TBox, TVStack, TTable, TBar } from "1ch";
|
|
@@ -19,9 +25,9 @@ function Dashboard() {
|
|
|
19
25
|
<TBar label="MEM" value={4.2} max={8} />
|
|
20
26
|
<TTable
|
|
21
27
|
columns={[
|
|
22
|
-
{ key: "service",
|
|
23
|
-
{ key: "status",
|
|
24
|
-
{ key: "uptime",
|
|
28
|
+
{ key: "service", width: 20 },
|
|
29
|
+
{ key: "status", width: 10 },
|
|
30
|
+
{ key: "uptime", width: 12 },
|
|
25
31
|
]}
|
|
26
32
|
data={[
|
|
27
33
|
{ service: "api-gateway", status: "UP", uptime: "14d 3h" },
|
|
@@ -36,70 +42,93 @@ function Dashboard() {
|
|
|
36
42
|
}
|
|
37
43
|
```
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
<!-- TODO: Add screenshot of the above rendering -->
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
- **React-native.** Not a wrapper around a terminal emulator. Custom React reconciler that turns a component tree into character grids. React handles lifecycle, hooks, state. The reconciler handles layout.
|
|
43
|
-
- **Responsive.** All layout is lazy - functions of width, not pre-computed blocks. Resize the container and everything reflows.
|
|
44
|
-
- **Themeable.** Full token system: palette, semantic, syntax, markdown, component colors. Light/dark/system. Runtime-switchable. End-user JSON theming with validation.
|
|
45
|
-
- **Three input formats.** Render React components, or feed it markdown, HTML, or JSON and get terminal layouts back.
|
|
47
|
+
## Why this exists
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
Most UI components are styled `<div>`s pretending to be something else. Terminal UIs skip the pretense - every character is either content or structure, and the grid makes boundaries obvious.
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
npm install 1ch react react-dom
|
|
51
|
-
```
|
|
51
|
+
Under the hood, 1ch uses a custom React reconciler that turns your component tree into character grids. React handles lifecycle, hooks, state. The reconciler handles layout. Resize the container and everything reflows.
|
|
52
52
|
|
|
53
53
|
## Components
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
**Layout** - `TVStack`, `THStack`, `TBox`, `TSeparator`, `TBlank`, `TLine`, `TSpan`
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
**Data** - `TTable`, `TTree`, `TList`, `TCode`, `TUnifiedDiff`, `TDiffCompute`, `TBar`, `TSpark`
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
| ------------ | ---------------------------------------------------- |
|
|
61
|
-
| `TVStack` | Vertical stack with optional `gap` |
|
|
62
|
-
| `THStack` | Horizontal stack with optional `gap` and `widths` |
|
|
63
|
-
| `TBox` | Bordered box with optional `title` and `borderColor` |
|
|
64
|
-
| `TSeparator` | Horizontal line |
|
|
65
|
-
| `TBlank` | Empty line |
|
|
66
|
-
| `TLine` | Single-line container |
|
|
67
|
-
| `TSpan` | Inline styled text |
|
|
59
|
+
**Interactive** - `TTabs`, `TButton`, `TStatusbar`
|
|
68
60
|
|
|
69
|
-
|
|
61
|
+
**Documents** - `TMarkdown`, `THtml`, `TJson`
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
| --------- | ---------------------------------------------------------- |
|
|
73
|
-
| `TTable` | Bordered table with column widths, wrapping, header colors |
|
|
74
|
-
| `TTree` | Nested tree view with `├─` and `└─` branch characters |
|
|
75
|
-
| `TList` | Bulleted or numbered list |
|
|
76
|
-
| `TCode` | Syntax-highlighted code block in a box |
|
|
77
|
-
| `TDiff` | Colored diff output |
|
|
78
|
-
| `TBar` | Progress bar (`████░░░░`) |
|
|
79
|
-
| `TSpark` | Sparkline (`▁▂▃▄▅▆▇█`) |
|
|
63
|
+
The document components accept raw strings. Feed `TMarkdown` a markdown string, `THtml` an HTML string, or `TJson` a JSON object, and you get terminal-styled output back.
|
|
80
64
|
|
|
81
|
-
|
|
65
|
+
## A more complete example
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| `TButton` | Clickable styled button |
|
|
87
|
-
| `TStatusbar` | Footer bar with left/right content |
|
|
67
|
+
```tsx
|
|
68
|
+
import { TermUI, TBox, TCode, TTabs, TTab, TTable, TBar, TVStack } from "1ch";
|
|
69
|
+
import "1ch/style.css";
|
|
88
70
|
|
|
89
|
-
|
|
71
|
+
function App() {
|
|
72
|
+
const [tab, setTab] = useState(0);
|
|
90
73
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
return (
|
|
75
|
+
<TermUI width={80}>
|
|
76
|
+
<TTabs active={tab} onSelect={setTab}>
|
|
77
|
+
<TTab name="Overview">
|
|
78
|
+
<TBox title="Status">
|
|
79
|
+
<TVStack>
|
|
80
|
+
<TBar label="CPU" value={73} max={100} />
|
|
81
|
+
<TBar label="MEM" value={4.2} max={8} />
|
|
82
|
+
</TVStack>
|
|
83
|
+
</TBox>
|
|
84
|
+
</TTab>
|
|
96
85
|
|
|
97
|
-
|
|
86
|
+
<TTab name="Logs">
|
|
87
|
+
<TTable
|
|
88
|
+
columns={[
|
|
89
|
+
{ key: "time", width: 12 },
|
|
90
|
+
{ key: "level", width: 8 },
|
|
91
|
+
{ key: "message", width: 40 },
|
|
92
|
+
]}
|
|
93
|
+
data={[
|
|
94
|
+
{
|
|
95
|
+
time: "14:03:21",
|
|
96
|
+
level: "INFO",
|
|
97
|
+
message: "Server started on :3000",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
time: "14:03:22",
|
|
101
|
+
level: "INFO",
|
|
102
|
+
message: "Connected to database",
|
|
103
|
+
},
|
|
104
|
+
{ time: "14:05:01", level: "WARN", message: "Slow query: 2.3s" },
|
|
105
|
+
]}
|
|
106
|
+
/>
|
|
107
|
+
</TTab>
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
<TTab name="Config">
|
|
110
|
+
<TCode
|
|
111
|
+
code={`export default {
|
|
112
|
+
port: 3000,
|
|
113
|
+
database: "postgres://localhost/app",
|
|
114
|
+
cache: { ttl: 300, max: 1000 },
|
|
115
|
+
};`}
|
|
116
|
+
language="typescript"
|
|
117
|
+
title="config.ts"
|
|
118
|
+
/>
|
|
119
|
+
</TTab>
|
|
120
|
+
</TTabs>
|
|
121
|
+
</TermUI>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Don't want React? Skip it.
|
|
127
|
+
|
|
128
|
+
Every component has an imperative equivalent that returns a layout function. Call it with a width and get a character grid back.
|
|
100
129
|
|
|
101
130
|
```typescript
|
|
102
|
-
import { box, vstack, table, code, bar
|
|
131
|
+
import { box, vstack, table, code, bar } from "1ch";
|
|
103
132
|
|
|
104
133
|
const layout = vstack(
|
|
105
134
|
box(
|
|
@@ -121,57 +150,12 @@ const layout = vstack(
|
|
|
121
150
|
code(`console.log("hello")`, { language: "javascript" })
|
|
122
151
|
);
|
|
123
152
|
|
|
124
|
-
// Materialize at any width
|
|
125
|
-
const block: Block = layout(80);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Available builders
|
|
129
|
-
|
|
130
|
-
**Layout:** `box`, `hstack`, `vstack`, `separator`, `blank`, `lines`
|
|
131
|
-
|
|
132
|
-
**Data:** `table`, `tree`, `list`, `code`, `diff`
|
|
133
|
-
|
|
134
|
-
**Widgets:** `tabs`, `statusbar`, `badge`, `bar`, `spark`
|
|
135
|
-
|
|
136
|
-
**Text:** `pad`, `hl`, `padLine`
|
|
137
|
-
|
|
138
|
-
## Document Pipeline
|
|
139
|
-
|
|
140
|
-
Feed it markdown, HTML, or JSON. Get terminal layouts back.
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
import { TermDocument } from "1ch";
|
|
144
|
-
|
|
145
|
-
// Markdown
|
|
146
|
-
<TermDocument
|
|
147
|
-
source={{ format: "markdown", value: "# Hello\n\nSome **bold** text." }}
|
|
148
|
-
width={60}
|
|
149
|
-
/>
|
|
150
|
-
|
|
151
|
-
// HTML
|
|
152
|
-
<TermDocument
|
|
153
|
-
source={{ format: "html", value: "<h1>Hello</h1><p>Some text.</p>" }}
|
|
154
|
-
width={60}
|
|
155
|
-
/>
|
|
156
|
-
|
|
157
|
-
// Or use the functions directly
|
|
158
|
-
import { fromMarkdown, fromHtml, fromJson } from "1ch";
|
|
159
|
-
|
|
160
|
-
const layout = fromMarkdown("# Hello\n\n- one\n- two", { theme });
|
|
161
153
|
const block = layout(80);
|
|
162
154
|
```
|
|
163
155
|
|
|
164
|
-
|
|
156
|
+
## Theming
|
|
165
157
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
`<term>` acts as an optional root - if present, only its subtree renders.
|
|
169
|
-
|
|
170
|
-
Color attributes: `color`, `data-color`, `marker-color`, `header-color`, `border-color`, `text-color`. Values can be theme keys (`yellow`, `cyan`) or CSS colors (`#56b6c2`).
|
|
171
|
-
|
|
172
|
-
## Theme System
|
|
173
|
-
|
|
174
|
-
Two built-in themes (dark and light), runtime-switchable, fully customizable via JSON.
|
|
158
|
+
Dark and light themes built in, and are switchable at runtime. You can override everything via JSON - palette, semantic colors, syntax highlighting, markdown rendering, component tokens. Use `parseThemeSpec()` to check the JSON before applying it.
|
|
175
159
|
|
|
176
160
|
```tsx
|
|
177
161
|
import {
|
|
@@ -184,96 +168,14 @@ import {
|
|
|
184
168
|
const parsed = parseThemeSpec(userThemeJson);
|
|
185
169
|
const spec = parsed.ok ? parsed.theme : defaultThemeSpec;
|
|
186
170
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<TermUI width={80}>{/* components inherit the theme */}</TermUI>
|
|
191
|
-
</TermThemeProvider>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
A `ThemeSpec` defines both light and dark modes. Each mode has:
|
|
197
|
-
|
|
198
|
-
- **Palette** - 10 ANSI-like colors (black, red, green, yellow, blue, magenta, cyan, white, brightBlack, brightWhite)
|
|
199
|
-
- **Semantic** - UI intent (border, info, success, warn, danger, frameBg, frameFg, etc.)
|
|
200
|
-
- **Syntax** - Code highlighting (keyword, string, number, comment, function, type, punctuation)
|
|
201
|
-
- **Markdown** - Document rendering (headings h1-h6, paragraph, lists, tables, quotes, code)
|
|
202
|
-
- **Components** - Widget colors (tabs, statusbar, badge, button)
|
|
203
|
-
|
|
204
|
-
Use `validateThemeSpec()` to check user-provided JSON before applying it.
|
|
205
|
-
|
|
206
|
-
## Hooks
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import { useSpinner, useTick, useTermWidth, useStreamingText } from "1ch";
|
|
210
|
-
|
|
211
|
-
// Animated braille spinner
|
|
212
|
-
const spinner = useSpinner(80);
|
|
213
|
-
|
|
214
|
-
// Interval counter for animations
|
|
215
|
-
const tick = useTick(500);
|
|
216
|
-
|
|
217
|
-
// Measure container width in character units (uses ResizeObserver)
|
|
218
|
-
const ref = useRef(null);
|
|
219
|
-
const width = useTermWidth(ref, 80);
|
|
220
|
-
|
|
221
|
-
// Streaming text with lerp (detects appends, doesn't restart)
|
|
222
|
-
const displayed = useStreamingText(streamingText, 0.08);
|
|
171
|
+
<TermThemeProvider initialTheme={spec} initialMode="system">
|
|
172
|
+
<TermUI width={80}>{/* components inherit the theme */}</TermUI>
|
|
173
|
+
</TermThemeProvider>;
|
|
223
174
|
```
|
|
224
175
|
|
|
225
|
-
##
|
|
226
|
-
|
|
227
|
-
```tsx
|
|
228
|
-
import { TermUI, TBox, TCode, TTabs, TTab, TTable, TBar, TVStack } from "1ch";
|
|
229
|
-
import "1ch/style.css";
|
|
230
|
-
|
|
231
|
-
function App() {
|
|
232
|
-
const [tab, setTab] = useState(0);
|
|
233
|
-
|
|
234
|
-
return (
|
|
235
|
-
<TermUI width={80}>
|
|
236
|
-
<TTabs active={tab} onSelect={setTab}>
|
|
237
|
-
<TTab name="Overview">
|
|
238
|
-
<TBox title="Status">
|
|
239
|
-
<TVStack>
|
|
240
|
-
<TBar label="CPU" value={73} max={100} />
|
|
241
|
-
<TBar label="MEM" value={4.2} max={8} />
|
|
242
|
-
</TVStack>
|
|
243
|
-
</TBox>
|
|
244
|
-
</TTab>
|
|
245
|
-
|
|
246
|
-
<TTab name="Logs">
|
|
247
|
-
<TTable
|
|
248
|
-
columns={[
|
|
249
|
-
{ key: "time", header: "Time", width: 12 },
|
|
250
|
-
{ key: "level", header: "Level", width: 8 },
|
|
251
|
-
{ key: "message", header: "Message", width: 40 },
|
|
252
|
-
]}
|
|
253
|
-
data={[
|
|
254
|
-
{ time: "14:03:21", level: "INFO", message: "Server started on :3000" },
|
|
255
|
-
{ time: "14:03:22", level: "INFO", message: "Connected to database" },
|
|
256
|
-
{ time: "14:05:01", level: "WARN", message: "Slow query: 2.3s" },
|
|
257
|
-
]}
|
|
258
|
-
/>
|
|
259
|
-
</TTab>
|
|
176
|
+
## Docs
|
|
260
177
|
|
|
261
|
-
|
|
262
|
-
<TCode
|
|
263
|
-
code={`export default {
|
|
264
|
-
port: 3000,
|
|
265
|
-
database: "postgres://localhost/app",
|
|
266
|
-
cache: { ttl: 300, max: 1000 },
|
|
267
|
-
};`}
|
|
268
|
-
language="typescript"
|
|
269
|
-
title="config.ts"
|
|
270
|
-
/>
|
|
271
|
-
</TTab>
|
|
272
|
-
</TTabs>
|
|
273
|
-
</TermUI>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
```
|
|
178
|
+
Full API reference, theme spec details, hooks (`useSpinner`, `useTick`, `useTermWidth`, `useStreamingText`), and the document pipeline - all in the [docs](1ch.app) (coming soon).
|
|
277
179
|
|
|
278
180
|
## License
|
|
279
181
|
|