Skip to content

Commit 11af870

Browse files
authored
Support extend in variants config (#2651)
* WIP * It's alive * Pull default variant order from config
1 parent 82b7be6 commit 11af870

File tree

4 files changed

+215
-38
lines changed

4 files changed

+215
-38
lines changed

__tests__/resolveConfig.test.js

+127
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,133 @@ test('user theme extensions take precedence over plugin theme extensions with th
17381738
})
17391739
})
17401740

1741+
test('variants can be extended', () => {
1742+
const userConfig = {
1743+
variants: {
1744+
borderColor: ({ after }) => after(['group-focus'], 'hover'),
1745+
extend: {
1746+
backgroundColor: ['active', 'disabled', 'group-hover'],
1747+
},
1748+
},
1749+
}
1750+
1751+
const otherConfig = {
1752+
variants: {
1753+
extend: {
1754+
textColor: ['hover', 'focus-within'],
1755+
},
1756+
},
1757+
}
1758+
1759+
const defaultConfig = {
1760+
prefix: '',
1761+
important: false,
1762+
separator: ':',
1763+
theme: {},
1764+
variants: {
1765+
borderColor: ['hover', 'focus'],
1766+
backgroundColor: ['responsive', 'hover', 'focus'],
1767+
textColor: ['responsive', 'focus'],
1768+
},
1769+
}
1770+
1771+
const result = resolveConfig([userConfig, otherConfig, defaultConfig])
1772+
1773+
expect(result).toMatchObject({
1774+
variants: {
1775+
borderColor: ['hover', 'group-focus', 'focus'],
1776+
backgroundColor: ['responsive', 'group-hover', 'hover', 'focus', 'active', 'disabled'],
1777+
textColor: ['responsive', 'focus-within', 'hover', 'focus'],
1778+
},
1779+
})
1780+
})
1781+
1782+
test('variant sort order can be customized', () => {
1783+
const userConfig = {
1784+
variantOrder: [
1785+
'disabled',
1786+
'focus',
1787+
'group-hover',
1788+
'focus-within',
1789+
'active',
1790+
'hover',
1791+
'responsive',
1792+
],
1793+
variants: {
1794+
borderColor: ({ after }) => after(['group-focus'], 'hover'),
1795+
extend: {
1796+
backgroundColor: ['active', 'disabled', 'group-hover'],
1797+
},
1798+
},
1799+
}
1800+
1801+
const otherConfig = {
1802+
variants: {
1803+
extend: {
1804+
textColor: ['hover', 'focus-within'],
1805+
},
1806+
},
1807+
}
1808+
1809+
const defaultConfig = {
1810+
prefix: '',
1811+
important: false,
1812+
separator: ':',
1813+
theme: {},
1814+
variants: {
1815+
borderColor: ['hover', 'focus'],
1816+
backgroundColor: ['responsive', 'hover', 'focus'],
1817+
textColor: ['responsive', 'focus'],
1818+
},
1819+
}
1820+
1821+
const result = resolveConfig([userConfig, otherConfig, defaultConfig])
1822+
1823+
expect(result).toMatchObject({
1824+
variants: {
1825+
borderColor: ['hover', 'group-focus', 'focus'],
1826+
backgroundColor: ['disabled', 'focus', 'group-hover', 'active', 'hover', 'responsive'],
1827+
textColor: ['focus', 'focus-within', 'hover', 'responsive'],
1828+
},
1829+
})
1830+
})
1831+
1832+
test('custom variants go to the beginning by default when sort is applied', () => {
1833+
const userConfig = {
1834+
variants: {
1835+
extend: {
1836+
backgroundColor: ['active', 'custom-variant-1', 'group-hover', 'custom-variant-2'],
1837+
},
1838+
},
1839+
}
1840+
1841+
const defaultConfig = {
1842+
prefix: '',
1843+
important: false,
1844+
separator: ':',
1845+
theme: {},
1846+
variants: {
1847+
backgroundColor: ['responsive', 'hover', 'focus'],
1848+
},
1849+
}
1850+
1851+
const result = resolveConfig([userConfig, defaultConfig])
1852+
1853+
expect(result).toMatchObject({
1854+
variants: {
1855+
backgroundColor: [
1856+
'responsive',
1857+
'custom-variant-1',
1858+
'custom-variant-2',
1859+
'group-hover',
1860+
'hover',
1861+
'focus',
1862+
'active',
1863+
],
1864+
},
1865+
})
1866+
})
1867+
17411868
test('variants can be defined as a function', () => {
17421869
const userConfig = {
17431870
variants: {

src/util/resolveConfig.js

+69-37
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import isUndefined from 'lodash/isUndefined'
55
import defaults from 'lodash/defaults'
66
import map from 'lodash/map'
77
import get from 'lodash/get'
8+
import uniq from 'lodash/uniq'
89
import toPath from 'lodash/toPath'
910
import negateValue from './negateValue'
1011
import { corePluginList } from '../corePluginList'
1112
import configurePlugins from './configurePlugins'
13+
import defaultConfig from '../../stubs/defaultConfig.stub'
1214

1315
const configUtils = {
1416
negative(scale) {
@@ -39,31 +41,29 @@ function value(valueToResolve, ...args) {
3941
return isFunction(valueToResolve) ? valueToResolve(...args) : valueToResolve
4042
}
4143

42-
function mergeThemes(themes) {
43-
const theme = (({ extend: _, ...t }) => t)(
44-
themes.reduce((merged, t) => {
45-
return defaults(merged, t)
46-
}, {})
47-
)
44+
function collectExtends(items) {
45+
return items.reduce((merged, { extend }) => {
46+
return mergeWith(merged, extend, (mergedValue, extendValue) => {
47+
if (isUndefined(mergedValue)) {
48+
return [extendValue]
49+
}
4850

51+
if (Array.isArray(mergedValue)) {
52+
return [extendValue, ...mergedValue]
53+
}
54+
55+
return [extendValue, mergedValue]
56+
})
57+
}, {})
58+
}
59+
60+
function mergeThemes(themes) {
4961
return {
50-
...theme,
62+
...themes.reduce((merged, theme) => defaults(merged, theme), {}),
5163

5264
// In order to resolve n config objects, we combine all of their `extend` properties
5365
// into arrays instead of objects so they aren't overridden.
54-
extend: themes.reduce((merged, { extend }) => {
55-
return mergeWith(merged, extend, (mergedValue, extendValue) => {
56-
if (isUndefined(mergedValue)) {
57-
return [extendValue]
58-
}
59-
60-
if (Array.isArray(mergedValue)) {
61-
return [extendValue, ...mergedValue]
62-
}
63-
64-
return [extendValue, mergedValue]
65-
})
66-
}, {}),
66+
extend: collectExtends(themes),
6767
}
6868
}
6969

@@ -130,12 +130,8 @@ function extractPluginConfigs(configs) {
130130
return allConfigs
131131
}
132132

133-
function resolveVariants([firstConfig, ...variantConfigs]) {
134-
if (Array.isArray(firstConfig)) {
135-
return firstConfig
136-
}
137-
138-
return [firstConfig, ...variantConfigs].reverse().reduce((resolved, variants) => {
133+
function mergeVariants(variants) {
134+
const mergedVariants = variants.reduce((resolved, variants) => {
139135
Object.entries(variants || {}).forEach(([plugin, pluginVariants]) => {
140136
if (isFunction(pluginVariants)) {
141137
resolved[plugin] = pluginVariants({
@@ -187,10 +183,39 @@ function resolveVariants([firstConfig, ...variantConfigs]) {
187183

188184
return resolved
189185
}, {})
186+
187+
return {
188+
...mergedVariants,
189+
extend: collectExtends(variants),
190+
}
191+
}
192+
193+
function mergeVariantExtensions({ extend, ...variants }, variantOrder) {
194+
return mergeWith(variants, extend, (variantsValue, extensions) => {
195+
const merged = uniq([...variantsValue, ...extensions].flat())
196+
197+
if (extensions.flat().length === 0) {
198+
return merged
199+
}
200+
201+
return merged.sort((a, z) => variantOrder.indexOf(a) - variantOrder.indexOf(z))
202+
})
203+
}
204+
205+
function resolveVariants([firstConfig, ...variantConfigs], variantOrder) {
206+
// Global variants configuration like `variants: ['hover', 'focus']`
207+
if (Array.isArray(firstConfig)) {
208+
return firstConfig
209+
}
210+
211+
return mergeVariantExtensions(
212+
mergeVariants([firstConfig, ...variantConfigs].reverse()),
213+
variantOrder
214+
)
190215
}
191216

192217
function resolveCorePlugins(corePluginConfigs) {
193-
const result = [...corePluginConfigs].reverse().reduce((resolved, corePluginConfig) => {
218+
const result = [...corePluginConfigs].reduceRight((resolved, corePluginConfig) => {
194219
if (isFunction(corePluginConfig)) {
195220
return corePluginConfig({ corePlugins: resolved })
196221
}
@@ -201,31 +226,38 @@ function resolveCorePlugins(corePluginConfigs) {
201226
}
202227

203228
function resolvePluginLists(pluginLists) {
204-
const result = [...pluginLists].reverse().reduce((resolved, pluginList) => {
229+
const result = [...pluginLists].reduceRight((resolved, pluginList) => {
205230
return [...resolved, ...pluginList]
206231
}, [])
207232

208233
return result
209234
}
210235

211236
export default function resolveConfig(configs) {
212-
const allConfigs = extractPluginConfigs(configs)
237+
const allConfigs = [
238+
...extractPluginConfigs(configs),
239+
{
240+
darkMode: false,
241+
prefix: '',
242+
important: false,
243+
separator: ':',
244+
variantOrder: defaultConfig.variantOrder,
245+
},
246+
]
247+
const { variantOrder } = allConfigs.find((c) => c.variantOrder)
213248

214249
return defaults(
215250
{
216251
theme: resolveFunctionKeys(
217252
mergeExtensions(mergeThemes(map(allConfigs, (t) => get(t, 'theme', {}))))
218253
),
219-
variants: resolveVariants(allConfigs.map((c) => c.variants)),
254+
variants: resolveVariants(
255+
allConfigs.map((c) => get(c, 'variants', {})),
256+
variantOrder
257+
),
220258
corePlugins: resolveCorePlugins(allConfigs.map((c) => c.corePlugins)),
221259
plugins: resolvePluginLists(configs.map((c) => get(c, 'plugins', []))),
222260
},
223-
...allConfigs,
224-
{
225-
darkMode: false,
226-
prefix: '',
227-
important: false,
228-
separator: ':',
229-
}
261+
...allConfigs
230262
)
231263
}

stubs/defaultConfig.stub.js

+16
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,22 @@ module.exports = {
668668
},
669669
},
670670
},
671+
variantOrder: [
672+
'first',
673+
'last',
674+
'odd',
675+
'even',
676+
'visited',
677+
'checked',
678+
'group-hover',
679+
'group-focus',
680+
'focus-within',
681+
'hover',
682+
'focus',
683+
'focus-visible',
684+
'active',
685+
'disabled',
686+
],
671687
variants: {
672688
accessibility: ['responsive', 'focus'],
673689
alignContent: ['responsive'],

stubs/simpleConfig.stub.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module.exports = {
44
theme: {
55
extend: {},
66
},
7-
variants: {},
7+
variants: {
8+
extend: {},
9+
},
810
plugins: [],
911
}

0 commit comments

Comments
 (0)