【问题标题】:Connect React Material UI: Mini Variant + Responsive drawerConnect React Material UI:迷你变体 + 响应式抽屉
【发布时间】:2021-11-03 07:34:10
【问题描述】:

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

这是我用于 Mini Variant Drawer 的代码:

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>
  );
}

【问题讨论】:

  • 请澄清您的具体问题或提供其他详细信息以准确突出您的需求。正如目前所写的那样,很难准确地说出你在问什么。

标签: reactjs drawer


【解决方案1】:

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

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

在我的例子中,抽屉用于选择要显示的“页面”。所以我将从那里开始。

“页面”组件只包含一个Router。它的内容放在Boxdisplay: flex 中:

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

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>
        </>
    );
}

在这里您可以看到,为了清楚起见,我为“移动”和“桌面”定义了两个不同的组件。抽屉的内容是共享的,并在一个单独的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>
    </>;
}

现在是有趣的部分! Drawers 和 AppBar。因此,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>;
}

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>;
}

请注意,这里我充分利用了sx 属性的函数形式,它使您可以访问theme。我通过“合并”我在 Mini-Variant 示例和 Responsive 示例中看到的值得出了正确的值。

现在是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>;
}

请注意,您要在此处:

  • 删除IconButtonsx 中的display: { sm: 'none' } 以切换状态
  • widthml 设置只能包含在 open 状态中

通过所有这些配置,我得到了一个抽屉,可以像迷你变体一样打开/关闭更大的屏幕:

但也是响应式的:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-09
    • 2021-06-14
    • 1970-01-01
    • 2020-07-07
    • 2021-07-11
    • 1970-01-01
    • 2021-05-04
    相关资源
    最近更新 更多