import React, { CSSProperties, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml, escapeAttr } from '../../utils/escape'; interface AccordionItem { title: string; content: string; isOpen?: boolean; } interface AccordionProps { items?: AccordionItem[]; style?: CSSProperties; headerBg?: string; headerColor?: string; contentBg?: string; borderColor?: string; anchorId?: string; } const defaultItems: AccordionItem[] = [ { title: 'What is this product?', content: 'Our product is a powerful yet easy-to-use tool designed to help you build beautiful websites without writing a single line of code.', isOpen: true }, { title: 'How do I get started?', content: 'Simply sign up for a free account, choose a template, and start customizing. Our drag-and-drop editor makes it easy to create professional pages in minutes.', isOpen: false }, { title: 'Is there a free plan?', content: 'Yes! We offer a generous free tier that includes all core features. Upgrade anytime to unlock advanced capabilities like custom domains and analytics.', isOpen: false }, ]; export const Accordion: UserComponent = ({ items = defaultItems, style = {}, headerBg = '#f8fafc', headerColor = '#18181b', contentBg = '#ffffff', borderColor = '#e2e8f0', anchorId, }) => { const { connectors: { connect, drag }, selected, } = useNode((node) => ({ selected: node.events.selected, })); const [openIndexes, setOpenIndexes] = useState>(() => { const initial = new Set(); items.forEach((item, i) => { if (item.isOpen) initial.add(i); }); return initial; }); const toggle = (index: number) => { setOpenIndexes((prev) => { const next = new Set(prev); if (next.has(index)) next.delete(index); else next.add(index); return next; }); }; return (
{ if (ref) connect(drag(ref)); }} id={anchorId || undefined} style={{ padding: '60px 20px', backgroundColor: '#ffffff', outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} >
{items.map((item, i) => { const isOpen = openIndexes.has(i); return (
toggle(i)} style={{ padding: '16px 20px', backgroundColor: headerBg, color: headerColor, cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontWeight: '600', fontSize: '16px', userSelect: 'none', ...(i === 0 ? { borderTopLeftRadius: '7px', borderTopRightRadius: '7px' } : {}), ...(i === items.length - 1 && !isOpen ? { borderBottomLeftRadius: '7px', borderBottomRightRadius: '7px' } : {}), }} > {item.title}
{isOpen && (
{item.content}
)}
); })}
); }; /* ---------- Craft config ---------- */ Accordion.craft = { displayName: 'Accordion', props: { items: defaultItems, style: { backgroundColor: '#ffffff' }, headerBg: '#f8fafc', headerColor: '#18181b', contentBg: '#ffffff', borderColor: '#e2e8f0', anchorId: '', }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, }; /* ---------- HTML export ---------- */ (Accordion as any).toHtml = (props: AccordionProps, _childrenHtml: string) => { const sectionStyle = cssPropsToString({ padding: '60px 20px', ...props.style, }); const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : ''; const headerBg = props.headerBg || '#f8fafc'; const headerColor = props.headerColor || '#18181b'; const contentBg = props.contentBg || '#ffffff'; const borderColor = props.borderColor || '#e2e8f0'; const items = props.items || defaultItems; const panels = items.map((item, i) => { const openAttr = item.isOpen ? ' open' : ''; const topRadius = i === 0 ? 'border-top-left-radius:8px;border-top-right-radius:8px;' : ''; const bottomRadius = i === items.length - 1 ? 'border-bottom-left-radius:8px;border-bottom-right-radius:8px;' : ''; const borderBottom = i === items.length - 1 ? `border:1px solid ${borderColor};` : `border:1px solid ${borderColor};border-bottom:none;`; return ` ${escapeHtml(item.title)}
${escapeHtml(item.content)}
`; }).join('\n '); return { html: `
${panels}
`, }; };