|
import plugin from 'tailwindcss/plugin' |
|
|
|
type MaybeString = string | null | undefined |
|
type UtilType = 'group' | 'peer' |
|
|
|
// Example |
|
// <div class="group relative hovered-action:bg-blue-100 pressed-action:bg-blue-600"> |
|
// <p class="group-hovered-action:text-blue-600 group-pressed-action:text-white">Mykolas Mankevicius</p> |
|
// <button class="hovered:bg-red-50 hovered:text-red-600 z-10">Remove</button> |
|
// <button data-action class="before:absolute before:inset-0 hovered:bg-slate-100">View Profile</button> |
|
// </div> |
|
|
|
// Main plugin |
|
export default plugin((api) => { |
|
const betterStates = api.theme('betterStates', {}) |
|
|
|
const values = { |
|
values: { |
|
DEFAULT: '', |
|
...betterStates |
|
} |
|
} |
|
|
|
const selector = (state: string, prefix: MaybeString) => { |
|
return `${prefix ?? '&'}:${state}:not(:disabled)` |
|
} |
|
|
|
const maybeWrap = (state: string, prefix: string, value: MaybeString) => { |
|
return ['', null, undefined].includes(value) ? selector(state, prefix) : `${prefix}:has(${selector(state, value)})` |
|
} |
|
|
|
const merge = ( |
|
type: UtilType, |
|
value: MaybeString, |
|
modifier: string | null, |
|
callback: (value: MaybeString, prefix: string, merge: boolean) => string[] |
|
) => { |
|
const typeClass = modifier ? `.${type}\\/${api.e(modifier)}` : `.${type}` |
|
const append = type === 'group' ? ' &' : ' ~ &' |
|
return callback(value, `:merge(${typeClass})`, true).map((x) => `${x}${append}`) |
|
} |
|
|
|
// Hovered ________________________________________________________________________ |
|
const hovered = (value: MaybeString = null, prefix = '&', merge = false) => { |
|
const postfix = merge ? ' & ' : '' |
|
return [ |
|
`@media (hover: hover) and (pointer: fine) { ${maybeWrap('hover', prefix, value)}${postfix}}`, |
|
`@media (hover: hover) and (pointer: fine) { ${maybeWrap('focus-visible', prefix, value)}${postfix}}`, |
|
`@media (hover: hover) and (pointer: fine) { ${maybeWrap('has(:focus-visible)', prefix, value)}${postfix}}` |
|
] |
|
} |
|
|
|
api.matchVariant('hovered', (value) => hovered(value), values) |
|
api.matchVariant('group-hovered', (value, { modifier }) => merge('group', value, modifier, hovered), values) |
|
api.matchVariant('peer-hovered', (value, { modifier }) => merge('peer', value, modifier, hovered), values) |
|
|
|
// Pressed ________________________________________________________________________ |
|
const pressed = (value: MaybeString = null, prefix = '&', merge = false) => { |
|
const postfix = merge ? ' & ' : '' |
|
return [ |
|
maybeWrap('active', prefix, value), |
|
maybeWrap('has(:active)', prefix, value), |
|
// Wrap the entire selector in the media query, not just the state |
|
`@media (hover: none) or (pointer: coarse) { ${maybeWrap('hover', prefix, value)}${postfix}}`, |
|
`@media (hover: none) or (pointer: coarse) { ${maybeWrap('focus-visible', prefix, value)}${postfix}}`, |
|
`@media (hover: none) or (pointer: coarse) { ${maybeWrap('has(:focus-visible)', prefix, value)}${postfix}}` |
|
] |
|
} |
|
|
|
api.matchVariant('pressed', (value) => pressed(value), values) |
|
api.matchVariant('group-pressed', (value, { modifier }) => merge('group', value, modifier, pressed), values) |
|
api.matchVariant('peer-pressed', (value, { modifier }) => merge('peer', value, modifier, pressed), values) |
|
}) |