From 9af1d0d29ce22e423aee69cbb0c409fd20bcaf09 Mon Sep 17 00:00:00 2001 From: Alex Regan Date: Sun, 13 Aug 2017 15:55:25 -0700 Subject: [PATCH 1/3] feat(col): BS4 column component --- lib/components/col.js | 103 ++++++++++++++++++++++++++++++++++++++++ lib/utils/index.js | 3 +- lib/utils/upperFirst.js | 4 ++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 lib/components/col.js create mode 100644 lib/utils/upperFirst.js diff --git a/lib/components/col.js b/lib/components/col.js new file mode 100644 index 00000000000..3ae1411ef73 --- /dev/null +++ b/lib/components/col.js @@ -0,0 +1,103 @@ +import { mergeData, upperFirst } from "../utils"; +import { concat } from "../utils/array"; +import { keys, assign, create } from "../utils/object"; + +/** + * Generates a prop object with a type of + * [Boolean, String, Number] + */ +function boolStrNum() { + return { + type: [Boolean, String, Number], + default: false + }; +} + +/** + * Generates a prop object with a type of + * [String, Number] + */ +function strNum() { + return { + type: [String, Number], + default: null + }; +} + +function suffix(str, suffix) { + return suffix ? str + upperFirst(suffix) : str; +} + +function computeBkPt(prefix, bkpt, val) { + if (!val) { + return null; + } + if (val === true && bkpt) { + return `${prefix}-${bkpt}`; + } + if (val && !bkpt) { + return `${prefix}-${val}`; + } + + return `${prefix}-${bkpt}-${val}`; +} + +const BKPTS = ["sm", "md", "lg", "xl"]; +// This generates plain order props without bkpt suffixes. +const BKPTS_PADDED = concat(null, BKPTS); + +const breakpointProps = BKPTS.reduce((memo, bkpt) => ((memo[bkpt] = boolStrNum()), memo), {}); +const orderProps = BKPTS_PADDED.reduce((memo, bkpt) => ((memo[suffix("order", bkpt)] = strNum()), memo), {}); + +// For loop doesn't need to check hasOwnProperty +// when using an object created from null +const propKeyMap = assign(create(null), { + col: keys(breakpointProps), + order: keys(orderProps) +}); + +export const props = assign( + { + tag: { + type: String, + default: "div" + }, + col: { + type: Boolean, + default: false + }, + cols: { + type: [String, Number], + default: null + } + }, + breakpointProps, + orderProps +); + +/** + * We need ".col" to default in when no other props are passed, + * but always render when col=true. + */ +export default { + functional: true, + props, + render(h, { props, data, children }) { + const classList = []; + for (const type in propKeyMap) { + const keys = propKeyMap[type]; + for (let i = 0; i < keys.length; i++) { + if (props[keys[i]]) { + classList.push(computeBkPt(type, keys[i].replace(type, "").toLowerCase(), props[keys[i]])); + } + } + } + + classList.push({ + col: props.col || (classList.length === 0 && !props.cols), + [`col-${props.cols}`]: props.cols + }); + + return h(props.tag, mergeData(data, { class: classList }), children); + } +}; diff --git a/lib/utils/index.js b/lib/utils/index.js index b64626af905..b4ec9772082 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -4,6 +4,7 @@ import * as object from "./object"; import mergeData from "vue-functional-data-merge/dist/lib.common.js"; import observeDom from "./observe-dom"; import pluckProps from "./pluckProps"; +import upperFirst from "./upperFirst"; import warn from "./warn"; -export { addEventListenerOnce, array, mergeData, object, observeDom, pluckProps, warn }; +export { addEventListenerOnce, array, mergeData, object, observeDom, pluckProps, upperFirst, warn }; diff --git a/lib/utils/upperFirst.js b/lib/utils/upperFirst.js new file mode 100644 index 00000000000..4f365088eb0 --- /dev/null +++ b/lib/utils/upperFirst.js @@ -0,0 +1,4 @@ +export default function upperFirst(str) { + str = String(str); + return str.charAt(0).toUpperCase() + str.slice(1); +} From b575f9b408d78aa10b27813caf506fa4c6f81b69 Mon Sep 17 00:00:00 2001 From: Alex Regan Date: Tue, 15 Aug 2017 10:27:47 -0700 Subject: [PATCH 2/3] feat(col): getting column working --- docs/components/col/README.md | 177 ++++++++++++++++++++++++++++++ docs/components/col/index.js | 4 + docs/components/col/meta.json | 4 + docs/components/index.js | 1 + docs/nuxt/assets/css/styles.css | 110 +++++++++++-------- lib/components/col.js | 74 +++++++------ lib/components/index.js | 2 + lib/utils/addEventListenerOnce.js | 14 +-- lib/utils/index.js | 6 +- lib/utils/memoize.js | 10 ++ lib/utils/suffixPropName.js | 12 ++ 11 files changed, 329 insertions(+), 85 deletions(-) create mode 100755 docs/components/col/README.md create mode 100755 docs/components/col/index.js create mode 100755 docs/components/col/meta.json create mode 100644 lib/utils/memoize.js create mode 100644 lib/utils/suffixPropName.js diff --git a/docs/components/col/README.md b/docs/components/col/README.md new file mode 100755 index 00000000000..f2afe1f5b7b --- /dev/null +++ b/docs/components/col/README.md @@ -0,0 +1,177 @@ +# Grid System + +> Use our powerful mobile-first flexbox grid to build layouts of all shapes and sizes thanks to a twelve column system, five default responsive tiers, Sass variables and mixins, and dozens of predefined classes. + +```html +
+
+ 1 of 2 + 2 of 2 +
+
+ 1 of 3 + 2 of 3 + 3 of 3 +
+
+ + +``` + +```html +
+
+ Column + Column +
+ Column + Column +
+
+ + +``` + +```html +
+
+
+ +

Grid Columns

+
+
+
+
+
+ +
+ +

Equal Width

+
+
+
+ 1 of 2 + 1 of 2 +
+
+ 1 of 3 + 1 of 3 + 1 of 3 +
+ +
+ +

Setting one column width

+
+
+
+ 1 of 3 + 2 of 3 (cols = 5) + 3 of 3 +
+
+ 1 of 3 + 2 of 3 (cols = 6) + 3 of 3 +
+ + +
+ +

Variable width content

+
+
+
+ 1 of 3 + Variable width content + 3 of 3 +
+
+ 1 of 3 + Variable width content + 3 of 3 +
+ + +
+ +

Equal-width multi-row

+
+
+
+ col + col +
+ col + col +
+ +
+ +

All breakpoints

+
+
+
+ col + col + col + col +
+
+ col-8 + col-4 +
+ +
+ +

Stacked to horizontal

+
+
+
+ col-sm-8 + col-sm-4 +
+
+ col-sm + col-sm + col-sm +
+ +
+ +

Mix and match

+
+
+ +
+ .col .col-md-8 + .col-6 .col-md-4 +
+ + +
+ .col-6 .col-md-4 + .col-6 .col-md-4 + .col-6 .col-md-4 +
+ + +
+ .col-6 + .col-6 +
+
+
+
+ + +``` diff --git a/docs/components/col/index.js b/docs/components/col/index.js new file mode 100755 index 00000000000..a0eb1ad8815 --- /dev/null +++ b/docs/components/col/index.js @@ -0,0 +1,4 @@ +import meta from './meta.json'; +import readme from './README.md'; + +export default {meta, readme}; diff --git a/docs/components/col/meta.json b/docs/components/col/meta.json new file mode 100755 index 00000000000..87fffce9151 --- /dev/null +++ b/docs/components/col/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Column", + "component": "bCol" +} diff --git a/docs/components/index.js b/docs/components/index.js index f691f5387ef..330bc5b3d72 100755 --- a/docs/components/index.js +++ b/docs/components/index.js @@ -7,6 +7,7 @@ export default { 'button': require('./button').default, 'button-group': require('./button-group').default, 'button-toolbar': require('./button-toolbar').default, + 'col': require('./col').default, 'card': require('./card').default, 'carousel': require('./carousel').default, 'collapse': require('./collapse').default, diff --git a/docs/nuxt/assets/css/styles.css b/docs/nuxt/assets/css/styles.css index 6c1482f41f8..f910f037991 100644 --- a/docs/nuxt/assets/css/styles.css +++ b/docs/nuxt/assets/css/styles.css @@ -1,76 +1,100 @@ @import './fonts.css'; -.page-enter-active, .page-leave-active { - transition: opacity .25s, transform .25s; +.page-enter-active, +.page-leave-active { + transition: opacity .25s, transform .25s; } -.page-enter, .page-leave-to { - opacity: 0; - transform: scale(1.01); +.page-enter, +.page-leave-to { + opacity: 0; + transform: scale(1.01); } .hljs { - font-family: "Inconsolata", "Monaco", "Andale Mono", "Lucida Console", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-size: 17px; - overflow-x: auto; - position: relative; - background-color: #f9f9f9; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125); - padding: 1em 1em 1em 1em; - margin-bottom: 1em; + font-family: "Inconsolata", "Monaco", "Andale Mono", "Lucida Console", "Bitstream Vera Sans Mono", "Courier New", + Courier, monospace !important; + font-size: 17px; + overflow-x: auto; + position: relative; + background-color: #f9f9f9; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125); + padding: 1em 1em 1em 1em; + margin-bottom: 1em; } .CodeMirror { - background-color: #f9f9f9; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125); + background-color: #f9f9f9; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125); } code { - color: #795da3; + color: #795da3; } blockquote { - font-size: 1.25rem; - font-weight: 300; + font-size: 1.25rem; + font-weight: 300; } -.bd-content>h1, .bd-content>h2, .bd-content>h3, .bd-content>h4, .bd-content>h5 { - padding-top: 25px; - padding-bottom: 15px; +.bd-content > h1, +.bd-content > h2, +.bd-content > h3, +.bd-content > h4, +.bd-content > h5 { + padding-top: 25px; + padding-bottom: 15px; } #__nuxt > .progress { - z-index: 100 !important; + z-index: 100 !important; } pre.editable:after { - content: '(double click to edit)'; - position: absolute; - top: 0; - right: 0; - color: #aaa; - text-align: right; - font-size: .75em; - padding: 5px 10px 0; - line-height: 15px; - height: 15px; - font-weight: 600; + content: '(double click to edit)'; + position: absolute; + top: 0; + right: 0; + color: #aaa; + text-align: right; + font-size: .75em; + padding: 5px 10px 0; + line-height: 15px; + height: 15px; + font-weight: 600; } pre.editable:hover:after { - font-size: .9em; - color: #444; - cursor: pointer; + font-size: .9em; + color: #444; + cursor: pointer; } pre.editable.live:after { - content: 'Live'; + content: 'Live'; } .bd-footer { - padding: 4rem 0; - margin-top: 4rem; - font-size: 85%; - text-align: center; - background-color: #f7f7f7; -} \ No newline at end of file + padding: 4rem 0; + margin-top: 4rem; + font-size: 85%; + text-align: center; + background-color: #f7f7f7; +} + +.bv-example-row .row + .row { + margin-top: 1rem; +} + +.bv-example-row .row > .col:not(.header), +.bv-example-row .row > [class^=col-] { + padding-top: .75rem; + padding-bottom: .75rem; + background-color: rgba(86, 61, 124, .15); + border: 1px solid rgba(86, 61, 124, .2); +} + +.bv-example-row-flex-cols .row { + min-height: 10rem; + background-color: rgba(255, 0, 0, .1); +} diff --git a/lib/components/col.js b/lib/components/col.js index 3ae1411ef73..04058258f3e 100644 --- a/lib/components/col.js +++ b/lib/components/col.js @@ -1,5 +1,4 @@ -import { mergeData, upperFirst } from "../utils"; -import { concat } from "../utils/array"; +import { mergeData, memoize, suffixPropName } from "../utils"; import { keys, assign, create } from "../utils/object"; /** @@ -24,36 +23,33 @@ function strNum() { }; } -function suffix(str, suffix) { - return suffix ? str + upperFirst(suffix) : str; -} - -function computeBkPt(prefix, bkpt, val) { - if (!val) { - return null; - } - if (val === true && bkpt) { - return `${prefix}-${bkpt}`; - } - if (val && !bkpt) { - return `${prefix}-${val}`; +const computeBreakPoint = memoize(function computeBkPt(type, breakpoint, val) { + // Handle boolean style prop. + if (val === true) { + // .col-md + return `${type}-${breakpoint}`.toLowerCase(); } + // .order-md-6 + return `${type}-${breakpoint}-${val}`.toLowerCase(); +}); - return `${prefix}-${bkpt}-${val}`; -} - -const BKPTS = ["sm", "md", "lg", "xl"]; -// This generates plain order props without bkpt suffixes. -const BKPTS_PADDED = concat(null, BKPTS); - -const breakpointProps = BKPTS.reduce((memo, bkpt) => ((memo[bkpt] = boolStrNum()), memo), {}); -const orderProps = BKPTS_PADDED.reduce((memo, bkpt) => ((memo[suffix("order", bkpt)] = strNum()), memo), {}); +const BREAKPOINTS = ["sm", "md", "lg", "xl"]; +// Supports classes like: .col-sm, .col-md-6, .col-lg-auto +const breakpointCols = BREAKPOINTS.reduce( + (memo, breakpoint) => ((memo[breakpoint] = boolStrNum()), memo), + create(null) +); +// Supports classes like: .order-md-1, .order-lg-12 +const breakpointOrder = BREAKPOINTS.reduce( + (memo, breakpoint) => ((memo[suffixPropName(breakpoint, "order")] = strNum()), memo), + create(null) +); // For loop doesn't need to check hasOwnProperty // when using an object created from null -const propKeyMap = assign(create(null), { - col: keys(breakpointProps), - order: keys(orderProps) +const breakpointMap = assign(create(null), { + col: keys(breakpointCols), + order: keys(breakpointOrder) }); export const props = assign( @@ -62,17 +58,24 @@ export const props = assign( type: String, default: "div" }, + // Generic flexbox .col col: { type: Boolean, default: false }, + // .col-[1-12]|auto cols: { type: [String, Number], default: null + }, + // Flex ordering utility .order-[1-12] + order: { + type: [String, Number], + default: null } }, - breakpointProps, - orderProps + breakpointCols, + breakpointOrder ); /** @@ -84,18 +87,23 @@ export default { props, render(h, { props, data, children }) { const classList = []; - for (const type in propKeyMap) { - const keys = propKeyMap[type]; + // Loop through `col` & `order` breakpoint props + for (const type in breakpointMap) { + // Returns colSm, orderMd, etc. + const keys = breakpointMap[type]; for (let i = 0; i < keys.length; i++) { if (props[keys[i]]) { - classList.push(computeBkPt(type, keys[i].replace(type, "").toLowerCase(), props[keys[i]])); + // computeBkPt(col, colSm => Sm, value=[String, Number, Boolean]) + classList.push(computeBreakPoint(type, keys[i].replace(type, ""), props[keys[i]])); } } } classList.push({ + // Default to .col if no other classes generated. col: props.col || (classList.length === 0 && !props.cols), - [`col-${props.cols}`]: props.cols + [`col-${props.cols}`]: props.cols, + [`order-${props.order}`]: props.order }); return h(props.tag, mergeData(data, { class: classList }), children); diff --git a/lib/components/index.js b/lib/components/index.js index cec623a036c..23fb94c4a20 100755 --- a/lib/components/index.js +++ b/lib/components/index.js @@ -9,6 +9,7 @@ import bButtonGroup from "./button-group"; import bInputGroup from "./input-group.vue"; import bInputGroupAddon from "./input-group-addon.vue"; import bInputGroupButton from "./input-group-button.vue"; +import bCol from "./col"; import bCard from "./card"; import bCardBody from "./card-body"; import bCardHeader from "./card-header"; @@ -71,6 +72,7 @@ export { bInputGroupAddon, bInputGroupButton, bInputGroupButton as bInputGroupBtn, + bCol, bCard, bCardBody, bCardHeader, diff --git a/lib/utils/addEventListenerOnce.js b/lib/utils/addEventListenerOnce.js index c45066df890..c23552e35be 100644 --- a/lib/utils/addEventListenerOnce.js +++ b/lib/utils/addEventListenerOnce.js @@ -1,15 +1,13 @@ /** - * Register and event to listen on specifed element once. + * Register and event to listen on specified element once. * @param {Element} element to listen on * @param {String} event to listen for * @param {Function} callback when event fires */ -function addEventListenerOnce(el, evtName, calback) { - const fnOnce = () => { +export default function addEventListenerOnce(el, evtName, callback) { + function fnOnce() { el.removeEventListener(evtName, fnOnce); - return callback.apply(null, argmuments); - }; - el.addEventListener(event, fnOnce) + return callback.apply(null, arguments); + } + el.addEventListener(event, fnOnce); } - -export default addEventListenerOnce diff --git a/lib/utils/index.js b/lib/utils/index.js index f135f7813ca..5507092e12f 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,13 +1,15 @@ import addEventListenerOnce from "./addEventListenerOnce"; import * as array from "./array"; import * as object from "./object"; -import copyProps from "./copyProps" +import copyProps from "./copyProps"; import lowerFirst from "./lowerFirst"; import identity from "./identity"; import mergeData from "vue-functional-data-merge/dist/lib.common.js"; +import memoize from "./memoize" import observeDom from "./observe-dom"; import pluckProps from "./pluckProps"; import prefixPropName from "./prefixPropName"; +import suffixPropName from "./suffixPropName"; import unPrefixPropName from "./unPrefixPropName"; import upperFirst from "./upperFirst"; import warn from "./warn"; @@ -19,10 +21,12 @@ export { lowerFirst, identity, mergeData, + memoize, object, observeDom, pluckProps, prefixPropName, + suffixPropName, upperFirst, unPrefixPropName, warn diff --git a/lib/utils/memoize.js b/lib/utils/memoize.js new file mode 100644 index 00000000000..810331fc5ec --- /dev/null +++ b/lib/utils/memoize.js @@ -0,0 +1,10 @@ +import { create } from "./object"; + +export default function memoize(fn) { + const cache = create(null); + + return function memoizedFn() { + const args = JSON.stringify(arguments); + return (cache[args] = cache[args] || fn.apply(null, arguments)); + }; +} diff --git a/lib/utils/suffixPropName.js b/lib/utils/suffixPropName.js new file mode 100644 index 00000000000..36e97e87368 --- /dev/null +++ b/lib/utils/suffixPropName.js @@ -0,0 +1,12 @@ +import upperFirst from "./upperFirst"; + +/** + * Suffix can be a falsey value so nothing is appended to string. + * (helps when looping over props & some shouldn't change) + * Use data last parameters to allow for currying. + * @param {string} suffix + * @param {string} str + */ +export default function suffixPropName(suffix, str) { + return str + (suffix ? upperFirst(suffix) : ""); +} From 9949caa92dfb1a22710d54993bb20e0ba3a7c3eb Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Mon, 21 Aug 2017 19:26:56 -0300 Subject: [PATCH 3/3] fixed index.js from merge --- lib/components/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/index.js b/lib/components/index.js index 342958b8203..f60b1e5d1e9 100755 --- a/lib/components/index.js +++ b/lib/components/index.js @@ -7,8 +7,8 @@ import bButton from "./button"; import bButtonToolbar from "./button-toolbar.vue"; import bButtonGroup from "./button-group"; import bInputGroup from "./input-group.vue"; -import bInputGroupAddon from "./input-group-addon.vue"; -import bInputGroupButton from "./input-group-button.vue"; +import bInputGroupAddon from "./input-group-addon"; +import bInputGroupButton from "./input-group-button"; import bCol from "./col"; import bCard from "./card"; import bCardBody from "./card-body";