diff --git a/.github/workflows/validate-registries.yml b/.github/workflows/validate-registries.yml index f507116a11..fc86ae3317 100644 --- a/.github/workflows/validate-registries.yml +++ b/.github/workflows/validate-registries.yml @@ -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 diff --git a/apps/v4/components/mode-switcher.tsx b/apps/v4/components/mode-switcher.tsx index d287df42ea..5858decc72 100644 --- a/apps/v4/components/mode-switcher.tsx +++ b/apps/v4/components/mode-switcher.tsx @@ -7,12 +7,6 @@ import { useTheme } from "next-themes" import { cn } from "@/lib/utils" import { useMetaColor } from "@/hooks/use-meta-color" import { Button } from "@/registry/new-york-v4/ui/button" -import { Kbd } from "@/registry/new-york-v4/ui/kbd" -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/registry/new-york-v4/ui/tooltip" export const DARK_MODE_FORWARD_TYPE = "dark-mode-forward" @@ -35,40 +29,33 @@ export function ModeSwitcher({ }, [resolvedTheme, setTheme]) return ( - - - - - - Toggle Mode D - - + ) } diff --git a/apps/v4/content/docs/(root)/figma.mdx b/apps/v4/content/docs/(root)/figma.mdx index 5884181802..0ce70c92e8 100644 --- a/apps/v4/content/docs/(root)/figma.mdx +++ b/apps/v4/content/docs/(root)/figma.mdx @@ -16,7 +16,7 @@ description: Every component recreated in Figma. With customizable props, typogr ## Paid -- [shadcn/ui kit](https://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff. +- [shadcn/ui kit](https://shadcndesign.com) by [Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff. - [Shadcraft UI Kit](https://shadcraft.com) - The most advanced shadcn-compatible kit with instant theming via [tweakcn](https://tweakcn.com), a pro library of components and templates, and complete coverage of shadcn components and blocks. - [shadcn/studio UI Kit](https://shadcnstudio.com/figma) - Accelerate design & development with a shadcn/ui compatible Figma kit with updated components, 550+ blocks, 10+ templates, 20+ themes, and an AI tool that converts designs into shadcn/ui code. - [Shadcnblocks.com](https://www.shadcnblocks.com) - A Premium Shadcn Figma UI Kit with components, 500+ pro blocks, shadcn theme variables, light/dark mode and Figma MCP ready. diff --git a/apps/v4/content/docs/(root)/index.mdx b/apps/v4/content/docs/(root)/index.mdx index b35634805e..16c70a9e01 100644 --- a/apps/v4/content/docs/(root)/index.mdx +++ b/apps/v4/content/docs/(root)/index.mdx @@ -27,7 +27,7 @@ shadcn/ui hands you the actual component code. You have full control to customiz _In a typical library, if you need to change a button’s behavior, you have to override styles or wrap the component. With shadcn/ui, you simply edit the button code directly._ - + How do I pull upstream updates in an Open Code approach? diff --git a/apps/v4/content/docs/(root)/new.mdx b/apps/v4/content/docs/(root)/new.mdx index 24efec1b32..be1a42797c 100644 --- a/apps/v4/content/docs/(root)/new.mdx +++ b/apps/v4/content/docs/(root)/new.mdx @@ -3,7 +3,7 @@ title: Your project is ready! description: You've created a new project with shadcn/ui. --- -Here's a few things you can do to get started building with shadcn/ui. +Here are a few things you can do to get started building with shadcn/ui. ## Add Components diff --git a/apps/v4/content/docs/(root)/react-19.mdx b/apps/v4/content/docs/(root)/react-19.mdx index 5d40383d73..832b0de7c8 100644 --- a/apps/v4/content/docs/(root)/react-19.mdx +++ b/apps/v4/content/docs/(root)/react-19.mdx @@ -121,7 +121,7 @@ The process for adding components is the same as above. Select a flag to resolve ## Upgrade Status -To make it easy for you track the progress of the upgrade, I've created a table below with React 19 support status for the shadcn/ui dependencies. +To make it easy for you to track the progress of the upgrade, here is a table with the React 19 support status for the shadcn/ui dependencies. - ✅ - Works with React 19 using npm, pnpm, and bun. - 🚧 - Works with React 19 using pnpm and bun. Requires flag for npm. PR is in progress. diff --git a/apps/v4/content/docs/(root)/tailwind-v4.mdx b/apps/v4/content/docs/(root)/tailwind-v4.mdx index 5b4bfc9544..0f35dff8d2 100644 --- a/apps/v4/content/docs/(root)/tailwind-v4.mdx +++ b/apps/v4/content/docs/(root)/tailwind-v4.mdx @@ -193,7 +193,7 @@ Here's how you do it: } ``` -This change makes it much simpler to access your theme variables in both utility classes and outside of CSS for eg. using color values in JavaScript. +This change makes it much simpler to access your theme variables in both utility classes and outside of CSS, e.g. using color values in JavaScript. ### 3. Update colors for charts @@ -281,7 +281,7 @@ function AccordionItem({ We've deprecated `tailwindcss-animate` in favor of `tw-animate-css`. -New project will have `tw-animate-css` installed by default. +New projects will have `tw-animate-css` installed by default. For existing projects, follow the steps below to migrate. diff --git a/apps/v4/content/docs/changelog/2024-03-blocks.mdx b/apps/v4/content/docs/changelog/2024-03-blocks.mdx index 6cd765ca7d..10d475b211 100644 --- a/apps/v4/content/docs/changelog/2024-03-blocks.mdx +++ b/apps/v4/content/docs/changelog/2024-03-blocks.mdx @@ -6,7 +6,7 @@ date: 2024-03-22 One of the most requested features since launch has been layouts: admin dashboards with sidebar, marketing page sections, cards and more. -**Today, we're launching [**Blocks**](/blocks)**. +**Today, we're launching [Blocks](/blocks).** Lift Mode diff --git a/apps/v4/content/docs/changelog/2025-04-shadcn-2-5.mdx b/apps/v4/content/docs/changelog/2025-04-shadcn-2-5.mdx index 86af7f5fdb..4292893e55 100644 --- a/apps/v4/content/docs/changelog/2025-04-shadcn-2-5.mdx +++ b/apps/v4/content/docs/changelog/2025-04-shadcn-2-5.mdx @@ -1,5 +1,5 @@ --- -title: March 2025 - shadcn 2.5.0 +title: April 2025 - shadcn 2.5.0 description: Resolve anywhere - registries can now place files anywhere in an app. date: 2025-04-26 --- diff --git a/apps/v4/content/docs/changelog/2025-06-radix-ui.mdx b/apps/v4/content/docs/changelog/2025-06-radix-ui.mdx index 235b371eb4..b8fd675fef 100644 --- a/apps/v4/content/docs/changelog/2025-06-radix-ui.mdx +++ b/apps/v4/content/docs/changelog/2025-06-radix-ui.mdx @@ -13,7 +13,7 @@ npx shadcn@latest migrate radix It will automatically update all imports in your `ui` components and install `radix-ui` as a dependency. ```diff showLineNumbers title="components/ui/alert-dialog.tsx" -- import * as AlertDialogPrimitive from "@radix-ui/react-dialog" +- import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + import { AlertDialog as AlertDialogPrimitive } from "radix-ui" ``` diff --git a/apps/v4/content/docs/changelog/2025-08-cli-3-mcp.mdx b/apps/v4/content/docs/changelog/2025-08-cli-3-mcp.mdx index f559654668..5c2946b25e 100644 --- a/apps/v4/content/docs/changelog/2025-08-cli-3-mcp.mdx +++ b/apps/v4/content/docs/changelog/2025-08-cli-3-mcp.mdx @@ -91,7 +91,7 @@ Need to keep your components private? We've got you covered. Configure authentic Your private components stay private. Perfect for enterprise teams with proprietary UI libraries. -We support all major authentication methods: basic auth, bearer token, api key query params and custom headers. +We support all major authentication methods: basic auth, bearer token, API key query params and custom headers. See the [authentication docs](/docs/registry/authentication) for more details. @@ -125,7 +125,7 @@ Preview components before installing them. Search across multiple registries. Se src="/images/mcp.jpeg" width="1432" height="1050" - alt="Lift Mode" + alt="MCP Server" className="mt-6 w-full overflow-hidden rounded-lg border" /> @@ -175,7 +175,7 @@ Missing environment variables? The CLI tells you exactly what's needed: Registry "@private" requires the following environment variables: • REGISTRY_TOKEN -Set the required environment variables to your .env or .env.local file. +Set the required environment variables in your .env or .env.local file. ``` Registry authors can provide custom error messages in their responses to help users and AI agents understand and fix issues quickly. diff --git a/apps/v4/content/docs/changelog/2025-09-registry-index.mdx b/apps/v4/content/docs/changelog/2025-09-registry-index.mdx index 32885f8cd3..480f374050 100644 --- a/apps/v4/content/docs/changelog/2025-09-registry-index.mdx +++ b/apps/v4/content/docs/changelog/2025-09-registry-index.mdx @@ -6,7 +6,7 @@ date: 2025-09-02 We've created an index of open source registries that you can install items from. -You can search, view and add items from the registry index without configuring the `.components.json` file. +You can search, view and add items from the registry index without configuring the `components.json` file. They'll be automatically added to your `components.json` file for you. diff --git a/apps/v4/content/docs/changelog/2025-10-new-components.mdx b/apps/v4/content/docs/changelog/2025-10-new-components.mdx index cba759ba92..1e3745f023 100644 --- a/apps/v4/content/docs/changelog/2025-10-new-components.mdx +++ b/apps/v4/content/docs/changelog/2025-10-new-components.mdx @@ -173,7 +173,7 @@ You can also add buttons to the input group. className="[&_.preview]:h-[300px] [&_pre]:h-[300px]!" /> -Or text, labels, tooltips,... +Or text, labels, tooltips, ... -Wait here's more. Wrap your fields in `FieldLabel` to create a selectable field group. Really easy. And it looks great. +Wait, here's more. Wrap your fields in `FieldLabel` to create a selectable field group. Really easy. And it looks great. -### Link +### As Link You can use the `buttonVariants` helper to make a link look like a button. diff --git a/apps/v4/content/docs/components/base/carousel.mdx b/apps/v4/content/docs/components/base/carousel.mdx index 1c07e3a4b7..a9a77c3b0a 100644 --- a/apps/v4/content/docs/components/base/carousel.mdx +++ b/apps/v4/content/docs/components/base/carousel.mdx @@ -182,7 +182,7 @@ You can pass options to the carousel using the `opts` prop. See the [Embla Carou ## API -Use a state and the `setApi` props to get an instance of the carousel API. +Use a state and the `setApi` prop to get an instance of the carousel API. @@ -240,7 +240,7 @@ Use the `disabled` prop to disable the combobox. ### Auto Highlight -Use the `autoHighlight` prop automatically highlight the first item on filter. +Use the `autoHighlight` prop to automatically highlight the first item on filter. diff --git a/apps/v4/content/docs/components/base/data-table.mdx b/apps/v4/content/docs/components/base/data-table.mdx index 03a90f65fc..4a02b4a496 100644 --- a/apps/v4/content/docs/components/base/data-table.mdx +++ b/apps/v4/content/docs/components/base/data-table.mdx @@ -316,13 +316,13 @@ You can use the same approach to format other cells and headers. ## Row Actions -Let's add row actions to our table. We'll use a `` component for this. +Let's add row actions to our table. We'll use a `` component for this. ### Update columns definition -Update our columns definition to add a new `actions` column. The `actions` cell returns a `` component. +Update our columns definition to add a new `actions` column. The `actions` cell returns a `` component. ```tsx showLineNumbers title="app/payments/columns.tsx" {4,6-14,18-45} "use client" @@ -472,7 +472,7 @@ Let's make the email column sortable. ### Update `` -```tsx showLineNumbers title="app/payments/data-table.tsx" showLineNumbers {3,6,10,18,25-29} +```tsx showLineNumbers title="app/payments/data-table.tsx" {3,6,10,18,25-29} "use client" import * as React from "react" diff --git a/apps/v4/content/docs/components/base/dropdown-menu.mdx b/apps/v4/content/docs/components/base/dropdown-menu.mdx index b63bfc10bd..3913e0518c 100644 --- a/apps/v4/content/docs/components/base/dropdown-menu.mdx +++ b/apps/v4/content/docs/components/base/dropdown-menu.mdx @@ -82,8 +82,8 @@ import { My Account Profile Billing - + Team Subscription diff --git a/apps/v4/content/docs/components/base/input-otp.mdx b/apps/v4/content/docs/components/base/input-otp.mdx index f64f7d6666..3b23441422 100644 --- a/apps/v4/content/docs/components/base/input-otp.mdx +++ b/apps/v4/content/docs/components/base/input-otp.mdx @@ -1,6 +1,6 @@ --- title: Input OTP -description: Accessible one-time password component with copy paste functionality. +description: Accessible one-time password component with copy-paste functionality. base: base component: true links: diff --git a/apps/v4/content/docs/components/base/scroll-area.mdx b/apps/v4/content/docs/components/base/scroll-area.mdx index 580fcdb46c..3d961ff9a9 100644 --- a/apps/v4/content/docs/components/base/scroll-area.mdx +++ b/apps/v4/content/docs/components/base/scroll-area.mdx @@ -59,7 +59,7 @@ npm install @base-ui/react ## Usage ```tsx showLineNumbers -import { ScrollArea } from "@/components/ui/scroll-area" +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area" ``` ```tsx showLineNumbers diff --git a/apps/v4/content/docs/components/base/sonner.mdx b/apps/v4/content/docs/components/base/sonner.mdx index aaa65a4e6a..02cd129e8d 100644 --- a/apps/v4/content/docs/components/base/sonner.mdx +++ b/apps/v4/content/docs/components/base/sonner.mdx @@ -31,7 +31,7 @@ Sonner is built and maintained by [emilkowalski](https://twitter.com/emilkowalsk npx shadcn@latest add sonner ``` -Add the Toaster component +Add the Toaster component. ```tsx title="app/layout.tsx" {1,9} showLineNumbers import { Toaster } from "@/components/ui/sonner" @@ -71,7 +71,7 @@ npm install sonner next-themes styleName="base-nova" /> -Add the Toaster component +Add the Toaster component. ```tsx showLineNumbers title="app/layout.tsx" {1,8} import { Toaster } from "@/components/ui/sonner" diff --git a/apps/v4/content/docs/components/base/spinner.mdx b/apps/v4/content/docs/components/base/spinner.mdx index d209690563..8146e22ce8 100644 --- a/apps/v4/content/docs/components/base/spinner.mdx +++ b/apps/v4/content/docs/components/base/spinner.mdx @@ -88,13 +88,13 @@ Use the `size-*` utility class to change the size of the spinner. ### Button -Add a spinner to a button to indicate a loading state. Remember to use the `data-icon="inline-start"` prop to add the spinner to the start of the button and the `data-icon="inline-end"` prop to add the spinner to the end of the button. +Add a spinner to a button to indicate a loading state. Place the `` before the label with `data-icon="inline-start"` for a start position, or after the label with `data-icon="inline-end"` for an end position. ### Badge -Add a spinner to a badge to indicate a loading state. Remember to use the `data-icon="inline-start"` prop to add the spinner to the start of the badge and the `data-icon="inline-end"` prop to add the spinner to the end of the badge. +Add a spinner to a badge to indicate a loading state. Place the `` before the label with `data-icon="inline-start"` for a start position, or after the label with `data-icon="inline-end"` for an end position. diff --git a/apps/v4/content/docs/components/base/typography.mdx b/apps/v4/content/docs/components/base/typography.mdx index 6d2eff786e..21953c1ed0 100644 --- a/apps/v4/content/docs/components/base/typography.mdx +++ b/apps/v4/content/docs/components/base/typography.mdx @@ -1,6 +1,6 @@ --- title: Typography -description: Styles for headings, paragraphs, lists...etc +description: Styles for headings, paragraphs, lists, etc. base: base component: true --- diff --git a/apps/v4/content/docs/components/radix/alert-dialog.mdx b/apps/v4/content/docs/components/radix/alert-dialog.mdx index b36577099b..425aa94937 100644 --- a/apps/v4/content/docs/components/radix/alert-dialog.mdx +++ b/apps/v4/content/docs/components/radix/alert-dialog.mdx @@ -161,7 +161,7 @@ To enable RTL support in shadcn/ui, see the [RTL configuration guide](/docs/rtl) ### size -Use the `size` props on the `AlertDialogContent` component to control the size of the alert dialog. It accepts the following values: +Use the `size` prop on the `AlertDialogContent` component to control the size of the alert dialog. It accepts the following values: | Prop | Type | Default | | ------ | ------------------- | ----------- | diff --git a/apps/v4/content/docs/components/radix/button.mdx b/apps/v4/content/docs/components/radix/button.mdx index 10d6fecdd2..263526b364 100644 --- a/apps/v4/content/docs/components/radix/button.mdx +++ b/apps/v4/content/docs/components/radix/button.mdx @@ -135,7 +135,7 @@ To create a button group, use the `ButtonGroup` component. See the [Button Group -### Link +### As Child You can use the `asChild` prop on ` diff --git a/apps/v4/content/docs/registry/examples.mdx b/apps/v4/content/docs/registry/examples.mdx index 66715513da..a537a4a505 100644 --- a/apps/v4/content/docs/registry/examples.mdx +++ b/apps/v4/content/docs/registry/examples.mdx @@ -44,7 +44,7 @@ The following registry item is a custom style that extends shadcn/ui. On `npx sh The following registry item is a custom style that doesn't extend shadcn/ui. See the `extends: none` field. -It can be used to create a new style from scratch i.e custom components, css vars, dependencies, etc. +It can be used to create a new style from scratch, i.e. custom components, css vars, dependencies, etc. On `npx shadcn add`, the following will: @@ -69,21 +69,21 @@ On `npx shadcn add`, the following will: ], "cssVars": { "theme": { - "font-sans": "Inter, sans-serif", - } + "font-sans": "Inter, sans-serif" + }, "light": { "main": "#88aaee", "bg": "#dfe5f2", "border": "#000", "text": "#000", - "ring": "#000", + "ring": "#000" }, "dark": { "main": "#88aaee", "bg": "#272933", "border": "#000", "text": "#e6e6e6", - "ring": "#fff", + "ring": "#fff" } } } @@ -147,7 +147,7 @@ The following style will init using shadcn/ui defaults and then add a custom `br ### Custom block -This blocks installs the `login-01` block from the shadcn/ui registry. +This block installs the `login-01` block from the shadcn/ui registry. ```json title="login-01.json" showLineNumbers { @@ -174,7 +174,7 @@ This blocks installs the `login-01` block from the shadcn/ui registry. ### Install a block and override primitives -You can install a block fromt the shadcn/ui registry and override the primitives using your custom ones. +You can install a block from the shadcn/ui registry and override the primitives using your custom ones. On `npx shadcn add`, the following will: @@ -877,7 +877,7 @@ Note: you need to define both `@keyframes` in css and `theme` in cssVars to use You can add environment variables using the `envVars` field. ```json title="example-item.json" showLineNumbers {5-9} -{» +{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "custom-item", "type": "registry:item", @@ -921,7 +921,7 @@ Here's an example of a registry item that installs custom Cursor rules for _pyth } ``` -Here's another example for installation custom ESLint config: +Here's another example for installing a custom ESLint config: ```json title=".eslintrc.json" showLineNumbers {9} { @@ -944,7 +944,7 @@ You can also have a universal item that installs multiple files: ```json title="my-custom-starter-template.json" showLineNumbers {9} { "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "my-custom-start-template", + "name": "my-custom-starter-template", "type": "registry:item", "dependencies": ["better-auth"], "files": [ diff --git a/apps/v4/content/docs/registry/faq.mdx b/apps/v4/content/docs/registry/faq.mdx index 2e1963caae..a59cae6f02 100644 --- a/apps/v4/content/docs/registry/faq.mdx +++ b/apps/v4/content/docs/registry/faq.mdx @@ -36,7 +36,7 @@ Here's an example of a complex component that installs a page, two components, a }, { "path": "registry/new-york/hello-world/lib/format-date.ts", - "type": "registry:utils" + "type": "registry:lib" }, { "path": "registry/new-york/hello-world/hello.config.ts", diff --git a/apps/v4/content/docs/registry/open-in-v0.mdx b/apps/v4/content/docs/registry/open-in-v0.mdx index 66ea29226a..c4416db364 100644 --- a/apps/v4/content/docs/registry/open-in-v0.mdx +++ b/apps/v4/content/docs/registry/open-in-v0.mdx @@ -18,7 +18,7 @@ See [Build your Open in v0 button](https://v0.dev/chat/button) for more informat Here's a simple example of how to add a `Open in v0` button to your site. -```jsx showLineNumbers +```tsx showLineNumbers import { Button } from "@/components/ui/button" export function OpenInV0Button({ url }: { url: string }) { diff --git a/apps/v4/content/docs/registry/registry-index.mdx b/apps/v4/content/docs/registry/registry-index.mdx index aae64f7834..ad55d2a19a 100644 --- a/apps/v4/content/docs/registry/registry-index.mdx +++ b/apps/v4/content/docs/registry/registry-index.mdx @@ -56,7 +56,6 @@ Here's an example of a valid registry: } ] } - } ] } ``` diff --git a/apps/v4/package.json b/apps/v4/package.json index 3f5a441df2..af10a7627b 100644 --- a/apps/v4/package.json +++ b/apps/v4/package.json @@ -76,7 +76,7 @@ "rehype-pretty-code": "^0.14.1", "rimraf": "^6.0.1", "server-only": "^0.0.1", - "shadcn": "4.0.7", + "shadcn": "4.0.8", "shiki": "^1.10.1", "sonner": "^2.0.0", "swr": "^2.3.6", diff --git a/apps/v4/public/r/registries-legacy.json b/apps/v4/public/r/registries-legacy.json index 995f92c5a3..1c25f0bd89 100644 --- a/apps/v4/public/r/registries-legacy.json +++ b/apps/v4/public/r/registries-legacy.json @@ -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", diff --git a/apps/v4/public/r/registries.json b/apps/v4/public/r/registries.json index f2c0d2e4bc..652e11a585 100644 --- a/apps/v4/public/r/registries.json +++ b/apps/v4/public/r/registries.json @@ -11,6 +11,12 @@ "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": "@unlumen-ui", + "homepage": "https://ui.unlumen.com", + "url": "https://ui.unlumen.com/r/{name}.json", + "description": "Primitives and components with serious attention to animation and design. Copy, own, ship." + }, { "name": "@auth0", "homepage": "https://auth0.com", @@ -252,7 +258,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." diff --git a/apps/v4/public/r/styles/new-york-v4/form-next-complex.json b/apps/v4/public/r/styles/new-york-v4/form-next-complex.json index 9c4b29bb2d..750678a7cd 100644 --- a/apps/v4/public/r/styles/new-york-v4/form-next-complex.json +++ b/apps/v4/public/r/styles/new-york-v4/form-next-complex.json @@ -1,7 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "form-next-complex", - "type": "registry:example", "registryDependencies": [ "field", "input", @@ -20,16 +19,7 @@ "path": "registry/new-york-v4/examples/form-next-complex.tsx", "content": "\"use client\"\n\nimport * as React from \"react\"\nimport Form from \"next/form\"\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport { Card, CardContent, CardFooter } from \"@/registry/new-york-v4/ui/card\"\nimport { Checkbox } from \"@/registry/new-york-v4/ui/checkbox\"\nimport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldTitle,\n} from \"@/registry/new-york-v4/ui/field\"\nimport {\n RadioGroup,\n RadioGroupItem,\n} from \"@/registry/new-york-v4/ui/radio-group\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/new-york-v4/ui/select\"\nimport { Spinner } from \"@/registry/new-york-v4/ui/spinner\"\nimport { Switch } from \"@/registry/new-york-v4/ui/switch\"\n\nimport { complexFormAction } from \"./form-next-complex-action\"\nimport { addons, type FormState } from \"./form-next-complex-schema\"\n\nexport default function FormNextComplex() {\n const [formState, formAction, pending] = React.useActionState<\n FormState,\n FormData\n >(complexFormAction, {\n values: {\n plan: \"basic\",\n billingPeriod: \"monthly\",\n addons: [],\n emailNotifications: false,\n },\n errors: null,\n success: false,\n })\n\n React.useEffect(() => {\n if (formState.success) {\n toast.success(\"Preferences saved\", {\n description: \"Your subscription plan has been updated.\",\n })\n }\n }, [formState.success])\n\n return (\n \n \n \n \n
\n Subscription Plan\n \n Choose your subscription plan.\n \n \n \n \n \n Basic\n \n For individuals and small teams\n \n \n \n \n \n \n \n \n Pro\n \n For businesses with higher demands\n \n \n \n \n \n \n {formState.errors?.plan && (\n {formState.errors.plan[0]}\n )}\n
\n \n \n Billing Period\n \n \n \n \n \n Monthly\n Yearly\n \n \n \n Choose how often you want to be billed.\n \n {formState.errors?.billingPeriod && (\n {formState.errors.billingPeriod[0]}\n )}\n \n \n
\n Add-ons\n \n Select additional features you'd like to include.\n \n \n {addons.map((addon) => (\n \n \n \n {addon.title}\n {addon.description}\n \n \n ))}\n \n {formState.errors?.addons && (\n {formState.errors.addons[0]}\n )}\n
\n \n \n \n \n Email Notifications\n \n \n Receive email updates about your subscription\n \n \n \n \n
\n \n
\n \n \n \n \n \n
\n )\n}\n", "type": "registry:example" - }, - { - "path": "registry/new-york-v4/examples/form-next-complex-schema.ts", - "content": "import { z } from \"zod\"\n\nexport const formSchema = z.object({\n plan: z\n .string({\n required_error: \"Please select a subscription plan\",\n })\n .min(1, \"Please select a subscription plan\")\n .refine((value) => value === \"basic\" || value === \"pro\", {\n message: \"Invalid plan selection. Please choose Basic or Pro\",\n }),\n billingPeriod: z\n .string({\n required_error: \"Please select a billing period\",\n })\n .min(1, \"Please select a billing period\"),\n addons: z\n .array(z.string())\n .min(1, \"Please select at least one add-on\")\n .max(3, \"You can select up to 3 add-ons\")\n .refine(\n (value) => value.every((addon) => addons.some((a) => a.id === addon)),\n {\n message: \"You selected an invalid add-on\",\n }\n ),\n emailNotifications: z.boolean(),\n})\n\nexport type FormState = {\n values: z.infer\n errors: null | Partial, string[]>>\n success: boolean\n}\n\nexport const addons = [\n {\n id: \"analytics\",\n title: \"Analytics\",\n description: \"Advanced analytics and reporting\",\n },\n {\n id: \"backup\",\n title: \"Backup\",\n description: \"Automated daily backups\",\n },\n {\n id: \"support\",\n title: \"Priority Support\",\n description: \"24/7 premium customer support\",\n },\n] as const\n", - "type": "registry:example" - }, - { - "path": "registry/new-york-v4/examples/form-next-complex-action.ts", - "content": "\"use server\"\n\nimport { formSchema, type FormState } from \"./form-next-complex-schema\"\n\nexport async function complexFormAction(\n _prevState: FormState,\n formData: FormData\n) {\n // Sleep for 1 second\n await new Promise((resolve) => setTimeout(resolve, 1000))\n\n const values = {\n plan: formData.get(\"plan\") as FormState[\"values\"][\"plan\"],\n billingPeriod: formData.get(\"billingPeriod\") as string,\n addons: formData.getAll(\"addons\") as string[],\n emailNotifications: formData.get(\"emailNotifications\") === \"on\",\n }\n\n const result = formSchema.safeParse(values)\n\n if (!result.success) {\n return {\n values,\n success: false,\n errors: result.error.flatten().fieldErrors,\n }\n }\n\n // Do something with the values.\n // Call your database or API here.\n\n return {\n values,\n errors: null,\n success: true,\n }\n}\n", - "type": "registry:example" } - ] + ], + "type": "registry:example" } \ No newline at end of file diff --git a/apps/v4/public/r/styles/new-york-v4/form-next-demo.json b/apps/v4/public/r/styles/new-york-v4/form-next-demo.json index b0d0325ad7..f5ac897d02 100644 --- a/apps/v4/public/r/styles/new-york-v4/form-next-demo.json +++ b/apps/v4/public/r/styles/new-york-v4/form-next-demo.json @@ -1,7 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "form-next-demo", - "type": "registry:example", "registryDependencies": [ "field", "input", @@ -16,5 +15,6 @@ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport Form from \"next/form\"\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupText,\n InputGroupTextarea,\n} from \"@/registry/new-york-v4/ui/input-group\"\nimport { Spinner } from \"@/registry/new-york-v4/ui/spinner\"\n\nimport { demoFormAction } from \"./form-next-demo-action\"\nimport { type FormState } from \"./form-next-demo-schema\"\n\nexport default function FormNextDemo() {\n const [formState, formAction, pending] = React.useActionState<\n FormState,\n FormData\n >(demoFormAction, {\n values: {\n title: \"\",\n description: \"\",\n },\n errors: null,\n success: false,\n })\n const [descriptionLength, setDescriptionLength] = React.useState(0)\n\n React.useEffect(() => {\n if (formState.success) {\n toast(\"Thank you for your feedback\", {\n description: \"We'll review your report and get back to you soon.\",\n })\n }\n }, [formState.success])\n\n React.useEffect(() => {\n setDescriptionLength(formState.values.description.length)\n }, [formState.values.description])\n\n return (\n \n \n Bug Report\n \n Help us improve by reporting bugs you encounter.\n \n \n \n
\n \n \n Bug Title\n \n {formState.errors?.title && (\n {formState.errors.title[0]}\n )}\n \n \n Description\n \n setDescriptionLength(e.target.value.length)}\n />\n \n \n {descriptionLength}/100 characters\n \n \n \n \n Include steps to reproduce, expected behavior, and what actually\n happened.\n \n {formState.errors?.description && (\n {formState.errors.description[0]}\n )}\n \n \n
\n
\n \n \n \n \n \n
\n )\n}\n", "type": "registry:example" } - ] + ], + "type": "registry:example" } \ No newline at end of file diff --git a/apps/v4/public/r/styles/new-york-v4/registry.json b/apps/v4/public/r/styles/new-york-v4/registry.json index 2fb79c73ae..bcea5cd601 100644 --- a/apps/v4/public/r/styles/new-york-v4/registry.json +++ b/apps/v4/public/r/styles/new-york-v4/registry.json @@ -3696,6 +3696,47 @@ ], "type": "registry:example" }, + { + "name": "form-next-demo", + "registryDependencies": [ + "field", + "input", + "textarea", + "button", + "card", + "spinner" + ], + "files": [ + { + "path": "registry/new-york-v4/examples/form-next-demo.tsx", + "type": "registry:example" + } + ], + "type": "registry:example" + }, + { + "name": "form-next-complex", + "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" + } + ], + "type": "registry:example" + }, { "name": "form-rhf-demo", "dependencies": ["react-hook-form", "@hookform/resolvers", "zod"], diff --git a/apps/v4/registry/__index__.tsx b/apps/v4/registry/__index__.tsx index f1f0f65357..2d4efb54ca 100644 --- a/apps/v4/registry/__index__.tsx +++ b/apps/v4/registry/__index__.tsx @@ -4888,6 +4888,44 @@ export const Index: Record> = { 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", diff --git a/apps/v4/registry/_legacy-colors.ts b/apps/v4/registry/_legacy-colors.ts index d5da076ee5..1494a350b1 100644 --- a/apps/v4/registry/_legacy-colors.ts +++ b/apps/v4/registry/_legacy-colors.ts @@ -409,6 +409,322 @@ export const colors = { oklch: "oklch(0.13,0.03,262)", }, ], + mauve: [ + { + scale: 50, + hex: "#fafafb", + rgb: "rgb(250,250,251)", + hsl: "hsl(272.4,15%,98.2%)", + oklch: "oklch(0.98,0.00,310)", + }, + { + scale: 100, + hex: "#f4f2f6", + rgb: "rgb(244,242,246)", + hsl: "hsl(272.4,16.1%,95.8%)", + oklch: "oklch(0.96,0.01,310)", + }, + { + scale: 200, + hex: "#e5e1e8", + rgb: "rgb(229,225,232)", + hsl: "hsl(272.5,13%,89.6%)", + oklch: "oklch(0.92,0.01,310)", + }, + { + scale: 300, + hex: "#d0c9d5", + rgb: "rgb(208,201,213)", + hsl: "hsl(272.6,12.6%,81.2%)", + oklch: "oklch(0.84,0.02,310)", + }, + { + scale: 400, + hex: "#a69bae", + rgb: "rgb(166,155,174)", + hsl: "hsl(272.9,10.6%,64.6%)", + oklch: "oklch(0.70,0.03,310)", + }, + { + scale: 500, + hex: "#81758b", + rgb: "rgb(129,117,139)", + hsl: "hsl(273.2,8.6%,50.1%)", + oklch: "oklch(0.58,0.04,310)", + }, + { + scale: 600, + hex: "#675d70", + rgb: "rgb(103,93,112)", + hsl: "hsl(273.2,9.2%,40.2%)", + oklch: "oklch(0.49,0.03,310)", + }, + { + scale: 700, + hex: "#524959", + rgb: "rgb(82,73,89)", + hsl: "hsl(273.3,9.7%,31.7%)", + oklch: "oklch(0.42,0.03,310)", + }, + { + scale: 800, + hex: "#3d3642", + rgb: "rgb(61,54,66)", + hsl: "hsl(273.3,10.2%,23.6%)", + oklch: "oklch(0.34,0.02,310)", + }, + { + scale: 900, + hex: "#2a252e", + rgb: "rgb(42,37,46)", + hsl: "hsl(273.3,10.9%,16.4%)", + oklch: "oklch(0.28,0.02,310)", + }, + { + scale: 950, + hex: "#161218", + rgb: "rgb(22,18,24)", + hsl: "hsl(273.3,14.1%,8.3%)", + oklch: "oklch(0.19,0.01,310)", + }, + ], + olive: [ + { + scale: 50, + hex: "#f9faf9", + rgb: "rgb(249,250,249)", + hsl: "hsl(136.8,13.4%,97.9%)", + oklch: "oklch(0.98,0.00,155)", + }, + { + scale: 100, + hex: "#f1f4f2", + rgb: "rgb(241,244,242)", + hsl: "hsl(136.9,14.2%,95.2%)", + oklch: "oklch(0.96,0.01,155)", + }, + { + scale: 200, + hex: "#dee5e0", + rgb: "rgb(222,229,224)", + hsl: "hsl(137,10.6%,88.5%)", + oklch: "oklch(0.92,0.01,155)", + }, + { + scale: 300, + hex: "#c4cfc8", + rgb: "rgb(196,207,200)", + hsl: "hsl(137.2,10.3%,79.2%)", + oklch: "oklch(0.84,0.02,155)", + }, + { + scale: 400, + hex: "#94a599", + rgb: "rgb(148,165,153)", + hsl: "hsl(137.8,8.7%,61.4%)", + oklch: "oklch(0.70,0.03,155)", + }, + { + scale: 500, + hex: "#6c8072", + rgb: "rgb(108,128,114)", + hsl: "hsl(138.4,8.6%,46.3%)", + oklch: "oklch(0.58,0.03,155)", + }, + { + scale: 600, + hex: "#56675b", + rgb: "rgb(86,103,91)", + hsl: "hsl(138.4,9.1%,37%)", + oklch: "oklch(0.49,0.03,155)", + }, + { + scale: 700, + hex: "#435147", + rgb: "rgb(67,81,71)", + hsl: "hsl(138.4,9.5%,29.1%)", + oklch: "oklch(0.42,0.02,155)", + }, + { + scale: 800, + hex: "#313c35", + rgb: "rgb(49,60,53)", + hsl: "hsl(138.4,10.2%,21.5%)", + oklch: "oklch(0.34,0.02,155)", + }, + { + scale: 900, + hex: "#222a24", + rgb: "rgb(34,42,36)", + hsl: "hsl(138.5,11.2%,14.8%)", + oklch: "oklch(0.28,0.02,155)", + }, + { + scale: 950, + hex: "#101512", + rgb: "rgb(16,21,18)", + hsl: "hsl(138.4,14.2%,7.3%)", + oklch: "oklch(0.19,0.01,155)", + }, + ], + mist: [ + { + scale: 50, + hex: "#f9fafb", + rgb: "rgb(249,250,251)", + hsl: "hsl(189.3,20.3%,97.9%)", + oklch: "oklch(0.98,0.00,210)", + }, + { + scale: 100, + hex: "#f0f4f5", + rgb: "rgb(240,244,245)", + hsl: "hsl(189.4,21.6%,95.1%)", + oklch: "oklch(0.96,0.01,210)", + }, + { + scale: 200, + hex: "#dce5e6", + rgb: "rgb(220,229,230)", + hsl: "hsl(189.4,16.2%,88.4%)", + oklch: "oklch(0.92,0.01,210)", + }, + { + scale: 300, + hex: "#c1cfd2", + rgb: "rgb(193,207,210)", + hsl: "hsl(189.4,15.8%,79%)", + oklch: "oklch(0.84,0.02,210)", + }, + { + scale: 400, + hex: "#8da5a9", + rgb: "rgb(141,165,169)", + hsl: "hsl(189.3,14%,60.9%)", + oklch: "oklch(0.70,0.03,210)", + }, + { + scale: 500, + hex: "#648085", + rgb: "rgb(100,128,133)", + hsl: "hsl(189.3,14.2%,45.7%)", + oklch: "oklch(0.58,0.03,210)", + }, + { + scale: 600, + hex: "#4f676b", + rgb: "rgb(79,103,107)", + hsl: "hsl(189.3,15.1%,36.5%)", + oklch: "oklch(0.49,0.03,210)", + }, + { + scale: 700, + hex: "#3d5155", + rgb: "rgb(61,81,85)", + hsl: "hsl(189.3,15.9%,28.6%)", + oklch: "oklch(0.42,0.03,210)", + }, + { + scale: 800, + hex: "#2d3c3f", + rgb: "rgb(45,60,63)", + hsl: "hsl(189.3,17.2%,21.2%)", + oklch: "oklch(0.34,0.02,210)", + }, + { + scale: 900, + hex: "#1e2a2c", + rgb: "rgb(30,42,44)", + hsl: "hsl(189.3,19.2%,14.6%)", + oklch: "oklch(0.28,0.02,210)", + }, + { + scale: 950, + hex: "#0e1517", + rgb: "rgb(14,21,23)", + hsl: "hsl(189.3,25.3%,7.1%)", + oklch: "oklch(0.19,0.01,210)", + }, + ], + taupe: [ + { + scale: 50, + hex: "#fbfaf9", + rgb: "rgb(251,250,249)", + hsl: "hsl(34,21.3%,97.9%)", + oklch: "oklch(0.98,0.00,75)", + }, + { + scale: 100, + hex: "#f5f3f0", + rgb: "rgb(245,243,240)", + hsl: "hsl(34,22.7%,95.1%)", + oklch: "oklch(0.96,0.01,75)", + }, + { + scale: 200, + hex: "#e7e2dc", + rgb: "rgb(231,226,220)", + hsl: "hsl(34.1,18.7%,88.4%)", + oklch: "oklch(0.92,0.01,75)", + }, + { + scale: 300, + hex: "#d3cbc0", + rgb: "rgb(211,203,192)", + hsl: "hsl(34.1,18.3%,79%)", + oklch: "oklch(0.84,0.02,75)", + }, + { + scale: 400, + hex: "#aa9e8d", + rgb: "rgb(170,158,141)", + hsl: "hsl(34.2,14.8%,61.1%)", + oklch: "oklch(0.70,0.03,75)", + }, + { + scale: 500, + hex: "#867865", + rgb: "rgb(134,120,101)", + hsl: "hsl(34.3,14.1%,46.1%)", + oklch: "oklch(0.58,0.03,75)", + }, + { + scale: 600, + hex: "#6c6050", + rgb: "rgb(108,96,80)", + hsl: "hsl(34.3,14.9%,36.8%)", + oklch: "oklch(0.49,0.03,75)", + }, + { + scale: 700, + hex: "#554b3e", + rgb: "rgb(85,75,62)", + hsl: "hsl(34.3,15.7%,28.9%)", + oklch: "oklch(0.42,0.03,75)", + }, + { + scale: 800, + hex: "#40382d", + rgb: "rgb(64,56,45)", + hsl: "hsl(34.3,17%,21.4%)", + oklch: "oklch(0.34,0.02,75)", + }, + { + scale: 900, + hex: "#2c271f", + rgb: "rgb(44,39,31)", + hsl: "hsl(34.3,17.7%,14.8%)", + oklch: "oklch(0.28,0.02,75)", + }, + { + scale: 950, + hex: "#17130e", + rgb: "rgb(23,19,14)", + hsl: "hsl(34.4,24.8%,7.2%)", + oklch: "oklch(0.19,0.01,75)", + }, + ], red: [ { scale: 50, diff --git a/apps/v4/registry/directory.json b/apps/v4/registry/directory.json index 3ca3c1c4a0..891ab13ab3 100644 --- a/apps/v4/registry/directory.json +++ b/apps/v4/registry/directory.json @@ -13,6 +13,13 @@ "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.", "logo": " " }, + { + "name": "@unlumen-ui", + "homepage": "https://ui.unlumen.com", + "url": "https://ui.unlumen.com/r/{name}.json", + "description": "Primitives and components with serious attention to animation and design. Copy, own, ship.", + "logo": "" + }, { "name": "@auth0", "homepage": "https://auth0.com", @@ -295,7 +302,7 @@ "logo": "" }, { - "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.", diff --git a/apps/v4/registry/new-york-v4/examples/_registry.ts b/apps/v4/registry/new-york-v4/examples/_registry.ts index 43ba976275..ac78c238fc 100644 --- a/apps/v4/registry/new-york-v4/examples/_registry.ts +++ b/apps/v4/registry/new-york-v4/examples/_registry.ts @@ -950,6 +950,47 @@ 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", diff --git a/packages/shadcn/CHANGELOG.md b/packages/shadcn/CHANGELOG.md index 741fafbbe8..8a9c7bdc3f 100644 --- a/packages/shadcn/CHANGELOG.md +++ b/packages/shadcn/CHANGELOG.md @@ -1,5 +1,11 @@ # @shadcn/ui +## 4.0.8 + +### Patch Changes + +- [#10041](https://github.com/shadcn-ui/ui/pull/10041) [`a97ebe54f1824032d8ad00d1d0c079e3dc6f52d7`](https://github.com/shadcn-ui/ui/commit/a97ebe54f1824032d8ad00d1d0c079e3dc6f52d7) Thanks [@shadcn](https://github.com/shadcn)! - Bundle @antfu/ni and tinyexec to fix missing module error with npx + ## 4.0.7 ### Patch Changes diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json index 3d8390328b..52fe4fed2a 100644 --- a/packages/shadcn/package.json +++ b/packages/shadcn/package.json @@ -1,6 +1,6 @@ { "name": "shadcn", - "version": "4.0.7", + "version": "4.0.8", "description": "Add components to your apps.", "publishConfig": { "access": "public" @@ -79,7 +79,6 @@ "mcp:inspect": "pnpm dlx @modelcontextprotocol/inspector node dist/index.js mcp" }, "dependencies": { - "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", @@ -116,6 +115,7 @@ "zod-to-json-schema": "^3.24.6" }, "devDependencies": { + "@antfu/ni": "^25.0.0", "@types/babel__core": "^7.20.5", "@types/fs-extra": "^11.0.4", "@types/prompts": "^2.4.9", diff --git a/packages/shadcn/tsup.config.ts b/packages/shadcn/tsup.config.ts index 12cb99bf2d..e7a411da60 100644 --- a/packages/shadcn/tsup.config.ts +++ b/packages/shadcn/tsup.config.ts @@ -19,6 +19,9 @@ export default defineConfig({ target: "esnext", outDir: "dist", treeshake: true, + // Bundle @antfu/ni and its dependency tinyexec to avoid + // module resolution failures with npx temporary installs. + noExternal: ["@antfu/ni", "tinyexec"], onSuccess: async () => { copyFileSync("src/tailwind.css", "dist/tailwind.css") }, diff --git a/packages/tests/src/tests/init.test.ts b/packages/tests/src/tests/init.test.ts index 358063700b..8433838949 100644 --- a/packages/tests/src/tests/init.test.ts +++ b/packages/tests/src/tests/init.test.ts @@ -12,9 +12,6 @@ import { createRegistryServer } from "../utils/registry" describe("shadcn init - next-app", () => { it("should init with default configuration", async () => { - // Sleep for 1 second to avoid race condition with the registry server. - await new Promise((resolve) => setTimeout(resolve, 2000)) - const fixturePath = await createFixtureTestDirectory("next-app") await npxShadcn(fixturePath, ["init", "--defaults"]) diff --git a/packages/tests/src/utils/helpers.ts b/packages/tests/src/utils/helpers.ts index 31b0a10397..b6437e2dc5 100644 --- a/packages/tests/src/utils/helpers.ts +++ b/packages/tests/src/utils/helpers.ts @@ -47,7 +47,7 @@ export async function runCommand( }, input: options?.input, reject: false, - timeout: options?.timeout ?? 30000, + timeout: options?.timeout ?? 60000, }) const result = await childProcess diff --git a/packages/tests/src/utils/setup.ts b/packages/tests/src/utils/setup.ts index 98b3acf24c..6b14b85fb5 100644 --- a/packages/tests/src/utils/setup.ts +++ b/packages/tests/src/utils/setup.ts @@ -4,9 +4,59 @@ import { rimraf } from "rimraf" export const TEMP_DIR = path.join(__dirname, "../../temp") +const SHADCN_CLI_PATH = path.join(__dirname, "../../../shadcn/dist/index.js") + +async function waitForCondition( + label: string, + check: () => Promise, + timeoutMs = 60000 +) { + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + if (await check()) return + await new Promise((resolve) => setTimeout(resolve, 500)) + } + throw new Error(`Timed out waiting for: ${label} (${timeoutMs}ms)`) +} + export default async function setup() { await fs.ensureDir(TEMP_DIR) + // The v4 dev script runs `pnpm --filter=shadcn build` in the background + // while `next dev` starts immediately. On fast CI runs the server can be + // ready before the CLI binary is built, so we wait for it explicitly. + await waitForCondition("shadcn CLI binary", () => + fs.pathExists(SHADCN_CLI_PATH) + ) + + // The CLI's first request goes to the dynamic /init route. On a cold Next.js + // dev server, this route needs to be compiled on first access (~1.8s). That + // compilation time, on top of everything else init does, pushes the first + // test over the 30s CLI timeout. Warming up the route here ensures it is + // already compiled before any test starts. + const registryUrl = process.env.REGISTRY_URL || "http://localhost:4000/r" + const shadcnUrl = registryUrl.replace(/\/r\/?$/, "") + const initWarmupUrl = `${shadcnUrl}/init?base=base&style=nova&baseColor=neutral&theme=neutral&iconLibrary=lucide&font=geist&rtl=false&menuAccent=subtle&menuColor=default&radius=default&template=next` + + await waitForCondition("init route warm-up", async () => { + try { + const res = await fetch(initWarmupUrl) + return res.ok + } catch { + return false + } + }) + + // The CLI fetches registry paths that may not exist (e.g. font files), + // causing Next.js to compile the /_not-found/page route on first 404. + // That compilation takes ~4-5s and contributes to the first test timing + // out. Trigger one 404 here so the not-found page is pre-compiled. + try { + await fetch(`${shadcnUrl}/r/styles/new-york-v4/font-geist.json`) + } catch { + // Best effort — don't block setup if this fails. + } + return async () => { try { await rimraf(TEMP_DIR) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f89d549ec..01336aa661 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,7 +278,7 @@ importers: specifier: ^0.0.1 version: 0.0.1 shadcn: - specifier: 4.0.7 + specifier: 4.0.8 version: link:../../packages/shadcn shiki: specifier: ^1.10.1 @@ -359,9 +359,6 @@ importers: packages/shadcn: dependencies: - '@antfu/ni': - specifier: ^25.0.0 - version: 25.0.0 '@babel/core': specifier: ^7.28.0 version: 7.28.0 @@ -465,6 +462,9 @@ importers: specifier: ^3.24.6 version: 3.24.6(zod@3.25.76) devDependencies: + '@antfu/ni': + specifier: ^25.0.0 + version: 25.0.0 '@types/babel__core': specifier: ^7.20.5 version: 7.20.5