mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-30 11:47:55 +01:00
feat(module): smart safelisting (#268)
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
This commit is contained in:
151
src/colors.ts
Normal file
151
src/colors.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
const colorsToExclude = [
|
||||
'inherit',
|
||||
'transparent',
|
||||
'current',
|
||||
'white',
|
||||
'black',
|
||||
'slate',
|
||||
'gray',
|
||||
'zinc',
|
||||
'neutral',
|
||||
'stone',
|
||||
'cool'
|
||||
]
|
||||
|
||||
const omit = (obj: object, keys: string[]) => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).filter(([key]) => !keys.includes(key))
|
||||
)
|
||||
}
|
||||
|
||||
const kebabCase = (str: string) => {
|
||||
return str
|
||||
?.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
||||
?.map(x => x.toLowerCase())
|
||||
?.join('-')
|
||||
}
|
||||
|
||||
const safelistByComponent = {
|
||||
avatar: (colorsAsRegex) => [{
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex}|gray)-500`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex}|gray)-400`),
|
||||
variants: ['dark']
|
||||
}],
|
||||
badge: (colorsAsRegex) => [{
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-50`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${colorsAsRegex})-500`)
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}],
|
||||
button: (colorsAsRegex) => [{
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-50`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-100`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
||||
variants: ['dark', 'dark:disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-500`),
|
||||
variants: ['disabled', 'dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-600`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-900`),
|
||||
variants: ['dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex})-950`),
|
||||
variants: ['dark', 'dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex})-500`),
|
||||
variants: ['dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex})-600`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${colorsAsRegex})-400`),
|
||||
variants: ['dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${colorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
||||
variants: ['dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}],
|
||||
input: (colorsAsRegex) => [{
|
||||
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
||||
variants: ['dark', 'dark:focus']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
||||
variants: ['focus']
|
||||
}],
|
||||
notification: (colorsAsRegex) => [{
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex}|gray)-500`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${colorsAsRegex}|gray)-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex}|gray)-500`)
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${colorsAsRegex}|gray)-400`),
|
||||
variants: ['dark']
|
||||
}]
|
||||
}
|
||||
|
||||
const colorsAsRegex = (colors: string[]): string => colors.join('|')
|
||||
|
||||
export const excludeColors = (colors: object) => Object.keys(omit(colors, colorsToExclude)).map(color => kebabCase(color)) as string[]
|
||||
|
||||
export const generateSafelist = (colors: string[]) => ['avatar', 'badge', 'button', 'input', 'notification'].flatMap(component => safelistByComponent[component](colorsAsRegex(colors)))
|
||||
|
||||
export const customSafelistExtractor = (prefix, content: string) => {
|
||||
const classes = []
|
||||
const regex = /<(\w+)\s+[^>]*color=["']([^"']+)["'][^>]*>/gs
|
||||
const matches = [...content.matchAll(regex)]
|
||||
|
||||
for (const match of matches) {
|
||||
const [, component, color] = match
|
||||
|
||||
if (colorsToExclude.includes(color)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (Object.keys(safelistByComponent).map(component => `${prefix}${component.charAt(0).toUpperCase() + component.slice(1)}`).includes(component)) {
|
||||
const name = component.replace(prefix, '').toLowerCase()
|
||||
|
||||
const matchClasses = safelistByComponent[name](color).flatMap(group => {
|
||||
return ['', ...(group.variants || [])].flatMap(variant => {
|
||||
const matches = group.pattern.source.match(/\(([^)]+)\)/g)
|
||||
|
||||
return matches.map(match => {
|
||||
const colorOptions = match.substring(1, match.length - 1).split('|')
|
||||
return colorOptions.map(color => `${variant ? variant + ':' : ''}` + group.pattern.source.replace(match, color))
|
||||
}).flat()
|
||||
})
|
||||
})
|
||||
|
||||
classes.push(...matchClasses)
|
||||
}
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
Reference in New Issue
Block a user