diff --git a/.changeset/ten-timers-change.md b/.changeset/ten-timers-change.md new file mode 100644 index 0000000000000..cf0d7a96dd585 --- /dev/null +++ b/.changeset/ten-timers-change.md @@ -0,0 +1,8 @@ +--- +"@refinedev/core": patch +--- + +- Add custom route navigation to core router. +- Supported by all router integrations; react, nextjs, remix. + +[Resolves #6864](https://github.com/refinedev/refine/issues/6864) diff --git a/documentation/docs/guides-concepts/routing/index.md b/documentation/docs/guides-concepts/routing/index.md index ff754518d4d15..d1d9d9e5561c2 100644 --- a/documentation/docs/guides-concepts/routing/index.md +++ b/documentation/docs/guides-concepts/routing/index.md @@ -167,6 +167,7 @@ All you have to do is to define your resource and their routes. create: "/my-products/new", // http://localhost:3000/my-products/new edit: "/my-products/:id/edit", // http://localhost:3000/my-products/1/edit clone: "/my-products/:id/clone", // http://localhost:3000/my-products/1/clone + custom: ["/my-products/{custom-route}"], // http://localhost:3000/my-products/{custom-route} }, ]} /> diff --git a/documentation/docs/guides-concepts/routing/nextjs/resource-and-routes-usage.tsx b/documentation/docs/guides-concepts/routing/nextjs/resource-and-routes-usage.tsx index 8ec321f796ed7..e3bcaf3f13478 100644 --- a/documentation/docs/guides-concepts/routing/nextjs/resource-and-routes-usage.tsx +++ b/documentation/docs/guides-concepts/routing/nextjs/resource-and-routes-usage.tsx @@ -82,6 +82,7 @@ function App({ Component, pageProps }: AppProps) { // create: "/my-products/new", // edit: "/my-products/:id/edit", // clone: "/my-products/:id/clone", + // custom: ["/my-products/setttings", "/my-products/statistics", "/my-products/etc"], }, ]} > diff --git a/documentation/docs/guides-concepts/routing/react-router/resource-and-routes-usage.tsx b/documentation/docs/guides-concepts/routing/react-router/resource-and-routes-usage.tsx index a0af5b080d844..535aa0cb12157 100644 --- a/documentation/docs/guides-concepts/routing/react-router/resource-and-routes-usage.tsx +++ b/documentation/docs/guides-concepts/routing/react-router/resource-and-routes-usage.tsx @@ -62,6 +62,7 @@ export default function App() { // create: "/my-products/new", // edit: "/my-products/:id/edit", // clone: "/my-products/:id/clone", + // custom: ["/my-products/setttings", "/my-products/statistics", "/my-products/etc"], }, ]} > diff --git a/documentation/docs/guides-concepts/routing/remix/resource-and-routes-usage.tsx b/documentation/docs/guides-concepts/routing/remix/resource-and-routes-usage.tsx index 8a8e7b4288237..7fd8b64c87c27 100644 --- a/documentation/docs/guides-concepts/routing/remix/resource-and-routes-usage.tsx +++ b/documentation/docs/guides-concepts/routing/remix/resource-and-routes-usage.tsx @@ -65,6 +65,7 @@ export default function App() { // create: "/my-products/new", // edit: "/my-products/:id/edit", // clone: "/my-products/:id/clone", + // custom: ["/my-products/setttings", "/my-products/statistics", "/my-products/etc"], }, ]} > diff --git a/packages/core/src/contexts/resource/types.ts b/packages/core/src/contexts/resource/types.ts index 8edd12085bb64..7ce326590fd5f 100644 --- a/packages/core/src/contexts/resource/types.ts +++ b/packages/core/src/contexts/resource/types.ts @@ -12,6 +12,7 @@ export interface IResourceComponents { clone?: ResourceRoutePath; edit?: ResourceRoutePath; show?: ResourceRoutePath; + custom?: Array; } export type AnyString = string & { __ignore?: never }; diff --git a/packages/core/src/contexts/router/types.ts b/packages/core/src/contexts/router/types.ts index 19499d32aa8b1..8a781af9d5442 100644 --- a/packages/core/src/contexts/router/types.ts +++ b/packages/core/src/contexts/router/types.ts @@ -29,7 +29,7 @@ import type { BaseKey, CrudFilter, CrudSort } from "../data/types"; import type { IResourceItem } from "../resource/types"; -export type Action = "create" | "edit" | "list" | "show" | "clone"; +export type Action = "create" | "edit" | "list" | "show" | "clone" | "custom"; export type GoConfig = { to?: string; diff --git a/packages/core/src/definitions/helpers/router/get-action-routes-from-resource.ts b/packages/core/src/definitions/helpers/router/get-action-routes-from-resource.ts index 218388126b2a2..55fcb19175236 100644 --- a/packages/core/src/definitions/helpers/router/get-action-routes-from-resource.ts +++ b/packages/core/src/definitions/helpers/router/get-action-routes-from-resource.ts @@ -20,15 +20,29 @@ export const getActionRoutesFromResource = ( const actionList: Action[] = ["list", "show", "edit", "create", "clone"]; actionList.forEach((action) => { - const route: string | undefined = resource[action]; + const route = resource[action]; - if (route) { - actions.push({ - action, - resource, - route: `/${route.replace(/^\//, "")}`, + if (!route) return; + + if (action === "custom") { + const customRoutes = route as string[]; + + return customRoutes.forEach((r) => { + actions.push({ + action, + resource, + route: `/${r.replace(/^\//, "")}`, + }); }); } + + const regularRoute = route as string; + + return actions.push({ + action, + resource, + route: `/${regularRoute.replace(/^\//, "")}`, + }); }); return actions; diff --git a/packages/core/src/hooks/button/navigation-button/index.tsx b/packages/core/src/hooks/button/navigation-button/index.tsx index 60f757867a6f4..7a5fedb8488e1 100644 --- a/packages/core/src/hooks/button/navigation-button/index.tsx +++ b/packages/core/src/hooks/button/navigation-button/index.tsx @@ -69,6 +69,7 @@ export function useNavigationButton( switch (props.action) { case "create": case "list": + case "custom": return navigation[`${props.action}Url`](resource, props.meta); default: if (!id) return ""; diff --git a/packages/core/src/hooks/navigation/index.ts b/packages/core/src/hooks/navigation/index.ts index 559f76fbee3ee..afb166b6bfb44 100644 --- a/packages/core/src/hooks/navigation/index.ts +++ b/packages/core/src/hooks/navigation/index.ts @@ -165,6 +165,31 @@ export const useNavigation = () => { }) as string; }; + const customUrl = ( + resource: string | IResourceItem, + meta: MetaQuery = {}, + ) => { + const resourceItem = + typeof resource === "string" + ? pickResource(resource, resources) ?? { name: resource } + : resource; + + const customActionRoute = getActionRoutesFromResource( + resourceItem, + resources, + ).find((r) => r.action === "custom")?.route; + + if (!customActionRoute) { + return ""; + } + + return go({ + to: composeRoute(customActionRoute, resourceItem?.meta, parsed, meta), + type: "path", + query: meta.query, + }) as string; + }; + const create = ( resource: string | IResourceItem, type: HistoryType = "push", @@ -208,6 +233,14 @@ export const useNavigation = () => { handleUrl(listUrl(resource, meta), type); }; + const custom = ( + resource: string | IResourceItem, + type: HistoryType = "push", + meta: MetaQuery = {}, + ) => { + handleUrl(createUrl(resource, meta), type); + }; + return { create, createUrl, @@ -219,5 +252,7 @@ export const useNavigation = () => { showUrl, list, listUrl, + custom, + customUrl, }; };