@silvery/examples 0.5.2 → 0.5.3

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/{examples/apps → apps}/aichat/index.tsx +4 -3
  3. package/{examples/apps → apps}/async-data.tsx +4 -4
  4. package/apps/components.tsx +658 -0
  5. package/{examples/apps → apps}/data-explorer.tsx +8 -8
  6. package/{examples/apps → apps}/dev-tools.tsx +35 -19
  7. package/{examples/apps → apps}/inline-bench.tsx +3 -1
  8. package/{examples/apps → apps}/kanban.tsx +20 -22
  9. package/{examples/apps → apps}/layout-ref.tsx +6 -6
  10. package/{examples/apps → apps}/panes/index.tsx +1 -1
  11. package/{examples/apps → apps}/paste-demo.tsx +2 -2
  12. package/{examples/apps → apps}/scroll.tsx +2 -2
  13. package/{examples/apps → apps}/search-filter.tsx +1 -1
  14. package/apps/selection.tsx +342 -0
  15. package/apps/spatial-focus-demo.tsx +368 -0
  16. package/{examples/apps → apps}/task-list.tsx +1 -1
  17. package/apps/terminal-caps-demo.tsx +334 -0
  18. package/apps/text-selection-demo.tsx +189 -0
  19. package/apps/textarea.tsx +155 -0
  20. package/{examples/apps → apps}/theme.tsx +1 -1
  21. package/apps/vterm-demo/index.tsx +216 -0
  22. package/dist/cli.d.mts +1 -0
  23. package/dist/cli.mjs +190 -0
  24. package/dist/cli.mjs.map +1 -0
  25. package/layout/dashboard.tsx +953 -0
  26. package/layout/live-resize.tsx +282 -0
  27. package/layout/overflow.tsx +51 -0
  28. package/layout/text-layout.tsx +283 -0
  29. package/package.json +27 -11
  30. package/bin/cli.ts +0 -294
  31. package/examples/apps/components.tsx +0 -463
  32. package/examples/apps/textarea.tsx +0 -91
  33. /package/{examples/_banner.tsx → _banner.tsx} +0 -0
  34. /package/{examples/apps → apps}/aichat/components.tsx +0 -0
  35. /package/{examples/apps → apps}/aichat/script.ts +0 -0
  36. /package/{examples/apps → apps}/aichat/state.ts +0 -0
  37. /package/{examples/apps → apps}/aichat/types.ts +0 -0
  38. /package/{examples/apps → apps}/app-todo.tsx +0 -0
  39. /package/{examples/apps → apps}/cli-wizard.tsx +0 -0
  40. /package/{examples/apps → apps}/clipboard.tsx +0 -0
  41. /package/{examples/apps → apps}/explorer.tsx +0 -0
  42. /package/{examples/apps → apps}/gallery.tsx +0 -0
  43. /package/{examples/apps → apps}/outline.tsx +0 -0
  44. /package/{examples/apps → apps}/terminal.tsx +0 -0
  45. /package/{examples/apps → apps}/transform.tsx +0 -0
  46. /package/{examples/apps → apps}/virtual-10k.tsx +0 -0
  47. /package/{examples/components → components}/counter.tsx +0 -0
  48. /package/{examples/components → components}/hello.tsx +0 -0
  49. /package/{examples/components → components}/progress-bar.tsx +0 -0
  50. /package/{examples/components → components}/select-list.tsx +0 -0
  51. /package/{examples/components → components}/spinner.tsx +0 -0
  52. /package/{examples/components → components}/text-input.tsx +0 -0
  53. /package/{examples/components → components}/virtual-list.tsx +0 -0
@@ -0,0 +1,658 @@
1
+ /**
2
+ * Components Showcase
3
+ *
4
+ * A UI component gallery demonstrating silvery's built-in components:
5
+ * - Typography: H1-H3, Strong, Muted, Small, Lead, Code, Blockquote, lists
6
+ * - Inputs: TextInput, TextArea, SelectList, Toggle with focus cycling
7
+ * - Display: ProgressBar, Spinner, Badge, border styles, ModalDialog
8
+ */
9
+
10
+ import React, { useState, useCallback } from "react"
11
+ import {
12
+ render,
13
+ Box,
14
+ Text,
15
+ Muted,
16
+ useInput,
17
+ useApp,
18
+ createTerm,
19
+ // Typography
20
+ H1,
21
+ H2,
22
+ H3,
23
+ P,
24
+ Lead,
25
+ Small,
26
+ Strong,
27
+ Em,
28
+ Code,
29
+ Blockquote,
30
+ CodeBlock,
31
+ HR,
32
+ UL,
33
+ OL,
34
+ LI,
35
+ // Inputs
36
+ TextInput,
37
+ TextArea,
38
+ SelectList,
39
+ Toggle,
40
+ Button,
41
+ // Display
42
+ ProgressBar,
43
+ Spinner,
44
+ Badge,
45
+ Divider,
46
+ Kbd,
47
+ ModalDialog,
48
+ type Key,
49
+ } from "silvery"
50
+ import { ExampleBanner, type ExampleMeta } from "../_banner.js"
51
+
52
+ export const meta: ExampleMeta = {
53
+ name: "Components",
54
+ description: "UI component gallery with typography, inputs, and dialogs",
55
+ demo: true,
56
+ features: ["Typography", "TextInput", "SelectList", "ModalDialog", "ProgressBar", "focus ring"],
57
+ }
58
+
59
+ // ============================================================================
60
+ // Typography Tab
61
+ // ============================================================================
62
+
63
+ function TypographyTab({ scrollOffset }: { scrollOffset?: number }) {
64
+ return (
65
+ <Box flexDirection="column" gap={1} paddingX={1} overflow="scroll" scrollOffset={scrollOffset} flexGrow={1}>
66
+ <Box flexDirection="column">
67
+ <H1>Getting Started with Silvery</H1>
68
+ <Lead>Build modern terminal UIs with React — layout feedback, semantic theming, and 30+ components.</Lead>
69
+ </Box>
70
+
71
+ <HR />
72
+
73
+ <Box flexDirection="row" gap={2}>
74
+ <Box flexDirection="column" flexGrow={1} flexBasis={0}>
75
+ <H2>Typography</H2>
76
+ <Box flexDirection="column">
77
+ <Text bold color="$primary">
78
+ H1 — Page Title (bold, $primary)
79
+ </Text>
80
+ <Text bold color="$accent">
81
+ H2 — Section Heading (bold, $accent)
82
+ </Text>
83
+ <Text color="$primary">H3 — Group Heading ($primary)</Text>
84
+ <P>P — Body paragraph text</P>
85
+ <Lead>Lead — Introductory italic text</Lead>
86
+ <Muted>Muted — Secondary information</Muted>
87
+ <Small>Small — Fine print and captions</Small>
88
+ </Box>
89
+ </Box>
90
+ <Box flexDirection="column" flexGrow={1} flexBasis={0}>
91
+ <H2>Inline Styles</H2>
92
+ <Box flexDirection="column">
93
+ <Text>
94
+ <Strong>Strong</Strong> — bold emphasis
95
+ </Text>
96
+ <Text>
97
+ <Em>Em</Em> — italic emphasis
98
+ </Text>
99
+ <Text>
100
+ <Strong>
101
+ <Em>Strong + Em</Em>
102
+ </Strong>{" "}
103
+ — bold italic
104
+ </Text>
105
+ <Text>
106
+ <Text underline>Underline</Text> — underlined text
107
+ </Text>
108
+ <Text>
109
+ <Text strikethrough>Strikethrough</Text> — deleted text
110
+ </Text>
111
+ <Text>
112
+ <Code>Code</Code> — inline code span
113
+ </Text>
114
+ <Text>
115
+ <Kbd>Kbd</Kbd> — keyboard shortcut
116
+ </Text>
117
+ </Box>
118
+ </Box>
119
+ </Box>
120
+
121
+ <HR />
122
+
123
+ <H2>Semantic Colors</H2>
124
+ <Box flexDirection="column">
125
+ <Box gap={1}>
126
+ <Text backgroundColor="$primary" color="$primary-fg" bold>
127
+ {" $primary "}
128
+ </Text>
129
+ <Text backgroundColor="$accent" color="$accent-fg" bold>
130
+ {" $accent "}
131
+ </Text>
132
+ <Text backgroundColor="$success" color="$success-fg" bold>
133
+ {" $success "}
134
+ </Text>
135
+ <Text backgroundColor="$warning" color="$warning-fg" bold>
136
+ {" $warning "}
137
+ </Text>
138
+ <Text backgroundColor="$error" color="$error-fg" bold>
139
+ {" $error "}
140
+ </Text>
141
+ </Box>
142
+ <Box gap={1} marginTop={1}>
143
+ <Text color="$primary">{"████"} primary</Text>
144
+ <Text color="$accent">{"████"} accent</Text>
145
+ <Text color="$success">{"████"} success</Text>
146
+ <Text color="$warning">{"████"} warning</Text>
147
+ <Text color="$error">{"████"} error</Text>
148
+ <Text color="$muted">{"████"} muted</Text>
149
+ </Box>
150
+ </Box>
151
+
152
+ <HR />
153
+
154
+ <H2>Block Elements</H2>
155
+ <Blockquote>
156
+ The best color code is no color code — most components already use the right semantic tokens.
157
+ </Blockquote>
158
+ <CodeBlock>{"bun add silvery # install\nbun run dev # start dev server"}</CodeBlock>
159
+
160
+ <H2>Lists</H2>
161
+ <Box flexDirection="row" gap={4}>
162
+ <Box flexDirection="column" flexGrow={1} flexBasis={0}>
163
+ <H3>Unordered</H3>
164
+ <UL>
165
+ <LI>
166
+ <Strong>SelectList</Strong> — j/k navigation, scroll
167
+ </LI>
168
+ <LI>
169
+ <Strong>TextInput</Strong> — full readline support
170
+ </LI>
171
+ <LI>
172
+ <Strong>ModalDialog</Strong> — overlay with input blocking
173
+ </LI>
174
+ <LI>
175
+ <Strong>ProgressBar</Strong> — determinate + indeterminate
176
+ </LI>
177
+ </UL>
178
+ </Box>
179
+ <Box flexDirection="column" flexGrow={1} flexBasis={0}>
180
+ <H3>Ordered</H3>
181
+ <OL>
182
+ <LI>
183
+ Install with <Code>bun add silvery</Code>
184
+ </LI>
185
+ <LI>
186
+ Use <Code>$tokens</Code> for semantic colors
187
+ </LI>
188
+ <LI>
189
+ Layout with <Code>flexbox</Code> via Flexily
190
+ </LI>
191
+ <LI>
192
+ Test with <Code>createTermless()</Code>
193
+ </LI>
194
+ </OL>
195
+ </Box>
196
+ </Box>
197
+
198
+ <Small>silvery v0.0.1 — 38 palettes, 30+ components — silvery.dev</Small>
199
+ </Box>
200
+ )
201
+ }
202
+
203
+ // ============================================================================
204
+ // Inputs Tab
205
+ // ============================================================================
206
+
207
+ const frameworkItems = [
208
+ { label: "Silvery", value: "silvery" },
209
+ { label: "Ink", value: "ink" },
210
+ { label: "Blessed", value: "blessed", disabled: true },
211
+ { label: "Terminal Kit", value: "terminal-kit" },
212
+ { label: "React Curse", value: "react-curse" },
213
+ ]
214
+
215
+ function InputsTab() {
216
+ const [textValue, setTextValue] = useState("")
217
+ const [areaValue, setAreaValue] = useState("")
218
+ const [selectedFramework, setSelectedFramework] = useState(0)
219
+ const [darkMode, setDarkMode] = useState(true)
220
+ const [notifications, setNotifications] = useState(false)
221
+ const [autoSave, setAutoSave] = useState(true)
222
+ const [focusIndex, setFocusIndex] = useState(0)
223
+
224
+ const focusableCount = 5
225
+
226
+ useInput((_input: string, key: Key) => {
227
+ if (key.tab && !key.shift) {
228
+ setFocusIndex((prev) => (prev + 1) % focusableCount)
229
+ }
230
+ if (key.tab && key.shift) {
231
+ setFocusIndex((prev) => (prev - 1 + focusableCount) % focusableCount)
232
+ }
233
+ })
234
+
235
+ const resetAll = useCallback(() => {
236
+ setTextValue("")
237
+ setAreaValue("")
238
+ setSelectedFramework(0)
239
+ setDarkMode(true)
240
+ setNotifications(false)
241
+ setAutoSave(true)
242
+ }, [])
243
+
244
+ return (
245
+ <Box flexDirection="column" gap={1} paddingX={1} overflow="scroll" flexGrow={1}>
246
+ <Box flexDirection="row" gap={2} flexGrow={1}>
247
+ {/* Left column: Input controls */}
248
+ <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
249
+ <H2>Text Input</H2>
250
+ <TextInput
251
+ value={textValue}
252
+ onChange={setTextValue}
253
+ onSubmit={() => setTextValue("")}
254
+ placeholder="Type something..."
255
+ prompt="search: "
256
+ borderStyle="round"
257
+ isActive={focusIndex === 0}
258
+ />
259
+
260
+ <H2>Text Area</H2>
261
+ <TextArea
262
+ value={areaValue}
263
+ onChange={setAreaValue}
264
+ placeholder="Write your thoughts..."
265
+ height={4}
266
+ borderStyle="round"
267
+ isActive={focusIndex === 1}
268
+ />
269
+
270
+ <H2>Select List</H2>
271
+ <Box borderStyle="round" borderColor={focusIndex === 2 ? "$focusborder" : "$border"} paddingX={1}>
272
+ <SelectList
273
+ items={frameworkItems}
274
+ highlightedIndex={selectedFramework}
275
+ onHighlight={setSelectedFramework}
276
+ isActive={focusIndex === 2}
277
+ />
278
+ </Box>
279
+ </Box>
280
+
281
+ {/* Right column: Toggles + Summary */}
282
+ <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
283
+ <H2>Toggles</H2>
284
+ <Box
285
+ flexDirection="column"
286
+ borderStyle="round"
287
+ borderColor={focusIndex === 3 ? "$focusborder" : "$border"}
288
+ paddingX={1}
289
+ paddingY={1}
290
+ gap={1}
291
+ >
292
+ <Toggle value={darkMode} onChange={setDarkMode} label="Dark mode" isActive={focusIndex === 3} />
293
+ <Toggle value={notifications} onChange={setNotifications} label="Notifications" isActive={false} />
294
+ <Toggle value={autoSave} onChange={setAutoSave} label="Auto-save" isActive={false} />
295
+ </Box>
296
+
297
+ <H2>Button</H2>
298
+ <Button label="Reset All" onPress={resetAll} isActive={focusIndex === 4} />
299
+
300
+ <HR />
301
+
302
+ <H2>Current Values</H2>
303
+ <Box flexDirection="column" backgroundColor="$surfacebg" paddingX={1} paddingY={1} borderStyle="round">
304
+ <Text color="$surface">
305
+ <Strong>Text:</Strong> {textValue || <Muted>(empty)</Muted>}
306
+ </Text>
307
+ <Text color="$surface">
308
+ <Strong>Area:</Strong>{" "}
309
+ {areaValue ? areaValue.split("\n")[0] + (areaValue.includes("\n") ? "..." : "") : <Muted>(empty)</Muted>}
310
+ </Text>
311
+ <Text color="$surface">
312
+ <Strong>Framework:</Strong> {frameworkItems[selectedFramework]?.label}
313
+ </Text>
314
+ <Text color="$surface">
315
+ <Strong>Dark mode:</Strong> {darkMode ? "on" : "off"}
316
+ </Text>
317
+ <Text color="$surface">
318
+ <Strong>Notifications:</Strong> {notifications ? "on" : "off"}
319
+ </Text>
320
+ <Text color="$surface">
321
+ <Strong>Auto-save:</Strong> {autoSave ? "on" : "off"}
322
+ </Text>
323
+ </Box>
324
+ </Box>
325
+ </Box>
326
+
327
+ {/* keyboard hints removed for static screenshots */}
328
+ </Box>
329
+ )
330
+ }
331
+
332
+ // ============================================================================
333
+ // Display Tab
334
+ // ============================================================================
335
+
336
+ function DisplayTab({ scrollOffset }: { scrollOffset?: number }) {
337
+ const [showModal, setShowModal] = useState(false)
338
+ const [selectedBorder, setSelectedBorder] = useState(0)
339
+
340
+ const borderStyles = ["round", "bold", "single", "double", "classic"] as const
341
+
342
+ useInput((input: string, key: Key) => {
343
+ if (key.return && !showModal) {
344
+ setShowModal(true)
345
+ }
346
+ if ((key.escape || input === "q") && showModal) {
347
+ setShowModal(false)
348
+ }
349
+ if (input === "j" && !showModal) {
350
+ setSelectedBorder((prev) => Math.min(prev + 1, borderStyles.length - 1))
351
+ }
352
+ if (input === "k" && !showModal) {
353
+ setSelectedBorder((prev) => Math.max(prev - 1, 0))
354
+ }
355
+ })
356
+
357
+ const cell = {
358
+ flexGrow: 1,
359
+ flexBasis: 0,
360
+ borderStyle: "round" as const,
361
+ borderColor: "$border",
362
+ paddingX: 1,
363
+ paddingY: 1,
364
+ flexDirection: "column" as const,
365
+ gap: 1,
366
+ }
367
+
368
+ return (
369
+ <Box flexDirection="column" gap={1} paddingX={1}>
370
+ {/* Row 1 */}
371
+ <Box flexDirection="row" gap={1}>
372
+ <Box {...cell}>
373
+ <Text color="$primary">
374
+ <Strong>Progress Bars</Strong>
375
+ </Text>
376
+ <Box flexDirection="column">
377
+ <Box>
378
+ <Text color="$muted">{"Build "}</Text>
379
+ <Box flexGrow={1}>
380
+ <ProgressBar value={1.0} label="✓" />
381
+ </Box>
382
+ </Box>
383
+ <Box>
384
+ <Text color="$muted">{"Test "}</Text>
385
+ <Box flexGrow={1}>
386
+ <ProgressBar value={0.73} />
387
+ </Box>
388
+ </Box>
389
+ <Box>
390
+ <Text color="$muted">{"Deploy "}</Text>
391
+ <Box flexGrow={1}>
392
+ <ProgressBar value={0.35} />
393
+ </Box>
394
+ </Box>
395
+ <Box>
396
+ <Text color="$muted">{"Install "}</Text>
397
+ <Box flexGrow={1}>
398
+ <ProgressBar />
399
+ </Box>
400
+ </Box>
401
+ </Box>
402
+ <Box flexDirection="column">
403
+ <Spinner type="dots" label="Loading packages..." />
404
+ <Spinner type="line" label="Compiling..." />
405
+ <Spinner type="arc" label="Optimizing bundle..." />
406
+ </Box>
407
+ </Box>
408
+ <Box {...cell}>
409
+ <Text color="$primary">
410
+ <Strong>Input Controls</Strong>
411
+ </Text>
412
+ <Box flexDirection="column" gap={1}>
413
+ <Box gap={1}>
414
+ <Muted>Search:</Muted>
415
+ <Box flexGrow={1}>
416
+ <TextInput value="flutter widgets" onChange={() => {}} showUnderline underlineWidth={25} />
417
+ </Box>
418
+ </Box>
419
+ <Box gap={2} wrap="truncate">
420
+ <Toggle label="Dark mode" value={true} onChange={() => {}} />
421
+ <Toggle label="Notifications" value={false} onChange={() => {}} />
422
+ </Box>
423
+ </Box>
424
+ <SelectList
425
+ items={[
426
+ { label: "React", value: "react" },
427
+ { label: "Vue", value: "vue" },
428
+ { label: "Svelte", value: "svelte" },
429
+ { label: "Angular", value: "angular" },
430
+ ]}
431
+ highlightedIndex={0}
432
+ onHighlight={() => {}}
433
+ isActive={false}
434
+ />
435
+ </Box>
436
+ </Box>
437
+
438
+ {/* Row 2: Border Styles | Design Tokens | Modal Dialog */}
439
+ <Box flexDirection="row" gap={1}>
440
+ {/* Left half: two stacked boxes */}
441
+ <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
442
+ <Box {...cell} flexGrow={1} flexBasis={0}>
443
+ <Text color="$primary">
444
+ <Strong>Border Styles</Strong>
445
+ </Text>
446
+ <Box flexDirection="column" gap={0}>
447
+ {borderStyles.map((style, i) => (
448
+ <Box
449
+ key={style}
450
+ borderStyle={style as any}
451
+ borderColor={i === selectedBorder ? "$primary" : "$border"}
452
+ borderLeft={true}
453
+ borderRight={true}
454
+ borderTop={i === 0}
455
+ borderBottom={true}
456
+ paddingX={1}
457
+ >
458
+ <Text bold={i === selectedBorder}>
459
+ {i === selectedBorder ? "▸ " : " "}
460
+ {style}
461
+ </Text>
462
+ </Box>
463
+ ))}
464
+ </Box>
465
+ </Box>
466
+ <Box {...cell} flexGrow={1} flexBasis={0}>
467
+ <Text color="$primary">
468
+ <Strong>Design Tokens</Strong>
469
+ </Text>
470
+ <Box flexDirection="row" gap={2}>
471
+ <Box flexDirection="column" width={14}>
472
+ <Text color="$success">
473
+ {"●"} {"$success".padEnd(10)}
474
+ </Text>
475
+ <Text color="$warning">
476
+ {"●"} {"$warning".padEnd(10)}
477
+ </Text>
478
+ <Text color="$error">
479
+ {"●"} {"$error".padEnd(10)}
480
+ </Text>
481
+ <Text color="$info">
482
+ {"●"} {"$info".padEnd(10)}
483
+ </Text>
484
+ <Text color="$primary">
485
+ {"●"} {"$primary".padEnd(10)}
486
+ </Text>
487
+ <Muted>
488
+ {"●"} {"$muted".padEnd(10)}
489
+ </Muted>
490
+ </Box>
491
+ <Box flexDirection="column">
492
+ <Text backgroundColor="$primary" color="$primary-fg">
493
+ {" $primary "}
494
+ </Text>
495
+ <Text backgroundColor="$fg" color="$bg">
496
+ {" $inverse "}
497
+ </Text>
498
+ <Text backgroundColor="$muted-bg" color="$fg">
499
+ {" $surface "}
500
+ </Text>
501
+ <Text backgroundColor="$surfacebg" color="$surface">
502
+ {" $surfacebg "}
503
+ </Text>
504
+ </Box>
505
+ </Box>
506
+ </Box>
507
+ </Box>
508
+ {/* Right half: Modal Dialog */}
509
+ <Box {...cell} backgroundColor="$surfacebg" paddingRight={2}>
510
+ <Box justifyContent="space-between" paddingBottom={1}>
511
+ <Text color="$primary">
512
+ <Strong>Modal Dialog</Strong>
513
+ </Text>
514
+ <Small color="$muted">Esc to close</Small>
515
+ </Box>
516
+ <Box flexDirection="column" gap={1}>
517
+ <Box gap={1}>
518
+ <Muted>Branch:</Muted>
519
+ <TextInput value="main" onChange={() => {}} showUnderline underlineWidth={25} isActive={true} />
520
+ </Box>
521
+ <Box flexDirection="column">
522
+ <Text>
523
+ <Text color="$success">{"✓"}</Text> All checks passed
524
+ </Text>
525
+ <Text>
526
+ <Text color="$success">{"✓"}</Text> Tests: 247 passed
527
+ </Text>
528
+ <Text>
529
+ <Text color="$warning">{"⚠"}</Text> 2 deprecation warnings
530
+ </Text>
531
+ <Text>
532
+ <Text color="$muted">{"ℹ"}</Text> Deploy target: us-east-1
533
+ </Text>
534
+ </Box>
535
+ <Box gap={2}>
536
+ <Text backgroundColor="$primary" color="$primary-fg">
537
+ {" Deploy "}
538
+ </Text>
539
+ <Text backgroundColor="$muted-bg" color="$fg">
540
+ {" Cancel "}
541
+ </Text>
542
+ </Box>
543
+ </Box>
544
+ </Box>
545
+ </Box>
546
+
547
+ {showModal && (
548
+ <Box position="absolute" display="flex" justifyContent="center" alignItems="center" width="100%" height="100%">
549
+ <ModalDialog title="Component Gallery" width={50} footer="ESC or q to close">
550
+ <Box flexDirection="column" gap={1}>
551
+ <P>
552
+ This gallery demonstrates <Strong>silvery</Strong>'s built-in UI components. Every component uses
553
+ semantic theme tokens — they adapt to any of the 38 built-in palettes automatically.
554
+ </P>
555
+ <HR />
556
+ <Box flexDirection="column">
557
+ <Text color="$success">{"✓ Typography presets (H1-H3, Lead, Muted, Code)"}</Text>
558
+ <Text color="$success">{"✓ Input components (TextInput, TextArea, SelectList)"}</Text>
559
+ <Text color="$success">{"✓ Display widgets (ProgressBar, Spinner, Badge)"}</Text>
560
+ <Text color="$success">{"✓ Layout primitives (Box, Divider, border styles)"}</Text>
561
+ <Text color="$success">{"✓ Dialog system (ModalDialog with input blocking)"}</Text>
562
+ </Box>
563
+ </Box>
564
+ </ModalDialog>
565
+ </Box>
566
+ )}
567
+ </Box>
568
+ )
569
+ }
570
+
571
+ // ============================================================================
572
+ // App
573
+ // ============================================================================
574
+
575
+ export function ComponentsApp() {
576
+ const { exit } = useApp()
577
+ const [activeTab, setActiveTab] = useState("display")
578
+ const [scrollOffset, setScrollOffset] = useState(0)
579
+
580
+ // Reset scroll when switching tabs
581
+ const handleTabChange = useCallback((tab: string) => {
582
+ setActiveTab(tab)
583
+ setScrollOffset(0)
584
+ }, [])
585
+
586
+ const tabs = ["display", "inputs", "typography"] as const
587
+
588
+ useInput((input: string, key: Key) => {
589
+ // Only quit with q when not on the inputs tab (where user may be typing)
590
+ if (input === "q" && activeTab !== "inputs") {
591
+ exit()
592
+ }
593
+ if (key.escape && activeTab !== "display") {
594
+ exit()
595
+ }
596
+
597
+ // Tab switching with h/l
598
+ if (input === "l" && activeTab !== "inputs") {
599
+ const idx = tabs.indexOf(activeTab as (typeof tabs)[number])
600
+ handleTabChange(tabs[(idx + 1) % tabs.length]!)
601
+ return
602
+ }
603
+ if (input === "h" && activeTab !== "inputs") {
604
+ const idx = tabs.indexOf(activeTab as (typeof tabs)[number])
605
+ handleTabChange(tabs[(idx - 1 + tabs.length) % tabs.length]!)
606
+ return
607
+ }
608
+
609
+ // Arrow keys / j/k scroll the active tab content (typography and display tabs)
610
+ if (activeTab !== "inputs") {
611
+ if (key.downArrow || (activeTab === "typography" && input === "j")) {
612
+ setScrollOffset((prev) => prev + 1)
613
+ }
614
+ if (key.upArrow || (activeTab === "typography" && input === "k")) {
615
+ setScrollOffset((prev) => Math.max(0, prev - 1))
616
+ }
617
+ if (key.pageDown) {
618
+ setScrollOffset((prev) => prev + 10)
619
+ }
620
+ if (key.pageUp) {
621
+ setScrollOffset((prev) => Math.max(0, prev - 10))
622
+ }
623
+ if (key.home || (activeTab === "typography" && input === "g")) {
624
+ setScrollOffset(0)
625
+ }
626
+ if (key.end || (activeTab === "typography" && input === "G")) {
627
+ setScrollOffset(999) // will be clamped by scroll phase
628
+ }
629
+ }
630
+ })
631
+
632
+ return (
633
+ <Box flexDirection="column" flexGrow={1} padding={1}>
634
+ {activeTab === "display" && <DisplayTab scrollOffset={scrollOffset} />}
635
+ {activeTab === "inputs" && <InputsTab />}
636
+ {activeTab === "typography" && <TypographyTab scrollOffset={scrollOffset} />}
637
+ </Box>
638
+ )
639
+ }
640
+
641
+ // ============================================================================
642
+ // Main
643
+ // ============================================================================
644
+
645
+ export async function main() {
646
+ using term = createTerm()
647
+ const { waitUntilExit } = await render(
648
+ <ExampleBanner meta={meta} controls="h/l tab Tab cycle inputs j/k navigate Enter modal Esc/q quit">
649
+ <ComponentsApp />
650
+ </ExampleBanner>,
651
+ term,
652
+ )
653
+ await waitUntilExit()
654
+ }
655
+
656
+ if (import.meta.main) {
657
+ main().catch(console.error)
658
+ }
@@ -246,20 +246,20 @@ function ProcessRow({ proc, isSelected, width }: { proc: ProcessInfo; isSelected
246
246
 
247
247
  return (
248
248
  <Box paddingX={1} backgroundColor={isSelected ? "$primary" : undefined}>
249
- <Text color={isSelected ? "white" : "$muted"}>{String(proc.pid).padEnd(cols.pidW)}</Text>
250
- <Text bold={isSelected} color={isSelected ? "white" : 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
251
  {displayName.padEnd(cols.nameW)}
252
252
  </Text>
253
- <Text color={isSelected ? "white" : cpuColor}>{proc.cpu.toFixed(1).padStart(cols.cpuW - 1)}%</Text>
254
- <Text color={isSelected ? "white" : memColor}>{proc.mem.toFixed(1).padStart(cols.memW - 1)}%</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
255
  <Text>{" "}</Text>
256
- <Text color={isSelected ? "white" : STATUS_COLORS[proc.status]}>
256
+ <Text color={isSelected ? "$primary-fg" : STATUS_COLORS[proc.status]}>
257
257
  {STATUS_ICONS[proc.status]} {proc.status.padEnd(cols.statusW - 2)}
258
258
  </Text>
259
- <Text color={isSelected ? "white" : "$muted"}>{proc.user.padEnd(cols.userW)}</Text>
260
- <Text color={isSelected ? "white" : "$muted"}>{String(proc.threads).padStart(cols.threadsW)}</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
261
  <Text>{" "}</Text>
262
- <Text color={isSelected ? "white" : "$muted"}>{proc.uptime.padStart(cols.uptimeW)}</Text>
262
+ <Text color={isSelected ? "$primary-fg" : "$muted"}>{proc.uptime.padStart(cols.uptimeW)}</Text>
263
263
  </Box>
264
264
  )
265
265
  }