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.
148 lines
4.2 KiB
TypeScript
Executable File
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 }
|