silvery 0.3.0 → 0.4.1
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/dist/chalk.js +3 -0
- package/dist/chalk.js.map +11 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +282 -0
- package/dist/ink.js +129 -0
- package/dist/ink.js.map +140 -0
- package/dist/runtime.js +394 -0
- package/dist/runtime.js.map +286 -0
- package/dist/theme.js +343 -0
- package/dist/theme.js.map +286 -0
- package/dist/ui/animation.js +3 -0
- package/dist/ui/animation.js.map +15 -0
- package/dist/ui/ansi.js +3 -0
- package/dist/ui/ansi.js.map +10 -0
- package/dist/ui/cli.js +8 -0
- package/dist/ui/cli.js.map +14 -0
- package/dist/ui/display.js +4 -0
- package/dist/ui/display.js.map +10 -0
- package/dist/ui/image.js +4 -0
- package/dist/ui/image.js.map +15 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/input.js.map +11 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/progress.js.map +20 -0
- package/dist/ui/react.js +3 -0
- package/dist/ui/react.js.map +15 -0
- package/dist/ui/utils.js +3 -0
- package/dist/ui/utils.js.map +10 -0
- package/dist/ui/wrappers.js +14 -0
- package/dist/ui/wrappers.js.map +19 -0
- package/dist/ui.js +17 -0
- package/dist/ui.js.map +20 -0
- package/package.json +67 -15
- 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
|
@@ -1,563 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screenshot Generator for silvery README
|
|
3
|
-
*
|
|
4
|
-
* Renders example components headlessly and captures PNG screenshots
|
|
5
|
-
* using bufferToHTML() + Playwright.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* cd vendor/silvery && bun run examples/screenshots/generate.tsx
|
|
9
|
-
*
|
|
10
|
-
* Prerequisites:
|
|
11
|
-
* bunx playwright install chromium
|
|
12
|
-
*
|
|
13
|
-
* Output:
|
|
14
|
-
* docs/images/dashboard.png - Multi-pane dashboard with borders and colors
|
|
15
|
-
* docs/images/task-list.png - Scrollable task list with selection
|
|
16
|
-
* docs/images/kanban.png - 3-column kanban board with cards
|
|
17
|
-
* docs/images/layout-feedback.png - Layout feedback with useContentRect() values
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { mkdir } from "node:fs/promises"
|
|
21
|
-
import { dirname, resolve } from "node:path"
|
|
22
|
-
import React, { useState } from "react"
|
|
23
|
-
import { render, createRenderer, ensureEngine, bufferToHTML } from "../../src/testing/index.tsx"
|
|
24
|
-
import { Box, Text, Divider, useContentRect, useApp } from "../../src/index.js"
|
|
25
|
-
import { createScreenshotter } from "../../src/screenshot.js"
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Output directory
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
const OUTPUT_DIR = resolve(dirname(import.meta.path), "../../docs/images")
|
|
32
|
-
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// Screenshot Components
|
|
35
|
-
// These are static versions of the examples, frozen at a specific state
|
|
36
|
-
// for consistent screenshot output.
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
// --- 1. Dashboard -----------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
function ProgressBar({ percent, width = 24 }: { percent: number; width?: number }): JSX.Element {
|
|
42
|
-
const filled = Math.round((percent / 100) * width)
|
|
43
|
-
const empty = width - filled
|
|
44
|
-
const dot = filled < width ? "╸" : ""
|
|
45
|
-
const filledBar = "━".repeat(Math.max(0, filled - (dot ? 1 : 0)))
|
|
46
|
-
const emptyBar = "─".repeat(Math.max(0, empty - (dot ? 0 : 0)))
|
|
47
|
-
return (
|
|
48
|
-
<Text>
|
|
49
|
-
<Text color="$success">{filledBar}</Text>
|
|
50
|
-
<Text color="$success">{dot}</Text>
|
|
51
|
-
<Text dim>{emptyBar}</Text>
|
|
52
|
-
</Text>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function DashboardScreenshot(): JSX.Element {
|
|
57
|
-
return (
|
|
58
|
-
<Box flexDirection="column" padding={1}>
|
|
59
|
-
<Box marginBottom={1}>
|
|
60
|
-
<Text bold color="$warning">
|
|
61
|
-
Dashboard
|
|
62
|
-
</Text>
|
|
63
|
-
</Box>
|
|
64
|
-
|
|
65
|
-
<Box flexGrow={1} flexDirection="row" gap={1}>
|
|
66
|
-
{/* System Stats pane (selected) */}
|
|
67
|
-
<Box flexDirection="column" flexGrow={1} borderStyle="round" borderColor="$primary" padding={1}>
|
|
68
|
-
<Box marginBottom={1}>
|
|
69
|
-
<Text bold color="$primary">
|
|
70
|
-
System Stats
|
|
71
|
-
</Text>
|
|
72
|
-
</Box>
|
|
73
|
-
<Box flexDirection="column" gap={1}>
|
|
74
|
-
<Box flexDirection="row" justifyContent="space-between">
|
|
75
|
-
<Text>CPU Usage</Text>
|
|
76
|
-
<Box>
|
|
77
|
-
<Text bold color="$success">
|
|
78
|
-
45%
|
|
79
|
-
</Text>
|
|
80
|
-
<Text color="$success"> +2%</Text>
|
|
81
|
-
</Box>
|
|
82
|
-
</Box>
|
|
83
|
-
<Box flexDirection="row" justifyContent="space-between">
|
|
84
|
-
<Text>Memory</Text>
|
|
85
|
-
<Box>
|
|
86
|
-
<Text bold color="$success">
|
|
87
|
-
8.2 GB
|
|
88
|
-
</Text>
|
|
89
|
-
<Text color="$error"> -0.3</Text>
|
|
90
|
-
</Box>
|
|
91
|
-
</Box>
|
|
92
|
-
<Box flexDirection="row" justifyContent="space-between">
|
|
93
|
-
<Text>Disk</Text>
|
|
94
|
-
<Text bold color="$success">
|
|
95
|
-
234 GB
|
|
96
|
-
</Text>
|
|
97
|
-
</Box>
|
|
98
|
-
<Box flexDirection="row" justifyContent="space-between">
|
|
99
|
-
<Text>Network</Text>
|
|
100
|
-
<Box>
|
|
101
|
-
<Text bold color="$success">
|
|
102
|
-
1.2 Mb/s
|
|
103
|
-
</Text>
|
|
104
|
-
<Text color="$success"> +0.5</Text>
|
|
105
|
-
</Box>
|
|
106
|
-
</Box>
|
|
107
|
-
</Box>
|
|
108
|
-
</Box>
|
|
109
|
-
|
|
110
|
-
{/* Recent Activity pane */}
|
|
111
|
-
<Box flexDirection="column" flexGrow={1} borderStyle="round" borderColor="$border" padding={1}>
|
|
112
|
-
<Box marginBottom={1}>
|
|
113
|
-
<Text bold>Recent Activity</Text>
|
|
114
|
-
</Box>
|
|
115
|
-
<Box flexDirection="column">
|
|
116
|
-
<Text>{">"} User login: admin</Text>
|
|
117
|
-
<Text>{" "}Backup completed</Text>
|
|
118
|
-
<Text>{" "}Config updated</Text>
|
|
119
|
-
<Text dim>{" "}Service restarted</Text>
|
|
120
|
-
<Text dim>{" "}Cache cleared</Text>
|
|
121
|
-
</Box>
|
|
122
|
-
</Box>
|
|
123
|
-
|
|
124
|
-
{/* Project Progress pane */}
|
|
125
|
-
<Box flexDirection="column" flexGrow={1} borderStyle="round" borderColor="$border" padding={1}>
|
|
126
|
-
<Box marginBottom={1}>
|
|
127
|
-
<Text bold>Project Progress</Text>
|
|
128
|
-
</Box>
|
|
129
|
-
<Box flexDirection="column" gap={1}>
|
|
130
|
-
{[
|
|
131
|
-
{ label: "Frontend", percent: 85 },
|
|
132
|
-
{ label: "Backend", percent: 72 },
|
|
133
|
-
{ label: "Testing", percent: 45 },
|
|
134
|
-
{ label: "Docs", percent: 30 },
|
|
135
|
-
].map((item) => (
|
|
136
|
-
<Box key={item.label} flexDirection="column">
|
|
137
|
-
<Box justifyContent="space-between">
|
|
138
|
-
<Text>{item.label}</Text>
|
|
139
|
-
<Text bold>{item.percent}%</Text>
|
|
140
|
-
</Box>
|
|
141
|
-
<ProgressBar percent={item.percent} />
|
|
142
|
-
</Box>
|
|
143
|
-
))}
|
|
144
|
-
</Box>
|
|
145
|
-
</Box>
|
|
146
|
-
</Box>
|
|
147
|
-
|
|
148
|
-
<Box marginTop={1}>
|
|
149
|
-
<Text dim>
|
|
150
|
-
{" "}
|
|
151
|
-
Selected: Pane 1{" "}
|
|
152
|
-
<Text bold dim>
|
|
153
|
-
h/l
|
|
154
|
-
</Text>{" "}
|
|
155
|
-
navigate{" "}
|
|
156
|
-
<Text bold dim>
|
|
157
|
-
q
|
|
158
|
-
</Text>{" "}
|
|
159
|
-
quit
|
|
160
|
-
</Text>
|
|
161
|
-
</Box>
|
|
162
|
-
</Box>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// --- 2. Task List -----------------------------------------------------------
|
|
167
|
-
|
|
168
|
-
function TaskListScreenshot(): JSX.Element {
|
|
169
|
-
const tasks = [
|
|
170
|
-
{ id: 1, title: "Review authentication refactor", completed: false, priority: "high" as const },
|
|
171
|
-
{ id: 2, title: "Update API documentation", completed: true, priority: "medium" as const },
|
|
172
|
-
{
|
|
173
|
-
id: 3,
|
|
174
|
-
title: "Fix timezone handling in scheduler",
|
|
175
|
-
completed: false,
|
|
176
|
-
priority: "high" as const,
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
id: 4,
|
|
180
|
-
title: "Add rate limiting to endpoints",
|
|
181
|
-
completed: false,
|
|
182
|
-
priority: "medium" as const,
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
id: 5,
|
|
186
|
-
title: "Write integration tests for payments",
|
|
187
|
-
completed: true,
|
|
188
|
-
priority: "low" as const,
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
id: 6,
|
|
192
|
-
title: "Migrate user table to new schema",
|
|
193
|
-
completed: false,
|
|
194
|
-
priority: "high" as const,
|
|
195
|
-
},
|
|
196
|
-
{ id: 7, title: "Set up staging environment", completed: false, priority: "low" as const },
|
|
197
|
-
{ id: 8, title: "Refactor notification service", completed: true, priority: "medium" as const },
|
|
198
|
-
]
|
|
199
|
-
const cursor = 2
|
|
200
|
-
|
|
201
|
-
const priorityLabels = { high: "P1", medium: "P2", low: "P3" }
|
|
202
|
-
const priorityColors = { high: "$error", medium: "$warning", low: "$success" }
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<Box flexDirection="column" padding={1}>
|
|
206
|
-
<Box marginBottom={1}>
|
|
207
|
-
<Text bold color="$warning">
|
|
208
|
-
Task List
|
|
209
|
-
</Text>
|
|
210
|
-
<Text dim>
|
|
211
|
-
{" "}
|
|
212
|
-
{tasks.filter((t) => t.completed).length}/{tasks.length} completed
|
|
213
|
-
</Text>
|
|
214
|
-
</Box>
|
|
215
|
-
|
|
216
|
-
<Box
|
|
217
|
-
flexGrow={1}
|
|
218
|
-
flexDirection="column"
|
|
219
|
-
borderStyle="round"
|
|
220
|
-
borderColor="$primary"
|
|
221
|
-
overflow="hidden"
|
|
222
|
-
paddingX={1}
|
|
223
|
-
>
|
|
224
|
-
{tasks.map((task, index) => {
|
|
225
|
-
const checkbox = task.completed ? "☑" : "☐"
|
|
226
|
-
const isSelected = index === cursor
|
|
227
|
-
const showSeparator = index < tasks.length - 1
|
|
228
|
-
const label = priorityLabels[task.priority]
|
|
229
|
-
const labelColor = priorityColors[task.priority]
|
|
230
|
-
|
|
231
|
-
return (
|
|
232
|
-
<Box key={task.id} flexDirection="column">
|
|
233
|
-
{isSelected ? (
|
|
234
|
-
<Text>
|
|
235
|
-
<Text backgroundColor="$primary" color="black">
|
|
236
|
-
{" "}
|
|
237
|
-
{checkbox} {task.title}{" "}
|
|
238
|
-
</Text>{" "}
|
|
239
|
-
<Text color={labelColor} bold>
|
|
240
|
-
{label}
|
|
241
|
-
</Text>
|
|
242
|
-
</Text>
|
|
243
|
-
) : (
|
|
244
|
-
<Text strikethrough={task.completed} dim={task.completed}>
|
|
245
|
-
{checkbox} {task.title}{" "}
|
|
246
|
-
<Text color={labelColor} bold>
|
|
247
|
-
{label}
|
|
248
|
-
</Text>
|
|
249
|
-
</Text>
|
|
250
|
-
)}
|
|
251
|
-
{showSeparator && <Divider />}
|
|
252
|
-
</Box>
|
|
253
|
-
)
|
|
254
|
-
})}
|
|
255
|
-
</Box>
|
|
256
|
-
|
|
257
|
-
<Box marginTop={1} justifyContent="space-between">
|
|
258
|
-
<Text dim>
|
|
259
|
-
{" "}
|
|
260
|
-
<Text bold dim>
|
|
261
|
-
j/k
|
|
262
|
-
</Text>{" "}
|
|
263
|
-
navigate{" "}
|
|
264
|
-
<Text bold dim>
|
|
265
|
-
space
|
|
266
|
-
</Text>{" "}
|
|
267
|
-
toggle{" "}
|
|
268
|
-
<Text bold dim>
|
|
269
|
-
enter
|
|
270
|
-
</Text>{" "}
|
|
271
|
-
expand{" "}
|
|
272
|
-
<Text bold dim>
|
|
273
|
-
q
|
|
274
|
-
</Text>{" "}
|
|
275
|
-
quit
|
|
276
|
-
</Text>
|
|
277
|
-
<Text dim>
|
|
278
|
-
{" "}
|
|
279
|
-
<Text bold>3</Text>/8{" "}
|
|
280
|
-
</Text>
|
|
281
|
-
</Box>
|
|
282
|
-
</Box>
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// --- 3. Kanban Board --------------------------------------------------------
|
|
287
|
-
|
|
288
|
-
function KanbanScreenshot(): JSX.Element {
|
|
289
|
-
const columns = [
|
|
290
|
-
{
|
|
291
|
-
id: "todo",
|
|
292
|
-
title: "To Do",
|
|
293
|
-
isSelected: true,
|
|
294
|
-
cards: [
|
|
295
|
-
{
|
|
296
|
-
title: "Design new landing page",
|
|
297
|
-
tags: ["design"],
|
|
298
|
-
isSelected: true,
|
|
299
|
-
},
|
|
300
|
-
{ title: "Write API documentation", tags: ["docs"], isSelected: false },
|
|
301
|
-
{ title: "Set up monitoring", tags: ["devops"], isSelected: false },
|
|
302
|
-
{ title: "Create onboarding flow", tags: ["ux"], isSelected: false },
|
|
303
|
-
],
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
id: "inProgress",
|
|
307
|
-
title: "In Progress",
|
|
308
|
-
isSelected: false,
|
|
309
|
-
cards: [
|
|
310
|
-
{
|
|
311
|
-
title: "User authentication",
|
|
312
|
-
tags: ["backend", "security"],
|
|
313
|
-
isSelected: false,
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
title: "Dashboard redesign",
|
|
317
|
-
tags: ["frontend", "design"],
|
|
318
|
-
isSelected: false,
|
|
319
|
-
},
|
|
320
|
-
{ title: "API rate limiting", tags: ["backend"], isSelected: false },
|
|
321
|
-
],
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
id: "done",
|
|
325
|
-
title: "Done",
|
|
326
|
-
isSelected: false,
|
|
327
|
-
cards: [
|
|
328
|
-
{ title: "Project setup", tags: ["devops"], isSelected: false },
|
|
329
|
-
{ title: "CI/CD pipeline", tags: ["devops"], isSelected: false },
|
|
330
|
-
{ title: "Initial wireframes", tags: ["design"], isSelected: false },
|
|
331
|
-
],
|
|
332
|
-
},
|
|
333
|
-
]
|
|
334
|
-
|
|
335
|
-
const tagColors: Record<string, string> = {
|
|
336
|
-
frontend: "$info",
|
|
337
|
-
backend: "$accent",
|
|
338
|
-
design: "$warning",
|
|
339
|
-
devops: "$success",
|
|
340
|
-
docs: "$primary",
|
|
341
|
-
ux: "$muted",
|
|
342
|
-
security: "$error",
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return (
|
|
346
|
-
<Box flexDirection="column" padding={1}>
|
|
347
|
-
<Box marginBottom={1}>
|
|
348
|
-
<Text bold color="$warning">
|
|
349
|
-
Kanban Board
|
|
350
|
-
</Text>
|
|
351
|
-
</Box>
|
|
352
|
-
|
|
353
|
-
<Box flexGrow={1} flexDirection="row" gap={1} overflow="hidden">
|
|
354
|
-
{columns.map((col) => (
|
|
355
|
-
<Box
|
|
356
|
-
key={col.id}
|
|
357
|
-
flexDirection="column"
|
|
358
|
-
flexGrow={1}
|
|
359
|
-
borderStyle="round"
|
|
360
|
-
borderColor={col.isSelected ? "$primary" : "$border"}
|
|
361
|
-
>
|
|
362
|
-
<Box backgroundColor={col.isSelected ? "$primary" : undefined} paddingX={1}>
|
|
363
|
-
<Text bold color={col.isSelected ? "black" : undefined}>
|
|
364
|
-
{col.title}
|
|
365
|
-
</Text>
|
|
366
|
-
<Text color={col.isSelected ? "black" : "$muted"}> ({col.cards.length})</Text>
|
|
367
|
-
</Box>
|
|
368
|
-
|
|
369
|
-
<Box flexDirection="column" paddingX={1} flexGrow={1} gap={1}>
|
|
370
|
-
{col.cards.map((card, idx) => (
|
|
371
|
-
<Box
|
|
372
|
-
key={idx}
|
|
373
|
-
flexDirection="column"
|
|
374
|
-
borderStyle="round"
|
|
375
|
-
borderColor={card.isSelected ? "$primary" : "$border"}
|
|
376
|
-
paddingX={1}
|
|
377
|
-
>
|
|
378
|
-
{card.isSelected ? (
|
|
379
|
-
<Text backgroundColor="$primary" color="black" bold>
|
|
380
|
-
{card.title}
|
|
381
|
-
</Text>
|
|
382
|
-
) : (
|
|
383
|
-
<Text>{card.title}</Text>
|
|
384
|
-
)}
|
|
385
|
-
<Box gap={1}>
|
|
386
|
-
{card.tags.map((tag) => (
|
|
387
|
-
<Text key={tag} color={tagColors[tag] ?? "$muted"} dim>
|
|
388
|
-
#{tag}
|
|
389
|
-
</Text>
|
|
390
|
-
))}
|
|
391
|
-
</Box>
|
|
392
|
-
</Box>
|
|
393
|
-
))}
|
|
394
|
-
</Box>
|
|
395
|
-
</Box>
|
|
396
|
-
))}
|
|
397
|
-
</Box>
|
|
398
|
-
|
|
399
|
-
<Text dim>
|
|
400
|
-
{" "}
|
|
401
|
-
<Text bold dim>
|
|
402
|
-
h/l
|
|
403
|
-
</Text>{" "}
|
|
404
|
-
column{" "}
|
|
405
|
-
<Text bold dim>
|
|
406
|
-
j/k
|
|
407
|
-
</Text>{" "}
|
|
408
|
-
card{" "}
|
|
409
|
-
<Text bold dim>
|
|
410
|
-
{"</>"}
|
|
411
|
-
</Text>{" "}
|
|
412
|
-
move{" "}
|
|
413
|
-
<Text bold dim>
|
|
414
|
-
q
|
|
415
|
-
</Text>{" "}
|
|
416
|
-
quit
|
|
417
|
-
</Text>
|
|
418
|
-
</Box>
|
|
419
|
-
)
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// --- 4. Layout Feedback -----------------------------------------------------
|
|
423
|
-
|
|
424
|
-
function LayoutPane({ title, color, grow = 1 }: { title: string; color: string; grow?: number }): JSX.Element {
|
|
425
|
-
const rect = useContentRect()
|
|
426
|
-
return (
|
|
427
|
-
<Box flexGrow={grow} borderStyle="round" borderColor={color} padding={1} flexDirection="column">
|
|
428
|
-
<Text bold color={color}>
|
|
429
|
-
{title}
|
|
430
|
-
</Text>
|
|
431
|
-
<Box marginTop={1}>
|
|
432
|
-
<Text dim>
|
|
433
|
-
{rect.width}x{rect.height} at ({rect.x},{rect.y})
|
|
434
|
-
</Text>
|
|
435
|
-
</Box>
|
|
436
|
-
</Box>
|
|
437
|
-
)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function LayoutFeedbackScreenshot(): JSX.Element {
|
|
441
|
-
return (
|
|
442
|
-
<Box flexDirection="column" padding={1}>
|
|
443
|
-
<Box marginBottom={1}>
|
|
444
|
-
<Text bold color="$warning">
|
|
445
|
-
Layout Feedback Demo
|
|
446
|
-
</Text>
|
|
447
|
-
</Box>
|
|
448
|
-
|
|
449
|
-
<Box flexDirection="row" gap={1} height={8}>
|
|
450
|
-
<LayoutPane title="Sidebar" color="$success" grow={1} />
|
|
451
|
-
<LayoutPane title="Main Content" color="$primary" grow={2} />
|
|
452
|
-
<LayoutPane title="Detail" color="$info" grow={1} />
|
|
453
|
-
</Box>
|
|
454
|
-
|
|
455
|
-
<Box marginTop={1} borderStyle="single" borderColor="$border" padding={1}>
|
|
456
|
-
<Box flexDirection="column">
|
|
457
|
-
<Text bold>useContentRect() — components know their size during render</Text>
|
|
458
|
-
<Text dim>No ResizeObserver, no second render, no layout jank.</Text>
|
|
459
|
-
<Text dim>Each pane above displays its own dimensions via useContentRect().</Text>
|
|
460
|
-
</Box>
|
|
461
|
-
</Box>
|
|
462
|
-
|
|
463
|
-
<Text dim>
|
|
464
|
-
{" "}
|
|
465
|
-
<Text bold dim>
|
|
466
|
-
i
|
|
467
|
-
</Text>{" "}
|
|
468
|
-
inspect{" "}
|
|
469
|
-
<Text bold dim>
|
|
470
|
-
Esc
|
|
471
|
-
</Text>{" "}
|
|
472
|
-
quit
|
|
473
|
-
</Text>
|
|
474
|
-
</Box>
|
|
475
|
-
)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ============================================================================
|
|
479
|
-
// Screenshot Generation
|
|
480
|
-
// ============================================================================
|
|
481
|
-
|
|
482
|
-
interface ScreenshotConfig {
|
|
483
|
-
name: string
|
|
484
|
-
filename: string
|
|
485
|
-
element: JSX.Element
|
|
486
|
-
cols: number
|
|
487
|
-
rows: number
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const screenshots: ScreenshotConfig[] = [
|
|
491
|
-
{
|
|
492
|
-
name: "Dashboard",
|
|
493
|
-
filename: "dashboard.png",
|
|
494
|
-
element: <DashboardScreenshot />,
|
|
495
|
-
cols: 120,
|
|
496
|
-
rows: 25,
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
name: "Task List",
|
|
500
|
-
filename: "task-list.png",
|
|
501
|
-
element: <TaskListScreenshot />,
|
|
502
|
-
cols: 80,
|
|
503
|
-
rows: 23,
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
name: "Kanban Board",
|
|
507
|
-
filename: "kanban.png",
|
|
508
|
-
element: <KanbanScreenshot />,
|
|
509
|
-
cols: 120,
|
|
510
|
-
rows: 27,
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
name: "Layout Feedback",
|
|
514
|
-
filename: "layout-feedback.png",
|
|
515
|
-
element: <LayoutFeedbackScreenshot />,
|
|
516
|
-
cols: 90,
|
|
517
|
-
rows: 20,
|
|
518
|
-
},
|
|
519
|
-
]
|
|
520
|
-
|
|
521
|
-
async function main() {
|
|
522
|
-
await mkdir(OUTPUT_DIR, { recursive: true })
|
|
523
|
-
|
|
524
|
-
await ensureEngine()
|
|
525
|
-
await using screenshotter = createScreenshotter()
|
|
526
|
-
|
|
527
|
-
for (const config of screenshots) {
|
|
528
|
-
const { name, filename, element, cols, rows } = config
|
|
529
|
-
const outputPath = resolve(OUTPUT_DIR, filename)
|
|
530
|
-
|
|
531
|
-
console.log(`Generating ${name} (${cols}x${rows})...`)
|
|
532
|
-
|
|
533
|
-
const app = render(element, { cols, rows })
|
|
534
|
-
const buffer = app.lastBuffer()
|
|
535
|
-
|
|
536
|
-
if (!buffer) {
|
|
537
|
-
console.error(` ERROR: No buffer for ${name}`)
|
|
538
|
-
app.unmount()
|
|
539
|
-
continue
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const html = bufferToHTML(buffer, {
|
|
543
|
-
fontFamily: "JetBrains Mono, Menlo, Consolas, monospace",
|
|
544
|
-
fontSize: 14,
|
|
545
|
-
theme: "dark",
|
|
546
|
-
})
|
|
547
|
-
|
|
548
|
-
await screenshotter.capture(html, outputPath)
|
|
549
|
-
console.log(` Saved: ${outputPath}`)
|
|
550
|
-
|
|
551
|
-
app.unmount()
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
console.log("\nDone! Generated screenshots:")
|
|
555
|
-
for (const config of screenshots) {
|
|
556
|
-
console.log(` docs/images/${config.filename}`)
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
main().catch((err) => {
|
|
561
|
-
console.error("Screenshot generation failed:", err)
|
|
562
|
-
process.exit(1)
|
|
563
|
-
})
|