From 585e473367bebd98506b2a249b07792345b826fb Mon Sep 17 00:00:00 2001 From: arndom <aminonimisi2@gmail.com> Date: Tue, 21 Jan 2025 15:36:13 +0100 Subject: [PATCH 1/2] fix(core): add dynamic ids as part of breadcrumbs - display end id when on action(show/edit) page - display nested id in route - add test case for these - add changeset --- .changeset/shaggy-pants-argue.md | 7 +++ .../core/src/hooks/breadcrumb/index.spec.tsx | 58 +++++++++++++++++++ packages/core/src/hooks/breadcrumb/index.ts | 50 ++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 .changeset/shaggy-pants-argue.md diff --git a/.changeset/shaggy-pants-argue.md b/.changeset/shaggy-pants-argue.md new file mode 100644 index 000000000000..ce3c6ad6172d --- /dev/null +++ b/.changeset/shaggy-pants-argue.md @@ -0,0 +1,7 @@ +--- +"@refinedev/core": patch +--- + +Update `useBreadcrumb` to include dynamic ids in return. + +[Resolves #6584](https://github.com/refinedev/refine/issues/6584) diff --git a/packages/core/src/hooks/breadcrumb/index.spec.tsx b/packages/core/src/hooks/breadcrumb/index.spec.tsx index 0425186dd0b5..5bfd2ac8b683 100644 --- a/packages/core/src/hooks/breadcrumb/index.spec.tsx +++ b/packages/core/src/hooks/breadcrumb/index.spec.tsx @@ -181,4 +181,62 @@ describe("useBreadcrumb Hook", () => { { label: "buttons.show" }, ]); }); + + it("if resource has nested resource with dynamic id -> route/:id/nested-route, the nested should be in breadcrumbs", async () => { + const { result } = renderHook(() => useBreadcrumb(), { + wrapper: renderWrapper({ + resources: [ + { + name: "blog_posts", + list: "/blog-posts", + show: "/blog-posts/show/:id", + create: "/blog-posts/create", + edit: "/blog-posts/edit/:id", + meta: { + canDelete: true, + }, + }, + + { + name: "comments", + list: "blog-posts/show/:id/comments", + show: "blog-posts/show/:id/comments/:id", + meta: { + parent: "blog-posts", + }, + }, + ], + routerProvider: mockRouterProvider({ + action: "list", + id: "2", + pathname: "/blog-posts/show/2/comments", + resource: { + name: "comments", + list: "blog-posts/show/:id/comments", + show: "blog-posts/show/:id/comments/:id", + meta: { + parent: "blog-posts", + }, + }, + }), + }), + }); + + expect(result.current.breadcrumbs).toEqual([ + { + label: "Blog posts", + href: undefined, + icon: undefined, + }, + { + label: "2", + href: "blog-posts/show/2", + }, + { + label: "Comments", + href: "/blog-posts/show/2/comments", + icon: undefined, + }, + ]); + }); }); diff --git a/packages/core/src/hooks/breadcrumb/index.ts b/packages/core/src/hooks/breadcrumb/index.ts index 762e7b9a1630..d2ef95b7ef5d 100644 --- a/packages/core/src/hooks/breadcrumb/index.ts +++ b/packages/core/src/hooks/breadcrumb/index.ts @@ -80,6 +80,48 @@ export const useBreadcrumb = ({ : composeRoute(hrefRaw, parentResource?.meta, parsed, metaFromProps) : undefined; + // innner route params: /:id in main-route/:id/sub-route + if (hrefRaw) { + const regex = /[^/]+\/:id\//g; + const hrefRegexMatch = hrefRaw.match(regex); + + if (hrefRegexMatch) { + const matches = hrefRegexMatch.map((match) => { + const end = hrefRaw.indexOf(match) + match.length; + return hrefRaw.slice(0, end); + }); + + if (matches && parsed.pathname) { + const lastMatch = matches.at(-1); + + if (lastMatch) { + const pathnameSegments = parsed.pathname + .split("/") + .filter((segment) => segment); + const lastMatchSegments = lastMatch + .split("/") + .filter((segment) => segment); + + const urlBits: string[] = []; + lastMatchSegments.forEach((segment, index) => { + let x = segment; + + if (segment !== pathnameSegments[index]) { + x = pathnameSegments[index]; + } + + urlBits.push(x); + }); + + const label = urlBits.at(-1); + const href = [...urlBits].join("/"); + + breadcrumbs.push({ label: String(label), href }); + } + } + } + } + breadcrumbs.push({ label: pickNotDeprecated( @@ -121,6 +163,14 @@ export const useBreadcrumb = ({ label: translate(key, textTransformers.humanize(action)), }); } + + // add action path param to breadcrum + if (parsed.id) { + breadcrumbs.push({ + label: String(parsed.id), + href: parsed.pathname, + }); + } } return { From e7a23153df0fce2b6ed60a1b0c7264238acbe1f2 Mon Sep 17 00:00:00 2001 From: arndom <aminonimisi2@gmail.com> Date: Thu, 23 Jan 2025 16:07:28 +0100 Subject: [PATCH 2/2] fix(core): update regex to match any `:id` formats in breadcrumb - to match ids like: `:orgId`, `:projectId`, not just `:id` --- packages/core/src/hooks/breadcrumb/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/hooks/breadcrumb/index.ts b/packages/core/src/hooks/breadcrumb/index.ts index d2ef95b7ef5d..58aaf6fb45ba 100644 --- a/packages/core/src/hooks/breadcrumb/index.ts +++ b/packages/core/src/hooks/breadcrumb/index.ts @@ -82,7 +82,11 @@ export const useBreadcrumb = ({ // innner route params: /:id in main-route/:id/sub-route if (hrefRaw) { - const regex = /[^/]+\/:id\//g; + // [^/]+ --> any number of letters not `/` + // \/: ---> start with /: + // [^/]+ --> any number of letters not `/` + // \/ -- end with / + const regex = /[^/]+\/:[^/]+\//g; const hrefRegexMatch = hrefRaw.match(regex); if (hrefRegexMatch) {