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.
Files changed (120) hide show
  1. package/README.md +41 -145
  2. package/dist/chalk.js +3 -0
  3. package/dist/chalk.js.map +11 -0
  4. package/dist/index.js +340 -0
  5. package/dist/index.js.map +282 -0
  6. package/dist/ink.js +129 -0
  7. package/dist/ink.js.map +140 -0
  8. package/dist/runtime.js +394 -0
  9. package/dist/runtime.js.map +286 -0
  10. package/dist/theme.js +343 -0
  11. package/dist/theme.js.map +286 -0
  12. package/dist/ui/animation.js +3 -0
  13. package/dist/ui/animation.js.map +15 -0
  14. package/dist/ui/ansi.js +3 -0
  15. package/dist/ui/ansi.js.map +10 -0
  16. package/dist/ui/cli.js +8 -0
  17. package/dist/ui/cli.js.map +14 -0
  18. package/dist/ui/display.js +4 -0
  19. package/dist/ui/display.js.map +10 -0
  20. package/dist/ui/image.js +4 -0
  21. package/dist/ui/image.js.map +15 -0
  22. package/dist/ui/input.js +3 -0
  23. package/dist/ui/input.js.map +11 -0
  24. package/dist/ui/progress.js +8 -0
  25. package/dist/ui/progress.js.map +20 -0
  26. package/dist/ui/react.js +3 -0
  27. package/dist/ui/react.js.map +15 -0
  28. package/dist/ui/utils.js +3 -0
  29. package/dist/ui/utils.js.map +10 -0
  30. package/dist/ui/wrappers.js +14 -0
  31. package/dist/ui/wrappers.js.map +19 -0
  32. package/dist/ui.js +17 -0
  33. package/dist/ui.js.map +20 -0
  34. package/package.json +67 -15
  35. package/src/index.ts +67 -1
  36. package/src/runtime.ts +4 -0
  37. package/src/theme.ts +4 -0
  38. package/src/ui/animation.ts +2 -0
  39. package/src/ui/ansi.ts +2 -0
  40. package/src/ui/cli.ts +2 -0
  41. package/src/ui/display.ts +2 -0
  42. package/src/ui/image.ts +2 -0
  43. package/src/ui/input.ts +2 -0
  44. package/src/ui/progress.ts +2 -0
  45. package/src/ui/react.ts +2 -0
  46. package/src/ui/utils.ts +2 -0
  47. package/src/ui/wrappers.ts +2 -0
  48. package/src/ui.ts +4 -0
  49. package/examples/CLAUDE.md +0 -75
  50. package/examples/_banner.tsx +0 -60
  51. package/examples/cli.ts +0 -228
  52. package/examples/index.md +0 -101
  53. package/examples/inline/inline-nontty.tsx +0 -98
  54. package/examples/inline/inline-progress.tsx +0 -79
  55. package/examples/inline/inline-simple.tsx +0 -63
  56. package/examples/inline/scrollback.tsx +0 -185
  57. package/examples/interactive/_input-debug.tsx +0 -110
  58. package/examples/interactive/_stdin-test.ts +0 -71
  59. package/examples/interactive/_textarea-bare.tsx +0 -45
  60. package/examples/interactive/aichat/components.tsx +0 -468
  61. package/examples/interactive/aichat/index.tsx +0 -207
  62. package/examples/interactive/aichat/script.ts +0 -460
  63. package/examples/interactive/aichat/state.ts +0 -326
  64. package/examples/interactive/aichat/types.ts +0 -19
  65. package/examples/interactive/app-todo.tsx +0 -198
  66. package/examples/interactive/async-data.tsx +0 -208
  67. package/examples/interactive/cli-wizard.tsx +0 -332
  68. package/examples/interactive/clipboard.tsx +0 -183
  69. package/examples/interactive/components.tsx +0 -463
  70. package/examples/interactive/data-explorer.tsx +0 -506
  71. package/examples/interactive/dev-tools.tsx +0 -379
  72. package/examples/interactive/explorer.tsx +0 -747
  73. package/examples/interactive/gallery.tsx +0 -652
  74. package/examples/interactive/inline-bench.tsx +0 -136
  75. package/examples/interactive/kanban.tsx +0 -267
  76. package/examples/interactive/layout-ref.tsx +0 -185
  77. package/examples/interactive/outline.tsx +0 -171
  78. package/examples/interactive/paste-demo.tsx +0 -198
  79. package/examples/interactive/scroll.tsx +0 -77
  80. package/examples/interactive/search-filter.tsx +0 -240
  81. package/examples/interactive/task-list.tsx +0 -279
  82. package/examples/interactive/terminal.tsx +0 -798
  83. package/examples/interactive/textarea.tsx +0 -103
  84. package/examples/interactive/theme.tsx +0 -336
  85. package/examples/interactive/transform.tsx +0 -256
  86. package/examples/interactive/virtual-10k.tsx +0 -413
  87. package/examples/kitty/canvas.tsx +0 -519
  88. package/examples/kitty/generate-samples.ts +0 -236
  89. package/examples/kitty/image-component.tsx +0 -273
  90. package/examples/kitty/images.tsx +0 -604
  91. package/examples/kitty/input.tsx +0 -371
  92. package/examples/kitty/keys.tsx +0 -378
  93. package/examples/kitty/paint.tsx +0 -1017
  94. package/examples/layout/dashboard.tsx +0 -551
  95. package/examples/layout/live-resize.tsx +0 -290
  96. package/examples/layout/overflow.tsx +0 -51
  97. package/examples/playground/README.md +0 -69
  98. package/examples/playground/build.ts +0 -61
  99. package/examples/playground/index.html +0 -420
  100. package/examples/playground/playground-app.tsx +0 -416
  101. package/examples/runtime/elm-counter.tsx +0 -206
  102. package/examples/runtime/hello-runtime.tsx +0 -73
  103. package/examples/runtime/pipe-composition.tsx +0 -184
  104. package/examples/runtime/run-counter.tsx +0 -78
  105. package/examples/runtime/runtime-counter.tsx +0 -197
  106. package/examples/screenshots/generate.tsx +0 -563
  107. package/examples/scrollback-perf.tsx +0 -230
  108. package/examples/viewer.tsx +0 -654
  109. package/examples/web/build.ts +0 -365
  110. package/examples/web/canvas-app.tsx +0 -80
  111. package/examples/web/canvas.html +0 -89
  112. package/examples/web/dom-app.tsx +0 -81
  113. package/examples/web/dom.html +0 -113
  114. package/examples/web/showcase-app.tsx +0 -107
  115. package/examples/web/showcase.html +0 -34
  116. package/examples/web/showcases/index.tsx +0 -56
  117. package/examples/web/viewer-app.tsx +0 -555
  118. package/examples/web/viewer.html +0 -30
  119. package/examples/web/xterm-app.tsx +0 -105
  120. 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
- })