/* Reset (input, button, list)
cookbook/elements/forms.md
cookbook/elements/lists.md */
/**
* basic resets control to remove default styles from buttons inputs and lists
*
* @param [type] {String} - reset 'input' | 'button' | 'list'
* @return {Object}
*/
function reset (type = 'input') {
const map = {
button: {
'::-moz-focus-inner': {
border: '0',
padding: '0'
}
},
input: {
'-webkit-appearance': 'none',
'-moz-appearance': 'none',
'::-moz-focus-inner': {
border: '0',
padding: '0'
}
},
list: {
listStyle: 'none'
}
}
return map[type] || map.input
}
/* Layout utils
cookbook/layout/utilities.md */
/**
* Center block elements along the x-axis
*
* @type {{marginRight: string, marginLeft: string}}
*/
const center = {
marginRight: 'auto',
marginLeft: 'auto'
}
/* Tables
cookbook/elements/tables.md */
/**
* Sets cells inside a
to have shared borders.
* Sets the distance between the borders of adjacent cells to 0
*
* @type {{borderCollapse: string, borderSpacing: string}}
*/
const collapse = {
borderCollapse: 'collapse',
borderSpacing: '0'
}
/**
* Sets background color of odd rows in a
*
* @param [color] {String} - color definition
* @return {{':nth-child(odd)': {backgroundColor: string}}}
*/
function striped (color = 'lightgrey') {
return {
':nth-child(odd)': {
backgroundColor: color
}
}
}
/* BORDER BOX
cookbook/layout/box-sizing.md */
/**
* Tells the browser to account for any border and padding
* in the values specified for an element's width and height.
*
* If you set an element's width to 100 pixels, that 100 pixels
* will include any border or padding you added,
* and the content box will shrink to absorb that extra width.
* This typically makes it much easier to size elements.
*
* @type {{boxSizing: string}}
*/
const borderBox = { boxSizing: 'border-box' } // b('box-sizing: border-box;')
/* Background Size
cookbook/themed/background-size.md */
/**
* Scales the image as large as possible without stretching the image.
* If the proportions of the image differ from the element,
* it is cropped either vertically or horizontally so that
* no empty space remains.
*
* Often used in combination with background image set as an inline style
* on an html element.
*
* @example
* b`
* background-image: url(http://placekitten.com/g/500/500);
* width: 100%;
* `.bgCover
*
* @type {{backgroundSize: string}}
*/
const bgCover = { backgroundSize: 'cover' }
/**
* Scales the image as large as possible without cropping or stretching the image.
*
* Often used in combination with background image set as an inline style
* on an html element.
*
* @example
* b`
* background-image: url(http://placekitten.com/g/500/500);
* width: 100%;
* `.bgContain
*
* @type {{backgroundSize: string}}
*/
const bgContain = { backgroundSize: 'contain' }
/* Borders
cookbook/themed/borders.md */
/**
* use css border short hand with additional optional direction argument
* argument order does not matter
*
* @param [args] {[String, String, String, String]}
*
* @example
* b.setBorder('currentcolor medium solid', 'all')
* * @example
* b.setBorder('top', '3px', 'darkred', 'dashed')
*
* @return {Object}
*/
function setBorder (...args) {
let width
let style
let color
let dir
const widthMap = { thin: 'thin', medium: 'medium', thick: 'thick' }
const styleMap = {
none: 'none',
hidden: 'hidden',
dotted: 'dotted',
dashed: 'dashed',
solid: 'solid',
double: 'double',
groove: 'groove',
ridge: 'ridge',
inset: 'inset',
outset: 'inset'
}
const dirMap = {
all: 'all',
top: 'top',
right: 'right',
bottom: 'bottom',
left: 'left'
}
for (let i = 0; i < 4; i++) {
const arg = args[i]
if (arg == null) continue
if (dirMap[arg] != null) dir = arg
else if (styleMap[arg] != null) style = arg
else if (widthMap[arg] != null || (!isNaN(Number.parseFloat(arg)) && arg.split(' ').length < 2)) width = arg
else color = arg
}
const direction = dir || 'all'
const border = (direction === 'all') ? args.filter(x => x !== dirMap[x]).join(' ') || 'medium solid currentcolor' : undefined
const borderWidth = width || 'medium'
const borderStyle = style || 'solid'
const borderColor = color || 'currentcolor'
const fallback = (!width && !style && color)
const resMap = {
all: fallback ? { border } : { borderWidth, borderStyle, borderColor },
top: fallback
? { border: color, borderLeftStyle: 'none', borderBottomStyle: 'none', borderRightStyle: 'none' }
: { borderWidth, borderTopStyle: borderStyle, borderColor },
right: fallback
? { border: color, borderLeftStyle: 'none', borderBottomStyle: 'none', borderTopStyle: 'none' }
: { borderWidth, borderRightStyle: borderStyle, borderColor },
bottom: fallback
? { border: color, borderLeftStyle: 'none', borderRightStyle: 'none', borderTopStyle: 'none' }
: { borderWidth, borderBottomStyle: borderStyle, borderColor },
left: fallback
? { border: color, borderBottomStyle: 'none', borderRightStyle: 'none', borderTopStyle: 'none' }
: { borderWidth, borderLeftStyle: borderStyle, borderColor }
}
return resMap[direction]
}
/**
*
* @param args {[String, String]} - ,
* @return {*|{borderStyle: string}}
*/
function setBorderStyle (...args) {
let value
let dir
// let values = [ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', 'initial' ]
const directions = [ 'all', 'top', 'right', 'bottom', 'left' ]
for (let i = 0; i < 2; i++) {
const arg = args[i]
if (arg == null) continue
if (directions.find(x => x === arg)) dir = arg
else if (typeof arg === 'string') value = arg
}
const direction = dir || 'all'
const borderStyle = direction === 'all' ? value || 'initial' : value.split(' ')[0] || 'initial'
const resMap = {
all: { borderStyle },
top: { borderTopStyle: borderStyle },
right: { borderRightStyle: borderStyle },
bottom: { borderBottomStyle: borderStyle },
left: { borderLeftStyle: borderStyle }
}
return resMap[direction] || { borderStyle: 'initial' }
}
/**
*
* @param args {[String, String]}
* @return {Object}
*/
function setBorderColor (...args) {
let value
let dir
const directions = [ 'all', 'top', 'right', 'bottom', 'left' ]
for (let i = 0; i < 2; i++) {
const arg = args[i]
if (arg == null) continue
if (directions.find(x => x === arg)) dir = arg
else value = arg
}
const direction = dir || 'all'
const borderColor = value || 'currentcolor'
const resMap = {
all: { borderColor },
top: { borderTopColor: borderColor },
right: { borderRightColor: borderColor },
bottom: { borderBottomColor: borderColor },
left: { borderLeftColor: borderColor }
}
return resMap[direction]
}
function setBorderWidth (...args) {
let value
let dir
const directions = [ 'all', 'top', 'right', 'bottom', 'left' ]
for (let i = 0; i < 2; i++) {
const arg = args[i]
if (arg == null) continue
if (directions.find(x => x === arg)) dir = arg
else value = arg
}
const direction = dir || 'all'
const borderWidth = direction === 'all' ? value || 'initial' : value.split(' ')[0] || 'initial'
const resMap = {
all: { borderWidth },
top: { borderTopWidth: borderWidth },
right: { borderRightWidth: borderWidth },
bottom: { borderBottomWidth: borderWidth },
left: { borderLeftWidth: borderWidth }
}
return resMap[direction]
}
/* Border Radii
cookbook/themed/border-radii.md */
/**
*
* @param [args = ['0', 'all']] {Array} - [ | , ]
* @return {Object}
*/
function setBorderRadius (...args) {
let value = '0'
let dir = 'all'
const directions = [ 'all', 'top', 'right', 'bottom', 'left', 'topLeft', 'topRight', 'bottomRight', 'bottomLeft' ]
for (let i = 0; i < 2; i++) {
const arg = args[i]
if (arg == null) continue
if (directions.find(x => x === arg)) dir = arg
else value = arg
}
const direction = dir || 'all'
const borderRadius = direction === 'all' ? value || '0' : value.split(' ')[0] || '0'
const valMap = { pill: { borderRadius: '9999px' } }
const dirMap = {
all: { borderRadius },
top: { borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius },
right: { borderTopRightRadius: borderRadius, borderBottomRightRadius: borderRadius },
bottom: { borderBottomRightRadius: borderRadius, borderBottomLeftRadius: borderRadius },
left: { borderBottomLeftRadius: borderRadius, borderTopLeftRadius: borderRadius },
topLeft: { borderTopLeftRadius: borderRadius },
topRight: { borderTopRightRadius: borderRadius },
bottomRight: { borderBottomRightRadius: borderRadius },
bottomLeft: { borderBottomLeftRadius: borderRadius }
}
return valMap[value] || dirMap[direction]
}
/* DISPLAY
cookbook/layout/display.md */
/**
*
* @type {{display: string}}
*/
const block = { display: 'block' } // b('display: block;')
/**
*
* @type {{display: string}}
*/
const hidden = { display: 'none' } // b('display: none;')
/**
*
* @type {{border: string, clip: string, height: string, overflow: string, padding: string, position: string, width: string}}
*/
const hiddenVisually = {
border: '0px',
clip: 'rect(1px,1px,1px,1px)',
height: '1px',
overflow: 'hidden',
padding: '0px',
position: 'absolute',
width: '1px'
}
/**
*
* @type {{display: string}}
*/
const inline = { display: 'inline' } // b('display: inline;')
/**
*
* 1. Fix for Firefox bug: an image styled `max-width:100%` within an
* inline-block will display at its default size, and not limit its width to
* 100% of an ancestral container.
*
* @type {{display: string, maxWidth: string}}
*/
const inlineBlock = {
display: 'inline-block',
maxWidth: '100%'
}
/**
*
* @type {{display: string}}
*/
const table = { display: 'table' } // b('display: table;')
/**
*
* @type {{display: string}}
*/
const tableRow = { display: 'table-row' } // b('display: table-row;')
/**
*
* @type {{display: string}}
*/
const tableRowGroup = { display: 'table-row-group' } // b('display: table-row-group;')
/**
*
* @type {{display: string}}
*/
const tableCell = { display: 'table-cell' } // b('display: table-cell;')
/**
*
* @type {{display: string}}
*/
const tableColumn = { display: 'table-column' } // b('display: table-column;')
/**
*
* @type {{display: string}}
*/
const tableColumnGroup = { display: 'table-column-group' } // b('display: table-column-group;')
/* FLEXBOX
cookbook/layout/flexbox.md */
/* Applies to flex container
========================================================================== */
/**
* Flexbox layout is defined using the flex or inline-flex values
* of the display property on the parent item. This element then
* becomes a flex container, and each one of its children
* becomes a flex item.
*
* @type {{display: string}}
*/
// causes the element to become a block level flex container
const flexBlock = { display: 'flex' }
// causes the element to become an inline level flex container
const flexInline = { display: 'inline-flex' }
/**
* flex-direction sets how flex items are placed in the flex container
* defining the main axis and the direction (normal or reversed)
*
* @type {{flexDirection: string}}
*/
// Main Axis equals text direction, Main Start and End points equal content direction
const flexRow = { flexDirection: 'row' }
// Same as flexRow with Main Start and End permuted
const flexRowReverse = { flexDirection: 'row-reverse' }
// Main Axis equals block-axis, Main Start and End points equal before and after points
const flexCol = { flexDirection: 'column' }
// same as flexCol with Main Start and End permuted.
const flexColReverse = { flexDirection: 'column-reverse' }
/**
*
* sets whether flex items are forced onto one line or can wrap onto multiple lines.
* If wrapping is allowed, it sets the direction that lines are stacked.
* https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap
*
* @example
* b.flexNoWrap // { flexWrap: 'nowrap' } The flex items are laid out in a single line (default)
* b.flexWrap // { flexWrap: 'wrap' } The flex items break into multiple lines
* b.flexWrapReverse // { flexWrap: 'wrap-reverse' } Same as wrap with items in reverse order
*
* @type {{flexWrap: string}}
*/
const flexNoWrap = { flexWrap: 'nowrap' }
const flexDoWrap = { flexWrap: 'wrap' } // will overwrite initial b.flexWrap() helper
const flexWrapReverse = { flexWrap: 'wrap-reverse' }
/**
*
* Define how the browser distributes space between and around content items along
* the Main Axis (x-axis for flex direction row) of the current line of a flex container.
* https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content
*
* @param [v = 'initial'] {String<'start' | 'end' | 'center' | 'between' | 'around'>}
* @return {Object}
*/
function flexJustify (v = 'initial') {
const map = {
start: 'flex-start',
end: 'flex-end',
center: 'center',
between: 'space-between',
around: 'space-around',
evenly: 'space-evenly'
}
return { justifyContent: map[v] || 'initial' }
}
/**
*
* Control the alignment of items along the Cross Axis (y-axis when flex direction is 'row')
* by setting the 'align-self' value on all direct children as a group.
*
* Behaves similar to `justify-content` but in the perpendicular
* direction of the current line of a flex container.
* https://developer.mozilla.org/en-US/docs/Web/CSS/align-items
*
* @param v {String<'start' | 'end' | 'center' | 'stretch' | 'baseline'>}
* @return {Object}
*/
function flexAlignItems (v = 'initial') {
const map = {
start: 'flex-start',
end: 'flex-end',
center: 'center',
stretch: 'stretch',
baseline: 'baseline'
}
return { alignItems: map[v] || v }
}
/**
*
* Define how the browser distributes space between and around content items
* along the Cross Axis (y-axis for fex-direction: row;) of a flexbox container (when there is extra space).
* https://developer.mozilla.org/en-US/docs/Web/CSS/align-content
*
* Has no effect when there is only one line / row of flex items.
*
* @param v {String<'start' | 'end' | 'center' | 'stretch' | 'between' | 'around'>}
* @return {{alignContent: String}}
*/
function flexAlignContent (v = 'initial') {
const map = {
start: 'flex-start',
end: 'flex-end',
center: 'center',
stretch: 'stretch',
between: 'space-between',
around: 'space-around',
evenly: 'space-evenly'
}
return { alignContent: map[v] || v }
}
/* Applies to flex items
========================================================================== */
/**
*
* Aligns flex items of the current flex line, overriding the align-items value.
* If any of the item's cross-axis margin is set to auto, then align-self is ignored.
* https://developer.mozilla.org/en-US/docs/Web/CSS/align-self
*
* @example
* flexAlignSelf('start') // { { alignSelf: 'flex-start' }}
*
* @param [v='initial'] {String<'start' | 'end' | 'center' | 'stretch' | 'baseline' | 'auto'>}
* @return {{alignSelf: string}}
*/
function flexAlignSelf (v = 'initial') {
const map = {
start: 'flex-start',
end: 'flex-end',
center: 'center',
stretch: 'stretch',
baseline: 'baseline',
auto: 'auto'
}
return { alignSelf: map[v] || v }
}
/**
*
* Change flex item order without editing underlying HTML
*
* @param n {Number | String} - e. g. 1, -1, 999, 'first, 'last', 'none' or 0
* @return {{order: String | Number}}
*/
function flexOrder (n = 'initial') {
const map = {
first: -1,
last: 1,
none: 0,
initial: 'initial',
inherit: 'inherit',
unset: 'unset'
}
return { order: isNaN(Number.parseInt(n, 10)) ? map[n] || 'initial' : n }
}
/**
*
* Specify the flex grow factor, which determines how much the flex item will
* grow relative to the rest of the flex items in the flex container.
*
* Supports Number or Sring (global values) proportions
*
* 1. Provide all values to avoid IE10 bug with shorthand flex
* - http://git.io/vllC7
*
* Use `0%` to avoid bug in IE10/11 with unitless flex basis. Using this
* instead of `auto` as this matches what the default would be with `flex`
* shorthand - http://git.io/vllWx
*
* @param n = {Number}
* @return {{flexGrow: string, flexShrink: string, flexBasis: string}}
*/
function flexGrow (n) {
return {
flexGrow: isNaN(Number.parseFloat(n)) ? '0' : n,
flexShrink: '1',
flexBasis: '0%'
} /* 1 */
}
/**
*
* Specify the flex shrink factor, which determines how much the flex item will
* shrink relative to the rest of the flex items in the flex container.
*
* @param v
* @return {Object}
*/
// redundant
// function flexShrink (v = 'initial') {
// const n = parseInt(v, 10)
// return b({ __style: { flexShrink: isNaN(n) ? 'initial' : n } })
// }
/**
*
* Align item in flex container with `auto` margins
* http://www.w3.org/TR/css-flexbox-1/#auto-margins
*
* valid params are 'all', 'top', 'right', 'bottom', 'left'
*
* @example
* flexExpand('all') // { margin: 'auto' }
* flexExpand('right') // { marginRight: 'auto' }
*
* @param [direction='auto'] {String}
* @return {{margin: string | marginTop: string | marginRight: string | marginBottom: string | marginLeft: string}}
*/
function flexExpand (direction = 'all') {
const resMap = {
all: { margin: 'auto' },
top: { marginTop: 'auto' },
right: { marginRight: 'auto' },
bottom: { marginBottom: 'auto' },
left: { marginLeft: 'auto' }
}
return resMap[direction] || { margin: 'initial' }
}
/**
*
* set the initial main size of a flex item.
* It sets the size of the content box unless otherwise set with box-sizing.
*
* @param [v = 'initial'] {String | Number} -
* @return {Object}
*/
// redundant
// function flexBasis (v = 'initial') {
// b({ __style: { flexBasis: v } })
// }
/*
* SHORTHAND
*
* Declares all values instead of keywords like 'initial' to work around IE10
* https://www.w3.org/TR/css-flexbox-1/#flex-common
*
* 1. Fixes issue in IE 10 where flex-basis is ignored - https://git.io/vllMt
* This ensures it overrides flex-basis set in other utilities.
*/
/**
*
* Sizes flex item based on the width/height properties
*
* @type {{flex: string, flexBasis: string}}
*/
const flexInitial = {
flex: '0 1 auto',
flexBasis: 'auto' /* 1 */
}
/**
*
* Sizes the item based on the width/height properties, but makes them fully
* flexible, so that they absorb any free space along the main axis.
*
* @type {{flex: string, flexBasis: string}}
*/
const flexAuto = {
flex: '1 1 auto',
flexBasis: 'auto' /* 1 */
}
/**
*
* Sizes the item according to the width/height properties, but makes the flex
* item fully inflexible. Similar to initial, except that flex items are
* not allowed to shrink, even in overflow situations.
*
* @type {{flex: string, flexBasis: string}}
*/
const flexNone = {
flex: '0 0 auto',
flexBasis: 'auto' /* 1 */
}
/* FLOATS
cookbook/layout/clearFix.md
cookbook/layout/floats.md */
/**
* Contain floats (micro clearfix)
*
* Make an element expand to contain floated children.
* Uses pseudo-elements (micro clearfix).
*
* 1. The space content is one way to avoid an Opera bug when the
* `contenteditable` attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of the
* element.
* 2. The use of `table` rather than `block` is only necessary if using
* `:before` to contain the top-margins of child elements.
*
* @type {{'::before': {content: string, display: string}, '::after': {content: string, display: string, clear: string}}}
*/
const clearfix = {
'::before': { content: '" "', display: 'table' },
'::after': { content: '" "', display: 'table', clear: 'both' }
}
/**
* New block formatting context
*
* This affords some useful properties to the element. It won't wrap under
* floats. Will also contain any floated children.
* N.B. This will clip overflow. Use the alternative method below if this is
* problematic.
*
* @type {{overflow: string}}
*/
const nbfc = { overflow: 'hidden' }
/**
* New block formatting context (alternative)
*
* Alternative method when overflow must not be clipped.
*
* 1. Create a new block formatting context.
* 2. Avoid shrink-wrap behaviour of table-cell.
*
* N.B. This breaks down in some browsers when elements within this element
* exceed its width.
*
* @type {{display: string, width: string}}
*/
const nbfcAlt = {
display: 'table-cell', /* 1 */
width: '10000px' /* 2 */
}
/* HOVER EFFECTS
cookbook/themed/links.md
cookbook/themed/hover-effects.md */
const _linkTransIn = 'color .15s ease-in'
const _linkHover = { transition: _linkTransIn }
/**
* Base preset for adding subtile hover animations to links & buttons
* Mix and match with hover FXs to generate a wide variety of link styles
*
* @example
* let A = b.link.dimHover
* let B = b.link.growHover
*
* @type {{textDecoration: string, transition: string, ':link': {transition: string}, ':visited': {transition: string}, ':hover': {transition: string}, ':active': {transition: string}, ':focus': {transition: string, outline: string}}}
*/
const link = {
textDecoration: 'none',
transition: _linkTransIn,
':link': _linkHover,
':visited': _linkHover,
':hover': _linkHover,
':active': _linkHover,
':focus': {
transition: _linkTransIn,
outline: '1px dotted currentcolor'
}
}
const _dimTransIn = 'opacity .15s ease-in'
const _dimHover = {
opacity: '.5',
transition: _dimTransIn
}
/**
*
* Dim element on hover by adding the dim helper.
*
* @type {{opacity: string, transition: string, ':hover': {opacity: string, transition: string}, ':focus': {opacity: string, transition: string}, ':active': {opacity: string, transition: string}}}
*/
const hoverDim = {
opacity: '1',
transition: _dimTransIn,
':hover': _dimHover,
':focus': _dimHover,
':active': {
opacity: '.8',
transition: 'opacity .15s ease-out'
}
}
const _glowTransIn = _dimTransIn // 'opacity .15s ease-in'
const _glowHover = {
opacity: '1',
transition: _glowTransIn
}
/**
*
* Animate opacity to 100% on hover by adding the glow helper.
* via http://tachyons.io/docs/themes/hovers/
*
* @type {{transition: string, ':hover': {opacity: string, transition: string}, ':focus': {opacity: string, transition: string}}}
*/
const hoverGlow = {
transition: _glowTransIn,
':hover': _glowHover,
':focus': _glowHover
}
const _underlineHover = { textDecoration: 'underline' }
/**
* Underline an element on hover.
* Works well on links in combination with `link' preset
*
* @example
* b.link.underlineHover.color('pink')
*
* @type {{':hover': {textDecoration: string}, ':focus': {textDecoration: string}}}
*/
const hoverUnderline = {
':hover': _underlineHover,
':focus': _underlineHover
}
const _growHover = { transform: 'scale(1.05)' }
const _growLargeHover = { transform: 'scale(1.2)' }
/**
*
* Grow an element on hover
*
* Can be combined with overflow-hidden to make background images grow on hover
* even if you are using background-size: cover
*
* @type {{'-moz-osx-font-smoothing': string, backfaceVisibility: string, transform: string, transition: string, ':hover': {transform: string}, ':focus': {transform: string}, ':active': {transform: string}}}
*/
const hoverGrow = {
'-moz-osx-font-smoothing': 'grayscale',
backfaceVisibility: 'hidden',
transform: 'translateZ(0)',
transition: 'transform 0.25s ease-out',
':hover': _growHover,
':focus': _growHover,
':active': { transform: 'scale(.90)' }
}
const hoverGrowLarge = {
'-moz-osx-font-smoothing': 'grayscale',
backfaceVisibility: 'hidden',
transform: 'translateZ(0)',
transition: 'transform 0.25s ease-out',
':hover': _growLargeHover,
':focus': _growLargeHover,
':active': { transform: 'scale(.95)' }
}
/**
*
* Add pointer on hover
*
* @type {{':hover': {cursor: string}}}
*/
const hoverPointer = { ':hover': { cursor: 'pointer' } }
/**
*
* Reveal box-shadow on hover.
*
* Performant box-shadow animation pattern from
* http://tobiasahlin.com/blog/how-to-animate-box-shadow/
*
* @param [shadow='0px 5px 15px rgba(0, 0, 0, 0.3)'] {String}
* @param [transition='all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1)'] {String}
* @return {{
* position: string,
* transition: string,
* '::after': {
* content: string,
* boxShadow: string,
* borderRadius: string,
* opacity: string,
* position: string,
* top: string,
* left: string,
* width: string,
* height: string,
* zIndex: string,
* transition: string
* },
* ':hover': {
* '::after': { opacity: string}
* },
* ':focus': {
* '::after': { opacity: string }
* }
* }}
*/
function hoverShadow (
shadow = '0px 5px 15px rgba(0, 0, 0, 0.3)',
transition = 'all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1)'
) {
return {
// cursor: 'pointer',
position: 'relative',
transition, // 'all 0.5s cubic-bezier(0.165, 0.84, 0.44, 1)',
'::after': {
content: '""',
boxShadow: shadow, // '0px 0px 16px 2px rgba(0, 0, 0, .2)',
borderRadius: 'inherit',
opacity: '0',
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
zIndex: '-1',
transition // 'opacity 0.5s cubic-bezier(0.165, 0.84, 0.44, 1)'
},
':hover': { '::after': { opacity: '1' } },
':focus': { '::after': { opacity: '1' } }
}
}
const _bgAnimateTrans = 'background-color .15s ease-in-out'
const _bgAnimateHover = { transition: _bgAnimateTrans }
/**
* add smooth transition effect to an element background on hover
*
* Combine with hoverBg and hoberColor presets for
* many different transition possibilities.
*
* @type {{transition: string, ':hover': {transition: string}, ':focus': {transition: string}}}
*/
const bgAnimate = {
transition: _bgAnimateTrans,
':hover': _bgAnimateHover,
':focus': _bgAnimateHover
}
/* Skin pseudo
cookbook/themed/skins-pseudo.md */
/**
* Customize the background color of an element when
* it is focused or hovered over.
*
* @param color {String}
* @return {{} | {':hover': { backgroundColor: color }, ':focus': { backgroundColor: color }}}
*/
function hoverBg (color) {
const hvrbg = { backgroundColor: color }
return color ? { ':hover': hvrbg, ':focus': hvrbg } : {}
}
/**
* Customize the color of an element when
* it is focused or hovered over.
*
* @param color {String}
* @return {{} | {':hover': { color }, ':focus': { color }}}
*/
function hoverColor (color) {
const hvrcol = { color }
return color ? { ':hover': hvrcol, ':focus': hvrcol } : {}
}
/* SIZE
cookbook/layout/widths.md
cookbook/layout/heights.md */
/**
*
* @type {{flex: string, flexBasis: string}}
*/
const fill = {
flex: '1 1 0%',
flexBasis: '0%'
}
/**
*
* @type {{flex: string, flexBasis: string}}
*/
const fillAlt = {
flex: '1 1 auto',
flexBasis: 'auto'
}
/**
*
* @type {{flexBasis: string}}
*/
const fit = { flexBasis: 'auto' }
/**
*
* @type {{width: string}}
*/
const full = { width: '100%' }
/**
* set css width and height attributes of an element.
*
* By default, defines the size of the content area.
* If box-sizing is set to border-box, however, it instead
* determines the height of the border area.
*
* Automatically convert values between 0 and 1 to % and values > 1 to px
*
* @example
* setSize('150px', '50vh')) // { width: '150px', width: '50vh' }
*
* @param width {String | Number} - | | 'auto'
* @param height {String | Number } - | | 'auto'
* @return {{ width, height? }}
*/
function setSize (width, height) {
const h = height < 1 && height > 0 ? height * 100 + '%' : height
const w = width < 1 && width > 0 ? width * 100 + '%' : width
const wMap = {
fit: height ? { flexBasis: 'auto', height: h } : fit,
fill: height ? { flex: '1 1 0%', flexBasis: '0%', height: h } : fill,
fillAlt: height ? { flex: '1 1 auto', flexBasis: 'auto', height: h } : fill,
full: height ? { width: '100%', height: h } : { width: '100%' }
}
if (!width && !height) return { width: 'initial', height: 'initial' }
else return wMap[width] || ((width && height) ? { width: w, height: h } : width ? { width: w } : { height: h })
}
/* VERTICAL ALIGN
cookbook/typography/vertical-align.md */
/**
*
* Set vertical alignment of an inline or table-cell box.
*
* @param [v = 'initial'] {String} - 'baseline' | 'sub' | 'super' | 'text-top' | 'text-bottom' | 'middle' | 'top' | 'bottom' |
* @return {{verticalAlign: string}}
*/
function align (v = 'initial') {
return { verticalAlign: v }
}
/* WORD BRAKING
cookbook/typography/word-breaking.md */
/**
* Word breaking
*
* Break strings when their length exceeds the width of their container.
*
* @type {{overflowWrap: string}}
*/
const textBreak = {
// wordWrap: 'break-word',
overflowWrap: 'break-word'
}
/* TEXT ALIGNMENT
cookbook/typography/text-align.md */
/**
* Horizontal text alignment
*
* @type {{textAlign: string}}
*/
const textCenter = { textAlign: 'center' }
const textLeft = { textAlign: 'left' }
const textRight = { textAlign: 'right' }
const textJustify = { textAlign: 'justify' }
// const textInheritColor = {
// color: 'inherit'
// }
/* TEXT Kerning
cookbook/typography/text-kerning.md */
/**
* Enables font kerning in all browsers.
* http://blog.typekit.com/2014/02/05/kerning-on-the-web/
*
* 1. Chrome (not Windows), Firefox, IE 10+
* 2. Safari 7 and future browsers
* 3. Chrome (not Windows), Firefox, Safari 6+, iOS, Android
*/
const textKern = {
fontFeatureSettings: '"kern" 1', /* 1 */
fontKerning: 'normal', /* 2 */
textRendering: 'optimizeLegibility' /* 3 */
}
/* WHITE SPACE
cookbook/typography/white-space.md */
/**
* Control whitespace wrapping
* https://developer.mozilla.org/en-US/docs/Web/CSS/white-space
*
* @type {{whiteSpace: string}}
*/
const textNoWrap = { whiteSpace: 'nowrap' }
// const textPre = { whiteSpace: 'pre' }
// const textPreWrap = { whiteSpace: 'pre-wrap' }
// const textPreLine = { whiteSpace: 'pre-line' }
/* MEASURE
cookbook/typography/measure.md */
/**
* Limit element width to ~66 characters
*
* @type {{maxWidth: string}}
*/
const textMeasure = { maxWidth: '30em' }
/**
* Limit element width to ~80 characters
*
* @type {{maxWidth: string}}
*/
const textMeasureWide = { maxWidth: '34em' }
/**
* Limit element width to to ~45 characters
*
* @type {{maxWidth: string}}
*/
const textMeasureNarrow = { maxWidth: '20em' }
/**
* Book paragraph style - paragraphs are indented with no vertical spacing.
*
* @type {{textIndent: string, marginTop: string, marginBottom: string}}
*/
const textIndent = {
textIndent: '1em',
marginTop: '0',
marginBottom: '0'
}
/* TEXT TRANSFORM
/cookbook/typography/text-decoration
/cookbook/typography/text-transform */
/**
* Specifies how to capitalize an element's text.
*
* @type {{textTransform: string}}
*/
const textCaps = { textTransform: 'capitalize' }
const textLowercase = { textTransform: 'lowercase' }
const textUppercase = { textTransform: 'uppercase' }
const textNoTransform = { textTransform: 'none', fontVariantCaps: 'normal' }
/**
* Control the use of alternate glyphs for capital letters
*
* @type {{fontVariantCaps: string}}
*/
const textSmallCaps = { fontVariantCaps: 'small-caps' }
const textSmallCapsAll = { fontVariantCaps: 'all-small-caps' }
const textTitlingCaps = { fontVariantCaps: 'titling-caps' }
/* TEXT DECORATION
/cookbook/typography/text-decoration.md */
/**
* Set the appearance of decorative lines on text.
*
* The text-decoration CSS property is a shorthand
* for text-decoration-line, text-decoration-color,
* and text-decoration-style.
*
* @type {{textDecoration: string}}
*/
const textStrikethrough = { textDecoration: 'line-through' }
const textUnderline = { textDecoration: 'underline' }
const textNoUnderline = { textDecoration: 'none' }
/**
* Text truncation
*
* Prevent text from wrapping onto multiple lines, and truncate with an
* ellipsis.
*
* 1. Ensure that the node has a maximum width after which truncation can
* occur.
* 2. Fix for IE 8/9 if `word-wrap: break-word` is in effect on ancestor
* nodes.
*
* @type {{maxWidth: string, overflow: string, textOverflow: string, whiteSpace: string, wordWrap: string}}
*/
const textTruncate = {
maxWidth: '100%', /* 1 */
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
wordWrap: 'normal' /* 2 */
}
/* TEXT TRACKING / LETTER SPACING
cookbook/typography/letter-spacing.md */
/**
* Adjust letter-spacing for uppercase text to improve readability.
*
* @example
* let txt = b`
* fontSize: 28px;
* text-transform: uppercase;`
* .textTrackedTight
*
* @type {{letterSpacing: string}}
*/
const textTracked = { letterSpacing: '.1em' }
const textTrackedTight = { letterSpacing: '-.05em' }
const textTrackedMega = { letterSpacing: '.25em' }
/* TEXT LINEHEIGHT / LEADING
cookbook/typography/letter-spacing.md */
const lineHeightSolid = { lineHeight: '1' }
const lineHeightTitle = { lineHeight: '1.25' }
const lineHeightCopy = { lineHeight: '1.5' }
/**
*
* @param b {Function}
* @param [theme = {}] {Object}
* @return {Object}
*/
export function createBssHelpers (b, theme = {}) {
/* MEDIA QUERIES
cookbook/media-queries.md */
const breakpoints = theme.breakpoints || [ '30em', '60em' ]
/**
* Not-small screen
*
* @param style
* @return {Object}
*/
function notSmall (style) {
return b({
__style: { [ '@media screen and (min-width: ' + breakpoints[0] + ')' ]: b(style).__style }
})
}
/**
* Medium screen
*
* @param style
* @return {Object}
*/
function medium (style) {
return b({
__style: {
[ '@media screen and (min-width: ' + breakpoints[0] + ') and (max-width: ' + breakpoints[1] + ')' ]:
b(style).__style
}
})
}
/**
* Large screen
*
* @param style
* @return {Object}
*/
function large (style) {
return b({
__style: { [ '@media screen and (min-width: ' + breakpoints[1] + ')' ]: b(style).__style }
})
}
/**
*
* Hide child & reveal on hover:
*
* Put the hideChild helper on a parent element and any nested element with the
* .child class will be hidden and displayed on hover or focus.
*
* @type {Object}
*/
const _hideChildTransIn = _dimTransIn // 'opacity .15s ease-in'
const _hideChildeHover = {
opacity: '1',
transition: _hideChildTransIn
}
const hideChild = b
.$nest('.child', { __style: { opacity: 0, transition: _hideChildTransIn } })
.$nest(':hover .child', b({ __style: _hideChildeHover }))
.$nest(':focus .child', b({ __style: _hideChildeHover }))
.$nest(':active .child', b({ __style: _hideChildeHover }))
return {
// media queries
'$notSmall': notSmall,
'$medium': medium,
'$large': large,
// background-size
bgCover: b({ __style: bgCover }),
bgContain: b({ __style: bgContain }),
// borderbox
borderBox: b({ __style: borderBox }),
// borders
setBorder: (...values) =>
b({ __style: setBorder(...values) }),
setBorderStyle: (...values) =>
b({ __style: setBorderStyle(...values) }),
setBorderColor: (...values) =>
b({ __style: setBorderColor(...values) }),
setBorderWidth: (...values) =>
b({ __style: setBorderWidth(...values) }),
// border-radii
setBorderRadius: function (...values) {
return b({ __style: setBorderRadius(...values) })
},
// display
block: b({ __style: block }),
hidden: b({ __style: hidden }),
hiddenVisually: b({ __style: hiddenVisually }),
inline: b({ __style: inline }),
inlineBlock: b({ __style: inlineBlock }),
table: b({ __style: table }),
tableRow: b({ __style: tableRow }),
tableRowGroup: b({ __style: tableRowGroup }),
tableCell: b({ __style: tableCell }),
tableColumn: b({ __style: tableColumn }),
tableColumnGroup: b({ __style: tableColumnGroup }),
// flexbox
flexBlock: b({ __style: flexBlock }),
flexInline: b({ __style: flexInline }),
flexRow: b({ __style: flexRow }),
flexRowReverse: b({ __style: flexRowReverse }),
flexCol: b({ __style: flexCol }),
flexColReverse: b({ __style: flexColReverse }),
flexNoWrap: b({ __style: flexNoWrap }),
flexDoWrap: b({ __style: flexDoWrap }),
flexWrapReverse: b({ __style: flexWrapReverse }),
flexJustify: value =>
b({ __style: flexJustify(value) }),
flexAlignItems: value =>
b({ __style: flexAlignItems(value) }),
flexAlignContent: value =>
b({ __style: flexAlignContent(value) }),
flexAlignSelf: value =>
b({ __style: flexAlignSelf(value) }),
flexOrder: value =>
b({ __style: flexOrder(value) }),
flexGrow: num =>
b({ __style: flexGrow(num) }),
// flexShrink, // note: is redundant
flexExpand: direction =>
b({ __style: flexExpand(direction) }),
// flexBasis, // note: is redundant
flexInitial: b({ __style: flexInitial }),
flexAuto: b({ __style: flexAuto }),
flexNone: b({ __style: flexNone }),
// floats
clearfix: b({ __style: clearfix }),
cf: b({ __style: clearfix }),
nbfc: b({ __style: nbfc }),
nbfcAlt: b({ __style: nbfcAlt }),
// hover-fx
link: b({ __style: link }),
hoverDim: b({ __style: hoverDim }),
hoverGlow: b({ __style: hoverGlow }),
hideChild: b({ __style: hideChild }),
hoverUnderline: b({ __style: hoverUnderline }),
hoverGrow: b({ __style: hoverGrow }),
hoverGrowLarge: b({ __style: hoverGrowLarge }),
hoverPointer: b({ __style: hoverPointer }),
hoverShadow: (shadow, transition) =>
b({ __style: hoverShadow(shadow, transition) }),
bgAnimate,
// skins-pseudo
hoverBg: color =>
b({ __style: hoverBg(color) }),
hoverColor: color =>
b({ __style: hoverColor(color) }),
// size TODO merge fil, fit full into sizeFn
fill: b({ __style: fill }),
fillAlt: b({ __style: fillAlt }),
fit: b({ __style: fit }),
full: b({ __style: full }),
size: (width, height) => b({ __style: setSize(width, height) }),
// word-breaking
textBreak: b({ __style: textBreak }),
// horizontal-text-alignment
textCenter: b({ __style: textCenter }),
textLeft: b({ __style: textLeft }),
textRight: b({ __style: textRight }),
textJustify: b({ __style: textJustify }),
// textInheritColor: b({ __style: textInheritColor }),
// text-kerning
textKern: b({ __style: textKern }),
// text-white-space
textNoWrap: b({ __style: textNoWrap }),
// textPre: b({ __style: textPre }),
// textPreWrap: b({ __style: textPreWrap }),
// textPreLine: b({ __style: textPreLine }),
// text-measure
textMeasure: b({ __style: textMeasure }),
textMeasureWide: b({ __style: textMeasureWide }),
textMeasureNarrow: b({ __style: textMeasureNarrow }),
textIndent: b({ __style: textIndent }),
textTruncate: b({ __style: textTruncate }),
// text-decoration
textStrikethrough: b({ _style: textStrikethrough }),
textUnderline: b({ __style: textUnderline }),
textNoUnderline: b({ __style: textNoUnderline }),
// text-transform
textNoTransform: b({ __style: textNoTransform }),
textCaps: b({ __style: textCaps }),
textLowercase: b({ __style: textLowercase }),
textUppercase: b({ __style: textUppercase }),
textTitlingCaps: b({ __style: textTitlingCaps }),
textSmallCaps: b({ __style: textSmallCaps }),
textSmallCapsAll: b({ __style: textSmallCapsAll }),
// text-letter-spacing
textTracked: b({ __style: textTracked }),
textTrackedTight: b({ __style: textTrackedTight }),
textTrackedMega: b({ __style: textTrackedMega }),
// line-height
lineHeightSolid: b({ __style: lineHeightSolid }),
lineHeightTitle: b({ __style: lineHeightTitle }),
lineHeightCopy: b({ __style: lineHeightCopy }),
// form reset
reset: function (value) {
return b({ __style: reset(value) })
},
// layout util
center: b({ __style: center }),
// table
collapse: b({ __style: collapse }),
striped: (color) => b({ __style: striped(color) }),
// vertical-align
align: value =>
b({ __style: align(value) })
}
}