@silvery/examples 0.17.3 → 0.17.5

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 (111) hide show
  1. package/dist/UPNG-ShUlaTDh.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-Bopa5BFR.mjs +4 -0
  3. package/dist/_banner-A70_y2Vi.mjs +43 -0
  4. package/dist/ansi-0VXlUmNn.mjs +16397 -0
  5. package/dist/apng-B0gRaDVT.mjs +3 -0
  6. package/dist/apng-BTRDTfDW.mjs +68 -0
  7. package/dist/apps/aichat/index.mjs +1298 -0
  8. package/dist/apps/app-todo.mjs +138 -0
  9. package/dist/apps/async-data.mjs +203 -0
  10. package/dist/apps/cli-wizard.mjs +338 -0
  11. package/dist/apps/clipboard.mjs +197 -0
  12. package/dist/apps/components.mjs +863 -0
  13. package/dist/apps/data-explorer.mjs +482 -0
  14. package/dist/apps/dev-tools.mjs +396 -0
  15. package/dist/apps/explorer.mjs +697 -0
  16. package/dist/apps/gallery.mjs +765 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +279 -0
  19. package/dist/apps/layout-ref.mjs +186 -0
  20. package/dist/apps/outline.mjs +202 -0
  21. package/dist/apps/paste-demo.mjs +188 -0
  22. package/dist/apps/scroll.mjs +85 -0
  23. package/dist/apps/search-filter.mjs +286 -0
  24. package/dist/apps/selection.mjs +354 -0
  25. package/dist/apps/spatial-focus-demo.mjs +387 -0
  26. package/dist/apps/task-list.mjs +257 -0
  27. package/dist/apps/terminal-caps-demo.mjs +314 -0
  28. package/dist/apps/terminal.mjs +871 -0
  29. package/dist/apps/text-selection-demo.mjs +253 -0
  30. package/dist/apps/textarea.mjs +177 -0
  31. package/dist/apps/theme.mjs +660 -0
  32. package/dist/apps/transform.mjs +214 -0
  33. package/dist/apps/virtual-10k.mjs +421 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Dj-11kZF.mjs +1179 -0
  36. package/dist/backends-U3QwStfO.mjs +3 -0
  37. package/dist/{cli.mjs → bin/cli.mjs} +15 -19
  38. package/dist/chunk-BSw8zbkd.mjs +37 -0
  39. package/dist/components/counter.mjs +47 -0
  40. package/dist/components/hello.mjs +30 -0
  41. package/dist/components/progress-bar.mjs +58 -0
  42. package/dist/components/select-list.mjs +84 -0
  43. package/dist/components/spinner.mjs +56 -0
  44. package/dist/components/text-input.mjs +61 -0
  45. package/dist/components/virtual-list.mjs +50 -0
  46. package/dist/flexily-zero-adapter-ByVzLTFP.mjs +3374 -0
  47. package/dist/gif-B6NGH5gs.mjs +3 -0
  48. package/dist/gif-CfkOF-iG.mjs +71 -0
  49. package/dist/gifenc-BI4ihP_T.mjs +728 -0
  50. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  51. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  52. package/dist/layout/dashboard.mjs +1203 -0
  53. package/dist/layout/live-resize.mjs +302 -0
  54. package/dist/layout/overflow.mjs +69 -0
  55. package/dist/layout/text-layout.mjs +334 -0
  56. package/dist/node-nsrAOjH4.mjs +1083 -0
  57. package/dist/plugins-CT0DdV_E.mjs +3056 -0
  58. package/dist/resvg-js-Cnk2o49d.mjs +201 -0
  59. package/dist/src-9ZhfQyzD.mjs +814 -0
  60. package/dist/src-CUUOuRH6.mjs +5322 -0
  61. package/dist/src-jO3Zuzjj.mjs +23538 -0
  62. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  63. package/dist/yoga-adapter-BSQHuMV9.mjs +237 -0
  64. package/package.json +21 -14
  65. package/_banner.tsx +0 -60
  66. package/apps/aichat/components.tsx +0 -469
  67. package/apps/aichat/index.tsx +0 -220
  68. package/apps/aichat/script.ts +0 -460
  69. package/apps/aichat/state.ts +0 -325
  70. package/apps/aichat/types.ts +0 -19
  71. package/apps/app-todo.tsx +0 -201
  72. package/apps/async-data.tsx +0 -196
  73. package/apps/cli-wizard.tsx +0 -332
  74. package/apps/clipboard.tsx +0 -183
  75. package/apps/components.tsx +0 -658
  76. package/apps/data-explorer.tsx +0 -490
  77. package/apps/dev-tools.tsx +0 -395
  78. package/apps/explorer.tsx +0 -731
  79. package/apps/gallery.tsx +0 -653
  80. package/apps/inline-bench.tsx +0 -138
  81. package/apps/kanban.tsx +0 -265
  82. package/apps/layout-ref.tsx +0 -173
  83. package/apps/outline.tsx +0 -160
  84. package/apps/panes/index.tsx +0 -203
  85. package/apps/paste-demo.tsx +0 -185
  86. package/apps/scroll.tsx +0 -80
  87. package/apps/search-filter.tsx +0 -240
  88. package/apps/selection.tsx +0 -346
  89. package/apps/spatial-focus-demo.tsx +0 -372
  90. package/apps/task-list.tsx +0 -271
  91. package/apps/terminal-caps-demo.tsx +0 -317
  92. package/apps/terminal.tsx +0 -784
  93. package/apps/text-selection-demo.tsx +0 -193
  94. package/apps/textarea.tsx +0 -155
  95. package/apps/theme.tsx +0 -515
  96. package/apps/transform.tsx +0 -229
  97. package/apps/virtual-10k.tsx +0 -405
  98. package/apps/vterm-demo/index.tsx +0 -216
  99. package/components/counter.tsx +0 -49
  100. package/components/hello.tsx +0 -38
  101. package/components/progress-bar.tsx +0 -52
  102. package/components/select-list.tsx +0 -54
  103. package/components/spinner.tsx +0 -44
  104. package/components/text-input.tsx +0 -61
  105. package/components/virtual-list.tsx +0 -56
  106. package/dist/cli.d.mts +0 -1
  107. package/dist/cli.mjs.map +0 -1
  108. package/layout/dashboard.tsx +0 -953
  109. package/layout/live-resize.tsx +0 -282
  110. package/layout/overflow.tsx +0 -51
  111. package/layout/text-layout.tsx +0 -283
@@ -1,490 +0,0 @@
1
- /**
2
- * Data Explorer — Process Table Example
3
- *
4
- * A process explorer with a searchable, scrollable table demonstrating:
5
- * - Table-like display with responsive column widths via useBoxRect()
6
- * - TextInput for live search/filter with useDeferredValue
7
- * - ListView for smooth scrolling through 500+ rows
8
- * - Keyboard navigation with j/k and vim-style jumps
9
- * - Color-coded status indicators
10
- *
11
- * Usage: bun run examples/apps/data-explorer.tsx
12
- *
13
- * Controls:
14
- * j/k or Up/Down - Navigate rows
15
- * d/u - Half-page down/up
16
- * g/G - Jump to first/last
17
- * / - Toggle search mode
18
- * Esc - Exit search / quit
19
- * q - Quit (when not searching)
20
- */
21
-
22
- import React, { useState, useCallback, useMemo, useDeferredValue } from "react"
23
- import {
24
- render,
25
- Box,
26
- Text,
27
- ListView,
28
- TextInput,
29
- Divider,
30
- useBoxRect,
31
- useInput,
32
- useApp,
33
- createTerm,
34
- Kbd,
35
- Muted,
36
- Lead,
37
- type Key,
38
- } from "silvery"
39
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
40
-
41
- export const meta: ExampleMeta = {
42
- name: "Data Explorer",
43
- description: "Process explorer table with search, ListView, and responsive column widths",
44
- features: ["useBoxRect()", "TextInput", "useInput()", "responsive layout", "useDeferredValue"],
45
- }
46
-
47
- // ============================================================================
48
- // Types
49
- // ============================================================================
50
-
51
- type ProcessStatus = "running" | "sleeping" | "stopped" | "zombie"
52
-
53
- interface ProcessInfo {
54
- pid: number
55
- name: string
56
- cpu: number
57
- mem: number
58
- status: ProcessStatus
59
- user: string
60
- threads: number
61
- uptime: string
62
- }
63
-
64
- // ============================================================================
65
- // Data Generation
66
- // ============================================================================
67
-
68
- const PROCESS_NAMES = [
69
- "node",
70
- "python3",
71
- "nginx",
72
- "redis-server",
73
- "postgres",
74
- "docker",
75
- "sshd",
76
- "systemd",
77
- "cron",
78
- "rsyslogd",
79
- "webpack",
80
- "vite",
81
- "chrome",
82
- "firefox",
83
- "code",
84
- "vim",
85
- "tmux",
86
- "bash",
87
- "zsh",
88
- "containerd",
89
- "kubelet",
90
- "etcd",
91
- "coredns",
92
- "flannel",
93
- "prometheus",
94
- "grafana",
95
- "elasticsearch",
96
- "kibana",
97
- "logstash",
98
- "rabbitmq",
99
- "kafka",
100
- "zookeeper",
101
- "consul",
102
- "vault",
103
- "haproxy",
104
- "traefik",
105
- "envoy",
106
- "istio-proxy",
107
- "jaeger",
108
- "mysql",
109
- "mongo",
110
- "cassandra",
111
- "clickhouse",
112
- "influxdb",
113
- "jenkins",
114
- "gitlab-runner",
115
- "buildkitd",
116
- "registry",
117
- "cadvisor",
118
- "node-exporter",
119
- "alertmanager",
120
- "telegraf",
121
- "bun",
122
- "deno",
123
- "esbuild",
124
- "swc",
125
- "turbo",
126
- "pnpm",
127
- ]
128
-
129
- const USERS = ["root", "www-data", "postgres", "redis", "node", "admin", "deploy", "monitor"]
130
- const STATUSES: ProcessStatus[] = ["running", "sleeping", "stopped", "zombie"]
131
-
132
- function seededRandom(seed: number): () => number {
133
- let s = seed
134
- return () => {
135
- s = (s * 1664525 + 1013904223) & 0x7fffffff
136
- return s / 0x7fffffff
137
- }
138
- }
139
-
140
- function formatUptime(seconds: number): string {
141
- if (seconds < 60) return `${seconds}s`
142
- if (seconds < 3600) return `${Math.floor(seconds / 60)}m`
143
- if (seconds < 86400) return `${Math.floor(seconds / 3600)}h${Math.floor((seconds % 3600) / 60)}m`
144
- return `${Math.floor(seconds / 86400)}d${Math.floor((seconds % 86400) / 3600)}h`
145
- }
146
-
147
- function generateProcesses(count: number): ProcessInfo[] {
148
- const rng = seededRandom(42)
149
- const processes: ProcessInfo[] = []
150
-
151
- for (let i = 0; i < count; i++) {
152
- const nameBase = PROCESS_NAMES[Math.floor(rng() * PROCESS_NAMES.length)]!
153
- const hasInstance = rng() > 0.6
154
- const name = hasInstance ? `${nameBase}:${Math.floor(rng() * 20)}` : nameBase
155
- const status = rng() < 0.7 ? "running" : STATUSES[Math.floor(rng() * STATUSES.length)]!
156
-
157
- processes.push({
158
- pid: 1000 + i,
159
- name,
160
- cpu: status === "running" ? Math.round(rng() * 1000) / 10 : 0,
161
- mem: Math.round(rng() * 500) / 10,
162
- status,
163
- user: USERS[Math.floor(rng() * USERS.length)]!,
164
- threads: 1 + Math.floor(rng() * 64),
165
- uptime: formatUptime(Math.floor(rng() * 864000)),
166
- })
167
- }
168
-
169
- // Sort by CPU descending initially
170
- return processes.sort((a, b) => b.cpu - a.cpu)
171
- }
172
-
173
- const TOTAL_PROCESSES = 600
174
- const ALL_PROCESSES = generateProcesses(TOTAL_PROCESSES)
175
-
176
- // ============================================================================
177
- // Constants
178
- // ============================================================================
179
-
180
- const STATUS_COLORS: Record<ProcessStatus, string> = {
181
- running: "$success",
182
- sleeping: "$muted",
183
- stopped: "$warning",
184
- zombie: "$error",
185
- }
186
-
187
- const STATUS_ICONS: Record<ProcessStatus, string> = {
188
- running: "\u25b6",
189
- sleeping: "\u25cc",
190
- stopped: "\u25a0",
191
- zombie: "\u2620",
192
- }
193
-
194
- // ============================================================================
195
- // Components
196
- // ============================================================================
197
-
198
- /** Column layout helper -- computes column widths based on available space */
199
- function useColumns(totalWidth: number) {
200
- return useMemo(() => {
201
- // Fixed columns
202
- const pidW = 6
203
- const cpuW = 7
204
- const memW = 7
205
- const statusW = 10
206
- const threadsW = 5
207
- const uptimeW = 8
208
- const userW = 10
209
- const fixed = pidW + cpuW + memW + statusW + threadsW + uptimeW + userW + 8 // gaps
210
-
211
- // Name gets the rest
212
- const nameW = Math.max(10, totalWidth - fixed)
213
-
214
- return { pidW, nameW, cpuW, memW, statusW, userW, threadsW, uptimeW }
215
- }, [totalWidth])
216
- }
217
-
218
- function TableHeader({ width }: { width: number }) {
219
- const cols = useColumns(width)
220
-
221
- return (
222
- <Box paddingX={1}>
223
- <Text bold color="$muted">
224
- {"PID".padEnd(cols.pidW)}
225
- {"NAME".padEnd(cols.nameW)}
226
- {"CPU%".padStart(cols.cpuW)}
227
- {"MEM%".padStart(cols.memW)}
228
- {" "}
229
- {"STATUS".padEnd(cols.statusW)}
230
- {"USER".padEnd(cols.userW)}
231
- {"THR".padStart(cols.threadsW)}
232
- {" "}
233
- {"UPTIME".padStart(cols.uptimeW)}
234
- </Text>
235
- </Box>
236
- )
237
- }
238
-
239
- function ProcessRow({ proc, isSelected, width }: { proc: ProcessInfo; isSelected: boolean; width: number }) {
240
- const cols = useColumns(width)
241
- const cpuColor = proc.cpu > 80 ? "$error" : proc.cpu > 40 ? "$warning" : "$success"
242
- const memColor = proc.mem > 40 ? "$warning" : "$muted"
243
-
244
- // Truncate name to fit column
245
- const displayName = proc.name.length > cols.nameW - 1 ? proc.name.slice(0, cols.nameW - 2) + "\u2026" : proc.name
246
-
247
- return (
248
- <Box paddingX={1} backgroundColor={isSelected ? "$primary" : undefined}>
249
- <Text color={isSelected ? "$primary-fg" : "$muted"}>{String(proc.pid).padEnd(cols.pidW)}</Text>
250
- <Text bold={isSelected} color={isSelected ? "$primary-fg" : undefined}>
251
- {displayName.padEnd(cols.nameW)}
252
- </Text>
253
- <Text color={isSelected ? "$primary-fg" : cpuColor}>{proc.cpu.toFixed(1).padStart(cols.cpuW - 1)}%</Text>
254
- <Text color={isSelected ? "$primary-fg" : memColor}>{proc.mem.toFixed(1).padStart(cols.memW - 1)}%</Text>
255
- <Text>{" "}</Text>
256
- <Text color={isSelected ? "$primary-fg" : STATUS_COLORS[proc.status]}>
257
- {STATUS_ICONS[proc.status]} {proc.status.padEnd(cols.statusW - 2)}
258
- </Text>
259
- <Text color={isSelected ? "$primary-fg" : "$muted"}>{proc.user.padEnd(cols.userW)}</Text>
260
- <Text color={isSelected ? "$primary-fg" : "$muted"}>{String(proc.threads).padStart(cols.threadsW)}</Text>
261
- <Text>{" "}</Text>
262
- <Text color={isSelected ? "$primary-fg" : "$muted"}>{proc.uptime.padStart(cols.uptimeW)}</Text>
263
- </Box>
264
- )
265
- }
266
-
267
- function SummaryBar({ processes, query }: { processes: ProcessInfo[]; query: string }) {
268
- const stats = useMemo(() => {
269
- let running = 0
270
- let totalCpu = 0
271
- let totalMem = 0
272
- for (const p of processes) {
273
- if (p.status === "running") running++
274
- totalCpu += p.cpu
275
- totalMem += p.mem
276
- }
277
- return { running, totalCpu: totalCpu.toFixed(1), totalMem: totalMem.toFixed(1) }
278
- }, [processes])
279
-
280
- return (
281
- <Box paddingX={1} gap={2}>
282
- <Text bold>{processes.length}</Text>
283
- <Muted>processes</Muted>
284
- <Text color="$success" bold>
285
- {stats.running}
286
- </Text>
287
- <Muted>running</Muted>
288
- <Muted>|</Muted>
289
- <Text color="$primary">CPU: {stats.totalCpu}%</Text>
290
- <Text color="$warning">MEM: {stats.totalMem}%</Text>
291
- {query && (
292
- <>
293
- <Muted>|</Muted>
294
- <Text dim>filter: &quot;{query}&quot;</Text>
295
- </>
296
- )}
297
- </Box>
298
- )
299
- }
300
-
301
- /** Inner component that reads the flex container's height */
302
- function ProcessListArea({ processes, cursor, width }: { processes: ProcessInfo[]; cursor: number; width: number }) {
303
- const { height } = useBoxRect()
304
-
305
- return (
306
- <ListView
307
- items={processes}
308
- height={height}
309
- estimateHeight={1}
310
- scrollTo={cursor}
311
- overscan={5}
312
- renderItem={(proc, index) => (
313
- <ProcessRow key={proc.pid} proc={proc} isSelected={index === cursor} width={width} />
314
- )}
315
- />
316
- )
317
- }
318
-
319
- // ============================================================================
320
- // Main App
321
- // ============================================================================
322
-
323
- export function DataExplorer() {
324
- const { exit } = useApp()
325
- const { width } = useBoxRect()
326
- const [cursor, setCursor] = useState(0)
327
- const [searchMode, setSearchMode] = useState(false)
328
- const [query, setQuery] = useState("")
329
- const deferredQuery = useDeferredValue(query)
330
-
331
- // Filter processes based on deferred query
332
- const filtered = useMemo(() => {
333
- if (!deferredQuery) return ALL_PROCESSES
334
- const q = deferredQuery.toLowerCase()
335
- return ALL_PROCESSES.filter(
336
- (p) =>
337
- p.name.toLowerCase().includes(q) ||
338
- p.user.toLowerCase().includes(q) ||
339
- p.status.includes(q) ||
340
- String(p.pid).includes(q),
341
- )
342
- }, [deferredQuery])
343
-
344
- const listHeight = useMemo(() => Math.max(5, filtered.length), [filtered.length])
345
- const halfPage = Math.max(1, Math.floor(listHeight / 4))
346
-
347
- // Clamp cursor when filter changes
348
- const effectiveCursor = Math.min(cursor, Math.max(0, filtered.length - 1))
349
-
350
- const handleSearchSubmit = useCallback(() => {
351
- setSearchMode(false)
352
- }, [])
353
-
354
- useInput(
355
- useCallback(
356
- (input: string, key: Key) => {
357
- // In search mode, only handle Esc to exit
358
- if (searchMode) {
359
- if (key.escape) {
360
- setSearchMode(false)
361
- return
362
- }
363
- // TextInput handles all other input
364
- return
365
- }
366
-
367
- // Normal mode
368
- if (input === "q" || key.escape) {
369
- exit()
370
- return
371
- }
372
-
373
- if (input === "/") {
374
- setSearchMode(true)
375
- return
376
- }
377
-
378
- // Navigation
379
- if (input === "j" || key.downArrow) {
380
- setCursor((c) => Math.min(filtered.length - 1, c + 1))
381
- }
382
- if (input === "k" || key.upArrow) {
383
- setCursor((c) => Math.max(0, c - 1))
384
- }
385
- if (input === "d" || key.pageDown) {
386
- setCursor((c) => Math.min(filtered.length - 1, c + halfPage))
387
- }
388
- if (input === "u" || key.pageUp) {
389
- setCursor((c) => Math.max(0, c - halfPage))
390
- }
391
- if (input === "g" || key.home) {
392
- setCursor(0)
393
- }
394
- if (input === "G" || key.end) {
395
- setCursor(filtered.length - 1)
396
- }
397
-
398
- // Clear filter
399
- if (key.backspace && query) {
400
- setQuery("")
401
- setCursor(0)
402
- }
403
- },
404
- [searchMode, exit, filtered.length, halfPage, query],
405
- ),
406
- )
407
-
408
- return (
409
- <Box flexDirection="column" flexGrow={1}>
410
- {/* Summary bar */}
411
- <SummaryBar processes={filtered} query={deferredQuery} />
412
-
413
- {/* Search bar */}
414
- <Box paddingX={1}>
415
- {searchMode ? (
416
- <Box>
417
- <Text color="$primary" bold>
418
- /{" "}
419
- </Text>
420
- <TextInput
421
- value={query}
422
- onChange={(v) => {
423
- setQuery(v)
424
- setCursor(0)
425
- }}
426
- onSubmit={handleSearchSubmit}
427
- prompt=""
428
- isActive={searchMode}
429
- />
430
- </Box>
431
- ) : query ? (
432
- <Muted>
433
- filter: <Text bold>{query}</Text> (backspace to clear, / to edit)
434
- </Muted>
435
- ) : (
436
- <Muted>
437
- Press <Kbd>/</Kbd> to search
438
- </Muted>
439
- )}
440
- </Box>
441
-
442
- {/* Table header */}
443
- <TableHeader width={width} />
444
- <Box paddingX={1}>
445
- <Divider />
446
- </Box>
447
-
448
- {/* Process list */}
449
- <Box flexGrow={1} flexDirection="column">
450
- {filtered.length > 0 ? (
451
- <ProcessListArea processes={filtered} cursor={effectiveCursor} width={width} />
452
- ) : (
453
- <Box paddingX={1} justifyContent="center">
454
- <Lead>No processes match &quot;{deferredQuery}&quot;</Lead>
455
- </Box>
456
- )}
457
- </Box>
458
-
459
- {/* Scroll indicator + help */}
460
- <Box paddingX={1} justifyContent="space-between">
461
- <Muted>
462
- <Kbd>j/k</Kbd> navigate <Kbd>d/u</Kbd> half-page <Kbd>g/G</Kbd> start/end <Kbd>/</Kbd> search <Kbd>Esc/q</Kbd>{" "}
463
- quit
464
- </Muted>
465
- <Muted>
466
- {effectiveCursor + 1}/{filtered.length}
467
- </Muted>
468
- </Box>
469
- </Box>
470
- )
471
- }
472
-
473
- // ============================================================================
474
- // Main
475
- // ============================================================================
476
-
477
- export async function main() {
478
- using term = createTerm()
479
- const { waitUntilExit } = await render(
480
- <ExampleBanner meta={meta} controls="j/k navigate d/u half-page g/G start/end / search Esc/q quit">
481
- <DataExplorer />
482
- </ExampleBanner>,
483
- term,
484
- )
485
- await waitUntilExit()
486
- }
487
-
488
- if (import.meta.main) {
489
- await main()
490
- }