Frontend: hamburger menu v hlavicke na mobile
Hlavicka GameListu sa na uzkych displejoch nezmestila do jedneho riadku. Pod sm: breakpointom sa polozky zbalia do dropdownu. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface Props {
|
||||
username?: string;
|
||||
onHistory: () => void;
|
||||
onDonate: () => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
/** Header navigation: a row of links from `sm:` up, a hamburger dropdown below it. */
|
||||
export default function HeaderMenu({ username, onHistory, onDonate, onLogout }: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
if (!ref.current?.contains(e.target as Node)) setOpen(false);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setOpen(false);
|
||||
};
|
||||
document.addEventListener('pointerdown', onPointerDown);
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('pointerdown', onPointerDown);
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const pick = (action: () => void) => () => {
|
||||
setOpen(false);
|
||||
action();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden sm:flex items-center gap-3 text-sm">
|
||||
<span className="text-green-dim">{username}</span>
|
||||
<button onClick={onHistory} className="text-gold hover:text-gold-bright">
|
||||
História
|
||||
</button>
|
||||
<button onClick={onDonate} className="text-gold hover:text-gold-bright">
|
||||
Na kávu
|
||||
</button>
|
||||
<button onClick={onLogout} className="text-green-dim hover:text-gold">
|
||||
Odhlásiť
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref={ref} className="relative sm:hidden">
|
||||
<button
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
aria-label="Menu"
|
||||
aria-expanded={open}
|
||||
aria-haspopup="menu"
|
||||
className="flex flex-col justify-center gap-[5px] w-9 h-9 items-center rounded-lg border border-gold/20 text-gold hover:border-gold/50 transition-colors"
|
||||
>
|
||||
<span className="block w-4 h-px bg-current" />
|
||||
<span className="block w-4 h-px bg-current" />
|
||||
<span className="block w-4 h-px bg-current" />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div
|
||||
role="menu"
|
||||
className="absolute right-0 top-11 z-40 w-44 bg-header border border-[#142018] rounded-xl py-1 shadow-[0_20px_60px_rgba(0,0,0,.6)]"
|
||||
>
|
||||
{username && (
|
||||
<p className="px-4 py-2 text-xs text-green-dim border-b border-[#142018] truncate">
|
||||
{username}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
role="menuitem"
|
||||
onClick={pick(onHistory)}
|
||||
className="block w-full text-left px-4 py-2.5 text-sm text-gold hover:bg-gold/10"
|
||||
>
|
||||
História
|
||||
</button>
|
||||
<button
|
||||
role="menuitem"
|
||||
onClick={pick(onDonate)}
|
||||
className="block w-full text-left px-4 py-2.5 text-sm text-gold hover:bg-gold/10"
|
||||
>
|
||||
Na kávu
|
||||
</button>
|
||||
<button
|
||||
role="menuitem"
|
||||
onClick={pick(onLogout)}
|
||||
className="block w-full text-left px-4 py-2.5 text-sm text-green-dim hover:bg-gold/10 hover:text-gold"
|
||||
>
|
||||
Odhlásiť
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useGameStore } from '../store/gameStore';
|
||||
import { emit, socket, setAuthToken } from '../lib/socket';
|
||||
import { trackEvent } from '../lib/track';
|
||||
import HeaderMenu from '../components/HeaderMenu';
|
||||
import NameModal from '../components/NameModal';
|
||||
import RulesModal from '../components/RulesModal';
|
||||
|
||||
@@ -33,18 +34,12 @@ export default function GameList() {
|
||||
<div className="max-w-md mx-auto p-4 pt-8 min-h-screen">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="font-serif text-2xl text-gold tracking-wide">Bridžik</h1>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<span className="text-green-dim">{account?.username}</span>
|
||||
<button onClick={() => navigate('/history')} className="text-gold hover:text-gold-bright">
|
||||
História
|
||||
</button>
|
||||
<button onClick={() => navigate('/donate')} className="text-gold hover:text-gold-bright">
|
||||
Na kávu
|
||||
</button>
|
||||
<button onClick={handleLogout} className="text-green-dim hover:text-gold">
|
||||
Odhlásiť
|
||||
</button>
|
||||
</div>
|
||||
<HeaderMenu
|
||||
username={account?.username}
|
||||
onHistory={() => navigate('/history')}
|
||||
onDonate={() => navigate('/donate')}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 mb-6">
|
||||
|
||||
Reference in New Issue
Block a user