// ---------------------------------------------------------------------------
// Generic atomic NodeView
//
// Renders any "atomic" component (no editable children) as a placeholder
// card showing the component type, icon, and its field values.
// ---------------------------------------------------------------------------
import React, { useCallback } from "react";
import { NodeViewWrapper } from "@tiptap/react";
import type { NodeViewProps } from "@tiptap/react";
import type { ComponentDef, ComponentField } from "./registry";
function AtomicFieldRow({
field,
value,
onChange,
}: {
field: ComponentField;
value: unknown;
onChange: (val: unknown) => void;
}) {
if (field.type === "boolean") {
return (
);
}
if (field.type === "select" && field.options) {
return (
{field.label}
);
}
return (
{field.label}
onChange(e.target.value)}
style={{
background: "var(--surface-bg)",
border: "1px solid var(--border-color)",
borderRadius: 4,
color: "var(--text-color)",
fontSize: 13,
padding: "4px 8px",
outline: "none",
flex: 1,
minWidth: 0,
}}
/>
);
}
export function makeAtomicView(def: ComponentDef) {
function AtomicNodeView({ node, updateAttributes }: NodeViewProps) {
const handleFieldChange = useCallback(
(fieldName: string, value: unknown) => {
updateAttributes({ [fieldName]: value });
},
[updateAttributes],
);
const primaryField = def.fields.find((f) => f.name === "src" || f.name === "title") || def.fields[0];
const primaryValue = primaryField ? String(node.attrs[primaryField.name] || "") : "";
return (
{/* Header */}
0 ? 10 : 0 }}>
{def.icon}
{def.label}
{primaryValue && (
{primaryValue}
)}
{/* Fields */}
{def.fields.length > 0 && (
{def.fields.map((f) => (
handleFieldChange(f.name, v)}
/>
))}
)}
);
}
AtomicNodeView.displayName = `${def.tag}View`;
return AtomicNodeView;
}