Files
ui/src/colors.ts
2023-06-13 14:52:32 +02:00

152 lines
4.5 KiB
TypeScript

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
}