Skip to content

Commit

Permalink
Merge pull request #6603 from marmelab/extract-sidebar-toggle-button
Browse files Browse the repository at this point in the history
Extract sidebar toggle button
  • Loading branch information
fzaninotto authored Sep 27, 2021
2 parents 22c972b + b6883cc commit 73d779e
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 66 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.md
6 changes: 6 additions & 0 deletions docs/Theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,12 @@ export default MyAppBar;

Take note that this uses *material-ui's `<AppBar>`* instead of *react-admin's `<AppBar>`*. To use this custom `AppBar` component, pass it as prop to a custom `Layout`, as explained in the previous section.

To make it easier to customize, we export some of the components and hooks used by the `<AppBar>`:

- `<LoadingIndicator>`: A `CircularProgress` bound to the dataProvider activity.
- `<SidebarToggleButton>`: An `IconButton` used to toggle the `<Sidebar>`.
- `useToggleSidebar`: A hook that returns the sidebar open state and a function to toggle it. Used internally by `<SidebarToggleButton>`.

## Adding Dark Mode Support

The `<ToggleThemeButton>` component is part of `ra-preferences`, an [Enterprise Edition](https://marmelab.com/ra-enterprise)<img class="icon" src="./img/premium.svg" /> module. It lets users switch from light to dark mode, and persists that choice in local storage so that users only have to do it once.
Expand Down
99 changes: 33 additions & 66 deletions packages/ra-ui-materialui/src/layout/AppBar.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,23 @@
import * as React from 'react';
import { Children, cloneElement, memo } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import classNames from 'classnames';
import {
AppBar as MuiAppBar,
AppBarProps as MuiAppBarProps,
IconButton,
Toolbar,
Tooltip,
Typography,
useMediaQuery,
Theme,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import MenuIcon from '@material-ui/icons/Menu';
import { toggleSidebar, useTranslate, ComponentPropType } from 'ra-core';
import { ComponentPropType } from 'ra-core';

import { SidebarToggleButton } from './SidebarToggleButton';
import LoadingIndicator from './LoadingIndicator';
import DefaultUserMenu from './UserMenu';
import HideOnScroll from './HideOnScroll';
import { ClassesOverride } from '../types';

const useStyles = makeStyles(
theme => ({
toolbar: {
paddingRight: 24,
},
menuButton: {
marginLeft: '0.2em',
marginRight: '0.2em',
},
menuButtonIconClosed: {
transition: theme.transitions.create(['transform'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
transform: 'rotate(0deg)',
},
menuButtonIconOpen: {
transition: theme.transitions.create(['transform'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
transform: 'rotate(180deg)',
},
title: {
flex: 1,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
},
}),
{ name: 'RaAppBar' }
);

/**
* The AppBar component renders a custom MuiAppBar.
*
Expand Down Expand Up @@ -106,11 +69,13 @@ const AppBar = (props: AppBarProps): JSX.Element => {
...rest
} = props;
const classes = useStyles(props);
const dispatch = useDispatch();
const sidebarToggleButtonClasses = {
menuButtonIconClosed: classes.menuButtonIconClosed,
menuButtonIconOpen: classes.menuButtonIconOpen,
};
const isXSmall = useMediaQuery<Theme>(theme =>
theme.breakpoints.down('xs')
);
const translate = useTranslate();

return (
<Container>
Expand All @@ -120,31 +85,10 @@ const AppBar = (props: AppBarProps): JSX.Element => {
variant={isXSmall ? 'regular' : 'dense'}
className={classes.toolbar}
>
<Tooltip
title={translate(
open
? 'ra.action.close_menu'
: 'ra.action.open_menu',
{
_: 'Open/Close menu',
}
)}
enterDelay={500}
>
<IconButton
color="inherit"
onClick={() => dispatch(toggleSidebar())}
className={classNames(classes.menuButton)}
>
<MenuIcon
classes={{
root: open
? classes.menuButtonIconOpen
: classes.menuButtonIconClosed,
}}
/>
</IconButton>
</Tooltip>
<SidebarToggleButton
className={classes.menuButton}
classes={sidebarToggleButtonClasses}
/>
{Children.count(children) === 0 ? (
<Typography
variant="h6"
Expand Down Expand Up @@ -183,6 +127,7 @@ AppBar.propTypes = {
]),
container: ComponentPropType,
logout: PropTypes.element,
// @deprecated
open: PropTypes.bool,
userMenu: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]),
};
Expand All @@ -192,10 +137,32 @@ AppBar.defaultProps = {
container: HideOnScroll,
};

const useStyles = makeStyles(
theme => ({
toolbar: {
paddingRight: 24,
},
menuButton: {
marginLeft: '0.2em',
marginRight: '0.2em',
},
menuButtonIconClosed: {},
menuButtonIconOpen: {},
title: {
flex: 1,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
},
}),
{ name: 'RaAppBar' }
);

export interface AppBarProps extends Omit<MuiAppBarProps, 'title' | 'classes'> {
classes?: ClassesOverride<typeof useStyles>;
container?: React.ElementType<any>;
logout?: React.ReactNode;
// @deprecated
open?: boolean;
title?: string | JSX.Element;
userMenu?: JSX.Element | boolean;
Expand Down
71 changes: 71 additions & 0 deletions packages/ra-ui-materialui/src/layout/SidebarToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react';
import { IconButton, Tooltip } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import MenuIcon from '@material-ui/icons/Menu';
import { useTranslate } from 'ra-core';
import { ClassesOverride } from '../types';
import { useToggleSidebar } from './useToggleSidebar';

/**
* A button that toggles the sidebar. Used by default in the <AppBar>.
* @param props The component props
* @param {String} props.className An optional class name to apply to the button
* @param {ClassesOverride<typeof useStyles>} props.classes An object containing styles.
*/
export const SidebarToggleButton = (props: SidebarToggleButtonProps) => {
const translate = useTranslate();
const classes = useStyles(props);
const { className } = props;
const [open, toggleSidebar] = useToggleSidebar();

return (
<Tooltip
title={translate(
open ? 'ra.action.close_menu' : 'ra.action.open_menu',
{
_: 'Open/Close menu',
}
)}
enterDelay={500}
>
<IconButton
color="inherit"
onClick={() => toggleSidebar()}
className={className}
>
<MenuIcon
classes={{
root: open
? classes.menuButtonIconOpen
: classes.menuButtonIconClosed,
}}
/>
</IconButton>
</Tooltip>
);
};

const useStyles = makeStyles(
theme => ({
menuButtonIconClosed: {
transition: theme.transitions.create(['transform'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
transform: 'rotate(0deg)',
},
menuButtonIconOpen: {
transition: theme.transitions.create(['transform'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
transform: 'rotate(180deg)',
},
}),
{ name: 'RaSidebarToggleButton' }
);

export type SidebarToggleButtonProps = {
className?: string;
classes?: ClassesOverride<typeof useStyles>;
};
29 changes: 29 additions & 0 deletions packages/ra-ui-materialui/src/layout/useToggleSidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useDispatch, useSelector } from 'react-redux';
import { ReduxState, toggleSidebar } from 'ra-core';

/**
* A hook that returns the sidebar open state and a function to toggle it.
* @returns A tuple containing a boolean indicating whether the sidebar is open or not and a function to toggle the sidebar.
* @example
* const MyButton = () => {
* const [sidebarOpen, toggleSidebar] = useToggleSidebar();
* return (
* <Button
* color="inherit"
* onClick={() => toggleSidebar()}
* >
* {open ? 'Open' : 'Close'}
* </Button>
* );
*/
export const useToggleSidebar = (): UseToggleSidebarResult => {
const open = useSelector<ReduxState, boolean>(
state => state.admin.ui.sidebarOpen
);
const dispatch = useDispatch();

const toggle = () => dispatch(toggleSidebar());
return [open, toggle];
};

export type UseToggleSidebarResult = [boolean, () => void];

0 comments on commit 73d779e

Please sign in to comment.