From 460b57e792c8fe063167472cb2509e154bfa5514 Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Fri, 26 Jun 2026 16:39:20 +0300 Subject: [PATCH] =?UTF-8?q?UI:=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20?= =?UTF-8?q?=C2=AB=D0=9C=D0=B5=D0=BD=D1=8E=C2=BB=20(=D0=B3=D0=B0=D0=BC?= =?UTF-8?q?=D0=B1=D1=83=D1=80=D0=B3=D0=B5=D1=80)=20=D0=B2=20=D0=BD=D0=B8?= =?UTF-8?q?=D0=B6=D0=BD=D0=B5=D0=B9=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8?= =?UTF-8?q?=20=E2=86=92=20=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8E=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B0?= =?UTF-8?q?=20(=D0=BF=D0=BB=D0=B0=D1=88=D0=BA=D0=B0=20=D0=B0=D0=BA=D0=BA?= =?UTF-8?q?=D0=B0=D1=83=D0=BD=D1=82=D0=B0=20+=20=D0=92=D1=8B=D0=B9=D1=82?= =?UTF-8?q?=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- frontend/src/components/AppShell.tsx | 6 ++- frontend/src/components/BottomBar.tsx | 10 ++--- frontend/src/components/SideMenu.tsx | 51 ++++++++++++++++++++++ frontend/src/styles/layout.css | 61 +++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/SideMenu.tsx diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index 196fe46..1cb573c 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; import { useMe } from "../hooks/auth"; @@ -5,6 +6,7 @@ import { useServerEvents } from "../hooks/useServerEvents"; import { BottomBar } from "./BottomBar"; import { NotificationBell } from "./NotificationBell"; import { NotificationToaster } from "./NotificationToaster"; +import { SideMenu } from "./SideMenu"; const TITLES: Record = { "/": "Forbidden Stars", @@ -19,6 +21,7 @@ export function AppShell() { const location = useLocation(); const navigate = useNavigate(); const { data: me } = useMe(); + const [menuOpen, setMenuOpen] = useState(false); useServerEvents(!!me); // живые обновления (SSE) только при наличии сессии const title = TITLES[location.pathname] ?? @@ -49,7 +52,8 @@ export function AppShell() { - + setMenuOpen(false)} /> + setMenuOpen(true)} /> ); } diff --git a/frontend/src/components/BottomBar.tsx b/frontend/src/components/BottomBar.tsx index 25d6a4c..f656d54 100644 --- a/frontend/src/components/BottomBar.tsx +++ b/frontend/src/components/BottomBar.tsx @@ -1,10 +1,10 @@ -import { Plus, Settings, Users } from "lucide-react"; +import { Menu, Plus, Users } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useToast } from "../context/ToastContext"; import { useMe } from "../hooks/auth"; -export function BottomBar() { +export function BottomBar({ onMenu }: { onMenu: () => void }) { const { data: me } = useMe(); const navigate = useNavigate(); const toast = useToast(); @@ -29,9 +29,9 @@ export function BottomBar() {
- + )} + +
+ +
+ + + ); +} diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index ea09fec..b942944 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -194,3 +194,64 @@ font-size: 13px; } .place-badge.first { background: var(--gold); color: #1a1a1a; border-color: var(--gold); } + +/* ─── Боковое меню (выезжает справа) ─────────────────────────────────────────── */ +.drawer-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + z-index: 60; +} +.drawer-overlay.open { opacity: 1; pointer-events: auto; } + +.side-menu { + position: fixed; + top: 0; + right: 0; + height: 100%; + width: min(300px, 84vw); + background: var(--surface); + border-left: 1px solid var(--border); + box-shadow: -10px 0 30px rgba(0, 0, 0, 0.5); + transform: translateX(100%); + transition: transform 0.22s ease; + z-index: 61; + display: flex; + flex-direction: column; + padding-top: var(--safe-top); +} +.side-menu.open { transform: translateX(0); } + +.side-account { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + text-align: left; + padding: 16px; + background: none; + border: none; + border-bottom: 1px solid var(--border); + color: var(--text); +} +.side-account-info { display: flex; flex-direction: column; min-width: 0; } +.side-account-info b { font-size: 16px; overflow: hidden; text-overflow: ellipsis; } + +.side-menu-items { display: flex; flex-direction: column; padding: 8px; } +.side-menu-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + text-align: left; + padding: 12px 14px; + background: none; + border: none; + border-radius: var(--radius-sm); + color: var(--text); + font-weight: 600; +} +.side-menu-item.danger { color: var(--danger); }