diff --git a/craft/src/panels/right/styles/ArrayItemFields.tsx b/craft/src/panels/right/styles/ArrayItemFields.tsx new file mode 100644 index 0000000..d2d07fd --- /dev/null +++ b/craft/src/panels/right/styles/ArrayItemFields.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { useEditor } from '@craftjs/core'; +import { CollapsibleSection, ArrayPropEditor, smallInputStyle } from './shared'; + +/* ---------- Shared array-item field editor ---------- + Extracted from SectionTypePanel and GenericPropsEditor, which both had a + near-byte-identical per-item field renderer for generic array props + (features/items/plans/testimonials/etc.). Infers an input type per field: + boolean -> checkbox, number -> number, /color/ -> color swatch, long + string -> textarea, else text. Fields are derived from Object.keys(items[0]). + Callers keep their own special-casing (e.g. SectionTypePanel routes + key === 'features' to FeaturesEditor instead of using this component). */ +export const ArrayItemFieldsEditor: React.FC<{ selectedId: string; propKey: string; items: any[] }> = ({ + selectedId, propKey, items, +}) => { + const { actions } = useEditor(); + const arrayItems = items; + const sampleItem = arrayItems[0] || {}; + const itemFields = typeof sampleItem === 'object' && sampleItem !== null ? Object.keys(sampleItem) : []; + + return ( + + { + if (typeof item !== 'object' || item === null) { + return ( + { + actions.setProp(selectedId, (props: any) => { + const updated = [...(props[propKey] || [])]; + updated[index] = e.target.value; + props[propKey] = updated; + }); + }} + style={smallInputStyle} + /> + ); + } + return ( +
+ {itemFields.map((field) => { + const fieldVal = item[field]; + if (typeof fieldVal === 'boolean') { + return ( + + ); + } + if (typeof fieldVal === 'number') { + return ( +
+ + { + actions.setProp(selectedId, (props: any) => { + const updated = [...(props[propKey] || [])]; + updated[index] = { ...updated[index], [field]: parseFloat(e.target.value) || 0 }; + props[propKey] = updated; + }); + }} style={smallInputStyle} /> +
+ ); + } + // color fields + if (/color/i.test(field) && typeof fieldVal === 'string') { + return ( +
+ + { + actions.setProp(selectedId, (props: any) => { + const updated = [...(props[propKey] || [])]; + updated[index] = { ...updated[index], [field]: e.target.value }; + props[propKey] = updated; + }); + }} style={{ width: 24, height: 20, border: 'none', cursor: 'pointer', background: 'none', padding: 0 }} /> +
+ ); + } + // long text + const strVal = String(fieldVal ?? ''); + const isLongField = strVal.length > 50 || field === 'description' || field === 'text' || field === 'content'; + return ( +
+ + {isLongField ? ( +