サイドナビゲーションブロック
SideNavigationコンポーネントは、ReactとTypeScriptで構築された完全にカスタマイズ可能でアクセシブルなサイドナビゲーションメニューです。モダンなデザインパターン、モバイルレスポンシブ動作、フローティングと固定モード、柔軟なカスタマイズオプションを備えた完全なナビゲーションインターフェースを提供します。
🚀 インストール
npm install @nodeblocks/frontend-side-navigation-block@0.3.0
📖 使用法
import {SideNavigation} from '@nodeblocks/frontend-side-navigation-block';
- 基本的な使用法
- 高度な使用法
function SimpleSideNavigation() { const links = [ {icon: DashboardIcon, text: 'ダッシュボード', href: '/dashboard'}, {icon: InventoryIcon, text: 'プロダクト', href: '/products'}, {icon: ShoppingCartIcon, text: '注文', href: '/orders'}, {icon: PeopleIcon, text: '顧客', href: '/customers'}, {icon: AnalyticsIcon, text: '分析', href: '/analytics'}, {icon: SettingsIcon, text: '設定', href: '/settings'}, ]; const header = { icon: HomeIcon, text: 'マイアプリ', href: '/', }; return ( <SideNavigation header={header} links={links}> <SideNavigation.MenuButton /> <SideNavigation.Links /> </SideNavigation> ); }
function AdvancedSideNavigation() { const [isMenuOpen, setIsMenuOpen] = useState(false); const links = [ {icon: DashboardIcon, text: 'ダッシュボード', href: '/dashboard'}, {icon: InventoryIcon, text: 'プロダクト', href: '/products'}, {icon: ShoppingCartIcon, text: '注文', href: '/orders'}, {icon: PeopleIcon, text: '顧客', href: '/customers'}, {icon: AnalyticsIcon, text: '分析', href: '/analytics'}, {icon: SettingsIcon, text: '設定', href: '/settings'}, ]; const header = { icon: HomeIcon, text: 'Acme Inc', href: '/', }; return ( <SideNavigation header={header} links={links} isFloating={true} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} sx={{ position: 'relative', minHeight: 400, }} > {({defaultBlocks, defaultBlockOrder}) => { const customMenuButton = ( <div style={{ position: 'fixed', top: '16px', left: '16px', zIndex: 1200, }} > <button onClick={() => setIsMenuOpen(!isMenuOpen)} style={{ width: '48px', height: '48px', borderRadius: '12px', background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ffffff', fontSize: '24px', boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)', }} > {isMenuOpen ? '✕' : '☰'} </button> </div> ); const customOverlay = isMenuOpen ? ( <div onClick={() => setIsMenuOpen(false)} style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0, 0, 0, 0.5)', zIndex: 1100, }} /> ) : null; const customLinks = ( <nav style={{ position: 'fixed', top: 0, left: isMenuOpen ? 0 : '-300px', width: '280px', height: '100vh', background: 'linear-gradient(180deg, #1e1b4b 0%, #312e81 100%)', zIndex: 1150, transition: 'left 0.3s ease', display: 'flex', flexDirection: 'column', }} > <div style={{ padding: '24px 20px', borderBottom: '1px solid rgba(255,255,255,0.1)', display: 'flex', alignItems: 'center', gap: '12px', }} > <div> <div style={{color: '#ffffff', fontSize: '16px', fontWeight: '700'}}>Acme Inc</div> <div style={{color: '#a5b4fc', fontSize: '12px'}}>Enterprise</div> </div> </div> <div style={{padding: '16px 12px', flex: 1}}> {links.map((link, index) => { const IconComponent = link.icon; return ( <a key={index} href={link.href} style={{ display: 'flex', alignItems: 'center', gap: '12px', padding: '12px 16px', borderRadius: '12px', textDecoration: 'none', marginBottom: '4px', color: '#a5b4fc', transition: 'all 0.2s ease', }} > {IconComponent && <IconComponent style={{fontSize: '20px'}} />} <span style={{fontSize: '14px', fontWeight: '500'}}>{link.text}</span> </a> ); })} </div> <div style={{ padding: '16px 20px', borderTop: '1px solid rgba(255,255,255,0.1)', }} > <div style={{display: 'flex', alignItems: 'center', gap: '12px'}}> <div style={{ width: '36px', height: '36px', borderRadius: '50%', background: '#6366f1', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: '14px', fontWeight: '600', }} > JD </div> <div style={{flex: 1}}> <div style={{color: '#ffffff', fontSize: '14px', fontWeight: '500'}}>John Doe</div> <div style={{color: '#a5b4fc', fontSize: '12px'}}>Admin</div> </div> </div> </div> </nav> ); return { blocks: { ...defaultBlocks, menuButton: customMenuButton, overlay: customOverlay, links: customLinks, }, blockOrder: defaultBlockOrder, }; }} </SideNavigation> ); }
🔧 プロパティリファレンス
メインコンポーネントプロパティ
メインSideNavigationコンポーネントは、component="aside"を持つMUI Stackコンポーネントのすべてのプロパティを継承します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
isFloating | boolean | undefined | ナビゲーションがコンテンツの上にフロート(モバイルスタイル)するか、固定位置にするか |
isMenuOpen | boolean | false | ナビゲーションメニューが開いているか閉じているかを制御。提供されない場合、内部状態を使用 |
setIsMenuOpen | (isMenuOpen: boolean) => void | 内部セッター | メニューの開閉状態を制御する関数。提供されない場合、内部状態セッターを使用 |
header | { icon?: SvgIconComponent; text?: ReactNode; href: string } | undefined | オプションのMUIアイコンコンポーネント、テキストコンテンツ、hrefを持つヘッダーリンクオブジェクト |
links | { icon?: SvgIconComponent; text?: ReactNode; href: string }[] | 必須 | オプションのMUIアイコンコンポーネントを持つナビゲーションリンクオブジェクトの配列 |
children | BlocksOverride | undefined | デフォルトブロックをオーバーライドするか、カスタムナビゲーションコンポーネントを追加する関数 |
className | string | undefined | 追加のCSSクラス名 |
sx | SxProps<Theme> | 下記参照 | スタイリング用のMUIシステムプロパティ |
デフォルトsxスタイリング:
{
display: 'inline-flex',
maxWidth: 300,
alignItems: 'stretch',
backgroundColor: 'background.paper',
width: 'fit-content',
height: '100%',
// 開いている場合: minWidth: 240
// フローティングの場合: position: 'absolute', top: 0, right: 0, zIndex: theme.zIndex.drawer
// フローティングで閉じている場合: display: 'none'
}
サブコンポーネント
SideNavigationコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。
SideNavigation.Overlay
フローティングメニューが開いているときに表示されるフルスクリーンオーバーレイ。すべてのMUI Boxプロパティを継承します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | オーバーレイのカスタムコンテンツ |
setIsMenuOpen | (isMenuOpen: boolean) => void | コンテキストから | オーバーレイがクリックされたときにメニュー状態を制御する関数 |
onClick | (event) => void | メニューを閉じる | クリックハンドラー(デフォルトでsetIsMenuOpen(false)を呼び出す) |
className | string | undefined | 追加のCSSクラス名 |
sx | SxProps<Theme> | 下記参照 | スタイリング用のMUIシステムプロパティ |
デフォルトsxスタイリング:
{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#1d1e20b2',
zIndex: 'inherit',
}
注意: このコンポーネントはすべてのMUI Boxコンポーネントプロパティを継承します。
SideNavigation.MenuButton
ナビゲーションを切り替えるハンバーガーメニューボタン。すべてのMUI IconButtonプロパティを継承します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | <SvgIcon component={Menu} /> | ボタンのカスタムコンテンツ(デフォルトはMenu/MenuOpenアイコン) |
isFloating | boolean | コンテキストから | フローティングモードかどうか |
isMenuOpen | boolean | コンテキストから | 現在のメニュー開閉状態 |
setIsMenuOpen | (isMenuOpen: boolean) => void | コンテキストから | メニュー状態を切り替える関数 |
onClick | (event) => void | メニューを切り替え | クリックハンドラー(デフォルトでisMenuOpenを切り替え) |
className | string | undefined | 追加のCSSクラス名 |
sx | SxProps<Theme> | 下記参照 | スタイリング用のMUIシステムプロパティ |
デフォルトsxスタイリング:
{
display: 'inline-flex',
border: 'none',
background: 'none',
alignSelf: 'flex-start',
// フローティング: p: 1.25
// 非フローティング: p: 1.5, mx: 1.5
}
注意: このコンポーネントはすべてのMUI IconButtonコンポーネントプロパティを継承します。デフォルトアイコンは状態に基づいてMenuとMenuOpenの間で切り替わります(非フローティングモード)。
SideNavigation.Links
ナビゲーションリンクコンテナ。component="nav"を持つすべてのMUI Stackプロパティを継承します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | デフォルトリンクレンダリング | デフォルトリンクレンダリングをオーバーライドするカスタムコンテンツ |
isFloating | boolean | コンテキストから | フローティングモードかどうか(リンクスタイリングに影響) |
isMenuOpen | boolean | コンテキストから | メニューが開いているかどうか(テキストの表示を制御) |
header | { icon?: SvgIconComponent; text?: ReactNode; href: string } | コンテキストから | 表示するヘッダーリンクオブジェクト |
links | { icon?: SvgIconComponent; text?: ReactNode; href: string }[] | コンテキストから | 表示するナビゲーションリンクの配列 |
className | string | undefined | 追加のCSSクラス名 |
sx | SxProps<Theme> | 下記参照 | スタイリング用のMUIシステムプロパティ |
デフォルトsxスタイリング:
{
position: 'relative',
zIndex: 'inherit',
}
注意: このコンポーネントはすべてのMUI Stackコンポーネントプロパティを継承します。各リンクは一貫したスタイリングでMUI Linkとしてレンダリングされます。
🎨 設定例
カスタムスタイリング
import HomeIcon from '@mui/icons-material/Home';
import FolderIcon from '@mui/icons-material/Folder';
import GroupIcon from '@mui/icons-material/Group';
import SettingsIcon from '@mui/icons-material/Settings';
function CustomStyledSideNavigation() {
const links = [
{icon: HomeIcon, text: 'ホーム', href: '/'},
{icon: FolderIcon, text: 'ファイル', href: '/files'},
{icon: GroupIcon, text: 'プロファイル', href: '/profile'},
{icon: SettingsIcon, text: '設定', href: '/settings'},
];
const header = {
text: 'メニュー',
href: '/',
};
return (
<SideNavigation
header={header}
links={links}
sx={{
width: 260,
bgcolor: 'grey.900',
borderRight: '1px solid',
borderColor: 'grey.800',
minHeight: 400,
}}
>
<SideNavigation.Links
sx={{
p: 2,
'& .MuiListItem-root': {
borderRadius: 1,
mb: 0.5,
color: 'grey.300',
'&:hover': {
bgcolor: 'grey.800',
},
},
'& .MuiSvgIcon-root': {
color: 'primary.light',
},
}}
/>
</SideNavigation>
);
}
フローティングナビゲーション
import {useState} from 'react';
import DashboardIcon from '@mui/icons-material/Dashboard';
import InventoryIcon from '@mui/icons-material/Inventory';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import SettingsIcon from '@mui/icons-material/Settings';
import HomeIcon from '@mui/icons-material/Home';
function FloatingSideNavigation() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const links = [
{icon: DashboardIcon, text: 'ダッシュボード', href: '/dashboard'},
{icon: InventoryIcon, text: 'プロダクト', href: '/products'},
{icon: ShoppingCartIcon, text: '注文', href: '/orders'},
{icon: SettingsIcon, text: '設定', href: '/settings'},
];
const header = {
icon: HomeIcon,
text: 'アプリ',
href: '/',
};
return (
<SideNavigation
header={header}
links={links}
isFloating={true}
isMenuOpen={isMenuOpen}
setIsMenuOpen={setIsMenuOpen}
>
<SideNavigation.Overlay />
<SideNavigation.MenuButton />
<SideNavigation.Links />
</SideNavigation>
);
}
カスタムリンクコンテナスタイリング
<SideNavigation.Links
sx={{
bgcolor: 'grey.100',
borderRadius: 2,
p: 1
}}
/>
カスタムメニューボタンスタイリング
<SideNavigation.MenuButton
sx={{
color: 'primary.main',
'&:hover': { bgcolor: 'primary.light' }
}}
/>
カスタムオーバーレイスタイリング
<SideNavigation.Overlay
sx={{
bgcolor: 'rgba(0, 0, 0, 0.7)'
}}
/>
🔧 TypeScriptサポート
包括的な型定義による完全なTypeScriptサポート:
import {SideNavigation} from '@nodeblocks/frontend-side-navigation-block';
import {SvgIconComponent} from '@mui/icons-material';
import DashboardIcon from '@mui/icons-material/Dashboard';
import AnalyticsIcon from '@mui/icons-material/Analytics';
import InventoryIcon from '@mui/icons-material/Inventory';
import SettingsIcon from '@mui/icons-material/Settings';
import HomeIcon from '@mui/icons-material/Home';
import {useState} from 'react';
interface NavLink {
icon?: SvgIconComponent;
text?: React.ReactNode;
href: string;
}
interface NavHeader {
icon?: SvgIconComponent;
text?: React.ReactNode;
href: string;
}
function TypedSideNavigation() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const navigationLinks: NavLink[] = [
{
icon: DashboardIcon,
text: 'ダッシュボード',
href: '/dashboard',
},
{
icon: AnalyticsIcon,
text: '分析',
href: '/analytics',
},
{
icon: InventoryIcon,
text: 'プロダクト',
href: '/products',
},
{
icon: SettingsIcon,
text: '設定',
href: '/settings',
},
];
const navigationHeader: NavHeader = {
icon: HomeIcon,
text: 'アプリ名',
href: '/',
};
return (
<SideNavigation
header={navigationHeader}
links={navigationLinks}
isFloating={false}
isMenuOpen={isMenuOpen}
setIsMenuOpen={setIsMenuOpen}
sx={{
width: 280,
minHeight: '100vh',
borderRight: '1px solid #e5e7eb',
}}
>
<SideNavigation.Links />
</SideNavigation>
);
}
📝 注意事項
-
MUI統合: コンポーネントは内部でMUI
Stack、Box、IconButton、Link、SvgIconコンポーネントを使用しています。 -
アイコンタイプ:
iconプロパティは文字列ではなく、@mui/icons-materialからのSvgIconComponentを期待しています。Home、Dashboardなどのアイコンをインポートしてください。 -
内部状態管理:
isMenuOpenとsetIsMenuOpenが提供されない場合、コンポーネントは内部で独自の状態を管理します。 -
フローティング vs 固定モード:
- フローティングモード: ナビゲーションはオーバーレイと共にコンテンツの上に表示され、右側に絶対位置で配置されます。メニューボタンはナビゲーションの外側に表示されます。
- 固定モード: ナビゲーションはコンテンツとインラインです。メニューボタンはナビゲーション内に表示され、開いているときは外側に閉じるボタンが表示されます。
-
レスポンシブ動作: フローティングモードでは、閉じているときナビゲーションは非表示になります(
display: 'none')。オーバーレイはページコンテンツとのインタラクションをブロックします。 -
ブロックオーバーライドパターン:
children関数を使用して、デフォルトのブロック構造を維持しながらサブコンポーネントをカスタマイズします。 -
Z-Index: コンポーネントはフローティングモードで適切なレイヤリングを確保するために
theme.zIndex.drawerを使用します。
React、TypeScript、MUI、モダンWebスタンダードを使用して❤️で構築されました。