mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
286 lines
8.6 KiB
TypeScript
286 lines
8.6 KiB
TypeScript
import { omit } from './lodash'
|
|
import { kebabCase, camelCase, upperFirst } from 'scule'
|
|
|
|
const colorsToExclude = [
|
|
'inherit',
|
|
'transparent',
|
|
'current',
|
|
'white',
|
|
'black',
|
|
'slate',
|
|
'gray',
|
|
'zinc',
|
|
'neutral',
|
|
'stone',
|
|
'cool'
|
|
]
|
|
|
|
const safelistByComponent = {
|
|
alert: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-50`)
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`)
|
|
}],
|
|
avatar: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}],
|
|
badge: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-50`)
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`)
|
|
}],
|
|
button: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-50`),
|
|
variants: ['hover', 'disabled']
|
|
}, {
|
|
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', 'dark:disabled']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark', 'dark:disabled']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`),
|
|
variants: ['dark:hover', 'disabled']
|
|
}, {
|
|
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(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark', 'dark:focus']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
|
variants: ['focus']
|
|
}],
|
|
radio: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark:focus-visible']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
|
variants: ['focus-visible']
|
|
}],
|
|
checkbox: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark:focus-visible']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
|
variants: ['focus-visible']
|
|
}],
|
|
toggle: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark:focus-visible']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
|
variants: ['focus-visible']
|
|
}],
|
|
range: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
|
|
variants: ['dark:focus-visible']
|
|
}, {
|
|
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
|
|
variants: ['focus-visible']
|
|
}],
|
|
progress: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}],
|
|
meter: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}],
|
|
notification: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
|
|
}],
|
|
chip: (colorsAsRegex) => [{
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
|
|
variants: ['dark']
|
|
}, {
|
|
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
|
|
}]
|
|
}
|
|
|
|
const safelistComponentAliasesMap = {
|
|
'USelect': 'UInput',
|
|
'USelectMenu': 'UInput',
|
|
'UTextarea': 'UInput',
|
|
'URadioGroup': 'URadio',
|
|
'UMeterGroup': 'UMeter'
|
|
}
|
|
|
|
const colorsAsRegex = (colors: string[]): string => colors.join('|')
|
|
|
|
export const excludeColors = (colors: object): string[] => {
|
|
return Object.entries(omit(colors, colorsToExclude))
|
|
.filter(([, value]) => typeof value === 'object')
|
|
.map(([key]) => kebabCase(key))
|
|
}
|
|
|
|
export const generateSafelist = (colors: string[], globalColors) => {
|
|
const baseSafelist = Object.keys(safelistByComponent).flatMap(component => safelistByComponent[component](colorsAsRegex(colors)))
|
|
|
|
// Ensure `red` color is safelisted for form elements so that `error` prop of `UFormGroup` always works
|
|
const formsSafelist = ['input', 'radio', 'checkbox', 'toggle', 'range'].flatMap(component => safelistByComponent[component](colorsAsRegex(['red'])))
|
|
|
|
return [
|
|
...baseSafelist,
|
|
...formsSafelist,
|
|
// Ensure all global colors are safelisted for the Notification (toast.add)
|
|
...safelistByComponent['notification'](colorsAsRegex(globalColors)),
|
|
// Gray safelist for Avatar & Notification
|
|
'bg-gray-500',
|
|
'dark:bg-gray-400',
|
|
'text-gray-500',
|
|
'dark:text-gray-400'
|
|
]
|
|
}
|
|
|
|
export const customSafelistExtractor = (prefix, content: string, colors: string[], safelistColors: string[]) => {
|
|
const classes: string[] = []
|
|
const regex = /<([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)*)\s+(?![^>]*:color\b)[^>]*\bcolor=["']([^"']+)["'][^>]*>/gs
|
|
|
|
const matches = content.matchAll(regex)
|
|
|
|
const components = Object.keys(safelistByComponent).map(component => `${prefix}${component.charAt(0).toUpperCase() + component.slice(1)}`)
|
|
|
|
for (const match of matches) {
|
|
const [, component, color] = match
|
|
|
|
const camelComponent = upperFirst(camelCase(component))
|
|
|
|
if (!colors.includes(color) || safelistColors.includes(color)) {
|
|
continue
|
|
}
|
|
|
|
let name = safelistComponentAliasesMap[camelComponent] ? safelistComponentAliasesMap[camelComponent] : camelComponent
|
|
|
|
if (!components.includes(name)) {
|
|
continue
|
|
}
|
|
|
|
name = name.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
|
|
}
|