feat(shadcn): extend styles (#7033)

* feat(shadcn): extend styles

* fix(shadcn): typecheck
This commit is contained in:
shadcn
2025-03-26 17:11:08 +04:00
committed by GitHub
parent ca7fbc3b64
commit 074eed5605
8 changed files with 50 additions and 23 deletions

View File

@@ -171,6 +171,10 @@
"type": "string",
"description": "The categories of the registry item. This is an array of strings."
}
},
"extends": {
"type": "string",
"description": "The name of the registry item to extend. This is used to extend the base shadcn/ui style. Set to none to start fresh. This is available for registry:style items only."
}
},
"required": ["name", "type"]

View File

@@ -148,6 +148,7 @@ export const add = new Command()
isNewProject: false,
srcDir: options.srcDir,
cssVariables: options.cssVariables,
style: "index",
})
}
@@ -179,6 +180,7 @@ export const add = new Command()
isNewProject: true,
srcDir: options.srcDir,
cssVariables: options.cssVariables,
style: "index",
})
shouldUpdateAppIndex =

View File

@@ -4,6 +4,7 @@ import { preFlightInit } from "@/src/preflights/preflight-init"
import {
BASE_COLORS,
getRegistryBaseColors,
getRegistryItem,
getRegistryStyles,
} from "@/src/registry/api"
import { addComponents } from "@/src/utils/add-components"
@@ -74,6 +75,7 @@ export const initOptionsSchema = z.object({
).join("', '")}'`,
}
),
style: z.string(),
})
export const init = new Command()
@@ -118,16 +120,20 @@ export const init = new Command()
cwd: path.resolve(opts.cwd),
isNewProject: false,
components,
style: "index",
...opts,
})
// We need to check if we're initializing with a new style.
// We fetch the payload of the first item.
// This is okay since the request is cached and deduped.
const item = await getRegistryItem(components[0], "")
// Skip base color if style.
// We set a default and let the style override it.
const isStyle =
components?.length === 1 &&
components[0].split("/").pop().startsWith("style-")
if (isStyle) {
if (item?.type === "registry:style") {
options.baseColor = "neutral"
options.style = item.extends ?? "index"
}
await runInit(options)
@@ -200,11 +206,15 @@ export async function runInit(
// Add components.
const fullConfig = await resolveConfigPaths(options.cwd, config)
const components = ["index", ...(options.components || [])]
const components = [
...(options.style === "none" ? [] : [options.style]),
...(options.components ?? []),
]
await addComponents(components, fullConfig, {
// Init will always overwrite files.
overwrite: true,
silent: options.silent,
style: options.style,
isNewProject:
options.isNewProject || projectInfo?.framework.name === "next-app",
})

View File

@@ -415,11 +415,12 @@ export async function registryGetTheme(name: string, config: Config) {
if (config.tailwind.cssVariables) {
theme.tailwind.config.theme.extend.colors = {
...theme.tailwind.config.theme.extend.colors,
...buildTailwindThemeColorsFromCssVars(baseColor.cssVars.dark),
...buildTailwindThemeColorsFromCssVars(baseColor.cssVars.dark ?? {}),
}
theme.cssVars = {
theme: {
...theme.cssVars,
...baseColor.cssVars.theme,
...theme.cssVars.theme,
},
light: {
...baseColor.cssVars.light,
@@ -434,7 +435,8 @@ export async function registryGetTheme(name: string, config: Config) {
if (tailwindVersion === "v4" && baseColor.cssVarsV4) {
theme.cssVars = {
theme: {
...theme.cssVars,
...baseColor.cssVarsV4.theme,
...theme.cssVars.theme,
},
light: {
radius: "0.625rem",

View File

@@ -11,10 +11,10 @@ export const registryItemTypeSchema = z.enum([
"registry:hook",
"registry:page",
"registry:file",
"registry:theme",
"registry:style",
// Internal use only
"registry:theme",
"registry:example",
"registry:internal",
])
@@ -53,6 +53,7 @@ export const registryItemCssVarsSchema = z.object({
export const registryItemSchema = z.object({
$schema: z.string().optional(),
extends: z.string().optional(),
name: z.string(),
type: registryItemTypeSchema,
title: z.string().optional(),
@@ -98,16 +99,8 @@ export const registryBaseColorSchema = z.object({
light: z.record(z.string(), z.string()),
dark: z.record(z.string(), z.string()),
}),
cssVars: z.object({
light: z.record(z.string(), z.string()),
dark: z.record(z.string(), z.string()),
}),
cssVarsV4: z
.object({
light: z.record(z.string(), z.string()),
dark: z.record(z.string(), z.string()),
})
.optional(),
cssVars: registryItemCssVarsSchema,
cssVarsV4: registryItemCssVarsSchema.optional(),
inlineColorsTemplate: z.string(),
cssVarsTemplate: z.string(),
})

View File

@@ -32,12 +32,14 @@ export async function addComponents(
overwrite?: boolean
silent?: boolean
isNewProject?: boolean
style?: string
}
) {
options = {
overwrite: false,
silent: false,
isNewProject: false,
style: "index",
...options,
}
@@ -64,12 +66,14 @@ async function addProjectComponents(
overwrite?: boolean
silent?: boolean
isNewProject?: boolean
style?: string
}
) {
const registrySpinner = spinner(`Checking registry.`, {
silent: options.silent,
})?.start()
const tree = await registryResolveItemsTree(components, config)
if (!tree) {
registrySpinner?.fail()
return handleError(new Error("Failed to fetch components from registry."))
@@ -90,6 +94,7 @@ async function addProjectComponents(
tailwindVersion,
tailwindConfig: tree.tailwind?.config,
overwriteCssVars,
initIndex: options.style ? options.style === "index" : false,
})
await updateDependencies(tree.dependencies, config, {
@@ -114,6 +119,7 @@ async function addWorkspaceComponents(
silent?: boolean
isNewProject?: boolean
isRemote?: boolean
style?: string
}
) {
const registrySpinner = spinner(`Checking registry.`, {

View File

@@ -21,6 +21,7 @@ export async function updateCssVars(
options: {
cleanupDefaultNextStyles?: boolean
overwriteCssVars?: boolean
initIndex?: boolean
silent?: boolean
tailwindVersion?: TailwindVersion
tailwindConfig?: z.infer<typeof registryItemTailwindSchema>["config"]
@@ -35,6 +36,7 @@ export async function updateCssVars(
silent: false,
tailwindVersion: "v3",
overwriteCssVars: false,
initIndex: true,
...options,
}
const cssFilepath = config.resolvedPaths.tailwindCss
@@ -54,6 +56,7 @@ export async function updateCssVars(
tailwindVersion: options.tailwindVersion,
tailwindConfig: options.tailwindConfig,
overwriteCssVars: options.overwriteCssVars,
initIndex: options.initIndex,
})
await fs.writeFile(cssFilepath, output, "utf8")
cssVarsSpinner.succeed()
@@ -68,11 +71,13 @@ export async function transformCssVars(
tailwindVersion?: TailwindVersion
tailwindConfig?: z.infer<typeof registryItemTailwindSchema>["config"]
overwriteCssVars?: boolean
initIndex?: boolean
} = {
cleanupDefaultNextStyles: false,
tailwindVersion: "v3",
tailwindConfig: undefined,
overwriteCssVars: false,
initIndex: true,
}
) {
options = {
@@ -80,6 +85,7 @@ export async function transformCssVars(
tailwindVersion: "v3",
tailwindConfig: undefined,
overwriteCssVars: false,
initIndex: true,
...options,
}
@@ -97,7 +103,8 @@ export async function transformCssVars(
const packageInfo = getPackageInfo(config.resolvedPaths.cwd)
if (
!packageInfo?.dependencies?.["tailwindcss-animate"] &&
!packageInfo?.devDependencies?.["tailwindcss-animate"]
!packageInfo?.devDependencies?.["tailwindcss-animate"] &&
options.initIndex
) {
plugins.push(addCustomImport({ params: "tw-animate-css" }))
}
@@ -123,7 +130,7 @@ export async function transformCssVars(
}
}
if (config.tailwind.cssVariables) {
if (config.tailwind.cssVariables && options.initIndex) {
plugins.push(
updateBaseLayerPlugin({ tailwindVersion: options.tailwindVersion })
)

View File

@@ -1,7 +1,10 @@
import { promises as fs } from "fs"
import { tmpdir } from "os"
import path from "path"
import { registryItemTailwindSchema } from "@/src/registry/schema"
import {
registryItemCssVarsSchema,
registryItemTailwindSchema,
} from "@/src/registry/schema"
import { Config } from "@/src/utils/get-config"
import { TailwindVersion } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
@@ -499,7 +502,7 @@ function parseValue(node: any): any {
}
export function buildTailwindThemeColorsFromCssVars(
cssVars: Record<string, string>
cssVars: z.infer<typeof registryItemCssVarsSchema>
) {
const result: Record<string, any> = {}