= {
up: { icon: '/icons/room.png', label: 'Full height' },
cutaway: { icon: '/icons/wallcut.png', label: 'Cutaway' },
down: { icon: '/icons/walllow.png', label: 'Low' },
}
const SHADING_OPTIONS = [
{ id: 'solid', name: 'Solid', detail: 'Flat and fast — no ambient occlusion', icon: Box },
{ id: 'rendered', name: 'Rendered', detail: 'Full ambient occlusion', icon: Sparkles },
] as const
function ViewModeControl() {
const viewMode = useEditor((state) => state.viewMode)
const setViewMode = useEditor((state) => state.setViewMode)
return (
{VIEW_MODES.map((mode) => {
const isActive = viewMode === mode.id
return (
)
})}
)
}
function CollapseSidebarButton() {
const isCollapsed = useSidebarStore((state) => state.isCollapsed)
const setIsCollapsed = useSidebarStore((state) => state.setIsCollapsed)
const toggle = useCallback(() => {
setIsCollapsed(!isCollapsed)
}, [isCollapsed, setIsCollapsed])
return (
)
}
function LevelModeToggle() {
const levelMode = useViewer((state) => state.levelMode)
const setLevelMode = useViewer((state) => state.setLevelMode)
const isDefault = levelMode === 'stacked' || levelMode === 'manual'
const cycle = () => {
if (levelMode === 'manual') {
setLevelMode('stacked')
return
}
const index = levelModeOrder.indexOf(levelMode as (typeof levelModeOrder)[number])
const next = levelModeOrder[(index + 1) % levelModeOrder.length]
if (next) setLevelMode(next)
}
const label = `Levels: ${levelMode === 'manual' ? 'Manual' : (levelModeLabels[levelMode] ?? 'Stack')}`
return (
)
}
function WallModeToggle() {
const wallMode = useViewer((state) => state.wallMode)
const setWallMode = useViewer((state) => state.setWallMode)
const config = wallModeConfig[wallMode] ?? wallModeConfig.cutaway!
const cycle = () => {
const index = wallModeOrder.indexOf(wallMode as (typeof wallModeOrder)[number])
const next = wallModeOrder[(index + 1) % wallModeOrder.length]
if (next) setWallMode(next)
}
return (
)
}
// One dropdown that gathers every "how the scene looks" control: grid, shadows,
// camera projection, units, render mode, edges and scene theme.
const EDGE_OPTIONS = [
{ id: 'off', name: 'Off', detail: 'No edge lines' },
{ id: 'soft', name: 'Soft', detail: 'Faint outline of major creases' },
{ id: 'strong', name: 'Strong', detail: 'Crisp, opaque edge lines' },
] as const satisfies readonly { id: EdgeMode; name: string; detail: string }[]
const SUBMENU_CONTENT_CLASS = 'min-w-56 rounded-xl border-border/45 bg-popover/95 backdrop-blur-xl'
function DisplayMenu() {
const showGrid = useViewer((state) => state.showGrid)
const setShowGrid = useViewer((state) => state.setShowGrid)
const unit = useViewer((state) => state.unit)
const setUnit = useViewer((state) => state.setUnit)
const cameraMode = useViewer((state) => state.cameraMode)
const setCameraMode = useViewer((state) => state.setCameraMode)
const shading = useViewer((state) => state.shading)
const setShading = useViewer((state) => state.setShading)
const sceneTheme = useViewer((state) => state.sceneTheme)
const setSceneTheme = useViewer((state) => state.setSceneTheme)
const edges = useViewer((state) => state.edges)
const setEdges = useViewer((state) => state.setEdges)
const shadows = useViewer((state) => state.shadows)
const setShadows = useViewer((state) => state.setShadows)
const magneticSnap = useEditor((state) => state.magneticSnap)
const setMagneticSnap = useEditor((state) => state.setMagneticSnap)
const activeShading =
SHADING_OPTIONS.find((option) => option.id === shading) ?? SHADING_OPTIONS[0]
const activeEdges = EDGE_OPTIONS.find((option) => option.id === edges) ?? EDGE_OPTIONS[0]
const activeTheme = getSceneTheme(sceneTheme)
// Keep the menu open when flipping a toggle.
const keepOpen = (event: Event, fn: () => void) => {
event.preventDefault()
fn()
}
return (
keepOpen(e, () => setShowGrid(!showGrid))}>
Grid
{showGrid ? (
) : (
)}
keepOpen(e, () => setMagneticSnap(!magneticSnap))}>
Magnetic snap
{magneticSnap ? 'On' : 'Off'}
keepOpen(e, () => setShadows(!shadows))}>
Shadows
{shadows ? 'On' : 'Off'}
keepOpen(e, () =>
setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective'),
)
}
>
Camera
{cameraMode === 'perspective' ? 'Perspective' : 'Orthographic'}
keepOpen(e, () => setUnit(unit === 'metric' ? 'imperial' : 'metric'))}
>
{unit === 'metric' ? 'm' : 'ft'}
Units
{unit === 'metric' ? 'Metric' : 'Imperial'}
Render
{activeShading.name}
{SHADING_OPTIONS.map((option) => {
const OptionIcon = option.icon
return (
setShading(option.id)}>
{option.name}
{option.detail}
{shading === option.id ? (
) : null}
)
})}
Edges
{activeEdges.name}
{EDGE_OPTIONS.map((option) => (
setEdges(option.id)}>
{option.name}
{option.detail}
{edges === option.id ? : null}
))}
Theme
{activeTheme.name}
{SCENE_THEMES.map((theme) => {
const swatches = (['wall', 'roof', 'floor', 'glazing'] as const).map(
(role) => theme.clayTints?.[role] ?? CLAY_PALETTE[role],
)
return (
setSceneTheme(theme.id)}>
{swatches.map((color, index) => (
))}
{theme.name}
{sceneTheme === theme.id ? (
) : null}
)
})}
)
}
function WalkthroughButton() {
const isFirstPersonMode = useEditor((state) => state.isFirstPersonMode)
const setFirstPersonMode = useEditor((state) => state.setFirstPersonMode)
return (
)
}
function PreviewButton() {
return (
)
}
export function CommunityViewerToolbarLeft() {
return (
<>
>
)
}
export function CommunityViewerToolbarRight() {
return (
)
}