-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[material-ui-nextjs] Add Link
integration component
#40265
Changes from all commits
b738d42
1521d24
1016743
130cf09
8c2f7d5
d390e38
f1ee9b9
c0024f1
2c4af10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,35 @@ This option ensures that the styles generated by Material UI will be wrapped in | |
|
||
To learn more about it, see [the MDN CSS layer documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer). | ||
|
||
### Link | ||
|
||
The package provides a thin wrapper Link component that combines the functionality of the Next.js [Link](https://nextjs.org/docs/api-reference/next/link) and the styles of Material UI [Link](https://mui.com/components/links/). | ||
|
||
You can pass any props supported by both components to the Link. | ||
|
||
```js | ||
import { Link } from '@mui/material-nextjs/v13-appRouter'; // or `v14-appRouter` if you are using Next.js v14 | ||
|
||
function Nav() { | ||
return ( | ||
<div> | ||
<Link href="/dashboard" variant="caption" sx={{ bgcolor: 'grey.100' }}> | ||
Dashboard | ||
</Link> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
To learn more about the supported props, visit: | ||
|
||
- Next.js related props: [Next.js Link](https://nextjs.org/docs/app/api-reference/components/link#props) | ||
- Material UI related props: [Material UI Link](/material-ui/react-link/) | ||
|
||
:::warning | ||
Do not use the Link from <b>`@mui/material-nextjs/*-pagesRouter`</b> for App Router. | ||
::: | ||
|
||
Comment on lines
+87
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
## Pages Router | ||
|
||
This section walks through the Material UI integration with the Next.js [Pages Router](https://nextjs.org/docs/pages/building-your-application), for both [Server Side Rendering](https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering) (SSR) and [Static Site Generation](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation) (SSG). | ||
|
@@ -149,3 +178,30 @@ If you are using TypeScript, add `DocumentHeadTagsProps` to the Document's props | |
... | ||
} | ||
``` | ||
|
||
### Link | ||
|
||
For pages router, use the Link only from `@mui/material-nextjs/*-pagesRouter`: | ||
|
||
```js | ||
import { Link } from '@mui/material-nextjs/v13-pagesRouter'; // or `v14-pagesRouter` if you are using Next.js v14 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should also document how to use a Link with a Button. import Button from '@mui/material/Button';
import NextLink from 'next/link';
<Button component={NextLink} href="/foo"> Actually, I don't know if this works (maybe types issue, maybe href prop conflict) but this is definitely an important use case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it should be possible to pass in |
||
|
||
function Nav() { | ||
return ( | ||
<div> | ||
<Link href="/dashboard" variant="caption" sx={{ bgcolor: 'grey.100' }}> | ||
Dashboard | ||
</Link> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
To learn more about the supported props, visit: | ||
|
||
- Next.js related props: [Next.js Link](https://nextjs.org/docs/pages/api-reference/components/link#props) | ||
- Material UI related props: [Material UI Link](/material-ui/react-link/) | ||
|
||
:::warning | ||
Do not use the Link from <b>`@mui/material-nextjs/*-appRouter`</b> for Pages Router. | ||
::: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as React from 'react'; | ||
import { Link } from '@mui/material-nextjs/v13-appRouter'; | ||
|
||
// @ts-expect-error href is required by Next.js | ||
<Link />; | ||
<Link href="" />; | ||
<Link | ||
href={{ | ||
pathname: '/about', | ||
query: { name: 'test' }, | ||
}} | ||
/>; | ||
<Link href="/dashboard" replace> | ||
Dashboard | ||
</Link>; | ||
<Link href="/dashboard" scroll={false}> | ||
Dashboard | ||
</Link>; | ||
<Link href="/dashboard" prefetch={false}> | ||
Dashboard | ||
</Link>; | ||
|
||
<Link href="" sx={{ color: 'secondary.dark' }} />; | ||
<Link href="" variant="caption" />; | ||
<Link href="" underline="always" />; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,63 @@ | ||||||||
'use client'; | ||||||||
import * as React from 'react'; | ||||||||
import clsx from 'clsx'; | ||||||||
import { usePathname } from 'next/navigation'; | ||||||||
import NextLink, { LinkProps as NextLinkProps } from 'next/link'; | ||||||||
import MuiLink, { LinkOwnProps as MuiLinkProps } from '@mui/material/Link'; | ||||||||
import linkClasses from './linkClasses'; | ||||||||
|
||||||||
export interface LinkProps | ||||||||
extends Omit<NextLinkProps, 'passHref' | 'legacyBehavior'>, | ||||||||
MuiLinkProps { | ||||||||
/** | ||||||||
* Extra class name to apply to the link. | ||||||||
*/ | ||||||||
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What we usually use for the className:
Suggested change
Did this mui/mui-x#11693 accordingly. |
||||||||
className?: string; | ||||||||
/** | ||||||||
* The active class name to apply when the current page is the same as the link's href. | ||||||||
* @default 'Mui-active' | ||||||||
*/ | ||||||||
activeClassName?: string; | ||||||||
} | ||||||||
|
||||||||
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link( | ||||||||
{ | ||||||||
href, | ||||||||
replace, | ||||||||
scroll, | ||||||||
shallow, | ||||||||
prefetch, | ||||||||
locale, | ||||||||
as, | ||||||||
className: classNameProp, | ||||||||
activeClassName = linkClasses.active, | ||||||||
...muiLinkProps | ||||||||
}, | ||||||||
ref, | ||||||||
) { | ||||||||
const nextPathname = usePathname(); | ||||||||
const pathname = typeof href === 'string' ? href : href.pathname; | ||||||||
const className = clsx(classNameProp, { | ||||||||
[activeClassName]: nextPathname === pathname && activeClassName, | ||||||||
}); | ||||||||
return ( | ||||||||
<NextLink | ||||||||
{...{ | ||||||||
href, | ||||||||
replace, | ||||||||
scroll, | ||||||||
shallow, | ||||||||
prefetch, | ||||||||
locale, | ||||||||
as, | ||||||||
}} | ||||||||
// below props are required for NextLink to work with MUI Link | ||||||||
passHref | ||||||||
legacyBehavior | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should update to the latest stable API, part of is:
Suggested change
I don't see why it's needed, I would expect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to work without |
||||||||
> | ||||||||
<MuiLink ref={ref} className={className} {...muiLinkProps} /> | ||||||||
</NextLink> | ||||||||
); | ||||||||
}); | ||||||||
|
||||||||
export default Link; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { default as Link } from './Link'; | ||
export { default as linkClasses, getLinkUtilityClass } from './linkClasses'; | ||
export type { LinkProps } from './Link'; | ||
export type { LinkClasses } from './linkClasses'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; | ||
import generateUtilityClass from '@mui/utils/generateUtilityClass'; | ||
import { LinkClasses as MuiLinkClasses } from '@mui/material/Link'; | ||
|
||
export interface LinkClasses extends MuiLinkClasses { | ||
/** | ||
* Styles applied to the root element when the link is active. | ||
*/ | ||
active: string; | ||
} | ||
|
||
export function getLinkUtilityClass(slot: string): string { | ||
return generateUtilityClass('MuiLink', slot); | ||
} | ||
|
||
const linkClasses: LinkClasses = generateUtilityClasses('MuiLink', [ | ||
'root', | ||
'underlineNone', | ||
'underlineHover', | ||
'underlineAlways', | ||
'button', | ||
'focusVisible', | ||
'active', | ||
]); | ||
|
||
export default linkClasses; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
export { default as AppRouterCacheProvider } from './appRouterV13'; | ||
export * from './appRouterV13'; | ||
export * from './Link'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as React from 'react'; | ||
import { Link } from '@mui/material-nextjs/v13-pagesRouter'; | ||
|
||
// @ts-expect-error href is required by Next.js | ||
<Link />; | ||
<Link href="" />; | ||
<Link | ||
href={{ | ||
pathname: '/about', | ||
query: { name: 'test' }, | ||
}} | ||
/>; | ||
<Link href="/dashboard" replace> | ||
Dashboard | ||
</Link>; | ||
<Link href="/dashboard" scroll={false}> | ||
Dashboard | ||
</Link>; | ||
<Link href="/dashboard" prefetch={false}> | ||
Dashboard | ||
</Link>; | ||
|
||
<Link href="" sx={{ color: 'secondary.dark' }} />; | ||
<Link href="" variant="caption" />; | ||
<Link href="" underline="always" />; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
'use client'; | ||
import * as React from 'react'; | ||
import clsx from 'clsx'; | ||
import { useRouter } from 'next/router'; | ||
import NextLink, { LinkProps as NextLinkProps } from 'next/link'; | ||
import MuiLink, { LinkOwnProps as MuiLinkProps } from '@mui/material/Link'; | ||
import linkClasses from './linkClasses'; | ||
|
||
export interface LinkProps | ||
extends Omit<NextLinkProps, 'passHref' | 'legacyBehavior'>, | ||
MuiLinkProps { | ||
/** | ||
* Extra class name to apply to the link. | ||
*/ | ||
className?: string; | ||
/** | ||
* The active class name to apply when the current page is the same as the link's href. | ||
* @default 'Mui-active' | ||
*/ | ||
activeClassName?: string; | ||
} | ||
|
||
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link( | ||
{ | ||
href, | ||
replace, | ||
scroll, | ||
shallow, | ||
prefetch, | ||
locale, | ||
as, | ||
className: classNameProp, | ||
activeClassName = linkClasses.active, | ||
...muiLinkProps | ||
}, | ||
ref, | ||
) { | ||
const router = useRouter(); | ||
const pathname = typeof href === 'string' ? href : href.pathname; | ||
const className = clsx(classNameProp, { | ||
[activeClassName]: router.pathname === pathname && activeClassName, | ||
}); | ||
return ( | ||
<NextLink | ||
{...{ | ||
href, | ||
replace, | ||
scroll, | ||
shallow, | ||
prefetch, | ||
locale, | ||
as, | ||
}} | ||
// below props are required for NextLink to work with MUI Link | ||
passHref | ||
legacyBehavior | ||
> | ||
<MuiLink ref={ref} className={className} {...muiLinkProps} /> | ||
</NextLink> | ||
); | ||
}); | ||
|
||
export default Link; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { default as Link } from './Link'; | ||
export { default as linkClasses, getLinkUtilityClass } from './linkClasses'; | ||
export type { LinkProps } from './Link'; | ||
export type { LinkClasses } from './linkClasses'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; | ||
import generateUtilityClass from '@mui/utils/generateUtilityClass'; | ||
import { LinkClasses as MuiLinkClasses } from '@mui/material/Link'; | ||
|
||
export interface LinkClasses extends MuiLinkClasses { | ||
/** | ||
* Styles applied to the root element when the link is active. | ||
*/ | ||
active: string; | ||
} | ||
|
||
export function getLinkUtilityClass(slot: string): string { | ||
return generateUtilityClass('MuiLink', slot); | ||
} | ||
|
||
const linkClasses: LinkClasses = generateUtilityClasses('MuiLink', [ | ||
'root', | ||
'underlineNone', | ||
'underlineHover', | ||
'underlineAlways', | ||
'button', | ||
'focusVisible', | ||
'active', | ||
]); | ||
|
||
export default linkClasses; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './pagesRouterV13Document'; | ||
export * from './pagesRouterV13App'; | ||
export * from './Link'; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates a horizontal scrollbar, and I think better if set to be generic.