mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-30 08:04:18 +00:00
feat(shadcn): install routes for next-pages, laravel and react-router (#6811)
* feat(shadcn): install routes for next-pages, laravel and react-router * chore: changeset
This commit is contained in:
5
.changeset/tasty-walls-drum.md
Normal file
5
.changeset/tasty-walls-drum.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
add support for route install for react-router and laravel
|
||||
@@ -23,6 +23,15 @@ export const FRAMEWORKS = {
|
||||
tailwind: "https://tailwindcss.com/docs/guides/remix",
|
||||
},
|
||||
},
|
||||
"react-router": {
|
||||
name: "react-router",
|
||||
label: "React Router",
|
||||
links: {
|
||||
installation: "https://ui.shadcn.com/docs/installation/react-router",
|
||||
tailwind:
|
||||
"https://tailwindcss.com/docs/installation/framework-guides/react-router",
|
||||
},
|
||||
},
|
||||
vite: {
|
||||
name: "vite",
|
||||
label: "Vite",
|
||||
|
||||
@@ -14,7 +14,7 @@ import { z } from "zod"
|
||||
|
||||
export type TailwindVersion = "v3" | "v4" | null
|
||||
|
||||
type ProjectInfo = {
|
||||
export type ProjectInfo = {
|
||||
framework: Framework
|
||||
isSrcDir: boolean
|
||||
isRSC: boolean
|
||||
@@ -50,11 +50,14 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
|
||||
aliasPrefix,
|
||||
packageJson,
|
||||
] = await Promise.all([
|
||||
fg.glob("**/{next,vite,astro,app}.config.*|gatsby-config.*|composer.json", {
|
||||
cwd,
|
||||
deep: 3,
|
||||
ignore: PROJECT_SHARED_IGNORE,
|
||||
}),
|
||||
fg.glob(
|
||||
"**/{next,vite,astro,app}.config.*|gatsby-config.*|composer.json|react-router.config.*",
|
||||
{
|
||||
cwd,
|
||||
deep: 3,
|
||||
ignore: PROJECT_SHARED_IGNORE,
|
||||
}
|
||||
),
|
||||
fs.pathExists(path.resolve(cwd, "src")),
|
||||
isTypeScriptProject(cwd),
|
||||
getTailwindConfigFile(cwd),
|
||||
@@ -128,6 +131,14 @@ export async function getProjectInfo(cwd: string): Promise<ProjectInfo | null> {
|
||||
return type
|
||||
}
|
||||
|
||||
// React Router.
|
||||
if (
|
||||
configFiles.find((file) => file.startsWith("react-router.config."))?.length
|
||||
) {
|
||||
type.framework = FRAMEWORKS["react-router"]
|
||||
return type
|
||||
}
|
||||
|
||||
// Vite.
|
||||
// Some Remix templates also have a vite.config.* file.
|
||||
// We'll assume that it got caught by the Remix check above.
|
||||
|
||||
@@ -3,7 +3,7 @@ import path, { basename } from "path"
|
||||
import { getRegistryBaseColor } from "@/src/registry/api"
|
||||
import { RegistryItem, registryItemFileSchema } from "@/src/registry/schema"
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
import { getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { ProjectInfo, getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import { spinner } from "@/src/utils/spinner"
|
||||
@@ -61,11 +61,17 @@ export async function updateFiles(
|
||||
|
||||
let filePath = resolveFilePath(file, config, {
|
||||
isSrcDir: projectInfo?.isSrcDir,
|
||||
framework: projectInfo?.framework.name,
|
||||
commonRoot: findCommonRoot(
|
||||
files.map((f) => f.path),
|
||||
file.path
|
||||
),
|
||||
})
|
||||
|
||||
if (!filePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
const fileName = basename(file.path)
|
||||
const targetDir = path.dirname(filePath)
|
||||
|
||||
@@ -216,6 +222,7 @@ export function resolveFilePath(
|
||||
options: {
|
||||
isSrcDir?: boolean
|
||||
commonRoot?: string
|
||||
framework?: ProjectInfo["framework"]["name"]
|
||||
}
|
||||
) {
|
||||
if (file.target) {
|
||||
@@ -223,13 +230,18 @@ export function resolveFilePath(
|
||||
return path.join(config.resolvedPaths.cwd, file.target.replace("~/", ""))
|
||||
}
|
||||
|
||||
let target = file.target
|
||||
|
||||
if (file.type === "registry:page") {
|
||||
target = resolvePageTarget(target, options.framework)
|
||||
if (!target) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
return options.isSrcDir
|
||||
? path.join(
|
||||
config.resolvedPaths.cwd,
|
||||
"src",
|
||||
file.target.replace("src/", "")
|
||||
)
|
||||
: path.join(config.resolvedPaths.cwd, file.target.replace("src/", ""))
|
||||
? path.join(config.resolvedPaths.cwd, "src", target.replace("src/", ""))
|
||||
: path.join(config.resolvedPaths.cwd, target.replace("src/", ""))
|
||||
}
|
||||
|
||||
const targetDir = resolveFileTargetDirectory(file, config)
|
||||
@@ -323,3 +335,39 @@ export function resolveNestedFilePath(
|
||||
export async function getNormalizedFileContent(content: string) {
|
||||
return content.replace(/\r\n/g, "\n").trim()
|
||||
}
|
||||
|
||||
export function resolvePageTarget(
|
||||
target: string,
|
||||
framework?: ProjectInfo["framework"]["name"]
|
||||
) {
|
||||
if (!framework) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (framework === "next-app") {
|
||||
return target
|
||||
}
|
||||
|
||||
if (framework === "next-pages") {
|
||||
let result = target.replace(/^app\//, "pages/")
|
||||
result = result.replace(/\/page(\.[jt]sx?)$/, "$1")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
if (framework === "react-router") {
|
||||
let result = target.replace(/^app\//, "app/routes/")
|
||||
result = result.replace(/\/page(\.[jt]sx?)$/, "$1")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
if (framework === "laravel") {
|
||||
let result = target.replace(/^app\//, "resources/js/pages/")
|
||||
result = result.replace(/\/page(\.[jt]sx?)$/, "$1")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ const DialogOverlay = React.forwardRef<
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -444,6 +444,178 @@ describe("resolveFilePath", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolveFilePath with framework", () => {
|
||||
test("should not resolve for unknown or unsupported framework", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/login/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/login/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/login/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/login/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
framework: "vite",
|
||||
}
|
||||
)
|
||||
).toBe("")
|
||||
})
|
||||
|
||||
test("should resolve for next-app", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/login/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/login/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
framework: "next-app",
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/app/login/page.tsx")
|
||||
})
|
||||
|
||||
test("should resolve for next-pages", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/login/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/login/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: true,
|
||||
framework: "next-pages",
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/src/pages/login.tsx")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/blog/[slug]/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/blog/[slug]/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/primitives",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
framework: "next-pages",
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/pages/blog/[slug].tsx")
|
||||
})
|
||||
|
||||
test("should resolve for react-router", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/login/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/login/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/app/components",
|
||||
ui: "/foo/bar/app/components/ui",
|
||||
lib: "/foo/bar/app/lib",
|
||||
hooks: "/foo/bar/app/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
framework: "react-router",
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/app/routes/login.tsx")
|
||||
})
|
||||
|
||||
test("should resolve for laravel", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/app/login/page.tsx",
|
||||
type: "registry:page",
|
||||
target: "app/login/page.tsx",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/resources/js/components",
|
||||
ui: "/foo/bar/resources/js/components/ui",
|
||||
lib: "/foo/bar/resources/js/lib",
|
||||
hooks: "/foo/bar/resources/js/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
framework: "laravel",
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/resources/js/pages/login.tsx")
|
||||
})
|
||||
})
|
||||
|
||||
describe("findCommonRoot", () => {
|
||||
test.each([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user