From 84bd724d97005f98b54d064aed5883b79aac1fd3 Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 29 Oct 2025 15:07:56 +0400 Subject: [PATCH] feat: refactor registry (#8598) * feat: refactor registry * fix: remove components * refactor: getActiveStyle * fix: prettier in build-registry Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix * Update apps/v4/scripts/build-registry.mts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix * Update apps/v4/scripts/build-registry.mts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update apps/v4/components/block-viewer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../app/(app)/blocks/[...categories]/page.tsx | 10 +- apps/v4/app/(app)/blocks/page.tsx | 5 +- apps/v4/app/(app)/charts/[type]/page.tsx | 3 + apps/v4/app/(app)/llm/[[...slug]]/route.ts | 9 +- .../v4/app/(sandbox)/sandbox/[style]/page.tsx | 105 + .../(view)/view/{ => [style]}/[name]/page.tsx | 78 +- apps/v4/components/block-display.tsx | 27 +- apps/v4/components/block-viewer.tsx | 36 +- apps/v4/components/blocks-nav.tsx | 2 +- apps/v4/components/chart-display.tsx | 17 +- apps/v4/components/component-preview.tsx | 15 +- apps/v4/components/component-source.tsx | 9 +- apps/v4/components/theme-customizer.tsx | 6 +- apps/v4/lib/blocks.ts | 26 +- .../categories.ts} | 0 apps/v4/lib/colors.ts | 2 +- apps/v4/lib/llm.ts | 5 +- apps/v4/lib/registry.ts | 31 +- apps/v4/lib/rehype.ts | 5 +- apps/v4/lib/themes.ts | 2 +- .../styles/new-york-v4/form-rhf-password.json | 2 +- .../r/styles/new-york-v4/sheet-side.json | 2 +- .../r/styles/new-york-v4/sidebar-11.json | 2 +- .../styles/new-york-v4/typography-table.json | 2 +- apps/v4/registry/__index__.tsx | 18116 ++++++---------- ...registry-base-colors.ts => base-colors.ts} | 0 .../{registry-colors.ts => colors.ts} | 0 .../registry/{registry-icons.ts => icons.ts} | 0 .../blocks/_registry.ts} | 0 .../charts/_registry.ts} | 0 .../examples/_registry.ts} | 0 .../hooks/_registry.ts} | 0 .../internal/_registry.ts} | 0 .../lib/_registry.ts} | 0 .../{index.ts => new-york-v4/registry.ts} | 16 +- .../ui/_registry.ts} | 0 apps/v4/registry/styles.ts | 14 + .../{registry-themes.ts => themes.ts} | 2 +- apps/v4/scripts/build-registry.mts | 255 +- 39 files changed, 7688 insertions(+), 11116 deletions(-) create mode 100644 apps/v4/app/(sandbox)/sandbox/[style]/page.tsx rename apps/v4/app/(view)/view/{ => [style]}/[name]/page.tsx (52%) rename apps/v4/{registry/registry-categories.ts => lib/categories.ts} (100%) rename apps/v4/registry/{registry-base-colors.ts => base-colors.ts} (100%) rename apps/v4/registry/{registry-colors.ts => colors.ts} (100%) rename apps/v4/registry/{registry-icons.ts => icons.ts} (100%) rename apps/v4/registry/{registry-blocks.ts => new-york-v4/blocks/_registry.ts} (100%) rename apps/v4/registry/{registry-charts.ts => new-york-v4/charts/_registry.ts} (100%) rename apps/v4/registry/{registry-examples.ts => new-york-v4/examples/_registry.ts} (100%) rename apps/v4/registry/{registry-hooks.ts => new-york-v4/hooks/_registry.ts} (100%) rename apps/v4/registry/{registry-internal.ts => new-york-v4/internal/_registry.ts} (100%) rename apps/v4/registry/{registry-lib.ts => new-york-v4/lib/_registry.ts} (100%) rename apps/v4/registry/{index.ts => new-york-v4/registry.ts} (76%) rename apps/v4/registry/{registry-ui.ts => new-york-v4/ui/_registry.ts} (100%) create mode 100644 apps/v4/registry/styles.ts rename apps/v4/registry/{registry-themes.ts => themes.ts} (84%) diff --git a/apps/v4/app/(app)/blocks/[...categories]/page.tsx b/apps/v4/app/(app)/blocks/[...categories]/page.tsx index 9c3b9f227..3cca42607 100644 --- a/apps/v4/app/(app)/blocks/[...categories]/page.tsx +++ b/apps/v4/app/(app)/blocks/[...categories]/page.tsx @@ -1,6 +1,7 @@ import { getAllBlockIds } from "@/lib/blocks" +import { registryCategories } from "@/lib/categories" import { BlockDisplay } from "@/components/block-display" -import { registryCategories } from "@/registry/registry-categories" +import { getActiveStyle } from "@/registry/styles" export const revalidate = false export const dynamic = "force-static" @@ -17,13 +18,16 @@ export default async function BlocksPage({ }: { params: Promise<{ categories?: string[] }> }) { - const { categories = [] } = await params + const [{ categories = [] }, activeStyle] = await Promise.all([ + params, + getActiveStyle(), + ]) const blocks = await getAllBlockIds(["registry:block"], categories) return (
{blocks.map((name) => ( - + ))}
) diff --git a/apps/v4/app/(app)/blocks/page.tsx b/apps/v4/app/(app)/blocks/page.tsx index e9687a962..f79e95d65 100644 --- a/apps/v4/app/(app)/blocks/page.tsx +++ b/apps/v4/app/(app)/blocks/page.tsx @@ -2,6 +2,7 @@ import Link from "next/link" import { BlockDisplay } from "@/components/block-display" import { Button } from "@/registry/new-york-v4/ui/button" +import { getActiveStyle } from "@/registry/styles" export const dynamic = "force-static" export const revalidate = false @@ -15,10 +16,12 @@ const FEATURED_BLOCKS = [ ] export default async function BlocksPage() { + const activeStyle = await getActiveStyle() + return (
{FEATURED_BLOCKS.map((name) => ( - + ))}
diff --git a/apps/v4/app/(app)/charts/[type]/page.tsx b/apps/v4/app/(app)/charts/[type]/page.tsx index 65cdac9ab..28c6a17e8 100644 --- a/apps/v4/app/(app)/charts/[type]/page.tsx +++ b/apps/v4/app/(app)/charts/[type]/page.tsx @@ -3,6 +3,7 @@ import { notFound } from "next/navigation" import { cn } from "@/lib/utils" import { ChartDisplay } from "@/components/chart-display" +import { getActiveStyle } from "@/registry/styles" import { charts } from "@/app/(app)/charts/charts" export const revalidate = false @@ -41,6 +42,7 @@ export default async function ChartPage({ params }: ChartPageProps) { const chartType = type as ChartType const chartList = charts[chartType] + const activeStyle = await getActiveStyle() return (
@@ -54,6 +56,7 @@ export default async function ChartPage({ params }: ChartPageProps) { diff --git a/apps/v4/app/(app)/llm/[[...slug]]/route.ts b/apps/v4/app/(app)/llm/[[...slug]]/route.ts index c5da2bd21..8cbf7705a 100644 --- a/apps/v4/app/(app)/llm/[[...slug]]/route.ts +++ b/apps/v4/app/(app)/llm/[[...slug]]/route.ts @@ -3,6 +3,7 @@ import { NextResponse, type NextRequest } from "next/server" import { processMdxForLLMs } from "@/lib/llm" import { source } from "@/lib/source" +import { getActiveStyle } from "@/registry/styles" export const revalidate = false @@ -10,14 +11,18 @@ export async function GET( _req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> } ) { - const slug = (await params).slug + const [{ slug }, activeStyle] = await Promise.all([params, getActiveStyle()]) + const page = source.getPage(slug) if (!page) { notFound() } - const processedContent = processMdxForLLMs(await page.data.getText("raw")) + const processedContent = processMdxForLLMs( + await page.data.getText("raw"), + activeStyle.name + ) return new NextResponse(processedContent, { headers: { diff --git a/apps/v4/app/(sandbox)/sandbox/[style]/page.tsx b/apps/v4/app/(sandbox)/sandbox/[style]/page.tsx new file mode 100644 index 000000000..9fa89e5db --- /dev/null +++ b/apps/v4/app/(sandbox)/sandbox/[style]/page.tsx @@ -0,0 +1,105 @@ +import { Metadata } from "next" +import { notFound } from "next/navigation" + +import { siteConfig } from "@/lib/config" +import { getRegistryComponent, getRegistryItems } from "@/lib/registry" +import { absoluteUrl, cn } from "@/lib/utils" +import { getStyle, STYLES } from "@/registry/styles" + +export const revalidate = false +export const dynamic = "force-static" +export const dynamicParams = false + +const allowedTypes = ["registry:example"] + +export async function generateMetadata({ + params, +}: { + params: Promise<{ + style: string + }> +}): Promise { + const { style: styleName } = await params + const style = getStyle(styleName) + + if (!style) { + return {} + } + + const title = style.title + + return { + title, + openGraph: { + title, + type: "article", + url: absoluteUrl(`/sandbox/${style.name}`), + images: [ + { + url: siteConfig.ogImage, + width: 1200, + height: 630, + alt: siteConfig.name, + }, + ], + }, + twitter: { + card: "summary_large_image", + title, + images: [siteConfig.ogImage], + creator: "@shadcn", + }, + } +} + +export async function generateStaticParams() { + return STYLES.map((style) => ({ + style: style.name, + })) +} + +export default async function BlockPage({ + params, +}: { + params: Promise<{ + style: string + }> +}) { + const { style: styleName } = await params + const style = getStyle(styleName) + + if (!style) { + return notFound() + } + + const items = await getRegistryItems(style.name, (item) => + allowedTypes.includes(item.type) + ) + + if (items.length === 0) { + return notFound() + } + + return ( + <> +
+ {items + .filter((item) => item !== null) + .map((item) => { + const Component = getRegistryComponent(item.name, style.name) + if (!Component) { + return null + } + return ( +
+ +
+ ) + })} +
+ + ) +} diff --git a/apps/v4/app/(view)/view/[name]/page.tsx b/apps/v4/app/(view)/view/[style]/[name]/page.tsx similarity index 52% rename from apps/v4/app/(view)/view/[name]/page.tsx rename to apps/v4/app/(view)/view/[style]/[name]/page.tsx index 422591ec6..bdd384ba3 100644 --- a/apps/v4/app/(view)/view/[name]/page.tsx +++ b/apps/v4/app/(view)/view/[style]/[name]/page.tsx @@ -2,30 +2,38 @@ import * as React from "react" import { Metadata } from "next" import { notFound } from "next/navigation" -import { registryItemSchema } from "shadcn/schema" -import { z } from "zod" import { siteConfig } from "@/lib/config" import { getRegistryComponent, getRegistryItem } from "@/lib/registry" import { absoluteUrl, cn } from "@/lib/utils" +import { getStyle, STYLES, type Style } from "@/registry/styles" export const revalidate = false export const dynamic = "force-static" export const dynamicParams = false -const getCachedRegistryItem = React.cache(async (name: string) => { - return await getRegistryItem(name) -}) +const getCachedRegistryItem = React.cache( + async (name: string, styleName: Style["name"]) => { + return await getRegistryItem(name, styleName) + } +) export async function generateMetadata({ params, }: { params: Promise<{ + style: string name: string }> }): Promise { - const { name } = await params - const item = await getCachedRegistryItem(name) + const { style: styleName, name } = await params + const style = getStyle(styleName) + + if (!style) { + return {} + } + + const item = await getCachedRegistryItem(name, style.name) if (!item) { return {} @@ -35,13 +43,13 @@ export async function generateMetadata({ const description = item.description return { - title: item.description, + title: item.name, description, openGraph: { title, description, type: "article", - url: absoluteUrl(`/view/${item.name}`), + url: absoluteUrl(`/view/${style.name}/${item.name}`), images: [ { url: siteConfig.ogImage, @@ -63,32 +71,52 @@ export async function generateMetadata({ export async function generateStaticParams() { const { Index } = await import("@/registry/__index__") - const index = z.record(registryItemSchema).parse(Index) + const params: Array<{ style: string; name: string }> = [] - return Object.values(index) - .filter((block) => - [ - "registry:block", - "registry:component", - "registry:example", - "registry:internal", - ].includes(block.type) - ) - .map((block) => ({ - name: block.name, - })) + for (const style of STYLES) { + if (!Index[style.name]) { + continue + } + + const styleIndex = Index[style.name] + for (const itemName in styleIndex) { + const item = styleIndex[itemName] + if ( + [ + "registry:block", + "registry:component", + "registry:example", + "registry:internal", + ].includes(item.type) + ) { + params.push({ + style: style.name, + name: item.name, + }) + } + } + } + + return params } export default async function BlockPage({ params, }: { params: Promise<{ + style: string name: string }> }) { - const { name } = await params - const item = await getCachedRegistryItem(name) - const Component = getRegistryComponent(name) + const { style: styleName, name } = await params + const style = getStyle(styleName) + + if (!style) { + return notFound() + } + + const item = await getCachedRegistryItem(name, style.name) + const Component = getRegistryComponent(name, style.name) if (!item || !Component) { return notFound() diff --git a/apps/v4/components/block-display.tsx b/apps/v4/components/block-display.tsx index f860cff15..00abe037b 100644 --- a/apps/v4/components/block-display.tsx +++ b/apps/v4/components/block-display.tsx @@ -10,9 +10,16 @@ import { import { cn } from "@/lib/utils" import { BlockViewer } from "@/components/block-viewer" import { ComponentPreview } from "@/components/component-preview" +import { type Style } from "@/registry/styles" -export async function BlockDisplay({ name }: { name: string }) { - const item = await getCachedRegistryItem(name) +export async function BlockDisplay({ + name, + styleName, +}: { + name: string + styleName: Style["name"] +}) { + const item = await getCachedRegistryItem(name, styleName) if (!item?.files) { return null @@ -24,9 +31,15 @@ export async function BlockDisplay({ name }: { name: string }) { ]) return ( - + .p-6]:p-0", @@ -37,9 +50,11 @@ export async function BlockDisplay({ name }: { name: string }) { ) } -const getCachedRegistryItem = React.cache(async (name: string) => { - return await getRegistryItem(name) -}) +const getCachedRegistryItem = React.cache( + async (name: string, styleName: Style["name"]) => { + return await getRegistryItem(name, styleName) + } +) const getCachedFileTree = React.cache( async (files: Array<{ path: string; target?: string }>) => { diff --git a/apps/v4/components/block-viewer.tsx b/apps/v4/components/block-viewer.tsx index 5a65b1e7f..509c94ded 100644 --- a/apps/v4/components/block-viewer.tsx +++ b/apps/v4/components/block-viewer.tsx @@ -54,6 +54,7 @@ import { ToggleGroup, ToggleGroupItem, } from "@/registry/new-york-v4/ui/toggle-group" +import { type Style } from "@/registry/styles" type BlockViewerContext = { item: z.infer @@ -128,7 +129,15 @@ function BlockViewerProvider({ ) } -function BlockViewerToolbar() { +type BlockViewerProps = Pick< + BlockViewerContext, + "item" | "tree" | "highlightedFiles" +> & { + children: React.ReactNode + styleName: Style["name"] +} + +function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) { const { setView, view, item, resizablePanelRef, setIframeKey } = useBlockViewer() const { copyToClipboard, isCopied } = useCopyToClipboard() @@ -181,7 +190,7 @@ function BlockViewerToolbar() { asChild title="Open in New Tab" > - + Open in New Tab @@ -222,13 +231,19 @@ function BlockViewerToolbar() { ) } -function BlockViewerIframe({ className }: { className?: string }) { +function BlockViewerIframe({ + className, + styleName, +}: { + className?: string + styleName: Style["name"] +}) { const { item, iframeKey } = useBlockViewer() return (