@alfadocs/ui-kit-debug 0.31.8 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/dist/_chunks/{agenda-card-DtGlQde1.js → agenda-card-CFyWSX7Z.js} +2 -2
  2. package/dist/_chunks/{agenda-card-DtGlQde1.js.map → agenda-card-CFyWSX7Z.js.map} +1 -1
  3. package/dist/_chunks/{agenda-tray-CXmlwt2K.js → agenda-tray-By_asPN9.js} +2 -2
  4. package/dist/_chunks/{agenda-tray-CXmlwt2K.js.map → agenda-tray-By_asPN9.js.map} +1 -1
  5. package/dist/_chunks/{badge-B9Cr6iEB.js → badge-zsf5i5bH.js} +9 -2
  6. package/dist/_chunks/badge-zsf5i5bH.js.map +1 -0
  7. package/dist/_chunks/{benefit-card-B86DH-PE.js → benefit-card-_Sc-MGha.js} +18 -8
  8. package/dist/_chunks/benefit-card-_Sc-MGha.js.map +1 -0
  9. package/dist/_chunks/{booking-A4o9xI2n.js → booking-h_kBZM6M.js} +24 -26
  10. package/dist/_chunks/{booking-A4o9xI2n.js.map → booking-h_kBZM6M.js.map} +1 -1
  11. package/dist/_chunks/{card-CNri9ssR.js → card-DPmk26CL.js} +2 -2
  12. package/dist/_chunks/{card-CNri9ssR.js.map → card-DPmk26CL.js.map} +1 -1
  13. package/dist/_chunks/{contact-card-Dos7Tley.js → contact-card-Cf8Ktyt3.js} +2 -2
  14. package/dist/_chunks/{contact-card-Dos7Tley.js.map → contact-card-Cf8Ktyt3.js.map} +1 -1
  15. package/dist/_chunks/{practice-profile-card-CfAMeTxQ.js → contact-profile-card-Ce-LIDU8.js} +128 -125
  16. package/dist/_chunks/contact-profile-card-Ce-LIDU8.js.map +1 -0
  17. package/dist/_chunks/{editable-currency-cell-renderer-YvTwkFrD.js → editable-currency-cell-renderer-B9VRSV_S.js} +2 -2
  18. package/dist/_chunks/{editable-currency-cell-renderer-YvTwkFrD.js.map → editable-currency-cell-renderer-B9VRSV_S.js.map} +1 -1
  19. package/dist/_chunks/{key-value-pair-CYE7NSpM.js → key-value-pair-CkQIb9EG.js} +42 -32
  20. package/dist/_chunks/key-value-pair-CkQIb9EG.js.map +1 -0
  21. package/dist/_chunks/{map-view-WEWqXzof.js → map-view-knHSNLoe.js} +140 -128
  22. package/dist/_chunks/{map-view-WEWqXzof.js.map → map-view-knHSNLoe.js.map} +1 -1
  23. package/dist/_chunks/{operator-hero-BsjE-kJF.js → operator-hero-7LiiP7zi.js} +4 -4
  24. package/dist/_chunks/operator-hero-7LiiP7zi.js.map +1 -0
  25. package/dist/_chunks/{patient-search-CSDru7QW.js → patient-search-CBq62kmL.js} +2 -2
  26. package/dist/_chunks/{patient-search-CSDru7QW.js.map → patient-search-CBq62kmL.js.map} +1 -1
  27. package/dist/_chunks/{practice-results-CIkAdwRm.js → practice-results-Bw5fJTYF.js} +4 -4
  28. package/dist/_chunks/practice-results-Bw5fJTYF.js.map +1 -0
  29. package/dist/_chunks/{reviews-panel-Cjys8G8K.js → reviews-panel-B-18RBSn.js} +39 -36
  30. package/dist/_chunks/reviews-panel-B-18RBSn.js.map +1 -0
  31. package/dist/_chunks/{stat-CYEx8sIR.js → stat-EC2Papj7.js} +14 -14
  32. package/dist/_chunks/stat-EC2Papj7.js.map +1 -0
  33. package/dist/_chunks/{timeline-BZC7qGdy.js → timeline-DQa5Tyz4.js} +2 -2
  34. package/dist/_chunks/{timeline-BZC7qGdy.js.map → timeline-DQa5Tyz4.js.map} +1 -1
  35. package/dist/_chunks/{workflow-map-DfpjDZHK.js → workflow-map-uSiHbOWQ.js} +4 -4
  36. package/dist/_chunks/{workflow-map-DfpjDZHK.js.map → workflow-map-uSiHbOWQ.js.map} +1 -1
  37. package/dist/agent-catalog.json +28 -28
  38. package/dist/components/agenda-card/index.js +1 -1
  39. package/dist/components/agenda-tray/index.js +1 -1
  40. package/dist/components/badge/badge.d.ts.map +1 -1
  41. package/dist/components/badge/index.js +1 -1
  42. package/dist/components/benefit-card/benefit-card.d.ts.map +1 -1
  43. package/dist/components/benefit-card/index.js +1 -1
  44. package/dist/components/booking/index.js +1 -1
  45. package/dist/components/card/card.d.ts.map +1 -1
  46. package/dist/components/card/index.js +1 -1
  47. package/dist/components/contact-card/index.js +1 -1
  48. package/dist/components/contact-profile-card/contact-profile-card.agent.d.ts +4 -0
  49. package/dist/components/contact-profile-card/contact-profile-card.agent.d.ts.map +1 -0
  50. package/dist/components/{practice-profile-card/practice-profile-card.d.ts → contact-profile-card/contact-profile-card.d.ts} +9 -9
  51. package/dist/components/contact-profile-card/contact-profile-card.d.ts.map +1 -0
  52. package/dist/components/contact-profile-card/index.d.ts +4 -0
  53. package/dist/components/contact-profile-card/index.d.ts.map +1 -0
  54. package/dist/components/contact-profile-card/index.js +8 -0
  55. package/dist/components/data-table/index.js +1 -1
  56. package/dist/components/index.d.ts +1 -1
  57. package/dist/components/index.d.ts.map +1 -1
  58. package/dist/components/key-value-pair/index.js +1 -1
  59. package/dist/components/key-value-pair/key-value-pair.d.ts.map +1 -1
  60. package/dist/components/map-view/index.js +1 -1
  61. package/dist/components/map-view/map-view.d.ts +1 -1
  62. package/dist/components/map-view/map-view.d.ts.map +1 -1
  63. package/dist/components/operator-hero/index.js +1 -1
  64. package/dist/components/operator-hero/operator-hero.d.ts +1 -1
  65. package/dist/components/patient-search/index.js +1 -1
  66. package/dist/components/practice-results/index.js +1 -1
  67. package/dist/components/practice-results/practice-results.d.ts.map +1 -1
  68. package/dist/components/reviews-panel/index.js +1 -1
  69. package/dist/components/reviews-panel/reviews-panel.d.ts.map +1 -1
  70. package/dist/components/stat/index.js +1 -1
  71. package/dist/components/stat/stat.d.ts.map +1 -1
  72. package/dist/components/timeline/index.js +1 -1
  73. package/dist/components/workflow/index.js +1 -1
  74. package/dist/i18n/locales/ar.d.ts +1 -1
  75. package/dist/i18n/locales/ar.js +2 -2
  76. package/dist/i18n/locales/ar.js.map +1 -1
  77. package/dist/i18n/locales/de.d.ts +1 -1
  78. package/dist/i18n/locales/de.js +2 -2
  79. package/dist/i18n/locales/de.js.map +1 -1
  80. package/dist/i18n/locales/el.d.ts +1 -1
  81. package/dist/i18n/locales/el.js +2 -2
  82. package/dist/i18n/locales/el.js.map +1 -1
  83. package/dist/i18n/locales/en.d.ts +1 -1
  84. package/dist/i18n/locales/en.js +2 -2
  85. package/dist/i18n/locales/en.js.map +1 -1
  86. package/dist/i18n/locales/es.d.ts +1 -1
  87. package/dist/i18n/locales/es.js +2 -2
  88. package/dist/i18n/locales/es.js.map +1 -1
  89. package/dist/i18n/locales/fr.d.ts +1 -1
  90. package/dist/i18n/locales/fr.js +2 -2
  91. package/dist/i18n/locales/fr.js.map +1 -1
  92. package/dist/i18n/locales/hi.d.ts +1 -1
  93. package/dist/i18n/locales/hi.js +2 -2
  94. package/dist/i18n/locales/hi.js.map +1 -1
  95. package/dist/i18n/locales/it.d.ts +1 -1
  96. package/dist/i18n/locales/it.js +2 -2
  97. package/dist/i18n/locales/it.js.map +1 -1
  98. package/dist/i18n/locales/ja.d.ts +1 -1
  99. package/dist/i18n/locales/ja.js +2 -2
  100. package/dist/i18n/locales/ja.js.map +1 -1
  101. package/dist/i18n/locales/nl.d.ts +1 -1
  102. package/dist/i18n/locales/nl.js +2 -2
  103. package/dist/i18n/locales/nl.js.map +1 -1
  104. package/dist/i18n/locales/pl.d.ts +1 -1
  105. package/dist/i18n/locales/pl.js +2 -2
  106. package/dist/i18n/locales/pl.js.map +1 -1
  107. package/dist/i18n/locales/pt.d.ts +1 -1
  108. package/dist/i18n/locales/pt.js +2 -2
  109. package/dist/i18n/locales/pt.js.map +1 -1
  110. package/dist/i18n/locales/ro.d.ts +1 -1
  111. package/dist/i18n/locales/ro.js +2 -2
  112. package/dist/i18n/locales/ro.js.map +1 -1
  113. package/dist/i18n/locales/ru.d.ts +1 -1
  114. package/dist/i18n/locales/ru.js +2 -2
  115. package/dist/i18n/locales/ru.js.map +1 -1
  116. package/dist/i18n/locales/sq.d.ts +1 -1
  117. package/dist/i18n/locales/sq.js +2 -2
  118. package/dist/i18n/locales/sq.js.map +1 -1
  119. package/dist/i18n/locales/sv.d.ts +1 -1
  120. package/dist/i18n/locales/sv.js +2 -2
  121. package/dist/i18n/locales/sv.js.map +1 -1
  122. package/dist/i18n/locales/tr.d.ts +1 -1
  123. package/dist/i18n/locales/tr.js +2 -2
  124. package/dist/i18n/locales/tr.js.map +1 -1
  125. package/dist/i18n/locales/zh.d.ts +1 -1
  126. package/dist/i18n/locales/zh.js +2 -2
  127. package/dist/i18n/locales/zh.js.map +1 -1
  128. package/dist/index.js +113 -113
  129. package/dist/locales/ar.json +1 -1
  130. package/dist/locales/de.json +1 -1
  131. package/dist/locales/el.json +1 -1
  132. package/dist/locales/en.json +1 -1
  133. package/dist/locales/es.json +1 -1
  134. package/dist/locales/fr.json +1 -1
  135. package/dist/locales/hi.json +1 -1
  136. package/dist/locales/it.json +1 -1
  137. package/dist/locales/ja.json +1 -1
  138. package/dist/locales/nl.json +1 -1
  139. package/dist/locales/pl.json +1 -1
  140. package/dist/locales/pt.json +1 -1
  141. package/dist/locales/ro.json +1 -1
  142. package/dist/locales/ru.json +1 -1
  143. package/dist/locales/sq.json +1 -1
  144. package/dist/locales/sv.json +1 -1
  145. package/dist/locales/tr.json +1 -1
  146. package/dist/locales/zh.json +1 -1
  147. package/dist/tokens.css +1 -1
  148. package/package.json +4 -4
  149. package/dist/_chunks/badge-B9Cr6iEB.js.map +0 -1
  150. package/dist/_chunks/benefit-card-B86DH-PE.js.map +0 -1
  151. package/dist/_chunks/key-value-pair-CYE7NSpM.js.map +0 -1
  152. package/dist/_chunks/operator-hero-BsjE-kJF.js.map +0 -1
  153. package/dist/_chunks/practice-profile-card-CfAMeTxQ.js.map +0 -1
  154. package/dist/_chunks/practice-results-CIkAdwRm.js.map +0 -1
  155. package/dist/_chunks/reviews-panel-Cjys8G8K.js.map +0 -1
  156. package/dist/_chunks/stat-CYEx8sIR.js.map +0 -1
  157. package/dist/components/practice-profile-card/index.d.ts +0 -4
  158. package/dist/components/practice-profile-card/index.d.ts.map +0 -1
  159. package/dist/components/practice-profile-card/index.js +0 -8
  160. package/dist/components/practice-profile-card/practice-profile-card.agent.d.ts +0 -4
  161. package/dist/components/practice-profile-card/practice-profile-card.agent.d.ts.map +0 -1
  162. package/dist/components/practice-profile-card/practice-profile-card.d.ts.map +0 -1
  163. /package/dist/components/{practice-profile-card → contact-profile-card}/index.js.map +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"practice-results-CIkAdwRm.js","sources":["../../node_modules/lucide-react/dist/esm/icons/map.js","../../src/components/practice-results/practice-results.agent.ts","../../src/components/practice-results/practice-results.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.8.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z\",\n key: \"169xi5\"\n }\n ],\n [\"path\", { d: \"M15 5.764v15\", key: \"1pn4in\" }],\n [\"path\", { d: \"M9 3.236v15\", key: \"1uimfh\" }]\n];\nconst Map = createLucideIcon(\"map\", __iconNode);\n\nexport { __iconNode, Map as default };\n//# sourceMappingURL=map.js.map\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — PracticeResults (0.31.0). */\n/* */\n/* PracticeResults is the SERP primitive — a three-way A/B-testable */\n/* surface (`split-list-map`, `map-first`, `rich-cards`). The adapter */\n/* exposes the active variant + read-only actions for scrolling a card */\n/* into view and focusing a pin. Selection state and filter values are */\n/* consumer-owned via `value` / `onChange` — never read here (the */\n/* result list itself is generally non-sensitive, but follows the same */\n/* pattern as Booking / PatientSearch for consistency). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { PracticeResultsHandle } from './practice-results';\n\nexport const practiceResultsAgent: AgentAdapter<PracticeResultsHandle> = {\n id: 'practice-results',\n capabilities: ['navigate', 'view_change'],\n state: {\n variant: {\n type: 'string',\n descriptionKey: 'ui.agent.practiceResults.state.variant',\n description:\n 'Active variant — one of `split-list-map`, `map-first`, `rich-cards`.',\n read: (handle) => handle.getVariant(),\n },\n },\n actions: {\n scroll_to_result: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.agent.practiceResults.actions.scrollToResult',\n description:\n 'Scroll the result card with the given id into the visible viewport.',\n invoke: (handle, args: { id: string }) => {\n handle.scrollToResult(args.id);\n },\n },\n focus_pin: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.agent.practiceResults.actions.focusPin',\n description:\n 'Highlight the map pin for the given result id (no-op on rich-cards).',\n invoke: (handle, args: { id: string }) => {\n handle.focusPin(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'practice-results',\n description: 'Marks the PracticeResults root region.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport {\n CalendarClock,\n ChevronDown,\n Languages,\n Map as MapIcon,\n MapPin,\n RefreshCw,\n ShieldCheck,\n Heart,\n} from 'lucide-react';\nimport { Alert } from '../alert/alert';\nimport { Avatar } from '../avatar/avatar';\nimport { Button } from '../button/button';\nimport { DropdownMenu } from '../dropdown-menu/dropdown-menu';\nimport { EmptyState } from '../empty-state/empty-state';\nimport {\n MapView,\n type MapMarker,\n type MapViewHandle,\n} from '../map-view/map-view';\nimport { Rating } from '../rating/rating';\nimport { Sheet } from '../sheet/sheet';\nimport { Skeleton } from '../skeleton/skeleton';\nimport { Spinner } from '../spinner/spinner';\nimport { Badge } from '../badge/badge';\nimport { useMediaQuery } from '../../hooks/use-media-query';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { practiceResultsAgent } from './practice-results.agent';\n\n/* -------------------------------------------------------------------- */\n/* PracticeResults */\n/* */\n/* SERP primitive — a three-way A/B-testable surface. One `variant` */\n/* prop selects between Doctolib-style split-list-map (default), */\n/* map-first (map dominant, list rail), and rich-cards (no live map, */\n/* each card carries a static thumbnail + distance + languages + */\n/* insurance + next-available CTA). Telemetry and the */\n/* `PracticeResultsValue` contract are identical across variants so */\n/* A/B comparison is like-for-like. */\n/* */\n/* The consumer owns the search backend (results array, totalCount, */\n/* bbox-driven re-queries via `onBoundsChange`, pagination via */\n/* `onLoadMore`) and the routing (`onResultSelect`). The kit owns */\n/* layout, filter/sort logic, pin↔card sync, accessibility, telemetry. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\nexport type PracticeResultsVariant =\n | 'split-list-map'\n | 'map-first'\n | 'rich-cards';\n\nexport type PracticeResultsSort =\n | 'distance'\n | 'rating'\n | 'next-available'\n | 'relevance';\n\n/**\n * Where the result-selection event originated.\n *\n * - `card` — patient clicked the card body. The kit's behaviour is\n * **sync only** (centre the map on the matching pin) — it does NOT\n * navigate. The consumer's `onResultSelect` handler should typically\n * no-op or augment with hover-like analytics for this source.\n * - `pin` — patient clicked a map pin. Sync-only (scroll list to the\n * matching card); no navigation.\n * - `open-button` — patient clicked the explicit \"Open\" CTA on a card.\n * This is the **only** source that should drive navigation. Booking-\n * website's consumer routes to `result.href` on this source value.\n */\nexport type PracticeResultsSelectSource = 'card' | 'pin' | 'open-button';\n\nexport interface PracticeResultsBounds {\n north: number;\n south: number;\n east: number;\n west: number;\n}\n\nexport interface PracticeResult {\n id: string;\n /** Stable URL for the practice or operator detail page. */\n href: string;\n name: string;\n /** Profession or specialty caption. */\n subtitle?: string;\n /** Avatar / logo URL. */\n imageUrl?: string;\n /** Practice location. Required by the two map variants. */\n location?: {\n lat: number;\n lng: number;\n /** Display string e.g. \"Via Brera 14, 20121 Milano\". */\n address?: string;\n /** Distance from search centre, in km. Rendered by rich-cards. */\n distanceKm?: number;\n };\n rating?: {\n value: number;\n count: number;\n };\n /** ISO 639-1 codes. Rendered as chips by rich-cards. */\n languages?: string[];\n /** Short brand names. Rendered as chips by rich-cards. */\n insurances?: string[];\n /** Anxious-patient friendly — renders an icon chip on rich-cards. */\n specializedInFearPatients?: boolean;\n /** Next available slot — rendered as inline CTA on rich-cards. */\n nextAvailableSlot?: {\n /** ISO datetime, e.g. \"2026-05-23T10:30:00+02:00\". */\n dateTime: string;\n /** Direct deep-link to the kit's Booking with this slot pre-selected. */\n bookingHref: string;\n };\n /** Opaque payload echoed back in onResultSelect. */\n __payload?: unknown;\n}\n\nexport interface PracticeResultsValue {\n /** Selected facets — keys match consumer's `facets[].key`. */\n facets?: Record<string, string[]>;\n /** Sort key. Default `'distance'` when searchCentre is set, otherwise `'relevance'`. */\n sort?: PracticeResultsSort;\n}\n\n/** Shared facet shape — same as PatientSearch. */\nexport interface FacetOption {\n value: string;\n label: string;\n}\n\nexport interface FacetGroup {\n key: string;\n label: string;\n /** Empty array = boolean toggle. Non-empty = dropdown. */\n options: FacetOption[];\n multi?: boolean;\n}\n\nexport interface PracticeResultsHandle {\n getVariant: () => PracticeResultsVariant;\n /** Scroll a specific result into view (useful for deep-link → highlight). */\n scrollToResult: (id: string) => void;\n /** Imperatively focus a pin on the map (no-op on rich-cards). */\n focusPin: (id: string) => void;\n}\n\nexport interface PracticeResultsProps\n extends\n Omit<\n ComponentPropsWithoutRef<'div'>,\n 'aria-label' | 'onChange' | 'onSelect' | 'results' | 'defaultValue'\n >,\n VariantProps<typeof rootVariants> {\n /** Default: 'split-list-map'. Pass to override (PostHog / dev-menu). */\n variant?: PracticeResultsVariant;\n\n // Data\n results: PracticeResult[];\n /** Total count for the \"{{count}} risultati\" header. May differ from results.length. */\n totalCount?: number;\n /** Visible search location — drives map centre + distance calcs. */\n searchCentre?: { lat: number; lng: number; label?: string };\n\n // Filters (same shape as PatientSearch)\n facets?: FacetGroup[];\n\n // Map config (used by split + map-first; ignored by rich-cards)\n googleMapsApiKey?: string;\n /** Initial map zoom. Default 13. */\n defaultZoom?: number;\n /** Fires when patient pans/zooms (split + map-first only). */\n onBoundsChange?: (bounds: PracticeResultsBounds) => void;\n /**\n * GDPR consent gate, forwarded to the embedded MapView. When `false`\n * (default) the kit renders the same consent placeholder MapView ships\n * by default — third-party scripts never load until the consumer\n * captures opt-in. Also gates the rich-cards static thumbnails (which\n * are still IP-disclosing third-party loads).\n */\n consentGranted?: boolean;\n /** Fires when the patient clicks the \"Load map\" CTA in the consent placeholder. */\n onConsentRequest?: () => void;\n /**\n * @default false\n * 0.31.4: bypass the embedded `<MapView>`'s GDPR consent placeholder\n * entirely. Use when the consumer's page already gates the Google\n * Maps loader script at a higher level (e.g. Usercentrics / OneTrust\n * / any CMP that decides per-cookie-category whether to load Google\n * Maps). Forwarded to every internal `<MapView>` instance —\n * `split-list-map`'s right column and `map-first`'s main canvas.\n * `rich-cards` static thumbnails are unaffected (they dropped the\n * consent gate in 0.31.3 — see CHANGELOG).\n *\n * When `true`, `consentGranted` and `onConsentRequest` are ignored:\n * the kit assumes the CMP at the page level has already made the\n * call and the map loader script is either present or properly\n * blocked. Setting both `consentGranted={false}` and\n * `skipConsentGate={true}` is well-defined — gate skipped, no\n * placeholder rendered.\n */\n skipConsentGate?: boolean;\n\n // Pagination\n /** Fires when the patient scrolls to the bottom of the list. */\n onLoadMore?: () => void;\n /** True while the next page is loading — kit shows a Spinner at the list tail. */\n loadingMore?: boolean;\n\n // State\n value: PracticeResultsValue;\n onChange: (next: PracticeResultsValue) => void;\n onResultSelect: (\n result: PracticeResult,\n source: PracticeResultsSelectSource,\n ) => void;\n\n // UX\n /**\n * Initial loading skeletons. Per SSR-first design, skeletons render\n * only when `results.length === 0 && loading === true` so a server-\n * rendered list isn't flashed away on hydration.\n */\n loading?: boolean;\n /** Custom empty-state slot. Defaults to the localised EmptyState. */\n emptyResultsSlot?: ReactNode;\n /** Custom error slot. Renders when `error` is truthy. */\n errorSlot?: ReactNode;\n /** Truthy = render the error slot. Pair with `onRetry` for the default slot. */\n error?: boolean;\n /** Retry handler for the default error slot's CTA. */\n onRetry?: () => void;\n /** Heading rendered above the results (e.g. \"I migliori Dentisti a Milano\"). */\n heading?: string;\n /** Intro text below the heading. */\n intro?: string;\n /**\n * Label for the per-card \"Open\" button. Defaults to\n * `t('practiceResults.card.open')` (\"Apri\" / \"Open\"). The Open button\n * is the **only** card affordance that fires `onResultSelect` with\n * `source: 'open-button'` — that's the source the consumer should\n * navigate on. Card-body clicks and pin clicks fire with `'card'` /\n * `'pin'` and are sync-only.\n */\n cardOpenButtonLabel?: string;\n\n // a11y\n 'aria-label'?: string;\n /** Agent-readiness instance id. */\n id?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* Constants */\n/* -------------------------------------------------------------------- */\n\nconst DEFAULT_ZOOM = 13;\n\nconst SORT_OPTIONS: readonly PracticeResultsSort[] = [\n 'distance',\n 'rating',\n 'next-available',\n 'relevance',\n] as const;\n\n/* -------------------------------------------------------------------- */\n/* Language label resolution */\n/* */\n/* The kit's i18n bundles ship `languages.<iso>` keys for the 18 */\n/* supported ISO codes (en / it / fr / de / es / pt / nl / pl / ru / */\n/* ja / zh / hi / tr / sq / sv / el / ro / ar) translated into each of */\n/* the 18 locales. Look up the active locale's translation; fall back */\n/* to the uppercase code for unknown / unbundled codes. */\n/* */\n/* Same helper used by PracticeProfileCard's inline resolver — keep the */\n/* fallback rules in sync if you touch one. */\n/* -------------------------------------------------------------------- */\n\nfunction resolveLanguageLabel(\n code: string,\n t: ReturnType<typeof useTranslation>['t'],\n i18n: ReturnType<typeof useTranslation>['i18n'],\n): string {\n const key = `languages.${code}`;\n return i18n.exists(key) ? t(key) : code.toUpperCase();\n}\n\n/* -------------------------------------------------------------------- */\n/* Telemetry */\n/* */\n/* Same `window.track` convention as PatientSearch / booking-website. */\n/* Soft-fails outside the browser and when no host has wired `track`. */\n/* -------------------------------------------------------------------- */\n\ninterface TrackFn {\n (event: string, payload?: Record<string, unknown>): void;\n}\n\nfunction track(event: string, payload?: Record<string, unknown>): void {\n if (typeof window === 'undefined') return;\n const fn = (window as unknown as { track?: TrackFn }).track;\n try {\n fn?.(event, payload);\n } catch {\n // Telemetry must never throw into the UI. Swallow.\n }\n}\n\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\n/**\n * Emit `serp_result_clicked` + invoke the consumer's `onResultSelect`\n * with the result's `position` in the currently-rendered (filtered +\n * sorted) list — needed for the PostHog funnel's position-bias analysis\n * (which rank gets clicked across variants).\n */\nfunction emitResultClick(\n results: PracticeResult[],\n id: string,\n source: PracticeResultsSelectSource,\n onResultSelect: PracticeResultsProps['onResultSelect'],\n): void {\n const position = results.findIndex((r) => r.id === id);\n if (position < 0) return;\n const result = results[position];\n if (!result) return;\n track('serp_result_clicked', { resultId: id, source, position });\n onResultSelect(result, source);\n}\n\n/** Haversine distance between two lat/lng points, in km. */\nfunction haversineKm(\n a: { lat: number; lng: number },\n b: { lat: number; lng: number },\n): number {\n const R = 6371; // earth radius in km\n const dLat = ((b.lat - a.lat) * Math.PI) / 180;\n const dLng = ((b.lng - a.lng) * Math.PI) / 180;\n const lat1 = (a.lat * Math.PI) / 180;\n const lat2 = (b.lat * Math.PI) / 180;\n const sinDLat = Math.sin(dLat / 2);\n const sinDLng = Math.sin(dLng / 2);\n const h =\n sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLng * sinDLng;\n return 2 * R * Math.asin(Math.min(1, Math.sqrt(h)));\n}\n\n/** Apply selected facets to the result list — client-side narrowing. */\nfunction applyFilters(\n results: PracticeResult[],\n facets: PracticeResultsValue['facets'],\n facetGroups: FacetGroup[],\n): PracticeResult[] {\n if (!facets || Object.keys(facets).length === 0) return results;\n return results.filter((result) => {\n for (const group of facetGroups) {\n const selected = facets[group.key];\n if (!selected || selected.length === 0) continue;\n if (group.options.length === 0) {\n // Boolean toggle — match the convention used by PatientSearch.\n // Selection of `['true']` means the consumer's data shape should\n // expose a boolean field on the result. The kit can't enumerate\n // every possible boolean key, so we treat the chip as a hint\n // the consumer's backend / pre-filter should honour. Locally,\n // we attempt a heuristic: result has the same key set to true.\n const value = (result as unknown as Record<string, unknown>)[group.key];\n if (value !== true) return false;\n continue;\n }\n const field =\n group.key === 'insurance'\n ? result.insurances\n : group.key === 'language'\n ? result.languages\n : (result as unknown as Record<string, unknown>)[group.key];\n if (!Array.isArray(field)) return false;\n const hit = group.multi\n ? selected.some((v) => field.includes(v))\n : field.includes(selected[0]);\n if (!hit) return false;\n }\n return true;\n });\n}\n\n/** Sort results by the active sort key. Mutation-free. */\nfunction applySort(\n results: PracticeResult[],\n sort: PracticeResultsSort,\n searchCentre?: { lat: number; lng: number },\n): PracticeResult[] {\n const decorated = results.map((result) => {\n const distanceKm = result.location\n ? (result.location.distanceKm ??\n (searchCentre ? haversineKm(searchCentre, result.location) : undefined))\n : undefined;\n return { result, distanceKm };\n });\n const compare = (\n a: { result: PracticeResult; distanceKm?: number },\n b: { result: PracticeResult; distanceKm?: number },\n ): number => {\n switch (sort) {\n case 'distance': {\n if (a.distanceKm == null && b.distanceKm == null) return 0;\n if (a.distanceKm == null) return 1;\n if (b.distanceKm == null) return -1;\n return a.distanceKm - b.distanceKm;\n }\n case 'rating': {\n const av = a.result.rating?.value ?? -1;\n const bv = b.result.rating?.value ?? -1;\n return bv - av;\n }\n case 'next-available': {\n const av = a.result.nextAvailableSlot?.dateTime ?? '';\n const bv = b.result.nextAvailableSlot?.dateTime ?? '';\n if (!av && !bv) return 0;\n if (!av) return 1;\n if (!bv) return -1;\n return av.localeCompare(bv);\n }\n case 'relevance':\n default:\n return 0;\n }\n };\n return [...decorated].sort(compare).map(({ result, distanceKm }) =>\n distanceKm != null && result.location\n ? {\n ...result,\n location: { ...result.location, distanceKm },\n }\n : result,\n );\n}\n\n/** Compute the bounding box of an array of lat/lng pairs. */\nfunction boundsForResults(\n results: PracticeResult[],\n): PracticeResultsBounds | undefined {\n let north = -Infinity;\n let south = Infinity;\n let east = -Infinity;\n let west = Infinity;\n let anyPoint = false;\n for (const result of results) {\n if (!result.location) continue;\n anyPoint = true;\n const { lat, lng } = result.location;\n if (lat > north) north = lat;\n if (lat < south) south = lat;\n if (lng > east) east = lng;\n if (lng < west) west = lng;\n }\n if (!anyPoint) return undefined;\n return { north, south, east, west };\n}\n\n/* -------------------------------------------------------------------- */\n/* Root CVA */\n/* -------------------------------------------------------------------- */\n\nconst rootVariants = cva(\n 'ds:flex ds:w-full ds:flex-col ds:text-[var(--foreground)] ds:gap-[var(--spacing-md)]',\n {\n variants: {\n variant: {\n 'split-list-map': '',\n 'map-first': '',\n 'rich-cards': '',\n },\n },\n defaultVariants: { variant: 'split-list-map' },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* HeaderBlock — heading + intro + totalCount summary */\n/* -------------------------------------------------------------------- */\n\ninterface HeaderBlockProps {\n heading?: string;\n intro?: string;\n totalCount?: number;\n searchCentreLabel?: string;\n}\n\nfunction HeaderBlock({\n heading,\n intro,\n totalCount,\n searchCentreLabel,\n}: HeaderBlockProps) {\n const { t } = useTranslation();\n if (!heading && !intro && totalCount == null) return null;\n const summary =\n totalCount != null\n ? searchCentreLabel\n ? t('practiceResults.results.heading', {\n count: totalCount,\n place: searchCentreLabel,\n })\n : t('practiceResults.results.summary', { count: totalCount })\n : undefined;\n // Plain <div> rather than <header>: the surrounding region landmark\n // already names the section, and <header> inside a landmark can trip\n // axe's `landmark-banner-is-top-level` when the rule's heuristic\n // walks the DOM. The visual rendering is unchanged.\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n {heading ? (\n <h2 className=\"type-title-section ds:m-0 ds:text-[var(--foreground)]\">\n {heading}\n </h2>\n ) : null}\n {intro ? (\n <p className=\"type-body ds:m-0 ds:text-[var(--muted-foreground)]\">\n {intro}\n </p>\n ) : null}\n {summary ? (\n <p\n className=\"type-body-sm ds:m-0 ds:text-[var(--muted-foreground)]\"\n aria-live=\"polite\"\n >\n {summary}\n </p>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* FilterChipRow */\n/* */\n/* Shared chip rail — same shape as PatientSearch's FacetChipRow, lifted */\n/* here so the SERP can carry the same set of filters without depending */\n/* on PatientSearch internals. */\n/* -------------------------------------------------------------------- */\n\nconst facetChipVariants = cva(\n [\n 'ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]',\n 'ds:min-h-[var(--min-target-size)]',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:pt-[var(--spacing-xs)] ds:pb-[var(--spacing-xs)]',\n 'ds:rounded-[var(--radius-full)]',\n 'type-label',\n 'ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n ].join(' '),\n {\n variants: {\n state: {\n idle: 'ds:bg-[var(--secondary)] ds:text-[var(--foreground)] ds:border ds:border-[color:var(--border)] ds:hover:bg-[color-mix(in_srgb,var(--primary)_8%,var(--secondary))]',\n active:\n 'ds:bg-[var(--primary)] ds:text-[var(--primary-foreground)] ds:border ds:border-transparent ds:font-[var(--font-weight-semibold)]',\n },\n },\n defaultVariants: { state: 'idle' },\n },\n);\n\nconst filterChipRowVariants = cva(\n 'ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]',\n {\n variants: {\n placement: {\n inline: '',\n // Floating chrome on map-first: soft `--popover` surface so chips\n // read over the map without losing contrast. Same surface as\n // dropdowns / sheets — themed and forced-colors safe.\n floating: [\n 'ds:rounded-[var(--radius-full)]',\n 'ds:bg-[var(--popover)] ds:shadow-[var(--shadow-md)]',\n 'ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)]',\n 'ds:pt-[var(--spacing-2xs)] ds:pb-[var(--spacing-2xs)]',\n ].join(' '),\n },\n },\n defaultVariants: { placement: 'inline' },\n },\n);\n\nconst clearFiltersButtonVariants = cva(\n [\n 'ds:ms-[var(--spacing-xs)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]',\n 'ds:min-h-[var(--min-target-size)] ds:ps-[var(--spacing-xs)] ds:pe-[var(--spacing-xs)]',\n 'type-label ds:text-[color:var(--muted-foreground)]',\n 'ds:rounded-[var(--radius-sm)]',\n 'ds:hover:text-[color:var(--foreground)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n ].join(' '),\n);\n\ninterface FilterChipRowProps {\n facets: FacetGroup[];\n value: PracticeResultsValue['facets'];\n onChange: (next: Record<string, string[]>) => void;\n /** Visual placement — floating overlay on map-first; inline elsewhere. */\n floating?: boolean;\n}\n\nfunction FilterChipRow({\n facets,\n value,\n onChange,\n floating,\n}: FilterChipRowProps) {\n const { t } = useTranslation();\n const selections = useMemo(() => value ?? {}, [value]);\n\n const hasAnyActive = useMemo(\n () => Object.values(selections).some((arr) => arr && arr.length > 0),\n [selections],\n );\n\n const updateFacet = (key: string, values: string[]) => {\n const next = { ...selections };\n if (values.length === 0) delete next[key];\n else next[key] = values;\n onChange(next);\n track('serp_filter_changed', { key, values });\n };\n\n const toggleBoolean = (key: string) => {\n const isActive = (selections[key]?.length ?? 0) > 0;\n updateFacet(key, isActive ? [] : ['true']);\n };\n\n const toggleOption = (group: FacetGroup, option: string) => {\n const current = selections[group.key] ?? [];\n const active = current.includes(option);\n let next: string[];\n if (group.multi) {\n next = active\n ? current.filter((v) => v !== option)\n : [...current, option];\n } else {\n next = active ? [] : [option];\n }\n updateFacet(group.key, next);\n };\n\n if (facets.length === 0) return null;\n\n return (\n <div\n data-component=\"practice-results-filters\"\n role=\"group\"\n aria-label={t('practiceResults.filters.label')}\n className={filterChipRowVariants({\n placement: floating ? 'floating' : 'inline',\n })}\n >\n {facets.map((group) => {\n const isBoolean = group.options.length === 0;\n const current = selections[group.key] ?? [];\n const isActive = current.length > 0;\n\n if (isBoolean) {\n return (\n <button\n key={group.key}\n type=\"button\"\n aria-pressed={isActive}\n aria-label={group.label}\n onClick={() => toggleBoolean(group.key)}\n className={facetChipVariants({\n state: isActive ? 'active' : 'idle',\n })}\n >\n <span>{group.label}</span>\n </button>\n );\n }\n\n const countSuffix = isActive ? ` (${current.length})` : '';\n return (\n <DropdownMenu.Root key={group.key}>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n aria-label={`${group.label}${countSuffix}`}\n className={facetChipVariants({\n state: isActive ? 'active' : 'idle',\n })}\n >\n <span>\n {group.label}\n {countSuffix}\n </span>\n <ChevronDown\n aria-hidden=\"true\"\n className=\"ds:size-3.5 ds:shrink-0\"\n />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Content sideOffset={8} align=\"start\">\n {group.options.map((option) => (\n <DropdownMenu.CheckboxItem\n key={option.value}\n checked={current.includes(option.value)}\n onSelect={(event) => {\n if (group.multi) event.preventDefault();\n toggleOption(group, option.value);\n }}\n >\n {option.label}\n </DropdownMenu.CheckboxItem>\n ))}\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n })}\n {hasAnyActive ? (\n <button\n type=\"button\"\n onClick={() => {\n onChange({});\n track('serp_filter_changed', { key: '*', values: [] });\n }}\n className={clearFiltersButtonVariants()}\n >\n {t('practiceResults.filters.clear')}\n </button>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* SortControl — DropdownMenu chip for the active sort key. */\n/* -------------------------------------------------------------------- */\n\ninterface SortControlProps {\n value: PracticeResultsSort;\n onChange: (next: PracticeResultsSort) => void;\n}\n\nfunction SortControl({ value, onChange }: SortControlProps) {\n const { t } = useTranslation();\n const labelFor = (sort: PracticeResultsSort): string => {\n switch (sort) {\n case 'distance':\n return t('practiceResults.sort.distance');\n case 'rating':\n return t('practiceResults.sort.rating');\n case 'next-available':\n return t('practiceResults.sort.nextAvailable');\n case 'relevance':\n default:\n return t('practiceResults.sort.relevance');\n }\n };\n return (\n <DropdownMenu.Root>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n aria-label={`${t('practiceResults.sort.label')}: ${labelFor(value)}`}\n className={facetChipVariants({ state: 'idle' })}\n >\n <span>\n {t('practiceResults.sort.label')}: {labelFor(value)}\n </span>\n <ChevronDown aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Content sideOffset={8} align=\"end\">\n <DropdownMenu.RadioGroup\n value={value}\n onValueChange={(next) => {\n const sort = next as PracticeResultsSort;\n if (sort !== value) {\n onChange(sort);\n track('serp_sort_changed', { sort });\n }\n }}\n >\n {SORT_OPTIONS.map((sort) => (\n <DropdownMenu.RadioItem key={sort} value={sort}>\n {labelFor(sort)}\n </DropdownMenu.RadioItem>\n ))}\n </DropdownMenu.RadioGroup>\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* DistanceChip — formatted \"{km} km\" or \"< 1 km\" reading. */\n/* -------------------------------------------------------------------- */\n\ninterface DistanceChipProps {\n distanceKm: number;\n}\n\nfunction DistanceChip({ distanceKm }: DistanceChipProps) {\n const { t, i18n } = useTranslation();\n const label = useMemo(() => {\n if (distanceKm < 1) return t('practiceResults.distance.below');\n const formatted = new Intl.NumberFormat(i18n.language, {\n maximumFractionDigits: 1,\n }).format(distanceKm);\n return t('practiceResults.distance.km', { distance: formatted });\n }, [distanceKm, t, i18n.language]);\n // 0.31.3: Tag → Badge for visual parity with the rest of the kit's\n // chip language (hero badges, practice-profile badges, PatientSearch\n // facet chips). Same `leading` + `variant` semantics — no consumer\n // API change, purely a primitive swap.\n return (\n <Badge variant=\"neutral\" leading={<MapPin aria-hidden=\"true\" />}>\n {label}\n </Badge>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* StaticMapThumb — Google Static Maps `<img>`. Consent-gated. */\n/* -------------------------------------------------------------------- */\n\ninterface StaticMapThumbProps {\n apiKey?: string;\n center: { lat: number; lng: number };\n zoom?: number;\n /** Address line shown in the tonal placeholder when no `apiKey` is\n * available. Renders next to a MapPin glyph as a multi-line block. */\n address?: string;\n}\n\nconst staticMapThumbVariants = cva(\n [\n 'ds:inline-size-[12rem] ds:block-size-[8rem] ds:shrink-0',\n 'ds:rounded-[var(--radius-md)] ds:overflow-hidden',\n ].join(' '),\n {\n variants: {\n state: {\n // Tonal address-block placeholder — renders the MapPin glyph\n // + address text on a `--muted` surface. Used when the consumer\n // hasn't provided `googleMapsApiKey`; carries the spatial cue\n // (location is here, even if we can't draw the map).\n placeholder:\n 'ds:flex ds:flex-col ds:items-start ds:justify-end ds:gap-[var(--spacing-2xs)] ds:bg-[var(--muted)] ds:text-[var(--muted-foreground)] ds:p-[var(--spacing-sm)]',\n image: 'ds:[object-fit:cover]',\n },\n },\n defaultVariants: { state: 'placeholder' },\n },\n);\n\n/**\n * Resolve the kit's `--map-marker-color` token to a six-character hex\n * string (no leading `#`) for Google Static Maps' `markers=color:` URL\n * parameter, which requires a literal hex value at fetch time.\n *\n * The token is defined as a direct hex literal in `src/tokens/index.css`\n * (no `var()` indirection), so `getPropertyValue` returns the hex\n * verbatim and we strip the `#`. SSR-safe: when `document` is undefined\n * we fall back to the kit's brand violet (`--color-violet-700`).\n */\nfunction getStaticMapMarkerHex(): string {\n if (typeof document === 'undefined') return '4945a3';\n const raw = getComputedStyle(document.documentElement)\n .getPropertyValue('--map-marker-color')\n .trim();\n const match = /^#?([0-9a-fA-F]{6})$/.exec(raw);\n return match ? match[1] : '4945a3';\n}\n\nfunction StaticMapThumb({\n apiKey,\n center,\n zoom = 14,\n address,\n}: StaticMapThumbProps) {\n const { t } = useTranslation();\n // 0.31.3: dropped the consent gate. Static maps are a single\n // one-shot `<img>` fetch, not a dynamic tile loader — they're\n // categorically different from the interactive MapView (which\n // still respects `consentGranted` on the same root). Without a\n // consumer-supplied `apiKey`, fall back to a TONAL ADDRESS BLOCK\n // (MapPin + address text on `--muted`), not a bare pin glyph —\n // the address carries the spatial cue even without the map.\n if (!apiKey) {\n return (\n <div\n aria-hidden={address ? undefined : 'true'}\n className={staticMapThumbVariants({ state: 'placeholder' })}\n >\n <MapPin aria-hidden=\"true\" className=\"ds:size-5 ds:shrink-0\" />\n {address ? (\n <span className=\"type-meta ds:[overflow-wrap:anywhere]\">\n {address}\n </span>\n ) : null}\n </div>\n );\n }\n // Static Maps URL. 240×160 keeps the asset under the 1MP free tier\n // and looks crisp on 2× displays at the rendered 12rem × 8rem cell.\n // Marker colour is sourced from the `--map-marker-color` token at\n // runtime so the value stays in `src/tokens/**` per the closed-palette\n // constraint (Google's URL parameter can't read a CSS custom prop).\n const markerHex = getStaticMapMarkerHex();\n const src =\n `https://maps.googleapis.com/maps/api/staticmap` +\n `?center=${center.lat},${center.lng}` +\n `&zoom=${zoom}` +\n `&size=240x160&scale=2` +\n `&markers=color:0x${markerHex}%7C${center.lat},${center.lng}` +\n `&key=${encodeURIComponent(apiKey)}`;\n return (\n <img\n src={src}\n alt={t('practiceResults.thumbnail.alt')}\n loading=\"lazy\"\n decoding=\"async\"\n width={240}\n height={160}\n className={staticMapThumbVariants({ state: 'image' })}\n />\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* ResultCardBase */\n/* */\n/* Compact card used by `split-list-map` left list and `map-first` rail. */\n/* Avatar + name + subtitle + rating + optional chip row. */\n/* -------------------------------------------------------------------- */\n\nconst resultCardBaseVariants = cva(\n [\n 'ds:relative ds:flex ds:w-full ds:cursor-pointer',\n 'ds:rounded-[var(--radius-lg)]',\n 'ds:bg-[var(--card)] ds:text-[var(--card-foreground)]',\n 'ds:border ds:border-[color:var(--card-border)]',\n 'ds:p-[var(--spacing-md)]',\n 'ds:gap-[var(--spacing-md)]',\n 'ds:transition-[box-shadow,border-color] ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:hover:shadow-[var(--shadow-md)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n // Use `data-highlighted` for the pin↔card sync visual so consumers\n // can override (e.g. dim siblings) without fighting the kit.\n 'ds:data-[highlighted=true]:border-[color:var(--primary)]',\n 'ds:data-[highlighted=true]:shadow-[var(--shadow-md)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n ].join(' '),\n {\n variants: {\n density: {\n compact: '',\n rich: 'ds:p-[var(--spacing-lg)] ds:gap-[var(--spacing-lg)]',\n },\n },\n defaultVariants: { density: 'compact' },\n },\n);\n\ninterface ResultCardBaseProps {\n result: PracticeResult;\n highlighted: boolean;\n /**\n * Card-body click handler. Fires `onResultSelect(result, 'card')` —\n * sync-only by the kit's contract. The consumer typically no-ops or\n * augments with analytics; do NOT navigate from this source.\n */\n onSelect: () => void;\n /**\n * Open-button click handler. Fires `onResultSelect(result,\n * 'open-button')` — the navigation primitive. The button is a real\n * anchor so middle-click / ctrl-click \"open in new tab\" work\n * natively.\n */\n onOpenClick: () => void;\n onHover: () => void;\n /** Used for layout density (compact vs rich). */\n density?: 'compact' | 'rich';\n /** Localised \"Open\" CTA label. */\n openLabel: string;\n /**\n * Pre-formatted next-available-slot text — when present, the kit\n * renders an inline \"Next: …\" caption + an inline secondary booking\n * CTA on the card. The string is locale-formatted by the parent\n * (which has access to `i18n.language`).\n */\n nextSlotText?: string;\n /** Truncate the insurance chip row at this many entries. */\n maxInsuranceChips?: number;\n /** Truncate the language chip row at this many entries. */\n maxLanguageChips?: number;\n children?: ReactNode;\n}\n\nfunction ResultCardBase({\n result,\n highlighted,\n onSelect,\n onOpenClick,\n onHover,\n density = 'compact',\n openLabel,\n nextSlotText,\n maxInsuranceChips = 3,\n maxLanguageChips = 4,\n children,\n}: ResultCardBaseProps) {\n const { t, i18n } = useTranslation();\n const ratingLabel = result.rating;\n const distanceKm = result.location?.distanceKm;\n const showLanguages = (result.languages?.length ?? 0) > 0;\n const showInsurances = (result.insurances?.length ?? 0) > 0;\n const showFearPatient = result.specializedInFearPatients;\n const showChipRow = showLanguages || showInsurances || showFearPatient;\n const languageChips = (result.languages ?? []).slice(0, maxLanguageChips);\n const languageOverflow =\n (result.languages?.length ?? 0) - languageChips.length;\n const insuranceChips = (result.insurances ?? []).slice(0, maxInsuranceChips);\n const insuranceOverflow =\n (result.insurances?.length ?? 0) - insuranceChips.length;\n // `role=\"group\"` — the card is a discoverable SR landmark grouping\n // the avatar / name / chips / CTAs. It is NOT a single interactive\n // control: axe's `nested-interactive` forbids focusable descendants\n // (the inner Open / next-slot anchors) inside an interactive parent.\n //\n // Mouse users get a click handler on the outer container — clicking\n // anywhere (except the inner anchors, which `stopPropagation`)\n // triggers the sync action (pan the map to the matching pin).\n // Keyboard users tab through the inner anchors directly; the Open\n // button is the canonical kbd path. This matches Doctolib's card\n // pattern.\n //\n // jsx-a11y/no-noninteractive-element-interactions + click-events-\n // have-key-events disabled here: the card is deliberately non-\n // interactive at the SR layer (role=\"group\") to avoid axe's\n // nested-interactive violation against the inner Open / next-slot\n // anchors. The onClick is a mouse-only sync affordance; keyboard\n // users get equivalent navigation via the inner anchors.\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events -- card is role=\"group\" by design (axe nested-interactive); click handler is mouse-only sync.\n <div\n data-component=\"practice-result-card\"\n data-result-id={result.id}\n data-highlighted={highlighted ? 'true' : 'false'}\n role=\"group\"\n aria-labelledby={`practice-result-${result.id}-name`}\n onClick={onSelect}\n onMouseEnter={onHover}\n onFocus={onHover}\n className={resultCardBaseVariants({ density })}\n >\n <Avatar src={result.imageUrl} name={result.name} size=\"lg\" />\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-xs)]\">\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span\n id={`practice-result-${result.id}-name`}\n className=\"type-title-card ds:text-[var(--foreground)]\"\n >\n {result.name}\n </span>\n {distanceKm != null ? (\n <DistanceChip distanceKm={distanceKm} />\n ) : null}\n </div>\n {result.subtitle ? (\n <span className=\"type-body-sm ds:text-[var(--muted-foreground)]\">\n {result.subtitle}\n </span>\n ) : null}\n {result.location?.address ? (\n <span className=\"type-meta ds:text-[var(--muted-foreground)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\">\n <MapPin aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n {result.location.address}\n </span>\n ) : null}\n </div>\n {ratingLabel && ratingLabel.count > 0 ? (\n <Rating\n value={ratingLabel.value}\n reviews={{ count: ratingLabel.count }}\n size=\"sm\"\n />\n ) : null}\n {showChipRow ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {languageChips.map((lang) => (\n <Badge\n key={`lang-${lang}`}\n variant=\"neutral\"\n leading={<Languages aria-hidden=\"true\" />}\n >\n {resolveLanguageLabel(lang, t, i18n)}\n </Badge>\n ))}\n {languageOverflow > 0 ? (\n <Badge variant=\"neutral\">{`+${languageOverflow}`}</Badge>\n ) : null}\n {insuranceChips.map((ins) => (\n <Badge\n key={`ins-${ins}`}\n variant=\"info\"\n leading={<ShieldCheck aria-hidden=\"true\" />}\n >\n {ins}\n </Badge>\n ))}\n {insuranceOverflow > 0 ? (\n <Badge variant=\"neutral\">{`+${insuranceOverflow}`}</Badge>\n ) : null}\n {showFearPatient ? (\n <Badge variant=\"info\" leading={<Heart aria-hidden=\"true\" />}>\n {t('practiceResults.fearPatient.label')}\n </Badge>\n ) : null}\n </div>\n ) : null}\n {result.nextAvailableSlot && nextSlotText ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <span className=\"type-body-sm ds:text-[var(--muted-foreground)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <CalendarClock\n aria-hidden=\"true\"\n className=\"ds:size-4 ds:text-[var(--primary)]\"\n />\n {t('practiceResults.nextSlot.summary', { time: nextSlotText })}\n </span>\n <Button\n intent=\"primary\"\n size=\"sm\"\n asChild\n onClick={(event) => {\n event.stopPropagation();\n }}\n >\n <a\n href={result.nextAvailableSlot.bookingHref}\n data-component=\"practice-result-next-slot-cta\"\n >\n {t('practiceResults.nextSlot.cta', { time: nextSlotText })}\n </a>\n </Button>\n </div>\n ) : null}\n {children}\n <div className=\"ds:mt-[var(--spacing-xs)] ds:flex ds:justify-end\">\n <Button\n intent=\"secondary\"\n size=\"sm\"\n asChild\n onClick={(event) => {\n // The Open button IS the navigation primitive; stop the\n // click from bubbling to the card-body's sync handler.\n event.stopPropagation();\n onOpenClick();\n }}\n >\n <a href={result.href} data-component=\"practice-result-open-cta\">\n {openLabel}\n </a>\n </Button>\n </div>\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* RichResultCard */\n/* */\n/* Full-fat card for `rich-cards`. Thumbnail map + distance + language + */\n/* insurance chips + next-slot CTA + anxious-patient badge. */\n/* -------------------------------------------------------------------- */\n\ninterface RichResultCardProps {\n result: PracticeResult;\n apiKey?: string;\n highlighted: boolean;\n onSelect: () => void;\n onOpenClick: () => void;\n onHover: () => void;\n openLabel: string;\n}\n\nfunction RichResultCard({\n result,\n apiKey,\n highlighted,\n onSelect,\n onOpenClick,\n onHover,\n openLabel,\n}: RichResultCardProps) {\n const { t, i18n } = useTranslation();\n const slotLabel = useMemo(() => {\n if (!result.nextAvailableSlot) return undefined;\n try {\n return new Intl.DateTimeFormat(i18n.language, {\n weekday: 'short',\n hour: 'numeric',\n minute: 'numeric',\n }).format(new Date(result.nextAvailableSlot.dateTime));\n } catch {\n return result.nextAvailableSlot.dateTime;\n }\n }, [result.nextAvailableSlot, i18n.language]);\n const showLanguages = (result.languages?.length ?? 0) > 0;\n const showInsurances = (result.insurances?.length ?? 0) > 0;\n const showFearPatient = result.specializedInFearPatients;\n const showChipRow = showLanguages || showInsurances || showFearPatient;\n const distanceKm = result.location?.distanceKm;\n\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events -- card is role=\"group\" by design (axe nested-interactive); click handler is mouse-only sync.\n <div\n data-component=\"practice-result-card\"\n data-result-id={result.id}\n data-density=\"rich\"\n data-highlighted={highlighted ? 'true' : 'false'}\n role=\"group\"\n aria-labelledby={`practice-result-${result.id}-name`}\n onClick={onSelect}\n onMouseEnter={onHover}\n onFocus={onHover}\n className={resultCardBaseVariants({ density: 'rich' })}\n >\n {result.location ? (\n <StaticMapThumb\n apiKey={apiKey}\n center={{\n lat: result.location.lat,\n lng: result.location.lng,\n }}\n address={result.location.address}\n />\n ) : null}\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-sm)]\">\n <div className=\"ds:flex ds:items-start ds:gap-[var(--spacing-md)]\">\n <Avatar src={result.imageUrl} name={result.name} size=\"md\" />\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span\n id={`practice-result-${result.id}-name`}\n className=\"type-title-card ds:text-[var(--foreground)]\"\n >\n {result.name}\n </span>\n {distanceKm != null ? (\n <DistanceChip distanceKm={distanceKm} />\n ) : null}\n </div>\n {result.subtitle ? (\n <span className=\"type-body-sm ds:text-[var(--muted-foreground)]\">\n {result.subtitle}\n </span>\n ) : null}\n {result.location?.address ? (\n <span className=\"type-meta ds:text-[var(--muted-foreground)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\">\n <MapPin\n aria-hidden=\"true\"\n className=\"ds:size-3.5 ds:shrink-0\"\n />\n {result.location.address}\n </span>\n ) : null}\n </div>\n {result.rating && result.rating.count > 0 ? (\n <Rating\n value={result.rating.value}\n reviews={{ count: result.rating.count }}\n size=\"md\"\n />\n ) : null}\n </div>\n {showChipRow ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {showLanguages\n ? (result.languages ?? []).map((lang) => (\n <Badge\n key={`lang-${lang}`}\n variant=\"neutral\"\n leading={<Languages aria-hidden=\"true\" />}\n >\n {resolveLanguageLabel(lang, t, i18n)}\n </Badge>\n ))\n : null}\n {showInsurances\n ? (result.insurances ?? []).map((ins) => (\n <Badge\n key={`ins-${ins}`}\n variant=\"info\"\n leading={<ShieldCheck aria-hidden=\"true\" />}\n >\n {ins}\n </Badge>\n ))\n : null}\n {showFearPatient ? (\n <Badge variant=\"info\" leading={<Heart aria-hidden=\"true\" />}>\n {t('practiceResults.fearPatient.label')}\n </Badge>\n ) : null}\n </div>\n ) : null}\n {result.nextAvailableSlot && slotLabel ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <span className=\"type-body-sm ds:text-[var(--muted-foreground)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <CalendarClock\n aria-hidden=\"true\"\n className=\"ds:size-4 ds:text-[var(--primary)]\"\n />\n {t('practiceResults.nextSlot.summary', { time: slotLabel })}\n </span>\n <Button\n intent=\"primary\"\n size=\"sm\"\n asChild\n onClick={(event) => {\n // The card itself is interactive; clicking the inline\n // CTA must not bubble up and re-fire `onResultSelect`.\n event.stopPropagation();\n }}\n >\n <a\n href={result.nextAvailableSlot.bookingHref}\n data-component=\"practice-result-next-slot-cta\"\n >\n {t('practiceResults.nextSlot.cta', { time: slotLabel })}\n </a>\n </Button>\n </div>\n ) : null}\n <div className=\"ds:mt-[var(--spacing-xs)] ds:flex ds:justify-end\">\n <Button\n intent=\"secondary\"\n size=\"sm\"\n asChild\n onClick={(event) => {\n event.stopPropagation();\n onOpenClick();\n }}\n >\n <a href={result.href} data-component=\"practice-result-open-cta\">\n {openLabel}\n </a>\n </Button>\n </div>\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* ResultsList — scroll container + infinite-scroll observer + skeletons. */\n/* -------------------------------------------------------------------- */\n\ninterface ResultsListProps {\n results: PracticeResult[];\n highlightedId: string | null;\n onCardHover: (id: string) => void;\n onCardSelect: (id: string) => void;\n onCardOpenClick: (id: string) => void;\n openLabel: string;\n /**\n * Formatter for the next-available-slot text — locale-formatted by\n * the variant body (which has `i18n.language` in scope). Returns\n * undefined when the result has no slot.\n */\n formatNextSlot: (result: PracticeResult) => string | undefined;\n onLoadMore?: () => void;\n loadingMore?: boolean;\n loadingSkeletons?: boolean;\n density?: 'compact' | 'rich';\n apiKey?: string;\n /** Ref to the scrollable container so the parent can imperatively\n * scroll a card into view (handle.scrollToResult). */\n containerRef?: React.RefObject<HTMLDivElement>;\n}\n\nfunction ResultsList({\n results,\n highlightedId,\n onCardHover,\n onCardSelect,\n onCardOpenClick,\n openLabel,\n formatNextSlot,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n density = 'compact',\n apiKey,\n containerRef,\n}: ResultsListProps) {\n const { t } = useTranslation();\n const sentinelRef = useRef<HTMLDivElement | null>(null);\n\n // IntersectionObserver-driven `onLoadMore`. The kit fires once per\n // \"intersection\" — the consumer is responsible for guarding against\n // double-fires while a request is in-flight (`loadingMore`).\n useEffect(() => {\n if (!onLoadMore) return;\n const node = sentinelRef.current;\n if (!node) return;\n if (typeof IntersectionObserver === 'undefined') return;\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n track('serp_load_more', {\n currentCount: results.length,\n });\n onLoadMore();\n }\n }\n },\n { root: null, rootMargin: '200px' },\n );\n observer.observe(node);\n return () => observer.disconnect();\n // `results.length` is a snapshot at observe-time — re-bind so the\n // telemetry payload reflects the count at firing.\n }, [onLoadMore, results.length]);\n\n if (loadingSkeletons) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-label={t('practiceResults.loading')}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n {Array.from({ length: 4 }).map((_, i) => (\n <Skeleton\n key={`pr-skel-${i}`}\n variant=\"rounded\"\n height={density === 'rich' ? '11rem' : '7rem'}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div\n ref={containerRef}\n data-component=\"practice-results-list\"\n role=\"list\"\n aria-label={t('practiceResults.list.label')}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:overflow-y-auto\"\n >\n {results.map((result) => (\n <div key={result.id} role=\"listitem\">\n {density === 'rich' ? (\n <RichResultCard\n result={result}\n apiKey={apiKey}\n highlighted={highlightedId === result.id}\n onSelect={() => onCardSelect(result.id)}\n onOpenClick={() => onCardOpenClick(result.id)}\n onHover={() => onCardHover(result.id)}\n openLabel={openLabel}\n />\n ) : (\n <ResultCardBase\n result={result}\n highlighted={highlightedId === result.id}\n onSelect={() => onCardSelect(result.id)}\n onOpenClick={() => onCardOpenClick(result.id)}\n onHover={() => onCardHover(result.id)}\n openLabel={openLabel}\n nextSlotText={formatNextSlot(result)}\n />\n )}\n </div>\n ))}\n {onLoadMore ? (\n <div\n ref={sentinelRef}\n aria-hidden=\"true\"\n className=\"ds:flex ds:items-center ds:justify-center ds:py-[var(--spacing-md)]\"\n >\n {loadingMore ? (\n <Spinner size=\"md\" variant=\"pulse\" />\n ) : (\n <span className=\"ds:size-1\" />\n )}\n </div>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* MapPanel — wraps MapView with markers + bounds-change tracking. */\n/* -------------------------------------------------------------------- */\n\ninterface MapPanelProps {\n results: PracticeResult[];\n searchCentre?: { lat: number; lng: number };\n zoom: number;\n apiKey?: string;\n highlightedId: string | null;\n onPinClick: (id: string) => void;\n onPinHover: (id: string) => void;\n /**\n * Fires once per pin per session (de-duped at the root). Use to emit\n * the `serp_result_hovered_pin` telemetry without re-firing on every\n * click of the same pin. The kit's MapView wrapper only surfaces\n * click events from vis.gl AdvancedMarker (no native hover), so this\n * approximates \"the patient noticed a pin\" rather than a true mouse\n * hover.\n */\n onPinFirstInteract: (id: string) => void;\n onBoundsChange?: (bounds: PracticeResultsBounds) => void;\n consentGranted?: boolean;\n onConsentRequest?: () => void;\n /**\n * Forwards the embedded MapView's imperative handle to the root so\n * card-body clicks can pan the map to the matching pin. Pass a\n * mutable ref from the variant body.\n */\n mapHandleRef?: React.MutableRefObject<MapViewHandle | null>;\n}\n\nfunction MapPanel({\n results,\n searchCentre,\n zoom,\n apiKey,\n highlightedId,\n onPinClick,\n onPinHover,\n onPinFirstInteract,\n onBoundsChange,\n consentGranted,\n onConsentRequest,\n mapHandleRef,\n}: MapPanelProps) {\n const { t } = useTranslation();\n // Filter to results that carry a location — pins-without-coordinates\n // would land at 0,0.\n const markers = useMemo<MapMarker[]>(\n () =>\n results\n .filter(\n (\n result,\n ): result is PracticeResult & {\n location: { lat: number; lng: number };\n } => Boolean(result.location),\n )\n .map((result) => ({\n id: result.id,\n position: {\n lat: result.location.lat,\n lng: result.location.lng,\n },\n label: result.name,\n })),\n [results],\n );\n\n // Auto-fit centre: prefer the explicit `searchCentre`; otherwise use\n // the centroid of all visible markers; otherwise fall back to a safe\n // default (Milano).\n const center = useMemo(() => {\n if (searchCentre) return searchCentre;\n const bounds = boundsForResults(results);\n if (bounds) {\n return {\n lat: (bounds.north + bounds.south) / 2,\n lng: (bounds.east + bounds.west) / 2,\n };\n }\n return { lat: 45.4642, lng: 9.19 };\n }, [results, searchCentre]);\n\n // `serp_map_panned` fires on bbox change. The MapView itself doesn't\n // surface a bounds callback yet — until it does, we wire a passive\n // hover-on-pin handler to surface the events the prompt asks for.\n // The consumer's `onBoundsChange` still has to be invoked when bounds\n // *do* arrive; for now, we surface a one-shot synthetic call once\n // markers settle so the consumer can begin its bbox-based search.\n useEffect(() => {\n if (!onBoundsChange) return;\n const bounds = boundsForResults(results);\n if (!bounds) return;\n onBoundsChange(bounds);\n track('serp_map_panned', { bounds });\n }, [results, onBoundsChange]);\n\n // Pin hover is impossible to wire through Google Maps' AdvancedMarker\n // wrapper today (vis.gl exposes click only). The kit's data-component\n // ring still lets a consumer DOM-snoop, but for SR users the pin row\n // can't drive a hover state. We expose a `focusPin` imperative handle\n // instead so deep-link → pin highlight works via React, not DOM\n // hover events.\n //\n // TODO(`data-component=\"practice-result-pin\"`): The 0.31.0 prompt asks\n // for a per-pin `data-component` marker so agents / consumers can\n // target individual pins. The MapView's AdvancedMarker wrapper\n // doesn't currently expose a custom-attribute prop — adding it\n // requires changes to `src/components/map-view/map-view.tsx`. Until\n // that lands, agents use `handle.focusPin(id)` for pin-level\n // imperative access.\n return (\n <div\n data-component=\"practice-results-map-panel\"\n className=\"ds:relative ds:flex ds:size-full ds:min-h-[24rem] ds:flex-col\"\n >\n <MapView\n ref={mapHandleRef}\n apiKey={apiKey ?? ''}\n center={center}\n zoom={zoom}\n markers={markers}\n selectedMarkerId={highlightedId}\n consentGranted={Boolean(consentGranted)}\n onConsentRequest={onConsentRequest}\n onMarkerClick={(id) => {\n if (id == null) return;\n onPinHover(id);\n onPinFirstInteract(id);\n onPinClick(id);\n }}\n ariaLabel={t('practiceResults.map.label')}\n size=\"lg\"\n surface=\"bordered\"\n className=\"ds:size-full\"\n />\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* SplitListMapBody — desktop two-column, mobile list + \"Vedi mappa\". */\n/* -------------------------------------------------------------------- */\n\ninterface VariantBodyProps {\n results: PracticeResult[];\n totalCount?: number;\n searchCentre?: PracticeResultsProps['searchCentre'];\n facets: FacetGroup[];\n value: PracticeResultsValue;\n onChange: (next: PracticeResultsValue) => void;\n onResultSelect: PracticeResultsProps['onResultSelect'];\n onLoadMore?: PracticeResultsProps['onLoadMore'];\n loadingMore?: boolean;\n apiKey?: string;\n zoom: number;\n consentGranted?: boolean;\n onConsentRequest?: () => void;\n onBoundsChange?: PracticeResultsProps['onBoundsChange'];\n loadingSkeletons: boolean;\n emptyResultsSlot?: ReactNode;\n errorSlot?: ReactNode;\n error?: boolean;\n onRetry?: () => void;\n heading?: string;\n intro?: string;\n /** Highlight + sync state. */\n highlightedId: string | null;\n onCardHover: (id: string) => void;\n onPinClick: (id: string) => void;\n onPinFirstInteract: (id: string) => void;\n /**\n * Card-body click: sync (pan map to pin if a map is mounted) +\n * emit `serp_result_clicked` source='card'. Does NOT navigate.\n */\n onCardSelect: (id: string) => void;\n /**\n * Open-button click: emit `serp_result_clicked` source='open-button'.\n * The button is a real `<a href>` so middle-click works; navigation\n * is the consumer's responsibility (typically via window.location).\n */\n onCardOpenClick: (id: string) => void;\n /** Localised \"Open\" CTA label, resolved once at the root. */\n openLabel: string;\n /** Locale-aware next-slot formatter. */\n formatNextSlot: (result: PracticeResult) => string | undefined;\n /** Imperative ref to the scrollable list container. */\n listContainerRef: React.RefObject<HTMLDivElement>;\n /** Imperative ref to the MapView — used by card-body click → panTo. */\n mapHandleRef: React.MutableRefObject<MapViewHandle | null>;\n}\n\nfunction HeaderRow({\n variantBodyProps,\n}: {\n variantBodyProps: VariantBodyProps;\n}) {\n const { facets, value, onChange, heading, intro, totalCount, searchCentre } =\n variantBodyProps;\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n <HeaderBlock\n heading={heading}\n intro={intro}\n totalCount={totalCount}\n searchCentreLabel={searchCentre?.label}\n />\n {facets.length > 0 ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <FilterChipRow\n facets={facets}\n value={value.facets}\n onChange={(next) => onChange({ ...value, facets: next })}\n />\n <SortControl\n value={value.sort ?? 'relevance'}\n onChange={(next) => onChange({ ...value, sort: next })}\n />\n </div>\n ) : (\n <div className=\"ds:flex ds:justify-end\">\n <SortControl\n value={value.sort ?? 'relevance'}\n onChange={(next) => onChange({ ...value, sort: next })}\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction DefaultEmpty() {\n const { t } = useTranslation();\n return (\n <EmptyState\n variant=\"no-results\"\n title={t('practiceResults.empty.title')}\n description={t('practiceResults.empty.description')}\n />\n );\n}\n\nfunction DefaultError({ onRetry }: { onRetry?: () => void }) {\n const { t } = useTranslation();\n return (\n <Alert variant=\"error\">\n <Alert.Title as=\"h3\">{t('practiceResults.error.title')}</Alert.Title>\n <Alert.Description>\n {t('practiceResults.error.description')}\n </Alert.Description>\n {onRetry ? (\n <Alert.Action>\n <Button\n intent=\"secondary\"\n size=\"sm\"\n startIcon={<RefreshCw aria-hidden=\"true\" />}\n onClick={onRetry}\n >\n {t('practiceResults.error.retry')}\n </Button>\n </Alert.Action>\n ) : null}\n </Alert>\n );\n}\n\nfunction SplitListMapBody(props: VariantBodyProps) {\n const {\n results,\n apiKey,\n zoom,\n searchCentre,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n highlightedId,\n onCardHover,\n onPinClick,\n consentGranted,\n onConsentRequest,\n onBoundsChange,\n listContainerRef,\n } = props;\n const { t } = useTranslation();\n const [mobileMapOpen, setMobileMapOpen] = useState(false);\n\n const body = error ? (\n (errorSlot ?? <DefaultError onRetry={onRetry} />)\n ) : results.length === 0 && !loadingSkeletons ? (\n (emptyResultsSlot ?? <DefaultEmpty />)\n ) : (\n <ResultsList\n results={results}\n highlightedId={highlightedId}\n onCardHover={onCardHover}\n onCardSelect={props.onCardSelect}\n onCardOpenClick={props.onCardOpenClick}\n openLabel={props.openLabel}\n formatNextSlot={props.formatNextSlot}\n onLoadMore={onLoadMore}\n loadingMore={loadingMore}\n loadingSkeletons={loadingSkeletons}\n density=\"compact\"\n containerRef={listContainerRef}\n />\n );\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <HeaderRow variantBodyProps={props} />\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:lg:grid ds:lg:grid-cols-[55%_45%] ds:lg:gap-[var(--spacing-lg)] ds:lg:items-start\">\n {/*\n 0.31.4: changed lg:overflow-hidden → lg:overflow-y-auto so the\n list column actually scrolls. The inner <ResultsList> has\n overflow-y-auto but it had no internal overflow to scroll\n against (its content height matched its natural height), so\n the height cap on the parent + the scroll affordance on the\n child were on different elements — browser couldn't render\n a scrollbar. Promoting the parent to be the scroll container\n fixes it.\n */}\n <div className=\"ds:min-w-0 ds:lg:max-h-[40rem] ds:lg:overflow-y-auto\">\n {body}\n </div>\n <div className=\"ds:hidden ds:lg:block ds:sticky ds:top-[var(--spacing-md)] ds:h-[40rem]\">\n <MapPanel\n results={results}\n searchCentre={searchCentre}\n zoom={zoom}\n apiKey={apiKey}\n highlightedId={highlightedId}\n onPinClick={(id) => {\n emitResultClick(results, id, 'pin', props.onResultSelect);\n onPinClick(id);\n }}\n onPinHover={onCardHover}\n onPinFirstInteract={props.onPinFirstInteract}\n onBoundsChange={onBoundsChange}\n consentGranted={consentGranted}\n onConsentRequest={onConsentRequest}\n mapHandleRef={props.mapHandleRef}\n />\n </div>\n </div>\n {/* Mobile: sticky \"Vedi mappa\" CTA — bottom-of-viewport. */}\n <div className=\"ds:flex ds:lg:hidden ds:sticky ds:bottom-[var(--spacing-md)] ds:justify-center\">\n <Button\n intent=\"primary\"\n size=\"md\"\n startIcon={<MapIcon aria-hidden=\"true\" />}\n onClick={() => setMobileMapOpen(true)}\n >\n {t('practiceResults.viewMap')}\n </Button>\n </div>\n <Sheet.Root open={mobileMapOpen} onOpenChange={setMobileMapOpen}>\n <Sheet.Content side=\"bottom\" size=\"lg\">\n <Sheet.Header>\n <Sheet.Title>{t('practiceResults.map.label')}</Sheet.Title>\n </Sheet.Header>\n <Sheet.Body>\n <Sheet.Description className=\"ds:sr-only\">\n {t('practiceResults.sheet.description')}\n </Sheet.Description>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <div className=\"ds:h-[20rem]\">\n <MapPanel\n results={results}\n searchCentre={searchCentre}\n zoom={zoom}\n apiKey={apiKey}\n highlightedId={highlightedId}\n onPinClick={(id) => {\n emitResultClick(results, id, 'pin', props.onResultSelect);\n onPinClick(id);\n }}\n onPinHover={onCardHover}\n onPinFirstInteract={props.onPinFirstInteract}\n onBoundsChange={onBoundsChange}\n consentGranted={consentGranted}\n onConsentRequest={onConsentRequest}\n mapHandleRef={props.mapHandleRef}\n />\n </div>\n {body}\n </div>\n </Sheet.Body>\n </Sheet.Content>\n </Sheet.Root>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* MapFirstBody — map dominant, list rail. */\n/* -------------------------------------------------------------------- */\n\ntype MapFirstSheetState = 'peek' | 'half' | 'full';\n\nconst SHEET_STATE_SIZE: Record<MapFirstSheetState, 'sm' | 'md' | 'lg'> = {\n peek: 'sm',\n half: 'md',\n full: 'lg',\n};\n\nconst SNAP_STATES: readonly MapFirstSheetState[] = [\n 'peek',\n 'half',\n 'full',\n] as const;\n\ninterface SheetSnapRadioGroupProps {\n value: MapFirstSheetState;\n onChange: (next: MapFirstSheetState) => void;\n}\n\n/**\n * WAI-ARIA radiogroup for the mobile bottom-sheet's snap state. Only\n * the checked option lives in the tab stop; arrow keys cycle through\n * options and update the checked state in one motion (per the radio\n * pattern). RTL inverts the horizontal arrow keys so ArrowRight always\n * means \"logical next\".\n */\nfunction SheetSnapRadioGroup({ value, onChange }: SheetSnapRadioGroupProps) {\n const { t, i18n } = useTranslation();\n const isRTL = i18n.dir() === 'rtl';\n const buttonRefs = useRef<Map<MapFirstSheetState, HTMLButtonElement>>(\n new Map(),\n );\n const focusAt = useCallback((state: MapFirstSheetState) => {\n buttonRefs.current.get(state)?.focus();\n }, []);\n const handleKeyDown = useCallback(\n (\n event: React.KeyboardEvent<HTMLButtonElement>,\n current: MapFirstSheetState,\n ) => {\n const idx = SNAP_STATES.indexOf(current);\n const total = SNAP_STATES.length;\n const forwardKey = isRTL ? 'ArrowLeft' : 'ArrowRight';\n const backwardKey = isRTL ? 'ArrowRight' : 'ArrowLeft';\n let next: MapFirstSheetState | null = null;\n switch (event.key) {\n case forwardKey:\n case 'ArrowDown':\n next = SNAP_STATES[(idx + 1) % total];\n break;\n case backwardKey:\n case 'ArrowUp':\n next = SNAP_STATES[(idx - 1 + total) % total];\n break;\n case 'Home':\n next = SNAP_STATES[0];\n break;\n case 'End':\n next = SNAP_STATES[total - 1];\n break;\n default:\n return;\n }\n event.preventDefault();\n if (next && next !== current) onChange(next);\n if (next) focusAt(next);\n },\n [focusAt, isRTL, onChange],\n );\n return (\n <div\n role=\"radiogroup\"\n aria-label={t('practiceResults.sheet.snap.label')}\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\"\n >\n {SNAP_STATES.map((state) => {\n const isChecked = value === state;\n return (\n <button\n key={state}\n ref={(node) => {\n if (node) buttonRefs.current.set(state, node);\n else buttonRefs.current.delete(state);\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isChecked}\n aria-label={t(`practiceResults.sheet.snap.${state}`)}\n tabIndex={isChecked ? 0 : -1}\n onClick={() => onChange(state)}\n onKeyDown={(event) => handleKeyDown(event, state)}\n className={facetChipVariants({\n state: isChecked ? 'active' : 'idle',\n })}\n >\n {t(`practiceResults.sheet.snap.${state}`)}\n </button>\n );\n })}\n </div>\n );\n}\n\nfunction MapFirstBody(props: VariantBodyProps) {\n const {\n results,\n apiKey,\n zoom,\n searchCentre,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n highlightedId,\n onCardHover,\n onPinClick,\n consentGranted,\n onConsentRequest,\n onBoundsChange,\n listContainerRef,\n facets,\n value,\n onChange,\n } = props;\n const { t } = useTranslation();\n // Bottom-sheet snap state. The kit's Sheet ships three discrete\n // size tokens (sm/md/lg) — until drag-snap lands as a primitive, we\n // cycle between them on the snap radiogroup. Default is `'half'` so\n // the map remains the dominant visual on mobile (per the followup\n // spec) — 0.31.0 defaulted to 'peek' which buried the map under a\n // covering sheet on small viewports.\n //\n // 0.31.1: gate the Sheet on viewport. Radix Dialog portals to\n // `document.body`, so the previous `lg:hidden` wrapper around the\n // Sheet trigger didn't stop the portal from rendering on desktop —\n // patients saw the mobile bottom-sheet covering the entire desktop\n // map. Conditional render guarded by `useMediaQuery` is the only\n // robust gate.\n const [sheetState, setSheetState] = useState<MapFirstSheetState>('half');\n const sheetSize = SHEET_STATE_SIZE[sheetState];\n const isDesktop = useMediaQuery('(min-width: 1024px)');\n\n // Rail cards on map-first use the `compact` density — at 35%\n // viewport width the rich layout's static thumbnail (12rem × 8rem)\n // would push the card content off-screen. Compact still surfaces\n // distance / languages / insurance / fear / next-slot per the new\n // card-density spec (item 3) — only the thumbnail is dropped.\n const listContent = error ? (\n (errorSlot ?? <DefaultError onRetry={onRetry} />)\n ) : results.length === 0 && !loadingSkeletons ? (\n (emptyResultsSlot ?? <DefaultEmpty />)\n ) : (\n <ResultsList\n results={results}\n highlightedId={highlightedId}\n onCardHover={onCardHover}\n onCardSelect={props.onCardSelect}\n onCardOpenClick={props.onCardOpenClick}\n openLabel={props.openLabel}\n formatNextSlot={props.formatNextSlot}\n onLoadMore={onLoadMore}\n loadingMore={loadingMore}\n loadingSkeletons={loadingSkeletons}\n density=\"compact\"\n apiKey={apiKey}\n containerRef={listContainerRef}\n />\n );\n\n const mapPanel = (\n <MapPanel\n results={results}\n searchCentre={searchCentre}\n zoom={zoom}\n apiKey={apiKey}\n highlightedId={highlightedId}\n onPinClick={(id) => {\n emitResultClick(results, id, 'pin', props.onResultSelect);\n onPinClick(id);\n }}\n onPinHover={onCardHover}\n onPinFirstInteract={props.onPinFirstInteract}\n onBoundsChange={onBoundsChange}\n consentGranted={consentGranted}\n onConsentRequest={onConsentRequest}\n mapHandleRef={props.mapHandleRef}\n />\n );\n\n const floatingFilters =\n facets.length > 0 ? (\n <div className=\"ds:absolute ds:start-[var(--spacing-md)] ds:top-[var(--spacing-md)] ds:max-w-[calc(100%-var(--spacing-lg))]\">\n <FilterChipRow\n facets={facets}\n value={value.facets}\n onChange={(next) => onChange({ ...value, facets: next })}\n floating\n />\n </div>\n ) : null;\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <HeaderBlock\n heading={props.heading}\n intro={props.intro}\n totalCount={props.totalCount}\n searchCentreLabel={searchCentre?.label}\n />\n {isDesktop ? (\n // Desktop: 65/35 map left + scrollable card rail right. NO Sheet\n // (the Sheet would portal to body and occlude the desktop\n // layout regardless of the parent's `lg:hidden` class).\n //\n // 0.31.4: map + rail cells fill the viewport (`calc(100dvh -\n // var(--practice-results-chrome, 8rem))`) instead of a fixed\n // 40rem. Map-first is a viewport-dominant variant — the map\n // should command the visible canvas, not float as a 640px\n // block in the middle of the page. Consumers with a different\n // page chrome height override via the CSS custom property:\n // .my-page-wrapper { --practice-results-chrome: 4rem; }\n // The default 8rem approximates a typical sticky header.\n // `dvh` (dynamic viewport height) plays nice with mobile-\n // chrome show/hide.\n <div className=\"ds:grid ds:grid-cols-[65%_35%] ds:gap-[var(--spacing-lg)] ds:items-start\">\n <div className=\"ds:relative ds:h-[calc(100dvh-var(--practice-results-chrome,8rem))] ds:min-h-[40rem]\">\n {mapPanel}\n {floatingFilters}\n </div>\n <div className=\"ds:min-w-0 ds:h-[calc(100dvh-var(--practice-results-chrome,8rem))] ds:min-h-[40rem] ds:overflow-y-auto\">\n {listContent}\n </div>\n </div>\n ) : (\n // Mobile: full-bleed map + persistent bottom sheet defaulting to\n // 'half' state. No X close button — the sheet is core to the\n // layout, not dismissible. Patient cycles peek / half / full\n // via the snap radiogroup at the top of the sheet body.\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <div className=\"ds:relative ds:h-[60vh] ds:min-h-[20rem]\">\n {mapPanel}\n {floatingFilters}\n </div>\n <Sheet.Root open={true}>\n <Sheet.Content\n side=\"bottom\"\n size={sheetSize}\n // The sheet is non-dismissible on map-first; intercept\n // every dismiss path (Escape, outside click, drag-down)\n // since the layout depends on it.\n onEscapeKeyDown={(event) => event.preventDefault()}\n onPointerDownOutside={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n >\n <Sheet.Body>\n <Sheet.Description className=\"ds:sr-only\">\n {t('practiceResults.sheet.descriptionMapFirst')}\n </Sheet.Description>\n {/* Custom header in place of Sheet.Header — drops the X\n close button which would be misleading here (the\n sheet is not dismissible). The title still uses\n Radix's `Sheet.Title` so a11y label resolution\n works. */}\n <div className=\"ds:flex ds:items-start ds:justify-between ds:gap-[var(--spacing-sm)] ds:pb-[var(--spacing-sm)]\">\n <Sheet.Title className=\"type-title-card ds:m-0 ds:text-[var(--foreground)]\">\n {t('practiceResults.sheet.title', {\n count: props.totalCount ?? results.length,\n })}\n </Sheet.Title>\n <SheetSnapRadioGroup\n value={sheetState}\n onChange={setSheetState}\n />\n </div>\n <div className=\"ds:pb-[var(--spacing-sm)]\">\n <SortControl\n value={value.sort ?? 'relevance'}\n onChange={(next) => onChange({ ...value, sort: next })}\n />\n </div>\n {listContent}\n </Sheet.Body>\n </Sheet.Content>\n </Sheet.Root>\n </div>\n )}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* RichCardsBody — vertical column of rich cards. */\n/* -------------------------------------------------------------------- */\n\nfunction RichCardsBody(props: VariantBodyProps) {\n const {\n results,\n apiKey,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n highlightedId,\n onCardHover,\n listContainerRef,\n } = props;\n\n const body = error ? (\n (errorSlot ?? <DefaultError onRetry={onRetry} />)\n ) : results.length === 0 && !loadingSkeletons ? (\n (emptyResultsSlot ?? <DefaultEmpty />)\n ) : (\n <ResultsList\n results={results}\n highlightedId={highlightedId}\n onCardHover={onCardHover}\n onCardSelect={props.onCardSelect}\n onCardOpenClick={props.onCardOpenClick}\n openLabel={props.openLabel}\n formatNextSlot={props.formatNextSlot}\n onLoadMore={onLoadMore}\n loadingMore={loadingMore}\n loadingSkeletons={loadingSkeletons}\n density=\"rich\"\n apiKey={apiKey}\n containerRef={listContainerRef}\n />\n );\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <HeaderRow variantBodyProps={props} />\n {body}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* PracticeResults */\n/* -------------------------------------------------------------------- */\n\nexport const PracticeResults = forwardRef<\n PracticeResultsHandle,\n PracticeResultsProps\n>(function PracticeResults(\n {\n variant = 'split-list-map',\n results,\n totalCount,\n searchCentre,\n facets,\n googleMapsApiKey,\n defaultZoom = DEFAULT_ZOOM,\n onBoundsChange,\n consentGranted,\n onConsentRequest,\n skipConsentGate = false,\n onLoadMore,\n loadingMore,\n value,\n onChange,\n onResultSelect,\n loading,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n heading,\n intro,\n cardOpenButtonLabel,\n 'aria-label': ariaLabel,\n id,\n className,\n ...rest\n },\n ref,\n) {\n const { t, i18n } = useTranslation();\n\n // Track which card / pin is currently highlighted for the pin↔card\n // sync visual. Lives at the root so hover on either side updates the\n // other.\n const [highlightedId, setHighlightedId] = useState<string | null>(null);\n // Once-per-card hover telemetry de-dupe. PostHog charges per event;\n // we don't want to flood it with re-hovers on the same card.\n const hoveredCardIdsRef = useRef<Set<string>>(new Set());\n const hoveredPinIdsRef = useRef<Set<string>>(new Set());\n\n // Imperative handle — `scrollToResult` finds the card by id within the\n // currently-rendered list and scrolls it into view; `focusPin` updates\n // the highlight state which feeds the embedded MapView's\n // `selectedMarkerId`.\n const listContainerRef = useRef<HTMLDivElement>(null);\n const rootRef = useRef<HTMLDivElement>(null);\n\n const handle = useMemo<PracticeResultsHandle>(\n () => ({\n getVariant: () => variant,\n scrollToResult: (resultId: string) => {\n const root = rootRef.current;\n if (!root) return;\n const card = root.querySelector<HTMLElement>(\n `[data-component=\"practice-result-card\"][data-result-id=\"${CSS.escape(resultId)}\"]`,\n );\n if (!card) return;\n card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n setHighlightedId(resultId);\n },\n focusPin: (resultId: string) => {\n setHighlightedId(resultId);\n },\n }),\n [variant],\n );\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(practiceResultsAgent, handle, id);\n\n // Resolved at the root so every variant body sees the same string\n // and i18n re-renders cascade once. Consumer can override via the\n // `cardOpenButtonLabel` prop.\n const openLabel = cardOpenButtonLabel ?? t('practiceResults.card.open');\n const formatNextSlot = useCallback(\n (result: PracticeResult): string | undefined => {\n if (!result.nextAvailableSlot) return undefined;\n try {\n return new Intl.DateTimeFormat(i18n.language, {\n weekday: 'short',\n hour: 'numeric',\n minute: 'numeric',\n }).format(new Date(result.nextAvailableSlot.dateTime));\n } catch {\n return result.nextAvailableSlot.dateTime;\n }\n },\n [i18n.language],\n );\n\n // `serp_variant_exposed` — fire once per variant assignment.\n const exposedRef = useRef<PracticeResultsVariant | null>(null);\n useEffect(() => {\n if (exposedRef.current === variant) return;\n exposedRef.current = variant;\n track('serp_variant_exposed', {\n variant,\n totalCount: totalCount ?? results.length,\n });\n // Reset per-card de-dupe so the new variant doesn't share state.\n hoveredCardIdsRef.current = new Set();\n hoveredPinIdsRef.current = new Set();\n // Intentionally omit totalCount / results.length so the event fires\n // once per variant, not once per render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [variant]);\n\n // `serp_empty_results` — fire when the visible (filtered) list drops\n // to zero. Compared against the most-recent emit so consecutive\n // empties don't flood telemetry.\n const lastEmptyEmitRef = useRef<boolean>(false);\n\n // Resolved values: facets default to []; sort defaults to 'distance'\n // when searchCentre is set, otherwise 'relevance'. `resolvedFacets`\n // is memoised so the `processedResults` useMemo below doesn't\n // re-invalidate on every render when the consumer passes `undefined`\n // and `?? []` allocates a fresh array.\n const resolvedFacets = useMemo(() => facets ?? [], [facets]);\n const resolvedSort = value.sort ?? (searchCentre ? 'distance' : 'relevance');\n\n // Apply filters + sort. The kit does this client-side so consumers\n // get the right ordering without re-querying — the consumer's backend\n // is still free to pre-sort, but the kit's `sort` switch never\n // requires a network round-trip.\n const processedResults = useMemo(() => {\n const filtered = applyFilters(results, value.facets, resolvedFacets);\n return applySort(filtered, resolvedSort, searchCentre);\n }, [results, value.facets, resolvedFacets, resolvedSort, searchCentre]);\n\n useEffect(() => {\n const isEmpty = processedResults.length === 0;\n if (isEmpty && !lastEmptyEmitRef.current) {\n track('serp_empty_results', {\n appliedFacets: value.facets ?? {},\n });\n lastEmptyEmitRef.current = true;\n } else if (!isEmpty) {\n lastEmptyEmitRef.current = false;\n }\n }, [processedResults.length, value.facets]);\n\n // Hover debounce — `serp_result_hovered_card` only fires after 500ms\n // of continuous hover on the same card AND only once per card per\n // session. Per the prompt's instrumentation spec — distinguishes\n // genuine intent signals from accidental drag-throughs.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastHoverIdRef = useRef<string | null>(null);\n useEffect(\n () => () => {\n if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);\n },\n [],\n );\n const onCardHover = useCallback((resultId: string) => {\n setHighlightedId(resultId);\n // De-dupe: already-tracked-this-session cards skip the debounce\n // entirely so we don't queue a no-op timer.\n if (hoveredCardIdsRef.current.has(resultId)) return;\n if (lastHoverIdRef.current === resultId && hoverTimerRef.current) {\n return;\n }\n if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);\n lastHoverIdRef.current = resultId;\n hoverTimerRef.current = setTimeout(() => {\n if (!hoveredCardIdsRef.current.has(resultId)) {\n hoveredCardIdsRef.current.add(resultId);\n track('serp_result_hovered_card', { resultId });\n }\n hoverTimerRef.current = null;\n }, 500);\n }, []);\n const onPinClick = useCallback((resultId: string) => {\n setHighlightedId(resultId);\n // Scroll the matching card into view (split-list-map / map-first\n // behaviour — rich-cards has no map so this is unreachable there).\n const root = rootRef.current;\n if (!root) return;\n const card = root.querySelector<HTMLElement>(\n `[data-component=\"practice-result-card\"][data-result-id=\"${CSS.escape(resultId)}\"]`,\n );\n card?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }, []);\n // Once-per-pin telemetry de-dupe. `onPinFirstInteract` fires on the\n // first marker click per pin per session; subsequent clicks update\n // the highlight but don't re-fire `serp_result_hovered_pin`.\n const onPinFirstInteract = useCallback((resultId: string) => {\n if (hoveredPinIdsRef.current.has(resultId)) return;\n hoveredPinIdsRef.current.add(resultId);\n track('serp_result_hovered_pin', { resultId });\n }, []);\n // Map imperative ref — populated by MapPanel via its MapView. Card-\n // body click pans the map to the matching pin (split-list-map +\n // map-first only; no-op on rich-cards since there's no live map).\n const mapHandleRef = useRef<MapViewHandle | null>(null);\n const onCardSelect = useCallback(\n (resultId: string) => {\n setHighlightedId(resultId);\n const result = processedResults.find((r) => r.id === resultId);\n if (!result) return;\n // Sync-only — pan the map to centre on the matching pin. The\n // existing pin-click flow already handles the inverse (pin →\n // scroll list to card).\n if (result.location) {\n const handle = mapHandleRef.current;\n handle?.panTo({\n lat: result.location.lat,\n lng: result.location.lng,\n });\n handle?.setZoom(16);\n }\n emitResultClick(processedResults, resultId, 'card', onResultSelect);\n },\n [processedResults, onResultSelect],\n );\n const onCardOpenClick = useCallback(\n (resultId: string) => {\n // Open is the only source that should drive navigation. The\n // underlying `<a href>` handles the native navigation; the kit\n // just fires telemetry + the consumer's hook.\n emitResultClick(\n processedResults,\n resultId,\n 'open-button',\n onResultSelect,\n );\n },\n [processedResults, onResultSelect],\n );\n\n // SSR-first: only render skeletons when the consumer is actively\n // loading AND has nothing to show. A server-rendered list keeps its\n // bones during hydration even if the consumer flips `loading` on its\n // background re-fetch.\n const loadingSkeletons = Boolean(loading) && results.length === 0;\n\n const bodyProps: VariantBodyProps = {\n results: processedResults,\n totalCount,\n searchCentre,\n facets: resolvedFacets,\n value: { ...value, sort: resolvedSort },\n onChange,\n onResultSelect,\n onLoadMore,\n loadingMore,\n apiKey: googleMapsApiKey,\n zoom: defaultZoom,\n // 0.31.4: when `skipConsentGate` is true the kit synthesises\n // `consentGranted=true` for every internal MapView; the consumer's\n // CMP at the page level is responsible for whether the Google Maps\n // loader script is present at all. `onConsentRequest` is dropped\n // because no placeholder renders — nothing to fire it from.\n consentGranted: skipConsentGate ? true : consentGranted,\n onConsentRequest: skipConsentGate ? undefined : onConsentRequest,\n onBoundsChange,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n heading,\n intro,\n highlightedId,\n onCardHover,\n onPinClick,\n onPinFirstInteract,\n onCardSelect,\n onCardOpenClick,\n openLabel,\n formatNextSlot,\n listContainerRef,\n mapHandleRef,\n };\n\n const body = (() => {\n switch (variant) {\n case 'map-first':\n return <MapFirstBody {...bodyProps} />;\n case 'rich-cards':\n return <RichCardsBody {...bodyProps} />;\n case 'split-list-map':\n default:\n return <SplitListMapBody {...bodyProps} />;\n }\n })();\n\n const resolvedAriaLabel = ariaLabel ?? t('practiceResults.regionLabel');\n\n return (\n <div\n ref={rootRef}\n role=\"region\"\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"practice-results\"\n data-component-id={id}\n data-variant={variant}\n className={rootVariants({ variant, className })}\n {...rest}\n >\n {body}\n </div>\n );\n});\n\nPracticeResults.displayName = 'PracticeResults';\n"],"names":["__iconNode","Map","createLucideIcon","practiceResultsAgent","handle","args","DEFAULT_ZOOM","SORT_OPTIONS","resolveLanguageLabel","code","t","i18n","key","track","event","payload","fn","emitResultClick","results","id","source","onResultSelect","position","r","result","haversineKm","a","b","dLat","dLng","lat1","lat2","sinDLat","sinDLng","h","applyFilters","facets","facetGroups","group","selected","field","v","applySort","sort","searchCentre","decorated","distanceKm","compare","av","_a","_b","_c","bv","_d","boundsForResults","north","south","east","west","anyPoint","lat","lng","rootVariants","cva","HeaderBlock","heading","intro","totalCount","searchCentreLabel","useTranslation","summary","jsxs","jsx","facetChipVariants","filterChipRowVariants","clearFiltersButtonVariants","FilterChipRow","value","onChange","floating","selections","useMemo","hasAnyActive","arr","updateFacet","values","next","toggleBoolean","isActive","toggleOption","option","current","active","isBoolean","countSuffix","DropdownMenu","ChevronDown","SortControl","labelFor","DistanceChip","label","formatted","Badge","MapPin","staticMapThumbVariants","getStaticMapMarkerHex","raw","match","StaticMapThumb","apiKey","center","zoom","address","markerHex","src","resultCardBaseVariants","ResultCardBase","highlighted","onSelect","onOpenClick","onHover","density","openLabel","nextSlotText","maxInsuranceChips","maxLanguageChips","children","ratingLabel","showLanguages","showInsurances","showFearPatient","showChipRow","languageChips","languageOverflow","insuranceChips","insuranceOverflow","_e","Avatar","_f","Rating","lang","Languages","ins","ShieldCheck","Heart","CalendarClock","Button","RichResultCard","slotLabel","ResultsList","highlightedId","onCardHover","onCardSelect","onCardOpenClick","formatNextSlot","onLoadMore","loadingMore","loadingSkeletons","containerRef","sentinelRef","useRef","useEffect","node","observer","entries","entry","_","i","Skeleton","Spinner","MapPanel","onPinClick","onPinHover","onPinFirstInteract","onBoundsChange","consentGranted","onConsentRequest","mapHandleRef","markers","bounds","MapView","HeaderRow","variantBodyProps","DefaultEmpty","EmptyState","DefaultError","onRetry","Alert","RefreshCw","SplitListMapBody","props","emptyResultsSlot","errorSlot","error","listContainerRef","mobileMapOpen","setMobileMapOpen","useState","body","MapIcon","Sheet","SHEET_STATE_SIZE","SNAP_STATES","SheetSnapRadioGroup","isRTL","buttonRefs","focusAt","useCallback","state","handleKeyDown","idx","total","forwardKey","backwardKey","isChecked","MapFirstBody","sheetState","setSheetState","sheetSize","isDesktop","useMediaQuery","listContent","mapPanel","floatingFilters","RichCardsBody","PracticeResults","forwardRef","variant","googleMapsApiKey","defaultZoom","skipConsentGate","loading","cardOpenButtonLabel","ariaLabel","className","rest","ref","setHighlightedId","hoveredCardIdsRef","hoveredPinIdsRef","rootRef","resultId","root","card","useImperativeHandle","useAgentRegistration","exposedRef","lastEmptyEmitRef","resolvedFacets","resolvedSort","processedResults","filtered","isEmpty","hoverTimerRef","lastHoverIdRef","bodyProps","resolvedAriaLabel"],"mappings":"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","x_google_ignoreList":[0]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reviews-panel-Cjys8G8K.js","sources":["../../src/components/reviews-panel/reviews-panel.agent.ts","../../src/components/reviews-panel/reviews-panel.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — ReviewsPanel. */\n/* */\n/* Replaces the legacy patient-reviews Carousel with a single panel that */\n/* surfaces an aggregate score, a star-bucket filter, and a vertical list */\n/* of compact review cards. The adapter exposes the active filter and the */\n/* visible review count so a host agent can drive the panel and observe */\n/* what the user actually sees after the filter is applied. The reviews */\n/* themselves remain owned by the consumer — they never flow through the */\n/* adapter (PHI: pseudonyms + free-text comments stay on the page). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { ReviewFilter, ReviewsPanelHandle } from './reviews-panel';\n\nexport const reviewsPanelAgent: AgentAdapter<ReviewsPanelHandle> = {\n id: 'reviews-panel',\n capabilities: ['filter'],\n state: {\n filter: {\n type: 'string',\n descriptionKey: 'ui.agent.reviewsPanel.state.filter',\n description:\n 'Active star-bucket filter — `\"all\"` or an integer 1..5. Reads the controlled `filter` prop.',\n read: (handle) => handle.getFilter(),\n },\n visibleCount: {\n type: 'number',\n descriptionKey: 'ui.agent.reviewsPanel.state.visibleCount',\n description:\n 'Number of reviews currently rendered after the filter is applied.',\n read: (handle) => handle.getVisibleCount(),\n },\n },\n actions: {\n set_filter: {\n safety: 'read',\n argsType: '{ filter: \"all\" | 1 | 2 | 3 | 4 | 5 }',\n descriptionKey: 'ui.agent.reviewsPanel.actions.setFilter',\n description:\n 'Narrow the visible review list to a star bucket, or `\"all\"` to clear the filter.',\n invoke: (handle, args: { filter: ReviewFilter }) => {\n handle.setFilter(args.filter);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'reviews-panel',\n description: 'Marks the ReviewsPanel root region.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { ChevronDown } from 'lucide-react';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { reviewsPanelAgent } from './reviews-panel.agent';\nimport { Rating } from '../rating/rating';\nimport { Card } from '../card/card';\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from '../collapsible/collapsible';\nimport { DescriptionList } from '../description-list/description-list';\nimport { EmptyState } from '../empty-state/empty-state';\nimport { Skeleton } from '../skeleton/skeleton';\nimport { Button } from '../button/button';\n\n/* -------------------------------------------------------------------- */\n/* ReviewsPanel */\n/* */\n/* Replaces the legacy patient-reviews Carousel with a two-column panel */\n/* — a star-bucket filter sidebar + a vertical list of compact review */\n/* cards — that surfaces the aggregate score, the volume, and (via a */\n/* per-card Collapsible) the rich per-axis breakdown alfaDocs already */\n/* collects but the carousel buried. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\nexport interface ReviewItem {\n id: string;\n /** When the review was authored (ISO 8601). */\n authoredAt: string;\n /** Anonymised author display (\"Marco T.\", \"Anna_M\"). Falls back to a localised \"Anonymous\". */\n pseudonym?: string;\n /** 0..max — drives the visible star summary on the compact card. */\n overallScore: number;\n /** Short free-text comment shown on the compact card. */\n comment?: string;\n /** Practice reply (revealed in the expanded panel only). */\n reply?: string;\n /** Optional per-axis breakdown — when present, the card grows an\n * \"Expand\" affordance that reveals these in a DescriptionList. */\n breakdown?: {\n appointmentScore?: number;\n communicationScore?: number;\n staffScore?: number;\n overallScore?: number;\n };\n /** Optional categorical follow-ups, surfaced inside the breakdown. */\n futureVisit?: 'yes' | 'no' | 'unsure';\n recommendation?: 'yes' | 'no' | 'unsure';\n}\n\nexport interface ReviewsAggregate {\n /** Total review count across all star buckets (may exceed the loaded set when paginated). */\n count: number;\n /** Average score across the full population (drives the header `Rating`). */\n averageScore: number;\n /**\n * Optional histogram — per-bucket count keyed by integer star value 1..5.\n * When omitted, the panel derives buckets from the loaded `reviews` array\n * (best-effort: paginated consumers SHOULD provide this explicitly).\n */\n histogram?: Record<1 | 2 | 3 | 4 | 5, number>;\n}\n\nexport type ReviewFilter = 'all' | 1 | 2 | 3 | 4 | 5;\n\nexport interface ReviewsPanelHandle {\n getFilter: () => ReviewFilter;\n setFilter: (next: ReviewFilter) => void;\n /** Total visible after filter is applied. */\n getVisibleCount: () => number;\n}\n\nexport interface ReviewsPanelProps extends Omit<\n ComponentPropsWithoutRef<'section'>,\n 'aria-label' | 'children'\n> {\n reviews: ReviewItem[];\n aggregate: ReviewsAggregate;\n\n /**\n * Active star filter. Controlled — consumer owns it for shareability via\n * URL query strings. Omit for uncontrolled (panel-managed) filtering.\n */\n filter?: ReviewFilter;\n onFilterChange?: (next: ReviewFilter) => void;\n\n /**\n * Optional intro callout shown above the list (e.g. \"Riepilogo delle\n * opinioni — i pazienti raccontano…\"). Render-prop slot so the consumer\n * can drop in `<Alert>` / `<Callout>` / plain `<p>`.\n */\n introSlot?: ReactNode;\n\n /** Optional header right-end action (e.g. a \"Leave a review\" button). */\n headerActionSlot?: ReactNode;\n\n /** Override the default region aria-label. */\n 'aria-label'?: string;\n\n /** Agent-readiness id. */\n id?: string;\n\n className?: string;\n\n /**\n * Hide the filter sidebar — useful when the panel mounts under ~640px\n * width or inside a narrow column. Defaults to false.\n */\n hideFilter?: boolean;\n\n /**\n * @default 'flat'\n * `'elevated'` wraps the panel in the equivalent of `Card variant=\"elevated\"`\n * — same tokens (`--card`, `--card-border`, `--shadow-card`,\n * `--radius-lg`), same padding. Use to drop the consumer-side\n * `<Card><Card.Body>` wrap.\n */\n surface?: 'flat' | 'elevated';\n\n /** Render skeletons for the header and the list. */\n loading?: boolean;\n}\n\n/* -------------------------------------------------------------------- */\n/* Constants & helpers */\n/* -------------------------------------------------------------------- */\n\nconst FILTER_VALUES: readonly ReviewFilter[] = ['all', 5, 4, 3, 2, 1] as const;\n\nfunction bucketOf(score: number): 1 | 2 | 3 | 4 | 5 {\n const n = Math.min(5, Math.max(1, Math.floor(score)));\n return n as 1 | 2 | 3 | 4 | 5;\n}\n\nfunction deriveHistogram(\n reviews: ReviewItem[],\n): Record<1 | 2 | 3 | 4 | 5, number> {\n const out: Record<1 | 2 | 3 | 4 | 5, number> = {\n 1: 0,\n 2: 0,\n 3: 0,\n 4: 0,\n 5: 0,\n };\n for (const r of reviews) out[bucketOf(r.overallScore)] += 1;\n return out;\n}\n\n/* -------------------------------------------------------------------- */\n/* FilterSidebar — hand-rolled radiogroup with roving tabindex */\n/* */\n/* Mirrors `ServiceGrid` in `booking.tsx`: the WAI-ARIA RadioGroup */\n/* pattern with full-row click target + icon column. NOT Radix */\n/* `RadioGroup` because we need a row layout with a non-text leading */\n/* glyph (the star ramp) and a trailing count. */\n/* */\n/* Activation is automatic — Arrow keys move BOTH focus and selection, */\n/* so a keyboard user feels the same responsiveness as a mouse user. */\n/* That's the right call here because filter activation is cheap (re- */\n/* renders the visible list); contrast with the booking ServiceGrid */\n/* where activation cascades through downstream selection state. */\n/* -------------------------------------------------------------------- */\n\nconst filterRowVariants = cva(\n [\n 'ds:relative ds:flex ds:w-full ds:items-center ds:justify-between',\n 'ds:gap-[var(--spacing-sm)]',\n 'ds:min-h-[var(--min-target-size)]',\n 'ds:rounded-[var(--radius-md)]',\n 'ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)]',\n 'ds:pt-[var(--spacing-2xs)] ds:pb-[var(--spacing-2xs)]',\n 'ds:text-start',\n 'ds:transition-[background-color,color]',\n 'ds:duration-[var(--animation-duration)]',\n 'ds:motion-reduce:transition-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)]',\n 'ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[var(--ring)]',\n 'ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n 'ds:cursor-pointer',\n ].join(' '),\n {\n variants: {\n state: {\n idle: 'ds:hover:bg-[var(--muted)]/30 ds:text-[var(--foreground)]',\n selected: [\n 'ds:bg-[var(--primary)]',\n 'ds:text-[var(--primary-foreground)]',\n 'ds:font-[var(--font-weight-semibold)]',\n ].join(' '),\n },\n },\n defaultVariants: { state: 'idle' },\n },\n);\n\ninterface FilterSidebarProps {\n filter: ReviewFilter;\n histogram: Record<1 | 2 | 3 | 4 | 5, number>;\n totalCount: number;\n onChange: (next: ReviewFilter) => void;\n}\n\nfunction FilterSidebar({\n filter,\n histogram,\n totalCount,\n onChange,\n}: FilterSidebarProps) {\n const { t } = useTranslation();\n const labelId = useId();\n\n // Roving tabindex tracks where keyboard focus is. We use AUTOMATIC\n // activation: arrow keys move focus AND fire onChange (see the rationale\n // above the component). To keep the visible aria-checked row and the\n // tab-stop aligned when the parent changes `filter` externally (e.g.\n // from a `?filter=4` URL parameter on page load), sync focusIndex to\n // the current filter via the effect below — without it, the tab stop\n // could land on `All` while the highlight shows `4`.\n const [focusIndex, setFocusIndex] = useState<number>(() =>\n Math.max(0, FILTER_VALUES.indexOf(filter)),\n );\n useEffect(() => {\n const i = FILTER_VALUES.indexOf(filter);\n if (i >= 0) setFocusIndex(i);\n }, [filter]);\n\n const rowRefs = useRef<Map<number, HTMLButtonElement>>(new Map());\n const focusAt = useCallback((index: number) => {\n rowRefs.current.get(index)?.focus();\n }, []);\n\n const total = FILTER_VALUES.length;\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight': {\n event.preventDefault();\n const next = (index + 1) % total;\n setFocusIndex(next);\n focusAt(next);\n onChange(FILTER_VALUES[next]);\n break;\n }\n case 'ArrowUp':\n case 'ArrowLeft': {\n event.preventDefault();\n const prev = (index - 1 + total) % total;\n setFocusIndex(prev);\n focusAt(prev);\n onChange(FILTER_VALUES[prev]);\n break;\n }\n case 'Home': {\n event.preventDefault();\n setFocusIndex(0);\n focusAt(0);\n onChange(FILTER_VALUES[0]);\n break;\n }\n case 'End': {\n event.preventDefault();\n const last = total - 1;\n setFocusIndex(last);\n focusAt(last);\n onChange(FILTER_VALUES[last]);\n break;\n }\n case 'Enter':\n case ' ': {\n event.preventDefault();\n onChange(FILTER_VALUES[index]);\n break;\n }\n default:\n break;\n }\n },\n [total, focusAt, onChange],\n );\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n <h3\n id={labelId}\n className=\"type-label ds:font-[var(--font-weight-semibold)] ds:text-[var(--foreground)] ds:m-0\"\n >\n {t('reviewsPanel.filterTitle')}\n </h3>\n <div\n role=\"radiogroup\"\n aria-labelledby={labelId}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-2xs)]\"\n >\n {FILTER_VALUES.map((val, index) => {\n const isAll = val === 'all';\n const stars = isAll ? 0 : (val as 1 | 2 | 3 | 4 | 5);\n const count = isAll\n ? totalCount\n : histogram[stars as 1 | 2 | 3 | 4 | 5];\n const isSelected = val === filter;\n const isFocused = focusIndex === index;\n const ariaLabel = isAll\n ? t('reviewsPanel.filter.allLabel', { count })\n : t('reviewsPanel.filter.starsLabel', { stars, count });\n\n return (\n <button\n key={String(val)}\n ref={(node) => {\n if (node) rowRefs.current.set(index, node);\n else rowRefs.current.delete(index);\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n aria-label={ariaLabel}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => {\n setFocusIndex(index);\n onChange(val);\n }}\n onFocus={() => setFocusIndex(index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n data-filter-bucket={String(val)}\n className={filterRowVariants({\n state: isSelected ? 'selected' : 'idle',\n })}\n >\n <span\n aria-hidden=\"true\"\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\"\n >\n {isAll ? (\n <span className=\"type-label\">\n {t('reviewsPanel.filter.allShort')}\n </span>\n ) : (\n <Rating value={stars} size=\"sm\" decorative />\n )}\n </span>\n <span\n aria-hidden=\"true\"\n className=\"type-meta ds:text-[var(--muted-foreground)]\"\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* ReviewCard — compact card with optional Collapsible breakdown */\n/* -------------------------------------------------------------------- */\n\ninterface ReviewCardProps {\n review: ReviewItem;\n locale: string;\n}\n\nfunction ReviewCard({ review, locale }: ReviewCardProps) {\n const { t } = useTranslation();\n const triggerId = useId();\n const contentId = useId();\n const [open, setOpen] = useState(false);\n\n const breakdownHasValues = Boolean(\n review.breakdown && Object.values(review.breakdown).some((v) => v != null),\n );\n const hasDetails =\n Boolean(review.reply) ||\n breakdownHasValues ||\n Boolean(review.futureVisit) ||\n Boolean(review.recommendation);\n\n const formattedDate = useMemo(() => {\n if (!review.authoredAt) return '';\n try {\n const parsed = new Date(review.authoredAt);\n if (Number.isNaN(parsed.getTime())) return review.authoredAt;\n return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(\n parsed,\n );\n } catch {\n return review.authoredAt;\n }\n }, [review.authoredAt, locale]);\n\n const pseudonym = review.pseudonym ?? t('reviewsPanel.anonymous');\n\n // Compose the full Card body. The Collapsible wraps the footer + the\n // hidden content so both share the same Radix Root and the Trigger's\n // `data-state` reaches the Content automatically (used for the height\n // animation). The Footer + Content are direct Card children so the\n // Card root's flex-col places them in order.\n const footerAndDetails = (\n <Collapsible\n open={hasDetails ? open : false}\n onOpenChange={hasDetails ? setOpen : undefined}\n >\n <Card.Footer>\n <span className=\"type-meta ds:text-[var(--muted-foreground)]\">\n {formattedDate}\n </span>\n {hasDetails ? (\n <CollapsibleTrigger asChild>\n <button\n id={triggerId}\n type=\"button\"\n data-review-trigger\n className=\"ds:ms-auto ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)] ds:rounded-[var(--radius-sm)] ds:ps-[var(--spacing-2xs)] ds:pe-[var(--spacing-2xs)] ds:pt-[var(--spacing-2xs)] ds:pb-[var(--spacing-2xs)] ds:bg-transparent ds:border-0 ds:text-[length:var(--font-size-sm)] ds:text-[var(--primary)] ds:hover:underline ds:cursor-pointer ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]\"\n >\n {open\n ? t('reviewsPanel.card.collapse')\n : t('reviewsPanel.card.expand')}\n <ChevronDown\n aria-hidden=\"true\"\n className={[\n 'ds:size-4 ds:transition-transform ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n open ? 'ds:rotate-180' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n />\n </button>\n </CollapsibleTrigger>\n ) : null}\n </Card.Footer>\n {hasDetails ? (\n <CollapsibleContent id={contentId} aria-labelledby={triggerId}>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)] ds:pb-[var(--spacing-md)]\">\n {breakdownHasValues ||\n review.futureVisit ||\n review.recommendation ? (\n <DescriptionList layout=\"inline\" density=\"compact\">\n {review.breakdown?.appointmentScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.appointment')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating\n value={review.breakdown.appointmentScore}\n size=\"sm\"\n />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.breakdown?.communicationScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.communication')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating\n value={review.breakdown.communicationScore}\n size=\"sm\"\n />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.breakdown?.staffScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.staff')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating value={review.breakdown.staffScore} size=\"sm\" />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.breakdown?.overallScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.overall')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating value={review.breakdown.overallScore} size=\"sm\" />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.futureVisit ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.futureVisit')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n {t(`reviewsPanel.answer.${review.futureVisit}`)}\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.recommendation ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.recommendation')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n {t(`reviewsPanel.answer.${review.recommendation}`)}\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n </DescriptionList>\n ) : null}\n {review.reply ? (\n <div className=\"ds:rounded-[var(--radius-sm)] ds:bg-[var(--muted)]/40 ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)] ds:pt-[var(--spacing-sm)] ds:pb-[var(--spacing-sm)]\">\n <p className=\"type-label ds:m-0 ds:mb-[var(--spacing-2xs)] ds:text-[var(--muted-foreground)] ds:font-[var(--font-weight-semibold)]\">\n {t('reviewsPanel.replyTitle')}\n </p>\n <p className=\"type-body-sm ds:m-0 ds:text-[var(--foreground)] ds:[white-space:pre-line]\">\n {review.reply}\n </p>\n </div>\n ) : null}\n </div>\n </CollapsibleContent>\n ) : null}\n </Collapsible>\n );\n\n return (\n <Card variant=\"default\" className=\"ds:shadow-[var(--shadow-sm)]\">\n <Card.Header>\n <div className=\"ds:flex ds:items-start ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <h4 className=\"type-title-item ds:m-0 ds:text-[var(--foreground)]\">\n {pseudonym}\n </h4>\n <Rating value={review.overallScore} size=\"sm\" />\n </div>\n </Card.Header>\n {review.comment ? (\n <Card.Body>\n <p className=\"type-body ds:m-0 ds:text-[var(--foreground)] ds:line-clamp-2\">\n {review.comment}\n </p>\n </Card.Body>\n ) : null}\n {footerAndDetails}\n </Card>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* PanelSkeleton — loading state */\n/* -------------------------------------------------------------------- */\n\nfunction PanelSkeleton({ hideFilter }: { hideFilter: boolean }) {\n // The parent <section> already carries `aria-busy=\"true\"`, which is the\n // canonical SR signal for \"this region is loading\". Don't add `role=status`\n // + `aria-live=polite` here too — pairing both would double-announce on\n // mount. Skeleton itself is `aria-hidden`.\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <Skeleton variant=\"text\" width=\"40%\" />\n <Skeleton variant=\"text\" width=\"20%\" />\n </div>\n <div\n className={\n hideFilter\n ? 'ds:flex ds:flex-col ds:gap-[var(--spacing-md)]'\n : 'ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:md:grid-cols-[minmax(0,30%)_minmax(0,1fr)]'\n }\n >\n {!hideFilter ? (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n {Array.from({ length: 6 }, (_, i) => (\n <Skeleton key={`rp-fs-${i}`} variant=\"rounded\" height=\"2.5rem\" />\n ))}\n </div>\n ) : null}\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n {Array.from({ length: 3 }, (_, i) => (\n <Skeleton key={`rp-rs-${i}`} variant=\"rounded\" height=\"6rem\" />\n ))}\n </div>\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* Root variants */\n/* -------------------------------------------------------------------- */\n\nconst rootVariants = cva(\n 'ds:flex ds:w-full ds:flex-col ds:gap-[var(--spacing-md)] ds:text-[var(--foreground)]',\n {\n variants: {\n // `surface=\"elevated\"` matches `Card variant=\"elevated\"` — same\n // tokens, same padding, same boundary behaviour — so consumers can\n // drop their own Card wraps without producing a double-card look.\n // Default `'flat'` preserves 0.30.x rendering (no wrapping).\n surface: {\n flat: '',\n elevated: [\n 'ds:rounded-[var(--radius-lg)]',\n 'ds:bg-[var(--card)] ds:text-[var(--card-foreground)]',\n 'ds:border ds:border-[color:var(--card-border)]',\n 'ds:shadow-[var(--shadow-card)]',\n 'ds:p-[var(--spacing-md)]',\n ].join(' '),\n },\n },\n defaultVariants: { surface: 'flat' },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* ReviewsPanel */\n/* -------------------------------------------------------------------- */\n\nexport const ReviewsPanel = forwardRef<HTMLElement, ReviewsPanelProps>(\n (\n {\n reviews,\n aggregate,\n filter: filterProp,\n onFilterChange,\n introSlot,\n headerActionSlot,\n 'aria-label': ariaLabel,\n id,\n className,\n hideFilter = false,\n surface = 'flat',\n loading = false,\n ...rest\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n const locale = i18n.language ?? 'en';\n\n // Controlled / uncontrolled filter handling. When `filterProp` is\n // undefined the panel manages the filter internally — the consumer can\n // mount the panel and never wire `filter` / `onFilterChange` for the\n // common case where the URL doesn't carry the filter state. When the\n // prop IS provided, the consumer owns the truth.\n const [internalFilter, setInternalFilter] = useState<ReviewFilter>('all');\n const filter = filterProp ?? internalFilter;\n\n const handleFilterChange = useCallback(\n (next: ReviewFilter) => {\n if (filterProp === undefined) setInternalFilter(next);\n onFilterChange?.(next);\n },\n [filterProp, onFilterChange],\n );\n\n // Prefer the consumer-provided histogram; fall back to deriving from the\n // loaded reviews. Pagination consumers SHOULD pass an explicit histogram\n // so the per-bucket counts reflect server-side totals rather than only\n // what's loaded on this page.\n const histogram = useMemo(\n () => aggregate.histogram ?? deriveHistogram(reviews),\n [aggregate.histogram, reviews],\n );\n\n const visibleReviews = useMemo(() => {\n if (filter === 'all') return reviews;\n return reviews.filter((r) => bucketOf(r.overallScore) === filter);\n }, [reviews, filter]);\n\n /* Agent registration — exposes filter + visible count via refs so the\n imperative handle returns the latest values without re-creating the\n handle on every render. */\n const latestFilterRef = useRef(filter);\n latestFilterRef.current = filter;\n const latestVisibleCountRef = useRef(visibleReviews.length);\n latestVisibleCountRef.current = visibleReviews.length;\n\n const agentHandle = useMemo<ReviewsPanelHandle>(\n () => ({\n getFilter: () => latestFilterRef.current,\n setFilter: (next) => handleFilterChange(next),\n getVisibleCount: () => latestVisibleCountRef.current,\n }),\n [handleFilterChange],\n );\n\n const rootRef = useRef<HTMLElement>(null);\n useImperativeHandle(ref, () => rootRef.current as HTMLElement, []);\n useAgentRegistration(reviewsPanelAgent, agentHandle, id);\n\n const resolvedAriaLabel = ariaLabel ?? t('reviewsPanel.regionLabel');\n const dataFilter = String(filter);\n\n /* ---------------------------- Loading ---------------------------- */\n if (loading) {\n return (\n <section\n ref={rootRef as React.Ref<HTMLElement>}\n aria-label={resolvedAriaLabel}\n aria-busy=\"true\"\n id={id}\n data-component=\"reviews-panel\"\n data-component-id={id}\n data-filter={dataFilter}\n data-state=\"loading\"\n className={rootVariants({ surface, className })}\n {...rest}\n >\n <PanelSkeleton hideFilter={hideFilter} />\n </section>\n );\n }\n\n /* ----------------------- Zero-total empty ------------------------ */\n // No header, no filter — just the EmptyState. The region landmark is\n // still emitted so an agent can locate the empty panel by its\n // data-component marker.\n if (aggregate.count === 0 && reviews.length === 0) {\n return (\n <section\n ref={rootRef as React.Ref<HTMLElement>}\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"reviews-panel\"\n data-component-id={id}\n data-filter={dataFilter}\n data-state=\"empty\"\n className={rootVariants({ surface, className })}\n {...rest}\n >\n <EmptyState\n variant=\"no-results\"\n title={t('reviewsPanel.empty.title')}\n description={t('reviewsPanel.empty.description')}\n />\n </section>\n );\n }\n\n const showFilterSidebar = !hideFilter;\n\n return (\n <section\n ref={rootRef as React.Ref<HTMLElement>}\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"reviews-panel\"\n data-component-id={id}\n data-filter={dataFilter}\n className={rootVariants({ surface, className })}\n {...rest}\n >\n {/* Header row — plain <div>, not <header>: the panel root is a\n region landmark with an accessible name, so a nested <header>\n would emit a banner-inside-region pair that axe's\n landmark-banner-is-top-level rule flags. */}\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <h2 className=\"type-title-section ds:m-0 ds:text-[var(--foreground)]\">\n {t('reviewsPanel.title')}\n </h2>\n <div className=\"ds:flex ds:items-center ds:gap-[var(--spacing-md)]\">\n {headerActionSlot}\n <Rating\n value={aggregate.averageScore}\n reviews={{ count: aggregate.count }}\n size=\"md\"\n />\n </div>\n </div>\n\n <div\n className={\n showFilterSidebar\n ? 'ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:md:grid-cols-[minmax(0,30%)_minmax(0,1fr)]'\n : 'ds:flex ds:flex-col ds:gap-[var(--spacing-md)]'\n }\n >\n {showFilterSidebar ? (\n <FilterSidebar\n filter={filter}\n histogram={histogram}\n totalCount={aggregate.count}\n onChange={handleFilterChange}\n />\n ) : null}\n\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:min-w-0\">\n {introSlot ? (\n // Plain div, not <aside>: axe's\n // `landmark-complementary-is-top-level` rule fires when an\n // `<aside>` lands inside another landmark (this panel's\n // <section role=\"region\">), and the consumer's introSlot\n // already supplies its own semantics (e.g. an Alert with\n // role=\"status\").\n <div className=\"ds:rounded-[var(--radius-md)] ds:bg-[color-mix(in_srgb,var(--info)_8%,transparent)] ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)] ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]\">\n {introSlot}\n </div>\n ) : null}\n\n {visibleReviews.length === 0 ? (\n // `role=\"status\"` already implies `aria-live=\"polite\"` —\n // declaring both would queue two announcements when the\n // filter changes (the radio's aria-checked flip + this\n // container's text). Keeping `role` only.\n <div\n role=\"status\"\n data-state=\"no-match\"\n className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-sm)] ds:rounded-[var(--radius-md)] ds:border ds:border-dashed ds:border-[var(--border)] ds:p-[var(--spacing-lg)] ds:text-center\"\n >\n <p className=\"type-body ds:m-0 ds:text-[var(--muted-foreground)]\">\n {filter === 'all'\n ? t('reviewsPanel.empty.title')\n : t('reviewsPanel.empty.filterDescription', {\n stars: filter,\n })}\n </p>\n {filter !== 'all' ? (\n <Button\n intent=\"secondary\"\n size=\"sm\"\n onClick={() => handleFilterChange('all')}\n >\n {t('reviewsPanel.empty.resetFilter')}\n </Button>\n ) : null}\n </div>\n ) : (\n <ol\n aria-label={t('reviewsPanel.listLabel')}\n className=\"ds:list-none ds:m-0 ds:p-0 ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\"\n >\n {visibleReviews.map((review) => (\n <li key={review.id} data-review-id={review.id}>\n <ReviewCard review={review} locale={locale} />\n </li>\n ))}\n </ol>\n )}\n </div>\n </div>\n </section>\n );\n },\n);\n\nReviewsPanel.displayName = 'ReviewsPanel';\n"],"names":["reviewsPanelAgent","handle","args","FILTER_VALUES","bucketOf","score","deriveHistogram","reviews","out","r","filterRowVariants","cva","FilterSidebar","filter","histogram","totalCount","onChange","t","useTranslation","labelId","useId","focusIndex","setFocusIndex","useState","useEffect","i","rowRefs","useRef","focusAt","useCallback","index","_a","total","handleKeyDown","event","next","prev","last","jsxs","jsx","val","isAll","stars","count","isSelected","isFocused","ariaLabel","node","e","Rating","ReviewCard","review","locale","triggerId","contentId","open","setOpen","breakdownHasValues","hasDetails","formattedDate","useMemo","parsed","pseudonym","footerAndDetails","Collapsible","Card","CollapsibleTrigger","ChevronDown","CollapsibleContent","DescriptionList","_b","_c","_d","PanelSkeleton","hideFilter","Skeleton","_","rootVariants","ReviewsPanel","forwardRef","aggregate","filterProp","onFilterChange","introSlot","headerActionSlot","id","className","surface","loading","rest","ref","i18n","internalFilter","setInternalFilter","handleFilterChange","visibleReviews","latestFilterRef","latestVisibleCountRef","agentHandle","rootRef","useImperativeHandle","useAgentRegistration","resolvedAriaLabel","dataFilter","EmptyState","showFilterSidebar","Button"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"stat-CYEx8sIR.js","sources":["../../src/components/stat/stat.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useId,\n useMemo,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { useCountUp } from '../../hooks/use-count-up';\n\n/* ------------------------------------------------------------------ */\n/* CVA — stat root */\n/* ------------------------------------------------------------------ */\n\nconst statVariants = cva('ds:flex ds:flex-col', {\n variants: {\n variant: {\n default: 'ds:gap-[var(--spacing-xs)]',\n outlined: [\n 'ds:gap-[var(--spacing-xs)]',\n 'ds:border ds:border-[color:var(--border)]',\n 'ds:rounded-[var(--radius-md)]',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n ].join(' '),\n // White card surface with the shared card shadow — matches\n // `Card variant=\"elevated\"` tokens (`--card` + `--shadow-card`) so\n // a grid of elevated Stats reads as a set of shaded panels without\n // nesting a Card around each one.\n elevated: [\n 'ds:gap-[var(--spacing-xs)]',\n 'ds:bg-[var(--card)]',\n 'ds:text-[var(--card-foreground)]',\n 'ds:shadow-[var(--shadow-card)]',\n 'ds:rounded-[var(--radius-lg)]',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n // Forced-colors: UA strips shadow, so render a border as the\n // separation cue — mirrors Card's forced-colors behaviour.\n 'ds:forced-colors:border ds:forced-colors:border-[CanvasText]',\n ].join(' '),\n compact: 'ds:gap-[var(--spacing-none)]',\n },\n size: {\n sm: '',\n md: '',\n lg: '',\n },\n align: {\n start: 'ds:items-start ds:text-start',\n center: 'ds:items-center ds:text-center',\n end: 'ds:items-end ds:text-end',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'md',\n align: 'start',\n },\n});\n\n/* ------------------------------------------------------------------ */\n/* CVA — value element */\n/* ------------------------------------------------------------------ */\n\nconst valueVariants = cva('type-metric ds:text-[var(--foreground)]', {\n variants: {\n size: {\n sm: 'ds:[--type-metric-size:var(--font-size-xl)]',\n md: 'ds:[--type-metric-size:var(--font-size-3xl)]',\n lg: 'ds:[--type-metric-size:var(--font-size-5xl)]',\n },\n variant: {\n default: '',\n outlined: '',\n elevated: '',\n compact: '',\n },\n },\n compoundVariants: [\n {\n variant: 'compact',\n size: 'sm',\n className: 'ds:[--type-metric-size:var(--font-size-lg)]',\n },\n {\n variant: 'compact',\n size: 'md',\n className: 'ds:[--type-metric-size:var(--font-size-xl)]',\n },\n {\n variant: 'compact',\n size: 'lg',\n className: 'ds:[--type-metric-size:var(--font-size-2xl)]',\n },\n ],\n defaultVariants: {\n size: 'md',\n variant: 'default',\n },\n});\n\n/* ------------------------------------------------------------------ */\n/* CVA — trend element */\n/* ------------------------------------------------------------------ */\n\nconst trendVariants = cva(\n 'ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)] type-label',\n {\n variants: {\n trend: {\n up: 'ds:text-[var(--success)]',\n down: 'ds:text-[var(--destructive)]',\n flat: 'ds:text-[var(--muted-foreground)]',\n },\n },\n defaultVariants: {\n trend: 'flat',\n },\n },\n);\n\n/* ------------------------------------------------------------------ */\n/* StatTrend (internal — not exported) */\n/* ------------------------------------------------------------------ */\n\ninterface StatTrendProps {\n trend: 'up' | 'down' | 'flat';\n delta: number | string;\n deltaFormat?: 'decimal' | 'percent';\n locale?: string;\n}\n\nconst ARROW = { up: '↑', down: '↓', flat: '→' } as const;\nconst TREND_KEY = {\n up: 'stat.trend.up',\n down: 'stat.trend.down',\n flat: 'stat.trend.flat',\n} as const;\n\nfunction StatTrend({\n trend,\n delta,\n deltaFormat = 'decimal',\n locale,\n}: StatTrendProps) {\n const { t, i18n } = useTranslation();\n\n const formattedDelta =\n typeof delta === 'string'\n ? delta\n : new Intl.NumberFormat(locale ?? i18n.language, {\n style: deltaFormat === 'percent' ? 'percent' : 'decimal',\n }).format(deltaFormat === 'percent' ? delta / 100 : delta);\n\n return (\n <span className={trendVariants({ trend })}>\n <span aria-hidden=\"true\">{ARROW[trend]}</span>\n <span className=\"ds:sr-only\">{t(TREND_KEY[trend])}</span>\n {formattedDelta}\n </span>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Parse a string value into a numeric target for the count-up */\n/* ------------------------------------------------------------------ */\n\ntype ParsedValue = {\n /** Numeric target for the count-up. */\n target: number;\n /** Leading sign captured from the input string (e.g. \"+\" / \"-\"). */\n sign: '' | '+' | '-';\n /** Trailing shorthand suffix (e.g. \"K\" / \"M\" / \"B\") to re-append. */\n suffix: string;\n};\n\n/**\n * Strip locale-aware separators, a leading sign, and the K/M/B\n * shorthand suffix from a pre-formatted string so the count-up has a\n * pure numeric target to animate towards. Returns `null` for any input\n * that can't be parsed — the caller short-circuits to static rendering\n * in that case.\n */\nfunction parseStatValue(raw: string, locale: string): ParsedValue | null {\n const trimmed = raw.trim();\n if (trimmed === '') return null;\n\n // Capture leading sign so we can re-attach it every frame. Stat's\n // marketing usage (\"+500.000\", \"-12\") often relies on it.\n let sign: '' | '+' | '-' = '';\n let body = trimmed;\n if (body.startsWith('+')) {\n sign = '+';\n body = body.slice(1).trimStart();\n } else if (body.startsWith('-') || body.startsWith('−')) {\n // Hyphen-minus or the typographic minus sign — both treated as -.\n sign = '-';\n body = body.slice(1).trimStart();\n }\n\n // Trailing K / M / B shorthand (case-insensitive). Re-append verbatim\n // so \"+1.5K\" renders as \"+1.5K\" at every frame.\n let suffix = '';\n const suffixMatch = body.match(/[KMB]$/i);\n if (suffixMatch) {\n suffix = suffixMatch[0];\n body = body.slice(0, -1).trimEnd();\n }\n\n // Discover the locale's group + decimal glyphs from a probe value.\n // formatToParts is the canonical way; we fall back to `.` decimal /\n // `,` group if Intl can't tell us (no-locale envs).\n let group = '';\n let decimal = '.';\n try {\n const probe = new Intl.NumberFormat(locale).formatToParts(12345.6);\n for (const part of probe) {\n if (part.type === 'group') group = part.value;\n else if (part.type === 'decimal') decimal = part.value;\n }\n } catch {\n /* swallow — defaults already set */\n }\n\n // Heuristic for input strings that look like JS-native decimals\n // (e.g. `\"1500.25\"`) regardless of the requested locale: if the body\n // is a clean US-style decimal AND the fraction after the `.` is NOT\n // a three-digit thousands group in a locale that uses `.` as the\n // group separator, treat it as a direct numeric literal. This covers\n // the common case where a caller types `value=\"1500.25\"` and expects\n // the count-up to land on 1500.25 even though the output locale is\n // it-IT (which uses `.` for thousands).\n const usStyleMatch = body.match(/^(\\d+)\\.(\\d+)$/);\n if (usStyleMatch) {\n const fractionLen = usStyleMatch[2].length;\n if (!(fractionLen === 3 && group === '.')) {\n const direct = Number.parseFloat(body);\n if (Number.isFinite(direct)) {\n return { target: direct, sign, suffix };\n }\n }\n }\n\n // Strip every group separator we discovered, then normalise the\n // decimal separator to `.` so JS can parseFloat the result.\n // Note: when the locale's group separator is empty (e.g. some\n // numbering systems) the replace is a no-op.\n let normalised = body;\n if (group !== '') {\n normalised = normalised.split(group).join('');\n }\n if (decimal !== '.') {\n normalised = normalised.split(decimal).join('.');\n }\n\n // Defence-in-depth: strip whitespace plus NBSP (U+00A0) and NARROW\n // NO-BREAK SPACE (U+202F) that some locales (fr-FR, it-IT) use as\n // thousand separators. Hex escapes keep this source ASCII-clean.\n normalised = normalised.replace(/[\\s\\u00A0\\u202F]/g, '');\n\n const numeric = Number.parseFloat(normalised);\n if (!Number.isFinite(numeric)) return null;\n\n return { target: numeric, sign, suffix };\n}\n\n/* ------------------------------------------------------------------ */\n/* StatProps */\n/* ------------------------------------------------------------------ */\n\nexport interface StatProps\n extends HTMLAttributes<HTMLDivElement>, VariantProps<typeof statVariants> {\n /** Metric label rendered above the value. */\n label: string;\n /** Numeric or pre-formatted string value. */\n value: number | string;\n /** Number formatting style. Omit to use default decimal formatting. */\n format?: 'decimal' | 'currency' | 'percent';\n /** ISO 4217 currency code — required when format=\"currency\". */\n currency?: string;\n /** BCP 47 locale override. Defaults to the active i18n language. */\n locale?: string;\n /** Trend direction. Requires `delta` to render the trend row. */\n trend?: 'up' | 'down' | 'flat';\n /** Trend delta magnitude, e.g. 12 for \"↑ 12%\". */\n delta?: number | string;\n /** Number formatting style for the delta. Default \"decimal\". */\n deltaFormat?: 'decimal' | 'percent';\n /** Optional leading icon rendered at the start of the label row. */\n icon?: ReactNode;\n /** Renders animated skeleton placeholders instead of content. */\n loading?: boolean;\n /**\n * Animate the numeric value from 0 (or from the captured leading\n * sign) to the final value when the Stat first scrolls into view.\n * Default `false`. Marketing surfaces opt in; dashboards leave it\n * off so live metrics render immediately.\n */\n animate?: boolean;\n /**\n * When `true`, the count-up re-plays every time the Stat scrolls\n * out of view and back in. Default `false` — animate once per\n * element per session, matching the booking-website hero pattern.\n * Ignored under `prefers-reduced-motion: reduce`.\n */\n animateOnEveryView?: boolean;\n /** Animation duration in milliseconds. Default 1600. */\n animateDurationMs?: number;\n /**\n * BCP 47 locale used for the per-frame count-up formatting. Falls\n * back to `locale`, then to the active i18n language.\n */\n animateLocale?: string;\n /**\n * Override the locale's thousand / decimal glyphs for the count-up.\n * Wins over locale defaults. Pass partial overrides — `decimal`\n * alone is valid; the unspecified separator stays at the locale\n * default.\n */\n animateSeparators?: {\n thousand?: string;\n decimal?: string;\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Stat */\n/* ------------------------------------------------------------------ */\n\nexport const Stat = forwardRef<HTMLDivElement, StatProps>(\n (\n {\n label,\n value,\n format,\n currency,\n locale,\n trend,\n delta,\n deltaFormat = 'decimal',\n icon,\n loading = false,\n animate = false,\n animateOnEveryView = false,\n animateDurationMs = 1600,\n animateLocale,\n animateSeparators,\n variant = 'default',\n size = 'md',\n align = 'start',\n className,\n ...props\n },\n ref,\n ) => {\n const { i18n } = useTranslation();\n const labelId = useId();\n const lang = locale ?? i18n.language;\n\n /* -------------------------------------------------- */\n /* Static formatting — the value as it would appear */\n /* in the non-animated (or post-animation) state. */\n /* -------------------------------------------------- */\n\n const formatValue = useCallback((): string => {\n if (typeof value === 'string') return value;\n\n switch (format) {\n case 'currency':\n return new Intl.NumberFormat(lang, {\n style: 'currency',\n currency: currency ?? 'USD',\n }).format(value);\n case 'percent':\n return new Intl.NumberFormat(lang, { style: 'percent' }).format(\n value,\n );\n case 'decimal':\n return new Intl.NumberFormat(lang, { style: 'decimal' }).format(\n value,\n );\n default:\n return new Intl.NumberFormat(lang).format(value);\n }\n }, [value, format, lang, currency]);\n\n const staticValue = formatValue();\n\n /* -------------------------------------------------- */\n /* Parse the value into a numeric target for the */\n /* count-up. Numeric values use `value` directly; */\n /* string values are stripped of sign / shorthand / */\n /* locale separators and re-decorated each frame. */\n /* -------------------------------------------------- */\n\n const animationLocale = animateLocale ?? lang;\n const hasWarnedRef = useRef<boolean>(false);\n\n const parsed = useMemo<ParsedValue | null>(() => {\n if (!animate) return null;\n if (typeof value === 'number') {\n // Numeric: target is the number itself; no sign / suffix to\n // re-attach.\n return Number.isFinite(value)\n ? { target: value, sign: '', suffix: '' }\n : null;\n }\n return parseStatValue(value, animationLocale);\n }, [animate, value, animationLocale]);\n\n // Dev-mode warning when an animate-flagged Stat has an unparseable\n // string value. Render static, no throw — one warn per component.\n if (\n animate &&\n typeof value === 'string' &&\n parsed === null &&\n import.meta.env.DEV &&\n !hasWarnedRef.current\n ) {\n hasWarnedRef.current = true;\n console.warn(\n `Stat: value=\"${value}\" couldn't be parsed for the count-up animation. Rendering static.`,\n );\n }\n\n const shouldAnimate = animate && parsed !== null;\n\n /* -------------------------------------------------- */\n /* Wire the hook unconditionally — rules-of-hooks. */\n /* When `shouldAnimate` is false the hook still runs */\n /* but `to === from` so it's a no-op visually. */\n /* -------------------------------------------------- */\n\n const target = parsed?.target ?? 0;\n const sign = parsed?.sign ?? '';\n const suffix = parsed?.suffix ?? '';\n\n // Wrap the count-up formatter so the captured sign + suffix are\n // re-applied every frame. The hook hands us the locale-formatted\n // magnitude; we own the decoration around it.\n const formatter = useCallback(\n (n: number) => {\n // Use the absolute value for the magnitude so the captured\n // sign is the sole source of the rendered sign. Negative\n // numbers passed in (e.g. `to: -12`) come through unsigned;\n // we re-apply via `sign`. Always force `useGrouping: true` so\n // ICU's locale-default `min2` doesn't suppress the thousand\n // glyph on 4-digit values like 1.000 (it-IT). The user wrote\n // a stat value — they want it grouped.\n const magnitudeNF = (() => {\n try {\n return new Intl.NumberFormat(animationLocale, {\n useGrouping: true,\n });\n } catch {\n return null;\n }\n })();\n\n let magnitudeStr: string;\n if (magnitudeNF) {\n if (animateSeparators) {\n // Hand-build the grouped magnitude so we don't depend on\n // ICU's locale-by-locale grouping defaults (which differ\n // between engines and can drop the group at 4-digit\n // thresholds even with `useGrouping: true`). Locale only\n // influences sign handling, which we apply separately.\n const abs = Math.abs(n);\n const intPart = Math.trunc(abs);\n const fracPart = abs - intPart;\n const groupGlyph = animateSeparators.thousand ?? '';\n const intStr = String(intPart).replace(\n /\\B(?=(\\d{3})+(?!\\d))/g,\n groupGlyph,\n );\n if (fracPart === 0) {\n magnitudeStr = intStr;\n } else {\n const fracStr = fracPart.toFixed(6).replace(/0+$/, '').slice(2);\n const decimalGlyph = animateSeparators.decimal ?? '.';\n magnitudeStr = fracStr ? intStr + decimalGlyph + fracStr : intStr;\n }\n } else {\n magnitudeStr = magnitudeNF.format(Math.abs(n));\n }\n } else {\n magnitudeStr = String(Math.abs(n));\n }\n\n // Re-derive the sign every frame: the captured sign wins, but\n // a negative `to` with no leading sign in the source still\n // renders with a leading \"-\" so the final formatted output\n // matches locale conventions.\n let resolvedSign: string = sign;\n if (sign === '' && n < 0) resolvedSign = '-';\n\n return `${resolvedSign}${magnitudeStr}${suffix}`;\n },\n [animationLocale, animateSeparators, sign, suffix],\n );\n\n const countUp = useCountUp({\n to: target,\n from: 0,\n durationMs: animateDurationMs,\n startOnVisible: true,\n retriggerOnReEntry: animateOnEveryView,\n // `formatter` is the only path we use — it owns both the locale\n // formatting and the sign/suffix re-application.\n formatter,\n });\n\n /* -------------------------------------------------- */\n /* Loading state */\n /* -------------------------------------------------- */\n\n if (loading) {\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-busy=\"true\"\n aria-label={label}\n data-component=\"stat\"\n className={statVariants({ variant, size, align, className })}\n {...props}\n >\n <div className=\"ds:h-[var(--skeleton-label-h)] ds:w-[var(--skeleton-label-w)] ds:animate-pulse ds:rounded-[var(--radius-sm)] ds:bg-[var(--muted)]\" />\n <div className=\"ds:h-[var(--skeleton-value-h)] ds:w-[var(--skeleton-value-w)] ds:animate-pulse ds:rounded-[var(--radius-sm)] ds:bg-[var(--muted)]\" />\n </div>\n );\n }\n\n /* -------------------------------------------------- */\n /* Render */\n /* -------------------------------------------------- */\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-labelledby={labelId}\n data-component=\"stat\"\n className={statVariants({ variant, size, align, className })}\n {...props}\n >\n <span className=\"ds:inline-flex ds:items-center\">\n {icon && (\n <span\n aria-hidden=\"true\"\n className={[\n 'ds:me-[var(--spacing-xs)] ds:inline-flex ds:shrink-0',\n // Accent tint on the leading icon. The icon is decorative\n // (aria-hidden), so magenta-500's 3.2:1 contrast on white\n // is acceptable here — the Stat label + value still\n // carry the accessible name at `--foreground`.\n 'ds:text-[color:var(--accent)]',\n 'ds:forced-colors:text-[CanvasText]',\n ].join(' ')}\n >\n {icon}\n </span>\n )}\n <span\n id={labelId}\n className=\"type-label ds:text-[var(--muted-foreground)]\"\n >\n {label}\n </span>\n </span>\n {shouldAnimate ? (\n // The animated number is wrapped in an `aria-live=\"off\"` span\n // so screen readers don't announce every interpolated frame.\n // The `role=\"group\"` ancestor (with `aria-labelledby` pointing\n // at the label) carries the final accessible reading — the\n // animated frames are decorative only.\n <span\n ref={countUp.ref}\n aria-live=\"off\"\n className={valueVariants({ size, variant })}\n >\n {countUp.value}\n </span>\n ) : (\n <span className={valueVariants({ size, variant })}>\n {staticValue}\n </span>\n )}\n {trend != null && delta != null && (\n <StatTrend\n trend={trend}\n delta={delta}\n deltaFormat={deltaFormat}\n locale={locale}\n />\n )}\n </div>\n );\n },\n);\n\nStat.displayName = 'Stat';\n"],"names":["statVariants","cva","valueVariants","trendVariants","ARROW","TREND_KEY","StatTrend","trend","delta","deltaFormat","locale","t","i18n","useTranslation","formattedDelta","jsx","parseStatValue","raw","trimmed","sign","body","suffix","suffixMatch","group","decimal","probe","part","usStyleMatch","direct","normalised","numeric","Stat","forwardRef","label","value","format","currency","icon","loading","animate","animateOnEveryView","animateDurationMs","animateLocale","animateSeparators","variant","size","align","className","props","ref","labelId","useId","lang","staticValue","useCallback","animationLocale","useRef","parsed","useMemo","shouldAnimate","target","formatter","n","magnitudeNF","magnitudeStr","abs","intPart","fracPart","groupGlyph","intStr","fracStr","decimalGlyph","resolvedSign","countUp","useCountUp","jsxs"],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- export { PracticeProfileCard, PRACTICE_PROFILE_CARD_ROW_KEYS, PRACTICE_PROFILE_CARD_ROW_ICONS, } from './practice-profile-card';
2
- export type { PracticeProfileCardProps, PracticeProfileCardAddress, PracticeProfileCardHandle, PracticeProfileCardSurface, } from './practice-profile-card';
3
- export { practiceProfileCardAgent } from './practice-profile-card.agent';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/practice-profile-card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,8BAA8B,EAC9B,+BAA+B,GAChC,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -1,8 +0,0 @@
1
- import { P as r, a as P, b as R, p as _ } from "../../_chunks/practice-profile-card-CfAMeTxQ.js";
2
- export {
3
- r as PRACTICE_PROFILE_CARD_ROW_ICONS,
4
- P as PRACTICE_PROFILE_CARD_ROW_KEYS,
5
- R as PracticeProfileCard,
6
- _ as practiceProfileCardAgent
7
- };
8
- //# sourceMappingURL=index.js.map