import React, { CSSProperties, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml, escapeAttr, safeUrl, scopeId, cssValue } from '../../utils/escape'; import { tokenRef } from '../../utils/tokens'; import { NavLink, DROPDOWN_TOGGLE_ONCLICK, hasDropdown } from '../../utils/nav-links'; /* ---------- Types ---------- */ type MenuLink = NavLink; interface MenuProps { links?: MenuLink[]; /** #53: links follow the site's pages (materialised by PageContext). */ followPages?: boolean; alignment?: 'left' | 'center' | 'right'; linkColor?: string; linkHoverColor?: string; ctaBgColor?: string; ctaTextColor?: string; gap?: string; orientation?: 'horizontal' | 'vertical'; fontSize?: string; style?: CSSProperties; hideOnDesktop?: boolean; hideOnTablet?: boolean; hideOnMobile?: boolean; animation?: string; animationDelay?: string; } /* ---------- 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 = tokenRef('primary'), ctaBgColor = tokenRef('primary'), ctaTextColor = '#ffffff', gap = '24px', orientation = 'horizontal', fontSize = '14px', style = {}, }) => { const { connectors: { connect, drag }, } = useNode(); const [hoveredLink, setHoveredLink] = useState(null); const [openDropdown, setOpenDropdown] = useState(null); const justifyMap = { left: 'flex-start', center: 'center', right: 'flex-end' }; return ( ); }; const SUB_STYLE: CSSProperties = { position: 'absolute', top: '100%', left: 0, zIndex: 1001, minWidth: 180, margin: 0, padding: '8px 0', listStyle: 'none', background: 'var(--wsb-bg, #ffffff)', borderRadius: 6, boxShadow: '0 8px 24px rgba(0,0,0,0.12)', }; const SUB_STATIC_STYLE: CSSProperties = { margin: 0, padding: '4px 0 0 12px', listStyle: 'none' }; /* ---------- Craft config ---------- */ Menu.craft = { displayName: 'Menu', props: { links: defaultLinks, followPages: false, alignment: 'right', linkColor: '#3f3f46', linkHoverColor: tokenRef('primary'), ctaBgColor: tokenRef('primary'), ctaTextColor: '#ffffff', gap: '24px', orientation: 'horizontal', fontSize: '14px', style: {}, hideOnDesktop: false, hideOnTablet: false, hideOnMobile: false, animation: 'none', animationDelay: '0', } as MenuProps, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, }; /* ---------- HTML export ---------- */ (Menu as any).toHtml = (props: MenuProps, _childrenHtml: string, nodeId?: string) => { // Sanitized once here -- linkCol/hoverCol/ctaBg/ctaText/gap/fSize are raw // string-interpolation sinks below (hoverCol goes into a breakout -> arbitrary