🚀 新規事業

system-sidebar

最終更新 2026年05月04日 / 40_新規事業/SCALE_Build/カタログ/機能集/system-sidebar.md

SystemSidebar(左サイドバーナビ)

用途

左固定の240px幅サイドバー。グループ別にリンクを並べて、現在地をハイライト。
- グループタイトル(小文字大文字)
- 各リンクにアイコン + テキスト
- 現在のパスをハイライト表示
- aliasパスにも反応(href が /foo でも、/foo/bar で active 判定)
- mobile(md未満)は非表示(モバイルは別途ハンバーガー実装)

コード(コピペ用・骨格)

'use client';

import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { LayoutDashboard, Settings /* etc */ } from 'lucide-react';
import type { ComponentType, SVGProps } from 'react';

type IconType = ComponentType<SVGProps<SVGSVGElement> & { size?: number | string }>;
type Item = { label: string; href: string; icon: IconType; aliases?: string[] };
type Group = { name: string; items: Item[] };

// ─── ここをカスタマイズ ───
const GROUPS: Group[] = [
  {
    name: '全体',
    items: [
      { label: 'ダッシュボード', href: '/', icon: LayoutDashboard },
    ],
  },
  {
    name: '設定',
    items: [
      { label: 'アカウント', href: '/account', icon: Settings },
    ],
  },
];

function normalize(p: string) {
  if (!p) return '/';
  const trimmed = p.replace(/\/+$/, '');
  return trimmed === '' ? '/' : trimmed;
}

export default function SystemSidebar() {
  const pathname = usePathname() || '/';
  const current = normalize(pathname);

  return (
    <aside
      className="hidden md:flex md:flex-col w-[240px] shrink-0 bg-bg2 border-r border-border overflow-y-auto"
      style={{ height: 'calc(100vh - 44px)', position: 'sticky', top: 44 }}
    >
      <nav className="p-3">
        {GROUPS.map((group) => (
          <div key={group.name} className="mb-4">
            <div className="text-[10px] font-semibold text-text3 uppercase tracking-wider px-2 mb-1">
              {group.name}
            </div>
            {group.items.map((item) => {
              const itemHref = normalize(item.href);
              const aliasMatched = item.aliases?.some((a) =>
                current === normalize(a) || current.startsWith(normalize(a) + '/')
              );
              const active =
                current === itemHref ||
                (itemHref !== '/' && current.startsWith(itemHref + '/')) ||
                aliasMatched;
              const Icon = item.icon;
              return (
                <Link key={item.href} href={item.href}
                  className={`flex items-center gap-2 px-2 py-1.5 rounded text-xs transition-colors ${
                    active ? 'bg-bg3 text-text font-medium' : 'text-text2 hover:bg-bg3 hover:text-text'
                  }`}>
                  <Icon size={14} />
                  <span className="truncate">{item.label}</span>
                </Link>
              );
            })}
          </div>
        ))}
      </nav>
    </aside>
  );
}

使用例(layout.tsx に組み込み)

import SystemSidebar from '@/components/layout/SystemSidebar';

export default function RootLayout({ children }) {
  return (
    <html lang="ja">
      <body className="min-h-screen bg-bg text-text">
        <header className="sticky top-0 z-30 h-11 bg-bg2 border-b border-border px-4 flex items-center justify-between">
          {/* ヘッダー内容 */}
        </header>
        <div className="flex">
          <SystemSidebar />
          <main className="flex-1 min-w-0 p-4 md:p-6">{children}</main>
        </div>
      </body>
    </html>
  );
}

注意事項

  • header が固定 44px 前提(変える場合は SystemSidebar の top: 44 も合わせる)
  • 'use client' 必須(usePathname のため)
  • Next.js 16 App Router 前提(pages router では別途実装が必要)
  • aliases に複数パスを入れると、サブパス遷移時もハイライト維持できる

拡張アイデア

  • グループの折りたたみ(useState で open/close)
  • 検索ボックス(filter で items 絞り込み)
  • お気に入りピン留め
  • mobile ドロワー(md未満で表示・ハンバーガー)