Skip to content

Commit

Permalink
UI: Migrate to Storybook (#6507)
Browse files Browse the repository at this point in the history
I originally planned to add component documentation, but as this dragged on and I found that JSDoc-to-Markdown sometimes needed hand-tuning, I decided to skip it and focus on replicating what was already present in Freestyle. Adding documentation is a finite task that can be revisited in the future.

My goal was to migrate everything from Freestyle with as few changes as possible. Some adaptations that I found necessary:
• the DelayedArray and DelayedTruth utilities that delay component rendering until slightly after initial render because without them:
  ◦ charts were rendering with zero width
  ◦ the JSON viewer was rendering with empty content
• Storybook in Ember renders components in a routerless/controllerless context by default, so some component stories needed changes:
  ◦ table pagination/sorting stories access to query params, which necessitates some reaching into Ember internals to start routing and dynamically generate a Storybook route/controller to render components into
  ◦ some stories have a faux controller as part of their Storybook context that hosts setInterval-linked dynamic computed properties
• some jiggery-pokery with anchor tags
  ◦ inert href='#' had to become href='javascript:;
  ◦ links that are actually meant to navigate need target='_parent' so they don’t navigate inside the Storybook iframe

Maybe some of these could be addressed by fixes in ember-cli-storybook but I’m wary of digging around in there any more than I already have, as I’ve lost a lot of time to Storybook confusion and frustrations already 😞

The STORYBOOK=true environment variable tweaks some environment settings to get things working as expected in the Storybook context.

I chose to:
• use angle bracket invocation within stories rather than have to migrate them soon after having moved to Storybook
• keep Freestyle around for now for its palette and typeface components
  • Loading branch information
backspace authored Jan 21, 2020
1 parent 0aa58b9 commit 9f86f5a
Show file tree
Hide file tree
Showing 97 changed files with 7,490 additions and 3,369 deletions.
1 change: 1 addition & 0 deletions ui/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
STORYBOOK_NAME=nomad-ui
13 changes: 13 additions & 0 deletions ui/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,18 @@ module.exports = {
'node/no-unpublished-require': 'off'
}),
},
{
files: [
'stories/**/*.js'
],
parserOptions: {
sourceType: 'module',
},
env: {
browser: false,
node: true,
},
plugins: ['node'],
},
],
};
3 changes: 3 additions & 0 deletions ui/.storybook/addons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import '@storybook/addon-storysource/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-viewport/register';
27 changes: 27 additions & 0 deletions ui/.storybook/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-env node */
module.exports = {
presets: [
[
'@babel/preset-env',
{
shippedProposals: true,
useBuiltIns: 'usage',
corejs: '3',
targets: ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'],
},
],
],
plugins: [
[
'@babel/plugin-proposal-decorators',
{
legacy: true,
},
],
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-syntax-dynamic-import',
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
'babel-plugin-macros',
['emotion', { sourceMap: true, autoLabel: true }],
],
};
69 changes: 69 additions & 0 deletions ui/.storybook/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* eslint-env node */
import { addDecorator, addParameters, configure } from '@storybook/ember';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import theme from './theme.js';

addParameters({
viewport: { viewports: INITIAL_VIEWPORTS },
options: {
showPanel: true,
theme
},
});

addDecorator(storyFn => {
let { template, context } = storyFn();

let wrapperElementStyle = {
margin: '20px',
};

let applicationWrapperElement = document.createElement('div');
Object.assign(applicationWrapperElement.style, wrapperElementStyle);

let storybookElement = document.createElement('div');
storybookElement.setAttribute('id', 'storybook');

let wormhole = document.createElement('div');
wormhole.setAttribute('id', 'ember-basic-dropdown-wormhole');

storybookElement.appendChild(wormhole);

applicationWrapperElement.appendChild(storybookElement);
storybookElement.appendTo = function appendTo(el) {
el.appendChild(applicationWrapperElement);
};

/**
* Stories that require routing (table sorting/pagination) fail
* with the default iframe setup with this error:
* Path /iframe.html does not start with the provided rootURL /ui/
*
* Changing ENV.rootURL fixes that but then HistoryLocation.getURL
* fails because baseURL is undefined, which is usually set up by
* Ember CLI configuring the base element. This adds the href for
* Ember CLI to use.
*
* The default target="_parent" breaks table sorting and pagination
* by trying to navigate when clicking the query-params-changing
* elements. Removing the base target for the iframe means that
* navigation-requiring links within stories need to have the
* target themselves.
*/
let baseElement = document.querySelector('base');
baseElement.setAttribute('href', '/');
baseElement.removeAttribute('target');

return {
template,
context,
element: storybookElement,
};
});

// The order of import controls the sorting in the sidebar
configure([
require.context('../stories/theme', true, /\.stories\.js$/),
require.context('../stories/components', true, /\.stories\.js$/),
require.context('../stories/charts', true, /\.stories\.js$/),
], module);
23 changes: 23 additions & 0 deletions ui/.storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- This file is auto-generated by ember-cli-storybook -->
<meta name="nomad-ui/config/environment" content="%7B%22modulePrefix%22%3A%22nomad-ui%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_JQUERY_INTEGRATION%22%3Atrue%7D%2C%22APP%22%3A%7B%22blockingQueries%22%3Atrue%2C%22mirageScenario%22%3A%22smallCluster%22%2C%22mirageWithNamespaces%22%3Atrue%2C%22mirageWithTokens%22%3Atrue%2C%22mirageWithRegions%22%3Atrue%2C%22autoboot%22%3Afalse%7D%2C%22ember-cli-mirage%22%3A%7B%22enabled%22%3Atrue%2C%22excludeFilesFromBuild%22%3Afalse%2C%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />
<link rel="stylesheet" href="./assets/vendor.css" />
<link rel="stylesheet" href="./assets/nomad-ui.css" />
<link rel="icon" href=".//favicon.png" />
<script>
(function() {
var srcUrl = null;
var host = location.hostname || 'localhost';
var defaultPort = location.protocol === 'https:' ? 443 : 80;
var port = 4200;
var path = '';
var prefixURL = '';
var src = srcUrl || prefixURL + '/_lr/livereload.js?port=' + port + '&host=' + host + path;
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = location.protocol + '//' + host + ':4200' + src;
document.getElementsByTagName('head')[0].appendChild(script);
}());
</script>
<script src="./assets/vendor.js"></script>
<script>runningTests = true; Ember.testing=true;</script>
<script src="./assets/nomad-ui.js"></script>
35 changes: 35 additions & 0 deletions ui/.storybook/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { create } from '@storybook/theming';

// From Bulma
let blackBis = 'hsl(0, 0%, 7%)';
let greyLight = 'hsl(0, 0%, 71%)';

// From product-colors.scss
let vagrantBlue = '#1563ff';

export default create({
base: 'light',

colorPrimary: blackBis,
colorSecondary: vagrantBlue,

// UI
appBorderColor: greyLight,

// Typography
// From variables.scss
fontBase: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
// From Bulma
fontCode: 'monospace',

// Text colors
textColor: blackBis,

// Toolbar default and active colors
barTextColor: greyLight,
barSelectedColor: 'white',
barBg: blackBis,

brandTitle: 'Nomad Storybook',
brandUrl: 'https://www.nomadproject.io/',
});
10 changes: 10 additions & 0 deletions ui/.storybook/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-env node */
module.exports = function({ config }) {
config.module.rules.push({
test: /\.stories\.jsx?$/,
loaders: [require.resolve('@storybook/source-loader')],
enforce: 'pre',
});

return config;
};
6 changes: 6 additions & 0 deletions ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ Nomad UI releases are in lockstep with Nomad releases and are integrated into th

* UI branches should be prefix with `f-ui-` for feature work and `b-ui-` for bug fixes. This instructs CI to skip running nomad backend tests.

### Storybook UI Library

The Storybook project provides a browser to see what components and patterns are present in the application and how to use them. You can run it locally with `yarn storybook`. The latest version from the `master` branch is at [`nomad-storybook.netlify.com`](https://nomad-storybook.netlify.com/).

To generate a new story for a component, run `ember generate story component-name`. You can use the existing stories as a guide.

### Troubleshooting

#### The UI is running, but none of the API requests are working
Expand Down
6 changes: 0 additions & 6 deletions ui/app/components/freestyle/sg-accordion.js

This file was deleted.

27 changes: 0 additions & 27 deletions ui/app/components/freestyle/sg-boxed-section.js

This file was deleted.

97 changes: 0 additions & 97 deletions ui/app/components/freestyle/sg-colors.js

This file was deleted.

Loading

0 comments on commit 9f86f5a

Please sign in to comment.