Skip to content

Commit

Permalink
feat(shadcn): update tailwind config
Browse files Browse the repository at this point in the history
  • Loading branch information
shadcn committed Jan 29, 2025
1 parent 056e507 commit a086b89
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/shadcn/src/utils/add-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ async function addProjectComponents(
cleanupDefaultNextStyles: options.isNewProject,
silent: options.silent,
tailwindVersion,
tailwindConfig: tree.tailwind?.config,
})

await updateDependencies(tree.dependencies, config, {
Expand Down Expand Up @@ -176,6 +177,7 @@ async function addWorkspaceComponents(
await updateCssVars(component.cssVars, targetConfig, {
silent: true,
tailwindVersion,
tailwindConfig: component.tailwind?.config,
})
filesUpdated.push(
path.relative(workspaceRoot, targetConfig.resolvedPaths.tailwindCss)
Expand Down
100 changes: 84 additions & 16 deletions packages/shadcn/src/utils/updaters/update-css-vars.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { promises as fs } from "fs"
import path from "path"
import { registryItemCssVarsSchema } from "@/src/registry/schema"
import {
registryItemCssVarsSchema,
registryItemTailwindSchema,
} from "@/src/registry/schema"
import { Config } from "@/src/utils/get-config"
import { TailwindVersion, getProjectInfo } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
Expand All @@ -18,13 +21,10 @@ export async function updateCssVars(
cleanupDefaultNextStyles?: boolean
silent?: boolean
tailwindVersion?: TailwindVersion
tailwindConfig?: z.infer<typeof registryItemTailwindSchema>["config"]
}
) {
if (
!cssVars ||
!Object.keys(cssVars).length ||
!config.resolvedPaths.tailwindCss
) {
if (!config.resolvedPaths.tailwindCss) {
return
}

Expand All @@ -46,9 +46,10 @@ export async function updateCssVars(
}
).start()
const raw = await fs.readFile(cssFilepath, "utf8")
let output = await transformCssVars(raw, cssVars, config, {
let output = await transformCssVars(raw, cssVars ?? {}, config, {
cleanupDefaultNextStyles: options.cleanupDefaultNextStyles,
tailwindVersion: options.tailwindVersion,
tailwindConfig: options.tailwindConfig,
})
await fs.writeFile(cssFilepath, output, "utf8")
cssVarsSpinner.succeed()
Expand All @@ -61,14 +62,17 @@ export async function transformCssVars(
options: {
cleanupDefaultNextStyles?: boolean
tailwindVersion?: TailwindVersion
tailwindConfig?: z.infer<typeof registryItemTailwindSchema>["config"]
} = {
cleanupDefaultNextStyles: false,
tailwindVersion: "v3",
tailwindConfig: undefined,
}
) {
options = {
cleanupDefaultNextStyles: false,
tailwindVersion: "v3",
tailwindConfig: undefined,
...options,
}

Expand All @@ -80,6 +84,10 @@ export async function transformCssVars(
updateCssVarsPluginV4(cssVars),
updateThemePlugin(cssVars),
]

if (options.tailwindConfig) {
plugins.push(updateTailwindConfigPlugin(options.tailwindConfig))
}
}

if (options.cleanupDefaultNextStyles) {
Expand Down Expand Up @@ -371,6 +379,19 @@ function updateThemePlugin(cssVars: z.infer<typeof registryItemCssVarsSchema>) {
return {
postcssPlugin: "update-theme",
Once(root: Root) {
// Find unique color names from light and dark.
const colors = Array.from(
new Set(
Object.keys(cssVars).flatMap((key) =>
Object.keys(cssVars[key as keyof typeof cssVars] || {})
)
)
)

if (!colors.length) {
return
}

let themeNode = root.nodes.find(
(node): node is AtRule =>
node.type === "atrule" &&
Expand All @@ -388,15 +409,6 @@ function updateThemePlugin(cssVars: z.infer<typeof registryItemCssVarsSchema>) {
root.append(themeNode)
}

// Find unique color names from light and dark.
const colors = Array.from(
new Set(
Object.keys(cssVars).flatMap((key) =>
Object.keys(cssVars[key as keyof typeof cssVars] || {})
)
)
)

for (const color of colors) {
const colorVar = postcss.decl({
prop: `--color-${color.replace(/^--/, "")}`,
Expand Down Expand Up @@ -436,3 +448,59 @@ function addCustomVariant({ params }: { params: string }) {
},
}
}

function updateTailwindConfigPlugin(
tailwindConfig: z.infer<typeof registryItemTailwindSchema>["config"]
) {
return {
postcssPlugin: "update-tailwind-config",
Once(root: Root) {
if (!tailwindConfig?.plugins) {
return
}

const quoteType = getQuoteType(root)
const quote = quoteType === "single" ? "'" : '"'

const pluginNodes = root.nodes.filter(
(node): node is AtRule =>
node.type === "atrule" && node.name === "plugin"
)

const lastPluginNode =
pluginNodes[pluginNodes.length - 1] || root.nodes[0]

for (const plugin of tailwindConfig.plugins) {
const pluginName = plugin.replace(/^require\(["']|["']\)$/g, "")

// Check if the plugin is already present.
if (
pluginNodes.some((node) => {
return node.params.replace(/["']/g, "") === pluginName
})
) {
continue
}

root.insertAfter(
lastPluginNode,
postcss.atRule({
name: "plugin",
params: `${quote}${pluginName}${quote}`,
raws: { semicolon: true, before: "\n" },
})
)
}
},
}
}

function getQuoteType(root: Root): "single" | "double" {
const firstNode = root.nodes[0]
const raw = firstNode.toString()

if (raw.includes("'")) {
return "single"
}
return "double"
}
97 changes: 97 additions & 0 deletions packages/shadcn/test/utils/updaters/update-css-vars.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,4 +548,101 @@ describe("transformCssVarsV4", () => {
"
`)
})

test("should add plugin if not present", async () => {
expect(
await transformCssVars(
`@import "tailwindcss";
`,
{},
{ tailwind: { cssVariables: true } },
{
tailwindVersion: "v4",
tailwindConfig: { plugins: ['require("tailwindcss-animate")'] },
}
)
).toMatchInlineSnapshot(`
"@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
"
`)
})

test("should NOT add plugin if already present", async () => {
expect(
await transformCssVars(
`@import "tailwindcss";
@plugin "tailwindcss-animate";
`,
{},
{ tailwind: { cssVariables: true } },
{
tailwindVersion: "v4",
tailwindConfig: {
plugins: [
'require("tailwindcss-animate")',
'require("@tailwindcss/typography")',
],
},
}
)
).toMatchInlineSnapshot(`
"@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
@plugin "tailwindcss-animate";
@plugin "@tailwindcss/typography";
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
"
`)
})

test("should preserve quotes", async () => {
expect(
await transformCssVars(
`@import 'tailwindcss';
`,
{},
{ tailwind: { cssVariables: true } },
{
tailwindVersion: "v4",
tailwindConfig: {
plugins: [
'require("tailwindcss-animate")',
'require("@tailwindcss/typography")',
],
},
}
)
).toMatchInlineSnapshot(`
"@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@plugin 'tailwindcss-animate';
@custom-variant dark (&:is(.dark *));
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
"
`)
})
})

0 comments on commit a086b89

Please sign in to comment.