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,551 +0,0 @@
1
- /**
2
- * Dashboard Example
3
- *
4
- * A btop-style responsive dashboard demonstrating:
5
- * - Tab navigation with compound Tabs component
6
- * - Live-updating metrics with sparklines
7
- * - Responsive 2-column / 1-column layout via useContentRect()
8
- * - Semantic theme colors with severity-based color coding
9
- * - Flex-based progress bars
10
- */
11
-
12
- import React, { useState } from "react"
13
- import {
14
- render,
15
- Box,
16
- Text,
17
- H2,
18
- Strong,
19
- Small,
20
- Muted,
21
- Kbd,
22
- Tabs,
23
- TabList,
24
- Tab,
25
- TabPanel,
26
- ProgressBar,
27
- useContentRect,
28
- useInput,
29
- useApp,
30
- useInterval,
31
- createTerm,
32
- type Key,
33
- } from "../../src/index.js"
34
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
35
-
36
- export const meta: ExampleMeta = {
37
- name: "Dashboard",
38
- description: "Responsive multi-pane dashboard with live metrics and charts",
39
- demo: true,
40
- features: ["Box flexGrow", "useContentRect()", "responsive", "live data", "sparklines"],
41
- }
42
-
43
- // ============================================================================
44
- // Sparkline
45
- // ============================================================================
46
-
47
- const SPARK_CHARS = "▁▂▃▄▅▆▇█"
48
-
49
- function sparkline(values: number[], max: number): string {
50
- return values.map((v) => SPARK_CHARS[Math.round((v / max) * 7)] ?? SPARK_CHARS[0]).join("")
51
- }
52
-
53
- // ============================================================================
54
- // Data Helpers
55
- // ============================================================================
56
-
57
- function jitter(base: number, range: number): number {
58
- return Math.max(0, Math.min(100, base + (Math.random() - 0.5) * range))
59
- }
60
-
61
- function initHistory(base: number, range: number, len: number): number[] {
62
- return Array.from({ length: len }, () => jitter(base, range))
63
- }
64
-
65
- function pushHistory(history: number[], value: number): number[] {
66
- const next = [...history]
67
- if (next.length >= 20) next.shift()
68
- next.push(value)
69
- return next
70
- }
71
-
72
- function severityColor(pct: number): string {
73
- if (pct > 80) return "$error"
74
- if (pct > 60) return "$warning"
75
- return "$success"
76
- }
77
-
78
- // ============================================================================
79
- // State
80
- // ============================================================================
81
-
82
- interface CoreMetrics {
83
- usage: number
84
- history: number[]
85
- }
86
-
87
- interface MemoryMetrics {
88
- used: number
89
- cached: number
90
- buffers: number
91
- free: number
92
- swap: number
93
- swapTotal: number
94
- history: number[]
95
- }
96
-
97
- interface NetworkMetrics {
98
- downloadRate: number
99
- uploadRate: number
100
- downloadHistory: number[]
101
- uploadHistory: number[]
102
- connections: number
103
- packetsIn: number
104
- packetsOut: number
105
- }
106
-
107
- interface ProcessInfo {
108
- pid: number
109
- name: string
110
- cpu: number
111
- mem: number
112
- status: string
113
- }
114
-
115
- function createInitialState() {
116
- const cores: CoreMetrics[] = Array.from({ length: 8 }, (_, i) => ({
117
- usage: 20 + Math.random() * 60,
118
- history: initHistory(30 + i * 5, 20, 20),
119
- }))
120
-
121
- const memory: MemoryMetrics = {
122
- used: 8.2,
123
- cached: 3.1,
124
- buffers: 1.4,
125
- free: 3.3,
126
- swap: 0.8,
127
- swapTotal: 4.0,
128
- history: initHistory(55, 10, 20),
129
- }
130
-
131
- const network: NetworkMetrics = {
132
- downloadRate: 42.5,
133
- uploadRate: 12.3,
134
- downloadHistory: initHistory(40, 30, 20),
135
- uploadHistory: initHistory(12, 10, 20),
136
- connections: 147,
137
- packetsIn: 1842,
138
- packetsOut: 923,
139
- }
140
-
141
- const processes: ProcessInfo[] = [
142
- { pid: 1201, name: "node", cpu: 24.3, mem: 4.2, status: "running" },
143
- { pid: 892, name: "chrome", cpu: 18.7, mem: 12.1, status: "running" },
144
- { pid: 3456, name: "vscode", cpu: 12.1, mem: 8.4, status: "running" },
145
- { pid: 2103, name: "postgres", cpu: 8.9, mem: 3.7, status: "sleeping" },
146
- { pid: 4521, name: "docker", cpu: 6.2, mem: 5.1, status: "running" },
147
- { pid: 1893, name: "nginx", cpu: 3.4, mem: 1.2, status: "sleeping" },
148
- { pid: 7234, name: "redis", cpu: 2.1, mem: 0.8, status: "sleeping" },
149
- { pid: 5612, name: "bun", cpu: 1.8, mem: 2.3, status: "running" },
150
- ]
151
-
152
- return { cores, memory, network, processes }
153
- }
154
-
155
- function tickState(prev: ReturnType<typeof createInitialState>) {
156
- const cores = prev.cores.map((core) => {
157
- const usage = jitter(core.usage, 15)
158
- return { usage, history: pushHistory(core.history, usage) }
159
- })
160
-
161
- const totalMem = prev.memory.used + prev.memory.cached + prev.memory.buffers + prev.memory.free
162
- const usedJitter = (jitter((prev.memory.used / totalMem) * 100, 3) / 100) * totalMem
163
- const memory: MemoryMetrics = {
164
- ...prev.memory,
165
- used: Math.max(4, usedJitter),
166
- swap: (jitter((prev.memory.swap / prev.memory.swapTotal) * 100, 5) / 100) * prev.memory.swapTotal,
167
- history: pushHistory(prev.memory.history, (usedJitter / totalMem) * 100),
168
- }
169
-
170
- const downloadRate = jitter(prev.network.downloadRate, 20)
171
- const uploadRate = jitter(prev.network.uploadRate, 8)
172
- const network: NetworkMetrics = {
173
- downloadRate,
174
- uploadRate,
175
- downloadHistory: pushHistory(prev.network.downloadHistory, downloadRate),
176
- uploadHistory: pushHistory(prev.network.uploadHistory, uploadRate),
177
- connections: Math.max(50, Math.round(jitter(prev.network.connections, 20))),
178
- packetsIn: Math.max(100, Math.round(jitter(prev.network.packetsIn, 200))),
179
- packetsOut: Math.max(50, Math.round(jitter(prev.network.packetsOut, 100))),
180
- }
181
-
182
- const processes = prev.processes.map((p) => ({
183
- ...p,
184
- cpu: Math.max(0.1, jitter(p.cpu, 4)),
185
- mem: Math.max(0.1, jitter(p.mem, 1)),
186
- }))
187
-
188
- return { cores, memory, network, processes }
189
- }
190
-
191
- // ============================================================================
192
- // Components
193
- // ============================================================================
194
-
195
- function SectionHeader({ children }: { children: React.ReactNode }): JSX.Element {
196
- return <H2>{children}</H2>
197
- }
198
-
199
- function LabelValue({ label, value, color }: { label: string; value: string; color?: string }): JSX.Element {
200
- return (
201
- <Box gap={1}>
202
- <Muted>{label}</Muted>
203
- <Text color={color}>
204
- <Strong>{value}</Strong>
205
- </Text>
206
- </Box>
207
- )
208
- }
209
-
210
- // --- CPU Tab ---
211
-
212
- function CpuCore({ index, core }: { index: number; core: CoreMetrics }): JSX.Element {
213
- const pct = Math.round(core.usage)
214
- const color = severityColor(pct)
215
- return (
216
- <Box flexDirection="column">
217
- <Box justifyContent="space-between">
218
- <Text>
219
- Core {index}{" "}
220
- <Text color={color}>
221
- <Strong>{String(pct).padStart(3)}%</Strong>
222
- </Text>
223
- </Text>
224
- <Small>{sparkline(core.history, 100)}</Small>
225
- </Box>
226
- <ProgressBar value={core.usage / 100} color={color} showPercentage={false} />
227
- </Box>
228
- )
229
- }
230
-
231
- function CpuPane({ cores }: { cores: CoreMetrics[] }): JSX.Element {
232
- const avgCpu = cores.reduce((sum, c) => sum + c.usage, 0) / cores.length
233
- const maxCpu = Math.max(...cores.map((c) => c.usage))
234
- const load1 = ((avgCpu / 100) * 8 * 0.8 + Math.random() * 0.5).toFixed(2)
235
- const load5 = ((avgCpu / 100) * 8 * 0.7 + Math.random() * 0.3).toFixed(2)
236
- const load15 = ((avgCpu / 100) * 8 * 0.6 + Math.random() * 0.2).toFixed(2)
237
-
238
- return (
239
- <Box flexDirection="column" gap={1} flexGrow={1}>
240
- <SectionHeader>CPU</SectionHeader>
241
- <Box gap={2}>
242
- <LabelValue label="Avg:" value={`${Math.round(avgCpu)}%`} color={severityColor(avgCpu)} />
243
- <LabelValue label="Max:" value={`${Math.round(maxCpu)}%`} color={severityColor(maxCpu)} />
244
- <LabelValue label="Load:" value={`${load1} ${load5} ${load15}`} />
245
- </Box>
246
- {cores.map((core, i) => (
247
- <CpuCore key={i} index={i} core={core} />
248
- ))}
249
- </Box>
250
- )
251
- }
252
-
253
- // --- Memory Tab ---
254
-
255
- function StackedBar({ segments }: { segments: { value: number; color: string; char?: string }[] }): JSX.Element {
256
- return (
257
- <Box>
258
- {segments.map((seg, i) => (
259
- <Box key={i} flexGrow={Math.max(1, Math.round(seg.value * 100))}>
260
- <Text color={seg.color}>{(seg.char ?? "█").repeat(80)}</Text>
261
- </Box>
262
- ))}
263
- </Box>
264
- )
265
- }
266
-
267
- function MemoryPane({ memory }: { memory: MemoryMetrics }): JSX.Element {
268
- const total = memory.used + memory.cached + memory.buffers + memory.free
269
- const usedPct = (memory.used / total) * 100
270
- const swapPct = (memory.swap / memory.swapTotal) * 100
271
-
272
- return (
273
- <Box flexDirection="column" gap={1} flexGrow={1}>
274
- <SectionHeader>Memory</SectionHeader>
275
- <Box gap={2}>
276
- <LabelValue label="Total:" value={`${total.toFixed(1)} GB`} />
277
- <LabelValue label="Used:" value={`${memory.used.toFixed(1)} GB`} color={severityColor(usedPct)} />
278
- </Box>
279
- <Box flexDirection="column">
280
- <Muted>Memory Breakdown</Muted>
281
- <StackedBar
282
- segments={[
283
- { value: memory.used / total, color: severityColor(usedPct) },
284
- { value: memory.cached / total, color: "$info" },
285
- { value: memory.buffers / total, color: "$primary" },
286
- { value: memory.free / total, color: "$muted", char: "░" },
287
- ]}
288
- />
289
- <Box gap={2}>
290
- <Text color={severityColor(usedPct)}>
291
- {"█"} Used {memory.used.toFixed(1)}G
292
- </Text>
293
- <Text color="$info">
294
- {"█"} Cache {memory.cached.toFixed(1)}G
295
- </Text>
296
- <Text color="$primary">
297
- {"█"} Buf {memory.buffers.toFixed(1)}G
298
- </Text>
299
- <Muted>
300
- {"░"} Free {memory.free.toFixed(1)}G
301
- </Muted>
302
- </Box>
303
- </Box>
304
- <Box flexDirection="column">
305
- <Muted>
306
- Swap: {memory.swap.toFixed(1)}G / {memory.swapTotal.toFixed(1)}G
307
- </Muted>
308
- <ProgressBar value={swapPct / 100} color={severityColor(swapPct)} showPercentage />
309
- </Box>
310
- <Box flexDirection="column">
311
- <Muted>Top Consumers</Muted>
312
- <Box gap={2}>
313
- <Text>
314
- chrome <Strong color="$warning">12.1G</Strong>
315
- </Text>
316
- <Text>
317
- vscode <Strong color="$primary">8.4G</Strong>
318
- </Text>
319
- <Text>
320
- docker <Strong color="$primary">5.1G</Strong>
321
- </Text>
322
- </Box>
323
- </Box>
324
- <Box flexDirection="column">
325
- <Muted>History</Muted>
326
- <Text color="$primary">{sparkline(memory.history, 100)}</Text>
327
- </Box>
328
- </Box>
329
- )
330
- }
331
-
332
- // --- Network Tab ---
333
-
334
- function NetworkPane({ network }: { network: NetworkMetrics }): JSX.Element {
335
- return (
336
- <Box flexDirection="column" gap={1} flexGrow={1}>
337
- <SectionHeader>Network</SectionHeader>
338
- <Box flexDirection="column">
339
- <Box justifyContent="space-between">
340
- <Text color="$success">
341
- {"↓"} Download: <Strong>{network.downloadRate.toFixed(1)} MB/s</Strong>
342
- </Text>
343
- <Small>{sparkline(network.downloadHistory, 100)}</Small>
344
- </Box>
345
- <ProgressBar value={network.downloadRate / 100} color="$success" showPercentage={false} />
346
- </Box>
347
- <Box flexDirection="column">
348
- <Box justifyContent="space-between">
349
- <Text color="$info">
350
- {"↑"} Upload: <Strong>{network.uploadRate.toFixed(1)} MB/s</Strong>
351
- </Text>
352
- <Small>{sparkline(network.uploadHistory, 40)}</Small>
353
- </Box>
354
- <ProgressBar value={network.uploadRate / 40} color="$info" showPercentage={false} />
355
- </Box>
356
- <Box borderStyle="round" borderColor="$border" paddingX={1} flexDirection="column">
357
- <Muted>Connection Stats</Muted>
358
- <Box gap={2}>
359
- <LabelValue label="Connections:" value={String(network.connections)} />
360
- <LabelValue label="Packets In:" value={String(network.packetsIn)} />
361
- <LabelValue label="Packets Out:" value={String(network.packetsOut)} />
362
- </Box>
363
- <Box gap={2}>
364
- <LabelValue label="Interface:" value="en0" />
365
- <LabelValue label="MTU:" value="1500" />
366
- <LabelValue label="Duplex:" value="full" />
367
- </Box>
368
- </Box>
369
- </Box>
370
- )
371
- }
372
-
373
- // --- Processes Tab ---
374
-
375
- function ProcessRow({ proc, isTop }: { proc: ProcessInfo; isTop: boolean }): JSX.Element {
376
- const cpuColor = severityColor(proc.cpu)
377
- return (
378
- <Box gap={1}>
379
- <Text color="$muted">{String(proc.pid).padStart(5)}</Text>
380
- <Text bold={isTop}>{proc.name.padEnd(12)}</Text>
381
- <Text color={cpuColor}>{proc.cpu.toFixed(1).padStart(5)}%</Text>
382
- <Text color="$primary">{proc.mem.toFixed(1).padStart(5)}%</Text>
383
- <Text color={proc.status === "running" ? "$success" : "$muted"}>{proc.status}</Text>
384
- </Box>
385
- )
386
- }
387
-
388
- function ProcessPane({ processes }: { processes: ProcessInfo[] }): JSX.Element {
389
- const sorted = [...processes].sort((a, b) => b.cpu - a.cpu)
390
-
391
- return (
392
- <Box flexDirection="column" gap={1} flexGrow={1}>
393
- <SectionHeader>Processes</SectionHeader>
394
- <Box gap={1}>
395
- <Muted>{" PID".padStart(5)}</Muted>
396
- <Muted>{"Name".padEnd(12)}</Muted>
397
- <Muted>{" CPU".padStart(5)}</Muted>
398
- <Muted>{" MEM".padStart(5)}</Muted>
399
- <Muted>Status</Muted>
400
- </Box>
401
- {sorted.map((proc, i) => (
402
- <ProcessRow key={proc.pid} proc={proc} isTop={i === 0} />
403
- ))}
404
- <Box gap={2} paddingTop={1}>
405
- <LabelValue label="Total:" value={`${processes.length} processes`} />
406
- <LabelValue
407
- label="Running:"
408
- value={String(processes.filter((p) => p.status === "running").length)}
409
- color="$success"
410
- />
411
- <LabelValue
412
- label="Sleeping:"
413
- value={String(processes.filter((p) => p.status === "sleeping").length)}
414
- color="$muted"
415
- />
416
- </Box>
417
- </Box>
418
- )
419
- }
420
-
421
- // --- Responsive Layout ---
422
-
423
- function WideLayout({
424
- cores,
425
- memory,
426
- network,
427
- processes,
428
- }: {
429
- cores: CoreMetrics[]
430
- memory: MemoryMetrics
431
- network: NetworkMetrics
432
- processes: ProcessInfo[]
433
- }): JSX.Element {
434
- return (
435
- <Box flexDirection="column" flexGrow={1} gap={1}>
436
- <Box flexDirection="row" gap={1} flexGrow={1}>
437
- <Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
438
- <CpuPane cores={cores} />
439
- </Box>
440
- <Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
441
- <MemoryPane memory={memory} />
442
- </Box>
443
- </Box>
444
- <Box flexDirection="row" gap={1} flexGrow={1}>
445
- <Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
446
- <NetworkPane network={network} />
447
- </Box>
448
- <Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
449
- <ProcessPane processes={processes} />
450
- </Box>
451
- </Box>
452
- </Box>
453
- )
454
- }
455
-
456
- // ============================================================================
457
- // Dashboard
458
- // ============================================================================
459
-
460
- export function Dashboard(): JSX.Element {
461
- const { exit } = useApp()
462
- const { width } = useContentRect()
463
- const [state, setState] = useState(createInitialState)
464
- const [tick, setTick] = useState(0)
465
- const isWide = width > 100
466
-
467
- useInterval(() => {
468
- setState((prev) => tickState(prev))
469
- setTick((t) => t + 1)
470
- }, 500)
471
-
472
- useInput((input: string, key: Key) => {
473
- if (input === "q" || key.escape) exit()
474
- })
475
-
476
- if (isWide) {
477
- return (
478
- <Box flexDirection="column" flexGrow={1}>
479
- <Box justifyContent="space-between" paddingX={1}>
480
- <Text>
481
- <Strong>System Monitor</Strong> <Small>Tick #{tick}</Small>
482
- </Text>
483
- <Muted>
484
- <Kbd>q</Kbd> quit
485
- </Muted>
486
- </Box>
487
- <WideLayout cores={state.cores} memory={state.memory} network={state.network} processes={state.processes} />
488
- </Box>
489
- )
490
- }
491
-
492
- return (
493
- <Box flexDirection="column" flexGrow={1}>
494
- <Tabs defaultValue="cpu">
495
- <Box justifyContent="space-between" paddingX={1}>
496
- <TabList>
497
- <Tab value="cpu">CPU</Tab>
498
- <Tab value="memory">Memory</Tab>
499
- <Tab value="network">Network</Tab>
500
- <Tab value="processes">Processes</Tab>
501
- </TabList>
502
- <Small>Tick #{tick}</Small>
503
- </Box>
504
-
505
- <TabPanel value="cpu">
506
- <Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
507
- <CpuPane cores={state.cores} />
508
- </Box>
509
- </TabPanel>
510
- <TabPanel value="memory">
511
- <Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
512
- <MemoryPane memory={state.memory} />
513
- </Box>
514
- </TabPanel>
515
- <TabPanel value="network">
516
- <Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
517
- <NetworkPane network={state.network} />
518
- </Box>
519
- </TabPanel>
520
- <TabPanel value="processes">
521
- <Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
522
- <ProcessPane processes={state.processes} />
523
- </Box>
524
- </TabPanel>
525
- </Tabs>
526
- <Muted>
527
- {" "}
528
- <Kbd>h/l</Kbd> tabs <Kbd>Esc/q</Kbd> quit
529
- </Muted>
530
- </Box>
531
- )
532
- }
533
-
534
- // ============================================================================
535
- // Main
536
- // ============================================================================
537
-
538
- async function main() {
539
- using term = createTerm()
540
- const { waitUntilExit } = await render(
541
- <ExampleBanner meta={meta} controls="h/l tabs Esc/q quit">
542
- <Dashboard />
543
- </ExampleBanner>,
544
- term,
545
- )
546
- await waitUntilExit()
547
- }
548
-
549
- if (import.meta.main) {
550
- main().catch(console.error)
551
- }