refactor(builder): remove dead component settings UI

Each component defined a .craft.related.settings panel that was never
rendered -- the right panel renders only GuidedStyles (per-type
*StylePanel components), never .related.settings. Removed all dead
settings components across every component, their settings-only helpers
(including the dead uploadToWhp/showBrowser/handleBrowse asset-browse
blocks in Logo/Navbar/VideoBlock/FeaturesGrid, and CtasEditor in
_cta-helpers), and dropped the now-empty related keys. Render output,
.craft props/rules, and toHtml statics are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 13:16:53 -07:00
parent 4674a99ec9
commit 65a10a1ef9
40 changed files with 10 additions and 6718 deletions
@@ -2,7 +2,6 @@ import React, { CSSProperties } from 'react';
import { useNode, Element, UserComponent } from '@craftjs/core';
import { Container } from './Container';
import { cssPropsToString } from '../../utils/style-helpers';
import { AnchorIdField } from '../../ui/AnchorIdField';
import { escapeAttr } from '../../utils/escape';
interface BackgroundSectionProps {
@@ -70,93 +69,6 @@ export const BackgroundSection: UserComponent<BackgroundSectionProps> = ({
);
};
/* ---------- Settings panel ---------- */
const BackgroundSectionSettings: React.FC = () => {
const { actions: { setProp }, props } = useNode((node) => ({
props: node.data.props as BackgroundSectionProps,
}));
const bgColorPresets = ['#1e293b', '#0f172a', '#18181b', '#1e3a5f', '#312e81', '#064e3b', '#7f1d1d', '#ffffff'];
const overlayPresets = ['#000000', '#1e293b', '#0f172a', '#312e81', '#064e3b', '#7f1d1d'];
return (
<div style={{ padding: '12px', display: 'flex', flexDirection: 'column', gap: '14px' }}>
<AnchorIdField />
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Background Image URL</label>
<input
type="text"
value={props.bgImage || ''}
onChange={(e) => setProp((p: BackgroundSectionProps) => { p.bgImage = e.target.value; })}
placeholder="https://... or /storage/assets/..."
style={{ width: '100%', padding: '4px 8px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 12 }}
/>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Background Color</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{bgColorPresets.map((c) => (
<button
key={c}
onClick={() => setProp((p: BackgroundSectionProps) => { p.bgColor = c; })}
style={{
width: 24, height: 24, borderRadius: 4, border: '1px solid #3f3f46',
backgroundColor: c, cursor: 'pointer',
outline: props.bgColor === c ? '2px solid #3b82f6' : 'none',
outlineOffset: 1,
}}
/>
))}
</div>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Overlay Color</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{overlayPresets.map((c) => (
<button
key={c}
onClick={() => setProp((p: BackgroundSectionProps) => { p.overlayColor = c; })}
style={{
width: 24, height: 24, borderRadius: 4, border: '1px solid #3f3f46',
backgroundColor: c, cursor: 'pointer',
outline: props.overlayColor === c ? '2px solid #3b82f6' : 'none',
outlineOffset: 1,
}}
/>
))}
</div>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>
Overlay Opacity: {Math.round((props.overlayOpacity ?? 0.4) * 100)}%
</label>
<input
type="range"
min={0}
max={100}
value={Math.round((props.overlayOpacity ?? 0.4) * 100)}
onChange={(e) => setProp((p: BackgroundSectionProps) => { p.overlayOpacity = parseInt(e.target.value, 10) / 100; })}
style={{ width: '100%' }}
/>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Inner Max Width</label>
<input
type="text"
value={props.innerMaxWidth || '1200px'}
onChange={(e) => setProp((p: BackgroundSectionProps) => { p.innerMaxWidth = e.target.value; })}
style={{ width: '100%', padding: '4px 8px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11 }}
/>
</div>
</div>
);
};
/* ---------- Craft config ---------- */
BackgroundSection.craft = {
@@ -175,9 +87,6 @@ BackgroundSection.craft = {
canMoveIn: () => false,
canMoveOut: () => true,
},
related: {
settings: BackgroundSectionSettings,
},
};
/* ---------- HTML export ---------- */
+1 -177
View File
@@ -1,8 +1,7 @@
import React, { CSSProperties, useState } from 'react';
import React, { CSSProperties } from 'react';
import { useNode, Element, UserComponent } from '@craftjs/core';
import { Container } from './Container';
import { cssPropsToString } from '../../utils/style-helpers';
import { AnchorIdField } from '../../ui/AnchorIdField';
import { escapeAttr } from '../../utils/escape';
type SplitOption =
@@ -95,178 +94,6 @@ export const ColumnLayout: UserComponent<ColumnLayoutProps> = ({
);
};
/* ---------- Settings panel ---------- */
const ColumnLayoutSettings: React.FC = () => {
const { actions: { setProp }, props } = useNode((node) => ({
props: node.data.props as ColumnLayoutProps,
}));
const [showCustom, setShowCustom] = useState(false);
/* Preset options -- common splits up to 6 columns */
const presetOptions: { columns: number; split: SplitOption; label: string }[] = [
{ columns: 1, split: '100', label: '1 Col' },
{ columns: 2, split: '50-50', label: '2 Equal' },
{ columns: 2, split: '30-70', label: '30/70' },
{ columns: 2, split: '70-30', label: '70/30' },
{ columns: 2, split: '40-60', label: '40/60' },
{ columns: 2, split: '60-40', label: '60/40' },
{ columns: 3, split: '33-33-33', label: '3 Equal' },
{ columns: 3, split: '25-50-25', label: '25/50/25' },
{ columns: 4, split: '25-25-25-25', label: '4 Equal' },
{ columns: 5, split: '20-20-20-20-20', label: '5 Equal' },
{ columns: 6, split: '16-16-16-16-16-16', label: '6 Equal' },
];
const gapPresets = ['0px', '8px', '16px', '24px', '32px'];
const labelStyle: CSSProperties = { fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 };
const inputStyle: CSSProperties = {
width: '100%', padding: '3px 6px', background: '#27272a', color: '#e4e4e7',
border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11,
};
return (
<div style={{ padding: '12px', display: 'flex', flexDirection: 'column', gap: '14px' }}>
<AnchorIdField />
{/* Preset layouts */}
<div>
<label style={labelStyle}>Column Layout</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{presetOptions.map((opt) => (
<button
key={opt.label}
onClick={() => {
setProp((p: ColumnLayoutProps) => { p.columns = opt.columns; p.split = opt.split; });
setShowCustom(false);
}}
style={{
padding: '4px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer',
border: '1px solid #3f3f46',
background: props.split === opt.split && props.columns === opt.columns ? '#3b82f6' : '#27272a',
color: '#e4e4e7',
}}
>
{opt.label}
</button>
))}
</div>
</div>
{/* Custom column count (7-10) */}
<div>
<button
onClick={() => setShowCustom(!showCustom)}
style={{
padding: '4px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer',
border: '1px solid #3f3f46',
background: showCustom ? '#3b82f6' : '#27272a',
color: '#e4e4e7',
width: '100%',
}}
>
{showCustom ? 'Hide Custom' : 'Custom (7-10 columns)'}
</button>
{showCustom && (
<div style={{ marginTop: 8 }}>
<label style={labelStyle}>Number of Columns</label>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<input
type="range"
min={1}
max={10}
value={props.columns || 2}
onChange={(e) => {
const cols = parseInt(e.target.value);
setProp((p: ColumnLayoutProps) => { p.columns = cols; p.split = 'equal'; });
}}
style={{ flex: 1 }}
/>
<span style={{ fontSize: 12, color: '#e4e4e7', minWidth: 24, textAlign: 'center' }}>{props.columns || 2}</span>
</div>
</div>
)}
</div>
{/* Gap */}
<div>
<label style={labelStyle}>Gap</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{gapPresets.map((g) => (
<button
key={g}
onClick={() => setProp((p: ColumnLayoutProps) => { p.gap = g; })}
style={{
padding: '2px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer',
border: '1px solid #3f3f46',
background: props.gap === g ? '#3b82f6' : '#27272a',
color: '#e4e4e7',
}}
>
{g}
</button>
))}
</div>
</div>
{/* Individual Column Widths */}
<div>
<label style={labelStyle}>Column Widths (%)</label>
<p style={{ fontSize: 10, color: '#71717a', marginBottom: 6 }}>
Adjust each column's width. Values should roughly total 100%.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{Array.from({ length: props.columns || 2 }).map((_, i) => {
const currentWidths = getWidths(props.split || 'equal', props.columns || 2);
const currentPct = parseFloat(currentWidths[i]) || (100 / (props.columns || 2));
return (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<span style={{ fontSize: 10, color: '#71717a', minWidth: 40 }}>Col {i + 1}</span>
<input
type="range"
min={10}
max={90}
step={5}
value={Math.round(currentPct)}
onChange={(e) => {
const newPct = parseInt(e.target.value);
const cols = props.columns || 2;
const widths = getWidths(props.split || 'equal', cols).map(w => parseFloat(w));
const oldPct = widths[i];
const diff = newPct - oldPct;
widths[i] = newPct;
// Distribute the difference across other columns proportionally
const others = widths.filter((_, j) => j !== i);
const otherTotal = others.reduce((a, b) => a + b, 0);
if (otherTotal > 0) {
for (let j = 0; j < widths.length; j++) {
if (j !== i) {
widths[j] = widths[j] - (diff * (widths[j] / otherTotal));
if (widths[j] < 5) widths[j] = 5;
}
}
}
// Normalize to 100%
const total = widths.reduce((a, b) => a + b, 0);
const normalized = widths.map(w => ((w / total) * 100).toFixed(1) + '%');
const customSplit = normalized.map(w => parseFloat(w).toFixed(0)).join('-') as SplitOption;
setProp((p: ColumnLayoutProps) => { p.split = customSplit; });
}}
style={{ flex: 1 }}
/>
<span style={{ fontSize: 11, color: '#e4e4e7', minWidth: 35, textAlign: 'right' }}>
{Math.round(currentPct)}%
</span>
</div>
);
})}
</div>
</div>
</div>
);
};
/* ---------- Craft config ---------- */
ColumnLayout.craft = {
@@ -283,9 +110,6 @@ ColumnLayout.craft = {
canMoveIn: () => false,
canMoveOut: () => true,
},
related: {
settings: ColumnLayoutSettings,
},
};
/* ---------- HTML export ---------- */
-238
View File
@@ -1,10 +1,6 @@
import React, { CSSProperties } from 'react';
import { useNode, UserComponent } from '@craftjs/core';
import { cssPropsToString } from '../../utils/style-helpers';
import { SettingsTabs } from '../../ui/SettingsTabs';
import { BorderControl } from '../../ui/BorderControl';
import { AdvancedTab } from '../../ui/AdvancedTab';
import { AnchorIdField } from '../../ui/AnchorIdField';
import { escapeAttr } from '../../utils/escape';
interface ContainerProps {
@@ -70,237 +66,6 @@ export const Container: UserComponent<ContainerProps> = ({
return el;
};
/* ---------- Settings panel ---------- */
const cLabelStyle: React.CSSProperties = {
fontSize: 11, fontWeight: 600, color: '#a1a1aa', display: 'block', marginBottom: 6,
textTransform: 'uppercase', letterSpacing: '0.3px',
};
const cInputStyle: React.CSSProperties = {
width: '100%', padding: '5px 8px', background: '#27272a', color: '#e4e4e7',
border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11,
};
const cPresetBtnStyle = (active: boolean): React.CSSProperties => ({
padding: '3px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer',
border: '1px solid #3f3f46', background: active ? '#3b82f6' : '#27272a', color: active ? '#fff' : '#e4e4e7',
});
const cSwatchStyle = (color: string, active: boolean): React.CSSProperties => ({
width: 24, height: 24, borderRadius: 4, border: '1px solid #3f3f46', backgroundColor: color, cursor: 'pointer',
outline: active ? '2px solid #3b82f6' : 'none', outlineOffset: 1,
});
const cToggleBtnStyle = (active: boolean): React.CSSProperties => ({
flex: 1, padding: '5px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer',
border: '1px solid #3f3f46',
background: active ? '#3b82f6' : '#27272a',
color: active ? '#fff' : '#e4e4e7',
fontWeight: active ? 600 : 400,
textAlign: 'center',
});
const ContainerSettings: React.FC = () => {
const { actions: { setProp }, props } = useNode((node) => ({
props: node.data.props as ContainerProps,
}));
const bgColors = ['transparent', '#ffffff', '#f9fafb', '#f1f5f9', '#1f2937', '#111827', '#0f172a', '#3b82f6', '#10b981', '#8b5cf6', '#ec4899', '#f59e0b'];
const gradients = [
{ label: 'None', value: 'none' },
{ label: 'Purple', value: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
{ label: 'Blue', value: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
{ label: 'Sunset', value: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' },
{ label: 'Dark', value: 'linear-gradient(135deg, #0f172a 0%, #1e3a5f 100%)' },
{ label: 'Green', value: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' },
];
const alignPresets = [
{ label: 'Left', value: 'left', icon: 'fa-align-left' },
{ label: 'Center', value: 'center', icon: 'fa-align-center' },
{ label: 'Right', value: 'right', icon: 'fa-align-right' },
];
const currentBg = props.style?.backgroundColor || '';
const currentBgImage = props.style?.backgroundImage || '';
return (
<SettingsTabs
general={
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
<AnchorIdField />
{/* Tag */}
<div>
<label style={cLabelStyle}>HTML Element</label>
<select
value={props.tag || 'div'}
onChange={(e) => setProp((p: ContainerProps) => { p.tag = e.target.value as ContainerProps['tag']; })}
style={cInputStyle}
>
{['div', 'section', 'article', 'header', 'footer', 'main'].map((t) => (
<option key={t} value={t}>&lt;{t}&gt;</option>
))}
</select>
</div>
{/* Full Width */}
<div>
<label style={{ ...cLabelStyle, display: 'flex', alignItems: 'center', gap: 6, textTransform: 'none', fontWeight: 500, cursor: 'pointer' }}>
<input
type="checkbox"
checked={props.fullWidth || false}
onChange={(e) => setProp((p: ContainerProps) => { p.fullWidth = e.target.checked; })}
/>
Full Width
</label>
<span style={{ fontSize: 10, color: '#71717a', lineHeight: '1.3', display: 'block', marginTop: 2 }}>
Breaks out of parent constraints to fill the viewport width
</span>
</div>
{/* Content Width */}
<div>
<label style={cLabelStyle}>Content Width</label>
<div style={{ display: 'flex', gap: 4 }}>
<button
onClick={() => setProp((p: ContainerProps) => { p.contentWidth = 'full'; })}
style={cToggleBtnStyle((props.contentWidth || 'full') === 'full')}
>
Full
</button>
<button
onClick={() => setProp((p: ContainerProps) => { p.contentWidth = 'boxed'; })}
style={cToggleBtnStyle(props.contentWidth === 'boxed')}
>
Boxed (1200px)
</button>
</div>
<span style={{ fontSize: 10, color: '#71717a', lineHeight: '1.3', display: 'block', marginTop: 4 }}>
{props.contentWidth === 'boxed'
? 'Content is centered with a max-width of 1200px'
: 'Content fills the full container width'}
</span>
</div>
</div>
}
style={
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{/* Background Color */}
<div>
<label style={cLabelStyle}>Background Color</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{bgColors.map((c) => (
<button key={c} onClick={() => setProp((p: ContainerProps) => { p.style = { ...p.style, backgroundColor: c, backgroundImage: 'none' }; })}
style={cSwatchStyle(c === 'transparent' ? '#fff' : c, currentBg === c)} title={c} />
))}
</div>
</div>
{/* Background Gradient */}
<div>
<label style={cLabelStyle}>Gradient</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{gradients.map((g) => (
<button key={g.value} onClick={() => setProp((p: ContainerProps) => {
p.style = { ...p.style, backgroundImage: g.value === 'none' ? 'none' : g.value, backgroundColor: 'transparent' };
})} style={{
width: 32, height: 24, borderRadius: 4, cursor: 'pointer',
border: currentBgImage === g.value ? '2px solid #3b82f6' : '1px solid #3f3f46',
background: g.value === 'none' ? '#27272a' : g.value,
}} title={g.label} />
))}
</div>
</div>
{/* Background Image */}
<div>
<label style={cLabelStyle}>Background Image</label>
<input type="text" placeholder="Image URL..."
value={(props.style?.backgroundImage || '').replace(/^url\(['"]?|['"]?\)$/g, '')}
onChange={(e) => {
const val = e.target.value.trim();
setProp((p: ContainerProps) => {
p.style = { ...p.style, backgroundImage: val ? `url('${val}')` : 'none', backgroundSize: 'cover', backgroundPosition: 'center' };
});
}}
style={cInputStyle} />
<div style={{ display: 'flex', gap: 4, marginTop: 4 }}>
{['cover', 'contain', 'auto'].map((s) => (
<button key={s} onClick={() => setProp((p: ContainerProps) => { p.style = { ...p.style, backgroundSize: s }; })}
style={cPresetBtnStyle(props.style?.backgroundSize === s)}>{s}</button>
))}
{['center', 'top', 'bottom'].map((pos) => (
<button key={pos} onClick={() => setProp((p: ContainerProps) => { p.style = { ...p.style, backgroundPosition: pos }; })}
style={cPresetBtnStyle(props.style?.backgroundPosition === pos)}>{pos}</button>
))}
</div>
</div>
{/* Overlay */}
<div>
<label style={cLabelStyle}>Overlay Color</label>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<input type="color" value={props.style?.['--overlayColor' as keyof CSSProperties] || '#000000'}
onChange={(e) => setProp((p: ContainerProps) => { p.style = { ...p.style, ['--overlayColor' as keyof CSSProperties]: e.target.value }; })}
style={{ width: 32, height: 24, border: 'none', background: 'none', cursor: 'pointer' }} />
<span style={{ fontSize: 11, color: '#71717a' }}>Overlay (via CSS custom property)</span>
</div>
</div>
{/* Parallax */}
<div>
<label style={{ ...cLabelStyle, display: 'flex', alignItems: 'center', gap: 6, textTransform: 'none', fontWeight: 500 }}>
<input type="checkbox"
checked={props.style?.backgroundAttachment === 'fixed'}
onChange={(e) => setProp((p: ContainerProps) => { p.style = { ...p.style, backgroundAttachment: e.target.checked ? 'fixed' : 'scroll' }; })} />
Parallax Effect
</label>
</div>
{/* Text Alignment */}
<div>
<label style={cLabelStyle}>Content Alignment</label>
<div style={{ display: 'flex', gap: 4 }}>
{alignPresets.map((a) => (
<button key={a.value} onClick={() => setProp((p: ContainerProps) => { p.style = { ...p.style, textAlign: a.value as any }; })}
style={{ ...cPresetBtnStyle(props.style?.textAlign === a.value), flex: 1 }}>
<i className={`fa ${a.icon}`} />
</button>
))}
</div>
</div>
{/* Border */}
<BorderControl
style={props.style || {}}
onChange={(updates) => setProp((p: ContainerProps) => { p.style = { ...p.style, ...updates }; })}
/>
</div>
}
advanced={
<AdvancedTab
style={props.style || {}}
onStyleChange={(updates) => setProp((p: ContainerProps) => { p.style = { ...p.style, ...updates }; })}
showTagSelector
tag={props.tag || 'div'}
onTagChange={(tag) => setProp((p: ContainerProps) => { p.tag = tag as ContainerProps['tag']; })}
cssId={props.cssId || ''}
onCssIdChange={(id) => setProp((p: ContainerProps) => { p.cssId = id; })}
cssClass={props.cssClass || ''}
onCssClassChange={(cls) => setProp((p: ContainerProps) => { p.cssClass = cls; })}
hideOnDesktop={props.hideOnDesktop}
onHideOnDesktopChange={(v) => setProp((p: ContainerProps) => { p.hideOnDesktop = v; })}
hideOnTablet={props.hideOnTablet}
onHideOnTabletChange={(v) => setProp((p: ContainerProps) => { p.hideOnTablet = v; })}
hideOnMobile={props.hideOnMobile}
onHideOnMobileChange={(v) => setProp((p: ContainerProps) => { p.hideOnMobile = v; })}
animation={props.animation}
onAnimationChange={(v) => setProp((p: ContainerProps) => { p.animation = v; })}
animationDelay={props.animationDelay}
onAnimationDelayChange={(v) => setProp((p: ContainerProps) => { p.animationDelay = v; })}
/>
}
/>
);
};
/* ---------- Craft config ---------- */
Container.craft = {
@@ -317,9 +82,6 @@ Container.craft = {
canMoveIn: () => true,
canMoveOut: () => true,
},
related: {
settings: ContainerSettings,
},
};
/* ---------- HTML export ---------- */
-198
View File
@@ -2,7 +2,6 @@ import React, { CSSProperties } from 'react';
import { useNode, Element, UserComponent } from '@craftjs/core';
import { cssPropsToString } from '../../utils/style-helpers';
import { Container } from './Container';
import { AnchorIdField } from '../../ui/AnchorIdField';
import { escapeAttr } from '../../utils/escape';
/* ---------- Shape Divider SVG Paths ---------- */
@@ -17,8 +16,6 @@ const DIVIDER_PATHS: Record<Exclude<DividerShape, 'none'>, string> = {
zigzag: 'M0,120 L100,40 L200,120 L300,40 L400,120 L500,40 L600,120 L700,40 L800,120 L900,40 L1000,120 L1100,40 L1200,120 Z',
};
const DIVIDER_SHAPES: DividerShape[] = ['none', 'wave', 'angle', 'curve', 'triangle', 'zigzag'];
interface SectionProps {
style?: CSSProperties;
innerMaxWidth?: string;
@@ -134,198 +131,6 @@ export const Section: UserComponent<SectionProps> = ({
);
};
/* ---------- Settings panel ---------- */
const sLabelStyle: React.CSSProperties = {
fontSize: 11, fontWeight: 600, color: '#a1a1aa', display: 'block', marginBottom: 6,
textTransform: 'uppercase', letterSpacing: '0.3px',
};
const sInputStyle: React.CSSProperties = {
width: '100%', padding: '4px 8px', background: '#27272a', color: '#e4e4e7',
border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11,
};
const sSelectStyle: React.CSSProperties = {
width: '100%', padding: '5px 8px', background: '#27272a', color: '#e4e4e7',
border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11,
};
const DividerSettings: React.FC<{
label: string;
shape: DividerShape;
color: string;
height: string;
onShapeChange: (s: DividerShape) => void;
onColorChange: (c: string) => void;
onHeightChange: (h: string) => void;
}> = ({ label, shape, color, height, onShapeChange, onColorChange, onHeightChange }) => {
const heightNum = parseInt(height, 10) || 50;
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<label style={sLabelStyle}>{label}</label>
{/* Shape selector */}
<select
value={shape || 'none'}
onChange={(e) => onShapeChange(e.target.value as DividerShape)}
style={sSelectStyle}
>
{DIVIDER_SHAPES.map((s) => (
<option key={s} value={s}>{s === 'none' ? 'None' : s.charAt(0).toUpperCase() + s.slice(1)}</option>
))}
</select>
{shape && shape !== 'none' && (
<>
{/* Color picker */}
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<input
type="color"
value={color || '#ffffff'}
onChange={(e) => onColorChange(e.target.value)}
style={{ width: 32, height: 28, border: '1px solid #3f3f46', borderRadius: 4, background: 'none', cursor: 'pointer', padding: 0 }}
/>
<input
type="text"
value={color || '#ffffff'}
onChange={(e) => onColorChange(e.target.value)}
style={{ ...sInputStyle, flex: 1 }}
placeholder="#ffffff"
/>
</div>
{/* Height slider */}
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
<span style={{ fontSize: 10, color: '#71717a' }}>Height</span>
<span style={{ fontSize: 10, color: '#a1a1aa' }}>{heightNum}px</span>
</div>
<input
type="range"
min={10}
max={200}
value={heightNum}
onChange={(e) => onHeightChange(`${e.target.value}px`)}
style={{ width: '100%', accentColor: '#3b82f6' }}
/>
</div>
{/* Small SVG preview */}
<div style={{ background: '#18181b', borderRadius: 4, padding: 4, border: '1px solid #3f3f46', overflow: 'hidden' }}>
<svg viewBox="0 0 1200 120" preserveAspectRatio="none" style={{ width: '100%', height: 30, fill: color || '#ffffff', display: 'block' }}>
<path d={DIVIDER_PATHS[shape]} />
</svg>
</div>
</>
)}
</div>
);
};
const SectionSettings: React.FC = () => {
const { actions: { setProp }, props } = useNode((node) => ({
props: node.data.props as SectionProps,
}));
const bgPresets = ['#ffffff', '#f8fafc', '#f1f5f9', '#0f172a', '#1e293b', '#18181b', '#f0fdf4', '#eff6ff'];
const paddingPresets = ['0px', '20px', '40px', '60px', '80px', '120px'];
return (
<div style={{ padding: '12px', display: 'flex', flexDirection: 'column', gap: '14px' }}>
<AnchorIdField />
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Background Color</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{bgPresets.map((c) => (
<button
key={c}
onClick={() => setProp((p: SectionProps) => { p.style = { ...p.style, backgroundColor: c }; })}
style={{
width: 24, height: 24, borderRadius: 4, border: '1px solid #3f3f46',
backgroundColor: c, cursor: 'pointer',
outline: props.style?.backgroundColor === c ? '2px solid #3b82f6' : 'none',
outlineOffset: 1,
}}
/>
))}
</div>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Background Gradient</label>
<input
type="text"
placeholder="e.g. linear-gradient(135deg, #667eea, #764ba2)"
value={(props.style?.background as string) || ''}
onChange={(e) => setProp((p: SectionProps) => { p.style = { ...p.style, background: e.target.value }; })}
style={{ width: '100%', padding: '4px 8px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11 }}
/>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Padding (top/bottom)</label>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
{paddingPresets.map((p) => (
<button
key={p}
onClick={() => setProp((pr: SectionProps) => {
pr.style = { ...pr.style, paddingTop: p, paddingBottom: p };
})}
style={{
padding: '2px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer',
border: '1px solid #3f3f46',
background: props.style?.paddingTop === p ? '#3b82f6' : '#27272a',
color: '#e4e4e7',
}}
>
{p}
</button>
))}
</div>
</div>
<div>
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Inner Max Width</label>
<input
type="text"
value={props.innerMaxWidth || '1200px'}
onChange={(e) => setProp((p: SectionProps) => { p.innerMaxWidth = e.target.value; })}
style={{ width: '100%', padding: '4px 8px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11 }}
/>
</div>
{/* Divider separator */}
<div style={{ borderTop: '1px solid #3f3f46', paddingTop: 10 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: '#e4e4e7', marginBottom: 10 }}>Shape Dividers</div>
<DividerSettings
label="Top Divider"
shape={props.topDivider || 'none'}
color={props.topDividerColor || '#ffffff'}
height={props.topDividerHeight || '50px'}
onShapeChange={(s) => setProp((p: SectionProps) => { p.topDivider = s; })}
onColorChange={(c) => setProp((p: SectionProps) => { p.topDividerColor = c; })}
onHeightChange={(h) => setProp((p: SectionProps) => { p.topDividerHeight = h; })}
/>
<div style={{ height: 10 }} />
<DividerSettings
label="Bottom Divider"
shape={props.bottomDivider || 'none'}
color={props.bottomDividerColor || '#ffffff'}
height={props.bottomDividerHeight || '50px'}
onShapeChange={(s) => setProp((p: SectionProps) => { p.bottomDivider = s; })}
onColorChange={(c) => setProp((p: SectionProps) => { p.bottomDividerColor = c; })}
onHeightChange={(h) => setProp((p: SectionProps) => { p.bottomDividerHeight = h; })}
/>
</div>
</div>
);
};
/* ---------- Craft config ---------- */
Section.craft = {
@@ -346,9 +151,6 @@ Section.craft = {
canMoveIn: () => true,
canMoveOut: () => true,
},
related: {
settings: SectionSettings,
},
};
/* ---------- HTML export ---------- */