You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
4.5 KiB

import assign from 'object-assign'
export const merge = (a, b) => {
let result = assign({}, a, b)
for (const key in a) {
if (!a[key] || typeof b[key] !== 'object') continue
assign(result, {
[key]: assign(a[key], b[key]),
})
}
return result
}
// sort object-value responsive styles
const sort = obj => {
const next = {}
Object.keys(obj)
.sort((a, b) => a.localeCompare(b, undefined, {
numeric: true,
sensitivity: 'base',
}))
.forEach(key => {
next[key] = obj[key]
})
return next
}
const defaults = {
breakpoints: [40, 52, 64].map(n => n + 'em'),
}
const createMediaQuery = n => `@media screen and (min-width: ${n})`
const getValue = (n, scale) => get(scale, n, n)
export const get = (obj, key, def, p, undef) => {
key = key && key.split ? key.split('.') : [key]
for (p = 0; p < key.length; p++) {
obj = obj ? obj[key[p]] : undef
}
return obj === undef ? def : obj
}
export const createParser = config => {
const cache = {}
const parse = props => {
let styles = {}
let shouldSort = false
const isCacheDisabled = props.theme && props.theme.disableStyledSystemCache
for (const key in props) {
if (!config[key]) continue
const sx = config[key]
const raw = props[key]
const scale = get(props.theme, sx.scale, sx.defaults)
if (typeof raw === 'object') {
cache.breakpoints =
(!isCacheDisabled && cache.breakpoints) ||
get(props.theme, 'breakpoints', defaults.breakpoints)
if (Array.isArray(raw)) {
cache.media = (!isCacheDisabled && cache.media) || [
null,
...cache.breakpoints.map(createMediaQuery),
]
styles = merge(
styles,
parseResponsiveStyle(cache.media, sx, scale, raw, props)
)
continue
}
if (raw !== null) {
styles = merge(
styles,
parseResponsiveObject(cache.breakpoints, sx, scale, raw, props)
)
shouldSort = true
}
continue
}
assign(styles, sx(raw, scale, props))
}
// sort object-based responsive styles
if (shouldSort) {
styles = sort(styles)
}
return styles
}
parse.config = config
parse.propNames = Object.keys(config)
parse.cache = cache
const keys = Object.keys(config).filter(k => k !== 'config')
if (keys.length > 1) {
keys.forEach(key => {
parse[key] = createParser({ [key]: config[key] })
})
}
return parse
}
const parseResponsiveStyle = (mediaQueries, sx, scale, raw, _props) => {
let styles = {}
raw.slice(0, mediaQueries.length).forEach((value, i) => {
const media = mediaQueries[i]
const style = sx(value, scale, _props)
if (!media) {
assign(styles, style)
} else {
assign(styles, {
[media]: assign({}, styles[media], style),
})
}
})
return styles
}
const parseResponsiveObject = (breakpoints, sx, scale, raw, _props) => {
let styles = {}
for (let key in raw) {
const breakpoint = breakpoints[key]
const value = raw[key]
const style = sx(value, scale, _props)
if (!breakpoint) {
assign(styles, style)
} else {
const media = createMediaQuery(breakpoint)
assign(styles, {
[media]: assign({}, styles[media], style),
})
}
}
return styles
}
export const createStyleFunction = ({
properties,
property,
scale,
transform = getValue,
defaultScale,
}) => {
properties = properties || [property]
const sx = (value, scale, _props) => {
const result = {}
const n = transform(value, scale, _props)
if (n === null) return
properties.forEach(prop => {
result[prop] = n
})
return result
}
sx.scale = scale
sx.defaults = defaultScale
return sx
}
// new v5 API
export const system = (args = {}) => {
const config = {}
Object.keys(args).forEach(key => {
const conf = args[key]
if (conf === true) {
// shortcut definition
config[key] = createStyleFunction({
property: key,
scale: key,
})
return
}
if (typeof conf === 'function') {
config[key] = conf
return
}
config[key] = createStyleFunction(conf)
})
const parser = createParser(config)
return parser
}
export const compose = (...parsers) => {
let config = {}
parsers.forEach(parser => {
if (!parser || !parser.config) return
assign(config, parser.config)
})
const parser = createParser(config)
return parser
}