diff --git a/apps/www/components/callout.tsx b/apps/www/components/callout.tsx index 848d8717b21..34a9f23e72b 100644 --- a/apps/www/components/callout.tsx +++ b/apps/www/components/callout.tsx @@ -1,18 +1,19 @@ +import { cn } from "@/lib/utils" import { Alert, AlertDescription, AlertTitle, } from "@/registry/new-york/ui/alert" -interface CalloutProps { - icon?: string - title?: string - children?: React.ReactNode -} - -export function Callout({ title, children, icon, ...props }: CalloutProps) { +export function Callout({ + title, + children, + icon, + className, + ...props +}: React.ComponentProps & { icon?: string }) { return ( - + {icon && {icon}} {title && {title}} {children} diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx index c64cf6dd906..73fc1b39264 100644 --- a/apps/www/components/mdx-components.tsx +++ b/apps/www/components/mdx-components.tsx @@ -139,10 +139,10 @@ const components = {
), table: ({ className, ...props }: React.HTMLAttributes) => ( -
+
), tr: ({ className, ...props }: React.HTMLAttributes) => ( - + ), th: ({ className, ...props }: React.HTMLAttributes) => (
) => ( + +**Note:** If you are using charts with **React 19** or the **Next.js 15**, see the note [here](/docs/react-19#recharts). + + + -**Note:** If you are trying to use charts with **React 19** or the **Next.js 15**, you will need the [recharts@alpha](https://github.com/recharts/recharts/releases/tag/v2.13.0-alpha.4) release currently. +**Note:** If you are using charts with **React 19** or the **Next.js 15**, see the note [here](/docs/react-19#recharts). diff --git a/apps/www/content/docs/components/sidebar.mdx b/apps/www/content/docs/components/sidebar.mdx index fdac9d06508..55805481214 100644 --- a/apps/www/content/docs/components/sidebar.mdx +++ b/apps/www/content/docs/components/sidebar.mdx @@ -406,6 +406,40 @@ You can change the keyboard shortcut by updating the `SIDEBAR_KEYBOARD_SHORTCUT` const SIDEBAR_KEYBOARD_SHORTCUT = "b" ``` +### Persisted State + +The `SidebarProvider` supports persisting the sidebar state across page reloads and server-side rendering. It uses cookies to store the current state of the sidebar. When the sidebar state changes, a default cookie named `sidebar:state` is set with the current open/closed state. This cookie is then read on subsequent page loads to restore the sidebar state. + +To persist sidebar state in Next.js, set up your `SidebarProvider` in `app/layout.tsx` like this: + +```tsx showLineNumbers title="app/layout.tsx" +import { cookies } from "next/headers" + +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" +import { AppSidebar } from "@/components/app-sidebar" + +export async function Layout({ children }: { children: React.ReactNode }) { + const cookieStore = await cookies() + const defaultOpen = cookieStore.get("sidebar:state")?.value === "true" + + return ( + + +
+ + {children} +
+
+ ) +} +``` + +You can change the name of the cookie by updating the `SIDEBAR_COOKIE_NAME` variable in `sidebar.tsx`. + +```tsx showLineNumbers title="components/ui/sidebar.tsx" +const SIDEBAR_COOKIE_NAME = "sidebar:state" +``` + ## Sidebar The main `Sidebar` component used to render a collapsible sidebar. @@ -1323,7 +1357,30 @@ You can find more tips on using states for styling in this [Twitter thread](http ## Changelog -### 2024-10-21 +### 2024-10-30 Cookie handling in setOpen + +- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in ``. + +Update the `setOpen` callback in `` as follows: + +```tsx showLineNumbers +const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value + if (setOpenProp) { + setOpenProp(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProp, open] +) +``` + +### 2024-10-21 Fixed `text-sidebar-foreground` - [#5491](https://github.com/shadcn-ui/ui/pull/5491) - Moved `text-sidebar-foreground` from `` to `` component. diff --git a/apps/www/content/docs/installation/next.mdx b/apps/www/content/docs/installation/next.mdx index e9923e2541b..4051e57e25e 100644 --- a/apps/www/content/docs/installation/next.mdx +++ b/apps/www/content/docs/installation/next.mdx @@ -3,6 +3,12 @@ title: Next.js description: Install and configure Next.js. --- + + +**If you're using Next.js 15, see the [Next.js 15 + React 19](/docs/react-19) guide.** + + + ### Create project diff --git a/apps/www/content/docs/installation/vite.mdx b/apps/www/content/docs/installation/vite.mdx index 3dcfb4f4f76..2b74834a857 100644 --- a/apps/www/content/docs/installation/vite.mdx +++ b/apps/www/content/docs/installation/vite.mdx @@ -23,6 +23,29 @@ npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p ``` +Add this import header in your main css file, `src/index.css` in our case: + +```css {1-3} showLineNumbers +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* ... */ +``` + +Configure the tailwind template paths in `tailwind.config.js`: + +```js {3} +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + theme: { + extend: {}, + }, + plugins: [], +} +``` + ### Edit tsconfig.json file The current version of Vite splits TypeScript configuration into three files, two of which need to be edited. diff --git a/apps/www/content/docs/react-19.mdx b/apps/www/content/docs/react-19.mdx new file mode 100644 index 00000000000..4ba6a5ec8b9 --- /dev/null +++ b/apps/www/content/docs/react-19.mdx @@ -0,0 +1,168 @@ +--- +title: Next.js 15 + React 19 +description: Using shadcn/ui with Next.js 15 and React 19. +--- + + + **The following guide applies to any framework that supports React 19**. I + titled this page "Next.js 15 + React 19" to help people upgrading to Next.js + 15 find it. We are working with package maintainers to help upgrade to React + 19. + + +## TL;DR + +If you're using `npm`, you can install shadcn/ui dependencies with a flag. The `shadcn` CLI will prompt you to select a flag when you run it. No flags required for pnpm, bun, or yarn. + +See [Upgrade Status](#upgrade-status) for the status of React 19 support for each package. + +## What's happening? + +React 19 is now [rc](https://www.npmjs.com/package/react?activeTab=versions) and is [tested and supported in the latest Next.js 15 release](https://nextjs.org/blog/next-15#react-19). + +To support React 19, package maintainers will need to test and update their packages to include React 19 as a peer dependency. This is [already](https://github.com/radix-ui/primitives/pull/2952) [in](https://github.com/pacocoursey/cmdk/pull/318) [progress](https://github.com/emilkowalski/vaul/pull/498). + +```diff /^19.0/ +"peerDependencies": { +- "react": "^16.8 || ^17.0 || ^18.0", ++ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0", +- "react-dom": "^16.8 || ^17.0 || ^18.0" ++ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0" +}, +``` + + + You can check if a package lists React 19 as a peer dependency by running + `npm info peerDependencies`. + + +In the meantime, if you are installing a package that **does not** list React 19 as a peer dependency, you will see an error message like this: + +```bash +npm error code ERESOLVE +npm error ERESOLVE unable to resolve dependency tree +npm error +npm error While resolving: my-app@0.1.0 +npm error Found: react@19.0.0-rc-69d4b800-20241021 +npm error node_modules/react +npm error react@"19.0.0-rc-69d4b800-20241021" from the root project +``` + + + **Note:** This is npm only. PNPM and Bun will only show a silent warning. + + +## How to fix this + +### Solution 1: `--force` or `--legacy-peer-deps` + +You can force install a package with the `--force` or the `--legacy-peer-deps` flag. + +```bash +npm i --force + +npm i --legacy-peer-deps +``` + +This will install the package and ignore the peer dependency warnings. + + + + + What do the `--force` and `--legacy-peer-deps` flag do? + + + + - `--force`: Ignores and overrides any dependency conflicts, forcing the + installation of packages. + - `--legacy-peer-deps`: Skips strict peer dependency checks, allowing + installation of packages with unmet peer dependencies to avoid errors. + + + + + + +### Solution 2: Use React 18 + +You can downgrade `react` and `react-dom` to version 18, which is compatible with the package you are installing and upgrade when the dependency is updated. + +```bash +npm i react@18 react-dom@18 +``` + +Whichever solution you choose, make sure you test your app thoroughly to ensure +there are no regressions. + +## Using shadcn/ui on Next.js 15 + +### Using pnpm, bun, or yarn + +Follow the instructions in the [installation guide](/docs/installation/next) to install shadcn/ui. No flags are needed. + +### Using npm + +When you run `npx shadcn@latest init -d`, you will be prompted to select an option to resolve the peer dependency issues. + +```bash +It looks like you are using React 19. +Some packages may fail to install due to peer dependency issues (see https://ui.shadcn.com/react-19). + +? How would you like to proceed? › - Use arrow-keys. Return to submit. +❯ Use --force + Use --legacy-peer-deps +``` + +You can then run the command with the flag you choose. + +## Adding components + +The process for adding components is the same as above. Select a flag to resolve the peer dependency issues. + +**Remember to always test your app after installing new dependencies.** + +## 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. + +- ✅ - 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. + +| Package | Status | Note | +| ---------------------------------------------------------------------------------- | ------ | ----------------------------------------------------------- | +| [radix-ui](https://www.npmjs.com/package/@radix-ui/react-icons) | ✅ | | +| [lucide-react](https://www.npmjs.com/package/lucide-react) | ✅ | | +| [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) | ✅ | Does not list React 19 as a peer dependency. | +| [tailwindcss-animate](https://www.npmjs.com/package/tailwindcss-animate) | ✅ | Does not list React 19 as a peer dependency. | +| [embla-carousel-react](https://www.npmjs.com/package/embla-carousel-react) | ✅ | | +| [recharts](https://www.npmjs.com/package/recharts) | ✅ | See note [below](#recharts) | +| [react-hook-form](https://www.npmjs.com/package/react-hook-form) | ✅ | | +| [react-resizable-panels](https://www.npmjs.com/package/react-resizable-panels) | ✅ | | +| [sonner](https://www.npmjs.com/package/sonner) | ✅ | | +| [react-day-picker](https://www.npmjs.com/package/react-day-picker) | ✅ | Works with flag for npm. Work to upgrade to v9 in progress. | +| [input-otp](https://www.npmjs.com/package/input-otp) | ✅ | | +| [vaul](https://www.npmjs.com/package/vaul) | ✅ | | +| [@radix-ui/react-icons](https://www.npmjs.com/package/@radix-ui/react-icons) | ✅ | | +| [cmdk](https://www.npmjs.com/package/cmdk) | 🚧 | See [PR #318](https://github.com/pacocoursey/cmdk/pull/318) | + +If you have any questions, please [open an issue](https://github.com/shadcn/ui/issues) on GitHub. + +## Recharts + +To use recharts with React 19, you will need to override the `react-is` dependency. + + + +Add the following to your `package.json` + +```json title="package.json" +"overrides": { + "react-is": "^19.0.0-rc-69d4b800-20241021" +} +``` + +Note: the `react-is` version needs to match the version of React 19 you are using. The above is an example. + +Run `npm install --legacy-peer-deps` + + diff --git a/apps/www/next.config.mjs b/apps/www/next.config.mjs index 02f9ee7aa12..299da42690e 100644 --- a/apps/www/next.config.mjs +++ b/apps/www/next.config.mjs @@ -58,6 +58,11 @@ const nextConfig = { destination: "/docs/components/sidebar", permanent: true, }, + { + source: "/react-19", + destination: "/docs/react-19", + permanent: true, + }, ] }, } diff --git a/apps/www/public/r/styles/default/sidebar.json b/apps/www/public/r/styles/default/sidebar.json index 14d2533d576..22414a5f23d 100644 --- a/apps/www/public/r/styles/default/sidebar.json +++ b/apps/www/public/r/styles/default/sidebar.json @@ -18,7 +18,7 @@ "files": [ { "path": "ui/sidebar.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { PanelLeft } from \"lucide-react\"\n\nimport { useIsMobile } from \"@/registry/default/hooks/use-mobile\"\nimport { cn } from \"@/registry/default/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Separator } from \"@/registry/default/ui/separator\"\nimport { Sheet, SheetContent } from \"@/registry/default/ui/sheet\"\nimport { Skeleton } from \"@/registry/default/ui/skeleton\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\"\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar:state\"\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\"\n\ntype SidebarContext = {\n state: \"expanded\" | \"collapsed\"\n open: boolean\n setOpen: (open: boolean) => void\n openMobile: boolean\n setOpenMobile: (open: boolean) => void\n isMobile: boolean\n toggleSidebar: () => void\n}\n\nconst SidebarContext = React.createContext(null)\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext)\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\")\n }\n\n return context\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean\n open?: boolean\n onOpenChange?: (open: boolean) => void\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref\n ) => {\n const isMobile = useIsMobile()\n const [openMobile, setOpenMobile] = React.useState(false)\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen)\n const open = openProp ?? _open\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n if (setOpenProp) {\n return setOpenProp?.(\n typeof value === \"function\" ? value(open) : value\n )\n }\n\n _setOpen(value)\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n },\n [setOpenProp, open]\n )\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile\n ? setOpenMobile((open) => !open)\n : setOpen((open) => !open)\n }, [isMobile, setOpen, setOpenMobile])\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n (event.metaKey || event.ctrlKey)\n ) {\n event.preventDefault()\n toggleSidebar()\n }\n }\n\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [toggleSidebar])\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\"\n\n const contextValue = React.useMemo(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n )\n\n return (\n \n \n \n {children}\n \n \n \n )\n }\n)\nSidebarProvider.displayName = \"SidebarProvider\"\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\"\n variant?: \"sidebar\" | \"floating\" | \"inset\"\n collapsible?: \"offcanvas\" | \"icon\" | \"none\"\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar()\n\n if (collapsible === \"none\") {\n return (\n \n {children}\n \n )\n }\n\n if (isMobile) {\n return (\n \n button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n
{children}
\n \n
\n )\n }\n\n return (\n