@mdxui/terminal 2.0.0

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 (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. package/src/types.ts +103 -0
package/src/index.ts ADDED
@@ -0,0 +1,542 @@
1
+ /**
2
+ * @mdxui/terminal
3
+ *
4
+ * OpenTUI renderer for MDXUI primitives.
5
+ * Build CLI dashboards with the same component contracts as web.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { CLI, Box, Text } from '@mdxui/terminal'
10
+ *
11
+ * async function main() {
12
+ * const cli = await CLI()
13
+ * cli.render(
14
+ * <Box border="single">
15
+ * <Text color="cyan">Hello Terminal!</Text>
16
+ * </Box>
17
+ * )
18
+ * }
19
+ *
20
+ * main()
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * // For more control, use the lower-level API
26
+ * import { createCliRenderer, createRoot } from '@mdxui/terminal'
27
+ *
28
+ * async function main() {
29
+ * const renderer = await createCliRenderer()
30
+ * const root = createRoot(renderer)
31
+ * root.render(<App />)
32
+ * }
33
+ * ```
34
+ */
35
+
36
+ import React, { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
37
+ import { detectColorSupport } from './theme'
38
+
39
+ // Re-export components mapped to OpenTUI
40
+ export * from './components'
41
+ export * from './theme'
42
+ export * from './keyboard'
43
+ export * from './types'
44
+ export * from './data'
45
+
46
+ // Core compiler
47
+ export * from './core/compiler'
48
+
49
+ // Core parser
50
+ export { parseUINode, parseUINodeFromJSON, ParseError } from './core/parser'
51
+
52
+ // Core types (selective export to avoid conflicts with components/types RenderContext)
53
+ export type { UINode, RenderTier, ThemeTokens } from './core/types'
54
+ export type { RenderContext as CoreRenderContext } from './core/types'
55
+ export { UINodeSchema, RenderTierSchema, ThemeTokensSchema, RenderContextSchema } from './core/types'
56
+
57
+ // Renderers
58
+ export { renderText } from './renderers/text'
59
+ export type { TextRenderOptions } from './renderers/text'
60
+
61
+ // ANSI to CSS conversion for web rendering
62
+ export {
63
+ ansiToCSS,
64
+ ansiToHTML,
65
+ parseAnsiToSpans,
66
+ spanToInlineStyle,
67
+ } from './renderers/ansi-css'
68
+ export type {
69
+ CSSStyleProperties,
70
+ StyledSpan,
71
+ ANSIToCSSResult,
72
+ } from './renderers/ansi-css'
73
+
74
+ // ============================================================================
75
+ // Renderer Setup
76
+ // ============================================================================
77
+
78
+ /**
79
+ * CLI renderer configuration options
80
+ */
81
+ export interface CliRendererConfig {
82
+ /** Input stream (defaults to process.stdin) */
83
+ stdin?: NodeJS.ReadStream
84
+ /** Output stream (defaults to process.stdout) */
85
+ stdout?: NodeJS.WriteStream
86
+ /** Exit on Ctrl+C (defaults to true) */
87
+ exitOnCtrlC?: boolean
88
+ /** Target frames per second */
89
+ targetFps?: number
90
+ /** Use alternate screen buffer */
91
+ useAlternateScreen?: boolean
92
+ /** Enable mouse support */
93
+ useMouse?: boolean
94
+ /** Background color */
95
+ backgroundColor?: string
96
+ }
97
+
98
+ /**
99
+ * CLI renderer instance
100
+ */
101
+ export interface CliRenderer {
102
+ /** Terminal width in columns */
103
+ width: number
104
+ /** Terminal height in rows */
105
+ height: number
106
+ /** Start the render loop */
107
+ start(): void
108
+ /** Stop the render loop */
109
+ stop(): void
110
+ /** Destroy the renderer and clean up */
111
+ destroy(): void
112
+ /** Request a render update */
113
+ requestRender(): void
114
+ }
115
+
116
+ /**
117
+ * Root interface for React rendering
118
+ */
119
+ export interface Root {
120
+ /** Render a React element */
121
+ render(element: ReactNode): void
122
+ /** Unmount and clean up */
123
+ unmount(): void
124
+ }
125
+
126
+ /**
127
+ * Create an OpenTUI CLI renderer for terminal rendering.
128
+ *
129
+ * @param config - Optional configuration options
130
+ * @returns Promise resolving to a CLI renderer instance
131
+ *
132
+ * @example
133
+ * ```tsx
134
+ * import { createCliRenderer, createRoot } from '@mdxui/terminal'
135
+ *
136
+ * async function main() {
137
+ * const renderer = await createCliRenderer()
138
+ * const root = createRoot(renderer)
139
+ * root.render(<App />)
140
+ * }
141
+ * ```
142
+ */
143
+ export async function createCliRenderer(config?: CliRendererConfig): Promise<CliRenderer> {
144
+ const { createCliRenderer: opentuiCreateCliRenderer } = await import('@opentui/core')
145
+ return opentuiCreateCliRenderer(config)
146
+ }
147
+
148
+ /**
149
+ * Create a root for rendering a React tree with the given CLI renderer.
150
+ *
151
+ * @param renderer - The CLI renderer to use (from createCliRenderer)
152
+ * @returns A root object with render and unmount methods
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * const renderer = await createCliRenderer()
157
+ * const root = createRoot(renderer)
158
+ * root.render(<App />)
159
+ *
160
+ * // Later, to clean up:
161
+ * root.unmount()
162
+ * renderer.destroy()
163
+ * ```
164
+ */
165
+ export function createRoot(renderer: CliRenderer): Root {
166
+ // Dynamic import to avoid issues in non-terminal environments
167
+ let rootInstance: Root | null = null
168
+ let initPromise: Promise<Root> | null = null
169
+
170
+ return {
171
+ render: async (element: ReactNode) => {
172
+ // Guard against race condition: if render() is called twice before
173
+ // the async import completes, we reuse the same initialization promise
174
+ // to ensure only one root instance is created
175
+ if (!initPromise) {
176
+ initPromise = import('@opentui/react').then(({ createRoot: opentuiCreateRoot }) => {
177
+ rootInstance = opentuiCreateRoot(renderer as any)
178
+ return rootInstance
179
+ })
180
+ }
181
+ const root = await initPromise
182
+ root.render(element)
183
+ },
184
+ unmount: () => {
185
+ if (rootInstance) {
186
+ rootInstance.unmount()
187
+ rootInstance = null
188
+ }
189
+ initPromise = null
190
+ },
191
+ }
192
+ }
193
+
194
+ // ============================================================================
195
+ // High-Level CLI API
196
+ // ============================================================================
197
+
198
+ /**
199
+ * Options for the CLI() function
200
+ */
201
+ export interface CLIOptions {
202
+ /** Color support level. Auto-detected if not specified. */
203
+ colorSupport?: 'none' | '16' | '256' | 'truecolor'
204
+ /** Input stream (defaults to process.stdin) */
205
+ stdin?: NodeJS.ReadStream
206
+ /** Output stream (defaults to process.stdout) */
207
+ stdout?: NodeJS.WriteStream
208
+ /** Exit on Ctrl+C (defaults to true) */
209
+ exitOnCtrlC?: boolean
210
+ /** Target frames per second */
211
+ targetFps?: number
212
+ /** Use alternate screen buffer */
213
+ useAlternateScreen?: boolean
214
+ /** Enable mouse support */
215
+ useMouse?: boolean
216
+ /** Background color */
217
+ backgroundColor?: string
218
+ }
219
+
220
+ /**
221
+ * CLI instance returned by CLI()
222
+ */
223
+ export interface CLIInstance {
224
+ /** Terminal width in columns */
225
+ readonly width: number
226
+ /** Terminal height in rows */
227
+ readonly height: number
228
+ /** Render a React element to the terminal */
229
+ render: (element: ReactNode) => void
230
+ /** Clear the terminal */
231
+ clear: () => void
232
+ /** Destroy the CLI instance and clean up resources */
233
+ destroy: () => void
234
+ /** Request a render update */
235
+ requestRender: () => void
236
+ /** The underlying renderer instance */
237
+ readonly renderer: CliRenderer
238
+ /** The underlying root instance */
239
+ readonly root: Root
240
+ }
241
+
242
+ /**
243
+ * Create a high-level CLI instance for terminal rendering.
244
+ *
245
+ * This is the recommended entry point for building terminal UIs with @mdxui/terminal.
246
+ * It combines `createCliRenderer` and `createRoot` into a single, easy-to-use API.
247
+ *
248
+ * @param options - Optional configuration options
249
+ * @returns Promise resolving to a CLI instance with render, clear, and destroy methods
250
+ *
251
+ * @example
252
+ * ```tsx
253
+ * import { CLI, Box, Text } from '@mdxui/terminal'
254
+ *
255
+ * async function main() {
256
+ * const cli = await CLI()
257
+ * cli.render(
258
+ * <Box border="single">
259
+ * <Text color="cyan">Hello Terminal!</Text>
260
+ * </Box>
261
+ * )
262
+ * }
263
+ *
264
+ * main()
265
+ * ```
266
+ *
267
+ * @example
268
+ * ```tsx
269
+ * // With custom options
270
+ * const cli = await CLI({
271
+ * useAlternateScreen: true,
272
+ * useMouse: true,
273
+ * exitOnCtrlC: true,
274
+ * })
275
+ * ```
276
+ */
277
+ export async function CLI(options?: CLIOptions): Promise<CLIInstance> {
278
+ // Detect color support if not explicitly specified
279
+ const colorSupport = options?.colorSupport ?? detectColorSupport()
280
+
281
+ // Map our simplified color support to OpenTUI's config
282
+ // OpenTUI doesn't have a direct colorSupport option, but we can use it
283
+ // for future color adaptation in our components
284
+
285
+ // Create the underlying renderer - use dynamic import to get the real OpenTUI renderer
286
+ const { createCliRenderer: opentuiCreateCliRenderer } = await import('@opentui/core')
287
+ const { createRoot: opentuiCreateRoot } = await import('@opentui/react')
288
+
289
+ const opentuiRenderer = await opentuiCreateCliRenderer({
290
+ stdin: options?.stdin,
291
+ stdout: options?.stdout,
292
+ exitOnCtrlC: options?.exitOnCtrlC,
293
+ targetFps: options?.targetFps,
294
+ useAlternateScreen: options?.useAlternateScreen,
295
+ useMouse: options?.useMouse,
296
+ backgroundColor: options?.backgroundColor,
297
+ })
298
+
299
+ // Create the React root with the actual OpenTUI renderer
300
+ const root = opentuiCreateRoot(opentuiRenderer)
301
+
302
+ // Create a simplified interface that exposes what consumers need
303
+ const renderer: CliRenderer = {
304
+ get width() {
305
+ return opentuiRenderer.width
306
+ },
307
+ get height() {
308
+ return opentuiRenderer.height
309
+ },
310
+ start: () => opentuiRenderer.start(),
311
+ stop: () => opentuiRenderer.stop(),
312
+ destroy: () => opentuiRenderer.destroy(),
313
+ requestRender: () => opentuiRenderer.requestRender(),
314
+ }
315
+
316
+ return {
317
+ get width() {
318
+ return opentuiRenderer.width
319
+ },
320
+ get height() {
321
+ return opentuiRenderer.height
322
+ },
323
+ render: (element: ReactNode) => {
324
+ root.render(element)
325
+ },
326
+ clear: () => {
327
+ // Render empty to clear
328
+ root.render(null)
329
+ },
330
+ destroy: () => {
331
+ root.unmount()
332
+ opentuiRenderer.destroy()
333
+ },
334
+ requestRender: () => {
335
+ opentuiRenderer.requestRender()
336
+ },
337
+ renderer,
338
+ root,
339
+ }
340
+ }
341
+
342
+ // ============================================================================
343
+ // Terminal-Specific Hooks
344
+ // ============================================================================
345
+
346
+ /**
347
+ * Get the current terminal dimensions, with automatic updates on resize.
348
+ *
349
+ * Returns `{ width, height }` representing the terminal's column and row count.
350
+ *
351
+ * **Behavior by environment:**
352
+ * - **Node.js TTY**: Uses `process.stdout.columns/rows` and subscribes to resize events
353
+ * - **Browser/Non-TTY**: Falls back to 80x24 defaults
354
+ *
355
+ * @returns Current terminal width and height in columns/rows
356
+ *
357
+ * @example
358
+ * ```tsx
359
+ * function MyComponent() {
360
+ * const { width, height } = useTerminalSize()
361
+ * return <Text>Terminal: {width}x{height}</Text>
362
+ * }
363
+ * ```
364
+ */
365
+ export function useTerminalSize(): { width: number; height: number } {
366
+ // Helper to get current dimensions
367
+ const getSize = (): { width: number; height: number } => {
368
+ if (typeof process !== 'undefined' && process.stdout?.columns && process.stdout?.rows) {
369
+ return {
370
+ width: process.stdout.columns,
371
+ height: process.stdout.rows,
372
+ }
373
+ }
374
+ // Default fallback for non-terminal environments (e.g., browser, tests)
375
+ return { width: 80, height: 24 }
376
+ }
377
+
378
+ const [size, setSize] = useState(getSize)
379
+
380
+ useEffect(() => {
381
+ // Only subscribe to resize events in Node.js TTY environment
382
+ if (typeof process === 'undefined' || !process.stdout?.on) {
383
+ return
384
+ }
385
+
386
+ const handleResize = () => {
387
+ setSize(getSize())
388
+ }
389
+
390
+ // Subscribe to resize events
391
+ process.stdout.on('resize', handleResize)
392
+
393
+ // Sync with current size in case it changed between initial render and effect
394
+ const currentSize = getSize()
395
+ if (currentSize.width !== size.width || currentSize.height !== size.height) {
396
+ setSize(currentSize)
397
+ }
398
+
399
+ // Cleanup listener on unmount
400
+ return () => {
401
+ process.stdout.off('resize', handleResize)
402
+ }
403
+ }, []) // Empty deps - we only want to set up listener once
404
+
405
+ return size
406
+ }
407
+
408
+ /**
409
+ * Access terminal context and capabilities.
410
+ *
411
+ * Provides terminal dimensions plus color support detection and raw mode status.
412
+ * Uses @opentui/react for dimensions and our color-support module for capabilities.
413
+ *
414
+ * @returns Terminal state including dimensions, color support level, and raw mode
415
+ *
416
+ * @example
417
+ * ```tsx
418
+ * function StatusBar() {
419
+ * const { width, height, colorSupport } = useTerminal()
420
+ * return (
421
+ * <Box>
422
+ * <Text>Size: {width}x{height}</Text>
423
+ * <Text>Colors: {colorSupport}</Text>
424
+ * </Box>
425
+ * )
426
+ * }
427
+ * ```
428
+ */
429
+ export function useTerminal(): {
430
+ width: number
431
+ height: number
432
+ colorSupport: 'none' | 'basic' | '256' | 'truecolor'
433
+ isRaw: boolean
434
+ } {
435
+ const { width, height } = useTerminalSize()
436
+
437
+ // Detect color support using our existing utility
438
+ // detectColorSupport returns 'none' | '16' | '256' | 'truecolor'
439
+ // We map '16' to 'basic' for a more intuitive API
440
+ const detected = detectColorSupport()
441
+ const colorSupport: 'none' | 'basic' | '256' | 'truecolor' =
442
+ detected === '16' ? 'basic' : detected
443
+
444
+ // Check raw mode from process.stdin
445
+ const isRaw = typeof process !== 'undefined' && process.stdin?.isRaw === true
446
+
447
+ return {
448
+ width,
449
+ height,
450
+ colorSupport,
451
+ isRaw,
452
+ }
453
+ }
454
+
455
+ // useFocus is exported from ./keyboard
456
+ // useKeyboard and keyboard bindings are exported from ./keyboard
457
+ // For hotkey functionality, use createKeyboardManager with VIM_BINDINGS or COMMON_BINDINGS
458
+
459
+ // ============================================================================
460
+ // Theme Context
461
+ // ============================================================================
462
+
463
+ interface ThemeContextValue {
464
+ theme: 'light' | 'dark'
465
+ setTheme: (theme: 'light' | 'dark') => void
466
+ colors: Record<string, string>
467
+ }
468
+
469
+ const ThemeContext = createContext<ThemeContextValue | null>(null)
470
+
471
+ /**
472
+ * Access current theme and colors
473
+ */
474
+ export function useTheme(): ThemeContextValue {
475
+ const context = useContext(ThemeContext)
476
+ if (!context) {
477
+ // Return default theme if not in provider
478
+ return {
479
+ theme: 'dark',
480
+ setTheme: () => {},
481
+ colors: {},
482
+ }
483
+ }
484
+ return context
485
+ }
486
+
487
+ // ============================================================================
488
+ // Providers
489
+ // ============================================================================
490
+
491
+ export interface TerminalProviderProps {
492
+ children?: ReactNode
493
+ }
494
+
495
+ /**
496
+ * Terminal context provider - wraps app with terminal capabilities
497
+ */
498
+ export function TerminalProvider(props: TerminalProviderProps): React.ReactElement {
499
+ return React.createElement('terminal-provider', props)
500
+ }
501
+
502
+ export interface ThemeProviderProps {
503
+ children?: ReactNode
504
+ theme?: 'light' | 'dark'
505
+ }
506
+
507
+ /**
508
+ * Theme provider - provides color scheme to components
509
+ */
510
+ export function ThemeProvider(props: ThemeProviderProps): React.ReactElement {
511
+ const value: ThemeContextValue = {
512
+ theme: props.theme || 'dark',
513
+ setTheme: () => {},
514
+ colors: {},
515
+ }
516
+ return React.createElement(
517
+ ThemeContext.Provider,
518
+ { value },
519
+ props.children
520
+ )
521
+ }
522
+
523
+ // FocusProvider is exported from ./keyboard
524
+
525
+ // ============================================================================
526
+ // App Shell
527
+ // ============================================================================
528
+
529
+ export interface TerminalAppProps {
530
+ children: React.ReactNode
531
+ /** Show status bar at bottom */
532
+ statusBar?: React.ReactNode
533
+ /** Handle quit (Ctrl+C, q) */
534
+ onQuit?: () => void
535
+ }
536
+
537
+ /**
538
+ * Terminal app shell with standard layout
539
+ */
540
+ export function TerminalApp(props: TerminalAppProps): React.ReactElement {
541
+ return React.createElement('terminal-app', props)
542
+ }