import React, { CSSProperties, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { AnchorIdField } from '../../ui/AnchorIdField'; 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}
)}
); })}
); }; /* ---------- Settings panel ---------- */ const AccordionSettings: React.FC = () => { const { actions: { setProp }, props } = useNode((node) => ({ props: node.data.props as AccordionProps, })); const items = props.items || defaultItems; const inputStyle: CSSProperties = { width: '100%', padding: '3px 6px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11, }; const labelStyle: CSSProperties = { fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }; const updateItem = (index: number, field: keyof AccordionItem, value: string | boolean) => { setProp((p: AccordionProps) => { const updated = [...(p.items || defaultItems)]; updated[index] = { ...updated[index], [field]: value }; p.items = updated; }); }; const addItem = () => { setProp((p: AccordionProps) => { p.items = [...(p.items || defaultItems), { title: 'New Question', content: 'Answer goes here.', isOpen: false }]; }); }; const removeItem = (index: number) => { setProp((p: AccordionProps) => { const updated = [...(p.items || defaultItems)]; updated.splice(index, 1); p.items = updated; }); }; const colorSwatches = ['#f8fafc', '#f1f5f9', '#e2e8f0', '#ffffff', '#18181b', '#1e293b', '#3b82f6', '#8b5cf6']; return (
{colorSwatches.map((c) => (
{['#18181b', '#1f2937', '#374151', '#ffffff', '#e2e8f0', '#3b82f6'].map((c) => (
{['#ffffff', '#f8fafc', '#f1f5f9', '#18181b', '#1e293b'].map((c) => (
{['#e2e8f0', '#cbd5e1', '#d1d5db', '#3f3f46', '#52525b'].map((c) => (
{items.map((item, i) => (
updateItem(i, 'title', e.target.value)} placeholder="Title" style={{ ...inputStyle, flex: 1 }} />