import React, { CSSProperties, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { useSiteDesign } from '../../state/SiteDesignContext'; import { escapeHtml, escapeAttr, safeUrl, safeImageUrl, cssValue, scopeId } from '../../utils/escape'; /* ---------- Types ---------- */ interface NavLink { text: string; href: string; isExternal?: boolean; isCta?: boolean; /** Adds the `download` attribute to the exported anchor (F3: links to files). */ download?: boolean; } interface NavbarProps { logoType?: 'text' | 'image'; logoText?: string; logoImage?: string; logoWidth?: string; logoUrl?: string; logoFontFamily?: string; logoFontSize?: string; logoColor?: string; links?: NavLink[]; backgroundColor?: string; textColor?: string; hoverColor?: string; ctaColor?: string; ctaTextColor?: string; padding?: string; navAlignment?: 'left' | 'center' | 'right' | 'space-between'; isSticky?: boolean; showMobileMenu?: boolean; style?: CSSProperties; hideOnDesktop?: boolean; hideOnTablet?: boolean; hideOnMobile?: boolean; animation?: string; animationDelay?: string; } /* ---------- Defaults ---------- */ const defaultLinks: NavLink[] = [ { text: 'Home', href: '/' }, { text: 'About', href: '#about' }, { text: 'Services', href: '#services' }, { text: 'Contact', href: '#contact', isCta: true }, ]; const PADDING_PRESETS = [ { label: 'Compact', value: '8px 16px' }, { label: 'Normal', value: '16px 24px' }, { label: 'Relaxed', value: '20px 32px' }, { label: 'Spacious', value: '24px 48px' }, ]; /* ---------- Component ---------- */ export const Navbar: UserComponent = ({ logoType = 'text', logoText = 'MySite', logoImage = '', logoWidth = '120px', logoUrl = '/', logoFontFamily = 'Inter, sans-serif', logoFontSize = '20px', logoColor, links = defaultLinks, backgroundColor = '#ffffff', textColor = '#3f3f46', hoverColor = '#3b82f6', ctaColor = '#3b82f6', ctaTextColor = '#ffffff', padding = '16px 24px', navAlignment = 'space-between', isSticky = false, showMobileMenu = false, style = {}, }) => { const { connectors: { connect, drag }, selected, } = useNode((node) => ({ selected: node.events.selected, })); const { design } = useSiteDesign(); const resolvedLogoColor = logoColor || (backgroundColor === '#ffffff' || backgroundColor === '#f8fafc' || backgroundColor === '#f9fafb' ? design.textColor : '#ffffff'); const resolvedTextColor = textColor || (backgroundColor === '#ffffff' || backgroundColor === '#f8fafc' || backgroundColor === '#f9fafb' ? '#3f3f46' : '#e4e4e7'); const [hoveredLink, setHoveredLink] = useState(null); return ( ); }; /* ---------- Craft config ---------- */ Navbar.craft = { displayName: 'Navbar', props: { logoType: 'text', logoText: 'MySite', logoImage: '', logoWidth: '120px', logoUrl: '/', logoFontFamily: 'Inter, sans-serif', logoFontSize: '20px', logoColor: undefined, links: defaultLinks, backgroundColor: '#ffffff', textColor: '#3f3f46', hoverColor: '#3b82f6', ctaColor: '#3b82f6', ctaTextColor: '#ffffff', padding: '16px 24px', navAlignment: 'space-between', isSticky: false, showMobileMenu: false, style: { borderBottom: '1px solid #e4e4e7', }, hideOnDesktop: false, hideOnTablet: false, hideOnMobile: false, animation: 'none', animationDelay: '0', } as NavbarProps, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, }; /* ---------- HTML export ---------- */ (Navbar as any).toHtml = (props: NavbarProps, _childrenHtml: string, nodeId?: string) => { // Sanitized once here -- these are raw string-interpolation sinks below // (hoverCol/bgColor go into a // breakout -> arbitrary