-
Notifications
You must be signed in to change notification settings - Fork 1
/
_header.tsx
124 lines (111 loc) · 3.71 KB
/
_header.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"use client";
import { createContext, useRef, useState } from "react";
import Logo from "./partials/_logo";
import MainMenu from "./partials/_main-menu";
import SecondaryMenu from "./partials/_secondary-menu";
import {
type HeaderContextType,
type MenuItems,
type UtilityItems,
} from "./types/headerTypes";
import { type Maybe, type ReferenceDataFragment } from "@/gql/graphql";
import { extractLabel } from "@/labels/client";
import dynamic from "next/dynamic";
const Searchbox = dynamic(() => import("../search/searchbox"), { ssr: false });
import { searchEngine } from "@/engine";
const MobileMenu = dynamic(() => import("./partials/_mobile-menu"), {
ssr: false,
});
export const HeaderContext = createContext<HeaderContextType>({
menuItems: [],
utilityItems: [],
currentMenu: "",
setCurrentMenu: (name: string) => {},
mobileMenuOpen: false,
});
type HeaderProps = {
logoItem?: Maybe<ReferenceDataFragment>;
darkLogoItem?: Maybe<ReferenceDataFragment>;
menuItems: MenuItems;
utilityItems: UtilityItems;
labels?: Record<string, string>;
};
/**
* Renders the Header component with a specific locale.
*
* @return the rendered Header component
*/
export default function Header({
menuItems,
utilityItems,
logoItem,
darkLogoItem,
labels = {},
}: HeaderProps) {
const logoRef = useRef<HTMLDivElement>(null);
const secondaryMenuRef = useRef<HTMLUListElement>(null);
const [currentMenu, setCurrentMenu] = useState("");
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const mouseLeaveTimer = useRef<undefined | NodeJS.Timeout>();
const headerContext = {
menuItems,
utilityItems,
currentMenu,
setCurrentMenu,
mobileMenuOpen,
};
// This will close the dropdown after 500ms of the mouse not being over any header element.
function handleMouseLeave() {
mouseLeaveTimer.current = setTimeout(() => {
setCurrentMenu("");
}, 800);
}
// Clears timeout if we detect that the mouse is still over the header
function handleMouseEnter() {
clearTimeout(mouseLeaveTimer.current);
}
// Checks if the focus leaves the header and closes the dropdown menu if it does.
function handleFocusLeave(e: React.FocusEvent<HTMLElement>) {
if (
(secondaryMenuRef.current &&
secondaryMenuRef.current.contains(e.relatedTarget)) ||
(logoRef.current && logoRef.current.contains(e.relatedTarget))
) {
setCurrentMenu("");
}
}
return (
<HeaderContext.Provider value={headerContext}>
<header
className="outer-padding bg-ghost-white text-vulcan dark:bg-vulcan dark:text-ghost-white"
onMouseLeave={handleMouseLeave}
onMouseEnter={handleMouseEnter}
onBlur={handleFocusLeave}
>
<div className="py-8 container mx-auto flex items-center w-full justify-between lg:justify-normal">
<Logo ref={logoRef} logoItem={logoItem} darkLogoItem={darkLogoItem} />
<div className="lg:hidden">
<button
className="btn btn--secondary ml-[10px]"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<div className="btn__content">
{mobileMenuOpen
? extractLabel(labels, "close", { fallback: "CLOSE" })
: extractLabel(labels, "menu", { fallback: "MENU" })}
</div>
</button>
{mobileMenuOpen && <MobileMenu />}
</div>
<div className="hidden justify-between grow lg:flex">
<MainMenu />
<div className="searchbox">
<Searchbox engine={searchEngine} />
</div>
<SecondaryMenu ref={secondaryMenuRef} />
</div>
</div>
</header>
</HeaderContext.Provider>
);
}