xtendui
Version:
Xtend UI is a powerful frontend library of Tailwind CSS components enhanced by vanilla js. It helps you build interfaces with advanced interactions and animations.
267 lines (251 loc) • 9.64 kB
JavaScript
const plugin = require('tailwindcss/plugin')
const _ = require('lodash')
module.exports = plugin.withOptions(() => {
return function ({ addComponents, addVariant, matchVariant, e, theme }) {
const componentsBase = require('./tailwind.components.js') || {}
const componentsCustom = theme(`xtendui`, {}) || {}
/**
* components
*/
for (const component of Object.keys(componentsBase)) {
const componentBase = componentsBase[component] || {}
const componentCustom = componentsCustom[component] || {}
if (componentCustom !== false && componentCustom.component !== false) {
const base =
typeof componentBase.component === 'function' ? componentBase.component(theme) : componentBase.component
const custom =
typeof componentCustom.component === 'function' ? componentCustom.component(theme) : componentCustom.component
const css = _.merge(..._.castArray(base || {}), custom || {})
addComponents(css, {
respectPrefix: false,
})
}
}
/**
* utilities
*/
for (const component of Object.keys(componentsBase)) {
const componentBase = componentsBase[component] || {}
const componentCustom = componentsCustom[component] || {}
if (componentCustom !== false && componentCustom.utility !== false) {
const base = typeof componentBase.utility === 'function' ? componentBase.utility(theme) : componentBase.utility
const custom =
typeof componentCustom.utility === 'function' ? componentCustom.utility(theme) : componentCustom.utility
const variants = _.merge(componentBase.variants || [], componentCustom.variants || [])
const options = _.merge(..._.castArray(base || {}), custom || {})
const utilities = Object.keys(options)
for (const utility of utilities) {
if (componentsCustom[utility] !== false) {
if (component === 'list' && utility === 'space') {
// list space
const css = {}
Object.keys(options[utility]).forEach(name => {
const value = options[utility][name]
css[`.${e(`xt-list-${name}`)}`] = {
marginTop: `-${value}`,
marginLeft: `-${value}`,
'> *': {
marginTop: `${value}`,
marginLeft: `${value}`,
},
}
css[`.${e(`xt-list-x-${name}`)}`] = {
marginLeft: `-${value}`,
'> *': {
marginLeft: `${value}`,
},
}
css[`.${e(`xt-list-y-${name}`)}`] = {
marginTop: `-${value}`,
'> *': {
marginTop: `${value}`,
},
}
})
addComponents(css, {
variants: variants,
respectPrefix: false,
})
} else if (component === 'row' && utility === 'space') {
// row space
const css = {}
Object.keys(options[utility]).forEach(name => {
const value = options[utility][name]
css[`.${e(`xt-row-${name}`)}`] = {
marginTop: `-${value}`,
marginLeft: `-${value}`,
'> *': {
paddingTop: `${value}`,
paddingLeft: `${value}`,
},
}
css[`.${e(`xt-row-x-${name}`)}`] = {
marginLeft: `-${value}`,
'> *': {
paddingLeft: `${value}`,
},
}
css[`.${e(`xt-row-y-${name}`)}`] = {
marginTop: `-${value}`,
'> *': {
paddingTop: `${value}`,
},
}
})
addComponents(css, {
variants: variants,
respectPrefix: false,
})
} else if (component === 'overlay' && utility === '.xt-overlay-container') {
const css = {}
Object.keys(options[utility]).forEach(name => {
const value = options[utility][name]
if (name === 'DEFAULT') {
css[utility] = {
padding: value,
}
} else {
css[utility] = {
...css[utility],
[`@screen ${name}`]: {
padding: value,
},
}
}
})
addComponents(css, {
respectPrefix: false,
})
} else if (component === 'global' && utility === '.xt-container-y') {
const css = {}
Object.keys(options[utility]).forEach(name => {
const value = options[utility][name]
if (name === 'DEFAULT') {
css[utility] = {
paddingTop: value,
paddingBottom: value,
}
} else {
css[utility] = {
...css[utility],
[`@screen ${name}`]: {
paddingTop: value,
paddingBottom: value,
},
}
}
})
addComponents(css, {
variants: variants,
respectPrefix: false,
})
} else if (component === 'global' && utility === '.xt-container-remove') {
const css = {}
Object.keys(options[utility]).forEach(name => {
const value = options[utility][name]
if (name === 'DEFAULT') {
css[utility] = {
marginLeft: `-${value}`,
marginRight: `-${value}`,
}
} else {
css[utility] = {
...css[utility],
[`@screen ${name}`]: {
marginLeft: `-${value}`,
marginRight: `-${value}`,
},
}
}
})
addComponents(css, {
variants: variants,
respectPrefix: false,
})
} else if (component === 'global' && utility === '.xt-container-y-remove') {
const css = {}
Object.keys(options[utility]).forEach(name => {
const value = options[utility][name]
if (name === 'DEFAULT') {
css[utility] = {
marginTop: `-${value}`,
marginBottom: `-${value}`,
}
} else {
css[utility] = {
...css[utility],
[`@screen ${name}`]: {
marginTop: `-${value}`,
marginBottom: `-${value}`,
},
}
}
})
addComponents(css, {
variants: variants,
respectPrefix: false,
})
} else {
// all others
const css = {}
css[utility] = options[utility]
addComponents(css, {
variants: variants,
respectPrefix: false,
})
}
}
}
}
}
/**
* variant
*/
const pseudoVariants = [
['off-before', '&.dir-before:not(.on):not(.in):not(.out)'],
['off-after', '&.dir-after:not(.on):not(.in):not(.out)'],
['on-before', '&.on.dir-before'],
['on-after', '&.on.dir-after'],
['in-before', '&.in.dir-before'],
['in-after', '&.in.dir-after'],
['out-before', '&.out.dir-before'],
['out-after', '&.out.dir-after'],
['done-before', '&.done.dir-before'],
['done-after', '&.done.dir-after'],
['dir-before', '&.dir-before'],
['dir-after', '&.dir-after'],
['off', '&:not(.on):not(.in):not(.out)'],
['on', '&.on'],
['in', '&.in'],
['out', '&.out'],
['done', '&.done'],
['valid-submit', '&.valid-submit'],
['invalid-submit', '&.invalid-submit'],
].map(variant => (Array.isArray(variant) ? variant : [variant, `&:${variant}`]))
// group and peer variants
// https://github.com/tailwindlabs/tailwindcss/discussions/9713#discussioncomment-4040990
const variants = {
group: (_, { modifier }) => (modifier ? [`:merge(.group\\/${modifier})`, ' &'] : [`:merge(.group)`, ' &']),
peer: (_, { modifier }) => (modifier ? [`:merge(.peer\\/${modifier})`, ' ~ &'] : [`:merge(.peer)`, ' ~ &']),
}
for (const [name, fn] of Object.entries(variants)) {
matchVariant(
name,
(value = '', extra) => {
let result = value
if (!result.includes('&')) result = `&${result}`
const [a, b] = fn('', extra)
return result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)
},
{ values: Object.fromEntries(pseudoVariants) },
)
}
// simple variants
for (const values of pseudoVariants) {
addVariant(values[0], values[1])
}
// other variants
addVariant('hover-none', '@media (hover: none), (pointer: coarse)')
addVariant('hover-hover', '@media (hover: hover), not (pointer: coarse)')
}
})