概要
MUI(Material UI)を使って、共通レイアウトを作っていく。 MUIとはデザイナーやアプリ開発者向けに作られたUIコンポーネントのライブラリである。Reactを用いてUIを構築する際にかっこいいモノができる。
実装方法
インストール
まずはreactプロジェクトにMUIをインストールする。
npm install @mui/material @emotion/react @emotion/styled
参考: Installation - Material UI
Drawerコンポーネントのコピー
Drawerと呼ばれる、開閉可能なNavigationバーのようなものを共通レイアウトにする。
下記サイトをスクロールしていくと「Responsive Drawer」というものがある。そこで「show code」(TypeScriptの方)を開き、コピーしてAppLayout.tsxに張り付ける。
mui.com
この状態でnpm startするとサンプルが表示される。
コードの解説
全文
import * as React from 'react'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import CssBaseline from '@mui/material/CssBaseline'; import Divider from '@mui/material/Divider'; import Drawer from '@mui/material/Drawer'; import IconButton from '@mui/material/IconButton'; import InboxIcon from '@mui/icons-material/MoveToInbox'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import MailIcon from '@mui/icons-material/Mail'; import MenuIcon from '@mui/icons-material/Menu'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; const drawerWidth = 240; interface Props { /** * Injected by the documentation to work in an iframe. * Remove this when copying and pasting into your project. */ window?: () => Window; } export default function ResponsiveDrawer(props: Props) { const { window } = props; const [mobileOpen, setMobileOpen] = React.useState(false); const [isClosing, setIsClosing] = React.useState(false); const handleDrawerClose = () => { setIsClosing(true); setMobileOpen(false); }; const handleDrawerTransitionEnd = () => { setIsClosing(false); }; const handleDrawerToggle = () => { if (!isClosing) { setMobileOpen(!mobileOpen); } }; const drawer = ( <div> <Toolbar /> <Divider /> <List> {['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => ( <ListItem key={text} disablePadding> <ListItemButton> <ListItemIcon> {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} </ListItemIcon> <ListItemText primary={text} /> </ListItemButton> </ListItem> ))} </List> <Divider /> <List> {['All mail', 'Trash', 'Spam'].map((text, index) => ( <ListItem key={text} disablePadding> <ListItemButton> <ListItemIcon> {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} </ListItemIcon> <ListItemText primary={text} /> </ListItemButton> </ListItem> ))} </List> </div> ); // Remove this const when copying and pasting into your project. const container = window !== undefined ? () => window().document.body : undefined; return ( <Box sx={{ display: 'flex' }}> <CssBaseline /> <AppBar position="fixed" sx={{ width: { sm: `calc(100% - ${drawerWidth}px)` }, ml: { sm: `${drawerWidth}px` }, }} > <Toolbar> <IconButton color="inherit" aria-label="open drawer" edge="start" onClick={handleDrawerToggle} sx={{ mr: 2, display: { sm: 'none' } }} > <MenuIcon /> </IconButton> <Typography variant="h6" noWrap component="div"> Responsive drawer </Typography> </Toolbar> </AppBar> <Box component="nav" sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }} aria-label="mailbox folders" > {/* The implementation can be swapped with js to avoid SEO duplication of links. */} <Drawer container={container} variant="temporary" open={mobileOpen} onTransitionEnd={handleDrawerTransitionEnd} onClose={handleDrawerClose} ModalProps={{ keepMounted: true, // Better open performance on mobile. }} sx={{ display: { xs: 'block', sm: 'none' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, }} > {drawer} </Drawer> <Drawer variant="permanent" sx={{ display: { xs: 'none', sm: 'block' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, }} open > {drawer} </Drawer> </Box> <Box component="main" sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` } }} > <Toolbar /> <Typography sx={{ marginBottom: 2 }}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus. Convallis convallis tellus id interdum velit laoreet id donec ultrices. Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis feugiat vivamus at augue. At augue eget arcu dictum varius duis at consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa sapien faucibus et molestie ac. </Typography> <Typography sx={{ marginBottom: 2 }}> Consequat mauris nunc congue nisi vitae suscipit. Fringilla est ullamcorper eget nulla facilisi etiam dignissim diam. Pulvinar elementum integer enim neque volutpat ac tincidunt. Ornare suspendisse sed nisi lacus sed viverra tellus. Purus sit amet volutpat consequat mauris. Elementum eu facilisis sed odio morbi. Euismod lacinia at quis risus sed vulputate odio. Morbi tincidunt ornare massa eget egestas purus viverra accumsan in. In hendrerit gravida rutrum quisque non tellus orci ac. Pellentesque nec nam aliquam sem et tortor. Habitant morbi tristique senectus et. Adipiscing elit duis tristique sollicitudin nibh sit. Ornare aenean euismod elementum nisi quis eleifend. Commodo viverra maecenas accumsan lacus vel facilisis. Nulla posuere sollicitudin aliquam ultrices sagittis orci a. </Typography> </Box> </Box> ); }
要らない部分の削除
(1) interface Props { /** * Injected by the documentation to work in an iframe. * Remove this when copying and pasting into your project. */ window?: () => Window; } (2) props: Props ※export default function ResponsiveDrawer()のところ (3) const { window } = props; (4) // Remove this const when copying and pasting into your project. const container = window !== undefined ? () => window().document.body : undefined; (5) container={container} ※{/* The implementation can be swapped with js to avoid SEO duplication of links. */}直下のところ
構造
全文を上から虱潰しに読んでいくと辛いので、構造から把握していく。
import * as React from 'react'; インポート文の塊 export default function ResponsiveDrawer(props: Props) { モバイル画面が開いてるかどうかのstate ドロワーが閉じる動作中かのstate const handleDrawerClose = () => { ドロワーが閉じる処理 }; const handleDrawerTransitionEnd = () => { ドロワーの閉じるアニメーションが終了したときの処理 } const handleDrawerToggle = () => { ドロワーを開閉するためのトグル処理。 } const drawer = メニュー項目の定義 return ( <Box> <AppBar>ヘッダー</AppBar> <Box>サイドバー</Box> <Box>メインコンテンツ</Box> </Box> ); }
こういう構造になっている。
drawerの解説
const drawer = ( <div> <Toolbar /> <Divider /> <List> {['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => ( <ListItem key={text} disablePadding> <ListItemButton> <ListItemIcon> {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} </ListItemIcon> <ListItemText primary={text} /> </ListItemButton> </ListItem> ))} </List> <Divider /> <List> {['All mail', 'Trash', 'Spam'].map((text, index) => ( <ListItem key={text} disablePadding> <ListItemButton> <ListItemIcon> {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} </ListItemIcon> <ListItemText primary={text} /> </ListItemButton> </ListItem> ))} </List> </div> );
<Toolbar />
はMUIのコンポーネントである。ここでは上部にスペースを設けるために使用される。通常、タイトルやメニューアイコンを置く場所になる。MUI の `<Toolbar/>` について<Divider />
は線を引いて要素同士を区切るためのMUIコンポーネントである。<List>...</ List>
はリスト表示を簡単に作成できるMUIコンポーネントである。アイテムのリストを整理して表示するために使用され、特にモバイルアプリやウェブアプリでよく見かけるリストのスタイルを作成するのに便利。
List→ListItem→ListItemTextの順で入れ子にして使う。```- 内にリストアイテムを表示する。
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
map関数により配列の要素を分解し、各項目に対してListItemを作っている。textは配列の各要素。indexは配列の要素番号。<ListItem key={text} disablePadding>
ListItemは、個々のリストアイテムを表すMUIコンポーネントである。key={text}
により、各アイテムを一意に識別できるようにしている。disablePadding
のところは、disablePadding={true}
の省略記法。これを書くことでリストアイテム間のデフォルトのパディングを無効にしている。<ListItemButton> ... </ListItemButton>
これはリストアイテムをボタンのようにクリック可能にするMUIコンポーネントである。これにより、アイテムをクリックするとアクションが実行されるようになる。<ListItemIcon> ... </ListItemIcon>
これはリストアイテムの左側にアイコンを配置するためのコンポーネントである。```を使用すると、アイコンがリストアイテム内で適切にスタイルされ、アイコン部分に自動的に余白やサイズ調整が適用される。 {index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
単純にリストのアイコンをと を交互に並べるための記述。 <ListItemText primary={text} />
リストアイテムのテキスト部分を表示するためのコンポーネントである。