mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-28 15:14:12 +00:00
feat(cli): add support for custom Tailwind prefix transformer (#770)
* feat(cli): add support for custom Tailwind prefix * fix(cli): add tw prefix on classes applied in the css file * feat(cli): add support for custom tailwind prefix * chore: add changeset * style(shadcn-ui): code format --------- Co-authored-by: shadcn <m@shadcn.com>
This commit is contained in:
@@ -28,6 +28,7 @@ export const rawConfigSchema = z
|
||||
css: z.string(),
|
||||
baseColor: z.string(),
|
||||
cssVariables: z.boolean().default(true),
|
||||
prefix: z.string().default("").optional(),
|
||||
}),
|
||||
aliases: z.object({
|
||||
components: z.string(),
|
||||
|
||||
@@ -23,6 +23,7 @@ module.exports = {
|
||||
'./app/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
],
|
||||
prefix: "<%- prefix %>",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
@@ -60,6 +61,7 @@ module.exports = {
|
||||
'./app/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
],
|
||||
prefix: "<%- prefix %>",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
@@ -138,6 +140,7 @@ const config = {
|
||||
'./app/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
],
|
||||
prefix: "<%- prefix %>",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
@@ -178,6 +181,7 @@ const config = {
|
||||
'./app/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
'./src/**/*.{<%- extension %>,<%- extension %>x}',
|
||||
],
|
||||
prefix: "<%- prefix %>",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { transformRsc } from "@/src/utils/transformers/transform-rsc"
|
||||
import { Project, ScriptKind, type SourceFile } from "ts-morph"
|
||||
import * as z from "zod"
|
||||
|
||||
import { transformTwPrefixes } from "./transform-tw-prefix"
|
||||
|
||||
export type TransformOpts = {
|
||||
filename: string
|
||||
raw: string
|
||||
@@ -27,6 +29,7 @@ const transformers: Transformer[] = [
|
||||
transformImport,
|
||||
transformRsc,
|
||||
transformCssVars,
|
||||
transformTwPrefixes,
|
||||
]
|
||||
|
||||
const project = new Project({
|
||||
|
||||
201
packages/cli/src/utils/transformers/transform-tw-prefix.ts
Normal file
201
packages/cli/src/utils/transformers/transform-tw-prefix.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Transformer } from "@/src/utils/transformers"
|
||||
import { SyntaxKind } from "ts-morph"
|
||||
|
||||
import { splitClassName } from "./transform-css-vars"
|
||||
|
||||
export const transformTwPrefixes: Transformer = async ({
|
||||
sourceFile,
|
||||
config,
|
||||
}) => {
|
||||
if (!config.tailwind?.prefix) {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
// Find the cva function calls.
|
||||
sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||
.filter((node) => node.getExpression().getText() === "cva")
|
||||
.forEach((node) => {
|
||||
// cva(base, ...)
|
||||
if (node.getArguments()[0]?.isKind(SyntaxKind.StringLiteral)) {
|
||||
const defaultClassNames = node.getArguments()[0]
|
||||
if (defaultClassNames) {
|
||||
defaultClassNames.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
defaultClassNames.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// cva(..., { variants: { ... } })
|
||||
if (node.getArguments()[1]?.isKind(SyntaxKind.ObjectLiteralExpression)) {
|
||||
node
|
||||
.getArguments()[1]
|
||||
?.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
|
||||
.find((node) => node.getName() === "variants")
|
||||
?.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
|
||||
.forEach((node) => {
|
||||
node
|
||||
.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
|
||||
.forEach((node) => {
|
||||
const classNames = node.getInitializerIfKind(
|
||||
SyntaxKind.StringLiteral
|
||||
)
|
||||
if (classNames) {
|
||||
classNames?.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
classNames.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Find all jsx attributes with the name className.
|
||||
sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute).forEach((node) => {
|
||||
if (node.getName() === "className") {
|
||||
// className="..."
|
||||
if (node.getInitializer()?.isKind(SyntaxKind.StringLiteral)) {
|
||||
const value = node.getInitializer()
|
||||
if (value) {
|
||||
value.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
value.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// className={...}
|
||||
if (node.getInitializer()?.isKind(SyntaxKind.JsxExpression)) {
|
||||
// Check if it's a call to cn().
|
||||
const callExpression = node
|
||||
.getInitializer()
|
||||
?.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||
.find((node) => node.getExpression().getText() === "cn")
|
||||
if (callExpression) {
|
||||
// Loop through the arguments.
|
||||
callExpression.getArguments().forEach((node) => {
|
||||
if (
|
||||
node.isKind(SyntaxKind.ConditionalExpression) ||
|
||||
node.isKind(SyntaxKind.BinaryExpression)
|
||||
) {
|
||||
node
|
||||
.getChildrenOfKind(SyntaxKind.StringLiteral)
|
||||
.forEach((node) => {
|
||||
node.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
node.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (node.isKind(SyntaxKind.StringLiteral)) {
|
||||
node.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
node.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// classNames={...}
|
||||
if (node.getName() === "classNames") {
|
||||
if (node.getInitializer()?.isKind(SyntaxKind.JsxExpression)) {
|
||||
node
|
||||
.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
|
||||
.forEach((node) => {
|
||||
if (node.getInitializer()?.isKind(SyntaxKind.CallExpression)) {
|
||||
const callExpression = node.getInitializerIfKind(
|
||||
SyntaxKind.CallExpression
|
||||
)
|
||||
if (callExpression) {
|
||||
// Loop through the arguments.
|
||||
callExpression.getArguments().forEach((arg) => {
|
||||
if (arg.isKind(SyntaxKind.ConditionalExpression)) {
|
||||
arg
|
||||
.getChildrenOfKind(SyntaxKind.StringLiteral)
|
||||
.forEach((node) => {
|
||||
node.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
node.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (arg.isKind(SyntaxKind.StringLiteral)) {
|
||||
arg.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
arg.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (node.getInitializer()?.isKind(SyntaxKind.StringLiteral)) {
|
||||
if (node.getName() !== "variant") {
|
||||
const classNames = node.getInitializer()
|
||||
if (classNames) {
|
||||
classNames.replaceWithText(
|
||||
`"${applyPrefix(
|
||||
classNames.getText()?.replace(/"/g, ""),
|
||||
config.tailwind.prefix
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
export function applyPrefix(input: string, prefix: string = "") {
|
||||
const classNames = input.split(" ")
|
||||
const prefixed: string[] = []
|
||||
for (let className of classNames) {
|
||||
const [variant, value, modifier] = splitClassName(className)
|
||||
if (variant) {
|
||||
modifier
|
||||
? prefixed.push(`${variant}:${prefix}${value}/${modifier}`)
|
||||
: prefixed.push(`${variant}:${prefix}${value}`)
|
||||
} else {
|
||||
modifier
|
||||
? prefixed.push(`${prefix}${value}/${modifier}`)
|
||||
: prefixed.push(`${prefix}${value}`)
|
||||
}
|
||||
}
|
||||
return prefixed.join(" ")
|
||||
}
|
||||
|
||||
export function applyPrefixesCss(css: string, prefix: string) {
|
||||
const lines = css.split("\n")
|
||||
for (let line of lines) {
|
||||
if (line.includes("@apply")) {
|
||||
const originalTWCls = line.replace("@apply", "").trim()
|
||||
const prefixedTwCls = applyPrefix(originalTWCls, prefix)
|
||||
css = css.replace(originalTWCls, prefixedTwCls)
|
||||
}
|
||||
}
|
||||
return css
|
||||
}
|
||||
Reference in New Issue
Block a user