UI: Migrate to Storybook (#6507)

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
This commit is contained in:
Buck Doyle
2020-01-21 15:46:32 -06:00
committed by GitHub
parent 0aa58b90c3
commit 9f86f5a1fc
97 changed files with 7492 additions and 3371 deletions

1
ui/.env Normal file
View File

@@ -0,0 +1 @@
STORYBOOK_NAME=nomad-ui

View File

@@ -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
ui/.storybook/addons.js Normal file
View File

@@ -0,0 +1,3 @@
import '@storybook/addon-storysource/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-viewport/register';

View File

@@ -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
ui/.storybook/config.js Normal file
View File

@@ -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);

View File

@@ -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
ui/.storybook/theme.js Normal file
View File

@@ -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/',
});

View File

@@ -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;
};

View File

@@ -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

View File

@@ -1,6 +0,0 @@
import Component from '@ember/component';
import productMetadata from 'nomad-ui/utils/styleguide/product-metadata';
export default Component.extend({
products: productMetadata,
});

View File

@@ -1,27 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
variants: computed(() => [
{
key: 'Normal',
title: 'Normal',
slug: '',
},
{
key: 'Info',
title: 'Info',
slug: 'is-info',
},
{
key: 'Warning',
title: 'Warning',
slug: 'is-warning',
},
{
key: 'Danger',
title: 'Danger',
slug: 'is-danger',
},
]),
});

View File

@@ -1,97 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
nomadTheme: computed(() => [
{
name: 'Primary',
base: '#25ba81',
},
{
name: 'Primary Dark',
base: '#1d9467',
},
{
name: 'Text',
base: '#0a0a0a',
},
{
name: 'Link',
base: '#1563ff',
},
{
name: 'Gray',
base: '#bbc4d1',
},
{
name: 'Off-white',
base: '#f5f5f5',
},
]),
productColors: computed(() => [
{
name: 'Consul Pink',
base: '#ff0087',
},
{
name: 'Consul Pink Dark',
base: '#c62a71',
},
{
name: 'Packer Blue',
base: '#1daeff',
},
{
name: 'Packer Blue Dark',
base: '#1d94dd',
},
{
name: 'Terraform Purple',
base: '#5c4ee5',
},
{
name: 'Terraform Purple Dark',
base: '#4040b2',
},
{
name: 'Vagrant Blue',
base: '#1563ff',
},
{
name: 'Vagrant Blue Dark',
base: '#104eb2',
},
{
name: 'Nomad Green',
base: '#25ba81',
},
{
name: 'Nomad Green Dark',
base: '#1d9467',
},
{
name: 'Nomad Green Darker',
base: '#16704d',
},
]),
emotiveColors: computed(() => [
{
name: 'Success',
base: '#23d160',
},
{
name: 'Warning',
base: '#fa8e23',
},
{
name: 'Danger',
base: '#c84034',
},
{
name: 'Info',
base: '#1563ff',
},
]),
});

View File

@@ -1,355 +0,0 @@
import Component from '@ember/component';
const generateDiff = changeset => ({
Fields: null,
ID: 'insertions-only',
Objects: null,
TaskGroups: [
{
Fields: [{ Annotations: null, Name: 'Count', New: '2', Old: '2', Type: 'None' }],
Name: 'cache',
Objects: [
{
Fields: changeset,
Name: 'RestartPolicy',
Objects: null,
Type: 'Edited',
},
],
Type: 'Edited',
Updates: null,
},
],
Type: 'Edited',
});
export default Component.extend({
insertionsOnly: generateDiff([
{ Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' },
{ Annotations: null, Name: 'Delay', New: '25000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Interval', New: '900000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Mode', New: 'delay', Old: 'delay', Type: 'None' },
]),
deletionsOnly: generateDiff([
{ Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' },
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '25000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '900000000000',
Type: 'None',
},
{ Annotations: null, Name: 'Mode', New: '', Old: 'delay', Type: 'Deleted' },
]),
editsOnly: generateDiff([
{ Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' },
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '25000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '250000000000',
Type: 'Edited',
},
{ Annotations: null, Name: 'Mode', New: 'delay', Old: 'delay', Type: 'None' },
]),
largeDiff: {
Fields: null,
ID: 'example',
Objects: null,
TaskGroups: [
{
Fields: null,
Name: 'cache',
Objects: null,
Tasks: [
{
Annotations: null,
Fields: [
{
Annotations: null,
Name: 'Meta[one]',
New: "flew over the cuckoo's nest",
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Meta[two]',
New: 'birds on a wire',
Old: '',
Type: 'Added',
},
],
Name: 'redis',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'image',
New: 'redis:3.4',
Old: 'redis:3.2',
Type: 'Edited',
},
{
Annotations: null,
Name: 'port_map[0][db]',
New: '6380',
Old: '6379',
Type: 'Edited',
},
],
Name: 'Config',
Objects: null,
Type: 'Edited',
},
{
Fields: [
{ Annotations: null, Name: 'CPU', New: '1000', Old: '500', Type: 'Edited' },
{ Annotations: null, Name: 'DiskMB', New: '0', Old: '0', Type: 'None' },
{ Annotations: null, Name: 'IOPS', New: '0', Old: '0', Type: 'None' },
{ Annotations: null, Name: 'MemoryMB', New: '512', Old: '256', Type: 'Edited' },
],
Name: 'Resources',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'MBits', New: '100', Old: '', Type: 'Added' },
],
Name: 'Network',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Label', New: 'db', Old: '', Type: 'Added' },
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Added',
},
],
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'MBits', New: '', Old: '10', Type: 'Deleted' },
],
Name: 'Network',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Label', New: '', Old: 'db', Type: 'Deleted' },
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Deleted',
},
],
Type: 'Deleted',
},
],
Type: 'Edited',
},
{
Fields: [
{
Annotations: null,
Name: 'AddressMode',
New: 'auto',
Old: 'auto',
Type: 'None',
},
{
Annotations: null,
Name: 'Name',
New: 'redis-cache',
Old: 'redis-cache',
Type: 'None',
},
{ Annotations: null, Name: 'PortLabel', New: 'db', Old: 'db', Type: 'None' },
],
Name: 'Service',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Tags', New: 'redis', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Tags', New: 'cache', Old: 'cache', Type: 'None' },
{
Annotations: null,
Name: 'Tags',
New: 'global',
Old: 'global',
Type: 'None',
},
],
Name: 'Tags',
Objects: null,
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'AddressMode', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'Command', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'GRPCService', New: '', Old: '', Type: 'None' },
{
Annotations: null,
Name: 'GRPCUseTLS',
New: 'false',
Old: 'false',
Type: 'None',
},
{ Annotations: null, Name: 'InitialStatus', New: '', Old: '', Type: 'None' },
{
Annotations: null,
Name: 'Interval',
New: '15000000000',
Old: '10000000000',
Type: 'Edited',
},
{ Annotations: null, Name: 'Method', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'Name', New: 'alive', Old: 'alive', Type: 'None' },
{ Annotations: null, Name: 'Path', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'PortLabel', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'Protocol', New: '', Old: '', Type: 'None' },
{
Annotations: null,
Name: 'TLSSkipVerify',
New: 'false',
Old: 'false',
Type: 'None',
},
{
Annotations: null,
Name: 'Timeout',
New: '7000000000',
Old: '2000000000',
Type: 'Edited',
},
{ Annotations: null, Name: 'Type', New: 'tcp', Old: 'tcp', Type: 'None' },
],
Name: 'Check',
Objects: null,
Type: 'Edited',
},
],
Type: 'Edited',
},
],
Type: 'Edited',
},
],
Type: 'Edited',
Updates: null,
},
{
Fields: [
{ Annotations: null, Name: 'Count', New: '1', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Meta[key]', New: 'value', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Meta[red]', New: 'fish', Old: '', Type: 'Added' },
],
Name: 'cache2',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Attempts', New: '2', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Delay', New: '15000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Interval', New: '1800000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Mode', New: 'fail', Old: '', Type: 'Added' },
],
Name: 'RestartPolicy',
Objects: null,
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'Migrate', New: 'false', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'SizeMB', New: '300', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Sticky', New: 'false', Old: '', Type: 'Added' },
],
Name: 'EphemeralDisk',
Objects: null,
Type: 'Added',
},
],
Tasks: [
{
Annotations: null,
Fields: [
{ Annotations: null, Name: 'Driver', New: 'docker', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'KillTimeout', New: '5000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Leader', New: 'false', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'ShutdownDelay', New: '0', Old: '', Type: 'Added' },
],
Name: 'redis',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'image', New: 'redis:3.2', Old: '', Type: 'Added' },
{
Annotations: null,
Name: 'port_map[0][db]',
New: '6379',
Old: '',
Type: 'Added',
},
],
Name: 'Config',
Objects: null,
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'CPU', New: '500', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'DiskMB', New: '0', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'IOPS', New: '0', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'MemoryMB', New: '256', Old: '', Type: 'Added' },
],
Name: 'Resources',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'MBits', New: '10', Old: '', Type: 'Added' },
],
Name: 'Network',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Label', New: 'db', Old: '', Type: 'Added' },
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
Updates: null,
},
],
Type: 'Edited',
},
});

View File

@@ -1,13 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
distributionBarData: computed(() => {
return [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 0 },
{ label: 'four', value: 35 },
];
}),
});

View File

@@ -1,48 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { on } from '@ember/object/evented';
export default Component.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
distributionBarDatum: computed(() => {
return [{ label: 'one', value: 10 }];
}),
distributionBarData: computed(() => {
return [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 30 },
];
}),
distributionBarDataWithClasses: computed(() => {
return [
{ label: 'Queued', value: 10, className: 'queued' },
{ label: 'Complete', value: 20, className: 'complete' },
{ label: 'Failed', value: 30, className: 'failed' },
];
}),
distributionBarDataRotating: computed('timerTicks', () => {
return [
{ label: 'one', value: Math.round(Math.random() * 50) },
{ label: 'two', value: Math.round(Math.random() * 50) },
{ label: 'three', value: Math.round(Math.random() * 50) },
];
}),
});

View File

@@ -1,30 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
options: [
{ name: 'Consul' },
{ name: 'Nomad' },
{ name: 'Packer' },
{ name: 'Terraform' },
{ name: 'Vagrant' },
{ name: 'Vault' },
],
manyOptions: [
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
'Eleven',
'Twelve',
'Thirteen',
'Fourteen',
'Fifteen',
].map(name => ({ name })),
});

View File

@@ -1,148 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
jsonSmall: {
foo: 'bar',
number: 123456789,
products: ['Consul', 'Nomad', 'Packer', 'Terraform', 'Vagrant', 'Vault'],
currentTime: new Date().toISOString(),
nested: {
obj: 'ject',
},
nonexistent: null,
huh: undefined,
isTrue: false,
},
jsonLarge: {
Stop: false,
Region: 'global',
Namespace: 'default',
ID: 'syslog',
ParentID: '',
Name: 'syslog',
Type: 'system',
Priority: 50,
AllAtOnce: false,
Datacenters: ['dc1', 'dc2'],
Constraints: null,
TaskGroups: [
{
Name: 'syslog',
Count: 1,
Update: {
Stagger: 10000000000,
MaxParallel: 1,
HealthCheck: 'checks',
MinHealthyTime: 10000000000,
HealthyDeadline: 300000000000,
ProgressDeadline: 600000000000,
AutoRevert: false,
Canary: 0,
},
Migrate: null,
Constraints: [
{
LTarget: '',
RTarget: '',
Operand: 'distinct_hosts',
},
],
RestartPolicy: {
Attempts: 10,
Interval: 300000000000,
Delay: 25000000000,
Mode: 'delay',
},
Tasks: [
{
Name: 'syslog',
Driver: 'docker',
User: '',
Config: {
port_map: [
{
tcp: 601.0,
udp: 514.0,
},
],
image: 'balabit/syslog-ng:latest',
},
Env: null,
Services: null,
Vault: null,
Templates: null,
Constraints: null,
Resources: {
CPU: 500,
MemoryMB: 256,
DiskMB: 0,
IOPS: 0,
Networks: [
{
Device: '',
CIDR: '',
IP: '',
MBits: 10,
ReservedPorts: [
{
Label: 'udp',
Value: 514,
},
{
Label: 'tcp',
Value: 601,
},
],
DynamicPorts: null,
},
],
},
DispatchPayload: null,
Meta: null,
KillTimeout: 5000000000,
LogConfig: {
MaxFiles: 10,
MaxFileSizeMB: 10,
},
Artifacts: null,
Leader: false,
ShutdownDelay: 0,
KillSignal: '',
},
],
EphemeralDisk: {
Sticky: false,
SizeMB: 300,
Migrate: false,
},
Meta: null,
ReschedulePolicy: null,
},
],
Update: {
Stagger: 10000000000,
MaxParallel: 1,
HealthCheck: '',
MinHealthyTime: 0,
HealthyDeadline: 0,
ProgressDeadline: 0,
AutoRevert: false,
Canary: 0,
},
Periodic: null,
ParameterizedJob: null,
Dispatched: false,
Payload: null,
Meta: null,
VaultToken: '',
Status: 'running',
StatusDescription: '',
Stable: false,
Version: 0,
SubmitTime: 1530052201331477665,
CreateIndex: 27,
ModifyIndex: 27,
JobModifyIndex: 27,
},
});

View File

@@ -1,77 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import d3TimeFormat from 'd3-time-format';
export default Component.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
const ref = this.lineChartLive;
ref.addObject({ ts: Date.now(), val: Math.random() * 30 + 20 });
if (ref.length > 60) {
ref.splice(0, ref.length - 60);
}
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
lineChartData: computed(() => {
return [
{ year: 2010, value: 10 },
{ year: 2011, value: 10 },
{ year: 2012, value: 20 },
{ year: 2013, value: 30 },
{ year: 2014, value: 50 },
{ year: 2015, value: 80 },
{ year: 2016, value: 130 },
{ year: 2017, value: 210 },
{ year: 2018, value: 340 },
];
}),
lineChartMild: computed(() => {
return [
{ year: 2010, value: 100 },
{ year: 2011, value: 90 },
{ year: 2012, value: 120 },
{ year: 2013, value: 130 },
{ year: 2014, value: 115 },
{ year: 2015, value: 105 },
{ year: 2016, value: 90 },
{ year: 2017, value: 85 },
{ year: 2018, value: 90 },
];
}),
lineChartGapData: computed(() => {
return [
{ year: 2010, value: 10 },
{ year: 2011, value: 10 },
{ year: 2012, value: null },
{ year: 2013, value: 30 },
{ year: 2014, value: 50 },
{ year: 2015, value: 80 },
{ year: 2016, value: null },
{ year: 2017, value: 210 },
{ year: 2018, value: 340 },
];
}),
lineChartLive: computed(() => {
return [];
}),
secondsFormat() {
return d3TimeFormat.timeFormat('%H:%M:%S');
},
});

View File

@@ -1,33 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
mode1: 'stdout',
isPlaying1: true,
sampleOutput: `Sample output
> 1
> 2
> 3
[00:12:58] Log output here
[00:15:29] [ERR] Uh oh
Loading.
Loading..
Loading...
>> Done! <<
`,
sampleError: `Sample error
[====|--------------------] 20%
!!! Unrecoverable error:
Cannot continue beyond this point. Exception should be caught.
This is not a mistake. You did something wrong. Check the code.
No, you will not receive any more details or guidance from this
error message.
`,
});

View File

@@ -1,45 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
options1: computed(() => [
{ key: 'option-1', label: 'Option One' },
{ key: 'option-2', label: 'Option Two' },
{ key: 'option-3', label: 'Option Three' },
{ key: 'option-4', label: 'Option Four' },
{ key: 'option-5', label: 'Option Five' },
]),
selection1: computed(() => ['option-2', 'option-4', 'option-5']),
optionsMany: computed(() =>
Array(100)
.fill(null)
.map((_, i) => ({ label: `Option ${i}`, key: `option-${i}` }))
),
selectionMany: computed(() => []),
optionsDatacenter: computed(() => [
{ key: 'pdx-1', label: 'pdx-1' },
{ key: 'jfk-1', label: 'jfk-1' },
{ key: 'jfk-2', label: 'jfk-2' },
{ key: 'muc-1', label: 'muc-1' },
]),
selectionDatacenter: computed(() => ['jfk-1', 'jfk-2']),
optionsType: computed(() => [
{ key: 'batch', label: 'Batch' },
{ key: 'service', label: 'Service' },
{ key: 'system', label: 'System' },
{ key: 'periodic', label: 'Periodic' },
{ key: 'parameterized', label: 'Parameterized' },
]),
selectionType: computed(() => ['system', 'service']),
optionsStatus: computed(() => [
{ key: 'pending', label: 'Pending' },
{ key: 'running', label: 'Running' },
{ key: 'dead', label: 'Dead' },
]),
selectionStatus: computed(() => []),
});

View File

@@ -1,36 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { on } from '@ember/object/evented';
export default Component.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 1000)
);
}),
willDestroy() {
clearInterval(this.timer);
},
denominator: computed('timerTicks', function() {
return Math.round(Math.random() * 1000);
}),
percentage: computed('timerTicks', function() {
return Math.round(Math.random() * 100) / 100;
}),
numerator: computed('denominator', 'percentage', function() {
return Math.round(this.denominator * this.percentage * 100) / 100;
}),
liveDetails: computed('denominator', 'numerator', 'percentage', function() {
return this.getProperties('denominator', 'numerator', 'percentage');
}),
});

View File

@@ -1,76 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import d3TimeFormat from 'd3-time-format';
import moment from 'moment';
export default Component.extend({
timerTicks: 0,
startTimer: function() {
this.set(
'timer',
setInterval(() => {
const metricsHigh = this.metricsHigh;
const prev = metricsHigh.length ? metricsHigh[metricsHigh.length - 1].value : 0.9;
this.appendTSValue(
metricsHigh,
Math.min(Math.max(prev + Math.random() * 0.05 - 0.025, 0.5), 1)
);
const metricsLow = this.metricsLow;
const prev2 = metricsLow.length ? metricsLow[metricsLow.length - 1].value : 0.1;
this.appendTSValue(
metricsLow,
Math.min(Math.max(prev2 + Math.random() * 0.05 - 0.025, 0), 0.5)
);
}, 1000)
);
}.on('init'),
appendTSValue(array, value, maxLength = 300) {
array.addObject({
timestamp: Date.now(),
value,
});
if (array.length > maxLength) {
array.splice(0, array.length - maxLength);
}
},
willDestroy() {
clearInterval(this.timer);
},
metricsHigh: computed(() => {
return [];
}),
metricsLow: computed(() => {
return [];
}),
staticMetrics: computed(() => {
const ts = offset =>
moment()
.subtract(offset, 'm')
.toDate();
return [
{ timestamp: ts(20), value: 0.5 },
{ timestamp: ts(18), value: 0.5 },
{ timestamp: ts(16), value: 0.4 },
{ timestamp: ts(14), value: 0.3 },
{ timestamp: ts(12), value: 0.9 },
{ timestamp: ts(10), value: 0.3 },
{ timestamp: ts(8), value: 0.3 },
{ timestamp: ts(6), value: 0.4 },
{ timestamp: ts(4), value: 0.5 },
{ timestamp: ts(2), value: 0.6 },
{ timestamp: ts(0), value: 0.6 },
];
}),
secondsFormat() {
return d3TimeFormat.timeFormat('%H:%M:%S');
},
});

View File

@@ -1,24 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
attributes: {
key: 'val',
deep: {
key: 'val',
more: 'stuff',
},
array: ['one', 'two', 'three', 'four'],
very: {
deep: {
key: {
incoming: {
one: 1,
two: 2,
three: 3,
four: 'surprisingly long value that is unlike the other properties in this object',
},
},
},
},
},
});

View File

@@ -1,118 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import productMetadata from 'nomad-ui/utils/styleguide/product-metadata';
export default Component.extend({
searchTerm: '',
currentPage: 1,
sortProperty: 'name',
sortDescending: false,
shortList: productMetadata,
longList: [
{ city: 'New York', growth: 0.048, population: '8405837', rank: '1', state: 'New York' },
{ city: 'Los Angeles', growth: 0.048, population: '3884307', rank: '2', state: 'California' },
{ city: 'Chicago', growth: -0.061, population: '2718782', rank: '3', state: 'Illinois' },
{ city: 'Houston', growth: 0.11, population: '2195914', rank: '4', state: 'Texas' },
{
city: 'Philadelphia',
growth: 0.026,
population: '1553165',
rank: '5',
state: 'Pennsylvania',
},
{ city: 'Phoenix', growth: 0.14, population: '1513367', rank: '6', state: 'Arizona' },
{ city: 'San Antonio', growth: 0.21, population: '1409019', rank: '7', state: 'Texas' },
{ city: 'San Diego', growth: 0.105, population: '1355896', rank: '8', state: 'California' },
{ city: 'Dallas', growth: 0.056, population: '1257676', rank: '9', state: 'Texas' },
{ city: 'San Jose', growth: 0.105, population: '998537', rank: '10', state: 'California' },
{ city: 'Austin', growth: 0.317, population: '885400', rank: '11', state: 'Texas' },
{ city: 'Indianapolis', growth: 0.078, population: '843393', rank: '12', state: 'Indiana' },
{ city: 'Jacksonville', growth: 0.143, population: '842583', rank: '13', state: 'Florida' },
{
city: 'San Francisco',
growth: 0.077,
population: '837442',
rank: '14',
state: 'California',
},
{ city: 'Columbus', growth: 0.148, population: '822553', rank: '15', state: 'Ohio' },
{
city: 'Charlotte',
growth: 0.391,
population: '792862',
rank: '16',
state: 'North Carolina',
},
{ city: 'Fort Worth', growth: 0.451, population: '792727', rank: '17', state: 'Texas' },
{ city: 'Detroit', growth: -0.271, population: '688701', rank: '18', state: 'Michigan' },
{ city: 'El Paso', growth: 0.194, population: '674433', rank: '19', state: 'Texas' },
{ city: 'Memphis', growth: -0.053, population: '653450', rank: '20', state: 'Tennessee' },
{ city: 'Seattle', growth: 0.156, population: '652405', rank: '21', state: 'Washington' },
{ city: 'Denver', growth: 0.167, population: '649495', rank: '22', state: 'Colorado' },
{
city: 'Washington',
growth: 0.13,
population: '646449',
rank: '23',
state: 'District of Columbia',
},
{ city: 'Boston', growth: 0.094, population: '645966', rank: '24', state: 'Massachusetts' },
{
city: 'Nashville-Davidson',
growth: 0.162,
population: '634464',
rank: '25',
state: 'Tennessee',
},
{ city: 'Baltimore', growth: -0.04, population: '622104', rank: '26', state: 'Maryland' },
{ city: 'Oklahoma City', growth: 0.202, population: '610613', rank: '27', state: 'Oklahoma' },
{
city: 'Louisville/Jefferson County',
growth: 0.1,
population: '609893',
rank: '28',
state: 'Kentucky',
},
{ city: 'Portland', growth: 0.15, population: '609456', rank: '29', state: 'Oregon' },
{ city: 'Las Vegas', growth: 0.245, population: '603488', rank: '30', state: 'Nevada' },
{ city: 'Milwaukee', growth: 0.003, population: '599164', rank: '31', state: 'Wisconsin' },
{ city: 'Albuquerque', growth: 0.235, population: '556495', rank: '32', state: 'New Mexico' },
{ city: 'Tucson', growth: 0.075, population: '526116', rank: '33', state: 'Arizona' },
{ city: 'Fresno', growth: 0.183, population: '509924', rank: '34', state: 'California' },
{ city: 'Sacramento', growth: 0.172, population: '479686', rank: '35', state: 'California' },
{ city: 'Long Beach', growth: 0.015, population: '469428', rank: '36', state: 'California' },
{ city: 'Kansas City', growth: 0.055, population: '467007', rank: '37', state: 'Missouri' },
{ city: 'Mesa', growth: 0.135, population: '457587', rank: '38', state: 'Arizona' },
{ city: 'Virginia Beach', growth: 0.051, population: '448479', rank: '39', state: 'Virginia' },
{ city: 'Atlanta', growth: 0.062, population: '447841', rank: '40', state: 'Georgia' },
{
city: 'Colorado Springs',
growth: 0.214,
population: '439886',
rank: '41',
state: 'Colorado',
},
{ city: 'Omaha', growth: 0.059, population: '434353', rank: '42', state: 'Nebraska' },
{ city: 'Raleigh', growth: 0.487, population: '431746', rank: '43', state: 'North Carolina' },
{ city: 'Miami', growth: 0.149, population: '417650', rank: '44', state: 'Florida' },
{ city: 'Oakland', growth: 0.013, population: '406253', rank: '45', state: 'California' },
{ city: 'Minneapolis', growth: 0.045, population: '400070', rank: '46', state: 'Minnesota' },
{ city: 'Tulsa', growth: 0.013, population: '398121', rank: '47', state: 'Oklahoma' },
{ city: 'Cleveland', growth: -0.181, population: '390113', rank: '48', state: 'Ohio' },
{ city: 'Wichita', growth: 0.097, population: '386552', rank: '49', state: 'Kansas' },
{ city: 'Arlington', growth: 0.133, population: '379577', rank: '50', state: 'Texas' },
],
filteredShortList: computed('searchTerm', 'shortList.[]', function() {
const term = this.searchTerm.toLowerCase();
return this.shortList.filter(product => product.name.toLowerCase().includes(term));
}),
sortedShortList: computed('shortList.[]', 'sortProperty', 'sortDescending', function() {
const sorted = this.shortList.sortBy(this.sortProperty);
return this.sortDescending ? sorted.reverse() : sorted;
}),
});

View File

@@ -1,8 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import moment from 'moment';
export default Component.extend({
yesterday: computed(() => moment().subtract(1, 'd')),
today: computed(() => moment()),
});

View File

@@ -41,10 +41,6 @@ Router.map(function() {
this.route('tokens');
});
if (config.environment === 'development') {
this.route('freestyle');
}
this.route('not-found', { path: '/*' });
});

View File

@@ -6,4 +6,4 @@
@import './charts';
// Only necessary in dev
@import './styleguide.scss';
@import './storybook.scss';

View File

@@ -1,4 +1,4 @@
#styleguide {
#storybook {
.mock-content {
display: flex;
min-height: 250px;
@@ -52,4 +52,8 @@
.mock-spacing {
padding: 2em;
}
.annotation {
font-size: 0.9rem;
}
}

View File

@@ -1,53 +0,0 @@
{{#freestyle-usage "accordion" title="Accordion"}}
{{#list-accordion source=products key="name" as |ac|}}
{{#ac.head buttonLabel="details"}}
<div class="columns inline-definitions">
<div class="column is-1">{{ac.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{ac.item.lang}}</span>
</div>
</div>
{{/ac.head}}
{{#ac.body}}
<h1 class="title is-4">{{ac.item.name}}</h1>
<p>{{ac.item.desc}}</p>
<p><a href="{{ac.item.link}}">Learn more...</a></p>
{{/ac.body}}
{{/list-accordion}}
{{/freestyle-usage}}
{{#freestyle-usage "accordion-single" title="Accordion, One Item"}}
{{#list-accordion source=(take 1 products) key="name" as |a|}}
{{#a.head buttonLabel="details"}}
<div class="columns inline-definitions">
<div class="column is-1">{{a.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{a.item.lang}}</span>
</div>
</div>
{{/a.head}}
{{#a.body}}
<h1 class="title is-4">{{a.item.name}}</h1>
<p>{{a.item.desc}}</p>
<p><a href="{{a.item.link}}">Learn more...</a></p>
{{/a.body}}
{{/list-accordion}}
{{/freestyle-usage}}
{{#freestyle-usage "accordion-not-expandable" title="Accordion, Not Expandable"}}
{{#list-accordion source=products key="name" as |a|}}
{{#a.head buttonLabel="details" isExpandable=(eq a.item.lang "golang")}}
<div class="columns inline-definitions">
<div class="column is-1">{{a.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{a.item.lang}}</span>
</div>
</div>
{{/a.head}}
{{#a.body}}
<h1 class="title is-4">{{a.item.name}}</h1>
<p>{{a.item.desc}}</p>
<p><a href="{{a.item.link}}">Learn more...</a></p>
{{/a.body}}
{{/list-accordion}}
{{/freestyle-usage}}

View File

@@ -1,84 +0,0 @@
{{#freestyle-usage "alert-standard" title="Alert"}}
<div class="notification is-info">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Alerts use Bulma's notification component.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "alert-colors" title="Alert colors"}}
<div class="notification is-info">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-success">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-warning">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-danger">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Alerts are always paired with an emotive color. If there is no emotive association with the content of the alert, then an alert is the wrong component to use.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "alert-dismissal" title="Alert dismissal"}}
<div class="notification is-info">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-info">Okay</button>
</div>
</div>
</div>
<div class="notification is-success">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-success">Okay</button>
</div>
</div>
</div>
<div class="notification is-warning">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-warning">Okay</button>
</div>
</div>
</div>
<div class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-danger">Okay</button>
</div>
</div>
</div>
{{/freestyle-usage}}

View File

@@ -1,183 +0,0 @@
{{#freestyle-collection defaultKey="Normal" as |collection|}}
{{#collection.variant key="Normal"}}
{{#freestyle-usage "boxed-section-normal-normal" title="Normal Boxed Section"}}
<div class="boxed-section">
<div class="boxed-section-head">
Normal Boxed Section
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Info"}}
{{#freestyle-usage "boxed-section-normal-info" title="Info Boxed Section"}}
<div class="boxed-section is-info">
<div class="boxed-section-head">
Normal Boxed Section
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Warning"}}
{{#freestyle-usage "boxed-section-normal-warning" title="Warning Boxed Section"}}
<div class="boxed-section is-warning">
<div class="boxed-section-head">
Normal Boxed Section
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Danger"}}
{{#freestyle-usage "boxed-section-normal-danger" title="Danger Boxed Section"}}
<div class="boxed-section is-danger">
<div class="boxed-section-head">
Normal Boxed Section
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/freestyle-collection}}
{{#freestyle-collection defaultKey="Normal" as |collection|}}
{{#each variants as |variant|}}
{{#collection.variant key=variant.key}}
{{#freestyle-usage "boxed-section-right-hand-normal" title=(concat variant.title "Normal Boxed Section With Right Hand Details")}}
<div class="boxed-section {{variant.slug}}">
<div class="boxed-section-head">
Boxed Section With Right Hand Details
<span class="pull-right">{{now interval=1000}}</span>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/each}}
{{/freestyle-collection}}
{{#freestyle-collection defaultKey="Normal" as |collection|}}
{{#each variants as |variant|}}
{{#collection.variant key=variant.key}}
{{#freestyle-usage "boxed-section-left-badge-normal" title=(concat variant.title " Normal Boxed Section With Title Decoration")}}
<div class="boxed-section {{variant.slug}}">
<div class="boxed-section-head">
Boxed Section With Title Decoration
<span class="badge is-white">7</span>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/each}}
{{/freestyle-collection}}
{{#freestyle-collection defaultKey="Normal" as |collection|}}
{{#each variants as |variant|}}
{{#collection.variant key=variant.key}}
{{#freestyle-usage "boxed-section-with-foot-normal" title=(concat variant.title " Boxed Section With Foot")}}
<div class="boxed-section {{variant.slug}}">
<div class="boxed-section-head">
Boxed Section With Large Header
</div>
<div class="boxed-section-body with-foot">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
<div class="boxed-section-foot">
<span>Left-aligned message</span>
<a href="#" class="pull-right">Toggle or other action</a>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/each}}
{{/freestyle-collection}}
{{#freestyle-collection defaultKey="Normal" as |collection|}}
{{#each variants as |variant|}}
{{#collection.variant key=variant.key}}
{{#freestyle-usage "boxed-section-with-large-header" title=(concat variant.title " Boxed Section With Large Header")}}
<div class="boxed-section {{variant.slug}}">
<div class="boxed-section-head">
<div class="boxed-section-row">
Boxed Section With Large Header
<span class="badge is-white is-subtle bumper-left">Status</span>
</div>
<div class="boxed-section-row">
<span class="tag is-outlined">A tag that goes on a second line because it's rather long</span>
</div>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/each}}
{{/freestyle-collection}}
{{#freestyle-collection defaultKey="Normal" as |collection|}}
{{#each variants as |variant|}}
{{#collection.variant key=variant.key}}
{{#freestyle-usage "boxed-section-with-dark-body" title=(concat variant.title " Boxed Section With Dark Body")}}
<div class="boxed-section {{variant.slug}}">
<div class="boxed-section-head">
Boxed Section With Dark Body
</div>
<div class="boxed-section-body is-dark">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/each}}
{{/freestyle-collection}}

View File

@@ -1,33 +0,0 @@
{{#freestyle-usage 'breadcrumbs-standard' title='Standard Breadcrumbs'}}
<div class="navbar is-secondary">
<div class="navbar-item"></div>
<nav class="breadcrumb is-large">
<li>
<a href="#">Topic</a>
</li>
<li>
<a href="#">Sub-topic</a>
</li>
<li class="is-active">
<a href="#">Active Topic</a>
</li>
</nav>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Breadcrumbs are only ever used in the secondary nav of the primary header.
{{/freestyle-annotation}}
{{#freestyle-usage 'breadcrumbs-single' title='Single Breadcrumb'}}
<div class="navbar is-secondary">
<div class="navbar-item"></div>
<nav class="breadcrumb is-large">
<li>
<a href="#">Topic</a>
</li>
</nav>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Breadcrumbs are given a lot of emphasis and often double as a page title. Since they are also global state, they are important for helping a user keep their bearings.
{{/freestyle-annotation}}

View File

@@ -1,16 +0,0 @@
{{#freestyle-usage "colors"}}
{{freestyle-palette
colorPalette=nomadTheme
title="Nomad Theme"
description="Accent and neutrals."}}
{{freestyle-palette
colorPalette=productColors
title="Product Colors"
description="Colors from other HashiCorp products. Often borrowed for alternative accents and color schemes."}}
{{freestyle-palette
colorPalette=emotiveColors
title="Emotive Colors"
description="Colors used in conjunction with an emotional response."}}
{{/freestyle-usage}}

View File

@@ -1,6 +0,0 @@
{{#freestyle-usage "copy-button" title="Copy Button"}}
<span class="tag is-hollow is-small no-text-transform">
e8c898a0-794b-9063-7a7f-bf0c4a405f83
{{copy-button clipboardText="e8c898a0-794b-9063-7a7f-bf0c4a405f83"}}
</span>
{{/freestyle-usage}}

View File

@@ -1,33 +0,0 @@
{{#freestyle-usage "diff-viewer-insertions" title="Diff Viewer with Insertions"}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=insertionsOnly}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "diff-viewer-deletion" title="Diff Viewer with Deletions"}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=deletionsOnly}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "diff-viewer-insertions-verbose" title="Diff Viewer with Edits"}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=editsOnly}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Often times a diff will only have a couple lines. Minor tweaks to a job spec result in small diffs.
{{/freestyle-annotation}}
{{#freestyle-usage "diff-viewer-large" title="Diff Viewer with Many Changes"}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=largeDiff}}
</div>
</div>
{{/freestyle-usage}}

View File

@@ -1,25 +0,0 @@
{{#freestyle-usage "jumbo-distribution-bar"}}
{{#distribution-bar data=distributionBarData class="split-view" as |chart|}}
<ol class="legend">
{{#each chart.data as |datum index|}}
<li class="{{datum.className}} {{if (eq datum.index chart.activeDatum.index) "is-active"}} {{if (eq datum.value 0) "is-empty"}}">
<span class="color-swatch {{if datum.className datum.className (concat "swatch-" index)}}" />
<span class="value" data-test-legend-value="{{datum.className}}">{{datum.value}}</span>
<span class="label">
{{datum.label}}
</span>
</li>
{{/each}}
</ol>
{{/distribution-bar}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<div class="block">
A variation of the Distribution Bar component for when the distribution bar is the central component of the page. It's a larger format that requires no interaction to see the data labels and values.
</div>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=distributionBarData}}
</div>
</div>
{{/freestyle-annotation}}

View File

@@ -1,77 +0,0 @@
{{#freestyle-collection as |collection|}}
{{#collection.variant key="standard"}}
{{#freestyle-usage 'distribution-bar-standard'}}
<div class="block" style="height:50px; width:200px;">
{{distribution-bar data=distributionBarData}}
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<div class="block">
The distribution bar chart proportionally show data in a single bar. It includes a tooltip out of the box, assumes the size of the container element, and is designed to be styled with CSS.
</div>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=distributionBarData}}
</div>
</div>
{{/freestyle-annotation}}
{{/collection.variant}}
{{#collection.variant key="with classes"}}
{{#freestyle-usage 'distribution-bar-with-classes'}}
<div class="block" style="height:50px; width:200px;">
{{distribution-bar data=distributionBarDataWithClasses}}
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<div class="block">
If a datum provides a <code>className</code> property, it will be assigned to the corresponding <code>rect</code> element, allowing for custom colorization.
</div>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=distributionBarDataWithClasses}}
</div>
</div>
{{/freestyle-annotation}}
{{/collection.variant}}
{{#collection.variant key="flexibility"}}
{{#freestyle-usage 'distribution-bar-sizing-1'}}
<div class="block" style="height:10px; width:600px;">
{{distribution-bar data=distributionBarData}}
</div>
{{/freestyle-usage}}
{{#freestyle-usage 'distribution-bar-sizing-2'}}
<div class="block" style="height:200px; width:30px;">
{{distribution-bar data=distributionBarData}}
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<div class="block">
Distribution bar assumes the dimensions of the container.
</div>
{{/freestyle-annotation}}
{{/collection.variant}}
{{#collection.variant key="live updating"}}
{{#freestyle-usage 'distribution-bar-updating'}}
<div class="block" style="height:50px; width:600px;">
{{distribution-bar data=distributionBarDataRotating}}
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<div class="block">
Distribution bar animates with data changes.
</div>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=distributionBarDataRotating}}
</div>
</div>
{{/freestyle-annotation}}
{{/collection.variant}}
{{#collection.variant key="single bar"}}
{{#freestyle-usage 'distribution-bar-single'}}
<div class="block" style="height:50px; width:600px;">
{{distribution-bar data=distributionBarDatum}}
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/freestyle-collection}}

View File

@@ -1,55 +0,0 @@
{{#freestyle-usage "dropdown" title="Simple Dropdown"}}
{{#power-select
options=options
selected=selectedOption
searchField="name"
searchEnabled=(gt options.length 10)
onChange=(action (mut selectedOption))
as |option|}}
{{option.name}}
{{/power-select}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
Power Select currently fulfills all of Nomad's dropdown needs out of the box.
{{/freestyle-annotation}}
{{#freestyle-usage "dropdown-sized" title="Dropdown Resized"}}
<div class="columns">
<div class="column is-3">
{{#power-select
options=options
selected=selectedOption2
searchField="name"
searchEnabled=(gt options.length 10)
onChange=(action (mut selectedOption2))
as |option|}}
{{option.name}}
{{/power-select}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Dropdowns are always 100% wide. To control the width of a dropdown, adjust the dimensions of its container. One way to achieve this is using columns.
{{/freestyle-annotation}}
{{#freestyle-usage "dropdown-search" title="Dropdown with Search"}}
<div class="columns">
<div class="column is-3">
{{#power-select
options=manyOptions
selected=selectedOption3
searchField="name"
searchEnabled=(gt manyOptions.length 10)
onChange=(action (mut selectedOption3))
as |option|}}
{{option.name}}
{{/power-select}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Whether or not the dropdown has a search box is configurable. Typically the default is to show a search once a dropdown has more than 10 options.
{{/freestyle-annotation}}

View File

@@ -1,14 +0,0 @@
{{#freestyle-usage "font-sizing"}}
<div class="block">
<h1 class="title">Large Title</h1>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
<div class="block">
<h2 class="title is-4">Medium Title</h2>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
<div class="block">
<h3 class="title is-5">Small Title</h3>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
{{/freestyle-usage}}

View File

@@ -1,52 +0,0 @@
{{#freestyle-collection inline=true as |collection|}}
{{#collection.variant key="-apple-system"}}
{{#freestyle-usage "font-apple-system" title="-apple-system"}}
{{freestyle-typeface fontFamily="-apple-system"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="BlinkMacSystemFont"}}
{{#freestyle-usage "font-blink-mac-system" title="BlinkMacSystemFont"}}
{{freestyle-typeface fontFamily="BlinkMacSystemFont"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Segoe UI"}}
{{#freestyle-usage "font-segoe-ui" title="Segoe UI"}}
{{freestyle-typeface fontFamily="Segoe UI"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Roboto"}}
{{#freestyle-usage "font-roboto" title="Roboto"}}
{{freestyle-typeface fontFamily="Roboto"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Oxygen Sans"}}
{{#freestyle-usage "font-oxygen-sans" title="Oxygen Sans"}}
{{freestyle-typeface fontFamily="Oxygen-Sans"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Ubuntu"}}
{{#freestyle-usage "font-ubuntu" title="Ubuntu"}}
{{freestyle-typeface fontFamily="Ubuntu"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Cantarell"}}
{{#freestyle-usage "font-cantarell" title="Cantarell"}}
{{freestyle-typeface fontFamily="Cantarell"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="Helvetica Neue"}}
{{#freestyle-usage "font-helvetica-neue" title="Helvetica Neue"}}
{{freestyle-typeface fontFamily="Helvetica Neue"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="sans-serif"}}
{{#freestyle-usage "font-sans-serif" title="sans-serif"}}
{{freestyle-typeface fontFamily="sans-serif"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="monospace"}}
{{#freestyle-usage "font-monospace" title="monospace"}}
{{freestyle-typeface fontFamily="monospace"}}
{{/freestyle-usage}}
{{/collection.variant}}
{{/freestyle-collection}}

View File

@@ -1,156 +0,0 @@
{{#freestyle-usage "gutter-nav" title="Gutter Menu"}}
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="#" class="is-active">Place One</a></li>
<li><a href="#">Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="#">Feature One</a></li>
<li><a href="#">Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "gutter-nav-rich-components" title="Gutter Navigation with Rich Components"}}
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li>
<div class="menu-item">
{{#power-select
selected=(or selection "One")
options=(array "One" "Two" "Three")
onChange=(action (mut selection))
as |option|}}
{{option}}
{{/power-select}}
</div>
</li>
<li><a href="#" class="is-active">Place One</a></li>
<li><a href="#">Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="#">Feature One</a></li>
<li><a href="#">Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
In order to keep the gutter navigation streamlined and easy to navigation, rich components should be avoided when possible. When not possible, they should be kept near the top.
{{/freestyle-annotation}}
{{#freestyle-usage "gutter-nav-many-items" title="Hypothetical Gutter Navigation with Many Items"}}
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="#">Place {{item}}</a></li>
{{/each}}
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="#">Feature {{item}}</a></li>
{{/each}}
</ul>
<p class="menu-label">Other</p>
<ul class="menu-list">
<li><a href="#" class="is-active">The one that didn't fit in</a></li>
</ul>
<p class="menu-label">Things</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="#">Thing {{item}}</a></li>
{{/each}}
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
There will only ever be one gutter menu in the Nomad UI, but it helps to imagine a situation where there are many navigation items in the gutter.
{{/freestyle-annotation}}
{{#freestyle-usage "gutter-nav-icon-items" title="Hypothetical Gutter Navigation with Icon Items"}}
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="#">{{x-icon "clock"}} Place One</a></li>
<li><a href="#" class="is-active">{{x-icon "history"}} Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="#">{{x-icon "warning"}} Feature One</a></li>
<li><a href="#">{{x-icon "media-pause"}} Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
In the future, the gutter menu may have icons.
{{/freestyle-annotation}}
{{#freestyle-usage "gutter-nav-global" title="Global Gutter Navigation"}}
<div class="columns">
<div class="column is-4">
{{#gutter-menu}}
{{!-- Page content here --}}
{{/gutter-menu}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Since there will only ever be one gutter menu in the UI, it makes sense to express the menu as a singleton component. This is what that singleton component looks like.</p>
<p><strong>Note:</strong> Normally the gutter menu is rendered within a page layout and is fixed position. The columns shown in this example are only to imitate the actual width without applying fixed positioning.</p>
{{/freestyle-annotation}}

View File

@@ -1,17 +0,0 @@
{{#freestyle-usage "header" title="Global Header"}}
<nav class="navbar is-primary">
<div class="navbar-brand">
<span class="gutter-toggle" aria-label="menu">
{{partial "partials/hamburger-menu"}}
</span>
<span class="navbar-item is-logo">
{{partial "partials/nomad-logo"}}
</span>
</div>
<div class="navbar-end">
<a class="navbar-item">Secondary</a>
<a class="navbar-item">Links</a>
<a class="navbar-item">Here</a>
</div>
</nav>
{{/freestyle-usage}}

View File

@@ -1,80 +0,0 @@
{{#freestyle-usage "inline-definitions" title="Inline Definitions"}}
<div class="boxed-section is-small">
<div class="boxed-section-body inline-definitions">
<span class="label">Some Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Running?</span>
<span>Yes</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
A way to tightly display key/value information. Typically seen at the top of pages.
{{/freestyle-annotation}}
{{#freestyle-usage "inline-definitions-variants" title="Inline Definitions Variants"}}
<div class="boxed-section is-small is-success">
<div class="boxed-section-body inline-definitions">
<span class="label">Success Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-warning">
<div class="boxed-section-body inline-definitions">
<span class="label">Warning Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-danger">
<div class="boxed-section-body inline-definitions">
<span class="label">Danger Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-info">
<div class="boxed-section-body inline-definitions">
<span class="label">Info Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Inline definitions are meant to pair well with emotive color variations.
{{/freestyle-annotation}}

View File

@@ -1,15 +0,0 @@
{{#freestyle-usage "json-viewer" title="JSON Viewer"}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=jsonSmall}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "json-viewer-full" title="JSON Viewer for Full Document"}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=jsonLarge}}
</div>
</div>
{{/freestyle-usage}}

View File

@@ -1,42 +0,0 @@
{{#freestyle-usage "line-chart-standard" title="Standard"}}
<div class="block" style="height:100px; width: 400px;">
{{line-chart data=lineChartData xProp="year" yProp="value" chartClass="is-primary"}}
</div>
<div class="block" style="height:100px; width: 400px;">
{{line-chart data=lineChartMild xProp="year" yProp="value" chartClass="is-info"}}
</div>
{{/freestyle-usage}}
{{#freestyle-usage "line-chart-fill-width" title="Fluid width"}}
<div class="block" style="height:250px;">
{{line-chart data=lineChartData xProp="year" yProp="value" chartClass="is-danger"}}
</div>
<div class="block" style="height:250px;">
{{line-chart data=lineChartMild xProp="year" yProp="value" chartClass="is-warning"}}
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>A line chart will assume the width of its container. This includes the dimensions of the axes, which are calculated based on real DOM measurements. This requires a two-pass render: first the axes are placed with their real domains (in order to capture width and height of tick labels), second the axes are adjusted to make sure both the x and y axes are within the height and width bounds of the container.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "line-chart-live-data" title="Live data"}}
<div class="block" style="height:250px">
{{line-chart
data=lineChartLive
xProp="ts"
yProp="val"
timeseries=true
chartClass="is-primary"
xFormat=secondsFormat}}
</div>
{{/freestyle-usage}}
{{#freestyle-usage "line-chart-with-gaps" title="Data with gaps"}}
<div class="block" style="height:250px">
{{line-chart
data=lineChartGapData
xProp="year"
yProp="value"
chartClass="is-primary"}}
</div>
{{/freestyle-usage}}

View File

@@ -1,24 +0,0 @@
{{#freestyle-usage "log-stream" title="Log Stream"}}
<div class="boxed-section">
<div class="boxed-section-head">
<span>
<button
class="button {{if (eq mode1 "stdout") "is-info"}}"
onclick={{action (mut mode1) "stdout"}}>stdout</button>
<button
class="button {{if (eq mode1 "stderr") "is-danger"}}"
onclick={{action (mut mode1) "stderr"}}>stderr</button>
</span>
<span class="pull-right">
<button class="button is-white">Head</button>
<button class="button is-white">Tail</button>
<button class="button is-white" onclick={{toggle "isPlaying1" this}}>
{{x-icon (if isPlaying1 "media-play" "media-pause") class="is-text"}}
</button>
</span>
</div>
<div class="boxed-section-body is-dark is-full-bleed">
<pre class="cli-window"><code>{{if (eq mode1 "stdout") sampleOutput sampleError}}</code></pre>
</div>
</div>
{{/freestyle-usage}}

View File

@@ -1,125 +0,0 @@
{{#freestyle-usage "metrics" title="Metrics"}}
<div class="metric-group">
<div class="metric">
<h3 class="label">Label</h3>
<p class="value">12</p>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Metrics are a way to show simple values (generally numbers). Labels are smaller than numbers to put emphasis on the data.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "metric-groups" title="Metric Groups"}}
<div class="metric-group">
<div class="metric">
<h3 class="label">Label</h3>
<p class="value">1 / 2</p>
</div>
<div class="metric">
<h3 class="label">Number</h3>
<p class="value">1,300</p>
</div>
<div class="metric">
<h3 class="label">Datacenter</h3>
<p class="value">dc1</p>
</div>
</div>
<div class="metric-group">
<div class="metric">
<h3 class="label">Today</h3>
<p class="value">81º</p>
</div>
<div class="metric">
<h3 class="label">Tomorrow</h3>
<p class="value">73º</p>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Related metrics should be lumped together in metric groups. All metrics have to be in a metric group. By putting multiple metrics in a single group, they will be visually lumped together.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "metric-colors" title="Metric Colors"}}
<div class="metric-group">
<div class="metric is-info">
<h3 class="label">Info</h3>
<p class="value">1</p>
</div>
<div class="metric is-success">
<h3 class="label">Success</h3>
<p class="value">2</p>
</div>
<div class="metric is-warning">
<h3 class="label">Warning</h3>
<p class="value">3</p>
</div>
<div class="metric is-danger">
<h3 class="label">Danger</h3>
<p class="value">4</p>
</div>
</div>
<div class="metric-group">
<div class="metric is-white">
<h3 class="label">White</h3>
<p class="value">5</p>
</div>
<div class="metric is-light">
<h3 class="label">Light</h3>
<p class="value">6</p>
</div>
<div class="metric is-primary">
<h3 class="label">Primary</h3>
<p class="value">7</p>
</div>
<div class="metric is-dark">
<h3 class="label">Dark</h3>
<p class="value">8</p>
</div>
<div class="metric is-black">
<h3 class="label">Black</h3>
<p class="value">9</p>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>All color-modifiers work for metrics, but some work better than others.</p>
<p>Emotive colors work well and are put to use when applicable. Other colors have worse support and less utility.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "metric-states" title="Metric States"}}
<div class="metric-group">
<div class="metric is-primary is-faded">
<h3 class="label">One</h3>
<p class="value">A</p>
</div>
<div class="metric is-primary">
<h3 class="label">Two</h3>
<p class="value">B</p>
</div>
<div class="metric is-primary is-faded">
<h3 class="label">Three</h3>
<p class="value">C</p>
</div>
</div>
<div class="metric-group">
<div class="metric is-danger is-faded">
<h3 class="label">One</h3>
<p class="value">A</p>
</div>
<div class="metric is-danger is-faded">
<h3 class="label">Two</h3>
<p class="value">B</p>
</div>
<div class="metric is-danger">
<h3 class="label">Three</h3>
<p class="value">C</p>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Metrics have a disabled state. This is used when a metric is non-existent or irrelevant. It's just as important to show the lack of value as it is to show a value, so simply not rendering non-existent or irrelevant metrics would be worse.</p>
{{/freestyle-annotation}}

View File

@@ -1,58 +0,0 @@
{{#freestyle-usage "multi-select-dropdown" title="Multi-select dropdown"}}
{{multi-select-dropdown
label="Example Dropdown"
options=options1
selection=selection1
onSelect=(action (mut selection1))}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
A wrapper around basic-dropdown for creating a list of checkboxes and tracking the state thereof.
{{/freestyle-annotation}}
{{#freestyle-usage "multi-select-right-aligned" title="Multi-select dropdown right-aligned"}}
<div style="display:flex; justify-content:flex-end">
{{multi-select-dropdown
label="Example right-aligned Dropdown"
options=options1
selection=selection1
onSelect=(action (mut selection1))}}
</div>
{{/freestyle-usage}}
{{#freestyle-usage "multi-select-dropdown-may" title="Multi-select dropdown with many options"}}
{{multi-select-dropdown
label="Lots of options in here"
options=optionsMany
selection=selectionMany
onSelect=(action (mut selectionMany))}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
A strength of the multi-select-dropdown is its simple presentation. It is quick to select options and it is quick to remove options.
However, this strength becomes a weakness when there are too many options. Since the selection isn't pinned in any way, removing a selection
can become an adventure of scrolling up and down. Also since the selection isn't pinned, this component can't support search, since search would
entirely mask the selection.
{{/freestyle-annotation}}
{{#freestyle-usage "multi-select-dropdown-bar" title="Multi-select dropdown bar"}}
<div class="button-bar">
{{multi-select-dropdown
label="Datacenter"
options=optionsDatacenter
selection=selectionDatacenter
onSelect=(action (mut selectionDatacenter))}}
{{multi-select-dropdown
label="Type"
options=optionsType
selection=selectionType
onSelect=(action (mut selectionType))}}
{{multi-select-dropdown
label="Status"
options=optionsStatus
selection=selectionStatus
onSelect=(action (mut selectionStatus))}}
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
Since this is a core component for faceted search, it makes sense to construct an arrangement of multi-select dropdowns.
Do this by wrapping all the options in a <code>.button-bar</code> container.
{{/freestyle-annotation}}

View File

@@ -1,18 +0,0 @@
{{#freestyle-usage "page-tabs" title="Page Tabs"}}
<div class="tabs">
<ul>
<li><a href="#">Overview</a></li>
<li><a href="#" class="is-active">Definition</a></li>
<li><a href="#">Versions</a></li>
<li><a href="#">Deployments</a></li>
</ul>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "page-tabs-single" title="Single Page Tab"}}
<div class="tabs">
<ul>
<li><a href="#" class="is-active">Overview</a></li>
</ul>
</div>
{{/freestyle-usage}}

View File

@@ -1,51 +0,0 @@
{{#freestyle-usage "page-title" title="Page Title"}}
<div class="mock-spacing">
<h1 class="title">This is the Page Title</h1>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>In its simplest form, a page title is just an H1.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "page-title-after-elements" title="Page Title with After Elements"}}
<div class="mock-spacing">
<h1 class="title">
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
</h1>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>It is common to put high-impact tags and badges to the right of titles. These tags should only ever appear on the right-hand side of the title, and they should be listed in descending weights. Tags with a background are heavier than tags that are hollow. Longer values are heavier than shorter values.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "page-title-with-status-light" title="Page Title with Status Light"}}
<div class="mock-spacing">
<h1 class="title">
<span class="node-status-light initializing"></span>
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
</h1>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>A simple color or pattern is faster to scan than a title and can often say more than words can. For pages that have an important status component to them (e.g., client detail page), a status light can be shown to the left of the title where typically eyes will begin to scan a page.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "page-title-with-actions" title="Page Title with Actions"}}
<div class="mock-spacing">
<h1 class="title">
<span class="node-status-light initializing"></span>
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
<button class="button is-warning is-small is-inline">If you wish</button>
<button class="button is-danger is-outlined is-important is-small is-inline">No Regrets</button>
</h1>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>When actions apply to the entire context of a page, (e.g., job actions on the job detail page), buttons for these actions go in the page title. Buttons are always placed on the far right end of a page title. No elements can go to the right of these buttons.</p>
{{/freestyle-annotation}}

View File

@@ -1,77 +0,0 @@
{{#freestyle-usage "progress-bar" title="Progress Bar"}}
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-primary is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "progress-bar-colors" title="Progress Bar Colors"}}
<div class="columns">
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-info is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-success is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-warning is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-danger is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "progress-bar-live" title="Progress Bar Live Updates"}}
<div class="columns">
<div class="column is-one-third">
<div class="inline-chart tooltip" role="tooltip" aria-label="{{numerator}} / {{denominator}}">
<progress
class="progress is-primary is-small"
value="{{percentage}}"
max="1">
{{percentage}}
</progress>
</div>
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{json-viewer json=liveDetails}}
</div>
</div>
{{/freestyle-annotation}}

View File

@@ -1,3 +0,0 @@
{{#freestyle-usage "proxy-tag" title="Proxy Tag"}}
{{proxy-tag}}
{{/freestyle-usage}}

View File

@@ -1,19 +0,0 @@
{{#freestyle-usage "search-box" title="Search Box"}}
{{search-box
searchTerm=(mut searchTerm1)
placeholder="Search things..."}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>The search box component is a thin wrapper around a simple input. Although the searchTerm passed to it is a mutable reference, internally search term is debounced. This is to prevent potentially expensive code that depends on searchTerm from recomputing many times as a user types.</p>
<p>There is no form of the search box component that defers updating the searchTerm reference until the user manually clicks a "Search" button. This can be achieved by placing a button next to the search bar component and using it to perform search, but search should be automatic whenever possible.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "search-box-compact" title="Search Box Compact"}}
{{search-box
searchTerm=(mut searchTerm2)
placeholder="Search things..."
inputClass="is-compact"}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Search box provides an inputClass property to control the inner input. This is nice for fitting the search box into smaller spaces, such as boxed-section heads.</p>
{{/freestyle-annotation}}

View File

@@ -1,20 +0,0 @@
{{#freestyle-usage "stats-time-series-standard" title="Stats Time Series"}}
<div class="block" style="height:100px; width: 400px;">
{{stats-time-series data=staticMetrics chartClass="is-primary"}}
</div>
{{/freestyle-usage}}
{{#freestyle-usage "stats-time-series-comparison" title="Stats Time Series High/Low Comparison"}}
<div class="columns">
<div class="block column" style="height:200px; width:400px">
{{stats-time-series data=metricsHigh chartClass="is-info"}}
</div>
<div class="block column" style="height:200px; width:400px">
{{stats-time-series data=metricsLow chartClass="is-info"}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Line charts, and therefore stats time series charts, use a constant linear gradient with a height equal to the canvas. This makes the color intensity of the gradient at values consistent across charts as long as those charts have the same y-axis domain.</p>
<p>This is used to great effect with stats charts since they all have a y-axis domain of 0-100%.</p>
{{/freestyle-annotation}}

View File

@@ -1,3 +0,0 @@
{{#freestyle-usage "table-configuration" title="Table, Configuration"}}
{{attributes-table attributes=attributes class="attributes-table"}}
{{/freestyle-usage}}

View File

@@ -1,257 +0,0 @@
{{#freestyle-usage "table-simple" title="Table"}}
{{#list-table source=shortList as |t|}}
{{#t.head}}
<th>Name</th>
<th>Language</th>
<th>Description</th>
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Tables have airy designs with a minimal amount of borders. This maximizes their utility.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-search" title="Table Search"}}
<div class="boxed-section">
<div class="boxed-section-head">
Table Name
{{search-box
searchTerm=(mut searchTerm)
placeholder="Search..."
class="is-inline pull-right"
inputClass="is-compact"}}
</div>
<div class="boxed-section-body {{if filteredShortList.length "is-full-bleed"}}">
{{#if filteredShortList.length}}
{{#list-table source=filteredShortList as |t|}}
{{#t.head}}
<th>Name</th>
<th>Language</th>
<th>Description</th>
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{else}}
<div class="empty-message">
<h3 class="empty-message-headline">No Matches</h3>
<p class="empty-message-body">No products match your query.</p>
</div>
{{/if}}
</div>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Tables compose with boxed-section and boxed-section composes with search box.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-sortable-columns" title="Table Sortable Columns"}}
{{#list-table
source=sortedShortList
sortProperty=sortProperty
sortDescending=sortDescending as |t|}}
{{#t.head}}
{{#t.sort-by prop="name"}}Name{{/t.sort-by}}
{{#t.sort-by prop="lang" class="is-2"}}Language{{/t.sort-by}}
<th>Description</th>
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>The list-table component provides a <code>sort-by</code> contextual component for building <code>link-to</code> components with the appropriate query params.</p>
<p>This leaves the component stateless, relying on data to be passed down and sending actions back up via the router (via link-to).</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-multi-row" title="Table Multi-row"}}
{{#list-table
source=sortedShortList
sortProperty=sortProperty
sortDescending=sortDescending
class="is-striped" as |t|}}
{{#t.head}}
{{#t.sort-by prop="name"}}Name{{/t.sort-by}}
{{#t.sort-by prop="lang"}}Language{{/t.sort-by}}
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
</tr>
<tr>
<td colspan="2">{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>THe list-table component attempts to be as flexible as possible. For this reason, <code>t.body</code> does not provide the typical <code>tr</code> element. It's sometimes desired to have multiple elements per record.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-pagination" title="Table Pagination"}}
{{#list-pagination source=longList size=5 page=currentPage as |p|}}
{{#list-table source=p.list class="with-foot" as |t|}}
{{#t.head}}
<th class="is-1">Rank</th>
<th>City</th>
<th>State</th>
<th>Population</th>
<th>Growth</th>
{{/t.head}}
{{#t.body key="model.rank" as |row|}}
<tr>
<td>{{row.model.rank}}</td>
<td>{{row.model.city}}</td>
<td>{{row.model.state}}</td>
<td>{{row.model.population}}</td>
<td>{{format-percentage row.model.growth total=1}}</td>
</tr>
{{/t.body}}
{{/list-table}}
<div class="table-foot">
<nav class="pagination">
<span class="bumper-left">U.S. City population and growth from 2000-2013</span>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
</div>
{{#p.prev class="pagination-previous"}} &lt; {{/p.prev}}
{{#p.next class="pagination-next"}} &gt; {{/p.next}}
<ul class="pagination-list"></ul>
</nav>
</div>
{{/list-pagination}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Pagination works like sorting: using <code>link-to</code>s to set a query param.</p>
<p>Pagination, like Table, is a minimal design. Only a next and previous button are available. The current place in the set of pages is tracked by showing which slice of items is currently shown.</p>
<p>The pagination component exposes first and last components (for jumping to the beginning and end of a list) as well as pageLinks for generating links around the current page.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-row-links" title="Table Row Links"}}
{{#list-table source=shortList as |t|}}
{{#t.head}}
<th>Name</th>
<th>Language</th>
<th>Description</th>
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr class="is-interactive">
<td><a href="#" class="is-primary">{{row.model.name}}</a></td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>It is common for tables to act as lists of links, (e.g., clients list all allocations, each row links to the allocation detail). The helper class <code>is-interactive</code> on the <code>tr</code> makes table rows have a pointer cursor. The helper class <code>is-primary</code> on the <code>a</code> element in a table row makes the link bold and black instead of blue. This makes the link stand out less, since the entire row is a link.</p>
<p>A few rules for using table row links:</p>
<ol>
<li>The <code>is-primary</code> cell should always be the first cell</li>
<li>The <code>is-primary</code> cell should always contain a link to the destination in the form of an <code>a</code> element. This is to support opening a link in a new tab.</li>
<li>The full row should transition to the destination on click. This is to improve the usability of a table by creating a larger click area.</li>
</ol>
{{/freestyle-annotation}}
{{#freestyle-usage "table-cell-links" title="Table Cell Links"}}
{{#list-table source=shortList as |t|}}
{{#t.head}}
<th>Name</th>
<th>Language</th>
<th>Description</th>
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr>
<td><a href={{row.model.link}}>{{row.model.name}}</a></td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Links in table cells are just links.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-cell-decorations" title="Table Cell Decorations"}}
{{#list-table source=shortList as |t|}}
{{#t.head}}
<th>Name</th>
<th>Language</th>
<th>Description</th>
{{/t.head}}
{{#t.body key="model.name" as |row|}}
<tr>
<td><a href={{row.model.link}}>{{row.model.name}}</a></td>
<td class="nowrap">
<span class="color-swatch
{{if (eq row.model.lang "ruby") "swatch-6"}}
{{if (eq row.model.lang "golang") "swatch-5"}}" />
{{row.model.lang}}
</td>
<td>{{row.model.desc}}</td>
</tr>
{{/t.body}}
{{/list-table}}
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Small icons and accents of color make tables easier to scan.</p>
{{/freestyle-annotation}}
{{#freestyle-usage "table-cell-icons" title="Table Cell Icons"}}
{{#list-pagination source=longList size=5 page=currentPage as |p|}}
{{#list-table source=p.list class="with-foot" as |t|}}
{{#t.head}}
<th class="is-narrow"></th>
<th class="is-1">Rank</th>
<th>City</th>
<th>State</th>
<th>Population</th>
<th>Growth</th>
{{/t.head}}
{{#t.body key="model.rank" as |row|}}
<tr>
<td class="is-narrow">
{{#if (lt row.model.growth 0)}}
{{x-icon "warning" class="is-warning"}}
{{/if}}
</td>
<td>{{row.model.rank}}</td>
<td>{{row.model.city}}</td>
<td>{{row.model.state}}</td>
<td>{{row.model.population}}</td>
<td>{{format-percentage row.model.growth total=1}}</td>
</tr>
{{/t.body}}
{{/list-table}}
<div class="table-foot">
<nav class="pagination">
<span class="bumper-left">U.S. City population and growth from 2000-2013. Cities with negative growth denoted.</span>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
</div>
{{#p.prev class="pagination-previous"}} &lt; {{/p.prev}}
{{#p.next class="pagination-next"}} &gt; {{/p.next}}
<ul class="pagination-list"></ul>
</nav>
</div>
{{/list-pagination}}
{{/freestyle-usage}}

View File

@@ -1,204 +0,0 @@
{{#freestyle-usage 'timeline' title="Simple Timeline"}}
<ol class="timeline">
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number one
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number two
</div>
</div>
</li>
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number three
</div>
</div>
</li>
</ol>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<p>Timelines are a combination of objects and notes. Objects compose with boxed sections to create structure.</p>
<p>Timeline notes should be used sparingly when possible. In this example there is a note per day rather than a note per object.</p>
{{/freestyle-annotation}}
{{#freestyle-usage 'timeline-intricate' title="Detailed Timeline"}}
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-running">Running</span>
<span class="bumper-left pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span class="tooltip" aria-label="{{format-month-ts (now)}}">{{moment-from-now (now)}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-complete">Complete</span>
<span class="bumper-left pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-month-ts yesterday}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-error">Failed</span>
<span class="bumper-left pair is-faded">
<span class="term">Reverted</span>
<span class="badge is-light is-faded"><code>fec9218e</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-month-ts yesterday}}</span>
</span>
</div>
</div>
</li>
</ol>
{{/freestyle-usage}}
{{#freestyle-usage 'timeline-toggles' title='Toggling Timeline Objects'}}
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-running">Running</span>
<span class="bumper-left pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<button
class="button is-light is-compact pull-right"
onclick={{action (mut toggle1) (not toggle1)}}>
{{if toggle1 "Close" "Open"}}
</button>
</div>
{{#if toggle1}}
<div class="boxed-section-body">
<p>Some details for the timeline object.</p>
</div>
{{/if}}
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-complete">Complete</span>
<span class="bumper-left pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<button
class="button is-light is-compact pull-right"
onclick={{action (mut toggle2) (not toggle2)}}>
{{if toggle2 "Close" "Open"}}
</button>
</div>
{{#if toggle2}}
<div class="boxed-section-body">
<p>Some details for the timeline object.</p>
</div>
{{/if}}
</div>
</li>
</ol>
{{/freestyle-usage}}
{{#freestyle-usage 'timeline-emphasis' title='Emphasizing a Timeline Object'}}
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span class="tooltip" aria-label="{{format-ts (now)}}">{{moment-from-now (now)}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head">
Pay attention here
</div>
<div class="boxed-section-body">
<span class="pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-ts yesterday}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="pair is-faded">
<span class="term">Reverted</span>
<span class="badge is-light is-faded"><code>fec9218e</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-ts yesterday}}</span>
</span>
</div>
</div>
</li>
</ol>
{{/freestyle-usage}}
{{#freestyle-annotation}}
By using a full boxed-section for an emphasized timeline object, the object takes up more space and gets more visual weight. It also adheres to existing patterns.
{{/freestyle-annotation}}

View File

@@ -1,40 +0,0 @@
{{#freestyle-usage "two-step-button" title="Two Step Button"}}
<div class="mock-spacing">
{{two-step-button
idleText="Scary Action"
cancelText="Nvm"
confirmText="Yep"
confirmationMessage="Wait, really? Like...seriously?"}}
</div>
{{/freestyle-usage}}
{{#freestyle-usage "two-step-button-title" title="Two Step Button in Title"}}
<div class="mock-spacing">
<h1 class="title">
This is a page title
{{two-step-button
idleText="Scary Action"
cancelText="Nvm"
confirmText="Yep"
confirmationMessage="Wait, really? Like...seriously?"}}
</h1>
</div>
{{/freestyle-usage}}
{{#freestyle-usage "two-step-button-loading" title="Two Step Button Loading State"}}
<div class="mock-spacing">
<h1 class="title">
This is a page title
{{two-step-button
idleText="Scary Action"
cancelText="Nvm"
confirmText="Yep"
confirmationMessage="Wait, really? Like...seriously?"
awaitingConfirmation=true
state="prompt"}}
</h1>
</div>
{{/freestyle-usage}}
{{#freestyle-annotation}}
<strong>Note:</strong> the <code>state</code> property is internal state and only used here to bypass the idle state for demonstration purposes.
{{/freestyle-annotation}}

View File

@@ -8,9 +8,6 @@
{{/link-to}}
</div>
<div class="navbar-end">
{{#if (eq config.environment "development")}}
{{#link-to "freestyle" class="navbar-item"}}Styleguide{{/link-to}}
{{/if}}
<a href="https://nomadproject.io/docs" class="navbar-item">Documentation</a>
{{#link-to "settings.tokens" class="navbar-item"}}ACL Tokens{{/link-to}}
</div>

View File

@@ -1,137 +0,0 @@
<div id="styleguide">
{{#freestyle-guide title='Nomad UI' subtitle='Styles and Patterns'}}
{{#freestyle-section name="Theme" as |section|}}
{{#section.subsection name="Font Stacks"}}
{{freestyle/sg-font-stacks}}
{{/section.subsection}}
{{#section.subsection name="Text Sizing"}}
{{freestyle/sg-font-sizing}}
{{/section.subsection}}
{{#section.subsection name="Colors"}}
{{freestyle/sg-colors}}
{{/section.subsection}}
{{/freestyle-section}}
{{#freestyle-section name="Components" as |section|}}
{{#section.subsection name="Accordion"}}
{{freestyle/sg-accordion}}
{{/section.subsection}}
{{#section.subsection name="Alerts"}}
{{freestyle/sg-alerts}}
{{/section.subsection}}
{{#section.subsection name="Boxed section"}}
{{freestyle/sg-boxed-section}}
{{/section.subsection}}
{{#section.subsection name="Breadcrumbs"}}
{{freestyle/sg-breadcrumbs}}
{{/section.subsection}}
{{#section.subsection name="Buttons"}}
{{freestyle/sg-buttons}}
{{/section.subsection}}
{{#section.subsection name="Copy Button"}}
{{freestyle/sg-copy-button}}
{{/section.subsection}}
{{#section.subsection name="Diff Viewer"}}
{{freestyle/sg-diff-viewer}}
{{/section.subsection}}
{{#section.subsection name="Dropdown"}}
{{freestyle/sg-dropdown}}
{{/section.subsection}}
{{#section.subsection name="Gutter menu"}}
{{freestyle/sg-gutter-menu}}
{{/section.subsection}}
{{#section.subsection name="Header"}}
{{freestyle/sg-header}}
{{/section.subsection}}
{{#section.subsection name="Inline definitions"}}
{{freestyle/sg-inline-definitions}}
{{/section.subsection}}
{{#section.subsection name="JSON Viewer"}}
{{freestyle/sg-json-viewer}}
{{/section.subsection}}
{{#section.subsection name="Log Stream"}}
{{freestyle/sg-log-stream}}
{{/section.subsection}}
{{#section.subsection name="Metrics"}}
{{freestyle/sg-metrics}}
{{/section.subsection}}
{{#section.subsection name="Multi-select dropdown"}}
{{freestyle/sg-multi-select-dropdown}}
{{/section.subsection}}
{{#section.subsection name="Page tabs"}}
{{freestyle/sg-page-tabs}}
{{/section.subsection}}
{{#section.subsection name="Page title"}}
{{freestyle/sg-page-title}}
{{/section.subsection}}
{{#section.subsection name="Proxy Tag"}}
{{freestyle/sg-proxy-tag}}
{{/section.subsection}}
{{#section.subsection name="Search box"}}
{{freestyle/sg-search-box}}
{{/section.subsection}}
{{#section.subsection name="Table"}}
{{freestyle/sg-table
sortProperty=sortProperty
sortDescending=sortDescending
currentPage=currentPage}}
{{/section.subsection}}
{{#section.subsection name="Table, Configuration"}}
{{freestyle/sg-table-configuration}}
{{/section.subsection}}
{{#section.subsection name="Timeline"}}
{{freestyle/sg-timeline}}
{{/section.subsection}}
{{#section.subsection name="Two-step Button"}}
{{freestyle/sg-two-step-button}}
{{/section.subsection}}
{{/freestyle-section}}
{{#freestyle-section name="Charts" as |section|}}
{{#section.subsection name="Distribution Bar"}}
{{freestyle/sg-distribution-bar}}
{{/section.subsection}}
{{#section.subsection name="Jumbo Distribution Bar"}}
{{freestyle/sg-distribution-bar-jumbo}}
{{/section.subsection}}
{{#section.subsection name="Line Chart"}}
{{freestyle/sg-line-chart}}
{{/section.subsection}}
{{#section.subsection name="Progress Bar"}}
{{freestyle/sg-progress-bar}}
{{/section.subsection}}
{{#section.subsection name="Stats Time Series"}}
{{freestyle/sg-stats-time-series}}
{{/section.subsection}}
{{/freestyle-section}}
{{/freestyle-guide}}
</div>

View File

@@ -0,0 +1,15 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|<%= classifiedModuleName %>',
};
export let <%= classifiedModuleName %> = () => {
return {
template: hbs`
<h5 class="title is-5"><%= header %></h5>
<<%= classifiedModuleName %>/>
`,
context: {},
}
};

View File

@@ -0,0 +1,36 @@
const getPathOption = require('ember-cli-get-component-path-option');
const stringUtil = require('ember-cli-string-utils');
const path = require('path');
module.exports = {
description: 'generates a story for storybook',
fileMapTokens: function() {
let { project } = this;
return {
__path__: function() {
return path.relative(project.root, project.root);
},
__markdownname__: function(options) {
return options.dasherizedModuleName;
},
__name__: function(options) {
return options.dasherizedModuleName;
},
};
},
locals: function(options) {
let contents = '';
return {
contents: contents,
path: getPathOption(options),
header: stringUtil
.dasherize(options.entity.name)
.split('-')
.map(word => stringUtil.capitalize(word))
.join(' '),
};
},
};

View File

@@ -43,6 +43,11 @@ module.exports = function(environment) {
enabled: USE_MIRAGE,
excludeFilesFromBuild: !USE_MIRAGE,
};
if (process.env.STORYBOOK === 'true') {
ENV.APP.autoboot = false;
ENV.rootURL = '/';
}
}
if (environment === 'test') {

View File

@@ -13,6 +13,8 @@
"lint:hbs": "ember-template-lint .",
"lint:js": "eslint .",
"start": "./node_modules/ember-cli/bin/ember server",
"build-storybook": "STORYBOOK=true ember build && build-storybook -s dist",
"storybook": "STORYBOOK=true ember serve & start-storybook -p 6006 -s dist",
"test": "./node_modules/ember-cli/bin/ember test"
},
"husky": {
@@ -21,7 +23,7 @@
}
},
"lint-staged": {
"{app,tests,config,lib,mirage}/**/*.js": [
"{app,tests,config,lib,mirage,stories}/**/*.js": [
"prettier --write",
"git add"
],
@@ -35,6 +37,7 @@
"@ember/jquery": "^0.6.0",
"@ember/optional-features": "^0.7.0",
"@hashicorp/structure-icons": "^1.3.0",
"@storybook/ember-cli-storybook": "^0.2.0",
"anser": "^1.4.8",
"broccoli-asset-rev": "^3.0.0",
"bulma": "0.6.1",
@@ -109,6 +112,17 @@
"qunit-dom": "^0.9.0",
"sass": "^1.17.3"
},
"optionalDependencies": {
"@babel/plugin-transform-member-expression-literals": "^7.2.0",
"@storybook/addon-knobs": "^5.2.5",
"@storybook/addon-storysource": "^5.2.5",
"@storybook/addon-viewport": "^5.2.5",
"@storybook/addons": "^5.2.5",
"@storybook/ember": "^5.2.5",
"babel-loader": "^8.0.6",
"ember-cli-get-component-path-option": "^1.0.0",
"ember-cli-string-utils": "^1.1.0"
},
"engines": {
"node": "8.* || >= 10.*"
},

View File

@@ -0,0 +1,174 @@
import hbs from 'htmlbars-inline-precompile';
import EmberObject, { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import DelayedTruth from '../utils/delayed-truth';
export default {
title: 'Charts|Distribution Bar',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar</h5>
<div class="block" style="height:50px; width:200px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} />
{{/if}}
</div>
<p class="annotation">The distribution bar chart proportionally show data in a single bar. It includes a tooltip out of the box, assumes the size of the container element, and is designed to be styled with CSS.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarData: [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 30 },
],
},
};
};
export let WithClasses = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar with classes</h5>
<div class="block" style="height:50px; width:200px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarDataWithClasses}} />
{{/if}}
</div>
<p class="annotation">If a datum provides a <code>className</code> property, it will be assigned to the corresponding <code>rect</code> element, allowing for custom colorization.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarDataWithClasses: [
{ label: 'Queued', value: 10, className: 'queued' },
{ label: 'Complete', value: 20, className: 'complete' },
{ label: 'Failed', value: 30, className: 'failed' },
],
},
};
};
export let Flexibility = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar flexibility</h5>
<div class="block" style="height:10px; width:600px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} />
{{/if}}
</div>
<div class="block" style="height:200px; width:30px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} />
{{/if}}
</div>
<p class="annotation">Distribution bar assumes the dimensions of the container.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarData: [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 30 },
],
},
};
};
export let LiveUpdating = () => {
return {
template: hbs`
<h5 class="title is-5">Live-updating Distribution Bar</h5>
<div class="block" style="height:50px; width:600px;">
<DistributionBar @data={{controller.distributionBarDataRotating}} />
</div>
<p class="annotation">Distribution bar animates with data changes.</p>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JsonViewer @json={{controller.distributionBarDataRotating}} />
</div>
</div>
`,
context: {
controller: EmberObject.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
distributionBarDataRotating: computed('timerTicks', () => {
return [
{ label: 'one', value: Math.round(Math.random() * 50) },
{ label: 'two', value: Math.round(Math.random() * 50) },
{ label: 'three', value: Math.round(Math.random() * 50) },
];
}),
}).create(),
},
};
};
export let SingleBar = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar with single bar</h5>
<div class="block" style="height:50px; width:600px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarDatum}} />
{{/if}}
</div>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarDatum: [{ label: 'one', value: 10 }],
},
};
};
export let Jumbo = () => {
return {
template: hbs`
<h5 class="title is-5">Jumbo Distribution Bar</h5>
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} @class="split-view" as |chart|>
<ol class="legend">
{{#each chart.data as |datum index|}}
<li class="{{datum.className}} {{if (eq datum.index chart.activeDatum.index) "is-active"}} {{if (eq datum.value 0) "is-empty"}}">
<span class="color-swatch {{if datum.className datum.className (concat "swatch-" index)}}" />
<span class="value" data-test-legend-value="{{datum.className}}">{{datum.value}}</span>
<span class="label">
{{datum.label}}
</span>
</li>
{{/each}}
</ol>
</DistributionBar>
{{/if}}
<p class="annotation">A variation of the Distribution Bar component for when the distribution bar is the central component of the page. It's a larger format that requires no interaction to see the data labels and values.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarData: [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 0 },
{ label: 'four', value: 35 },
],
},
};
};

View File

@@ -0,0 +1,147 @@
import hbs from 'htmlbars-inline-precompile';
import EmberObject from '@ember/object';
import { on } from '@ember/object/evented';
import moment from 'moment';
import DelayedArray from '../utils/delayed-array';
export default {
title: 'Charts|Line Chart',
};
let data1 = [
{ year: 2010, value: 10 },
{ year: 2011, value: 10 },
{ year: 2012, value: 20 },
{ year: 2013, value: 30 },
{ year: 2014, value: 50 },
{ year: 2015, value: 80 },
{ year: 2016, value: 130 },
{ year: 2017, value: 210 },
{ year: 2018, value: 340 },
];
let data2 = [
{ year: 2010, value: 100 },
{ year: 2011, value: 90 },
{ year: 2012, value: 120 },
{ year: 2013, value: 130 },
{ year: 2014, value: 115 },
{ year: 2015, value: 105 },
{ year: 2016, value: 90 },
{ year: 2017, value: 85 },
{ year: 2018, value: 90 },
];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Line Chart</h5>
<div class="block" style="height:100px; width: 400px;">
{{#if lineChartData}}
<LineChart @data={{lineChartData}} @xProp="year" @yProp="value" @chartClass="is-primary" />
{{/if}}
</div>
<div class="block" style="height:100px; width: 400px;">
{{#if lineChartMild}}
<LineChart @data={{lineChartMild}} @xProp="year" @yProp="value" @chartClass="is-info" />
{{/if}}
</div>
`,
context: {
lineChartData: DelayedArray.create(data1),
lineChartMild: DelayedArray.create(data2),
},
};
};
export let FluidWidth = () => {
return {
template: hbs`
<h5 class="title is-5">Fluid-width Line Chart</h5>
<div class="block" style="height:250px;">
{{#if lineChartData}}
<LineChart @data={{lineChartData}} @xProp="year" @yProp="value" @chartClass="is-danger" />
{{/if}}
</div>
<div class="block" style="height:250px;">
{{#if lineChartMild}}
<LineChart @data={{lineChartMild}} @xProp="year" @yProp="value" @chartClass="is-warning" />
{{/if}}
</div>
<p class="annotation">A line chart will assume the width of its container. This includes the dimensions of the axes, which are calculated based on real DOM measurements. This requires a two-pass render: first the axes are placed with their real domains (in order to capture width and height of tick labels), second the axes are adjusted to make sure both the x and y axes are within the height and width bounds of the container.</p>
`,
context: {
lineChartData: DelayedArray.create(data1),
lineChartMild: DelayedArray.create(data2),
},
};
};
export let LiveData = () => {
return {
template: hbs`
<h5 class="title is-5">Live data Line Chart</h5>
<div class="block" style="height:250px">
{{#if controller.lineChartLive}}
<LineChart @data={{controller.lineChartLive}} @xProp="ts" @yProp="val" @timeseries={{true}} @chartClass="is-primary" @xFormat={{controller.secondsFormat}} />
{{/if}}
</div>
`,
context: {
controller: EmberObject.extend({
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
let ref = this.lineChartLive;
ref.addObject({ ts: Date.now(), val: Math.random() * 30 + 20 });
if (ref.length > 60) {
ref.splice(0, ref.length - 60);
}
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
lineChartLive: [],
secondsFormat() {
return date => moment(date).format('HH:mm:ss');
},
}).create(),
},
};
};
export let Gaps = () => {
return {
template: hbs`
<h5 class="title is-5">Line Chart data with gaps</h5>
<div class="block" style="height:250px">
{{#if lineChartGapData}}
<LineChart @data={{lineChartGapData}} @xProp="year" @yProp="value" @chartClass="is-primary" />
{{/if}}
</div>
`,
context: {
lineChartGapData: DelayedArray.create([
{ year: 2010, value: 10 },
{ year: 2011, value: 10 },
{ year: 2012, value: null },
{ year: 2013, value: 30 },
{ year: 2014, value: 50 },
{ year: 2015, value: 80 },
{ year: 2016, value: null },
{ year: 2017, value: 210 },
{ year: 2018, value: 340 },
]),
},
};
};

View File

@@ -0,0 +1,135 @@
import hbs from 'htmlbars-inline-precompile';
import EmberObject, { computed } from '@ember/object';
import { on } from '@ember/object/evented';
export default {
title: 'Charts|Progress Bar',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Progress Bar</h5>
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-primary is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
`,
};
};
export let Colors = () => {
return {
template: hbs`
<h5 class="title is-5">Progress Bar colors</h5>
<div class="columns">
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-info is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-success is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-warning is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-danger is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
</div>
`,
};
};
export let LiveUpdates = () => {
return {
template: hbs`
<h5 class="title is-5">Progress Bar with live updates</h5>
<div class="columns">
<div class="column is-one-third">
<div class="inline-chart tooltip" role="tooltip" aria-label="{{data.numerator}} / {{data.denominator}}">
<progress
class="progress is-primary is-small"
value="{{data.percentage}}"
max="1">
{{data.percentage}}
</progress>
</div>
</div>
</div>
<p class="annotation">
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JsonViewer @json={{data.liveDetails}} />
</div>
</div>
</p>
`,
context: {
data: EmberObject.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 1000)
);
}),
willDestroy() {
clearInterval(this.timer);
},
denominator: computed('timerTicks', function() {
return Math.round(Math.random() * 1000);
}),
percentage: computed('timerTicks', function() {
return Math.round(Math.random() * 100) / 100;
}),
numerator: computed('denominator', 'percentage', function() {
return Math.round(this.denominator * this.percentage * 100) / 100;
}),
liveDetails: computed('denominator', 'numerator', 'percentage', function() {
return this.getProperties('denominator', 'numerator', 'percentage');
}),
}).create(),
},
};
};

View File

@@ -0,0 +1,119 @@
import hbs from 'htmlbars-inline-precompile';
import EmberObject, { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import moment from 'moment';
import DelayedArray from '../utils/delayed-array';
export default {
title: 'Charts|Stats Time Series',
};
let ts = offset =>
moment()
.subtract(offset, 'm')
.toDate();
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Stats Time Series</h5>
<div class="block" style="height:100px; width: 400px;">
{{#if staticMetrics}}
<StatsTimeSeries @data={{staticMetrics}} @chartClass="is-primary" />
{{/if}}
</div>
`,
context: {
staticMetrics: DelayedArray.create([
{ timestamp: ts(20), percent: 0.5 },
{ timestamp: ts(18), percent: 0.5 },
{ timestamp: ts(16), percent: 0.4 },
{ timestamp: ts(14), percent: 0.3 },
{ timestamp: ts(12), percent: 0.9 },
{ timestamp: ts(10), percent: 0.3 },
{ timestamp: ts(8), percent: 0.3 },
{ timestamp: ts(6), percent: 0.4 },
{ timestamp: ts(4), percent: 0.5 },
{ timestamp: ts(2), percent: 0.6 },
{ timestamp: ts(0), percent: 0.6 },
]),
},
};
};
export let HighLowComparison = () => {
return {
template: hbs`
<h5 class="title is-5">Stats Time Series high/low comparison</h5>
<div class="columns">
<div class="block column" style="height:200px; width:400px">
{{#if data.metricsHigh}}
<StatsTimeSeries @data={{data.metricsHigh}} @chartClass="is-info" />
{{/if}}
</div>
<div class="block column" style="height:200px; width:400px">
{{#if data.metricsLow}}
<StatsTimeSeries @data={{data.metricsLow}} @chartClass="is-info" />
{{/if}}
</div>
</div>
<p class="annotation">Line charts, and therefore stats time series charts, use a letant linear gradient with a height equal to the canvas. This makes the color intensity of the gradient at values consistent across charts as long as those charts have the same y-axis domain.</p>
<p class="annotation">This is used to great effect with stats charts since they all have a y-axis domain of 0-100%.</p>
`,
context: {
data: EmberObject.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
let metricsHigh = this.metricsHigh;
let prev = metricsHigh.length ? metricsHigh[metricsHigh.length - 1].percent : 0.9;
this.appendTSValue(
metricsHigh,
Math.min(Math.max(prev + Math.random() * 0.05 - 0.025, 0.5), 1)
);
let metricsLow = this.metricsLow;
let prev2 = metricsLow.length ? metricsLow[metricsLow.length - 1].percent : 0.1;
this.appendTSValue(
metricsLow,
Math.min(Math.max(prev2 + Math.random() * 0.05 - 0.025, 0), 0.5)
);
}, 1000)
);
}),
appendTSValue(array, percent, maxLength = 300) {
array.addObject({
timestamp: Date.now(),
percent,
});
if (array.length > maxLength) {
array.splice(0, array.length - maxLength);
}
},
willDestroy() {
clearInterval(this.timer);
},
metricsHigh: computed(() => {
return [];
}),
metricsLow: computed(() => {
return [];
}),
secondsFormat() {
return date => moment(date).format('HH:mm:ss');
},
}).create(),
},
};
};

View File

@@ -0,0 +1,84 @@
import hbs from 'htmlbars-inline-precompile';
import productMetadata from '../../app/utils/styleguide/product-metadata';
export default {
title: 'Components|Accordion',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Accordion</h5>
<ListAccordion @source={{products}} @key="name" as |ac|>
<ac.head @buttonLabel="details">
<div class="columns inline-definitions">
<div class="column is-1">{{ac.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{ac.item.lang}}</span>
</div>
</div>
</ac.head>
<ac.body>
<h1 class="title is-4">{{ac.item.name}}</h1>
<p>{{ac.item.desc}}</p>
<p><a href="{{ac.item.link}}" target="_parent">Learn more...</a></p>
</ac.body>
</ListAccordion>
`,
context: {
products: productMetadata,
},
};
};
export let OneItem = () => {
return {
template: hbs`
<h5 class="title is-5">Accordion, one item</h5>
<ListAccordion @source={{take 1 products}} @key="name" as |a|>
<a.head @buttonLabel="details">
<div class="columns inline-definitions">
<div class="column is-1">{{a.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{a.item.lang}}</span>
</div>
</div>
</a.head>
<a.body>
<h1 class="title is-4">{{a.item.name}}</h1>
<p>{{a.item.desc}}</p>
<p><a href="{{a.item.link}}">Learn more...</a></p>
</a.body>
</ListAccordion>
`,
context: {
products: productMetadata,
},
};
};
export let NotExpandable = () => {
return {
template: hbs`
<h5 class="title is-5">Accordion, not expandable</h5>
<ListAccordion @source={{products}} @key="name" as |a|>
<a.head @buttonLabel="details" @isExpandable={{eq a.item.lang "golang"}}>
<div class="columns inline-definitions">
<div class="column is-1">{{a.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{a.item.lang}}</span>
</div>
</div>
</a.head>
<a.body>
<h1 class="title is-4">{{a.item.name}}</h1>
<p>{{a.item.desc}}</p>
<p><a href="{{a.item.link}}">Learn more...</a></p>
</a.body>
</ListAccordion>
`,
context: {
products: productMetadata,
},
};
};

View File

@@ -0,0 +1,102 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Alerts',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Alert</h5>
<div class="notification is-info">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<p class="annotation">Alerts use Bulma's notification component.</p>
`,
};
};
export let Colors = () => {
return {
template: hbs`
<h5 class="title is-5">Alert colors</h5>
<div class="notification is-info">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-success">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-warning">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-danger">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<p class="annotation">Alerts are always paired with an emotive color. If there is no emotive association with the content of the alert, then an alert is the wrong component to use.</p>
`,
};
};
export let Dismissal = () => {
return {
template: hbs`
<h5 class="title is-5">Alert dismissal</h5>
<div class="notification is-info">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-info">Okay</button>
</div>
</div>
</div>
<div class="notification is-success">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-success">Okay</button>
</div>
</div>
</div>
<div class="notification is-warning">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-warning">Okay</button>
</div>
</div>
</div>
<div class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-danger">Okay</button>
</div>
</div>
</div>
`,
};
};

View File

@@ -0,0 +1,164 @@
import hbs from 'htmlbars-inline-precompile';
import { withKnobs, optionsKnob } from '@storybook/addon-knobs';
export default {
title: 'Components|Boxed Section',
decorators: [withKnobs],
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let RightHandDetails = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with right hand details</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Right Hand Details
<span class="pull-right">{{now interval=1000}}</span>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let TitleDecoration = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with title decoration</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Title Decoration
<span class="badge is-white">7</span>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let Foot = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with foot</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Large Header
</div>
<div class="boxed-section-body with-foot">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
<div class="boxed-section-foot">
<span>Left-aligned message</span>
<a href="javascript:;" class="pull-right">Toggle or other action</a>
</div>
</div>
`,
context: contextFactory(),
};
};
export let LargeHeader = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with large header</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
<div class="boxed-section-row">
Boxed Section With Large Header
<span class="badge is-white is-subtle bumper-left">Status</span>
</div>
<div class="boxed-section-row">
<span class="tag is-outlined">A tag that goes on a second line because it's rather long</span>
</div>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let DarkBody = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with dark body</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Dark Body
</div>
<div class="boxed-section-body is-dark">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
function contextFactory() {
return {
variant: optionsKnob(
'Variant',
{
Normal: '',
Info: 'is-info',
Warning: 'is-warning',
Danger: 'is-danger',
},
'',
{
display: 'inline-radio',
},
'variant-id'
),
};
}

View File

@@ -0,0 +1,45 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Breadcrumbs',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Breadcrumbs</h5>
<div class="navbar is-secondary">
<div class="navbar-item"></div>
<nav class="breadcrumb is-large">
<li>
<a href="javascript:;">Topic</a>
</li>
<li>
<a href="javascript:;">Sub-topic</a>
</li>
<li class="is-active">
<a href="javascript:;">Active Topic</a>
</li>
</nav>
</div>
<p class="annotation">Breadcrumbs are only ever used in the secondary nav of the primary header.</p>
`,
};
};
export let Single = () => {
return {
template: hbs`
<h5 class="title is-5">Single breadcrumb</h5>
<div class="navbar is-secondary">
<div class="navbar-item"></div>
<nav class="breadcrumb is-large">
<li>
<a href="javascript:;">Topic</a>
</li>
</nav>
</div>
<p class="annotation">Breadcrumbs are given a lot of emphasis and often double as a page title. Since they are also global state, they are important for helping a user keep their bearings.</p>
`,
};
};

View File

@@ -1,6 +1,13 @@
{{#freestyle-collection defaultKey="standard" as |collection|}}
{{#collection.variant key="standard"}}
{{#freestyle-usage 'buttons-standard' title='Standard Buttons'}}
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Buttons',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Buttons</h5>
<div class="block">
<a class="button">Button</a>
<a class="button is-white">White</a>
@@ -16,10 +23,14 @@
<a class="button is-warning">Warning</a>
<a class="button is-danger">Danger</a>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="outlines"}}
{{#freestyle-usage 'buttons-outlines' title='Outline Buttons'}}
`,
};
};
export let Outline = () => {
return {
template: hbs`
<h5 class="title is-5">Outline buttons</h5>
<div class="block">
<a class="button is-outlined">Outlined</a>
<a class="button is-primary is-outlined">Primary</a>
@@ -28,10 +39,14 @@
<a class="button is-warning is-outlined">Warning</a>
<a class="button is-danger is-outlined is-important">Danger</a>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="hollow"}}
{{#freestyle-usage 'buttons-hollow' title='Hollow Buttons'}}
`,
};
};
export let Hollow = () => {
return {
template: hbs`
<h5 class="title is-5">Hollow buttons</h5>
<div class="block" style="background:#25BA81; padding:30px">
<a class="button is-primary is-inverted is-outlined">Primary</a>
<a class="button is-info is-inverted is-outlined">Info</a>
@@ -39,16 +54,20 @@
<a class="button is-warning is-inverted is-outlined">Warning</a>
<a class="button is-danger is-inverted is-outlined">Danger</a>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{#collection.variant key="sizing"}}
{{#freestyle-usage 'buttons-sizing' title='Button Sizes'}}
`,
};
};
export let Sizes = () => {
return {
template: hbs`
<h5 class="title is-5">Button sizes</h5>
<div class="block">
<a class="button is-small">Small</a>
<a class="button">Normal</a>
<a class="button is-medium">Medium</a>
<a class="button is-large">Large</a>
</div>
{{/freestyle-usage}}
{{/collection.variant}}
{{/freestyle-collection}}
`,
};
};

View File

@@ -0,0 +1,22 @@
import hbs from 'htmlbars-inline-precompile';
import { withKnobs, text } from '@storybook/addon-knobs';
export default {
title: 'Components|Copy Button',
decorators: [withKnobs],
};
export let CopyButton = () => {
return {
template: hbs`
<h5 class="title is-5">Copy Button</h5>
<span class="tag is-hollow is-small no-text-transform">
{{clipboardText}}
<CopyButton @clipboardText={{clipboardText}} />
</span>
`,
context: {
clipboardText: text('Clipboard Text', 'e8c898a0-794b-9063-7a7f-bf0c4a405f83'),
},
};
};

View File

@@ -0,0 +1,488 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Diff Viewer',
};
export let DiffViewerWithInsertions = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with insertions</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=insertionsOnly}}
</div>
</div>
`,
context: {
insertionsOnly: generateDiff([
{ Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' },
{ Annotations: null, Name: 'Delay', New: '25000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Interval', New: '900000000000', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Mode', New: 'delay', Old: 'delay', Type: 'None' },
]),
},
};
};
export let DiffViewerWithDeletions = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with deletions</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=deletionsOnly}}
</div>
</div>
`,
context: {
deletionsOnly: generateDiff([
{ Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' },
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '25000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '900000000000',
Type: 'None',
},
{ Annotations: null, Name: 'Mode', New: '', Old: 'delay', Type: 'Deleted' },
]),
},
};
};
export let DiffViewerWithEdits = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with edits</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=editsOnly}}
</div>
<p class="annotation">Often times a diff will only have a couple lines. Minor tweaks to a job spec result in small diffs.</p>
</div>
`,
context: {
editsOnly: generateDiff([
{ Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' },
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '25000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '250000000000',
Type: 'Edited',
},
{ Annotations: null, Name: 'Mode', New: 'delay', Old: 'delay', Type: 'None' },
]),
},
};
};
export let DiffViewerWithManyChanges = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with many changes</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
{{job-diff diff=largeDiff}}
</div>
</div>
`,
context: {
largeDiff: {
Fields: null,
ID: 'example',
Objects: null,
TaskGroups: [
{
Fields: null,
Name: 'cache',
Objects: null,
Tasks: [
{
Annotations: null,
Fields: [
{
Annotations: null,
Name: 'Meta[one]',
New: "flew over the cuckoo's nest",
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Meta[two]',
New: 'birds on a wire',
Old: '',
Type: 'Added',
},
],
Name: 'redis',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'image',
New: 'redis:3.4',
Old: 'redis:3.2',
Type: 'Edited',
},
{
Annotations: null,
Name: 'port_map[0][db]',
New: '6380',
Old: '6379',
Type: 'Edited',
},
],
Name: 'Config',
Objects: null,
Type: 'Edited',
},
{
Fields: [
{ Annotations: null, Name: 'CPU', New: '1000', Old: '500', Type: 'Edited' },
{ Annotations: null, Name: 'DiskMB', New: '0', Old: '0', Type: 'None' },
{ Annotations: null, Name: 'IOPS', New: '0', Old: '0', Type: 'None' },
{
Annotations: null,
Name: 'MemoryMB',
New: '512',
Old: '256',
Type: 'Edited',
},
],
Name: 'Resources',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'MBits', New: '100', Old: '', Type: 'Added' },
],
Name: 'Network',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Label',
New: 'db',
Old: '',
Type: 'Added',
},
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Added',
},
],
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'MBits', New: '', Old: '10', Type: 'Deleted' },
],
Name: 'Network',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Label',
New: '',
Old: 'db',
Type: 'Deleted',
},
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Deleted',
},
],
Type: 'Deleted',
},
],
Type: 'Edited',
},
{
Fields: [
{
Annotations: null,
Name: 'AddressMode',
New: 'auto',
Old: 'auto',
Type: 'None',
},
{
Annotations: null,
Name: 'Name',
New: 'redis-cache',
Old: 'redis-cache',
Type: 'None',
},
{ Annotations: null, Name: 'PortLabel', New: 'db', Old: 'db', Type: 'None' },
],
Name: 'Service',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Tags', New: 'redis', Old: '', Type: 'Added' },
{
Annotations: null,
Name: 'Tags',
New: 'cache',
Old: 'cache',
Type: 'None',
},
{
Annotations: null,
Name: 'Tags',
New: 'global',
Old: 'global',
Type: 'None',
},
],
Name: 'Tags',
Objects: null,
Type: 'Added',
},
{
Fields: [
{
Annotations: null,
Name: 'AddressMode',
New: '',
Old: '',
Type: 'None',
},
{ Annotations: null, Name: 'Command', New: '', Old: '', Type: 'None' },
{
Annotations: null,
Name: 'GRPCService',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'GRPCUseTLS',
New: 'false',
Old: 'false',
Type: 'None',
},
{
Annotations: null,
Name: 'InitialStatus',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '15000000000',
Old: '10000000000',
Type: 'Edited',
},
{ Annotations: null, Name: 'Method', New: '', Old: '', Type: 'None' },
{
Annotations: null,
Name: 'Name',
New: 'alive',
Old: 'alive',
Type: 'None',
},
{ Annotations: null, Name: 'Path', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'PortLabel', New: '', Old: '', Type: 'None' },
{ Annotations: null, Name: 'Protocol', New: '', Old: '', Type: 'None' },
{
Annotations: null,
Name: 'TLSSkipVerify',
New: 'false',
Old: 'false',
Type: 'None',
},
{
Annotations: null,
Name: 'Timeout',
New: '7000000000',
Old: '2000000000',
Type: 'Edited',
},
{ Annotations: null, Name: 'Type', New: 'tcp', Old: 'tcp', Type: 'None' },
],
Name: 'Check',
Objects: null,
Type: 'Edited',
},
],
Type: 'Edited',
},
],
Type: 'Edited',
},
],
Type: 'Edited',
Updates: null,
},
{
Fields: [
{ Annotations: null, Name: 'Count', New: '1', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Meta[key]', New: 'value', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Meta[red]', New: 'fish', Old: '', Type: 'Added' },
],
Name: 'cache2',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'Attempts', New: '2', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Delay', New: '15000000000', Old: '', Type: 'Added' },
{
Annotations: null,
Name: 'Interval',
New: '1800000000000',
Old: '',
Type: 'Added',
},
{ Annotations: null, Name: 'Mode', New: 'fail', Old: '', Type: 'Added' },
],
Name: 'RestartPolicy',
Objects: null,
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'Migrate', New: 'false', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'SizeMB', New: '300', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'Sticky', New: 'false', Old: '', Type: 'Added' },
],
Name: 'EphemeralDisk',
Objects: null,
Type: 'Added',
},
],
Tasks: [
{
Annotations: null,
Fields: [
{ Annotations: null, Name: 'Driver', New: 'docker', Old: '', Type: 'Added' },
{
Annotations: null,
Name: 'KillTimeout',
New: '5000000000',
Old: '',
Type: 'Added',
},
{ Annotations: null, Name: 'Leader', New: 'false', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'ShutdownDelay', New: '0', Old: '', Type: 'Added' },
],
Name: 'redis',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'image',
New: 'redis:3.2',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'port_map[0][db]',
New: '6379',
Old: '',
Type: 'Added',
},
],
Name: 'Config',
Objects: null,
Type: 'Added',
},
{
Fields: [
{ Annotations: null, Name: 'CPU', New: '500', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'DiskMB', New: '0', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'IOPS', New: '0', Old: '', Type: 'Added' },
{ Annotations: null, Name: 'MemoryMB', New: '256', Old: '', Type: 'Added' },
],
Name: 'Resources',
Objects: [
{
Fields: [
{ Annotations: null, Name: 'MBits', New: '10', Old: '', Type: 'Added' },
],
Name: 'Network',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Label',
New: 'db',
Old: '',
Type: 'Added',
},
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
Updates: null,
},
],
Type: 'Edited',
},
},
};
};
function generateDiff(changeset) {
return {
Fields: null,
ID: 'insertions-only',
Objects: null,
TaskGroups: [
{
Fields: [{ Annotations: null, Name: 'Count', New: '2', Old: '2', Type: 'None' }],
Name: 'cache',
Objects: [
{
Fields: changeset,
Name: 'RestartPolicy',
Objects: null,
Type: 'Edited',
},
],
Type: 'Edited',
Updates: null,
},
],
Type: 'Edited',
};
}

View File

@@ -0,0 +1,83 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Dropdown',
};
let options = [
{ name: 'Consul' },
{ name: 'Nomad' },
{ name: 'Packer' },
{ name: 'Terraform' },
{ name: 'Vagrant' },
{ name: 'Vault' },
];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Dropdown</h5>
<PowerSelect @options={{options}} @selected={{selectedOption}} @searchField="name" @searchEnabled={{gt options.length 10}} @onChange={{action (mut selectedOption)}} as |option|>
{{option.name}}
</PowerSelect>
<p class="annotation">Power Select currently fulfills all of Nomad's dropdown needs out of the box.</p>
`,
context: {
options,
},
};
};
export let Resized = () => {
return {
template: hbs`
<h5 class="title is-5">Dropdown resized</h5>
<div class="columns">
<div class="column is-3">
<PowerSelect @options={{options}} @selected={{selectedOption2}} @searchField="name" @searchEnabled={{gt options.length 10}} @onChange={{action (mut selectedOption2)}} as |option|>
{{option.name}}
</PowerSelect>
</div>
</div>
<p class="annotation">Dropdowns are always 100% wide. To control the width of a dropdown, adjust the dimensions of its container. One way to achieve this is using columns.</p>
`,
context: {
options,
},
};
};
export let Search = () => {
return {
template: hbs`
<h5 class="title is-5">Dropdown with search</h5>
<div class="columns">
<div class="column is-3">
<PowerSelect @options={{manyOptions}} @selected={{selectedOption3}} @searchField="name" @searchEnabled={{gt manyOptions.length 10}} @onChange={{action (mut selectedOption3)}} as |option|>
{{option.name}}
</PowerSelect>
</div>
</div>
<p class="annotation">Whether or not the dropdown has a search box is configurable. Typically the default is to show a search once a dropdown has more than 10 options.</p>
`,
context: {
manyOptions: [
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
'Eleven',
'Twelve',
'Thirteen',
'Fourteen',
'Fifteen',
].map(name => ({ name })),
},
};
};

View File

@@ -0,0 +1,174 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Gutter Menu',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Gutter menu</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="javascript:;" class="is-active">Place One</a></li>
<li><a href="javascript:;">Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">Feature One</a></li>
<li><a href="javascript:;">Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
`,
};
};
export let RichComponents = () => {
return {
template: hbs`
<h5 class="title is-5">Gutter navigation with rich components</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li>
<div class="menu-item">
<PowerSelect @selected={{or selection "One"}} @options={{array "One" "Two" "Three"}} @onChange={{action (mut selection)}} as |option|>
{{option}}
</PowerSelect>
</div>
</li>
<li><a href="javascript:;" class="is-active">Place One</a></li>
<li><a href="javascript:;">Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">Feature One</a></li>
<li><a href="javascript:;">Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">In order to keep the gutter navigation streamlined and easy to navigation, rich components should be avoided when possible. When not possible, they should be kept near the top.</p>
`,
};
};
export let ManyItems = () => {
return {
template: hbs`
<h5 class="title is-5">Hypothetical gutter navigation with many items</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="javascript:;">Place {{item}}</a></li>
{{/each}}
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="javascript:;">Feature {{item}}</a></li>
{{/each}}
</ul>
<p class="menu-label">Other</p>
<ul class="menu-list">
<li><a href="javascript:;" class="is-active">The one that didn't fit in</a></li>
</ul>
<p class="menu-label">Things</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="javascript:;">Thing {{item}}</a></li>
{{/each}}
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">There will only ever be one gutter menu in the Nomad UI, but it helps to imagine a situation where there are many navigation items in the gutter.</p>
`,
};
};
export let IconItems = () => {
return {
template: hbs`
<h5 class="title is-5">Hypothetical gutter navigation with icon items</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="javascript:;">{{x-icon "clock"}} Place One</a></li>
<li><a href="javascript:;" class="is-active">{{x-icon "history"}} Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">{{x-icon "warning"}} Feature One</a></li>
<li><a href="javascript:;">{{x-icon "media-pause"}} Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">In the future, the gutter menu may have icons.</p>
`,
};
};
export let Global = () => {
return {
template: hbs`
<h5 class="title is-5">Global gutter navigation</h5>
<div class="columns">
<div class="column is-4">
<GutterMenu>
{{!-- Page content here --}}
</GutterMenu>
</div>
</div>
<p class="annotation">Since there will only ever be one gutter menu in the UI, it makes sense to express the menu as a singleton component. This is what that singleton component looks like.</p>
<p class="annotation"><strong>Note:</strong> Normally the gutter menu is rendered within a page layout and is fixed position. The columns shown in this example are only to imitate the actual width without applying fixed positioning.</p>
`,
};
};

View File

@@ -0,0 +1,28 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Header',
};
export let Header = () => {
return {
template: hbs`
<h5 class="title is-5">Global header</h5>
<nav class="navbar is-primary">
<div class="navbar-brand">
<span class="gutter-toggle" aria-label="menu">
{{partial "partials/hamburger-menu"}}
</span>
<span class="navbar-item is-logo">
{{partial "partials/nomad-logo"}}
</span>
</div>
<div class="navbar-end">
<a class="navbar-item">Secondary</a>
<a class="navbar-item">Links</a>
<a class="navbar-item">Here</a>
</div>
</nav>
`,
};
};

View File

@@ -0,0 +1,92 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Inline Definitions',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Inline definitions</h5>
<div class="boxed-section is-small">
<div class="boxed-section-body inline-definitions">
<span class="label">Some Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Running?</span>
<span>Yes</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<p class="annotation">A way to tightly display key/value information. Typically seen at the top of pages.</p>
`,
};
};
export let Variants = () => {
return {
template: hbs`
<h5 class="title is-5">Inline definitions variants</h5>
<div class="boxed-section is-small is-success">
<div class="boxed-section-body inline-definitions">
<span class="label">Success Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-warning">
<div class="boxed-section-body inline-definitions">
<span class="label">Warning Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-danger">
<div class="boxed-section-body inline-definitions">
<span class="label">Danger Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-info">
<div class="boxed-section-body inline-definitions">
<span class="label">Info Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<p class="annotation">Inline definitions are meant to pair well with emotive color variations.</p>
`,
};
};

View File

@@ -0,0 +1,183 @@
import hbs from 'htmlbars-inline-precompile';
import DelayedTruth from '../utils/delayed-truth';
export default {
title: 'Components|JSON Viewer',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">JSON Viewer</h5>
{{#if delayedTruth.complete}}
<JsonViewer @json={{jsonSmall}} />
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
jsonSmall: {
delayedData: {},
data: {
foo: 'bar',
number: 123456789,
products: ['Consul', 'Nomad', 'Packer', 'Terraform', 'Vagrant', 'Vault'],
currentTime: '2019-10-16T14:24:12.378Z',
nested: {
obj: 'ject',
},
nonexistent: null,
isTrue: false,
},
},
},
};
};
export let FullDocument = () => {
return {
template: hbs`
<h5 class="title is-5">JSON Viewer for full document</h5>
{{#if delayedTruth.complete}}
<JsonViewer @json={{jsonLarge}} />
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
jsonLarge: {
delayedData: {},
data: {
Stop: false,
Region: 'global',
Namespace: 'default',
ID: 'syslog',
ParentID: '',
Name: 'syslog',
Type: 'system',
Priority: 50,
AllAtOnce: false,
Datacenters: ['dc1', 'dc2'],
letraints: null,
TaskGroups: [
{
Name: 'syslog',
Count: 1,
Update: {
Stagger: 10000000000,
MaxParallel: 1,
HealthCheck: 'checks',
MinHealthyTime: 10000000000,
HealthyDeadline: 300000000000,
ProgressDeadline: 600000000000,
AutoRevert: false,
Canary: 0,
},
Migrate: null,
letraints: [
{
LTarget: '',
RTarget: '',
Operand: 'distinct_hosts',
},
],
RestartPolicy: {
Attempts: 10,
Interval: 300000000000,
Delay: 25000000000,
Mode: 'delay',
},
Tasks: [
{
Name: 'syslog',
Driver: 'docker',
User: '',
Config: {
port_map: [
{
tcp: 601,
udp: 514,
},
],
image: 'balabit/syslog-ng:latest',
},
Env: null,
Services: null,
Vault: null,
Templates: null,
letraints: null,
Resources: {
CPU: 500,
MemoryMB: 256,
DiskMB: 0,
IOPS: 0,
Networks: [
{
Device: '',
CIDR: '',
IP: '',
MBits: 10,
ReservedPorts: [
{
Label: 'udp',
Value: 514,
},
{
Label: 'tcp',
Value: 601,
},
],
DynamicPorts: null,
},
],
},
DispatchPayload: null,
Meta: null,
KillTimeout: 5000000000,
LogConfig: {
MaxFiles: 10,
MaxFileSizeMB: 10,
},
Artifacts: null,
Leader: false,
ShutdownDelay: 0,
KillSignal: '',
},
],
EphemeralDisk: {
Sticky: false,
SizeMB: 300,
Migrate: false,
},
Meta: null,
ReschedulePolicy: null,
},
],
Update: {
Stagger: 10000000000,
MaxParallel: 1,
HealthCheck: '',
MinHealthyTime: 0,
HealthyDeadline: 0,
ProgressDeadline: 0,
AutoRevert: false,
Canary: 0,
},
Periodic: null,
ParameterizedJob: null,
Dispatched: false,
Payload: null,
Meta: null,
VaultToken: '',
Status: 'running',
StatusDescription: '',
Stable: false,
Version: 0,
SubmitTime: 1530052201331477800,
CreateIndex: 27,
ModifyIndex: 27,
JobModifyIndex: 27,
},
},
},
};
};

View File

@@ -0,0 +1,66 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Log Stream',
};
export let LogStream = () => {
return {
template: hbs`
<h5 class="title is-5">Log stream</h5>
<div class="boxed-section">
<div class="boxed-section-head">
<span>
<button
class="button {{if (eq mode1 "stdout") "is-info"}}"
onclick={{action (mut mode1) "stdout"}}>stdout</button>
<button
class="button {{if (eq mode1 "stderr") "is-danger"}}"
onclick={{action (mut mode1) "stderr"}}>stderr</button>
</span>
<span class="pull-right">
<button class="button is-white">Head</button>
<button class="button is-white">Tail</button>
<button class="button is-white" onclick={{toggle "isPlaying1" this}}>
{{x-icon (if isPlaying1 "media-play" "media-pause") class="is-text"}}
</button>
</span>
</div>
<div class="boxed-section-body is-dark is-full-bleed">
<pre class="cli-window"><code>{{if (eq mode1 "stdout") sampleOutput sampleError}}</code></pre>
</div>
</div>
`,
context: {
mode1: 'stdout',
isPlaying1: true,
sampleOutput: `Sample output
> 1
> 2
> 3
[00:12:58] Log output here
[00:15:29] [ERR] Uh oh
Loading.
Loading..
Loading...
>> Done! <<
`,
sampleError: `Sample error
[====|--------------------] 20%
!!! Unrecoverable error:
Cannot continue beyond this point. Exception should be caught.
This is not a mistake. You did something wrong. Check the code.
No, you will not receive any more details or guidance from this
error message.
`,
},
};
};

View File

@@ -0,0 +1,143 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Metrics',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Metrics</h5>
<div class="metric-group">
<div class="metric">
<h3 class="label">Label</h3>
<p class="value">12</p>
</div>
</div>
<p class="annotation">Metrics are a way to show simple values (generally numbers). Labels are smaller than numbers to put emphasis on the data.</p>
`,
};
};
export let Groups = () => {
return {
template: hbs`
<h5 class="title is-5">Metric groups</h5>
<div class="metric-group">
<div class="metric">
<h3 class="label">Label</h3>
<p class="value">1 / 2</p>
</div>
<div class="metric">
<h3 class="label">Number</h3>
<p class="value">1,300</p>
</div>
<div class="metric">
<h3 class="label">Datacenter</h3>
<p class="value">dc1</p>
</div>
</div>
<div class="metric-group">
<div class="metric">
<h3 class="label">Today</h3>
<p class="value">81º</p>
</div>
<div class="metric">
<h3 class="label">Tomorrow</h3>
<p class="value">73º</p>
</div>
</div>
<p class="annotation">Related metrics should be lumped together in metric groups. All metrics have to be in a metric group. By putting multiple metrics in a single group, they will be visually lumped together.</p>
`,
};
};
export let Colors = () => {
return {
template: hbs`
<h5 class="title is-5">Metric colors</h5>
<div class="metric-group">
<div class="metric is-info">
<h3 class="label">Info</h3>
<p class="value">1</p>
</div>
<div class="metric is-success">
<h3 class="label">Success</h3>
<p class="value">2</p>
</div>
<div class="metric is-warning">
<h3 class="label">Warning</h3>
<p class="value">3</p>
</div>
<div class="metric is-danger">
<h3 class="label">Danger</h3>
<p class="value">4</p>
</div>
</div>
<div class="metric-group">
<div class="metric is-white">
<h3 class="label">White</h3>
<p class="value">5</p>
</div>
<div class="metric is-light">
<h3 class="label">Light</h3>
<p class="value">6</p>
</div>
<div class="metric is-primary">
<h3 class="label">Primary</h3>
<p class="value">7</p>
</div>
<div class="metric is-dark">
<h3 class="label">Dark</h3>
<p class="value">8</p>
</div>
<div class="metric is-black">
<h3 class="label">Black</h3>
<p class="value">9</p>
</div>
</div>
<p class="annotation">All color-modifiers work for metrics, but some work better than others.</p>
<p class="annotation">Emotive colors work well and are put to use when applicable. Other colors have worse support and less utility.</p>
`,
};
};
export let States = () => {
return {
template: hbs`
<h5 class="title is-5">Metric states</h5>
<div class="metric-group">
<div class="metric is-primary is-faded">
<h3 class="label">One</h3>
<p class="value">A</p>
</div>
<div class="metric is-primary">
<h3 class="label">Two</h3>
<p class="value">B</p>
</div>
<div class="metric is-primary is-faded">
<h3 class="label">Three</h3>
<p class="value">C</p>
</div>
</div>
<div class="metric-group">
<div class="metric is-danger is-faded">
<h3 class="label">One</h3>
<p class="value">A</p>
</div>
<div class="metric is-danger is-faded">
<h3 class="label">Two</h3>
<p class="value">B</p>
</div>
<div class="metric is-danger">
<h3 class="label">Three</h3>
<p class="value">C</p>
</div>
</div>
<p class="annotation">Metrics have a disabled state. This is used when a metric is non-existent or irrelevant. It's just as important to show the lack of value as it is to show a value, so simply not rendering non-existent or irrelevant metrics would be worse.</p>
`,
};
};

View File

@@ -0,0 +1,131 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Multi-Select Dropdown',
};
let options1 = [
{ key: 'option-1', label: 'Option One' },
{ key: 'option-2', label: 'Option Two' },
{ key: 'option-3', label: 'Option Three' },
{ key: 'option-4', label: 'Option Four' },
{ key: 'option-5', label: 'Option Five' },
];
let selection1 = ['option-2', 'option-4', 'option-5'];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown</h5>
<MultiSelectDropdown
@label="Example Dropdown"
@options={{options1}}
@selection={{selection1}}
@onSelect={{action (mut selection1)}} />
<p class="annotation">A wrapper around basic-dropdown for creating a list of checkboxes and tracking the state thereof.</p>
`,
context: {
options1,
selection1,
},
};
};
export let RightAligned = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown right-aligned</h5>
<div style="display:flex; justify-content:flex-end">
<MultiSelectDropdown
@label="Example right-aligned Dropdown"
@options={{options1}}
@selection={{selection1}}
@onSelect={{action (mut selection1)}} />
</div>
`,
context: {
options1,
selection1,
},
};
};
export let ManyOptions = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown with many options</h5>
<MultiSelectDropdown
@label="Lots of options in here"
@options={{optionsMany}}
@selection={{selectionMany}}
@onSelect={{action (mut selectionMany)}} />
<p class="annotation">
A strength of the multi-select-dropdown is its simple presentation. It is quick to select options and it is quick to remove options.
However, this strength becomes a weakness when there are too many options. Since the selection isn't pinned in any way, removing a selection
can become an adventure of scrolling up and down. Also since the selection isn't pinned, this component can't support search, since search would
entirely mask the selection.
</p>
`,
context: {
optionsMany: Array(100)
.fill(null)
.map((_, i) => ({ label: `Option ${i}`, key: `option-${i}` })),
selectionMany: [],
},
};
};
export let Bar = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown bar</h5>
<div class="button-bar">
<MultiSelectDropdown
@label="Datacenter"
@options={{optionsDatacenter}}
@selection={{selectionDatacenter}}
@onSelect={{action (mut selectionDatacenter)}} />
<MultiSelectDropdown
@label="Type"
@options={{optionsType}}
@selection={{selectionType}}
@onSelect={{action (mut selectionType)}} />
<MultiSelectDropdown
@label="Status"
@options={{optionsStatus}}
@selection={{selectionStatus}}
@onSelect={{action (mut selectionStatus)}} />
</div>
<p class="annotation">
Since this is a core component for faceted search, it makes sense to letruct an arrangement of multi-select dropdowns.
Do this by wrapping all the options in a <code>.button-bar</code> container.
</p>
`,
context: {
optionsDatacenter: [
{ key: 'pdx-1', label: 'pdx-1' },
{ key: 'jfk-1', label: 'jfk-1' },
{ key: 'jfk-2', label: 'jfk-2' },
{ key: 'muc-1', label: 'muc-1' },
],
selectionDatacenter: ['jfk-1', 'jfk-2'],
optionsType: [
{ key: 'batch', label: 'Batch' },
{ key: 'service', label: 'Service' },
{ key: 'system', label: 'System' },
{ key: 'periodic', label: 'Periodic' },
{ key: 'parameterized', label: 'Parameterized' },
],
selectionType: ['system', 'service'],
optionsStatus: [
{ key: 'pending', label: 'Pending' },
{ key: 'running', label: 'Running' },
{ key: 'dead', label: 'Dead' },
],
selectionStatus: [],
},
};
};

View File

@@ -0,0 +1,34 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Page Tabs',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Page tabs</h5>
<div class="tabs">
<ul>
<li><a href="javascript:;">Overview</a></li>
<li><a href="javascript:;" class="is-active">Definition</a></li>
<li><a href="javascript:;">Versions</a></li>
<li><a href="javascript:;">Deployments</a></li>
</ul>
</div>
`,
};
};
export let Single = () => {
return {
template: hbs`
<h5 class="title is-5">Single page tab</h5>
<div class="tabs">
<ul>
<li><a href="javascript:;" class="is-active">Overview</a></li>
</ul>
</div>
`,
};
};

View File

@@ -0,0 +1,69 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Page Title',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Page title</h5>
<div class="mock-spacing">
<h1 class="title">This is the Page Title</h1>
</div>
<p class="annotation">In its simplest form, a page title is just an H1.</p>
`,
};
};
export let AfterElements = () => {
return {
template: hbs`
<h5 class="title is-5">Page title with after elements</h5>
<div class="mock-spacing">
<h1 class="title">
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
</h1>
</div>
<p class="annotation">It is common to put high-impact tags and badges to the right of titles. These tags should only ever appear on the right-hand side of the title, and they should be listed in descending weights. Tags with a background are heavier than tags that are hollow. Longer values are heavier than shorter values.</p>
`,
};
};
export let StatusLight = () => {
return {
template: hbs`
<h5 class="title is-5">Page title with status light</h5>
<div class="mock-spacing">
<h1 class="title">
<span class="node-status-light initializing"></span>
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
</h1>
</div>
<p class="annotation">A simple color or pattern is faster to scan than a title and can often say more than words can. For pages that have an important status component to them (e.g., client detail page), a status light can be shown to the left of the title where typically eyes will begin to scan a page.</p>
`,
};
};
export let Actions = () => {
return {
template: hbs`
<h5 class="title is-5">Page title with actions</h5>
<div class="mock-spacing">
<h1 class="title">
<span class="node-status-light initializing"></span>
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
<button class="button is-warning is-small is-inline">If you wish</button>
<button class="button is-danger is-outlined is-important is-small is-inline">No Regrets</button>
</h1>
</div>
<p class="annotation">When actions apply to the entire context of a page, (e.g., job actions on the job detail page), buttons for these actions go in the page title. Buttons are always placed on the far right end of a page title. No elements can go to the right of these buttons.</p>
`,
};
};

View File

@@ -0,0 +1,14 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Proxy Tag',
};
export let ProxyTag = () => {
return {
template: hbs`
<h5 class="title is-5">Proxy Tag</h5>
<h6 class="title is-6">Some kind of title <ProxyTag/></h6>
`,
};
};

View File

@@ -0,0 +1,31 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Search Box',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Search Box</h5>
<SearchBox
@searchTerm={{mut searchTerm1}}
@placeholder="Search things..." />
<p class="annotation">The search box component is a thin wrapper around a simple input. Although the searchTerm passed to it is a mutable reference, internally search term is debounced. This is to prevent potentially expensive code that depends on searchTerm from recomputing many times as a user types.</p>
<p class="annotation">There is no form of the search box component that defers updating the searchTerm reference until the user manually clicks a "Search" button. This can be achieved by placing a button next to the search bar component and using it to perform search, but search should be automatic whenever possible.</p>
`,
};
};
export let Compact = () => {
return {
template: hbs`
<h5 class="title is-5">Compact Search Box</h5>
<SearchBox
@searchTerm={{mut searchTerm2}}
@placeholder="Search things..."
@inputClass="is-compact" />
<p class="annotation">Search box provides an inputClass property to control the inner input. This is nice for fitting the search box into smaller spaces, such as boxed-section heads.</p>
`,
};
};

View File

@@ -0,0 +1,40 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Table, Configuration',
};
export let TableConfiguration = () => {
return {
template: hbs`
<h5 class="title is-5">Table, configuration</h5>
<AttributesTable @attributes={{attributes}} @class="attributes-table" />
`,
context: {
attributes: {
key: 'val',
deep: {
key: 'val',
more: 'stuff',
},
array: ['one', 'two', 'three', 'four'],
very: {
deep: {
key: {
incoming: {
one: 1,
two: 2,
three: 3,
four: 'surprisingly long value that is unlike the other properties in this object',
},
},
},
},
},
},
};
};
TableConfiguration.story = {
title: 'Table, Configuration',
};

View File

@@ -0,0 +1,483 @@
import hbs from 'htmlbars-inline-precompile';
import productMetadata from '../../app/utils/styleguide/product-metadata';
import EmberObject, { computed } from '@ember/object';
import { getOwner } from '@ember/application';
import { on } from '@ember/object/evented';
import Controller from '@ember/controller';
export default {
title: 'Components|Table',
};
/**
* The Ember integration for Storybook renders a container component with no routing,
* which means things that need query parameters, like sorting and pagination, wont work.
* This initialiser turns on routing and accepts a controller definition that gets wired up
* to a generated `storybook` route. The controller is attached to the Storybook component
* as the `controller` property so its query parameters are accessible from the template.
*/
function injectRoutedController(controllerClass) {
return on('init', function() {
let container = getOwner(this);
container.register('controller:storybook', controllerClass);
let routerFactory = container.factoryFor('router:main');
routerFactory.class.map(function() {
this.route('storybook');
});
let router = container.lookup('router:main');
router.initialURL = 'storybook';
router.startRouting(true);
this.set('controller', container.lookup('controller:storybook'));
});
}
let longList = [
{ city: 'New York', growth: 0.048, population: '8405837', rank: '1', state: 'New York' },
{ city: 'Los Angeles', growth: 0.048, population: '3884307', rank: '2', state: 'California' },
{ city: 'Chicago', growth: -0.061, population: '2718782', rank: '3', state: 'Illinois' },
{ city: 'Houston', growth: 0.11, population: '2195914', rank: '4', state: 'Texas' },
{
city: 'Philadelphia',
growth: 0.026,
population: '1553165',
rank: '5',
state: 'Pennsylvania',
},
{ city: 'Phoenix', growth: 0.14, population: '1513367', rank: '6', state: 'Arizona' },
{ city: 'San Antonio', growth: 0.21, population: '1409019', rank: '7', state: 'Texas' },
{ city: 'San Diego', growth: 0.105, population: '1355896', rank: '8', state: 'California' },
{ city: 'Dallas', growth: 0.056, population: '1257676', rank: '9', state: 'Texas' },
{ city: 'San Jose', growth: 0.105, population: '998537', rank: '10', state: 'California' },
{ city: 'Austin', growth: 0.317, population: '885400', rank: '11', state: 'Texas' },
{ city: 'Indianapolis', growth: 0.078, population: '843393', rank: '12', state: 'Indiana' },
{ city: 'Jacksonville', growth: 0.143, population: '842583', rank: '13', state: 'Florida' },
{
city: 'San Francisco',
growth: 0.077,
population: '837442',
rank: '14',
state: 'California',
},
{ city: 'Columbus', growth: 0.148, population: '822553', rank: '15', state: 'Ohio' },
{
city: 'Charlotte',
growth: 0.391,
population: '792862',
rank: '16',
state: 'North Carolina',
},
{ city: 'Fort Worth', growth: 0.451, population: '792727', rank: '17', state: 'Texas' },
{ city: 'Detroit', growth: -0.271, population: '688701', rank: '18', state: 'Michigan' },
{ city: 'El Paso', growth: 0.194, population: '674433', rank: '19', state: 'Texas' },
{ city: 'Memphis', growth: -0.053, population: '653450', rank: '20', state: 'Tennessee' },
{ city: 'Seattle', growth: 0.156, population: '652405', rank: '21', state: 'Washington' },
{ city: 'Denver', growth: 0.167, population: '649495', rank: '22', state: 'Colorado' },
{
city: 'Washington',
growth: 0.13,
population: '646449',
rank: '23',
state: 'District of Columbia',
},
{ city: 'Boston', growth: 0.094, population: '645966', rank: '24', state: 'Massachusetts' },
{
city: 'Nashville-Davidson',
growth: 0.162,
population: '634464',
rank: '25',
state: 'Tennessee',
},
{ city: 'Baltimore', growth: -0.04, population: '622104', rank: '26', state: 'Maryland' },
{ city: 'Oklahoma City', growth: 0.202, population: '610613', rank: '27', state: 'Oklahoma' },
{
city: 'Louisville/Jefferson County',
growth: 0.1,
population: '609893',
rank: '28',
state: 'Kentucky',
},
{ city: 'Portland', growth: 0.15, population: '609456', rank: '29', state: 'Oregon' },
{ city: 'Las Vegas', growth: 0.245, population: '603488', rank: '30', state: 'Nevada' },
{ city: 'Milwaukee', growth: 0.003, population: '599164', rank: '31', state: 'Wisconsin' },
{ city: 'Albuquerque', growth: 0.235, population: '556495', rank: '32', state: 'New Mexico' },
{ city: 'Tucson', growth: 0.075, population: '526116', rank: '33', state: 'Arizona' },
{ city: 'Fresno', growth: 0.183, population: '509924', rank: '34', state: 'California' },
{ city: 'Sacramento', growth: 0.172, population: '479686', rank: '35', state: 'California' },
{ city: 'Long Beach', growth: 0.015, population: '469428', rank: '36', state: 'California' },
{ city: 'Kansas City', growth: 0.055, population: '467007', rank: '37', state: 'Missouri' },
{ city: 'Mesa', growth: 0.135, population: '457587', rank: '38', state: 'Arizona' },
{ city: 'Virginia Beach', growth: 0.051, population: '448479', rank: '39', state: 'Virginia' },
{ city: 'Atlanta', growth: 0.062, population: '447841', rank: '40', state: 'Georgia' },
{
city: 'Colorado Springs',
growth: 0.214,
population: '439886',
rank: '41',
state: 'Colorado',
},
{ city: 'Omaha', growth: 0.059, population: '434353', rank: '42', state: 'Nebraska' },
{ city: 'Raleigh', growth: 0.487, population: '431746', rank: '43', state: 'North Carolina' },
{ city: 'Miami', growth: 0.149, population: '417650', rank: '44', state: 'Florida' },
{ city: 'Oakland', growth: 0.013, population: '406253', rank: '45', state: 'California' },
{ city: 'Minneapolis', growth: 0.045, population: '400070', rank: '46', state: 'Minnesota' },
{ city: 'Tulsa', growth: 0.013, population: '398121', rank: '47', state: 'Oklahoma' },
{ city: 'Cleveland', growth: -0.181, population: '390113', rank: '48', state: 'Ohio' },
{ city: 'Wichita', growth: 0.097, population: '386552', rank: '49', state: 'Kansas' },
{ city: 'Arlington', growth: 0.133, population: '379577', rank: '50', state: 'Texas' },
];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Table</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">Tables have airy designs with a minimal amount of borders. This maximizes their utility.</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let Search = () => {
return {
template: hbs`
<h5 class="title is-5">Table search</h5>
<div class="boxed-section">
<div class="boxed-section-head">
Table Name
<SearchBox
@searchTerm={{mut controller.searchTerm}}
@placeholder="Search..."
@class="is-inline pull-right"
@inputClass="is-compact" />
</div>
<div class="boxed-section-body {{if controller.filteredShortList.length "is-full-bleed"}}">
{{#if controller.filteredShortList.length}}
<ListTable @source={{controller.filteredShortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
{{else}}
<div class="empty-message">
<h3 class="empty-message-headline">No Matches</h3>
<p class="empty-message-body">No products match your query.</p>
</div>
{{/if}}
</div>
</div>
<p class="annotation">Tables compose with boxed-section and boxed-section composes with search box.</p>
`,
context: {
controller: EmberObject.extend({
searchTerm: '',
filteredShortList: computed('searchTerm', function() {
let term = this.searchTerm.toLowerCase();
return productMetadata.filter(product => product.name.toLowerCase().includes(term));
}),
}).create(),
},
};
};
export let SortableColumns = () => {
return {
template: hbs`
<h5 class="title is-5">Table with sortable columns</h5>
<ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} as |t|>
<t.head>
<t.sort-by @prop="name">Name</t.sort-by>
<t.sort-by @prop="lang" @class="is-2">Language</t.sort-by>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">The list-table component provides a <code>sort-by</code> contextual component for building <code>link-to</code> components with the appropriate query params.</p>
<p class="annotation">This leaves the component stateless, relying on data to be passed down and sending actions back up via the router (via link-to).</p>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['sortProperty', 'sortDescending'],
sortProperty: 'name',
sortDescending: false,
})
),
sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() {
let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name');
return this.get('controller.sortDescending') ? sorted.reverse() : sorted;
}),
},
};
};
export let MultiRow = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-row Table</h5>
<ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} @class="is-striped" as |t|>
<t.head>
<t.sort-by @prop="name">Name</t.sort-by>
<t.sort-by @prop="lang">Language</t.sort-by>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
</tr>
<tr>
<td colspan="2">{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">The list-table component attempts to be as flexible as possible. For this reason, <code>t.body</code> does not provide the typical <code>tr</code> element. It's sometimes desired to have multiple elements per record.</p>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['sortProperty', 'sortDescending'],
sortProperty: 'name',
sortDescending: false,
})
),
sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() {
let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name');
return this.get('controller.sortDescending') ? sorted.reverse() : sorted;
}),
},
};
};
export let Pagination = () => {
return {
template: hbs`
<h5 class="title is-5">Table pagination</h5>
<ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|>
<ListTable @source={{p.list}} @class="with-foot" as |t|>
<t.head>
<th class="is-1">Rank</th>
<th>City</th>
<th>State</th>
<th>Population</th>
<th>Growth</th>
</t.head>
<t.body @key="model.rank" as |row|>
<tr>
<td>{{row.model.rank}}</td>
<td>{{row.model.city}}</td>
<td>{{row.model.state}}</td>
<td>{{row.model.population}}</td>
<td>{{format-percentage row.model.growth total=1}}</td>
</tr>
</t.body>
</ListTable>
<div class="table-foot">
<nav class="pagination">
<span class="bumper-left">U.S. City population and growth from 2000-2013</span>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
</div>
<p.prev @class="pagination-previous"> &lt; </p.prev>
<p.next @class="pagination-next"> &gt; </p.next>
<ul class="pagination-list"></ul>
</nav>
</div>
</ListPagination>
<p class="annotation">Pagination works like sorting: using <code>link-to</code>s to set a query param.</p>
<p class="annotation">Pagination, like Table, is a minimal design. Only a next and previous button are available. The current place in the set of pages is tracked by showing which slice of items is currently shown.</p>
<p class="annotation">The pagination component exposes first and last components (for jumping to the beginning and end of a list) as well as pageLinks for generating links around the current page.</p>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['currentPage'],
currentPage: 1,
})
),
longList,
},
};
};
export let RowLinks = () => {
return {
template: hbs`
<h5 class="title is-5">Table row links</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr class="is-interactive">
<td><a href="javascript:;" class="is-primary">{{row.model.name}}</a></td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">It is common for tables to act as lists of links, (e.g., clients list all allocations, each row links to the allocation detail). The helper class <code>is-interactive</code> on the <code>tr</code> makes table rows have a pointer cursor. The helper class <code>is-primary</code> on the <code>a</code> element in a table row makes the link bold and black instead of blue. This makes the link stand out less, since the entire row is a link.</p>
<p class="annotation">
A few rules for using table row links:
<ol>
<li>The <code>is-primary</code> cell should always be the first cell</li>
<li>The <code>is-primary</code> cell should always contain a link to the destination in the form of an <code>a</code> element. This is to support opening a link in a new tab.</li>
<li>The full row should transition to the destination on click. This is to improve the usability of a table by creating a larger click area.</li>
</ol>
</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let CellLinks = () => {
return {
template: hbs`
<h5 class="title is-5">Table cell links</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td><a href={{row.model.link}} target="_parent">{{row.model.name}}</a></td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">Links in table cells are just links.</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let CellDecorations = () => {
return {
template: hbs`
<h5 class="title is-5">Table cell decorations</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td><a href={{row.model.link}}>{{row.model.name}}</a></td>
<td class="nowrap">
<span class="color-swatch
{{if (eq row.model.lang "ruby") "swatch-6"}}
{{if (eq row.model.lang "golang") "swatch-5"}}" />
{{row.model.lang}}
</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">Small icons and accents of color make tables easier to scan.</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let CellIcons = () => {
return {
template: hbs`
<h5 class="title is-5">Table cell icons</h5>
<ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|>
<ListTable @source={{p.list}} @class="with-foot" as |t|>
<t.head>
<th class="is-narrow"></th>
<th class="is-1">Rank</th>
<th>City</th>
<th>State</th>
<th>Population</th>
<th>Growth</th>
</t.head>
<t.body @key="model.rank" as |row|>
<tr>
<td class="is-narrow">
{{#if (lt row.model.growth 0)}}
{{x-icon "warning" class="is-warning"}}
{{/if}}
</td>
<td>{{row.model.rank}}</td>
<td>{{row.model.city}}</td>
<td>{{row.model.state}}</td>
<td>{{row.model.population}}</td>
<td>{{format-percentage row.model.growth total=1}}</td>
</tr>
</t.body>
</ListTable>
<div class="table-foot">
<nav class="pagination">
<span class="bumper-left">U.S. City population and growth from 2000-2013. Cities with negative growth denoted.</span>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
</div>
<p.prev @class="pagination-previous"> &lt; </p.prev>
<p.next @class="pagination-next"> &gt; </p.next>
<ul class="pagination-list"></ul>
</nav>
</div>
</ListPagination>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['currentPage'],
currentPage: 1,
})
),
longList,
},
};
};

View File

@@ -0,0 +1,243 @@
import hbs from 'htmlbars-inline-precompile';
import moment from 'moment';
export default {
title: 'Components|Timeline',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Timeline</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number one
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number two
</div>
</div>
</li>
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number three
</div>
</div>
</li>
</ol>
<p class="annotation">Timelines are a combination of objects and notes. Objects compose with boxed sections to create structure.</p>
<p class="annotation">Timeline notes should be used sparingly when possible. In this example there is a note per day rather than a note per object.</p>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};
export let Detailed = () => {
return {
template: hbs`
<h5 class="title is-5">Detailed timeline</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-running">Running</span>
<span class="bumper-left pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span class="tooltip" aria-label="{{format-month-ts (now)}}">{{moment-from-now (now)}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-complete">Complete</span>
<span class="bumper-left pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-month-ts yesterday}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-error">Failed</span>
<span class="bumper-left pair is-faded">
<span class="term">Reverted</span>
<span class="badge is-light is-faded"><code>fec9218e</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-month-ts yesterday}}</span>
</span>
</div>
</div>
</li>
</ol>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};
export let Toggling = () => {
return {
template: hbs`
<h5 class="title is-5">Toggling timeline objects</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-running">Running</span>
<span class="bumper-left pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<button
class="button is-light is-compact pull-right"
onclick={{action (mut toggle1) (not toggle1)}}>
{{if toggle1 "Close" "Open"}}
</button>
</div>
{{#if toggle1}}
<div class="boxed-section-body">
<p>Some details for the timeline object.</p>
</div>
{{/if}}
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-complete">Complete</span>
<span class="bumper-left pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<button
class="button is-light is-compact pull-right"
onclick={{action (mut toggle2) (not toggle2)}}>
{{if toggle2 "Close" "Open"}}
</button>
</div>
{{#if toggle2}}
<div class="boxed-section-body">
<p>Some details for the timeline object.</p>
</div>
{{/if}}
</div>
</li>
</ol>
<p class="annotation"></p>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};
export let Emphasizing = () => {
return {
template: hbs`
<h5 class="title is-5">Emphasizing timeline objects</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span class="tooltip" aria-label="{{format-ts (now)}}">{{moment-from-now (now)}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head">
Pay attention here
</div>
<div class="boxed-section-body">
<span class="pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-ts yesterday}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="pair is-faded">
<span class="term">Reverted</span>
<span class="badge is-light is-faded"><code>fec9218e</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-ts yesterday}}</span>
</span>
</div>
</div>
</li>
</ol>
<p class="annotation">By using a full boxed-section for an emphasized timeline object, the object takes up more space and gets more visual weight. It also adheres to existing patterns.</p>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};

View File

@@ -0,0 +1,59 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|Two-Step Button',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button</h5>
<br><br>
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
/>
`,
};
};
export let InTitle = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button in title</h5>
<br><br>
<h1 class="title">
This is a page title
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
/>
</h1>
`,
};
};
export let LoadingState = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button loading state</h5>
<br><br>
<h1 class="title">
This is a page title
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
@awaitingConfirmation={{true}}
@state="prompt"
/>
</h1>
<p class="annotation"> <strong>Note:</strong> the <code>state</code> property is internal state and only used here to bypass the idle state for demonstration purposes.</p>
`,
};
};

View File

@@ -0,0 +1,111 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Theme|Colors',
};
export let Colors = () => {
return {
template: hbs`
<FreestylePalette @colorPalette={{nomadTheme}} @title="Nomad Theme" @description="Accent and neutrals." />
<FreestylePalette @colorPalette={{productColors}} @title="Product Colors" @description="Colors from other HashiCorp products. Often borrowed for alternative accents and color schemes." />
<FreestylePalette @colorPalette={{emotiveColors}} @title="Emotive Colors" @description="Colors used in conjunction with an emotional response." />
`,
context: {
nomadTheme: [
{
name: 'Primary',
base: '#25ba81',
},
{
name: 'Primary Dark',
base: '#1d9467',
},
{
name: 'Text',
base: '#0a0a0a',
},
{
name: 'Link',
base: '#1563ff',
},
{
name: 'Gray',
base: '#bbc4d1',
},
{
name: 'Off-white',
base: '#f5f5f5',
},
],
productColors: [
{
name: 'Consul Pink',
base: '#ff0087',
},
{
name: 'Consul Pink Dark',
base: '#c62a71',
},
{
name: 'Packer Blue',
base: '#1daeff',
},
{
name: 'Packer Blue Dark',
base: '#1d94dd',
},
{
name: 'Terraform Purple',
base: '#5c4ee5',
},
{
name: 'Terraform Purple Dark',
base: '#4040b2',
},
{
name: 'Vagrant Blue',
base: '#1563ff',
},
{
name: 'Vagrant Blue Dark',
base: '#104eb2',
},
{
name: 'Nomad Green',
base: '#25ba81',
},
{
name: 'Nomad Green Dark',
base: '#1d9467',
},
{
name: 'Nomad Green Darker',
base: '#16704d',
},
],
emotiveColors: [
{
name: 'Success',
base: '#23d160',
},
{
name: 'Warning',
base: '#fa8e23',
},
{
name: 'Danger',
base: '#c84034',
},
{
name: 'Info',
base: '#1563ff',
},
],
},
};
};

View File

@@ -0,0 +1,33 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Theme|Font Stacks',
};
export let FontStacks = () => {
return {
template: hbs`
<h5 class="title is-5">Font Stacks</h5>
{{#each fontFamilies as |fontFamily|}}
<h6 class="title is-6 with-headroom">{{fontFamily}}</h6>
<FreestyleTypeface @fontFamily={{fontFamily}} />
<br>
{{/each}}
`,
context: {
fontFamilies: [
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'Oxygen-Sans',
'Ubuntu',
'Cantarell',
'Helvetica Neue',
'sans-serif',
'monospace',
],
},
};
};

View File

@@ -0,0 +1,25 @@
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Theme|Text Sizing',
};
export let TextSizing = () => {
return {
template: hbs`
<h5 class="title is-5">Text sizing</h5>
<div class="block">
<h1 class="title">Large Title</h1>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
<div class="block">
<h2 class="title is-4">Medium Title</h2>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
<div class="block">
<h3 class="title is-5">Small Title</h3>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
`,
};
};

View File

@@ -0,0 +1,20 @@
import { A } from '@ember/array';
import ArrayProxy from '@ember/array/proxy';
import { next } from '@ember/runloop';
/**
* This is an array whose content is empty until the next
* tick, which fixes Storybook race condition rendering
* problems.
*/
export default ArrayProxy.extend({
init(array) {
this.set('content', A([]));
this._super(...arguments);
next(this, () => {
this.set('content', A(array));
});
},
});

View File

@@ -0,0 +1,19 @@
import EmberObject from '@ember/object';
import { next } from '@ember/runloop';
/**
* This has a `complete` property that turns from false
* to true in the next tick, which helps with some
* Storybook race condition rendering problems.
*/
export default EmberObject.extend({
init() {
this._super(...arguments);
this.set('complete', false);
next(this, () => {
this.set('complete', true);
});
},
});

File diff suppressed because it is too large Load Diff