silvery 0.3.0 → 0.4.1
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/README.md +41 -145
- package/dist/chalk.js +3 -0
- package/dist/chalk.js.map +11 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +282 -0
- package/dist/ink.js +129 -0
- package/dist/ink.js.map +140 -0
- package/dist/runtime.js +394 -0
- package/dist/runtime.js.map +286 -0
- package/dist/theme.js +343 -0
- package/dist/theme.js.map +286 -0
- package/dist/ui/animation.js +3 -0
- package/dist/ui/animation.js.map +15 -0
- package/dist/ui/ansi.js +3 -0
- package/dist/ui/ansi.js.map +10 -0
- package/dist/ui/cli.js +8 -0
- package/dist/ui/cli.js.map +14 -0
- package/dist/ui/display.js +4 -0
- package/dist/ui/display.js.map +10 -0
- package/dist/ui/image.js +4 -0
- package/dist/ui/image.js.map +15 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/input.js.map +11 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/progress.js.map +20 -0
- package/dist/ui/react.js +3 -0
- package/dist/ui/react.js.map +15 -0
- package/dist/ui/utils.js +3 -0
- package/dist/ui/utils.js.map +10 -0
- package/dist/ui/wrappers.js +14 -0
- package/dist/ui/wrappers.js.map +19 -0
- package/dist/ui.js +17 -0
- package/dist/ui.js.map +20 -0
- package/package.json +67 -15
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- package/examples/web/xterm.html +0 -118
|
@@ -1,171 +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 useContentRect()
|
|
13
|
-
*
|
|
14
|
-
* Run: bun vendor/silvery/examples/interactive/outline.tsx
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React, { useState } from "react"
|
|
18
|
-
import {
|
|
19
|
-
render,
|
|
20
|
-
Box,
|
|
21
|
-
Text,
|
|
22
|
-
Kbd,
|
|
23
|
-
Muted,
|
|
24
|
-
useInput,
|
|
25
|
-
useApp,
|
|
26
|
-
useContentRect,
|
|
27
|
-
createTerm,
|
|
28
|
-
type Key,
|
|
29
|
-
} from "../../src/index.js"
|
|
30
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
31
|
-
|
|
32
|
-
export const meta: ExampleMeta = {
|
|
33
|
-
name: "Outline vs Border",
|
|
34
|
-
description: "Side-by-side comparison showing outline (no layout impact) vs border",
|
|
35
|
-
features: ["outlineStyle", "borderStyle", "useContentRect()", "layout dimensions"],
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ============================================================================
|
|
39
|
-
// Types
|
|
40
|
-
// ============================================================================
|
|
41
|
-
|
|
42
|
-
type StyleVariant = "single" | "double" | "round" | "bold"
|
|
43
|
-
|
|
44
|
-
const STYLES: StyleVariant[] = ["single", "double", "round", "bold"]
|
|
45
|
-
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// Components
|
|
48
|
-
// ============================================================================
|
|
49
|
-
|
|
50
|
-
function ContentWithSize({ label }: { label: string }): JSX.Element {
|
|
51
|
-
const { width, height } = useContentRect()
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Box flexDirection="column">
|
|
55
|
-
<Text bold>{label}</Text>
|
|
56
|
-
<Text>
|
|
57
|
-
Content area:{" "}
|
|
58
|
-
<Text color="$success" bold>
|
|
59
|
-
{width}
|
|
60
|
-
</Text>
|
|
61
|
-
x
|
|
62
|
-
<Text color="$success" bold>
|
|
63
|
-
{height}
|
|
64
|
-
</Text>
|
|
65
|
-
</Text>
|
|
66
|
-
<Text dim>The quick brown fox</Text>
|
|
67
|
-
<Text dim>jumps over the lazy</Text>
|
|
68
|
-
<Text dim>dog on a sunny day.</Text>
|
|
69
|
-
</Box>
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function BorderPanel({ style, highlight }: { style: StyleVariant; highlight: boolean }): JSX.Element {
|
|
74
|
-
return (
|
|
75
|
-
<Box flexDirection="column" flexGrow={1} gap={1}>
|
|
76
|
-
<Text bold color={highlight ? "$primary" : undefined}>
|
|
77
|
-
borderStyle="{style}"
|
|
78
|
-
</Text>
|
|
79
|
-
<Box borderStyle={style} borderColor={highlight ? "$primary" : "$border"} width={30} height={9}>
|
|
80
|
-
<ContentWithSize label="Border Box" />
|
|
81
|
-
</Box>
|
|
82
|
-
<Muted>Border adds to layout.</Muted>
|
|
83
|
-
<Muted>Content is pushed inward.</Muted>
|
|
84
|
-
</Box>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function OutlinePanel({ style, highlight }: { style: StyleVariant; highlight: boolean }): JSX.Element {
|
|
89
|
-
return (
|
|
90
|
-
<Box flexDirection="column" flexGrow={1} gap={1}>
|
|
91
|
-
<Text bold color={highlight ? "$warning" : undefined}>
|
|
92
|
-
outlineStyle="{style}"
|
|
93
|
-
</Text>
|
|
94
|
-
<Box outlineStyle={style} outlineColor={highlight ? "$warning" : "$border"} width={30} height={9}>
|
|
95
|
-
<ContentWithSize label="Outline Box" />
|
|
96
|
-
</Box>
|
|
97
|
-
<Muted>Outline overlaps content.</Muted>
|
|
98
|
-
<Muted>No layout impact at all.</Muted>
|
|
99
|
-
</Box>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function OutlineDemo(): JSX.Element {
|
|
104
|
-
const { exit } = useApp()
|
|
105
|
-
const [styleIndex, setStyleIndex] = useState(0)
|
|
106
|
-
const [focusedSide, setFocusedSide] = useState<"border" | "outline">("border")
|
|
107
|
-
|
|
108
|
-
const currentStyle = STYLES[styleIndex]!
|
|
109
|
-
|
|
110
|
-
useInput((input: string, key: Key) => {
|
|
111
|
-
if (input === "q" || key.escape) {
|
|
112
|
-
exit()
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Toggle focus between panels
|
|
117
|
-
if (key.tab || input === "\t") {
|
|
118
|
-
setFocusedSide((prev) => (prev === "border" ? "outline" : "border"))
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Cycle through border/outline styles
|
|
122
|
-
if (key.rightArrow || input === "l") {
|
|
123
|
-
setStyleIndex((prev) => (prev + 1) % STYLES.length)
|
|
124
|
-
}
|
|
125
|
-
if (key.leftArrow || input === "h") {
|
|
126
|
-
setStyleIndex((prev) => (prev - 1 + STYLES.length) % STYLES.length)
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<Box flexDirection="column" padding={1} gap={1}>
|
|
132
|
-
<Box gap={1}>
|
|
133
|
-
<Text bold>Style:</Text>
|
|
134
|
-
{STYLES.map((s, i) => (
|
|
135
|
-
<Text key={s} color={i === styleIndex ? "$primary" : "$muted"} bold={i === styleIndex}>
|
|
136
|
-
{i === styleIndex ? `[${s}]` : s}
|
|
137
|
-
</Text>
|
|
138
|
-
))}
|
|
139
|
-
</Box>
|
|
140
|
-
|
|
141
|
-
<Box flexDirection="row" gap={2}>
|
|
142
|
-
<BorderPanel style={currentStyle} highlight={focusedSide === "border"} />
|
|
143
|
-
<OutlinePanel style={currentStyle} highlight={focusedSide === "outline"} />
|
|
144
|
-
</Box>
|
|
145
|
-
|
|
146
|
-
<Muted>
|
|
147
|
-
{" "}
|
|
148
|
-
<Kbd>Tab</Kbd> toggle focus <Kbd>h/l</Kbd> change style <Kbd>Esc/q</Kbd> quit
|
|
149
|
-
</Muted>
|
|
150
|
-
</Box>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ============================================================================
|
|
155
|
-
// Main
|
|
156
|
-
// ============================================================================
|
|
157
|
-
|
|
158
|
-
async function main() {
|
|
159
|
-
using term = createTerm()
|
|
160
|
-
const { waitUntilExit } = await render(
|
|
161
|
-
<ExampleBanner meta={meta} controls="Tab toggle h/l style Esc/q quit">
|
|
162
|
-
<OutlineDemo />
|
|
163
|
-
</ExampleBanner>,
|
|
164
|
-
term,
|
|
165
|
-
)
|
|
166
|
-
await waitUntilExit()
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (import.meta.main) {
|
|
170
|
-
main().catch(console.error)
|
|
171
|
-
}
|
|
@@ -1,198 +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/interactive/paste-demo.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
|
-
createTerm,
|
|
30
|
-
type Key,
|
|
31
|
-
} from "../../src/index.js"
|
|
32
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
33
|
-
|
|
34
|
-
export const meta: ExampleMeta = {
|
|
35
|
-
name: "Bracketed Paste",
|
|
36
|
-
description: "Receive pasted text as a single event via bracketed paste mode",
|
|
37
|
-
features: ["onPaste", "useInput", "bracketed paste mode"],
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ============================================================================
|
|
41
|
-
// Types
|
|
42
|
-
// ============================================================================
|
|
43
|
-
|
|
44
|
-
interface PasteEvent {
|
|
45
|
-
id: number
|
|
46
|
-
text: string
|
|
47
|
-
charCount: number
|
|
48
|
-
lineCount: number
|
|
49
|
-
timestamp: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ============================================================================
|
|
53
|
-
// Components
|
|
54
|
-
// ============================================================================
|
|
55
|
-
|
|
56
|
-
function PasteIndicator(): JSX.Element {
|
|
57
|
-
return (
|
|
58
|
-
<Box gap={1} paddingX={1}>
|
|
59
|
-
<Text color="$success" bold>
|
|
60
|
-
{"●"}
|
|
61
|
-
</Text>
|
|
62
|
-
<Text>Paste mode:</Text>
|
|
63
|
-
<Text color="$success" bold>
|
|
64
|
-
ENABLED
|
|
65
|
-
</Text>
|
|
66
|
-
<Muted>(bracketed paste is automatic with render())</Muted>
|
|
67
|
-
</Box>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function PasteEventCard({ event, isLatest }: { event: PasteEvent; isLatest: boolean }): JSX.Element {
|
|
72
|
-
const preview = event.text.length > 60 ? event.text.slice(0, 57) + "..." : event.text
|
|
73
|
-
const displayText = preview.replace(/\n/g, "\\n").replace(/\t/g, "\\t")
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Box
|
|
77
|
-
flexDirection="column"
|
|
78
|
-
borderStyle="round"
|
|
79
|
-
borderColor={isLatest ? "$primary" : "$border"}
|
|
80
|
-
paddingX={1}
|
|
81
|
-
marginBottom={0}
|
|
82
|
-
>
|
|
83
|
-
<Box justifyContent="space-between">
|
|
84
|
-
<H1 color={isLatest ? "$primary" : "white"}>Paste #{event.id}</H1>
|
|
85
|
-
<Small>{event.timestamp}</Small>
|
|
86
|
-
</Box>
|
|
87
|
-
<Box gap={2}>
|
|
88
|
-
<Small>
|
|
89
|
-
{event.charCount} char{event.charCount !== 1 ? "s" : ""}
|
|
90
|
-
</Small>
|
|
91
|
-
<Small>
|
|
92
|
-
{event.lineCount} line{event.lineCount !== 1 ? "s" : ""}
|
|
93
|
-
</Small>
|
|
94
|
-
</Box>
|
|
95
|
-
<Box marginTop={1}>
|
|
96
|
-
<Text color="yellow">{displayText}</Text>
|
|
97
|
-
</Box>
|
|
98
|
-
</Box>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function EmptyState(): JSX.Element {
|
|
103
|
-
return (
|
|
104
|
-
<Box flexDirection="column" padding={2} alignItems="center">
|
|
105
|
-
<Muted>No paste events yet.</Muted>
|
|
106
|
-
<Lead>Try pasting some text from your clipboard!</Lead>
|
|
107
|
-
<Lead>(Cmd+V on macOS, Ctrl+Shift+V on Linux)</Lead>
|
|
108
|
-
</Box>
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function PasteDemo(): JSX.Element {
|
|
113
|
-
const { exit } = useApp()
|
|
114
|
-
const [pasteHistory, setPasteHistory] = useState<PasteEvent[]>([])
|
|
115
|
-
const [nextId, setNextId] = useState(1)
|
|
116
|
-
|
|
117
|
-
useInput(
|
|
118
|
-
(input: string, key: Key) => {
|
|
119
|
-
if (input === "q" || key.escape) {
|
|
120
|
-
exit()
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Clear history
|
|
125
|
-
if (input === "x") {
|
|
126
|
-
setPasteHistory([])
|
|
127
|
-
setNextId(1)
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
onPaste: (text: string) => {
|
|
132
|
-
const now = new Date()
|
|
133
|
-
const timestamp = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`
|
|
134
|
-
|
|
135
|
-
const event: PasteEvent = {
|
|
136
|
-
id: nextId,
|
|
137
|
-
text,
|
|
138
|
-
charCount: text.length,
|
|
139
|
-
lineCount: text.split("\n").length,
|
|
140
|
-
timestamp,
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
setPasteHistory((prev) => [event, ...prev].slice(0, 10))
|
|
144
|
-
setNextId((prev) => prev + 1)
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<Box flexDirection="column" padding={1} gap={1}>
|
|
151
|
-
<PasteIndicator />
|
|
152
|
-
|
|
153
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$primary" paddingX={1}>
|
|
154
|
-
<Box marginBottom={1}>
|
|
155
|
-
<H1>Paste History</H1>
|
|
156
|
-
<Small>
|
|
157
|
-
{" "}
|
|
158
|
-
— {pasteHistory.length} event{pasteHistory.length !== 1 ? "s" : ""}
|
|
159
|
-
</Small>
|
|
160
|
-
</Box>
|
|
161
|
-
|
|
162
|
-
{pasteHistory.length === 0 ? (
|
|
163
|
-
<EmptyState />
|
|
164
|
-
) : (
|
|
165
|
-
<Box flexDirection="column" overflow="scroll" height={12} gap={1}>
|
|
166
|
-
{pasteHistory.map((event, index) => (
|
|
167
|
-
<PasteEventCard key={event.id} event={event} isLatest={index === 0} />
|
|
168
|
-
))}
|
|
169
|
-
</Box>
|
|
170
|
-
)}
|
|
171
|
-
</Box>
|
|
172
|
-
|
|
173
|
-
<Muted>
|
|
174
|
-
{" "}
|
|
175
|
-
<Kbd>Paste text</Kbd> to see events <Kbd>x</Kbd> clear <Kbd>Esc/q</Kbd> quit
|
|
176
|
-
</Muted>
|
|
177
|
-
</Box>
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// ============================================================================
|
|
182
|
-
// Main
|
|
183
|
-
// ============================================================================
|
|
184
|
-
|
|
185
|
-
async function main() {
|
|
186
|
-
using term = createTerm()
|
|
187
|
-
const { waitUntilExit } = await render(
|
|
188
|
-
<ExampleBanner meta={meta} controls="Paste text to see events x clear Esc/q quit">
|
|
189
|
-
<PasteDemo />
|
|
190
|
-
</ExampleBanner>,
|
|
191
|
-
term,
|
|
192
|
-
)
|
|
193
|
-
await waitUntilExit()
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (import.meta.main) {
|
|
197
|
-
main().catch(console.error)
|
|
198
|
-
}
|
|
@@ -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 "../../src/index.js"
|
|
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" 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 ? "black" : "white"} 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
|
-
}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Search Filter Example
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates React concurrent features for responsive typing:
|
|
5
|
-
* - useTransition for low-priority state updates
|
|
6
|
-
* - useDeferredValue for deferred filtering
|
|
7
|
-
* - Typing remains responsive even with heavy filtering
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import React, { useState, useDeferredValue, useTransition } from "react"
|
|
11
|
-
import { render, Box, Text, Kbd, Muted, Strong, Lead, useInput, useApp, createTerm, type Key } from "../../src/index.js"
|
|
12
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
13
|
-
|
|
14
|
-
export const meta: ExampleMeta = {
|
|
15
|
-
name: "Search Filter",
|
|
16
|
-
description: "useTransition + useDeferredValue for responsive concurrent search",
|
|
17
|
-
features: ["useDeferredValue", "useTransition", "useInput"],
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Types
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
interface Item {
|
|
25
|
-
id: number
|
|
26
|
-
name: string
|
|
27
|
-
category: string
|
|
28
|
-
tags: string[]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ============================================================================
|
|
32
|
-
// Data
|
|
33
|
-
// ============================================================================
|
|
34
|
-
|
|
35
|
-
const items: Item[] = [
|
|
36
|
-
{
|
|
37
|
-
id: 1,
|
|
38
|
-
name: "React Hooks Guide",
|
|
39
|
-
category: "docs",
|
|
40
|
-
tags: ["react", "hooks", "tutorial"],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: 2,
|
|
44
|
-
name: "TypeScript Patterns",
|
|
45
|
-
category: "docs",
|
|
46
|
-
tags: ["typescript", "patterns"],
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: 3,
|
|
50
|
-
name: "Build Configuration",
|
|
51
|
-
category: "config",
|
|
52
|
-
tags: ["webpack", "vite", "build"],
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: 4,
|
|
56
|
-
name: "Testing Best Practices",
|
|
57
|
-
category: "docs",
|
|
58
|
-
tags: ["testing", "jest", "vitest"],
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: 5,
|
|
62
|
-
name: "API Documentation",
|
|
63
|
-
category: "docs",
|
|
64
|
-
tags: ["api", "rest", "graphql"],
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 6,
|
|
68
|
-
name: "Database Schema",
|
|
69
|
-
category: "config",
|
|
70
|
-
tags: ["database", "sql", "migration"],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: 7,
|
|
74
|
-
name: "Authentication Flow",
|
|
75
|
-
category: "docs",
|
|
76
|
-
tags: ["auth", "security", "jwt"],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 8,
|
|
80
|
-
name: "CI/CD Pipeline",
|
|
81
|
-
category: "config",
|
|
82
|
-
tags: ["ci", "deployment", "github"],
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
id: 9,
|
|
86
|
-
name: "Performance Tuning",
|
|
87
|
-
category: "docs",
|
|
88
|
-
tags: ["performance", "optimization"],
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: 10,
|
|
92
|
-
name: "Error Handling",
|
|
93
|
-
category: "docs",
|
|
94
|
-
tags: ["errors", "debugging", "logging"],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: 11,
|
|
98
|
-
name: "State Management",
|
|
99
|
-
category: "docs",
|
|
100
|
-
tags: ["state", "redux", "zustand"],
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 12,
|
|
104
|
-
name: "CSS Architecture",
|
|
105
|
-
category: "docs",
|
|
106
|
-
tags: ["css", "tailwind", "styled"],
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
id: 13,
|
|
110
|
-
name: "Security Guidelines",
|
|
111
|
-
category: "docs",
|
|
112
|
-
tags: ["security", "owasp", "audit"],
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
id: 14,
|
|
116
|
-
name: "Deployment Scripts",
|
|
117
|
-
category: "config",
|
|
118
|
-
tags: ["deploy", "docker", "k8s"],
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
id: 15,
|
|
122
|
-
name: "Monitoring Setup",
|
|
123
|
-
category: "config",
|
|
124
|
-
tags: ["monitoring", "metrics", "logs"],
|
|
125
|
-
},
|
|
126
|
-
]
|
|
127
|
-
|
|
128
|
-
// ============================================================================
|
|
129
|
-
// Components
|
|
130
|
-
// ============================================================================
|
|
131
|
-
|
|
132
|
-
function SearchInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {
|
|
133
|
-
return (
|
|
134
|
-
<Box>
|
|
135
|
-
<Strong color="$primary">Search: </Strong>
|
|
136
|
-
<Text>{value}</Text>
|
|
137
|
-
<Text dim>|</Text>
|
|
138
|
-
</Box>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function FilteredList({ query, isPending }: { query: string; isPending: boolean }) {
|
|
143
|
-
// Simulate expensive filtering (in real app this might be fuzzy search)
|
|
144
|
-
const filtered = items.filter((item) => {
|
|
145
|
-
const searchLower = query.toLowerCase()
|
|
146
|
-
return (
|
|
147
|
-
item.name.toLowerCase().includes(searchLower) ||
|
|
148
|
-
item.category.toLowerCase().includes(searchLower) ||
|
|
149
|
-
item.tags.some((tag) => tag.toLowerCase().includes(searchLower))
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<Box flexDirection="column" marginTop={1}>
|
|
155
|
-
<Box marginBottom={1}>
|
|
156
|
-
<Muted>
|
|
157
|
-
{filtered.length} results
|
|
158
|
-
{isPending && " (filtering...)"}
|
|
159
|
-
</Muted>
|
|
160
|
-
</Box>
|
|
161
|
-
{filtered.map((item) => (
|
|
162
|
-
<Box key={item.id} marginBottom={1}>
|
|
163
|
-
<Text bold>{item.name}</Text>
|
|
164
|
-
<Text dim> [{item.category}]</Text>
|
|
165
|
-
<Text color="gray"> {item.tags.join(", ")}</Text>
|
|
166
|
-
</Box>
|
|
167
|
-
))}
|
|
168
|
-
{filtered.length === 0 && <Lead>No matches found</Lead>}
|
|
169
|
-
</Box>
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function SearchApp(): JSX.Element {
|
|
174
|
-
const { exit } = useApp()
|
|
175
|
-
const [query, setQuery] = useState("")
|
|
176
|
-
|
|
177
|
-
// useDeferredValue: The filtered list uses a deferred version of the query
|
|
178
|
-
// This keeps typing responsive while the list catches up
|
|
179
|
-
const deferredQuery = useDeferredValue(query)
|
|
180
|
-
|
|
181
|
-
// useTransition: Mark filtering as low-priority (optional, shows pending state)
|
|
182
|
-
const [isPending, startTransition] = useTransition()
|
|
183
|
-
|
|
184
|
-
useInput((input: string, key: Key) => {
|
|
185
|
-
if (key.escape) {
|
|
186
|
-
exit()
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (key.backspace || key.delete) {
|
|
191
|
-
// Backspace: remove last character
|
|
192
|
-
startTransition(() => {
|
|
193
|
-
setQuery((prev) => prev.slice(0, -1))
|
|
194
|
-
})
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Add printable characters
|
|
199
|
-
if (input && !key.ctrl && !key.meta) {
|
|
200
|
-
startTransition(() => {
|
|
201
|
-
setQuery((prev) => prev + input)
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<Box flexDirection="column" padding={1}>
|
|
208
|
-
<SearchInput value={query} onChange={setQuery} />
|
|
209
|
-
|
|
210
|
-
{/* List uses deferredQuery so typing stays responsive */}
|
|
211
|
-
<Box flexGrow={1}>
|
|
212
|
-
<FilteredList query={deferredQuery} isPending={isPending} />
|
|
213
|
-
</Box>
|
|
214
|
-
|
|
215
|
-
<Muted>
|
|
216
|
-
{" "}
|
|
217
|
-
<Kbd>type</Kbd> to search <Kbd>Esc/q</Kbd> quit
|
|
218
|
-
</Muted>
|
|
219
|
-
</Box>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ============================================================================
|
|
224
|
-
// Main
|
|
225
|
-
// ============================================================================
|
|
226
|
-
|
|
227
|
-
async function main() {
|
|
228
|
-
using term = createTerm()
|
|
229
|
-
const { waitUntilExit } = await render(
|
|
230
|
-
<ExampleBanner meta={meta} controls="type to search Esc quit">
|
|
231
|
-
<SearchApp />
|
|
232
|
-
</ExampleBanner>,
|
|
233
|
-
term,
|
|
234
|
-
)
|
|
235
|
-
await waitUntilExit()
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (import.meta.main) {
|
|
239
|
-
main().catch(console.error)
|
|
240
|
-
}
|