はじめに
サイドメニューは、今やWEBアプリを実装する人にとっては避けて通れないものとなっています。
大抵のWEBサイトには、サイドメニューがあるのではないでしょうか。
今回はそんなサイドメニューを実装してみたいと思います。
サイドメニューの実装担当になる前に、ここで押さえておきましょう。
実物はこちら
リポジトリはこちら
概要
今回は、ハンバーガーメニューやアコーディオンのようなギミックはなく、4つの項目を持つ最もシンプルな形のサイドメニューとなります。
以下のケースを想定して作成しています。
- ロゴと4つの項目があり、今後増えたり減ったりすることはない(動的にしない)
- 項目を選択すると、対応する画面が右エリアに表示される
実装
いつものように、先にコード全容です。
なお、今回はスタイルの勝手が効くため MUI のコンポーネントを多用しています。
慣れない方は Box は div、◯◯アイコンは svg、Typography は span に読み替えていただければイメージしやすいかと思います。
'use client'
import {Box, Typography} from "@mui/material";
import LocalConvenienceStoreIcon from '@mui/icons-material/LocalConvenienceStore';
import HomeRepairServiceIcon from '@mui/icons-material/HomeRepairService';
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import PeopleIcon from '@mui/icons-material/People';
import BuildIcon from '@mui/icons-material/Build';
//import {usePathname} from "next/navigation";
import {useState} from "react";
const Logo = () => (
<Box
p={2}
display="flex"
alignItems="center"
sx={{
backgroundColor: "#6573c3",
textWrap: "nowrap"
}}
>
<LocalConvenienceStoreIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem"
}}
/>
<Typography
variant="h5"
component="span"
sx={{
color: "white"
}}
>
Shop System
</Typography>
</Box>
)
export default function SideMenu() {
//const pathname = usePathname()
const [pathname, setPathname] = useState<string>('/');
function getPageName() {
switch (pathname) {
case '/product':
return 'Product'
case '/work-schedule':
return 'Work Schedule'
case '/staff':
return 'Staff'
case '/system-setting':
return 'System Setting'
default:
return ''
}
}
return (
<Box display="flex">
<Box
component="nav"
height="100vh"
bgcolor="primary"
style={{
backgroundColor: "#7c88cc"
}}
>
<Logo />
<ul>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/product' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/product')}
>
<HomeRepairServiceIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
Product
</Typography>
</Box>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/work-schedule' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/work-schedule')}
>
<CalendarMonthIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
Work Schedule
</Typography>
</Box>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/staff' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/staff')}
>
<PeopleIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
Staff
</Typography>
</Box>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/system-setting' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/system-setting')}
>
<BuildIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
System Setting
</Typography>
</Box>
</ul>
</Box>
<Box
component="main"
sx={{
backgroundColor: "#fffafa"
}}
width="100%"
m={2}
>
<Typography variant="h3" sx={{ color: "#6573c3"}}>
{
getPageName()
}
</Typography>
</Box>
</Box>
)
}
各部分の解説
現在のページのURLを取得する
サイドメニューは各ページへのリンクであることが多いと思います。
あるページに遷移したとき、いま表示されている画面がわかるようサイドメニューの対応する項目をハイライトするとユーザーに親切ですね。
そのためには現在のページのURLが必要になります。
Next.js の App Router を使用している場合は、next/navigation
の usePathname
フックを使用します。
今回は画面遷移しないので state で代用しています。
//import {usePathname} from "next/navigation";
export default function SideMenu() {
//const pathname = usePathname()
const [pathname, setPathname] = useState<string>('/');
サイドメニューとメイン画面を横並びに配置する
メニューを左側に、メイン画面を右側に配置するために1行目の display="flex"
で横並びにしています。
サイドメニューは height: 100vh
で縦幅いっぱいに、メイン画面は width: 100%
で横幅いっぱいになるよう指定しています。
<Box display="flex">
<Box
component="nav"
height="100vh"
bgcolor="primary"
style={{
backgroundColor: "#7c88cc"
}}
>
<Logo />
<ul>
============ 中略 ============
</ul>
</Box>
<Box
component="main"
sx={{
backgroundColor: "#fffafa"
}}
width="100%"
m={2}
>
<Typography variant="h3" sx={{ color: "#6573c3"}}>
{
getPageName()
}
</Typography>
</Box>
サイドメニュー
サイドメニューの4つの項目は MUI の Box コンポーネントで出力しています。component="li"
と指定することで、<li>
として出力することができます。
ポイントはハイライトした 15〜22行目の箇所です。
sx プロパティで CSS を指定しているのと、サイドメニューの項目がクリックされたときの処理を指定しています。
以下の指定がされていることを確認してください。
- マウスホバー
&:hover
でopacity
を変更している - 現在の URL とサイドメニューの項目が同じ場合はハイライトする
- サイドメニューの項目をクリックすると、その画面に遷移する(今回はサンプルなので state を更新する)
<Box
component="nav"
height="100vh"
bgcolor="primary"
style={{
backgroundColor: "#7c88cc"
}}
>
<Logo />
<ul>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/product' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/product')}
>
<HomeRepairServiceIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
Product
</Typography>
</Box>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/work-schedule' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/work-schedule')}
>
<CalendarMonthIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
Work Schedule
</Typography>
</Box>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/staff' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/staff')}
>
<PeopleIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
Staff
</Typography>
</Box>
<Box
component="li"
pl={5}
py={2}
sx={{
borderBottom: '1px solid white',
'&:hover': {
opacity: 0.7
},
backgroundColor: pathname === '/system-setting' ? '#afaffa' : 'transparent'
}}
onClick={() => setPathname('/system-setting')}
>
<BuildIcon
fontSize="large"
sx={{
color: "white",
marginRight: "0.5rem",
position: 'relative',
top: -3
}}
/>
<Typography
variant="subtitle1"
component="span"
sx={{
color: "white"
}}
>
System Setting
</Typography>
</Box>
</ul>
</Box>
おわりに
さて、いかがだったでしょうか?
今回は、WEB画面で見ない日はないサイドメニューを実装してみました。
この他にも、権限によって非活性になったり非表示になったり、アコーディオンで小項目を出したりなど、様々なパターンが考えられますね。
今回のコードもそうですが、サイドメニューの項目は同じコードなので、コンポーネント化もしていきたいところです。
ハコラトリは、Reactを習得したい新米エンジニアを応援しています。
Reactレシピでは、これからもReactで色々なUIを作って紹介していきますので、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったアイディアを募集中です!
コメントお待ちしております。
コメント