Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support extend in variants config #2651

Merged
merged 3 commits into from Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
127 changes: 127 additions & 0 deletions __tests__/resolveConfig.test.js
Expand Up @@ -1738,6 +1738,133 @@ test('user theme extensions take precedence over plugin theme extensions with th
})
})

test('variants can be extended', () => {
const userConfig = {
variants: {
borderColor: ({ after }) => after(['group-focus'], 'hover'),
extend: {
backgroundColor: ['active', 'disabled', 'group-hover'],
},
},
}

const otherConfig = {
variants: {
extend: {
textColor: ['hover', 'focus-within'],
},
},
}

const defaultConfig = {
prefix: '',
important: false,
separator: ':',
theme: {},
variants: {
borderColor: ['hover', 'focus'],
backgroundColor: ['responsive', 'hover', 'focus'],
textColor: ['responsive', 'focus'],
},
}

const result = resolveConfig([userConfig, otherConfig, defaultConfig])

expect(result).toMatchObject({
variants: {
borderColor: ['hover', 'group-focus', 'focus'],
backgroundColor: ['responsive', 'group-hover', 'hover', 'focus', 'active', 'disabled'],
textColor: ['responsive', 'focus-within', 'hover', 'focus'],
},
})
})

test('variant sort order can be customized', () => {
const userConfig = {
variantOrder: [
'disabled',
'focus',
'group-hover',
'focus-within',
'active',
'hover',
'responsive',
],
variants: {
borderColor: ({ after }) => after(['group-focus'], 'hover'),
extend: {
backgroundColor: ['active', 'disabled', 'group-hover'],
},
},
}

const otherConfig = {
variants: {
extend: {
textColor: ['hover', 'focus-within'],
},
},
}

const defaultConfig = {
prefix: '',
important: false,
separator: ':',
theme: {},
variants: {
borderColor: ['hover', 'focus'],
backgroundColor: ['responsive', 'hover', 'focus'],
textColor: ['responsive', 'focus'],
},
}

const result = resolveConfig([userConfig, otherConfig, defaultConfig])

expect(result).toMatchObject({
variants: {
borderColor: ['hover', 'group-focus', 'focus'],
backgroundColor: ['disabled', 'focus', 'group-hover', 'active', 'hover', 'responsive'],
textColor: ['focus', 'focus-within', 'hover', 'responsive'],
},
})
})

test('custom variants go to the beginning by default when sort is applied', () => {
const userConfig = {
variants: {
extend: {
backgroundColor: ['active', 'custom-variant-1', 'group-hover', 'custom-variant-2'],
},
},
}

const defaultConfig = {
prefix: '',
important: false,
separator: ':',
theme: {},
variants: {
backgroundColor: ['responsive', 'hover', 'focus'],
},
}

const result = resolveConfig([userConfig, defaultConfig])

expect(result).toMatchObject({
variants: {
backgroundColor: [
'responsive',
'custom-variant-1',
'custom-variant-2',
'group-hover',
'hover',
'focus',
'active',
],
},
})
})

test('variants can be defined as a function', () => {
const userConfig = {
variants: {
Expand Down
106 changes: 69 additions & 37 deletions src/util/resolveConfig.js
Expand Up @@ -5,10 +5,12 @@ import isUndefined from 'lodash/isUndefined'
import defaults from 'lodash/defaults'
import map from 'lodash/map'
import get from 'lodash/get'
import uniq from 'lodash/uniq'
import toPath from 'lodash/toPath'
import negateValue from './negateValue'
import { corePluginList } from '../corePluginList'
import configurePlugins from './configurePlugins'
import defaultConfig from '../../stubs/defaultConfig.stub'

const configUtils = {
negative(scale) {
Expand Down Expand Up @@ -39,31 +41,29 @@ function value(valueToResolve, ...args) {
return isFunction(valueToResolve) ? valueToResolve(...args) : valueToResolve
}

function mergeThemes(themes) {
const theme = (({ extend: _, ...t }) => t)(
themes.reduce((merged, t) => {
return defaults(merged, t)
}, {})
)
function collectExtends(items) {
return items.reduce((merged, { extend }) => {
return mergeWith(merged, extend, (mergedValue, extendValue) => {
if (isUndefined(mergedValue)) {
return [extendValue]
}

if (Array.isArray(mergedValue)) {
return [extendValue, ...mergedValue]
}

return [extendValue, mergedValue]
})
}, {})
}

function mergeThemes(themes) {
return {
...theme,
...themes.reduce((merged, theme) => defaults(merged, theme), {}),

// In order to resolve n config objects, we combine all of their `extend` properties
// into arrays instead of objects so they aren't overridden.
extend: themes.reduce((merged, { extend }) => {
return mergeWith(merged, extend, (mergedValue, extendValue) => {
if (isUndefined(mergedValue)) {
return [extendValue]
}

if (Array.isArray(mergedValue)) {
return [extendValue, ...mergedValue]
}

return [extendValue, mergedValue]
})
}, {}),
extend: collectExtends(themes),
}
}

Expand Down Expand Up @@ -130,12 +130,8 @@ function extractPluginConfigs(configs) {
return allConfigs
}

function resolveVariants([firstConfig, ...variantConfigs]) {
if (Array.isArray(firstConfig)) {
return firstConfig
}

return [firstConfig, ...variantConfigs].reverse().reduce((resolved, variants) => {
function mergeVariants(variants) {
const mergedVariants = variants.reduce((resolved, variants) => {
Object.entries(variants || {}).forEach(([plugin, pluginVariants]) => {
if (isFunction(pluginVariants)) {
resolved[plugin] = pluginVariants({
Expand Down Expand Up @@ -187,10 +183,39 @@ function resolveVariants([firstConfig, ...variantConfigs]) {

return resolved
}, {})

return {
...mergedVariants,
extend: collectExtends(variants),
}
}

function mergeVariantExtensions({ extend, ...variants }, variantOrder) {
return mergeWith(variants, extend, (variantsValue, extensions) => {
const merged = uniq([...variantsValue, ...extensions].flat())

if (extensions.flat().length === 0) {
return merged
}

return merged.sort((a, z) => variantOrder.indexOf(a) - variantOrder.indexOf(z))
})
}

function resolveVariants([firstConfig, ...variantConfigs], variantOrder) {
// Global variants configuration like `variants: ['hover', 'focus']`
if (Array.isArray(firstConfig)) {
return firstConfig
}

return mergeVariantExtensions(
mergeVariants([firstConfig, ...variantConfigs].reverse()),
variantOrder
)
}

function resolveCorePlugins(corePluginConfigs) {
const result = [...corePluginConfigs].reverse().reduce((resolved, corePluginConfig) => {
const result = [...corePluginConfigs].reduceRight((resolved, corePluginConfig) => {
if (isFunction(corePluginConfig)) {
return corePluginConfig({ corePlugins: resolved })
}
Expand All @@ -201,31 +226,38 @@ function resolveCorePlugins(corePluginConfigs) {
}

function resolvePluginLists(pluginLists) {
const result = [...pluginLists].reverse().reduce((resolved, pluginList) => {
const result = [...pluginLists].reduceRight((resolved, pluginList) => {
return [...resolved, ...pluginList]
}, [])

return result
}

export default function resolveConfig(configs) {
const allConfigs = extractPluginConfigs(configs)
const allConfigs = [
...extractPluginConfigs(configs),
{
darkMode: false,
prefix: '',
important: false,
separator: ':',
variantOrder: defaultConfig.variantOrder,
},
]
const { variantOrder } = allConfigs.find((c) => c.variantOrder)

return defaults(
{
theme: resolveFunctionKeys(
mergeExtensions(mergeThemes(map(allConfigs, (t) => get(t, 'theme', {}))))
),
variants: resolveVariants(allConfigs.map((c) => c.variants)),
variants: resolveVariants(
allConfigs.map((c) => get(c, 'variants', {})),
variantOrder
),
corePlugins: resolveCorePlugins(allConfigs.map((c) => c.corePlugins)),
plugins: resolvePluginLists(configs.map((c) => get(c, 'plugins', []))),
},
...allConfigs,
{
darkMode: false,
prefix: '',
important: false,
separator: ':',
}
...allConfigs
)
}
16 changes: 16 additions & 0 deletions stubs/defaultConfig.stub.js
Expand Up @@ -668,6 +668,22 @@ module.exports = {
},
},
},
variantOrder: [
'first',
'last',
'odd',
'even',
'visited',
'checked',
'group-hover',
'group-focus',
'focus-within',
'hover',
'focus',
'focus-visible',
'active',
'disabled',
],
variants: {
accessibility: ['responsive', 'focus'],
alignContent: ['responsive'],
Expand Down
4 changes: 3 additions & 1 deletion stubs/simpleConfig.stub.js
Expand Up @@ -4,6 +4,8 @@ module.exports = {
theme: {
extend: {},
},
variants: {},
variants: {
extend: {},
},
plugins: [],
}