From 00afe2d6b25000e9e58884a8d2549801a2459dd4 Mon Sep 17 00:00:00 2001 From: shats Date: Mon, 23 Feb 2026 23:56:55 +0700 Subject: [PATCH] feat(App): implement mobile sidebar overlay with backdrop and responsive adjustments; enhance layout for better usability on small screens --- frontend/src/App.css | 72 ++++++++++++++++++- frontend/src/App.jsx | 58 +++++++++++++-- frontend/src/Dashboard.jsx | 30 ++++---- frontend/src/DataManager.jsx | 10 +-- frontend/src/DomainsNewManager.jsx | 8 +-- frontend/src/ServerManager.jsx | 24 +++---- frontend/src/SettingsPage.jsx | 28 ++++---- frontend/src/components/CommunityStats.jsx | 12 ++-- frontend/src/components/ImportModal.jsx | 4 +- frontend/src/components/PageHeader.jsx | 2 +- frontend/src/components/QuickAddBar.jsx | 2 +- frontend/src/components/TopNStats.jsx | 6 +- .../components/server/LinkGeneratorModal.jsx | 6 +- frontend/src/index.css | 55 ++++++++++++-- 14 files changed, 241 insertions(+), 76 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index fecca8c..aed50ce 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -347,6 +347,52 @@ padding-left: 0; } +/* ===== Мобильный сайдбар: оверлей и бэкдроп (Tabler-style) ===== */ +.sidebar-backdrop { + position: fixed; + inset: 0; + z-index: 1035; + background: rgba(0, 0, 0, 0.4); + transition: opacity 0.2s ease; +} +body.sidebar-overlay-open { + overflow: hidden; + touch-action: none; +} + +@media (max-width: 767.98px) { + .page .navbar-vertical .navbar-collapse { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: min(100vw, 280px); + max-width: 280px; + z-index: 1040; + background: #1e293b; + margin: 0; + padding: 1rem 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15); + flex-direction: column; + } + .page .navbar-vertical .navbar-collapse.show { + display: flex !important; + } + .page-wrapper { + margin-left: 0 !important; + } + .navbar-vertical .nav-link, + .navbar-vertical .dropdown-item { + min-height: 44px; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + display: flex; + align-items: center; + } +} + /* Responsive navbar */ @media (max-width: 991.98px) { .navbar-collapse { @@ -461,7 +507,7 @@ /* Responsive sizing for small screens */ @media (max-width: 992px) { - .header-actions { gap: 0.4rem; } + .header-actions { gap: 0.4rem; flex-wrap: wrap; } .header-actions .btn { padding: 0.35rem 0.6rem; font-size: 0.875rem; @@ -474,6 +520,30 @@ .header-actions { width: 100%; } } +/* Мобильные отступы контента */ +@media (max-width: 767.98px) { + .page-body .container-fluid, + .page-body .container-xl { + padding-left: 0.75rem; + padding-right: 0.75rem; + } + .card .card-body { + padding: 0.75rem 1rem; + } + .offcanvas-start { + width: min(320px, 100vw) !important; + max-width: 100vw !important; + } + .modal-dialog { + margin: 0.5rem; + max-width: calc(100% - 1rem); + } + .modal-fullscreen-sm-down .modal-dialog { + max-width: 100%; + margin: 0; + } +} + /* Custom responsive adjustments */ @media (max-width: 992px) { .page-wrapper { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e71cc55..3ac8aa3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, createContext, useContext } from 'react'; +import { useState, useEffect, createContext, useContext, useRef } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { BrowserRouter as Router, @@ -142,16 +142,56 @@ function App() { const LAYOUT_FLUID = 'fluid'; const LAYOUT_HORIZONTAL = 'horizontal'; +const SIDEBAR_COLLAPSE_ID = 'sidebar-menu'; +const MOBILE_BREAKPOINT = 768; + function MainLayout() { const location = useLocation(); const { lang, setLang, t } = useContext(LanguageContext); const { theme, setTheme } = useContext(ThemeContext); const [layout, setLayout] = useState(() => localStorage.getItem('layout') || LAYOUT_FLUID); + const [sidebarBackdrop, setSidebarBackdrop] = useState(false); + const sidebarTogglerRef = useRef(null); useEffect(() => { localStorage.setItem('layout', layout); }, [layout]); + // Мобильный сайдбар: бэкдроп при открытом меню и закрытие при навигации + useEffect(() => { + if (layout !== LAYOUT_FLUID) return; + const collapseEl = document.getElementById(SIDEBAR_COLLAPSE_ID); + const toggler = () => sidebarTogglerRef.current?.click?.(); + const onShow = () => { + if (window.innerWidth < MOBILE_BREAKPOINT) { + setSidebarBackdrop(true); + document.body.classList.add('sidebar-overlay-open'); + } + }; + const onHide = () => { + setSidebarBackdrop(false); + document.body.classList.remove('sidebar-overlay-open'); + }; + if (collapseEl) { + collapseEl.addEventListener('show.bs.collapse', onShow); + collapseEl.addEventListener('hide.bs.collapse', onHide); + return () => { + collapseEl.removeEventListener('show.bs.collapse', onShow); + collapseEl.removeEventListener('hide.bs.collapse', onHide); + }; + } + }, [layout]); + + useEffect(() => { + if (layout !== LAYOUT_FLUID || window.innerWidth >= MOBILE_BREAKPOINT) return; + setSidebarBackdrop(false); + document.body.classList.remove('sidebar-overlay-open'); + const collapseEl = document.getElementById(SIDEBAR_COLLAPSE_ID); + if (collapseEl?.classList.contains('show')) { + sidebarTogglerRef.current?.click?.(); + } + }, [location.pathname, layout]); + // Состояние для управления выпадающими меню const [dropdownStates, setDropdownStates] = useState({ data: false, @@ -445,18 +485,19 @@ function MainLayout() {
-