UNPKG

16.7 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##### Padding
283
284###### paddingTop
285
286Type: `number`<br>
287Default: `0`
288
289###### paddingBottom
290
291Type: `number`<br>
292Default: `0`
293
294###### paddingLeft
295
296Type: `number`<br>
297Default: `0`
298
299###### paddingRight
300
301Type: `number`<br>
302Default: `0`
303
304###### paddingX
305
306Type: `number`<br>
307Default: `0`
308
309###### paddingY
310
311Type: `number`<br>
312Default: `0`
313
314###### padding
315
316Type: `number`<br>
317Default: `0`
318
319```jsx
320<Box paddingTop={2}>Top</Box>
321<Box paddingBottom={2}>Bottom</Box>
322<Box paddingLeft={2}>Left</Box>
323<Box paddingRight={2}>Right</Box>
324<Box paddingX={2}>Left and right</Box>
325<Box paddingY={2}>Top and bottom</Box>
326<Box padding={2}>Top, bottom, left and right</Box>
327```
328
329##### Margin
330
331###### marginTop
332
333Type: `number`<br>
334Default: `0`
335
336###### marginBottom
337
338Type: `number`<br>
339Default: `0`
340
341###### marginLeft
342
343Type: `number`<br>
344Default: `0`
345
346###### marginRight
347
348Type: `number`<br>
349Default: `0`
350
351###### marginX
352
353Type: `number`<br>
354Default: `0`
355
356###### marginY
357
358Type: `number`<br>
359Default: `0`
360
361###### margin
362
363Type: `number`<br>
364Default: `0`
365
366```jsx
367<Box marginTop={2}>Top</Box>
368<Box marginBottom={2}>Bottom</Box>
369<Box marginLeft={2}>Left</Box>
370<Box marginRight={2}>Right</Box>
371<Box marginX={2}>Left and right</Box>
372<Box marginY={2}>Top and bottom</Box>
373<Box margin={2}>Top, bottom, left and right</Box>
374```
375
376##### Flex
377
378###### flexGrow
379
380Type: `number`<br>
381Default: `0`
382
383See [flex-grow](https://css-tricks.com/almanac/properties/f/flex-grow/).
384
385```jsx
386<Box>
387 Label:
388 <Box flexGrow={1}>
389 Fills all remaining space
390 </Box>
391</Box>
392```
393
394###### flexShrink
395
396Type: `number`<br>
397Default: `1`
398
399See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
400
401```jsx
402<Box width={20}>
403 <Box flexShrink={2} width={10}>
404 Will be 1/4
405 </Box>
406 <Box width={10}>
407 Will be 3/4
408 </Box>
409</Box>
410```
411
412###### flexDirection
413
414Type: `string`<br>
415Allowed values: `row`, `row-reverse`, `column` and `column-reverse`
416
417See [flex-direction](https://css-tricks.com/almanac/properties/f/flex-direction/).
418
419```jsx
420<Box>
421 <Box marginRight={1}>X</Box>
422 <Box>Y</Box>
423</Box>
424// X Y
425
426<Box flexDirection="row-reverse">
427 <Box>X</Box>
428 <Box marginRight={1}>Y</Box>
429</Box>
430// Y X
431
432<Box flexDirection="column">
433 <Box>X</Box>
434 <Box>Y</Box>
435</Box>
436// X
437// Y
438
439<Box flexDirection="column-reverse">
440 <Box>X</Box>
441 <Box>Y</Box>
442</Box>
443// Y
444// X
445```
446
447###### alignItems
448
449Type: `string`<br>
450Allowed values: `flex-start`, `center` and `flex-end`
451
452See [align-items](https://css-tricks.com/almanac/properties/f/align-items/).
453
454```jsx
455<Box alignItems="flex-start">
456 <Box marginRight={1}>X</Box>
457 <Box>{`A\nB\nC`}</Box>
458</Box>
459// X A
460// B
461// C
462
463<Box alignItems="center">
464 <Box marginRight={1}>X</Box>
465 <Box>{`A\nB\nC`}</Box>
466</Box>
467// A
468// X B
469// C
470
471<Box alignItems="flex-end">
472 <Box marginRight={1}>X</Box>
473 <Box>{`A\nB\nC`}</Box>
474</Box>
475// A
476// B
477// X C
478```
479
480###### justifyContent
481
482Type: `string`<br>
483Allowed values: `flex-start`, `center`, `flex-end`, `space-between` and `space-around`.
484
485See [justify-content](https://css-tricks.com/almanac/properties/f/justify-content/).
486
487```jsx
488<Box justifyContent="flex-start">
489 <Box>X</Box>
490</Box>
491// [X ]
492
493<Box justifyContent="center">
494 <Box>X</Box>
495</Box>
496// [ X ]
497
498<Box justifyContent="flex-end">
499 <Box>X</Box>
500</Box>
501// [ X]
502
503<Box justifyContent="space-between">
504 <Box>X</Box>
505 <Box>Y</Box>
506</Box>
507// [X Y]
508
509<Box justifyContent="space-around">
510 <Box>X</Box>
511 <Box>Y</Box>
512</Box>
513// [ X Y ]
514```
515
516
517#### `<Color>`
518
519The `<Color>` compoment is a simple wrapper around [the `chalk` API](https://github.com/chalk/chalk#api).
520It supports all of the chalk's methods as `props`.
521
522Import:
523
524```js
525import {Color} from 'ink';
526```
527
528Usage:
529
530```jsx
531<Color rgb={[255, 255, 255]} bgKeyword="magenta">
532 Hello!
533</Color>
534
535<Color hex="#000000" bgHex="#FFFFFF">
536 Hey there
537</Color>
538
539<Color blue>
540 I'm blue
541</Color>
542```
543
544#### `<Text>`
545
546This component can change the style of the text, make it bold, underline, italic or strikethrough.
547
548Import:
549
550```js
551import {Text} from 'ink';
552```
553
554##### bold
555
556Type: `boolean`<br>
557Default: `false`
558
559##### italic
560
561Type: `boolean`<br>
562Default: `false`
563
564##### underline
565
566Type: `boolean`<br>
567Default: `false`
568
569##### strikethrough
570
571Type: `boolean`<br>
572Default: `false`
573
574Usage:
575
576```jsx
577<Text bold>I am bold</Text>
578<Text italic>I am italic</Text>
579<Text underline>I am underline</Text>
580<Text strikethrough>I am strikethrough</Text>
581```
582
583#### `<Static>`
584
585`<Static>` component allows permanently rendering output to stdout and preserving it across renders.
586Components passed to `<Static>` as children will be written to stdout only once and will never be rerendered.
587`<Static>` output comes first, before any other output from your components, no matter where it is in the tree.
588In 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.
589
590**Note:** `<Static>` accepts only an array of children and each of them must have a unique key.
591
592Example use case for this component is Jest's output:
593
594![](https://jestjs.io/img/content/feature-fast.png)
595
596Jest 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:
597
598```jsx
599<>
600 <Static>
601 {tests.map(test => (
602 <Test key={test.id} title={test.title}/>
603 ))}
604 </Static>
605
606 <Box marginTop={1}>
607 <TestResults passed={results.passed} failed={results.failed}/>
608 </Box>
609</>
610```
611
612See [examples/jest](examples/jest/jest.js) for a basic implementation of Jest's UI.
613
614#### `<AppContext>`
615
616`<AppContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes a method to manually exit the app (unmount).
617
618Import:
619
620```js
621import {AppContext} from 'ink';
622```
623
624##### exit
625
626Type: `Function`
627
628Exit (unmount) the whole Ink app.
629
630Usage:
631
632```jsx
633<AppContext.Consumer>
634 {({ exit }) => (
635 {/* Calling `onExit()` from within <MyApp> will unmount the app */}
636 <MyApp onExit={exit}/>
637 )}
638</AppContext.Consumer>
639```
640
641#### `<StdinContext>`
642
643`<StdinContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes input stream.
644
645Import:
646
647```js
648import {StdinContext} from 'ink';
649```
650
651##### stdin
652
653Type: `stream.Readable`<br>
654Default: `process.stdin`
655
656Stdin stream passed to `render()` in `options.stdin` or `process.stdin` by default.
657Useful if your app needs to handle user input.
658
659Usage:
660
661```jsx
662<StdinContext.Consumer>
663 {({ stdin }) => (
664 <MyComponent stdin={stdin}/>
665 )}
666</StdinContext.Consumer>
667```
668
669##### setRawMode
670
671Type: `function`<br>
672
673See [`setRawMode`](https://nodejs.org/api/tty.html#tty_readstream_setrawmode_mode).
674Ink 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.
675
676Usage:
677
678```jsx
679<StdinContext.Consumer>
680 {({ setRawMode }) => (
681 <MyComponent setRawMode={setRawMode}/>
682 )}
683</StdinContext.Consumer>
684```
685
686#### `<StdoutContext>`
687
688`<StdoutContext>` is a [React context](https://reactjs.org/docs/context.html#reactcreatecontext), which exposes stdout stream, where Ink renders your app.
689
690Import:
691
692```js
693import {StdoutContext} from 'ink';
694```
695
696##### stdout
697
698Type: `stream.Writable`<br>
699Default: `process.stdout`
700
701Usage:
702
703```jsx
704<StdoutContext.Consumer>
705 {({ stdout }) => (
706 <MyComponent stdout={stdout}/>
707 )}
708</StdoutContext.Consumer>
709```
710
711
712## Useful Components
713
714- [ink-text-input](https://github.com/vadimdemedes/ink-text-input) - Text input.
715- [ink-spinner](https://github.com/vadimdemedes/ink-spinner) - Spinner.
716- [ink-select-input](https://github.com/vadimdemedes/ink-select-input) - Select (dropdown) input.
717- [ink-link](https://github.com/sindresorhus/ink-link) - Link component.
718- [ink-box](https://github.com/sindresorhus/ink-box) - Styled box component.
719- [ink-gradient](https://github.com/sindresorhus/ink-gradient) - Gradient color component.
720- [ink-big-text](https://github.com/sindresorhus/ink-big-text) - Awesome text component.
721- [ink-image](https://github.com/kevva/ink-image) - Display images inside the terminal.
722- [ink-tab](https://github.com/jdeniau/ink-tab) - Tab component.
723
724### Incompatible components
725
726These are components that haven't migrated to Ink 2 yet:
727
728- [ink-progress-bar](https://github.com/brigand/ink-progress-bar) - Configurable component for rendering progress bars.
729- [ink-console](https://github.com/ForbesLindesay/ink-console) - Render output from `console[method]` calls in a scrollable panel.
730- [ink-confirm-input](https://github.com/kevva/ink-confirm-input) - Yes/No confirmation input.
731- [ink-checkbox-list](https://github.com/MaxMEllon/ink-checkbox-list) - Checkbox.
732- [ink-quicksearch](https://github.com/aicioara/ink-quicksearch) - Select Component with fast quicksearch-like navigation
733- [ink-autocomplete](https://github.com/maticzav/ink-autocomplete) - Autocomplete.
734- [ink-table](https://github.com/maticzav/ink-table) - Table component.
735- [ink-broadcast](https://github.com/jimmed/ink-broadcast) - Implementation of react-broadcast for Ink.
736- [ink-router](https://github.com/jimmed/ink-router) - Implementation of react-router for Ink.
737- [ink-select](https://github.com/karaggeorge/ink-select) - Select component.
738- [ink-scrollbar](https://github.com/karaggeorge/ink-scrollbar) - Scrollbar component.
739- [ink-text-animation](https://github.com/yardnsm/ink-text-animation) - Text animation component.
740- [ink-figlet](https://github.com/KimotoYanke/ink-figlet) - Large text component with Figlet fonts.
741- [ink-divider](https://github.com/JureSotosek/ink-divider) - A divider component.
742
743
744## Testing
745
746Ink components are simple to test with [ink-testing-library](https://github.com/vadimdemedes/ink-testing-library).
747Here's a simple example that checks how component is rendered:
748
749```jsx
750import React from 'react';
751import {Text} from 'ink';
752import {render} from 'ink-testing-library';
753
754const Test = () => <Text>Hello World</Text>;
755const {lastFrame} = render(<Test/>);
756
757lastFrame() === 'Hello World'; //=> true
758```
759
760Visit [ink-testing-library](https://github.com/vadimdemedes/ink-testing-library) for more examples and full documentation.
761
762
763## Maintainers
764
765- [Vadim Demedes](https://github.com/vadimdemedes)
766- [Sindre Sorhus](https://github.com/sindresorhus)
767
768
769## License
770
771MIT