Add first class support for extending the default theme

This commit is contained in:
Adam Wathan 2019-02-13 09:01:49 -05:00
parent acee88312a
commit 8ff2b59096
2 changed files with 252 additions and 5 deletions

View file

@ -430,3 +430,228 @@ test('functions in the user theme section are lazily evaluated', () => {
},
})
})
test('theme values in the extend section extend the existing theme', () => {
const userConfig = {
theme: {
extend: {
opacity: {
'25': '25',
'75': '.75',
},
backgroundColors: {
customBackground: '#bada55',
},
}
},
}
const defaultConfig = {
prefix: '-',
important: false,
separator: ':',
theme: {
colors: {
cyan: 'cyan',
magenta: 'magenta',
yellow: 'yellow',
},
opacity: {
'0': '0',
'50': '.5',
'100': '1',
},
backgroundColors: ({ colors }) => colors,
},
variants: {
backgroundColors: ['responsive', 'hover', 'focus'],
opacity: ['responsive', 'hover', 'focus'],
},
}
const result = resolveConfig([userConfig, defaultConfig])
expect(result).toEqual({
prefix: '-',
important: false,
separator: ':',
theme: {
colors: {
cyan: 'cyan',
magenta: 'magenta',
yellow: 'yellow',
},
opacity: {
'0': '0',
'50': '.5',
'100': '1',
'25': '25',
'75': '.75',
},
backgroundColors: {
cyan: 'cyan',
magenta: 'magenta',
yellow: 'yellow',
customBackground: '#bada55',
},
},
variants: {
backgroundColors: ['responsive', 'hover', 'focus'],
opacity: ['responsive', 'hover', 'focus'],
},
})
})
test('theme values in the extend section extend the user theme', () => {
const userConfig = {
theme: {
opacity: {
'0': '0',
'20': '.2',
'40': '.4',
},
height: theme => theme.width,
extend: {
opacity: {
'60': '.6',
'80': '.8',
'100': '1',
},
height: {
customHeight: '500vh',
},
}
},
}
const defaultConfig = {
prefix: '-',
important: false,
separator: ':',
theme: {
opacity: {
'0': '0',
'50': '.5',
'100': '1',
},
height: {
'0': 0,
'full': '100%',
},
width: {
'0': 0,
'1': '.25rem',
'2': '.5rem',
'3': '.75rem',
'4': '1rem',
},
},
variants: {
opacity: ['responsive', 'hover', 'focus'],
height: ['responsive'],
width: ['responsive'],
},
}
const result = resolveConfig([userConfig, defaultConfig])
expect(result).toEqual({
prefix: '-',
important: false,
separator: ':',
theme: {
opacity: {
'0': '0',
'20': '.2',
'40': '.4',
'60': '.6',
'80': '.8',
'100': '1',
},
height: {
'0': 0,
'1': '.25rem',
'2': '.5rem',
'3': '.75rem',
'4': '1rem',
customHeight: '500vh',
},
width: {
'0': 0,
'1': '.25rem',
'2': '.5rem',
'3': '.75rem',
'4': '1rem',
},
},
variants: {
opacity: ['responsive', 'hover', 'focus'],
height: ['responsive'],
width: ['responsive'],
},
})
})
test('theme values in the extend section can extend values that are depended on lazily', () => {
const userConfig = {
theme: {
extend: {
colors: {
red: 'red',
green: 'green',
blue: 'blue',
},
backgroundColors: {
customBackground: '#bada55',
},
}
},
}
const defaultConfig = {
prefix: '-',
important: false,
separator: ':',
theme: {
colors: {
cyan: 'cyan',
magenta: 'magenta',
yellow: 'yellow',
},
backgroundColors: ({ colors }) => colors,
},
variants: {
backgroundColors: ['responsive', 'hover', 'focus'],
},
}
const result = resolveConfig([userConfig, defaultConfig])
expect(result).toEqual({
prefix: '-',
important: false,
separator: ':',
theme: {
colors: {
cyan: 'cyan',
magenta: 'magenta',
yellow: 'yellow',
red: 'red',
green: 'green',
blue: 'blue',
},
backgroundColors: {
cyan: 'cyan',
magenta: 'magenta',
yellow: 'yellow',
red: 'red',
green: 'green',
blue: 'blue',
customBackground: '#bada55',
},
},
variants: {
backgroundColors: ['responsive', 'hover', 'focus'],
},
})
})

View file

@ -1,19 +1,41 @@
import _ from 'lodash'
import mergeWith from 'lodash/mergeWith'
import isFunction from 'lodash/isFunction'
import defaults from 'lodash/defaults'
import map from 'lodash/map'
import get from 'lodash/get'
function resolveFunctionKeys(object) {
return Object.keys(object).reduce((resolved, key) => {
return {
...resolved,
[key]: _.isFunction(object[key]) ? object[key](object) : object[key],
[key]: isFunction(object[key]) ? object[key](object) : object[key],
}
}, {})
}
function without(object, key) {
return (({[key]: _, ...rest }) => rest)(object)
}
function mergeExtensions(theme) {
return mergeWith({}, without(theme, 'extend'), theme.extend, (_, value, key) => {
return isFunction(theme[key])
? mergedTheme => ({
...theme[key](mergedTheme),
...get(theme.extend, key, {})
})
: {
...theme[key],
...get(theme.extend, key, {}),
}
})
}
export default function(configs) {
return _.defaults(
return defaults(
{
theme: resolveFunctionKeys(_.defaults(..._.map(configs, 'theme'))),
variants: _.defaults(..._.map(configs, 'variants')),
theme: resolveFunctionKeys(mergeExtensions(defaults(...map(configs, 'theme')))),
variants: defaults(...map(configs, 'variants')),
},
...configs
)