@silvery/examples 0.5.2 → 0.5.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.
- package/LICENSE +21 -0
- package/{examples/apps → apps}/aichat/index.tsx +4 -3
- package/{examples/apps → apps}/async-data.tsx +4 -4
- package/apps/components.tsx +658 -0
- package/{examples/apps → apps}/data-explorer.tsx +8 -8
- package/{examples/apps → apps}/dev-tools.tsx +35 -19
- package/{examples/apps → apps}/inline-bench.tsx +3 -1
- package/{examples/apps → apps}/kanban.tsx +20 -22
- package/{examples/apps → apps}/layout-ref.tsx +6 -6
- package/{examples/apps → apps}/panes/index.tsx +1 -1
- package/{examples/apps → apps}/paste-demo.tsx +2 -2
- package/{examples/apps → apps}/scroll.tsx +2 -2
- package/{examples/apps → apps}/search-filter.tsx +1 -1
- package/apps/selection.tsx +342 -0
- package/apps/spatial-focus-demo.tsx +368 -0
- package/{examples/apps → apps}/task-list.tsx +1 -1
- package/apps/terminal-caps-demo.tsx +334 -0
- package/apps/text-selection-demo.tsx +189 -0
- package/apps/textarea.tsx +155 -0
- package/{examples/apps → apps}/theme.tsx +1 -1
- package/apps/vterm-demo/index.tsx +216 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +190 -0
- package/dist/cli.mjs.map +1 -0
- package/layout/dashboard.tsx +953 -0
- package/layout/live-resize.tsx +282 -0
- package/layout/overflow.tsx +51 -0
- package/layout/text-layout.tsx +283 -0
- package/package.json +27 -11
- package/bin/cli.ts +0 -294
- package/examples/apps/components.tsx +0 -463
- package/examples/apps/textarea.tsx +0 -91
- /package/{examples/_banner.tsx → _banner.tsx} +0 -0
- /package/{examples/apps → apps}/aichat/components.tsx +0 -0
- /package/{examples/apps → apps}/aichat/script.ts +0 -0
- /package/{examples/apps → apps}/aichat/state.ts +0 -0
- /package/{examples/apps → apps}/aichat/types.ts +0 -0
- /package/{examples/apps → apps}/app-todo.tsx +0 -0
- /package/{examples/apps → apps}/cli-wizard.tsx +0 -0
- /package/{examples/apps → apps}/clipboard.tsx +0 -0
- /package/{examples/apps → apps}/explorer.tsx +0 -0
- /package/{examples/apps → apps}/gallery.tsx +0 -0
- /package/{examples/apps → apps}/outline.tsx +0 -0
- /package/{examples/apps → apps}/terminal.tsx +0 -0
- /package/{examples/apps → apps}/transform.tsx +0 -0
- /package/{examples/apps → apps}/virtual-10k.tsx +0 -0
- /package/{examples/components → components}/counter.tsx +0 -0
- /package/{examples/components → components}/hello.tsx +0 -0
- /package/{examples/components → components}/progress-bar.tsx +0 -0
- /package/{examples/components → components}/select-list.tsx +0 -0
- /package/{examples/components → components}/spinner.tsx +0 -0
- /package/{examples/components → components}/text-input.tsx +0 -0
- /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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|