mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Support defining variants as functions for easier extending (#2309)
* Support defining variants as functions for easier extending * Fix style * Remove commented code * Add 'without' helper to variant function API * Update changelog
This commit is contained in:
parent
ff013c5e9c
commit
476950ce40
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- New `layers` mode for `purge` ([#2288](https://github.com/tailwindlabs/tailwindcss/pull/2288))
|
||||
- New `font-variant-numeric` utilities ([#2305](https://github.com/tailwindlabs/tailwindcss/pull/2305))
|
||||
- New `place-items`, `place-content`, `place-self`, `justify-items`, and `justify-self` utilities ([#2306](https://github.com/tailwindlabs/tailwindcss/pull/2306))
|
||||
- Support configuring variants as functions ([#2309](https://github.com/tailwindlabs/tailwindcss/pull/2309))
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
||||
@ -1736,3 +1736,65 @@ test('user theme extensions take precedence over plugin theme extensions with th
|
||||
plugins: userConfig.plugins,
|
||||
})
|
||||
})
|
||||
|
||||
test('variants can be defined as a function', () => {
|
||||
const userConfig = {
|
||||
variants: {
|
||||
backgroundColor: ({ variants }) => [...variants('backgroundColor'), 'disabled'],
|
||||
padding: ({ before }) => before(['active']),
|
||||
float: ({ before }) => before(['disabled'], 'focus'),
|
||||
margin: ({ before }) => before(['hover'], 'focus'),
|
||||
borderWidth: ({ after }) => after(['active']),
|
||||
backgroundImage: ({ after }) => after(['disabled'], 'hover'),
|
||||
opacity: ({ after }) => after(['hover'], 'focus'),
|
||||
rotate: ({ without }) => without(['hover']),
|
||||
cursor: ({ before, after, without }) =>
|
||||
without(['responsive'], before(['checked'], 'hover', after(['hover'], 'focus'))),
|
||||
},
|
||||
}
|
||||
|
||||
const otherConfig = {
|
||||
variants: {
|
||||
backgroundColor: ({ variants }) => [...variants('backgroundColor'), 'active'],
|
||||
},
|
||||
}
|
||||
|
||||
const defaultConfig = {
|
||||
prefix: '',
|
||||
important: false,
|
||||
separator: ':',
|
||||
theme: {},
|
||||
variants: {
|
||||
backgroundColor: ['responsive', 'hover', 'focus'],
|
||||
padding: ['responsive', 'focus'],
|
||||
float: ['responsive', 'hover', 'focus'],
|
||||
margin: ['responsive'],
|
||||
borderWidth: ['responsive', 'focus'],
|
||||
backgroundImage: ['responsive', 'hover', 'focus'],
|
||||
opacity: ['responsive'],
|
||||
rotate: ['responsive', 'hover', 'focus'],
|
||||
cursor: ['responsive', 'focus'],
|
||||
},
|
||||
}
|
||||
|
||||
const result = resolveConfig([userConfig, otherConfig, defaultConfig])
|
||||
|
||||
expect(result).toEqual({
|
||||
prefix: '',
|
||||
important: false,
|
||||
separator: ':',
|
||||
theme: {},
|
||||
variants: {
|
||||
backgroundColor: ['responsive', 'hover', 'focus', 'active', 'disabled'],
|
||||
padding: ['active', 'responsive', 'focus'],
|
||||
float: ['responsive', 'hover', 'disabled', 'focus'],
|
||||
margin: ['responsive', 'hover'],
|
||||
borderWidth: ['responsive', 'focus', 'active'],
|
||||
backgroundImage: ['responsive', 'hover', 'disabled', 'focus'],
|
||||
opacity: ['hover', 'responsive'],
|
||||
rotate: ['responsive', 'focus'],
|
||||
cursor: ['focus', 'checked', 'hover'],
|
||||
},
|
||||
plugins: userConfig.plugins,
|
||||
})
|
||||
})
|
||||
|
||||
@ -83,7 +83,7 @@ function mergeExtensions({ extend, ...theme }) {
|
||||
}
|
||||
|
||||
function resolveFunctionKeys(object) {
|
||||
const resolveThemePath = (key, defaultValue) => {
|
||||
const resolvePath = (key, defaultValue) => {
|
||||
const path = toPath(key)
|
||||
|
||||
let index = 0
|
||||
@ -91,7 +91,7 @@ function resolveFunctionKeys(object) {
|
||||
|
||||
while (val !== undefined && val !== null && index < path.length) {
|
||||
val = val[path[index++]]
|
||||
val = isFunction(val) ? val(resolveThemePath, configUtils) : val
|
||||
val = isFunction(val) ? val(resolvePath, configUtils) : val
|
||||
}
|
||||
|
||||
return val === undefined ? defaultValue : val
|
||||
@ -100,7 +100,7 @@ function resolveFunctionKeys(object) {
|
||||
return Object.keys(object).reduce((resolved, key) => {
|
||||
return {
|
||||
...resolved,
|
||||
[key]: isFunction(object[key]) ? object[key](resolveThemePath, configUtils) : object[key],
|
||||
[key]: isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key],
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
@ -128,6 +128,65 @@ function extractPluginConfigs(configs) {
|
||||
return allConfigs
|
||||
}
|
||||
|
||||
function resolveVariants([firstConfig, ...variantConfigs]) {
|
||||
if (Array.isArray(firstConfig)) {
|
||||
return firstConfig
|
||||
}
|
||||
|
||||
return [firstConfig, ...variantConfigs].reverse().reduce((resolved, variants) => {
|
||||
Object.entries(variants || {}).forEach(([plugin, pluginVariants]) => {
|
||||
if (isFunction(pluginVariants)) {
|
||||
resolved[plugin] = pluginVariants({
|
||||
variants(path) {
|
||||
return get(resolved, path, [])
|
||||
},
|
||||
before(toInsert, variant, existingPluginVariants = get(resolved, plugin, [])) {
|
||||
if (variant === undefined) {
|
||||
return [...toInsert, ...existingPluginVariants]
|
||||
}
|
||||
|
||||
const index = existingPluginVariants.indexOf(variant)
|
||||
|
||||
if (index === -1) {
|
||||
return [...existingPluginVariants, ...toInsert]
|
||||
}
|
||||
|
||||
return [
|
||||
...existingPluginVariants.slice(0, index),
|
||||
...toInsert,
|
||||
...existingPluginVariants.slice(index),
|
||||
]
|
||||
},
|
||||
after(toInsert, variant, existingPluginVariants = get(resolved, plugin, [])) {
|
||||
if (variant === undefined) {
|
||||
return [...existingPluginVariants, ...toInsert]
|
||||
}
|
||||
|
||||
const index = existingPluginVariants.indexOf(variant)
|
||||
|
||||
if (index === -1) {
|
||||
return [...toInsert, ...existingPluginVariants]
|
||||
}
|
||||
|
||||
return [
|
||||
...existingPluginVariants.slice(0, index + 1),
|
||||
...toInsert,
|
||||
...existingPluginVariants.slice(index + 1),
|
||||
]
|
||||
},
|
||||
without(toRemove, existingPluginVariants = get(resolved, plugin, [])) {
|
||||
return existingPluginVariants.filter(v => !toRemove.includes(v))
|
||||
},
|
||||
})
|
||||
} else {
|
||||
resolved[plugin] = pluginVariants
|
||||
}
|
||||
})
|
||||
|
||||
return resolved
|
||||
}, {})
|
||||
}
|
||||
|
||||
export default function resolveConfig(configs) {
|
||||
const allConfigs = extractPluginConfigs(configs)
|
||||
|
||||
@ -136,11 +195,7 @@ export default function resolveConfig(configs) {
|
||||
theme: resolveFunctionKeys(
|
||||
mergeExtensions(mergeThemes(map(allConfigs, t => get(t, 'theme', {}))))
|
||||
),
|
||||
variants: (firstVariants => {
|
||||
return Array.isArray(firstVariants)
|
||||
? firstVariants
|
||||
: defaults({}, ...map(allConfigs, 'variants'))
|
||||
})(defaults({}, ...map(allConfigs)).variants),
|
||||
variants: resolveVariants(allConfigs.map(c => c.variants)),
|
||||
},
|
||||
...allConfigs
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user