1 | <h1 align="center">
|
2 | <br>
|
3 | <br>
|
4 | <img width="300" alt="Ink" src="media/logo.png">
|
5 | <br>
|
6 | <br>
|
7 | <br>
|
8 | </h1>
|
9 |
|
10 | > React for CLIs. Build and test your CLI output using components.
|
11 |
|
12 | [![Build Status](https://travis-ci.org/vadimdemedes/ink.svg?branch=next)](https://travis-ci.org/vadimdemedes/ink)
|
13 |
|
14 |
|
15 | ## Install
|
16 |
|
17 | ```
|
18 | $ npm install ink@next
|
19 | ```
|
20 |
|
21 |
|
22 | ## Usage
|
23 |
|
24 | ```jsx
|
25 | import React, {Component} from 'react';
|
26 | import {render, Color} from 'ink';
|
27 |
|
28 | class Counter extends Component {
|
29 | constructor() {
|
30 | super();
|
31 |
|
32 | this.state = {
|
33 | i: 0
|
34 | };
|
35 | }
|
36 |
|
37 | render() {
|
38 | return (
|
39 | <Color green>
|
40 | {this.state.i} tests passed
|
41 | </Color>
|
42 | );
|
43 | }
|
44 |
|
45 | componentDidMount() {
|
46 | this.timer = setInterval(() => {
|
47 | this.setState({
|
48 | i: this.state.i + 1
|
49 | });
|
50 | }, 100);
|
51 | }
|
52 |
|
53 | componentWillUnmount() {
|
54 | clearInterval(this.timer);
|
55 | }
|
56 | }
|
57 |
|
58 | render(<Counter/>);
|
59 | ```
|
60 |
|
61 | <img src="media/demo.svg" width="600">
|
62 |
|
63 | You can also check it out live on [repl.it sandbox](https://ink-counter-demo.vadimdemedes.repl.run/).
|
64 | Feel free to play around with the code and fork this repl at [https://repl.it/@vadimdemedes/ink-counter-demo](https://repl.it/@vadimdemedes/ink-counter-demo).
|
65 |
|
66 |
|
67 | ## Built with Ink
|
68 |
|
69 | - [emoj](https://github.com/sindresorhus/emoj) - Find relevant emoji on the command-line.
|
70 | - [emma](https://github.com/maticzav/emma-cli) - Terminal assistant to find and install npm packages.
|
71 | - [swiff](https://github.com/simple-integrated-marketing/swiff) - Multi-environment command line tools for time-saving web developers.
|
72 |
|
73 |
|
74 | ## Table of Contents
|
75 |
|
76 | - [Getting Started](#getting-started)
|
77 | - [Examples](#examples)
|
78 | - [API](#api)
|
79 | - [Building Layouts](#building-layouts)
|
80 | - [Built-in Components](#built-in-components)
|
81 | - [Useful Components](#useful-components)
|
82 |
|
83 |
|
84 | ## Getting Started
|
85 |
|
86 | Ink's goal is to provide the same component-based UI building experience that React provides, but for command-line apps. It uses [yoga-layout](https://github.com/facebook/yoga) to allow Flexbox layouts in the terminal. If you are already familiar with React, you already know Ink.
|
87 |
|
88 | The key difference you have to remember is that the rendering result isn't a DOM, but a string, which Ink writes to the output.
|
89 |
|
90 | To ensure all examples work and you can begin your adventure with Ink, make sure to set up Babel with a React preset. After [installing Babel](https://babeljs.io/docs/en/usage), configure it in `package.json`:
|
91 |
|
92 | ```json
|
93 | {
|
94 | "babel": {
|
95 | "presets": [
|
96 | "@babel/preset-react"
|
97 | ]
|
98 | }
|
99 | }
|
100 | ```
|
101 |
|
102 | Don't forget to import `React` into every file that contains JSX:
|
103 |
|
104 | ```jsx
|
105 | import React from 'react';
|
106 | import {render, Box} from 'ink';
|
107 |
|
108 | const Demo = () => (
|
109 | <Box>
|
110 | Hello World
|
111 | </Box>
|
112 | );
|
113 |
|
114 | render(<Demo/>);
|
115 | ```
|
116 |
|
117 |
|
118 | ## Examples
|
119 |
|
120 | - [Jest](examples/jest/jest.js) - Implementation of basic Jest UI [(live demo)](https://ink-jest-demo.vadimdemedes.repl.run/).
|
121 | - [Counter](examples/counter/counter.js) - Simple counter that increments every 100ms [(live demo)](https://ink-counter-demo.vadimdemedes.repl.run/).
|
122 |
|
123 |
|
124 | ## API
|
125 |
|
126 | Since Ink is a React renderer, it means that all features of React are supported.
|
127 | Head over to [React](https://reactjs.org/) website for documentation on how to use it.
|
128 | In this readme only Ink's methods will be documented.
|
129 |
|
130 | #### render(tree, options)
|
131 |
|
132 | Returns: `App`
|
133 |
|
134 | Mount a component and render the output.
|
135 |
|
136 | ##### tree
|
137 |
|
138 | Type: `ReactElement`
|
139 |
|
140 | ##### options
|
141 |
|
142 | ###### stdout
|
143 |
|
144 | Type: `Stream`<br>
|
145 | Default: `process.stdout`
|
146 |
|
147 | Output stream where app will be rendered.
|
148 |
|
149 | ###### stdin
|
150 |
|
151 | Type: `Stream`<br>
|
152 | Default: `process.stdin`
|
153 |
|
154 | Input stream where app will listen for input.
|
155 |
|
156 | ###### exitOnCtrlC
|
157 |
|
158 | Type: `Boolean`<br>
|
159 | Default: `true`
|
160 |
|
161 | Configure whether Ink should listen to Ctrl+C keyboard input and exit the app.
|
162 | This is needed in case `process.stdin` is in [raw mode](https://nodejs.org/api/tty.html#tty_readstream_setrawmode_mode), because then Ctrl+C is ignored by default and process is expected to handle it manually.
|
163 |
|
164 | ###### debug
|
165 |
|
166 | Type: `Boolean`<br>
|
167 | Default: `false`
|
168 |
|
169 | If `true`, each update will be rendered as a separate output, without replacing the previous one.
|
170 |
|
171 | ```jsx
|
172 | import React, {Component} from 'react';
|
173 | import {render, Box} from 'ink';
|
174 |
|
175 | class Counter extends Component {
|
176 | constructor() {
|
177 | super();
|
178 |
|
179 | this.state = {
|
180 | i: 0
|
181 | };
|
182 | }
|
183 |
|
184 | render() {
|
185 | return (
|
186 | <Box>
|
187 | Iteration #{this.state.i}
|
188 | </Box>
|
189 | );
|
190 | }
|
191 |
|
192 | componentDidMount() {
|
193 | this.timer = setInterval(() => {
|
194 | this.setState(prevState => ({
|
195 | i: prevState.i + 1
|
196 | }));
|
197 | }, 100);
|
198 | }
|
199 |
|
200 | componentWillUnmount() {
|
201 | clearInterval(this.timer);
|
202 | }
|
203 | }
|
204 |
|
205 | const app = render(<Counter/>);
|
206 |
|
207 | setTimeout(() => {
|
208 | // Enough counting
|
209 | app.unmount();
|
210 | }, 1000);
|
211 | ```
|
212 |
|
213 | There's also a shortcut to avoid passing `options` object:
|
214 |
|
215 | ```jsx
|
216 | render(<Counter>, process.stdout);
|
217 | ```
|
218 |
|
219 | #### App
|
220 |
|
221 | This is the object that `render()` returns.
|
222 |
|
223 | ##### unmount
|
224 |
|
225 | Manually unmount the whole Ink app.
|
226 |
|
227 | ```jsx
|
228 | const app = render(<MyApp/>);
|
229 | app.unmount();
|
230 | ```
|
231 |
|
232 | ##### waitUntilExit
|
233 |
|
234 | Returns a promise, which resolves when app is unmounted.
|
235 |
|
236 | ```jsx
|
237 | const app = render(<MyApp/>);
|
238 |
|
239 | setTimeout(() => {
|
240 | app.unmount();
|
241 | }, 1000);
|
242 |
|
243 | await app.waitUntilExit(); // resolves after `app.unmount()` is called
|
244 | ```
|
245 |
|
246 | ## Building Layouts
|
247 |
|
248 | Ink uses [Yoga](https://github.com/facebook/yoga) - a Flexbox layout engine to build great user interfaces for your CLIs.
|
249 | It's important to remember that each element is a Flexbox container.
|
250 | Think of it as if each `<div>` in the browser had `display: flex`.
|
251 | See `<Box>` built-in component below for documentation on how to use Flexbox layouts in Ink.
|
252 |
|
253 |
|
254 | ### Built-in Components
|
255 |
|
256 | #### <Box>
|
257 |
|
258 | `<Box>` it's an essential Ink component to build your layout. It's like a `<div style="display: flex">` in a browser.
|
259 |
|
260 | Import:
|
261 |
|
262 | ```js
|
263 | import {Box} from 'ink';
|
264 | ```
|
265 |
|
266 | ##### Padding
|
267 |
|
268 | ###### paddingTop
|
269 |
|
270 | Type: `number`<br>
|
271 | Default: `0`
|
272 |
|
273 | ###### paddingBottom
|
274 |
|
275 | Type: `number`<br>
|
276 | Default: `0`
|
277 |
|
278 | ###### paddingLeft
|
279 |
|
280 | Type: `number`<br>
|
281 | Default: `0`
|
282 |
|
283 | ###### paddingRight
|
284 |
|
285 | Type: `number`<br>
|
286 | Default: `0`
|
287 |
|
288 | ###### paddingX
|
289 |
|
290 | Type: `number`<br>
|
291 | Default: `0`
|
292 |
|
293 | ###### paddingY
|
294 |
|
295 | Type: `number`<br>
|
296 | Default: `0`
|
297 |
|
298 | ###### padding
|
299 |
|
300 | Type: `number`<br>
|
301 | Default: `0`
|
302 |
|
303 | ```jsx
|
304 | <Box paddingTop={2}>Top</Box>
|
305 | <Box paddingBottom={2}>Bottom</Box>
|
306 | <Box paddingLeft={2}>Left</Box>
|
307 | <Box paddingRight={2}>Right</Box>
|
308 | <Box paddingX={2}>Left and right</Box>
|
309 | <Box paddingY={2}>Top and bottom</Box>
|
310 | <Box padding={2}>Top, bottom, left and right</Box>
|
311 | ```
|
312 |
|
313 | ##### Margin
|
314 |
|
315 | ###### marginTop
|
316 |
|
317 | Type: `number`<br>
|
318 | Default: `0`
|
319 |
|
320 | ###### marginBottom
|
321 |
|
322 | Type: `number`<br>
|
323 | Default: `0`
|
324 |
|
325 | ###### marginLeft
|
326 |
|
327 | Type: `number`<br>
|
328 | Default: `0`
|
329 |
|
330 | ###### marginRight
|
331 |
|
332 | Type: `number`<br>
|
333 | Default: `0`
|
334 |
|
335 | ###### marginX
|
336 |
|
337 | Type: `number`<br>
|
338 | Default: `0`
|
339 |
|
340 | ###### marginY
|
341 |
|
342 | Type: `number`<br>
|
343 | Default: `0`
|
344 |
|
345 | ###### margin
|
346 |
|
347 | Type: `number`<br>
|
348 | Default: `0`
|
349 |
|
350 | ```jsx
|
351 | <Box marginTop={2}>Top</Box>
|
352 | <Box marginBottom={2}>Bottom</Box>
|
353 | <Box marginLeft={2}>Left</Box>
|
354 | <Box marginRight={2}>Right</Box>
|
355 | <Box marginX={2}>Left and right</Box>
|
356 | <Box marginY={2}>Top and bottom</Box>
|
357 | <Box margin={2}>Top, bottom, left and right</Box>
|
358 | ```
|
359 |
|
360 | ##### Flex
|
361 |
|
362 | ###### flexGrow
|
363 |
|
364 | Type: `number`<br>
|
365 | Default: `0`
|
366 |
|
367 | See [flex-grow](https://css-tricks.com/almanac/properties/f/flex-grow/).
|
368 |
|
369 | ```jsx
|
370 | <Box>
|
371 | Label:
|
372 | <Box flexGrow={1}>
|
373 | Fills all remaining space
|
374 | </Box>
|
375 | </Box>
|
376 | ```
|
377 |
|
378 | ###### flexShrink
|
379 |
|
380 | Type: `number`<br>
|
381 | Default: `1`
|
382 |
|
383 | See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
|
384 |
|
385 | ```jsx
|
386 | <Box width={20}>
|
387 | <Box flexShrink={2} width={10}>
|
388 | Will be 1/4
|
389 | </Box>
|
390 | <Box width={10}>
|
391 | Will be 3/4
|
392 | </Box>
|
393 | </Box>
|
394 | ```
|
395 |
|
396 | ###### flexDirection
|
397 |
|
398 | Type: `string`<br>
|
399 | Allowed values: `row`, `row-reverse`, `column` and `column-reverse`
|
400 |
|
401 | See [flex-direction](https://css-tricks.com/almanac/properties/f/flex-direction/).
|
402 |
|
403 | ```jsx
|
404 | <Box>
|
405 | <Box marginRight={1}>X</Box>
|
406 | <Box>Y</Box>
|
407 | </Box>
|
408 | // X Y
|
409 |
|
410 | <Box flexDirection="row-reverse">
|
411 | <Box>X</Box>
|
412 | <Box marginRight={1}>Y</Box>
|
413 | </Box>
|
414 | // Y X
|
415 |
|
416 | <Box flexDirection="column">
|
417 | <Box>X</Box>
|
418 | <Box>Y</Box>
|
419 | </Box>
|
420 | // X
|
421 | // Y
|
422 |
|
423 | <Box flexDirection="column-reverse">
|
424 | <Box>X</Box>
|
425 | <Box>Y</Box>
|
426 | </Box>
|
427 | // Y
|
428 | // X
|
429 | ```
|
430 |
|
431 | ###### alignItems
|
432 |
|
433 | Type: `string`<br>
|
434 | Allowed values: `flex-start`, `center` and `flex-end`
|
435 |
|
436 | See [align-items](https://css-tricks.com/almanac/properties/f/align-items/).
|
437 |
|
438 | ```jsx
|
439 | <Box alignItems="flex-start">
|
440 | <Box marginRight={1}>X</Box>
|
441 | <Box>{`A\nB\nC`}</Box>
|
442 | </Box>
|
443 | // X A
|
444 | // B
|
445 | // C
|
446 |
|
447 | <Box alignItems="center">
|
448 | <Box marginRight={1}>X</Box>
|
449 | <Box>{`A\nB\nC`}</Box>
|
450 | </Box>
|
451 | // A
|
452 | // X B
|
453 | // C
|
454 |
|
455 | <Box alignItems="flex-end">
|
456 | <Box marginRight={1}>X</Box>
|
457 | <Box>{`A\nB\nC`}</Box>
|
458 | </Box>
|
459 | // A
|
460 | // B
|
461 | // X C
|
462 | ```
|
463 |
|
464 | ###### justifyContent
|
465 |
|
466 | Type: `string`<br>
|
467 | Allowed values: `flex-start`, `center`, `flex-end`, `space-between` and `space-around`.
|
468 |
|
469 | See [justify-content](https://css-tricks.com/almanac/properties/f/justify-content/).
|
470 |
|
471 | ```jsx
|
472 | <Box justifyContent="flex-start">
|
473 | <Box>X</Box>
|
474 | </Box>
|
475 | // [X ]
|
476 |
|
477 | <Box justifyContent="center">
|
478 | <Box>X</Box>
|
479 | </Box>
|
480 | // [ X ]
|
481 |
|
482 | <Box justifyContent="flex-end">
|
483 | <Box>X</Box>
|
484 | </Box>
|
485 | // [ X]
|
486 |
|
487 | <Box justifyContent="space-between">
|
488 | <Box>X</Box>
|
489 | <Box>Y</Box>
|
490 | </Box>
|
491 | // [X Y]
|
492 |
|
493 | <Box justifyContent="space-around">
|
494 | <Box>X</Box>
|
495 | <Box>Y</Box>
|
496 | </Box>
|
497 | // [ X Y ]
|
498 | ```
|
499 |
|
500 |
|
501 | #### <Color>
|
502 |
|
503 | The `<Color>` compoment is a simple wrapper around [the `chalk` API](https://github.com/chalk/chalk#api).
|
504 | It supports all of the chalk's methods as `props`.
|
505 |
|
506 | Import:
|
507 |
|
508 | ```js
|
509 | import {Color} from 'ink';
|
510 | ```
|
511 |
|
512 | Usage:
|
513 |
|
514 | ```jsx
|
515 | <Color rgb={[255, 255, 255]} bgKeyword="magenta">
|
516 | Hello!
|
517 | </Color>
|
518 |
|
519 | <Color hex="#000000" bgHex="#FFFFFF">
|
520 | Hey there
|
521 | </Color>
|
522 |
|
523 | <Color blue>
|
524 | I'm blue
|
525 | </Color>
|
526 | ```
|
527 |
|
528 | #### <Text>
|
529 |
|
530 | This component can change the style of the text, make it bold, underline, italic or strikethrough.
|
531 |
|
532 | Import:
|
533 |
|
534 | ```js
|
535 | import {Text} from 'ink';
|
536 | ```
|
537 |
|
538 | ##### bold
|
539 |
|
540 | Type: `boolean`<br>
|
541 | Default: `false`
|
542 |
|
543 | ##### italic
|
544 |
|
545 | Type: `boolean`<br>
|
546 | Default: `false`
|
547 |
|
548 | ##### underline
|
549 |
|
550 | Type: `boolean`<br>
|
551 | Default: `false`
|
552 |
|
553 | ##### strikethrough
|
554 |
|
555 | Type: `boolean`<br>
|
556 | Default: `false`
|
557 |
|
558 | Usage:
|
559 |
|
560 | ```jsx
|
561 | <Text bold>I am bold</Text>
|
562 | <Text italic>I am italic</Text>
|
563 | <Text underline>I am underline</Text>
|
564 | <Text strikethrough>I am strikethrough</Text>
|
565 | ```
|
566 |
|
567 | #### <Static>
|
568 |
|
569 | `<Static>` component allows permanently rendering output to stdout and preserving it across renders.
|
570 | Components passed to `<Static>` as children will be written to stdout only once and will never be rerendered.
|
571 | `<Static>` output comes first, before any other output from your components, no matter where it is in the tree.
|
572 | In order for this mechanism to work properly, at most one `<Static>` component must be present in your node tree and components that were rendered must never update their output. Ink will detect new children appended to `<Static>` and render them to stdout.
|
573 |
|
574 | **Note:** `<Static>` accepts only an array of children and each of them must have a unique key.
|
575 |
|
576 | Example use case for this component is Jest's output:
|
577 |
|
578 | ![](https://jestjs.io/img/content/feature-fast.png)
|
579 |
|
580 | Jest continuosuly writes the list of completed tests to the output, while updating test results at the bottom of the output in real-time. Here's how this user interface could be implemented with Ink:
|
581 |
|
582 | ```jsx
|
583 | <Fragment>
|
584 | <Static>
|
585 | {tests.map(test => (
|
586 | <Test key={test.id} title={test.title}/>
|
587 | ))}
|
588 | </Static>
|
589 |
|
590 | <Box marginTop={1}>
|
591 | <TestResults passed={results.passed} failed={results.failed}/>
|
592 | </Box>
|
593 | </Fragment>
|
594 | ```
|
595 |
|
596 | See [examples/jest](examples/jest/jest.js) for a basic implementation of Jest's UI.
|
597 |
|
598 | #### <AppContext>
|
599 |
|
600 | `<StdinContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes a method to manually exit the app (unmount).
|
601 |
|
602 | Import:
|
603 |
|
604 | ```js
|
605 | import {AppContext} from 'ink';
|
606 | ```
|
607 |
|
608 | ##### exit
|
609 |
|
610 | Type: `Function`
|
611 |
|
612 | Exit (unmount) the whole Ink app.
|
613 |
|
614 | Usage:
|
615 |
|
616 | ```jsx
|
617 | <AppContext.Consumer>
|
618 | {({ exit }) => (
|
619 | {/* Calling `onExit()` from within <MyApp> will unmount the app */}
|
620 | <MyApp onExit={exit}/>
|
621 | )}
|
622 | </AppContext.Consumer>
|
623 | ```
|
624 |
|
625 | #### <StdinContext>
|
626 |
|
627 | `<StdinContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes input stream.
|
628 |
|
629 | Import:
|
630 |
|
631 | ```js
|
632 | import {StdinContext} from 'ink';
|
633 | ```
|
634 |
|
635 | ##### stdin
|
636 |
|
637 | Type: `Stream`<br>
|
638 | Default: `process.stdin`
|
639 |
|
640 | Stdin stream passed to `render()` in `options.stdin` or `process.stdin` by default.
|
641 | Useful if your app needs to handle user input.
|
642 |
|
643 | Usage:
|
644 |
|
645 | ```jsx
|
646 | <StdinContext.Consumer>
|
647 | {({ stdin }) => (
|
648 | <MyComponent stdin={stdin}/>
|
649 | )}
|
650 | </StdinContext.Consumer>
|
651 | ```
|
652 |
|
653 | ##### setRawMode
|
654 |
|
655 | Type: `function`<br>
|
656 |
|
657 | See [setRawMode](https://nodejs.org/api/tty.html#tty_readstream_setrawmode_mode).
|
658 | Ink exposes this function via own `<StdinContext>` to be able to handle <kbd>Ctrl</kbd>+<kbd>C</kbd>, that's why you should use Ink's `setRawMode` instead of `process.stdin.setRawMode`.
|
659 |
|
660 | Usage:
|
661 |
|
662 | ```jsx
|
663 | <StdinContext.Consumer>
|
664 | {({ setRawMode }) => (
|
665 | <MyComponent setRawMode={setRawMode}/>
|
666 | )}
|
667 | </StdinContext.Consumer>
|
668 | ```
|
669 |
|
670 | #### <StdoutContext>
|
671 |
|
672 | `<StdoutContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes stdout stream, where Ink renders your app.
|
673 |
|
674 | Import:
|
675 |
|
676 | ```js
|
677 | import {StdoutContext} from 'ink';
|
678 | ```
|
679 |
|
680 | ##### stdout
|
681 |
|
682 | Type: `Stream`<br>
|
683 | Default: `process.stdout`
|
684 |
|
685 | Usage:
|
686 |
|
687 | ```jsx
|
688 | <StdoutContext.Consumer>
|
689 | {({ stdout }) => (
|
690 | <MyComponent stdout={stdout}/>
|
691 | )}
|
692 | </StdoutContext.Consumer>
|
693 | ```
|
694 |
|
695 |
|
696 | ## Useful Components
|
697 |
|
698 | - [ink-text-input](https://github.com/vadimdemedes/ink-text-input) - Text input.
|
699 | - [ink-spinner](https://github.com/vadimdemedes/ink-spinner) - Spinner.
|
700 | - [ink-select-input](https://github.com/vadimdemedes/ink-select-input) - Select (dropdown) input.
|
701 | - [ink-link](https://github.com/sindresorhus/ink-link) - Link component.
|
702 | - [ink-box](https://github.com/sindresorhus/ink-box) - Box component.
|
703 | - [ink-gradient](https://github.com/sindresorhus/ink-gradient) - Gradient color component.
|
704 | - [ink-big-text](https://github.com/sindresorhus/ink-big-text) - Awesome text component.
|
705 | - [ink-image](https://github.com/kevva/ink-image) - Display images inside the terminal.
|
706 |
|
707 | ### Incompatible components
|
708 |
|
709 | These are components that haven't migrated to Ink 2 yet:
|
710 |
|
711 | - [ink-progress-bar](https://github.com/brigand/ink-progress-bar) - Configurable component for rendering progress bars.
|
712 | - [ink-console](https://github.com/ForbesLindesay/ink-console) - Render output from `console[method]` calls in a scrollable panel.
|
713 | - [ink-confirm-input](https://github.com/kevva/ink-confirm-input) - Yes/No confirmation input.
|
714 | - [ink-checkbox-list](https://github.com/MaxMEllon/ink-checkbox-list) - Checkbox.
|
715 | - [ink-quicksearch](https://github.com/aicioara/ink-quicksearch) - Select Component with fast quicksearch-like navigation
|
716 | - [ink-autocomplete](https://github.com/maticzav/ink-autocomplete) - Autocomplete.
|
717 | - [ink-table](https://github.com/maticzav/ink-table) - Table component.
|
718 | - [ink-broadcast](https://github.com/jimmed/ink-broadcast) - Implementation of react-broadcast for Ink.
|
719 | - [ink-router](https://github.com/jimmed/ink-router) - Implementation of react-router for Ink.
|
720 | - [ink-tab](https://github.com/jdeniau/ink-tab) - Tab component.
|
721 | - [ink-select](https://github.com/karaggeorge/ink-select) - Select component.
|
722 | - [ink-scrollbar](https://github.com/karaggeorge/ink-scrollbar) - Scrollbar component.
|
723 | - [ink-text-animation](https://github.com/yardnsm/ink-text-animation) - Text animation component.
|
724 | - [ink-figlet](https://github.com/KimotoYanke/ink-figlet) - Large text component with Figlet fonts.
|
725 | - [ink-divider](https://github.com/JureSotosek/ink-divider) - A divider component.
|
726 |
|
727 |
|
728 | ## License
|
729 |
|
730 | MIT © [Vadim Demedes](https://github.com/vadimdemedes)
|