import { type AnyRoute, useMatchRoute, useNavigate } from "@tanstack/react-router"; import { useTranslation } from "react-i18next"; import { AnimatePresence } from "motion/react"; import GlassSurface from "@components/surface/GlassSurface"; import classes from "./Navigation.module.css"; import ShopRoute from "@/routes/shop"; import ApiaryRoute from "@/routes/apiary"; import GameRoute from "@/routes/game"; import CashdeskRoute from "@/routes/cashdesk"; import RouletteRoute from "@/routes/roulette"; import TasksRoute from "@/routes/tasks"; import EarningsRoute from "@/routes/earnings"; import { useCallback, useEffect, useRef, useState } from "react"; import shopIcon from "./assets/shop.svg"; import apiaryIcon from "./assets/apiary.svg"; import gameIcon from "./assets/game.svg"; import cashdeskIcon from "./assets/cashdesk.svg"; import menuIcon from "./assets/menu.svg"; import rouletteIcon from "./assets/roulette.svg"; import tasksIcon from "./assets/tasks.svg"; import earningsIcon from "./assets/earnings.svg"; import tg from "@/tg"; const ANIMATION_DURATION = 0.2; const SPRING_ANIMATION = { type: "spring", stiffness: 400, damping: 20, duration: ANIMATION_DURATION, } as const; const BAR_HEIGHT = 64; const ACTIVE_BAR_HEIGHT = 74; const OFFSCREEN_BAR_OFFSET = 20; const NAV_ITEMS = [ { key: "nav.shop", route: ShopRoute, icon: shopIcon }, { key: "nav.apiary", route: ApiaryRoute, icon: apiaryIcon }, { key: "nav.game", route: GameRoute, icon: gameIcon }, { key: "nav.cashdesk", route: CashdeskRoute, icon: cashdeskIcon }, { key: "nav.menu", isMenu: true, icon: menuIcon }, ]; const HORIZONTAL_ENTRANCE_DELAYS = [0.2, 0.1, 0, 0.1, 0.2]; const MENU_ITEMS = [ { key: "nav.roulette", route: RouletteRoute, icon: rouletteIcon, delay: 0.1 }, { key: "nav.tasks", route: TasksRoute, icon: tasksIcon, delay: 0.05 }, { key: "nav.earnings", route: EarningsRoute, icon: earningsIcon, delay: 0 }, ]; type BarProps = { labelKey: string; icon: string; active: boolean; entranceDelay: number; onClick: () => void; }; function NavBar({ labelKey, icon, active, entranceDelay, onClick }: BarProps) { const { t } = useTranslation(); const isInitial = useRef(true); useEffect(() => { isInitial.current = false; }, []); const height = active ? ACTIVE_BAR_HEIGHT : BAR_HEIGHT; return (
{t(labelKey)}
); } type MenuBarProps = { labelKey: string; icon: string; active: boolean; delay: number; onClick: () => void; }; const MENU_BAR_WIDTH = 94; const ACTIVE_MENU_BAR_WIDTH = 104; const OFFSCREEN_MENU_BAR_OFFSET = 20; function MenuBar({ labelKey, icon, delay, active, onClick }: MenuBarProps) { const { t } = useTranslation(); const [isReady, setIsReady] = useState(false); const width = active ? ACTIVE_MENU_BAR_WIDTH : MENU_BAR_WIDTH; return ( variant === "visible" && setIsReady(true)} initial="hidden" animate={isReady ? "ready" : "visible"} exit="exit" whileTap={{ scale: 0.95 }} onClick={onClick} >
{t(labelKey)}
); } export default function Navigation() { const matchRoute = useMatchRoute(); const navigate = useNavigate(); const [menuOpen, setMenuOpen] = useState(0); const navRef = useRef(null); const handleOutsideClick = useCallback((e: MouseEvent) => { if (navRef.current && !navRef.current.contains(e.target as Node)) { setMenuOpen(0); } }, []); useEffect(() => { if (menuOpen) { document.addEventListener("click", handleOutsideClick); return () => document.removeEventListener("click", handleOutsideClick); } }, [menuOpen, handleOutsideClick]); const navigateRoute = async (route: AnyRoute, wait = false) => { tg.hapticFeedback.click(); const redirection = navigate({ to: route.to }); if (wait) { await new Promise((resolve) => setTimeout(resolve, 1000 * ANIMATION_DURATION - 100)); await redirection; } setMenuOpen(0); }; return (
{menuOpen && MENU_ITEMS.map((item) => ( navigateRoute(item.route, true)} /> ))}
); }