feat: add docs for base-ui components (#9304)

* feat: add base and radix docs

* feat: transform code for display

* fix

* fix

* fix

* fix

* fix

* chore: remove claude files

* fix

* fix

* fix

* chore: run format:write

* fix

* feat: add more examples

* fix

* feat: add aspect-ratio

* feat: add avatar

* feat: add badge

* feat: add breadcrumb

* fix

* feat: add button

* fix

* fix

* fix

* feat: add calendar and card

* feat: add carousel

* fix: chart

* feat: add checkbox

* feat: add collapsible

* feat: add combobox

* feat: add command

* feat: add context menu

* feat: add data-table dialog and drawer

* feat: dropdown-menu

* feat: add date-picker

* feat: add empty

* feat: add field and hover-card

* fix: input

* feat: add input

* feat: add input-group

* feat: add input-otp

* feat: add item

* feat: add kbd and label

* feat: add menubar

* feat: add native-select

* feat: add more components

* feat: more components

* feat: more components

* feat: add skeleton, slider and sonner

* feat: add spinner and switch

* feat: add more components

* fix: tabs

* fix: tabs

* feat: add docs for sidebar

* fix

* fix

* fi

* docs: update

* fix: create page

* fix

* fix

* chore: add changelog

* fix
This commit is contained in:
shadcn
2026-01-20 19:31:38 +04:00
committed by GitHub
parent 25e88fe4e9
commit 47c47eaed2
2309 changed files with 75081 additions and 8903 deletions

View File

@@ -0,0 +1,72 @@
import { promises as fs } from "fs"
import path from "path"
import { rimraf } from "rimraf"
import { BASES } from "@/registry/bases"
async function buildExamplesIndex() {
const cwd = process.cwd()
const examplesDir = path.join(cwd, "examples")
console.log("📋 Generating examples/__index__.tsx...")
let index = `// @ts-nocheck
// This file is autogenerated by scripts/build-examples-index.mts
// Do not edit this file directly.
import * as React from "react"
export const ExamplesIndex: Record<string, Record<string, any>> = {`
for (const base of BASES) {
const baseDir = path.join(examplesDir, base.name)
try {
await fs.access(baseDir)
} catch {
console.log(` Skipping ${base.name} - directory does not exist`)
continue
}
const allEntries = await fs.readdir(baseDir, { withFileTypes: true })
const files = allEntries
.filter((entry) => entry.isFile() && entry.name.endsWith(".tsx"))
.map((entry) => entry.name)
.sort()
console.log(` Found ${files.length} demos for ${base.name}`)
index += `
"${base.name}": {`
for (const file of files) {
const name = file.replace(/\.tsx$/, "")
const filePath = `examples/${base.name}/${file}`
index += `
"${name}": {
name: "${name}",
filePath: "${filePath}",
component: React.lazy(async () => {
const mod = await import("./${base.name}/${name}")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || "${name}"
return { default: mod.default || mod[exportName] }
}),
},`
}
index += `
},`
}
index += `
}
`
const indexPath = path.join(examplesDir, "__index__.tsx")
rimraf.sync(indexPath)
await fs.writeFile(indexPath, index)
console.log(`\n✅ Generated examples/__index__.tsx`)
}
buildExamplesIndex().catch(console.error)

View File

@@ -37,9 +37,13 @@ try {
const stylesToBuild = getStylesToBuild()
// We only need the legacy styles here for backward compatibility.
// Build index for legacy styles and whitelisted base-style combinations.
console.log(`📦 Building registry/__index__.tsx...`)
await buildRegistryIndex([...legacyStyles])
const stylesForIndex = WHITELISTED_STYLES.map((name) => ({
name,
title: name,
}))
await buildRegistryIndex(stylesForIndex)
console.log("💅 Building styles...")
for (const style of stylesToBuild) {
@@ -54,6 +58,10 @@ try {
console.log("\n⚙ Building public/r/config.json...")
await buildConfig()
// Copy UI to examples before cleanup.
console.log("\n📋 Copying UI to examples...")
await copyUIToExamples()
console.log("\n📋 Building public/r/registries.json...")
await buildRegistriesJson()
@@ -96,6 +104,11 @@ export const Index: Record<string, Record<string, any>> = {`
"${base.name}": {`
for (const item of registry.items) {
// Skip demos - they're handled by the examples index.
if (item.type === "registry:internal") {
continue
}
const files =
item.files?.map((file) => ({
path: typeof file === "string" ? file : file.path,
@@ -169,7 +182,10 @@ async function buildBases(bases: Base[]) {
throw new Error(`Invalid registry schema for ${base.name}`)
}
const registryItems = result.data.items
// Filter out demos - they're handled by the examples index.
const registryItems = result.data.items.filter(
(item) => item.type !== "registry:internal"
)
for (const style of STYLES) {
console.log(`${base.name}-${style.name}...`)
@@ -217,11 +233,25 @@ async function buildBases(bases: Base[]) {
const fileExtension = path.extname(file.path)
const shouldTransform =
fileExtension === ".tsx" || fileExtension === ".ts"
const transformedContent = shouldTransform
? await transformStyle(source, {
styleMap: styleMap,
})
: source
let transformedContent = source
if (shouldTransform) {
// Transform style classes (cn-* -> Tailwind).
transformedContent = await transformStyle(source, {
styleMap: styleMap,
})
// Transform import paths from base to style-specific paths.
// e.g., @/registry/bases/radix/ui/button -> @/registry/radix-nova/ui/button
transformedContent = transformedContent.replace(
new RegExp(
`@/registry/bases/${base.name}/`,
"g"
),
`@/registry/${base.name}-${style.name}/`
)
}
const outputPath = path.join(
process.cwd(),
@@ -264,6 +294,11 @@ export const Index: Record<string, Record<string, any>> = {`
"${style.name}": {`
for (const item of registry.items) {
// Skip demos - they're handled by the examples index.
if (item.type === "registry:internal") {
continue
}
const files =
item.files?.map((file) => ({
path: typeof file === "string" ? file : file.path,
@@ -455,6 +490,46 @@ async function buildConfig() {
})
}
async function copyUIToExamples() {
const defaultStyle = "nova"
const directories = ["ui", "lib", "hooks"]
for (const base of BASES) {
const sourceStyle = `${base.name}-${defaultStyle}`
for (const dir of directories) {
const fromDir = path.join(process.cwd(), `registry/${sourceStyle}/${dir}`)
const toDir = path.join(process.cwd(), `examples/${base.name}/${dir}`)
try {
await fs.access(fromDir)
} catch {
console.log(` ⚠️ registry/${sourceStyle}/${dir} not found, skipping`)
continue
}
rimraf.sync(toDir)
await fs.mkdir(toDir, { recursive: true })
const files = await fs.readdir(fromDir)
for (const file of files) {
const sourcePath = path.join(fromDir, file)
const targetPath = path.join(toDir, file)
let content = await fs.readFile(sourcePath, "utf-8")
content = content.replace(
new RegExp(`@/registry/${sourceStyle}/`, "g"),
`@/examples/${base.name}/`
)
await fs.writeFile(targetPath, content)
}
console.log(
` ✅ registry/${sourceStyle}/${dir} → examples/${base.name}/${dir}`
)
}
}
}
// Build public/r/registries.json from registry/directory.json.
// This generates a slim version without logos for CLI consumption.
async function buildRegistriesJson() {