UNPKG

19 kBMarkdownView Raw
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=master)](https://travis-ci.org/vadimdemedes/ink)
13
14
15## Install
16
17```
18$ npm install ink react
19```
20
21
22## Usage
23
24```jsx
25import React, {Component} from 'react';
26import {render, Color} from 'ink';
27
28class 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
58render(<Counter/>);
59```
60
61<img src="media/demo.svg" width="600">
62
63You can also check it out live on [repl.it sandbox](https://ink-counter-demo.vadimdemedes.repl.run/).
64Feel 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- [changelog-view](https://github.com/jdeniau/changelog-view) - Tool view changelog in console.
73
74
75## Contents
76
77- [Getting Started](#getting-started)
78- [Examples](#examples)
79- [API](#api)
80- [Building Layouts](#building-layouts)
81- [Built-in Components](#built-in-components)
82- [Useful Components](#useful-components)
83- [Testing](#testing)
84
85
86## Getting Started
87
88Ink'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.
89
90The key difference you have to remember is that the rendering result isn't a DOM, but a string, which Ink writes to the output.
91
92To 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`:
93
94```json
95{
96 "babel": {
97 "presets": [
98 "@babel/preset-react"
99 ]
100 }
101}
102```
103
104Don't forget to import `React` into every file that contains JSX:
105
106```jsx
107import React from 'react';
108import {render, Box} from 'ink';
109
110const Demo = () => (
111 <Box>
112 Hello World
113 </Box>
114);
115
116render(<Demo/>);
117```
118
119
120## Examples
121
122- [Jest](examples/jest/jest.js) - Implementation of basic Jest UI [(live demo)](https://ink-jest-demo.vadimdemedes.repl.run/).
123- [Counter](examples/counter/counter.js) - Simple counter that increments every 100ms [(live demo)](https://ink-counter-demo.vadimdemedes.repl.run/).
124
125
126## API
127
128Since Ink is a React renderer, it means that all features of React are supported.
129Head over to [React](https://reactjs.org) website for documentation on how to use it.
130In this readme only Ink's methods will be documented.
131
132#### render(tree, options)
133
134Returns: `Instance`
135
136Mount a component and render the output.
137
138##### tree
139
140Type: `ReactElement`
141
142##### options
143
144Type: `Object`
145
146###### stdout
147
148Type: `stream.Writable`<br>
149Default: `process.stdout`
150
151Output stream where app will be rendered.
152
153###### stdin
154
155Type: `stream.Readable`<br>
156Default: `process.stdin`
157
158Input stream where app will listen for input.
159
160###### exitOnCtrlC
161
162Type: `boolean`<br>
163Default: `true`
164
165Configure whether Ink should listen to Ctrl+C keyboard input and exit the app.
166This 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.
167
168###### debug
169
170Type: `boolean`<br>
171Default: `false`
172
173If `true`, each update will be rendered as a separate output, without replacing the previous one.
174
175```jsx
176import React, {Component} from 'react';
177import {render, Box} from 'ink';
178
179class Counter extends Component {
180 constructor() {
181 super();
182
183 this.state = {
184 i: 0
185 };
186 }
187
188 render() {
189 return (
190 <Box>
191 Iteration #{this.state.i}
192 </Box>
193 );
194 }
195
196 componentDidMount() {
197 this.timer = setInterval(() => {
198 this.setState(prevState => ({
199 i: prevState.i + 1
200 }));
201 }, 100);
202 }
203
204 componentWillUnmount() {
205 clearInterval(this.timer);
206 }
207}
208
209const app = render(<Counter/>);
210
211setTimeout(() => {
212 // Enough counting
213 app.unmount();
214}, 1000);
215```
216
217There's also a shortcut to avoid passing `options` object:
218
219```jsx
220render(<Counter>, process.stdout);
221```
222
223#### Instance
224
225This is the object that `render()` returns.
226
227##### rerender
228
229Replace previous root node with a new one or update props of the current root node.
230
231```jsx
232// Update props of the root node
233const {rerender} = render(<Counter count={1}/>);
234rerender(<Counter count={2}/>);
235
236// Replace root node
237const {rerender} = render(<OldCounter/>);
238rerender(<NewCounter/>);
239```
240
241##### unmount
242
243Manually unmount the whole Ink app.
244
245```jsx
246const {unmount} = render(<MyApp/>);
247unmount();
248```
249
250##### waitUntilExit
251
252Returns a promise, which resolves when app is unmounted.
253
254```jsx
255const {unmount, waitUntilExit} = render(<MyApp/>);
256
257setTimeout(unmount, 1000);
258
259await waitUntilExit(); // resolves after `unmount()` is called
260```
261
262## Building Layouts
263
264Ink uses [Yoga](https://github.com/facebook/yoga) - a Flexbox layout engine to build great user interfaces for your CLIs.
265It's important to remember that each element is a Flexbox container.
266Think of it as if each `<div>` in the browser had `display: flex`.
267See `<Box>` built-in component below for documentation on how to use Flexbox layouts in Ink.
268
269
270### Built-in Components
271
272#### `<Box>`
273
274`<Box>` it's an essential Ink component to build your layout. It's like a `<div style="display: flex">` in a browser.
275
276Import:
277
278```js
279import {Box} from 'ink';
280```
281
282##### Dimensions
283
284###### width
285
286Type: `number`, `string`
287
288Width of the element in spaces. You can also set it in percent, which will calculate the width based on the width of parent element.
289
290```jsx
291<Box width={4}>X</Box> //=> 'X '
292```
293
294```jsx
295<Box width={10}>
296 <Box width="50%">X</Box>
297 Y
298</Box> //=> 'X Y'
299```
300
301###### height
302
303Type: `number`, `string`
304
305Height of the element in lines (rows). You can also set it in percent, which will calculate the height based on the height of parent element.
306
307```jsx
308<Box height={4}>X</Box> //=> 'X\n\n\n'
309```
310
311```jsx
312<Box height={6} flexDirection="column">
313 <Box height="50%">X</Box>
314 Y
315</Box> //=> 'X\n\n\nY\n\n'
316```
317
318###### minWidth
319
320Type: `number`
321
322Sets a minimum width of the element. Percentages aren't supported yet, see https://github.com/facebook/yoga/issues/872.
323
324###### minHeight
325
326Type: `number`
327
328Sets a minimum height of the element. Percentages aren't supported yet, see https://github.com/facebook/yoga/issues/872.
329
330##### Wrapping
331
332###### textWrap
333
334Type: `string`<br>
335Values: `wrap` `truncate` `truncate-start` `truncate-middle` `truncate-end`
336
337This property tells Ink to wrap or truncate text content of `<Box>` if its width is larger than container. If `wrap` is passed, Ink will wrap text and split it into multiple lines. If `truncate-*` is passed, Ink will truncate text instead, which will result in one line of text with the rest cut off.
338
339*Note:* Ink doesn't wrap text by default.
340
341```jsx
342<Box textWrap="wrap">Hello World</Box>
343//=> 'Hello\nWorld'
344
345// `truncate` is an alias to `truncate-end`
346<Box textWrap="truncate">Hello World</Box>
347//=> 'Hello…'
348
349<Box textWrap="truncate-middle">Hello World</Box>
350//=> 'He…ld'
351
352<Box textWrap="truncate-start">Hello World</Box>
353//=> '…World'
354```
355
356##### Padding
357
358###### paddingTop
359
360Type: `number`<br>
361Default: `0`
362
363###### paddingBottom
364
365Type: `number`<br>
366Default: `0`
367
368###### paddingLeft
369
370Type: `number`<br>
371Default: `0`
372
373###### paddingRight
374
375Type: `number`<br>
376Default: `0`
377
378###### paddingX
379
380Type: `number`<br>
381Default: `0`
382
383###### paddingY
384
385Type: `number`<br>
386Default: `0`
387
388###### padding
389
390Type: `number`<br>
391Default: `0`
392
393```jsx
394<Box paddingTop={2}>Top</Box>
395<Box paddingBottom={2}>Bottom</Box>
396<Box paddingLeft={2}>Left</Box>
397<Box paddingRight={2}>Right</Box>
398<Box paddingX={2}>Left and right</Box>
399<Box paddingY={2}>Top and bottom</Box>
400<Box padding={2}>Top, bottom, left and right</Box>
401```
402
403##### Margin
404
405###### marginTop
406
407Type: `number`<br>
408Default: `0`
409
410###### marginBottom
411
412Type: `number`<br>
413Default: `0`
414
415###### marginLeft
416
417Type: `number`<br>
418Default: `0`
419
420###### marginRight
421
422Type: `number`<br>
423Default: `0`
424
425###### marginX
426
427Type: `number`<br>
428Default: `0`
429
430###### marginY
431
432Type: `number`<br>
433Default: `0`
434
435###### margin
436
437Type: `number`<br>
438Default: `0`
439
440```jsx
441<Box marginTop={2}>Top</Box>
442<Box marginBottom={2}>Bottom</Box>
443<Box marginLeft={2}>Left</Box>
444<Box marginRight={2}>Right</Box>
445<Box marginX={2}>Left and right</Box>
446<Box marginY={2}>Top and bottom</Box>
447<Box margin={2}>Top, bottom, left and right</Box>
448```
449
450##### Flex
451
452###### flexGrow
453
454Type: `number`<br>
455Default: `0`
456
457See [flex-grow](https://css-tricks.com/almanac/properties/f/flex-grow/).
458
459```jsx
460<Box>
461 Label:
462 <Box flexGrow={1}>
463 Fills all remaining space
464 </Box>
465</Box>
466```
467
468###### flexShrink
469
470Type: `number`<br>
471Default: `1`
472
473See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
474
475```jsx
476<Box width={20}>
477 <Box flexShrink={2} width={10}>
478 Will be 1/4
479 </Box>
480 <Box width={10}>
481 Will be 3/4
482 </Box>
483</Box>
484```
485
486###### flexBasis
487
488Type: `number`, `string`<br>
489
490See [flex-basis](https://css-tricks.com/almanac/properties/f/flex-basis/).
491
492```jsx
493<Box width={6}>
494 <Box flexBasis={3}>X</Box>
495 Y
496</Box> //=> 'X Y'
497```
498
499```jsx
500<Box width={6}>
501 <Box flexBasis="50%">X</Box>
502 Y
503</Box> //=> 'X Y'
504```
505
506###### flexDirection
507
508Type: `string`<br>
509Allowed values: `row`, `row-reverse`, `column` and `column-reverse`
510
511See [flex-direction](https://css-tricks.com/almanac/properties/f/flex-direction/).
512
513```jsx
514<Box>
515 <Box marginRight={1}>X</Box>
516 <Box>Y</Box>
517</Box>
518// X Y
519
520<Box flexDirection="row-reverse">
521 <Box>X</Box>
522 <Box marginRight={1}>Y</Box>
523</Box>
524// Y X
525
526<Box flexDirection="column">
527 <Box>X</Box>
528 <Box>Y</Box>
529</Box>
530// X
531// Y
532
533<Box flexDirection="column-reverse">
534 <Box>X</Box>
535 <Box>Y</Box>
536</Box>
537// Y
538// X
539```
540
541###### alignItems
542
543Type: `string`<br>
544Allowed values: `flex-start`, `center` and `flex-end`
545
546See [align-items](https://css-tricks.com/almanac/properties/f/align-items/).
547
548```jsx
549<Box alignItems="flex-start">
550 <Box marginRight={1}>X</Box>
551 <Box>{`A\nB\nC`}</Box>
552</Box>
553// X A
554// B
555// C
556
557<Box alignItems="center">
558 <Box marginRight={1}>X</Box>
559 <Box>{`A\nB\nC`}</Box>
560</Box>
561// A
562// X B
563// C
564
565<Box alignItems="flex-end">
566 <Box marginRight={1}>X</Box>
567 <Box>{`A\nB\nC`}</Box>
568</Box>
569// A
570// B
571// X C
572```
573
574###### justifyContent
575
576Type: `string`<br>
577Allowed values: `flex-start`, `center`, `flex-end`, `space-between` and `space-around`.
578
579See [justify-content](https://css-tricks.com/almanac/properties/f/justify-content/).
580
581```jsx
582<Box justifyContent="flex-start">
583 <Box>X</Box>
584</Box>
585// [X ]
586
587<Box justifyContent="center">
588 <Box>X</Box>
589</Box>
590// [ X ]
591
592<Box justifyContent="flex-end">
593 <Box>X</Box>
594</Box>
595// [ X]
596
597<Box justifyContent="space-between">
598 <Box>X</Box>
599 <Box>Y</Box>
600</Box>
601// [X Y]
602
603<Box justifyContent="space-around">
604 <Box>X</Box>
605 <Box>Y</Box>
606</Box>
607// [ X Y ]
608```
609
610
611#### `<Color>`
612
613The `<Color>` component is a simple wrapper around [the `chalk` API](https://github.com/chalk/chalk#api).
614It supports all of the chalk's methods as `props`.
615
616Import:
617
618```js
619import {Color} from 'ink';
620```
621
622Usage:
623
624```jsx
625<Color rgb={[255, 255, 255]} bgKeyword="magenta">
626 Hello!
627</Color>
628
629<Color hex="#000000" bgHex="#FFFFFF">
630 Hey there
631</Color>
632
633<Color blue>
634 I'm blue
635</Color>
636```
637
638#### `<Text>`
639
640This component can change the style of the text, make it bold, underline, italic or strikethrough.
641
642Import:
643
644```js
645import {Text} from 'ink';
646```
647
648##### bold
649
650Type: `boolean`<br>
651Default: `false`
652
653##### italic
654
655Type: `boolean`<br>
656Default: `false`
657
658##### underline
659
660Type: `boolean`<br>
661Default: `false`
662
663##### strikethrough
664
665Type: `boolean`<br>
666Default: `false`
667
668Usage:
669
670```jsx
671<Text bold>I am bold</Text>
672<Text italic>I am italic</Text>
673<Text underline>I am underline</Text>
674<Text strikethrough>I am strikethrough</Text>
675```
676
677#### `<Static>`
678
679`<Static>` component allows permanently rendering output to stdout and preserving it across renders.
680Components passed to `<Static>` as children will be written to stdout only once and will never be rerendered.
681`<Static>` output comes first, before any other output from your components, no matter where it is in the tree.
682In 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.
683
684**Note:** `<Static>` accepts only an array of children and each of them must have a unique key.
685
686Example use case for this component is Jest's output:
687
688![](https://jestjs.io/img/content/feature-fast.png)
689
690Jest continuously 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:
691
692```jsx
693<>
694 <Static>
695 {tests.map(test => (
696 <Test key={test.id} title={test.title}/>
697 ))}
698 </Static>
699
700 <Box marginTop={1}>
701 <TestResults passed={results.passed} failed={results.failed}/>
702 </Box>
703</>
704```
705
706See [examples/jest](examples/jest/jest.js) for a basic implementation of Jest's UI.
707
708#### `<AppContext>`
709
710`<AppContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes a method to manually exit the app (unmount).
711
712Import:
713
714```js
715import {AppContext} from 'ink';
716```
717
718##### exit
719
720Type: `Function`
721
722Exit (unmount) the whole Ink app.
723
724Usage:
725
726```jsx
727<AppContext.Consumer>
728 {({ exit }) => (
729 {/* Calling `onExit()` from within <MyApp> will unmount the app */}
730 <MyApp onExit={exit}/>
731 )}
732</AppContext.Consumer>
733```
734
735If `exit` is called with an Error, `waitUntilExit` will reject with that error.
736
737#### `<StdinContext>`
738
739`<StdinContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes input stream.
740
741Import:
742
743```js
744import {StdinContext} from 'ink';
745```
746
747##### stdin
748
749Type: `stream.Readable`<br>
750Default: `process.stdin`
751
752Stdin stream passed to `render()` in `options.stdin` or `process.stdin` by default.
753Useful if your app needs to handle user input.
754
755Usage:
756
757```jsx
758<StdinContext.Consumer>
759 {({ stdin }) => (
760 <MyComponent stdin={stdin}/>
761 )}
762</StdinContext.Consumer>
763```
764
765##### setRawMode
766
767Type: `function`<br>
768
769See [`setRawMode`](https://nodejs.org/api/tty.html#tty_readstream_setrawmode_mode).
770Ink 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`. Ink also enables `keypress` events via [`readline.emitKeypressEvents()`](https://nodejs.org/api/readline.html#readline_readline_emitkeypressevents_stream_interface) when raw mode is enabled.
771
772Usage:
773
774```jsx
775<StdinContext.Consumer>
776 {({ setRawMode }) => (
777 <MyComponent setRawMode={setRawMode}/>
778 )}
779</StdinContext.Consumer>
780```
781
782#### `<StdoutContext>`
783
784`<StdoutContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes stdout stream, where Ink renders your app.
785
786Import:
787
788```js
789import {StdoutContext} from 'ink';
790```
791
792##### stdout
793
794Type: `stream.Writable`<br>
795Default: `process.stdout`
796
797Usage:
798
799```jsx
800<StdoutContext.Consumer>
801 {({ stdout }) => (
802 <MyComponent stdout={stdout}/>
803 )}
804</StdoutContext.Consumer>
805```
806
807
808## Useful Components
809
810- [ink-text-input](https://github.com/vadimdemedes/ink-text-input) - Text input.
811- [ink-spinner](https://github.com/vadimdemedes/ink-spinner) - Spinner.
812- [ink-select-input](https://github.com/vadimdemedes/ink-select-input) - Select (dropdown) input.
813- [ink-link](https://github.com/sindresorhus/ink-link) - Link component.
814- [ink-box](https://github.com/sindresorhus/ink-box) - Styled box component.
815- [ink-gradient](https://github.com/sindresorhus/ink-gradient) - Gradient color component.
816- [ink-big-text](https://github.com/sindresorhus/ink-big-text) - Awesome text component.
817- [ink-image](https://github.com/kevva/ink-image) - Display images inside the terminal.
818- [ink-tab](https://github.com/jdeniau/ink-tab) - Tab component.
819- [ink-color-pipe](https://github.com/LitoMore/ink-color-pipe) - Create color text with simpler style strings in Ink.
820
821### Incompatible components
822
823These are components that haven't migrated to Ink 2 yet:
824
825- [ink-progress-bar](https://github.com/brigand/ink-progress-bar) - Configurable component for rendering progress bars.
826- [ink-console](https://github.com/ForbesLindesay/ink-console) - Render output from `console[method]` calls in a scrollable panel.
827- [ink-confirm-input](https://github.com/kevva/ink-confirm-input) - Yes/No confirmation input.
828- [ink-checkbox-list](https://github.com/MaxMEllon/ink-checkbox-list) - Checkbox.
829- [ink-quicksearch](https://github.com/aicioara/ink-quicksearch) - Select Component with fast quicksearch-like navigation
830- [ink-autocomplete](https://github.com/maticzav/ink-autocomplete) - Autocomplete.
831- [ink-table](https://github.com/maticzav/ink-table) - Table component.
832- [ink-broadcast](https://github.com/jimmed/ink-broadcast) - Implementation of react-broadcast for Ink.
833- [ink-router](https://github.com/jimmed/ink-router) - Implementation of react-router for Ink.
834- [ink-select](https://github.com/karaggeorge/ink-select) - Select component.
835- [ink-scrollbar](https://github.com/karaggeorge/ink-scrollbar) - Scrollbar component.
836- [ink-text-animation](https://github.com/yardnsm/ink-text-animation) - Text animation component.
837- [ink-figlet](https://github.com/KimotoYanke/ink-figlet) - Large text component with Figlet fonts.
838- [ink-divider](https://github.com/JureSotosek/ink-divider) - A divider component.
839
840
841## Testing
842
843Ink components are simple to test with [ink-testing-library](https://github.com/vadimdemedes/ink-testing-library).
844Here's a simple example that checks how component is rendered:
845
846```jsx
847import React from 'react';
848import {Text} from 'ink';
849import {render} from 'ink-testing-library';
850
851const Test = () => <Text>Hello World</Text>;
852const {lastFrame} = render(<Test/>);
853
854lastFrame() === 'Hello World'; //=> true
855```
856
857Visit [ink-testing-library](https://github.com/vadimdemedes/ink-testing-library) for more examples and full documentation.
858
859
860## Maintainers
861
862- [Vadim Demedes](https://github.com/vadimdemedes)
863- [Sindre Sorhus](https://github.com/sindresorhus)
864
865
866## License
867
868MIT