A curses-like blazingly fast react renderer
- It is fast, intuitive and easy to use
- It draws only changed characters
- It uses a small amount of SSH traffic
See it in action:
Still here? Let's go deeper:
- It has fancy components that are ready to use or can be tree-shaked from your final bundle
- It supports keyboard and mouse
- It works in fullscreen and inline modes
- It has cool hooks like animation with trail
- It is solely dependent on react
- It can generate an all-in-one bundle around 100 kb
You can easily build full-scale terminal UI applications like:
- mngr - Database manager supports mongodb, mysql/mariadb, postgresql, sqlite and json-server
- nfi - Simple nerd fonts icons cheat sheet that allows you to quickly find and copy glyph to clipboard
Just run npm init react-curse
answer a few questions and you are ready to go
import React from 'react'
import ReactCurse, { Text } from 'react-curse'
const App = ({ text }) => {
return <Text color="Red">{text}</Text>
}
ReactCurse.render(<App text="hello world" />)
import React, { useState } from 'react'
import ReactCurse, { Text, useInput, useExit } from 'react-curse'
const App = () => {
const [counter, setCounter] = useState(0)
useInput(
input => {
if (input === 'k') setCounter(counter + 1)
if (input === 'j') setCounter(counter - 1)
if (input === 'q') useExit()
},
[counter]
)
return (
<Text>
counter: <Text bold>{counter}</Text>
</Text>
)
}
ReactCurse.render(<App />)
import React from 'react'
import ReactCurse, { useAnimation } from 'react-curse'
const App = () => {
const { interpolate, interpolateColor } = useAnimation(1000)
return <Text width={interpolate(0, 80)} background={interpolateColor('#282828', '#d79921')} />
}
ReactCurse.render(<App />)
Base component
The only component required to do anything
Every other component uses this one to draw
Position from top left corner relative to parent
Content will be cropped by parent
See absolute
to avoid this behavior
Example: 32, '100%', '100%-8'
Size of block, will be cropped by parent
See absolute
to avoid this behavior
Makes position and size ignoring parent container
Background and foreground color
Example: 31, 'Red', '#f04020', '#f42'
Clears block before drawing content
height
and width
Moves cursor to a new line after its content relative to parent
Text modifiers
<Text color="Red" block>hello world</Text>
<Text color="Green" bold block>hello world</Text>
<Text color="BrightBlue" underline block>hello world</Text>
<Text y={0} x="50%">
<Text color={128} italic block>hello world</Text>
<Text x="100%-11" color="#1ff" strikethrough block>hello world</Text>
<Text x="50%-5" color="#e94691" inverse>hello world</Text>
</Text>
Text input component with cursor movement and text scroll support
If its height is more than 1, then it switches to multiline, like textarea
Most terminal shortcuts are supported
Makes it active
type?: 'text'
| 'password'
| 'hidden'
= ‘text'
<Input background="#404040" height={1} width={8} />
Displays big text
<Banner>{new Date().toTimeString().substring(0, 8)}</Banner>
Displays vertical or horizontal bar with 1/8 character resolution
<>
{[...Array(24)].map((_, index) => (
<Bar key={index} type="vertical" x={index * 2} height={(index + 1) / 8} />
))}
</>
Compare to <Text>
Aligns content
<Block>left</Block>
<Block align="center">center</Block>
<Block align="right">right</Block>
Create a canvas for drawing with one these modes
Pixels per character
Size in pixels
Draws a point at the coordinates
Draws a line using coordinates
<Canvas width={80} height={6}>
<Point x={1} y={1} color="Yellow" />
<Line x={0} y={5} dx={79} dy={0} />
</Canvas>
Braille's font demo ({ h: 4, w: 2 }
)
Draws frame around its content
<Frame type="single" color="Red">single border type</Frame>
<Frame type="double" color="Green" y={0}>double border type</Frame>
<Frame type="rounded" color="Blue" y={0}>rounded border type</Frame>
Creates a list with navigation support
Vim shortcuts are supported
const items = [...Array(8)].map((_, index) => ({ id: index + 1, title: `Task ${index + 1}` }))
return (
<List
data={items}
renderItem={({ item, selected }) => <Text color={selected ? 'Green' : undefined}>{item.title}</Text>}
/>
)
Creates a table with navigation support
Vim shortcuts are supported
const head = ['id', 'title']
const items = [...Array(8)].map((_, index) => [index + 1, `Task ${index + 1}`])
return (
<ListTable
head={head}
renderHead={({ item }) =>
item.map((i, key) => (
<Text key={key} width={8}>
{i}
</Text>
))
}
data={items}
renderItem={({ item, x, y, index }) =>
item.map((text, key) => (
<Text key={key} color={y === index && x === key ? 'Green' : undefined} width={8}>
{text}
</Text>
))
}
/>
)
Draws a scrollbar with 1/8 character resolution
<Scrollbar type="horizontal" offset={10} limit={80} length={160} />
Draws a vertical or horizontal line
<Separator type="vertical" height={3} />
<Separator type="horizontal" y={1} x={1} width={79} />
Draws an animated spinner
<Spinner block />
<Spinner color="BrightGreen">-\|/</Spinner>
Creates a scrollable viewport
Vim shortcuts are supported
<View>{JSON.stringify(json, null, 2)}</View>
Creates a timer for a specified duration
That gives you time and interpolation functions each frame of animation
const { ms } = useAnimation(1000, 4)
return ms // 0, 250, 500, 750, 1000
const { interpolate } = useAnimation(1000, 4)
return interpolate(0, 80) // 0, 20, 40, 60, 80
const { interpolateColor } = useAnimation(1000, 4)
return interpolateColor('#000', '#0f8') // #000, #042, #084, #0c6, #0f8
Mutate array of items to show one by one with latency
const items = [...Array(8)].map((_, index) => ({ id: index + 1, title: `Task ${index + 1}` }))
return (
<Trail delay={100}>
{items.map(({ id, title }) => (
<Text key={id} block>
{title}
</Text>
))}
</Trail>
)
Same as <Trail>
but hook
You can pass it to data
property of <List>
component for example
<List data={useTrail(items)} />
Makes a terminal bell
useBell() // ding
Gives you content size
useChildrenSize('1\n22\n333') // { height: 3, width: 3 }
Allows you to work with the system clipboard
const { getClipboard, setClipboard } = useClipboard()
const string = getClipboard()
setClipboard(string.toUpperCase()) // copied
Allows you to exit from an application that waits for user input or has timers
useInput(input => {
if (input === 'q') useExit()
})
Allows you to handle keyboard input
set[(counter, setCounter)] = useState(0)
useInput(
input => {
if (input === 'k') setCounter(counter + 1)
if (input === 'j') setCounter(counter - 1)
},
[counter]
)
Allows you to handle mouse input
set[(counter, setCounter)] = useState(0)
useMouse(
event => {
if (event.type === 'wheelup') setCounter(counter + 1)
if (event.type === 'wheeldown') setCounter(counter - 1)
},
[counter]
)
Gives you terminal size
Updates when size is changing
useSize() // { height: 24, width: 80 }
Gives your text a word wrap
useWordWrap('hello world', 5) // hello\nworld
Renders your fullscreen application to stdout
Renders your inline application to stdout