basecafe.jp/src/components/Header.tsx
2025-10-28 20:52:11 +09:00

290 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import type { CompanyInfo } from "@/types/company";
type Props = {
company: CompanyInfo;
};
import Link from "next/link";
import { useState, useEffect, useRef } from "react";
import React from "react";
// Font Awesome
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faInstagram } from "@fortawesome/free-brands-svg-icons";
// Lucide
import { ShoppingBag } from "lucide-react";
type MenuItem = {
label: string;
href?: string;
external?: boolean;
children?: MenuItem[];
icon?: React.ReactNode;
};
// --- 省略 ---
const menuItems: MenuItem[] = [
{ label: "スケジュール", href: "/schedule" },
{ label: "新着情報", href: "/news" },
{ label: "ブランド", href: "/brand" },
{ label: "会社情報", href: "/company" },
{
label: "ウェブストア",
href: "https://store.basecafe.jp",
external: true,
icon: <ShoppingBag className="mr-2 w-4 h-4 text-stone-700" />,
},
{
label: "Instagram",
icon: <FontAwesomeIcon icon={faInstagram} className="mr-2 text-pink-600 w-4 h-4" />,
children: [
{
label: "Base Bettaku",
href: "https://www.instagram.com/basebettaku/",
external: true,
},
{
label: "BaseCafe",
href: "https://www.instagram.com/basecafe_2024/",
external: true,
},
],
},
{ label: "個室予約", href: "/reservation" },
];
export default function Header({ company }: Props) {
const [openSubMenu, setOpenSubMenu] = useState<string | null>(null);
const [isDesktop, setIsDesktop] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const [scrolled, setScrolled] = useState(false); // ← 追加
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// ウィンドウ幅チェック
const checkSize = () => setIsDesktop(window.innerWidth >= 1024);
checkSize();
window.addEventListener("resize", checkSize);
// クリックアウトサイドでサブメニュー閉じる
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setOpenSubMenu(null);
}
};
document.addEventListener("mousedown", handleClickOutside);
// スクロール監視Headerの半透明切替
const handleScroll = () => {
setScrolled(window.scrollY > 20); // 20px以上スクロールで true
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("resize", checkSize);
document.removeEventListener("mousedown", handleClickOutside);
window.removeEventListener("scroll", handleScroll);
};
}, []);
const toggleSubMenu = (label: string) => {
setOpenSubMenu(openSubMenu === label ? null : label);
};
return (
<header
className={`
lg:fixed lg:top-0 lg:left-0 lg:right-0 lg:z-50
transition-colors duration-300
${isDesktop && scrolled ? "bg-white/90 shadow-md backdrop-blur-md" : "bg-white"}
`}
>
<div className="max-w-7xl mx-auto flex justify-between items-center px-6 py-4">
{/* ロゴ */}
<Link href="/" className="text-2xl font-heading text-brand">
{company.name}
</Link>
{/* PCメニュー */}
<nav className="hidden lg:flex justify-center flex-1 space-x-6" ref={menuRef}>
{menuItems.map((item) => (
<div
key={item.label}
className="relative"
{...(isDesktop && {
onMouseEnter: () => setOpenSubMenu(item.label),
})}
>
{item.children ? (
<button
onClick={() => toggleSubMenu(item.label)}
className="hover:text-brand px-2 py-1 flex items-center"
>
{item.icon && item.icon}
{item.label}
</button>
) : item.external ? (
<a
href={item.href}
target="_blank"
rel="noopener noreferrer"
className="hover:text-brand px-2 py-1 flex items-center"
>
{item.icon && item.icon}
{item.label}
</a>
) : (
<Link
href={item.href || "#"}
className="hover:text-brand px-2 py-1 flex items-center"
>
{item.icon && item.icon}
{item.label}
</Link>
)}
{/* サブメニュー */}
{isDesktop && item.children && openSubMenu === item.label && (
<div className="absolute left-0 mt-2 bg-white shadow-lg rounded-md min-w-[160px] z-50">
{item.children.map((child) =>
child.external ? (
<a
key={child.label}
href={child.href}
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 hover:bg-gray-100 whitespace-nowrap"
>
{child.label}
</a>
) : (
<Link
key={child.label}
href={child.href || "#"}
className="block px-4 py-2 hover:bg-gray-100 whitespace-nowrap"
>
{child.label}
</Link>
)
)}
</div>
)}
</div>
))}
</nav>
{/* モバイルメニュー ボタン */}
<button className="lg:hidden" onClick={() => setMobileOpen((prev) => !prev)}>
{mobileOpen ? (
// Xアイコン
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
) : (
// ハンバーガーアイコン
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
)}
</button>
</div>
{/* モバイルメニュー */}
{mobileOpen && (
<nav
className={`lg:hidden fixed top-[64px] left-0 w-full h-[calc(100vh-64px)]
bg-white shadow-md z-40 overflow-y-auto transition-all duration-300
${mobileOpen ? "animate-fadeInUp" : "animate-fadeOutUp pointer-events-none"}`}
>
{menuItems.map((item) => (
<div key={item.label} className="border-b border-stone-200">
{item.children ? (
<>
<button
onClick={() => toggleSubMenu(item.label)}
className="w-full text-left px-4 py-3 hover:bg-stone-100 flex items-center text-stone-800"
>
{item.icon && item.icon}
{item.label}
</button>
{openSubMenu === item.label && (
<div className="pl-6 bg-stone-50">
{item.children.map((child) =>
child.external ? (
<a
key={child.label}
href={child.href}
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 hover:bg-stone-100"
onClick={() => setMobileOpen(false)}
>
{child.label}
</a>
) : (
<Link
key={child.label}
href={child.href || "#"}
className="block px-4 py-2 hover:bg-stone-100"
onClick={() => setMobileOpen(false)}
>
{child.label}
</Link>
)
)}
</div>
)}
</>
) : item.external ? (
<a
href={item.href}
target="_blank"
rel="noopener noreferrer"
className="flex px-4 py-3 hover:bg-stone-100 items-center text-stone-800"
onClick={() => setMobileOpen(false)}
>
{item.icon && item.icon}
{item.label}
</a>
) : (
<Link
href={item.href || "#"}
className="flex px-4 py-3 hover:bg-stone-100 items-center text-stone-800"
onClick={() => setMobileOpen(false)}
>
{item.icon && item.icon}
{item.label}
</Link>
)}
</div>
))}
</nav>
)}
</header>
);
}