npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-curse

v1.0.15

Published

A curses-like blazingly fast react renderer

Downloads

244

Readme

react-curse

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:

Apps that use it

  • 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

Installation

Just run npm init react-curse answer a few questions and you are ready to go

Examples

Hello world

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" />)

How to handle input

import React, { useState } from 'react'
import ReactCurse, { Text, useInput, exit } 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') exit()
    },
    [counter]
  )

  return (
    <Text>
      counter: <Text bold>{counter}</Text>
    </Text>
  )
}

ReactCurse.render(<App />)

How to animate

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 />)

Contents

Components

<Text>

Base component
The only component required to do anything
Every other component uses this one to draw

y?, x?: number | string

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'

height?, width?: number | string

Size of block, will be cropped by parent
See absolute to avoid this behavior

absolute?: boolean

Makes position and size ignoring parent container

background?, color?: number | string

Background and foreground color
Example: 31, 'Red', '#f04020', '#f42'

clear?: boolean

Clears block before drawing content
height and width

block?: boolean

Moves cursor to a new line after its content relative to parent

bold?, dim?, italic?, underline?, blinking?, inverse?, strikethrough?: boolean

Text modifiers

Examples

<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>

<Input>

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

focus?: boolean = true

Makes it active

type?: 'text' | 'password' | 'hidden' = ‘text'
initialValue?: string
cursorBackground?: number | string
onCancel?: () => void
onChange?: (string) => void
onSubmit?: (string) => void

Examples

<Input background="#404040" height={1} width={8} />

<Banner>

Displays big text

y?, x?: number | string
background?, color?: number | string
children: string

Examples

<Banner>{new Date().toTimeString().substring(0, 8)}</Banner>

<Bar>

Displays vertical or horizontal bar with 1/8 character resolution

type: 'vertical' | 'horizontal'
y & height, x & width: number

Examples

<>
  {[...Array(24)].map((_, index) => (
    <Bar key={index} type="vertical" x={index * 2} height={(index + 1) / 8} />
  ))}
</>

Compare to <Text>

<Block>

Aligns content

width?: number
align?: 'left' | 'center' | 'right' = 'left'

Examples

<Block>left</Block>
<Block align="center">center</Block>
<Block align="right">right</Block>

<Canvas>

Create a canvas for drawing with one these modes

mode: { h: 1, w: 1 } | { h: 2, w: 1 } | { h: 2, w: 2 } | { h: 4, w: 2 }

Pixels per character

height, width: number

Size in pixels

children: (Point | Line)[]

<Point>

Draws a point at the coordinates

y, x: number
color?: number | string

<Line>

Draws a line using coordinates

y, x, dy, dx: number
color?: number | string

Examples

<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 })

<Frame>

Draws frame around its content

children: string
type?: 'single' | 'double' | 'rounded' = 'single'
height?, width?: number

Examples

<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>

<List>

Creates a list with navigation support
Vim shortcuts are supported

focus?: boolean
initialPos?: { y: number }
data?: any[]
renderItem?: (object) => JSX.Element
height?, width?: number
scrollbar?: boolean
scrollbarBackground?: boolean
scrollbarColor?: boolean
vi?: boolean = true
pass?: any
onChange?: (object) => void
onSubmit?: (object) => void

Examples

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>}
  />
)

<ListTable>: <List>

Creates a table with navigation support
Vim shortcuts are supported

mode?: 'cell' | 'row' = 'cell'
head?: any[]
renderHead?: (object) => JSX.Element
data?: any[][]

Examples

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>
      ))
    }
  />
)

<Scrollbar>

Draws a scrollbar with 1/8 character resolution

type?: 'vertical' | 'horizontal' = 'vertical'
offset: number
limit: number
length: number
background?, color?: number | string

Examples

<Scrollbar type="horizontal" offset={10} limit={80} length={160} />

<Separator>

Draws a vertical or horizontal line

type: 'vertical' | 'horizontal'
height, width: number

Examples

<Separator type="vertical" height={3} />
<Separator type="horizontal" y={1} x={1} width={79} />

<Spinner>

Draws an animated spinner

children?: string

Examples

<Spinner block />
<Spinner color="BrightGreen">-\|/</Spinner>

<View>

Creates a scrollable viewport
Vim shortcuts are supported

focus?: boolean
height?: number
scrollbar?: boolean
vi?: boolean = true
children: any

Examples

<View>{JSON.stringify(json, null, 2)}</View>

hooks

useAnimation

(time: number, fps?: 'number' = 60) => object

Creates a timer for a specified duration
That gives you time and interpolation functions each frame of animation

return

ms: number
interpolate: (from: number, to: number, delay?: number)
interpolateColor: (from: string, to: string: delay?: number)

Examples

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

<Trail>

Mutate array of items to show one by one with latency

delay: number
children: JSX.Element[]

Examples

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>
)

useTrail

(delay: number, items: JSX.Element[], key?: string = 'key') => JSX.Element[]

Same as <Trail> but hook
You can pass it to data property of <List> component for example

Examples

<List data={useTrail(items)} />

useChildrenSize

(value: string) => object

Gives you content size

return

height, width: number

Examples

useChildrenSize('1\n22\n333') // { height: 3, width: 3 }

useClipboard

() => array

Allows you to work with the system clipboard

return

getClipboard: () => string
setClipboard: (value: string) => void

Examples

const { getClipboard, setClipboard } = useClipboard()
const string = getClipboard()
setClipboard(string.toUpperCase()) // copied

useInput

(callback: (string) => void, dependencies: any[]) => void

Allows you to handle keyboard input

Examples

set[(counter, setCounter)] = useState(0)

useInput(
  input => {
    if (input === 'k') setCounter(counter + 1)
    if (input === 'j') setCounter(counter - 1)
  },
  [counter]
)

useMouse

(callback: (object) => void, dependencies: any[])

Allows you to handle mouse input

Examples

set[(counter, setCounter)] = useState(0)

useMouse(
  event => {
    if (event.type === 'wheelup') setCounter(counter + 1)
    if (event.type === 'wheeldown') setCounter(counter - 1)
  },
  [counter]
)

useSize

() => object

Gives you terminal size
Updates when size is changing

return

height, width: number

Examples

useSize() // { height: 24, width: 80 }

useWordWrap

(text: string, width?: number) => object

Gives your text a word wrap

return

height, width: number

Examples

useWordWrap('hello world', 5) // hello\nworld

API

render (children: JSX.Element) => void

Renders your fullscreen application to stdout

inline (children: JSX.Element) => void

Renders your inline application to stdout

bell

() => void

Makes a terminal bell

bell() // ding

exit

(code: number = 0) => void

Allows you to exit from an application that waits for user input or has timers

Examples

useInput(input => {
  if (input === 'q') exit()
})