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

Extract sidebar toggle button #6603

Merged
merged 3 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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];