diff --git a/.changeset/silly-eels-crash.md b/.changeset/silly-eels-crash.md new file mode 100644 index 000000000..4d5142493 --- /dev/null +++ b/.changeset/silly-eels-crash.md @@ -0,0 +1,5 @@ +--- +"shadcn": minor +--- + +add registry add command diff --git a/.github/ISSUE_TEMPLATE/registry_directory.yml b/.github/ISSUE_TEMPLATE/registry_directory.yml deleted file mode 100644 index 544c2af69..000000000 --- a/.github/ISSUE_TEMPLATE/registry_directory.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Add registry to directory -description: Add your registry to the directory -title: "[Registry Directory]: " -labels: ["registry", "directory"] -assignees: [] -body: - - type: input - id: name - attributes: - label: Name - description: The name of your registry. This is also the namespace. - placeholder: e.g., "@acme" - validations: - required: true - - - type: input - id: url - attributes: - label: URL - description: The URL to your registry index. Use {name} placeholder. - placeholder: https://ui.acme.com/r/{name}.json - validations: - required: true - - - type: input - id: homepage - attributes: - label: Homepage - description: The URL to your registry homepage. This is where users can browse your registry. - placeholder: https://ui.acme.com - validations: - required: true - - - type: textarea - id: description - attributes: - label: Description - description: Briefly describe what is your registry and what type of components or code it distributes. - placeholder: - validations: - required: true - - - type: textarea - id: logo - attributes: - label: Logo - description: Add your SVG logo here. - placeholder: - validations: - required: true - - - type: checkboxes - id: requirements - attributes: - label: Checklist - description: Verify that your registry meets the following requirements. - options: - - label: The registry must be open source and publicly accessible. - - label: The registry must be a valid JSON file that conforms to the [registry schema](https://ui.shadcn.com/docs/registry/registry-json) specification. - - label: The `files` array, if present on your registry items, must NOT include a `content` property. - - label: I've attached a square SVG logo to this issue - validations: - required: true diff --git a/apps/v4/components/directory-add-button.tsx b/apps/v4/components/directory-add-button.tsx index 1c8c5ce45..ad39bacd7 100644 --- a/apps/v4/components/directory-add-button.tsx +++ b/apps/v4/components/directory-add-button.tsx @@ -1,13 +1,11 @@ "use client" import * as React from "react" -import Link from "next/link" -import { IconCheck } from "@tabler/icons-react" +import { IconCheck, IconCopy, IconPlus } from "@tabler/icons-react" -import { cn } from "@/lib/utils" -import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard" +import { useConfig } from "@/hooks/use-config" import { useIsMobile } from "@/hooks/use-mobile" -import { CopyButton } from "@/components/copy-button" +import { copyToClipboardWithMeta } from "@/components/copy-button" import { Button } from "@/registry/new-york-v4/ui/button" import { Dialog, @@ -29,77 +27,114 @@ import { DrawerTitle, DrawerTrigger, } from "@/registry/new-york-v4/ui/drawer" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/registry/new-york-v4/ui/tabs" +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/registry/new-york-v4/ui/tooltip" export function DirectoryAddButton({ registry, }: { - registry: { - name: string - url: string - } + registry: { name: string } }) { - const { copyToClipboard, isCopied } = useCopyToClipboard() - const isMobile = useIsMobile() + const [config, setConfig] = useConfig() + const [hasCopied, setHasCopied] = React.useState(false) const [open, setOpen] = React.useState(false) + const isMobile = useIsMobile() - const jsonValue = `{ - "registries": { - "${registry.name}": "${registry.url}" - } -}` + const packageManager = config.packageManager || "pnpm" + + const commands = React.useMemo(() => { + return { + pnpm: `pnpm dlx shadcn@latest registry add ${registry.name}`, + npm: `npx shadcn@latest registry add ${registry.name}`, + yarn: `yarn dlx shadcn@latest registry add ${registry.name}`, + bun: `bunx --bun shadcn@latest registry add ${registry.name}`, + } + }, [registry.name]) + + const command = commands[packageManager] + + React.useEffect(() => { + if (hasCopied) { + const timer = setTimeout(() => setHasCopied(false), 2000) + return () => clearTimeout(timer) + } + }, [hasCopied]) + + const handleCopy = React.useCallback(() => { + copyToClipboardWithMeta(command, { + name: "copy_registry_add_command", + properties: { + command, + registry: registry.name, + }, + }) + setHasCopied(true) + }, [command, registry.name]) const Trigger = ( - ) const Content = ( - <> -
- -
components.json
-
-          
-            {"{"}
-            {'  "registries": {'}
-            {`    "${registry.name}": "${registry.url}"`}
-            {"  }"}
-            {"}"}
-          
-        
-
- + { + setConfig({ + ...config, + packageManager: value as "pnpm" | "npm" | "yarn" | "bun", + }) + }} + className="gap-0 overflow-hidden rounded-lg border" + > +
+ + pnpm + npm + yarn + bun + + + + + + + {hasCopied ? "Copied!" : "Copy command"} + + +
+ {Object.entries(commands).map(([key, cmd]) => ( + +
+
+ {cmd} +
+
+
+ ))} +
) if (isMobile) { @@ -108,20 +143,16 @@ export function DirectoryAddButton({ {Trigger} - Configure MCP + Add Registry - Copy and paste the following code into your project's - components.json. + Run this command to add {registry.name} to your project. -
{Content}
+
{Content}
- + -
@@ -131,22 +162,15 @@ export function DirectoryAddButton({ return ( {Trigger} - e.preventDefault()} - > + - Configure MCP + Add Registry - Copy and paste the following code into your project's - components.json. + Run this command to add {registry.name} to your project. {Content} - - + diff --git a/apps/v4/components/directory-list.tsx b/apps/v4/components/directory-list.tsx index f7064996a..cbfccef82 100644 --- a/apps/v4/components/directory-list.tsx +++ b/apps/v4/components/directory-list.tsx @@ -50,8 +50,10 @@ export function DirectoryList() { href={getHomepageUrl(registry.homepage)} target="_blank" rel="noopener noreferrer external" + className="group flex items-center gap-1" > - {registry.name} + {registry.name}{" "} + {registry.description && ( @@ -61,15 +63,6 @@ export function DirectoryList() { )} - diff --git a/apps/v4/content/docs/(root)/directory.mdx b/apps/v4/content/docs/(root)/directory.mdx index 2655e8f12..95ad24df7 100644 --- a/apps/v4/content/docs/(root)/directory.mdx +++ b/apps/v4/content/docs/(root)/directory.mdx @@ -10,17 +10,17 @@ These registries are built into the CLI with no additional configuration require } - className="gap-2! border-amber-200 bg-amber-50 p-2 font-semibold dark:border-amber-800 dark:bg-amber-900 *:[svg]:translate-y-1" + className="gap-2! border-amber-200 bg-amber-50 p-2 font-semibold dark:border-amber-900 dark:bg-amber-950/80 *:[svg]:translate-y-1" > Community registries are maintained by third-party developers and are not officially curated. Always review code on installation to ensure it meets your security and quality standards. - - Don't see a registry? Learn how to [add it here](/docs/registry/registry-index). + + ## Documentation You can use the `shadcn` CLI to run your own code registry. Running your own registry allows you to distribute your custom components, hooks, pages, config, rules and other files to any project. diff --git a/apps/v4/content/docs/registry/registry-index.mdx b/apps/v4/content/docs/registry/registry-index.mdx index 87b3622e4..d5e726f67 100644 --- a/apps/v4/content/docs/registry/registry-index.mdx +++ b/apps/v4/content/docs/registry/registry-index.mdx @@ -11,9 +11,10 @@ You can see the full list at [https://ui.shadcn.com/r/registries.json](https://u ## Adding a Registry -You can open an issue to add a registry to the index by filling out the [registry directory issue template](https://github.com/shadcn-ui/ui/issues/new?template=registry_directory.yml). +1. Add your registry to [`apps/v4/registry/directory.json`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/directory.json) +2. Create a pull request to https://github.com/shadcn-ui/ui -Once you have submitted your issue, it will be validated and reviewed by the team. +Once you have submitted your request, it will be validated and reviewed by the team. ### Requirements diff --git a/apps/v4/lib/events.ts b/apps/v4/lib/events.ts index 9e0281034..ef37e9bb6 100644 --- a/apps/v4/lib/events.ts +++ b/apps/v4/lib/events.ts @@ -19,6 +19,7 @@ const eventSchema = z.object({ "search_query", "create_app", "copy_create_share_url", + "copy_registry_add_command", ]), // declare type AllowedPropertyValues = string | number | boolean | null properties: z diff --git a/apps/v4/public/r/registries-legacy.json b/apps/v4/public/r/registries-legacy.json new file mode 100644 index 000000000..3708dd6c6 --- /dev/null +++ b/apps/v4/public/r/registries-legacy.json @@ -0,0 +1,137 @@ +{ + "@8bitcn": "https://8bitcn.com/r/{name}.json", + "@8starlabs-ui": "https://ui.8starlabs.com/r/{name}.json", + "@97cn": "https://97cn.itzik.co/r/{name}.json", + "@abstract": "https://build.abs.xyz/r/{name}/json", + "@abui": "https://abui.io/r/{name}.json", + "@aceternity": "https://ui.aceternity.com/registry/{name}.json", + "@aevr": "https://ui.aevr.space/r/{name}.json", + "@ai-blocks": "https://webllm.org/r/{name}.json", + "@ai-elements": "https://registry.ai-sdk.dev/{name}.json", + "@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json", + "@algolia": "https://sitesearch.algolia.com/r/{name}.json", + "@alpine": "https://alpine-registry.vercel.app/r/{name}.json", + "@aliimam": "https://aliimam.in/r/{name}.json", + "@animate-ui": "https://animate-ui.com/r/{name}.json", + "@assistant-ui": "https://r.assistant-ui.com/{name}.json", + "@austin-ui": "https://austin-ui.netlify.app/r/{name}.json", + "@basecn": "https://basecn.dev/r/{name}.json", + "@better-upload": "https://better-upload.com/r/{name}.json", + "@billingsdk": "https://billingsdk.com/r/{name}.json", + "@blocks": "https://blocks.so/r/{name}.json", + "@bucharitesh": "https://bucharitesh.in/r/{name}.json", + "@bundui": "https://bundui.io/r/{name}.json", + "@cardcn": "https://cardcn.dev/r/{name}.json", + "@chamaac": "https://chamaac.com/r/{name}.json", + "@clerk": "https://clerk.com/r/{name}.json", + "@coss": "https://coss.com/ui/r/{name}.json", + "@commercn": "https://commercn.com/r/{name}.json", + "@chisom-ui": "https://chisom-ui.netlify.app/r/{name}.json", + "@creative-tim": "https://www.creative-tim.com/ui/r/{name}.json", + "@cult-ui": "https://cult-ui.com/r/{name}.json", + "@diceui": "https://diceui.com/r/{name}.json", + "@doras-ui": "https://ui.doras.to/r/{name}.json", + "@efferd": "https://efferd.com/r/{name}.json", + "@einui": "https://ui.eindev.ir/r/{name}.json", + "@eldoraui": "https://eldoraui.site/r/{name}.json", + "@elements": "https://tryelements.dev/r/{name}.json", + "@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json", + "@fancy": "https://fancycomponents.dev/r/{name}.json", + "@formcn": "https://formcn.dev/r/{name}.json", + "@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.vercel.app/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", + "@kokonutui": "https://kokonutui.com/r/{name}.json", + "@lens-blocks": "https://lensblocks.com/r/{name}.json", + "@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json", + "@lucide-animated": "https://lucide-animated.com/r/{name}.json", + "@lytenyte": "https://www.1771technologies.com/r/{name}.json", + "@marmelab": "https://marmelab.com/shadcn-admin-kit/r/{name}.json", + "@magicui": "https://magicui.design/r/{name}.json", + "@magicui-pro": "https://pro.magicui.design/registry/{name}", + "@motion-primitives": "https://motion-primitives.com/c/{name}.json", + "@manifest": "https://ui.manifest.build/r/{name}.json", + "@mui-treasury": "https://mui-treasury.com/r/{name}.json", + "@nativeui": "https://nativeui.io/registry/{name}.json", + "@nexus-elements": "https://elements.nexus.availproject.org/r/{name}.json", + "@ncdai": "https://chanhdai.com/r/{name}.json", + "@neobrutalism": "https://www.neobrutalism.dev/r/{name}.json", + "@nuqs": "https://nuqs.dev/r/{name}.json", + "@optics": "https://optics.agusmayol.com.ar/r/{name}.json", + "@oui": "https://oui.mw10013.workers.dev/r/{name}.json", + "@paceui": "https://ui.paceui.com/r/{name}.json", + "@plate": "https://platejs.org/r/{name}.json", + "@prompt-kit": "https://prompt-kit.com/c/{name}.json", + "@prosekit": "https://prosekit.dev/r/{name}.json", + "@phucbm": "https://phucbm.com/r/{name}.json", + "@react-aria": "https://react-aria.adobe.com/registry/{name}.json", + "@react-bits": "https://reactbits.dev/r/{name}.json", + "@react-market": "https://www.react-market.com/get/{name}.json", + "@retroui": "https://retroui.dev/r/{name}.json", + "@reui": "https://reui.io/r/{name}.json", + "@rigidui": "https://rigidui.com/r/{name}.json", + "@roiui": "https://roiui.com/r/{name}.json", + "@solaceui": "https://www.solaceui.com/r/{name}.json", + "@scrollxui": "https://www.scrollxui.dev/registry/{name}.json", + "@systaliko-ui": "https://systaliko-ui.vercel.app/r/{name}.json", + "@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json", + "@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json", + "@shadcn-studio": "https://shadcnstudio.com/r/{name}.json", + "@shadcnblocks": "https://shadcnblocks.com/r/{name}.json", + "@shadcnui-blocks": "https://shadcnui-blocks.com/r/{name}.json", + "@shadcraft": "https://shadcraft-free.vercel.app/r/{name}.json", + "@simple-ai": "https://simple-ai.dev/r/{name}.json", + "@skiper-ui": "https://skiper-ui.com/registry/{name}.json", + "@skyr": "https://ui-play.skyroc.me/r/{name}.json", + "@smoothui": "https://smoothui.dev/r/{name}.json", + "@spectrumui": "https://ui.spectrumhq.in/r/{name}.json", + "@supabase": "https://supabase.com/ui/r/{name}.json", + "@svgl": "https://svgl.app/r/{name}.json", + "@tailark": "https://tailark.com/r/{name}.json", + "@tailwind-admin": "https://tailwind-admin.com/r/{name}.json", + "@tailwind-builder": "https://tailwindbuilder.ai/r/{name}.json", + "@tweakcn": "https://tweakcn.com/r/themes/{name}.json", + "@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json", + "@animbits": "https://animbits.dev/r/{name}.json", + "@wandry-ui": "https://ui.wandry.com.ua/r/{name}.json", + "@wigggle-ui": "https://wigggle-ui.vercel.app/r/{name}.json", + "@paykit-sdk": "https://www.usepaykit.dev/r/{name}.json", + "@pixelact-ui": "https://www.pixelactui.com/r/{name}.json", + "@zippystarter": "https://zippystarter.com/r/{name}.json", + "@shadcndesign": "https://shadcndesign-free.vercel.app/r/{name}.json", + "@ha-components": "https://hacomponents.keshuac.com/r/{name}.json", + "@shadix-ui": "https://shadix-ui.vercel.app/r/{name}.json", + "@utilcn": "https://utilcn.dev/r/{name}.json", + "@hextaui": "https://hextaui.com/r/{name}.json", + "@taki": "https://taki-ui.com/r/{name}.json", + "@square-ui": "https://square.lndev.me/registry/{name}.json", + "@moleculeui": "https://moleculeui.design/r/{name}.json", + "@uicapsule": "https://uicapsule.com/r/{name}.json", + "@uitripled": "https://ui.tripled.work/r/{name}.json", + "@ui-layouts": "https://ui-layouts.com/r/{name}.json", + "@pureui": "https://pure.kam-ui.com/r/{name}.json", + "@tour": "https://onboarding-tour.vercel.app/r/{name}.json", + "@tb-blocks": "https://tailwindbuilder.ai/r/blocks/{name}.json", + "@heroicons-animated": "https://www.heroicons-animated.com/r/{name}.json", + "@icons-animated": "https://icons.lndev.me/r/{name}.json", + "@forgeui": "https://forgeui.in/r/{name}.json", + "@darx": "https://darshitdev.in/r/{name}.json", + "@gamifykit": "https://gamifykit.com/r/{name}.json", + "@beste-ui": "https://ui.beste.co/r/{name}.json", + "@tokenui": "https://www.tokenui.dev/r/{name}.json", + "@lumiui": "https://www.lumiui.dev/r/{name}.json", + "@uselayouts": "https://uselayouts.com/r/{name}.json", + "@joyco": "https://registry.joyco.studio/r/{name}.json", + "@gooseui": "https://gooseui.pro/r/{name}.json", + "@baselayer": "https://www.baselayer.dev/r/{name}.json", + "@jolyui": "https://www.jolyui.dev/r/{name}.json", + "@fab-ui": "https://fab-ui.com/r/{name}.json", + "@asanshay": "https://ds.asanshay.com/r/{name}.json", + "@headcodecms": "https://headcodecms.com/r/{name}.json", + "@typedora-ui": "https://typedora-ui.netlify.app/r/{name}.json", + "@agents-ui": "https://livekit.io/ui/r/{name}.json" +} diff --git a/apps/v4/public/r/registries.json b/apps/v4/public/r/registries.json index 3708dd6c6..c1ad4238a 100644 --- a/apps/v4/public/r/registries.json +++ b/apps/v4/public/r/registries.json @@ -1,137 +1,686 @@ -{ - "@8bitcn": "https://8bitcn.com/r/{name}.json", - "@8starlabs-ui": "https://ui.8starlabs.com/r/{name}.json", - "@97cn": "https://97cn.itzik.co/r/{name}.json", - "@abstract": "https://build.abs.xyz/r/{name}/json", - "@abui": "https://abui.io/r/{name}.json", - "@aceternity": "https://ui.aceternity.com/registry/{name}.json", - "@aevr": "https://ui.aevr.space/r/{name}.json", - "@ai-blocks": "https://webllm.org/r/{name}.json", - "@ai-elements": "https://registry.ai-sdk.dev/{name}.json", - "@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json", - "@algolia": "https://sitesearch.algolia.com/r/{name}.json", - "@alpine": "https://alpine-registry.vercel.app/r/{name}.json", - "@aliimam": "https://aliimam.in/r/{name}.json", - "@animate-ui": "https://animate-ui.com/r/{name}.json", - "@assistant-ui": "https://r.assistant-ui.com/{name}.json", - "@austin-ui": "https://austin-ui.netlify.app/r/{name}.json", - "@basecn": "https://basecn.dev/r/{name}.json", - "@better-upload": "https://better-upload.com/r/{name}.json", - "@billingsdk": "https://billingsdk.com/r/{name}.json", - "@blocks": "https://blocks.so/r/{name}.json", - "@bucharitesh": "https://bucharitesh.in/r/{name}.json", - "@bundui": "https://bundui.io/r/{name}.json", - "@cardcn": "https://cardcn.dev/r/{name}.json", - "@chamaac": "https://chamaac.com/r/{name}.json", - "@clerk": "https://clerk.com/r/{name}.json", - "@coss": "https://coss.com/ui/r/{name}.json", - "@commercn": "https://commercn.com/r/{name}.json", - "@chisom-ui": "https://chisom-ui.netlify.app/r/{name}.json", - "@creative-tim": "https://www.creative-tim.com/ui/r/{name}.json", - "@cult-ui": "https://cult-ui.com/r/{name}.json", - "@diceui": "https://diceui.com/r/{name}.json", - "@doras-ui": "https://ui.doras.to/r/{name}.json", - "@efferd": "https://efferd.com/r/{name}.json", - "@einui": "https://ui.eindev.ir/r/{name}.json", - "@eldoraui": "https://eldoraui.site/r/{name}.json", - "@elements": "https://tryelements.dev/r/{name}.json", - "@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json", - "@fancy": "https://fancycomponents.dev/r/{name}.json", - "@formcn": "https://formcn.dev/r/{name}.json", - "@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.vercel.app/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", - "@kokonutui": "https://kokonutui.com/r/{name}.json", - "@lens-blocks": "https://lensblocks.com/r/{name}.json", - "@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json", - "@lucide-animated": "https://lucide-animated.com/r/{name}.json", - "@lytenyte": "https://www.1771technologies.com/r/{name}.json", - "@marmelab": "https://marmelab.com/shadcn-admin-kit/r/{name}.json", - "@magicui": "https://magicui.design/r/{name}.json", - "@magicui-pro": "https://pro.magicui.design/registry/{name}", - "@motion-primitives": "https://motion-primitives.com/c/{name}.json", - "@manifest": "https://ui.manifest.build/r/{name}.json", - "@mui-treasury": "https://mui-treasury.com/r/{name}.json", - "@nativeui": "https://nativeui.io/registry/{name}.json", - "@nexus-elements": "https://elements.nexus.availproject.org/r/{name}.json", - "@ncdai": "https://chanhdai.com/r/{name}.json", - "@neobrutalism": "https://www.neobrutalism.dev/r/{name}.json", - "@nuqs": "https://nuqs.dev/r/{name}.json", - "@optics": "https://optics.agusmayol.com.ar/r/{name}.json", - "@oui": "https://oui.mw10013.workers.dev/r/{name}.json", - "@paceui": "https://ui.paceui.com/r/{name}.json", - "@plate": "https://platejs.org/r/{name}.json", - "@prompt-kit": "https://prompt-kit.com/c/{name}.json", - "@prosekit": "https://prosekit.dev/r/{name}.json", - "@phucbm": "https://phucbm.com/r/{name}.json", - "@react-aria": "https://react-aria.adobe.com/registry/{name}.json", - "@react-bits": "https://reactbits.dev/r/{name}.json", - "@react-market": "https://www.react-market.com/get/{name}.json", - "@retroui": "https://retroui.dev/r/{name}.json", - "@reui": "https://reui.io/r/{name}.json", - "@rigidui": "https://rigidui.com/r/{name}.json", - "@roiui": "https://roiui.com/r/{name}.json", - "@solaceui": "https://www.solaceui.com/r/{name}.json", - "@scrollxui": "https://www.scrollxui.dev/registry/{name}.json", - "@systaliko-ui": "https://systaliko-ui.vercel.app/r/{name}.json", - "@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json", - "@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json", - "@shadcn-studio": "https://shadcnstudio.com/r/{name}.json", - "@shadcnblocks": "https://shadcnblocks.com/r/{name}.json", - "@shadcnui-blocks": "https://shadcnui-blocks.com/r/{name}.json", - "@shadcraft": "https://shadcraft-free.vercel.app/r/{name}.json", - "@simple-ai": "https://simple-ai.dev/r/{name}.json", - "@skiper-ui": "https://skiper-ui.com/registry/{name}.json", - "@skyr": "https://ui-play.skyroc.me/r/{name}.json", - "@smoothui": "https://smoothui.dev/r/{name}.json", - "@spectrumui": "https://ui.spectrumhq.in/r/{name}.json", - "@supabase": "https://supabase.com/ui/r/{name}.json", - "@svgl": "https://svgl.app/r/{name}.json", - "@tailark": "https://tailark.com/r/{name}.json", - "@tailwind-admin": "https://tailwind-admin.com/r/{name}.json", - "@tailwind-builder": "https://tailwindbuilder.ai/r/{name}.json", - "@tweakcn": "https://tweakcn.com/r/themes/{name}.json", - "@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json", - "@animbits": "https://animbits.dev/r/{name}.json", - "@wandry-ui": "https://ui.wandry.com.ua/r/{name}.json", - "@wigggle-ui": "https://wigggle-ui.vercel.app/r/{name}.json", - "@paykit-sdk": "https://www.usepaykit.dev/r/{name}.json", - "@pixelact-ui": "https://www.pixelactui.com/r/{name}.json", - "@zippystarter": "https://zippystarter.com/r/{name}.json", - "@shadcndesign": "https://shadcndesign-free.vercel.app/r/{name}.json", - "@ha-components": "https://hacomponents.keshuac.com/r/{name}.json", - "@shadix-ui": "https://shadix-ui.vercel.app/r/{name}.json", - "@utilcn": "https://utilcn.dev/r/{name}.json", - "@hextaui": "https://hextaui.com/r/{name}.json", - "@taki": "https://taki-ui.com/r/{name}.json", - "@square-ui": "https://square.lndev.me/registry/{name}.json", - "@moleculeui": "https://moleculeui.design/r/{name}.json", - "@uicapsule": "https://uicapsule.com/r/{name}.json", - "@uitripled": "https://ui.tripled.work/r/{name}.json", - "@ui-layouts": "https://ui-layouts.com/r/{name}.json", - "@pureui": "https://pure.kam-ui.com/r/{name}.json", - "@tour": "https://onboarding-tour.vercel.app/r/{name}.json", - "@tb-blocks": "https://tailwindbuilder.ai/r/blocks/{name}.json", - "@heroicons-animated": "https://www.heroicons-animated.com/r/{name}.json", - "@icons-animated": "https://icons.lndev.me/r/{name}.json", - "@forgeui": "https://forgeui.in/r/{name}.json", - "@darx": "https://darshitdev.in/r/{name}.json", - "@gamifykit": "https://gamifykit.com/r/{name}.json", - "@beste-ui": "https://ui.beste.co/r/{name}.json", - "@tokenui": "https://www.tokenui.dev/r/{name}.json", - "@lumiui": "https://www.lumiui.dev/r/{name}.json", - "@uselayouts": "https://uselayouts.com/r/{name}.json", - "@joyco": "https://registry.joyco.studio/r/{name}.json", - "@gooseui": "https://gooseui.pro/r/{name}.json", - "@baselayer": "https://www.baselayer.dev/r/{name}.json", - "@jolyui": "https://www.jolyui.dev/r/{name}.json", - "@fab-ui": "https://fab-ui.com/r/{name}.json", - "@asanshay": "https://ds.asanshay.com/r/{name}.json", - "@headcodecms": "https://headcodecms.com/r/{name}.json", - "@typedora-ui": "https://typedora-ui.netlify.app/r/{name}.json", - "@agents-ui": "https://livekit.io/ui/r/{name}.json" -} +[ + { + "name": "@8bitcn", + "homepage": "https://www.8bitcn.com", + "url": "https://www.8bitcn.com/r/{name}.json", + "description": "A set of 8-bit styled retro components. Works with your favorite frameworks. Open Source. Open Code." + }, + { + "name": "@8starlabs-ui", + "homepage": "https://ui.8starlabs.com", + "url": "https://ui.8starlabs.com/r/{name}.json", + "description": "A set of beautifully designed components designed for developers who want niche, high-utility UI elements that you won't find in standard libraries." + }, + { + "name": "@abui", + "homepage": "https://abui.io", + "url": "https://abui.io/r/{name}.json", + "description": "A shadcn-compatible registry of reusable components, blocks, and utilities conforming to Vercel's components.build specification" + }, + { + "name": "@abstract", + "homepage": "https://build.abs.xyz", + "url": "https://build.abs.xyz/r/{name}/json", + "description": "A collection of React components for the most common crypto patterns" + }, + { + "name": "@aceternity", + "homepage": "https://ui.aceternity.com", + "url": "https://ui.aceternity.com/registry/{name}.json", + "description": "A modern component library built with Tailwind CSS and Motion for React, Aceternity UI contains unique and interactive components that can make your landing pages look 100x better." + }, + { + "name": "@agents-ui", + "homepage": "https://livekit.io/ui", + "url": "https://livekit.io/ui/r/{name}.json", + "description": "This is a shadcn/ui component registry that distributes copy-paste React components for building LiveKit AI Agent interfaces." + }, + { + "name": "@aevr", + "homepage": "https://ui.aevr.space", + "url": "https://ui.aevr.space/r/{name}.json", + "description": "A small collection of focused, production‑ready components and primitives for React/Next.js projects—built on shadcn/ui and complementary libraries." + }, + { + "name": "@ai-blocks", + "homepage": "https://webllm.org/blocks", + "url": "https://webllm.org/r/{name}.json", + "description": "AI components for the web. No server. No API keys. Built on WebLLM." + }, + { + "name": "@ai-elements", + "homepage": "https://ai-sdk.dev/elements", + "url": "https://registry.ai-sdk.dev/{name}.json", + "description": "Pre-built components like conversations, messages and more to help you build AI-native applications faster." + }, + { + "name": "@algolia", + "homepage": "https://sitesearch.algolia.com", + "url": "https://sitesearch.algolia.com/r/{name}.json", + "description": "Enterprises and developers use Algolia's AI search infrastructure to understand users and show them what they're looking for." + }, + { + "name": "@aliimam", + "homepage": "https://aliimam.in", + "url": "https://aliimam.in/r/{name}.json", + "description": "I create digital experiences that connect and inspire. I build apps, websites, brands, and products end-to-end." + }, + { + "name": "@animate-ui", + "homepage": "https://animate-ui.com", + "url": "https://animate-ui.com/r/{name}.json", + "description": "A fully animated, open-source React component distribution. Browse a list of animated primitives, components and icons you can install and use in your projects." + }, + { + "name": "@assistant-ui", + "homepage": "https://www.assistant-ui.com", + "url": "https://r.assistant-ui.com/{name}.json", + "description": "Radix-style React primitives for AI chat with adapters for AI SDK, LangGraph, Mastra, and custom backends." + }, + { + "name": "@better-upload", + "homepage": "https://better-upload.com", + "url": "https://better-upload.com/r/{name}.json", + "description": "Simple and easy file uploads for React. Upload directly to any S3-compatible service with minimal setup." + }, + { + "name": "@basecn", + "homepage": "https://basecn.dev", + "url": "https://basecn.dev/r/{name}", + "description": "Beautifully crafted shadcn/ui components powered by Base UI" + }, + { + "name": "@billingsdk", + "homepage": "https://billingsdk.com", + "url": "https://billingsdk.com/r/{name}.json", + "description": "BillingSDK is an open-source React and Next.js component library for SaaS billing and payments. It offers ready-to-use, customizable components for subscriptions, invoices, usage-based pricing and billing - fully compatible with Dodo Payments and Stripe." + }, + { + "name": "@blocks", + "homepage": "https://blocks.so", + "url": "https://blocks.so/r/{name}.json", + "description": "A set of clean, modern application building blocks for you in your applications. Free and Open Source" + }, + { + "name": "@bundui", + "homepage": "https://bundui.io", + "url": "https://bundui.io/r/{name}.json", + "description": "A collection of 150+ handcrafted UI components built with Tailwind CSS and shadcn/ui, covering marketing, e-commerce, dashboards, real estate, and more." + }, + { + "name": "@cardcn", + "homepage": "https://cardcn.dev", + "url": "https://cardcn.dev/r/{name}.json", + "description": "A set of beautifully-designed shadcn card components" + }, + { + "name": "@chamaac", + "homepage": "https://chamaac.com", + "url": "https://chamaac.com/r/{name}.json", + "description": "A collection of beautiful, animated components to elevate your web projects instantly." + }, + { + "name": "@clerk", + "homepage": "https://clerk.com/docs/guides/development/shadcn-cli", + "url": "https://clerk.com/r/{name}.json", + "description": "The easiest way to add authentication and user management to your application. Purpose-built for React, Next.js, Remix, and The Modern Web." + }, + { + "name": "@commercn", + "homepage": "https://commercn.com", + "url": "https://commercn.com/r/{name}.json", + "description": "Shadcn UI Blocks for Ecommerce websites" + }, + { + "name": "@coss", + "homepage": "https://coss.com/ui", + "url": "https://coss.com/ui/r/{name}.json", + "description": "A new, modern UI component library built on top of Base UI. Built for developers and AI." + }, + { + "name": "@creative-tim", + "homepage": "https://www.creative-tim.com/ui", + "url": "https://www.creative-tim.com/ui/r/{name}.json", + "description": "A collection of open-source UI components, blocks and AI Agents. Integrate them in v0, Lovable, Claude or in your application." + }, + { + "name": "@cult-ui", + "homepage": "https://www.cult-ui.com", + "url": "https://cult-ui.com/r/{name}.json", + "description": "Cult UI is a rare, curated set of shadcn-compatible, headless and composable components—tastefully animated with Framer Motion." + }, + { + "name": "@diceui", + "homepage": "https://www.diceui.com/", + "url": "https://diceui.com/r/{name}.json", + "description": "Accessible shadcn/ui components built with React, TypeScript, and Tailwind CSS. Copy-paste ready, and customizable." + }, + { + "name": "@doras-ui", + "homepage": "https://ui.doras.to/", + "url": "https://ui.doras.to/r/{name}.json", + "description": "A collection of beautiful, reusable component blocks built with React" + }, + { + "name": "@elements", + "homepage": "https://www.tryelements.dev", + "url": "https://www.tryelements.dev/r/registry.json", + "description": "Full-stack shadcn/ui components that go beyond UI. Add auth, monetization, uploads, and AI to your app in seconds." + }, + { + "name": "@elevenlabs-ui", + "homepage": "https://ui.elevenlabs.io", + "url": "https://ui.elevenlabs.io/r/{name}.json", + "description": "A collection of Open Source agent and audio components that you can customize and extend." + }, + { + "name": "@efferd", + "homepage": "https://efferd.com/", + "url": "https://efferd.com/r/registry.json", + "description": "A collection of beautifully crafted Shadcn/UI blocks, designed to help developers build modern websites with ease." + }, + { + "name": "@einui", + "homepage": "https://ui.eindev.ir", + "url": "https://ui.eindev.ir/r/{name}.json", + "description": "Beautiful, responsive Shadcn components with frosted glass morphism. Built for modern web applications with full dark mode support." + }, + { + "name": "@eldoraui", + "homepage": "https://eldoraui.site", + "url": "https://eldoraui.site/r/{name}.json", + "description": "An open-source, modern UI component library for React, built with TypeScript, Tailwind CSS, and Framer Motion. Eldora UI offers beautifully crafted, reusable components designed for performance and elegance." + }, + { + "name": "@formcn", + "homepage": "https://formcn.dev", + "url": "https://formcn.dev/r/{name}.json", + "description": "Build production-ready forms with a few clicks using shadcn components and modern tools." + }, + { + "name": "@gaia", + "homepage": "https://ui.heygaia.io", + "url": "https://ui.heygaia.io/r/{name}.json", + "description": "Production-ready UI components designed for building beautiful AI assistants and conversational interfaces, from the team behind GAIA." + }, + { + "name": "@glass-ui", + "homepage": "https://glass-ui.crenspire.com", + "url": "https://glass-ui.crenspire.com/r/{name}.json", + "description": "A shadcn-ui compatible registry distributing 40+ glassmorphic React/TypeScript components with Apple-inspired design. Components include enhanced visual effects (glow, shimmer, ripple), theme support, and customizable glassmorphism styling." + }, + { + "name": "@ha-components", + "homepage": "https://hacomponents.keshuac.com", + "url": "https://hacomponents.keshuac.com/r/{name}.json", + "description": "A collection of customisable components to build Home Assistant dashboards." + }, + { + "name": "@hextaui", + "homepage": "https://hextaui.com", + "url": "https://hextaui.com/r/{name}.json", + "description": "Ready-to-use foundation components/blocks built on top of shadcn/ui." + }, + { + "name": "@hooks", + "homepage": "https://shadcn-hooks.vercel.app", + "url": "https://shadcn-hooks.vercel.app/r/{name}.json", + "description": "A comprehensive React Hooks Collection built with Shadcn." + }, + { + "name": "@intentui", + "homepage": "https://intentui.com", + "url": "https://intentui.com/r/{name}", + "description": "Accessible React component library to copy, customize, and own your UI." + }, + { + "name": "@kibo-ui", + "homepage": "https://www.kibo-ui.com/", + "url": "https://www.kibo-ui.com/r/{name}.json", + "description": "Kibo UI is a custom registry of composable, accessible and open source components designed for use with shadcn/ui." + }, + { + "name": "@kanpeki", + "homepage": "https://kanpeki.vercel.app", + "url": "https://kanpeki.vercel.app/r/{name}.json", + "description": "A set of perfect-designed components built on top of React Aria and Motion." + }, + { + "name": "@kokonutui", + "homepage": "https://kokonutui.com", + "url": "https://kokonutui.com/r/{name}.json", + "description": "Collection of stunning components built with Tailwind CSS, shadcn/ui and Motion to use on your websites." + }, + { + "name": "@lens-blocks", + "homepage": "https://lensblocks.com", + "url": "https://lensblocks.com/r/{name}.json", + "description": "A collection of social media components for use with Lens Social Protocol." + }, + { + "name": "@limeplay", + "homepage": "https://limeplay.winoffrg.dev", + "url": "https://limeplay.winoffrg.dev/r/{name}.json", + "description": "Modern UI Library for building media players in React. Powered by Shaka Player." + }, + { + "name": "@lucide-animated", + "homepage": "https://lucide-animated.com", + "url": "https://lucide-animated.com/r/{name}.json", + "description": "An open-source collection of smooth animated lucide icons for your projects" + }, + { + "name": "@lytenyte", + "homepage": "https://www.1771technologies.com", + "url": "https://www.1771technologies.com/r/{name}.json", + "description": "LyteNyte Grid is a high performance, light weight, headless, React data grid. Our registry provides LyteNyte Grid themed using Tailwind and the Shadcn theme variables." + }, + { + "name": "@magicui", + "homepage": "https://magicui.design", + "url": "https://magicui.design/r/{name}", + "description": "UI Library for Design Engineers. 150+ free and open-source animated components and effects built with React, Typescript, Tailwind CSS, and Motion. Perfect companion for shadcn/ui." + }, + { + "name": "@manifest", + "homepage": "https://ui.manifest.build", + "url": "https://ui.manifest.build/r/{name}.json", + "description": "Agentic UI toolkit for building MCP Apps. Open-source components and blocks ready to use within your chat app." + }, + { + "name": "@mui-treasury", + "homepage": "https://www.mui-treasury.com", + "url": "https://mui-treasury.com/r/{name}.json", + "description": "A collection of hand-crafted interfaces built on top of MUI components" + }, + { + "name": "@moleculeui", + "homepage": "https://www.moleculeui.design/", + "url": "https://www.moleculeui.design/r/{name}.json", + "description": "A modern React component library focused on intuitive interactions and seamless user experiences." + }, + { + "name": "@motion-primitives", + "homepage": "https://www.motion-primitives.com", + "url": "https://motion-primitives.com/c/registry.json", + "description": "Beautifully designed motions components. Easy copy-paste. Customizable. Open Source. Built for engineers and designers." + }, + { + "name": "@ncdai", + "homepage": "https://chanhdai.com/components", + "url": "https://chanhdai.com/r/{name}.json", + "description": "A collection of reusable components." + }, + { + "name": "@nuqs", + "homepage": "https://nuqs.dev/registry", + "url": "https://nuqs.dev/r/{name}.json", + "description": "Custom parsers, adapters and utilities from the community for type-safe URL state management." + }, + { + "name": "@neobrutalism", + "homepage": "https://www.neobrutalism.dev", + "url": "https://www.neobrutalism.dev/r/{name}.json", + "description": "A collection of neobrutalism-styled components based on shadcn/ui" + }, + { + "name": "@nexus-elements", + "homepage": "https://elements.nexus.availproject.org/docs/view-components", + "url": "https://elements.nexus.availproject.org/r/{name}.json", + "description": "Ready-made React components for almost any use case. Use as is or customise and go to market fast" + }, + { + "name": "@optics", + "homepage": "https://optics.agusmayol.com.ar", + "url": "https://optics.agusmayol.com.ar/r/{name}.json", + "description": "A design system that distributes re-styled components, utilities, and hooks ready to use." + }, + { + "name": "@oui", + "homepage": "https://oui.mw10013.workers.dev", + "url": "https://oui.mw10013.workers.dev/r/{name}.json", + "description": "React Aria Components with shadcn characteristics.Copy-and-paste react aria components that run side-by-side with shadcn components." + }, + { + "name": "@paceui", + "homepage": "https://ui.paceui.com", + "url": "https://ui.paceui.com/r/{name}.json", + "description": "Animated components and building blocks built for smooth interaction and rich detail. Copy, customise, and create without the extra setup." + }, + { + "name": "@paykit-sdk", + "homepage": "https://www.usepaykit.dev", + "url": "https://www.usepaykit.dev/r/{name}.json", + "description": "Unified payments SDK for builders — handle checkout, billing, and webhooks across Stripe, PayPal, Adyen, and regional gateways with a single integration." + }, + { + "name": "@plate", + "homepage": "https://platejs.org", + "url": "https://platejs.org/r/{name}.json", + "description": "AI-powered rich text editor for React." + }, + { + "name": "@prompt-kit", + "homepage": "https://www.prompt-kit.com", + "url": "https://www.prompt-kit.com/c/registry.json", + "description": "Core building blocks for AI apps. High-quality, accessible, and customizable components for AI interfaces." + }, + { + "name": "@prosekit", + "homepage": "https://prosekit.dev", + "url": "https://prosekit.dev/r/{name}.json", + "description": "Powerful and flexible rich text editor for React, Vue, Preact, Svelte, and SolidJS." + }, + { + "name": "@phucbm", + "homepage": "https://phucbm.com/components", + "url": "https://phucbm.com/r/{name}.json", + "description": "A collection of modern React UI components with GSAP animations." + }, + { + "name": "@react-aria", + "homepage": "https://react-aria.adobe.com", + "url": "https://react-aria.adobe.com/registry/{name}.json", + "description": "Customizable Tailwind and Vanilla CSS components with adaptive interactions, top-tier accessibility, and internationalization." + }, + { + "name": "@react-bits", + "homepage": "https://reactbits.dev", + "url": "https://reactbits.dev/r/{name}.json", + "description": "A large collection of animated, interactive & fully customizable React components for building memorable websites. From smooth text animations all the way to eye-catching backgrounds, you can find it here." + }, + { + "name": "@retroui", + "homepage": "https://retroui.dev", + "url": "https://retroui.dev/r/{name}.json", + "description": "A Neobrutalism styled React + TailwindCSS UI library for building bold, modern web apps. Perfect for any project using Shadcn/ui." + }, + { + "name": "@reui", + "homepage": "https://reui.io", + "url": "https://reui.io/r/{name}.json", + "description": "Open-source collection of UI components and animated effects built with React, Typescript, Tailwind CSS, and Motion. Pairs beautifully with shadcn/ui." + }, + { + "name": "@scrollxui", + "homepage": "https://www.scrollxui.dev", + "url": "https://www.scrollxui.dev/registry/{name}.json", + "description": "ScrollX UI is an open-source React and shadcn-compatible component library for animated, interactive, and customizable user interfaces. It offers motion-driven components that blend seamlessly with modern ShadCN setups." + }, + { + "name": "@square-ui", + "homepage": "https://square.lndev.me", + "url": "https://square.lndev.me/registry/{name}.json", + "description": "Collection of beautifully crafted open-source layouts UI built with shadcn/ui." + }, + { + "name": "@systaliko-ui", + "homepage": "https://systaliko-ui.vercel.app", + "url": "https://systaliko-ui.vercel.app/r/{name}.json", + "description": "UI component library, Designed for flexibility, built for customization, and crafted to scale across variants and use cases." + }, + { + "name": "@roiui", + "homepage": "https://roiui.com", + "url": "https://roiui.com/r/{name}.json", + "description": "Roi UI is a library that offers UI components and blocks built with Base UI primitives. Some blocks and components use motion (framer). Everything is open-source and will be forever." + }, + { + "name": "@solaceui", + "homepage": "https://www.solaceui.com", + "url": "https://www.solaceui.com/r/{name}.json", + "description": "Production-ready and tastefully crafted sections, animated components, and full-page templates for Next.js, Tailwind CSS & Motion" + }, + { + "name": "@shadcnblocks", + "homepage": "https://shadcnblocks.com", + "url": "https://shadcnblocks.com/r/{name}.json", + "description": "A registry with hundreds of extra blocks for shadcn ui." + }, + { + "name": "@shadcndesign", + "homepage": "https://www.shadcndesign.com", + "url": "https://shadcndesign-free.vercel.app/r/{name}.json", + "description": "A growing collection of high-quality blocks and themes for shadcn/ui." + }, + { + "name": "@shadcn-map", + "homepage": "https://shadcn-map.vercel.app", + "url": "http://shadcn-map.vercel.app/r/{name}.json", + "description": "A map component for shadcn/ui. Built with Leaflet and React Leaflet." + }, + { + "name": "@shadcn-studio", + "homepage": "https://shadcnstudio.com", + "url": "https://shadcnstudio.com/r/{name}.json", + "description": "An open-source set of shadcn/ui components, blocks, and templates with a powerful theme generator." + }, + { + "name": "@shadcn-editor", + "homepage": "https://shadcn-editor.vercel.app", + "url": "https://shadcn-editor.vercel.app/r/{name}.json", + "description": "Accessible, Customizable, Rich Text Editor. Made with Lexical and Shadcn/UI. Open Source. Open Code." + }, + { + "name": "@shadcnui-blocks", + "homepage": "https://shadcnui-blocks.com", + "url": "https://shadcnui-blocks.com/r/{name}.json", + "description": "A collection of premium, production-ready shadcn/ui blocks, components and templates." + }, + { + "name": "@shadcraft", + "homepage": "https://shadcraft-free.vercel.app", + "url": "https://shadcraft-free.vercel.app/r/{name}.json", + "description": "A collection of polished shadcn/ui components and marketing blocks built to production standards. Fast to use, easy to extend, and ready for any modern web project." + }, + { + "name": "@smoothui", + "homepage": "https://smoothui.dev", + "url": "https://smoothui.dev/r/{name}.json", + "description": "A collection of beautifully crafted motion components built with React, Framer Motion, and TailwindCSS. Designed to elevate microinteractions, each component focuses on smooth animations, subtle feedback, and delightful UX. Perfect for designers and developers who want to add refined motion to their interfaces — copy, paste, and make your UI come alive." + }, + { + "name": "@spectrumui", + "homepage": "https://ui.spectrumhq.in", + "url": "https://ui.spectrumhq.in/r/{name}.json", + "description": "A modern component library built with shadcn/ui and Tailwind CSS. Spectrum UI offers elegant, responsive components and smooth animations designed for high-quality interfaces." + }, + { + "name": "@supabase", + "homepage": "https://supabase.com/ui", + "url": "https://supabase.com/ui/r/{name}.json", + "description": "A collection of React components and blocks built on the shadcn/ui library that connect your front-end to your Supabase back-end via a single command." + }, + { + "name": "@svgl", + "homepage": "https://svgl.app", + "url": "https://svgl.app/r/{name}.json", + "description": "A beautiful library with SVG logos." + }, + { + "name": "@tailark", + "homepage": "https://tailark.com", + "url": "https://tailark.com/r/{name}.json", + "description": "Shadcn blocks designed for building modern marketing websites." + }, + { + "name": "@taki", + "homepage": "https://taki-ui.com", + "url": "https://taki-ui.com/r/{name}.json", + "description": "Beautifully designed, accessible components that you can copy and paste into your apps. Made with React Aria Components and Shadcn tokens." + }, + { + "name": "@tour", + "homepage": "https://onboarding-tour.vercel.app", + "url": "https://onboarding-tour.vercel.app/r/{name}.json", + "description": "A component for building onboarding tours. Designed to integrate with shadcn/ui." + }, + { + "name": "@uitripled", + "homepage": "https://ui.tripled.work", + "url": "https://ui.tripled.work/r/{name}.json", + "description": "An open-source, Production-ready UI components and blocks powered by shadcn/ui and Framer Motion" + }, + { + "name": "@utilcn", + "homepage": "https://utilcn.dev", + "url": "https://utilcn.dev/r/{name}.json", + "description": "Fullstack registry items to start those big features. Utilcn has ChatGPT Apps, file uploading (with progress bars) and downloading, and a way to make your env vars typesafe on the backend." + }, + { + "name": "@wandry-ui", + "homepage": "http://ui.wandry.com.ua/", + "url": "https://ui.wandry.com.ua/r/{name}.json", + "description": "A set of open source fully controlled React Inertia form elements" + }, + { + "name": "@wigggle-ui", + "homepage": "https://wigggle-ui.vercel.app", + "url": "https://wigggle-ui.vercel.app/r/{name}.json", + "description": "A beautiful collection of copy-and-paste widgets for your next project." + }, + { + "name": "@zippystarter", + "homepage": "https://zippystarter.com", + "url": "https://zippystarter.com/r/{name}.json", + "description": "Expertly crafted blocks, components & themes for shadcn/ui." + }, + { + "name": "@uicapsule", + "homepage": "https://uicapsule.com", + "url": "https://uicapsule.com/r/{name}.json", + "description": "A curated collection of components that spark joy. Featuring interactive concepts, design experiments, and components in the intersection of AI/UI." + }, + { + "name": "@ui-layouts", + "homepage": "https://ui-layouts.com/", + "url": "https://ui-layouts.com/r/{name}.json", + "description": "UI Layouts offers components, effects, design tools, and ready-made blocks that make building modern interfaces more efficient—built with React, Next.js, Tailwind CSS, and shadcn/ui." + }, + { + "name": "@pureui", + "homepage": "https://pure.kam-ui.com/", + "url": "https://pure.kam-ui.com/r/{name}.json", + "description": "Pure UI is a curated collection of refined, animated, and accessible components built with Base UI, Tailwind CSS, Motion, and other high-quality open source libraries." + }, + { + "name": "@tailwind-builder", + "homepage": "https://tailwindbuilder.ai/", + "url": "https://tailwindbuilder.ai/r/{name}.json", + "description": "Tailwind Builder is a collection of free ui blocks and components and provide ai tools to generate production-ready forms, tables, and charts in seconds. Built with React, Next.js, Tailwind & ShadCN." + }, + { + "name": "@tailwind-admin", + "homepage": "https://tailwind-admin.com/", + "url": "https://tailwind-admin.com/r/{name}.json", + "description": "Tailwind Builder provides free tailwind admin dashboard templates, components and ui-blocks built with React, Next.js, Tailwind CSS, and shadcn/ui to help you build admin panels quickly and efficiently." + }, + { + "name": "@forgeui", + "homepage": "https://forgeui.in/", + "url": "https://forgeui.in/r/{name}.json", + "description": "Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source." + }, + { + "name": "@skiper-ui", + "homepage": "https://skiper-ui.com/", + "url": "https://skiper-ui.com/registry/{name}.json", + "description": "Brand new uncommon components for your Next.js project. Use with ease through shadcn CLI 3.0, featuring fast-growing components and collections that are easy to edit and use." + }, + { + "name": "@animbits", + "homepage": "https://animbits.dev", + "url": "https://animbits.dev/r/{name}.json", + "description": "AnimBits is a collection animated UI components for React that use Framer Motion. The components provided include buttons, cards, text, icons, lists, loaders, and page transitions, animation hooks all of which have general-purpose effects that are not flashy and easy on the eyes, making them easy to use." + }, + { + "name": "@icons-animated", + "homepage": "https://icons.lndev.me", + "url": "https://icons.lndev.me/r/{name}.json", + "description": "An open-source library of meticulously animated icons (Tabler, Phosphor, and more) for your projects, inspired by lucide-animated.com" + }, + { + "name": "@heroicons-animated", + "homepage": "https://www.heroicons-animated.com/", + "url": "https://www.heroicons-animated.com/r/{name}.json", + "description": "An open-source collection of 316 beautifully animated heroicons for your projects." + }, + { + "name": "@darx", + "homepage": "https://darshitdev.in/arts", + "url": "https://darshitdev.in/r/{name}.json", + "description": "Magic 3D Tabs component featuring mouse-interactive 3D rotation, floating particles background effect, and smooth spring animations." + }, + { + "name": "@beste-ui", + "homepage": "https://ui.beste.co", + "url": "https://ui.beste.co/r/registry.json", + "description": "Production-ready UI blocks for landing pages, dashboards, and web apps." + }, + { + "name": "@tokenui", + "homepage": "https://www.tokenui.dev", + "url": "https://www.tokenui.dev/r/{name}.json", + "description": "Beautiful, interactive documentation components for your design tokens following industry standards." + }, + { + "name": "@lumiui", + "homepage": "https://www.lumiui.dev", + "url": "https://www.lumiui.dev/r/{name}.json", + "description": "Composable React components powered by Base UI and Tailwind CSS — Build fast, customize everything." + }, + { + "name": "@uselayouts", + "homepage": "https://uselayouts.com", + "url": "https://uselayouts.com/r/{name}.json", + "description": "A collection of premium animated React components and micro-interactions built with Motion for building fluid, professional interfaces." + }, + { + "name": "@joyco", + "homepage": "https://registry.joyco.studio", + "url": "https://registry.joyco.studio/r/{name}.json", + "description": "Components including MobileMenu, ScrollArea with gradients, Chat UI, HLSVideoPlayer, and Marquee." + }, + { + "name": "@gooseui", + "homepage": "https://gooseui.pro", + "url": "https://gooseui.pro/r/{name}.json", + "description": "Open source component library with animated components, beautiful effects, and custom toast notifications. Built with Radix UI and Tailwind CSS." + }, + { + "name": "@baselayer", + "homepage": "https://www.baselayer.dev", + "url": "https://www.baselayer.dev/r/{name}.json", + "description": "A collection of components built on React Aria, Tailwind CSS, and tailwind-variants." + }, + { + "name": "@jolyui", + "homepage": "https://www.jolyui.dev", + "url": "https://www.jolyui.dev/r/{name}.json", + "description": "JolyUI is a modern React component library built with TypeScript and Tailwind CSS." + }, + { + "name": "@fab-ui", + "homepage": "https://fab-ui.com", + "url": "https://fab-ui.com/r/{name}.json", + "description": "A collection of beautifully designed UI components for building modern web applications." + }, + { + "name": "@asanshay", + "homepage": "https://ds.asanshay.com", + "url": "https://ds.asanshay.com/r/{name}.json", + "description": "Clean, beautiful, and simple UI primitives and AI elements." + }, + { + "name": "@headcodecms", + "homepage": "https://headcodecms.com", + "url": "https://headcodecms.com/r/{name}.json", + "description": "A Minimalistic Web CMS for Next.js, optimized for Cache Components." + }, + { + "name": "@typedora-ui", + "homepage": "https://typedora-ui.netlify.app", + "url": "https://typedora-ui.netlify.app/r/{name}.json", + "description": "Typedora UI is a next-generation extension layer for shadcn/ui, designed to bring full type-safety to your UI components." + } +] diff --git a/apps/v4/scripts/build-registry.mts b/apps/v4/scripts/build-registry.mts index 7592b91dc..80be0111f 100644 --- a/apps/v4/scripts/build-registry.mts +++ b/apps/v4/scripts/build-registry.mts @@ -54,6 +54,9 @@ try { console.log("\n⚙️ Building public/r/config.json...") await buildConfig() + console.log("\n📋 Building public/r/registries.json...") + await buildRegistriesJson() + // Clean up intermediate files and generated base directories. console.log("\n🧹 Cleaning up...") await cleanUp(stylesToBuild) @@ -451,3 +454,42 @@ async function buildConfig() { }) }) } + +// Build public/r/registries.json from registry/directory.json. +// This generates a slim version without logos for CLI consumption. +async function buildRegistriesJson() { + // Read the source directory.json. + const directoryPath = path.join(process.cwd(), "registry/directory.json") + const directoryContent = await fs.readFile(directoryPath, "utf8") + const directory = JSON.parse(directoryContent) as Array<{ + name: string + homepage?: string + url: string + description?: string + featured?: boolean + logo?: string + }> + + // Transform to slim format (without logos and featured). + const registries = directory.map((entry) => ({ + name: entry.name, + homepage: entry.homepage, + url: entry.url, + description: entry.description, + })) + + // Write to public/r/registries.json. + const outputPath = path.join(process.cwd(), "public/r/registries.json") + await fs.writeFile(outputPath, JSON.stringify(registries, null, 2)) + + // Format with prettier. + await new Promise((resolve, reject) => { + execFile("prettier", ["--write", outputPath], (error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) + }) +} diff --git a/packages/shadcn/src/commands/registry/add.ts b/packages/shadcn/src/commands/registry/add.ts new file mode 100644 index 000000000..773c1f41c --- /dev/null +++ b/packages/shadcn/src/commands/registry/add.ts @@ -0,0 +1,247 @@ +import path from "path" +import { getRegistries } from "@/src/registry/api" +import { BUILTIN_REGISTRIES } from "@/src/registry/constants" +import { handleError } from "@/src/utils/handle-error" +import { highlighter } from "@/src/utils/highlighter" +import { logger } from "@/src/utils/logger" +import { spinner } from "@/src/utils/spinner" +import { Command } from "commander" +import fs from "fs-extra" +import prompts from "prompts" +import { z } from "zod" + +const addOptionsSchema = z.object({ + cwd: z.string(), + silent: z.boolean(), +}) + +export const add = new Command() + .name("add") + .description("add registries to your project") + .argument( + "[registries...]", + "registries (@namespace) or registry URLs (@namespace=url)" + ) + .option( + "-c, --cwd ", + "the working directory. defaults to the current directory.", + process.cwd() + ) + .option("-s, --silent", "mute output.", false) + .action(async (registries: string[], opts) => { + try { + const options = addOptionsSchema.parse({ + cwd: path.resolve(opts.cwd), + silent: opts.silent, + }) + + const registryArgs = + registries.length > 0 + ? registries + : await promptForRegistries({ silent: options.silent }) + + await addRegistriesToConfig(registryArgs, options.cwd, { + silent: options.silent, + }) + } catch (error) { + logger.break() + handleError(error) + } + }) + +export function parseRegistryArg(arg: string): { + namespace: string + url?: string +} { + const [namespace, ...rest] = arg.split("=") + const url = rest.length > 0 ? rest.join("=") : undefined + + if (!namespace.startsWith("@")) { + throw new Error( + `Invalid registry namespace: ${highlighter.info(namespace)}. ` + + `Registry names must start with @ (e.g., @acme).` + ) + } + + return { namespace, url } +} + +function pluralize(count: number, singular: string, plural: string) { + return `${count} ${count === 1 ? singular : plural}` +} + +async function addRegistriesToConfig( + registryArgs: string[], + cwd: string, + options: { silent?: boolean } +) { + const configPath = path.resolve(cwd, "components.json") + if (!fs.existsSync(configPath)) { + throw new Error( + `No ${highlighter.info("components.json")} found. Run ${highlighter.info( + "shadcn init" + )} first.` + ) + } + + const parsed = registryArgs.map(parseRegistryArg) + const needsLookup = parsed.filter((p) => !p.url) + let registriesIndex: { name: string; url: string }[] = [] + if (needsLookup.length > 0) { + const fetchSpinner = spinner("Fetching registries.", { + silent: options.silent, + }).start() + const registries = await getRegistries() + if (!registries) { + fetchSpinner.fail() + throw new Error("Failed to fetch registries.") + } + fetchSpinner.succeed() + registriesIndex = registries + } + + const registriesToAdd: Record = {} + for (const { namespace, url } of parsed) { + if (namespace in BUILTIN_REGISTRIES) { + logger.warn( + `${highlighter.info( + namespace + )} is a built-in registry and cannot be added.` + ) + continue + } + + if (url) { + if (!url.includes("{name}")) { + throw new Error( + `Invalid registry URL for ${highlighter.info( + namespace + )}. URL must include {name} placeholder. Example: ${highlighter.info( + `${namespace}=https://example.com/r/{name}.json` + )}` + ) + } + registriesToAdd[namespace] = url + } else { + const registry = registriesIndex.find((r) => r.name === namespace) + if (!registry) { + throw new Error( + `Registry ${highlighter.info(namespace)} not found. ` + + `Provide a URL: ${highlighter.info( + `${namespace}=https://.../{name}.json` + )}` + ) + } + registriesToAdd[namespace] = registry.url + } + } + + if (Object.keys(registriesToAdd).length === 0) { + return { addedRegistries: [] } + } + + const existingConfig = await fs.readJson(configPath) + const existingRegistries = existingConfig.registries || {} + const newRegistries: Record = {} + const skipped: string[] = [] + for (const [ns, url] of Object.entries(registriesToAdd)) { + if (existingRegistries[ns]) { + skipped.push(ns) + } else { + newRegistries[ns] = url + } + } + + if (Object.keys(newRegistries).length === 0) { + if (skipped.length > 0 && !options.silent) { + spinner( + `Skipped ${pluralize( + skipped.length, + "registry", + "registries" + )}: (already configured)`, + { silent: options.silent } + )?.info() + for (const name of skipped) { + logger.log(` - ${name}`) + } + } else if (!options.silent) { + logger.info("No new registries to add.") + } + return + } + + const updatedConfig = { + ...existingConfig, + registries: { + ...existingRegistries, + ...newRegistries, + }, + } + + const writeSpinner = spinner("Updating components.json.", { + silent: options.silent, + }).start() + await fs.writeJson(configPath, updatedConfig, { spaces: 2 }) + writeSpinner.succeed() + + if (!options.silent) { + const newRegistryNames = Object.keys(newRegistries) + spinner( + `Added ${pluralize(newRegistryNames.length, "registry", "registries")}:`, + { silent: options.silent } + )?.succeed() + for (const name of newRegistryNames) { + logger.log(` - ${name}`) + } + + if (skipped.length > 0) { + spinner( + `Skipped ${pluralize( + skipped.length, + "registry", + "registries" + )}: (already configured)`, + { silent: options.silent } + )?.info() + for (const name of skipped) { + logger.log(` - ${name}`) + } + } + } +} + +async function promptForRegistries(options: { silent?: boolean }) { + const fetchSpinner = spinner("Fetching registries.", { + silent: options.silent, + }).start() + const registries = await getRegistries() + if (!registries) { + fetchSpinner.fail() + throw new Error("Failed to fetch registries.") + } + fetchSpinner.succeed() + + const sorted = [...registries].sort((a, b) => a.name.localeCompare(b.name)) + + const { selected } = await prompts({ + type: "autocompleteMultiselect", + name: "selected", + message: "Which registries would you like to add?", + hint: "Space to select. A to toggle all. Enter to submit.", + instructions: false, + choices: sorted.map((r) => ({ + title: r.name, + description: r.description, + value: r.name, + })), + }) + + if (!selected?.length) { + logger.warn("No registries selected. Exiting.") + logger.info("") + process.exit(1) + } + + return selected as string[] +} diff --git a/packages/shadcn/src/commands/registry/index.ts b/packages/shadcn/src/commands/registry/index.ts new file mode 100644 index 000000000..2b3c1aaa2 --- /dev/null +++ b/packages/shadcn/src/commands/registry/index.ts @@ -0,0 +1,7 @@ +import { add } from "@/src/commands/registry/add" +import { Command } from "commander" + +export const registry = new Command() + .name("registry") + .description("manage registries") + .addCommand(add) diff --git a/packages/shadcn/src/index.ts b/packages/shadcn/src/index.ts index a3cef652a..61527f901 100644 --- a/packages/shadcn/src/index.ts +++ b/packages/shadcn/src/index.ts @@ -7,6 +7,7 @@ import { info } from "@/src/commands/info" import { init } from "@/src/commands/init" import { mcp } from "@/src/commands/mcp" import { migrate } from "@/src/commands/migrate" +import { registry } from "@/src/commands/registry" import { build as registryBuild } from "@/src/commands/registry/build" import { mcp as registryMcp } from "@/src/commands/registry/mcp" import { search } from "@/src/commands/search" @@ -39,7 +40,8 @@ async function main() { .addCommand(info) .addCommand(build) .addCommand(mcp) - // Registry commands + .addCommand(registry) + // Legacy registry commands. program.addCommand(registryBuild).addCommand(registryMcp) program.parse() diff --git a/packages/shadcn/src/registry/api.test.ts b/packages/shadcn/src/registry/api.test.ts index 446ae9e55..2b231efb8 100644 --- a/packages/shadcn/src/registry/api.test.ts +++ b/packages/shadcn/src/registry/api.test.ts @@ -28,6 +28,7 @@ import { import { z } from "zod" import { + getRegistries, getRegistriesConfig, getRegistriesIndex, getRegistry, @@ -104,11 +105,24 @@ const server = setupServer( }) }), http.get(`${REGISTRY_URL}/registries.json`, () => { - return HttpResponse.json({ - "@shadcn": "https://ui.shadcn.com/r/styles/{style}/{name}.json", - "@example": "https://example.com/registry/styles/{style}/{name}.json", - "@test": "https://test.com/registry/{name}.json", - }) + return HttpResponse.json([ + { + name: "@shadcn", + url: "https://ui.shadcn.com/r/styles/{style}/{name}.json", + description: "The official shadcn/ui registry.", + }, + { + name: "@example", + homepage: "https://example.com", + url: "https://example.com/registry/styles/{style}/{name}.json", + description: "An example registry for testing.", + }, + { + name: "@test", + url: "https://test.com/registry/{name}.json", + description: "A test registry.", + }, + ]) }) ) @@ -1664,10 +1678,70 @@ describe("getRegistriesConfig", () => { }) }) + describe("getRegistries", () => { + it("should fetch and parse registries successfully", async () => { + const result = await getRegistries() + + expect(result).toEqual([ + { + name: "@shadcn", + url: "https://ui.shadcn.com/r/styles/{style}/{name}.json", + description: "The official shadcn/ui registry.", + }, + { + name: "@example", + homepage: "https://example.com", + url: "https://example.com/registry/styles/{style}/{name}.json", + description: "An example registry for testing.", + }, + { + name: "@test", + url: "https://test.com/registry/{name}.json", + description: "A test registry.", + }, + ]) + }) + + it("should respect cache options", async () => { + const result1 = await getRegistries({ useCache: false }) + expect(result1).toBeDefined() + + const result2 = await getRegistries({ useCache: true }) + expect(result2).toBeDefined() + + expect(result1).toEqual(result2) + }) + + it("should handle network errors properly", async () => { + server.use( + http.get(`${REGISTRY_URL}/registries.json`, () => { + return new HttpResponse(null, { status: 500 }) + }) + ) + + await expect(getRegistries({ useCache: false })).rejects.toThrow() + }) + + it("should handle invalid JSON response", async () => { + server.use( + http.get(`${REGISTRY_URL}/registries.json`, () => { + return HttpResponse.json({ + invalid: "format", + }) + }) + ) + + await expect(getRegistries({ useCache: false })).rejects.toThrow( + RegistriesIndexParseError + ) + }) + }) + describe("getRegistriesIndex", () => { it("should fetch and parse the registries index successfully", async () => { const result = await getRegistriesIndex() + // getRegistriesIndex transforms array format to object format. expect(result).toEqual({ "@shadcn": "https://ui.shadcn.com/r/styles/{style}/{name}.json", "@example": "https://example.com/registry/styles/{style}/{name}.json", diff --git a/packages/shadcn/src/registry/api.ts b/packages/shadcn/src/registry/api.ts index 0fd3447db..060cc8b30 100644 --- a/packages/shadcn/src/registry/api.ts +++ b/packages/shadcn/src/registry/api.ts @@ -27,6 +27,7 @@ import { configJsonSchema, iconsSchema, registriesIndexSchema, + registriesSchema, registryBaseColorSchema, registryConfigSchema, registryIndexSchema, @@ -279,7 +280,8 @@ export async function getItemTargetPath( ) } -export async function getRegistriesIndex(options?: { useCache?: boolean }) { +// Fetch registries with new schema (array of objects with name, homepage, url, featured). +export async function getRegistries(options?: { useCache?: boolean }) { options = { useCache: true, ...options, @@ -291,7 +293,7 @@ export async function getRegistriesIndex(options?: { useCache?: boolean }) { }) try { - return registriesIndexSchema.parse(data) + return registriesSchema.parse(data) } catch (error) { if (error instanceof z.ZodError) { throw new RegistriesIndexParseError(error) @@ -301,6 +303,18 @@ export async function getRegistriesIndex(options?: { useCache?: boolean }) { } } +/** + * @deprecated Use getRegistries() instead. + */ +export async function getRegistriesIndex(options?: { useCache?: boolean }) { + // Fetch new format and transform to old Record for backward compatibility. + const registries = await getRegistries(options) + if (!registries) return null + return Object.fromEntries(registries.map((r) => [r.name, r.url])) as z.infer< + typeof registriesIndexSchema + > +} + export async function getPresets(options?: { useCache?: boolean }) { options = { useCache: true, diff --git a/packages/shadcn/src/registry/index.ts b/packages/shadcn/src/registry/index.ts index 8ea1eb48b..b3f0a6c76 100644 --- a/packages/shadcn/src/registry/index.ts +++ b/packages/shadcn/src/registry/index.ts @@ -1,4 +1,5 @@ export { + getRegistries, getRegistryItems, resolveRegistryItems, getRegistry, diff --git a/packages/shadcn/src/registry/schema.ts b/packages/shadcn/src/registry/schema.ts index 2acdc9fd7..e4cdc1065 100644 --- a/packages/shadcn/src/registry/schema.ts +++ b/packages/shadcn/src/registry/schema.ts @@ -260,11 +260,22 @@ export const searchResultsSchema = z.object({ items: z.array(searchResultItemSchema), }) +// Legacy schema for getRegistriesIndex() backward compatibility. export const registriesIndexSchema = z.record( z.string().regex(/^@[a-zA-Z0-9][a-zA-Z0-9-_]*$/), z.string() ) +// New schema for getRegistries(). +export const registriesSchema = z.array( + z.object({ + name: z.string(), + homepage: z.string().optional(), + url: z.string(), + description: z.string().optional(), + }) +) + export const presetSchema = z.object({ name: z.string(), title: z.string(), diff --git a/packages/shadcn/test/utils/add-registries.test.ts b/packages/shadcn/test/utils/add-registries.test.ts new file mode 100644 index 000000000..c87fd72e8 --- /dev/null +++ b/packages/shadcn/test/utils/add-registries.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "vitest" + +import { parseRegistryArg } from "../../src/commands/registry/add" + +describe("parseRegistryArg", () => { + it("should parse namespace without URL", () => { + expect(parseRegistryArg("@magicui")).toEqual({ namespace: "@magicui" }) + expect(parseRegistryArg("@aceternity")).toEqual({ namespace: "@aceternity" }) + }) + + it("should parse namespace with URL", () => { + expect( + parseRegistryArg("@mycompany=https://example.com/r/{name}.json") + ).toEqual({ + namespace: "@mycompany", + url: "https://example.com/r/{name}.json", + }) + }) + + it("should handle URL with query params containing =", () => { + expect( + parseRegistryArg("@foo=https://example.com/r/{name}.json?token=abc") + ).toEqual({ + namespace: "@foo", + url: "https://example.com/r/{name}.json?token=abc", + }) + }) + + it("should handle URL with multiple = in query params", () => { + expect( + parseRegistryArg( + "@bar=https://example.com/r/{name}.json?token=abc&key=xyz" + ) + ).toEqual({ + namespace: "@bar", + url: "https://example.com/r/{name}.json?token=abc&key=xyz", + }) + }) + + it("should handle URL with port number", () => { + expect( + parseRegistryArg("@local=http://localhost:8080/r/{name}.json") + ).toEqual({ + namespace: "@local", + url: "http://localhost:8080/r/{name}.json", + }) + }) + + it("should throw for namespace without @", () => { + expect(() => parseRegistryArg("foo")).toThrow("must start with @") + expect(() => parseRegistryArg("magicui")).toThrow("must start with @") + expect(() => + parseRegistryArg("mycompany=https://example.com/r/{name}.json") + ).toThrow("must start with @") + }) +}) diff --git a/packages/tests/src/tests/add.test.ts b/packages/tests/src/tests/add.test.ts index 43ec6cbaa..971a2b7b2 100644 --- a/packages/tests/src/tests/add.test.ts +++ b/packages/tests/src/tests/add.test.ts @@ -375,3 +375,96 @@ describe("shadcn add", () => { ).toBe(true) }) }) + +describe("shadcn registry add", () => { + it("should add registry from index to components.json", async () => { + const fixturePath = await createFixtureTestDirectory("next-app") + await npxShadcn(fixturePath, ["init", "--base-color=neutral"]) + await npxShadcn(fixturePath, ["registry", "add", "@magicui"]) + + const componentsJson = await fs.readJson( + path.join(fixturePath, "components.json") + ) + expect(componentsJson.registries).toBeDefined() + expect(componentsJson.registries["@magicui"]).toBeDefined() + expect(componentsJson.registries["@magicui"]).toContain("{name}") + }) + + it("should add custom registry with URL", async () => { + const fixturePath = await createFixtureTestDirectory("next-app") + await npxShadcn(fixturePath, ["init", "--base-color=neutral"]) + await npxShadcn(fixturePath, [ + "registry", + "add", + "@mycompany=https://example.com/r/{name}.json", + ]) + + const componentsJson = await fs.readJson( + path.join(fixturePath, "components.json") + ) + expect(componentsJson.registries["@mycompany"]).toBe( + "https://example.com/r/{name}.json" + ) + }) + + it("should add multiple registries", async () => { + const fixturePath = await createFixtureTestDirectory("next-app") + await npxShadcn(fixturePath, ["init", "--base-color=neutral"]) + await npxShadcn(fixturePath, ["registry", "add", "@magicui", "@aceternity"]) + + const componentsJson = await fs.readJson( + path.join(fixturePath, "components.json") + ) + expect(componentsJson.registries["@magicui"]).toBeDefined() + expect(componentsJson.registries["@aceternity"]).toBeDefined() + }) + + it("should skip already configured registries", async () => { + const fixturePath = await createFixtureTestDirectory("next-app") + await npxShadcn(fixturePath, ["init", "--base-color=neutral"]) + await npxShadcn(fixturePath, ["registry", "add", "@magicui"]) + + // Add again - should not error. + await npxShadcn(fixturePath, ["registry", "add", "@magicui"]) + + const componentsJson = await fs.readJson( + path.join(fixturePath, "components.json") + ) + expect(componentsJson.registries["@magicui"]).toBeDefined() + }) + + it("should error for registry not in index without URL", async () => { + const fixturePath = await createFixtureTestDirectory("next-app") + await npxShadcn(fixturePath, ["init", "--base-color=neutral"]) + + const result = await npxShadcn(fixturePath, [ + "registry", + "add", + "@nonexistent-registry-12345", + ]) + expect(result.exitCode).toBe(1) + expect(result.stdout).toContain("not found") + }) + + it("should error for invalid URL missing {name}", async () => { + const fixturePath = await createFixtureTestDirectory("next-app") + await npxShadcn(fixturePath, ["init", "--base-color=neutral"]) + + const result = await npxShadcn(fixturePath, [ + "registry", + "add", + "@foo=https://example.com/bad.json", + ]) + expect(result.exitCode).toBe(1) + expect(result.stdout).toContain("{name}") + }) + + it("should require components.json for adding registries", async () => { + const fixturePath = await createFixtureTestDirectory("no-framework") + + // No init, so no components.json. + const result = await npxShadcn(fixturePath, ["registry", "add", "@magicui"]) + expect(result.exitCode).toBe(1) + expect(result.stdout).toContain("components.json") + }) +}) diff --git a/packages/tests/src/tests/registries.test.ts b/packages/tests/src/tests/registries.test.ts index adbd74d9a..36f3d8d8d 100644 --- a/packages/tests/src/tests/registries.test.ts +++ b/packages/tests/src/tests/registries.test.ts @@ -1188,7 +1188,7 @@ describe("registries", () => { describe("registries:init", () => { beforeEach(async () => { - process.env.REGISTRY_URL = "http://localhost:4000/r" + process.env.REGISTRY_URL = "http://localhost:4040/r" }) it("should error when init with unconfigured registries", async () => { diff --git a/packages/tests/src/utils/registry.ts b/packages/tests/src/utils/registry.ts index 7f2f5d890..44d4e330f 100644 --- a/packages/tests/src/utils/registry.ts +++ b/packages/tests/src/utils/registry.ts @@ -15,11 +15,11 @@ export async function createRegistryServer( const server = createServer((request, response) => { const urlRaw = request.url?.split("?")[0] - // Handle registries.json endpoint (don't strip .json for this one) + // Handle registries.json endpoint (don't strip .json for this one). if (urlRaw?.endsWith("/registries.json")) { response.writeHead(200, { "Content-Type": "application/json" }) - // Return empty registry index for tests - we want to test manual configuration. - response.end(JSON.stringify({})) + // Return empty registry array for tests - we want to test manual configuration. + response.end(JSON.stringify([])) return } @@ -103,7 +103,23 @@ export async function createRegistryServer( return } - if (urlWithoutQuery?.includes("index")) { + // Match /styles/{style}/index for the registry style index (e.g., /styles/new-york-v4/index). + if (urlWithoutQuery?.match(/\/styles\/[^/]+\/index$/)) { + response.writeHead(200, { "Content-Type": "application/json" }) + response.end( + JSON.stringify({ + name: "index", + type: "registry:style", + registryDependencies: ["utils"], + cssVars: {}, + files: [], + }) + ) + return + } + + // Match /r/index for the registry index (but NOT paths like /styles/foo/index). + if (urlWithoutQuery?.match(/\/r\/index$/)) { response.writeHead(200, { "Content-Type": "application/json" }) response.end( JSON.stringify([ diff --git a/registries.json b/registries.json deleted file mode 120000 index 66d4d5be0..000000000 --- a/registries.json +++ /dev/null @@ -1 +0,0 @@ -apps/v4/public/r/registries.json \ No newline at end of file