snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:44:43 +05:00
commit 1416db152c
5727 changed files with 630238 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
import { type Metadata } from "next"
import { notFound } from "next/navigation"
import { getComponent } from "@/examples/__components__"
import { ExamplesIndex } from "@/examples/__index__"
import { siteConfig } from "@/lib/config"
import { absoluteUrl } from "@/lib/utils"
export const dynamicParams = true
export const revalidate = 3600
function getExample(base: string, name: string) {
const item = ExamplesIndex[base]?.[name]
const Component = getComponent(base, name)
if (!item || !Component) {
return null
}
return { item, Component }
}
export async function generateMetadata({
params,
}: {
params: Promise<{ base: string; name: string }>
}): Promise<Metadata> {
const { base, name } = await params
const example = getExample(base, name)
if (!example) {
return {}
}
const title = example.item.name
return {
title,
openGraph: {
title,
type: "article",
url: absoluteUrl(`/examples/${base}/${title}`),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title,
images: [siteConfig.ogImage],
creator: "@shadcn",
},
}
}
export default async function ExamplePage({
params,
}: {
params: Promise<{ base: string; name: string }>
}) {
const { base, name } = await params
const example = getExample(base, name)
if (!example) {
return notFound()
}
const { Component } = example
return <Component />
}

View File

@@ -0,0 +1,149 @@
import * as React from "react"
import { type Metadata } from "next"
import { notFound } from "next/navigation"
import { siteConfig } from "@/lib/config"
import { absoluteUrl } from "@/lib/utils"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { Toaster as BaseToaster } from "@/registry/bases/base/ui/toast"
import { BASES, type Base } from "@/registry/config"
import { ActionMenuScript } from "@/app/(app)/(create)/components/action-menu"
import { DesignSystemProvider } from "@/app/(app)/(create)/components/design-system-provider"
import { HistoryScript } from "@/app/(app)/(create)/components/history-buttons"
import { DarkModeScript } from "@/app/(app)/(create)/components/mode-switcher"
import { OpenPresetScript } from "@/app/(app)/(create)/components/open-preset"
import { PreviewStyle } from "@/app/(app)/(create)/components/preview-style"
import { RandomizeScript } from "@/app/(app)/(create)/components/random-button"
import { getBaseComponent, getBaseItem } from "@/app/(app)/(create)/lib/api"
import "@/app/style-registry.css"
import "streamdown/styles.css"
export const revalidate = false
export const dynamic = "force-static"
export const dynamicParams = true
const STATIC_PREVIEW_ITEMS = ["preview", "preview-02"] as const
function PreventScrollOnFocusScript() {
return (
<script
dangerouslySetInnerHTML={{
__html: `(function(){var f=HTMLElement.prototype.focus;HTMLElement.prototype.focus=function(o){f.call(this,Object.assign({},o,{preventScroll:true}))};})();`,
}}
/>
)
}
const getCacheRegistryItem = React.cache(
async (name: string, base: Base["name"]) => {
return await getBaseItem(name, base)
}
)
const getCachedRegistryComponent = React.cache(
async (name: string, base: Base["name"]) => {
return await getBaseComponent(name, base)
}
)
export async function generateMetadata({
params,
}: {
params: Promise<{
base: string
name: string
}>
}): Promise<Metadata> {
const paramBag = await params
const base = BASES.find((l) => l.name === paramBag.base)
if (!base) {
return {}
}
const item = await getBaseItem(paramBag.name, base.name)
if (!item) {
return {}
}
const title = item.name
const description = item.description
return {
title: item.name,
description,
openGraph: {
title,
description,
type: "article",
url: absoluteUrl(`/preview/${base.name}/${item.name}`),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [siteConfig.ogImage],
creator: "@shadcn",
},
}
}
export async function generateStaticParams() {
return BASES.flatMap((base) =>
STATIC_PREVIEW_ITEMS.map((name) => ({
base: base.name,
name,
}))
)
}
export default async function BlockPage({
params,
}: {
params: Promise<{
base: string
name: string
}>
}) {
const paramBag = await params
const base = BASES.find((l) => l.name === paramBag.base)
if (!base) {
return notFound()
}
const [item, Component] = await Promise.all([
getCacheRegistryItem(paramBag.name, base.name),
getCachedRegistryComponent(paramBag.name, base.name),
])
if (!item || !Component) {
return notFound()
}
return (
<div className="relative bg-background">
<PreventScrollOnFocusScript />
<PreviewStyle />
<ActionMenuScript />
<OpenPresetScript />
<RandomizeScript />
<HistoryScript />
<DarkModeScript />
<DesignSystemProvider>
<Component />
</DesignSystemProvider>
{base.name === "base" && <BaseToaster />}
<TailwindIndicator forceMount />
</div>
)
}

View File

@@ -0,0 +1,21 @@
"use client"
import * as React from "react"
export function PreviewFontVariables({ className }: { className: string }) {
React.useLayoutEffect(() => {
const classNames = className.split(/\s+/).filter(Boolean)
if (!classNames.length) {
return
}
document.documentElement.classList.add(...classNames)
return () => {
document.documentElement.classList.remove(...classNames)
}
}, [className])
return null
}

View File

@@ -0,0 +1,189 @@
import {
DM_Sans,
EB_Garamond,
Figtree,
Geist,
Geist_Mono,
IBM_Plex_Sans,
Instrument_Sans,
Instrument_Serif,
Inter,
JetBrains_Mono,
Lora,
Manrope,
Merriweather,
Montserrat,
Noto_Sans,
Noto_Serif,
Nunito_Sans,
Outfit,
Oxanium,
Playfair_Display,
Public_Sans,
Raleway,
Roboto,
Roboto_Slab,
Source_Sans_3,
Space_Grotesk,
} from "next/font/google"
import { cn } from "cn"
const geistSans = Geist({
subsets: ["latin"],
variable: "--font-geist-sans",
})
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
})
const notoSans = Noto_Sans({
subsets: ["latin"],
variable: "--font-noto-sans",
})
const nunitoSans = Nunito_Sans({
subsets: ["latin"],
variable: "--font-nunito-sans",
})
const figtree = Figtree({
subsets: ["latin"],
variable: "--font-figtree",
})
const roboto = Roboto({
subsets: ["latin"],
variable: "--font-roboto",
})
const raleway = Raleway({
subsets: ["latin"],
variable: "--font-raleway",
})
const dmSans = DM_Sans({
subsets: ["latin"],
variable: "--font-dm-sans",
})
const publicSans = Public_Sans({
subsets: ["latin"],
variable: "--font-public-sans",
})
const outfit = Outfit({
subsets: ["latin"],
variable: "--font-outfit",
})
const oxanium = Oxanium({
subsets: ["latin"],
variable: "--font-oxanium",
})
const manrope = Manrope({
subsets: ["latin"],
variable: "--font-manrope",
})
const spaceGrotesk = Space_Grotesk({
subsets: ["latin"],
variable: "--font-space-grotesk",
})
const montserrat = Montserrat({
subsets: ["latin"],
variable: "--font-montserrat",
})
const ibmPlexSans = IBM_Plex_Sans({
subsets: ["latin"],
variable: "--font-ibm-plex-sans",
})
const sourceSans3 = Source_Sans_3({
subsets: ["latin"],
variable: "--font-source-sans-3",
})
const instrumentSans = Instrument_Sans({
subsets: ["latin"],
variable: "--font-instrument-sans",
})
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
})
const geistMono = Geist_Mono({
subsets: ["latin"],
variable: "--font-geist-mono",
})
const notoSerif = Noto_Serif({
subsets: ["latin"],
variable: "--font-noto-serif",
})
const robotoSlab = Roboto_Slab({
subsets: ["latin"],
variable: "--font-roboto-slab",
})
const merriweather = Merriweather({
subsets: ["latin"],
variable: "--font-merriweather",
})
const lora = Lora({
subsets: ["latin"],
variable: "--font-lora",
})
const playfairDisplay = Playfair_Display({
subsets: ["latin"],
variable: "--font-playfair-display",
})
const ebGaramond = EB_Garamond({
subsets: ["latin"],
variable: "--font-eb-garamond",
})
const instrumentSerif = Instrument_Serif({
subsets: ["latin"],
weight: "400",
variable: "--font-instrument-serif",
})
export const previewFontVariables = cn(
geistSans.variable,
inter.variable,
notoSans.variable,
nunitoSans.variable,
figtree.variable,
roboto.variable,
raleway.variable,
dmSans.variable,
publicSans.variable,
outfit.variable,
oxanium.variable,
manrope.variable,
spaceGrotesk.variable,
montserrat.variable,
ibmPlexSans.variable,
sourceSans3.variable,
instrumentSans.variable,
geistMono.variable,
jetbrainsMono.variable,
notoSerif.variable,
robotoSlab.variable,
merriweather.variable,
lora.variable,
playfairDisplay.variable,
ebGaramond.variable,
instrumentSerif.variable
)

View File

@@ -0,0 +1,15 @@
import { PreviewFontVariables } from "@/app/(view)/preview/font-variables"
import { previewFontVariables } from "@/app/(view)/preview/fonts"
export default function PreviewLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className={previewFontVariables}>
<PreviewFontVariables className={previewFontVariables} />
{children}
</div>
)
}

View File

@@ -0,0 +1,47 @@
import { notFound } from "next/navigation"
import {
AVAILABLE_CONTENT_OPTIONS,
FIXTURES,
} from "@/app/(app)/(typeset)/lib/fixtures"
import { CHAT_QUESTION } from "@/app/(app)/(typeset)/lib/fixtures/chat"
export const dynamicParams = false
export function generateStaticParams() {
return AVAILABLE_CONTENT_OPTIONS.map((option) => ({ name: option.value }))
}
export default async function TypesetFixturePage({
params,
}: {
params: Promise<{ name: string }>
}) {
const { name } = await params
if (!AVAILABLE_CONTENT_OPTIONS.some((option) => option.value === name)) {
notFound()
}
const content = (
<div
className="typeset w-full"
dangerouslySetInnerHTML={{
__html: FIXTURES[name as keyof typeof FIXTURES],
}}
/>
)
if (name === "chat") {
return (
<div className="flex w-full flex-col gap-10">
<div className="ml-auto w-fit max-w-[65%] rounded-3xl bg-muted px-4 py-2.5">
{CHAT_QUESTION}
</div>
{content}
</div>
)
}
return content
}

View File

@@ -0,0 +1,70 @@
import { Suspense } from "react"
import {
DarkModeScript,
HistoryScript,
RandomizeScript,
} from "@/app/(app)/(typeset)/components/forward-scripts"
import { TypesetPreviewProvider } from "@/app/(app)/(typeset)/components/preview-provider"
import { findFont } from "@/app/(app)/(typeset)/lib/fonts"
import {
loadTypesetSearchParams,
TYPESET_MEASURES,
} from "@/app/(app)/(typeset)/lib/search-params"
import "@/app/(app)/(typeset)/typeset.css"
const DEFAULTS = loadTypesetSearchParams(new URLSearchParams())
const DEFAULT_FONT = findFont(DEFAULTS.body)?.value
const DEFAULT_MONO = findFont(DEFAULTS.mono)?.value
const DEFAULT_MEASURE = TYPESET_MEASURES.find(
(option) => option.value === DEFAULTS.measure
)?.width
const PARAMS_STYLE = `.preview-params .typeset {
--typeset-leading: var(--preview-leading, ${DEFAULTS.leading});
--typeset-flow: var(--preview-flow, ${DEFAULTS.flow});
}`
export default function TypesetPreviewLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<DarkModeScript />
<RandomizeScript />
<HistoryScript />
{/* TypesetPreviewProvider reads useSearchParams() via nuqs, which opts
this subtree out of prerendering up to this boundary. */}
<Suspense>
<TypesetPreviewProvider>
<style dangerouslySetInnerHTML={{ __html: PARAMS_STYLE }} />
<div
className="flex min-h-svh justify-center px-6 pt-8 pb-24 md:pt-24 md:pb-32"
style={
{
fontFamily: `var(--preview-font, ${DEFAULT_FONT})`,
"--font-heading": `var(--preview-font-heading, ${DEFAULT_FONT})`,
"--font-mono": `var(--preview-font-mono, ${DEFAULT_MONO})`,
} as React.CSSProperties
}
>
<div
className="preview-params w-full"
style={
{
fontSize: `var(--preview-size, ${DEFAULTS.scale}px)`,
maxWidth: `var(--preview-measure, ${DEFAULT_MEASURE})`,
} as React.CSSProperties
}
>
{children}
</div>
</div>
</TypesetPreviewProvider>
</Suspense>
</>
)
}

View File

@@ -0,0 +1,13 @@
import { cn } from "cn"
export function ComponentPreview({ children }: { children: React.ReactNode }) {
return (
<div
className={cn(
"bg-background *:data-[slot=card]:has-[[data-slot=chart]]:shadow-none"
)}
>
{children}
</div>
)
}

View File

@@ -0,0 +1,188 @@
/* eslint-disable react-hooks/static-components */
import * as React from "react"
import { type Metadata } from "next"
import { notFound } from "next/navigation"
import { siteConfig } from "@/lib/config"
import {
getDemoItem,
getRegistryComponent,
getRegistryItem,
} from "@/lib/registry"
import { absoluteUrl } from "@/lib/utils"
import { getStyle, legacyStyles, type Style } from "@/registry/_legacy-styles"
import "@/app/legacy-themes.css"
import { ComponentPreview } from "./component-preview"
export const revalidate = false
export const dynamic = "force-static"
export const dynamicParams = false
const getCachedRegistryItem = React.cache(
async (name: string, styleName: Style["name"]) => {
// Try registry item first, then fallback to demo item (for examples).
const item = await getRegistryItem(name, styleName)
if (item) {
return item
}
return await getDemoItem(name, styleName)
}
)
export async function generateMetadata({
params,
}: {
params: Promise<{
style: string
name: string
}>
}): Promise<Metadata> {
const { style: styleName, name } = await params
const style = getStyle(styleName)
if (!style) {
return {}
}
const item = await getCachedRegistryItem(name, style.name)
if (!item) {
return {}
}
const title = item.name
const description = item.description
return {
title: item.name,
description,
openGraph: {
title,
description,
type: "article",
url: absoluteUrl(`/view/${style.name}/${item.name}`),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [siteConfig.ogImage],
creator: "@shadcn",
},
}
}
export async function generateStaticParams() {
const { Index } = await import("@/registry/__index__")
// const { Index: BasesIndex } = await import("@/registry/bases/__index__")
const { ExamplesIndex } = await import("@/examples/__index__")
const params: Array<{ style: string; name: string }> = []
for (const style of legacyStyles) {
// Check if this is a base-prefixed style (e.g., base-nova, radix-nova).
const baseMatch = style.name.match(/^(base|radix|aria)-/)
if (baseMatch) {
const baseName = baseMatch[1]
// Add examples from ExamplesIndex.
const examples = ExamplesIndex[baseName]
if (examples) {
for (const exampleName of Object.keys(examples)) {
if (exampleName.startsWith("sidebar-")) {
params.push({
style: style.name,
name: exampleName,
})
}
}
}
// // Add UI components from BasesIndex.
// const baseIndex = BasesIndex[baseName]
// if (baseIndex) {
// for (const itemName in baseIndex) {
// const item = baseIndex[itemName]
// if (
// [
// "registry:block",
// "registry:component",
// "registry:example",
// "registry:internal",
// ].includes(item.type)
// ) {
// params.push({
// style: style.name,
// name: item.name,
// })
// }
// }
// }
continue
}
// Handle legacy styles (e.g., new-york-v4).
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 { 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()
}
return (
<ComponentPreview>
<Component />
</ComponentPreview>
)
}