@silvery/examples 0.5.1 → 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 +31 -11
  30. package/bin/cli.ts +0 -293
  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
@@ -1,463 +0,0 @@
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
- ModalDialog,
47
- // Tabs
48
- Tabs,
49
- TabList,
50
- Tab,
51
- TabPanel,
52
- type Key,
53
- } from "silvery"
54
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
55
-
56
- export const meta: ExampleMeta = {
57
- name: "Components",
58
- description: "UI component gallery with typography, inputs, and dialogs",
59
- demo: true,
60
- features: ["Typography", "TextInput", "SelectList", "ModalDialog", "ProgressBar", "focus ring"],
61
- }
62
-
63
- // ============================================================================
64
- // Typography Tab
65
- // ============================================================================
66
-
67
- function TypographyTab() {
68
- return (
69
- <Box flexDirection="column" gap={1} paddingX={1} overflow="scroll" flexGrow={1}>
70
- <H1>Getting Started with Silvery</H1>
71
- <Lead>Build modern terminal UIs with React — layout feedback, semantic theming, and 30+ components.</Lead>
72
-
73
- <HR />
74
-
75
- <H2>Installation</H2>
76
- <P>
77
- Install silvery and its peer dependencies. The framework uses <Strong>React 19</Strong> with a custom reconciler
78
- — no DOM required.
79
- </P>
80
- <CodeBlock>{"bun add silvery"}</CodeBlock>
81
-
82
- <H2>Core Concepts</H2>
83
- <P>
84
- Silvery follows <Em>The Silvery Way</Em> — 10 principles that keep your TUI apps shiny. Here are the most
85
- important ones:
86
- </P>
87
-
88
- <H3>Use Built-in Components</H3>
89
- <P>
90
- <Code>silvery/ui</Code> ships 30+ components. They handle keyboard navigation, theming, mouse support, and
91
- dozens of edge cases.
92
- </P>
93
- <UL>
94
- <LI>
95
- <Strong>SelectList</Strong> — keyboard-navigable single-select with j/k, wrapping, and scroll
96
- </LI>
97
- <LI>
98
- <Strong>TextInput</Strong> — full readline: Ctrl+A/E/K/U, Alt+B/F, kill ring, clipboard
99
- </LI>
100
- <LI>
101
- <Strong>ModalDialog</Strong> — double-border dialog with title, footer, and input blocking
102
- </LI>
103
- <LI>
104
- <Strong>ProgressBar</Strong> — determinate and indeterminate modes with auto-width
105
- </LI>
106
- </UL>
107
-
108
- <H3>Semantic Theme Colors</H3>
109
- <P>
110
- Use <Code>$tokens</Code> instead of hardcoded colors. Your app adapts to 38 built-in palettes automatically:
111
- </P>
112
- <OL>
113
- <LI>
114
- <Text color="$primary">$primary</Text> — brand emphasis, active elements
115
- </LI>
116
- <LI>
117
- <Text color="$accent">$accent</Text> — contrasting hue for attention
118
- </LI>
119
- <LI>
120
- <Text color="$success">$success</Text> — completion, checkmarks
121
- </LI>
122
- <LI>
123
- <Text color="$warning">$warning</Text> — caution signals
124
- </LI>
125
- <LI>
126
- <Text color="$error">$error</Text> — failures, destructive actions
127
- </LI>
128
- </OL>
129
-
130
- <Blockquote>
131
- Less is more. The best color code is no color code — most components already use the right tokens.
132
- </Blockquote>
133
-
134
- <H3>Think in Flexbox</H3>
135
- <P>
136
- Silvery uses CSS flexbox via Flexily. Components know their size via <Code>useBoxRect()</Code> — synchronous,
137
- during render. No effects, no flash.
138
- </P>
139
-
140
- <Small>Last updated: silvery v0.0.1 — see silvery.dev for full documentation</Small>
141
- </Box>
142
- )
143
- }
144
-
145
- // ============================================================================
146
- // Inputs Tab
147
- // ============================================================================
148
-
149
- const frameworkItems = [
150
- { label: "Silvery", value: "silvery" },
151
- { label: "Ink", value: "ink" },
152
- { label: "Blessed", value: "blessed", disabled: true },
153
- { label: "Terminal Kit", value: "terminal-kit" },
154
- { label: "React Curse", value: "react-curse" },
155
- ]
156
-
157
- function InputsTab() {
158
- const [textValue, setTextValue] = useState("")
159
- const [areaValue, setAreaValue] = useState("")
160
- const [selectedFramework, setSelectedFramework] = useState(0)
161
- const [darkMode, setDarkMode] = useState(true)
162
- const [notifications, setNotifications] = useState(false)
163
- const [autoSave, setAutoSave] = useState(true)
164
- const [focusIndex, setFocusIndex] = useState(0)
165
-
166
- const focusableCount = 5
167
-
168
- useInput((_input: string, key: Key) => {
169
- if (key.tab && !key.shift) {
170
- setFocusIndex((prev) => (prev + 1) % focusableCount)
171
- }
172
- if (key.tab && key.shift) {
173
- setFocusIndex((prev) => (prev - 1 + focusableCount) % focusableCount)
174
- }
175
- })
176
-
177
- const resetAll = useCallback(() => {
178
- setTextValue("")
179
- setAreaValue("")
180
- setSelectedFramework(0)
181
- setDarkMode(true)
182
- setNotifications(false)
183
- setAutoSave(true)
184
- }, [])
185
-
186
- return (
187
- <Box flexDirection="column" gap={1} paddingX={1} overflow="scroll" flexGrow={1}>
188
- <Box flexDirection="row" gap={2} flexGrow={1}>
189
- {/* Left column: Input controls */}
190
- <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
191
- <H2>Text Input</H2>
192
- <TextInput
193
- value={textValue}
194
- onChange={setTextValue}
195
- onSubmit={() => setTextValue("")}
196
- placeholder="Type something..."
197
- prompt="search: "
198
- borderStyle="round"
199
- isActive={focusIndex === 0}
200
- />
201
-
202
- <H2>Text Area</H2>
203
- <TextArea
204
- value={areaValue}
205
- onChange={setAreaValue}
206
- placeholder="Write your thoughts..."
207
- height={4}
208
- borderStyle="round"
209
- isActive={focusIndex === 1}
210
- />
211
-
212
- <H2>Select List</H2>
213
- <Box borderStyle="round" borderColor={focusIndex === 2 ? "$focusborder" : "$border"} paddingX={1}>
214
- <SelectList
215
- items={frameworkItems}
216
- highlightedIndex={selectedFramework}
217
- onHighlight={setSelectedFramework}
218
- isActive={focusIndex === 2}
219
- />
220
- </Box>
221
- </Box>
222
-
223
- {/* Right column: Toggles + Summary */}
224
- <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
225
- <H2>Toggles</H2>
226
- <Box
227
- flexDirection="column"
228
- borderStyle="round"
229
- borderColor={focusIndex === 3 ? "$focusborder" : "$border"}
230
- paddingX={1}
231
- paddingY={1}
232
- gap={1}
233
- >
234
- <Toggle value={darkMode} onChange={setDarkMode} label="Dark mode" isActive={focusIndex === 3} />
235
- <Toggle value={notifications} onChange={setNotifications} label="Notifications" isActive={false} />
236
- <Toggle value={autoSave} onChange={setAutoSave} label="Auto-save" isActive={false} />
237
- </Box>
238
-
239
- <H2>Button</H2>
240
- <Button label="Reset All" onPress={resetAll} isActive={focusIndex === 4} />
241
-
242
- <HR />
243
-
244
- <H2>Current Values</H2>
245
- <Box flexDirection="column" backgroundColor="$surfacebg" paddingX={1} paddingY={1} borderStyle="round">
246
- <Text color="$surface">
247
- <Strong>Text:</Strong> {textValue || <Muted>(empty)</Muted>}
248
- </Text>
249
- <Text color="$surface">
250
- <Strong>Area:</Strong>{" "}
251
- {areaValue ? areaValue.split("\n")[0] + (areaValue.includes("\n") ? "..." : "") : <Muted>(empty)</Muted>}
252
- </Text>
253
- <Text color="$surface">
254
- <Strong>Framework:</Strong> {frameworkItems[selectedFramework]?.label}
255
- </Text>
256
- <Text color="$surface">
257
- <Strong>Dark mode:</Strong> {darkMode ? "on" : "off"}
258
- </Text>
259
- <Text color="$surface">
260
- <Strong>Notifications:</Strong> {notifications ? "on" : "off"}
261
- </Text>
262
- <Text color="$surface">
263
- <Strong>Auto-save:</Strong> {autoSave ? "on" : "off"}
264
- </Text>
265
- </Box>
266
- </Box>
267
- </Box>
268
-
269
- <Small>Tab/Shift+Tab to cycle focus — Space toggles — Enter submits</Small>
270
- </Box>
271
- )
272
- }
273
-
274
- // ============================================================================
275
- // Display Tab
276
- // ============================================================================
277
-
278
- function DisplayTab() {
279
- const [showModal, setShowModal] = useState(false)
280
- const [selectedBorder, setSelectedBorder] = useState(0)
281
-
282
- const borderStyles = ["round", "bold", "single", "double", "classic"] as const
283
-
284
- useInput((input: string, key: Key) => {
285
- if (key.return && !showModal) {
286
- setShowModal(true)
287
- }
288
- if ((key.escape || input === "q") && showModal) {
289
- setShowModal(false)
290
- }
291
- if (input === "j" && !showModal) {
292
- setSelectedBorder((prev) => Math.min(prev + 1, borderStyles.length - 1))
293
- }
294
- if (input === "k" && !showModal) {
295
- setSelectedBorder((prev) => Math.max(prev - 1, 0))
296
- }
297
- })
298
-
299
- return (
300
- <Box flexDirection="column" gap={1} paddingX={1} overflow="scroll" flexGrow={1}>
301
- <Box flexDirection="row" gap={2} flexGrow={1}>
302
- {/* Left column */}
303
- <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
304
- <H2>Progress Bars</H2>
305
- <Box flexDirection="column" gap={1}>
306
- <Box>
307
- <Text color="$muted">{"Build "}</Text>
308
- <Box flexGrow={1}>
309
- <ProgressBar value={1.0} label="✓" />
310
- </Box>
311
- </Box>
312
- <Box>
313
- <Text color="$muted">{"Test "}</Text>
314
- <Box flexGrow={1}>
315
- <ProgressBar value={0.73} />
316
- </Box>
317
- </Box>
318
- <Box>
319
- <Text color="$muted">{"Deploy "}</Text>
320
- <Box flexGrow={1}>
321
- <ProgressBar value={0.35} />
322
- </Box>
323
- </Box>
324
- <Box>
325
- <Text color="$muted">{"Install "}</Text>
326
- <Box flexGrow={1}>
327
- <ProgressBar />
328
- </Box>
329
- </Box>
330
- </Box>
331
-
332
- <H2>Spinners</H2>
333
- <Box flexDirection="column">
334
- <Spinner type="dots" label="Loading packages..." />
335
- <Spinner type="line" label="Compiling..." />
336
- <Spinner type="arc" label="Optimizing bundle..." />
337
- <Spinner type="bounce" label="Connecting..." />
338
- </Box>
339
-
340
- <H2>Badges</H2>
341
- <Box gap={1} flexWrap="wrap">
342
- <Badge label="Stable" variant="success" />
343
- <Badge label="Beta" variant="warning" />
344
- <Badge label="Deprecated" variant="error" />
345
- <Badge label="v0.0.1" variant="primary" />
346
- <Badge label="MIT" />
347
- </Box>
348
- </Box>
349
-
350
- {/* Right column */}
351
- <Box flexDirection="column" gap={1} flexGrow={1} flexBasis={0}>
352
- <H2>Border Styles</H2>
353
- <Box flexDirection="column" gap={1}>
354
- {borderStyles.map((style, i) => (
355
- <Box
356
- key={style}
357
- borderStyle={style as any}
358
- borderColor={i === selectedBorder ? "$primary" : "$border"}
359
- paddingX={1}
360
- >
361
- <Text bold={i === selectedBorder}>
362
- {i === selectedBorder ? "▸ " : " "}
363
- {style}
364
- </Text>
365
- </Box>
366
- ))}
367
- </Box>
368
-
369
- <Divider title="Status" />
370
-
371
- <Box flexDirection="column">
372
- <Text color="$success">✓ All checks passed</Text>
373
- <Text color="$warning">⚠ 2 deprecation warnings</Text>
374
- <Text color="$error">✗ 1 vulnerability found</Text>
375
- <Text color="$info">ℹ 47 packages installed</Text>
376
- </Box>
377
-
378
- <Small>j/k select border — Enter opens modal — q quits</Small>
379
- </Box>
380
- </Box>
381
-
382
- {showModal && (
383
- <Box position="absolute" display="flex" justifyContent="center" alignItems="center" width="100%" height="100%">
384
- <ModalDialog title="Component Gallery" width={50} footer="ESC or q to close">
385
- <Box flexDirection="column" gap={1}>
386
- <P>
387
- This gallery demonstrates <Strong>silvery</Strong>'s built-in UI components. Every component uses
388
- semantic theme tokens — they adapt to any of the 38 built-in palettes automatically.
389
- </P>
390
- <HR />
391
- <Box flexDirection="column">
392
- <Text color="$success">✓ Typography presets (H1-H3, Lead, Muted, Code)</Text>
393
- <Text color="$success">✓ Input components (TextInput, TextArea, SelectList)</Text>
394
- <Text color="$success">✓ Display widgets (ProgressBar, Spinner, Badge)</Text>
395
- <Text color="$success">✓ Layout primitives (Box, Divider, border styles)</Text>
396
- <Text color="$success">✓ Dialog system (ModalDialog with input blocking)</Text>
397
- </Box>
398
- </Box>
399
- </ModalDialog>
400
- </Box>
401
- )}
402
- </Box>
403
- )
404
- }
405
-
406
- // ============================================================================
407
- // App
408
- // ============================================================================
409
-
410
- export function ComponentsApp() {
411
- const { exit } = useApp()
412
- const [activeTab, setActiveTab] = useState("typography")
413
-
414
- useInput((input: string, key: Key) => {
415
- // Only quit with q when not on the inputs tab (where user may be typing)
416
- if (input === "q" && activeTab !== "inputs") {
417
- exit()
418
- }
419
- if (key.escape && activeTab !== "display") {
420
- exit()
421
- }
422
- })
423
-
424
- return (
425
- <Box flexDirection="column" flexGrow={1}>
426
- <Tabs defaultValue="typography" onChange={setActiveTab}>
427
- <TabList>
428
- <Tab value="typography">Typography</Tab>
429
- <Tab value="inputs">Inputs</Tab>
430
- <Tab value="display">Display</Tab>
431
- </TabList>
432
- <TabPanel value="typography">
433
- <TypographyTab />
434
- </TabPanel>
435
- <TabPanel value="inputs">
436
- <InputsTab />
437
- </TabPanel>
438
- <TabPanel value="display">
439
- <DisplayTab />
440
- </TabPanel>
441
- </Tabs>
442
- </Box>
443
- )
444
- }
445
-
446
- // ============================================================================
447
- // Main
448
- // ============================================================================
449
-
450
- export async function main() {
451
- using term = createTerm()
452
- const { waitUntilExit } = await render(
453
- <ExampleBanner meta={meta} controls="h/l tab Tab cycle inputs j/k navigate Enter modal Esc/q quit">
454
- <ComponentsApp />
455
- </ExampleBanner>,
456
- term,
457
- )
458
- await waitUntilExit()
459
- }
460
-
461
- if (import.meta.main) {
462
- main().catch(console.error)
463
- }
@@ -1,91 +0,0 @@
1
- /**
2
- * TextArea Example
3
- *
4
- * A simple note editor demonstrating:
5
- * - Multi-line text input with word wrapping
6
- * - Cursor movement (arrow keys, Home/End, Ctrl+A/E)
7
- * - Kill operations (Ctrl+K, Ctrl+U)
8
- * - Scrolling within the textarea (PageUp/PageDown)
9
- * - Submit with Ctrl+Enter
10
- */
11
-
12
- import React, { useState } from "react"
13
- import { render, Box, Text, H1, Strong, Muted, TextArea, useInput, useApp, createTerm, type Key } from "silvery"
14
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
15
-
16
- export const meta: ExampleMeta = {
17
- name: "TextArea",
18
- description: "Multi-line text input with word wrap, scrolling, and kill operations",
19
- features: ["TextArea", "useBoxRect()", "Ctrl+Enter submit"],
20
- }
21
-
22
- export function NoteEditor() {
23
- const { exit } = useApp()
24
- const [notes, setNotes] = useState<string[]>([])
25
- const [value, setValue] = useState("")
26
-
27
- useInput((input: string, key: Key) => {
28
- if (key.escape) {
29
- exit()
30
- }
31
- })
32
-
33
- function handleSubmit(text: string) {
34
- if (text.trim()) {
35
- setNotes((prev) => [...prev, text.trim()])
36
- setValue("")
37
- }
38
- }
39
-
40
- return (
41
- <Box flexDirection="column" padding={1}>
42
- {notes.length > 0 && (
43
- <Box flexDirection="column" marginBottom={1}>
44
- {notes.map((note, i) => (
45
- <Box key={i} borderStyle="round" borderColor="gray" paddingX={1}>
46
- <Text>
47
- <Strong color="$success">#{i + 1}</Strong> {note}
48
- </Text>
49
- </Box>
50
- ))}
51
- </Box>
52
- )}
53
-
54
- <Box borderStyle="single" borderColor="$primary" flexDirection="column">
55
- <Box paddingX={1}>
56
- <H1>New Note</H1>
57
- </Box>
58
- <Box paddingX={1}>
59
- <TextArea
60
- value={value}
61
- onChange={setValue}
62
- onSubmit={handleSubmit}
63
- height={6}
64
- placeholder="Start typing..."
65
- />
66
- </Box>
67
- </Box>
68
-
69
- <Box marginTop={1}>
70
- <Muted>
71
- {notes.length} note{notes.length !== 1 ? "s" : ""} submitted
72
- </Muted>
73
- </Box>
74
- </Box>
75
- )
76
- }
77
-
78
- async function main() {
79
- using term = createTerm()
80
- const { waitUntilExit } = await render(
81
- <ExampleBanner meta={meta} controls="Ctrl+Enter submit Esc quit">
82
- <NoteEditor />
83
- </ExampleBanner>,
84
- term,
85
- )
86
- await waitUntilExit()
87
- }
88
-
89
- if (import.meta.main) {
90
- main().catch(console.error)
91
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes