584 lines
25 KiB
Vue

// src/components/Header.vue
<template>
<header
v-if="appStore.headerData"
:style="`background-image: url('${appStore.imageBaseUrl}${appStore.headerData.header_background.url}'); background-size: cover; background-position: center;`"
class="relative z-10"
>
<nav ref="navbarRef" class="navbar p-2 md:p-4 shadow-md" :style="{ backgroundColor: appStore.headerData.mainColor }">
<div class="container mx-auto flex items-center justify-between w-full">
<div class="navbar-start w-auto flex-shrink-0">
<router-link to="/home" class="flex flex-col items-start leading-tight">
<span class="text-white text-lg font-bold">
{{ appStore.checkLang.isTh ? 'ฮิวแมนเทคไทยแลนด์' : 'HumanTech' }}
</span>
<span class="text-white text-xs opacity-80">
{{ appStore.checkLang.isTh ? 'HumanTech' : 'ฮิวแมนเทคไทยแลนด์' }}
</span>
</router-link>
</div>
<div class="navbar-center hidden md:flex flex-1 items-center justify-end space-x-2 lg:space-x-4">
<ul class="menu menu-horizontal px-1 flex-nowrap">
<li v-for="menu in orderedMainMenus" :key="menu.id" class="relative">
<template v-if="menu.sub_menu_groups && menu.sub_menu_groups.length || (menu.sub_menus && menu.sub_menus.length)">
<button
class="text-white font-semibold text-base hover:bg-gray-700 hover:text-white py-2 px-3 rounded-md flex items-center gap-1"
:ref="el => menuButtonRefs.set(menu.id, el)"
@click="toggleDropdown(menu.id, menu.sub_menu_groups, menu.sub_menus)"
>
<span>{{ appStore.checkLang.isTh ? menu.main_menu : menu.main_menu_en }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-180': activeMenuId === menu.id }"
>
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 10.94l3.71-3.71a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0L5.23 8.27a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</button>
</template>
<template v-else>
<router-link
:to="menu.link"
class="text-white font-semibold text-base hover:bg-gray-700 hover:text-white py-2 px-3 rounded-md"
:ref="el => menuButtonRefs.set(menu.id, el)"
@click="activeMenuId = null"
>
{{ appStore.checkLang.isTh ? menu.main_menu : menu.main_menu_en }}
</router-link>
</template>
</li>
</ul>
<div class="flex items-center space-x-0 ml-4">
<button class="btn btn-sm rounded-l-lg rounded-r-none normal-case text-base font-bold px-4 py-2"
:class="isLangButtonActive('th') ? 'active-lang-button' : 'inactive-lang-button'"
:disabled="isLangButtonActive('th')"
@click="switchLanguageToThai">TH</button>
<button class="btn btn-sm rounded-r-lg rounded-l-none normal-case text-base font-bold px-4 py-2"
:class="isLangButtonActive('en') ? 'active-lang-button' : 'inactive-lang-button'"
:disabled="isLangButtonActive('en')"
@click="switchLanguageToEnglish">EN</button>
</div>
</div>
<div class="navbar-end md:hidden">
<div class="dropdown dropdown-end relative">
<div role="button" class="btn btn-ghost text-white hover:bg-gray-700" ref="mobileMenuToggleButtonRef" @click="isMobileMenuOpen = !isMobileMenuOpen">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</div>
<ul
v-if="isMobileMenuOpen"
class="absolute top-full right-0 z-[9999] mt-2 w-screen max-w-xs bg-base-100 shadow rounded-box p-4 text-black max-h-[80vh] overflow-y-auto"
ref="mobileMenuDropdownRef"
>
<li v-for="menu in orderedMainMenus" :key="menu.id" class="mb-2">
<template v-if="menu.sub_menu_groups && menu.sub_menu_groups.length > 0">
<details :open="activeMobileMenuId === menu.id" @toggle="handleMobileDetailsToggle(menu.id, $event)">
<summary class="cursor-pointer py-2 font-semibold text-base hover:bg-gray-200 rounded px-2 flex items-center justify-between">
<span>{{ appStore.checkLang.isTh ? menu.main_menu : menu.main_menu_en }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-180': activeMobileMenuId === menu.id }"
>
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 10.94l3.71-3.71a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0L5.23 8.27a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</summary>
<ul class="pl-4 py-2 bg-base-100">
<template v-for="group in orderedSubMenuGroups(menu.sub_menu_groups)" :key="group.group_title_th">
<li v-if="group.group_title_th || group.group_title_en" class="font-bold text-sm text-gray-700 mt-2 mb-1">
{{ appStore.checkLang.isTh ? group.group_title_th : group.group_title_en }}
</li>
<li v-for="subMenu in (appStore.checkLang.isTh ? orderedSubMenuItems(group.items) : orderedSubMenuItemsEng(group.items))"
:key="subMenu.id">
<router-link :to="subMenu.link" class="block py-1 px-2 rounded mobile-submenu-item" @click="closeMobileMenu">
{{ appStore.checkLang.isTh ? subMenu.title_th : subMenu.title_en }}
</router-link>
</li>
</template>
</ul>
</details>
</template>
<template v-else-if="menu.sub_menus && menu.sub_menus.length > 0">
<details :open="activeMobileMenuId === menu.id" @toggle="handleMobileDetailsToggle(menu.id, $event)">
<summary class="cursor-pointer py-2 font-semibold text-base hover:bg-gray-200 rounded px-2 flex items-center justify-between">
<span>{{ appStore.checkLang.isTh ? menu.main_menu : menu.main_menu_en }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-180': activeMobileMenuId === menu.id }"
>
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 10.94l3.71-3.71a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0L5.23 8.27a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</summary>
<ul class="pl-4 py-2 bg-base-100">
<li v-for="subMenu in (appStore.checkLang.isTh ? orderedSubMenuItems(menu.sub_menus) : orderedSubMenuItemsEng(menu.sub_menus))"
:key="subMenu.id">
<router-link :to="subMenu.link" class="block py-1 px-2 rounded mobile-submenu-item" @click="closeMobileMenu">
{{ appStore.checkLang.isTh ? subMenu.title_th : subMenu.title_en }}
</router-link>
</li>
</ul>
</details>
</template>
<template v-else>
<router-link
:to="menu.link"
class="block py-2 px-2 font-semibold text-base hover:bg-gray-200 rounded"
@click="closeMobileMenu">
{{ appStore.checkLang.isTh ? menu.main_menu : menu.main_menu_en }}
</router-link>
</template>
</li>
<li class="mt-4">
<div class="flex items-center justify-around w-full p-2">
<button
class="btn btn-sm rounded-l-lg rounded-r-none normal-case text-base font-bold px-4 py-2 w-1/2"
:class="isLangButtonActive('th') ? 'active-lang-button' : 'inactive-lang-button-mobile'"
:disabled="isLangButtonActive('th')"
@click="switchLanguageToThai"
>
TH
</button>
<button
class="btn btn-sm rounded-r-lg rounded-l-none normal-case text-base font-bold px-4 py-2 w-1/2"
:class="isLangButtonActive('en') ? 'active-lang-button' : 'inactive-lang-button-mobile'"
:disabled="isLangButtonActive('en')"
@click="switchLanguageToEnglish"
>
EN
</button>
</div>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div
v-if="activeMenuId !== null"
class="absolute bg-white shadow-lg rounded p-4 z-50 desktop-dropdown"
:style="dropdownStyles"
ref="desktopDropdownRef"
>
<template v-if="activeDropdownData.sub_menu_groups && activeDropdownData.sub_menu_groups.length">
<div class="multi-column-dropdown-content">
<div
v-for="(group, groupIndex) in orderedSubMenuGroups(activeDropdownData.sub_menu_groups)"
:key="groupIndex"
class="flex flex-col"
>
<h3 v-if="group.group_title_th || group.group_title_en" class="font-bold text-lg mb-2 text-gray-700 whitespace-nowrap">
{{ appStore.checkLang.isTh ? group.group_title_th : group.group_title_en }}
</h3>
<ul class="space-y-1">
<li
v-for="subMenu in (appStore.checkLang.isTh ? orderedSubMenuItems(group.items) : orderedSubMenuItemsEng(group.items))"
:key="subMenu.id"
>
<router-link :to="subMenu.link" class="desktop-submenu-item block py-1 px-0 text-sm whitespace-nowrap" @click="closeDropdown">
{{ appStore.checkLang.isTh ? subMenu.title_th : subMenu.title_en }}
</router-link>
</li>
</ul>
</div>
</div>
</template>
<template v-else-if="activeDropdownData.sub_menus && activeDropdownData.sub_menus.length">
<ul class="single-column-dropdown-content">
<li
v-for="subMenu in (appStore.checkLang.isTh ? orderedSubMenuItems(activeDropdownData.sub_menus) : orderedSubMenuItemsEng(activeDropdownData.sub_menus))"
:key="subMenu.id"
>
<router-link :to="subMenu.link" class="desktop-submenu-item block py-1 px-0 text-sm whitespace-nowrap" @click="closeDropdown">
{{ appStore.checkLang.isTh ? subMenu.title_th : subMenu.title_en }}
</router-link>
</li>
</ul>
</template>
</div>
</header>
</template>
<script setup>
import { ref, computed, onMounted, nextTick, watch } from 'vue';
import { useAppStore } from '@/stores/app';
import _ from 'lodash';
const appStore = useAppStore();
// State for active desktop menu dropdown
const activeMenuId = ref(null);
const activeDropdownData = ref({}); // Stores the data (sub_menu_groups/sub_menus) for the active dropdown
const dropdownStyles = ref({}); // Stores the dynamic CSS styles for the active dropdown
// Refs for DOM elements
const navbarRef = ref(null); // Reference to the <nav> element
const menuButtonRefs = new Map(); // Map to store references to each menu button (summary/router-link)
const desktopDropdownRef = ref(null); // Reference to the floating desktop dropdown container
// State for active mobile menu dropdown (uses DaisyUI details behavior)
const activeMobileMenuId = ref(null);
// New state for controlling mobile menu visibility
const isMobileMenuOpen = ref(false); // <--- NEW STATE
// New refs for mobile menu elements
const mobileMenuDropdownRef = ref(null); // Reference to the <ul> element that acts as the mobile dropdown container
const mobileMenuToggleButtonRef = ref(null); // Reference to the button that toggles the mobile menu (hamburger icon)
// --- Computed Properties for Menu Ordering ---
const orderedMainMenus = computed(() => {
const menus = appStore.allMainMenus || [];
const active = menus.filter(m => appStore.checkLang.isTh ? m.active : m.active_en);
const home = active.find(m => m.id === 1); // Assuming ID 1 is always Home
const others = active.filter(m => m.id !== 1);
return home ? [home, ..._.orderBy(others, 'order')] : _.orderBy(active, 'order');
});
const orderedSubMenuGroups = groups => _.orderBy(groups || [], 'order');
const orderedSubMenuItems = items => _.orderBy((items || []).filter(f => f.active), 'order');
const orderedSubMenuItemsEng = items => _.orderBy((items || []).filter(f => f.active_en), 'order');
// --- Desktop Dropdown Logic ---
const toggleDropdown = async (menuId, subMenuGroups, subMenus) => {
// If clicking the same menu, close it
if (activeMenuId.value === menuId) {
activeMenuId.value = null;
activeDropdownData.value = {};
return;
}
activeMenuId.value = menuId;
activeDropdownData.value = {
sub_menu_groups: subMenuGroups,
sub_menus: subMenus
};
// Wait for DOM to update with the new active dropdown
await nextTick();
const button = menuButtonRefs.get(menuId); // Get the specific button ref
if (button && navbarRef.value && desktopDropdownRef.value) {
const buttonRect = button.getBoundingClientRect();
const navbarRect = navbarRef.value.getBoundingClientRect();
const dropdownRect = desktopDropdownRef.value.getBoundingClientRect();
const dropdownTop = navbarRect.bottom + window.scrollY; // Position right below the navbar
let dropdownLeft = buttonRect.left + window.scrollX; // Default align to button left
let transform = 'translateX(0)';
// Adjust for overflow on right side
const spaceToRight = window.innerWidth - (buttonRect.left + dropdownRect.width);
if (spaceToRight < 20) { // If less than 20px space to right, align right
dropdownLeft = buttonRect.right + window.scrollX - dropdownRect.width;
}
// Adjust for overflow on left side
if (dropdownLeft < 10) { // If less than 10px from left edge
dropdownLeft = 10 + window.scrollX;
}
dropdownStyles.value = {
top: `${dropdownTop}px`,
left: `${dropdownLeft}px`,
minWidth: `${buttonRect.width}px`,
transform: transform,
maxWidth: `calc(100vw - 40px)`,
};
}
};
const closeDropdown = () => {
activeMenuId.value = null;
activeDropdownData.value = {};
};
// --- Mobile Dropdown Logic (uses DaisyUI details behavior) ---
const handleMobileDetailsToggle = (menuId, event) => {
if (event.target.open) {
// Close other open details elements in the same mobile menu list
document.querySelectorAll('ul[ref="mobileMenuDropdownRef"] details[open]').forEach(detail => { // Corrected selector
if (detail !== event.target) detail.open = false;
});
activeMobileMenuId.value = menuId;
} else {
activeMobileId.value = null; // Changed from activeMobileMenuId.value = null; due to typo, ensure consistent
}
};
const closeMobileMenu = () => {
isMobileMenuOpen.value = false; // <--- NEW: Set state to close the main menu
activeMobileMenuId.value = null; // Close any open details within the mobile menu
// Also, close any open details by iterating directly
// This line is often not strictly necessary if `activeMobileMenuId` is properly managed,
// but acts as a fallback to ensure all <details> are closed.
document.querySelectorAll('ul[ref="mobileMenuDropdownRef"] details[open]').forEach(detail => {
detail.open = false;
});
};
// --- Language Switch Logic ---
const switchLanguageToThai = () => {
// Only switch if not already Thai
if (!appStore.checkLang.isTh) {
appStore.toggleLanguage('th');
closeMobileMenu(); // Use the new function for mobile
closeDropdown();
}
};
const switchLanguageToEnglish = () => {
// Only switch if not already English (i.e., if currently Thai)
if (appStore.checkLang.isTh) {
appStore.toggleLanguage('en');
closeMobileMenu(); // Use the new function for mobile
closeDropdown();
}
};
const isLangButtonActive = computed(() => lang => {
return (lang === 'th' && appStore.checkLang.isTh) || (lang === 'en' && !appStore.checkLang.isTh);
});
// --- Lifecycle Hooks and Watchers ---
onMounted(() => {
// Set initial language from localStorage
const savedLang = localStorage.getItem('lang');
if (savedLang === 'en') {
appStore.isTh = false;
} else {
appStore.isTh = true;
}
// Event listener for clicks outside dropdowns to close them
document.addEventListener('click', (event) => {
// Desktop dropdown close logic
if (activeMenuId.value !== null && desktopDropdownRef.value) {
const dropdownElement = desktopDropdownRef.value;
const clickedButton = menuButtonRefs.get(activeMenuId.value);
// Check if click is inside the desktop dropdown or the button that opened it
if (!dropdownElement.contains(event.target) && (!clickedButton || !clickedButton.contains(event.target))) {
closeDropdown();
}
}
// Mobile dropdown close logic
// Only check if mobile menu is currently open
if (isMobileMenuOpen.value && mobileMenuDropdownRef.value && mobileMenuToggleButtonRef.value) {
const mobileDropdownElement = mobileMenuDropdownRef.value;
const mobileToggleButton = mobileMenuToggleButtonRef.value;
// Check if click is outside the mobile dropdown container AND outside the toggle button
if (!mobileDropdownElement.contains(event.target) && !mobileToggleButton.contains(event.target)) {
closeMobileMenu();
}
}
});
// Watch for window resize to adjust desktop dropdown position
window.addEventListener('resize', () => {
if (activeMenuId.value !== null) {
// Re-calculate position for the currently open desktop dropdown
const currentMenu = orderedMainMenus.value.find(m => m.id === activeMenuId.value);
if (currentMenu) {
toggleDropdown(activeMenuId.value, currentMenu.sub_menu_groups, currentMenu.sub_menus);
}
}
// For mobile, if the screen size changes to desktop, close mobile menu
if (window.innerWidth >= 768 && isMobileMenuOpen.value) { // 768px is Tailwind's 'md' breakpoint
closeMobileMenu();
}
});
});
// Watch for activeMenuId change to trigger position adjustment
watch(activeMenuId, (newId, oldId) => {
if (newId !== null) {
// Re-trigger toggleDropdown to recalculate position if the menu data itself is needed
// The actual data is passed into toggleDropdown
}
});
// Watch for language change to adjust dropdown position if needed (due to text width changes)
watch(() => appStore.checkLang.isTh, () => {
if (activeMenuId.value !== null) {
const currentMenu = orderedMainMenus.value.find(m => m.id === activeMenuId.value);
if (currentMenu) {
toggleDropdown(activeMenuId.value, currentMenu.sub_menu_groups, currentMenu.sub_menus);
}
}
});
</script>
<style scoped>
/*
GLOBAL STYLES FOR DESKTOP DROPDOWN CONTAINER (The one that floats)
*/
.desktop-dropdown {
background-color: white;
padding: 1.5rem; /* p-4 in example template was p-6 for multi-column originally */
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
z-index: 1000; /* Ensure it's above other content */
}
/*
STYLES FOR MULTI-COLUMN CONTENT INSIDE THE FLOATING DROPDOWN
*/
.multi-column-dropdown-content {
display: grid;
grid-auto-flow: column; /* Force content into columns first */
grid-auto-columns: max-content; /* Each column takes only as much width as its widest content */
gap: 2rem; /* Gap between columns */
overflow-x: auto; /* Allow horizontal scrolling if columns still overflow */
min-width: 200px; /* Base min width for the content area itself */
}
/*
STYLES FOR SINGLE-COLUMN CONTENT INSIDE THE FLOATING DROPDOWN
*/
.single-column-dropdown-content {
min-width: 200px; /* Base min width for the content area itself */
}
/* --- NEW: Desktop Submenu Item Hover Effect --- */
.desktop-submenu-item {
color: #333; /* Default text color for desktop submenu items */
padding: 0.25rem 0.5rem; /* py-1 px-2 */
display: block;
white-space: nowrap; /* Prevent menu item text from wrapping */
position: relative; /* Needed for pseudo-elements if used, or for absolute child */
transition: all 0.2s ease-in-out; /* Smooth transition for all properties */
border: 1px solid transparent; /* Start with transparent border */
border-radius: 0.25rem; /* Match rounded-md */
}
.desktop-submenu-item:hover {
background-color: #EBF8FF; /* blue-100 for light background */
color: #2563EB; /* blue-600 for text */
border-color: #60A5FA; /* blue-400 for border */
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
/* --- NEW: Mobile Submenu Item Hover Effect --- */
.mobile-submenu-item {
color: #333; /* Default text color for mobile submenu items */
padding: 0.25rem 0.5rem; /* block py-1 px-2 */
display: block;
border-radius: 0.25rem; /* Match rounded */
transition: all 0.2s ease-in-out; /* Smooth transition */
border: 1px solid transparent; /* Start with transparent border */
}
.mobile-submenu-item:hover {
background-color: #EBF8FF; /* blue-100 for light background */
color: #2563EB; /* blue-600 for text */
border-color: #60A5FA; /* blue-400 for border */
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
/* --- Language Button Styles --- */
.active-lang-button {
background-color: #e0e0e0;
color: #333;
border: 1px solid #ccc;
cursor: default; /* Change cursor to indicate it's not clickable */
}
.inactive-lang-button {
background-color: transparent;
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.5);
cursor: pointer;
}
.inactive-lang-button:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
}
/* Styles for disabled state to reduce visual clutter */
.active-lang-button[disabled] {
opacity: 0.7; /* Make it slightly transparent */
cursor: default; /* Ensure cursor is default */
background-color: #e0e0e0; /* Maintain active background */
color: #333; /* Maintain active text color */
}
/* Specific border adjustments for the language buttons to match the image */
.active-lang-button:first-child, .inactive-lang-button:first-child { border-right: none; }
.active-lang-button:last-child, .inactive-lang-button:last-child { border-left: none; }
.flex.items-center.space-x-0 > .btn:not(:first-child) { margin-left: 0; }
.flex.items-center.space-x-0 > .btn:not(:last-child) { margin-right: 0; }
.inactive-lang-button-mobile {
background-color: transparent;
color: #333;
border: 1px solid #ccc;
cursor: pointer;
}
.inactive-lang-button-mobile:first-child { border-right: none; }
.inactive-lang-button-mobile:last-child { border-left: none; }
/* Styles for disabled state on mobile buttons */
.inactive-lang-button-mobile[disabled] {
opacity: 0.7;
cursor: default;
background-color: #e0e0e0; /* Match active-lang-button */
color: #333;
}
/*
MOBILE MENU STYLES (largely unchanged, using DaisyUI dropdown-end behavior)
*/
.navbar-end.md\:hidden .dropdown-content {
position: absolute;
top: 100%; /* Relative to the dropdown-end parent */
right: 0;
width: screen; /* Full width on small screens */
max-width: 280px; /* Constrain max width for mobile dropdown */
margin-top: 0.5rem; /* mt-2 */
z-index: 9999;
padding: 1rem; /* p-4 */
box-sizing: border-box;
max-height: 80vh;
overflow-y: auto;
}
.navbar-end.md\:hidden .dropdown-content .font-bold.text-sm.text-gray-700 { /* This targets the group title */
margin-top: 0.5rem; /* mt-2 */
margin-bottom: 0.25rem; /* mb-1 */
padding-left: 0.5rem; /* Consistent padding */
padding-right: 0.5rem;
}
.navbar-end.md\:hidden .dropdown-content ul.pl-4 { /* Targets the ul for sub-items in mobile */
padding-left: 1rem; /* Indent mobile sub-items */
}
</style>