Skip to content
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

[ButtonBase] Allow to customize the link component via theme #25331

Merged
merged 13 commits into from
Mar 18, 2021
1 change: 1 addition & 0 deletions docs/pages/api-docs/button-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"disableTouchRipple": { "type": { "name": "bool" } },
"focusRipple": { "type": { "name": "bool" } },
"focusVisibleClassName": { "type": { "name": "string" } },
"LinkComponent": { "type": { "name": "elementType" }, "default": "'a'" },
"onFocusVisible": { "type": { "name": "func" } },
"sx": { "type": { "name": "object" } },
"TouchRippleProps": { "type": { "name": "object" } }
Expand Down
20 changes: 20 additions & 0 deletions docs/pages/guides/routing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const pageFilename = 'guides/routing';
const requireDemo = require.context('docs/src/pages/guides/routing', false, /\.(js|tsx)$/);
const requireRaw = require.context(
'!raw-loader!../../src/pages/guides/routing',
false,
/\.(js|md|tsx)$/,
);

export default function Page({ demos, docs }) {
return <MarkdownDocs demos={demos} docs={docs} requireDemo={requireDemo} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
1 change: 1 addition & 0 deletions docs/src/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ const pages: MuiPage[] = [
{ pathname: '/guides/interoperability', title: 'Style Library Interoperability' },
{ pathname: '/guides/minimizing-bundle-size' },
{ pathname: '/guides/composition' },
{ pathname: '/guides/routing' },
{ pathname: '/guides/server-rendering' },
{ pathname: '/guides/responsive-ui', title: 'Responsive UI' },
{ pathname: '/guides/pickers-migration', title: 'Migration from @material-ui/pickers' },
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/components/buttons/buttons.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ component forwards this ref to the underlying DOM node.
Given that many of the interactive components rely on `ButtonBase`, you should be
able to take advantage of it everywhere.

Here is an [integration example with react-router](/guides/composition/#button).
Here is an [integration example with react-router](/guides/routing/#button).

## Limitations

Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/components/links/Links.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as React from 'react';
import Box from '@material-ui/core/Box';
import Link from '@material-ui/core/Link';

export default function Links() {
const preventDefault = (event) => event.preventDefault();
const preventDefault = (event) => event.preventDefault();

export default function Links() {
return (
<Box
sx={{
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/components/links/Links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as React from 'react';
import Box from '@material-ui/core/Box';
import Link from '@material-ui/core/Link';

export default function Links() {
const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();
const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();

export default function Links() {
return (
<Box
sx={{
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/components/links/UnderlineLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as React from 'react';
import Box from '@material-ui/core/Box';
import Link from '@material-ui/core/Link';

export default function UnderlineLink() {
const preventDefault = (event) => event.preventDefault();
const preventDefault = (event) => event.preventDefault();

export default function UnderlineLink() {
return (
<Box
sx={{
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/components/links/UnderlineLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as React from 'react';
import Box from '@material-ui/core/Box';
import Link from '@material-ui/core/Link';

export default function UnderlineLink() {
const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();
const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();

export default function UnderlineLink() {
return (
<Box
sx={{
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/components/links/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ When you use `target="_blank"` with Links, it is [recommended](https://developer
One common use case is to perform navigation on the client only, without an HTTP round-trip to the server.
The `Link` component provides the `component` prop to handle this use case.

Here is an [integration example with react-router](/guides/composition/#link).
Here is an [integration example with react-router](/guides/routing/#link).

## Accessibility

Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/components/lists/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function ListItemLink(props) {
</ListItemLink>;
```

You can find a [demo with React Router following this section](/guides/composition/#react-router) of the documentation.
You can find a [demo with React Router following this section](/guides/routing/#list) of the documentation.

## Nested List

Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/getting-started/faq/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ or are already familiar with a different API, and don't want to learn a new one?
[Style Library Interoperability](/guides/interoperability/) section,
where we show how simple it is to restyle Material-UI components with alternative style libraries.

## When should I use inline-style vs CSS?
## When should I use inline-style vs. CSS?

As a rule of thumb, only use inline-styles for dynamic style properties.
The CSS alternative provides more advantages, such as:
Expand All @@ -139,7 +139,7 @@ The CSS alternative provides more advantages, such as:

## How do I use react-router?

We detail the [integration with third-party routing libraries](/guides/composition/#routing-libraries) like react-router, Gatsby or Next.js in our guide.
We detail the [integration with third-party routing libraries](/guides/routing/) like react-router, Gatsby or Next.js in our guide.

## How can I access the DOM element?

Expand Down
21 changes: 1 addition & 20 deletions docs/src/pages/guides/composition/composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

In order to provide the maximum flexibility and performance,
we need a way to know the nature of the child elements a component receives.
To solve this problem we tag some of the components
To solve this problem, we tag some of the components
with a `muiName` static property when needed.

You may, however, need to wrap a component in order to enhance it,
Expand Down Expand Up @@ -128,25 +128,6 @@ The component providing the `component` prop (e.g. ListItem) might not forward a

You can find the details in the [TypeScript guide](/guides/typescript/#usage-of-component-prop).

## Routing libraries

The integration with third-party routing libraries is achieved with the `component` prop.
The behavior is identical to the description of the prop above.
Here are a few demos with [react-router-dom](https://github.com/ReactTraining/react-router).
They cover the Button, Link, and List components. You can apply the same strategy with all the components (BottomNavigation, Card, etc.).

### Button

{{"demo": "pages/guides/composition/ButtonRouter.js"}}

### Link

{{"demo": "pages/guides/composition/LinkRouter.js"}}

### List

{{"demo": "pages/guides/composition/ListRouter.js"}}

## Caveat with refs

This section covers caveats when using a custom component as `children` or for the
Expand Down
15 changes: 15 additions & 0 deletions docs/src/pages/guides/routing/ButtonDemo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';

const preventDefault = (event) => event.preventDefault();

export default function ButtonDemo() {
return (
<Box role="presentation" onClick={preventDefault}>
<Button href="/" variant="contained">
Link
</Button>
</Box>
);
}
15 changes: 15 additions & 0 deletions docs/src/pages/guides/routing/ButtonDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';

const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();

export default function ButtonDemo() {
return (
<Box role="presentation" onClick={preventDefault}>
<Button href="/" variant="contained">
Link
</Button>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ const LinkBehavior = React.forwardRef((props, ref) => (

export default function ButtonRouter() {
return (
<Router>
<div>
<div>
<Router>
<Button component={RouterLink} to="/">
With prop forwarding
</Button>
<br />
<Button component={LinkBehavior}>Without prop forwarding</Button>
</div>
</Router>
</Router>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ const LinkBehavior = React.forwardRef<any, Omit<RouterLinkProps, 'to'>>(

export default function ButtonRouter() {
return (
<Router>
<div>
<div>
<Router>
<Button component={RouterLink} to="/">
With prop forwarding
</Button>
<br />
<Button component={LinkBehavior}>Without prop forwarding</Button>
</div>
</Router>
</Router>
</div>
);
}
13 changes: 13 additions & 0 deletions docs/src/pages/guides/routing/LinkDemo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import Link from '@material-ui/core/Link';
import Box from '@material-ui/core/Box';

const preventDefault = (event) => event.preventDefault();

export default function LinkDemo() {
return (
<Box sx={{ typography: 'body1' }} role="presentation" onClick={preventDefault}>
<Link href="/">Link</Link>
</Box>
);
}
13 changes: 13 additions & 0 deletions docs/src/pages/guides/routing/LinkDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import Link from '@material-ui/core/Link';
import Box from '@material-ui/core/Box';

const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();

export default function LinkDemo() {
return (
<Box sx={{ typography: 'body1' }} role="presentation" onClick={preventDefault}>
<Link href="/">Link</Link>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import * as React from 'react';
import { MemoryRouter as Router } from 'react-router';
import { Link as RouterLink } from 'react-router-dom';
import Link from '@material-ui/core/Link';
import Box from '@material-ui/core/Box';

const LinkBehavior = React.forwardRef((props, ref) => (
<RouterLink ref={ref} to="/getting-started/installation/" {...props} />
));

export default function LinkRouter() {
return (
<Router>
<div>
<Box sx={{ typography: 'body1' }}>
<Router>
<Link component={RouterLink} to="/">
With prop forwarding
</Link>
<br />
<Link component={LinkBehavior}>Without prop forwarding</Link>
</div>
</Router>
</Router>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import { MemoryRouter as Router } from 'react-router';
import { Link as RouterLink, LinkProps as RouterLinkProps } from 'react-router-dom';
import Link from '@material-ui/core/Link';
import Box from '@material-ui/core/Box';

const LinkBehavior = React.forwardRef<any, Omit<RouterLinkProps, 'to'>>(
(props, ref) => (
Expand All @@ -12,14 +13,14 @@ const LinkBehavior = React.forwardRef<any, Omit<RouterLinkProps, 'to'>>(

export default function LinkRouter() {
return (
<Router>
<div>
<Box sx={{ typography: 'body1' }}>
<Router>
<Link component={RouterLink} to="/">
With prop forwarding
</Link>
<br />
<Link component={LinkBehavior}>Without prop forwarding</Link>
</div>
</Router>
</Router>
</Box>
);
}
59 changes: 59 additions & 0 deletions docs/src/pages/guides/routing/LinkRouterWithTheme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { MemoryRouter as Router } from 'react-router';
import { Link as RouterLink } from 'react-router-dom';
import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';
import Link from '@material-ui/core/Link';

const LinkBehavior = React.forwardRef((props, ref) => {
const { href, ...other } = props;
// Map href (Material-UI) -> to (react-router)
return <RouterLink data-testid="custom-link" ref={ref} to={href} {...other} />;
});

LinkBehavior.propTypes = {
href: PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.string])
.isRequired,
};

const themeSetter = (outerTheme) =>
createMuiTheme(outerTheme, {
components: {
MuiLink: {
defaultProps: {
// @ts-ignore
component: LinkBehavior,
},
},
MuiButtonBase: {
defaultProps: {
LinkComponent: LinkBehavior,
},
},
},
});

export default function LinkRouterWithTheme() {
return (
<Box
sx={{
display: 'flex',
typography: 'body1',
flexDirection: 'column',
alignItems: 'center', // TODO Replace with Stack
'& > :not(style) + :not(style)': { mt: 1 },
}}
>
<ThemeProvider theme={themeSetter}>
<Router>
<Link href="/">Link</Link>
<Button href="/" variant="contained">
Link
</Button>
</Router>
</ThemeProvider>
</Box>
);
}
Loading