mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
feat: upgrade to Next.js 16 (#8615)
* feat: upgrade to Next.js 16 * chore: deps * fix * fix * fix * fix: workaround zod 4 for now * fix * fix: copy button * fix: update apps/v4/hooks/use-is-mac.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix * fix: remove --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
4
.github/workflows/prerelease.yml
vendored
4
.github/workflows/prerelease.yml
vendored
@@ -27,10 +27,10 @@ jobs:
|
||||
with:
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 18
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -23,11 +23,11 @@ jobs:
|
||||
with:
|
||||
version: 9.0.6
|
||||
|
||||
- name: Use Node.js 18
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
version: 9.0.6
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
IconArrowRight,
|
||||
IconArrowUpRight,
|
||||
} from "@tabler/icons-react"
|
||||
import { findNeighbour } from "fumadocs-core/server"
|
||||
import fm from "front-matter"
|
||||
import { findNeighbour } from "fumadocs-core/page-tree"
|
||||
import z from "zod"
|
||||
|
||||
import { source } from "@/lib/source"
|
||||
import { absoluteUrl } from "@/lib/utils"
|
||||
@@ -25,7 +27,7 @@ export function generateStaticParams() {
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
params: Promise<{ slug: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
@@ -73,7 +75,7 @@ export async function generateMetadata(props: {
|
||||
}
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
params: Promise<{ slug: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
@@ -82,12 +84,21 @@ export default async function Page(props: {
|
||||
}
|
||||
|
||||
const doc = page.data
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const MDX = doc.body
|
||||
const neighbours = await findNeighbour(source.pageTree, page.url)
|
||||
const neighbours = findNeighbour(source.pageTree, page.url)
|
||||
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const links = doc.links
|
||||
const raw = await page.data.getText("raw")
|
||||
const { attributes } = fm(raw)
|
||||
const { links } = z
|
||||
.object({
|
||||
links: z
|
||||
.object({
|
||||
doc: z.string().optional(),
|
||||
api: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.parse(attributes)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -104,11 +115,7 @@ export default async function Page(props: {
|
||||
{doc.title}
|
||||
</h1>
|
||||
<div className="docs-nav bg-background/80 border-border/50 fixed inset-x-0 bottom-0 isolate z-50 flex items-center gap-2 border-t px-6 py-4 backdrop-blur-sm sm:static sm:z-0 sm:border-t-0 sm:bg-transparent sm:px-0 sm:pt-1.5 sm:backdrop-blur-none">
|
||||
<DocsCopyPage
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
page={doc.content}
|
||||
url={absoluteUrl(page.url)}
|
||||
/>
|
||||
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -195,10 +202,8 @@ export default async function Page(props: {
|
||||
</div>
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--footer-height)+2rem)] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
{doc.toc?.length ? (
|
||||
<div className="no-scrollbar overflow-y-auto px-8">
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
<DocsTableOfContents toc={doc.toc} />
|
||||
<div className="h-12" />
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const revalidate = false
|
||||
|
||||
export async function GET(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string[] }> }
|
||||
{ params }: { params: Promise<{ slug?: string[] }> }
|
||||
) {
|
||||
const slug = (await params).slug
|
||||
const page = source.getPage(slug)
|
||||
@@ -17,8 +17,7 @@ export async function GET(
|
||||
notFound()
|
||||
}
|
||||
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const processedContent = processMdxForLLMs(page.data.content)
|
||||
const processedContent = processMdxForLLMs(await page.data.getText("raw"))
|
||||
|
||||
return new NextResponse(processedContent, {
|
||||
headers: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/static-components */
|
||||
import * as React from "react"
|
||||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
@@ -13,7 +13,6 @@ import { showMcpDocs } from "@/lib/flags"
|
||||
import { source } from "@/lib/source"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
import { useIsMac } from "@/hooks/use-is-mac"
|
||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -50,7 +49,6 @@ export function CommandMenu({
|
||||
navItems?: { href: string; label: string }[]
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const isMac = useIsMac()
|
||||
const [config] = useConfig()
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [selectedType, setSelectedType] = React.useState<
|
||||
@@ -206,7 +204,7 @@ export function CommandMenu({
|
||||
<span className="inline-flex lg:hidden">Search...</span>
|
||||
<div className="absolute top-1.5 right-1.5 hidden gap-1 sm:flex">
|
||||
<KbdGroup>
|
||||
<Kbd className="border">{isMac ? "⌘" : "Ctrl"}</Kbd>
|
||||
<Kbd className="border">⌘</Kbd>
|
||||
<Kbd className="border">K</Kbd>
|
||||
</KbdGroup>
|
||||
</div>
|
||||
@@ -404,7 +402,7 @@ export function CommandMenu({
|
||||
<>
|
||||
<Separator orientation="vertical" className="!h-4" />
|
||||
<div className="flex items-center gap-1">
|
||||
<CommandMenuKbd>{isMac ? "⌘" : "Ctrl"}</CommandMenuKbd>
|
||||
<CommandMenuKbd>⌘</CommandMenuKbd>
|
||||
<CommandMenuKbd>C</CommandMenuKbd>
|
||||
{copyPayload}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Fragment } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useBreadcrumb } from "fumadocs-core/breadcrumb"
|
||||
import type { PageTree } from "fumadocs-core/server"
|
||||
import type { Root } from "fumadocs-core/page-tree"
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -19,7 +19,7 @@ export function DocsBreadcrumb({
|
||||
tree,
|
||||
className,
|
||||
}: {
|
||||
tree: PageTree.Root
|
||||
tree: Root
|
||||
className?: string
|
||||
}) {
|
||||
const pathname = usePathname()
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { dirname } from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { FlatCompat } from "@eslint/eslintrc"
|
||||
import { defineConfig, globalIgnores } from "eslint/config"
|
||||
import nextVitals from "eslint-config-next/core-web-vitals"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
})
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.config({
|
||||
extends: ["next/core-web-vitals", "next/typescript"],
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
globalIgnores([
|
||||
"node_modules/**",
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
".source/**",
|
||||
]),
|
||||
{
|
||||
rules: {
|
||||
"@next/next/no-duplicate-head": "off",
|
||||
"react-hooks/incompatible-library": "off",
|
||||
"react-hooks/purity": "off",
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
])
|
||||
|
||||
export default eslintConfig
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export function useIsMac() {
|
||||
const [isMac, setIsMac] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setIsMac(navigator.platform.toUpperCase().includes("MAC"))
|
||||
}, [])
|
||||
|
||||
return isMac
|
||||
}
|
||||
@@ -61,7 +61,10 @@ const Layout = ({
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = !value ? ["layout-fixed", "layout-full"] : Object.values(value)
|
||||
const attrs = React.useMemo(
|
||||
() => (!value ? ["layout-fixed", "layout-full"] : Object.values(value)),
|
||||
[value]
|
||||
)
|
||||
|
||||
const applyLayout = React.useCallback(
|
||||
(layout: Layout) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ export function useIsMobile(mobileBreakpoint = 768) {
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < mobileBreakpoint)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
}, [mobileBreakpoint])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Index } from "@/registry/__index__"
|
||||
|
||||
export function processMdxForLLMs(content: string) {
|
||||
const componentPreviewRegex =
|
||||
/<ComponentPreview\s+[^>]*name="([^"]+)"[^>]*\/>/g
|
||||
/<ComponentPreview[\s\S]*?name="([^"]+)"[\s\S]*?\/>/g
|
||||
|
||||
return content.replace(componentPreviewRegex, (match, name) => {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { docs } from "@/.source"
|
||||
import { loader } from "fumadocs-core/source"
|
||||
|
||||
export const source: ReturnType<typeof loader> = loader({
|
||||
export const source = loader({
|
||||
baseUrl: "/docs",
|
||||
source: docs.toFumadocsSource(),
|
||||
})
|
||||
|
||||
@@ -128,7 +128,6 @@ export const mdxComponents = {
|
||||
/>
|
||||
),
|
||||
img: ({ className, alt, ...props }: React.ComponentProps<"img">) => (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img className={cn("rounded-md", className)} alt={alt} {...props} />
|
||||
),
|
||||
hr: ({ ...props }: React.ComponentProps<"hr">) => (
|
||||
@@ -281,7 +280,7 @@ export const mdxComponents = {
|
||||
}: React.ComponentProps<"img">) => (
|
||||
<Image
|
||||
className={cn("mt-6 rounded-md border", className)}
|
||||
src={src || ""}
|
||||
src={(src as string) || ""}
|
||||
width={Number(width)}
|
||||
height={Number(height)}
|
||||
alt={alt || ""}
|
||||
|
||||
@@ -25,6 +25,9 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
experimental: {
|
||||
turbopackFileSystemCacheForDev: true,
|
||||
},
|
||||
redirects() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"dev": "next dev --turbopack --port 4000",
|
||||
"build": "pnpm --filter=shadcn build && next build",
|
||||
"start": "next start --port 4000",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
|
||||
@@ -67,22 +67,23 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-autoplay": "8.5.2",
|
||||
"embla-carousel-react": "8.5.2",
|
||||
"fumadocs-core": "15.3.1",
|
||||
"front-matter": "^4.0.2",
|
||||
"fumadocs-core": "16.0.5",
|
||||
"fumadocs-docgen": "2.0.0",
|
||||
"fumadocs-mdx": "11.6.3",
|
||||
"fumadocs-ui": "15.3.1",
|
||||
"fumadocs-mdx": "13.0.2",
|
||||
"fumadocs-ui": "16.0.5",
|
||||
"input-otp": "^1.4.2",
|
||||
"jotai": "^2.1.0",
|
||||
"little-date": "^1.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "0.474.0",
|
||||
"motion": "^12.12.1",
|
||||
"next": "15.3.1",
|
||||
"next": "16.0.0",
|
||||
"next-themes": "0.4.6",
|
||||
"postcss": "^8.5.1",
|
||||
"react": "19.1.0",
|
||||
"react": "19.2.0",
|
||||
"react-day-picker": "^9.7.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
@@ -95,20 +96,19 @@
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"ts-morph": "18.0.0",
|
||||
"vaul": "1.1.2",
|
||||
"zod": "^3.24.1"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2",
|
||||
"@typescript-eslint/parser": "^8.31.0",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.1",
|
||||
"eslint-config-next": "16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^4.1.11",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -100,7 +100,9 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
)
|
||||
}
|
||||
|
||||
function Tree({ item }: { item: string | any[] }) {
|
||||
type TreeItem = string | TreeItem[]
|
||||
|
||||
function Tree({ item }: { item: TreeItem }) {
|
||||
const [name, ...items] = Array.isArray(item) ? item : [item]
|
||||
|
||||
if (!items.length) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import * as React from "react"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { CheckIcon } from "lucide-react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import { Controller, useForm, useWatch } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
import * as z from "zod"
|
||||
|
||||
@@ -83,7 +83,10 @@ export default function FormRhfPassword() {
|
||||
},
|
||||
})
|
||||
|
||||
const password = form.watch("password")
|
||||
const password = useWatch({
|
||||
control: form.control,
|
||||
name: "password",
|
||||
})
|
||||
|
||||
// Calculate password strength.
|
||||
const metRequirements = passwordRequirements.filter((req) =>
|
||||
|
||||
@@ -30,7 +30,8 @@ export default function SheetSide() {
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit profile</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
Make changes to your profile here. Click save when you're
|
||||
done.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
|
||||
@@ -5,10 +5,10 @@ export default function TypographyTable() {
|
||||
<thead>
|
||||
<tr className="even:bg-muted m-0 border-t p-0">
|
||||
<th className="border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right">
|
||||
King's Treasury
|
||||
King's Treasury
|
||||
</th>
|
||||
<th className="border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right">
|
||||
People's happiness
|
||||
People's happiness
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineConfig,
|
||||
defineDocs,
|
||||
frontmatterSchema,
|
||||
} from "fumadocs-mdx/config"
|
||||
import { defineConfig, defineDocs } from "fumadocs-mdx/config"
|
||||
import rehypePrettyCode from "rehype-pretty-code"
|
||||
import { z } from "zod"
|
||||
|
||||
import { transformers } from "@/lib/highlight-code"
|
||||
|
||||
@@ -13,8 +8,6 @@ export default defineConfig({
|
||||
rehypePlugins: (plugins) => {
|
||||
plugins.shift()
|
||||
plugins.push([
|
||||
// TODO: fix the type.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
rehypePrettyCode as any,
|
||||
{
|
||||
theme: {
|
||||
@@ -32,14 +25,15 @@ export default defineConfig({
|
||||
|
||||
export const docs = defineDocs({
|
||||
dir: "content/docs",
|
||||
docs: {
|
||||
schema: frontmatterSchema.extend({
|
||||
links: z
|
||||
.object({
|
||||
doc: z.string().optional(),
|
||||
api: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
},
|
||||
// TODO: Fix this when we upgrade to zod v4.
|
||||
// docs: {
|
||||
// schema: frontmatterSchema.extend({
|
||||
// links: z.optional(
|
||||
// z.object({
|
||||
// doc: z.string().optional(),
|
||||
// api: z.string().optional(),
|
||||
// })
|
||||
// ),
|
||||
// }),
|
||||
// },
|
||||
})
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -11,7 +15,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
@@ -20,8 +24,12 @@
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"react": ["./node_modules/@types/react"]
|
||||
"@/*": [
|
||||
"./*"
|
||||
],
|
||||
"react": [
|
||||
"./node_modules/@types/react"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
@@ -30,7 +38,10 @@
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"scripts/build-registry.mts",
|
||||
"next.config.mjs"
|
||||
"next.config.mjs",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -89,5 +89,11 @@
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"start-server-and-test": "^2.0.12",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3981
pnpm-lock.yaml
generated
3981
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user