Skip to content

Commit

Permalink
[core] Use router specific Link components (#4661)
Browse files Browse the repository at this point in the history
  • Loading branch information
bharatkashyap authored Feb 24, 2025
1 parent b6bb68e commit f5def45
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function Content({ router }) {

Content.propTypes = {
router: PropTypes.shape({
Link: PropTypes.func,
navigate: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
searchParams: PropTypes.instanceOf(URLSearchParams).isRequired,
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/toolpad/core/api/app-provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"router": {
"type": {
"name": "shape",
"description": "{ navigate: func, pathname: string, searchParams: URLSearchParams }"
"description": "{ Link?: func, navigate: func, pathname: string, searchParams: URLSearchParams }"
},
"default": "null"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/toolpad-core/src/AppProvider/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
RouterContext,
WindowContext,
} from '../shared/context';
import type { LinkProps } from '../shared/Link';
import { AppThemeProvider } from './AppThemeProvider';
import { LocalizationProvider, type LocaleText } from './LocalizationProvider';

Expand All @@ -28,6 +29,7 @@ export interface Router {
pathname: string;
searchParams: URLSearchParams;
navigate: Navigate;
Link?: React.ComponentType<LinkProps>;
}

export interface Branding {
Expand Down Expand Up @@ -254,6 +256,7 @@ AppProvider.propTypes /* remove-proptypes */ = {
* @default null
*/
router: PropTypes.shape({
Link: PropTypes.func,
navigate: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
searchParams: PropTypes.instanceOf(URLSearchParams).isRequired,
Expand Down
2 changes: 2 additions & 0 deletions packages/toolpad-core/src/nextjs/NextAppProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ vi.mock('next/router', () => ({ useRouter: () => null }));

vi.mock('next/compat/router', () => ({ useRouter: () => null }));

vi.mock('next/link', () => ({ default: () => null }));

interface RouterTestProps {
children: React.ReactNode;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/toolpad-core/src/nextjs/NextAppProviderApp.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import * as React from 'react';
import NextLink from 'next/link';
import { usePathname, useSearchParams, useRouter } from 'next/navigation';
import { LinkProps } from '../shared/Link';
import { AppProvider } from '../AppProvider';
import type { AppProviderProps, Navigate, Router } from '../AppProvider';

const Link = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { href, history, ...rest } = props;
return <NextLink ref={ref} href={href} replace={history === 'replace'} {...rest} />;
});
/**
* @ignore - internal component.
*/
Expand All @@ -29,6 +35,7 @@ export function NextAppProviderApp(props: AppProviderProps) {
pathname,
searchParams,
navigate,
Link,
}),
[pathname, navigate, searchParams],
);
Expand Down
8 changes: 8 additions & 0 deletions packages/toolpad-core/src/nextjs/NextAppProviderPages.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import * as React from 'react';
import NextLink from 'next/link';
import { asArray } from '@toolpad/utils/collections';
import { useRouter } from 'next/router';
import { LinkProps } from '../shared/Link';
import { AppProvider } from '../AppProvider';
import type { AppProviderProps, Navigate, Router } from '../AppProvider';

const Link = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { href, history, ...rest } = props;
return <NextLink ref={ref} href={href} replace={history === 'replace'} {...rest} />;
});

/**
* @ignore - internal component.
*/
Expand Down Expand Up @@ -41,6 +48,7 @@ export function NextAppProviderPages(props: AppProviderProps) {
pathname,
searchParams,
navigate,
Link,
}),
[navigate, pathname, searchParams],
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
'use client';
import * as React from 'react';
import { useSearchParams, useLocation, useNavigate } from 'react-router';
import { useSearchParams, useLocation, useNavigate, Link as ReactRouterLink } from 'react-router';
import { AppProvider, type AppProviderProps, Navigate, Router } from '../AppProvider/AppProvider';
import { LinkProps } from '../shared/Link';

const Link = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { href, history, ...rest } = props;
return <ReactRouterLink ref={ref} to={href} replace={history === 'replace'} {...rest} />;
});

function ReactRouterAppProvider(props: AppProviderProps) {
const { pathname } = useLocation();
Expand All @@ -26,6 +32,7 @@ function ReactRouterAppProvider(props: AppProviderProps) {
pathname,
searchParams,
navigate: navigateImpl,
Link,
}),
[pathname, searchParams, navigateImpl],
);
Expand Down
21 changes: 20 additions & 1 deletion packages/toolpad-core/src/shared/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import { RouterContext } from './context';
*/

export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
/*
* "replace" will replace the history stack with the URL being navigated to
* "push" will push the URL being navigated to as a new entry onto the history stack
* "auto" is the default and the mimics the "push" behaviour
*/
history?: 'auto' | 'push' | 'replace';
href: string;
}

export const Link = React.forwardRef(function Link(
export const DefaultLink = React.forwardRef(function Link(
props: LinkProps,
ref: React.ForwardedRef<HTMLAnchorElement>,
) {
Expand All @@ -34,3 +40,16 @@ export const Link = React.forwardRef(function Link(
</a>
);
});

export const Link = React.forwardRef(function Link(
props: LinkProps,
ref: React.ForwardedRef<HTMLAnchorElement>,
) {
const routerContext = React.useContext(RouterContext);
const LinkComponent = routerContext?.Link ?? DefaultLink;
return (
<LinkComponent ref={ref} {...props}>
{props.children}
</LinkComponent>
);
});

0 comments on commit f5def45

Please sign in to comment.