Created
December 24, 2025 16:09
-
-
Save JonathanTurnock/181aafec9bd0a43e9856c6e1b6b6690f to your computer and use it in GitHub Desktop.
Bun INK Fullscreen App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Box, render, Text, useStdout } from "ink"; | |
| import { useCallback, useEffect, useState } from "react"; | |
| import { debounceTime, filter, firstValueFrom, fromEvent, map } from "rxjs"; | |
| export function useScreenSize() { | |
| const { stdout } = useStdout(); | |
| const getSize = useCallback(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]); | |
| const [size, setSize] = useState(getSize); | |
| useEffect(() => { | |
| function onResize() { | |
| setSize(getSize()); | |
| } | |
| stdout.on("resize", onResize); | |
| return () => { | |
| stdout.off("resize", onResize); | |
| }; | |
| }, [stdout, getSize]); | |
| return size; | |
| } | |
| export default function App(props: { width: number; height: number }) { | |
| return ( | |
| <Box height={props.height} width={props.width} borderStyle={"single"}> | |
| <Box flexDirection={"column"}> | |
| <Text>Hello</Text> | |
| <Box> | |
| <Text bold>My Fullscreen TUI</Text> | |
| </Box> | |
| <Box flexGrow={1}> | |
| <Text>Content area (fills remaining space)</Text> | |
| </Box> | |
| </Box> | |
| </Box> | |
| ); | |
| } | |
| function write(data: string | Uint8Array): Promise<void> { | |
| return new Promise((resolve, reject) => { | |
| process.stdout.write(data, (err) => { | |
| if (err) { | |
| reject(err); | |
| } else { | |
| resolve(); | |
| } | |
| }); | |
| }); | |
| } | |
| if (process.stdin.isTTY && process.stdin.setRawMode) { | |
| process.stdin.setRawMode(true); | |
| } | |
| process.stdin.resume(); | |
| process.stdin.setEncoding("utf8"); | |
| export enum Keys { | |
| CTRL_C = "\u0003", | |
| UP_ARROW = "\u001b[A", | |
| DOWN_ARROW = "\u001b[B", | |
| E = "e", | |
| D = "d", | |
| R = "r", | |
| C = "c", | |
| ESCAPE = "\u001b", | |
| } | |
| export enum ANSIEscapeSequences { | |
| ALTERNATE_SCREEN_BUFFER_ENABLE = "\x1b[?1049h", | |
| ALTERNATE_SCREEN_BUFFER_DISABLE = "\x1b[?1049l", | |
| CLEAR_SCREEN = "\x1b[2J\x1b[H", | |
| } | |
| const keyFilter = (filterKey: Keys) => filter((key) => key === filterKey); | |
| const key$ = fromEvent(process.stdin, "data").pipe(map((keyBuffer) => (keyBuffer as Buffer).toString() as Keys)); | |
| const exit$ = key$.pipe(keyFilter(Keys.CTRL_C)); | |
| const windowSize$ = fromEvent(process.stdout, "resize").pipe(debounceTime(50)); | |
| export async function startTui(onDestroy?: () => void) { | |
| console.log("Starting TUI..."); | |
| await new Promise((resolve) => setTimeout(resolve, 500)); | |
| await write(ANSIEscapeSequences.ALTERNATE_SCREEN_BUFFER_ENABLE); | |
| const inst = render(null); | |
| async function reRender() { | |
| const [width, height] = process.stdout.getWindowSize(); | |
| inst.clear(); | |
| await write(ANSIEscapeSequences.CLEAR_SCREEN); | |
| inst.rerender(<App height={height} width={width} />); | |
| } | |
| const sxn = windowSize$.subscribe(reRender); | |
| await reRender(); | |
| await firstValueFrom(exit$); | |
| inst.unmount(); | |
| sxn.unsubscribe(); | |
| onDestroy?.(); | |
| await write(ANSIEscapeSequences.ALTERNATE_SCREEN_BUFFER_DISABLE); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Giving issues with fullscreen :(
Bun 1.3.5