@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
@@ -1,282 +0,0 @@
1
- /**
2
- * Live Resize Demo
3
- *
4
- * THE showcase demo for silvery's unique capability: components that know their size.
5
- *
6
- * Demonstrates:
7
- * - useBoxRect() providing real-time width/height during render
8
- * - Multi-column layout that reflows from 1 to 2 to 3 columns based on width
9
- * - Responsive breakpoints with visual feedback
10
- * - Content that adapts its presentation based on available space
11
- * - No useEffect, no layout thrashing — dimensions are synchronous
12
- *
13
- * Usage: bun run examples/live-resize/index.tsx
14
- *
15
- * Try resizing your terminal to see the layout reflow in real-time!
16
- *
17
- * Controls:
18
- * Esc/q or Ctrl+C - Quit
19
- */
20
-
21
- import React from "react"
22
- import { Box, Text, H1, H3, Kbd, Muted, Small, useBoxRect } from "silvery"
23
- import { run, useInput, type Key } from "silvery/runtime"
24
- import { useCallback } from "react"
25
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
26
-
27
- export const meta: ExampleMeta = {
28
- name: "Live Resize",
29
- description: "Responsive multi-column grid that reflows based on terminal width",
30
- features: ["useBoxRect()", "responsive breakpoints", "Box flexDirection"],
31
- }
32
-
33
- // ============================================================================
34
- // Types
35
- // ============================================================================
36
-
37
- interface CardData {
38
- title: string
39
- icon: string
40
- value: string
41
- detail: string
42
- color: string
43
- sparkline: string
44
- }
45
-
46
- // ============================================================================
47
- // Data
48
- // ============================================================================
49
-
50
- const CARDS: CardData[] = [
51
- {
52
- title: "CPU Usage",
53
- icon: "\u{1f4bb}",
54
- value: "42%",
55
- detail: "4 cores, 2.4 GHz base",
56
- color: "green",
57
- sparkline: "\u2582\u2583\u2585\u2587\u2586\u2584\u2583\u2585\u2587\u2588\u2586\u2584\u2583\u2582\u2583\u2585",
58
- },
59
- {
60
- title: "Memory",
61
- icon: "\u{1f9e0}",
62
- value: "8.2 GB",
63
- detail: "of 16 GB (51% used)",
64
- color: "cyan",
65
- sparkline: "\u2584\u2584\u2585\u2585\u2585\u2586\u2586\u2586\u2585\u2585\u2586\u2586\u2587\u2587\u2586\u2586",
66
- },
67
- {
68
- title: "Disk I/O",
69
- icon: "\u{1f4be}",
70
- value: "234 MB/s",
71
- detail: "Read: 180 MB/s Write: 54 MB/s",
72
- color: "yellow",
73
- sparkline: "\u2581\u2582\u2583\u2587\u2588\u2587\u2584\u2582\u2581\u2582\u2585\u2587\u2586\u2583\u2582\u2581",
74
- },
75
- {
76
- title: "Network",
77
- icon: "\u{1f310}",
78
- value: "1.2 Gb/s",
79
- detail: "In: 800 Mb/s Out: 400 Mb/s",
80
- color: "magenta",
81
- sparkline: "\u2583\u2584\u2585\u2586\u2587\u2586\u2585\u2584\u2585\u2586\u2587\u2588\u2587\u2586\u2585\u2584",
82
- },
83
- {
84
- title: "Processes",
85
- icon: "\u{2699}\u{fe0f}",
86
- value: "247",
87
- detail: "12 running, 235 sleeping",
88
- color: "blue",
89
- sparkline: "\u2585\u2585\u2585\u2586\u2585\u2585\u2585\u2585\u2586\u2585\u2585\u2585\u2586\u2585\u2585\u2585",
90
- },
91
- {
92
- title: "Temperature",
93
- icon: "\u{1f321}\u{fe0f}",
94
- value: "62 C",
95
- detail: "Max: 85 C (safe range)",
96
- color: "red",
97
- sparkline: "\u2583\u2583\u2584\u2584\u2585\u2585\u2586\u2586\u2585\u2585\u2584\u2584\u2583\u2584\u2585\u2585",
98
- },
99
- ]
100
-
101
- // ============================================================================
102
- // Components
103
- // ============================================================================
104
-
105
- function MetricCard({ card, compact }: { card: CardData; compact: boolean }) {
106
- if (compact) {
107
- // Minimal: single-line card for narrow terminals
108
- return (
109
- <Box borderStyle="round" borderColor={card.color} paddingX={1} flexDirection="row" justifyContent="space-between">
110
- <H1 color={card.color}>{card.title}</H1>
111
- <H3>{card.value}</H3>
112
- </Box>
113
- )
114
- }
115
-
116
- // Full card with sparkline and details
117
- return (
118
- <Box borderStyle="round" borderColor={card.color} paddingX={1} flexDirection="column" flexGrow={1}>
119
- <Box justifyContent="space-between">
120
- <H1 color={card.color}>{card.title}</H1>
121
- <H1 color={card.color}>{card.value}</H1>
122
- </Box>
123
- <Text color={card.color}>{card.sparkline}</Text>
124
- <Small>{card.detail}</Small>
125
- </Box>
126
- )
127
- }
128
-
129
- function BreakpointIndicator({ width, columns }: { width: number; columns: number }) {
130
- const breakpoints = [
131
- { threshold: 0, cols: 1, label: "< 60" },
132
- { threshold: 60, cols: 2, label: "60-99" },
133
- { threshold: 100, cols: 3, label: "100+" },
134
- ]
135
-
136
- return (
137
- <Box gap={2} paddingX={1}>
138
- {breakpoints.map((bp) => {
139
- const isActive = bp.cols === columns
140
- return (
141
- <Box key={bp.cols} gap={1}>
142
- <Text color={isActive ? "green" : "gray"} bold={isActive}>
143
- {isActive ? "\u25cf" : "\u25cb"}
144
- </Text>
145
- <Text color={isActive ? "white" : "gray"} bold={isActive}>
146
- {bp.cols} col{bp.cols > 1 ? "s" : " "} ({bp.label})
147
- </Text>
148
- </Box>
149
- )
150
- })}
151
- </Box>
152
- )
153
- }
154
-
155
- function GridLayout({ cards, columns, compact }: { cards: CardData[]; columns: number; compact: boolean }) {
156
- if (columns === 1) {
157
- return (
158
- <Box flexDirection="column" gap={compact ? 0 : 1} flexGrow={1}>
159
- {cards.map((card) => (
160
- <MetricCard key={card.title} card={card} compact={compact} />
161
- ))}
162
- </Box>
163
- )
164
- }
165
-
166
- // Build rows of N columns
167
- const rows: CardData[][] = []
168
- for (let i = 0; i < cards.length; i += columns) {
169
- rows.push(cards.slice(i, i + columns))
170
- }
171
-
172
- return (
173
- <Box flexDirection="column" gap={1} flexGrow={1}>
174
- {rows.map((row, rowIndex) => (
175
- <Box key={rowIndex} flexDirection="row" gap={1}>
176
- {row.map((card) => (
177
- <Box key={card.title} flexGrow={1} flexBasis={0}>
178
- <MetricCard card={card} compact={false} />
179
- </Box>
180
- ))}
181
- {/* Fill remaining slots for even spacing */}
182
- {row.length < columns &&
183
- Array.from({ length: columns - row.length }, (_, i) => (
184
- <Box key={`spacer-${i}`} flexGrow={1} flexBasis={0} />
185
- ))}
186
- </Box>
187
- ))}
188
- </Box>
189
- )
190
- }
191
-
192
- function CodeSnippet({ width }: { width: number }) {
193
- const showSnippet = width >= 60
194
-
195
- if (!showSnippet) {
196
- return (
197
- <Box paddingX={1}>
198
- <Text dim italic>
199
- (Widen terminal to see the code that powers this)
200
- </Text>
201
- </Box>
202
- )
203
- }
204
-
205
- return (
206
- <Box flexDirection="column" borderStyle="single" borderColor="$border" paddingX={1}>
207
- <H1 color="yellow">How it works:</H1>
208
- <Text color="gray">
209
- {" "}
210
- <Text color="magenta">const</Text> {"{"} width {"}"} = <Text color="cyan">useBoxRect</Text>()
211
- </Text>
212
- <Text color="gray">
213
- {" "}
214
- <Text color="magenta">const</Text> columns = width {">"} 100 ? <Text color="green">3</Text> : width {">"} 60 ?{" "}
215
- <Text color="green">2</Text> : <Text color="green">1</Text>
216
- </Text>
217
- <Text dim italic>
218
- {" "}// No useEffect, no layout thrashing. Synchronous.
219
- </Text>
220
- </Box>
221
- )
222
- }
223
-
224
- // ============================================================================
225
- // Main App
226
- // ============================================================================
227
-
228
- function LiveResize() {
229
- const { width, height } = useBoxRect()
230
-
231
- // Responsive breakpoints
232
- const columns = width >= 100 ? 3 : width >= 60 ? 2 : 1
233
- const compact = height < 20 || width < 40
234
-
235
- useInput(
236
- useCallback((input: string, key: Key) => {
237
- if (input === "q" || key.escape || (key.ctrl && input === "c")) {
238
- return "exit"
239
- }
240
- }, []),
241
- )
242
-
243
- return (
244
- <Box flexDirection="column" width="100%" height="100%" padding={1}>
245
- {/* Breakpoint indicator */}
246
- <BreakpointIndicator width={width} columns={columns} />
247
-
248
- {/* Main grid */}
249
- <Box flexGrow={1} flexDirection="column" marginTop={1}>
250
- <GridLayout cards={CARDS} columns={columns} compact={compact} />
251
- </Box>
252
-
253
- {/* Code snippet showing how it works */}
254
- {!compact && <CodeSnippet width={width} />}
255
-
256
- {/* Footer */}
257
- <Box justifyContent="space-between" paddingX={1}>
258
- <Muted>Resize your terminal to see the layout reflow</Muted>
259
- <Muted>
260
- <Kbd>Esc/q</Kbd> quit
261
- </Muted>
262
- </Box>
263
- </Box>
264
- )
265
- }
266
-
267
- // ============================================================================
268
- // Main
269
- // ============================================================================
270
-
271
- async function main() {
272
- const handle = await run(
273
- <ExampleBanner meta={meta} controls="Resize terminal to see reflow Esc/q quit">
274
- <LiveResize />
275
- </ExampleBanner>,
276
- )
277
- await handle.waitUntilExit()
278
- }
279
-
280
- if (import.meta.main) {
281
- main().catch(console.error)
282
- }
@@ -1,51 +0,0 @@
1
- import React from "react"
2
- import { render, Box, Text, useApp, useInput, createTerm } from "silvery"
3
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
4
-
5
- export const meta: ExampleMeta = {
6
- name: "Overflow",
7
- description: 'overflow="hidden" content clipping demonstration',
8
- features: ['overflow="hidden"', "Box height"],
9
- }
10
-
11
- export function OverflowApp() {
12
- const { exit } = useApp()
13
- useInput((input, key) => {
14
- if (input === "q" || key.escape) exit()
15
- })
16
-
17
- return (
18
- <Box flexDirection="column" padding={1}>
19
- <Text color="yellow">Title</Text>
20
-
21
- <Box borderStyle="single" borderColor="$primary" height={5} overflow="hidden">
22
- <Box flexDirection="column" flexGrow={1}>
23
- <Text>Line 1</Text>
24
- <Text>Line 2</Text>
25
- <Text>Line 3</Text>
26
- <Text>Line 4</Text>
27
- <Text>Line 5</Text>
28
- <Text>Line 6 - should NOT appear</Text>
29
- <Text>Line 7 - should NOT appear</Text>
30
- </Box>
31
- </Box>
32
-
33
- <Text color="$success">This should NOT be corrupted</Text>
34
- </Box>
35
- )
36
- }
37
-
38
- async function main() {
39
- using term = createTerm()
40
- const { waitUntilExit } = await render(
41
- <ExampleBanner meta={meta} controls="Esc/q quit">
42
- <OverflowApp />
43
- </ExampleBanner>,
44
- term,
45
- )
46
- await waitUntilExit()
47
- }
48
-
49
- if (import.meta.main) {
50
- main().catch(console.error)
51
- }
@@ -1,283 +0,0 @@
1
- /**
2
- * Pretext Demo — snug-content bubbles + even wrapping comparison
3
- *
4
- * Interactive demo showcasing Silvery's Pretext-inspired text layout:
5
- * - width="snug-content" — tightest box width for same line count (shrinkwrap)
6
- * - wrap="even" — minimum-raggedness line breaking (Knuth-Plass)
7
- *
8
- * Inspired by https://chenglou.me/pretext/
9
- *
10
- * Usage: bun examples/pretext-demo.tsx
11
- *
12
- * Controls:
13
- * j/k - Cycle demo sections
14
- * Esc/q - Quit
15
- */
16
-
17
- import React, { useState, useCallback } from "react"
18
- import { Box, Text, H2, Muted, Small, Kbd, Divider } from "silvery"
19
- import { run, useInput, type Key } from "silvery/runtime"
20
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
21
-
22
- export const meta: ExampleMeta = {
23
- name: "text layout",
24
- description: "Snug-content bubbles + even wrapping — inspired by chenglou/pretext",
25
- demo: true,
26
- features: ['width="snug-content"', 'wrap="even"', "chat bubbles", "paragraph layout"],
27
- }
28
-
29
- // ============================================================================
30
- // Sample Data
31
- // ============================================================================
32
-
33
- const CHAT_MESSAGES = [
34
- { sender: "Alice", text: "Hey!" },
35
- { sender: "Bob", text: "What are you working on?" },
36
- { sender: "Alice", text: "Building a terminal UI framework with beautiful text layout." },
37
- {
38
- sender: "Bob",
39
- text: "That sounds interesting. Does it handle word wrapping well? Most terminal apps have really ugly ragged text.",
40
- },
41
- {
42
- sender: "Alice",
43
- text: "Yes! It uses Pretext-inspired algorithms for snug bubbles and even line breaking.",
44
- },
45
- ]
46
-
47
- const PARAGRAPH =
48
- "The quick brown fox jumps over the lazy dog. " +
49
- "Typography in terminal applications has always been limited by the character grid, " +
50
- "but modern algorithms can distribute text across lines for minimum raggedness, " +
51
- "producing results that rival print-quality typesetting. " +
52
- "Silvery brings these techniques to the terminal."
53
-
54
- // ============================================================================
55
- // Components
56
- // ============================================================================
57
-
58
- /** A single chat bubble with configurable width and wrap mode. */
59
- function Bubble({
60
- sender,
61
- text,
62
- width,
63
- wrap,
64
- align,
65
- }: {
66
- sender: string
67
- text: string
68
- width: "fit-content" | "snug-content"
69
- wrap?: "wrap" | "even"
70
- align?: "flex-start" | "flex-end"
71
- }) {
72
- return (
73
- <Box flexDirection="column" alignItems={align ?? "flex-start"}>
74
- <Small> {sender}</Small>
75
- <Box width={width} borderStyle="round" borderColor="$border" paddingX={1} maxWidth={48}>
76
- <Text wrap={wrap ?? "wrap"}>{text}</Text>
77
- </Box>
78
- </Box>
79
- )
80
- }
81
-
82
- /** Column of chat bubbles with a label. */
83
- function BubbleColumn({
84
- label,
85
- sublabel,
86
- width,
87
- wrap,
88
- }: {
89
- label: string
90
- sublabel: string
91
- width: "fit-content" | "snug-content"
92
- wrap?: "wrap" | "even"
93
- }) {
94
- return (
95
- <Box flexDirection="column" flexGrow={1} flexBasis={0}>
96
- <Text bold color="$accent">
97
- {label}
98
- </Text>
99
- <Muted>{sublabel}</Muted>
100
- <Text> </Text>
101
- <Box flexDirection="column" gap={1}>
102
- {CHAT_MESSAGES.map((msg, i) => (
103
- <Bubble
104
- key={i}
105
- sender={msg.sender}
106
- text={msg.text}
107
- width={width}
108
- wrap={wrap}
109
- align={msg.sender === "Bob" ? "flex-end" : "flex-start"}
110
- />
111
- ))}
112
- </Box>
113
- </Box>
114
- )
115
- }
116
-
117
- /** Side-by-side paragraph comparison. */
118
- function ParagraphComparison({ label, sublabel, wrap }: { label: string; sublabel: string; wrap: "wrap" | "even" }) {
119
- return (
120
- <Box flexDirection="column" flexGrow={1} flexBasis={0}>
121
- <Text bold color="$accent">
122
- {label}
123
- </Text>
124
- <Muted>{sublabel}</Muted>
125
- <Text> </Text>
126
- <Box width={52} borderStyle="single" borderColor="$border" paddingX={1}>
127
- <Text wrap={wrap}>{PARAGRAPH}</Text>
128
- </Box>
129
- </Box>
130
- )
131
- }
132
-
133
- // ============================================================================
134
- // Demo Sections
135
- // ============================================================================
136
-
137
- function Demo1Bubbles() {
138
- return (
139
- <Box flexDirection="column">
140
- <H2>Chat Bubbles: fit-content vs snug-content</H2>
141
- <Muted>{" "}fit-content sizes to the widest wrapped line (dead space on short lines).</Muted>
142
- <Muted>{" "}snug-content binary-searches for the tightest width with the same line count.</Muted>
143
- <Text> </Text>
144
- <Box flexDirection="row" gap={3} paddingX={1}>
145
- <BubbleColumn label='width="fit-content"' sublabel="CSS default — dead space" width="fit-content" />
146
- <BubbleColumn label='width="snug-content"' sublabel="Pretext shrinkwrap — tight" width="snug-content" />
147
- </Box>
148
- </Box>
149
- )
150
- }
151
-
152
- function Demo2EvenWrap() {
153
- return (
154
- <Box flexDirection="column">
155
- <H2>Paragraph Layout: greedy vs even wrapping</H2>
156
- <Muted>{" "}Greedy fills each line left-to-right, leaving a ragged right edge.</Muted>
157
- <Muted>{" "}Even uses minimum-raggedness DP to distribute words across all lines.</Muted>
158
- <Text> </Text>
159
- <Box flexDirection="row" gap={3} paddingX={1}>
160
- <ParagraphComparison label='wrap="wrap"' sublabel="Greedy — ragged right edge" wrap="wrap" />
161
- <ParagraphComparison label='wrap="even"' sublabel="Min-raggedness — balanced lines" wrap="even" />
162
- </Box>
163
- </Box>
164
- )
165
- }
166
-
167
- function Demo3Combined() {
168
- return (
169
- <Box flexDirection="column">
170
- <H2>Combined: snug-content + even wrapping</H2>
171
- <Muted>{" "}The tightest, most beautiful text layout — both features together.</Muted>
172
- <Text> </Text>
173
- <Box flexDirection="row" gap={3} paddingX={1}>
174
- <Box flexDirection="column" flexGrow={1} flexBasis={0}>
175
- <Text bold color="$accent">
176
- Default (fit-content + greedy)
177
- </Text>
178
- <Muted>Widest line sets width, lines fill greedily</Muted>
179
- <Text> </Text>
180
- <Box flexDirection="column" gap={1}>
181
- <Box width="fit-content" borderStyle="round" borderColor="$border" paddingX={1} maxWidth={48}>
182
- <Text wrap="wrap">
183
- Typography in terminal applications has always been limited by the character grid, but modern algorithms
184
- change that.
185
- </Text>
186
- </Box>
187
- <Box width="fit-content" borderStyle="round" borderColor="$border" paddingX={1} maxWidth={48}>
188
- <Text wrap="wrap">Silvery brings Pretext-inspired layout to the terminal with two simple props.</Text>
189
- </Box>
190
- </Box>
191
- </Box>
192
- <Box flexDirection="column" flexGrow={1} flexBasis={0}>
193
- <Text bold color="$accent">
194
- Pretext (snug-content + even)
195
- </Text>
196
- <Muted>Tightest width, balanced line lengths</Muted>
197
- <Text> </Text>
198
- <Box flexDirection="column" gap={1}>
199
- <Box width="snug-content" borderStyle="round" borderColor="$primary" paddingX={1} maxWidth={48}>
200
- <Text wrap="even">
201
- Typography in terminal applications has always been limited by the character grid, but modern algorithms
202
- change that.
203
- </Text>
204
- </Box>
205
- <Box width="snug-content" borderStyle="round" borderColor="$primary" paddingX={1} maxWidth={48}>
206
- <Text wrap="even">Silvery brings Pretext-inspired layout to the terminal with two simple props.</Text>
207
- </Box>
208
- </Box>
209
- </Box>
210
- </Box>
211
- </Box>
212
- )
213
- }
214
-
215
- // ============================================================================
216
- // Main App
217
- // ============================================================================
218
-
219
- const DEMOS = [Demo1Bubbles, Demo2EvenWrap, Demo3Combined]
220
- const DEMO_LABELS = ["Chat Bubbles", "Even Wrapping", "Combined"]
221
-
222
- function PretextDemo() {
223
- const [demoIndex, setDemoIndex] = useState(0)
224
-
225
- useInput(
226
- useCallback((input: string, key: Key) => {
227
- if (input === "q" || key.escape) return "exit"
228
- if (input === "j" || key.downArrow || key.rightArrow) {
229
- setDemoIndex((i) => Math.min(i + 1, DEMOS.length - 1))
230
- }
231
- if (input === "k" || key.upArrow || key.leftArrow) {
232
- setDemoIndex((i) => Math.max(i - 1, 0))
233
- }
234
- if (input === "1") setDemoIndex(0)
235
- if (input === "2") setDemoIndex(1)
236
- if (input === "3") setDemoIndex(2)
237
- }, []),
238
- )
239
-
240
- const Demo = DEMOS[demoIndex]!
241
-
242
- return (
243
- <Box flexDirection="column" padding={1} gap={1}>
244
- {/* Tab bar */}
245
- <Box gap={2}>
246
- {DEMO_LABELS.map((label, i) => (
247
- <Text key={i} bold={i === demoIndex} color={i === demoIndex ? "$primary" : "$muted"}>
248
- {i === demoIndex ? "▸ " : " "}
249
- {i + 1}. {label}
250
- </Text>
251
- ))}
252
- </Box>
253
- <Divider />
254
- {/* Active demo */}
255
- <Demo />
256
- {/* Footer */}
257
- <Box>
258
- <Muted>
259
- {" "}
260
- <Kbd>j/k</Kbd> or <Kbd>1-3</Kbd> switch demo{" "}
261
- <Kbd>Esc/q</Kbd> quit
262
- </Muted>
263
- </Box>
264
- </Box>
265
- )
266
- }
267
-
268
- // ============================================================================
269
- // Entry Point
270
- // ============================================================================
271
-
272
- async function main() {
273
- const handle = await run(
274
- <ExampleBanner meta={meta} controls="j/k switch demo 1-3 jump Esc/q quit">
275
- <PretextDemo />
276
- </ExampleBanner>,
277
- )
278
- await handle.waitUntilExit()
279
- }
280
-
281
- if (import.meta.main) {
282
- main().catch(console.error)
283
- }