サイドナビゲーションブロック
SideNavigation は、MUI Stack(component="aside")をベースにした折りたたみ可能なサイドメニューで、オプションのフローティングオーバーレイモードとデータ駆動のリンクを備えています。
インストール
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-side-navigation-block
yarn add @nodeblocks/frontend-side-navigation-block
pnpm add @nodeblocks/frontend-side-navigation-block
bun add @nodeblocks/frontend-side-navigation-block
必要なもの
| 項目 | 理由 |
|---|---|
links | ナビゲーション項目(href、任意の text、アイコン、isActive、unreadCount) |
header (optional) | 任意のアイコンとラベルを持つ上部リンク |
isMenuOpen / setIsMenuOpen (optional) | 開閉状態をまとめて制御します。両方省略すると内部状態を使います(初期状態は閉じています) |
isFloating (optional) | ポータル、背景、閉じるボタン付きのフローティングドロワー(デフォルトは false) |
親からメニューを制御する場合は、isMenuOpen と setIsMenuOpen を一緒に渡してください。両方を省略すると、このブロックは独自の状態を保持します(メニューボタンがクリックされるまで閉じたままです)。
例ではインラインモード(isFloating={false})を使い、メニューを開いた状態から始めるため、ドキュメントプレビューでサイドバーが表示されます。ポータルと背景付きのドロワーには isFloating を使ってください(Storybook を参照)。
コード例
- クイックスタート
- リンクとレイアウト
- コンパウンドコンポーネント
- ブロックのオーバーライド
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(true); return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={{ icon: HomeOutlined, text: 'Home', href: '#home' }} links={[ { icon: DashboardOutlined, text: 'Dashboard', href: '#dashboard' }, { icon: ShoppingCartOutlined, text: 'Products', href: '#products' }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]} /> </div> ); }
リンク状態、未読バッジ、パネル幅(ルートの aside に対する sx)をカスタマイズします。
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(true); return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={{ icon: HomeOutlined, text: 'Workspace', href: '#home' }} links={[ { icon: DashboardOutlined, text: 'Overview', href: '#overview', isActive: true }, { icon: ShoppingCartOutlined, text: 'Inbox', href: '#inbox', unreadCount: 12 }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]} sx={{ maxWidth: 280 }} /> </div> ); }
SideNavigation.Links の formatTextForUnreadCount を上書きしない限り、99 を超える件数は 99... と表示されます。
ヘッダー、メニューボタン、開いたメニューパネル、リンク一覧のサブブロックを明示的に構成します。
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(true); const header = { icon: HomeOutlined, text: 'Home', href: '#home' }; const links = [ { icon: DashboardOutlined, text: 'Dashboard', href: '#dashboard' }, { icon: ShoppingCartOutlined, text: 'Products', href: '#products' }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]; return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} links={links} > <SideNavigation.MenuButton isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} /> <SideNavigation.OpenMenu isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} > <SideNavigation.Links isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={header} links={links} /> </SideNavigation.OpenMenu> </SideNavigation> </div> ); }
ルート JSX の children はデフォルトブロックを置き換えます。このパターンでは、子ブロック(MenuButton、OpenMenu、Links)を明示的に構成し、それぞれのサブブロックに props を直接渡します。
function child を使ってブロックを先頭に追加したり、順序を変更したりします(Storybook と同じパターンです)。
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(false); return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={{ icon: HomeOutlined, text: 'Home', href: '#home' }} links={[ { icon: DashboardOutlined, text: 'Dashboard', href: '#dashboard' }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]} > {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { ...defaultBlocks, customBlock: isMenuOpen ? ( <div style={{ padding: 12, margin: 8, background: '#eef4ff', border: '1px solid #cddcff', borderRadius: 8, fontSize: 14, }} > Custom notification block </div> ) : null, }, blockOrder: ['customBlock', ...defaultBlockOrder], })} </SideNavigation> </div> ); }
ブロックのオーバーライドを使うタイミング
デフォルトブロックの上にバナーやカスタムセクションが必要で、かつメニューの動作を維持したい場合にオーバーライドを使います。defaultBlockOrder は menuButton、openMenu、overlay、closeButton、links です。ルートの aside で描画されるのは menuButton と openMenu のみで、overlay、closeButton、links は openMenu 内で構成されます(フローティング: ポータル + 背景、インライン: Links のみ)。
重要なプロパティ
コアプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
links | { icon?: SvgIconComponent; text?: ReactNode; href: string; isActive?: boolean; unreadCount?: number }[] | はい | - | ナビゲーションリンク項目 |
header | { icon?: SvgIconComponent; text?: ReactNode; href: string } | いいえ | undefined | links の上にある任意のヘッダーリンク |
isFloating | boolean | いいえ | false | true のとき、開いたメニューはポータル、背景、閉じるボタンを使います |
isMenuOpen | boolean | いいえ | 内部 false | メニューパネルが開いているかどうか |
setIsMenuOpen | (open: boolean) => void | いいえ | 内部セッター | 制御された使用では isMenuOpen と一緒に渡します |
コンテンツプロパティ
ルートに links と header を設定します。サブコンポーネントは、ローカルでブロックを上書きしない限り、コンテキストからそれらを読み取ります。
| コンポーネント | プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|---|
Links | children | ReactNode | いいえ | header + links を描画 | デフォルトのヘッダーとリンク一覧を置き換えます |
Links | formatTextForUnreadCount | (unreadCount: number) => number | string | いいえ | 件数が 99 を超えると 99...、それ以外は件数 | unreadCount のバッジテキスト |
MenuButton | children | ReactNode | いいえ | Menu / MenuOpen アイコン | トグルボタンのコンテンツ |
MenuButton | onClick | React.MouseEventHandler | いいえ | isMenuOpen を切り替え | カスタムクリックハンドラ |
Overlay | onClick | () => void | いいえ | () => setIsMenuOpen(false) | 背景のクリック(フローティングモード) |
CloseButton | children | ReactNode | いいえ | Close アイコン | フローティングパネル内の閉じるコントロール |
OpenMenu | container | PortalProps['container'] | いいえ | document.body | isFloating 時のポータル先 |
Links、MenuButton、Overlay、CloseButton、OpenMenu はそれぞれ SideNavigation.Links などです。リンクの icon 値には MUI SvgIconComponent を使います(例: HomeOutlined)。
レイアウトと構成のプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | いいえ | undefined | コンパウンドサブコンポーネント、または blocks と blockOrder を返す関数オーバーライド |
className | string | いいえ | undefined | ルート aside のクラス名(デフォルトで nbb-side-navigation が適用されます) |
sx | SxProps | いいえ | undefined | ルート aside 用の MUI システムスタイル |
SideNavigation は StackProps(children を除く)を継承します。ルートは component="aside"、maxWidth: 300、direction="column" を使います。ルートレベルの描画順序は menuButton、次に openMenu で、defaultBlockOrder は menuButton、openMenu、overlay、closeButton、links です。
デフォルト UI ブロック
| ブロック | 基盤 | 備考 |
|---|---|---|
SideNavigation(ルート) | Stack(component="aside") | maxWidth: 300。デフォルトでは menuButton + openMenu を描画します |
SideNavigation.MenuButton | IconButton + SvgIcon | メニューを切り替えます。デフォルトは Menu アイコンで、インラインで開いているときは MenuOpen になります |
SideNavigation.OpenMenu | Box + Portal | フローティング: Overlay、パネル、CloseButton、Links を含むポータル。インライン: Links のみ |
SideNavigation.Overlay | Box | 開いているときのフローティング openMenu 内の背景 |
SideNavigation.CloseButton | IconButton | フローティングパネル内(Close アイコン)。メニューを閉じます |
SideNavigation.Links | Stack(component="nav")+ Link | デフォルトのヘッダー/リンク一覧。開いているときはテキストと未読件数が表示されます |
TypeScript
import { SideNavigation } from '@nodeblocks/frontend-side-navigation-block';
import { DashboardOutlined, HomeOutlined } from '@mui/icons-material';
import type { SvgIconComponent } from '@mui/icons-material';
import type { ReactNode } from 'react';
type NavLink = {
icon?: SvgIconComponent;
text?: ReactNode;
href: string;
isActive?: boolean;
unreadCount?: number;
};
const links: NavLink[] = [
{ icon: DashboardOutlined, text: 'Dashboard', href: '/dashboard', isActive: true },
];
<SideNavigation
header={{ icon: HomeOutlined, text: 'Home', href: '/home' }}
links={links}
/>;