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:
@@ -49,12 +49,15 @@ export const DialogOverlay = React.forwardRef<
|
||||
)
|
||||
})
|
||||
|
||||
// Mobile <880px = fullscreen (per handoff line 465); ≥880px = centered с max-w.
|
||||
// Используем arbitrary `min-[880px]:` (Tailwind v4 inline breakpoint) для точной
|
||||
// границы — handoff specifically wants 880, не стандартный md (768) или lg (1024).
|
||||
const SIZE_CLASSES: Record<string, string> = {
|
||||
sm: 'max-w-[440px]',
|
||||
md: 'max-w-[560px]',
|
||||
lg: 'max-w-[720px]',
|
||||
xl: 'max-w-[880px]',
|
||||
full: 'max-w-full max-h-full sm:max-w-[920px]',
|
||||
sm: 'min-[880px]:max-w-[440px]',
|
||||
md: 'min-[880px]:max-w-[560px]',
|
||||
lg: 'min-[880px]:max-w-[720px]',
|
||||
xl: 'min-[880px]:max-w-[880px]',
|
||||
full: 'min-[880px]:max-w-[920px]',
|
||||
}
|
||||
|
||||
export type DialogContentProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||
@@ -72,7 +75,12 @@ export const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-1/2 top-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 border border-line bg-surface p-6 shadow-2xl duration-200 sm:rounded-lg',
|
||||
'fixed z-50 grid gap-4 border border-line bg-surface p-6 shadow-2xl duration-200',
|
||||
// Mobile <880px (per handoff): fullscreen 100vw × 100dvh, no corners,
|
||||
// no centered transform. Editor body становится single-column natively.
|
||||
'inset-0 w-full h-full',
|
||||
// Desktop ≥880px: centered modal с rounded corners + max-w из SIZE_CLASSES.
|
||||
'min-[880px]:left-1/2 min-[880px]:top-1/2 min-[880px]:inset-auto min-[880px]:h-auto min-[880px]:w-full min-[880px]:-translate-x-1/2 min-[880px]:-translate-y-1/2 min-[880px]:rounded-lg',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
|
||||
@@ -61,7 +61,10 @@ export function LanguageSwitch({
|
||||
: 'text-mute hover:text-ink hover:bg-surface-2',
|
||||
)}
|
||||
>
|
||||
{opt.short ?? v.toUpperCase()}
|
||||
{/* Display priority: short → label (if compact like "RU"/"EN") →
|
||||
* uppercase id. Label predominates когда caller передал human
|
||||
* label типа "RU"/"EN" в options. */}
|
||||
{opt.short ?? opt.label ?? v.toUpperCase()}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -49,11 +49,13 @@ const sheetVariants = cva(
|
||||
left: 'inset-y-0 left-0 h-full border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',
|
||||
right: 'inset-y-0 right-0 h-full border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||
},
|
||||
// Per handoff (line 465): drawers fullscreen <880px, max-w на ≥880px.
|
||||
// arbitrary breakpoint `min-[880px]:` гарантирует точную границу.
|
||||
size: {
|
||||
sm: 'sm:max-w-sm w-full',
|
||||
md: 'sm:max-w-[520px] w-full',
|
||||
lg: 'sm:max-w-[720px] w-full',
|
||||
xl: 'sm:max-w-[880px] w-full',
|
||||
sm: 'min-[880px]:max-w-sm w-full',
|
||||
md: 'min-[880px]:max-w-[520px] w-full',
|
||||
lg: 'min-[880px]:max-w-[720px] w-full',
|
||||
xl: 'min-[880px]:max-w-[880px] w-full',
|
||||
},
|
||||
},
|
||||
defaultVariants: { side: 'right', size: 'md' },
|
||||
|
||||
Reference in New Issue
Block a user