@alfadocs/ui-kit-debug 1.90.1 → 1.90.2

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 (151) hide show
  1. package/dist/_chunks/address-autocomplete-DR4Xn23Z.js +519 -0
  2. package/dist/_chunks/address-autocomplete-DR4Xn23Z.js.map +1 -0
  3. package/dist/_chunks/{address-fields-CfnXK8RT.js → address-fields-qxQkGwPf.js} +5 -5
  4. package/dist/_chunks/{address-fields-CfnXK8RT.js.map → address-fields-qxQkGwPf.js.map} +1 -1
  5. package/dist/_chunks/{ai-permission-notice-CoW22jx1.js → ai-permission-notice-VNh3W57L.js} +2 -2
  6. package/dist/_chunks/{ai-permission-notice-CoW22jx1.js.map → ai-permission-notice-VNh3W57L.js.map} +1 -1
  7. package/dist/_chunks/{anamnesis-summary-rows-fbEQwcuW.js → anamnesis-summary-rows-EKPfVCmh.js} +2 -2
  8. package/dist/_chunks/{anamnesis-summary-rows-fbEQwcuW.js.map → anamnesis-summary-rows-EKPfVCmh.js.map} +1 -1
  9. package/dist/_chunks/autocomplete-BXMcNoa3.js +562 -0
  10. package/dist/_chunks/autocomplete-BXMcNoa3.js.map +1 -0
  11. package/dist/_chunks/{balance-badge-cell-C4FYwmrZ.js → balance-badge-cell-D9VhFp0L.js} +3 -3
  12. package/dist/_chunks/{balance-badge-cell-C4FYwmrZ.js.map → balance-badge-cell-D9VhFp0L.js.map} +1 -1
  13. package/dist/_chunks/{booking-C0FoW8Yx.js → booking-CcE_oviI.js} +1896 -1859
  14. package/dist/_chunks/booking-CcE_oviI.js.map +1 -0
  15. package/dist/_chunks/{calendar-appointment-editor-dialog-C1v2MNE4.js → calendar-appointment-editor-dialog-CiAGAX2t.js} +7 -7
  16. package/dist/_chunks/{calendar-appointment-editor-dialog-C1v2MNE4.js.map → calendar-appointment-editor-dialog-CiAGAX2t.js.map} +1 -1
  17. package/dist/_chunks/{checkbox-BjDv-r3o.js → checkbox-a7Sh-dXb.js} +32 -31
  18. package/dist/_chunks/checkbox-a7Sh-dXb.js.map +1 -0
  19. package/dist/_chunks/{checkbox-group-Cw2H5qaq.js → checkbox-group-eGdv9fZc.js} +2 -2
  20. package/dist/_chunks/{checkbox-group-Cw2H5qaq.js.map → checkbox-group-eGdv9fZc.js.map} +1 -1
  21. package/dist/_chunks/{city-input-CJVGswxp.js → city-input-CA59CjnV.js} +2 -2
  22. package/dist/_chunks/{city-input-CJVGswxp.js.map → city-input-CA59CjnV.js.map} +1 -1
  23. package/dist/_chunks/{combobox-CDPm_n0p.js → combobox-D-gpgEDy.js} +314 -294
  24. package/dist/_chunks/combobox-D-gpgEDy.js.map +1 -0
  25. package/dist/_chunks/{country-select-DQ8q7ypA.js → country-select-BfmX9y5i.js} +2 -2
  26. package/dist/_chunks/{country-select-DQ8q7ypA.js.map → country-select-BfmX9y5i.js.map} +1 -1
  27. package/dist/_chunks/{data-table-B_IcS0Ke.js → data-table-CqQgoNsU.js} +2 -2
  28. package/dist/_chunks/{data-table-B_IcS0Ke.js.map → data-table-CqQgoNsU.js.map} +1 -1
  29. package/dist/_chunks/{face-scheme-BgJGFDTa.js → face-scheme-BKSE98bo.js} +2 -2
  30. package/dist/_chunks/{face-scheme-BgJGFDTa.js.map → face-scheme-BKSE98bo.js.map} +1 -1
  31. package/dist/_chunks/{file-manager-C9YOT-Dn.js → file-manager-BnI1ZWZu.js} +2 -2
  32. package/dist/_chunks/{file-manager-C9YOT-Dn.js.map → file-manager-BnI1ZWZu.js.map} +1 -1
  33. package/dist/_chunks/{header-settings-BVhjndtV.js → header-settings-BeA2fnol.js} +24 -23
  34. package/dist/_chunks/header-settings-BeA2fnol.js.map +1 -0
  35. package/dist/_chunks/{heatmap-HTz3Jvzy.js → heatmap-fCxwNz2o.js} +2 -2
  36. package/dist/_chunks/{heatmap-HTz3Jvzy.js.map → heatmap-fCxwNz2o.js.map} +1 -1
  37. package/dist/_chunks/{link-cell-renderer-C0ynDik7.js → link-cell-renderer-CnCgudvq.js} +3 -3
  38. package/dist/_chunks/{link-cell-renderer-C0ynDik7.js.map → link-cell-renderer-CnCgudvq.js.map} +1 -1
  39. package/dist/_chunks/{login-dev-card-DYwbL5sD.js → login-dev-card-BZBR58mu.js} +2 -2
  40. package/dist/_chunks/{login-dev-card-DYwbL5sD.js.map → login-dev-card-BZBR58mu.js.map} +1 -1
  41. package/dist/_chunks/{map-view-Bvi06RVh.js → map-view-UrNgxBy3.js} +260 -237
  42. package/dist/_chunks/map-view-UrNgxBy3.js.map +1 -0
  43. package/dist/_chunks/{index.modern-BeRXQVdc.js → maps-core-gate-DkNqGkm3.js} +360 -319
  44. package/dist/_chunks/maps-core-gate-DkNqGkm3.js.map +1 -0
  45. package/dist/_chunks/{marketplace-app-shell-x_abo_ye.js → marketplace-app-shell-CmZx0Oam.js} +2 -2
  46. package/dist/_chunks/{marketplace-app-shell-x_abo_ye.js.map → marketplace-app-shell-CmZx0Oam.js.map} +1 -1
  47. package/dist/_chunks/{notes-panel-CZO1qsD-.js → notes-panel-Dywo6R55.js} +2 -2
  48. package/dist/_chunks/{notes-panel-CZO1qsD-.js.map → notes-panel-Dywo6R55.js.map} +1 -1
  49. package/dist/_chunks/{patient-details-D0P5eog5.js → patient-details-BUr7Oemx.js} +2 -2
  50. package/dist/_chunks/{patient-details-D0P5eog5.js.map → patient-details-BUr7Oemx.js.map} +1 -1
  51. package/dist/_chunks/{patient-field-row-R73dZJY6.js → patient-field-row-bJk8Q_qr.js} +5 -5
  52. package/dist/_chunks/{patient-field-row-R73dZJY6.js.map → patient-field-row-bJk8Q_qr.js.map} +1 -1
  53. package/dist/_chunks/patient-search-Ca9j5cWb.js +1306 -0
  54. package/dist/_chunks/patient-search-Ca9j5cWb.js.map +1 -0
  55. package/dist/_chunks/{patient-summary-card-6U74-ivO.js → patient-summary-card-CCQZ8X37.js} +2 -2
  56. package/dist/_chunks/{patient-summary-card-6U74-ivO.js.map → patient-summary-card-CCQZ8X37.js.map} +1 -1
  57. package/dist/_chunks/{periodontal-diagnosis-B9YmmVmM.js → periodontal-diagnosis-DD3hrg9X.js} +2 -2
  58. package/dist/_chunks/{periodontal-diagnosis-B9YmmVmM.js.map → periodontal-diagnosis-DD3hrg9X.js.map} +1 -1
  59. package/dist/_chunks/{permission-matrix-BoQjENGV.js → permission-matrix-BJ6sg3Zp.js} +3 -3
  60. package/dist/_chunks/{permission-matrix-BoQjENGV.js.map → permission-matrix-BJ6sg3Zp.js.map} +1 -1
  61. package/dist/_chunks/{practice-results-B61krlwu.js → practice-results-DQuOfqej.js} +2 -2
  62. package/dist/_chunks/{practice-results-B61krlwu.js.map → practice-results-DQuOfqej.js.map} +1 -1
  63. package/dist/_chunks/{province-select-CPdTwEob.js → province-select-D64whw0m.js} +2 -2
  64. package/dist/_chunks/{province-select-CPdTwEob.js.map → province-select-D64whw0m.js.map} +1 -1
  65. package/dist/_chunks/{registration-form-DmP3YUfp.js → registration-form-CRm6DaB9.js} +2 -2
  66. package/dist/_chunks/{registration-form-DmP3YUfp.js.map → registration-form-CRm6DaB9.js.map} +1 -1
  67. package/dist/_chunks/{reviews-panel-UzQQLb-z.js → reviews-panel-B7InBORU.js} +42 -41
  68. package/dist/_chunks/reviews-panel-B7InBORU.js.map +1 -0
  69. package/dist/_chunks/{rich-text-editor-BZTyxQBd.js → rich-text-editor-BfEu57dk.js} +3 -3
  70. package/dist/_chunks/{rich-text-editor-BZTyxQBd.js.map → rich-text-editor-BfEu57dk.js.map} +1 -1
  71. package/dist/_chunks/{slot-grid-Bb13H9Rl.js → slot-grid-DrwynKeN.js} +91 -89
  72. package/dist/_chunks/{slot-grid-Bb13H9Rl.js.map → slot-grid-DrwynKeN.js.map} +1 -1
  73. package/dist/_chunks/{task-editor-BOETWbjU.js → task-editor-CI1SKUNc.js} +2 -2
  74. package/dist/_chunks/{task-editor-BOETWbjU.js.map → task-editor-CI1SKUNc.js.map} +1 -1
  75. package/dist/_chunks/{use-appointment-details-read-CWN-nJCZ.js → use-appointment-details-read-BytPq3Ux.js} +2 -2
  76. package/dist/_chunks/{use-appointment-details-read-CWN-nJCZ.js.map → use-appointment-details-read-BytPq3Ux.js.map} +1 -1
  77. package/dist/_chunks/{voice-control-panel-BziKE-KK.js → voice-control-panel-BP0otrID.js} +2 -2
  78. package/dist/_chunks/{voice-control-panel-BziKE-KK.js.map → voice-control-panel-BP0otrID.js.map} +1 -1
  79. package/dist/_chunks/{workflow-webhook-action-DulCpCRz.js → workflow-webhook-action-B_nBJ_ht.js} +2 -2
  80. package/dist/_chunks/{workflow-webhook-action-DulCpCRz.js.map → workflow-webhook-action-B_nBJ_ht.js.map} +1 -1
  81. package/dist/agent-catalog.json +2 -2
  82. package/dist/agent-i18n/en.json +1 -1
  83. package/dist/components/_shared/maps-core-gate.d.ts +18 -0
  84. package/dist/components/_shared/maps-core-gate.d.ts.map +1 -0
  85. package/dist/components/address-autocomplete/address-autocomplete.d.ts.map +1 -1
  86. package/dist/components/address-autocomplete/index.js +1 -1
  87. package/dist/components/address-fields/index.js +1 -1
  88. package/dist/components/ai-permission-notice/index.js +1 -1
  89. package/dist/components/appointment-details-panel/index.js +1 -1
  90. package/dist/components/autocomplete/autocomplete.agent.d.ts.map +1 -1
  91. package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
  92. package/dist/components/autocomplete/index.js +1 -1
  93. package/dist/components/booking/booking.d.ts.map +1 -1
  94. package/dist/components/booking/details-form.d.ts.map +1 -1
  95. package/dist/components/booking/index.js +1 -1
  96. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  97. package/dist/components/checkbox/index.js +1 -1
  98. package/dist/components/checkbox-group/index.js +1 -1
  99. package/dist/components/city-input/index.js +1 -1
  100. package/dist/components/combobox/combobox.d.ts.map +1 -1
  101. package/dist/components/combobox/index.js +1 -1
  102. package/dist/components/country-select/index.js +1 -1
  103. package/dist/components/data-table/index.js +2 -2
  104. package/dist/components/face-scheme/index.js +1 -1
  105. package/dist/components/file-manager/index.js +1 -1
  106. package/dist/components/header-settings/header-settings.d.ts +2 -1
  107. package/dist/components/header-settings/header-settings.d.ts.map +1 -1
  108. package/dist/components/header-settings/index.js +1 -1
  109. package/dist/components/heatmap/index.js +1 -1
  110. package/dist/components/map-view/index.js +1 -1
  111. package/dist/components/map-view/map-view.d.ts.map +1 -1
  112. package/dist/components/notes-panel/index.js +1 -1
  113. package/dist/components/patient-details/index.js +1 -1
  114. package/dist/components/patient-search/index.js +1 -1
  115. package/dist/components/patient-search/patient-search.d.ts.map +1 -1
  116. package/dist/components/patient-summary-card/index.js +1 -1
  117. package/dist/components/patient-table/index.js +1 -1
  118. package/dist/components/periodontal-diagnosis/index.js +1 -1
  119. package/dist/components/permission-matrix/index.js +1 -1
  120. package/dist/components/practice-results/index.js +1 -1
  121. package/dist/components/province-select/index.js +1 -1
  122. package/dist/components/reviews-panel/index.js +1 -1
  123. package/dist/components/reviews-panel/reviews-panel.d.ts.map +1 -1
  124. package/dist/components/rich-text-editor/index.js +1 -1
  125. package/dist/components/slot-grid/index.js +1 -1
  126. package/dist/components/slot-grid/slot-grid.d.ts.map +1 -1
  127. package/dist/components/task-editor/index.js +1 -1
  128. package/dist/components/voice-control-panel/index.js +1 -1
  129. package/dist/host-compat.css +1 -10
  130. package/dist/index.js +37 -37
  131. package/dist/patterns/anamnesis/index.js +1 -1
  132. package/dist/patterns/calendar/index.js +1 -1
  133. package/dist/patterns/login-form/index.js +1 -1
  134. package/dist/patterns/marketplace-app-shell/index.js +1 -1
  135. package/dist/patterns/registration-form/index.js +1 -1
  136. package/dist/patterns/workflow-webhook-action/index.js +1 -1
  137. package/dist/tokens.css +1 -1
  138. package/package.json +1 -1
  139. package/dist/_chunks/address-autocomplete-bPR2mNBt.js +0 -498
  140. package/dist/_chunks/address-autocomplete-bPR2mNBt.js.map +0 -1
  141. package/dist/_chunks/autocomplete-CnB1mHGc.js +0 -539
  142. package/dist/_chunks/autocomplete-CnB1mHGc.js.map +0 -1
  143. package/dist/_chunks/booking-C0FoW8Yx.js.map +0 -1
  144. package/dist/_chunks/checkbox-BjDv-r3o.js.map +0 -1
  145. package/dist/_chunks/combobox-CDPm_n0p.js.map +0 -1
  146. package/dist/_chunks/header-settings-BVhjndtV.js.map +0 -1
  147. package/dist/_chunks/index.modern-BeRXQVdc.js.map +0 -1
  148. package/dist/_chunks/map-view-Bvi06RVh.js.map +0 -1
  149. package/dist/_chunks/patient-search-CCOUZABE.js +0 -1196
  150. package/dist/_chunks/patient-search-CCOUZABE.js.map +0 -1
  151. package/dist/_chunks/reviews-panel-UzQQLb-z.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"patient-search-CCOUZABE.js","sources":["../../node_modules/lucide-react/dist/esm/icons/locate-fixed.mjs","../../src/components/patient-search/patient-search.agent.ts","../../src/components/patient-search/patient-search.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.22.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.mjs';\n\nconst __iconNode = [\n [\"line\", { x1: \"2\", x2: \"5\", y1: \"12\", y2: \"12\", key: \"bvdh0s\" }],\n [\"line\", { x1: \"19\", x2: \"22\", y1: \"12\", y2: \"12\", key: \"1tbv5k\" }],\n [\"line\", { x1: \"12\", x2: \"12\", y1: \"2\", y2: \"5\", key: \"11lu5j\" }],\n [\"line\", { x1: \"12\", x2: \"12\", y1: \"19\", y2: \"22\", key: \"x3vr5v\" }],\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"7\", key: \"fim9np\" }],\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"3\", key: \"1v7zrd\" }]\n];\nconst LocateFixed = createLucideIcon(\"locate-fixed\", __iconNode);\n\nexport { __iconNode, LocateFixed as default };\n//# sourceMappingURL=locate-fixed.mjs.map\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — PatientSearch. */\n/* */\n/* PatientSearch is a top-of-funnel widget with four variants that vary */\n/* which search dimensions are exposed. The adapter surfaces the active */\n/* variant + lightweight actions (`reset`, `focus_primary`) so an agent */\n/* can drive the form across surfaces without knowing which variant is */\n/* in play. The selection state itself is owned by the consumer via the */\n/* `value` / `onChange` contract — never read here. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { PatientSearchHandle } from './patient-search';\n\nexport const patientSearchAgent: AgentAdapter<PatientSearchHandle> = {\n id: 'patient-search',\n // `filter` is the conceptual capability of this search widget. Building the\n // actual query (WHO/WHERE/WHEN/facets/geo) is host-delegated: selection is\n // owned by the consumer via the `value` / `onChange` contract, so the agent\n // surface here is limited to clearing the query (`reset`) and focusing the\n // primary field. The previous `navigate` / `view_change` capabilities were\n // spurious — the variant is fixed by props (not agent-settable) and no\n // action navigates.\n capabilities: ['filter'],\n state: {\n variant: {\n type: 'string',\n descriptionKey: 'ui.agent.patientSearch.state.variant',\n description:\n 'Active variant — one of `who-where`, `who-where-when`, `faceted`, `discovery`.',\n read: (handle) => handle.getVariant(),\n },\n },\n actions: {\n reset: {\n safety: 'write',\n idempotent: true,\n descriptionKey: 'ui.agent.patientSearch.actions.reset',\n description:\n 'Clear the current search query (WHO, WHERE, WHEN, facets, geo) via the consumer-owned onChange.',\n invoke: (handle) => {\n handle.reset();\n },\n },\n focus_primary: {\n safety: 'read',\n idempotent: true,\n descriptionKey: 'ui.agent.patientSearch.actions.focusPrimary',\n description:\n 'Move keyboard focus to the most-relevant search field for the current variant.',\n invoke: (handle) => {\n handle.focusPrimary();\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'patient-search',\n description: 'Marks the PatientSearch 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, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport {\n ChevronDown,\n LocateFixed,\n MapPin,\n Search as SearchIcon,\n Stethoscope,\n X,\n} from 'lucide-react';\nimport { Autocomplete } from '../autocomplete/autocomplete';\nimport { Button } from '../button/button';\nimport { Card } from '../card/card';\nimport { Slider } from '../slider/slider';\nimport { Alert } from '../alert/alert';\nimport { DropdownMenu } from '../dropdown-menu/dropdown-menu';\nimport type { OptionShape } from '../_shared/option';\nimport { useAgentRegistration } from '../../agent';\nimport { patientSearchAgent } from './patient-search.agent';\n\n/* -------------------------------------------------------------------- */\n/* PatientSearch */\n/* */\n/* Top-of-funnel search primitive with four A/B-testable variants. Each */\n/* variant exposes a different combination of patient decision-making */\n/* dimensions (WHO, WHERE, WHEN, INSURANCE/LANGUAGE, DISCOVERY) so */\n/* consumers can run PostHog-driven funnel experiments without having to */\n/* re-implement the form per surface. */\n/* */\n/* Every variant's WHERE input bakes in a \"use my location\" affordance — */\n/* a `LocateFixed` crosshair button on the inline-end of the field, with */\n/* a radius slider that appears beneath on grant. Geolocation isn't a */\n/* variant of its own; it's a universal WHERE behaviour the kit always */\n/* offers. */\n/* */\n/* Consumers own the data loaders (`loadObjects`, `loadPlaces`, */\n/* `reverseGeocode`, `discoveryCards`) + the routing logic in */\n/* `onSubmit` / `onObjectSelect` / `onPlaceSelect`. The kit owns layout, */\n/* variant gating, accessibility, telemetry, and the shared `SearchValue`*/\n/* contract that every variant feeds into. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\nexport type PatientSearchVariant =\n | 'who-where'\n | 'who-where-when'\n | 'faceted'\n | 'discovery';\n\nexport type WhenFilter = 'today' | 'tomorrow' | 'this_week' | 'anytime';\n\n/** Result from `loadObjects` — a profession, specialty, operator, or practice. */\nexport interface PatientSearchObject {\n /** Stable opaque key, e.g. `\"profession:dentista\"` or `\"operator:mario-rossi-9001\"`. */\n value: string;\n label: string;\n description?: string;\n /** Opaque consumer payload — used in `onObjectSelect` to route. */\n __payload?: unknown;\n}\n\n/** Selected place, geolocation result, or opted-in typed address. */\nexport interface PatientSearchPlace {\n /** Provider value, `geo:<lat>,<lng>`, or `text:<trimmed address>` with `allowTypedPlace`. */\n value: string;\n /** Display label, e.g. `\"Milano, Italia\"`. */\n label: string;\n lat?: number;\n lng?: number;\n}\n\n/** Curated card for the `discovery` variant. */\nexport interface DiscoveryCard {\n id: string;\n label: string;\n description?: string;\n href: string;\n icon?: ReactNode;\n /**\n * Accent token. `auto` rotates through the brand palette by row index.\n * Defaults to `auto`.\n */\n accent?: 'violet' | 'purple' | 'magenta' | 'blue' | 'auto';\n badge?: string;\n}\n\nexport interface FacetOption {\n value: string;\n label: string;\n}\n\nexport interface FacetGroup {\n /** Stable key — e.g. `\"insurance\"`, `\"language\"`. */\n key: string;\n /** Display label, e.g. `\"Convenzione\"`. */\n label: string;\n /** Empty array = boolean toggle chip. Non-empty = dropdown chip. */\n options: FacetOption[];\n /** Whether multiple options can be selected. Default false. */\n multi?: boolean;\n}\n\nexport interface SearchValue {\n object?: PatientSearchObject;\n place?: PatientSearchPlace;\n when?: WhenFilter;\n facets?: Record<string, string[]>;\n geo?: {\n lat: number;\n lng: number;\n radiusKm: number;\n label?: string;\n };\n}\n\n/** Curated imperative handle — used by the agent adapter. */\nexport interface PatientSearchHandle {\n getVariant: () => PatientSearchVariant;\n reset: () => void;\n /** Move focus to the most-relevant field for the current variant. */\n focusPrimary: () => void;\n}\n\n/* -------------------------------------------------------------------- */\n/* Telemetry */\n/* */\n/* Mirrors the booking-website convention (`window.track?.(event, p)`). */\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/* Constants */\n/* -------------------------------------------------------------------- */\n\nconst WHEN_OPTIONS: readonly WhenFilter[] = [\n 'today',\n 'tomorrow',\n 'this_week',\n 'anytime',\n] as const;\n\n/** Discrete radius presets per spec (1 / 5 / 10 / 25 / 50 km). */\nconst RADIUS_PRESETS: readonly number[] = [1, 5, 10, 25, 50] as const;\n\nconst DEFAULT_RADIUS_KM = 5;\n\nconst AUTOCOMPLETE_DEBOUNCE_MS = 200;\n\n/** Maps `defaultRadiusKm` to the nearest preset index. */\nfunction radiusToStepIndex(km: number): number {\n let best = 0;\n let bestDelta = Number.POSITIVE_INFINITY;\n RADIUS_PRESETS.forEach((preset, index) => {\n const delta = Math.abs(preset - km);\n if (delta < bestDelta) {\n bestDelta = delta;\n best = index;\n }\n });\n return best;\n}\n\n/* -------------------------------------------------------------------- */\n/* Root variants */\n/* -------------------------------------------------------------------- */\n\nconst rootVariants = cva(\n 'ds:flex ds:w-full ds:flex-col ds:text-[var(--foreground)]',\n {\n variants: {\n variant: {\n 'who-where': 'ds:gap-[var(--spacing-md)]',\n 'who-where-when': 'ds:gap-[var(--spacing-md)]',\n faceted: 'ds:gap-[var(--spacing-md)]',\n discovery: 'ds:gap-[var(--spacing-lg)]',\n },\n },\n defaultVariants: { variant: 'who-where' },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* Public props */\n/* -------------------------------------------------------------------- */\n\nexport interface PatientSearchProps\n extends\n Omit<\n ComponentPropsWithoutRef<'div'>,\n 'aria-label' | 'onChange' | 'onSubmit'\n >,\n VariantProps<typeof rootVariants> {\n variant: PatientSearchVariant;\n\n // Data loaders — consumer-injected; kit calls them debounced.\n loadObjects: (\n query: string,\n ctx: { signal: AbortSignal },\n ) => Promise<PatientSearchObject[]>;\n loadPlaces: (\n query: string,\n ctx: { signal: AbortSignal },\n ) => Promise<PatientSearchPlace[]>;\n\n // Selection callbacks — the consumer decides whether to route.\n onObjectSelect?: (option: PatientSearchObject) => void;\n onPlaceSelect?: (place: PatientSearchPlace) => void;\n\n // Facet config — used by the `faceted` variant; ignored by others.\n facets?: FacetGroup[];\n\n // Geo config — applies to every variant's WHERE input. The crosshair\n // overlay on the inline-end of the input triggers geolocation on click;\n // on success the input fills with the reverse-geocoded label and a\n // radius slider appears beneath. Consumer-supplied `reverseGeocode` is\n // optional — when omitted, `value.geo.label` falls back to the localised\n // \"Vicino a me\" string.\n /** Default radius in km when geolocation is granted. Snaps to the nearest preset. */\n defaultRadiusKm?: number;\n /** Reverse-geocode lat/lng → human label. Optional. */\n reverseGeocode?: (lat: number, lng: number) => Promise<string>;\n\n // Discovery cards — used by the `discovery` variant.\n discoveryCards?: DiscoveryCard[];\n /**\n * Heading level (`h2`, `h3` or `h4`) of the discovery section title.\n * Pick the level that follows the heading above the search on the host\n * page, so the page keeps a correct heading order. Used by the\n * `discovery` variant only.\n * @default 3\n */\n discoveryHeadingLevel?: 2 | 3 | 4;\n\n /** Forwarded to the consumer's loaders if they want to constrain by country. */\n countryCode?: 'it' | 'de' | string;\n\n // State — controlled.\n value: SearchValue;\n onChange: (next: SearchValue) => void;\n onSubmit: (value: SearchValue) => void | Promise<void>;\n\n // UX\n submitting?: boolean;\n /**\n * Record unselected, trimmed WHERE text in `value.place` as\n * `{ value: 'text:' + typed, label: typed }`, without coordinates.\n * Empty text clears the place. Applies to every variant.\n * @default false\n */\n allowTypedPlace?: boolean;\n /** Custom CTA label override; defaults to `ui.patientSearch.submit.cta`. */\n submitLabel?: string;\n /**\n * @default false\n * By default the kit disables the submit button until at least one\n * search dimension is set (object, place, geo, non-anytime when, or\n * a facet). Set to `true` to allow an empty submit — useful for\n * \"browse all\" patterns where the consumer wants to show all results\n * with no initial filter.\n */\n allowEmptySubmit?: boolean;\n\n // a11y\n 'aria-label'?: string;\n /** Agent-readiness instance id. */\n id?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* WhoField — Autocomplete over union(profession, specialty, operator, */\n/* practice). */\n/* -------------------------------------------------------------------- */\n\ninterface WhoFieldProps {\n value: SearchValue;\n onChange: (next: SearchValue) => void;\n loadObjects: PatientSearchProps['loadObjects'];\n onObjectSelect?: PatientSearchProps['onObjectSelect'];\n inputRef?: React.Ref<HTMLInputElement>;\n}\n\nfunction WhoField({\n value,\n onChange,\n loadObjects,\n onObjectSelect,\n inputRef,\n}: WhoFieldProps) {\n const { t } = useTranslation('ui');\n\n // The Autocomplete owns its own visible text; the kit only intercepts\n // selection events to update `value.object`. We mirror external resets\n // (consumer clears `value.object` programmatically) into the visible text\n // via the controlled `value` prop, but otherwise let typed input flow\n // through Autocomplete's internal state. The local `query` state breaks\n // the controlled-input cycle: typing fires onChange(next), we store\n // `next` locally, the next render passes that exact string back as\n // `value` — no flicker, no truncation.\n const [query, setQuery] = useState<string>(value.object?.label ?? '');\n const selectedValueRef = useRef<string | undefined>(value.object?.value);\n\n useEffect(() => {\n // Sync external selection changes (e.g. consumer-driven reset) into the\n // visible text. We watch `value.object?.value` rather than `label` so an\n // identity update with the same label doesn't clobber a fresh query.\n if (value.object?.value !== selectedValueRef.current) {\n selectedValueRef.current = value.object?.value;\n setQuery(value.object?.label ?? '');\n }\n }, [value.object?.value, value.object?.label]);\n\n // Adapt consumer's `loadObjects` to Autocomplete's `loadOptions` signature.\n // PatientSearchObject is structurally a superset of OptionShape, so the\n // returned array is safe to pass through as `OptionShape<string>[]`.\n const loadOptions = useCallback(\n async (q: string, { signal }: { signal: AbortSignal; locale: string }) =>\n (await loadObjects(q, { signal })) as OptionShape<string>[],\n [loadObjects],\n );\n\n const handleSelect = (option: OptionShape<string>) => {\n const next: PatientSearchObject = {\n value: option.value,\n label: option.label,\n description: option.description,\n __payload: (option as PatientSearchObject).__payload,\n };\n setQuery(next.label);\n selectedValueRef.current = next.value;\n onChange({ ...value, object: next });\n track('patient_search_object_selected', {\n value: next.value,\n type:\n next.__payload && typeof next.__payload === 'object'\n ? (next.__payload as { type?: string }).type\n : undefined,\n });\n onObjectSelect?.(next);\n };\n\n // Autocomplete's public props don't include `onFocus`, so we capture focus\n // on a wrapping <div> via the bubbling event from the embedded input.\n // `data-component=\"patient-search-field-who\"` lets agents target the\n // wrapper for instrumentation hooks without depending on Autocomplete's\n // internal structure.\n const focusTrackedRef = useRef(false);\n return (\n <div\n data-component=\"patient-search-field\"\n data-field=\"who\"\n onFocus={() => {\n if (focusTrackedRef.current) return;\n focusTrackedRef.current = true;\n track('patient_search_object_focused');\n }}\n onBlur={(e) => {\n // Reset the once-per-mount focus flag only when focus leaves the\n // wrapper entirely (no relatedTarget inside our subtree).\n if (\n e.currentTarget &&\n !e.currentTarget.contains(e.relatedTarget as Node | null)\n ) {\n focusTrackedRef.current = false;\n }\n }}\n >\n {/* Large, the same height as the large search button beside it. */}\n <Autocomplete\n ref={inputRef}\n size=\"lg\"\n value={query}\n onChange={(next) => {\n setQuery(next);\n // User edited the input after selection — drop the structured\n // selection so submit can't reuse stale data.\n if (value.object && next !== value.object.label) {\n selectedValueRef.current = undefined;\n onChange({ ...value, object: undefined });\n }\n }}\n onSelect={handleSelect}\n loadOptions={loadOptions}\n debounceMs={AUTOCOMPLETE_DEBOUNCE_MS}\n placeholder={t('patientSearch.who.placeholder')}\n aria-label={t('patientSearch.who.label')}\n startAdornment={<SearchIcon aria-hidden=\"true\" />}\n />\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* WhereField — Autocomplete over the consumer's places provider. */\n/* -------------------------------------------------------------------- */\n\ninterface WhereFieldProps {\n value: SearchValue;\n onChange: (next: SearchValue) => void;\n loadPlaces: PatientSearchProps['loadPlaces'];\n onPlaceSelect?: PatientSearchProps['onPlaceSelect'];\n allowTypedPlace?: boolean;\n inputRef?: React.Ref<HTMLInputElement>;\n /**\n * Default radius (km) applied on a successful geolocate. Snaps to the\n * nearest preset (1 / 5 / 10 / 25 / 50). Defaults to 5 km.\n */\n defaultRadiusKm?: number;\n /** Reverse-geocode lat/lng → human label. Optional. */\n reverseGeocode?: PatientSearchProps['reverseGeocode'];\n}\n\ntype GeoStatus = 'pristine' | 'requesting' | 'granted' | 'denied';\n\nconst geoTriggerClasses = [\n 'ds:inline-flex ds:items-center ds:justify-center',\n 'ds:size-8 ds:rounded-[var(--radius-sm)]',\n 'ds:text-[color:var(--muted-foreground)] ds:hover:text-[color:var(--primary)]',\n 'ds:hover:bg-[color-mix(in_srgb,var(--primary)_8%,transparent)]',\n 'ds:aria-pressed:text-[color:var(--primary)]',\n 'ds:aria-pressed:bg-[color-mix(in_srgb,var(--primary)_12%,transparent)]',\n 'ds:disabled:opacity-50 ds:disabled:cursor-not-allowed',\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/**\n * Unified WHERE input — every variant uses it.\n *\n * Layout per state:\n * - pristine: `[MapPin] City... [LocateFixed]`\n * - granted : `[MapPin] Milano, Italia [X] [LocateFixed]` + radius slider beneath\n * - denied : `[MapPin] City... ` + Alert beneath\n *\n * The MapPin on the inline-start is the conventional \"this is a location\n * field\" marker. The LocateFixed crosshair on the inline-end is the\n * geolocate trigger — these are deliberately different glyphs (Doctolib /\n * Google Maps / Booking pattern) so users don't conflate the field marker\n * with the action.\n */\nfunction WhereField({\n value,\n onChange,\n loadPlaces,\n onPlaceSelect,\n allowTypedPlace,\n inputRef,\n defaultRadiusKm = DEFAULT_RADIUS_KM,\n reverseGeocode,\n}: WhereFieldProps) {\n const { t } = useTranslation('ui');\n const [query, setQuery] = useState<string>(value.place?.label ?? '');\n const selectedValueRef = useRef<string | undefined>(value.place?.value);\n const initialStatus: GeoStatus = value.geo ? 'granted' : 'pristine';\n const [status, setStatus] = useState<GeoStatus>(initialStatus);\n\n useEffect(() => {\n if (value.place?.value !== selectedValueRef.current) {\n selectedValueRef.current = value.place?.value;\n setQuery(value.place?.label ?? '');\n }\n }, [value.place?.value, value.place?.label]);\n\n const loadOptions = useCallback(\n async (q: string, { signal }: { signal: AbortSignal; locale: string }) =>\n (await loadPlaces(q, { signal })) as OptionShape<string>[],\n [loadPlaces],\n );\n\n const handleSelect = (option: OptionShape<string>) => {\n const place: PatientSearchPlace = {\n value: option.value,\n label: option.label,\n lat: (option as PatientSearchPlace).lat,\n lng: (option as PatientSearchPlace).lng,\n };\n setQuery(place.label);\n selectedValueRef.current = place.value;\n // Typing or selecting a city through the autocomplete cancels any\n // active geolocate — the consumer's search API should now run plain\n // text search rather than lat/lng-radius.\n onChange({ ...value, place, geo: undefined });\n if (status === 'granted') setStatus('pristine');\n track('patient_search_place_selected', { value: place.value });\n onPlaceSelect?.(place);\n };\n\n const requestLocation = () => {\n if (typeof window === 'undefined' || !navigator?.geolocation) {\n setStatus('denied');\n track('patient_search_geo_denied', { reason: 'unsupported' });\n return;\n }\n setStatus('requesting');\n track('patient_search_place_focused');\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude } = position.coords;\n let label: string | undefined;\n try {\n label = await reverseGeocode?.(latitude, longitude);\n } catch {\n label = undefined;\n }\n const km = defaultRadiusKm;\n const resolvedLabel = label ?? t('patientSearch.geo.fallbackLabel');\n // Mirror the geo label into `value.place` so the visible input\n // shows the reverse-geocoded text. `value.geo` carries the\n // structured lat/lng/radius the consumer's search API actually\n // needs. The synthetic place carries a `geo:` value prefix so\n // the consumer can distinguish a geo-derived place from a\n // user-selected Google Places entry.\n const syntheticPlace: PatientSearchPlace = {\n value: `geo:${latitude.toFixed(4)},${longitude.toFixed(4)}`,\n label: resolvedLabel,\n lat: latitude,\n lng: longitude,\n };\n onChange({\n ...value,\n place: syntheticPlace,\n geo: {\n lat: latitude,\n lng: longitude,\n radiusKm: km,\n label: resolvedLabel,\n },\n });\n setStatus('granted');\n track('patient_search_geo_granted', { radiusKm: km });\n },\n () => {\n setStatus('denied');\n track('patient_search_geo_denied', { reason: 'user' });\n },\n { enableHighAccuracy: false, maximumAge: 60_000, timeout: 10_000 },\n );\n };\n\n const clearGeo = () => {\n onChange({ ...value, geo: undefined, place: undefined });\n setQuery('');\n selectedValueRef.current = undefined;\n setStatus('pristine');\n };\n\n const isGranted = status === 'granted' && Boolean(value.geo);\n const isRequesting = status === 'requesting';\n const isDenied = status === 'denied';\n\n const focusTrackedRef = useRef(false);\n // Overlay button(s) that sit visually inside the inline-end of the\n // input. Rendered as siblings of the Autocomplete (not inside the\n // `endAdornment` slot, which the kit wraps in `aria-hidden` and so\n // cannot host an interactive trigger). The decorative spacer in\n // `endAdornment` carves out room so typed text doesn't run under\n // the overlay.\n // Gate the geo-assist UI on the consumer wiring `reverseGeocode`.\n // Without it, clicking \"Vicino a me\" would set value.geo to lat/lng\n // coordinates the kit can't translate into a human-readable label —\n // the patient sees coordinates or nothing. Presence of the dep\n // implies intent.\n const geoEnabled = Boolean(reverseGeocode);\n const overlay =\n isDenied || !geoEnabled ? null : (\n <span className=\"ds:absolute ds:end-[var(--spacing-xs)] ds:top-1/2 ds:-translate-y-1/2 ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n {isGranted ? (\n <button\n type=\"button\"\n aria-label={t('patientSearch.geo.clear')}\n onClick={clearGeo}\n className={geoTriggerClasses}\n >\n <X aria-hidden=\"true\" className=\"ds:size-4\" />\n </button>\n ) : null}\n <button\n type=\"button\"\n aria-label={t('patientSearch.geo.cta')}\n aria-pressed={isGranted || undefined}\n disabled={isRequesting}\n onClick={isGranted ? clearGeo : requestLocation}\n className={geoTriggerClasses}\n >\n {/*\n The geo trigger uses `LocateFixed` (target-with-dot crosshair)\n rather than `MapPin`. The WHERE input's start-adornment is the\n `MapPin` (\"this is a location field\"); reusing the pin glyph\n here would visually conflate the field marker with the action.\n Crosshair = \"centre on me\" — Doctolib / Google Maps / Booking\n all use this distinction.\n */}\n <LocateFixed aria-hidden=\"true\" className=\"ds:size-4\" />\n </button>\n </span>\n );\n\n return (\n <div\n data-component=\"patient-search-field\"\n data-field=\"where\"\n onFocus={() => {\n if (focusTrackedRef.current) return;\n focusTrackedRef.current = true;\n track('patient_search_place_focused');\n }}\n onBlur={(e) => {\n if (\n e.currentTarget &&\n !e.currentTarget.contains(e.relatedTarget as Node | null)\n ) {\n focusTrackedRef.current = false;\n }\n }}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\"\n >\n <div className=\"ds:relative\">\n {/* Large, the same height as the large search button beside it. */}\n <Autocomplete\n ref={inputRef}\n size=\"lg\"\n value={query}\n onChange={(next) => {\n setQuery(next);\n if (\n (allowTypedPlace || value.place) &&\n next !== value.place?.label\n ) {\n const typed = next.trim();\n const place =\n allowTypedPlace && typed\n ? { value: `text:${typed}`, label: typed }\n : undefined;\n // Recognise our own controlled-state update so the sync effect\n // preserves the raw input, including spaces between words.\n selectedValueRef.current = place?.value;\n // Skip a no-op update (a trailing space, or spaces typed with no\n // place yet) so hosts that search on change are not re-triggered.\n if (place?.value === value.place?.value && !value.geo) return;\n // Manual edit cancels any active geolocate too — the typed\n // text owns the field now.\n onChange({ ...value, place, geo: undefined });\n if (status === 'granted') setStatus('pristine');\n }\n }}\n onSelect={handleSelect}\n loadOptions={loadOptions}\n debounceMs={AUTOCOMPLETE_DEBOUNCE_MS}\n placeholder={t('patientSearch.where.placeholder')}\n aria-label={t('patientSearch.where.label')}\n startAdornment={<MapPin aria-hidden=\"true\" />}\n endAdornment={\n isDenied ? undefined : (\n // Decorative spacer — reserves inline-end room so the typed\n // text doesn't run under the absolutely-positioned overlay.\n // Autocomplete wraps endAdornment in `aria-hidden`, which is\n // correct for this empty span. Granted-state shows an extra\n // clear-X chip, so the spacer doubles in width when granted.\n <span\n aria-hidden=\"true\"\n className={\n isGranted\n ? 'ds:inline-block ds:size-[4.5rem]'\n : 'ds:inline-block ds:size-8'\n }\n />\n )\n }\n />\n {overlay}\n </div>\n {isDenied ? (\n // Alert defaults to `role=\"status\"` + `aria-live=\"polite\"` for the\n // `info` variant — the user hears why the field reverted to plain\n // text when their browser blocked the geolocation prompt.\n <Alert variant=\"info\">\n <Alert.Description>{t('patientSearch.geo.denied')}</Alert.Description>\n </Alert>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* GeoRadiusBar — full-width radius slider rendered beneath the top */\n/* row when geo-assist is active. Lifted out of `WhereField` so the */\n/* WHO/WHERE/Submit row stays at single-row height when the slider is */\n/* visible (release notes 0.30.2 — see `ds-kit-followup-from-0.30.0`). */\n/* -------------------------------------------------------------------- */\n\ninterface GeoRadiusBarProps {\n value: SearchValue;\n onChange: (next: SearchValue) => void;\n defaultRadiusKm?: number;\n}\n\nfunction GeoRadiusBar({\n value,\n onChange,\n defaultRadiusKm = DEFAULT_RADIUS_KM,\n}: GeoRadiusBarProps) {\n const { t } = useTranslation('ui');\n const [radiusIndex, setRadiusIndex] = useState<number>(() =>\n radiusToStepIndex(value.geo?.radiusKm ?? defaultRadiusKm),\n );\n if (!value.geo) return null;\n const commitRadius = (index: number) => {\n const km = RADIUS_PRESETS[index] ?? defaultRadiusKm;\n onChange({ ...value, geo: { ...value.geo!, radiusKm: km } });\n track('patient_search_radius_changed', { radiusKm: km });\n };\n const formatRadius = (n: number) => {\n const km = RADIUS_PRESETS[Math.round(n)] ?? defaultRadiusKm;\n return t('patientSearch.geo.radiusFormat', { km });\n };\n return (\n <div\n data-component=\"patient-search-geo-radius-bar\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)] ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)] ds:pb-[var(--spacing-md)]\"\n >\n <span\n id=\"patient-search-radius-label\"\n className=\"type-meta ds:text-[color:var(--muted-foreground)]\"\n >\n {t('patientSearch.geo.radius')}\n </span>\n <Slider\n value={[radiusIndex]}\n min={0}\n max={RADIUS_PRESETS.length - 1}\n step={1}\n marks={RADIUS_PRESETS.map((km, index) => ({\n value: index,\n label: `${km}`,\n }))}\n formatValue={formatRadius}\n alwaysShowValue\n aria-labelledby=\"patient-search-radius-label\"\n onValueChange={(next) => setRadiusIndex(next[0] ?? 0)}\n onValueCommit={(next) => commitRadius(next[0] ?? 0)}\n />\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* WhenChipStrip — radiogroup of {today, tomorrow, this_week, anytime} */\n/* */\n/* Hand-rolled radiogroup: `role=\"radiogroup\"` on the container, button */\n/* children carry `role=\"radio\"` + `aria-checked`, arrow-key navigation */\n/* moves focus + selection. Visually presents as suggestion-style chips. */\n/* -------------------------------------------------------------------- */\n\nconst whenChipVariants = cva(\n [\n 'ds:inline-flex ds:items-center ds:justify-center',\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 ds:font-[var(--font-weight-medium)]',\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 selected:\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\ninterface WhenChipStripProps {\n value?: WhenFilter;\n onChange: (next: WhenFilter) => void;\n}\n\nfunction WhenChipStrip({ value, onChange }: WhenChipStripProps) {\n const { t, i18n } = useTranslation('ui');\n const groupId = useId();\n const isRTL = i18n.dir() === 'rtl';\n\n const buttonRefs = useRef<Map<WhenFilter, HTMLButtonElement>>(new Map());\n\n const focusAt = useCallback((opt: WhenFilter) => {\n buttonRefs.current.get(opt)?.focus();\n }, []);\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLButtonElement>, current: WhenFilter) => {\n const idx = WHEN_OPTIONS.indexOf(current);\n const total = WHEN_OPTIONS.length;\n const forwardKey = isRTL ? 'ArrowLeft' : 'ArrowRight';\n const backwardKey = isRTL ? 'ArrowRight' : 'ArrowLeft';\n switch (event.key) {\n case forwardKey:\n case 'ArrowDown': {\n event.preventDefault();\n const next = WHEN_OPTIONS[(idx + 1) % total];\n onChange(next);\n track('patient_search_when_selected', { when: next });\n focusAt(next);\n break;\n }\n case backwardKey:\n case 'ArrowUp': {\n event.preventDefault();\n const next = WHEN_OPTIONS[(idx - 1 + total) % total];\n onChange(next);\n track('patient_search_when_selected', { when: next });\n focusAt(next);\n break;\n }\n case 'Home': {\n event.preventDefault();\n const next = WHEN_OPTIONS[0];\n onChange(next);\n track('patient_search_when_selected', { when: next });\n focusAt(next);\n break;\n }\n case 'End': {\n event.preventDefault();\n const next = WHEN_OPTIONS[total - 1];\n onChange(next);\n track('patient_search_when_selected', { when: next });\n focusAt(next);\n break;\n }\n default:\n break;\n }\n },\n [focusAt, isRTL, onChange],\n );\n\n // Roving tabindex — the selected option (or `anytime` fallback) is the only\n // tab stop. Matches the WAI-ARIA radiogroup pattern.\n const rovingOption: WhenFilter = value ?? 'anytime';\n\n const labelKeyFor: Record<WhenFilter, string> = {\n today: 'patientSearch.when.today',\n tomorrow: 'patientSearch.when.tomorrow',\n this_week: 'patientSearch.when.thisWeek',\n anytime: 'patientSearch.when.anytime',\n };\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={t('patientSearch.when.label')}\n id={groupId}\n className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\"\n >\n {WHEN_OPTIONS.map((opt) => {\n const isSelected = value === opt;\n const isRoving = rovingOption === opt;\n const label = t(labelKeyFor[opt]);\n return (\n <button\n key={opt}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n aria-label={label}\n tabIndex={isRoving ? 0 : -1}\n ref={(node) => {\n if (node) buttonRefs.current.set(opt, node);\n else buttonRefs.current.delete(opt);\n }}\n onClick={() => {\n onChange(opt);\n track('patient_search_when_selected', { when: opt });\n }}\n onKeyDown={(e) => handleKeyDown(e, opt)}\n className={whenChipVariants({\n state: isSelected ? 'selected' : 'idle',\n })}\n >\n {label}\n </button>\n );\n })}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* FacetChipRow — faceted variant. One chip per FacetGroup. */\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 // Matches the WHEN chip strip's `selected` look — solid primary fill,\n // primary-foreground text, transparent border. The earlier\n // `color-mix(primary 12% / secondary)` tint read as a washed-out\n // colour over the grey input row and didn't visually signal \"this\n // filter is active\" with enough weight. Solid fill matches the kit's\n // toggle-button precedent (SuggestionChip's `suggestion` intent + the\n // ServiceGrid radio's selected state in Booking).\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\ninterface FacetChipRowProps {\n facets: FacetGroup[];\n value: SearchValue['facets'];\n onChange: (next: Record<string, string[]>) => void;\n}\n\nfunction FacetChipRow({ facets, value, onChange }: FacetChipRowProps) {\n const { t } = useTranslation('ui');\n // `selections` is memoised because `value ?? {}` allocates a fresh `{}`\n // whenever the consumer passes `undefined`, which would otherwise invalidate\n // every downstream useMemo / useCallback that depends on it.\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('patient_search_facet_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 return (\n <div\n role=\"group\"\n aria-label={t('patientSearch.facets.label')}\n className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\"\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\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 // Keep the menu open for multi-select facets so the user\n // can tick multiple boxes without re-opening.\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('patient_search_facet_changed', { key: '*', values: [] });\n }}\n className={[\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 <X aria-hidden=\"true\" className=\"ds:size-3.5\" />\n {t('patientSearch.facets.clear')}\n </button>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* DiscoveryGrid — responsive grid of curated cards. */\n/* -------------------------------------------------------------------- */\n\nconst WHEEL_ACCENTS = ['violet', 'purple', 'magenta', 'blue'] as const;\ntype WheelAccent = (typeof WHEEL_ACCENTS)[number];\n\nconst discoveryCardVariants = cva(\n [\n 'ds:relative ds:flex ds:h-full ds:flex-col ds:gap-[var(--spacing-sm)]',\n 'ds:rounded-[var(--radius-lg)] ds:overflow-hidden',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n 'ds:bg-[var(--card)] ds:text-[var(--card-foreground)]',\n 'ds:border ds:border-[color:var(--card-border)]',\n 'ds:shadow-[var(--shadow-sm)]',\n 'ds:transition-[transform,box-shadow] ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:motion-safe:hover:-translate-y-0.5',\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:no-underline',\n ].join(' '),\n);\n\nconst accentStripVariants = cva(\n // `inset-x-0` compiles to logical `inset-inline`; the `inset-inline-0`\n // spelling is not a Tailwind utility and compiled to nothing.\n 'ds:absolute ds:inset-x-0 ds:top-0 ds:h-[3px] ds:pointer-events-none',\n {\n variants: {\n accent: {\n violet: 'ds:bg-[var(--color-violet-500)]',\n purple: 'ds:bg-[var(--color-purple-500)]',\n magenta: 'ds:bg-[var(--color-magenta-500)]',\n blue: 'ds:bg-[var(--color-blue-500)]',\n },\n },\n defaultVariants: { accent: 'violet' },\n },\n);\n\nconst iconChipVariants = cva(\n 'ds:inline-flex ds:size-9 ds:items-center ds:justify-center ds:rounded-[var(--radius-md)]',\n {\n variants: {\n accent: {\n violet:\n 'ds:bg-[color-mix(in_srgb,var(--color-violet-500)_12%,var(--background))] ds:text-[var(--color-violet-700)]',\n purple:\n 'ds:bg-[color-mix(in_srgb,var(--color-purple-500)_12%,var(--background))] ds:text-[var(--color-purple-700)]',\n magenta:\n 'ds:bg-[color-mix(in_srgb,var(--color-magenta-500)_12%,var(--background))] ds:text-[var(--color-magenta-700)]',\n blue: 'ds:bg-[color-mix(in_srgb,var(--color-blue-500)_12%,var(--background))] ds:text-[var(--color-blue-700)]',\n },\n },\n defaultVariants: { accent: 'violet' },\n },\n);\n\nfunction resolveAccent(\n cardAccent: DiscoveryCard['accent'] | undefined,\n index: number,\n): WheelAccent {\n if (cardAccent && cardAccent !== 'auto') return cardAccent;\n return WHEEL_ACCENTS[index % WHEEL_ACCENTS.length];\n}\n\ninterface DiscoveryGridProps {\n cards: DiscoveryCard[];\n headingLevel: 2 | 3 | 4;\n}\n\nfunction DiscoveryGrid({ cards, headingLevel }: DiscoveryGridProps) {\n const { t } = useTranslation('ui');\n const Heading = `h${headingLevel}` as const;\n\n if (cards.length === 0) {\n return (\n <p className=\"type-body-sm ds:text-[color:var(--muted-foreground)] ds:m-0\">\n {t('patientSearch.discovery.empty')}\n </p>\n );\n }\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <Heading className=\"type-title-card ds:text-[var(--foreground)] ds:m-0\">\n {t('patientSearch.discovery.heading')}\n </Heading>\n <ul\n className={[\n 'ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)]',\n 'ds:md:grid-cols-2 ds:lg:grid-cols-3',\n 'ds:m-0 ds:p-0 ds:list-none',\n ].join(' ')}\n >\n {cards.map((card, index) => {\n const accent = resolveAccent(card.accent, index);\n return (\n <li key={card.id}>\n <a\n href={card.href}\n data-component=\"patient-search-card\"\n data-card-id={card.id}\n aria-label={\n card.description\n ? `${card.label} — ${card.description}`\n : card.label\n }\n onClick={() =>\n track('patient_search_discovery_card_clicked', {\n cardId: card.id,\n })\n }\n className={discoveryCardVariants()}\n >\n <span\n aria-hidden=\"true\"\n className={accentStripVariants({ accent })}\n />\n <span className=\"ds:flex ds:items-center ds:gap-[var(--spacing-sm)]\">\n <span\n aria-hidden=\"true\"\n className={iconChipVariants({ accent })}\n >\n {card.icon ?? (\n <Stethoscope className=\"ds:size-4\" aria-hidden=\"true\" />\n )}\n </span>\n {card.badge ? (\n // The badge sits inside a softly-tinted accent bubble\n // (15% accent + 85% background). The text colour must\n // contrast with that mix, not with the accent's canonical\n // foreground — `--accent-foreground` is calibrated against\n // the 100% accent surface and fails AA on the 15% tint.\n // Use `--foreground` which contrasts with the\n // mostly-background mix in both light and dark themes.\n <span className=\"type-meta ds:ms-auto ds:inline-flex ds:items-center ds:rounded-[var(--radius-full)] ds:border ds:border-[color:var(--foreground)] ds:bg-[color-mix(in_srgb,var(--accent)_15%,var(--background))] ds:text-[color:var(--foreground)] ds:font-[var(--font-weight-semibold)] ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)] ds:pt-[var(--spacing-xs)] ds:pb-[var(--spacing-xs)]\">\n {card.badge}\n </span>\n ) : null}\n </span>\n <span className=\"type-label ds:font-[var(--font-weight-semibold)] ds:text-[var(--foreground)]\">\n {card.label}\n </span>\n {card.description ? (\n <span className=\"type-body-sm ds:text-[color:var(--muted-foreground)]\">\n {card.description}\n </span>\n ) : null}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* SubmitButton — shared CTA. Disabled only when every dimension is unset. */\n/* -------------------------------------------------------------------- */\n\ninterface SubmitButtonProps {\n value: SearchValue;\n variant: PatientSearchVariant;\n submitting?: boolean;\n submitLabel?: string;\n allowEmptySubmit?: boolean;\n onSubmit: (value: SearchValue) => void | Promise<void>;\n}\n\nfunction hasAnyDimension(value: SearchValue): boolean {\n if (value.object) return true;\n if (value.place) return true;\n if (value.geo) return true;\n if (value.when && value.when !== 'anytime') return true;\n if (value.facets) {\n for (const arr of Object.values(value.facets)) {\n if (arr && arr.length > 0) return true;\n }\n }\n return false;\n}\n\nfunction SubmitButton({\n value,\n variant,\n submitting,\n submitLabel,\n allowEmptySubmit,\n onSubmit,\n}: SubmitButtonProps) {\n const { t } = useTranslation('ui');\n // While submitting, `loading` blocks it and keeps focus on it.\n const disabled = !submitting && !allowEmptySubmit && !hasAnyDimension(value);\n const label = submitLabel ?? t('patientSearch.submit.cta');\n return (\n <Button\n type=\"button\"\n intent=\"primary\"\n size=\"lg\"\n startIcon={<SearchIcon aria-hidden=\"true\" />}\n loading={submitting}\n disabled={disabled}\n onClick={() => {\n track('patient_search_submitted', {\n variant,\n hasObject: Boolean(value.object),\n hasPlace: Boolean(value.place),\n hasGeo: Boolean(value.geo),\n when: value.when,\n facetKeys: value.facets ? Object.keys(value.facets) : [],\n });\n void onSubmit(value);\n }}\n >\n {label}\n </Button>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* Variant body props */\n/* -------------------------------------------------------------------- */\n\ninterface VariantBodyProps {\n variant: PatientSearchVariant;\n value: SearchValue;\n onChange: (next: SearchValue) => void;\n onSubmit: PatientSearchProps['onSubmit'];\n loadObjects: PatientSearchProps['loadObjects'];\n loadPlaces: PatientSearchProps['loadPlaces'];\n onObjectSelect?: PatientSearchProps['onObjectSelect'];\n onPlaceSelect?: PatientSearchProps['onPlaceSelect'];\n allowTypedPlace?: boolean;\n facets?: FacetGroup[];\n defaultRadiusKm: number;\n reverseGeocode?: PatientSearchProps['reverseGeocode'];\n discoveryCards: DiscoveryCard[];\n discoveryHeadingLevel: 2 | 3 | 4;\n submitting?: boolean;\n submitLabel?: string;\n allowEmptySubmit?: boolean;\n whoRef: React.RefObject<HTMLInputElement | null>;\n whereRef: React.RefObject<HTMLInputElement | null>;\n /**\n * Accessible name applied to the inner `<Card>` so its underlying\n * `<article>` landmark isn't unnamed when nested inside the\n * `role=\"region\"` root. Without this every variant produces an\n * unnamed article — a `landmark-unique` axe hazard once a consumer\n * page renders adjacent articles. Resolved once at the root with\n * `t('patientSearch.regionLabel')` and threaded through so the\n * variant bodies don't each have to call `useTranslation('ui')`.\n */\n cardLabel: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* WhoWhereBody — baseline variant. WHO + WHERE + submit. */\n/* -------------------------------------------------------------------- */\n\nfunction WhoWhereBody(props: VariantBodyProps) {\n const {\n variant,\n value,\n onChange,\n onSubmit,\n loadObjects,\n loadPlaces,\n onObjectSelect,\n onPlaceSelect,\n allowTypedPlace,\n submitting,\n submitLabel,\n allowEmptySubmit,\n whoRef,\n whereRef,\n defaultRadiusKm,\n reverseGeocode,\n cardLabel,\n } = props;\n return (\n <Card variant=\"elevated\" aria-label={cardLabel}>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:md:flex-row ds:md:items-start\">\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhoField\n value={value}\n onChange={onChange}\n loadObjects={loadObjects}\n onObjectSelect={onObjectSelect}\n inputRef={whoRef}\n />\n </div>\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhereField\n value={value}\n onChange={onChange}\n loadPlaces={loadPlaces}\n onPlaceSelect={onPlaceSelect}\n allowTypedPlace={allowTypedPlace}\n defaultRadiusKm={defaultRadiusKm}\n reverseGeocode={reverseGeocode}\n inputRef={whereRef}\n />\n </div>\n <SubmitButton\n value={value}\n variant={variant}\n submitting={submitting}\n submitLabel={submitLabel}\n allowEmptySubmit={allowEmptySubmit}\n onSubmit={onSubmit}\n />\n </div>\n <GeoRadiusBar\n value={value}\n onChange={onChange}\n defaultRadiusKm={defaultRadiusKm}\n />\n </div>\n </Card>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* WhoWhereWhenBody — WHO + WHERE + submit + WHEN chip strip below. */\n/* -------------------------------------------------------------------- */\n\nfunction WhoWhereWhenBody(props: VariantBodyProps) {\n const {\n variant,\n value,\n onChange,\n onSubmit,\n loadObjects,\n loadPlaces,\n onObjectSelect,\n onPlaceSelect,\n allowTypedPlace,\n submitting,\n submitLabel,\n allowEmptySubmit,\n whoRef,\n whereRef,\n defaultRadiusKm,\n reverseGeocode,\n cardLabel,\n } = props;\n return (\n <Card variant=\"elevated\" aria-label={cardLabel}>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:p-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:md:flex-row ds:md:items-start\">\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhoField\n value={value}\n onChange={onChange}\n loadObjects={loadObjects}\n onObjectSelect={onObjectSelect}\n inputRef={whoRef}\n />\n </div>\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhereField\n value={value}\n onChange={onChange}\n loadPlaces={loadPlaces}\n onPlaceSelect={onPlaceSelect}\n allowTypedPlace={allowTypedPlace}\n defaultRadiusKm={defaultRadiusKm}\n reverseGeocode={reverseGeocode}\n inputRef={whereRef}\n />\n </div>\n <SubmitButton\n value={value}\n variant={variant}\n submitting={submitting}\n submitLabel={submitLabel}\n allowEmptySubmit={allowEmptySubmit}\n onSubmit={onSubmit}\n />\n </div>\n <GeoRadiusBar\n value={value}\n onChange={onChange}\n defaultRadiusKm={defaultRadiusKm}\n />\n <WhenChipStrip\n value={value.when}\n onChange={(next) => onChange({ ...value, when: next })}\n />\n </div>\n </Card>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* FacetedBody — WHO + WHERE + submit + facet chip row below. */\n/* -------------------------------------------------------------------- */\n\nfunction FacetedBody(props: VariantBodyProps) {\n const {\n variant,\n value,\n onChange,\n onSubmit,\n loadObjects,\n loadPlaces,\n onObjectSelect,\n onPlaceSelect,\n allowTypedPlace,\n facets,\n submitting,\n submitLabel,\n allowEmptySubmit,\n whoRef,\n whereRef,\n defaultRadiusKm,\n reverseGeocode,\n cardLabel,\n } = props;\n return (\n <Card variant=\"elevated\" aria-label={cardLabel}>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:p-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:md:flex-row ds:md:items-start\">\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhoField\n value={value}\n onChange={onChange}\n loadObjects={loadObjects}\n onObjectSelect={onObjectSelect}\n inputRef={whoRef}\n />\n </div>\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhereField\n value={value}\n onChange={onChange}\n loadPlaces={loadPlaces}\n onPlaceSelect={onPlaceSelect}\n allowTypedPlace={allowTypedPlace}\n defaultRadiusKm={defaultRadiusKm}\n reverseGeocode={reverseGeocode}\n inputRef={whereRef}\n />\n </div>\n <SubmitButton\n value={value}\n variant={variant}\n submitting={submitting}\n submitLabel={submitLabel}\n allowEmptySubmit={allowEmptySubmit}\n onSubmit={onSubmit}\n />\n </div>\n <GeoRadiusBar\n value={value}\n onChange={onChange}\n defaultRadiusKm={defaultRadiusKm}\n />\n {facets && facets.length > 0 ? (\n <FacetChipRow\n facets={facets}\n value={value.facets}\n onChange={(next) => onChange({ ...value, facets: next })}\n />\n ) : null}\n </div>\n </Card>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* DiscoveryBody — hero search + card grid. */\n/* -------------------------------------------------------------------- */\n\nfunction DiscoveryBody(props: VariantBodyProps) {\n const {\n variant,\n value,\n onChange,\n onSubmit,\n loadObjects,\n loadPlaces,\n onObjectSelect,\n onPlaceSelect,\n allowTypedPlace,\n discoveryCards,\n discoveryHeadingLevel,\n submitting,\n submitLabel,\n allowEmptySubmit,\n whoRef,\n whereRef,\n defaultRadiusKm,\n reverseGeocode,\n cardLabel,\n } = props;\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]\">\n <Card variant=\"elevated\" aria-label={cardLabel}>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-md)] ds:md:flex-row ds:md:items-stretch\">\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhoField\n value={value}\n onChange={onChange}\n loadObjects={loadObjects}\n onObjectSelect={onObjectSelect}\n inputRef={whoRef}\n />\n </div>\n <div className=\"ds:flex-1 ds:min-w-0\">\n <WhereField\n value={value}\n onChange={onChange}\n loadPlaces={loadPlaces}\n onPlaceSelect={onPlaceSelect}\n allowTypedPlace={allowTypedPlace}\n defaultRadiusKm={defaultRadiusKm}\n reverseGeocode={reverseGeocode}\n inputRef={whereRef}\n />\n </div>\n <SubmitButton\n value={value}\n variant={variant}\n submitting={submitting}\n submitLabel={submitLabel}\n allowEmptySubmit={allowEmptySubmit}\n onSubmit={onSubmit}\n />\n </div>\n <GeoRadiusBar\n value={value}\n onChange={onChange}\n defaultRadiusKm={defaultRadiusKm}\n />\n </Card>\n <DiscoveryGrid\n cards={discoveryCards}\n headingLevel={discoveryHeadingLevel}\n />\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* PatientSearch */\n/* -------------------------------------------------------------------- */\n\nexport const PatientSearch = forwardRef<\n PatientSearchHandle,\n PatientSearchProps\n>(function PatientSearch(\n {\n variant,\n loadObjects,\n loadPlaces,\n onObjectSelect,\n onPlaceSelect,\n allowTypedPlace = false,\n facets,\n defaultRadiusKm = DEFAULT_RADIUS_KM,\n reverseGeocode,\n discoveryCards,\n discoveryHeadingLevel = 3,\n countryCode,\n value,\n onChange,\n onSubmit,\n submitting,\n submitLabel,\n allowEmptySubmit,\n 'aria-label': ariaLabel,\n id,\n className,\n ...rest\n },\n ref,\n) {\n const { t } = useTranslation('ui');\n\n const rootRef = useRef<HTMLDivElement>(null);\n\n // Primary-field refs — `focusPrimary` jumps to whichever input the variant\n // emphasises (WHO in most variants; no auto-focus on `discovery`).\n const whoRef = useRef<HTMLInputElement>(null);\n const whereRef = useRef<HTMLInputElement>(null);\n\n // Apply `when: anytime` default once on mount, but only for the variant\n // that actually renders the WHEN chip strip (`who-where-when`). The\n // `faceted` variant exposes facets, not WHEN — seeding `when: 'anytime'`\n // for it would silently pollute the consumer's value with a field the\n // form never displays.\n const seededWhenRef = useRef(false);\n useEffect(() => {\n if (seededWhenRef.current) return;\n if (variant !== 'who-where-when') return;\n if (value.when !== undefined) {\n seededWhenRef.current = true;\n return;\n }\n seededWhenRef.current = true;\n onChange({ ...value, when: 'anytime' });\n // We intentionally seed only on first render — the consumer can clear\n // back to `undefined` if they want, and we won't re-seed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [variant]);\n\n // Apply `facets: {}` default once on mount for the faceted variant so\n // the chip row treats undefined and empty consistently.\n const seededFacetsRef = useRef(false);\n useEffect(() => {\n if (seededFacetsRef.current) return;\n if (variant !== 'faceted') return;\n if (value.facets !== undefined) {\n seededFacetsRef.current = true;\n return;\n }\n seededFacetsRef.current = true;\n onChange({ ...value, facets: {} });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [variant]);\n\n // Exposure event — fired once per variant assignment so PostHog can\n // record A/B assignment even for users who bounce before submitting.\n const exposedRef = useRef<PatientSearchVariant | null>(null);\n useEffect(() => {\n if (exposedRef.current === variant) return;\n exposedRef.current = variant;\n track('patient_search_variant_exposed', { variant, country: countryCode });\n }, [variant, countryCode]);\n\n // Imperative handle — minimal surface (`getVariant`, `reset`,\n // `focusPrimary`). The `value` reset goes through the consumer-owned\n // `onChange` so the parent's state stays the source of truth.\n //\n // The same handle is surfaced two ways: through the React `ref` (for\n // consumers driving the kit directly) and through `useAgentRegistration`\n // (for the future agent / MCP runtime). Both reach the same object so a\n // direct `ref.current?.reset()` and an `agentRegistry.invoke('reset')`\n // do the same thing.\n const handle = useMemo<PatientSearchHandle>(\n () => ({\n getVariant: () => variant,\n reset: () => {\n onChange({});\n },\n focusPrimary: () => {\n // Discovery deliberately doesn't auto-focus the search field on\n // mount; agents hitting `focus_primary` still get pulled into WHO\n // since that's the natural starting point for any keyboard-driven\n // flow once the user opts in.\n requestAnimationFrame(() => {\n whoRef.current?.focus();\n });\n },\n }),\n [variant, onChange],\n );\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(patientSearchAgent, handle, id);\n\n const resolvedAriaLabel = ariaLabel ?? t('patientSearch.regionLabel');\n\n const bodyProps: VariantBodyProps = {\n variant,\n value,\n onChange,\n onSubmit,\n loadObjects,\n loadPlaces,\n onObjectSelect,\n onPlaceSelect,\n allowTypedPlace,\n facets,\n defaultRadiusKm,\n reverseGeocode,\n discoveryCards: discoveryCards ?? [],\n discoveryHeadingLevel,\n submitting,\n submitLabel,\n allowEmptySubmit,\n whoRef,\n whereRef,\n cardLabel: resolvedAriaLabel,\n };\n\n const body = (() => {\n switch (variant) {\n case 'who-where':\n return <WhoWhereBody {...bodyProps} />;\n case 'who-where-when':\n return <WhoWhereWhenBody {...bodyProps} />;\n case 'faceted':\n return <FacetedBody {...bodyProps} />;\n case 'discovery':\n return <DiscoveryBody {...bodyProps} />;\n /* c8 ignore next 2 */\n default:\n return null;\n }\n })();\n\n return (\n <div\n ref={rootRef}\n role=\"region\"\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"patient-search\"\n data-component-id={id}\n data-variant={variant}\n className={rootVariants({ variant, className })}\n {...rest}\n >\n {body}\n </div>\n );\n});\n\nPatientSearch.displayName = 'PatientSearch';\n"],"names":["__iconNode","LocateFixed","createLucideIcon","patientSearchAgent","handle","track","event","payload","fn","WHEN_OPTIONS","RADIUS_PRESETS","DEFAULT_RADIUS_KM","AUTOCOMPLETE_DEBOUNCE_MS","radiusToStepIndex","km","best","bestDelta","preset","index","delta","rootVariants","cva","WhoField","value","onChange","loadObjects","onObjectSelect","inputRef","t","useTranslation","query","setQuery","useState","_a","selectedValueRef","useRef","_b","useEffect","_c","_d","loadOptions","useCallback","q","signal","handleSelect","option","next","focusTrackedRef","jsx","e","Autocomplete","SearchIcon","geoTriggerClasses","WhereField","loadPlaces","onPlaceSelect","allowTypedPlace","defaultRadiusKm","reverseGeocode","initialStatus","status","setStatus","place","requestLocation","position","latitude","longitude","label","resolvedLabel","syntheticPlace","clearGeo","isGranted","isRequesting","isDenied","overlay","jsxs","X","typed","MapPin","Alert","GeoRadiusBar","radiusIndex","setRadiusIndex","commitRadius","formatRadius","n","Slider","whenChipVariants","WhenChipStrip","i18n","groupId","useId","isRTL","buttonRefs","focusAt","opt","handleKeyDown","current","idx","total","forwardKey","backwardKey","rovingOption","labelKeyFor","isSelected","isRoving","node","facetChipVariants","FacetChipRow","facets","selections","useMemo","hasAnyActive","arr","updateFacet","key","values","toggleBoolean","isActive","toggleOption","group","active","v","isBoolean","countSuffix","DropdownMenu","ChevronDown","WHEEL_ACCENTS","discoveryCardVariants","accentStripVariants","iconChipVariants","resolveAccent","cardAccent","DiscoveryGrid","cards","headingLevel","Heading","card","accent","Stethoscope","hasAnyDimension","SubmitButton","variant","submitting","submitLabel","allowEmptySubmit","onSubmit","disabled","Button","WhoWhereBody","props","whoRef","whereRef","cardLabel","Card","WhoWhereWhenBody","FacetedBody","DiscoveryBody","discoveryCards","discoveryHeadingLevel","PatientSearch","forwardRef","countryCode","ariaLabel","id","className","rest","ref","rootRef","seededWhenRef","seededFacetsRef","exposedRef","useImperativeHandle","useAgentRegistration","resolvedAriaLabel","bodyProps","body"],"mappings":"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","x_google_ignoreList":[0]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reviews-panel-UzQQLb-z.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 // Pure ephemeral narrowing of the visible list — no data loss.\n safety: 'read',\n argsType: '{ filter: \"all\" | 1 | 2 | 3 | 4 | 5 }',\n argsSchema: {\n type: 'object',\n properties: {\n filter: {\n description: 'Star bucket to show, or \"all\" to clear the filter.',\n oneOf: [\n { type: 'string', enum: ['all'] },\n { type: 'integer', enum: [1, 2, 3, 4, 5] },\n ],\n },\n },\n required: ['filter'],\n },\n idempotent: true,\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 examples: [\n { args: { filter: 5 }, note: 'Show only 5-star reviews.' },\n { args: { filter: 'all' }, note: 'Clear the star filter.' },\n ],\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, Star } 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';\nimport { PanelHeader } from '../panel-header';\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/* FilterBar — hand-rolled radiogroup with roving tabindex */\n/* */\n/* Lives in the shared `PanelHeader` filters row, so it lays out */\n/* HORIZONTALLY (a wrapping bar of star-bucket chips) rather than the */\n/* legacy vertical sidebar column. Still the WAI-ARIA RadioGroup pattern */\n/* — each chip is a full click target with a non-text leading glyph (the */\n/* star ramp) and a trailing count. NOT Radix `RadioGroup` because we */\n/* need that glyph-plus-count chip shape. */\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:inline-flex ds:items-center ds:justify-center',\n 'ds:gap-[var(--spacing-xs)]',\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 FilterBarProps {\n filter: ReviewFilter;\n histogram: Record<1 | 2 | 3 | 4 | 5, number>;\n totalCount: number;\n onChange: (next: ReviewFilter) => void;\n}\n\nfunction FilterBar({\n filter,\n histogram,\n totalCount,\n onChange,\n}: FilterBarProps) {\n const { t } = useTranslation('ui');\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 <>\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-wrap ds:items-center 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={[\n 'type-meta',\n // Idle row: count is dimmed (--muted-foreground over\n // --background ≈ 4.5:1, AA passes). Selected row\n // paints violet bg + white text on the BUTTON, but\n // the count's own explicit color override would\n // otherwise win the cascade and leave dark navy-grey\n // text on the violet pill (~3:1, AA-fail). Match the\n // row's foreground when selected; slight opacity\n // keeps the count secondary to the label.\n isSelected\n ? 'ds:text-[color:var(--primary-foreground)]'\n : 'ds:text-[var(--muted-foreground)]',\n ].join(' ')}\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </>\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('ui');\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\n dir=\"auto\"\n className=\"type-body-sm ds:m-0 ds:text-[var(--foreground)] ds:[white-space:pre-line]\"\n >\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 {/* The patient's pseudonym and comment, and the practice's reply,\n keep the direction they were typed in. The kit's own\n \"Anonymous\" follows the page. */}\n <h4\n dir={review.pseudonym ? 'auto' : undefined}\n className=\"type-title-item ds:m-0 ds:text-[var(--foreground)]\"\n >\n {pseudonym}\n </h4>\n <Rating value={review.overallScore} size=\"sm\" />\n </div>\n </Card.Header>\n {review.comment ? (\n <Card.Body>\n <p\n dir=\"auto\"\n className=\"type-body ds:m-0 ds:text-[var(--foreground)] ds:line-clamp-2\"\n >\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 {!hideFilter ? (\n <div className=\"ds:flex ds:flex-wrap ds:gap-[var(--spacing-xs)]\">\n {Array.from({ length: 6 }, (_, i) => (\n <Skeleton\n key={`rp-fs-${i}`}\n variant=\"rounded\"\n width=\"4rem\"\n height=\"2.5rem\"\n />\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 );\n}\n\n/* -------------------------------------------------------------------- */\n/* Root variants */\n/* -------------------------------------------------------------------- */\n\nconst rootVariants = cva(\n // No root padding: the shared `PanelHeader` owns the header's edge\n // padding (px/pt/pb) and the body wrapper below owns the content\n // padding, so the header divider can span flush to the panel edge.\n 'ds:flex ds:w-full ds:flex-col ds:text-[var(--foreground)]',\n {\n variants: {\n // `surface=\"elevated\"` matches `Card variant=\"elevated\"` — same\n // chrome tokens — so consumers can drop their own Card wraps\n // without producing a double-card look. Padding is supplied by the\n // header + body, not the root, so the header divider reaches the\n // card edge. Default `'flat'` adds no chrome.\n surface: {\n flat: '',\n elevated: [\n 'ds:overflow-hidden',\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 ].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('ui');\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 <div className=\"ds:p-[var(--spacing-md)]\">\n <PanelSkeleton hideFilter={hideFilter} />\n </div>\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 <div className=\"ds:p-[var(--spacing-md)]\">\n <EmptyState\n variant=\"no-results\"\n title={t('reviewsPanel.empty.title')}\n description={t('reviewsPanel.empty.description')}\n />\n </div>\n </section>\n );\n }\n\n const showFilter = !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 {/* Shared rail header — the panel root is a region landmark with an\n accessible name, so this nested <header> is a generic element\n (not a banner) and stays within the region. The rail injects the\n favourite / close actions via PanelHeaderActionsProvider; the\n panel's own aggregate Rating + headerActionSlot lead the cluster.\n The rating summary is wide (about 260px), so on a phone it moves\n under the heading rather than clipping it to a letter or two. */}\n <PanelHeader\n title={t('reviewsPanel.title')}\n icon={<Star aria-hidden=\"true\" />}\n titleScale=\"section\"\n sticky\n wrapActions\n actions={\n <>\n {headerActionSlot}\n <Rating\n value={aggregate.averageScore}\n reviews={{ count: aggregate.count }}\n size=\"md\"\n />\n </>\n }\n filters={\n showFilter ? (\n <FilterBar\n filter={filter}\n histogram={histogram}\n totalCount={aggregate.count}\n onChange={handleFilterChange}\n />\n ) : undefined\n }\n />\n\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:min-w-0 ds:p-[var(--spacing-md)]\">\n {introSlot\n ? // Render the slot raw — the consumer's introSlot owns\n // its own visual chrome (Alert, Infotip, plain <p>, …).\n // 0.31.x wrapped the slot in a `bg-info/8` tinted box,\n // but when the consumer passed an Alert with its own\n // tinted surface the visual stack rendered TWO concentric\n // tinted boxes (\"double effect\"). The wrapper added no\n // semantic value either (no role, no aria), so dropping\n // it is purely visual.\n introSlot\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 </section>\n );\n },\n);\n\nReviewsPanel.displayName = 'ReviewsPanel';\n"],"names":["reviewsPanelAgent","handle","args","FILTER_VALUES","bucketOf","score","deriveHistogram","reviews","out","r","filterRowVariants","cva","FilterBar","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","Fragment","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","showFilter","PanelHeader","Star","Button"],"mappings":"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"}