Material UIで当サイトのようなレイアウトを作成する。
// ヘッダーの高さを指定export headerHeight = '40px';export function Header() { return ( <AppBar enableColorOnDark > <Toolbar variant="dense" disableGutters sx={{ minHeight: headerHeight }}> <OpenNavButton /> // ヘッダーの内容 ... </Toolbar> </AppBar> );}
<OpenNavButton />
はのちのち作成する。
Toolbarは必ずvariant="dense"
にしなければならない。
これがないとメディアクエリにminHeightが指定されるので、minHeightが上書きされてしまう。
基本的に公式サイトのresponsive-drawerを参考にしている。
変更点として、
これらをする必要がない場合は公式サイトのものを参照したほうがシンプルにできる。
Material UI Responsive drawer
まず、atomの作成。
export const isOpenNav = atom<boolean>({ key: 'isOpenNav', default: false,});
開け閉めするボタン。
画面サイズがmd以上では表示させない。
Next.jsを使用している場合は先頭に'use client'を書く。
export function OpenButton() { const [isOpenNav, setIsOpenNav] = useRecoilState(isOpenNavAtom); const toggleNav = () => setIsOpenNav(!isOpenNav); return ( <IconButton color="inherit" aria-label="ナビを開く" onClick={toggleNav} sx={{ visibility: { md: 'hidden' } }} > <MenuIcon /> </IconButton> );};
ナビゲーション機能を有するコンポーネント。
Next.jsを使用している場合は先頭に'use client'を書く。
// ナビの幅を指定export const navWidth = '200px';export function NavWrapper({ children }: PropsWithChildren) { return ( <Box component="nav" sx={{ width: { md: navWidth } }}> <FixedNav>{children}</FixedNav> <DrawerNav>{children}</DrawerNav> </Box> );};// 大きい画面で表示される固定されたナビゲーション// ヘッダーの下に配置させるため、高さからheaderHeightを差し引くfunction FixedNav({ children }: PropsWithChildren) { return ( <Drawer variant="permanent" open={true} // こうすると影の大きさがヘッダーと同じになり自然な感じになる PaperProps={{ elevation: 4 }} sx={{ display: { xs: 'none', md: 'block' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: navWidth, top: headerHeight height: `calc(100% - ${headerHeight})`, } }} children={children} /> );};// 小さい画面で表示されるドロワーナビゲーションfunction DrawerNav({ children }: PropsWithChildren) { const [isOpenNav, setIsOpenNav] = useRecoilState(isOpenNavAtom); const closeNav = () => setIsOpenNav(false); return ( <Drawer variant="temporary" open={isOpenNav} onClose={closeNav} ModalProps={{ keepMounted: true, }} sx={{ display: { xs: 'block', md: 'none' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: navWidth } }} children={children} /> );};
ナビゲーションの本体。
export function Nav() { return ( <NavWrapper> // ナビゲーションの内容 ... </NavWrapper> );};
ヘッダーとナビゲーションに囲まれた内側のコンポーネント。
スマホ画面であれば画面幅いっぱいに表示され、それ以上であれば画面サイズからnavWidthを差し引いたwidthとなる。
フッターを最下部に配置させるため、flexにしてjustifyContent='space-between'
を設定している。
export function ContentsArea(props: PropsWithChildren) { return ( <Box marginTop={headerHeight} width={{ xs: '100%', md: `calc(100% - ${navWidth})` }} display='flex' flexDirection='column' justifyContent='space-between' children={props.children} // 必要であればpaddingも設定する // paddingX={{ xs: 0, sm: 2 }} // paddingTop={2} /> );};
flexGrowを0にすることでフッターを伸長させることなく最下部に配置できる。
export function Footer() { return ( <Box component='footer' flexGrow={0}> // フッターの内容 ... </Box> );};
全体をflexで囲むことでNavとContentsAreaを横並びにしている。
Headerはdisplay: fixed
になっているため上部に固定される。
export function Layout(props: PropsWithChildren) { return ( <Box display='flex' height='100%'> <Header /> <Nav /> <ContentsArea> <Box component='main'> {props.children} </Box> <Footer /> </ContentsArea> </Box> );};
このLayoutコンポーネントで囲むことでレイアウトが完成する。
Next.jsを使用している場合は、layout.tsxに書く。
export default function RootLayout({ children }: PropsWithChildren) { return ( <html lang="ja"> <body> <Provider> <CssBaseline /> <Layout> {children} </Layout> </Provider> </body> </html> );}