import React, { CSSProperties, useCallback, useRef, useEffect } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; interface FooterProps { text?: string; style?: CSSProperties; } export const Footer: UserComponent = ({ text = '© 2026 MySite. All rights reserved.', style = {}, }) => { const { connectors: { connect, drag }, selected, actions: { setProp }, } = useNode((node) => ({ selected: node.events.selected, })); const elRef = useRef(null); const editedTextRef = useRef(null); const commitText = useCallback(() => { if (elRef.current) { const newText = elRef.current.innerText; editedTextRef.current = newText; setProp((p: FooterProps) => { p.text = newText; }, 500); } }, [setProp]); // Commit on blur const handleBlur = useCallback(() => { commitText(); }, [commitText]); // Also commit on deselect via effect -- covers the case where selection // clears without a real blur (e.g. clicking a different element that // steals selection programmatically), which used to lose the in-progress // edit. Mirrors Heading.tsx's mechanism. useEffect(() => { if (!selected && editedTextRef.current !== null) { setProp((p: FooterProps) => { p.text = editedTextRef.current!; }, 500); editedTextRef.current = null; } }, [selected, setProp]); // Set DOM text on mount and when text prop changes externally (not during editing) useEffect(() => { if (elRef.current && !selected && editedTextRef.current === null) { elRef.current.innerText = text || ''; } }, [text, selected]); return (
{ elRef.current = ref; if (ref) connect(drag(ref)); }} contentEditable={selected} suppressContentEditableWarning onBlur={handleBlur} onInput={() => { // Track that we have unsaved edits if (elRef.current) { editedTextRef.current = elRef.current.innerText; } }} style={{ padding: '24px 20px', textAlign: 'center', outline: 'none', cursor: selected ? 'text' : 'pointer', ...style, }} > {selected ? undefined : (text || '')}
); }; /* ---------- Craft config ---------- */ Footer.craft = { displayName: 'Footer', props: { text: '© 2026 MySite. All rights reserved.', style: { backgroundColor: '#18181b', color: '#a1a1aa', fontSize: '14px', padding: '24px 20px', }, }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, }; /* ---------- HTML export ---------- */ (Footer as any).toHtml = (props: FooterProps, _childrenHtml: string) => { const styleStr = cssPropsToString({ padding: '24px 20px', textAlign: 'center', ...props.style, }); const escapedText = (props.text || '').replace(//g, '>'); return { html: `${escapedText}` }; };