1+ import { useEffect , useState } from "react" ;
12import { Box , Container , Flex , NavLink } from "theme-ui" ;
23
34import { genericInformation } from "~/data/generic" ;
@@ -6,6 +7,17 @@ import { Logo } from "./logo";
67
78type LinkComponent = React . FC < { href : string ; children : React . ReactNode } > ;
89
10+ const menuItems = [
11+ { href : "#about" , label : "About 🍕" } ,
12+ { href : "#cfp" , label : "CFP 🙋🏻♀️" } ,
13+ { href : "/compact-agenda/" , label : "Compact agenda 🔗" } ,
14+ { href : "#schedule" , label : "Program 📅" } ,
15+ { href : "#organizers" , label : "Organizers 👩🏻" } ,
16+ { href : "#venue" , label : "Venue 🏰" } ,
17+ { href : "#sponsors" , label : "Sponsors 💛" } ,
18+ { href : "#coc" , label : "CoC 💂" } ,
19+ ] ;
20+
921const MenuLink : LinkComponent = ( { href, children } ) => {
1022 return (
1123 < NavLink
@@ -61,6 +73,37 @@ const MenuButton: LinkComponent = ({ children, href }) => (
6173 </ Box >
6274) ;
6375
76+ type MobileMenuLinkProps = {
77+ href : string ;
78+ children : React . ReactNode ;
79+ onClick : ( ) => void ;
80+ } ;
81+
82+ const MobileMenuLink : React . FC < MobileMenuLinkProps > = ( {
83+ href,
84+ children,
85+ onClick,
86+ } ) => (
87+ < NavLink
88+ href = { href }
89+ onClick = { onClick }
90+ sx = { {
91+ color : "white" ,
92+ fontSize : "2.8rem" ,
93+ lineHeight : 1.15 ,
94+ py : ".7rem" ,
95+ textShadow : "0 .2rem 1rem rgba(80, 0, 0, 0.22)" ,
96+ "&:hover, &:focus" : {
97+ color : "white" ,
98+ textDecoration : "underline" ,
99+ textUnderlineOffset : ".5rem" ,
100+ } ,
101+ } }
102+ >
103+ { children }
104+ </ NavLink >
105+ ) ;
106+
64107export const ContentButton : LinkComponent = ( { children, href } ) => (
65108 < Box
66109 sx = { {
@@ -94,54 +137,220 @@ export const ContentButton: LinkComponent = ({ children, href }) => (
94137 </ Box >
95138) ;
96139
97- export const Header = ( ) => (
98- < Box
99- sx = { {
100- position : "absolute" ,
101- top : 0 ,
102- left : 0 ,
103- zIndex : 10 ,
140+ export const Header = ( ) => {
141+ const [ isMenuOpen , setIsMenuOpen ] = useState ( false ) ;
104142
105- width : "100%" ,
106- } }
107- >
108- < Container
109- sx = { {
110- width : "100%" ,
143+ useEffect ( ( ) => {
144+ const mediaQuery = window . matchMedia ( "(min-width: 64em)" ) ;
145+ const closeMenuOnDesktop = ( ) => {
146+ if ( mediaQuery . matches ) {
147+ setIsMenuOpen ( false ) ;
148+ }
149+ } ;
111150
112- display : "flex" ,
113- alignItems : "center" ,
114- justifyContent : "space-between" ,
115- flexDirection : [ "column" , null , "row" ] ,
151+ closeMenuOnDesktop ( ) ;
152+ mediaQuery . addEventListener ( "change" , closeMenuOnDesktop ) ;
116153
117- color : "white" ,
118- p : "secondary" ,
154+ return ( ) => {
155+ mediaQuery . removeEventListener ( "change" , closeMenuOnDesktop ) ;
156+ } ;
157+ } , [ ] ) ;
158+
159+ useEffect ( ( ) => {
160+ if ( ! isMenuOpen ) {
161+ return ;
162+ }
163+
164+ const handleKeyDown = ( event : KeyboardEvent ) => {
165+ if ( event . key === "Escape" ) {
166+ setIsMenuOpen ( false ) ;
167+ }
168+ } ;
169+
170+ document . body . style . overflow = "hidden" ;
171+ document . addEventListener ( "keydown" , handleKeyDown ) ;
172+
173+ return ( ) => {
174+ document . body . style . overflow = "" ;
175+ document . removeEventListener ( "keydown" , handleKeyDown ) ;
176+ } ;
177+ } , [ isMenuOpen ] ) ;
178+
179+ const closeMenu = ( ) => setIsMenuOpen ( false ) ;
180+
181+ return (
182+ < Box
183+ sx = { {
184+ position : "absolute" ,
185+ top : 0 ,
186+ left : 0 ,
187+ zIndex : isMenuOpen ? 30 : 10 ,
188+
189+ width : "100%" ,
119190 } }
120191 >
121- < Logo />
122- < Flex
192+ < Container
123193 sx = { {
194+ width : "100%" ,
195+
196+ display : "flex" ,
124197 alignItems : "center" ,
125- flexDirection : [ "column" , "row" ] ,
126- zIndex : 5 ,
198+ justifyContent : "space-between" ,
199+
200+ color : "white" ,
201+ p : "secondary" ,
127202 } }
203+ >
204+ < Logo />
205+ < button
206+ type = "button"
207+ aria-controls = "mobile-menu"
208+ aria-expanded = { isMenuOpen }
209+ aria-label = { isMenuOpen ? "Close menu" : "Open menu" }
210+ onClick = { ( ) => setIsMenuOpen ( ( isOpen ) => ! isOpen ) }
211+ sx = { {
212+ display : [ "inline-flex" , null , "none" ] ,
213+ position : "relative" ,
214+ zIndex : 40 ,
215+ width : "5.2rem" ,
216+ height : "5.2rem" ,
217+ alignItems : "center" ,
218+ justifyContent : "center" ,
219+ border : "1px solid rgba(255, 255, 255, 0.65)" ,
220+ borderRadius : "50%" ,
221+ background : "rgba(237, 67, 55, 0.2)" ,
222+ color : "white" ,
223+ cursor : "pointer" ,
224+ p : 0 ,
225+ backdropFilter : "blur(.5rem)" ,
226+ } }
227+ >
228+ < Box
229+ as = "span"
230+ sx = { {
231+ position : "relative" ,
232+ width : "2.4rem" ,
233+ height : ".2rem" ,
234+ borderRadius : ".2rem" ,
235+ background : isMenuOpen ? "transparent" : "white" ,
236+ transition : "background .2s ease" ,
237+ "&:before, &:after" : {
238+ content : '""' ,
239+ position : "absolute" ,
240+ left : 0 ,
241+ width : "2.4rem" ,
242+ height : ".2rem" ,
243+ borderRadius : ".2rem" ,
244+ background : "white" ,
245+ transition : "transform .2s ease, top .2s ease" ,
246+ } ,
247+ "&:before" : {
248+ top : isMenuOpen ? 0 : "-.8rem" ,
249+ transform : isMenuOpen ? "rotate(45deg)" : "none" ,
250+ } ,
251+ "&:after" : {
252+ top : isMenuOpen ? 0 : ".8rem" ,
253+ transform : isMenuOpen ? "rotate(-45deg)" : "none" ,
254+ } ,
255+ } }
256+ />
257+ </ button >
258+ < Flex
259+ sx = { {
260+ display : [ "none" , null , "flex" ] ,
261+ alignItems : "center" ,
262+ flexDirection : "row" ,
263+ zIndex : 5 ,
264+ } }
265+ as = "nav"
266+ >
267+ { menuItems . map ( ( { href, label } ) => (
268+ < MenuLink key = { href } href = { href } >
269+ { label }
270+ </ MenuLink >
271+ ) ) }
272+ < MenuButton href = { genericInformation . ticketsUrl } >
273+ Buy tickets 🎫
274+ </ MenuButton >
275+ </ Flex >
276+ </ Container >
277+ < Flex
278+ id = "mobile-menu"
128279 as = "nav"
280+ aria-hidden = { ! isMenuOpen }
281+ sx = { {
282+ display : [ "flex" , null , "none" ] ,
283+ position : "fixed" ,
284+ inset : 0 ,
285+ zIndex : 30 ,
286+ flexDirection : "column" ,
287+ alignItems : "center" ,
288+ justifyContent : "center" ,
289+ minHeight : "100vh" ,
290+ px : "secondary" ,
291+ py : "primary" ,
292+ background : "url(city.png) center / cover" ,
293+ color : "white" ,
294+ opacity : isMenuOpen ? 1 : 0 ,
295+ pointerEvents : isMenuOpen ? "auto" : "none" ,
296+ transform : isMenuOpen ? "translateY(0)" : "translateY(-1rem)" ,
297+ transition : "opacity .22s ease, transform .22s ease" ,
298+ "&:before" : {
299+ content : '""' ,
300+ position : "absolute" ,
301+ inset : 0 ,
302+ backgroundColor : "primary" ,
303+ opacity : 0.78 ,
304+ } ,
305+ } }
129306 >
130- < MenuLink href = "#about" > About 🍕</ MenuLink >
131- < MenuLink href = "#cfp" > CFP 🙋🏻♀️</ MenuLink >
132- < MenuLink href = "/compact-agenda/" > Compact agenda 🔗</ MenuLink >
133- < MenuLink href = "#schedule" > Program 📅</ MenuLink >
134- < MenuLink href = "#organizers" > Organizers 👩🏻</ MenuLink >
135- < MenuLink href = "#venue" > Venue 🏰</ MenuLink >
136- < MenuLink href = "#sponsors" > Sponsors 💛</ MenuLink >
137- < MenuLink href = "#coc" > CoC 💂</ MenuLink >
138- < MenuButton href = { genericInformation . ticketsUrl } >
139- Buy tickets 🎫
140- </ MenuButton >
307+ < Box sx = { { position : "relative" , zIndex : 1 , mb : "secondary" } } >
308+ < Logo />
309+ </ Box >
310+ < Flex
311+ sx = { {
312+ position : "relative" ,
313+ zIndex : 1 ,
314+ alignItems : "center" ,
315+ flexDirection : "column" ,
316+ gap : ".4rem" ,
317+ } }
318+ >
319+ { menuItems . map ( ( { href, label } ) => (
320+ < MobileMenuLink key = { href } href = { href } onClick = { closeMenu } >
321+ { label }
322+ </ MobileMenuLink >
323+ ) ) }
324+ < Box sx = { { mt : "secondary" , perspective : "24rem" } } >
325+ < NavLink
326+ href = { genericInformation . ticketsUrl }
327+ target = "_blank"
328+ rel = "noopener noreferrer"
329+ onClick = { closeMenu }
330+ sx = { {
331+ display : "inline-flex" ,
332+ px : "2.8rem" ,
333+ py : "1.3rem" ,
334+ border : "1px solid white" ,
335+ borderRadius : "10rem" ,
336+ background : "white" ,
337+ boxShadow : "0.6rem 0.4rem 2.4rem -0.8rem rgba(25, 0, 0, 0.5)" ,
338+ color : "primary" ,
339+ fontSize : "2rem" ,
340+ "&:hover, &:focus" : {
341+ color : "primary" ,
342+ transform : "translateY(-0.2rem)" ,
343+ } ,
344+ } }
345+ >
346+ Buy tickets 🎫
347+ </ NavLink >
348+ </ Box >
349+ </ Flex >
141350 </ Flex >
142- </ Container >
143- </ Box >
144- ) ;
351+ </ Box >
352+ ) ;
353+ } ;
145354
146355export const FloatingTicketsButton = ( ) => (
147356 < NavLink
0 commit comments