Compare commits

...

5 Commits

Author SHA1 Message Date
shadcn
69a88f9579 Merge branch 'main' into shadcn/code-format 2026-03-15 12:48:37 +04:00
shadcn
4e8263d7a3 ci: add code-format action 2026-03-15 12:47:01 +04:00
Frank
e000e17856 fix(registry): register next form examples (#10021) 2026-03-15 11:50:47 +04:00
shadcn
1be8f98c46 feat: add namespace validation for registries (#10033) 2026-03-15 11:07:13 +04:00
Copilot
6e476e4756 Rename @hooks registry to @shadcnhooks (#10036)
* Initial plan

* Rename @hooks registry to @shadcnhooks

Co-authored-by: shadcn <124599+shadcn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: shadcn <124599+shadcn@users.noreply.github.com>
2026-03-15 11:06:56 +04:00
7 changed files with 239 additions and 4 deletions

125
.github/workflows/code-format.yml vendored Normal file
View File

@@ -0,0 +1,125 @@
name: Code format
on:
workflow_run:
workflows: ["Code check"]
types:
- completed
permissions:
actions: read
contents: write
pull-requests: read
jobs:
format:
if: |
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
name: Run pnpm format:write
steps:
- name: Inspect failed workflow run
id: meta
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.workflow_run.pull_requests[0]
if (!pr) {
core.notice("Skipping because the failed run is not attached to a pull request.")
core.setOutput("should_run", "false")
return
}
const { data: pullRequest } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
})
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
per_page: 100,
})
const formatJob = jobs.jobs.find((job) => job.name === "pnpm format:check")
const sameRepo =
pullRequest.head.repo.full_name === `${context.repo.owner}/${context.repo.repo}`
const shouldRun = formatJob?.conclusion === "failure" && sameRepo
if (!formatJob) {
core.notice("Skipping because the format job could not be found in the failed run.")
} else if (formatJob.conclusion !== "failure") {
core.notice(
`Skipping because the format job concluded with "${formatJob.conclusion}".`
)
}
if (!sameRepo) {
core.notice(
`Skipping PR #${pullRequest.number} because its head branch lives in ${pullRequest.head.repo.full_name}.`
)
}
core.setOutput("head_ref", pullRequest.head.ref)
core.setOutput("should_run", shouldRun ? "true" : "false")
- name: Checkout pull request branch
if: steps.meta.outputs.should_run == 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.meta.outputs.head_ref }}
- name: Install Node.js
if: steps.meta.outputs.should_run == 'true'
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install pnpm
if: steps.meta.outputs.should_run == 'true'
uses: pnpm/action-setup@v4
with:
version: 9.0.6
run_install: false
- name: Get pnpm store directory
if: steps.meta.outputs.should_run == 'true'
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
if: steps.meta.outputs.should_run == 'true'
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
if: steps.meta.outputs.should_run == 'true'
run: pnpm install
- name: Apply formatting
if: steps.meta.outputs.should_run == 'true'
run: pnpm format:write
- name: Commit formatting changes
if: steps.meta.outputs.should_run == 'true'
run: |
if git diff --quiet; then
echo "No formatting changes to commit."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "style: apply automated formatting"
git push origin HEAD:${{ steps.meta.outputs.head_ref }}

View File

@@ -16,7 +16,7 @@ jobs:
check-registry-sync:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
name: Check registry sync
name: check-registry-sync
permissions:
contents: read
pull-requests: write
@@ -66,6 +66,44 @@ jobs:
with:
node-version: 20
- name: Block reserved registry namespaces
env:
RESERVED_NAMESPACES: "@shadcn,@ui,@blocks,@components,@block,@component,@util,@utils,@registry,@lib,@hook,@hooks,@theme,@themes,@chart,@charts"
run: |
node <<'EOF'
const fs = require("node:fs")
const files = [
"apps/v4/public/r/registries.json",
"apps/v4/registry/directory.json",
]
const reservedNamespaces = new Set(
process.env.RESERVED_NAMESPACES.split(",").filter(Boolean)
)
function readNames(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8")).map(
(entry) => entry.name
)
}
const violations = files.flatMap((filePath) => {
return readNames(filePath)
.filter((name) => reservedNamespaces.has(name))
.map((name) => `${filePath}: ${name}`)
})
if (violations.length > 0) {
console.error("Reserved registry namespaces are not allowed:")
for (const violation of violations) {
console.error(`- ${violation}`)
}
process.exit(1)
}
EOF
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install

View File

@@ -41,7 +41,7 @@
"@gaia": "https://ui.heygaia.io/r/{name}.json",
"@glass-ui": "https://glass-ui.crenspire.com/r/{name}.json",
"@heseui": "https://www.heseui.com/r/{name}.json",
"@hooks": "https://shadcn-hooks.com/r/{name}.json",
"@shadcnhooks": "https://shadcn-hooks.com/r/{name}.json",
"@intentui": "https://intentui.com/r/{name}",
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
"@kanpeki": "https://kanpeki.vercel.app/r/{name}.json",

View File

@@ -252,7 +252,7 @@
"description": "Ready-to-use foundation components/blocks built on top of shadcn/ui."
},
{
"name": "@hooks",
"name": "@shadcnhooks",
"homepage": "https://shadcn-hooks.com",
"url": "https://shadcn-hooks.com/r/{name}.json",
"description": "A comprehensive React Hooks Collection built with Shadcn."

View File

@@ -4888,6 +4888,44 @@ export const Index: Record<string, Record<string, any>> = {
categories: undefined,
meta: undefined,
},
"form-next-demo": {
name: "form-next-demo",
title: "undefined",
description: "",
type: "registry:example",
registryDependencies: ["field","input","textarea","button","card","spinner"],
files: [{
path: "registry/new-york-v4/examples/form-next-demo.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-next-demo.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-next-complex": {
name: "form-next-complex",
title: "undefined",
description: "",
type: "registry:example",
registryDependencies: ["field","input","textarea","button","card","spinner","checkbox","dialog","radio-group","select","switch"],
files: [{
path: "registry/new-york-v4/examples/form-next-complex.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-next-complex.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-demo": {
name: "form-rhf-demo",
title: "undefined",

File diff suppressed because one or more lines are too long

View File

@@ -950,6 +950,40 @@ export const examples: Registry["items"] = [
},
],
},
{
name: "form-next-demo",
type: "registry:example",
registryDependencies: ["field", "input", "textarea", "button", "card", "spinner"],
files: [
{
path: "examples/form-next-demo.tsx",
type: "registry:example",
},
],
},
{
name: "form-next-complex",
type: "registry:example",
registryDependencies: [
"field",
"input",
"textarea",
"button",
"card",
"spinner",
"checkbox",
"dialog",
"radio-group",
"select",
"switch",
],
files: [
{
path: "examples/form-next-complex.tsx",
type: "registry:example",
},
],
},
{
name: "form-rhf-demo",
type: "registry:example",