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 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
- Every UI framework treats the browser as a canvas of infinite resolution. This one doesn't. The smallest unit is one character cell. A button is `[ OK ]`. A progress bar is `████░░░░`. A table border is `│` and `─` and `┼`.
5
+ ![1ch dashboard example](https://raw.githubusercontent.com/twoabove/1ch/main/demo/assets/demo.png)
6
6
 
7
- The web primitives that make it work are almost embarrassingly simple: a monospace font, `white-space: pre`, and `ch` units. That's the entire rendering engine.
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", header: "Service", width: 20 },
23
- { key: "status", header: "Status", width: 10 },
24
- { key: "uptime", header: "Uptime", width: 12 },
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
- ## Why
45
+ <!-- TODO: Add screenshot of the above rendering -->
40
46
 
41
- - **Dense.** Every character is content or structure. Nothing is decorative. You can always tell where one element ends and another begins because the grid enforces it.
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
- ## Installation
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
- ```bash
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
- React components that render through a custom reconciler. No DOM elements - they produce `LayoutFn` internally.
55
+ **Layout** - `TVStack`, `THStack`, `TBox`, `TSeparator`, `TBlank`, `TLine`, `TSpan`
56
56
 
57
- ### Layout
57
+ **Data** - `TTable`, `TTree`, `TList`, `TCode`, `TUnifiedDiff`, `TDiffCompute`, `TBar`, `TSpark`
58
58
 
59
- | Component | What it does |
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
- ### Data
61
+ **Documents** - `TMarkdown`, `THtml`, `TJson`
70
62
 
71
- | Component | What it does |
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
- ### Interactive
65
+ ## A more complete example
82
66
 
83
- | Component | What it does |
84
- | ------------ | ---------------------------------- |
85
- | `TTabs` | Clickable tab bar with `onSelect` |
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
- ### Documents
71
+ function App() {
72
+ const [tab, setTab] = useState(0);
90
73
 
91
- | Component | What it does |
92
- | ----------- | ------------------------------------------ |
93
- | `TMarkdown` | Renders markdown string as terminal layout |
94
- | `THtml` | Renders HTML string as terminal layout |
95
- | `TJson` | Renders structured JSON as terminal layout |
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
- ## Imperative API
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
- If you don't want React components, every layout primitive has a function equivalent that returns a `LayoutFn`:
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, hstack } from "1ch";
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
- ### HTML support
156
+ ## Theming
165
157
 
166
- Supported tags: `h1`-`h6`, `p`, `ul`, `ol`, `li`, `table` (with `thead`/`tbody`/`tr`/`th`/`td`), `blockquote`, `hr`, `pre`, `div`, `section`, `article`, `main`, `header`, `footer`, `nav`.
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
- function App() {
188
- return (
189
- <TermThemeProvider initialTheme={spec} initialMode="system">
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
- ## Putting It Together
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
- <TTab name="Config">
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