From 8c2b03f99bfd64c7658b02afd7ccbcb13c312347 Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Mon, 18 May 2026 10:35:18 -0400 Subject: [PATCH] parametric-inspector: render `def.presentation.icon` before the title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User pointed out the auto-derived panel was title-only; the legacy panels rendered a small icon in front. Legacy used a URL path (next Image). The registry-driven path has structured `IconRef` values (iconify / svg / lazy component) declared on `def.presentation.icon`. - PanelWrapper's `icon` prop now accepts `string | React.ReactNode`. String → next/image (legacy URL behavior). Node → rendered as-is. - ParametricInspector resolves `def.presentation.icon` to a node: iconify → ``, svg → inline svg, component → Suspense + lazy. Kind-owned custom panels (slab/ceiling) keep their existing legacy URL icons since they pass `` themselves — no change needed there. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/ui/panels/panel-wrapper.tsx | 21 ++++++++++++--- .../ui/panels/parametric-inspector.tsx | 26 ++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/ui/panels/panel-wrapper.tsx b/packages/editor/src/components/ui/panels/panel-wrapper.tsx index 3f9aa19f..951a0127 100644 --- a/packages/editor/src/components/ui/panels/panel-wrapper.tsx +++ b/packages/editor/src/components/ui/panels/panel-wrapper.tsx @@ -7,7 +7,11 @@ import { cn } from '../../../lib/utils' interface PanelWrapperProps { title: string - icon?: string + /** Either a URL path (legacy panels pass `/icons/floor.png` etc., + * rendered via next/image) OR a React node (registry-driven + * inspector renders `` from + * `def.presentation.icon`). */ + icon?: string | React.ReactNode onClose?: () => void onReset?: () => void onBack?: () => void @@ -58,9 +62,18 @@ export function PanelWrapper({ )} - {icon && ( - - )} + {icon && + (typeof icon === 'string' ? ( + + ) : ( + {icon} + ))}

{title}

diff --git a/packages/editor/src/components/ui/panels/parametric-inspector.tsx b/packages/editor/src/components/ui/panels/parametric-inspector.tsx index ee0105e0..eec0dba5 100644 --- a/packages/editor/src/components/ui/panels/parametric-inspector.tsx +++ b/packages/editor/src/components/ui/panels/parametric-inspector.tsx @@ -3,11 +3,13 @@ import { type AnyNode, type AnyNodeId, + type IconRef, nodeRegistry, type ParamField, useScene, } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' +import { Icon } from '@iconify/react' import { Move, Trash2 } from 'lucide-react' import { type ComponentType, lazy, Suspense, useCallback } from 'react' import { sfxEmitter } from '../../../lib/sfx-bus' @@ -94,11 +96,12 @@ export function ParametricInspector() { const presentation = def.presentation const title = presentation?.label ?? nodeType ?? '' + const iconNode = renderIcon(presentation?.icon) const canMove = !!def.capabilities.movable const canDelete = def.capabilities.deletable !== false return ( - + {parametrics.groups.map((group, gi) => ( {group.fields.map((field, fi) => ( @@ -132,6 +135,27 @@ export function ParametricInspector() { ) } +function renderIcon(ref: IconRef | undefined): React.ReactNode | undefined { + if (!ref) return undefined + if (ref.kind === 'iconify') { + return + } + if (ref.kind === 'svg') { + return ( + + + + ) + } + // `component`: lazy-loaded custom icon component. Suspense-safe. + const LazyIcon = lazy(ref.module) + return ( + + + + ) +} + // Cache lazy custom panel components by their loader so React.lazy isn't // re-invoked across renders. const customPanelCache = new WeakMap<() => Promise, ComponentType>()