feat(mobile): Sidebar drawer <1024px + Modal/Sheet fullscreen <880px

Per design_handoff_ordinis_mdm/README.md responsive table (line 464-465):
- ≤1024px: Sidebar becomes overlay drawer (hamburger в TopBar)
- ≤880px:  Drawers и modals fullscreen (100vw × 100dvh, no border-radius)

Implementation:

src/components/layout/Sidebar.tsx
  - Extracted SidebarContent (logo + nav + user block) для DRY
  - Desktop <Sidebar /> wraps SidebarContent в hidden lg:flex aside
  - New <MobileSidebar open onClose /> — Sheet с тем же содержимым,
    260px wide, slides from left
  - SidebarLink принимает onClick → drawer auto-close при navigate

src/components/layout/TopBar.tsx
  - Optional onMenuClick prop — рендерит hamburger button (Menu icon
    из lucide-react) с lg:hidden
  - VersionBadge скрыт <md (768px) — diagnostic info, не critical UX

src/routes/__root.tsx
  - useState mobileNavOpen, передаём в MobileSidebar + TopBar

src/ui/components/dialog.tsx
  - SIZE_CLASSES получили  prefix (arbitrary Tailwind v4
    breakpoint per handoff exact 880px). На mobile <880px modal становится
    fullscreen (inset-0 w-full h-full no rounded), ≥880px — centered с
    translate + max-w из SIZE_CLASSES.

src/ui/components/sheet.tsx
  - size variants: sm:max-w-* → min-[880px]:max-w-*. На mobile <880px
    sheet берёт w-full (fullscreen drawer per handoff).

src/components/record/RecordDrawer.tsx
  - max-width переключатель 520/880 теперь применяется только ≥880px.
    На mobile drawer всегда fullscreen.

src/ui/components/language-switch.tsx
  - Display fallback: opt.short → opt.label → uppercase id.
    Previously показывал id ('ru-RU', 'en-US') когда short не был задан —
    теперь использует label ('RU', 'EN') как fallback.

Browser-verified в preview viewport 375×812:
  - Hamburger ☰ visible, click открывает Sheet с nav items
  - Backdrop fade + slide-from-left animation
  - TopBar fits в 375px (RU/EN compact, Sign in visible)
  - LoadingBlock + Alert text-wrap correctly
This commit is contained in:
Zimin A.N.
2026-05-11 15:39:23 +03:00
parent b6af155da2
commit 7a3b31b957
7 changed files with 176 additions and 35 deletions
+18 -10
View File
@@ -1,33 +1,41 @@
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
import { Sidebar } from '@/components/layout/Sidebar'
import { useState } from 'react'
import { Sidebar, MobileSidebar } from '@/components/layout/Sidebar'
import { TopBar } from '@/components/layout/TopBar'
import { UpdateBanner } from '@/components/version/UpdateBanner'
/**
* Root layout (Stage 3.1 design handoff):
* Root layout (Stage 3.1 + 3.x mobile responsive):
*
* ┌─ Sidebar 220px (lg:flex) ──┬─ main column (flex-1) ────────────┐
* │ logo │ UpdateBanner (when applicable) │
* │ nav rail с counters │ TopBar h=56 sticky
* │ user profile │ Outlet (route content)
* └────────────────────────────┴───────────────────────────────────┘
* ≥1024px (lg+):
* ┌─ Sidebar 220px ──┬─ main column (flex-1) ────┐
* │ logo │ TopBar (sticky)
* nav rail │ Outlet (route content) │
* │ user profile │ │
* └──────────────────┴───────────────────────────┘
*
* Responsive: на <1024px sidebar скрывается (hidden lg:flex). Main grid
* растягивается на весь viewport. Mobile hamburger drawer — TODO Stage 3.x.
* <1024px:
* ┌─ main column (flex-1) ────────────────────────┐
* │ TopBar [☰] (hamburger toggles MobileSidebar) │
* │ Outlet │
* └───────────────────────────────────────────────┘
* MobileSidebar — overlay Sheet (slide-in left, 260px).
*/
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
component: RootLayout,
})
function RootLayout() {
const [mobileNavOpen, setMobileNavOpen] = useState(false)
return (
<div className="h-screen flex flex-col bg-bg text-ink overflow-hidden">
<UpdateBanner />
<div className="flex-1 flex min-h-0">
<Sidebar />
<MobileSidebar open={mobileNavOpen} onClose={() => setMobileNavOpen(false)} />
<div className="flex-1 flex flex-col min-w-0">
<TopBar />
<TopBar onMenuClick={() => setMobileNavOpen(true)} />
<main className="flex-1 overflow-y-auto px-4 sm:px-6 py-4 sm:py-6">
<div className="max-w-7xl mx-auto w-full">
<Outlet />