React Material:UI Cookbook
上QQ阅读APP看书,第一时间看更新

There's more...

When the items in your drawer are links, you probably want a visual indication for the active link. The challenge is that you want to style the active link using Material-UI theme styles. Here's what the modified example looks like:

import React, { useState } from 'react';
import clsx from 'clsx';
import { Switch, Route, Link, NavLink } from 'react-router-dom';

import { withStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';

import HomeIcon from '@material-ui/icons/Home';
import WebIcon from '@material-ui/icons/Web';

const styles = theme => ({
alignContent: {
alignSelf: 'center'
},
activeListItem: {
color: theme.palette.primary.main
}
});

const NavListItem = withStyles(styles)(
({ classes, Icon, text, active, ...other }) => (
<ListItem component={NavLink} {...other}>
<ListItemIcon
classes={{
root: clsx({ [classes.activeListItem]: active })
}}
>
<Icon />
</ListItemIcon>
<ListItemText
classes={{
primary: clsx({
[classes.activeListItem]: active
})
}}
>
{text}
</ListItemText>
</ListItem>
)
);

const NavItem = props => (
<Switch>
<Route
exact
path={props.to}
render={() => <NavListItem active={true} {...props} />}
/>
<Route path="/" render={() => <NavListItem {...props} />} />
</Switch>
);

function DrawerItemNavigation({ classes }) {
const [open, setOpen] = useState(false);

return (
<Grid container justify="space-between">
<Grid item className={classes.alignContent}>
<Route
exact
path="/"
render={() => <Typography>Home</Typography>}
/>
<Route
exact
path="/page2"
render={() => <Typography>Page 2</Typography>}
/>
<Route
exact
path="/page3"
render={() => <Typography>Page 3</Typography>}
/>
</Grid>
<Grid item>
<Drawer
className={classes.drawerWidth}
open={open}
onClose={() => setOpen(false)}
>
<List>
<NavItem
to="/"
text="Home"
Icon={HomeIcon}
onClick={() => setOpen(false)}
/>
<NavItem
to="/page2"
text="Page 2"
Icon={WebIcon}
onClick={() => setOpen(false)}
/>
<NavItem
to="/page3"
text="Page 3"
Icon={WebIcon}
onClick={() => setOpen(false)}
/>
</List>
</Drawer>
</Grid>
<Grid item>
<Button onClick={() => setOpen(!open)}>
{open ? 'Hide' : 'Show'} Drawer
</Button>
</Grid>
</Grid>
);
}

export default withStyles(styles)(DrawerItemNavigation);

Now, when the screen first loads and you open the drawer, it should look similar to the following screenshot:

Since the Home link is active, it's styled using the primary color from the Material-UI theme. If you click on the Page 2 link and then open the drawer again, it should look similar to the following screenshot:

Let's take a look at the two new components that you've added, starting with NavItem:

const NavItem = props => (
<Switch>
<Route
exact
path={props.to}
render={() => <NavListItem active={true} {...props} />}
/>
<Route path="/" render={() => <NavListItem {...props} />} />
</Switch>
);

This component is used to determine whether or not the item is active, based on the current URL. It uses the Switch component from react-router-dom. Instead of just rendering Route components, Switch will only render the first route whose path matches the current URL. The first Route component in NavItem is the specific path (as it uses the exact property). If this Route component matches, it renders a NavListItem component with the active property set to true. Because it's in a Switch component, the second Route component will not be rendered.

If, on the other hand, the first Route component doesn't match, the second Route component will always match. This will render a NavListItem component without the active property. Now, let's take a look at the NavListItem component, as follows:

const NavListItem = withStyles(styles)(
({ classes, Icon, text, active, ...other }) => (
<ListItem component={NavLink} {...other}>
<ListItemIcon
classes={{
root: clsx({ [classes.activeListItem]: active })
}}
>
<Icon />
</ListItemIcon>
<ListItemText
classes={{
primary: clsx({
[classes.activeListItem]: active
})
}}
>
{text}
</ListItemText>
</ListItem>
)
);

The NavListItem component is now responsible for rendering the ListItem components in the Drawer component. It takes a text property and an Icon property to render the label and the icon respectively, just like before your enhancements. The active property is used to determine the class that gets applied to the ListItemIcon and ListItemText components. The activeListItem CSS class is applied to both of these components if active is true. This is how you're able to style the active item based on the Material-UI theme.

The clsx() function is used extensively by Material-UI–this isn't an extra dependency. It allows you to dynamically change the class of an element without introducing custom logic into your markup. For example, the  clsx({ [classes.activeListItem]: active }) syntax will only apply the activeListItem class if active is true. The alternative will involve introducing more logic into your component.

Lastly, let's take a look at the activeListItem class, as follows:

const styles = theme => ({
alignContent: {
alignSelf: 'center'
},
activeListItem: {
color: theme.palette.primary.main
}
});

The activeListItem class sets the color CSS property by using the theme.palette.primary.main value. This means that if the theme changes, your active link in the drawer will be styled accordingly.