silvery 0.3.0 → 0.4.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 +41 -145
- package/package.json +64 -12
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- package/examples/web/xterm.html +0 -118
package/examples/cli.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* silvery demo CLI
|
|
4
|
-
*
|
|
5
|
-
* Lists and runs interactive demos from the examples/ directory.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* bun demo — list all available demos grouped by category
|
|
9
|
-
* bun demo <name> — run a specific demo by name (fuzzy match)
|
|
10
|
-
* bun demo --list — same as no argument (list all)
|
|
11
|
-
* bun demo --help — show usage help
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { resolve } from "node:path"
|
|
15
|
-
import type { ExampleMeta } from "./_banner.js"
|
|
16
|
-
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// Types
|
|
19
|
-
// =============================================================================
|
|
20
|
-
|
|
21
|
-
interface Example {
|
|
22
|
-
name: string
|
|
23
|
-
file: string
|
|
24
|
-
description: string
|
|
25
|
-
category: string
|
|
26
|
-
features?: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// =============================================================================
|
|
30
|
-
// Auto-Discovery (matches viewer.tsx pattern)
|
|
31
|
-
// =============================================================================
|
|
32
|
-
|
|
33
|
-
const CATEGORY_DIRS = ["layout", "interactive", "runtime", "inline", "kitty"] as const
|
|
34
|
-
|
|
35
|
-
const CATEGORY_DISPLAY: Record<string, string> = {
|
|
36
|
-
kitty: "Kitty Protocol",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const CATEGORY_ORDER: Record<string, number> = {
|
|
40
|
-
Layout: 0,
|
|
41
|
-
Interactive: 1,
|
|
42
|
-
Runtime: 2,
|
|
43
|
-
Inline: 3,
|
|
44
|
-
"Kitty Protocol": 4,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function discoverExamples(): Promise<Example[]> {
|
|
48
|
-
const baseDir = new URL(".", import.meta.url).pathname
|
|
49
|
-
const results: Example[] = []
|
|
50
|
-
|
|
51
|
-
for (const dir of CATEGORY_DIRS) {
|
|
52
|
-
const category = CATEGORY_DISPLAY[dir] ?? dir.charAt(0).toUpperCase() + dir.slice(1)
|
|
53
|
-
const dirPath = resolve(baseDir, dir)
|
|
54
|
-
const files = [
|
|
55
|
-
...new Bun.Glob("*.tsx").scanSync({ cwd: dirPath }),
|
|
56
|
-
...new Bun.Glob("*/index.tsx").scanSync({ cwd: dirPath }),
|
|
57
|
-
]
|
|
58
|
-
|
|
59
|
-
for (const file of files) {
|
|
60
|
-
if (file.startsWith("_")) continue // skip internal files
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const mod = await import(resolve(dirPath, file))
|
|
64
|
-
if (!mod.meta?.name) continue
|
|
65
|
-
|
|
66
|
-
const meta: ExampleMeta = mod.meta
|
|
67
|
-
results.push({
|
|
68
|
-
name: meta.name,
|
|
69
|
-
description: meta.description ?? "",
|
|
70
|
-
file: resolve(dirPath, file),
|
|
71
|
-
category,
|
|
72
|
-
features: meta.features,
|
|
73
|
-
})
|
|
74
|
-
} catch {
|
|
75
|
-
// Skip files that fail to import
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
results.sort((a, b) => {
|
|
81
|
-
const catDiff = (CATEGORY_ORDER[a.category] ?? 99) - (CATEGORY_ORDER[b.category] ?? 99)
|
|
82
|
-
if (catDiff !== 0) return catDiff
|
|
83
|
-
return a.name.localeCompare(b.name)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
return results
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// =============================================================================
|
|
90
|
-
// Formatting Helpers
|
|
91
|
-
// =============================================================================
|
|
92
|
-
|
|
93
|
-
// ANSI color codes for lightweight terminal output without importing silvery
|
|
94
|
-
const RESET = "\x1b[0m"
|
|
95
|
-
const BOLD = "\x1b[1m"
|
|
96
|
-
const DIM = "\x1b[2m"
|
|
97
|
-
const YELLOW = "\x1b[33m"
|
|
98
|
-
const CYAN = "\x1b[36m"
|
|
99
|
-
const GREEN = "\x1b[32m"
|
|
100
|
-
const MAGENTA = "\x1b[35m"
|
|
101
|
-
const BLUE = "\x1b[34m"
|
|
102
|
-
const RED = "\x1b[31m"
|
|
103
|
-
const WHITE = "\x1b[37m"
|
|
104
|
-
|
|
105
|
-
const CATEGORY_COLOR_CODE: Record<string, string> = {
|
|
106
|
-
Layout: MAGENTA,
|
|
107
|
-
Interactive: CYAN,
|
|
108
|
-
Runtime: GREEN,
|
|
109
|
-
Inline: YELLOW,
|
|
110
|
-
"Kitty Protocol": BLUE,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function printHelp(): void {
|
|
114
|
-
console.log(`
|
|
115
|
-
${BOLD}${YELLOW}silvery demo${RESET} — browse and run interactive examples
|
|
116
|
-
|
|
117
|
-
${BOLD}Usage:${RESET}
|
|
118
|
-
bun demo List all available demos
|
|
119
|
-
bun demo ${DIM}<name>${RESET} Run a demo by name (case-insensitive, partial match)
|
|
120
|
-
bun demo --list List all available demos
|
|
121
|
-
bun demo --help Show this help
|
|
122
|
-
|
|
123
|
-
${BOLD}Examples:${RESET}
|
|
124
|
-
bun demo dashboard Run the Dashboard demo
|
|
125
|
-
bun demo kanban Run the Kanban Board demo
|
|
126
|
-
bun demo scroll Run the first demo matching "scroll"
|
|
127
|
-
`)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function printExampleList(examples: Example[]): void {
|
|
131
|
-
console.log(`\n${BOLD}${YELLOW} silvery${RESET}${DIM} examples${RESET}\n`)
|
|
132
|
-
|
|
133
|
-
let currentCategory = ""
|
|
134
|
-
|
|
135
|
-
for (const ex of examples) {
|
|
136
|
-
if (ex.category !== currentCategory) {
|
|
137
|
-
currentCategory = ex.category
|
|
138
|
-
const color = CATEGORY_COLOR_CODE[currentCategory] ?? WHITE
|
|
139
|
-
console.log(` ${color}${BOLD}${currentCategory}${RESET}`)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const nameStr = `${BOLD}${WHITE}${ex.name}${RESET}`
|
|
143
|
-
const descStr = `${DIM}${ex.description}${RESET}`
|
|
144
|
-
console.log(` ${nameStr} ${descStr}`)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
console.log(`\n ${DIM}Run a demo: bun demo <name>${RESET}\n`)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Find an example by name. Tries exact match first, then case-insensitive
|
|
151
|
-
* prefix match, then case-insensitive substring match. */
|
|
152
|
-
function findExample(examples: Example[], query: string): Example | undefined {
|
|
153
|
-
const q = query.toLowerCase()
|
|
154
|
-
|
|
155
|
-
// Exact match (case-insensitive)
|
|
156
|
-
const exact = examples.find((ex) => ex.name.toLowerCase() === q)
|
|
157
|
-
if (exact) return exact
|
|
158
|
-
|
|
159
|
-
// Prefix match (case-insensitive)
|
|
160
|
-
const prefix = examples.find((ex) => ex.name.toLowerCase().startsWith(q))
|
|
161
|
-
if (prefix) return prefix
|
|
162
|
-
|
|
163
|
-
// Substring match (case-insensitive)
|
|
164
|
-
const substring = examples.find((ex) => ex.name.toLowerCase().includes(q))
|
|
165
|
-
if (substring) return substring
|
|
166
|
-
|
|
167
|
-
return undefined
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function printNoMatch(query: string, examples: Example[]): void {
|
|
171
|
-
console.error(`\n${RED}${BOLD}Error:${RESET} No demo matching "${query}"\n`)
|
|
172
|
-
console.error(`${DIM}Available demos:${RESET}`)
|
|
173
|
-
|
|
174
|
-
for (const ex of examples) {
|
|
175
|
-
console.error(` ${WHITE}${ex.name}${RESET}`)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
console.error(`\n${DIM}Run ${BOLD}bun demo${RESET}${DIM} for full list with descriptions.${RESET}\n`)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// =============================================================================
|
|
182
|
-
// Main
|
|
183
|
-
// =============================================================================
|
|
184
|
-
|
|
185
|
-
async function main(): Promise<void> {
|
|
186
|
-
const args = process.argv.slice(2)
|
|
187
|
-
|
|
188
|
-
// Handle flags
|
|
189
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
190
|
-
printHelp()
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const examples = await discoverExamples()
|
|
195
|
-
|
|
196
|
-
// No argument or --list: show the list
|
|
197
|
-
if (args.length === 0 || args[0] === "--list") {
|
|
198
|
-
printExampleList(examples)
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Treat all non-flag arguments as the demo name query
|
|
203
|
-
const query = args.filter((a) => !a.startsWith("--")).join(" ")
|
|
204
|
-
if (!query) {
|
|
205
|
-
printExampleList(examples)
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const match = findExample(examples, query)
|
|
210
|
-
if (!match) {
|
|
211
|
-
printNoMatch(query, examples)
|
|
212
|
-
process.exit(1)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Run the matched example
|
|
216
|
-
console.log(`${DIM}Running ${BOLD}${match.name}${RESET}${DIM}...${RESET}\n`)
|
|
217
|
-
|
|
218
|
-
const proc = Bun.spawn(["bun", "run", match.file], {
|
|
219
|
-
stdio: ["inherit", "inherit", "inherit"],
|
|
220
|
-
})
|
|
221
|
-
const exitCode = await proc.exited
|
|
222
|
-
process.exit(exitCode)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
main().catch((err) => {
|
|
226
|
-
console.error(err)
|
|
227
|
-
process.exit(1)
|
|
228
|
-
})
|
package/examples/index.md
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# Silvery Examples
|
|
2
|
-
|
|
3
|
-
Interactive examples demonstrating Silvery features. Organized by category.
|
|
4
|
-
|
|
5
|
-
## Running Examples
|
|
6
|
-
|
|
7
|
-
Browse all examples in the interactive viewer:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
bun examples
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Run any example standalone:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
bun examples/<category>/<name>.tsx
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Structure
|
|
20
|
-
|
|
21
|
-
Examples are organized into category directories. Each exports a `meta` object
|
|
22
|
-
with `name` and `description`. The viewer auto-discovers all examples — no
|
|
23
|
-
registry to maintain.
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
examples/
|
|
27
|
-
_banner.tsx # Shared banner component (not an example)
|
|
28
|
-
viewer.tsx # Interactive example browser
|
|
29
|
-
layout/ # Layout and responsive design
|
|
30
|
-
interactive/ # Keyboard-driven interactive apps
|
|
31
|
-
kitty/ # Kitty protocol features (graphics, keyboard)
|
|
32
|
-
runtime/ # Runtime layer demos (Layer 1-3)
|
|
33
|
-
inline/ # Inline mode and scrollback
|
|
34
|
-
playground/ # Web playground (not an example)
|
|
35
|
-
screenshots/ # Screenshot generation tool
|
|
36
|
-
web/ # Web render targets (canvas, DOM)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Layout
|
|
40
|
-
|
|
41
|
-
| Example | File | Description |
|
|
42
|
-
| ----------- | ------------------------ | --------------------------------------------- |
|
|
43
|
-
| Dashboard | `layout/dashboard.tsx` | Multi-pane dashboard with keyboard navigation |
|
|
44
|
-
| Live Resize | `layout/live-resize.tsx` | Responsive columns via `useContentRect()` |
|
|
45
|
-
| Overflow | `layout/overflow.tsx` | `overflow="hidden"` content clipping |
|
|
46
|
-
|
|
47
|
-
## Interactive
|
|
48
|
-
|
|
49
|
-
| Example | File | Description |
|
|
50
|
-
| --------------- | ------------------------------- | ---------------------------------------------- |
|
|
51
|
-
| AI Coding Agent | `interactive/aichat/` | Coding agent with streaming, tool calls |
|
|
52
|
-
| Todo App | `interactive/app-todo.tsx` | Layer 3: `createApp()` with Zustand store |
|
|
53
|
-
| Async Data | `interactive/async-data.tsx` | Suspense boundaries with `use()` hook |
|
|
54
|
-
| Kanban | `interactive/kanban.tsx` | Multi-column kanban with card movement |
|
|
55
|
-
| Layout Ref | `interactive/layout-ref.tsx` | `forwardRef` + `onLayout` callbacks |
|
|
56
|
-
| Scroll | `interactive/scroll.tsx` | Basic scrollable list |
|
|
57
|
-
| Search Filter | `interactive/search-filter.tsx` | React concurrent features (`useDeferredValue`) |
|
|
58
|
-
| Task List | `interactive/task-list.tsx` | VirtualList with variable-height items |
|
|
59
|
-
| TextArea | `interactive/textarea.tsx` | Multi-line text input component |
|
|
60
|
-
| Virtual 10K | `interactive/virtual-10k.tsx` | VirtualList with 10,000 items |
|
|
61
|
-
| Clipboard | `interactive/clipboard.tsx` | OSC 52 clipboard copy/paste across sessions |
|
|
62
|
-
| Paste Demo | `interactive/paste-demo.tsx` | Bracketed paste mode — paste as single event |
|
|
63
|
-
| Outline | `interactive/outline.tsx` | Outline vs border side-by-side comparison |
|
|
64
|
-
| Transform | `interactive/transform.tsx` | Text post-processing with Transform component |
|
|
65
|
-
|
|
66
|
-
## Kitty Protocol
|
|
67
|
-
|
|
68
|
-
| Example | File | Description |
|
|
69
|
-
| --------------- | --------------------------- | ------------------------------------------------ |
|
|
70
|
-
| Image Viewer | `kitty/images.tsx` | Raw Kitty graphics protocol image display |
|
|
71
|
-
| Image Component | `kitty/image-component.tsx` | Declarative `<Image>` with protocol auto-detect |
|
|
72
|
-
| Key Events | `kitty/keys.tsx` | Interactive key chord tester with Kitty protocol |
|
|
73
|
-
| Input | `kitty/input.tsx` | Kitty keyboard input demonstration |
|
|
74
|
-
| Canvas | `kitty/canvas.tsx` | Canvas rendering via Kitty graphics |
|
|
75
|
-
| Paint | `kitty/paint.tsx` | Terminal paint app using Kitty graphics |
|
|
76
|
-
|
|
77
|
-
## Runtime
|
|
78
|
-
|
|
79
|
-
| Example | File | Description |
|
|
80
|
-
| --------------- | ----------------------------- | ------------------------------------------------ |
|
|
81
|
-
| Elm Counter | `runtime/elm-counter.tsx` | Layer 1: `createRuntime()` with Elm architecture |
|
|
82
|
-
| Hello Runtime | `runtime/hello-runtime.tsx` | Layer 1: minimal static render |
|
|
83
|
-
| Run Counter | `runtime/run-counter.tsx` | Layer 2: `run()` with React hooks |
|
|
84
|
-
| Runtime Counter | `runtime/runtime-counter.tsx` | Layer 1: `createRuntime()` with event loop |
|
|
85
|
-
|
|
86
|
-
## Inline
|
|
87
|
-
|
|
88
|
-
| Example | File | Description |
|
|
89
|
-
| --------------- | ---------------------------- | --------------------------------------------------- |
|
|
90
|
-
| Inline Simple | `inline/inline-simple.tsx` | Basic inline rendering |
|
|
91
|
-
| Inline Progress | `inline/inline-progress.tsx` | Inline progress bar |
|
|
92
|
-
| Inline Non-TTY | `inline/inline-nontty.tsx` | Inline output for piped/non-TTY |
|
|
93
|
-
| Scrollback | `inline/scrollback.tsx` | REPL with `useScrollback` + VirtualList virtualized |
|
|
94
|
-
|
|
95
|
-
## Creating New Examples
|
|
96
|
-
|
|
97
|
-
1. Add a `.tsx` file in the appropriate category directory
|
|
98
|
-
2. Export a `meta` object: `export const meta: ExampleMeta = { name: "...", description: "..." }`
|
|
99
|
-
3. Export your main component as a named function
|
|
100
|
-
4. Wrap with `ExampleBanner` in the `import.meta.main` block for standalone mode
|
|
101
|
-
5. The viewer discovers it automatically — no registry to update
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
/**
|
|
3
|
-
* Example: Non-TTY Mode Support (km-silvery-nontty)
|
|
4
|
-
*
|
|
5
|
-
* Demonstrates silvery's non-TTY mode support for rendering in environments
|
|
6
|
-
* without a terminal (pipes, CI, TERM=dumb).
|
|
7
|
-
*
|
|
8
|
-
* Run this example:
|
|
9
|
-
* # Normal TTY mode
|
|
10
|
-
* bun examples/inline-nontty.tsx
|
|
11
|
-
*
|
|
12
|
-
* # Piped output (auto-detects non-TTY)
|
|
13
|
-
* bun examples/inline-nontty.tsx | cat
|
|
14
|
-
*
|
|
15
|
-
* # Force plain text mode
|
|
16
|
-
* SILVERY_NONTTY=plain bun examples/inline-nontty.tsx
|
|
17
|
-
*
|
|
18
|
-
* # Force line-by-line mode
|
|
19
|
-
* SILVERY_NONTTY=line-by-line bun examples/inline-nontty.tsx
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import React, { useEffect, useState } from "react"
|
|
23
|
-
import { Box, render, Text, useApp, createTerm, type NonTTYMode } from "../../src/index.js"
|
|
24
|
-
import type { ExampleMeta } from "../_banner.js"
|
|
25
|
-
|
|
26
|
-
export const meta: ExampleMeta = {
|
|
27
|
-
name: "Non-TTY Mode",
|
|
28
|
-
description: "Graceful degradation for pipes, CI, and TERM=dumb",
|
|
29
|
-
features: ["renderString()", "non-TTY output", "pipe-safe"],
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function ProgressExample() {
|
|
33
|
-
const { exit } = useApp()
|
|
34
|
-
const [progress, setProgress] = useState(0)
|
|
35
|
-
const [done, setDone] = useState(false)
|
|
36
|
-
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
const timer = setInterval(() => {
|
|
39
|
-
setProgress((prev) => {
|
|
40
|
-
const next = prev + 20
|
|
41
|
-
if (next >= 100) {
|
|
42
|
-
setDone(true)
|
|
43
|
-
clearInterval(timer)
|
|
44
|
-
return 100
|
|
45
|
-
}
|
|
46
|
-
return next
|
|
47
|
-
})
|
|
48
|
-
}, 300)
|
|
49
|
-
|
|
50
|
-
return () => clearInterval(timer)
|
|
51
|
-
}, [])
|
|
52
|
-
|
|
53
|
-
// Exit cleanly after showing "Complete!" for a moment
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
if (!done) return
|
|
56
|
-
const timeout = setTimeout(() => exit(), 300)
|
|
57
|
-
return () => clearTimeout(timeout)
|
|
58
|
-
}, [done, exit])
|
|
59
|
-
|
|
60
|
-
const barWidth = 30
|
|
61
|
-
const filled = Math.floor((progress / 100) * barWidth)
|
|
62
|
-
const bar = "#".repeat(filled) + "-".repeat(barWidth - filled)
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Box flexDirection="column">
|
|
66
|
-
<Text>Processing files...</Text>
|
|
67
|
-
<Text>
|
|
68
|
-
[{bar}] {progress}%
|
|
69
|
-
</Text>
|
|
70
|
-
{done && <Text color="green">Complete!</Text>}
|
|
71
|
-
</Box>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function main() {
|
|
76
|
-
// Determine non-TTY mode from environment
|
|
77
|
-
const envMode = process.env.SILVERY_NONTTY as NonTTYMode | undefined
|
|
78
|
-
const nonTTYMode = envMode || "auto"
|
|
79
|
-
|
|
80
|
-
console.log(`Non-TTY mode: ${nonTTYMode}`)
|
|
81
|
-
console.log(`stdout.isTTY: ${process.stdout.isTTY}`)
|
|
82
|
-
console.log("---\n")
|
|
83
|
-
|
|
84
|
-
using term = createTerm()
|
|
85
|
-
const { waitUntilExit } = await render(<ProgressExample />, term, {
|
|
86
|
-
mode: "inline",
|
|
87
|
-
nonTTYMode,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
await waitUntilExit()
|
|
91
|
-
|
|
92
|
-
console.log("\n---")
|
|
93
|
-
console.log("Done!")
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (import.meta.main) {
|
|
97
|
-
main().catch(console.error)
|
|
98
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
/**
|
|
3
|
-
* Example: Inline Progress Indicator
|
|
4
|
-
*
|
|
5
|
-
* Demonstrates the new inline mode for progress bars and status indicators.
|
|
6
|
-
* Unlike fullscreen mode, inline mode renders from the current cursor position
|
|
7
|
-
* and updates in place using relative cursor positioning.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import React, { useState, useEffect } from "react"
|
|
11
|
-
import { render, Box, Text, useApp, createTerm } from "../../src/index.js"
|
|
12
|
-
import type { ExampleMeta } from "../_banner.js"
|
|
13
|
-
|
|
14
|
-
export const meta: ExampleMeta = {
|
|
15
|
-
name: "Inline Progress",
|
|
16
|
-
description: "Inline progress bar updating in place",
|
|
17
|
-
features: ["render() inline mode", "setInterval updates"],
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function InlineProgress() {
|
|
21
|
-
const { exit } = useApp()
|
|
22
|
-
const [progress, setProgress] = useState(0)
|
|
23
|
-
const [status, setStatus] = useState("Starting...")
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
const timer = setInterval(() => {
|
|
27
|
-
setProgress((prev) => {
|
|
28
|
-
const next = prev + 10
|
|
29
|
-
if (next >= 100) {
|
|
30
|
-
setStatus("Complete!")
|
|
31
|
-
clearInterval(timer)
|
|
32
|
-
return 100
|
|
33
|
-
}
|
|
34
|
-
setStatus(`Processing... ${next}%`)
|
|
35
|
-
return next
|
|
36
|
-
})
|
|
37
|
-
}, 500)
|
|
38
|
-
|
|
39
|
-
return () => clearInterval(timer)
|
|
40
|
-
}, [])
|
|
41
|
-
|
|
42
|
-
// Exit cleanly after showing "Complete!" for a moment
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
if (progress < 100) return
|
|
45
|
-
const timeout = setTimeout(() => exit(), 300)
|
|
46
|
-
return () => clearTimeout(timeout)
|
|
47
|
-
}, [progress, exit])
|
|
48
|
-
|
|
49
|
-
const barWidth = 40
|
|
50
|
-
const filled = Math.floor((progress / 100) * barWidth)
|
|
51
|
-
const bar = "█".repeat(filled) + "░".repeat(barWidth - filled)
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Box flexDirection="column">
|
|
55
|
-
<Text>{status}</Text>
|
|
56
|
-
<Text>
|
|
57
|
-
[{bar}] {progress}%
|
|
58
|
-
</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function main() {
|
|
64
|
-
console.log("This is regular console output before the progress bar.\n")
|
|
65
|
-
|
|
66
|
-
using term = createTerm()
|
|
67
|
-
const { waitUntilExit } = await render(<InlineProgress />, term, {
|
|
68
|
-
mode: "inline",
|
|
69
|
-
exitOnCtrlC: true,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
await waitUntilExit()
|
|
73
|
-
|
|
74
|
-
console.log("\nProgress complete! This is output after the progress bar.")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (import.meta.main) {
|
|
78
|
-
main().catch(console.error)
|
|
79
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Simple inline mode test
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { useState, useEffect } from "react"
|
|
7
|
-
import { render, Box, Text, useApp, createTerm } from "../../src/index.js"
|
|
8
|
-
import type { ExampleMeta } from "../_banner.js"
|
|
9
|
-
|
|
10
|
-
export const meta: ExampleMeta = {
|
|
11
|
-
name: "Inline Simple",
|
|
12
|
-
description: "Inline rendering from current cursor position",
|
|
13
|
-
features: ["render() inline mode", "one-shot output"],
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function Counter() {
|
|
17
|
-
const { exit } = useApp()
|
|
18
|
-
const [count, setCount] = useState(0)
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
const timer = setInterval(() => {
|
|
22
|
-
setCount((c) => {
|
|
23
|
-
if (c >= 5) {
|
|
24
|
-
clearInterval(timer)
|
|
25
|
-
return c
|
|
26
|
-
}
|
|
27
|
-
return c + 1
|
|
28
|
-
})
|
|
29
|
-
}, 500)
|
|
30
|
-
|
|
31
|
-
return () => clearInterval(timer)
|
|
32
|
-
}, [])
|
|
33
|
-
|
|
34
|
-
// Exit cleanly after count reaches 5
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (count < 5) return
|
|
37
|
-
const timeout = setTimeout(() => exit(), 300)
|
|
38
|
-
return () => clearTimeout(timeout)
|
|
39
|
-
}, [count, exit])
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<Box>
|
|
43
|
-
<Text>Count: {count}</Text>
|
|
44
|
-
</Box>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function main() {
|
|
49
|
-
console.log("Before\n")
|
|
50
|
-
|
|
51
|
-
using term = createTerm()
|
|
52
|
-
const { waitUntilExit } = await render(<Counter />, term, {
|
|
53
|
-
mode: "inline",
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
await waitUntilExit()
|
|
57
|
-
|
|
58
|
-
console.log("\nAfter")
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (import.meta.main) {
|
|
62
|
-
main().catch(console.error)
|
|
63
|
-
}
|