Browse Source

Include ui-* packages in repo (development speed) (#65)

* Include ui-* packages in repo (development speed)

* Bump

* Bump

* Cleanup dependencies
Jaco Greeff 6 years ago
parent
commit
ba36eede0b
100 changed files with 2292 additions and 61 deletions
  1. 1 1
      .flowconfig
  2. 17 1
      README.md
  3. 8 0
      flow-typed/semver.js
  4. 10 1
      jest.config.js
  5. 1 1
      lerna.json
  6. 4 1
      package.json
  7. 2 2
      packages/app-accounts/package.json
  8. 3 3
      packages/app-addresses/package.json
  9. 2 2
      packages/app-explorer/package.json
  10. 3 3
      packages/app-extrinsics/package.json
  11. 4 4
      packages/app-rpc/package.json
  12. 2 2
      packages/app-storage/package.json
  13. 3 3
      packages/app-toolbox/package.json
  14. 2 2
      packages/app-vanitygen/package.json
  15. 11 11
      packages/apps/package.json
  16. 0 17
      packages/apps/src/App/App.spec.js
  17. 1 1
      packages/apps/webpack.config.js
  18. 4 4
      packages/ui-app/package.json
  19. 16 0
      packages/ui-identicon/LICENSE
  20. 39 0
      packages/ui-identicon/README.md
  21. BIN
      packages/ui-identicon/demo.png
  22. 17 0
      packages/ui-identicon/index.html
  23. 21 0
      packages/ui-identicon/package.json
  24. 30 0
      packages/ui-identicon/src/colors.js
  25. 34 0
      packages/ui-identicon/src/colors.spec.js
  26. 30 0
      packages/ui-identicon/src/container.js
  27. 43 0
      packages/ui-identicon/src/container.spec.js
  28. 25 0
      packages/ui-identicon/src/defaults.js
  29. 35 0
      packages/ui-identicon/src/demo.js
  30. 32 0
      packages/ui-identicon/src/index.js
  31. 25 0
      packages/ui-identicon/src/index.spec.js
  32. 35 0
      packages/ui-identicon/src/seeder.js
  33. 27 0
      packages/ui-identicon/src/seeder.spec.js
  34. 24 0
      packages/ui-identicon/src/shape/circle.js
  35. 18 0
      packages/ui-identicon/src/shape/circle.spec.js
  36. 27 0
      packages/ui-identicon/src/shape/square.js
  37. 16 0
      packages/ui-identicon/src/svg/circle.js
  38. 17 0
      packages/ui-identicon/src/svg/circle.spec.js
  39. 18 0
      packages/ui-identicon/src/svg/element.js
  40. 17 0
      packages/ui-identicon/src/svg/element.spec.js
  41. 15 0
      packages/ui-identicon/src/svg/rect.js
  42. 17 0
      packages/ui-identicon/src/svg/rect.spec.js
  43. 11 0
      packages/ui-identicon/src/svg/svg.js
  44. 17 0
      packages/ui-identicon/src/svg/svg.spec.js
  45. 6 0
      packages/ui-identicon/src/types.js
  46. 26 0
      packages/ui-identicon/webpack.config.js
  47. 2 2
      packages/ui-keyring/package.json
  48. 15 0
      packages/ui-react-rx/LICENSE
  49. 46 0
      packages/ui-react-rx/README.md
  50. 14 0
      packages/ui-react-rx/index.html
  51. 50 0
      packages/ui-react-rx/package.json
  52. 134 0
      packages/ui-react-rx/src/Api.js
  53. 14 0
      packages/ui-react-rx/src/Balance.js
  54. 20 0
      packages/ui-react-rx/src/BestNumber.js
  55. 14 0
      packages/ui-react-rx/src/Chain.js
  56. 15 0
      packages/ui-react-rx/src/Connected.js
  57. 30 0
      packages/ui-react-rx/src/Div.js
  58. 14 0
      packages/ui-react-rx/src/NodeName.js
  59. 19 0
      packages/ui-react-rx/src/NodeTime.js
  60. 14 0
      packages/ui-react-rx/src/NodeVersion.js
  61. 14 0
      packages/ui-react-rx/src/Nonce.js
  62. 55 0
      packages/ui-react-rx/src/demo.js
  63. 30 0
      packages/ui-react-rx/src/index.js
  64. 48 0
      packages/ui-react-rx/src/types.js
  65. 22 0
      packages/ui-react-rx/src/util/intervalSubscribe.js
  66. 8 0
      packages/ui-react-rx/src/util/isEqual.js
  67. 16 0
      packages/ui-react-rx/src/util/numberFormat.js
  68. 25 0
      packages/ui-react-rx/src/util/triggerChange.js
  69. 45 0
      packages/ui-react-rx/src/with/api.js
  70. 74 0
      packages/ui-react-rx/src/with/apiCall.js
  71. 18 0
      packages/ui-react-rx/src/with/apiDiv.js
  72. 18 0
      packages/ui-react-rx/src/with/index.js
  73. 101 0
      packages/ui-react-rx/src/with/observable.js
  74. 16 0
      packages/ui-react-rx/src/with/observableParams.js
  75. 156 0
      packages/ui-react-rx/src/with/storage.js
  76. 19 0
      packages/ui-react-rx/src/with/storageDiv.js
  77. 8 0
      packages/ui-react-rx/src/with/transform/echo.js
  78. 21 0
      packages/ui-react-rx/src/with/transform/storage.js
  79. 42 0
      packages/ui-react-rx/src/with/types.js
  80. 12 0
      packages/ui-react-rx/test/enzyme.js
  81. 15 0
      packages/ui-react-rx/test/observable.js
  82. 29 0
      packages/ui-react-rx/webpack.config.js
  83. 15 0
      packages/ui-react/LICENSE
  84. 13 0
      packages/ui-react/README.md
  85. 9 0
      packages/ui-react/index.html
  86. 35 0
      packages/ui-react/package.json
  87. 1 0
      packages/ui-react/postcss.config.js
  88. 18 0
      packages/ui-react/src/Container/Container.css
  89. 39 0
      packages/ui-react/src/Container/Demo.js
  90. 25 0
      packages/ui-react/src/Container/index.js
  91. 26 0
      packages/ui-react/src/Container/index.spec.js
  92. 43 0
      packages/ui-react/src/IdentityIcon/Demo.js
  93. 7 0
      packages/ui-react/src/IdentityIcon/IdentityIcon.css
  94. 22 0
      packages/ui-react/src/IdentityIcon/appendIcon.js
  95. 33 0
      packages/ui-react/src/IdentityIcon/appendIcon.spec.js
  96. 27 0
      packages/ui-react/src/IdentityIcon/index.js
  97. 26 0
      packages/ui-react/src/IdentityIcon/index.spec.js
  98. 8 0
      packages/ui-react/src/demo.css
  99. 24 0
      packages/ui-react/src/demo.js
  100. 12 0
      packages/ui-react/src/index.js

+ 1 - 1
.flowconfig

@@ -15,4 +15,4 @@ all=warn
 include_warnings=true
 module.name_mapper='^@polkadot/app-\(accounts\|addresses\|explorer\|extrinsics\|home\|rpc\|settings\|storage\|toolbox\|vanitygen\)\(.*\)$' -> '<PROJECT_ROOT>/packages/app-\1/src\2'
 module.name_mapper='^@polkadot/apps\(.*\)$' -> '<PROJECT_ROOT>/packages/apps/src\1'
-module.name_mapper='^@polkadot/ui-\(app\|keyring\|signer\)\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-\1/src\2'
+module.name_mapper='^@polkadot/ui-\(app\|identicon\|keyring\|react-rx\|react\|signer\)\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-\1/src\2'

+ 17 - 1
README.md

@@ -29,5 +29,21 @@ The repo is split into a number of packages, each representing an application. T
 
 In addition the following libraries are also included in the repo. These are to be moved to the [@polkadot/ui](https://github.com/polkadot-js/ui/) repository once it reaches a base level of stability and usability. (At this point with the framework being tested on the apps above, it makes development easier having it close)
 
-- [ui-keyring](packages/ui-keyring/) A browser-specific wrapper around the base [@polkadot/util-keyring](https://github.com/polkadot-js/util/) libary.
 - [ui-app](packages/ui-app/) A reactive (using RxJS) application framework with a number of useful shared components.
+- [ui-keyring](packages/ui-keyring/) A browser-specific wrapper around the base [@polkadot/util-keyring](https://github.com/polkadot-js/util/) library.
+- [ui-signer](packages/ui-signer/) Signer implementation for apps.
+- [packages/ui-identicon](packages/ui-identicon/) Identity icon generator with raw publicKey as input
+- [packages/ui-react](packages/ui-react) A collection of base React components
+- [packages/ui-react-rx](packages/ui-react-rx) Base components that use the RxJS Observable APIs
+
+## Application
+
+For development purposes, the apps entry point can be launched with `yarn run start` and accessing the application of [http://localhost:3000](http://localhost:3000)
+
+## Demos
+
+Demos for the different libraries can be viewed with `yarn run demo:<name>` and then browsing to [http://localhost:3000](http://localhost:3000). Available demo -
+
+- `yarn run demo:identicon`
+- `yarn run demo:ui`
+- `yarn run demo:rx`

+ 8 - 0
flow-typed/semver.js

@@ -0,0 +1,8 @@
+// @flow
+
+declare module 'semver' {
+  declare module.exports: {
+    gt: (a: string, b: string) => boolean,
+    lt: (a: string, b: string) => boolean
+  }
+}

+ 10 - 1
jest.config.js

@@ -1 +1,10 @@
-module.exports = require('@polkadot/dev-react/config/jest');
+const config = require('@polkadot/dev-react/config/jest');
+
+module.exports = Object.assign({}, config, {
+  moduleNameMapper: {
+    '@polkadot/app-(accounts|addresses|explorer|extrinsics|rpc|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
+    '@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
+    '\\.(css|less)$': 'empty/object'
+  }
+});

+ 1 - 1
lerna.json

@@ -10,5 +10,5 @@
   "packages": [
     "packages/*"
   ],
-  "version": "0.7.2"
+  "version": "0.17.0"
 }

+ 4 - 1
package.json

@@ -9,10 +9,13 @@
     "build": "polkadot-dev-build-babel",
     "check": "eslint packages && flow check",
     "clean": "polkadot-dev-clean-build",
+    "demo:identicon": "webpack-serve --config packages/ui-identicon/webpack.config.js --content packages/ui-identicon --port 3000",
+    "demo:rx": "webpack-serve --config packages/ui-react-rx/webpack.config.js --content packages/ui-react-rx --port 3000",
+    "demo:ui": "webpack-serve --config packages/ui-react/webpack.config.js --content packages/ui-react --port 3000",
     "deploy:ghpages": "gh-pages -d packages/apps/build",
     "vanitygen": "polkadot-dev-build-babel && node packages/app-vanitygen/build/generator/cli.js",
     "start": "cd packages/apps && webpack-serve --config webpack.config.js --port 3000",
-    "test": "echo 'no tests executed'"
+    "test": "jest --coverage"
   },
   "devDependencies": {
     "@polkadot/dev-react": "^0.19.11",

+ 2 - 2
packages/app-accounts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-accounts",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,6 +12,6 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/ui-app": "^0.7.2"
+    "@polkadot/ui-app": "^0.17.0"
   }
 }

+ 3 - 3
packages/app-addresses/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-addresses",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/app-accounts": "^0.7.2",
-    "@polkadot/ui-app": "^0.7.2"
+    "@polkadot/app-accounts": "^0.17.0",
+    "@polkadot/ui-app": "^0.17.0"
   }
 }

+ 2 - 2
packages/app-explorer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-explorer",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -14,7 +14,7 @@
     "@babel/runtime": "^7.0.0-beta.47",
     "@polkadot/primitives-codec": "^0.17.2",
     "@polkadot/primitives-json": "^0.17.2",
-    "@polkadot/ui-app": "^0.7.2",
+    "@polkadot/ui-app": "^0.17.0",
     "@polkadot/util-crypto": "^0.22.2"
   }
 }

+ 3 - 3
packages/app-extrinsics/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-extrinsics",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -13,8 +13,8 @@
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
     "@polkadot/extrinsics-codec": "^0.17.2",
-    "@polkadot/ui-app": "^0.7.2",
-    "@polkadot/ui-signer": "^0.7.2",
+    "@polkadot/ui-app": "^0.17.0",
+    "@polkadot/ui-signer": "^0.17.0",
     "react-dropzone": "^4.2.9"
   }
 }

+ 4 - 4
packages/app-rpc/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-rpc",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,8 +12,8 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/ui-app": "^0.7.2",
-    "@polkadot/ui-keyring": "^0.7.2",
-    "@polkadot/ui-signer": "^0.7.2"
+    "@polkadot/ui-app": "^0.17.0",
+    "@polkadot/ui-keyring": "^0.17.0",
+    "@polkadot/ui-signer": "^0.17.0"
   }
 }

+ 2 - 2
packages/app-storage/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-storage",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,6 +12,6 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/ui-app": "^0.7.2"
+    "@polkadot/ui-app": "^0.17.0"
   }
 }

+ 3 - 3
packages/app-toolbox/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-toolbox",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/ui-app": "^0.7.2",
-    "@polkadot/ui-keyring": "^0.7.2"
+    "@polkadot/ui-app": "^0.17.0",
+    "@polkadot/ui-keyring": "^0.17.0"
   }
 }

+ 2 - 2
packages/app-vanitygen/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-vanitygen",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/ui-app": "^0.7.2",
+    "@polkadot/ui-app": "^0.17.0",
     "chalk": "^2.4.1",
     "yargs": "10.1.2"
   },

+ 11 - 11
packages/apps/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/apps",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "description": "An Apps portal into the Polkadot network",
   "main": "index.js",
   "homepage": ".",
@@ -13,16 +13,16 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/app-accounts": "^0.7.2",
-    "@polkadot/app-addresses": "^0.7.2",
-    "@polkadot/app-explorer": "^0.7.2",
-    "@polkadot/app-extrinsics": "^0.7.2",
-    "@polkadot/app-rpc": "^0.7.2",
-    "@polkadot/app-storage": "^0.7.2",
-    "@polkadot/app-toolbox": "^0.7.2",
-    "@polkadot/app-vanitygen": "^0.7.2",
-    "@polkadot/ui-app": "^0.7.2",
-    "@polkadot/ui-signer": "^0.7.2"
+    "@polkadot/app-accounts": "^0.17.0",
+    "@polkadot/app-addresses": "^0.17.0",
+    "@polkadot/app-explorer": "^0.17.0",
+    "@polkadot/app-extrinsics": "^0.17.0",
+    "@polkadot/app-rpc": "^0.17.0",
+    "@polkadot/app-storage": "^0.17.0",
+    "@polkadot/app-toolbox": "^0.17.0",
+    "@polkadot/app-vanitygen": "^0.17.0",
+    "@polkadot/ui-app": "^0.17.0",
+    "@polkadot/ui-signer": "^0.17.0"
   },
   "devDependencies": {
     "babel-loader": "^8.0.0-beta.2",

+ 0 - 17
packages/apps/src/App/App.spec.js

@@ -1,17 +0,0 @@
-// Copyright 2017-2018 Jaco Greeff
-// This software may be modified and distributed under the terms
-// of the ISC license. See the LICENSE file for details.
-// @flow
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './index';
-
-describe('App', () => {
-  it('renders without crashing', () => {
-    const div = document.createElement('div');
-
-    ReactDOM.render(<App />, div);
-    ReactDOM.unmountComponentAtNode(div);
-  });
-});

+ 1 - 1
packages/apps/webpack.config.js

@@ -10,7 +10,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
 
-const packages = ['app-accounts', 'app-addresses', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-keyring', 'ui-signer'];
+const packages = ['app-accounts', 'app-addresses', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-identicon', 'ui-keyring', 'ui-react-rx', 'ui-react', 'ui-signer'];
 
 function createWebpack ({ alias = {}, context, name = 'index' }) {
   const pkgJson = require(path.join(context, 'package.json'));

+ 4 - 4
packages/ui-app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/ui-app",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -14,9 +14,9 @@
     "@babel/runtime": "^7.0.0-beta.47",
     "@polkadot/extrinsics": "^0.17.2",
     "@polkadot/storage": "^0.17.2",
-    "@polkadot/ui-keyring": "^0.7.2",
-    "@polkadot/ui-react": "^0.14.3",
-    "@polkadot/ui-react-rx": "^0.14.3",
+    "@polkadot/ui-keyring": "^0.17.0",
+    "@polkadot/ui-react": "^0.17.0",
+    "@polkadot/ui-react-rx": "^0.17.0",
     "i18next": "^11.1.1",
     "i18next-browser-languagedetector": "^2.2.0",
     "i18next-xhr-backend": "^1.5.1",

+ 16 - 0
packages/ui-identicon/LICENSE

@@ -0,0 +1,16 @@
+ISC License (ISC)
+
+Copyright 2016 Dan Finlay
+Copyright 2017-2018 Jaco Greeff <jacogr@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.

+ 39 - 0
packages/ui-identicon/README.md

@@ -0,0 +1,39 @@
+[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
+![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
+[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
+[![npm](https://img.shields.io/npm/v/@polkadot/ui-identicon.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/ui-identicon)
+[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/ui)
+[![maintainability](https://img.shields.io/codeclimate/maintainability/polkadot-js/apps.svg?style=flat-square)](https://codeclimate.com/github/polkadot-js/apps/maintainability)
+[![coverage](https://img.shields.io/coveralls/polkadot-js/apps.svg?style=flat-square)](https://coveralls.io/github/polkadot-js/apps?branch=master)
+[![dependency](https://david-dm.org/polkadot-js/apps.svg?style=flat-square&path=packages/ui-identicon)](https://david-dm.org/polkadot-js/apps?path=packages/ui-identicon)
+[![devDependency](https://david-dm.org/polkadot-js/apps/dev-status.svg?style=flat-square&path=packages/ui-identicon)](https://david-dm.org/polkadot-js/apps?path=packages/ui-identicon#info=devDependencies)
+
+# @polkadot/ui-identicon
+
+Adapted from [Jazzicon](https://github.com/danfinlay/jazzicon) by Dan Finlay with the following changes -
+
+- Random values now is read from the Uint8Array supplied (as opposed to having the seed as a number). This allows us to give an publicKey/address as an input and use those values in the pattern generation.
+- Upgrade to the underlying [color](https://github.com/Qix-/color) library
+- Generate circles as shapes (instead of rectangles)
+- Interface updated to take in optional className & style
+- Update everywhere to use ES6
+- Split source into self-contained functions (TODO: future testing)
+- Everything has been updated to use flow
+- Test the library functions
+- Copyright headers added (original also under ISC)
+
+## Usage
+
+```js
+import identicon from '@polkadot/ui-identicon';
+
+const publicKey = new Uint8Array([...]); // 32 bytes
+const body = document.querySelector('body');
+const identity = identicon(publicKey, 100 /* diameter, optional */, 'my-class' /* className, optional */ /* style. optional */ /* colors, optional */);
+
+body.append(identity);
+```
+
+Also see [src/demo.js](src/demo.js) for a randomly generated example.
+
+![demo](https://raw.githubusercontent.com/polkadot-js/ui/master/packages/ui-identicon/demo.png)

BIN
packages/ui-identicon/demo.png


+ 17 - 0
packages/ui-identicon/index.html

@@ -0,0 +1,17 @@
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
+    <style>
+      #demo {
+        padding: 0.5em;
+      }
+      .padded {
+        margin: 0.5em !important;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="demo"></div>
+    <script src="./demo.js"></script>
+  </body>
+</html>

+ 21 - 0
packages/ui-identicon/package.json

@@ -0,0 +1,21 @@
+{
+  "name": "@polkadot/ui-identicon",
+  "version": "0.17.0",
+  "description": "Renders an SVG picture representing an address",
+  "main": "index.js",
+  "scripts": {
+    "build": "polkadot-dev-build-babel",
+    "check": "eslint src && flow check",
+    "test": "echo \"Tests only available from root wrapper\""
+  },
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "license": "ISC",
+  "dependencies": {
+    "@babel/runtime": "^7.0.0-beta.47",
+    "color": "^3.0.0"
+  },
+  "devDependencies": {
+    "@polkadot/util-crypto": "^0.22.2",
+    "xmlserializer": "^0.6.1"
+  }
+}

+ 30 - 0
packages/ui-identicon/src/colors.js

@@ -0,0 +1,30 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Seeder } from './types';
+
+type ColorGen = {
+  (alpha?: number): string
+};
+
+const Color = require('color');
+
+const { COLORS } = require('./defaults');
+
+const WOBBLE = 30;
+
+module.exports = function colors (seeder: Seeder): ColorGen {
+  const amount = (seeder() * WOBBLE) - (WOBBLE / 2);
+  const all = COLORS.map((hex) => Color(hex).rotate(amount));
+
+  return (alpha: number = 0.9): string => {
+    const index = Math.floor(all.length * seeder());
+
+    return all.splice(index, 1)[0]
+      .alpha(alpha)
+      .string();
+  };
+};

+ 34 - 0
packages/ui-identicon/src/colors.spec.js

@@ -0,0 +1,34 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const newSeeder = require('./seeder');
+const newColors = require('./colors');
+
+describe('colors', () => {
+  let colors;
+
+  beforeEach(() => {
+    colors = newColors(newSeeder());
+  });
+
+  it('generates using default alpha', () => {
+    expect(
+      colors()
+    ).toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.9)');
+  });
+
+  it('applies specified alpha', () => {
+    expect(
+      colors(0.5)
+    ).toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.5)');
+  });
+
+  it('rolates colors', () => {
+    colors();
+
+    expect(
+      colors()
+    ).not.toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.9)');
+  });
+});

+ 30 - 0
packages/ui-identicon/src/container.js

@@ -0,0 +1,30 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+module.exports = function container (diameter: number, background: string = 'white', className: string = '', _style: { [string]: string } = {}): HTMLElement {
+  const element = document.createElement('div');
+  const style = Object.assign({
+    background,
+    borderRadius: `${diameter / 2}px`,
+    display: 'inline-block',
+    height: `${diameter}px`,
+    margin: '0px',
+    overflow: 'hidden',
+    padding: '0px',
+    width: `${diameter}px`
+  }, _style);
+
+  element.className = className;
+  element.style.background = background;
+
+  // FIXME Flow seems to believe element.style[key] has number keys
+  // flowlint-next-line unclear-type:off
+  Object.keys(style).forEach((key: any) => {
+    element.style[key] = style[key];
+  });
+
+  return element;
+};

+ 43 - 0
packages/ui-identicon/src/container.spec.js

@@ -0,0 +1,43 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const container = require('./container');
+
+describe('container', () => {
+  it('applies default styles', () => {
+    expect(
+      container(100).style._values
+    ).toMatchObject({
+      'background': 'white',
+      'border-radius': '50px',
+      'display': 'inline-block',
+      'height': '100px',
+      'margin': '0px',
+      'overflow': 'hidden',
+      'padding': '0px',
+      'width': '100px'
+    });
+  });
+
+  it('overrides with supplied styles', () => {
+    expect(
+      container(50, 'black', '', { display: 'block' }).style._values
+    ).toMatchObject({
+      'background': 'black',
+      'border-radius': '25px',
+      'display': 'block',
+      'height': '50px',
+      'margin': '0px',
+      'overflow': 'hidden',
+      'padding': '0px',
+      'width': '50px'
+    });
+  });
+
+  it('applies the specified className', () => {
+    expect(
+      container(100, 'blue', 'testClass').className
+    ).toEqual('testClass');
+  });
+});

+ 25 - 0
packages/ui-identicon/src/defaults.js

@@ -0,0 +1,25 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const COLORS: Array<string> = [
+  '#01888C', // teal
+  '#FC7500', // bright orange
+  '#034F5D', // dark teal
+  '#F73F01', // orangered
+  '#FC1960', // magenta
+  '#C7144C', // raspberry
+  '#F3C100', // goldenrod
+  '#1598F2', // lightning blue
+  '#2465E1', // sail blue
+  '#F19E02' // gold
+];
+
+const SHAPE_COUNT = 5;
+
+module.exports = {
+  COLORS,
+  SHAPE_COUNT
+};

+ 35 - 0
packages/ui-identicon/src/demo.js

@@ -0,0 +1,35 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const randomAsU8a = require('@polkadot/util-crypto/random/asU8a');
+
+const identicon = require('./index');
+
+const element = document.getElementById('demo');
+
+if (!element) {
+  throw new Error('Unable to find #demo element');
+}
+
+function generateIcon (seed: Uint8Array = randomAsU8a(32)): void {
+  const start = Date.now();
+
+  element.append(
+    identicon(seed, 100, 'padded')
+  );
+
+  console.log(`Icon generated in ${(Date.now() - start)}ms`);
+}
+
+function generateIcons (count: number = 512): void {
+  generateIcon(new Uint8Array(32));
+
+  for (let index = 1; index < count; index++) {
+    generateIcon();
+  }
+}
+
+generateIcons();

+ 32 - 0
packages/ui-identicon/src/index.js

@@ -0,0 +1,32 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const colors = require('./colors');
+const newContainer = require('./container');
+const newSeeder = require('./seeder');
+const newShape = require('./shape/circle');
+const newElement = require('./svg/element');
+const { SHAPE_COUNT } = require('./defaults');
+
+module.exports = function identicon (seed: string | Uint8Array, diameter: number = 256, className: string = '', style?: { [string]: string }): HTMLElement {
+  const seeder = newSeeder(seed);
+  const colorGen = colors(seeder);
+  const outer = newContainer(diameter, 'white', className, style);
+  const container = newContainer(diameter, colorGen());
+  const svg = newElement(diameter);
+
+  outer.appendChild(container);
+  container.appendChild(svg);
+
+  for (let count = 0; count < SHAPE_COUNT; count++) {
+    const fill = colorGen();
+    const shape = newShape(seeder, fill, diameter, count);
+
+    svg.appendChild(shape);
+  }
+
+  return outer;
+};

+ 25 - 0
packages/ui-identicon/src/index.spec.js

@@ -0,0 +1,25 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const xmlserializer = require('xmlserializer');
+
+const identicon = require('./index');
+
+describe('identicon', () => {
+  it('generates a basic [0,..,0] identicon', () => {
+    expect(
+      xmlserializer.serializeToString(
+        identicon(new Uint8Array(32))
+      )
+    ).toEqual('<div xmlns="http://www.w3.org/1999/xhtml" class="" style="background: white; border-radius: 128px; display: inline-block; height: 256px; margin: 0px; overflow: hidden; padding: 0px; width: 256px;"><div class="" style="border-radius: 128px; display: inline-block; height: 256px; margin: 0px; overflow: hidden; padding: 0px; width: 256px;"><svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="256" height="256"><circle cx="128" cy="140.8" r="128" fill="hsla(12.899999999999977, 100%, 49.4%, 0.9)"/><circle cx="128" cy="153.6" r="102.4" fill="hsla(174.29999999999995, 93.7%, 18.8%, 0.9)"/><circle cx="128" cy="166.4" r="76.8" fill="hsla(0.10000000000002274, 99.2%, 48.6%, 0.9)"/><circle cx="128" cy="179.2" r="51.2" fill="hsla(326.20000000000005, 97.4%, 54.3%, 0.9)"/><circle cx="128" cy="192" r="25.6" fill="hsla(326.20000000000005, 81.7%, 42.9%, 0.9)"/></svg></div></div>');
+  });
+
+  it('allows overrides', () => {
+    expect(
+      xmlserializer.serializeToString(
+        identicon(new Uint8Array(32), 100, 'testClass', { display: 'block' })
+      )
+    ).toEqual('<div xmlns="http://www.w3.org/1999/xhtml" class="testClass" style="background: white; border-radius: 50px; display: block; height: 100px; margin: 0px; overflow: hidden; padding: 0px; width: 100px;"><div class="" style="border-radius: 50px; display: inline-block; height: 100px; margin: 0px; overflow: hidden; padding: 0px; width: 100px;"><svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="100" height="100"><circle cx="50" cy="55" r="50" fill="hsla(12.899999999999977, 100%, 49.4%, 0.9)"/><circle cx="50" cy="60" r="40" fill="hsla(174.29999999999995, 93.7%, 18.8%, 0.9)"/><circle cx="50" cy="65" r="30" fill="hsla(0.10000000000002274, 99.2%, 48.6%, 0.9)"/><circle cx="50" cy="70" r="20" fill="hsla(326.20000000000005, 97.4%, 54.3%, 0.9)"/><circle cx="50" cy="75" r="10" fill="hsla(326.20000000000005, 81.7%, 42.9%, 0.9)"/></svg></div></div>');
+  });
+});

+ 35 - 0
packages/ui-identicon/src/seeder.js

@@ -0,0 +1,35 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Seeder } from './types';
+
+const isU8a = require('@polkadot/util/is/u8a');
+const u8aFromString = require('@polkadot/util/u8a/fromString');
+
+const DIVISOR = 256 * 256;
+
+module.exports = function seeder (_seed: string | Uint8Array = new Uint8Array(32)): Seeder {
+  // $FlowFixMe type checking
+  const seed: Uint8Array = isU8a(_seed)
+    ? _seed
+    // $FlowFixMe type has been determined
+    : u8aFromString(_seed);
+
+  let index = (seed[Math.floor(seed.length / 2)] % seed.length) - 1;
+
+  const next = () => {
+    index += 1;
+
+    if (index === seed.length) {
+      index = 0;
+    }
+
+    return seed[index];
+  };
+
+  return (): number => {
+    return ((next() * 256) + next()) / DIVISOR;
+  };
+};

+ 27 - 0
packages/ui-identicon/src/seeder.spec.js

@@ -0,0 +1,27 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const newSeeder = require('./seeder');
+
+describe('seeder', () => {
+  let seeder;
+
+  beforeEach(() => {
+    seeder = newSeeder(new Uint8Array([1, 2, 3, 4]));
+  });
+
+  it('generates numbers using 2 spaces', () => {
+    expect(
+      seeder()
+    ).toEqual(0.0156402587890625);
+  });
+
+  it('generates numbers using 2 spaces (incremented)', () => {
+    seeder();
+
+    expect(
+      seeder()
+    ).toEqual(0.0078582763671875);
+  });
+});

+ 24 - 0
packages/ui-identicon/src/shape/circle.js

@@ -0,0 +1,24 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Seeder } from '../types';
+
+const newCircle = require('../svg/circle');
+const { SHAPE_COUNT } = require('../defaults');
+
+module.exports = function circle (seeder: Seeder, fill: string, diameter: number, count: number): Element {
+  const center = diameter / 2;
+  const angle = seeder() * 360;
+  const radius = (((SHAPE_COUNT - count) / SHAPE_COUNT) * (diameter / 2)) + ((diameter / 8) * seeder());
+  const offset = (diameter / 4) * (seeder() + ((count + 1) / SHAPE_COUNT));
+  const cx = (offset * Math.sin(angle)) + center;
+  const cy = (offset * Math.cos(angle)) + center;
+  const svg = newCircle(radius, cx, cy);
+
+  svg.setAttributeNS(null, 'fill', fill);
+
+  return svg;
+};

+ 18 - 0
packages/ui-identicon/src/shape/circle.spec.js

@@ -0,0 +1,18 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const xmlserializer = require('xmlserializer');
+
+const seeder = require('../seeder');
+const circle = require('./circle');
+
+describe('circle', () => {
+  it('creates a circle shape', () => {
+    expect(
+      xmlserializer.serializeToString(
+        circle(seeder(), 'blue', 50, 2)
+      )
+    ).toEqual('<circle xmlns="http://www.w3.org/2000/svg" cx="25" cy="32.5" r="15" fill="blue"/>');
+  });
+});

+ 27 - 0
packages/ui-identicon/src/shape/square.js

@@ -0,0 +1,27 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Seeder } from '../types';
+
+const newRect = require('../svg/rect');
+const { SHAPE_COUNT } = require('../defaults');
+
+module.exports = function square (seeder: Seeder, fill: string, diameter: number, count: number): Element {
+  const center = diameter / 2;
+  const svg = newRect(diameter);
+  const firstRot = seeder();
+  const angle = Math.PI * 2 * firstRot;
+  const scale = count / SHAPE_COUNT;
+  const velocity = ((diameter / SHAPE_COUNT) * seeder()) + (scale * diameter);
+  const tx = (Math.cos(angle) * velocity).toFixed(3);
+  const ty = (Math.sin(angle) * velocity).toFixed(3);
+  const rot = ((firstRot * 360) + (seeder() * 180)).toFixed(1);
+
+  svg.setAttributeNS(null, 'transform', `translate(${tx} ${ty}) rotate(${rot} ${center} ${center})`);
+  svg.setAttributeNS(null, 'fill', fill);
+
+  return svg;
+};

+ 16 - 0
packages/ui-identicon/src/svg/circle.js

@@ -0,0 +1,16 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const svg = require('./svg');
+
+module.exports = function circle (r: number, cx: number, cy: number): Element {
+  const elem = svg('circle');
+
+  elem.setAttributeNS(null, 'cx', `${cx}`);
+  elem.setAttributeNS(null, 'cy', `${cy}`);
+  elem.setAttributeNS(null, 'r', `${r}`);
+
+  return elem;
+};

+ 17 - 0
packages/ui-identicon/src/svg/circle.spec.js

@@ -0,0 +1,17 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const xmlserializer = require('xmlserializer');
+
+const circle = require('./circle');
+
+describe('circle', () => {
+  it('creates a basic SVG circle element', () => {
+    expect(
+      xmlserializer.serializeToString(
+        circle(123, 12, 34)
+      )
+    ).toEqual('<circle xmlns="http://www.w3.org/2000/svg" cx="12" cy="34" r="123"/>');
+  });
+});

+ 18 - 0
packages/ui-identicon/src/svg/element.js

@@ -0,0 +1,18 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const svg = require('./svg');
+
+module.exports = function element (size: number, type: string = 'svg', x: number = 0, y: number = 0): Element {
+  const elem = svg(type);
+
+  elem.setAttributeNS(null, 'x', `${x}`);
+  elem.setAttributeNS(null, 'y', `${y}`);
+  elem.setAttributeNS(null, 'width', `${size}`);
+  elem.setAttributeNS(null, 'height', `${size}`);
+
+  return elem;
+};

+ 17 - 0
packages/ui-identicon/src/svg/element.spec.js

@@ -0,0 +1,17 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const xmlserializer = require('xmlserializer');
+
+const element = require('./element');
+
+describe('element', () => {
+  it('creates a basic SVG element', () => {
+    expect(
+      xmlserializer.serializeToString(
+        element(123)
+      )
+    ).toEqual('<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="123" height="123"/>');
+  });
+});

+ 15 - 0
packages/ui-identicon/src/svg/rect.js

@@ -0,0 +1,15 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const element = require('./element');
+
+module.exports = function rect (size: number): Element {
+  const elem = element(size, 'rect');
+
+  elem.setAttributeNS(null, 'rx', `${size / 16}`);
+  elem.setAttributeNS(null, 'ry', `${size / 16}`);
+
+  return elem;
+};

+ 17 - 0
packages/ui-identicon/src/svg/rect.spec.js

@@ -0,0 +1,17 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const xmlserializer = require('xmlserializer');
+
+const rect = require('./rect');
+
+describe('rect', () => {
+  it('creates a basic SVG rect element', () => {
+    expect(
+      xmlserializer.serializeToString(
+        rect(123)
+      )
+    ).toEqual('<rect xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="123" height="123" rx="7.6875" ry="7.6875"/>');
+  });
+});

+ 11 - 0
packages/ui-identicon/src/svg/svg.js

@@ -0,0 +1,11 @@
+// Copyright 2016 Dan Finlay
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+const SVG_NS = 'http://www.w3.org/2000/svg';
+
+module.exports = function svg (type: string): Element {
+  return document.createElementNS(SVG_NS, type);
+};

+ 17 - 0
packages/ui-identicon/src/svg/svg.spec.js

@@ -0,0 +1,17 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const xmlserializer = require('xmlserializer');
+
+const svg = require('./svg');
+
+describe('svg', () => {
+  it('creates a basic SVG element', () => {
+    expect(
+      xmlserializer.serializeToString(
+        svg('rect')
+      )
+    ).toEqual('<rect xmlns="http://www.w3.org/2000/svg"/>');
+  });
+});

+ 6 - 0
packages/ui-identicon/src/types.js

@@ -0,0 +1,6 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+export type Seeder = () => number;

+ 26 - 0
packages/ui-identicon/webpack.config.js

@@ -0,0 +1,26 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+module.exports = {
+  context: __dirname,
+  entry: './src/demo.js',
+  mode: 'development',
+  output: {
+    path: __dirname,
+    filename: './demo.js'
+  },
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        exclude: /(node_modules)/,
+        use: [
+          'babel-loader'
+        ]
+      }
+    ]
+  },
+  plugins: []
+};

+ 2 - 2
packages/ui-keyring/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/ui-keyring",
-  "version": "0.7.2",
+  "version": "0.17.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@babel/runtime": "^7.0.0-beta.47",
-    "@polkadot/ui-react": "^0.14.3",
+    "@polkadot/ui-react": "^0.17.0",
     "@polkadot/util-keyring": "^0.22.2",
     "store": "^2.0.12"
   },

+ 15 - 0
packages/ui-react-rx/LICENSE

@@ -0,0 +1,15 @@
+ISC License (ISC)
+
+Copyright 2017-2018 Jaco Greeff <jacogr@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.

+ 46 - 0
packages/ui-react-rx/README.md

@@ -0,0 +1,46 @@
+[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
+![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
+[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
+[![npm](https://img.shields.io/npm/v/@polkadot/ui-react-rx.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/ui-react-rx)
+[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/ui)
+[![maintainability](https://img.shields.io/codeclimate/maintainability/polkadot-js/apps.svg?style=flat-square)](https://codeclimate.com/github/polkadot-js/apps/maintainability)
+[![coverage](https://img.shields.io/coveralls/polkadot-js/apps.svg?style=flat-square)](https://coveralls.io/github/polkadot-js/apps?branch=master)
+[![dependency](https://david-dm.org/polkadot-js/apps.svg?style=flat-square&path=packages/ui-react-rx)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react-rx)
+[![devDependency](https://david-dm.org/polkadot-js/apps/dev-status.svg?style=flat-square&path=packages/ui-react-rx)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react-rx#info=devDependencies)
+
+# @polkadot/ui-react-rx
+
+A collection of RxJS enabled React components that operate with the [@polkadot/api-rx](https://github.com/polkadot-js/api) library. It automatically manages subscriptions on behalf of the developer, providing a number of unstyled components that can be used to construct UIs.
+
+## usage
+
+Basic usage entails creating a `ContextProvider` and just using the components. For instance, to display the current node time,
+
+```js
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import { Api, NodeTime } from '@polkadot/ui-react-rx';
+
+...
+ReactDOM.render(
+  <Api>
+    <NodeTime />
+  </Api>,
+  document.querySelector('#container')
+);
+...
+```
+
+All components are provided unstyled making no assumptions on the actual use, however they all support (optionally) the `label`, `className` and `style` attributes, that can be used to style to component.
+
+```js
+...
+ReactDOM.render(
+  <Api>
+    <NodeTime className='rx-time' label='current node time:' />
+  </Api>,
+  document.querySelector('#container')
+);
+...
+```

+ 14 - 0
packages/ui-react-rx/index.html

@@ -0,0 +1,14 @@
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
+    <style>
+      #demo {
+        padding: 0.5em;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="demo"></div>
+    <script src="./demo.js"></script>
+  </body>
+</html>

+ 50 - 0
packages/ui-react-rx/package.json

@@ -0,0 +1,50 @@
+{
+  "name": "@polkadot/ui-react-rx",
+  "version": "0.17.0",
+  "description": "A collection of RxJs React components the Polkadot JS API",
+  "main": "index.js",
+  "keywords": [
+    "Polkadot",
+    "React",
+    "RxJs"
+  ],
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "license": "ISC",
+  "engines": {
+    "node": ">=8.0"
+  },
+  "publishConfig": {
+    "access": "public",
+    "registry": "https://registry.npmjs.org"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/polkadot-js/ui.git"
+  },
+  "bugs": {
+    "url": "https://github.com/polkadot-js/ui/issues"
+  },
+  "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-react-rx#readme",
+  "scripts": {
+    "build": "polkadot-dev-build-babel",
+    "check": "eslint src && flow check",
+    "test": "echo \"Tests only available from root wrapper\""
+  },
+  "dependencies": {
+    "@babel/runtime": "^7.0.0-beta.47",
+    "@polkadot/api-rx": "^0.13.2",
+    "@polkadot/extrinsics-codec": "^0.17.2",
+    "@polkadot/storage": "^0.17.2",
+    "semver": "^5.5.0"
+  },
+  "devDependencies": {
+    "babel-loader": "^8.0.0-beta.2",
+    "enzyme": "^3.3.0",
+    "enzyme-adapter-react-16": "^1.1.1",
+    "react": "^16.4.0",
+    "react-dom": "^16.4.0"
+  },
+  "peerDependencies": {
+    "react": "^16.4.0"
+  }
+}

+ 134 - 0
packages/ui-react-rx/src/Api.js

@@ -0,0 +1,134 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { ProviderInterface } from '@polkadot/api-provider/types';
+import type { RxApiInterface } from '@polkadot/api-rx/types';
+import type { EncodingVersions } from '@polkadot/extrinsics-codec/types';
+import type { ApiProps } from './types';
+
+import React from 'react';
+import semver from 'semver';
+import createWsProvider from '@polkadot/api-provider/ws';
+import createApi from '@polkadot/api-rx';
+import defaults from '@polkadot/api-rx/defaults';
+
+type Props = {
+  api?: RxApiInterface,
+  children: React$Node,
+  provider?: ProviderInterface,
+  url?: string
+};
+
+type State = ApiProps & {
+  subscriptions: Array<rxjs$ISubscription | null>;
+};
+
+const { Consumer, Provider } = React.createContext();
+
+function apiSupport (version?: string): EncodingVersions {
+  return version === undefined || semver.lt(version, '0.2.0')
+    ? 'poc-1'
+    : 'latest';
+}
+
+export default class Api extends React.Component<Props, State> {
+  static Provider = Provider;
+  static Consumer = Consumer
+
+  state: State;
+
+  constructor (props: Props) {
+    super(props);
+
+    const { provider, url = '' } = props;
+    const api = props.api || createApi(
+      url && url.length
+        ? createWsProvider(url)
+        : provider
+    );
+    const setApi = (api: RxApiInterface): void => {
+      this.setState({ api }, () => {
+        this.updateSubscriptions();
+      });
+    };
+    const setApiProvider = (provider?: ProviderInterface): void =>
+      setApi(createApi(provider));
+    const setApiWsUrl = (url?: string = defaults.WS_URL): void =>
+      setApiProvider(createWsProvider(url));
+
+    this.state = {
+      api,
+      apiConnected: false,
+      apiSupport: 'poc-1',
+      setApi,
+      setApiProvider,
+      setApiWsUrl,
+      subscriptions: []
+    };
+  }
+
+  componentDidMount () {
+    this.updateSubscriptions();
+  }
+
+  componentWillUnmount () {
+    this.unsubscribe();
+  }
+
+  updateSubscriptions () {
+    const { api } = this.state;
+
+    this.unsubscribe();
+    this.setState({
+      subscriptions:
+        [
+          () => api.isConnected().subscribe((isConnected?: boolean) => {
+            this.setState({ apiConnected: !!isConnected });
+          }),
+          () => api.system.version().subscribe((version?: string) => {
+            this.setState({ apiSupport: apiSupport(version) });
+          })
+        ].map((fn: () => rxjs$ISubscription): rxjs$ISubscription | null => {
+          try {
+            return fn();
+          } catch (error) {
+            console.error(error);
+            return null;
+          }
+        })
+    });
+  }
+
+  unsubscribe (): void {
+    const { subscriptions } = this.state;
+
+    subscriptions.forEach((subscription) => {
+      if (subscription) {
+        try {
+          subscription.unsubscribe();
+        } catch (error) {
+          console.error(error);
+        }
+      }
+    });
+  }
+
+  render () {
+    const { api, apiConnected, apiSupport, setApi, setApiProvider, setApiWsUrl } = this.state;
+
+    return (
+      <Provider value={{
+        api,
+        apiConnected,
+        apiSupport,
+        setApi,
+        setApiProvider,
+        setApiWsUrl
+      }}>
+        {this.props.children}
+      </Provider>
+    );
+  }
+}

+ 14 - 0
packages/ui-react-rx/src/Balance.js

@@ -0,0 +1,14 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import storage from '@polkadot/storage';
+
+import numberFormat from './util/numberFormat';
+import withStorageDiv from './with/storageDiv';
+
+export default withStorageDiv(storage.staking.public.freeBalanceOf)(
+  numberFormat,
+  { className: 'rx--Balance' }
+);

+ 20 - 0
packages/ui-react-rx/src/BestNumber.js

@@ -0,0 +1,20 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Header } from '@polkadot/primitives/header';
+
+import rpcs from '@polkadot/jsonrpc';
+
+import numberFormat from './util/numberFormat';
+import withApiDiv from './with/apiDiv';
+
+export default withApiDiv(rpcs.chain.public.newHead)(
+  (value?: Header): string => {
+    return value && value.number
+      ? numberFormat(value.number)
+      : 'unknown';
+  },
+  { className: 'rx--BestNumber' }
+);

+ 14 - 0
packages/ui-react-rx/src/Chain.js

@@ -0,0 +1,14 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import rpcs from '@polkadot/jsonrpc';
+
+import withApiDiv from './with/apiDiv';
+
+export default withApiDiv(rpcs.system.public.chain)(
+  (value: string = 'unknown'): string =>
+    value,
+  { className: 'rx--Chain' }
+);

+ 15 - 0
packages/ui-react-rx/src/Connected.js

@@ -0,0 +1,15 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import withApiDiv from './with/apiDiv';
+
+export default withApiDiv({ name: 'isConnected' })(
+  (value?: boolean = false): string => {
+    return value
+      ? 'connected'
+      : 'disconnected';
+  },
+  { className: 'rx--Connected' }
+);

+ 30 - 0
packages/ui-react-rx/src/Div.js

@@ -0,0 +1,30 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { BaseProps, BareProps } from './types';
+
+import React from 'react';
+
+type Props<T> = BaseProps<T>;
+
+// FIXME Not 100% on how to push through the generic type when used
+// flowlint-next-line unclear-type: off
+const echoFormatter = (value?: any): any =>
+  value;
+
+export default function Div<T> ({ children, className = 'rx--Div', render = echoFormatter, rxUpdated = false, label = '', style, value }: Props<T>): React$Node {
+  const props: BareProps = {
+    className: [className, rxUpdated ? 'rx--updated' : undefined]
+      .filter((c) => c)
+      .join(' ') || undefined,
+    style
+  };
+
+  return (
+    <div {...props}>
+      {label}{render(value) || ''}{children}
+    </div>
+  );
+}

+ 14 - 0
packages/ui-react-rx/src/NodeName.js

@@ -0,0 +1,14 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import rpcs from '@polkadot/jsonrpc';
+
+import withApiDiv from './with/apiDiv';
+
+export default withApiDiv(rpcs.system.public.name)(
+  (value: string = 'unknown'): string =>
+    value,
+  { className: 'rx--NodeName' }
+);

+ 19 - 0
packages/ui-react-rx/src/NodeTime.js

@@ -0,0 +1,19 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import storage from '@polkadot/storage';
+
+import withStorageDiv from './with/storageDiv';
+
+export default withStorageDiv(storage.timestamp.public.current)(
+  (value?: Date): string => {
+    if (!value || value.getTime() === 0) {
+      return 'unknown';
+    }
+
+    return value.toString();
+  },
+  { className: 'rx--NodeTime' }
+);

+ 14 - 0
packages/ui-react-rx/src/NodeVersion.js

@@ -0,0 +1,14 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import rpcs from '@polkadot/jsonrpc';
+
+import withApiDiv from './with/apiDiv';
+
+export default withApiDiv(rpcs.system.public.version)(
+  (value: string = 'unknown'): string =>
+    value,
+  { className: 'rx--NodeVersion' }
+);

+ 14 - 0
packages/ui-react-rx/src/Nonce.js

@@ -0,0 +1,14 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import storage from '@polkadot/storage';
+
+import numberFormat from './util/numberFormat';
+import withStorageDiv from './with/storageDiv';
+
+export default withStorageDiv(storage.system.public.accountIndexOf)(
+  numberFormat,
+  { className: 'rx--Nonce' }
+);

+ 55 - 0
packages/ui-react-rx/src/demo.js

@@ -0,0 +1,55 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import mockProvider from '@polkadot/api-provider/mock';
+
+import Api from './Api';
+import Balance from './Balance';
+import BestNumber from './BestNumber';
+import Chain from './Chain';
+import Connected from './Connected';
+import NodeName from './NodeName';
+import NodeTime from './NodeTime';
+import NodeVersion from './NodeVersion';
+import Nonce from './Nonce';
+import withApi from './with/api';
+
+const provider = mockProvider();
+
+const ALICE_ADDR = '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ';
+const ALICE_PUBLIC = '0xd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f';
+
+const element = document.getElementById('demo');
+
+if (!element) {
+  throw new Error('Unable to find #demo element');
+}
+
+const WithApiDebug = withApi(
+  (apiProps) => (
+    <pre style={{ background: '#f5f5f5', padding: '0.5em' }}>
+      {JSON.stringify(apiProps, undefined, 2)}
+    </pre>
+  )
+);
+
+ReactDOM.render(
+  <Api provider={provider}>
+    <Connected label='status =' />
+    <NodeName label='name =' />
+    <NodeVersion label='version =' />
+    <Chain label='chain =' />
+    <br />
+    <BestNumber label='best block #' />
+    <Balance label='balance =' params={[ALICE_PUBLIC]} />
+    <Nonce label='nonce =' params={ALICE_ADDR} />
+    <NodeTime label='node time =' />
+    <br />
+    <WithApiDebug />
+  </Api>,
+  element
+);

+ 30 - 0
packages/ui-react-rx/src/index.js

@@ -0,0 +1,30 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import Api from './Api';
+import Balance from './Balance';
+import BestNumber from './BestNumber';
+import Chain from './Chain';
+import Connected from './Connected';
+import Div from './Div';
+import NodeName from './NodeName';
+import NodeTime from './NodeTime';
+import NodeVersion from './NodeVersion';
+import Nonce from './Nonce';
+
+export * from './with';
+
+export {
+  Api,
+  Balance,
+  BestNumber,
+  Chain,
+  Connected,
+  Div,
+  NodeName,
+  NodeTime,
+  NodeVersion,
+  Nonce
+};

+ 48 - 0
packages/ui-react-rx/src/types.js

@@ -0,0 +1,48 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { ProviderInterface } from '@polkadot/api-provider/types';
+import type { RxApiInterface } from '@polkadot/api-rx/types';
+import type { EncodingVersions } from '@polkadot/extrinsics-codec/types';
+import type { Storage$Key$Values } from '@polkadot/storage/types';
+
+export type BareProps = {
+  className?: string,
+  style?: { [string]: mixed }
+};
+
+export type ApiProps = {
+  api: RxApiInterface,
+  apiConnected: boolean,
+  apiSupport: EncodingVersions,
+  setApi: (api: RxApiInterface) => void,
+  setApiProvider: (provider?: ProviderInterface) => void,
+  setApiWsUrl: (url?: string) => void
+};
+
+export type OnChangeCb<T> = rxjs$Subject<T> | (value: T) => void;
+
+export type ChangeProps<T> = {
+  onChange?: OnChangeCb<T>
+};
+
+export type ParamProps = {
+  params?: Storage$Key$Values
+};
+
+export type RxProps<T> = {
+  rxUpdated?: boolean;
+  rxUpdatedAt?: number;
+  value?: T;
+}
+
+export type BaseProps<T> = BareProps & ApiProps & ChangeProps<T> & ParamProps & RxProps<T> & {
+  children?: React$Node,
+  label?: string,
+  render?: (value?: T) => React$Node
+};
+
+// flowlint-next-line unclear-type:off
+export type Formatter = (value?: any) => string;

+ 22 - 0
packages/ui-react-rx/src/util/intervalSubscribe.js

@@ -0,0 +1,22 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { RxProps } from '../types';
+
+import { interval } from 'rxjs/observable/interval';
+
+const interval$ = interval(500);
+
+export default function intervalSubscribe<T, Props, State: RxProps<T>> (that: React$Component<Props, State>): rxjs$ISubscription {
+  return interval$.subscribe(() => {
+    const rxUpdated = (Date.now() - (that.state.rxUpdatedAt || 0)) <= 1500;
+
+    if (rxUpdated !== that.state.rxUpdated) {
+      that.setState({
+        rxUpdated
+      });
+    }
+  });
+}

+ 8 - 0
packages/ui-react-rx/src/util/isEqual.js

@@ -0,0 +1,8 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+export default function isEqual <T> (a?: T, b?: T): boolean {
+  return JSON.stringify({ test: a }) === JSON.stringify({ test: b });
+}

+ 16 - 0
packages/ui-react-rx/src/util/numberFormat.js

@@ -0,0 +1,16 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type BN from 'bn.js';
+
+const NUMBER_REGEX = new RegExp('(\\d+?)(?=(\\d{3})+(?!\\d)|$)', 'g');
+
+export default function numberFormat (value?: BN | number): string {
+  if (value === undefined) {
+    return '0';
+  }
+
+  return (value.toString().match(NUMBER_REGEX) || []).join(',');
+}

+ 25 - 0
packages/ui-react-rx/src/util/triggerChange.js

@@ -0,0 +1,25 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { OnChangeCb } from '../types';
+
+import isFunction from '@polkadot/util/is/function';
+import isObservable from '@polkadot/util/is/observable';
+
+export default function triggerChange<T> (value?: T, ...onChange: Array<?OnChangeCb<T>>): void {
+  if (!onChange || !onChange.length) {
+    return;
+  }
+
+  onChange.forEach((onChange) => {
+    if (isObservable(onChange)) {
+      // $FlowFixMe type determined
+      onChange.next(value);
+    } else if (isFunction(onChange)) {
+      // $FlowFixMe type determined
+      onChange(value);
+    }
+  });
+}

+ 45 - 0
packages/ui-react-rx/src/with/api.js

@@ -0,0 +1,45 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { ApiProps, BareProps, ChangeProps } from '../types';
+import type { DefaultProps } from './types';
+
+import React from 'react';
+import assert from '@polkadot/util/assert';
+
+import Api from '../Api';
+
+type InProps<T> = BareProps & ChangeProps<T>;
+
+type OutProps<T> = InProps<T> & ApiProps;
+
+export default function withApi<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>> (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps>> {
+  return class WithApi extends React.Component<InputProps> {
+    constructor (props: InputProps) {
+      super(props);
+
+      assert(Component, `Expected 'withApi' to wrap a React Component`);
+    }
+
+    render () {
+      return (
+        <Api.Consumer>
+          {(apiProps?: ApiProps): React$Node => {
+            assert(apiProps && apiProps.api, `Application root must be wrapped inside 'rx-react/Api' to provide API context`);
+
+            return (
+              // flowlint-next-line inexact-spread:off
+              <Component
+                {...defaultProps}
+                {...apiProps}
+                {...this.props}
+              />
+            );
+          }}
+        </Api.Consumer>
+      );
+    }
+  };
+}

+ 74 - 0
packages/ui-react-rx/src/with/apiCall.js

@@ -0,0 +1,74 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { RxApiInterface$Method } from '@polkadot/api-rx/types';
+import type { ApiProps, BareProps, ChangeProps, ParamProps, RxProps } from '../types';
+import type { ApiMethod, HOC, Options, DefaultProps } from './types';
+
+import React from 'react';
+import assert from '@polkadot/util/assert';
+
+import isEqual from '../util/isEqual';
+import withObservable from './observable';
+import withApi from './api';
+
+type InProps<T> = BareProps & ChangeProps<T> & ParamProps;
+
+type WithApiProps<T> = InProps<T> & ApiProps;
+
+type OutProps<T> = WithApiProps<T> & RxProps<T>;
+
+type State<ComponentProps> = ParamProps & {
+  Component: React$ComponentType<ComponentProps>,
+  fn: RxApiInterface$Method
+}
+
+export default function withApiCall<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>, InputApiProps: WithApiProps<T>> ({ name, section }: ApiMethod, options?: Options<T>): HOC<T> {
+  return (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps>> => {
+    class WithApiCall extends React.Component<InputApiProps, State<InputApiProps>> {
+      state: State<InputApiProps>;
+
+      constructor (props: InputApiProps) {
+        super(props);
+
+        const hasSection = !!section;
+
+        assert(!hasSection || props.api[section], `Unable to find 'api.${section}'`);
+
+        const fn: RxApiInterface$Method = hasSection
+          ? props.api[section][name]
+          // $FlowFixMe method check to be done
+          : ((props.api[name]: any): RxApiInterface$Method);
+
+        assert(fn, `Unable to find 'api${hasSection ? '.' : ''}${section || ''}.${name}'`);
+
+        this.state = ({ fn }: $Shape<State<InputApiProps>>);
+      }
+
+      static getDerivedStateFromProps ({ params = [] }: InputApiProps, prevState: State<ComponentProps>): $Shape<State<InputApiProps>> | null {
+        if (isEqual(params, prevState.params)) {
+          return null;
+        }
+
+        const observable = prevState.fn.apply(null, params);
+
+        return {
+          Component: withObservable(observable, options)(Component, defaultProps),
+          params
+        };
+      }
+
+      render () {
+        const { Component } = this.state;
+
+        return (
+          <Component {...this.props} />
+        );
+      }
+    }
+
+    return withApi(WithApiCall);
+  };
+}

+ 18 - 0
packages/ui-react-rx/src/with/apiDiv.js

@@ -0,0 +1,18 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { BaseProps } from '../types';
+import type { ApiMethod, ComponentRenderer, DefaultProps, RenderFn, Options } from './types';
+
+import Div from '../Div';
+import withApiCall from './apiCall';
+
+export default function withApiDiv<T, Props: BaseProps<T>> (call: ApiMethod, options?: Options<T>): ComponentRenderer<T> {
+  return (render: RenderFn, defaultProps?: DefaultProps<T> = {}): React$ComponentType<Props> =>
+    withApiCall(call, options)(Div, {
+      ...defaultProps,
+      render
+    });
+}

+ 18 - 0
packages/ui-react-rx/src/with/index.js

@@ -0,0 +1,18 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import withApi from './api';
+import withApiCall from './apiCall';
+import withObservable from './observable';
+import withObservableParams from './observableParams';
+import withStorage from './storage';
+
+export {
+  withApi,
+  withApiCall,
+  withObservable,
+  withObservableParams,
+  withStorage
+};

+ 101 - 0
packages/ui-react-rx/src/with/observable.js

@@ -0,0 +1,101 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+// TODO: This is now way more messy and way longer than it should be. Maintainability is lacking - apply some effort and split it into managable parts
+
+import type { BareProps, ChangeProps, RxProps } from '../types';
+import type { HOC, Options, DefaultProps } from './types';
+
+import React from 'react';
+import { map } from 'rxjs/operators/map';
+
+import assert from '@polkadot/util/assert';
+
+import intervalSubscribe from '../util/intervalSubscribe';
+import isEqual from '../util/isEqual';
+import triggerChange from '../util/triggerChange';
+import echoTransform from './transform/echo';
+
+type InProps<T> = BareProps & ChangeProps<T>;
+
+type OutProps<T> = InProps<T> & RxProps<T>;
+
+type State<T> = RxProps<T> & {
+  subscriptions: Array<rxjs$ISubscription>;
+}
+
+export default function withObservable<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>> (observable: rxjs$Observable<T> | rxjs$Subject<T>, { onChange, propName = 'value', transform = echoTransform }: Options<T> = {}): HOC<T> {
+  return (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps, State<T>>> =>
+    class WithObservable extends React.Component<InputProps, State<T>> {
+      state: State<T>;
+
+      constructor (props: InputProps) {
+        super(props);
+
+        assert(observable, `Component should have Observable to wrap`);
+        assert(Component, `Expected 'with*' to wrap a React Component`);
+
+        this.state = {
+          rxUpdated: false,
+          rxUpdatedAt: 0,
+          subscriptions: [],
+          value: void 0
+        };
+      }
+
+      componentDidMount () {
+        const subscriptions: Array<rxjs$ISubscription> = [
+          observable
+            .pipe(map(transform))
+            .subscribe((value) => {
+              this.triggerUpdate(this.props, value);
+            }),
+          intervalSubscribe(this)
+        ];
+
+        this.setState({
+          subscriptions
+        });
+      }
+
+      componentWillUnmount () {
+        this.state.subscriptions.forEach((subscription) => {
+          subscription.unsubscribe();
+        });
+      }
+
+      triggerUpdate = (props: InProps<T>, value?: T): void => {
+        if (isEqual(value, this.state.value)) {
+          return;
+        }
+
+        triggerChange(value, onChange, props.onChange || defaultProps.onChange);
+
+        this.setState({
+          rxUpdated: true,
+          rxUpdatedAt: Date.now(),
+          value
+        });
+      }
+
+      render () {
+        const { rxUpdated, rxUpdatedAt, value } = this.state;
+        // flowlint-next-line inexact-spread:off
+        const _props = {
+          ...defaultProps,
+          ...this.props,
+          rxUpdated,
+          rxUpdatedAt,
+          [propName]: value
+        };
+
+        delete _props.onChange;
+
+        return (
+          <Component {..._props} />
+        );
+      }
+    };
+}

+ 16 - 0
packages/ui-react-rx/src/with/observableParams.js

@@ -0,0 +1,16 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { HOC, Options } from './types';
+
+import withObservable from './observable';
+
+export default function withObservableParams<T> (observable: rxjs$Observable<T> | rxjs$Subject<T>, options?: Options<T> = {}): HOC<T> {
+  return withObservable(observable, {
+    propName: 'params',
+    // flowlint-next-line inexact-spread:off
+    ...options
+  });
+}

+ 156 - 0
packages/ui-react-rx/src/with/storage.js

@@ -0,0 +1,156 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+// TODO: Lots of duplicated code between this and withObservable, surely there ois a better way of doing this?
+
+import type { Storage$Key, Storage$Key$Values } from '@polkadot/storage/types';
+import type { ApiProps, BareProps, ChangeProps, ParamProps, RxProps } from '../types';
+import type { HOC, StorageOptions, DefaultProps, Transform } from './types';
+
+import React from 'react';
+import { map } from 'rxjs/operators/map';
+
+import storageKey from '@polkadot/storage/key';
+
+import intervalSubscribe from '../util/intervalSubscribe';
+import isEqual from '../util/isEqual';
+import triggerChange from '../util/triggerChange';
+import echoTransform from './transform/echo';
+import createTransform from './transform/storage';
+import withApi from './api';
+
+type InProps<T> = BareProps & ChangeProps<T> & ParamProps & {
+  transform?: Transform
+};
+
+type InApiProps<T> = ApiProps & InProps<T>;
+
+type OutProps<T> = InApiProps<T> & RxProps<T>;
+
+type State<T> = RxProps<T> & {
+  subscriptions: Array<rxjs$ISubscription>;
+}
+
+export default function withStorage<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>, InputApiProps: InApiProps<T>> (key: Storage$Key, { onChange, params, propName = 'value', transform }: StorageOptions<T> = {}): HOC<T> {
+  const keyCreator = storageKey(key);
+  const storageTransform = createTransform(key);
+  const createKey = (propParams?: Storage$Key$Values): Uint8Array => {
+    const values = [params, propParams].reduce((result, input = []) => {
+      if (Array.isArray(input)) {
+        return result.concat(input);
+      }
+
+      result.push(input);
+      return result;
+    }, []);
+
+    return keyCreator.apply(null, values);
+  };
+
+  return (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps>> => {
+    class WithStorage extends React.Component<InputApiProps, State<T>> {
+      state: State<T>;
+
+      constructor (props: InputApiProps) {
+        super(props);
+
+        this.state = {
+          rxUpdated: false,
+          rxUpdatedAt: 0,
+          subscriptions: [],
+          value: void 0
+        };
+      }
+
+      componentDidUpdate (prevProps: InputApiProps) {
+        if (!isEqual(this.props.params, prevProps.params)) {
+          this.triggerUpdate();
+        }
+      }
+
+      componentDidMount () {
+        const subscriptions: Array<rxjs$ISubscription> = [
+          this.props.api.chain
+            .newHead()
+            .subscribe((value) => {
+              this.triggerUpdate();
+            }),
+          intervalSubscribe(this)
+        ];
+
+        this.setState({
+          subscriptions
+        });
+      }
+
+      componentWillUnmount () {
+        this.state.subscriptions.forEach((subscription) => {
+          subscription.unsubscribe();
+        });
+      }
+
+      triggerUpdate = async (): Promise<void> => {
+        // flowlint-next-line unclear-type:off
+        const mapping = (value: Uint8Array, index: number): any => {
+          const outer = this.props.transform || echoTransform;
+          const inner = transform || echoTransform;
+
+          return outer(
+            inner(
+              storageTransform(value, index),
+              index
+            ),
+            index
+          );
+        };
+
+        const key = createKey(this.props.params);
+        let value;
+
+        try {
+          value = await this.props.api.state
+            .getStorage(key)
+            .pipe(map(mapping))
+            .toPromise();
+        } catch (error) {
+          // swallow?
+          // console.error(error);
+        }
+
+        if (isEqual(value, this.state.value)) {
+          return;
+        }
+
+        triggerChange(value, onChange, this.props.onChange || defaultProps.onChange);
+
+        this.setState({
+          rxUpdated: true,
+          rxUpdatedAt: Date.now(),
+          value
+        });
+      }
+
+      render () {
+        const { rxUpdated, rxUpdatedAt, value } = this.state;
+        // flowlint-next-line inexact-spread:off
+        const _props = {
+          ...defaultProps,
+          ...this.props,
+          rxUpdated,
+          rxUpdatedAt,
+          [propName]: value
+        };
+
+        delete _props.onChange;
+
+        return (
+          <Component {..._props} />
+        );
+      }
+    }
+
+    return withApi(WithStorage);
+  };
+}

+ 19 - 0
packages/ui-react-rx/src/with/storageDiv.js

@@ -0,0 +1,19 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Storage$Key } from '@polkadot/storage/types';
+import type { BaseProps } from '../types';
+import type { ComponentRenderer, DefaultProps, RenderFn, StorageOptions } from './types';
+
+import Div from '../Div';
+import withStorage from './storage';
+
+export default function withStorageDiv<T, Props: BaseProps<T>> (key: Storage$Key, options?: StorageOptions<T>): ComponentRenderer<T> {
+  return (render: RenderFn, defaultProps?: DefaultProps<T> = {}): React$ComponentType<Props> =>
+    withStorage(key, options)(Div, {
+      ...defaultProps,
+      render
+    });
+}

+ 8 - 0
packages/ui-react-rx/src/with/transform/echo.js

@@ -0,0 +1,8 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+export default function echoTransform <T> (x: T, index: number): T {
+  return x;
+}

+ 21 - 0
packages/ui-react-rx/src/with/transform/storage.js

@@ -0,0 +1,21 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Param$Values } from '@polkadot/params/types';
+import type { Storage$Key } from '@polkadot/storage/types';
+import type { StorageTransform } from '../types';
+
+import decodeParams from '@polkadot/params/decode';
+
+export default function storageTransform ({ type }: Storage$Key): StorageTransform {
+  return (input?: Uint8Array = new Uint8Array(0), index: number): Param$Values | null => {
+    try {
+      return decodeParams(type, input).value;
+    } catch (error) {
+      console.error('Decoding', type, '::', error);
+      return null;
+    }
+  };
+}

+ 42 - 0
packages/ui-react-rx/src/with/types.js

@@ -0,0 +1,42 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Interface$Method } from '@polkadot/jsonrpc/types';
+import type { Param$Values } from '@polkadot/params/types';
+import type { Storage$Key$Values } from '@polkadot/storage/types';
+import type { OnChangeCb } from '../types';
+
+import React from 'react';
+
+// flowlint-next-line unclear-type:off
+export type Transform = (value: any, index: number) => any;
+
+export type DefaultProps<T> = {
+  onChange?: OnChangeCb<T>,
+  [string]: mixed
+};
+
+export type Options<T> = {
+  onChange?: OnChangeCb<T>,
+  propName?: string,
+  transform?: Transform
+};
+
+export type StorageTransform = (value?: Uint8Array, index: number) => Param$Values | null;
+
+export type StorageOptions<T> = Options<T> & {
+  params?: Storage$Key$Values;
+};
+
+// flowlint-next-line unclear-type:off
+export type HOC<T> = (Component: React$ComponentType<any>, defaultProps?: DefaultProps<T>) => Class<React.Component<any, any>>;
+
+export type ApiMethod = $Shape<Interface$Method>;
+
+// flowlint-next-line unclear-type:off
+export type RenderFn = (value?: any) => any;
+
+// flowlint-next-line unclear-type:off
+export type ComponentRenderer<T> = (render: RenderFn, defaultProps?: DefaultProps<T>) => React$ComponentType<any>;

+ 12 - 0
packages/ui-react-rx/test/enzyme.js

@@ -0,0 +1,12 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const Adapter = require('enzyme-adapter-react-16');
+const Enzyme = require('enzyme');
+
+Enzyme.configure({
+  adapter: new Adapter()
+});
+
+module.exports = Enzyme;

+ 15 - 0
packages/ui-react-rx/test/observable.js

@@ -0,0 +1,15 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+const createObservable = require('@polkadot/api-rx/observable');
+
+module.exports = function observable (method) {
+  const fn = () => Promise.resolve(12345);
+
+  fn.unsubscribe = () => Promise.resolve(true);
+
+  return createObservable(`section_${method}`, method, {
+    [method]: fn
+  })();
+};

+ 29 - 0
packages/ui-react-rx/webpack.config.js

@@ -0,0 +1,29 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+module.exports = {
+  context: __dirname,
+  entry: './src/demo.js',
+  mode: 'development',
+  output: {
+    path: __dirname,
+    filename: './demo.js'
+  },
+  resolve: {
+    alias: {}
+  },
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        exclude: /(node_modules)/,
+        use: {
+          loader: 'babel-loader'
+        }
+      }
+    ]
+  },
+  plugins: []
+};

+ 15 - 0
packages/ui-react/LICENSE

@@ -0,0 +1,15 @@
+ISC License (ISC)
+
+Copyright 2017-2018 Jaco Greeff <jacogr@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.

+ 13 - 0
packages/ui-react/README.md

@@ -0,0 +1,13 @@
+[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
+![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
+[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
+[![npm](https://img.shields.io/npm/v/@polkadot/ui-react.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/ui-react)
+[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/ui)
+[![maintainability](https://img.shields.io/codeclimate/maintainability/polkadot-js/apps.svg?style=flat-square)](https://codeclimate.com/github/polkadot-js/apps/maintainability)
+[![coverage](https://img.shields.io/coveralls/polkadot-js/apps.svg?style=flat-square)](https://coveralls.io/github/polkadot-js/apps?branch=master)
+[![dependency](https://david-dm.org/polkadot-js/apps.svg?style=flat-square&path=packages/ui-react)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react)
+[![devDependency](https://david-dm.org/polkadot-js/apps/dev-status.svg?style=flat-square&path=packages/ui-react)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react#info=devDependencies)
+
+# @polkadot/ui-react
+
+A lightweight collection of React components used for construction of applications. See [src/demo.js](src/demo.js) for a sample utilising all components.

+ 9 - 0
packages/ui-react/index.html

@@ -0,0 +1,9 @@
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
+  </head>
+  <body>
+    <div id="demo"></div>
+    <script src="./demo.js"></script>
+  </body>
+</html>

+ 35 - 0
packages/ui-react/package.json

@@ -0,0 +1,35 @@
+{
+  "name": "@polkadot/ui-react",
+  "version": "0.17.0",
+  "description": "A collection of base React components",
+  "main": "index.js",
+  "scripts": {
+    "build": "polkadot-dev-build-babel",
+    "check": "stylelint 'src/**/*.css' && eslint src && flow check",
+    "test": "echo \"Tests only available from root wrapper\""
+  },
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "license": "ISC",
+  "dependencies": {
+    "@babel/runtime": "^7.0.0-beta.47",
+    "@polkadot/ui-identicon": "^0.17.0"
+  },
+  "devDependencies": {
+    "@polkadot/util-keyring": "^0.22.2",
+    "babel-loader": "^8.0.0-beta.2",
+    "css-loader": "0.28.9",
+    "empty": "^0.10.1",
+    "enzyme": "^3.3.0",
+    "enzyme-adapter-react-16": "^1.1.1",
+    "postcss": "^6.0.22",
+    "postcss-loader": "2.0.10",
+    "postcss-sass": "^0.3.0",
+    "react": "^16.4.0",
+    "react-dom": "^16.4.0",
+    "style-loader": "^0.19.1"
+  },
+  "peerDependencies": {
+    "react": "^16.4.0",
+    "react-router-dom": "^4.2.2"
+  }
+}

+ 1 - 0
packages/ui-react/postcss.config.js

@@ -0,0 +1 @@
+module.exports = {};

+ 18 - 0
packages/ui-react/src/Container/Container.css

@@ -0,0 +1,18 @@
+/* Copyright 2017-2018 Jaco Greeff
+/* This software may be modified and distributed under the terms
+/* of the ISC license. See the LICENSE file for details. */
+
+.ui--Container {
+  padding: 0.5em;
+}
+
+.ui--DemoContainer {
+  padding: 0.5em 0.5em 1.5em 5em;
+}
+
+.ui--DemoHeader {
+  background: #f5f5f5;
+  font-size: 0.75em;
+  margin: 0 0 1em -6em;
+  padding: 0.5em;
+}

+ 39 - 0
packages/ui-react/src/Container/Demo.js

@@ -0,0 +1,39 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import React from 'react';
+
+import Container from './index';
+
+type DemoProps = {
+  attrs: { [string]: mixed },
+  attrsReq: Array<string>,
+  children: React$Node,
+  component: string
+};
+
+export default function Demo ({ attrs, attrsReq, children, component }: DemoProps): React$Node {
+  const allAttrs = Object.assign({ className: '"..."', style: '{...}' }, attrs);
+  const attrsText = Object
+    .keys(allAttrs)
+    .map((attr) => {
+      const value = allAttrs[attr];
+      const bool = value === 'bool';
+      const opt = bool || !attrsReq.includes(attr);
+
+      return `${opt ? '[' : ''}${attr}${bool ? '' : '='}${bool ? '' : value}${opt ? ']' : ''}`;
+    })
+    .sort()
+    .join('\n  ');
+
+  return (
+    <Container className='ui--DemoContainer'>
+      <pre className='ui--DemoHeader'>
+        {`<${component}\n  ${attrsText}\n/>`}
+      </pre>
+      {children}
+    </Container>
+  );
+}

+ 25 - 0
packages/ui-react/src/Container/index.js

@@ -0,0 +1,25 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { BaseProps } from '../types';
+
+import './Container.css';
+
+import React from 'react';
+
+type Props = BaseProps & {
+  children: React$Node
+};
+
+export default function Container ({ children, className, style }: Props): React$Node {
+  return (
+    <div
+      className={['ui--Container', className].join(' ')}
+      style={style}
+    >
+      {children}
+    </div>
+  );
+}

+ 26 - 0
packages/ui-react/src/Container/index.spec.js

@@ -0,0 +1,26 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+import { shallow } from '../../test/enzyme';
+
+import React from 'react';
+
+import { Container } from '../index';
+
+describe('Container', () => {
+  let wrapper;
+
+  beforeEach(() => {
+    wrapper = shallow(
+      <Container className='test' />,
+      {}
+    );
+  });
+
+  it('creates the element', () => {
+    expect(
+      wrapper
+    ).toBeDefined();
+  });
+});

+ 43 - 0
packages/ui-react/src/IdentityIcon/Demo.js

@@ -0,0 +1,43 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import React from 'react';
+import randomAsU8a from '@polkadot/util-crypto/random/asU8a';
+import addressEncode from '@polkadot/util-keyring/address/encode';
+
+import Container from '../Container/Demo';
+import IdentityIcon from './index';
+
+export default function Demo () {
+  const identities = [new Uint8Array(32)];
+
+  while (identities.length !== 10) {
+    identities.push(
+      addressEncode(randomAsU8a(32))
+    );
+  }
+
+  return (
+    <Container
+      component='IdentityIcon'
+      attrs={{
+        size: '<number>',
+        value: '{...}'
+      }}
+      attrsReq={[
+        'value'
+      ]}
+    >
+      {
+        identities.map((value) => (
+          <IdentityIcon
+            key={value.toString()}
+            value={value}
+          />
+        ))
+      }
+    </Container>
+  );
+}

+ 7 - 0
packages/ui-react/src/IdentityIcon/IdentityIcon.css

@@ -0,0 +1,7 @@
+/* Copyright 2017-2018 Jaco Greeff
+/* This software may be modified and distributed under the terms
+/* of the ISC license. See the LICENSE file for details. */
+
+.ui--IdentityIcon {
+  display: inline-block;
+}

+ 22 - 0
packages/ui-react/src/IdentityIcon/appendIcon.js

@@ -0,0 +1,22 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import identicon from '@polkadot/ui-identicon';
+
+type NodeRefSet = (node: ?Element) => void;
+
+export default function appendIcon (address: string | Uint8Array, size: number): NodeRefSet {
+  return (node: ?Element): void => {
+    if (node) {
+      while (node.firstChild) {
+        node.removeChild(node.firstChild);
+      }
+
+      node.appendChild(
+        identicon(address, size)
+      );
+    }
+  };
+}

+ 33 - 0
packages/ui-react/src/IdentityIcon/appendIcon.spec.js

@@ -0,0 +1,33 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+describe('autoFocus', () => {
+  let appendIcon;
+  let mockIdenticon;
+  let node;
+
+  beforeEach(() => {
+    node = {
+      appendChild: jest.fn()
+    };
+
+    mockIdenticon = jest.fn(() => 'genIdentitycon');
+    jest.mock('@polkadot/ui-identicon', () => mockIdenticon);
+
+    appendIcon = require('./appendIcon').default;
+  });
+
+  it('generates & appends icon to the node', () => {
+    appendIcon('testAddr', 'testSize')(node);
+
+    expect(node.appendChild).toHaveBeenCalledWith('genIdentitycon');
+    expect(mockIdenticon).toHaveBeenCalledWith('testAddr', 'testSize');
+  });
+
+  it('does not call identicon when no node', () => {
+    appendIcon('testAddr', 'testSize')();
+
+    expect(mockIdenticon).not.toHaveBeenCalled();
+  });
+});

+ 27 - 0
packages/ui-react/src/IdentityIcon/index.js

@@ -0,0 +1,27 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { BaseProps } from '../types';
+
+import './IdentityIcon.css';
+
+import React from 'react';
+
+import appendIcon from './appendIcon';
+
+type Props = BaseProps & {
+  size?: number,
+  value: string | Uint8Array
+};
+
+export default function IdentityIcon ({ className, size = 64, style, value }: Props) {
+  return (
+    <div
+      className={['ui--IdentityIcon', className].join(' ')}
+      ref={appendIcon(value, size)}
+      style={style}
+    />
+  );
+}

+ 26 - 0
packages/ui-react/src/IdentityIcon/index.spec.js

@@ -0,0 +1,26 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+
+import { shallow } from '../../test/enzyme';
+
+import React from 'react';
+
+import { IdentityIcon } from '../index';
+
+describe('IdentityIcon', () => {
+  let wrapper;
+
+  beforeEach(() => {
+    wrapper = shallow(
+      <IdentityIcon className='test' />,
+      {}
+    );
+  });
+
+  it('creates the element', () => {
+    expect(
+      wrapper
+    ).toBeDefined();
+  });
+});

+ 8 - 0
packages/ui-react/src/demo.css

@@ -0,0 +1,8 @@
+/* Copyright 2017-2018 Jaco Greeff
+/* This software may be modified and distributed under the terms
+/* of the ISC license. See the LICENSE file for details. */
+
+.ui--Demo {
+  margin: 0;
+  padding: 0.5em;
+}

+ 24 - 0
packages/ui-react/src/demo.js

@@ -0,0 +1,24 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import './demo.css';
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import IdentityIcon from './IdentityIcon/Demo';
+
+const element = document.getElementById('demo');
+
+if (!element) {
+  throw new Error('Unable to find #demo element');
+}
+
+ReactDOM.render(
+  <div className='ui--Demo'>
+    <IdentityIcon />
+  </div>,
+  element
+);

+ 12 - 0
packages/ui-react/src/index.js

@@ -0,0 +1,12 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import Container from './Container';
+import IdentityIcon from './IdentityIcon';
+
+export {
+  Container,
+  IdentityIcon
+};

Some files were not shown because too many files changed in this diff