@silvery/examples 0.5.6 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
- package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
- package/dist/_banner-DLPxCqVy.mjs +44 -0
- package/dist/ansi-CCE2pVS0.mjs +16397 -0
- package/dist/apng-HhhBjRGt.mjs +68 -0
- package/dist/apng-mwUQbTTF.mjs +3 -0
- package/dist/apps/aichat/index.mjs +1299 -0
- package/dist/apps/app-todo.mjs +139 -0
- package/dist/apps/async-data.mjs +204 -0
- package/dist/apps/cli-wizard.mjs +339 -0
- package/dist/apps/clipboard.mjs +198 -0
- package/dist/apps/components.mjs +864 -0
- package/dist/apps/data-explorer.mjs +483 -0
- package/dist/apps/dev-tools.mjs +397 -0
- package/dist/apps/explorer.mjs +698 -0
- package/dist/apps/gallery.mjs +766 -0
- package/dist/apps/inline-bench.mjs +115 -0
- package/dist/apps/kanban.mjs +280 -0
- package/dist/apps/layout-ref.mjs +187 -0
- package/dist/apps/outline.mjs +203 -0
- package/dist/apps/paste-demo.mjs +189 -0
- package/dist/apps/scroll.mjs +86 -0
- package/dist/apps/search-filter.mjs +287 -0
- package/dist/apps/selection.mjs +355 -0
- package/dist/apps/spatial-focus-demo.mjs +388 -0
- package/dist/apps/task-list.mjs +258 -0
- package/dist/apps/terminal-caps-demo.mjs +315 -0
- package/dist/apps/terminal.mjs +872 -0
- package/dist/apps/text-selection-demo.mjs +254 -0
- package/dist/apps/textarea.mjs +178 -0
- package/dist/apps/theme.mjs +661 -0
- package/dist/apps/transform.mjs +215 -0
- package/dist/apps/virtual-10k.mjs +422 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backends-Bahh9mKN.mjs +1179 -0
- package/dist/backends-CCtCDQ94.mjs +3 -0
- package/dist/{cli.mjs → bin/cli.mjs} +21 -25
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/components/counter.mjs +48 -0
- package/dist/components/hello.mjs +31 -0
- package/dist/components/progress-bar.mjs +59 -0
- package/dist/components/select-list.mjs +85 -0
- package/dist/components/spinner.mjs +57 -0
- package/dist/components/text-input.mjs +62 -0
- package/dist/components/virtual-list.mjs +51 -0
- package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
- package/dist/gif-BZaqPPVX.mjs +3 -0
- package/dist/gif-BtnXuxLF.mjs +71 -0
- package/dist/gifenc-CLRW41dk.mjs +728 -0
- package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
- package/dist/key-mapping-5oYQdAQE.mjs +3 -0
- package/dist/key-mapping-D4LR1go6.mjs +130 -0
- package/dist/layout/dashboard.mjs +1204 -0
- package/dist/layout/live-resize.mjs +303 -0
- package/dist/layout/overflow.mjs +70 -0
- package/dist/layout/text-layout.mjs +335 -0
- package/dist/node-NuJ94BWl.mjs +1083 -0
- package/dist/plugins-D1KtkT4a.mjs +3057 -0
- package/dist/resvg-js-C_8Wps1F.mjs +201 -0
- package/dist/src-BTEVGpd9.mjs +23538 -0
- package/dist/src-CUUOuRH6.mjs +5322 -0
- package/dist/src-CzfRafCQ.mjs +814 -0
- package/dist/usingCtx-CsEf0xO3.mjs +57 -0
- package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
- package/package.json +19 -14
- package/_banner.tsx +0 -60
- package/apps/aichat/components.tsx +0 -469
- package/apps/aichat/index.tsx +0 -220
- package/apps/aichat/script.ts +0 -460
- package/apps/aichat/state.ts +0 -325
- package/apps/aichat/types.ts +0 -19
- package/apps/app-todo.tsx +0 -201
- package/apps/async-data.tsx +0 -196
- package/apps/cli-wizard.tsx +0 -332
- package/apps/clipboard.tsx +0 -183
- package/apps/components.tsx +0 -658
- package/apps/data-explorer.tsx +0 -490
- package/apps/dev-tools.tsx +0 -395
- package/apps/explorer.tsx +0 -731
- package/apps/gallery.tsx +0 -653
- package/apps/inline-bench.tsx +0 -138
- package/apps/kanban.tsx +0 -265
- package/apps/layout-ref.tsx +0 -173
- package/apps/outline.tsx +0 -160
- package/apps/panes/index.tsx +0 -203
- package/apps/paste-demo.tsx +0 -185
- package/apps/scroll.tsx +0 -77
- package/apps/search-filter.tsx +0 -240
- package/apps/selection.tsx +0 -342
- package/apps/spatial-focus-demo.tsx +0 -368
- package/apps/task-list.tsx +0 -271
- package/apps/terminal-caps-demo.tsx +0 -334
- package/apps/terminal.tsx +0 -800
- package/apps/text-selection-demo.tsx +0 -189
- package/apps/textarea.tsx +0 -155
- package/apps/theme.tsx +0 -515
- package/apps/transform.tsx +0 -229
- package/apps/virtual-10k.tsx +0 -405
- package/apps/vterm-demo/index.tsx +0 -216
- package/components/counter.tsx +0 -45
- package/components/hello.tsx +0 -34
- package/components/progress-bar.tsx +0 -48
- package/components/select-list.tsx +0 -50
- package/components/spinner.tsx +0 -40
- package/components/text-input.tsx +0 -57
- package/components/virtual-list.tsx +0 -52
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs.map +0 -1
- package/layout/dashboard.tsx +0 -953
- package/layout/live-resize.tsx +0 -282
- package/layout/overflow.tsx +0 -51
- package/layout/text-layout.tsx +0 -283
package/apps/async-data.tsx
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Async Data Example
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates React Suspense for async data loading:
|
|
5
|
-
* - Suspense boundaries with fallback UI
|
|
6
|
-
* - Multiple independent suspending components
|
|
7
|
-
* - Error handling with ErrorBoundary
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import React, { Suspense, useState, use } from "react"
|
|
11
|
-
import { render, Box, Text, H1, Kbd, Muted, useInput, useApp, createTerm, ErrorBoundary, type Key } from "silvery"
|
|
12
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
13
|
-
|
|
14
|
-
export const meta: ExampleMeta = {
|
|
15
|
-
name: "Async Data",
|
|
16
|
-
description: "React Suspense with independent data sources and error boundaries",
|
|
17
|
-
features: ["React Suspense", "use() hook", "ErrorBoundary"],
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Data Fetching (simulated)
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
// Cache for promises (React's use() requires stable promise references)
|
|
25
|
-
const cache = new Map<string, Promise<unknown>>()
|
|
26
|
-
|
|
27
|
-
function fetchData<T>(key: string, ms: number, data: T): Promise<T> {
|
|
28
|
-
if (!cache.has(key)) {
|
|
29
|
-
cache.set(key, new Promise<T>((resolve) => setTimeout(() => resolve(data), ms)))
|
|
30
|
-
}
|
|
31
|
-
return cache.get(key) as Promise<T>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function clearCache() {
|
|
35
|
-
cache.clear()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ============================================================================
|
|
39
|
-
// Async Components
|
|
40
|
-
// ============================================================================
|
|
41
|
-
|
|
42
|
-
interface UserData {
|
|
43
|
-
name: string
|
|
44
|
-
email: string
|
|
45
|
-
role: string
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function UserProfile() {
|
|
49
|
-
const user = use(
|
|
50
|
-
fetchData<UserData>("user", 1500, {
|
|
51
|
-
name: "Alice Chen",
|
|
52
|
-
email: "alice@example.com",
|
|
53
|
-
role: "Developer",
|
|
54
|
-
}),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$success" padding={1}>
|
|
59
|
-
<H1 color="$success">User Profile</H1>
|
|
60
|
-
<Text>Name: {user.name}</Text>
|
|
61
|
-
<Text>Email: {user.email}</Text>
|
|
62
|
-
<Text>Role: {user.role}</Text>
|
|
63
|
-
</Box>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface StatsData {
|
|
68
|
-
projects: number
|
|
69
|
-
commits: number
|
|
70
|
-
reviews: number
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function Statistics() {
|
|
74
|
-
const stats = use(
|
|
75
|
-
fetchData<StatsData>("stats", 2500, {
|
|
76
|
-
projects: 12,
|
|
77
|
-
commits: 847,
|
|
78
|
-
reviews: 156,
|
|
79
|
-
}),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$primary" padding={1}>
|
|
84
|
-
<H1>Statistics</H1>
|
|
85
|
-
<Text>Projects: {stats.projects}</Text>
|
|
86
|
-
<Text>Commits: {stats.commits}</Text>
|
|
87
|
-
<Text>Reviews: {stats.reviews}</Text>
|
|
88
|
-
</Box>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface Activity {
|
|
93
|
-
id: number
|
|
94
|
-
action: string
|
|
95
|
-
time: string
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function RecentActivity() {
|
|
99
|
-
const activities = use(
|
|
100
|
-
fetchData<Activity[]>("activity", 3500, [
|
|
101
|
-
{ id: 1, action: "Merged PR #423", time: "2h ago" },
|
|
102
|
-
{ id: 2, action: "Reviewed PR #421", time: "4h ago" },
|
|
103
|
-
{ id: 3, action: "Created issue #89", time: "1d ago" },
|
|
104
|
-
]),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$warning" padding={1}>
|
|
109
|
-
<H1 color="$warning">Recent Activity</H1>
|
|
110
|
-
{activities.map((a) => (
|
|
111
|
-
<Text key={a.id}>
|
|
112
|
-
<Text dim>{a.time}</Text> {a.action}
|
|
113
|
-
</Text>
|
|
114
|
-
))}
|
|
115
|
-
</Box>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Loading fallbacks
|
|
120
|
-
function LoadingBox({ label }: { label: string }) {
|
|
121
|
-
return (
|
|
122
|
-
<Box borderStyle="round" borderColor="$border" padding={1}>
|
|
123
|
-
<Text color="$muted">Loading {label}...</Text>
|
|
124
|
-
</Box>
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ============================================================================
|
|
129
|
-
// Main App
|
|
130
|
-
// ============================================================================
|
|
131
|
-
|
|
132
|
-
export function AsyncDataApp() {
|
|
133
|
-
const { exit } = useApp()
|
|
134
|
-
const [refreshKey, setRefreshKey] = useState(0)
|
|
135
|
-
|
|
136
|
-
useInput((input: string, key: Key) => {
|
|
137
|
-
if (input === "q" || key.escape) {
|
|
138
|
-
exit()
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
if (input === "r") {
|
|
142
|
-
// Refresh: clear cache and force re-render
|
|
143
|
-
clearCache()
|
|
144
|
-
setRefreshKey((k) => k + 1)
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
return (
|
|
149
|
-
<Box flexDirection="column" padding={1} key={refreshKey}>
|
|
150
|
-
<Box flexGrow={1} flexDirection="row" gap={1}>
|
|
151
|
-
{/* Each Suspense boundary loads independently */}
|
|
152
|
-
<ErrorBoundary fallback={<Text color="$error">User error</Text>}>
|
|
153
|
-
<Suspense fallback={<LoadingBox label="user" />}>
|
|
154
|
-
<UserProfile />
|
|
155
|
-
</Suspense>
|
|
156
|
-
</ErrorBoundary>
|
|
157
|
-
|
|
158
|
-
<ErrorBoundary fallback={<Text color="$error">Stats error</Text>}>
|
|
159
|
-
<Suspense fallback={<LoadingBox label="stats" />}>
|
|
160
|
-
<Statistics />
|
|
161
|
-
</Suspense>
|
|
162
|
-
</ErrorBoundary>
|
|
163
|
-
|
|
164
|
-
<ErrorBoundary fallback={<Text color="$error">Activity error</Text>}>
|
|
165
|
-
<Suspense fallback={<LoadingBox label="activity" />}>
|
|
166
|
-
<RecentActivity />
|
|
167
|
-
</Suspense>
|
|
168
|
-
</ErrorBoundary>
|
|
169
|
-
</Box>
|
|
170
|
-
|
|
171
|
-
<Muted>
|
|
172
|
-
{" "}
|
|
173
|
-
<Kbd>r</Kbd> refresh <Kbd>Esc/q</Kbd> quit
|
|
174
|
-
</Muted>
|
|
175
|
-
</Box>
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ============================================================================
|
|
180
|
-
// Main
|
|
181
|
-
// ============================================================================
|
|
182
|
-
|
|
183
|
-
async function main() {
|
|
184
|
-
using term = createTerm()
|
|
185
|
-
const { waitUntilExit } = await render(
|
|
186
|
-
<ExampleBanner meta={meta} controls="r refresh Esc/q quit">
|
|
187
|
-
<AsyncDataApp />
|
|
188
|
-
</ExampleBanner>,
|
|
189
|
-
term,
|
|
190
|
-
)
|
|
191
|
-
await waitUntilExit()
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (import.meta.main) {
|
|
195
|
-
main().catch(console.error)
|
|
196
|
-
}
|
package/apps/cli-wizard.tsx
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Wizard Example
|
|
3
|
-
*
|
|
4
|
-
* A multi-step project scaffolding wizard demonstrating:
|
|
5
|
-
* - SelectList for option selection with keyboard navigation
|
|
6
|
-
* - TextInput for free-form text entry
|
|
7
|
-
* - ProgressBar for visual progress feedback
|
|
8
|
-
* - Step-by-step flow with state transitions
|
|
9
|
-
*
|
|
10
|
-
* Usage: bun run examples/apps/cli-wizard.tsx
|
|
11
|
-
*
|
|
12
|
-
* Controls:
|
|
13
|
-
* j/k or Up/Down - Navigate options (step 1)
|
|
14
|
-
* Enter - Confirm selection / submit input
|
|
15
|
-
* Type - Enter project name (step 2)
|
|
16
|
-
* q or Esc - Quit (when not typing)
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import React, { useState, useCallback, useEffect } from "react"
|
|
20
|
-
import {
|
|
21
|
-
render,
|
|
22
|
-
Box,
|
|
23
|
-
Text,
|
|
24
|
-
SelectList,
|
|
25
|
-
TextInput,
|
|
26
|
-
ProgressBar,
|
|
27
|
-
Spinner,
|
|
28
|
-
useInput,
|
|
29
|
-
useApp,
|
|
30
|
-
createTerm,
|
|
31
|
-
H1,
|
|
32
|
-
Muted,
|
|
33
|
-
Lead,
|
|
34
|
-
Kbd,
|
|
35
|
-
Code,
|
|
36
|
-
type Key,
|
|
37
|
-
type SelectOption,
|
|
38
|
-
} from "silvery"
|
|
39
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
40
|
-
|
|
41
|
-
export const meta: ExampleMeta = {
|
|
42
|
-
name: "CLI Wizard",
|
|
43
|
-
description: "Multi-step project scaffolding wizard with selection, input, and progress",
|
|
44
|
-
demo: true,
|
|
45
|
-
features: ["SelectList", "TextInput", "ProgressBar", "Spinner", "useInput()"],
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ============================================================================
|
|
49
|
-
// Types
|
|
50
|
-
// ============================================================================
|
|
51
|
-
|
|
52
|
-
type WizardStep = "framework" | "name" | "installing" | "done"
|
|
53
|
-
|
|
54
|
-
interface WizardState {
|
|
55
|
-
step: WizardStep
|
|
56
|
-
framework: string | null
|
|
57
|
-
projectName: string
|
|
58
|
-
progress: number
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ============================================================================
|
|
62
|
-
// Data
|
|
63
|
-
// ============================================================================
|
|
64
|
-
|
|
65
|
-
const FRAMEWORKS: SelectOption[] = [
|
|
66
|
-
{ label: "React — A JavaScript library for building user interfaces", value: "react" },
|
|
67
|
-
{ label: "Vue — The progressive JavaScript framework", value: "vue" },
|
|
68
|
-
{ label: "Svelte — Cybernetically enhanced web apps", value: "svelte" },
|
|
69
|
-
{ label: "Solid — Simple and performant reactivity", value: "solid" },
|
|
70
|
-
{
|
|
71
|
-
label: "Angular — Platform for building web applications",
|
|
72
|
-
value: "angular",
|
|
73
|
-
disabled: true,
|
|
74
|
-
},
|
|
75
|
-
]
|
|
76
|
-
|
|
77
|
-
const INSTALL_STEPS = [
|
|
78
|
-
"Resolving dependencies...",
|
|
79
|
-
"Downloading packages...",
|
|
80
|
-
"Linking dependencies...",
|
|
81
|
-
"Building project...",
|
|
82
|
-
"Generating types...",
|
|
83
|
-
"Setting up config...",
|
|
84
|
-
"Done!",
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
// ============================================================================
|
|
88
|
-
// Components
|
|
89
|
-
// ============================================================================
|
|
90
|
-
|
|
91
|
-
/** Step indicator showing current position in the wizard */
|
|
92
|
-
function StepIndicator({ current, total }: { current: number; total: number }) {
|
|
93
|
-
return (
|
|
94
|
-
<Box paddingX={1} marginBottom={1}>
|
|
95
|
-
{Array.from({ length: total }, (_, i) => {
|
|
96
|
-
const isDone = i < current
|
|
97
|
-
const isCurrent = i === current
|
|
98
|
-
const dot = isDone ? "\u25cf" : isCurrent ? "\u25cb" : "\u25cb"
|
|
99
|
-
const color = isDone ? "$success" : isCurrent ? "$primary" : "$muted"
|
|
100
|
-
return (
|
|
101
|
-
<Text key={i} color={color} bold={isCurrent}>
|
|
102
|
-
{dot}
|
|
103
|
-
{i < total - 1 ? " \u2500 " : ""}
|
|
104
|
-
</Text>
|
|
105
|
-
)
|
|
106
|
-
})}
|
|
107
|
-
</Box>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Step 1: Framework selection */
|
|
112
|
-
function FrameworkStep({ onSelect }: { onSelect: (option: SelectOption) => void }) {
|
|
113
|
-
return (
|
|
114
|
-
<Box flexDirection="column" paddingX={1}>
|
|
115
|
-
<Box marginBottom={1}>
|
|
116
|
-
<H1>? Select a framework:</H1>
|
|
117
|
-
</Box>
|
|
118
|
-
<SelectList items={FRAMEWORKS} onSelect={onSelect} />
|
|
119
|
-
<Box marginTop={1}>
|
|
120
|
-
<Lead>(Angular is coming soon)</Lead>
|
|
121
|
-
</Box>
|
|
122
|
-
</Box>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Step 2: Project name input */
|
|
127
|
-
function NameStep({
|
|
128
|
-
value,
|
|
129
|
-
onChange,
|
|
130
|
-
onSubmit,
|
|
131
|
-
framework,
|
|
132
|
-
}: {
|
|
133
|
-
value: string
|
|
134
|
-
onChange: (v: string) => void
|
|
135
|
-
onSubmit: (v: string) => void
|
|
136
|
-
framework: string
|
|
137
|
-
}) {
|
|
138
|
-
return (
|
|
139
|
-
<Box flexDirection="column" paddingX={1}>
|
|
140
|
-
<Box marginBottom={1}>
|
|
141
|
-
<H1>? Project name</H1>
|
|
142
|
-
<Muted> ({framework})</Muted>
|
|
143
|
-
<H1>:</H1>
|
|
144
|
-
</Box>
|
|
145
|
-
<Box>
|
|
146
|
-
<Text color="$muted">{"\u276f "}</Text>
|
|
147
|
-
<TextInput value={value} onChange={onChange} onSubmit={onSubmit} prompt="" />
|
|
148
|
-
</Box>
|
|
149
|
-
<Box marginTop={1}>
|
|
150
|
-
<Muted>Press Enter to confirm</Muted>
|
|
151
|
-
</Box>
|
|
152
|
-
</Box>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Step 3: Installation progress */
|
|
157
|
-
function InstallStep({ progress, stepIndex }: { progress: number; stepIndex: number }) {
|
|
158
|
-
const currentStep = INSTALL_STEPS[Math.min(stepIndex, INSTALL_STEPS.length - 1)]!
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<Box flexDirection="column" paddingX={1}>
|
|
162
|
-
<Box marginBottom={1}>
|
|
163
|
-
<Spinner type="dots" />
|
|
164
|
-
<Text bold color="$warning">
|
|
165
|
-
{" "}
|
|
166
|
-
Installing dependencies...
|
|
167
|
-
</Text>
|
|
168
|
-
</Box>
|
|
169
|
-
|
|
170
|
-
<Box marginBottom={1}>
|
|
171
|
-
<ProgressBar value={progress} color="$primary" label="" />
|
|
172
|
-
</Box>
|
|
173
|
-
|
|
174
|
-
<Muted>{currentStep}</Muted>
|
|
175
|
-
</Box>
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** Step 4: Completion summary */
|
|
180
|
-
function DoneStep({ framework, projectName }: { framework: string; projectName: string }) {
|
|
181
|
-
return (
|
|
182
|
-
<Box flexDirection="column" paddingX={1}>
|
|
183
|
-
<Box marginBottom={1}>
|
|
184
|
-
<H1 color="$success">{"\u2714"} Project created successfully!</H1>
|
|
185
|
-
</Box>
|
|
186
|
-
|
|
187
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$success" paddingX={2} paddingY={1}>
|
|
188
|
-
<Box>
|
|
189
|
-
<Muted>Framework: </Muted>
|
|
190
|
-
<Text bold>{framework}</Text>
|
|
191
|
-
</Box>
|
|
192
|
-
<Box>
|
|
193
|
-
<Muted>Project: </Muted>
|
|
194
|
-
<Text bold>{projectName}</Text>
|
|
195
|
-
</Box>
|
|
196
|
-
<Box>
|
|
197
|
-
<Muted>Location: </Muted>
|
|
198
|
-
<Text bold>./{projectName}/</Text>
|
|
199
|
-
</Box>
|
|
200
|
-
</Box>
|
|
201
|
-
|
|
202
|
-
<Box flexDirection="column" marginTop={1}>
|
|
203
|
-
<Muted>Get started:</Muted>
|
|
204
|
-
<Code>
|
|
205
|
-
{" "}cd {projectName}
|
|
206
|
-
</Code>
|
|
207
|
-
<Code>{" "}bun install</Code>
|
|
208
|
-
<Code>{" "}bun dev</Code>
|
|
209
|
-
</Box>
|
|
210
|
-
|
|
211
|
-
<Box marginTop={1}>
|
|
212
|
-
<Muted>
|
|
213
|
-
Press <Kbd>q</Kbd> or <Kbd>Esc</Kbd> to exit
|
|
214
|
-
</Muted>
|
|
215
|
-
</Box>
|
|
216
|
-
</Box>
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ============================================================================
|
|
221
|
-
// Main App
|
|
222
|
-
// ============================================================================
|
|
223
|
-
|
|
224
|
-
export function CliWizard() {
|
|
225
|
-
const { exit } = useApp()
|
|
226
|
-
const [state, setState] = useState<WizardState>({
|
|
227
|
-
step: "framework",
|
|
228
|
-
framework: null,
|
|
229
|
-
projectName: "",
|
|
230
|
-
progress: 0,
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
// Handle framework selection
|
|
234
|
-
const handleFrameworkSelect = useCallback((option: SelectOption) => {
|
|
235
|
-
setState((prev) => ({
|
|
236
|
-
...prev,
|
|
237
|
-
step: "name",
|
|
238
|
-
framework: option.value,
|
|
239
|
-
projectName: `my-${option.value}-app`,
|
|
240
|
-
}))
|
|
241
|
-
}, [])
|
|
242
|
-
|
|
243
|
-
// Handle project name change
|
|
244
|
-
const handleNameChange = useCallback((value: string) => {
|
|
245
|
-
setState((prev) => ({ ...prev, projectName: value }))
|
|
246
|
-
}, [])
|
|
247
|
-
|
|
248
|
-
// Handle project name submission
|
|
249
|
-
const handleNameSubmit = useCallback((value: string) => {
|
|
250
|
-
if (value.trim()) {
|
|
251
|
-
setState((prev) => ({ ...prev, step: "installing", progress: 0 }))
|
|
252
|
-
}
|
|
253
|
-
}, [])
|
|
254
|
-
|
|
255
|
-
// Simulate installation progress
|
|
256
|
-
useEffect(() => {
|
|
257
|
-
if (state.step !== "installing") return
|
|
258
|
-
|
|
259
|
-
const timer = setInterval(() => {
|
|
260
|
-
setState((prev) => {
|
|
261
|
-
const next = prev.progress + 0.08 + Math.random() * 0.04
|
|
262
|
-
if (next >= 1) {
|
|
263
|
-
clearInterval(timer)
|
|
264
|
-
return { ...prev, step: "done", progress: 1 }
|
|
265
|
-
}
|
|
266
|
-
return { ...prev, progress: next }
|
|
267
|
-
})
|
|
268
|
-
}, 200)
|
|
269
|
-
|
|
270
|
-
return () => clearInterval(timer)
|
|
271
|
-
}, [state.step])
|
|
272
|
-
|
|
273
|
-
// Global quit handler (only when not in text input step)
|
|
274
|
-
useInput((input: string, key: Key) => {
|
|
275
|
-
if (state.step === "name") return // TextInput handles its own input
|
|
276
|
-
if (input === "q" || key.escape) {
|
|
277
|
-
exit()
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
// Map progress to step index for display
|
|
282
|
-
const installStepIndex = Math.floor(state.progress * (INSTALL_STEPS.length - 1))
|
|
283
|
-
|
|
284
|
-
const stepNumber = state.step === "framework" ? 0 : state.step === "name" ? 1 : state.step === "installing" ? 2 : 3
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
288
|
-
<Box borderStyle="single" borderColor="$primary" paddingX={2} marginBottom={1}>
|
|
289
|
-
<H1>create-app</H1>
|
|
290
|
-
<Muted> v1.0.0</Muted>
|
|
291
|
-
</Box>
|
|
292
|
-
|
|
293
|
-
<StepIndicator current={stepNumber} total={4} />
|
|
294
|
-
|
|
295
|
-
{state.step === "framework" && <FrameworkStep onSelect={handleFrameworkSelect} />}
|
|
296
|
-
|
|
297
|
-
{state.step === "name" && state.framework && (
|
|
298
|
-
<NameStep
|
|
299
|
-
value={state.projectName}
|
|
300
|
-
onChange={handleNameChange}
|
|
301
|
-
onSubmit={handleNameSubmit}
|
|
302
|
-
framework={state.framework}
|
|
303
|
-
/>
|
|
304
|
-
)}
|
|
305
|
-
|
|
306
|
-
{state.step === "installing" && <InstallStep progress={state.progress} stepIndex={installStepIndex} />}
|
|
307
|
-
|
|
308
|
-
{state.step === "done" && state.framework && (
|
|
309
|
-
<DoneStep framework={state.framework} projectName={state.projectName} />
|
|
310
|
-
)}
|
|
311
|
-
</Box>
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ============================================================================
|
|
316
|
-
// Main
|
|
317
|
-
// ============================================================================
|
|
318
|
-
|
|
319
|
-
async function main() {
|
|
320
|
-
using term = createTerm()
|
|
321
|
-
const { waitUntilExit } = await render(
|
|
322
|
-
<ExampleBanner meta={meta} controls="j/k navigate Enter select q/Esc quit">
|
|
323
|
-
<CliWizard />
|
|
324
|
-
</ExampleBanner>,
|
|
325
|
-
term,
|
|
326
|
-
)
|
|
327
|
-
await waitUntilExit()
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (import.meta.main) {
|
|
331
|
-
main().catch(console.error)
|
|
332
|
-
}
|
package/apps/clipboard.tsx
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OSC 52 Clipboard Demo
|
|
3
|
-
*
|
|
4
|
-
* Shows copy/paste across terminal sessions using the OSC 52 protocol.
|
|
5
|
-
* Select items from a list, copy them to the system clipboard, and
|
|
6
|
-
* request clipboard contents back — all without native clipboard access.
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Navigate a list of items with j/k
|
|
10
|
-
* - Press c to copy selected item via OSC 52
|
|
11
|
-
* - Press v to request clipboard contents
|
|
12
|
-
* - Status bar shows last copied/pasted text
|
|
13
|
-
*
|
|
14
|
-
* Run: bun vendor/silvery/examples/apps/clipboard.tsx
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React, { useState } from "react"
|
|
18
|
-
import {
|
|
19
|
-
render,
|
|
20
|
-
Box,
|
|
21
|
-
Text,
|
|
22
|
-
H1,
|
|
23
|
-
Small,
|
|
24
|
-
Kbd,
|
|
25
|
-
Muted,
|
|
26
|
-
Lead,
|
|
27
|
-
useInput,
|
|
28
|
-
useApp,
|
|
29
|
-
useStdout,
|
|
30
|
-
createTerm,
|
|
31
|
-
copyToClipboard,
|
|
32
|
-
requestClipboard,
|
|
33
|
-
parseClipboardResponse,
|
|
34
|
-
type Key,
|
|
35
|
-
} from "silvery"
|
|
36
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
37
|
-
|
|
38
|
-
export const meta: ExampleMeta = {
|
|
39
|
-
name: "Clipboard (OSC 52)",
|
|
40
|
-
description: "Copy/paste via OSC 52 terminal protocol",
|
|
41
|
-
features: ["copyToClipboard()", "requestClipboard()", "parseClipboardResponse()", "useStdout"],
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ============================================================================
|
|
45
|
-
// Data
|
|
46
|
-
// ============================================================================
|
|
47
|
-
|
|
48
|
-
const items = [
|
|
49
|
-
{ category: "Colors", values: ["Crimson", "Cerulean", "Chartreuse", "Coral", "Cobalt", "Cyan"] },
|
|
50
|
-
{ category: "Languages", values: ["TypeScript", "Rust", "Elixir", "Haskell", "Zig", "OCaml"] },
|
|
51
|
-
{
|
|
52
|
-
category: "Fruits",
|
|
53
|
-
values: ["Mango", "Passionfruit", "Dragon fruit", "Starfruit", "Lychee", "Rambutan"],
|
|
54
|
-
},
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
const allItems = items.flatMap((group) => group.values.map((value) => ({ category: group.category, value })))
|
|
58
|
-
|
|
59
|
-
// ============================================================================
|
|
60
|
-
// Components
|
|
61
|
-
// ============================================================================
|
|
62
|
-
|
|
63
|
-
function ListItem({ item, isSelected }: { item: (typeof allItems)[0]; isSelected: boolean }) {
|
|
64
|
-
return (
|
|
65
|
-
<Box paddingX={1}>
|
|
66
|
-
<Text
|
|
67
|
-
color={isSelected ? "$bg" : undefined}
|
|
68
|
-
backgroundColor={isSelected ? "$primary" : undefined}
|
|
69
|
-
bold={isSelected}
|
|
70
|
-
>
|
|
71
|
-
{isSelected ? " > " : " "}
|
|
72
|
-
{item.value}
|
|
73
|
-
</Text>
|
|
74
|
-
<Small> ({item.category})</Small>
|
|
75
|
-
</Box>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function StatusBar({ lastCopied, lastPasted }: { lastCopied: string | null; lastPasted: string | null }) {
|
|
80
|
-
return (
|
|
81
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$border" paddingX={1}>
|
|
82
|
-
<Box gap={1}>
|
|
83
|
-
<Muted>Copied:</Muted>
|
|
84
|
-
{lastCopied ? <Text color="$success">{lastCopied}</Text> : <Lead>nothing yet</Lead>}
|
|
85
|
-
</Box>
|
|
86
|
-
<Box gap={1}>
|
|
87
|
-
<Muted>Pasted:</Muted>
|
|
88
|
-
{lastPasted ? <Text color="$warning">{lastPasted}</Text> : <Lead>nothing yet</Lead>}
|
|
89
|
-
</Box>
|
|
90
|
-
</Box>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function ClipboardDemo() {
|
|
95
|
-
const { exit } = useApp()
|
|
96
|
-
const { stdout } = useStdout()
|
|
97
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
98
|
-
const [lastCopied, setLastCopied] = useState<string | null>(null)
|
|
99
|
-
const [lastPasted, setLastPasted] = useState<string | null>(null)
|
|
100
|
-
|
|
101
|
-
useInput((input: string, key: Key) => {
|
|
102
|
-
if (input === "q" || key.escape) {
|
|
103
|
-
exit()
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Navigation
|
|
108
|
-
if (key.upArrow || input === "k") {
|
|
109
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1))
|
|
110
|
-
}
|
|
111
|
-
if (key.downArrow || input === "j") {
|
|
112
|
-
setSelectedIndex((prev) => Math.min(allItems.length - 1, prev + 1))
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Copy selected item
|
|
116
|
-
if (input === "c") {
|
|
117
|
-
const text = allItems[selectedIndex]!.value
|
|
118
|
-
copyToClipboard(stdout, text)
|
|
119
|
-
setLastCopied(text)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Request clipboard
|
|
123
|
-
if (input === "v") {
|
|
124
|
-
requestClipboard(stdout)
|
|
125
|
-
// Note: The terminal responds with an OSC 52 sequence containing
|
|
126
|
-
// the clipboard contents. In a real app you'd parse stdin for the
|
|
127
|
-
// response using parseClipboardResponse(). For this demo we just
|
|
128
|
-
// show that the request was sent.
|
|
129
|
-
setLastPasted("(request sent — check terminal)")
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Try to parse clipboard response from raw input
|
|
133
|
-
const parsed = parseClipboardResponse(input)
|
|
134
|
-
if (parsed) {
|
|
135
|
-
setLastPasted(parsed)
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<Box flexDirection="column" padding={1} gap={1}>
|
|
141
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$primary" paddingX={1}>
|
|
142
|
-
<Box marginBottom={1}>
|
|
143
|
-
<H1>Items</H1>
|
|
144
|
-
<Small>
|
|
145
|
-
{" "}
|
|
146
|
-
— {selectedIndex + 1}/{allItems.length}
|
|
147
|
-
</Small>
|
|
148
|
-
</Box>
|
|
149
|
-
<Box flexDirection="column" overflow="scroll" scrollTo={selectedIndex} height={10}>
|
|
150
|
-
{allItems.map((item, index) => (
|
|
151
|
-
<ListItem key={`${item.category}-${item.value}`} item={item} isSelected={index === selectedIndex} />
|
|
152
|
-
))}
|
|
153
|
-
</Box>
|
|
154
|
-
</Box>
|
|
155
|
-
|
|
156
|
-
<StatusBar lastCopied={lastCopied} lastPasted={lastPasted} />
|
|
157
|
-
|
|
158
|
-
<Muted>
|
|
159
|
-
{" "}
|
|
160
|
-
<Kbd>j/k</Kbd> navigate <Kbd>c</Kbd> copy <Kbd>v</Kbd> paste <Kbd>Esc/q</Kbd> quit
|
|
161
|
-
</Muted>
|
|
162
|
-
</Box>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// Main
|
|
168
|
-
// ============================================================================
|
|
169
|
-
|
|
170
|
-
async function main() {
|
|
171
|
-
using term = createTerm()
|
|
172
|
-
const { waitUntilExit } = await render(
|
|
173
|
-
<ExampleBanner meta={meta} controls="j/k navigate c copy v paste Esc/q quit">
|
|
174
|
-
<ClipboardDemo />
|
|
175
|
-
</ExampleBanner>,
|
|
176
|
-
term,
|
|
177
|
-
)
|
|
178
|
-
await waitUntilExit()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (import.meta.main) {
|
|
182
|
-
main().catch(console.error)
|
|
183
|
-
}
|