/**
* Property inspector panel (direct-manipulation primitive).
*
* Click an object on any canvas → this panel shows its editable properties;
* every change calls back immediately so the canvas re-renders live (WYSIWYG).
* Field types: text, number, slider, select, toggle, readonly.
*/
import type { ReactNode } from 'react';
import Box from '@mui/material/Box';
import MenuItem from '@mui/material/MenuItem';
import Slider from '@mui/material/Slider';
import Switch from '@mui/material/Switch';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { InfoDot } from '../help/InfoTip';
import { tokens } from '../../theme';
export type InspectorField =
| { type: 'text'; key: string; label: string; value: string; help?: string }
| { type: 'number'; key: string; label: string; value: number; min?: number; max?: number; step?: number; help?: string }
| { type: 'slider'; key: string; label: string; value: number; min: number; max: number; step?: number; unit?: string; help?: string }
| { type: 'select'; key: string; label: string; value: string; options: Array<{ value: string; label: string }>; help?: string }
| { type: 'toggle'; key: string; label: string; value: boolean; help?: string }
| { type: 'readonly'; key: string; label: string; value: ReactNode; help?: string };
interface InspectorPanelProps {
title: string;
subtitle?: string;
accent?: string;
fields: InspectorField[];
onChange: (key: string, value: string | number | boolean) => void;
footer?: ReactNode;
emptyHint?: string;
}
function FieldLabel({ label, help }: { label: string; help?: string }) {
return (
{label}
{help ? : null}
);
}
export default function InspectorPanel({ title, subtitle, accent, fields, onChange, footer, emptyHint }: InspectorPanelProps) {
return (
{title}
{subtitle ? (
{subtitle}
) : null}
{fields.length === 0 && emptyHint ? (
{emptyHint}
) : null}
{fields.map((f) => {
switch (f.type) {
case 'text':
return (
onChange(f.key, e.target.value)} />
);
case 'number':
return (
{
const v = Number(e.target.value);
if (!Number.isNaN(v)) onChange(f.key, v);
}}
/>
);
case 'slider':
return (
{f.value}
{f.unit ?? ''}
onChange(f.key, v as number)}
sx={{ py: 0.5 }}
/>
);
case 'select':
return (
onChange(f.key, e.target.value)}>
{f.options.map((o) => (
))}
);
case 'toggle':
return (
onChange(f.key, e.target.checked)} />
);
case 'readonly':
return (
{f.value}
);
default:
return null;
}
})}
{footer}
);
}