连接 React Material UI:迷你变体 + 响应式抽屉

0 drawer reactjs

我是 React 和 Material UI 的新手,我一直在努力尝试将我现在拥有的迷你变体抽屉与响应式抽屉连接起来,因此当您在手机上使用它时,它会更改为响应式抽屉。如果有人能帮助我,这对我来说意义重大,我尝试过,但总是失败。它还可以帮助我获得与抽屉进一步联系的知识。

这是我用于迷你变体抽屉的代码:

import React, {useState} from 'react';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import Drawer from '@material-ui/core/Drawer';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import List from '@material-ui/core/List';
import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import MenuOpenIcon from '@material-ui/icons/MenuOpen';

import { MainListItems } from './listItems';
import AccountMenu from '../../components/AccountMenu'
import ChangePasswordDialog from './ChangePasswordDialog';



const drawerWidth = 240;

const useStyles = makeStyles((theme) => ({
  root: {
    display: 'flex',
  },
  toolbar: {
    paddingRight: 60, 
  },
  toolbarIcon: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-end',
    padding: '0 8px',
    ...theme.mixins.toolbar,
  },
  appBar: {
    zIndex: theme.zIndex.drawer + 1,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
  },
  appBarShift: {
    marginLeft: drawerWidth,
    width: `calc(100% - ${drawerWidth}px)`,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
  },
  menuButton: {
    marginRight: 36,
  },
  menuButtonHidden: {
    display: 'none',
  },
  mainTitle: {
    flexGrow: 1,
    marginLeft: 12
  },
  title: {
    flexGrow: 1,
  },
  drawerPaper: {
    position: 'relative',
    whiteSpace: 'nowrap',
    width: drawerWidth,
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
  },
  drawerPaperClose: {
    overflowX: 'hidden',
        transition: theme.transitions.create('width', {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen,
    }),
    width: theme.spacing.unit * 7,
    [theme.breakpoints.up('sm')]: {
      width: theme.spacing.unit * 9,
    },
  },
  appBarSpacer: theme.mixins.toolbar,
  content: {
    flexGrow: 1,
    height: '100vh',
    overflow: 'auto',
  },
  container: {
    paddingTop: theme.spacing(4),
    paddingBottom: theme.spacing(4),
  },
  paper: {
    padding: theme.spacing(2),
    display: 'flex',
    overflow: 'auto',
    flexDirection: 'column',
  },
  fixedHeight: {
    height: 240,
  },
}));

export default function SideMenuLayout({
  drawerOpen,
  setDrawerOpen,
  children,
  title
}) {
  const classes = useStyles();
  const handleDrawerOpen = () => {
    setDrawerOpen(true);
  };
  const handleDrawerClose = () => {
    setDrawerOpen(false);
  };
  
  const [changePasswordDialogOpen, setChangePasswordDialogOpen] = useState(false)

  return (
    <div className={classes.root}>
      <CssBaseline />
      <AppBar position="absolute" className={clsx(classes.appBar, drawerOpen && classes.appBarShift)}>
        <Toolbar className={classes.toolbar}>
          <IconButton
            edge="start"
            color="inherit"
            aria-label="open drawer"
            onClick={handleDrawerOpen}
            className={clsx(classes.menuButton, drawerOpen && classes.menuButtonHidden)}
          >
            <MenuIcon />
          </IconButton>
          <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}>
            
            {drawerOpen ? title : `MyTitle - ${title}`}
          </Typography>
        

          <AccountMenu onChangePassword={() => setChangePasswordDialogOpen(true)} />          
        </Toolbar>
      </AppBar>
      <Drawer
        variant="permanent"
        classes={{
          paper: clsx(classes.drawerPaper, !drawerOpen && classes.drawerPaperClose),
        }}
        open={drawerOpen}
      >
        <div className={classes.toolbarIcon}>
          <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.mainTitle}>
            MyTitle
          </Typography>
          <IconButton onClick={handleDrawerClose}>
            <MenuOpenIcon />
          </IconButton>
        </div>
        <Divider />
        <List><MainListItems /></List>
        
      </Drawer>
      <main className={classes.content}>
        <div className={classes.appBarSpacer} />
        {children}
      </main>
      
      <ChangePasswordDialog
        isOpen={changePasswordDialogOpen}
        setOpen={setChangePasswordDialogOpen}
      />
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

Bak*_*riu 5

我在尝试将两个抽屉变体混合在一起时也偶然发现了一些问题,但几个小时后我发现了一个似乎可以正常工作的配置。

注意:我绝对不是 JS/CSS/HTML 方面的专家,我更像是一个后端人员,所以这个解决方案当然可以优化和修改以满足您的需求。

就我而言,抽屉用于选择要显示的“页面”。所以我将从那里开始。

“页面”组件仅包含一个Router. 其内容放在Boxwith内display: flex

const HomePage = () => {
    return (
        <Router>
            <Box sx={{display: 'flex'}}>
                <HomePageInner />
                <Switch>
                    <Route path="/">
                         <p>Example</p>
                    </Route>
                </Switch>
            </Box>
        </Router>
    );
}
Run Code Online (Sandbox Code Playgroud)

HomePageInner呈现“菜单”和应用栏的组件。它跟踪打开/关闭状态:

const drawerWidth = 240;

const HomePageInner = () => {
    const [open, setOpen] = useState(true);
    const toggleDrawer = () => setOpen(!open);
    const drawer = <DrawerContents onClick={toggleDrawer} />;
    return (
        <>
            <MainAppBar open={open} onClick={toggleDrawer}/>
            <Box
                component="nav"
                aria-label="menu items"
            >
                <MobileDrawer open={open} onClose={toggleDrawer} drawer={drawer}/>
                <DesktopDrawer open={open} drawer={drawer}/>
            </Box>
        </>
    );
}
Run Code Online (Sandbox Code Playgroud)

在这里您可以看到,为了清楚起见,我为“移动”和“桌面”定义了两个不同的组件。抽屉的内容是共享的,并在DrawerContents作为子组件传递的单独组件中定义。

有一个外层Box包裹着两个Drawers。在此示例中,Box有一个sx属性指定width: {sm: drawerWidth}, flexShrink: {sm :0},应该flexShrink被删除,但尝试添加/删除该width设置似乎没有执行任何操作,但如果某些内容被破坏,您可以尝试设置该属性。

DrawerContents很简单。它将“V 形”图标按钮放在顶部,可用于切换状态并显示链接:

const DrawerContents: FunctionComponent<{ onClick: () => void }> = (props) => {
    return <>
        <Toolbar
            sx={{
                display: "flex",
                alignItems: "center",
                justifyContent: "flex-end",
                px: [1],
            }}
        >
            <IconButton onClick={props.onClick}>
                <ChevronLeftIcon/>
            </IconButton>
        </Toolbar>
        <Divider/>
        <List>
            <ListItemLink primary="First" to="/first" icon={<DashboardIcon/>}/>
        </List>
        <List>
            <ListItemLink primary="Second" to="/second" icon={<TodayIcon/>}/>
        </List>
        <Divider/>
        <List>
            <ListItemLink primary="Third" to="/third" icon={<SettingsIcon/>}/>
        </List>
    </>;
}
Run Code Online (Sandbox Code Playgroud)

现在是有趣的部分!sDrawerAppBar. 因此,它与“响应式抽屉”示例中的抽屉MobileDrawer几乎完全相同:temporary

const MobileDrawer: FunctionComponent<{ container?: (() => any), open: boolean, onClose: () => void }> = (props) => {
    return <Drawer
        variant="temporary"
        container={props.container}
        open={props.open}
        onClose={props.onClose}
        ModalProps={{
            keepMounted: true,
        }}
        sx={{
            display: {xs: "block", sm: "none"},
            "& .MuiDrawer-paper": {boxSizing: "border-box", width: drawerWidth},
        }}
    >
        {props.children}
    </Drawer>;
}
Run Code Online (Sandbox Code Playgroud)

对于响应式抽屉示例中的抽屉DesktopDrawer大部分相同permanent,但是您必须修改sx属性中的样式,以便在状态更改时更改宽度:

const DesktopDrawer: FunctionComponent<{ open: boolean }> = (props) => {
    return <Drawer
        variant="permanent"
        sx={{
            display: {xs: "none", sm: "block"},
            "& .MuiDrawer-paper": {
                position: "relative",
                whiteSpace: "nowrap",
                width: drawerWidth,
                transition: theme => theme.transitions.create('width', {
                    easing: theme.transitions.easing.sharp,
                    duration: theme.transitions.duration.enteringScreen,
                }),
                boxSizing: "border-box",
                ...(!props.open && {
                    overflowX: "hidden",
                    transition: theme => theme.transitions.create('width', {
                        easing: theme.transitions.easing.sharp,
                        duration: theme.transitions.duration.leavingScreen,
                    }),
                    width: theme => ({xs: theme.spacing(7), sm: theme.spacing(9)}),
                })
            },
        }}
        open={props.open}
    >
        {props.children}
    </Drawer>;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这里我充分利用了属性的函数形式sx,使您可以访问theme. 我通过“合并”我在迷你变体示例和响应式示例中看到的值得出了正确的值。

而现在的MainAppBar. 这也与响应式抽屉示例非常相似,但是我们必须再次修改该sx属性:

const MainAppBar: FunctionComponent<{ open: boolean, onClick: () => void }> = (props) => {
    const location = useLocation();
    const headers: { [key: string]: string } = {
        '/first': "First",
        '/second': "Second",
        '/third': "Third",
    };

    return <AppBar
        position="fixed"
        sx={{
            zIndex: theme => theme.zIndex.drawer + 1,
            transition: theme => theme.transitions.create(['width', 'margin'], {
                easing: theme.transitions.easing.sharp,
                duration: theme.transitions.duration.leavingScreen,
            }),
            ...(props.open && {
                ml: {sm: `${drawerWidth}px`},
                width: {sm: `calc(100% - ${drawerWidth}px)`},
                transition: theme => theme.transitions.create(['width', 'margin'], {
                    easing: theme.transitions.easing.sharp,
                    duration: theme.transitions.duration.enteringScreen,
                }),
            })
        }}
    >
        <Toolbar>
            <IconButton
                color="inherit"
                aria-label="open drawer"
                edge="start"
                onClick={props.onClick}
                sx={{mr: 2}}
            >
                <MenuIcon/>
            </IconButton>
            <Typography
                component="h1"
                variant="h6"
                color="inherit"
                noWrap
                sx={{flexGrow: 1}}
            >
                {headers[location.pathname] || "Error"}
            </Typography>
        </Toolbar>
    </AppBar>;
}
Run Code Online (Sandbox Code Playgroud)

请注意,在这里您想要:

  • 删除display: { sm: 'none' }sx的 以IconButton切换状态
  • width设置ml必须仅包含在open状态中

通过所有这些配置,我得到了一个抽屉,可以像迷你变体一样打开/关闭更大的屏幕: 示例应用程序的菜单在左侧关闭,它只显示图标 示例应用程序的菜单在左侧打开,它显示图标和文本。 应用程序的内容“移动”到右侧

但也有反应: 菜单关闭的示例应用程序的图片,内容占据所有宽度 打开菜单的示例应用程序的图片,菜单显示在内容上方“就像模态对话框”

PS:我没有时间写一个简短的答案,所以我通过复制粘贴我的代码并剪掉一些东西写了一个长答案。如果我找到时间,我可能会将示例代码修改为更短和/或独立。