Compare commits

...

1 Commits

Author SHA1 Message Date
shadcn
8392c7fa0c feat(cli): add css updates 2024-01-18 21:06:20 +04:00
8 changed files with 627 additions and 6 deletions

View File

@@ -1,5 +1,6 @@
import { existsSync, promises as fs } from "fs"
import path from "path"
import { applyCSSUpdates } from "@/src/utils/css"
import {
DEFAULT_COMPONENTS,
DEFAULT_TAILWIND_CONFIG,
@@ -357,16 +358,17 @@ export async function runInit(cwd: string, config: Config) {
// Write css file.
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
if (baseColor) {
await fs.writeFile(
const cssFileInput = await fs.readFile(
config.resolvedPaths.tailwindCss,
config.tailwind.cssVariables
? config.tailwind.prefix
? applyPrefixesCss(baseColor.cssVarsTemplate, config.tailwind.prefix)
: baseColor.cssVarsTemplate
: baseColor.inlineColorsTemplate,
"utf8"
)
// const cssFileTemplate = config.tailwind.cssVariables
// ? baseColor.cssVarsTemplate
// : baseColor.inlineColorsTemplate
const cssFileContent = applyCSSUpdates(cssFileInput, baseColor, config)
await fs.writeFile(config.resolvedPaths.tailwindCss, cssFileContent, "utf8")
}
// Write cn file.

View File

@@ -0,0 +1,86 @@
import { Config } from "@/src/utils/get-config"
import { registryBaseColorSchema } from "@/src/utils/registry/schema"
import { applyPrefixesCss } from "@/src/utils/transformers/transform-tw-prefix"
import * as z from "zod"
// This does a simple string replacement.
// TODO: Do we want to use a proper CSS transformer here? Probably not right now.
export function applyCSSUpdates(
input: string,
baseColor: Pick<z.infer<typeof registryBaseColorSchema>, "cssVars">,
config: Partial<Config>
) {
if (!isCSSUpdateRequired(input, config)) {
return input
}
let output = input
// Check if the file contains the `@tailwind` directives.
// We assume it does since Tailwind CSS is checked in preflight.
if (!input.includes("@tailwind base")) {
// Prepend the `@tailwind` directives.
output = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n${input}`
}
// No additional changes if not using CSS variables.
if (!config.tailwind?.cssVariables) {
return output
}
const lightColors = Object.entries(baseColor.cssVars.light).map(
([name, value]) => ` --${name}: ${value};`
)
const darkColors = Object.entries(baseColor.cssVars.dark).map(
([name, value]) => ` --${name}: ${value};`
)
output = output.replace(
/@tailwind utilities;/,
`@tailwind utilities;\n
@layer base {
:root {
${lightColors.join("\n")}
}
.dark {
${darkColors.join("\n")}
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}`
)
if (config.tailwind.prefix) {
output = applyPrefixesCss(output, config.tailwind.prefix)
}
return output
}
function isCSSUpdateRequired(input: string, config: Partial<Config>) {
// Check for tailwind directives.
const hasTailwindDirectives =
input.includes("@tailwind base") &&
input.includes("@tailwind utilities") &&
input.includes("@tailwind components")
// If we're not using CSS variables, we only check for the `@tailwind` directives.
if (!config.tailwind?.cssVariables) {
return !hasTailwindDirectives
}
// If we are using CSS variables, we check for the `@layer base` directives.
// And we check for the `--background` and `--foreground` variables.
return !(
input.includes("@layer base") &&
input.includes("--background") &&
input.includes("--foreground")
)
}

View File

@@ -0,0 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

View File

@@ -0,0 +1,38 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
body {
background-color: tomato;
}

View File

@@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: tomato;
}

View File

@@ -0,0 +1,3 @@
body {
background-color: tomato;
}

View File

@@ -0,0 +1,304 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`apply css updates 1`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: tomato;
}
"
`;
exports[`apply css updates 2`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
"
`;
exports[`apply css updates 3`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
body {
background-color: tomato;
}
"
`;
exports[`apply css updates 4`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
"
`;
exports[`apply css updates 5`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
}
}
@layer base {
* {
@apply tw-border-border;
}
body {
@apply tw-bg-background tw-text-foreground;
}
}
body {
background-color: tomato;
}
"
`;
exports[`apply css updates 6`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
}
}
@layer base {
* {
@apply cn-border-border;
}
body {
@apply cn-bg-background cn-text-foreground;
}
}
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
"
`;
exports[`apply css updates 7`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: tomato;
}
"
`;
exports[`apply css updates 8`] = `
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
body {
background-color: tomato;
}
"
`;

View File

@@ -0,0 +1,154 @@
import { promises as fs } from "fs"
import path from "path"
import { expect, test } from "vitest"
import { applyCSSUpdates } from "../../src/utils/css"
const baseColor = {
cssVars: {
light: {
background: "0 0% 100%",
foreground: "224 71.4% 4.1%",
primary: "220.9 39.3% 11%",
"primary-foreground": "210 20% 98%",
border: "220 13% 91%",
input: "220 13% 91%",
ring: "224 71.4% 4.1%",
},
dark: {
background: "224 71.4% 4.1%",
foreground: "210 20% 98%",
primary: "210 20% 98%",
"primary-foreground": "220.9 39.3% 11%",
border: "215 27.9% 16.9%",
input: "215 27.9% 16.9%",
ring: "216 12.2% 83.9%",
},
},
}
test("apply css updates", async () => {
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/no-tailwind.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: false,
},
}
)
).toMatchSnapshot()
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/app-globals.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: false,
},
}
)
).toMatchSnapshot()
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/no-tailwind.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: true,
},
}
)
).toMatchSnapshot()
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/app-globals.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: true,
},
}
)
).toMatchSnapshot()
// Prefix.
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/no-tailwind.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: true,
prefix: "tw-",
},
}
)
).toMatchSnapshot()
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/app-globals.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: true,
prefix: "cn-",
},
}
)
).toMatchSnapshot()
// Applied.
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/applied.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: false,
},
}
)
).toMatchSnapshot()
expect(
await applyCSSUpdates(
await fs.readFile(
path.resolve(__dirname, "../fixtures/css/applied-css-vars.css"),
"utf8"
),
baseColor,
{
tailwind: {
cssVariables: true,
},
}
)
).toMatchSnapshot()
})