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:
@@ -5,6 +5,7 @@ import { AuthBadge } from '@/auth/AuthBadge'
|
||||
import { ThemeSwitch } from '@/components/layout/ThemeSwitch'
|
||||
import { VersionBadge } from '@/components/version/VersionBadge'
|
||||
import { SearchInput } from '@/ui/components/search-input'
|
||||
import { Menu } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
/**
|
||||
@@ -21,7 +22,7 @@ const LANG_OPTIONS = [
|
||||
{ id: 'en-US', label: 'EN' },
|
||||
]
|
||||
|
||||
export function TopBar() {
|
||||
export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@@ -59,6 +60,18 @@ export function TopBar() {
|
||||
|
||||
return (
|
||||
<header className="h-14 shrink-0 border-b border-line bg-surface flex items-center px-4 sm:px-6 gap-3">
|
||||
{/* Hamburger (lg:hidden) — toggle MobileSidebar */}
|
||||
{onMenuClick && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('nav.menu', { defaultValue: 'Меню' })}
|
||||
onClick={onMenuClick}
|
||||
className="lg:hidden -ml-1 p-1.5 rounded-sm text-ink-2 hover:text-ink hover:bg-surface-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors"
|
||||
>
|
||||
<Menu size={20} strokeWidth={1.75} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Breadcrumb — left */}
|
||||
<nav className="flex items-center gap-1.5 text-body min-w-0">
|
||||
{breadcrumb.map((crumb, i) => {
|
||||
@@ -105,7 +118,10 @@ export function TopBar() {
|
||||
>
|
||||
{t('nav.docs')}
|
||||
</a>
|
||||
<VersionBadge />
|
||||
{/* VersionBadge hide на narrow viewport — diagnostic info, не critical UX */}
|
||||
<span className="hidden md:inline-flex">
|
||||
<VersionBadge />
|
||||
</span>
|
||||
<ThemeSwitch />
|
||||
<LanguageSwitch
|
||||
value={i18n.language}
|
||||
|
||||
Reference in New Issue
Block a user