From 8106adf35eb5ba321ecebf44f11534c530d19b30 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 2 Mar 2017 15:03:45 +0100 Subject: [PATCH] Buttonify (#282) * implement button component * fixup! add newButton * add newIcon, newButton pkgs * update icon * fixup! more progress * fixup! migrate main.js * fixup! all aboard the button train * fixup! move icons over * fixup! restore file names * fixup! fix tests * fixup! fix missing buttons * fix final button styles * fix alignment of network icon * fix alignment of checkmark icon in copy link modal * remove color variables from default icon styles * fix size and alignment of intro screen svgs --- elements/button.js | 166 +++++++++++++++++++++++++++++--------- elements/confirm-modal.js | 18 ++--- elements/crash-modal.js | 24 +++--- elements/dat-import.js | 7 +- elements/error-modal.js | 8 +- elements/header.js | 23 +++--- elements/icon.js | 26 ++++-- elements/link-modal.js | 9 ++- elements/table.js | 46 +++++------ package.json | 3 +- pages/main.js | 43 +++++----- 11 files changed, 222 insertions(+), 151 deletions(-) diff --git a/elements/button.js b/elements/button.js index 88d08a29..dc95f561 100644 --- a/elements/button.js +++ b/elements/button.js @@ -1,14 +1,15 @@ 'use strict' const html = require('choo/html') +const assert = require('assert') const css = require('sheetify') -const icon = require('./icon') +const xtend = require('xtend') -const prefix = css` +const baseStyles = css` :host { text-transform: uppercase; letter-spacing: .025em; - .btn-wrapper { + .btn-inner-wrapper { display: flex; flex-wrap: nowrap; flex-direction: row; @@ -17,71 +18,160 @@ const prefix = css` } } .icon-only { - .btn-text { - display: none; - } + .btn-text { display: none } } - .filled-green { +` + +var greenStyles = css` + :host { padding: .5rem .75rem; font-size: .75rem; background-color: var(--color-green); color: var(--color-neutral-04); } - .filled-green:hover, - .filled-green:focus { + :host:hover, + :host:focus { background-color: var(--color-green-hover); color: var(--color-white); } - .filled-red { +` + +var redStyles = css` + :host { padding: .5rem .75rem; font-size: .75rem; background-color: var(--color-red); color: var(--color-neutral-04); } - .filled-red:hover, - .filled-red:focus { + :host:hover, + :host:focus { background-color: var(--color-red-hover); color: var(--color-white); } - .plain { +` + +var plainStyles = css` + :host { padding: .5rem .75rem; font-size: .75rem; background-color: transparent; color: var(--color-neutral-40); } - .plain:hover, - .plain:focus { + :host:hover, + :host:focus { color: var(--color-neutral-70); } ` -module.exports = (props, click) => { - if (typeof click === 'function') props.click = click - - var child - if (props.icon) { - child = html` -
- ${icon({ - id: props.icon - })} - ${props.text} -
` +plainButton.green = greenButton +plainButton.icon = iconButton +plainButton.red = redButton +module.exports = plainButton + +// States: +// - Text only +// - Text and icon +function buttonElement (innerText, opts) { + if (!opts) { + opts = innerText + innerText = '' + } + + var icon = opts.icon + var innerHTML = null + + if (innerText && !icon) { + innerHTML = html` +
+ ${innerText} +
+ ` } else { - child = html` -
- ${props.text} -
` + innerHTML = html` +
+ ${icon} + ${innerText} +
+ ` + } + + var defaultProps = { + 'aria-label': innerText, + 'title': innerText } + var buttonProps = xtend(defaultProps, opts) + buttonProps.class = 'pointer ' + baseStyles + ' ' + buttonProps.class + return html` - ` } + +// - Icon only +function iconButton (innerText, opts) { + assert.equal(typeof innerText, 'string', 'elements/button: innerText should by type string') + assert.ok(innerText.length, 'elements/button: innerText should have a length >= 0') + assert.equal(typeof opts, 'object', 'elements/button: opts should by type object') + assert.ok(opts.icon, 'elements/button: opts.icon should exist') + + var icon = opts.icon + opts.class = (opts.class) + ? plainStyles + ' ' + opts.class + : plainStyles + + var innerHTML = html` +
+ ${icon} +
+ ` + + var defaultProps = { + 'aria-label': innerText, + 'title': innerText + } + + var buttonProps = xtend(defaultProps, opts) + buttonProps.class = 'pointer ' + baseStyles + ' ' + buttonProps.class + + return html` + + ` +} + +function greenButton (innerText, opts) { + if (!opts) { + opts = innerText + innerText = '' + } + + opts = opts || {} + opts.class = (opts.class) ? greenStyles + ' ' + opts.class : greenStyles + return buttonElement(innerText, opts) +} + +function redButton (innerText, opts) { + if (!opts) { + opts = innerText + innerText = '' + } + + opts = opts || {} + opts.class = (opts.class) ? redStyles + ' ' + opts.class : redStyles + return buttonElement(innerText, opts) +} + +function plainButton (innerText, opts) { + if (!opts) { + opts = innerText + innerText = '' + } + + opts = opts || {} + opts.class = (opts.class) ? plainStyles + ' ' + opts.class : plainStyles + return buttonElement(innerText, opts) +} diff --git a/elements/confirm-modal.js b/elements/confirm-modal.js index ac993510..f57c24d2 100644 --- a/elements/confirm-modal.js +++ b/elements/confirm-modal.js @@ -46,18 +46,14 @@ function createWidget () { function render (cb) { assert.equal(typeof cb, 'function', 'elements/confirm-modal: cb should be a function') - var deleteButton = button({ - text: 'Yes, Remove Dat', - style: 'filled-green', - cls: 'fr ml3', - click: ondelete + var deleteButton = button.green('Yes, Remove Dat', { + class: 'fr ml3', + onclick: ondelete }) - var exitButton = button({ - text: 'No, Cancel', - style: 'plain', - cls: 'fr', - click: onexit + var exitButton = button('No, Cancel', { + class: 'fr', + onclick: onexit }) return html` @@ -78,7 +74,7 @@ function createWidget () { onclick=${onexit} class="absolute pointer pa0 top-0 right-0 h2 w2 bg-transparent tc exit" aria-label="Close Modal"> - ${icon({id: 'cross'})} + ${icon('cross')} ` diff --git a/elements/crash-modal.js b/elements/crash-modal.js index fc6afbb7..34b8cf4a 100644 --- a/elements/crash-modal.js +++ b/elements/crash-modal.js @@ -52,11 +52,9 @@ function createWidget () { possible.

- ${button({ - text: 'Exit Application', - style: 'filled-green', - cls: 'fr ml3', - click: onexit + ${button.green('Exit Application', { + class: 'fr ml3', + onclick: onexit })}

@@ -67,17 +65,13 @@ function createWidget () { be reversed.

- ${button({ - text: 'Clear Database & exit', - style: 'filled-red', - cls: 'fr ml3', - click: clearDatabase + ${button.red('Clear Database & exit', { + class: 'fr ml3', + onclick: clearDatabase })} - ${button({ - text: 'Delete All Data & exit', - style: 'filled-red', - cls: 'fr ml3', - click: deleteData + ${button.red('Delete All Data & exit', { + class: 'fr ml3', + onclick: deleteData })}

diff --git a/elements/dat-import.js b/elements/dat-import.js index 21830f9f..7cc239e5 100644 --- a/elements/dat-import.js +++ b/elements/dat-import.js @@ -64,11 +64,6 @@ function datImportElement (props) { assert.equal(typeof onsubmit, 'function', 'dat-import: onsubmit should be type function') - const linkIcon = icon({ - id: 'link', - cls: 'absolute top-0 bottom-0 left-0' - }) - return html` ` diff --git a/elements/error-modal.js b/elements/error-modal.js index 0658083b..37ff4b94 100644 --- a/elements/error-modal.js +++ b/elements/error-modal.js @@ -35,11 +35,9 @@ function createWidget () { }) function render (message, onexit) { - var exitButton = button({ - text: 'Ok', - style: 'filled-green', - cls: 'fr ml3', - click: onexit + var exitButton = button.green('Ok', { + class: 'fr ml3', + onclick: onexit }) return html` diff --git a/elements/header.js b/elements/header.js index 0a7deb9b..bd1a58e6 100644 --- a/elements/header.js +++ b/elements/header.js @@ -3,8 +3,10 @@ const html = require('choo/html') const assert = require('assert') const css = require('sheetify') + const button = require('./button') const datImport = require('./dat-import') +const icon = require('./icon') module.exports = headerElement @@ -76,22 +78,17 @@ function headerElement (props) { var importButton = datImport({ onsubmit: onimport }) - var createButton = button({ - icon: 'create-new-dat', - text: 'Create New Dat', - cls: 'ml2 b--transparent header-action header-action-no-border', - click: oncreate + var createButton = button('Create New Dat', { + icon: icon('create-new-dat'), + class: 'ml2 b--transparent header-action header-action-no-border', + onclick: oncreate }) - var loginButton = button({ - text: 'Log In', - cls: 'ml2 header-action log-in-button' - }) + var loginButton = button('Log In', { class: 'ml2 header-action log-in-button' }) - var menuButton = button({ - icon: 'menu', - text: '', - cls: 'ml2 header-action header-action-no-border menu-trigger' + var menuButton = button.icon('Open Menu', { + icon: icon('menu'), + class: 'ml2 header-action header-action-no-border menu-trigger' }) return html` diff --git a/elements/icon.js b/elements/icon.js index 265ef4f9..1ab2f6eb 100644 --- a/elements/icon.js +++ b/elements/icon.js @@ -1,16 +1,30 @@ 'use strict' const html = require('choo/html') -const css = require('yo-css') +const assert = require('assert') +const css = require('sheetify') -module.exports = (props) => { - const style = { - fill: 'currentColor' +var prefix = css` + :host { + display: block; + fill: currentColor; } +` + +module.exports = iconElement + +function iconElement (iconName, opts) { + opts = opts || {} + + assert.equal(typeof iconName, 'string', 'elements/icon: iconName should be type string') + assert.equal(typeof opts, 'object', 'elements/icon: opts should be type object') + + var classNames = 'icon-' + iconName + ' ' + prefix + if (opts.class) classNames += (' ' + opts.class) return html` - - + + ` } diff --git a/elements/link-modal.js b/elements/link-modal.js index c2425b2e..48a17d40 100644 --- a/elements/link-modal.js +++ b/elements/link-modal.js @@ -95,6 +95,7 @@ const input = css` top: 2rem; } .icon-check { + display: inline-block; width: var(--icon-height); height: .875rem; vertical-align: -.15rem; @@ -151,13 +152,13 @@ function createModal () {

- ${icon({id: 'check'})} + ${icon('check')} Link copied to clipboard

- ${icon({id: 'link'})} + ${icon('link')}

@@ -167,7 +168,7 @@ function createModal () { onclick=${onexit} class="absolute pointer pa0 top-0 right-0 h2 w2 bg-transparent tc exit" aria-label="Close Modal"> - ${icon({id: 'cross'})} + ${icon('cross')} ` diff --git a/elements/table.js b/elements/table.js index a8c085b9..79d48c3e 100644 --- a/elements/table.js +++ b/elements/table.js @@ -4,8 +4,8 @@ const html = require('choo/html') const assert = require('assert') const css = require('sheetify') -const status = require('./status') const button = require('./button') +const status = require('./status') const icon = require('./icon') const table = css` @@ -90,6 +90,7 @@ const table = css` } } .icon-network { + display: inline-block; color: var(--color-neutral-20); vertical-align: middle; width: 1.1em; @@ -168,34 +169,28 @@ function row (dat, send) { : 'loading' const hexContent = { - loading: icon({id: 'hexagon-down', cls: 'color-blue hover-color-blue-hover'}), - paused: icon({id: 'hexagon-x', cls: 'color-neutral-30 hover-color-neutral-40'}), - complete: icon({id: 'hexagon-up', cls: 'color-green hover-color-green-hover'}) + loading: icon('hexagon-down', {class: 'color-blue hover-color-blue-hover'}), + paused: icon('hexagon-x', {class: 'color-neutral-30 hover-color-neutral-40'}), + complete: icon('hexagon-up', {class: 'color-green hover-color-green-hover'}) }[stats.state] - var finderButton = button({ - text: 'Open in Finder', - style: 'icon-only', - icon: 'open-in-finder', - cls: 'row-action', - click: () => send('repos:open', dat) + var finderButton = button.icon('Open in Finder', { + icon: icon('open-in-finder'), + class: 'row-action', + onclick: () => send('repos:open', dat) }) - var linkButton = button({ - text: 'Copy Dat Link', - style: 'icon-only', - icon: 'link', - cls: 'row-action', - click: () => send('repos:share', dat) + var linkButton = button.icon('Share Dat', { + icon: icon('link'), + class: 'row-action', + onclick: () => send('repos:share', dat) }) - var deleteButton = button({ - text: 'Remove Dat', - style: 'icon-only', - icon: 'delete', - cls: 'row-action', - click: function (e) { - // TODO: we're relying on DOM ordering here. Fix this in choo by moving + var deleteButton = button.icon('Remove Dat', { + icon: icon('delete'), + class: 'row-action', + onclick: function (e) { + // FIXME: we're relying on DOM ordering here. Fix this in choo by moving // to nanomorph; e.g. events are still copied over when reordering var target = e.target while (target.parentNode) { @@ -208,9 +203,8 @@ function row (dat, send) { } }) - var networkIcon = icon({ - id: 'network', - cls: (peers > 1) + var networkIcon = icon('network', { + class: (peers > 1) ? 'network-peers-many' : (peers > 0) ? 'network-peers-1' diff --git a/package.json b/package.json index 2885f7dc..37956b0f 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "tachyons": "^4.5.4", "toiletdb": "^1.2.0", "xhr": "^2.3.3", - "xtend": "^4.0.1", - "yo-css": "^1.1.0" + "xtend": "^4.0.1" }, "scripts": { "bundle": "./scripts/build bundle", diff --git a/pages/main.js b/pages/main.js index 5858ddd1..dd6404ac 100644 --- a/pages/main.js +++ b/pages/main.js @@ -34,20 +34,22 @@ const skeleton = css` .create-new-dat { top: 14.5rem; right: 4rem; + svg { + display: inline-block; + width: 2rem; + height: 2rem; + } } .link { top: 6rem; right: 8.5rem; color: red; - } - .icon-create-new-dat, - .icon-link { - width: 2rem; - height: 2rem; - fill: currentColor; - } - .icon-link { - margin-bottom: -.75rem; + svg { + display: inline-block; + width: 2rem; + height: 2rem; + margin-bottom: -.75rem; + } } } ` @@ -129,7 +131,7 @@ function mainView (state, prev, send) { } function WelcomeScreen (methods) { - const onExit = methods.onexit + const onexit = methods.onexit const onLoad = methods.onload return html` @@ -138,12 +140,7 @@ function WelcomeScreen (methods) {

Share data on the distributed web.

- ${button({ - text: 'Get Started', - style: 'filled-green', - cls: '', - click: onExit - })} + ${button.green('Get Started', { onclick: onexit })} ` } @@ -155,11 +152,10 @@ function EmptyState () {
- ${icon({ - id: 'create-new-dat', - cls: 'color-green-disabled' - })} + ${icon('create-new-dat', { class: 'color-green-disabled' })}

Create New Dat

… or select one of your local