Add Google Maps ProviderMap organism with clustering + popup flow
Introduces a full Google-Maps-backed provider map for the arrangement wizard's ProvidersStep. Clicking a pin morphs it into a MapPopup at the same coord; pins within 70px of each other collapse into a cluster (ceiling at zoom 13) that opens a ClusterPopup list on click. Row clicks pan + zoom the map to the provider and open their MapPopup. Map-background click routes through an exit transition that fades the popup out before reappearing the pin, via a matching fade-in keyframe on the atom markers. Key additions: - @vis.gl/react-google-maps + @googlemaps/markerclusterer deps - ClusterMarker atom (count badge; verified / unverified palettes) - ClusterPopup molecule (image-free rows; verified icon aligned to name; right-aligned "From $X" column; verified-first sort) - ProviderMap organism (APIProvider + Map + imperative AdvancedMarker layer via createRoot for clusterer compatibility) Component changes: - MapPin: promoted verified palette (brand-700); name now required; name-only and price-only variants dropped; active prop removed in favour of organism-level state; SVG nub with fill+stroke replaces the CSS border-triangle trick so the outline is continuous - MapPopup: `exiting` prop drives close animation; click events stop propagation so the map's onClick can't clear state mid-interaction - ProviderData type gains optional `coords`; demo fixtures populated with real NSW/QLD lat/lng for all 7 providers - ProvidersStep demo route wires ProviderMap into the mapPanel slot Memory: - docs/memory/component-registry updated (ClusterMarker, ClusterPopup, ProviderMap added; MapPin + MapPopup refined; MapCard retired) - docs/memory/session-log captures arc across 2026-04-21/22 and flags next-session work: ProvidersStep polish, mobile layout for list-map WizardLayout, and demo deploy Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,8 @@ duplicates) and MUST update it after completing one.
|
||||
| Card | done | elevated, outlined × default, compact, none padding × interactive × selected | card.borderRadius/padding/shadow/border/background, color.surface.raised/subtle/warm, color.border.default/brand, shadow.md/lg | Content container. Elevated (shadow) or outlined (border). Interactive adds hover bg fill + shadow lift. Selected adds brand border + warm bg. Three padding presets. |
|
||||
| Switch | done | bordered style × checked, unchecked, disabled | switch.track.width/height/borderRadius, switch.thumb.size, color.interactive.*, color.neutral.400 | Toggle for add-ons/options. Wraps MUI Switch. Bordered pill, brand.500 fill when active. From Parsons 1.0 Figma Style One. |
|
||||
| Radio | done | checked, unchecked, disabled | radio.size/dotSize, color.interactive.*, color.neutral.400 | Single-select option. Wraps MUI Radio. Brand.500 fill when selected. From Parsons 1.0 Figma. |
|
||||
| MapPin | done | name+price (two-line), name-only, price-only × verified, unverified × default, active | mapPin.paddingX/borderRadius/nub.size, color.brand.100-900, color.neutral.100-800 | Two-line label map marker: name (bold, truncated 180px) + "From $X" (centred, semibold). Name optional for price-only variant. Verified = brand palette, unverified = grey. Active inverts + scale. Pure CSS. role="button" + keyboard + focus ring. |
|
||||
| MapPin | done | name+price × verified, unverified | mapPin.paddingX/borderRadius/nub.size, color.brand-200/700, color.neutral-100-800 | Two-line label map marker: name (bold, required, truncated 180px) + "From $X" (centred, semibold). Verified = promoted brand palette (brand-700 bg, white text, brand-200 price). Unverified = neutral grey pill (neutral-100 bg, neutral-800 text). No active state — selection handled at the organism level (ProviderMap swaps pin → MapPopup on click). Pure CSS. role="button" + keyboard + focus ring. Name-only / price-only variants dropped in D042 (production providers always have both). |
|
||||
| ClusterMarker | done | count × verified (promoted), unverified (neutral) | color.brand-700, color.neutral-100-800, mapPin.nub.size | Circular 36px count badge for pin clusters. `hasVerified` flag drives the palette (if any provider in the cluster is verified, use the promoted brand-700 palette; else neutral grey). Same nub treatment + shadow tokens as MapPin for visual cohesion. Pure CSS + SVG. role="button" + keyboard + focus ring. Designed as the `render`-ed output of `@googlemaps/markerclusterer`. See D043. |
|
||||
| ColourToggle | planned | inactive, hover, active, locked × single, two-colour × desktop, mobile | | Circular colour swatch picker for products. Custom component. Deferred until product detail organisms. |
|
||||
| Slider | planned | single, range × desktop, mobile | | Price range filter. Wraps MUI Slider. Deferred until search/filtering molecules. |
|
||||
| Link | done | underline: hover/always/none × any MUI colour | color.text.brand (copper brand.600, 4.8:1), color.interactive.active | Navigation text link. Wraps MUI Link. Copper default, underline on hover, focus ring. |
|
||||
@@ -45,7 +46,7 @@ duplicates) and MUST update it after completing one.
|
||||
| FormField | planned | Input + Typography (label) + Typography (helper) | Standard form field with label and validation |
|
||||
| ProviderCard | done | Card + Typography + Badge + Tooltip | Provider listing card. Verified: image + logo (64px rounded rect) + "Verified" badge. Unverified: text-only with top accent bar. Capability badges with info icon + tooltip. Price split typography. No footer. 4 component tokens. |
|
||||
| VenueCard | done | Card + Typography | Venue listing card. Always has photo + location + capacity ("X guests") + price ("From $X"). No verification tiers, no logo, no badges. 3 component tokens. Critique: 33/40. |
|
||||
| MapCard | planned | Card + Typography + Badge | Compact horizontal map popup card. Deferred until map integration. |
|
||||
| ~~MapCard~~ | superseded | — | Retired 2026-04-21. Role (compact map popup card) fully covered by `MapPopup`. See D041. |
|
||||
| ServiceOption | done | Card (interactive, selected) + Typography | Selectable service option for arrangement flow. Heading + optional price (right-aligned) + optional description. role="radio" + aria-checked. Disabled state with opacity token. Maps to Figma ListItemPurchaseOption. |
|
||||
| SearchBar | done | Input + IconButton + Button | Search input with optional submit button. Enter-to-submit, progressive clear button, inline loading spinner. Guards empty submissions, refocuses after clear. role="search" landmark. Critique: 35/40. |
|
||||
| AddOnOption | done | Card (interactive, selected) + Typography + Switch | Toggleable add-on for arrangement flow extras. Heading + optional price + description + Switch. Click-anywhere toggle. Maps to Figma ListItemAddItem (2350:40658). |
|
||||
@@ -58,6 +59,7 @@ duplicates) and MUST update it after completing one.
|
||||
| ComparisonColumnCard | done | Card + Badge + Button + Divider + Typography + Tooltip + Link + StarRoundedIcon + VerifiedOutlinedIcon | Desktop column header card for ComparisonTable. Floating badge: **medium** (26px) filled brand + StarRoundedIcon for recommended; soft brand + VerifiedOutlinedIcon for verified. Provider name **wraps to 2 lines** (`WebkitLineClamp: 2`) in a reserved 36px minHeight slot bottom-aligned so 1-line names anchor with location/rating/price at a consistent baseline. Recommended card: 2px brand-600 border + warm `selected` Card state + inline VerifiedOutlinedIcon left of name. `pt: 5` (40px breathing above name), uniform regardless of verified/recommended. Remove link always renders as the same Link element (visibility-hidden when not applicable) so CTA+footer align across all cards. Per-column wrapper in ComparisonTable is `display: flex` with `flex: 1` passed to the card root so all cards stretch to row height. Extracted from ComparisonTable (2026-04-12). |
|
||||
| ComparisonTabCard | done | Card + Badge + Typography + StarRoundedIcon | Mobile tab rail card for ComparisonPage. Provider name + package name + price. Recommended badge in normal flow with negative margin overlap — **filled brand + StarRoundedIcon** (matches desktop ComparisonColumnCard treatment, size="small" at 14px icon). **Fixed 235px width** (was 210). Border `brand-600` when recommended (consistent with primary). No glow — uses standard `shadow-sm` like other cards. `pt: 3.5` inside card. Shared by V1 and V2 (extracted 2026-04-12). |
|
||||
| NearbyPackageCard | done | Card (outlined, interactive) + Typography + StarRoundedIcon + LocationOnOutlinedIcon | Compact card representing a package offered by a nearby verified provider — package name + price + provider + rating + location. Used in the "Similar packages from verified providers nearby" section of PackagesStep for unverified tiers. Click is a route change to that verified provider's PackagesStep with this package loaded. Extracted from UnverifiedPackageT2/T3 during 2026-04-17 consolidation. |
|
||||
| ClusterPopup | done | Paper + Typography + IconButton + ButtonBase + inline provider-rows (fixed verified-icon slot + name + location + rating) | Cluster list popup — appears when a cluster marker is clicked. Header bar ("N providers in this area" + close X), scrollable stack of image-free provider rows (reserved verified-icon slot so titles align across tiers, name in copper for verified / neutral for unverified, location + rating meta). Verified-first sort order. 320px wide, matches MapPopup's card + nub + drop-shadow. Click on a row calls `onSelectProvider(id)`; in the ProviderMap flow that pans+zooms the map to the provider's coords (zoom 15) and opens their single-provider popup — there's no back-to-list. See D043, D044. |
|
||||
|
||||
## Organisms
|
||||
|
||||
@@ -74,6 +76,7 @@ duplicates) and MUST update it after completing one.
|
||||
| ArrangementForm | planned | StepIndicator + ServiceSelector + AddOnOption + Button + Typography | Multi-step arrangement wizard. Deferred — build remaining atoms/molecules first. |
|
||||
| Navigation | done | AppBar + Link + IconButton + Button + Divider + Drawer | Responsive site header. Desktop: logo left, links right, optional CTA. Mobile: hamburger + drawer with nav items, CTA, help footer. Sticky, grey surface bg (surface.subtle). Real FA logo from brandassets/. Maps to Figma Main Nav (14:108) + Mobile Header (2391:41508). |
|
||||
| Footer | done | Link × n + Typography + Divider + Container + Grid | Light grey (surface.subtle) site footer — matches header. Logo + tagline + contact (phone/email) + link group columns + legal bar. Semantic HTML (footer, nav, ul). Critique: 38/40 (Excellent). |
|
||||
| ProviderMap | done | MapPin + ClusterMarker + MapPopup + ClusterPopup + `@vis.gl/react-google-maps` + `@googlemaps/markerclusterer` | Google Maps provider map with clustering. **Individual pins**: `MapPin` per provider — click morphs it into a `MapPopup` at the same coord. **Clusters**: pins within 70px of each other collapse into a `ClusterMarker` (count badge) *only while zoomed out at level 13 or below* — past that, every pin shows individually. Click a cluster → `ClusterPopup` list at the cluster centroid, verified-first. **Click a row → map pans + zooms to that provider's coords (zoom 15) and opens their `MapPopup`**; cluster state cleared (no back-to-list). Map-background click reverts to pins. Markers are rendered imperatively (via `createRoot` into AdvancedMarker content elements) so `markerclusterer` can group them; popup layer remains declarative React. `selectedProviderId` force-opens a popup for external selection. Subtle empty state when no API key or no providers have coords (no throw). API key read from `VITE_GOOGLE_MAPS_API_KEY`. Desktop-only for now; mobile map sheet deferred. See D041, D042, D043, D044. |
|
||||
|
||||
## Templates
|
||||
|
||||
|
||||
Reference in New Issue
Block a user