diff --git a/docs/ProgressBar.md b/docs/ProgressBar.md
new file mode 100644
index 000000000..e10805c5e
--- /dev/null
+++ b/docs/ProgressBar.md
@@ -0,0 +1,42 @@
+# ProgressBar
+
+The ProgressBar component represents progress. See examples/progress.js for an example app.
+
+## Usage
+
+```js
+const {h, Text, ProgressBar} = require('ink');
+
+
+```
+
+## Props
+
+### character
+
+The character to use for each item in the ProgressBar. Defaults to █ (block).
+
+### progress
+
+The percentage (between 0 and 1) of progress in the ProgressBar.
+
+### left/right
+
+The number of characters to subtract from each side of the ProgressBar. examples/progress.js demonstrates this. Commonly used if you want text before/after the progress bar on the same line.
+
+
+### {color}
+
+Pass any chalk colors (e.g. `green`, `bgBlue`), similar to Text.
+
+### ...
+
+Any other props are passed to Text as-is.
+
+
diff --git a/examples/progress.js b/examples/progress.js
new file mode 100644
index 000000000..c30b4dd27
--- /dev/null
+++ b/examples/progress.js
@@ -0,0 +1,49 @@
+/* @jsx h */
+const {h, mount, Component, Text, ProgressBar} = require('../');
+
+const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+const TASKS = 30;
+
+class ProgressApp extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ done: 0
+ };
+ }
+
+ render() {
+ const text = `Running `;
+ return (
+
+ );
+ }
+
+ componentDidMount() {
+ const promises = Array.from({length: TASKS}, () =>
+ delay(Math.floor(Math.random() * 1500))
+ .then(() => {
+ this.setState(state => ({done: state.done + 1}));
+ })
+ );
+
+ Promise.all(promises)
+ .then(() => delay(50))
+ // eslint-disable-next-line unicorn/no-process-exit
+ .then(() => process.exit(0));
+ }
+}
+
+mount(, process.stdout);
+
diff --git a/examples/run b/examples/run
new file mode 100755
index 000000000..34545713e
--- /dev/null
+++ b/examples/run
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+./node_modules/.bin/babel-node examples/$1.js
+
diff --git a/index.js b/index.js
index 50e157748..3d98ddad9 100644
--- a/index.js
+++ b/index.js
@@ -11,6 +11,7 @@ const Newline = require('./lib/components/newline');
const Indent = require('./lib/components/indent');
const Group = require('./lib/components/group');
const Text = require('./lib/components/text');
+const ProgressBar = require('./lib/components/progress-bar');
exports.StringComponent = StringComponent;
exports.Component = Component;
@@ -19,6 +20,7 @@ exports.Newline = Newline;
exports.Indent = Indent;
exports.Group = Group;
exports.Text = Text;
+exports.ProgressBar = ProgressBar;
const noop = () => {};
diff --git a/lib/components/progress-bar.js b/lib/components/progress-bar.js
new file mode 100644
index 000000000..5cc2ea0ac
--- /dev/null
+++ b/lib/components/progress-bar.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const blacklist = require('blacklist');
+const Component = require('../component');
+const h = require('../h');
+const Text = require('./text');
+
+const PROPS = ['percent', 'left', 'right', 'columns', 'character'];
+
+class Bar extends Component {
+ getString() {
+ const {
+ percent = 1,
+ left = 0,
+ right = 0,
+ character = '█'
+ } = this.props;
+ const screen = this.props.columns || process.stdout.columns || 80;
+ const space = screen - right - left;
+ const max = Math.min(Math.floor(space * percent), space);
+ return character.repeat(max);
+ }
+
+ render() {
+ const props = blacklist(this.props, PROPS);
+ return h(Text, props, this.getString());
+ }
+}
+
+module.exports = Bar;
+
diff --git a/package.json b/package.json
index 74c747cf9..0e3bd298d 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
"node": ">= 4"
},
"scripts": {
- "test": "xo && ava"
+ "test": "xo && ava",
+ "example:progress": "babel-node examples/progress.js"
},
"files": [
"lib",
@@ -25,6 +26,7 @@
],
"dependencies": {
"arrify": "^1.0.1",
+ "blacklist": "^1.1.4",
"chalk": "^2.0.1",
"indent-string": "^3.1.0",
"lodash.flattendeep": "^4.4.0",
@@ -33,7 +35,10 @@
},
"devDependencies": {
"ava": "^0.19.1",
+ "babel-cli": "^6.24.1",
+ "babel-core": "^6.25.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
+ "babel-preset-react": "^6.24.1",
"babel-register": "^6.24.1",
"eslint-config-xo-react": "^0.12.0",
"eslint-plugin-react": "^7.1.0",
@@ -73,5 +78,16 @@
"pragma": "h"
}
}
+ },
+ "babel": {
+ "presets": ["babel-preset-react"],
+ "plugins": [
+ [
+ "transform-react-jsx",
+ {
+ "pragma": "h"
+ }
+ ]
+ ]
}
}
diff --git a/test/progress-bar.js b/test/progress-bar.js
new file mode 100644
index 000000000..1761188e6
--- /dev/null
+++ b/test/progress-bar.js
@@ -0,0 +1,16 @@
+import test from 'ava';
+
+const ProgressBar = require('../lib/components/progress-bar.js');
+
+const run = (columns, left, right) => ProgressBar.prototype.getString.call({
+ props: {columns, left, right, char: 'x'}
+});
+
+test(`has correct length`, t => {
+ const str = run(50, 0, 0);
+ t.is(str.length, 50);
+
+ const str2 = run(60, 10, 9);
+ t.is(str2.length, 41);
+});
+