SCALE
SCALE Build Hub
機能集
UI部品 React Component

左サイドバーナビ(SystemSidebar)

出典: SCALE Finance / scale-x-app
実装時間
30〜90分
難度
簡単
価格
単体¥3,000 / カスタムグループ設計込み¥8,000

依存パッケージ

reactnext/linknext/navigation(usePathname)lucide-react(アイコン)

ファイル

components/layout/SystemSidebar.tsx

SystemSidebar(左サイドバーナビ)

:LiTarget: 用途

左固定の240px幅サイドバー。グループ別にリンクを並べて、現在地をハイライト。

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

'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>
  );
}

:LiHandPointer: 使用例(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>
  );
}

:LiAlertCircle: 注意事項

:LiLightbulb: 拡張アイデア