import React, { useEffect, useRef } from 'react'; import { useEditor } from '@craftjs/core'; import { CollapsibleSection, ArrayPropEditor, smallInputStyle } from './shared'; import { useLayerFocus } from '../../left/LayerFocusContext'; /* ---------- 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) : []; // Layers panel -> array editor "scroll to this item" hookup. The outer //
wraps ArrayPropEditor's rendered cards (the actual // per-item background box lives in shared.tsx's ArrayPropEditor, which is // also used by MediaStylePanel/FormStylePanel -- rather than touch that // shared component for one consumer, the data-array-item tag below goes // on the content renderItem returns, which is enough for scrollIntoView // to bring the right card into the viewport. const { focus } = useLayerFocus(); const rootRef = useRef(null); useEffect(() => { if (!focus || focus.prop !== propKey) return; const card = rootRef.current?.querySelector(`[data-array-item="${propKey}:${focus.index}"]`); card?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' }); // `focus.nonce` is in the dep list so clicking the SAME row twice // re-scrolls (the request object is otherwise identical). }, [focus?.nonce, focus?.prop, focus?.index, propKey]); 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 ? (