アプリヘッダー
:LiTarget: 用途
ロゴ・検索・通知・ユーザーメニューを含むダッシュボード共通ヘッダー。
:LiSparkle: 特徴
- ロゴ表示
- グローバル検索
- 通知バッジ
- ユーザーメニュー
:LiCode: 実コード(SCALE Base より自動抽出)
:LiInfo:
components/layout/Header.tsxの中身そのもの。コピペ即可。
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Bell, Search, Menu } from 'lucide-react';
import { getSystemByPath } from '@/lib/systems';
import { useAuth } from '@/lib/auth-context';
interface Props {
onMenuClick?: () => void;
}
export default function Header({ onMenuClick }: Props) {
const pathname = usePathname();
const { user } = useAuth();
const system = getSystemByPath(pathname);
const currentSection = system?.sections.find(
s => pathname === s.href || (s.href !== `/${system.id}` && pathname.startsWith(s.href + '/'))
);
return (
<>
<header className="h-14 bg-bg2 border-b border-border flex items-center justify-between px-4 md:px-6 shrink-0">
<div className="flex items-center gap-2 md:gap-3 min-w-0">
<button
onClick={onMenuClick}
className="md:hidden w-9 h-9 rounded-lg flex items-center justify-center text-text2 hover:text-text hover:bg-bg3 transition-colors shrink-0"
aria-label="メニュー"
>
<Menu size={18} />
</button>
{system && (
<div className="flex items-center gap-2 min-w-0">
<span className="text-text2 text-sm truncate hidden sm:inline">{system.name}</span>
<span className="text-text2 text-sm truncate sm:hidden">{system.shortName}</span>
{currentSection && (
<>
<span className="text-text3 shrink-0">/</span>
<span className="text-text text-sm font-medium truncate">{currentSection.label}</span>
</>
)}
</div>
)}
{!system && (
<span className="text-text text-sm font-medium">SCALE Base</span>
)}
</div>
<div className="flex items-center gap-2 md:gap-3 shrink-0">
{/* Search — desktop only */}
<div className="relative hidden lg:block">
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text3" />
<input
type="text"
placeholder="検索..."
className="h-8 w-48 rounded-lg bg-bg3 border border-border pl-8 pr-3 text-xs text-text placeholder:text-text3 outline-none focus:border-accent/50 transition-colors"
/>
</div>
<button className="relative w-9 h-9 rounded-lg flex items-center justify-center text-text3 hover:text-text hover:bg-bg3 transition-colors">
<Bell size={16} />
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-red" />
</button>
<div className="flex items-center gap-2 pl-2 border-l border-border">
<div className="w-7 h-7 rounded-full bg-accent/20 text-accent flex items-center justify-center text-xs font-bold">
{user?.avatar || '?'}
</div>
<div className="hidden lg:block">
<p className="text-xs text-text leading-tight">{user?.name}</p>
<p className="text-[10px] text-text3 leading-tight">{user?.departmentName}</p>
</div>
</div>
</div>
</header>
{/* Mobile section tabs (horizontal scroll) */}
{system && system.sections.length > 0 && (
<nav className="md:hidden bg-bg2 border-b border-border overflow-x-auto scrollbar-hide">
<div className="flex items-center gap-1 px-2 py-2 min-w-max">
{system.sections.map((item) => {
const isActive = pathname === item.href ||
(item.href !== `/${system.id}` && pathname.startsWith(item.href + '/'));
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs whitespace-nowrap transition-colors ${
isActive ? 'bg-bg4 text-text font-medium' : 'text-text2 hover:bg-bg3'
}`}
style={isActive ? { color: system.color } : undefined}
>
<Icon size={13} />
<span>{item.label}</span>
</Link>
);
})}
</div>
</nav>
)}
</>
);
}
:LiFolder: ソースファイルのパス
/Users/oogushiyuuki/Library/CloudStorage/GoogleDrive-y-ogushi@scale-group.co.jp/マイドライブ/AI/scale-base/components/layout/Header.tsx
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加