290 lines
7.9 KiB
TypeScript
290 lines
7.9 KiB
TypeScript
"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>
|
||
);
|
||
}
|