From ac1b3f3aa1e9cdf863896ad7a7464da25823c30a Mon Sep 17 00:00:00 2001 From: Matt Rosno Date: Tue, 29 Jan 2019 14:58:01 -0600 Subject: [PATCH] feat(button): initial button config generation --- package.json | 4 +- src/components/button/config/demo.js | 100 +++++++++++++++++++ src/components/button/config/generate.js | 83 +++++++++++++++ src/components/button/config/selectors.js | 67 +++++++++++++ src/components/button/docs/a11y.md | 0 src/components/button/docs/support.md | 0 src/components/button/docs/ux.md | 0 src/components/button/index.js | 35 +++++++ src/components/button/index.scss | 6 ++ src/components/button/requirements.feature | 6 ++ src/components/button/styles/_mixins.scss | 6 ++ src/components/button/styles/_variables.scss | 6 ++ src/components/button/tests/context.js | 6 ++ src/components/button/tests/requirements.js | 6 ++ src/components/button/tests/template.js | 6 ++ src/components/button/tests/unit.js | 29 ++++++ src/globalTypedefs.js | 29 ++++++ src/index.js | 2 + src/tools/component/demo/index.js | 46 +++++++++ src/tools/component/selectors/index.js | 26 +++++ yarn.lock | 2 +- 21 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 src/components/button/config/demo.js create mode 100644 src/components/button/config/generate.js create mode 100644 src/components/button/config/selectors.js create mode 100644 src/components/button/docs/a11y.md create mode 100644 src/components/button/docs/support.md create mode 100644 src/components/button/docs/ux.md create mode 100644 src/components/button/index.js create mode 100644 src/components/button/index.scss create mode 100644 src/components/button/requirements.feature create mode 100644 src/components/button/styles/_mixins.scss create mode 100644 src/components/button/styles/_variables.scss create mode 100644 src/components/button/tests/context.js create mode 100644 src/components/button/tests/requirements.js create mode 100644 src/components/button/tests/template.js create mode 100644 src/components/button/tests/unit.js create mode 100644 src/globalTypedefs.js create mode 100644 src/tools/component/demo/index.js create mode 100644 src/tools/component/selectors/index.js diff --git a/package.json b/package.json index 60e1ba0..7745dd8 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "jest-circus": "^23.6.0", "jest-junit": "^5.2.0", "lint-staged": "^8.1.0", + "lodash": "^4.17.11", "prettier": "^1.15.3", "rimraf": "^2.6.3", "rollup": "^1.1.0", @@ -87,7 +88,8 @@ "testMatch": [ "/**/__tests__/**/*.js?(x)", "/**/?(*.)(spec|test).js?(x)", - "/**/?(*-)(spec|test).js?(x)" + "/**/?(*-)(spec|test).js?(x)", + "/**/tests/unit.js?(x)" ], "transform": { "^.+\\.(js|jsx)$": "./tasks/jest/jsTransform.js", diff --git a/src/components/button/config/demo.js b/src/components/button/config/demo.js new file mode 100644 index 0000000..98dba41 --- /dev/null +++ b/src/components/button/config/demo.js @@ -0,0 +1,100 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Configuration for the Button component + * @module button/demo + */ +import startCase from 'lodash/startCase'; +import { generate } from './generate'; + +/** + * Creates a button template configuration for a variant + * @returns {globalTypedefs.fractalDemo} fractal demo object + */ +const variantButtons = function() { + const variants = {}; + + Object.keys(this.selectors.variants).forEach(variant => { + variants[variant] = { + name: variant, + label: startCase(variant), + context: generate.apply(this, [ + { + variant, + }, + ]), + }; + }); + + return variants; +}; + +/** + * Modifier: Anchor + * @returns {globalTypedefs.fractalDemo} fractal demo object + */ +const anchorButton = function() { + return { + name: 'anchor', + label: 'Anchor', + context: generate.apply(this, [ + { + element: 'a', + }, + ]), + }; +}; + +/** + * Modifier: Small + * @returns {globalTypedefs.fractalDemo} fractal demo object + */ +const smallButton = function() { + return { + name: 'small', + label: 'Small', + context: generate.apply(this, [ + { + size: 'small', + }, + ]), + }; +}; + +/** + * State: Disabled + * @returns {globalTypedefs.fractalDemo} fractal demo object + */ +const disabledButton = function() { + return { + name: 'disabled', + label: 'Disabled', + context: generate.apply(this, [ + { + disabled: true, + }, + ]), + }; +}; + +export default function demo() { + return { + variants: variantButtons.apply(this), + modifiers: { + types: { + anchor: anchorButton.apply(this), + }, + sizes: { + small: smallButton.apply(this), + }, + }, + states: { + disabled: disabledButton.apply(this), + }, + }; +} diff --git a/src/components/button/config/generate.js b/src/components/button/config/generate.js new file mode 100644 index 0000000..84a07de --- /dev/null +++ b/src/components/button/config/generate.js @@ -0,0 +1,83 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Template configuration generate functions for Button component + * @module button/generate + */ +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; + +// TODO JSDoc +export function generate(options) { + let opts = merge( + { + element: 'button', + content: 'Button', + attributes: {}, + disabled: false, + type: 'button', + tabIndex: 0, + variant: 'primary', + }, + options + ); + + const selectors = cloneDeep(this.selectors); + + let config = { + root: {}, + }; + + Object.keys(selectors.default).forEach(element => { + config = merge(config, { + [element]: { + attributes: {}, + classNames: selectors.default[element], + }, + }); + }); + + // Element + + config.root.element = opts.element; + + // Content + + config.root.content = opts.content; + + // Classes + + config.root.classNames += ` ${selectors.variants[opts.variant].root}`; + + if (opts.size) { + config.root.classNames += ` ${selectors.modifiers.sizes[opts.size].root}`; + } + + // Attributes + + if (opts.variant === 'danger') { + config.root.attributes['aria-label'] = 'danger'; + } + + if (opts.element === 'button') { + config.root.attributes.type = opts.type; + + if (opts.disabled) { + config.root.attributes.disabled = ''; + } + } else if (opts.element === 'a') { + config.root.attributes.href = opts.href || '#'; + config.root.attributes.role = 'button'; + } + + if (config.icon) { + config.icon.attributes['aria-hidden'] = true; + } + + return config; +} diff --git a/src/components/button/config/selectors.js b/src/components/button/config/selectors.js new file mode 100644 index 0000000..7a6b997 --- /dev/null +++ b/src/components/button/config/selectors.js @@ -0,0 +1,67 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Available CSS selectors for Button component + * @module button/selectors + */ + +/** + * Button component classes + * @typedef {object} buttonClasses + * @property {string} root - button element + * @property {string} content - wrapper element around button content + * @property {string} icon - button SVG + */ + +/** + * Button component selectors + * @typedef {object} buttonSelectors + * @property {buttonClasses} default - default button classes + * @property {object} variants - button variants + * @property {buttonClasses} variants.primary - `primary` variant + * @property {buttonClasses} variants.secondary - `secondary` variant + * @property {buttonClasses} variants.tertiary - `tertiary` variant + * @property {buttonClasses} variants.ghost - `ghost` variant + * @property {buttonClasses} variants.danger - `danger` variant + * @property {object} modifiers - style modifiers + * @property {object} modifiers.sizes - size modifiers + * @property {buttonClasses} modifiers.sizes.small - `small` modifier + */ +const selectors = { + default: { + root: 'btn', + content: 'btn__content', + icon: 'btn__icon', + }, + variants: { + primary: { + root: 'btn--primary', + }, + secondary: { + root: 'btn--secondary', + }, + tertiary: { + root: 'btn--tertiary', + }, + ghost: { + root: 'btn--ghost', + }, + danger: { + root: 'btn--danger', + }, + }, + modifiers: { + sizes: { + small: { + root: 'btn--sm', + }, + }, + }, +}; + +export default selectors; diff --git a/src/components/button/docs/a11y.md b/src/components/button/docs/a11y.md new file mode 100644 index 0000000..e69de29 diff --git a/src/components/button/docs/support.md b/src/components/button/docs/support.md new file mode 100644 index 0000000..e69de29 diff --git a/src/components/button/docs/ux.md b/src/components/button/docs/ux.md new file mode 100644 index 0000000..e69de29 diff --git a/src/components/button/index.js b/src/components/button/index.js new file mode 100644 index 0000000..ffeacd7 --- /dev/null +++ b/src/components/button/index.js @@ -0,0 +1,35 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Button component + * @module button + */ +import { prefixSelectors } from '../../tools/component/selectors'; +import demo from './config/demo'; +import { generate } from './config/generate'; +import selectors from './config/selectors'; + +/** + * Button spec + * @param {string} prefix - selector prefix + * @type {globalTypedefs.componentConfig} + */ +const buttonConfig = prefix => { + const config = { + selectors: prefixSelectors(selectors, prefix), + }; + + return { + label: 'Button', + demo: demo.bind(config), + generate: generate.bind(config), + selectors: config.selectors, + }; +}; + +export default buttonConfig; diff --git a/src/components/button/index.scss b/src/components/button/index.scss new file mode 100644 index 0000000..88c46d8 --- /dev/null +++ b/src/components/button/index.scss @@ -0,0 +1,6 @@ +// +// Copyright IBM Corp. 2018, 2018 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// diff --git a/src/components/button/requirements.feature b/src/components/button/requirements.feature new file mode 100644 index 0000000..acb16bb --- /dev/null +++ b/src/components/button/requirements.feature @@ -0,0 +1,6 @@ +# +# Copyright IBM Corp. 2018, 2018 +# +# This source code is licensed under the Apache-2.0 license found in the +# LICENSE file in the root directory of this source tree. +# diff --git a/src/components/button/styles/_mixins.scss b/src/components/button/styles/_mixins.scss new file mode 100644 index 0000000..88c46d8 --- /dev/null +++ b/src/components/button/styles/_mixins.scss @@ -0,0 +1,6 @@ +// +// Copyright IBM Corp. 2018, 2018 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// diff --git a/src/components/button/styles/_variables.scss b/src/components/button/styles/_variables.scss new file mode 100644 index 0000000..88c46d8 --- /dev/null +++ b/src/components/button/styles/_variables.scss @@ -0,0 +1,6 @@ +// +// Copyright IBM Corp. 2018, 2018 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// diff --git a/src/components/button/tests/context.js b/src/components/button/tests/context.js new file mode 100644 index 0000000..eb23490 --- /dev/null +++ b/src/components/button/tests/context.js @@ -0,0 +1,6 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/src/components/button/tests/requirements.js b/src/components/button/tests/requirements.js new file mode 100644 index 0000000..eb23490 --- /dev/null +++ b/src/components/button/tests/requirements.js @@ -0,0 +1,6 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/src/components/button/tests/template.js b/src/components/button/tests/template.js new file mode 100644 index 0000000..eb23490 --- /dev/null +++ b/src/components/button/tests/template.js @@ -0,0 +1,6 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/src/components/button/tests/unit.js b/src/components/button/tests/unit.js new file mode 100644 index 0000000..c731104 --- /dev/null +++ b/src/components/button/tests/unit.js @@ -0,0 +1,29 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import buttonConfig from '../'; + +describe('Button', () => { + it('Generates a button', () => { + const button = buttonConfig('bx'); + const myButton = button.generate({ + disabled: true, + size: 'small', + variant: 'danger', + }); + + console.log(JSON.stringify(button.label, null, 2)); + + console.log(JSON.stringify(button.demo(), null, 2)); + + console.log(JSON.stringify(myButton, null, 2)); + + console.log(JSON.stringify(button.selectors, null, 2)); + + expect(true).toEqual(true); + }); +}); diff --git a/src/globalTypedefs.js b/src/globalTypedefs.js new file mode 100644 index 0000000..2a7921e --- /dev/null +++ b/src/globalTypedefs.js @@ -0,0 +1,29 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @namespace globalTypedefs + */ + +/** + * Fractal demo/variant object + * @memberOf globalTypedefs + * @typedef {object} fractalDemo + * @property {string} name - name of the demo use in URL and handlebar partials + * @property {string} label - name of the demo displayed in navigation + * @property {object} context - context applied to template + */ + +/** + * Exported component configuration + * @memberOf globalTypedefs + * @typedef {object} componentConfig + * @property {string} label - component name + * @property {function} demo - demo objects + * @property {function} generate - generate function + * @property {object} selectors - selectors object + */ diff --git a/src/index.js b/src/index.js index c1f5c5c..fde2edd 100644 --- a/src/index.js +++ b/src/index.js @@ -8,3 +8,5 @@ export * from './error'; export * from './rules'; export * from './runner'; + +export { default as buttonConfig } from './components/button'; diff --git a/src/tools/component/demo/index.js b/src/tools/component/demo/index.js new file mode 100644 index 0000000..edaea89 --- /dev/null +++ b/src/tools/component/demo/index.js @@ -0,0 +1,46 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Turns a nested object of component examples in to a flattened array to be used by Fractal + * @param {object} types - object that contains component examples grouped by type + * @returns {globalTypedefs.fractalDemo[]} - flattened array of Fractal variants + */ +export function flattenDemosObject(types) { + const variants = []; + + if (!types) { + return variants; + } + + Object.keys(types).forEach(type => { + if (typeof types[type] === 'object') { + Object.keys(types[type]).forEach(variant => { + if ( + typeof types[type][variant] === 'object' && + !types[type][variant].name + ) { + Object.keys(types[type][variant]).forEach(modifier => { + if ( + typeof types[type][variant][modifier] === 'object' && + types[type][variant][modifier].name + ) { + variants.push(types[type][variant][modifier]); + } + }); + } else if ( + typeof types[type][variant] === 'object' && + types[type][variant].name + ) { + variants.push(types[type][variant]); + } + }); + } + }); + + return variants; +} diff --git a/src/tools/component/selectors/index.js b/src/tools/component/selectors/index.js new file mode 100644 index 0000000..33da1e4 --- /dev/null +++ b/src/tools/component/selectors/index.js @@ -0,0 +1,26 @@ +/** + * Copyright IBM Corp. 2018, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import cloneDeep from 'lodash/cloneDeep'; + +/** + * Applies a prefix to every class name in an object. + * @param {object} selectors - object with class name values + * @param {string} prefix - prepended with `--` to every class name + * @returns {object} - modified selectors object + */ +export function prefixSelectors(selectors, prefix) { + selectors = cloneDeep(selectors || {}); + + if (prefix) { + selectors = JSON.parse( + JSON.stringify(selectors).replace(/:"/g, `:"${prefix}--`) + ); + } + + return selectors; +} diff --git a/yarn.lock b/yarn.lock index d897d81..6c3990b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3505,7 +3505,7 @@ lodash.upperfirst@4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" -lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4: +lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==