@silvery/examples 0.17.3 → 0.17.4

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 (112) hide show
  1. package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
  3. package/dist/_banner-DLPxCqVy.mjs +44 -0
  4. package/dist/ansi-CCE2pVS0.mjs +16397 -0
  5. package/dist/apng-HhhBjRGt.mjs +68 -0
  6. package/dist/apng-mwUQbTTF.mjs +3 -0
  7. package/dist/apps/aichat/index.mjs +1299 -0
  8. package/dist/apps/app-todo.mjs +139 -0
  9. package/dist/apps/async-data.mjs +204 -0
  10. package/dist/apps/cli-wizard.mjs +339 -0
  11. package/dist/apps/clipboard.mjs +198 -0
  12. package/dist/apps/components.mjs +864 -0
  13. package/dist/apps/data-explorer.mjs +483 -0
  14. package/dist/apps/dev-tools.mjs +397 -0
  15. package/dist/apps/explorer.mjs +698 -0
  16. package/dist/apps/gallery.mjs +766 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +280 -0
  19. package/dist/apps/layout-ref.mjs +187 -0
  20. package/dist/apps/outline.mjs +203 -0
  21. package/dist/apps/paste-demo.mjs +189 -0
  22. package/dist/apps/scroll.mjs +86 -0
  23. package/dist/apps/search-filter.mjs +287 -0
  24. package/dist/apps/selection.mjs +355 -0
  25. package/dist/apps/spatial-focus-demo.mjs +388 -0
  26. package/dist/apps/task-list.mjs +258 -0
  27. package/dist/apps/terminal-caps-demo.mjs +315 -0
  28. package/dist/apps/terminal.mjs +872 -0
  29. package/dist/apps/text-selection-demo.mjs +254 -0
  30. package/dist/apps/textarea.mjs +178 -0
  31. package/dist/apps/theme.mjs +661 -0
  32. package/dist/apps/transform.mjs +215 -0
  33. package/dist/apps/virtual-10k.mjs +422 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Bahh9mKN.mjs +1179 -0
  36. package/dist/backends-CCtCDQ94.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 +48 -0
  40. package/dist/components/hello.mjs +31 -0
  41. package/dist/components/progress-bar.mjs +59 -0
  42. package/dist/components/select-list.mjs +85 -0
  43. package/dist/components/spinner.mjs +57 -0
  44. package/dist/components/text-input.mjs +62 -0
  45. package/dist/components/virtual-list.mjs +51 -0
  46. package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
  47. package/dist/gif-BZaqPPVX.mjs +3 -0
  48. package/dist/gif-BtnXuxLF.mjs +71 -0
  49. package/dist/gifenc-CLRW41dk.mjs +728 -0
  50. package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
  51. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  52. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  53. package/dist/layout/dashboard.mjs +1204 -0
  54. package/dist/layout/live-resize.mjs +303 -0
  55. package/dist/layout/overflow.mjs +70 -0
  56. package/dist/layout/text-layout.mjs +335 -0
  57. package/dist/node-NuJ94BWl.mjs +1083 -0
  58. package/dist/plugins-D1KtkT4a.mjs +3057 -0
  59. package/dist/resvg-js-C_8Wps1F.mjs +201 -0
  60. package/dist/src-BTEVGpd9.mjs +23538 -0
  61. package/dist/src-CUUOuRH6.mjs +5322 -0
  62. package/dist/src-CzfRafCQ.mjs +814 -0
  63. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  64. package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
  65. package/package.json +18 -13
  66. package/_banner.tsx +0 -60
  67. package/apps/aichat/components.tsx +0 -469
  68. package/apps/aichat/index.tsx +0 -220
  69. package/apps/aichat/script.ts +0 -460
  70. package/apps/aichat/state.ts +0 -325
  71. package/apps/aichat/types.ts +0 -19
  72. package/apps/app-todo.tsx +0 -201
  73. package/apps/async-data.tsx +0 -196
  74. package/apps/cli-wizard.tsx +0 -332
  75. package/apps/clipboard.tsx +0 -183
  76. package/apps/components.tsx +0 -658
  77. package/apps/data-explorer.tsx +0 -490
  78. package/apps/dev-tools.tsx +0 -395
  79. package/apps/explorer.tsx +0 -731
  80. package/apps/gallery.tsx +0 -653
  81. package/apps/inline-bench.tsx +0 -138
  82. package/apps/kanban.tsx +0 -265
  83. package/apps/layout-ref.tsx +0 -173
  84. package/apps/outline.tsx +0 -160
  85. package/apps/panes/index.tsx +0 -203
  86. package/apps/paste-demo.tsx +0 -185
  87. package/apps/scroll.tsx +0 -80
  88. package/apps/search-filter.tsx +0 -240
  89. package/apps/selection.tsx +0 -346
  90. package/apps/spatial-focus-demo.tsx +0 -372
  91. package/apps/task-list.tsx +0 -271
  92. package/apps/terminal-caps-demo.tsx +0 -317
  93. package/apps/terminal.tsx +0 -784
  94. package/apps/text-selection-demo.tsx +0 -193
  95. package/apps/textarea.tsx +0 -155
  96. package/apps/theme.tsx +0 -515
  97. package/apps/transform.tsx +0 -229
  98. package/apps/virtual-10k.tsx +0 -405
  99. package/apps/vterm-demo/index.tsx +0 -216
  100. package/components/counter.tsx +0 -49
  101. package/components/hello.tsx +0 -38
  102. package/components/progress-bar.tsx +0 -52
  103. package/components/select-list.tsx +0 -54
  104. package/components/spinner.tsx +0 -44
  105. package/components/text-input.tsx +0 -61
  106. package/components/virtual-list.tsx +0 -56
  107. package/dist/cli.d.mts +0 -1
  108. package/dist/cli.mjs.map +0 -1
  109. package/layout/dashboard.tsx +0 -953
  110. package/layout/live-resize.tsx +0 -282
  111. package/layout/overflow.tsx +0 -51
  112. 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
- }