import React, { CSSProperties } from 'react'; import { useNode, Element, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { Container } from './Container'; /* ---------- Shape Divider SVG Paths ---------- */ type DividerShape = 'none' | 'wave' | 'angle' | 'curve' | 'triangle' | 'zigzag'; const DIVIDER_PATHS: Record, string> = { wave: 'M0,0 C150,120 350,0 600,60 C850,120 1050,0 1200,60 L1200,120 L0,120 Z', angle: 'M0,0 L1200,120 L0,120 Z', curve: 'M0,0 Q600,140 1200,0 L1200,120 L0,120 Z', triangle: 'M0,120 L600,0 L1200,120 Z', 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; children?: React.ReactNode; topDivider?: DividerShape; topDividerColor?: string; topDividerHeight?: string; bottomDivider?: DividerShape; bottomDividerColor?: string; bottomDividerHeight?: string; } /* ---------- Divider renderer ---------- */ const ShapeDivider: React.FC<{ shape: DividerShape; color: string; height: string; position: 'top' | 'bottom'; }> = ({ shape, color, height, position }) => { if (!shape || shape === 'none') return null; const path = DIVIDER_PATHS[shape]; if (!path) return null; const isTop = position === 'top'; return (
); }; /* ---------- Component ---------- */ export const Section: UserComponent = ({ style = {}, innerMaxWidth = '1200px', children, topDivider = 'none', topDividerColor = '#ffffff', topDividerHeight = '50px', bottomDivider = 'none', bottomDividerColor = '#ffffff', bottomDividerHeight = '50px', }) => { const { connectors: { connect, drag } } = useNode(); const hasTopDivider = topDivider && topDivider !== 'none'; const hasBottomDivider = bottomDivider && bottomDivider !== 'none'; return (
{ if (ref) connect(drag(ref)); }} style={{ width: '100%', position: (hasTopDivider || hasBottomDivider) ? 'relative' : undefined, ...style, }} > {hasTopDivider && ( )} {children} {hasBottomDivider && ( )}
); }; /* ---------- 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 (
{/* Shape selector */} {shape && shape !== 'none' && ( <> {/* Color picker */}
onColorChange(e.target.value)} style={{ width: 32, height: 28, border: '1px solid #3f3f46', borderRadius: 4, background: 'none', cursor: 'pointer', padding: 0 }} /> onColorChange(e.target.value)} style={{ ...sInputStyle, flex: 1 }} placeholder="#ffffff" />
{/* Height slider */}
Height {heightNum}px
onHeightChange(`${e.target.value}px`)} style={{ width: '100%', accentColor: '#3b82f6' }} />
{/* Small SVG preview */}
)}
); }; 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 (
{bgPresets.map((c) => (
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 }} />
{paddingPresets.map((p) => ( ))}
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 }} />
{/* Divider separator */}
Shape Dividers
setProp((p: SectionProps) => { p.topDivider = s; })} onColorChange={(c) => setProp((p: SectionProps) => { p.topDividerColor = c; })} onHeightChange={(h) => setProp((p: SectionProps) => { p.topDividerHeight = h; })} />
setProp((p: SectionProps) => { p.bottomDivider = s; })} onColorChange={(c) => setProp((p: SectionProps) => { p.bottomDividerColor = c; })} onHeightChange={(h) => setProp((p: SectionProps) => { p.bottomDividerHeight = h; })} />
); }; /* ---------- Craft config ---------- */ Section.craft = { displayName: 'Section', props: { style: { padding: '40px 0', backgroundColor: '#ffffff' }, innerMaxWidth: '1200px', topDivider: 'none', topDividerColor: '#ffffff', topDividerHeight: '50px', bottomDivider: 'none', bottomDividerColor: '#ffffff', bottomDividerHeight: '50px', }, rules: { canDrag: () => true, canMoveIn: () => true, canMoveOut: () => true, }, related: { settings: SectionSettings, }, }; /* ---------- HTML export ---------- */ function buildDividerHtml( shape: DividerShape | undefined, color: string | undefined, height: string | undefined, position: 'top' | 'bottom', ): string { if (!shape || shape === 'none') return ''; const path = DIVIDER_PATHS[shape]; if (!path) return ''; const isTop = position === 'top'; const h = height || '50px'; const c = color || '#ffffff'; const wrapperStyle = cssPropsToString({ position: 'absolute', [position]: '0', left: '0', right: '0', height: h, overflow: 'hidden', lineHeight: '0', pointerEvents: 'none', } as CSSProperties); const svgTransform = isTop ? ' transform:rotate(180deg);' : ''; return `
`; } (Section as any).toHtml = (props: SectionProps, childrenHtml: string) => { const hasTopDivider = props.topDivider && props.topDivider !== 'none'; const hasBottomDivider = props.bottomDivider && props.bottomDivider !== 'none'; const outerStyle = cssPropsToString({ width: '100%', position: (hasTopDivider || hasBottomDivider) ? 'relative' : undefined, ...props.style, }); const innerStyle = cssPropsToString({ maxWidth: props.innerMaxWidth || '1200px', margin: '0 auto', position: (hasTopDivider || hasBottomDivider) ? 'relative' : undefined, zIndex: (hasTopDivider || hasBottomDivider) ? 1 : undefined, } as CSSProperties); const topHtml = buildDividerHtml(props.topDivider, props.topDividerColor, props.topDividerHeight, 'top'); const bottomHtml = buildDividerHtml(props.bottomDivider, props.bottomDividerColor, props.bottomDividerHeight, 'bottom'); return { html: `${topHtml}${childrenHtml}
${bottomHtml}`, }; };