Skip to content

Instantly share code, notes, and snippets.

@agriffis
Created Apr 26, 2021
Embed
What would you like to do?
Custom xstyled re-exports
import * as R from 'ramda'
import scStyled from 'styled-components'
import * as xstyled from '@xstyled/styled-components'
import {
css,
defaultTheme,
Preflight,
style,
ThemeProvider,
useTheme,
} from '@xstyled/styled-components'
import {compose, space, typography} from '@xstyled/system'
//----------------------------------------------------------------------
// system
//----------------------------------------------------------------------
void `
// xstyled v2 released
prop: 'ui',
themeGet: v => ({theme}) => typography({...theme.typography[v], theme}),
cssProperty: R.identity,
// xstyled v2 unreleased
prop: 'ui',
key: 'typography',
cssProperty: v => ({theme}) => typography({...v, theme}),
// xstyled v3 unreleased
prop: 'ui',
key: 'typography',
css: v => ({theme}) => typography({...v, theme}),
`
const ui = style({
prop: 'ui',
themeGet: v => ({theme}) => typography({...theme.typography[v], theme}),
cssProperty: R.identity,
})
const prose = style({
prop: 'prose',
themeGet: v => ({theme}) =>
compose(space, typography)({...theme.typography[v], theme}),
cssProperty: R.identity,
})
const system = compose(ui, prose, xstyled.system)
const sys = props => ({theme}) => system({...props, theme})
//----------------------------------------------------------------------
// shouldForwardProp and attrs
//----------------------------------------------------------------------
const systemSet = new Set(system.meta.props)
const shouldForwardProp = (prop, defaultValidatorFn, elementToBeCreated) => {
if (prop === 'as' || prop === 'theme') {
return false
}
if (typeof prop === 'string' && systemSet.has(prop)) {
return false
}
if (typeof elementToBeCreated === 'string') {
// We must test elementToBeCreated so we can pass through props for <x.div
// as={Component} />. However elementToBeCreated isn't available until
// styled-components 5.2.4 or 6, and in the meantime will be undefined.
// This means that HTML elements could get unwanted props, but ultimately
// this is a bug in the caller, because why are they passing unwanted
// props?
return defaultValidatorFn(prop)
}
return true
}
const partitionProps = R.curry((names, props) =>
R.compose(
names.has
? R.compose(
R.map(ps => R.pick(ps, props)),
R.partition(p => names.has(p)),
R.keys,
)
: R.juxt([R.pick(names), R.omit(names)]),
// Drop undefined values to approximate the effect of destructuring, since
// this function stands in for destructuring in a component.
R.reject(R.identical(undefined)),
)(props),
)
const partitionSystemProps = partitionProps(systemSet)
// .attrs(data('size', 'variant'))
const hoistDataAttrs = names => props => ({
// variant becomes data-variant, etc.
...R.zipObj(
R.map(s => `data-${s}`, names),
R.props(names, props),
),
// Remove the original props with {variant: undefined}
...R.fromPairs(R.map(name => [name], names)),
})
const defaultAttrs = attrs => props => ({...attrs, ...props})
//----------------------------------------------------------------------
// styled
//----------------------------------------------------------------------
const tags = Object.keys(scStyled)
const getCreateStyle = (baseCreateStyle, ...generators) => {
const createStyle = (...args) =>
baseCreateStyle`${css(...args, ...generators)}`
const redo = nextCreateStyle => getCreateStyle(nextCreateStyle, ...generators)
createStyle.attrs = attrs => redo(baseCreateStyle.attrs(attrs))
createStyle.withConfig = config => redo(baseCreateStyle.withConfig(config))
return createStyle
}
const styled = (component, ...generators) =>
getCreateStyle(scStyled(component), ...generators)
tags.forEach(key => {
styled[key] = styled(key, system).withConfig({shouldForwardProp})
})
//----------------------------------------------------------------------
// x
//----------------------------------------------------------------------
const x = {}
tags.forEach(key => {
x[key] = styled[key]``
})
//----------------------------------------------------------------------
// theme getters
//----------------------------------------------------------------------
const assert = (cond, ...rest) => {
console.assert(cond, ...rest)
return cond
}
const themeGetter = theme => (path, def) => {
if (typeof path === 'object' && typeof theme === 'string') {
;[path, theme] = [theme, path]
}
if (
assert(
typeof path === 'string' && typeof theme === 'object',
`themeGetter called with ${typeof path}, ${typeof theme}`,
)
) {
const v = R.pipe(
R.path(path.split('.')),
R.when(R.identical(undefined), R.always(def)),
)(theme)
assert(v !== undefined, 'not found in theme:', path)
return v
}
}
const th = (path, def) => props => themeGetter(props?.theme)(path, def)
const useThemeGetter = prefix => {
const theme = useTheme()
return (path, def) =>
themeGetter(theme)(prefix ? `${prefix}.${path}` : path, def)
}
//----------------------------------------------------------------------
// exports
//----------------------------------------------------------------------
export default styled
export {
css,
hoistDataAttrs,
defaultAttrs,
defaultTheme,
partitionProps,
partitionSystemProps,
Preflight,
shouldForwardProp,
sys,
system,
th,
themeGetter,
ThemeProvider,
useTheme,
useThemeGetter,
x,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment