import React, { CSSProperties } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; interface Counter { number: number; suffix: string; label: string; } interface NumberCounterProps { counters?: Counter[]; columns?: number; numberColor?: string; labelColor?: string; numberSize?: string; style?: CSSProperties; } const defaultCounters: Counter[] = [ { number: 150, suffix: '+', label: 'Projects' }, { number: 50, suffix: '+', label: 'Clients' }, { number: 10, suffix: '', label: 'Years' }, { number: 99, suffix: '%', label: 'Satisfaction' }, ]; export const NumberCounter: UserComponent = ({ counters = defaultCounters, columns = 4, numberColor = '#3b82f6', labelColor = '#6b7280', numberSize = '48px', style = {}, }) => { const { connectors: { connect, drag }, selected, } = useNode((node) => ({ selected: node.events.selected, })); const items = counters.length > 0 ? counters : defaultCounters; return (
{ if (ref) connect(drag(ref)); }} style={{ padding: '60px 20px', backgroundColor: '#ffffff', outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} >
{items.map((counter, i) => (
{counter.number}{counter.suffix} {counter.label}
))}
); }; /* ---------- Settings panel ---------- */ const NumberCounterSettings: React.FC = () => { const { actions: { setProp }, props } = useNode((node) => ({ props: node.data.props as NumberCounterProps, })); const items = props.counters || defaultCounters; 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, }; const columnOptions = [2, 3, 4, 5, 6]; const sizePresets = ['32px', '40px', '48px', '56px', '64px']; const numberColorPresets = ['#3b82f6', '#10b981', '#8b5cf6', '#ef4444', '#f59e0b', '#18181b', '#ec4899', '#0ea5e9']; const labelColorPresets = ['#6b7280', '#374151', '#9ca3af', '#a1a1aa', '#64748b', '#18181b']; const bgPresets = ['#ffffff', '#f8fafc', '#f1f5f9', '#18181b', '#0f172a']; const updateCounter = (index: number, field: keyof Counter, value: string | number) => { setProp((p: NumberCounterProps) => { const updated = [...(p.counters || defaultCounters)]; updated[index] = { ...updated[index], [field]: value }; p.counters = updated; }); }; const addCounter = () => { setProp((p: NumberCounterProps) => { p.counters = [...(p.counters || defaultCounters), { number: 100, suffix: '+', label: 'New Stat' }]; }); }; const removeCounter = (index: number) => { setProp((p: NumberCounterProps) => { const updated = [...(p.counters || defaultCounters)]; updated.splice(index, 1); p.counters = updated; }); }; return (
{/* Columns */}
{columnOptions.map((n) => ( ))}
{/* Number Size */}
{sizePresets.map((s) => ( ))}
{/* Number Color */}
{numberColorPresets.map((c) => (
{/* Label Color */}
{labelColorPresets.map((c) => (
{/* Background */}
{bgPresets.map((c) => (
{/* Counters */}
{items.map((counter, i) => (
updateCounter(i, 'number', parseInt(e.target.value) || 0)} placeholder="Number" style={{ ...inputStyle, width: 70, flex: 'none' }} /> updateCounter(i, 'suffix', e.target.value)} placeholder="Suffix" style={{ ...inputStyle, width: 40, flex: 'none' }} /> updateCounter(i, 'label', e.target.value)} placeholder="Label" style={{ ...inputStyle, flex: 1 }} />
))}
); }; /* ---------- Craft config ---------- */ NumberCounter.craft = { displayName: 'Number Counter', props: { counters: defaultCounters, columns: 4, numberColor: '#3b82f6', labelColor: '#6b7280', numberSize: '48px', style: { backgroundColor: '#ffffff' }, }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, related: { settings: NumberCounterSettings, }, }; /* ---------- HTML export ---------- */ (NumberCounter as any).toHtml = (props: NumberCounterProps, _childrenHtml: string) => { const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { counters = defaultCounters, columns = 4, numberColor = '#3b82f6', labelColor = '#6b7280', numberSize = '48px', style = {}, } = props; const items = counters.length > 0 ? counters : defaultCounters; const uid = 'nc_' + Math.random().toString(36).slice(2, 8); const sectionStyle = cssPropsToString({ padding: '60px 20px', backgroundColor: '#ffffff', ...style, }); const countersHtml = items.map((counter, i) => { return `
0${esc(counter.suffix)} ${esc(counter.label)}
`; }).join('\n '); return { html: `
${countersHtml}
`, }; };