import React from 'react'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; import Tooltip from '@mui/material/Tooltip'; import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import StarRoundedIcon from '@mui/icons-material/StarRounded'; import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined'; import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined'; import type { SxProps, Theme } from '@mui/material/styles'; import { Typography } from '../../atoms/Typography'; // ─── Types ────────────────────────────────────────────────────────────────── /** Props for the FA MapPopup molecule */ export interface MapPopupProps { /** Provider/venue name */ name: string; /** Hero image URL */ imageUrl?: string; /** Price in dollars — shown as "From $X" */ price?: number; /** Custom price label (e.g. "POA") — overrides formatted price */ priceLabel?: string; /** Location text (suburb, city) */ location?: string; /** Average rating (e.g. 4.8) */ rating?: number; /** Venue capacity */ capacity?: number; /** Whether this provider is verified — shows icon badge in image */ verified?: boolean; /** Click handler — entire card is clickable */ onClick?: () => void; /** MUI sx prop for the root element */ sx?: SxProps; } // ─── Constants ────────────────────────────────────────────────────────────── const POPUP_WIDTH = 260; const IMAGE_HEIGHT = 100; const NUB_SIZE = 8; // ─── Component ────────────────────────────────────────────────────────────── /** * Map popup card for the FA design system. * * Floating card anchored to a MapPin on click. Shows a compact * preview of a provider or venue — image, name, meta, and price. * The entire card is clickable to navigate to the provider/venue. * * Content hierarchy matches MiniCard: **title → meta → price**. * Truncated names show a tooltip on hover. Verified providers * show an icon-only badge floating in the image. * * Designed for use as a custom popup in Mapbox GL / Google Maps. * The parent map container handles positioning; this component * handles content and styling only. * * Composes: Paper + Typography + Tooltip. * * Usage: * ```tsx * selectProvider(id)} * /> * ``` */ export const MapPopup = React.forwardRef( ( { name, imageUrl, price, priceLabel, location, rating, capacity, verified = false, onClick, sx, }, ref, ) => { const hasMeta = location != null || rating != null || capacity != null; const hasPrice = price != null || priceLabel != null; // Detect name truncation for tooltip const nameRef = React.useRef(null); const [isTruncated, setIsTruncated] = React.useState(false); React.useEffect(() => { const el = nameRef.current; if (el) { setIsTruncated(el.scrollHeight > el.clientHeight + 1); } }, [name]); return ( { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); } } : undefined } aria-label={onClick ? `View ${name}` : undefined} sx={[ { display: 'inline-flex', flexDirection: 'column', alignItems: 'center', filter: 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))', cursor: onClick ? 'pointer' : 'default', transition: 'transform 150ms ease-in-out', '&:hover': onClick ? { transform: 'scale(1.02)', } : undefined, '&:focus-visible': { outline: '2px solid var(--fa-color-interactive-focus)', outlineOffset: '2px', borderRadius: 'var(--fa-card-border-radius-default)', }, }, ...(Array.isArray(sx) ? sx : [sx]), ]} > {/* ── Image ── */} {imageUrl && ( {/* Verified icon badge — floating top-right */} {verified && ( )} )} {/* ── Content ── */} {/* 1. Name — with tooltip when truncated */} {name} {/* 2. Meta row */} {hasMeta && ( {location && ( {location} )} {rating != null && ( {rating} )} {capacity != null && ( {capacity} )} )} {/* 3. Price */} {hasPrice && ( {priceLabel ? ( {priceLabel} ) : ( <> From ${price!.toLocaleString('en-AU')} )} )} {/* Verified indicator (no-image fallback) */} {verified && !imageUrl && ( Verified )} {/* Nub — downward pointer connecting to pin */} ); }, ); MapPopup.displayName = 'MapPopup'; export default MapPopup;