Aymeric Rabot aa43bb6a43
feat: backport monorepo PRs #297, #302, #306 — floorplan thumbnails + mobile editor UI
Port floorplan item thumbnails, full mobile editor UI, and
mobile polish from the private monorepo into the public editor.

Monorepo PR #297 (feat/item-admin-advanced):
- Add optional floorPlanUrl to item asset schema (core)
- Render 2D floor-plan images inside item footprints on floorplan
- Improve slider drag with modifier key re-anchoring

Monorepo PR #302 (feat/mobile-ui):
- Mobile editor layout with draggable bottom sheet
- Mobile tab bar, selection bar, and panel sheet
- Mobile-aware panel manager and panel wrapper
- useIsMobile rewrite (useSyncExternalStore, SSR-safe)
- Camera actions hideOrbit prop
- GridSnapControl and SecondaryToggles exports
- Action menu hides on mobile contextual tabs
- SidebarTab extended with mobileDefaultSnap/mobileIcon

Monorepo PR #306 (feat/mobile-ui-polish):
- Touch gesture mapping for camera controls (one/two/three finger)
- Snap ratio constants for bottom sheet
- Thumbnail generator WebGL2 fallback (bottom-up row flip)
- ErrorBoundary scope logging, viewer scene wrap
- GPUDeviceWatcher enhanced logging and uncaptured error handler
- WebGPURenderer init error handling and diagnostics
- Post-processing log improvements
- MergedOutlineNode WebGL2 FBO corruption fix

Excluded: apps/community/*, apps/editor/*, packages/community-*,
.cursor/*, .env.example (monorepo-only files)

Co-authored-by: Pascal <open@pascal.app>
2026-04-28 06:36:08 -07:00

47 lines
1.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { cn } from './../../../lib/utils'
import type { SidebarTab } from './tab-bar'
interface MobileTabBarProps {
tabs: SidebarTab[]
activeTab: string
onTabPress: (id: string) => void
}
export function MobileTabBar({ tabs, activeTab, onTabPress }: MobileTabBarProps) {
return (
<div
className="z-50 flex h-14 shrink-0 border-border/50 border-t bg-sidebar text-sidebar-foreground"
style={{
// Cap the safe-area inset — iOS Chrome can report its bottom UI bar
// (50–100px) as part of the safe area which would balloon the tab bar.
// 34px matches the iPhone home-indicator height (the typical max).
paddingBottom: 'min(env(safe-area-inset-bottom, 0px), 34px)',
}}
>
{tabs.map((tab) => {
const isActive = activeTab === tab.id
return (
<button
className={cn(
'flex flex-1 flex-col items-center justify-center gap-0.5 text-xs transition-colors',
isActive ? 'text-foreground' : 'text-muted-foreground',
)}
key={tab.id}
onClick={() => onTabPress(tab.id)}
type="button"
>
{tab.mobileIcon ? (
<span className={cn('flex h-5 w-5 items-center justify-center')}>
{tab.mobileIcon}
</span>
) : null}
<span className="font-medium">{tab.label}</span>
</button>
)
})}
</div>
)
}