From d620f06ab858027b11015981c8391084cb375aae Mon Sep 17 00:00:00 2001 From: Hewston Fox Date: Thu, 19 Mar 2026 01:46:34 +0200 Subject: [PATCH] feat: navabar --- public/locales/en.d.ts | 3 + public/locales/en.json | 5 +- public/locales/ru.d.ts | 3 + public/locales/ru.json | 5 +- src/routes/-/Header/assets/menu.svg | 162 +++ src/routes/-/Header/assets/user-bar.svg | 285 +++++ src/routes/-/Navigation/Navigation.module.css | 73 +- src/routes/-/Navigation/Navigation.tsx | 231 ++-- src/routes/-/Navigation/assets/apiary.svg | 1039 +++++++++++++++++ src/routes/-/Navigation/assets/cashdesk.svg | 139 +++ src/routes/-/Navigation/assets/earnings.svg | 140 +++ src/routes/-/Navigation/assets/game.svg | 280 +++++ src/routes/-/Navigation/assets/menu.svg | 81 ++ src/routes/-/Navigation/assets/roulette.svg | 89 ++ src/routes/-/Navigation/assets/shop.svg | 353 ++++++ src/routes/-/Navigation/assets/tasks.svg | 26 + src/routes/apiary/index.tsx | 1 + src/routes/cashdesk/index.tsx | 1 + src/routes/earnings/index.tsx | 1 + src/routes/game/index.tsx | 1 + src/routes/roulette/index.tsx | 1 + src/routes/shop/index.tsx | 1 + src/routes/tasks/index.tsx | 1 + 23 files changed, 2829 insertions(+), 92 deletions(-) create mode 100644 src/routes/-/Header/assets/menu.svg create mode 100644 src/routes/-/Header/assets/user-bar.svg create mode 100644 src/routes/-/Navigation/assets/apiary.svg create mode 100644 src/routes/-/Navigation/assets/cashdesk.svg create mode 100644 src/routes/-/Navigation/assets/earnings.svg create mode 100644 src/routes/-/Navigation/assets/game.svg create mode 100644 src/routes/-/Navigation/assets/menu.svg create mode 100644 src/routes/-/Navigation/assets/roulette.svg create mode 100644 src/routes/-/Navigation/assets/shop.svg create mode 100644 src/routes/-/Navigation/assets/tasks.svg diff --git a/public/locales/en.d.ts b/public/locales/en.d.ts index 8d4f107..f736aed 100644 --- a/public/locales/en.d.ts +++ b/public/locales/en.d.ts @@ -6,4 +6,7 @@ export declare const resources: { "nav.game": "Game"; "nav.cashdesk": "Cashdesk"; "nav.menu": "Menu"; + "nav.roulette": "Roulette"; + "nav.tasks": "Tasks"; + "nav.earnings": "Earnings"; }; diff --git a/public/locales/en.json b/public/locales/en.json index f5f8395..e16142f 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -5,5 +5,8 @@ "nav.apiary": "Apiary", "nav.game": "Game", "nav.cashdesk": "Cashdesk", - "nav.menu": "Menu" + "nav.menu": "Menu", + "nav.roulette": "Roulette", + "nav.tasks": "Tasks", + "nav.earnings": "Earnings" } diff --git a/public/locales/ru.d.ts b/public/locales/ru.d.ts index af8df85..f24b1fa 100644 --- a/public/locales/ru.d.ts +++ b/public/locales/ru.d.ts @@ -6,4 +6,7 @@ export declare const resources: { "nav.game": "Игра"; "nav.cashdesk": "Касса"; "nav.menu": "Меню"; + "nav.roulette": "Рулетка"; + "nav.tasks": "Задания"; + "nav.earnings": "Заработок"; }; diff --git a/public/locales/ru.json b/public/locales/ru.json index e655116..11d0099 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -5,5 +5,8 @@ "nav.apiary": "Пасека", "nav.game": "Игра", "nav.cashdesk": "Касса", - "nav.menu": "Меню" + "nav.menu": "Меню", + "nav.roulette": "Рулетка", + "nav.tasks": "Задания", + "nav.earnings": "Заработок" } diff --git a/src/routes/-/Header/assets/menu.svg b/src/routes/-/Header/assets/menu.svg new file mode 100644 index 0000000..43cf478 --- /dev/null +++ b/src/routes/-/Header/assets/menu.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Header/assets/user-bar.svg b/src/routes/-/Header/assets/user-bar.svg new file mode 100644 index 0000000..c6bb71c --- /dev/null +++ b/src/routes/-/Header/assets/user-bar.svg @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/Navigation.module.css b/src/routes/-/Navigation/Navigation.module.css index 1ffb666..f383d43 100644 --- a/src/routes/-/Navigation/Navigation.module.css +++ b/src/routes/-/Navigation/Navigation.module.css @@ -11,22 +11,14 @@ bottom: 0; left: 0; z-index: 10; + max-width: 320px; + margin: auto; display: flex; align-items: flex-end; justify-content: space-evenly; overflow: hidden; } - .barContainer { - display: flex; - align-items: flex-end; - } - - .barWrapper { - display: flex; - align-items: flex-end; - } - .bar { width: 54px; border-radius: 27px 27px 0 0; @@ -37,12 +29,6 @@ padding-bottom: var(--navigation-padding); } - .barActive { - } - - .barInactive { - } - .safeContent { display: flex; flex-direction: column; @@ -50,23 +36,21 @@ justify-content: center; width: 100%; height: 64px; + margin-top: -10px; } - .iconPlaceholder { - width: 20px; - height: 20px; - background: rgb(255 255 255 / 0.4); - border-radius: 4px; - flex-shrink: 0; - margin-top: -6px; + .icon { + width: 40px; + height: 40px; margin-bottom: -6px; } .label { - font-size: 11px; + font-size: 15px; + font-weight: 700; text-align: center; -webkit-text-stroke: 1px #331b01; - text-shadow: 0px 1px 2px 0px #0000008c; + text-shadow: 0px 2px 2px #0000008c; line-height: 1; } @@ -77,4 +61,43 @@ .labelActive { color: #ffbd42; } + + .menuOverlay { + position: absolute; + right: 0; + bottom: calc(var(--navigation-total) + 16px); + z-index: 9; + padding-bottom: 8px; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 8px; + overflow: hidden; + pointer-events: none; + } + + .menuOverlay > * { + pointer-events: auto; + } + + .menuBar { + height: 54px; + width: 94px; + border-radius: 27px 0 0 27px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding-right: 20px; + } + + .menuBarContent { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 54px; + height: 100%; + margin-left: -10px; + } } diff --git a/src/routes/-/Navigation/Navigation.tsx b/src/routes/-/Navigation/Navigation.tsx index dcd5ccb..6985004 100644 --- a/src/routes/-/Navigation/Navigation.tsx +++ b/src/routes/-/Navigation/Navigation.tsx @@ -1,97 +1,198 @@ -import { Link, useMatchRoute } from "@tanstack/react-router"; -import { motion } from "motion/react"; +import { 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"; -const ENTRANCE_DELAYS = [0.2, 0.1, 0, 0.1, 0.2]; +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"; -const SPRING_ENTRANCE = { type: "spring" as const, stiffness: 400, damping: 20 }; -const SPRING_HEIGHT = { type: "spring" as const, stiffness: 500, damping: 25 }; +const BAR_HEIGHT = 64; +const ACTIVE_BAR_HEIGHT = 74; +const OFFSCREEN_BAR_OFFSET = 20; -type NavItem = - | { key: string; route: string; isMenu?: false } - | { key: string; route?: undefined; isMenu: true }; - -const NAV_ITEMS: NavItem[] = [ - { key: "nav.shop", route: "/shop/" }, - { key: "nav.apiary", route: "/apiary/" }, - { key: "nav.game", route: "/game/" }, - { key: "nav.cashdesk", route: "/cashdesk/" }, - { key: "nav.menu", isMenu: true }, +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]; -function IconPlaceholder() { - return
; -} +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, active, entranceDelay }: BarProps) { +function NavBar({ labelKey, icon, active, entranceDelay, onClick }: BarProps) { + const { t } = useTranslation(); + const isInitial = useRef(true); + + useEffect(() => { + isInitial.current = false; + }, []); + + return ( + +
+ + + {t(labelKey)} + +
+
+ ); +} + +type MenuBarProps = { + labelKey: string; + icon: string; + delay: number; + onClick: () => void; +}; + +const MENU_SPRING = { type: "spring" as const, stiffness: 400, damping: 20 }; + +function MenuBar({ labelKey, icon, delay, onClick }: MenuBarProps) { const { t } = useTranslation(); return ( - - -
- - - {t(labelKey)} - - -
-
-
+
+ + {t(labelKey)} +
+ ); } export default function Navigation() { const matchRoute = useMatchRoute(); + const navigate = useNavigate(); + const [menuOpen, setMenuOpen] = useState(false); + const navRef = useRef(null); + + const handleOutsideClick = useCallback((e: MouseEvent) => { + if (navRef.current && !navRef.current.contains(e.target as Node)) { + setMenuOpen(false); + } + }, []); + + useEffect(() => { + if (menuOpen) { + document.addEventListener("click", handleOutsideClick); + return () => document.removeEventListener("click", handleOutsideClick); + } + }, [menuOpen, handleOutsideClick]); return ( - +
); } diff --git a/src/routes/-/Navigation/assets/apiary.svg b/src/routes/-/Navigation/assets/apiary.svg new file mode 100644 index 0000000..d97ad84 --- /dev/null +++ b/src/routes/-/Navigation/assets/apiary.svg @@ -0,0 +1,1039 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/cashdesk.svg b/src/routes/-/Navigation/assets/cashdesk.svg new file mode 100644 index 0000000..dedd5f4 --- /dev/null +++ b/src/routes/-/Navigation/assets/cashdesk.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/earnings.svg b/src/routes/-/Navigation/assets/earnings.svg new file mode 100644 index 0000000..be8039b --- /dev/null +++ b/src/routes/-/Navigation/assets/earnings.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/game.svg b/src/routes/-/Navigation/assets/game.svg new file mode 100644 index 0000000..60c3cb2 --- /dev/null +++ b/src/routes/-/Navigation/assets/game.svg @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/menu.svg b/src/routes/-/Navigation/assets/menu.svg new file mode 100644 index 0000000..28881f0 --- /dev/null +++ b/src/routes/-/Navigation/assets/menu.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/roulette.svg b/src/routes/-/Navigation/assets/roulette.svg new file mode 100644 index 0000000..ce931eb --- /dev/null +++ b/src/routes/-/Navigation/assets/roulette.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/shop.svg b/src/routes/-/Navigation/assets/shop.svg new file mode 100644 index 0000000..cae032c --- /dev/null +++ b/src/routes/-/Navigation/assets/shop.svg @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/-/Navigation/assets/tasks.svg b/src/routes/-/Navigation/assets/tasks.svg new file mode 100644 index 0000000..0e7a0c9 --- /dev/null +++ b/src/routes/-/Navigation/assets/tasks.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/apiary/index.tsx b/src/routes/apiary/index.tsx index 54605de..db7ba42 100644 --- a/src/routes/apiary/index.tsx +++ b/src/routes/apiary/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/ApiaryRoute"; export const Route = createFileRoute("/apiary/")({ component }); +export default Route; diff --git a/src/routes/cashdesk/index.tsx b/src/routes/cashdesk/index.tsx index 93fb8f4..69d4017 100644 --- a/src/routes/cashdesk/index.tsx +++ b/src/routes/cashdesk/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/CashdeskRoute"; export const Route = createFileRoute("/cashdesk/")({ component }); +export default Route; diff --git a/src/routes/earnings/index.tsx b/src/routes/earnings/index.tsx index 4c77a65..6017b80 100644 --- a/src/routes/earnings/index.tsx +++ b/src/routes/earnings/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/EarningsRoute"; export const Route = createFileRoute("/earnings/")({ component }); +export default Route; diff --git a/src/routes/game/index.tsx b/src/routes/game/index.tsx index b95b38f..1a36e66 100644 --- a/src/routes/game/index.tsx +++ b/src/routes/game/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/GameRoute"; export const Route = createFileRoute("/game/")({ component }); +export default Route; diff --git a/src/routes/roulette/index.tsx b/src/routes/roulette/index.tsx index f416996..8f82395 100644 --- a/src/routes/roulette/index.tsx +++ b/src/routes/roulette/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/RouletteRoute"; export const Route = createFileRoute("/roulette/")({ component }); +export default Route; diff --git a/src/routes/shop/index.tsx b/src/routes/shop/index.tsx index 3a35aee..92d1fb9 100644 --- a/src/routes/shop/index.tsx +++ b/src/routes/shop/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/ShopRoute"; export const Route = createFileRoute("/shop/")({ component }); +export default Route; diff --git a/src/routes/tasks/index.tsx b/src/routes/tasks/index.tsx index c50e0e5..386cf37 100644 --- a/src/routes/tasks/index.tsx +++ b/src/routes/tasks/index.tsx @@ -1,3 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; import component from "./-/TasksRoute"; export const Route = createFileRoute("/tasks/")({ component }); +export default Route;