Pascal 0a46a9deb4
sync: comprehensive monorepo → editor parity (2D/3D decoupling, UX polish, crash fixes)
Squash merge of 3 commits:
1. useLiveTransforms store for 2D/3D decoupling + floorplan overhaul + Sentry crash fixes
2. Comprehensive 59-file sync bringing editor to full monorepo parity (selection highlights, delete tool, furnish/zone modes, keyboard shortcuts, all panels)
3. Missing files fix (materials.ts, merged-outline-node.ts, type fix)

75 files changed, ~6K additions.
2026-04-07 19:21:10 -04:00

148 lines
4.2 KiB
TypeScript
Executable File

'use client'
import type { ComponentType, ReactNode } from 'react'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from './../../../components/ui/primitives/tooltip'
import { cn } from './../../../lib/utils'
export type PanelId = string
export type ExtraPanel = { id: string; icon: ReactNode; label: string; component: ComponentType }
interface IconRailProps {
activePanel: PanelId
onPanelChange: (panel: PanelId) => void
appMenuButton?: ReactNode
extraPanels?: ExtraPanel[]
className?: string
}
const sitePanel: { id: PanelId; iconSrc: string; label: string } = {
id: 'site',
iconSrc: '/icons/level.png',
label: 'Site',
}
const settingsPanel: { id: PanelId; iconSrc: string; label: string } = {
id: 'settings',
iconSrc: '/icons/settings.png',
label: 'Settings',
}
const panels: { id: PanelId; iconSrc: string; label: string }[] = [sitePanel, settingsPanel]
export function IconRail({
activePanel,
onPanelChange,
appMenuButton,
extraPanels,
className,
}: IconRailProps) {
return (
<div
className={cn(
'flex h-full w-11 flex-col items-center gap-1 border-border/50 border-r py-2',
className,
)}
>
{/* App menu slot */}
{appMenuButton}
{/* Divider */}
<div className="mb-1 h-px w-8 bg-border/50" />
{/* Site panel */}
{[sitePanel].map((panel) => {
const isActive = activePanel === panel.id
return (
<Tooltip key={panel.id}>
<TooltipTrigger asChild>
<button
className={cn(
'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
isActive ? 'bg-accent' : 'hover:bg-accent',
)}
onClick={() => onPanelChange(panel.id)}
type="button"
>
<img
alt={panel.label}
className={cn(
'h-6 w-6 object-contain transition-all',
!isActive && 'opacity-50 saturate-0',
)}
src={panel.iconSrc}
/>
</button>
</TooltipTrigger>
<TooltipContent side="right">{panel.label}</TooltipContent>
</Tooltip>
)
})}
{/* Extra panels (injected between site and settings) */}
{extraPanels?.map((panel) => {
const isActive = activePanel === panel.id
return (
<Tooltip key={panel.id}>
<TooltipTrigger asChild>
<button
className={cn(
'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
isActive ? 'bg-accent' : 'hover:bg-accent',
)}
onClick={() => onPanelChange(panel.id)}
type="button"
>
<span
className={cn(
'flex h-6 w-6 items-center justify-center transition-all',
!isActive && 'opacity-50',
)}
>
{panel.icon}
</span>
</button>
</TooltipTrigger>
<TooltipContent side="right">{panel.label}</TooltipContent>
</Tooltip>
)
})}
{/* Settings panel */}
{[settingsPanel].map((panel) => {
const isActive = activePanel === panel.id
return (
<Tooltip key={panel.id}>
<TooltipTrigger asChild>
<button
className={cn(
'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
isActive ? 'bg-accent' : 'hover:bg-accent',
)}
onClick={() => onPanelChange(panel.id)}
type="button"
>
<img
alt={panel.label}
className={cn(
'h-6 w-6 object-contain transition-all',
!isActive && 'opacity-50 saturate-0',
)}
src={panel.iconSrc}
/>
</button>
</TooltipTrigger>
<TooltipContent side="right">{panel.label}</TooltipContent>
</Tooltip>
)
})}
</div>
)
}
export { panels }