From 7fbae66d68a717aed5545103347a772cc0599ee5 Mon Sep 17 00:00:00 2001 From: sadman <68962712+sadmann7@users.noreply.github.com> Date: Sun, 20 Sep 2026 20:52:36 +0600 Subject: [PATCH] chore(registry): cleanup styles --- components.json | 2 +- next.config.ts | 19 ++-- package.json | 4 +- public/r/styles/index.json | 32 +++--- registry/bases.ts | 32 ++++++ registry/styles.tsx | 194 +++++++++++++++++++++++++++++++++++++ scripts/build-registry.ts | 47 +++++---- scripts/clean-registry.ts | 14 ++- src/registry/styles.ts | 33 ------- tsconfig.scripts.json | 26 +++++ 10 files changed, 318 insertions(+), 85 deletions(-) create mode 100644 registry/bases.ts create mode 100644 registry/styles.tsx delete mode 100644 src/registry/styles.ts create mode 100644 tsconfig.scripts.json diff --git a/components.json b/components.json index 5233ece6..f71e5f71 100644 --- a/components.json +++ b/components.json @@ -1,6 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-nova", + "style": "new-york", "rsc": true, "tsx": true, "tailwind": { diff --git a/next.config.ts b/next.config.ts index bef86b76..f7bd20e9 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,22 +6,27 @@ import type { NextConfig } from "next"; */ // Import environment validation synchronously import "./src/env.js"; -import { DEFAULT_STYLE_ID, getStyleIds } from "./src/registry/styles"; const nextConfig: NextConfig = { cacheComponents: true, + async redirects() { + return [ + { + source: "/docs/components/:name((?!radix|base)[^/]+)", + destination: "/docs/components/base/:name", + permanent: false, + }, + ]; + }, async rewrites() { return [ - // Published style ids resolve to their static directory. { - source: `/r/:style(${getStyleIds().join("|")})/:name.json`, + source: "/r/:style/:name.json", destination: "/r/styles/:style/:name.json", }, - // Bases we don't author (`aria-*`) and legacy v3 ids fall back to the - // default rather than 404ing on an otherwise valid install. { - source: "/r/:style/:name.json", - destination: `/r/styles/${DEFAULT_STYLE_ID}/:name.json`, + source: "/r/:name.json", + destination: "/r/styles/base-nova/:name.json", }, ]; }, diff --git a/package.json b/package.json index 222a2a2d..1e4abf76 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "typecheck": "tsc --noEmit", "check": "oxlint . && oxfmt --check && tsc --noEmit", "shadcn": "pnpm dlx shadcn@latest", - "build:registry": "tsx scripts/build-registry.ts", - "clean:registry": "tsx scripts/clean-registry.ts", + "build:registry": "tsx --tsconfig ./tsconfig.scripts.json scripts/build-registry.ts", + "clean:registry": "tsx --tsconfig ./tsconfig.scripts.json scripts/clean-registry.ts", "db:start": "docker compose up -d", "db:stop": "docker compose down", "db:generate": "dotenv drizzle-kit generate", diff --git a/public/r/styles/index.json b/public/r/styles/index.json index 40c7daf5..9e12e942 100644 --- a/public/r/styles/index.json +++ b/public/r/styles/index.json @@ -1,50 +1,50 @@ [ { - "name": "radix-nova" + "name": "base-vega" }, { - "name": "radix-vega" + "name": "base-nova" }, { - "name": "radix-maia" + "name": "base-maia" }, { - "name": "radix-lyra" + "name": "base-lyra" }, { - "name": "radix-mira" + "name": "base-mira" }, { - "name": "radix-luma" + "name": "base-luma" }, { - "name": "radix-sera" + "name": "base-sera" }, { - "name": "radix-rhea" + "name": "base-rhea" }, { - "name": "base-nova" + "name": "radix-vega" }, { - "name": "base-vega" + "name": "radix-nova" }, { - "name": "base-maia" + "name": "radix-maia" }, { - "name": "base-lyra" + "name": "radix-lyra" }, { - "name": "base-mira" + "name": "radix-mira" }, { - "name": "base-luma" + "name": "radix-luma" }, { - "name": "base-sera" + "name": "radix-sera" }, { - "name": "base-rhea" + "name": "radix-rhea" } ] diff --git a/registry/bases.ts b/registry/bases.ts new file mode 100644 index 00000000..604514a8 --- /dev/null +++ b/registry/bases.ts @@ -0,0 +1,32 @@ +/** + * @see https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/bases.ts + */ + +import type { RegistryItem } from "shadcn/schema"; + +export const BASES: RegistryItem[] = [ + { + name: "base", + type: "registry:style", + title: "Base UI", + description: + "Components for building accessible web apps and design systems.", + dependencies: ["@base-ui/react"], + meta: { + logo: "", + }, + }, + { + name: "radix", + type: "registry:style", + title: "Radix UI", + description: + "Optimized for fast development, easy maintenance, and accessibility.", + dependencies: ["radix-ui"], + meta: { + logo: "Radix UI", + }, + }, +] as const; + +export type Base = (typeof BASES)[number]; diff --git a/registry/styles.tsx b/registry/styles.tsx new file mode 100644 index 00000000..ad99855a --- /dev/null +++ b/registry/styles.tsx @@ -0,0 +1,194 @@ +/** + * @see https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/styles.tsx + */ + +export const STYLES = [ + { + name: "vega", + title: "Vega", + description: "Clean, neutral, and familiar", + icon: ( + + + + ), + }, + { + name: "nova", + title: "Nova", + description: "Reduced padding and margins", + icon: ( + + + + ), + }, + { + name: "maia", + title: "Maia", + description: "Rounded, with generous spacing.", + icon: ( + + + + ), + }, + { + name: "lyra", + title: "Lyra", + description: "Boxy and sharp. For mono fonts.", + icon: ( + + + + ), + }, + { + name: "mira", + title: "Mira", + description: "Made for compact interfaces.", + icon: ( + + + + ), + }, + { + name: "luma", + title: "Luma", + description: "Fluid, luminous, and soft.", + icon: ( + + + + ), + }, + { + name: "sera", + title: "Sera", + description: "Editorial and typographic.", + icon: ( + + + + ), + }, + { + name: "rhea", + title: "Rhea", + description: "Like Luma but compact.", + icon: ( + + + + ), + }, +] as const; + +export type Style = (typeof STYLES)[number]; diff --git a/scripts/build-registry.ts b/scripts/build-registry.ts index 6a805cd5..77e8ac9f 100644 --- a/scripts/build-registry.ts +++ b/scripts/build-registry.ts @@ -12,6 +12,8 @@ * to its base-tree counterpart when one exists. Files outside the bases (hooks, * lib) and primitives with no base variant stay shared, so base-agnostic code * lives in exactly one place. + * + * @see https://github.com/shadcn-ui/ui/blob/main/apps/v4/scripts/build-registry.mts */ import { execFileSync } from "node:child_process"; import { @@ -26,16 +28,21 @@ import { import { tmpdir } from "node:os"; import path from "node:path"; -import { - type Base, - BASES, - DEFAULT_STYLE_ID, - getStyleIds, - STYLES, -} from "../src/registry/styles"; +import { BASES } from "../registry/bases"; +import { STYLES } from "../registry/styles"; import { OUT_ROOT, STYLES_ROOT, wipeRegistryOutput } from "./clean-registry"; -const DEFAULT_BASE = DEFAULT_STYLE_ID.split("-")[0] as Base; +const DEFAULT_STYLE = "nova"; +const DEFAULT_BASE = "base"; + +const STYLE_COMBINATIONS = BASES.flatMap((base) => + STYLES.map((style) => ({ + base, + style, + name: `${base.name}-${style.name}`, + title: `${base.title} ${style.title}`, + })), +); const ROOT = process.cwd(); const RADIX_TREE = "src/registry/bases/radix/"; @@ -61,8 +68,8 @@ interface Registry { * Repoints a file at the base tree when an override exists there. * `target` is deliberately untouched so consumers get the same paths either way. */ -function resolveFile(file: RegistryFile, base: Base): RegistryFile { - if (base === "radix") return file; +function resolveFile(file: RegistryFile, baseName: string): RegistryFile { + if (baseName === "radix") return file; const override = file.path.replace(RADIX_TREE, BASE_TREE); @@ -103,13 +110,13 @@ function withImportedDependencies( return next.length > 0 ? next : dependencies; } -function buildBase(base: Base, outDir: string) { +function buildBase(baseName: string, outDir: string) { const registry = JSON.parse( readFileSync(path.join(ROOT, "registry.json"), "utf8"), ) as Registry; const items = registry.items.map((item) => { - const files = item.files?.map((file) => resolveFile(file, base)); + const files = item.files?.map((file) => resolveFile(file, baseName)); return { ...item, @@ -143,45 +150,43 @@ function main() { try { for (const base of BASES) { - const baseDir = path.join(scratch, base); + const baseDir = path.join(scratch, base.name); mkdirSync(baseDir, { recursive: true }); - const overrideCount = buildBase(base, baseDir); + const overrideCount = buildBase(base.name, baseDir); const built = path.join(baseDir, "r"); for (const style of STYLES) { - cpSync(built, path.join(STYLES_ROOT, `${base}-${style}`), { + cpSync(built, path.join(STYLES_ROOT, `${base.name}-${style.name}`), { recursive: true, }); } // Flat `/r/{name}.json` keeps serving the default base so existing // DiceUI redirects and style-less installs keep working. - if (base === DEFAULT_BASE) { + if (base.name === DEFAULT_BASE) { cpSync(built, OUT_ROOT, { recursive: true }); } console.log( - `${base}: ${overrideCount} base-tree file(s), ${STYLES.length} style ids`, + `${base.name}: ${overrideCount} base-tree file(s), ${STYLES.length} style ids`, ); } } finally { rmSync(scratch, { recursive: true, force: true }); } - const ids = getStyleIds(); - writeFileSync( path.join(STYLES_ROOT, "index.json"), `${JSON.stringify( - ids.map((name) => ({ name })), + STYLE_COMBINATIONS.map(({ name }) => ({ name })), null, 2, )}\n`, ); console.log( - `\nPublished ${ids.length} style ids (default: ${DEFAULT_BASE}).`, + `\nPublished ${STYLE_COMBINATIONS.length} style ids (default: ${DEFAULT_BASE}-${DEFAULT_STYLE}).`, ); } diff --git a/scripts/clean-registry.ts b/scripts/clean-registry.ts index ad746a8b..f45dfe86 100644 --- a/scripts/clean-registry.ts +++ b/scripts/clean-registry.ts @@ -15,7 +15,8 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { getStyleIds } from "../src/registry/styles"; +import { BASES } from "../registry/bases"; +import { STYLES } from "../registry/styles"; const ROOT = process.cwd(); const OUT_ROOT = path.join(ROOT, "public/r"); @@ -44,10 +45,13 @@ function getExpectedFiles() { files.add(path.join(OUT_ROOT, `${name}.json`)); } - for (const styleId of getStyleIds()) { - files.add(path.join(STYLES_ROOT, styleId, "registry.json")); - for (const name of items) { - files.add(path.join(STYLES_ROOT, styleId, `${name}.json`)); + for (const base of BASES) { + for (const style of STYLES) { + const styleId = `${base.name}-${style.name}`; + files.add(path.join(STYLES_ROOT, styleId, "registry.json")); + for (const name of items) { + files.add(path.join(STYLES_ROOT, styleId, `${name}.json`)); + } } } diff --git a/src/registry/styles.ts b/src/registry/styles.ts deleted file mode 100644 index 5064a3b3..00000000 --- a/src/registry/styles.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * The style ids we publish, shared by the registry build and the Next rewrites so - * the two can't drift. - * - * shadcn composes a style id as `{base}-{style}`. Upstream ships three bases - * (`radix`, `base`, `aria`); we author two of them, so `aria-*` and any legacy - * v3 id (`new-york`, `default`) fall back to the default base at the routing - * layer rather than 404ing. - */ -const BASES = ["radix", "base"] as const; - -const STYLES = [ - "nova", - "vega", - "maia", - "lyra", - "mira", - "luma", - "sera", - "rhea", -] as const; - -type Base = (typeof BASES)[number]; -type Style = (typeof STYLES)[number]; - -/** Must match `style` in components.json; also what flat `/r/{name}.json` serves. */ -const DEFAULT_STYLE_ID = "radix-nova"; - -function getStyleIds() { - return BASES.flatMap((base) => STYLES.map((style) => `${base}-${style}`)); -} - -export { BASES, DEFAULT_STYLE_ID, getStyleIds, STYLES, type Base, type Style }; diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 00000000..f765b39e --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "jsx": "react-jsx", + "skipLibCheck": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "esModuleInterop": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "types": ["node"] + }, + "include": [ + "scripts/**/*.ts", + "scripts/**/*.mts", + "registry/**/*.ts", + "registry/**/*.tsx" + ], + "exclude": ["node_modules"] +}