Header.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import React, {useState} from 'react';
  2. import menuItems from '@/data/menuItems';
  3. import {Link} from "react-router-dom";
  4. import {IMAGE_PREFIX} from "@/constants";
  5. const Header = () => {
  6. const [activeIndex, setActiveIndex] = useState<number | null>(null);
  7. const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  8. const [mobileActiveIndex, setMobileActiveIndex] = useState<number | null>(null);
  9. const [isMenuHovered, setIsMenuHovered] = useState(false);
  10. const scrollToTop = () => {
  11. window.scrollTo({
  12. top: 0,
  13. behavior: 'smooth'
  14. });
  15. };
  16. return (
  17. <header className="fixed top-0 left-0 right-0 flex items-center h-16 sm:h-20 w-full bg-white shadow-md z-50">
  18. <article
  19. id="sidebar-dimmer"
  20. className={`fixed top-16 sm:top-20 left-0 w-full h-full transition-opacity duration-500 ease-in-out ${isMenuHovered ? 'bg-black/50 opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'} hidden lg:block`}
  21. />
  22. <Link
  23. to="/"
  24. onClick={() => {
  25. setActiveIndex(null);
  26. setMobileMenuOpen(false);
  27. setMobileActiveIndex(null);
  28. scrollToTop();
  29. }}
  30. className="absolute left-4 sm:left-[40px] top-1/2 transform -translate-y-1/2 flex items-center gap-1 w-36"
  31. >
  32. <img className="w-12 h-8" src={`${IMAGE_PREFIX}/logo.png`} alt="logo"/>
  33. <div className="text-gray-600 text-2xl font-bold font-['Noto_Sans_KR']">반도산전</div>
  34. </Link>
  35. <nav
  36. className="hidden lg:flex justify-center items-center w-full h-full"
  37. onMouseEnter={() => setIsMenuHovered(true)}
  38. onMouseLeave={() => {
  39. setIsMenuHovered(false);
  40. setActiveIndex(null);
  41. }}
  42. >
  43. {menuItems.map((item, index) => (
  44. <div
  45. key={index}
  46. className="relative flex flex-col items-center px-[3vw] cursor-pointer"
  47. onMouseEnter={() => setActiveIndex(index)}
  48. >
  49. <div
  50. className={`relative group font-bold font-['Noto_Sans_KR'] cursor-pointer ${activeIndex === index ? 'text-blue-600' : 'text-gray-600'}`}>
  51. {item.label}
  52. <span
  53. className={`absolute left-0 bottom-[-2px] h-[1px] bg-blue-600 transition-all duration-300 ${activeIndex === index ? 'w-full' : 'w-0'}`}></span>
  54. </div>
  55. </div>
  56. ))}
  57. </nav>
  58. <button
  59. className="lg:hidden absolute right-4 top-1/2 transform -translate-y-1/2 flex flex-col justify-center items-center w-8 h-8 rounded-md hover:bg-gray-100 transition-colors duration-200"
  60. onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
  61. >
  62. <span className={`absolute block w-6 h-0.5 bg-gray-700 transition-all duration-300 ${mobileMenuOpen ? "rotate-45" : "-translate-y-2"}`}></span>
  63. <span className={`absolute block w-6 h-0.5 bg-gray-700 transition-all duration-300 ${mobileMenuOpen ? "opacity-0" : ""}`}></span>
  64. <span className={`absolute block w-6 h-0.5 bg-gray-700 transition-all duration-300 ${mobileMenuOpen ? "-rotate-45" : "translate-y-2"}`}></span>
  65. </button>
  66. {isMenuHovered && (
  67. <div
  68. id="menuBox"
  69. className="hidden lg:block absolute w-full h-[45vh] top-16 sm:top-20 bg-blue-900 overflow-hidden z-40"
  70. onMouseEnter={() => setIsMenuHovered(true)}
  71. onMouseLeave={() => {
  72. setIsMenuHovered(false);
  73. setActiveIndex(null);
  74. }}
  75. style={{
  76. animation: 'fadeIn 0.15s ease-in forwards'
  77. }}
  78. >
  79. <style>{`
  80. @keyframes fadeIn {
  81. from {
  82. opacity: 0;
  83. }
  84. to {
  85. opacity: 1;
  86. }
  87. }
  88. `}</style>
  89. <div className="max-w-7xl mx-auto flex justify-center items-start pt-8">
  90. {menuItems.map((item, index) => (
  91. <div key={index} className="flex flex-col items-center px-[3vw]">
  92. {item.children.length > 0 && (
  93. <div className="flex flex-col space-y-6 pt-4">
  94. {item.children.map((child, cIdx) => (
  95. <Link
  96. to={`${item.href}/${child.href}`}
  97. key={cIdx}
  98. className="relative inline-block text-md text-center font-medium font-['Noto_Sans_KR'] text-white whitespace-normal max-w-[100px] break-words hover:text-white/80 transition-all duration-200 group animate-in fade-in-0 slide-in-from-top-1"
  99. style={{ animationDelay: `${cIdx * 50}ms` }}
  100. onClick={() => {
  101. setActiveIndex(null);
  102. setIsMenuHovered(false);
  103. scrollToTop();
  104. }}
  105. >
  106. <span className="relative inline-block">
  107. {child.label}
  108. <span className="absolute left-0 bottom-[-2px] h-[1px] w-0 bg-white transition-all duration-300 group-hover:w-full"></span>
  109. </span>
  110. </Link>
  111. ))}
  112. </div>
  113. )}
  114. </div>
  115. ))}
  116. </div>
  117. </div>
  118. )}
  119. {mobileMenuOpen && (
  120. <div className="lg:hidden absolute top-16 sm:top-20 left-0 w-full bg-white shadow-xl border-t transform animate-in slide-in-from-top-2 duration-300">
  121. <nav className="flex flex-col">
  122. {menuItems.map((item, index) => (
  123. <div key={index} className="border-b border-gray-100">
  124. <div
  125. className="flex justify-between items-center px-5 py-3.5 text-gray-700 font-medium text-sm cursor-pointer hover:bg-blue-50 hover:text-blue-700 transition-all duration-200"
  126. onClick={() => setMobileActiveIndex(mobileActiveIndex === index ? null : index)}
  127. >
  128. <span>{item.label}</span>
  129. {item.children.length > 0 && (
  130. <svg
  131. className={`w-4 h-4 transition-transform duration-200 ${mobileActiveIndex === index ? 'rotate-180' : ''}`}
  132. fill="none"
  133. stroke="currentColor"
  134. viewBox="0 0 24 24"
  135. >
  136. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
  137. </svg>
  138. )}
  139. </div>
  140. {mobileActiveIndex === index && item.children.length > 0 && (
  141. <div className="bg-gray-50 animate-in slide-in-from-top-1 duration-200">
  142. {item.children.map((child, cIdx) => (
  143. <Link
  144. to={`${item.href}/${child.href}`}
  145. key={cIdx}
  146. className="block px-8 py-2.5 text-sm text-gray-600 hover:text-blue-600 hover:bg-white transition-colors duration-200 border-l-2 border-transparent hover:border-blue-500"
  147. onClick={() => {
  148. setMobileActiveIndex(null);
  149. setMobileMenuOpen(false);
  150. scrollToTop();
  151. }}
  152. >
  153. {child.label}
  154. </Link>
  155. ))}
  156. </div>
  157. )}
  158. </div>
  159. ))}
  160. </nav>
  161. </div>
  162. )}
  163. </header>
  164. );
  165. };
  166. export default Header;