@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.
Files changed (112) hide show
  1. package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
  3. package/dist/_banner-DLPxCqVy.mjs +44 -0
  4. package/dist/ansi-CCE2pVS0.mjs +16397 -0
  5. package/dist/apng-HhhBjRGt.mjs +68 -0
  6. package/dist/apng-mwUQbTTF.mjs +3 -0
  7. package/dist/apps/aichat/index.mjs +1299 -0
  8. package/dist/apps/app-todo.mjs +139 -0
  9. package/dist/apps/async-data.mjs +204 -0
  10. package/dist/apps/cli-wizard.mjs +339 -0
  11. package/dist/apps/clipboard.mjs +198 -0
  12. package/dist/apps/components.mjs +864 -0
  13. package/dist/apps/data-explorer.mjs +483 -0
  14. package/dist/apps/dev-tools.mjs +397 -0
  15. package/dist/apps/explorer.mjs +698 -0
  16. package/dist/apps/gallery.mjs +766 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +280 -0
  19. package/dist/apps/layout-ref.mjs +187 -0
  20. package/dist/apps/outline.mjs +203 -0
  21. package/dist/apps/paste-demo.mjs +189 -0
  22. package/dist/apps/scroll.mjs +86 -0
  23. package/dist/apps/search-filter.mjs +287 -0
  24. package/dist/apps/selection.mjs +355 -0
  25. package/dist/apps/spatial-focus-demo.mjs +388 -0
  26. package/dist/apps/task-list.mjs +258 -0
  27. package/dist/apps/terminal-caps-demo.mjs +315 -0
  28. package/dist/apps/terminal.mjs +872 -0
  29. package/dist/apps/text-selection-demo.mjs +254 -0
  30. package/dist/apps/textarea.mjs +178 -0
  31. package/dist/apps/theme.mjs +661 -0
  32. package/dist/apps/transform.mjs +215 -0
  33. package/dist/apps/virtual-10k.mjs +422 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Bahh9mKN.mjs +1179 -0
  36. package/dist/backends-CCtCDQ94.mjs +3 -0
  37. package/dist/{cli.mjs → bin/cli.mjs} +21 -25
  38. package/dist/chunk-BSw8zbkd.mjs +37 -0
  39. package/dist/components/counter.mjs +48 -0
  40. package/dist/components/hello.mjs +31 -0
  41. package/dist/components/progress-bar.mjs +59 -0
  42. package/dist/components/select-list.mjs +85 -0
  43. package/dist/components/spinner.mjs +57 -0
  44. package/dist/components/text-input.mjs +62 -0
  45. package/dist/components/virtual-list.mjs +51 -0
  46. package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
  47. package/dist/gif-BZaqPPVX.mjs +3 -0
  48. package/dist/gif-BtnXuxLF.mjs +71 -0
  49. package/dist/gifenc-CLRW41dk.mjs +728 -0
  50. package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
  51. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  52. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  53. package/dist/layout/dashboard.mjs +1204 -0
  54. package/dist/layout/live-resize.mjs +303 -0
  55. package/dist/layout/overflow.mjs +70 -0
  56. package/dist/layout/text-layout.mjs +335 -0
  57. package/dist/node-NuJ94BWl.mjs +1083 -0
  58. package/dist/plugins-D1KtkT4a.mjs +3057 -0
  59. package/dist/resvg-js-C_8Wps1F.mjs +201 -0
  60. package/dist/src-BTEVGpd9.mjs +23538 -0
  61. package/dist/src-CUUOuRH6.mjs +5322 -0
  62. package/dist/src-CzfRafCQ.mjs +814 -0
  63. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  64. package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
  65. package/package.json +19 -14
  66. package/_banner.tsx +0 -60
  67. package/apps/aichat/components.tsx +0 -469
  68. package/apps/aichat/index.tsx +0 -220
  69. package/apps/aichat/script.ts +0 -460
  70. package/apps/aichat/state.ts +0 -325
  71. package/apps/aichat/types.ts +0 -19
  72. package/apps/app-todo.tsx +0 -201
  73. package/apps/async-data.tsx +0 -196
  74. package/apps/cli-wizard.tsx +0 -332
  75. package/apps/clipboard.tsx +0 -183
  76. package/apps/components.tsx +0 -658
  77. package/apps/data-explorer.tsx +0 -490
  78. package/apps/dev-tools.tsx +0 -395
  79. package/apps/explorer.tsx +0 -731
  80. package/apps/gallery.tsx +0 -653
  81. package/apps/inline-bench.tsx +0 -138
  82. package/apps/kanban.tsx +0 -265
  83. package/apps/layout-ref.tsx +0 -173
  84. package/apps/outline.tsx +0 -160
  85. package/apps/panes/index.tsx +0 -203
  86. package/apps/paste-demo.tsx +0 -185
  87. package/apps/scroll.tsx +0 -77
  88. package/apps/search-filter.tsx +0 -240
  89. package/apps/selection.tsx +0 -342
  90. package/apps/spatial-focus-demo.tsx +0 -368
  91. package/apps/task-list.tsx +0 -271
  92. package/apps/terminal-caps-demo.tsx +0 -334
  93. package/apps/terminal.tsx +0 -800
  94. package/apps/text-selection-demo.tsx +0 -189
  95. package/apps/textarea.tsx +0 -155
  96. package/apps/theme.tsx +0 -515
  97. package/apps/transform.tsx +0 -229
  98. package/apps/virtual-10k.tsx +0 -405
  99. package/apps/vterm-demo/index.tsx +0 -216
  100. package/components/counter.tsx +0 -45
  101. package/components/hello.tsx +0 -34
  102. package/components/progress-bar.tsx +0 -48
  103. package/components/select-list.tsx +0 -50
  104. package/components/spinner.tsx +0 -40
  105. package/components/text-input.tsx +0 -57
  106. package/components/virtual-list.tsx +0 -52
  107. package/dist/cli.d.mts +0 -1
  108. package/dist/cli.mjs.map +0 -1
  109. package/layout/dashboard.tsx +0 -953
  110. package/layout/live-resize.tsx +0 -282
  111. package/layout/overflow.tsx +0 -51
  112. package/layout/text-layout.tsx +0 -283
package/apps/outline.tsx DELETED
@@ -1,160 +0,0 @@
1
- /**
2
- * Outline vs Border Comparison
3
- *
4
- * Side-by-side demonstration of outlineStyle vs borderStyle.
5
- * Borders push content inward (adding to layout dimensions), while
6
- * outlines overlap the content edge without affecting layout.
7
- *
8
- * Features:
9
- * - Left panel: Box with borderStyle — content area is smaller
10
- * - Right panel: Box with outlineStyle — content starts at edge
11
- * - Toggle between styles with Tab
12
- * - Live content dimensions via useBoxRect()
13
- *
14
- * Run: bun vendor/silvery/examples/apps/outline.tsx
15
- */
16
-
17
- import React, { useState } from "react"
18
- import { render, Box, Text, Kbd, Muted, useInput, useApp, useBoxRect, createTerm, type Key } from "silvery"
19
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
20
-
21
- export const meta: ExampleMeta = {
22
- name: "Outline vs Border",
23
- description: "Side-by-side comparison showing outline (no layout impact) vs border",
24
- features: ["outlineStyle", "borderStyle", "useBoxRect()", "layout dimensions"],
25
- }
26
-
27
- // ============================================================================
28
- // Types
29
- // ============================================================================
30
-
31
- type StyleVariant = "single" | "double" | "round" | "bold"
32
-
33
- const STYLES: StyleVariant[] = ["single", "double", "round", "bold"]
34
-
35
- // ============================================================================
36
- // Components
37
- // ============================================================================
38
-
39
- function ContentWithSize({ label }: { label: string }) {
40
- const { width, height } = useBoxRect()
41
-
42
- return (
43
- <Box flexDirection="column">
44
- <Text bold>{label}</Text>
45
- <Text>
46
- Content area:{" "}
47
- <Text color="$success" bold>
48
- {width}
49
- </Text>
50
- x
51
- <Text color="$success" bold>
52
- {height}
53
- </Text>
54
- </Text>
55
- <Text dim>The quick brown fox</Text>
56
- <Text dim>jumps over the lazy</Text>
57
- <Text dim>dog on a sunny day.</Text>
58
- </Box>
59
- )
60
- }
61
-
62
- function BorderPanel({ style, highlight }: { style: StyleVariant; highlight: boolean }) {
63
- return (
64
- <Box flexDirection="column" flexGrow={1} gap={1}>
65
- <Text bold color={highlight ? "$primary" : undefined}>
66
- borderStyle="{style}"
67
- </Text>
68
- <Box borderStyle={style} borderColor={highlight ? "$primary" : "$border"} width={30} height={9}>
69
- <ContentWithSize label="Border Box" />
70
- </Box>
71
- <Muted>Border adds to layout.</Muted>
72
- <Muted>Content is pushed inward.</Muted>
73
- </Box>
74
- )
75
- }
76
-
77
- function OutlinePanel({ style, highlight }: { style: StyleVariant; highlight: boolean }) {
78
- return (
79
- <Box flexDirection="column" flexGrow={1} gap={1}>
80
- <Text bold color={highlight ? "$warning" : undefined}>
81
- outlineStyle="{style}"
82
- </Text>
83
- <Box outlineStyle={style} outlineColor={highlight ? "$warning" : "$border"} width={30} height={9}>
84
- <ContentWithSize label="Outline Box" />
85
- </Box>
86
- <Muted>Outline overlaps content.</Muted>
87
- <Muted>No layout impact at all.</Muted>
88
- </Box>
89
- )
90
- }
91
-
92
- export function OutlineDemo() {
93
- const { exit } = useApp()
94
- const [styleIndex, setStyleIndex] = useState(0)
95
- const [focusedSide, setFocusedSide] = useState<"border" | "outline">("border")
96
-
97
- const currentStyle = STYLES[styleIndex]!
98
-
99
- useInput((input: string, key: Key) => {
100
- if (input === "q" || key.escape) {
101
- exit()
102
- return
103
- }
104
-
105
- // Toggle focus between panels
106
- if (key.tab || input === "\t") {
107
- setFocusedSide((prev) => (prev === "border" ? "outline" : "border"))
108
- }
109
-
110
- // Cycle through border/outline styles
111
- if (key.rightArrow || input === "l") {
112
- setStyleIndex((prev) => (prev + 1) % STYLES.length)
113
- }
114
- if (key.leftArrow || input === "h") {
115
- setStyleIndex((prev) => (prev - 1 + STYLES.length) % STYLES.length)
116
- }
117
- })
118
-
119
- return (
120
- <Box flexDirection="column" padding={1} gap={1}>
121
- <Box gap={1}>
122
- <Text bold>Style:</Text>
123
- {STYLES.map((s, i) => (
124
- <Text key={s} color={i === styleIndex ? "$primary" : "$muted"} bold={i === styleIndex}>
125
- {i === styleIndex ? `[${s}]` : s}
126
- </Text>
127
- ))}
128
- </Box>
129
-
130
- <Box flexDirection="row" gap={2}>
131
- <BorderPanel style={currentStyle} highlight={focusedSide === "border"} />
132
- <OutlinePanel style={currentStyle} highlight={focusedSide === "outline"} />
133
- </Box>
134
-
135
- <Muted>
136
- {" "}
137
- <Kbd>Tab</Kbd> toggle focus <Kbd>h/l</Kbd> change style <Kbd>Esc/q</Kbd> quit
138
- </Muted>
139
- </Box>
140
- )
141
- }
142
-
143
- // ============================================================================
144
- // Main
145
- // ============================================================================
146
-
147
- async function main() {
148
- using term = createTerm()
149
- const { waitUntilExit } = await render(
150
- <ExampleBanner meta={meta} controls="Tab toggle h/l style Esc/q quit">
151
- <OutlineDemo />
152
- </ExampleBanner>,
153
- term,
154
- )
155
- await waitUntilExit()
156
- }
157
-
158
- if (import.meta.main) {
159
- main().catch(console.error)
160
- }
@@ -1,203 +0,0 @@
1
- /**
2
- * Panes — tmux-style split pane demo.
3
- *
4
- * Two AI chat panes running independently, Tab to switch focus, Esc to quit.
5
- * SearchProvider with Ctrl+F bindings for app-global search.
6
- *
7
- * Run: bun examples/apps/panes/index.tsx [--fast]
8
- */
9
-
10
- import React, { useState, useEffect, useMemo } from "react"
11
- import { Box, Text, ListView } from "silvery"
12
- import { SearchProvider, SearchBar, useSearch } from "@silvery/ag-react"
13
- import { run, useInput, type Key } from "silvery/runtime"
14
- import type { ExampleMeta } from "../../_banner.js"
15
- import { SCRIPT } from "../aichat/script.js"
16
- import type { ScriptEntry } from "../aichat/types.js"
17
- import type { Exchange } from "../aichat/types.js"
18
- import { ExchangeItem } from "../aichat/components.js"
19
- // ListItemMeta type from ListView — inline to avoid tsconfig path issues
20
- interface ListItemMeta {
21
- isCursor: boolean
22
- }
23
-
24
- export const meta: ExampleMeta = {
25
- name: "Panes",
26
- description: "tmux-style split panes — ListView + SearchProvider + focus switching",
27
- demo: true,
28
- features: ["ListView", "SearchProvider", "split panes", "Tab focus"],
29
- }
30
-
31
- // ============================================================================
32
- // Auto-advancing chat content
33
- // ============================================================================
34
-
35
- function usePaneContent(script: ScriptEntry[], fastMode: boolean): Exchange[] {
36
- const [exchanges, setExchanges] = useState<Exchange[]>([])
37
- const [idx, setIdx] = useState(0)
38
-
39
- useEffect(() => {
40
- if (idx >= script.length) return
41
- const delay = fastMode ? 150 : 800 + Math.random() * 1200
42
- const timer = setTimeout(() => {
43
- const entry = script[idx]!
44
- setExchanges((prev) => [...prev, { ...entry, id: idx }])
45
- setIdx((i) => i + 1)
46
- }, delay)
47
- return () => clearTimeout(timer)
48
- }, [idx, script, fastMode])
49
-
50
- return exchanges
51
- }
52
-
53
- // ============================================================================
54
- // Chat pane
55
- // ============================================================================
56
-
57
- function ChatPane({
58
- script,
59
- fastMode,
60
- height,
61
- active,
62
- surfaceId,
63
- }: {
64
- script: ScriptEntry[]
65
- fastMode: boolean
66
- height: number
67
- active: boolean
68
- surfaceId: string
69
- }) {
70
- const exchanges = usePaneContent(script, fastMode)
71
-
72
- if (exchanges.length === 0) {
73
- return (
74
- <Box paddingX={1}>
75
- <Text color="$muted">Waiting...</Text>
76
- </Box>
77
- )
78
- }
79
-
80
- return (
81
- <ListView
82
- items={exchanges}
83
- height={height}
84
- getKey={(ex: Exchange) => ex.id}
85
- scrollTo={exchanges.length - 1}
86
- active={active}
87
- surfaceId={surfaceId}
88
- cache={{
89
- mode: "virtual",
90
- isCacheable: (_ex: Exchange, idx: number) => idx < exchanges.length - 1,
91
- }}
92
- search={{ getText: (ex: Exchange) => ex.content }}
93
- renderItem={(exchange: Exchange, _index: number, _meta: ListItemMeta) => (
94
- <ExchangeItem
95
- exchange={exchange}
96
- streamPhase="done"
97
- revealFraction={1}
98
- pulse={false}
99
- isLatest={false}
100
- isFirstInGroup={true}
101
- isLastInGroup={true}
102
- />
103
- )}
104
- />
105
- )
106
- }
107
-
108
- // ============================================================================
109
- // Main app
110
- // ============================================================================
111
-
112
- function PanesApp({ fastMode, rows }: { fastMode: boolean; rows: number }) {
113
- const [focusedPane, setFocusedPane] = useState<"left" | "right">("left")
114
- const search = useSearch()
115
-
116
- const midpoint = Math.ceil(SCRIPT.length / 2)
117
- const leftScript = useMemo(() => SCRIPT.slice(0, midpoint), [midpoint])
118
- const rightScript = useMemo(() => SCRIPT.slice(midpoint), [midpoint])
119
-
120
- useInput((input: string, key: Key) => {
121
- // Don't exit while search is active — Escape closes search first
122
- if (key.escape && !search.isActive) return "exit"
123
- if (key.tab && !search.isActive) {
124
- setFocusedPane((p) => (p === "left" ? "right" : "left"))
125
- }
126
- })
127
-
128
- // Pane content height: rows - border(2) - title(1) - status(1) = rows - 4
129
- const listHeight = Math.max(5, rows - 4)
130
-
131
- return (
132
- <Box flexDirection="column" height={rows}>
133
- <Box flexDirection="row" flexGrow={1}>
134
- <Box
135
- width="50%"
136
- flexDirection="column"
137
- borderStyle="single"
138
- borderColor={focusedPane === "left" ? "$primary" : "$border"}
139
- overflow="hidden"
140
- >
141
- <Box paddingX={1}>
142
- <Text color={focusedPane === "left" ? "$primary" : "$border"} bold={focusedPane === "left"}>
143
- Agent A
144
- </Text>
145
- </Box>
146
- <ChatPane
147
- script={leftScript}
148
- fastMode={fastMode}
149
- height={listHeight}
150
- active={focusedPane === "left"}
151
- surfaceId="left"
152
- />
153
- </Box>
154
- <Box
155
- width="50%"
156
- flexDirection="column"
157
- borderStyle="single"
158
- borderColor={focusedPane === "right" ? "$primary" : "$border"}
159
- overflow="hidden"
160
- >
161
- <Box paddingX={1}>
162
- <Text color={focusedPane === "right" ? "$primary" : "$border"} bold={focusedPane === "right"}>
163
- Agent B
164
- </Text>
165
- </Box>
166
- <ChatPane
167
- script={rightScript}
168
- fastMode={fastMode}
169
- height={listHeight}
170
- active={focusedPane === "right"}
171
- surfaceId="right"
172
- />
173
- </Box>
174
- </Box>
175
- <SearchBar />
176
- <Box paddingX={1}>
177
- <Text color="$muted">Tab: switch pane · Ctrl+F: search · Esc: quit</Text>
178
- </Box>
179
- </Box>
180
- )
181
- }
182
-
183
- // ============================================================================
184
- // Entry
185
- // ============================================================================
186
-
187
- export async function main() {
188
- const args = process.argv.slice(2)
189
- const fastMode = args.includes("--fast")
190
- const rows = process.stdout.rows ?? 40
191
-
192
- using handle = await run(
193
- <SearchProvider>
194
- <PanesApp fastMode={fastMode} rows={rows} />
195
- </SearchProvider>,
196
- { mode: "fullscreen", kitty: false, textSizing: false },
197
- )
198
- await handle.waitUntilExit()
199
- }
200
-
201
- if (import.meta.main) {
202
- main().catch(console.error)
203
- }
@@ -1,185 +0,0 @@
1
- /**
2
- * Bracketed Paste Demo
3
- *
4
- * Demonstrates bracketed paste mode — when text is pasted into the terminal,
5
- * it arrives as a single event rather than individual keystrokes. This prevents
6
- * pasted text from being interpreted as commands.
7
- *
8
- * Features:
9
- * - Shows paste mode status (always enabled with render())
10
- * - Displays pasted text as a single block event
11
- * - Shows character count and line count of pasted text
12
- * - Maintains a history of paste events
13
- *
14
- * Run: bun vendor/silvery/examples/apps/paste-demo.tsx
15
- */
16
-
17
- import React, { useState } from "react"
18
- import { render, Box, Text, H1, Small, Kbd, Muted, Lead, useInput, useApp, createTerm, type Key } from "silvery"
19
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
20
-
21
- export const meta: ExampleMeta = {
22
- name: "Bracketed Paste",
23
- description: "Receive pasted text as a single event via bracketed paste mode",
24
- features: ["onPaste", "useInput", "bracketed paste mode"],
25
- }
26
-
27
- // ============================================================================
28
- // Types
29
- // ============================================================================
30
-
31
- interface PasteEvent {
32
- id: number
33
- text: string
34
- charCount: number
35
- lineCount: number
36
- timestamp: string
37
- }
38
-
39
- // ============================================================================
40
- // Components
41
- // ============================================================================
42
-
43
- function PasteIndicator() {
44
- return (
45
- <Box gap={1} paddingX={1}>
46
- <Text color="$success" bold>
47
- {"●"}
48
- </Text>
49
- <Text>Paste mode:</Text>
50
- <Text color="$success" bold>
51
- ENABLED
52
- </Text>
53
- <Muted>(bracketed paste is automatic with render())</Muted>
54
- </Box>
55
- )
56
- }
57
-
58
- function PasteEventCard({ event, isLatest }: { event: PasteEvent; isLatest: boolean }) {
59
- const preview = event.text.length > 60 ? event.text.slice(0, 57) + "..." : event.text
60
- const displayText = preview.replace(/\n/g, "\\n").replace(/\t/g, "\\t")
61
-
62
- return (
63
- <Box
64
- flexDirection="column"
65
- borderStyle="round"
66
- borderColor={isLatest ? "$primary" : "$border"}
67
- paddingX={1}
68
- marginBottom={0}
69
- >
70
- <Box justifyContent="space-between">
71
- <H1 color={isLatest ? "$primary" : undefined}>Paste #{event.id}</H1>
72
- <Small>{event.timestamp}</Small>
73
- </Box>
74
- <Box gap={2}>
75
- <Small>
76
- {event.charCount} char{event.charCount !== 1 ? "s" : ""}
77
- </Small>
78
- <Small>
79
- {event.lineCount} line{event.lineCount !== 1 ? "s" : ""}
80
- </Small>
81
- </Box>
82
- <Box marginTop={1}>
83
- <Text color="$warning">{displayText}</Text>
84
- </Box>
85
- </Box>
86
- )
87
- }
88
-
89
- function EmptyState() {
90
- return (
91
- <Box flexDirection="column" padding={2} alignItems="center">
92
- <Muted>No paste events yet.</Muted>
93
- <Lead>Try pasting some text from your clipboard!</Lead>
94
- <Lead>(Cmd+V on macOS, Ctrl+Shift+V on Linux)</Lead>
95
- </Box>
96
- )
97
- }
98
-
99
- export function PasteDemo() {
100
- const { exit } = useApp()
101
- const [pasteHistory, setPasteHistory] = useState<PasteEvent[]>([])
102
- const [nextId, setNextId] = useState(1)
103
-
104
- useInput(
105
- (input: string, key: Key) => {
106
- if (input === "q" || key.escape) {
107
- exit()
108
- return
109
- }
110
-
111
- // Clear history
112
- if (input === "x") {
113
- setPasteHistory([])
114
- setNextId(1)
115
- }
116
- },
117
- {
118
- onPaste: (text: string) => {
119
- const now = new Date()
120
- const timestamp = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`
121
-
122
- const event: PasteEvent = {
123
- id: nextId,
124
- text,
125
- charCount: text.length,
126
- lineCount: text.split("\n").length,
127
- timestamp,
128
- }
129
-
130
- setPasteHistory((prev) => [event, ...prev].slice(0, 10))
131
- setNextId((prev) => prev + 1)
132
- },
133
- },
134
- )
135
-
136
- return (
137
- <Box flexDirection="column" padding={1} gap={1}>
138
- <PasteIndicator />
139
-
140
- <Box flexDirection="column" borderStyle="round" borderColor="$primary" paddingX={1}>
141
- <Box marginBottom={1}>
142
- <H1>Paste History</H1>
143
- <Small>
144
- {" "}
145
- — {pasteHistory.length} event{pasteHistory.length !== 1 ? "s" : ""}
146
- </Small>
147
- </Box>
148
-
149
- {pasteHistory.length === 0 ? (
150
- <EmptyState />
151
- ) : (
152
- <Box flexDirection="column" overflow="scroll" height={12} gap={1}>
153
- {pasteHistory.map((event, index) => (
154
- <PasteEventCard key={event.id} event={event} isLatest={index === 0} />
155
- ))}
156
- </Box>
157
- )}
158
- </Box>
159
-
160
- <Muted>
161
- {" "}
162
- <Kbd>Paste text</Kbd> to see events <Kbd>x</Kbd> clear <Kbd>Esc/q</Kbd> quit
163
- </Muted>
164
- </Box>
165
- )
166
- }
167
-
168
- // ============================================================================
169
- // Main
170
- // ============================================================================
171
-
172
- async function main() {
173
- using term = createTerm()
174
- const { waitUntilExit } = await render(
175
- <ExampleBanner meta={meta} controls="Paste text to see events x clear Esc/q quit">
176
- <PasteDemo />
177
- </ExampleBanner>,
178
- term,
179
- )
180
- await waitUntilExit()
181
- }
182
-
183
- if (import.meta.main) {
184
- main().catch(console.error)
185
- }
package/apps/scroll.tsx DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Scroll Example
3
- *
4
- * Demonstrates overflow="scroll" with keyboard navigation.
5
- */
6
-
7
- import React, { useState } from "react"
8
- import { Box, Text, Kbd, Muted, render, useInput, useApp, createTerm, type Key } from "silvery"
9
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
10
-
11
- export const meta: ExampleMeta = {
12
- name: "Scroll",
13
- description: 'Native overflow="scroll" with automatic scroll-to-selected',
14
- features: ['overflow="scroll"', "scrollTo", "useInput"],
15
- }
16
-
17
- // Generate sample items
18
- const items = Array.from({ length: 50 }, (_, i) => ({
19
- id: i,
20
- title: `Item ${i + 1}`,
21
- description: `This is the description for item number ${i + 1}`,
22
- }))
23
-
24
- export function ScrollExample() {
25
- const { exit } = useApp()
26
- const [selectedIndex, setSelectedIndex] = useState(0)
27
-
28
- useInput((input: string, key: Key) => {
29
- if (input === "q" || key.escape) {
30
- exit()
31
- }
32
- if (key.upArrow || input === "k") {
33
- setSelectedIndex((prev) => Math.max(0, prev - 1))
34
- }
35
- if (key.downArrow || input === "j") {
36
- setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1))
37
- }
38
- })
39
-
40
- return (
41
- <Box flexDirection="column" padding={1} width={60} height={20}>
42
- <Box
43
- flexGrow={1}
44
- flexDirection="column"
45
- borderStyle="round"
46
- borderColor="$primary"
47
- overflow="scroll"
48
- scrollTo={selectedIndex}
49
- height={10}
50
- >
51
- {items.map((item, index) => (
52
- <Box key={item.id} paddingX={1} backgroundColor={index === selectedIndex ? "$primary" : undefined}>
53
- <Text color={index === selectedIndex ? "$primary-fg" : undefined} bold={index === selectedIndex}>
54
- {item.title}
55
- </Text>
56
- </Box>
57
- ))}
58
- </Box>
59
-
60
- <Muted>
61
- {" "}
62
- <Kbd>j/k</Kbd> navigate <Kbd>Esc/q</Kbd> quit | Selected: {selectedIndex + 1}/{items.length}
63
- </Muted>
64
- </Box>
65
- )
66
- }
67
-
68
- // Run the app
69
- if (import.meta.main) {
70
- using term = createTerm()
71
- await render(
72
- <ExampleBanner meta={meta} controls="j/k navigate Esc/q quit">
73
- <ScrollExample />
74
- </ExampleBanner>,
75
- term,
76
- )
77
- }