目次
- 各コンポーネントの特徴と比較
- Modal
- Dialog
- Menu
- Popover
- Drawer
- Tooltip
1. 比較
特徴 | Modal | Dialog | Menu | Popover | Drawer | Tooltip |
---|---|---|---|---|---|---|
背景オーバーレイ | ◯ | ◯ | × | ◯ | ◯ | × |
スクロールブロック | ◯ | ◯ | × | × | ◯ | × |
アンカー要素 | 不要 | 不要 | 必要 | 必要 | 不要 | 必要 |
表示位置 | 画面中央 | 画面中央 | アンカー基準 | アンカー基準 | 画面端 | アンカー基準 |
主な用途 | 重要な操作 | 確認画面 | ナビゲーション | 詳細表示 | サイドバー | ヘルプテキスト |
キーボード操作 | Escで閉じる | Tab/Esc | 矢印キー | Tab/Esc | Tab/Esc | なし |
アニメーション | Fade | Fade/Slide | Grow | Grow | Slide | Fade |
2. Modal
- 複雑なフォームやコンテンツの表示
- 重要な操作や確認が必要な場合
- ユーザーの作業を意図的に中断させる場合
- 画面中央に表示
❌ Bad
- 軽微な確認での使用する
- 小さな通知やアラート
- 頻繁な表示/非表示の切り替え
- 複数のModalを重ねて表示する
✅ Good
- ユーザー登録フォーム等の複雑なフォームや大量の情報を表示する
- Modalは一度に一つだけ表示する
実装例
import { useState } from 'react'
import { Modal, Box, Button, Typography } from '@mui/material'
export const SimpleModal = () => {
const [open, setOpen] = useState(false)
return (
<>
<Button variant="contained" onClick={() => setOpen(true)}>
モーダルを開く
</Button>
<Modal open={open} onClose={() => setOpen(false)}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
width: 400,
p: 3,
}}
>
<Typography>モーダルのタイトル</Typography>
<Typography sx={{ mt: 2 }}>内容</Typography>
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end' }}>
<Button onClick={() => setOpen(false)}>閉じる</Button>
</Box>
</Box>
</Modal>
</>
)
}
3. Dialog
- シンプルな確認の際に使用する
- 短いメッセージを表示
- Yes/Noの選択を求める場合に使う
- 画面中央に表示する
❌ Bad
- 複雑なフォームの表示
- 大量のコンテンツの表示
- 長時間の作業が必要な操作
✅ Good
- 削除の確認
- 簡単な警告メッセージ
- 単純な選択肢の提示
実装例
import { useState } from 'react'
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
Box,
Typography,
} from '@mui/material'
export const SimpleDialog = () => {
const [open, setOpen] = useState(false)
const handleConfirm = () => {
// 削除するを押した時の処理
setOpen(false)
}
return (
<>
<Button variant="contained" color="error" onClick={() => setOpen(true)}>
アカウントを削除
</Button>
<Dialog open={open} onClose={() => setOpen(false)} fullWidth>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography>アカウントの削除確認</Typography>
</Box>
</DialogTitle>
<DialogContent>
<DialogContentText>
アカウントを削除すると、すべてのデータが完全に削除され、復元することはできません。
この操作は取り消すことができません。
</DialogContentText>
</DialogContent>
<DialogActions sx={{ p: 2 }}>
<Button onClick={() => setOpen(false)} color="inherit">
キャンセル
</Button>
<Button
onClick={handleConfirm}
variant="contained"
color="error"
autoFocus
>
削除する
</Button>
</DialogActions>
</Dialog>
</>
)
}
4. Menu
- シンプルなアクション一覧
- 単一選択の操作
❌ Bad
- 大量のコンテンツをMenuに詰め込む
- フォーム要素をMenuに配置
- 深いネストの階層構造
✅ Good
- シンプルな選択肢を表示する
- 大きなコンテンツはModalを使用
- フォームはPopoverやModalを使用
実装例
import { useState } from 'react'
import {
Menu,
MenuItem,
Button,
ListItemIcon,
ListItemText,
} from '@mui/material'
import {
Settings as SettingsIcon,
Person as PersonIcon,
Logout as LogoutIcon,
} from '@mui/icons-material'
export const SimpleMenu = () => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
return (
<>
<Button variant="contained" onClick={(e) => setAnchorEl(e.currentTarget)}>
メニューを開く
</Button>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
>
<MenuItem onClick={() => setAnchorEl(null)}>
<ListItemIcon>
<PersonIcon fontSize="small" />
</ListItemIcon>
<ListItemText>プロフィール</ListItemText>
</MenuItem>
<MenuItem onClick={() => setAnchorEl(null)}>
<ListItemIcon>
<SettingsIcon fontSize="small" />
</ListItemIcon>
<ListItemText>設定</ListItemText>
</MenuItem>
<MenuItem onClick={() => setAnchorEl(null)}>
<ListItemIcon>
<LogoutIcon fontSize="small" />
</ListItemIcon>
<ListItemText>ログアウト</ListItemText>
</MenuItem>
</Menu>
</>
)
}
5. Popover
- 表示位置を指定する
- カスタマイズ可能なレイアウト
- Menuと比べて柔軟なコンテンツ配置ができる
❌ Bad
- 重要な操作や確認に使う
- 大量のコンテンツを詰め込む
- スクロールが必要なほどの長いコンテンツ
✅ Good
- 重要な操作はModal/Dialogを使用する
- コンテンツは必要最小限にする
- 長いコンテンツはDrawerやModalを使用する
実装例
import { useState } from 'react'
import {
Popover,
Button,
Typography,
Box,
IconButton,
List,
ListItem,
ListItemText,
Divider,
} from '@mui/material'
import { Close as CloseIcon } from '@mui/icons-material'
export const SimplePopover = () => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const open = Boolean(anchorEl)
const id = open ? 'simple-popover' : undefined
return (
<>
<Button variant="contained" onClick={handleClick}>
詳細を表示
</Button>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<Box sx={{ width: 300, p: 2 }}>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 1,
}}
>
<Typography variant="h6">詳細情報</Typography>
<IconButton size="small" onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
<Divider sx={{ mb: 2 }} />
<List dense>
<ListItem>
<ListItemText primary="作成日" secondary="YYYY/MM/DD" />
</ListItem>
<ListItem>
<ListItemText primary="ステータス" secondary="処理中" />
</ListItem>
</List>
</Box>
</Popover>
</>
)
}
6. Drawer
- 画面端からスライドイン
- 大量のコンテンツを表示できる
- サイドバーやナビゲーションに最適
- 画面の一部を占有しながら、メインコンテンツも表示可能
❌ Bad
- 重要な確認ダイアログをDrawerで実装
- 画面全体を覆うコンテンツ
- 短いメニュー
✅ Good
- 確認にはModal/Dialogを使用
- コンテンツ幅を適切に指定する
- 設定項目やフォームを表示
- 複数の項目を持つサイドバー
実装例
import { useState } from 'react'
import {
Drawer,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
IconButton,
Box,
Typography,
Divider,
} from '@mui/material'
import {
Menu as MenuIcon,
Home as HomeIcon,
Person as PersonIcon,
Settings as SettingsIcon,
Mail as MailIcon,
Help as HelpIcon,
} from '@mui/icons-material'
export const SimpleDrawer = () => {
const [open, setOpen] = useState(false)
const menuItems = [
{ text: 'ホーム', icon: <HomeIcon />, path: '/' },
{ text: 'プロフィール', icon: <PersonIcon />, path: '/profile' },
{ text: 'メッセージ', icon: <MailIcon />, path: '/messages' },
{ text: '設定', icon: <SettingsIcon />, path: '/settings' },
{ text: 'ヘルプ', icon: <HelpIcon />, path: '/help' },
]
return (
<>
<IconButton edge="start" color="inherit" onClick={() => setOpen(true)}>
<MenuIcon />
</IconButton>
<Drawer anchor="left" open={open} onClose={() => setOpen(false)}>
<Box sx={{ width: 280 }}>
<Box sx={{ p: 2 }}>
<Typography variant="h6">サイドメニュー</Typography>
</Box>
<Divider />
<List>
{menuItems.map((item) => (
<ListItem key={item.text} disablePadding>
<ListItemButton onClick={() => setOpen(false)}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
</Drawer>
</>
)
}
7. Tooltip
- ホバーやフォーカスで一時的に表示
- シンプルなヘルプテキスト
- 簡潔な補足情報の表示(アイコンボタンの説明等)
❌ Bad
- 長文を入れる
- 重要な情報の表示
- インタラクティブな要素を入れる
✅ Good
- 長文にはPopperを使用する
- インタラクティブな要素にはMenuやModalを使用する
実装例
import { Box, IconButton, Tooltip } from '@mui/material'
import {
Edit as EditIcon,
Delete as DeleteIcon,
Share as ShareIcon,
} from '@mui/icons-material'
export const SimpleTooltip = () => {
return (
<Box sx={{ display: 'flex', gap: 1 }}>
<Tooltip title="編集する" arrow placement="top">
<IconButton>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="共有する" arrow placement="top">
<IconButton>
<ShareIcon />
</IconButton>
</Tooltip>
<Tooltip title="削除する" arrow placement="top">
<IconButton color="error">
<DeleteIcon />
</IconButton>
</Tooltip>
</Box>
)
}