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:
@@ -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,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 ---------- */
|
||||
|
||||
@@ -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}><{t}></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 ---------- */
|
||||
|
||||
@@ -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 ---------- */
|
||||
|
||||
Reference in New Issue
Block a user