import React, { CSSProperties, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml, escapeAttr, safeUrl } from '../../utils/escape'; /* ---------- Types ---------- */ interface MenuLink { text: string; href: string; isExternal?: boolean; isCta?: boolean; } interface MenuProps { links?: MenuLink[]; alignment?: 'left' | 'center' | 'right'; linkColor?: string; linkHoverColor?: string; ctaBgColor?: string; ctaTextColor?: string; gap?: string; orientation?: 'horizontal' | 'vertical'; fontSize?: string; style?: CSSProperties; } /* ---------- Defaults ---------- */ const defaultLinks: MenuLink[] = [ { text: 'Home', href: '/' }, { text: 'About', href: '#about' }, { text: 'Services', href: '#services' }, { text: 'Contact', href: '#contact', isCta: true }, ]; /* ---------- Component ---------- */ export const Menu: UserComponent = ({ links = defaultLinks, alignment = 'right', linkColor = '#3f3f46', linkHoverColor = '#3b82f6', ctaBgColor = '#3b82f6', ctaTextColor = '#ffffff', gap = '24px', orientation = 'horizontal', fontSize = '14px', style = {}, }) => { const { connectors: { connect, drag }, } = useNode(); const [hoveredLink, setHoveredLink] = useState(null); const justifyMap = { left: 'flex-start', center: 'center', right: 'flex-end' }; return ( ); }; /* ---------- Craft config ---------- */ Menu.craft = { displayName: 'Menu', props: { links: defaultLinks, alignment: 'right', linkColor: '#3f3f46', linkHoverColor: '#3b82f6', ctaBgColor: '#3b82f6', ctaTextColor: '#ffffff', gap: '24px', orientation: 'horizontal', fontSize: '14px', style: {}, } as MenuProps, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, }; /* ---------- HTML export ---------- */ (Menu as any).toHtml = (props: MenuProps, _childrenHtml: string) => { const linkCol = props.linkColor || '#3f3f46'; const hoverCol = props.linkHoverColor || '#3b82f6'; const ctaBg = props.ctaBgColor || '#3b82f6'; const ctaText = props.ctaTextColor || '#ffffff'; const gap = props.gap || '24px'; const orientation = props.orientation || 'horizontal'; const alignment = props.alignment || 'right'; const fSize = props.fontSize || '14px'; const justifyMap: Record = { left: 'flex-start', center: 'center', right: 'flex-end' }; const navStyle = cssPropsToString({ display: 'flex', flexDirection: orientation === 'vertical' ? 'column' : 'row', alignItems: orientation === 'vertical' ? (alignment === 'center' ? 'center' : alignment === 'right' ? 'flex-end' : 'flex-start') : 'center', justifyContent: orientation === 'horizontal' ? justifyMap[alignment] : undefined, gap, flexWrap: 'wrap', ...props.style, }); const links = props.links || defaultLinks; // Unique ID suffix for scoped CSS const scopeId = `menu-${Math.random().toString(36).slice(2, 8)}`; const linksHtml = links.map((link) => { const target = link.isExternal ? ' target="_blank" rel="noopener noreferrer"' : ''; const cls = link.isCta ? `${scopeId}-cta` : `${scopeId}-link`; const linkStyle = cssPropsToString({ textDecoration: 'none', fontSize: fSize, fontWeight: link.isCta ? '600' : '400', color: link.isCta ? ctaText : linkCol, backgroundColor: link.isCta ? ctaBg : 'transparent', padding: link.isCta ? '8px 20px' : '0', borderRadius: link.isCta ? '6px' : '0', transition: 'color 0.15s, background-color 0.15s', }); return `${escapeHtml(link.text)}`; }).join('\n '); const hoverCss = ``; return { html: `${hoverCss} ${linksHtml} `, }; };