shadcn-vue/packages/cli/src/utils/transformers/transform-css-vars.ts
Sadegh Barati 0e84af73de
feat: use unjs modules and improve cli from main shadcn-ui source, custom ui dir (#324)
* feat: add devDeps, add nypm for installing deps

* feat: custom ui dir

* refactor: use consola instead of chalk

* test: ui alias

* refactor: import { z } from 'zod' instead of *, replace node:path with pathe

* chore: add components name to `configFile` option

* chore: update `c12` which fix json5 parse issue

and it also supports .config directory

* chore: update `https-proxy-agent`

* fix: await until dependencies are installed then run detypes process

* feat: add tailwind prefix

* test: tw-prefix snapshot

* chore: add prefix option to init

* test: apply prefix

* fix: tw-prefix parse wrongly

* chore: hide prefix temporarily

---------

Co-authored-by: zernonia <zernonia@gmail.com>
2024-03-06 05:38:19 +03:30

126 lines
3.7 KiB
TypeScript

import type * as z from 'zod'
import MagicString from 'magic-string'
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import { parse } from '@vue/compiler-sfc'
import { SyntaxKind } from 'ts-morph'
import type { registryBaseColorSchema } from '@/src/utils/registry/schema'
import type { Transformer } from '@/src/utils/transformers'
export const transformCssVars: Transformer = async ({
sourceFile,
config,
baseColor,
}) => {
const isVueFile = sourceFile.getFilePath().endsWith('vue')
// No transform if using css variables.
if (config.tailwind?.cssVariables || !baseColor?.inlineColors)
return sourceFile
let template: SFCTemplateBlock | null = null
if (isVueFile) {
const parsed = parse(sourceFile.getText())
template = parsed.descriptor.template
if (!template)
return sourceFile
}
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => {
if (template && template.loc.start.offset >= node.getPos())
return sourceFile
const value = node.getText()
const hasClosingDoubleQuote = value.match(/"/g)?.length === 2
if (value.search('\'') === -1 && hasClosingDoubleQuote) {
const mapped = applyColorMapping(value.replace(/"/g, ''), baseColor.inlineColors)
node.replaceWithText(`"${mapped}"`)
}
else {
const s = new MagicString(value)
s.replace(/'(.*?)'/g, (substring) => {
return `'${applyColorMapping(substring.replace(/\'/g, ''), baseColor.inlineColors)}'`
})
node.replaceWithText(s.toString())
}
})
return sourceFile
}
// Splits a className into variant-name-alpha.
// eg. hover:bg-primary-100 -> [hover, bg-primary, 100]
export function splitClassName(className: string): (string | null)[] {
if (!className.includes('/') && !className.includes(':'))
return [null, className, null]
const parts: (string | null)[] = []
// First we split to find the alpha.
const [rest, alpha] = className.split('/')
// Check if rest has a colon.
if (!rest.includes(':'))
return [null, rest, alpha]
// Next we split the rest by the colon.
const split = rest.split(':')
// We take the last item from the split as the name.
const name = split.pop()
// We glue back the rest of the split.
const variant = split.join(':')
// Finally we push the variant, name and alpha.
parts.push(variant ?? null, name ?? null, alpha ?? null)
return parts
}
const PREFIXES = ['bg-', 'text-', 'border-', 'ring-offset-', 'ring-']
export function applyColorMapping(
input: string,
mapping: z.infer<typeof registryBaseColorSchema>['inlineColors'],
) {
// Handle border classes.
if (input.includes(' border '))
input = input.replace(' border ', ' border border-border ')
const classNames = input.split(' ')
const lightMode = new Set<string>()
const darkMode = new Set<string>()
for (const className of classNames) {
const [variant, value, modifier] = splitClassName(className)
const prefix = PREFIXES.find(prefix => value?.startsWith(prefix))
if (!prefix) {
if (!lightMode.has(className))
lightMode.add(className)
continue
}
const needle = value?.replace(prefix, '')
if (needle && needle in mapping.light) {
lightMode.add(
[variant, `${prefix}${mapping.light[needle]}`]
.filter(Boolean)
.join(':') + (modifier ? `/${modifier}` : ''),
)
darkMode.add(
['dark', variant, `${prefix}${mapping.dark[needle]}`]
.filter(Boolean)
.join(':') + (modifier ? `/${modifier}` : ''),
)
continue
}
if (!lightMode.has(className))
lightMode.add(className)
}
return [...Array.from(lightMode), ...Array.from(darkMode)].join(' ').trim()
}