Files
site-builder/craft/src/panels/right/styles/GenericPropsEditor.tsx
T

134 lines
5.5 KiB
TypeScript
Raw Normal View History

import React from 'react';
import {
TEXT_COLORS,
BG_COLORS,
SPACING_PRESETS,
RADIUS_PRESETS,
} from '../../../constants/presets';
import {
SectionLabel,
ColorSwatchGrid,
PresetButtonGrid,
CollapsibleSection,
ColorPickerField,
labelStyle,
inputStyle,
sectionGap,
useNodeProp,
} from './shared';
import { ArrayItemFieldsEditor } from './ArrayItemFields';
/* ---------- SMART GENERIC PROPS EDITOR (Fallback) ---------- */
export const GenericPropsEditor: React.FC<{ selectedId: string; nodeProps: Record<string, any>; typeName: string }> = ({
selectedId, nodeProps, typeName,
}) => {
const SKIP_PROPS = new Set(['style', 'children', 'cssId', 'cssClass']);
const { setProp: setPropValue, setPropStyle: setStyleValue } = useNodeProp(selectedId);
// Categorize all props
const allProps = Object.entries(nodeProps).filter(([key]) => !SKIP_PROPS.has(key));
const colorProps = allProps.filter(([key, val]) => typeof val === 'string' && /color/i.test(key));
const boolProps = allProps.filter(([_, val]) => typeof val === 'boolean');
const numberProps = allProps.filter(([key, val]) => typeof val === 'number' && !/color/i.test(key));
const stringProps = allProps.filter(([key, val]) => typeof val === 'string' && !/color/i.test(key));
const arrayProps = allProps.filter(([_, val]) => Array.isArray(val));
const style = nodeProps.style || {};
return (
<>
{/* String props */}
{stringProps.length > 0 && (
<CollapsibleSection title="Properties">
{stringProps.map(([key, val]) => {
const humanLabel = key.replace(/([A-Z])/g, ' $1').trim();
const isLong = String(val).length > 60 || key === 'description' || key === 'text' || key === 'content';
return (
<div key={key} style={sectionGap}>
<label style={labelStyle}>{humanLabel}</label>
{isLong ? (
<textarea value={String(val)} onChange={(e) => setPropValue(key, e.target.value)} rows={3} style={{ ...inputStyle, resize: 'vertical' }} />
) : (
<input type="text" value={String(val)} onChange={(e) => setPropValue(key, e.target.value)} style={inputStyle} />
)}
</div>
);
})}
</CollapsibleSection>
)}
{/* Number props */}
{numberProps.length > 0 && (
<CollapsibleSection title="Numbers" defaultOpen={true}>
{numberProps.map(([key, val]) => (
<div key={key} style={sectionGap}>
<label style={labelStyle}>{key.replace(/([A-Z])/g, ' $1').trim()}</label>
<input type="number" value={val as number} onChange={(e) => setPropValue(key, parseFloat(e.target.value) || 0)} style={inputStyle} />
</div>
))}
</CollapsibleSection>
)}
{/* Boolean props */}
{boolProps.length > 0 && (
<CollapsibleSection title="Options" defaultOpen={true}>
{boolProps.map(([key, val]) => (
<div key={key} style={sectionGap}>
<label style={{ ...labelStyle, display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input type="checkbox" checked={val as boolean} onChange={(e) => setPropValue(key, e.target.checked)} />
{key.replace(/([A-Z])/g, ' $1').trim()}
</label>
</div>
))}
</CollapsibleSection>
)}
{/* Color props */}
{colorProps.length > 0 && (
<CollapsibleSection title="Colors">
{colorProps.map(([key, val]) => (
<ColorPickerField key={key} label={key.replace(/([A-Z])/g, ' $1').trim()} value={String(val)} onChange={(v) => setPropValue(key, v)} />
))}
</CollapsibleSection>
)}
{/* Array props */}
{arrayProps.map(([key, items]) => (
<ArrayItemFieldsEditor key={key} selectedId={selectedId} propKey={key} items={items as any[]} />
))}
{/* Style controls */}
<CollapsibleSection title="Style">
<div className="guided-section">
<SectionLabel>Background</SectionLabel>
<ColorSwatchGrid colors={BG_COLORS} activeValue={style.backgroundColor} onSelect={(v: string) => setStyleValue('backgroundColor', v)} />
</div>
{/* Text color in style */}
<div className="guided-section">
<SectionLabel>Text Color</SectionLabel>
<ColorSwatchGrid colors={TEXT_COLORS} activeValue={style.color as string} onSelect={(v: string) => setStyleValue('color', v)} />
</div>
<div className="guided-section">
<SectionLabel>Padding</SectionLabel>
<PresetButtonGrid presets={SPACING_PRESETS} activeValue={style.padding as string} onSelect={(v) => setStyleValue('padding', v)} />
</div>
<div className="guided-section">
<SectionLabel>Text Alignment</SectionLabel>
<div className="preset-grid align-grid">
{(['left', 'center', 'right'] as const).map((a) => (
<button key={a} className={`preset-btn ${style.textAlign === a ? 'active' : ''}`} onClick={() => setStyleValue('textAlign', a)} title={a}>
<i className={`fa fa-align-${a}`} />
</button>
))}
</div>
</div>
<div className="guided-section">
<SectionLabel>Border Radius</SectionLabel>
<PresetButtonGrid presets={RADIUS_PRESETS} activeValue={style.borderRadius as string} onSelect={(v) => setStyleValue('borderRadius', v)} />
</div>
</CollapsibleSection>
</>
);
};