import React from 'react'; import Box from '@mui/material/Box'; import type { SxProps, Theme } from '@mui/material/styles'; // ─── Types ────────────────────────────────────────────────────────────────── /** Props for the FA MapPin atom */ export interface MapPinProps { /** Provider or venue name — always shown on the pin */ name: string; /** Starting package price in dollars — shown as "From $X" */ price?: number; /** Custom price label (e.g. "POA") — overrides formatted price */ priceLabel?: string; /** Whether this provider/venue is verified (brand colour vs neutral) */ verified?: boolean; /** Whether this pin is currently active/selected */ active?: boolean; /** Click handler */ onClick?: (e: React.MouseEvent) => void; /** MUI sx prop for the root element */ sx?: SxProps; } // ─── Constants ────────────────────────────────────────────────────────────── const PIN_PX = 'var(--fa-map-pin-padding-x)'; const PIN_RADIUS = 'var(--fa-map-pin-border-radius)'; const NUB_SIZE = 'var(--fa-map-pin-nub-size)'; const MAX_WIDTH = 180; // ─── Colour sets ──────────────────────────────────────────────────────────── const colours = { verified: { bg: 'var(--fa-color-brand-100)', name: 'var(--fa-color-brand-900)', price: 'var(--fa-color-brand-600)', activeBg: 'var(--fa-color-brand-700)', activeName: 'var(--fa-color-white)', activePrice: 'var(--fa-color-brand-200)', nub: 'var(--fa-color-brand-100)', activeNub: 'var(--fa-color-brand-700)', border: 'var(--fa-color-brand-300)', activeBorder: 'var(--fa-color-brand-700)', }, unverified: { bg: 'var(--fa-color-neutral-100)', name: 'var(--fa-color-neutral-800)', price: 'var(--fa-color-neutral-500)', activeBg: 'var(--fa-color-neutral-700)', activeName: 'var(--fa-color-white)', activePrice: 'var(--fa-color-neutral-200)', nub: 'var(--fa-color-neutral-100)', activeNub: 'var(--fa-color-neutral-700)', border: 'var(--fa-color-neutral-300)', activeBorder: 'var(--fa-color-neutral-700)', }, } as const; // ─── Component ────────────────────────────────────────────────────────────── /** * Map marker pin for the FA design system. * * Two-line label marker showing provider name and starting package * price. Renders as a rounded pill with a downward nub pointing to * the exact map location. * * - **Line 1**: Provider name (bold, truncated) * - **Line 2**: "From $X" (smaller, secondary colour) — optional * * Visual distinction: * - **Verified** providers: warm brand palette (gold bg, copper text) * - **Unverified** providers: neutral grey palette * - **Active/selected**: inverted colours (dark bg, white text) + scale-up * * Designed for use as custom HTML markers in Mapbox GL / Google Maps. * Pure CSS — no canvas, no SVG dependency. * * Usage: * ```tsx * * {/* No price, unverified *\/} * * ``` */ export const MapPin = React.forwardRef( ({ name, price, priceLabel, verified = false, active = false, onClick, sx }, ref) => { const palette = verified ? colours.verified : colours.unverified; const hasPrice = price != null || priceLabel != null; const priceText = priceLabel ?? (price != null ? `From $${price.toLocaleString('en-AU')}` : undefined); const handleKeyDown = (e: React.KeyboardEvent) => { if ((e.key === 'Enter' || e.key === ' ') && onClick) { e.preventDefault(); onClick(e as unknown as React.MouseEvent); } }; return ( .MapPin-label': { outline: '2px solid var(--fa-color-interactive-focus)', outlineOffset: '2px', }, }, }, ...(Array.isArray(sx) ? sx : [sx]), ]} > {/* Label pill */} {/* Name */} t.typography.fontFamily, lineHeight: 1.3, color: active ? palette.activeName : palette.name, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '100%', transition: 'color 150ms ease-in-out', }} > {name} {/* Price line */} {hasPrice && ( t.typography.fontFamily, lineHeight: 1.2, color: active ? palette.activePrice : palette.price, whiteSpace: 'nowrap', transition: 'color 150ms ease-in-out', }} > {priceText} )} {/* Nub — downward pointer */} ); }, ); MapPin.displayName = 'MapPin'; export default MapPin;