@alfadocs/ui-kit-debug 1.113.0 → 1.114.0
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.
- package/dist/_chunks/{anamnesis-card-BzuluoD1.js → anamnesis-card-Ddnrdfx3.js} +2 -2
- package/dist/_chunks/{anamnesis-card-BzuluoD1.js.map → anamnesis-card-Ddnrdfx3.js.map} +1 -1
- package/dist/_chunks/{anamnesis-summary-card-DdTRdNrQ.js → anamnesis-summary-card-S1H_AARG.js} +2 -2
- package/dist/_chunks/{anamnesis-summary-card-DdTRdNrQ.js.map → anamnesis-summary-card-S1H_AARG.js.map} +1 -1
- package/dist/_chunks/{anamnesis-summary-rows-DfDulR_c.js → anamnesis-summary-rows-DaS-j8Jd.js} +2 -2
- package/dist/_chunks/{anamnesis-summary-rows-DfDulR_c.js.map → anamnesis-summary-rows-DaS-j8Jd.js.map} +1 -1
- package/dist/_chunks/{antenatal-schedule-timeline-DmsY5g28.js → antenatal-schedule-timeline-DmlXxevC.js} +2 -2
- package/dist/_chunks/{antenatal-schedule-timeline-DmsY5g28.js.map → antenatal-schedule-timeline-DmlXxevC.js.map} +1 -1
- package/dist/_chunks/{bishop-score-CnIft4eV.js → bishop-score-QJu-sM1y.js} +2 -2
- package/dist/_chunks/{bishop-score-CnIft4eV.js.map → bishop-score-QJu-sM1y.js.map} +1 -1
- package/dist/_chunks/{bmi-calculator-NMkXc079.js → bmi-calculator-Ba4yoR31.js} +2 -2
- package/dist/_chunks/{bmi-calculator-NMkXc079.js.map → bmi-calculator-Ba4yoR31.js.map} +1 -1
- package/dist/_chunks/{calendar-appointment-editor-dialog-CaZm5wGR.js → calendar-appointment-editor-dialog-iyh9QqNw.js} +6 -6
- package/dist/_chunks/{calendar-appointment-editor-dialog-CaZm5wGR.js.map → calendar-appointment-editor-dialog-iyh9QqNw.js.map} +1 -1
- package/dist/_chunks/{card-C85_Ndhb.js → card-DVO3x8E-.js} +23 -19
- package/dist/_chunks/{card-C85_Ndhb.js.map → card-DVO3x8E-.js.map} +1 -1
- package/dist/_chunks/{clinical-note-card-BVkxGiGl.js → clinical-note-card-Zz5_GEz-.js} +2 -2
- package/dist/_chunks/{clinical-note-card-BVkxGiGl.js.map → clinical-note-card-Zz5_GEz-.js.map} +1 -1
- package/dist/_chunks/{connection-status-CWSU3eiq.js → connection-status-DjarASoU.js} +2 -2
- package/dist/_chunks/{connection-status-CWSU3eiq.js.map → connection-status-DjarASoU.js.map} +1 -1
- package/dist/_chunks/{contact-card-CymtNvy3.js → contact-card-Cv-Nwvac.js} +2 -2
- package/dist/_chunks/{contact-card-CymtNvy3.js.map → contact-card-Cv-Nwvac.js.map} +1 -1
- package/dist/_chunks/{contact-profile-card-CAL66GaH.js → contact-profile-card-DMXWBBkU.js} +2 -2
- package/dist/_chunks/{contact-profile-card-CAL66GaH.js.map → contact-profile-card-DMXWBBkU.js.map} +1 -1
- package/dist/_chunks/{cycle-calculator-ZMDvToIm.js → cycle-calculator-Dd-XbKmZ.js} +2 -2
- package/dist/_chunks/{cycle-calculator-ZMDvToIm.js.map → cycle-calculator-Dd-XbKmZ.js.map} +1 -1
- package/dist/_chunks/{document-preview-5xs64jME.js → document-preview-WvJKHJOP.js} +2 -2
- package/dist/_chunks/{document-preview-5xs64jME.js.map → document-preview-WvJKHJOP.js.map} +1 -1
- package/dist/_chunks/{due-date-calculator-Dr2cDa64.js → due-date-calculator-Dl6SyMHO.js} +3 -3
- package/dist/_chunks/{due-date-calculator-Dr2cDa64.js.map → due-date-calculator-Dl6SyMHO.js.map} +1 -1
- package/dist/_chunks/{entity-summary-BbqqYqF1.js → entity-summary-DGCBQDt1.js} +2 -2
- package/dist/_chunks/{entity-summary-BbqqYqF1.js.map → entity-summary-DGCBQDt1.js.map} +1 -1
- package/dist/_chunks/{face-scheme-e2M2HJGz.js → face-scheme-DlcQaQVR.js} +2 -2
- package/dist/_chunks/{face-scheme-e2M2HJGz.js.map → face-scheme-DlcQaQVR.js.map} +1 -1
- package/dist/_chunks/{fetal-weight-CkIwJgiH.js → fetal-weight-DmZzMRzB.js} +2 -2
- package/dist/_chunks/{fetal-weight-CkIwJgiH.js.map → fetal-weight-DmZzMRzB.js.map} +1 -1
- package/dist/_chunks/{finished-terminal-DXVAfhRr.js → finished-terminal-BKTFUk3S.js} +2 -2
- package/dist/_chunks/{finished-terminal-DXVAfhRr.js.map → finished-terminal-BKTFUk3S.js.map} +1 -1
- package/dist/_chunks/{forgot-password-form-YQVzSskq.js → forgot-password-form-Bc_UPax6.js} +2 -2
- package/dist/_chunks/{forgot-password-form-YQVzSskq.js.map → forgot-password-form-Bc_UPax6.js.map} +1 -1
- package/dist/_chunks/{gestational-age-calculator-Dv3IePb-.js → gestational-age-calculator-DmzYdW5e.js} +3 -3
- package/dist/_chunks/{gestational-age-calculator-Dv3IePb-.js.map → gestational-age-calculator-DmzYdW5e.js.map} +1 -1
- package/dist/_chunks/{hcg-doubling-DdyUt4gz.js → hcg-doubling-Tr72PetS.js} +2 -2
- package/dist/_chunks/{hcg-doubling-DdyUt4gz.js.map → hcg-doubling-Tr72PetS.js.map} +1 -1
- package/dist/_chunks/{heatmap-BiwC7Gdx.js → heatmap-CNxCDl0D.js} +2 -2
- package/dist/_chunks/{heatmap-BiwC7Gdx.js.map → heatmap-CNxCDl0D.js.map} +1 -1
- package/dist/_chunks/{kit-extensions-BOsUgWnE.js → kit-extensions-DZJRmDI6.js} +7 -7
- package/dist/_chunks/{kit-extensions-BOsUgWnE.js.map → kit-extensions-DZJRmDI6.js.map} +1 -1
- package/dist/_chunks/{login-dev-card-B6-zCFq3.js → login-dev-card-D7orCfJT.js} +2 -2
- package/dist/_chunks/{login-dev-card-B6-zCFq3.js.map → login-dev-card-D7orCfJT.js.map} +1 -1
- package/dist/_chunks/map-view-Bny79cWa.js +856 -0
- package/dist/_chunks/map-view-Bny79cWa.js.map +1 -0
- package/dist/_chunks/{marketplace-app-shell-C-4QF6mp.js → marketplace-app-shell-CPNVBVLH.js} +2 -2
- package/dist/_chunks/{marketplace-app-shell-C-4QF6mp.js.map → marketplace-app-shell-CPNVBVLH.js.map} +1 -1
- package/dist/_chunks/motion-is-off-BvAuNV5O.js +15 -0
- package/dist/_chunks/motion-is-off-BvAuNV5O.js.map +1 -0
- package/dist/_chunks/{notes-panel-Cz621DWV.js → notes-panel-DXMTObBv.js} +3 -3
- package/dist/_chunks/{notes-panel-Cz621DWV.js.map → notes-panel-DXMTObBv.js.map} +1 -1
- package/dist/_chunks/{operator-hero-BELhrv9o.js → operator-hero-BWayX73x.js} +71 -62
- package/dist/_chunks/{operator-hero-BELhrv9o.js.map → operator-hero-BWayX73x.js.map} +1 -1
- package/dist/_chunks/{patient-details-RsT3SaYh.js → patient-details-DdtXBNjt.js} +2 -2
- package/dist/_chunks/{patient-details-RsT3SaYh.js.map → patient-details-DdtXBNjt.js.map} +1 -1
- package/dist/_chunks/{patient-search-CxuqVUTB.js → patient-search-DDRDCjZi.js} +439 -482
- package/dist/_chunks/patient-search-DDRDCjZi.js.map +1 -0
- package/dist/_chunks/{patient-summary-card-BjZErlbt.js → patient-summary-card-CGHz-Bvm.js} +2 -2
- package/dist/_chunks/{patient-summary-card-BjZErlbt.js.map → patient-summary-card-CGHz-Bvm.js.map} +1 -1
- package/dist/_chunks/{payment-automation-upsell-u5EuFyCY.js → payment-automation-upsell-D7yaD4u1.js} +3 -3
- package/dist/_chunks/{payment-automation-upsell-u5EuFyCY.js.map → payment-automation-upsell-D7yaD4u1.js.map} +1 -1
- package/dist/_chunks/{payment-request-card-IR83TJax.js → payment-request-card-BmV2pLoM.js} +2 -2
- package/dist/_chunks/{payment-request-card-IR83TJax.js.map → payment-request-card-BmV2pLoM.js.map} +1 -1
- package/dist/_chunks/{periodontal-chart-DqdUEFxK.js → periodontal-chart-Bjz7O4wT.js} +2 -2
- package/dist/_chunks/{periodontal-chart-DqdUEFxK.js.map → periodontal-chart-Bjz7O4wT.js.map} +1 -1
- package/dist/_chunks/{periodontal-chart-card-CCkzStlm.js → periodontal-chart-card-CxEXHl2x.js} +2 -2
- package/dist/_chunks/{periodontal-chart-card-CCkzStlm.js.map → periodontal-chart-card-CxEXHl2x.js.map} +1 -1
- package/dist/_chunks/{periodontal-diagnosis-CF1d_0yl.js → periodontal-diagnosis-xheWfi4g.js} +2 -2
- package/dist/_chunks/{periodontal-diagnosis-CF1d_0yl.js.map → periodontal-diagnosis-xheWfi4g.js.map} +1 -1
- package/dist/_chunks/{periodontal-risk-chart-D2zyID0H.js → periodontal-risk-chart-BBPB9wf1.js} +2 -2
- package/dist/_chunks/{periodontal-risk-chart-D2zyID0H.js.map → periodontal-risk-chart-BBPB9wf1.js.map} +1 -1
- package/dist/_chunks/{practice-profile-hero-Bb4YyxHA.js → practice-profile-hero-Cw4NZp0A.js} +2 -2
- package/dist/_chunks/{practice-profile-hero-Bb4YyxHA.js.map → practice-profile-hero-Cw4NZp0A.js.map} +1 -1
- package/dist/_chunks/practice-results-BlxfBx-D.js +2181 -0
- package/dist/_chunks/practice-results-BlxfBx-D.js.map +1 -0
- package/dist/_chunks/{pregnancy-dating-Cs8xpZA8.js → pregnancy-dating-BvorzHrd.js} +3 -3
- package/dist/_chunks/{pregnancy-dating-Cs8xpZA8.js.map → pregnancy-dating-BvorzHrd.js.map} +1 -1
- package/dist/_chunks/{pregnancy-weight-gain-B_Zldlzq.js → pregnancy-weight-gain-DQoOM8QV.js} +2 -2
- package/dist/_chunks/{pregnancy-weight-gain-B_Zldlzq.js.map → pregnancy-weight-gain-DQoOM8QV.js.map} +1 -1
- package/dist/_chunks/{privacy-mask-DbEKI-Jv.js → privacy-mask-Dc3kdkCR.js} +2 -2
- package/dist/_chunks/{privacy-mask-DbEKI-Jv.js.map → privacy-mask-Dc3kdkCR.js.map} +1 -1
- package/dist/_chunks/{public-footer-DGjLTtNK.js → public-footer-CB7ruuLI.js} +295 -260
- package/dist/_chunks/public-footer-CB7ruuLI.js.map +1 -0
- package/dist/_chunks/{public-header.agent-_ArDD1Xr.js → public-header.agent-Db4wm6_S.js} +203 -193
- package/dist/_chunks/public-header.agent-Db4wm6_S.js.map +1 -0
- package/dist/_chunks/{registration-form-Cq5_OSIL.js → registration-form-Lw9T7IXr.js} +2 -2
- package/dist/_chunks/{registration-form-Cq5_OSIL.js.map → registration-form-Lw9T7IXr.js.map} +1 -1
- package/dist/_chunks/{reserved.agent-BMkapgom.js → reserved.agent-D2jwJPac.js} +21 -31
- package/dist/_chunks/reserved.agent-D2jwJPac.js.map +1 -0
- package/dist/_chunks/{reset-password-form-BVsL2fOJ.js → reset-password-form-C93SH0Tx.js} +2 -2
- package/dist/_chunks/{reset-password-form-BVsL2fOJ.js.map → reset-password-form-C93SH0Tx.js.map} +1 -1
- package/dist/_chunks/{reviews-panel-B03auSpQ.js → reviews-panel-CfJPEkH7.js} +2 -2
- package/dist/_chunks/{reviews-panel-B03auSpQ.js.map → reviews-panel-CfJPEkH7.js.map} +1 -1
- package/dist/_chunks/{section-card-D1sV4Voz.js → section-card-DFr2wGal.js} +2 -2
- package/dist/_chunks/{section-card-D1sV4Voz.js.map → section-card-DFr2wGal.js.map} +1 -1
- package/dist/_chunks/{send-registration-link-form-OgPomrZN.js → send-registration-link-form-DfbTjrEK.js} +2 -2
- package/dist/_chunks/{send-registration-link-form-OgPomrZN.js.map → send-registration-link-form-DfbTjrEK.js.map} +1 -1
- package/dist/_chunks/{signed-out-frame-CPow3aCZ.js → signed-out-frame-bh7fn_Px.js} +2 -2
- package/dist/_chunks/{signed-out-frame-CPow3aCZ.js.map → signed-out-frame-bh7fn_Px.js.map} +1 -1
- package/dist/_chunks/{stat-BFKZV50y.js → stat-WGLdn5Vn.js} +113 -107
- package/dist/_chunks/stat-WGLdn5Vn.js.map +1 -0
- package/dist/_chunks/{task-card-TSJjuuDe.js → task-card-DqVoitRk.js} +2 -2
- package/dist/_chunks/{task-card-TSJjuuDe.js.map → task-card-DqVoitRk.js.map} +1 -1
- package/dist/_chunks/{task-editor-gqWF1jI6.js → task-editor-D--LwVrg.js} +2 -2
- package/dist/_chunks/{task-editor-gqWF1jI6.js.map → task-editor-D--LwVrg.js.map} +1 -1
- package/dist/_chunks/{task-tray-DrXfMCuf.js → task-tray-C6j8HVMv.js} +2 -2
- package/dist/_chunks/{task-tray-DrXfMCuf.js.map → task-tray-C6j8HVMv.js.map} +1 -1
- package/dist/_chunks/{tasks-panel-OuIaydK5.js → tasks-panel-D6PuF4MQ.js} +2 -2
- package/dist/_chunks/{tasks-panel-OuIaydK5.js.map → tasks-panel-D6PuF4MQ.js.map} +1 -1
- package/dist/_chunks/{timeline-CKUaBZOx.js → timeline-AMqE-c2T.js} +2 -2
- package/dist/_chunks/{timeline-CKUaBZOx.js.map → timeline-AMqE-c2T.js.map} +1 -1
- package/dist/_chunks/{use-appointment-details-read-DvK7Looa.js → use-appointment-details-read-BK_Gw1GG.js} +5 -5
- package/dist/_chunks/{use-appointment-details-read-DvK7Looa.js.map → use-appointment-details-read-BK_Gw1GG.js.map} +1 -1
- package/dist/_chunks/{workflow-map-DWk-rZse.js → workflow-map-B4XkcET0.js} +2 -2
- package/dist/_chunks/{workflow-map-DWk-rZse.js.map → workflow-map-B4XkcET0.js.map} +1 -1
- package/dist/_chunks/{workflow-webhook-action-DILFqAVd.js → workflow-webhook-action-CtBn5JvO.js} +2 -2
- package/dist/_chunks/{workflow-webhook-action-DILFqAVd.js.map → workflow-webhook-action-CtBn5JvO.js.map} +1 -1
- package/dist/agent-catalog.json +6 -6
- package/dist/agent-i18n/en.json +2 -2
- package/dist/components/_shared/motion-is-off.d.ts +7 -0
- package/dist/components/_shared/motion-is-off.d.ts.map +1 -0
- package/dist/components/anamnesis-card/index.js +1 -1
- package/dist/components/anamnesis-summary-card/index.js +1 -1
- package/dist/components/appointment-details-panel/index.js +1 -1
- package/dist/components/bishop-score/index.js +1 -1
- package/dist/components/bmi-calculator/index.js +1 -1
- package/dist/components/booking/index.js +1 -1
- package/dist/components/booking/step-focus.d.ts +0 -6
- package/dist/components/booking/step-focus.d.ts.map +1 -1
- package/dist/components/card/card.d.ts.map +1 -1
- package/dist/components/card/index.js +1 -1
- package/dist/components/clinical-note-card/index.js +1 -1
- package/dist/components/connection-status/index.js +1 -1
- package/dist/components/contact-card/index.js +1 -1
- package/dist/components/contact-profile-card/index.js +1 -1
- package/dist/components/cycle-calculator/index.js +1 -1
- package/dist/components/document-preview/index.js +1 -1
- package/dist/components/due-date-calculator/index.js +2 -2
- package/dist/components/entity-summary/index.js +1 -1
- package/dist/components/face-scheme/index.js +1 -1
- package/dist/components/fetal-weight/index.js +1 -1
- package/dist/components/finished-terminal/index.js +1 -1
- package/dist/components/gestational-age-calculator/index.js +1 -1
- package/dist/components/hcg-doubling/index.js +1 -1
- package/dist/components/heatmap/index.js +1 -1
- package/dist/components/map-view/index.js +1 -1
- package/dist/components/map-view/map-view.d.ts +16 -0
- package/dist/components/map-view/map-view.d.ts.map +1 -1
- package/dist/components/notes-panel/index.js +1 -1
- package/dist/components/operator-hero/index.js +1 -1
- package/dist/components/operator-hero/operator-hero.d.ts.map +1 -1
- package/dist/components/patient-details/index.js +1 -1
- package/dist/components/patient-search/index.js +1 -1
- package/dist/components/patient-search/patient-search.d.ts +6 -2
- package/dist/components/patient-search/patient-search.d.ts.map +1 -1
- package/dist/components/patient-summary-card/index.js +1 -1
- package/dist/components/payment-card/index.js +1 -1
- package/dist/components/periodontal-chart/index.js +1 -1
- package/dist/components/periodontal-chart-card/index.js +1 -1
- package/dist/components/periodontal-diagnosis/index.js +1 -1
- package/dist/components/periodontal-risk-chart/index.js +1 -1
- package/dist/components/practice-profile-hero/index.js +1 -1
- package/dist/components/practice-results/index.js +1 -1
- package/dist/components/practice-results/practice-results.agent.d.ts.map +1 -1
- package/dist/components/practice-results/practice-results.d.ts +15 -2
- package/dist/components/practice-results/practice-results.d.ts.map +1 -1
- package/dist/components/pregnancy-dating/index.js +1 -1
- package/dist/components/pregnancy-weight-gain/index.js +1 -1
- package/dist/components/public-footer/index.js +1 -1
- package/dist/components/public-footer/public-footer.d.ts +23 -0
- package/dist/components/public-footer/public-footer.d.ts.map +1 -1
- package/dist/components/public-header/index.js +1 -1
- package/dist/components/public-header/public-header.d.ts +7 -0
- package/dist/components/public-header/public-header.d.ts.map +1 -1
- package/dist/components/reviews-panel/index.js +1 -1
- package/dist/components/rich-text-editor/index.js +1 -1
- package/dist/components/section-card/index.js +1 -1
- package/dist/components/stat/index.js +1 -1
- package/dist/components/stat/stat.d.ts +7 -0
- package/dist/components/stat/stat.d.ts.map +1 -1
- package/dist/components/task-card/index.js +1 -1
- package/dist/components/task-editor/index.js +1 -1
- package/dist/components/task-tray/index.js +1 -1
- package/dist/components/tasks-panel/index.js +1 -1
- package/dist/components/timeline/index.js +1 -1
- package/dist/components/workflow/index.js +1 -1
- package/dist/core.css +1 -1
- package/dist/host-compat.css +4 -1
- package/dist/i18n/locales/ar.d.ts +1 -0
- package/dist/i18n/locales/ar.d.ts.map +1 -1
- package/dist/i18n/locales/ar.js +2 -1
- package/dist/i18n/locales/ar.js.map +1 -1
- package/dist/i18n/locales/de.d.ts +1 -0
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/de.js +2 -1
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/el.d.ts +1 -0
- package/dist/i18n/locales/el.d.ts.map +1 -1
- package/dist/i18n/locales/el.js +2 -1
- package/dist/i18n/locales/el.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +1 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +1 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +1 -0
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/es.js +2 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +1 -0
- package/dist/i18n/locales/fr.d.ts.map +1 -1
- package/dist/i18n/locales/fr.js +2 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hi.d.ts +1 -0
- package/dist/i18n/locales/hi.d.ts.map +1 -1
- package/dist/i18n/locales/hi.js +2 -1
- package/dist/i18n/locales/hi.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +1 -0
- package/dist/i18n/locales/it.d.ts.map +1 -1
- package/dist/i18n/locales/it.js +2 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts +1 -0
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +2 -1
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/i18n/locales/nl.d.ts +1 -0
- package/dist/i18n/locales/nl.d.ts.map +1 -1
- package/dist/i18n/locales/nl.js +2 -1
- package/dist/i18n/locales/nl.js.map +1 -1
- package/dist/i18n/locales/pl.d.ts +1 -0
- package/dist/i18n/locales/pl.d.ts.map +1 -1
- package/dist/i18n/locales/pl.js +2 -1
- package/dist/i18n/locales/pl.js.map +1 -1
- package/dist/i18n/locales/pt.d.ts +1 -0
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/pt.js +2 -1
- package/dist/i18n/locales/pt.js.map +1 -1
- package/dist/i18n/locales/ro.d.ts +1 -0
- package/dist/i18n/locales/ro.d.ts.map +1 -1
- package/dist/i18n/locales/ro.js +2 -1
- package/dist/i18n/locales/ro.js.map +1 -1
- package/dist/i18n/locales/ru.d.ts +1 -0
- package/dist/i18n/locales/ru.d.ts.map +1 -1
- package/dist/i18n/locales/ru.js +2 -1
- package/dist/i18n/locales/ru.js.map +1 -1
- package/dist/i18n/locales/sq.d.ts +1 -0
- package/dist/i18n/locales/sq.d.ts.map +1 -1
- package/dist/i18n/locales/sq.js +2 -1
- package/dist/i18n/locales/sq.js.map +1 -1
- package/dist/i18n/locales/sv.d.ts +1 -0
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/i18n/locales/sv.js +2 -1
- package/dist/i18n/locales/sv.js.map +1 -1
- package/dist/i18n/locales/tr.d.ts +1 -0
- package/dist/i18n/locales/tr.d.ts.map +1 -1
- package/dist/i18n/locales/tr.js +2 -1
- package/dist/i18n/locales/tr.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +1 -0
- package/dist/i18n/locales/zh.d.ts.map +1 -1
- package/dist/i18n/locales/zh.js +2 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.js +60 -60
- package/dist/locales/ar.json +2 -1
- package/dist/locales/de.json +2 -1
- package/dist/locales/el.json +2 -1
- package/dist/locales/en.json +1 -0
- package/dist/locales/es.json +2 -1
- package/dist/locales/fr.json +2 -1
- package/dist/locales/hi.json +2 -1
- package/dist/locales/it.json +2 -1
- package/dist/locales/ja.json +2 -1
- package/dist/locales/nl.json +2 -1
- package/dist/locales/pl.json +2 -1
- package/dist/locales/pt.json +2 -1
- package/dist/locales/ro.json +2 -1
- package/dist/locales/ru.json +2 -1
- package/dist/locales/sq.json +2 -1
- package/dist/locales/sv.json +2 -1
- package/dist/locales/tr.json +2 -1
- package/dist/locales/zh.json +2 -1
- package/dist/patterns/anamnesis/index.js +1 -1
- package/dist/patterns/calendar/index.js +1 -1
- package/dist/patterns/forgot-password-form/index.js +1 -1
- package/dist/patterns/login-form/index.js +1 -1
- package/dist/patterns/marketplace-app-shell/index.js +1 -1
- package/dist/patterns/payment-automation-upsell/index.js +1 -1
- package/dist/patterns/registration-form/index.js +1 -1
- package/dist/patterns/reset-password-form/index.js +1 -1
- package/dist/patterns/send-registration-link-form/index.js +1 -1
- package/dist/patterns/signed-out-frame/index.js +1 -1
- package/dist/patterns/workflow-webhook-action/index.js +1 -1
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/map-view-Dn7aXZJw.js +0 -731
- package/dist/_chunks/map-view-Dn7aXZJw.js.map +0 -1
- package/dist/_chunks/patient-search-CxuqVUTB.js.map +0 -1
- package/dist/_chunks/practice-results-BByRCIuR.js +0 -1977
- package/dist/_chunks/practice-results-BByRCIuR.js.map +0 -1
- package/dist/_chunks/public-footer-DGjLTtNK.js.map +0 -1
- package/dist/_chunks/public-header.agent-_ArDD1Xr.js.map +0 -1
- package/dist/_chunks/reserved.agent-BMkapgom.js.map +0 -1
- package/dist/_chunks/stat-BFKZV50y.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { useTranslation as _s } from "react-i18next";
|
|
|
4
4
|
import { c as ws } from "./index-D2ZczOXr.js";
|
|
5
5
|
import { A as u } from "./alert-C4WKViaS.js";
|
|
6
6
|
import { B as D } from "./button-DohK_Ak_.js";
|
|
7
|
-
import { C as ms } from "./card-
|
|
7
|
+
import { C as ms } from "./card-DVO3x8E-.js";
|
|
8
8
|
import { E as ks } from "./email-input-DxwbREpx.js";
|
|
9
9
|
import { E as Vs } from "./error-summary-DxNhwf_G.js";
|
|
10
10
|
import { F as L } from "./form-field-CyHfOVKF.js";
|
|
@@ -364,4 +364,4 @@ export {
|
|
|
364
364
|
b as R,
|
|
365
365
|
nr as a
|
|
366
366
|
};
|
|
367
|
-
//# sourceMappingURL=reset-password-form-
|
|
367
|
+
//# sourceMappingURL=reset-password-form-C93SH0Tx.js.map
|
package/dist/_chunks/{reset-password-form-BVsL2fOJ.js.map → reset-password-form-C93SH0Tx.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-password-form-BVsL2fOJ.js","sources":["../../src/patterns/_shared/password-policy.ts","../../src/patterns/reset-password-form/reset-password-form.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* The platform's password rule, as the live checklist shows it. */\n/* */\n/* The platform's rule is written in ASCII (`[A-Z]`, `[a-z]`, `\\d`), so */\n/* the checklist counts the same characters and never shows green for a */\n/* password the server will refuse. Shipped with the patterns that */\n/* import it; not an export of its own. */\n/* -------------------------------------------------------------------- */\n\nimport type { UsePasswordRequirementsOptions } from '../../components/password-input';\n\nexport const PLATFORM_PASSWORD_POLICY: UsePasswordRequirementsOptions = {\n minLength: 12,\n uppercase: true,\n lowercase: true,\n digit: true,\n charset: 'ascii',\n};\n","/* -------------------------------------------------------------------- */\n/* ResetPasswordForm — the card the reset email opens, where the person */\n/* chooses a new password. */\n/* */\n/* Two password fields with the live rules checklist, then one of two */\n/* results in place of the form: the password has changed, or the link */\n/* has expired. The host checks the link and the code, sets the state */\n/* and owns every server rule; the card checks what a browser can check, */\n/* then hands the values to `onSubmit` or posts a real form. A password */\n/* is never put in an attribute. */\n/* -------------------------------------------------------------------- */\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { cva } from 'class-variance-authority';\nimport { Alert } from '../../components/alert';\nimport { Button } from '../../components/button';\nimport { Card } from '../../components/card';\nimport { EmailInput } from '../../components/email-input';\nimport { ErrorSummary } from '../../components/error-summary';\nimport { FormField } from '../../components/form-field';\nimport {\n PasswordInput,\n usePasswordRequirements,\n} from '../../components/password-input';\nimport { isSafeHref } from '../../components/_shared/safe-href';\nimport { useStateBackedInput } from '../../components/_shared/use-state-backed-input';\nimport { useFieldMatch } from '../../hooks/use-field-match';\nimport {\n AuthCardHeading,\n AuthFormMessage,\n AuthFormStatus,\n ExtraFields,\n useExtraFields,\n useFormAction,\n} from '../_shared/auth-form-parts';\nimport type { BrandMarkMotion } from '../_shared/brand-mark';\nimport { PLATFORM_PASSWORD_POLICY } from '../_shared/password-policy';\nimport {\n useErrorSummary,\n useFormMessageAnnounce,\n} from '../_shared/use-error-summary';\nimport { useRegistrationSubmit } from '../registration-form/use-registration-submit';\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/** The form's two fields. */\nexport const RESET_PASSWORD_FORM_FIELDS = [\n 'password',\n 'passwordConfirm',\n] as const;\n\nexport type ResetPasswordFormField =\n (typeof RESET_PASSWORD_FORM_FIELDS)[number];\n\n/** What the card shows: the form, the result after a good change, or the\n * result for a link that no longer works. */\nexport type ResetPasswordFormState = 'form' | 'done' | 'expired';\n\n/** What `onSubmit` receives once both passwords have passed their checks. */\nexport interface ResetPasswordFormValues {\n password: string;\n passwordConfirm: string;\n}\n\n/** Messages from the server, already translated: one for each field, plus\n * `form` for a message about the whole form. */\nexport type ResetPasswordFormErrors = Partial<\n Record<ResetPasswordFormField | 'form', string>\n>;\n\n/** The host's own form field names, for a native form post. They are\n * needed: a field without a name is not posted. */\nexport type ResetPasswordFormFieldNames = Record<\n ResetPasswordFormField,\n string\n>;\n\nexport interface ResetPasswordFormProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'title' | 'onSubmit'\n> {\n /**\n * What the card shows. Default `form`. `done` and `expired` replace the\n * form and take focus when they appear.\n */\n state?: ResetPasswordFormState;\n /** Replaces the form's heading \"Choose a new password\". Pass it\n * translated. The result states have their own heading. */\n title?: string;\n /** Replaces the line under the form's heading (\"Enter a new password\n * for your account.\"). Pass it translated. */\n description?: string;\n /**\n * The heading's level. Default 1: this page usually has no other\n * heading. Pass 2 on a page that has its own `h1`.\n */\n headingLevel?: 1 | 2 | 3;\n /** Shows the Alfadocs mark above the heading. Default true. */\n showLogo?: boolean;\n /** How the mark arrives when the card opens. Default `assemble`. */\n logoMotion?: BrandMarkMotion;\n /**\n * Host content between the heading and the fields, such as a notice. It\n * sits outside the form, so nothing in it is posted.\n */\n children?: ReactNode;\n /**\n * The address the password is for. In the form it shows read-only, and\n * is the username a password manager saves the new password under. In\n * the `done` state it is named in the line under the result.\n */\n email?: string;\n /**\n * The `done` state's action, \"Sign in\". A path on the same site, or an\n * address written in full. Anything else shows no button.\n */\n loginHref?: string;\n /**\n * The `expired` state's action, \"Ask for a new link\". A path on the\n * same site, or an address written in full. Anything else shows no\n * button.\n */\n requestHref?: string;\n /**\n * Messages from the server, already translated. A field's message shows\n * on the field until the user changes it; `form` shows above the\n * fields. When messages arrive, focus moves to the error summary when a\n * field has one, or to the message above the fields when only that\n * says something. Passwords the server refused are emptied.\n */\n errors?: ResetPasswordFormErrors;\n /** Replaces the button's \"Change password\". Pass it translated. */\n submitLabel?: string;\n /**\n * True while the host sends the passwords: the button shows a spinner\n * and a second submit is ignored. Set it inside `onSubmit`, before\n * anything is awaited.\n */\n submitting?: boolean;\n /** For React hosts: called with both passwords once they have passed\n * their checks. Not called when `fieldNames` is set. */\n onSubmit?: (values: ResetPasswordFormValues) => void;\n /**\n * For a native form post: the host's own field names, e.g.\n * `{ password: 'lost_password_reset[password1]', passwordConfirm:\n * 'lost_password_reset[password2]' }`. When set, the inputs carry those\n * names and the browser posts the form.\n */\n fieldNames?: ResetPasswordFormFieldNames;\n /**\n * Where a native form post goes. Default: the current address. Only a\n * path, or an address on the page's own origin, is accepted: another\n * origin is never sent what the user typed. Does nothing without\n * `fieldNames`.\n */\n action?: string;\n /**\n * Extra hidden inputs posted with a native form, by name: a CSRF token,\n * e.g. `{ 'lost_password_reset[_token]': token }`. Not used with\n * `onSubmit`. A name that is also in `fieldNames` is left out.\n */\n hiddenFields?: Readonly<Record<string, string>>;\n}\n\n/* -------------------------------------------------------------------- */\n/* Styles */\n/* -------------------------------------------------------------------- */\n\n// 400px at most, in px: a host whose root font size is not 16px would\n// stretch or squeeze a width in rem.\nconst resetPasswordFormVariants = cva(\n 'ds:mx-auto ds:w-full ds:max-w-[400px] ds:text-start ds:text-[var(--foreground)]',\n);\n\n// The result takes focus when it appears, so it shows a ring.\nconst resultVariants = cva(\n 'ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] 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\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\ntype FocusTarget = ResetPasswordFormField | 'form';\n\n/** Unicode isolates: they keep an address's order inside any sentence. */\nconst LTR_ISOLATE = '\\u2066';\nconst POP_ISOLATE = '\\u2069';\n\n/** The result's heading, by the level of the card's own. */\nconst RESULT_HEADING = { 1: 'h2', 2: 'h3', 3: 'h4' } as const;\n\nconst hasMessage = (errors: ResetPasswordFormErrors): boolean =>\n Object.values(errors).some(Boolean);\n\nconst focusTargetFor = (\n messages: ResetPasswordFormErrors,\n): FocusTarget | undefined =>\n RESET_PASSWORD_FORM_FIELDS.find((field) => messages[field]) ??\n (messages.form ? 'form' : undefined);\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\n/**\n * The card for the page the reset email opens. Mount it on the\n * reset-password page, inside `SignedOutFrame` or the host's own chrome.\n */\nexport const ResetPasswordForm = forwardRef<\n HTMLDivElement,\n ResetPasswordFormProps\n>(\n (\n {\n state = 'form',\n title,\n description,\n headingLevel = 1,\n showLogo = true,\n logoMotion = 'assemble',\n children,\n email,\n loginHref,\n requestHref,\n errors,\n submitLabel,\n submitting = false,\n onSubmit,\n fieldNames,\n action,\n hiddenFields,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const titleId = useId();\n const resultTitleId = useId();\n const resultDescriptionId = useId();\n const emailFieldId = useId();\n const passwordFieldId = useId();\n const passwordConfirmFieldId = useId();\n const formMessageId = useId();\n const native = fieldNames !== undefined;\n const fieldIds: Record<ResetPasswordFormField, string> = {\n password: passwordFieldId,\n passwordConfirm: passwordConfirmFieldId,\n };\n\n /* ------------------------------ State ----------------------------- */\n\n // Passwords always start empty and are held in state only: the inputs\n // get them as a DOM property, never as an attribute.\n const [values, setValues] = useState<ResetPasswordFormValues>({\n password: '',\n passwordConfirm: '',\n });\n const [touched, setTouched] = useState<\n Partial<Record<ResetPasswordFormField, boolean>>\n >({});\n const [submitted, setSubmitted] = useState(false);\n const [serverErrors, setServerErrors] = useState<ResetPasswordFormErrors>(\n () => errors ?? {},\n );\n\n const set = (field: ResetPasswordFormField, value: string) => {\n setValues((previous) => ({ ...previous, [field]: value }));\n // The server's message was about the old value.\n setServerErrors((previous) =>\n previous[field] ? { ...previous, [field]: undefined } : previous,\n );\n };\n const leave = (field: ResetPasswordFormField) =>\n setTouched((previous) =>\n previous[field] ? previous : { ...previous, [field]: true },\n );\n const emptyPasswords = () => {\n setValues({ password: '', passwordConfirm: '' });\n setTouched({});\n };\n\n /* ---------------------------- Validation -------------------------- */\n\n const requirements = usePasswordRequirements(PLATFORM_PASSWORD_POLICY);\n const match = useFieldMatch({\n value: values.password,\n confirmValue: values.passwordConfirm,\n kind: 'password',\n submitted,\n });\n\n /** What stops a submit, whether or not the message shows yet. */\n const invalid: Record<ResetPasswordFormField, boolean> = {\n password:\n values.password === '' ||\n !requirements.every((rule) => rule.test(values.password)),\n passwordConfirm: values.passwordConfirm === '' || !match.matches,\n };\n // A message shows once the user has left the field or tried to submit.\n const due = (field: ResetPasswordFormField) =>\n submitted || touched[field] === true;\n const ownErrors: Record<ResetPasswordFormField, string | undefined> = {\n password:\n due('password') && invalid.password\n ? values.password === ''\n ? t('resetPasswordForm.passwordRequired')\n : t('resetPasswordForm.passwordInvalid')\n : undefined,\n passwordConfirm:\n values.passwordConfirm === ''\n ? due('passwordConfirm')\n ? t('resetPasswordForm.passwordConfirmRequired')\n : undefined\n : match.error,\n };\n const errorFor = (field: ResetPasswordFormField) =>\n serverErrors[field] ?? ownErrors[field];\n\n /* ------------------------------ Focus ----------------------------- */\n\n const controls = useRef<\n Partial<Record<ResetPasswordFormField, HTMLElement | null>>\n >({});\n const control = useMemo(\n () => ({\n password: (node: HTMLElement | null) => {\n controls.current.password = node;\n },\n passwordConfirm: (node: HTMLElement | null) => {\n controls.current.passwordConfirm = node;\n },\n }),\n [],\n );\n const formMessage = useRef<HTMLDivElement>(null);\n const result = useRef<HTMLDivElement>(null);\n\n // The address stays out of the input's `value` attribute, where a\n // copied outerHTML or a session replay could read it.\n const emailInput = useRef<HTMLInputElement>(null);\n const emailField = useStateBackedInput(email ?? '', emailInput);\n\n // Every field with a message after a refused submit, or after a server\n // answer, is listed at the top of the form, and focus goes there. A\n // page the server rendered after a failed post opens that way too.\n const [openedWithFieldMessage] = useState(() =>\n RESET_PASSWORD_FORM_FIELDS.some((field) => Boolean(errors?.[field])),\n );\n const summary = useErrorSummary(\n RESET_PASSWORD_FORM_FIELDS,\n errorFor,\n openedWithFieldMessage,\n );\n const formMessageAnnounce = useFormMessageAnnounce(\n serverErrors.form,\n summary.listed.length > 0,\n );\n\n // A new object for each request, so asking twice for the same place\n // moves focus twice.\n const [focusRequest, setFocusRequest] = useState<{\n target: FocusTarget;\n } | null>(() => {\n const target = openedWithFieldMessage\n ? undefined\n : focusTargetFor(errors ?? {});\n return target ? { target } : null;\n });\n useEffect(() => {\n if (!focusRequest) return;\n if (focusRequest.target === 'form') formMessage.current?.focus();\n else controls.current[focusRequest.target]?.focus();\n }, [focusRequest]);\n\n // The form, and the button that had focus, are gone once a result\n // shows. Move focus to it. Not on a page that opens on a result: that\n // one is read from the top like any other.\n // A password does not wait in memory behind a result.\n const clearOnResult = state !== 'form';\n useEffect(() => {\n if (clearOnResult) {\n setValues({ password: '', passwordConfirm: '' });\n setTouched({});\n }\n }, [clearOnResult]);\n const wasForm = useRef(state === 'form');\n useEffect(() => {\n if (state !== 'form' && wasForm.current) result.current?.focus();\n wasForm.current = state === 'form';\n }, [state]);\n\n /* ------------------------------ Submit ---------------------------- */\n\n const submit = useRegistrationSubmit<ResetPasswordFormField>({\n errors,\n submitting,\n native,\n captcha: false,\n firstProblem: () =>\n RESET_PASSWORD_FORM_FIELDS.find((field) => invalid[field]),\n onRefused: (field) => {\n setSubmitted(true);\n summary.open(() => setFocusRequest({ target: field }));\n },\n // Only a React host gets here: a native post is the browser's own.\n deliver: () =>\n onSubmit?.({\n password: values.password,\n passwordConfirm: values.passwordConfirm,\n }),\n onAnswer: (answer) => {\n const next: ResetPasswordFormErrors = answer;\n setServerErrors(next);\n if (!hasMessage(next)) return;\n // Never keep a password the server did not accept.\n emptyPasswords();\n setSubmitted(false);\n summary.open(() => {\n const target = focusTargetFor(next);\n if (target) setFocusRequest({ target });\n });\n },\n // The page came back from the browser's cache as it was left: the\n // passwords must not come back with it.\n onRestore: emptyPasswords,\n });\n const { busy } = submit;\n\n /* ------------------------------ Render ---------------------------- */\n\n const formAction = useFormAction('ResetPasswordForm', native, action);\n const extraFields = useExtraFields(\n 'ResetPasswordForm',\n native,\n fieldNames,\n hiddenFields,\n );\n const name = (field: ResetPasswordFormField) =>\n native ? fieldNames[field] : field;\n const safe = (href: string | undefined) =>\n href && isSafeHref(href) ? href : undefined;\n const loginLink = safe(loginHref);\n const requestLink = safe(requestHref);\n const showingForm = state === 'form';\n const labels: Record<ResetPasswordFormField, string> = {\n password: t('resetPasswordForm.password'),\n passwordConfirm: t('resetPasswordForm.passwordConfirm'),\n };\n const resultHeading = RESULT_HEADING[headingLevel];\n\n return (\n <div\n ref={ref}\n {...rest}\n data-component=\"reset-password-form\"\n data-state={state}\n className={resetPasswordFormVariants({ className })}\n >\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]\">\n <AuthCardHeading\n id={titleId}\n title={\n showingForm\n ? (title ?? t('resetPasswordForm.title'))\n : t('resetPasswordForm.pageTitle')\n }\n description={\n showingForm\n ? (description ?? t('resetPasswordForm.description'))\n : undefined\n }\n level={headingLevel}\n showLogo={showLogo}\n logoMotion={logoMotion}\n />\n\n {children ? (\n <div\n data-slot=\"host-content\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n {children}\n </div>\n ) : null}\n\n {state === 'done' && (\n <>\n <Alert\n ref={result}\n variant=\"success\"\n tabIndex={-1}\n aria-labelledby={resultTitleId}\n aria-describedby={resultDescriptionId}\n data-slot=\"result\"\n className={resultVariants()}\n >\n <Alert.Title id={resultTitleId} as={resultHeading}>\n {t('resetPasswordForm.doneTitle')}\n </Alert.Title>\n <Alert.Description id={resultDescriptionId}>\n {email\n ? t('resetPasswordForm.doneDescriptionEmail', {\n // Isolated left to right, so the address keeps its\n // order inside a right-to-left sentence.\n email: `${LTR_ISOLATE}${email}${POP_ISOLATE}`,\n })\n : t('resetPasswordForm.doneDescription')}\n </Alert.Description>\n </Alert>\n {loginLink && (\n <Button\n asChild\n intent=\"primary\"\n size=\"lg\"\n className=\"ds:w-full\"\n >\n <a href={loginLink} data-action=\"login\">\n {t('resetPasswordForm.signIn')}\n </a>\n </Button>\n )}\n </>\n )}\n\n {state === 'expired' && (\n <>\n <Alert\n ref={result}\n variant=\"warning\"\n // Focus moves here: an assertive alert as well would read\n // it twice, and over a page that opens on it.\n announce=\"polite\"\n tabIndex={-1}\n aria-labelledby={resultTitleId}\n aria-describedby={resultDescriptionId}\n data-slot=\"result\"\n className={resultVariants()}\n >\n <Alert.Title id={resultTitleId} as={resultHeading}>\n {t('resetPasswordForm.expiredTitle')}\n </Alert.Title>\n <Alert.Description id={resultDescriptionId}>\n {t('resetPasswordForm.expiredDescription')}\n </Alert.Description>\n </Alert>\n {requestLink && (\n <Button\n asChild\n intent=\"primary\"\n size=\"lg\"\n className=\"ds:w-full\"\n >\n <a href={requestLink} data-action=\"request-link\">\n {t('resetPasswordForm.requestLink')}\n </a>\n </Button>\n )}\n </>\n )}\n\n {showingForm && (\n <>\n <form\n noValidate\n method=\"post\"\n action={formAction}\n aria-labelledby={titleId}\n aria-busy={busy || undefined}\n onSubmit={submit.handleSubmit}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n <ErrorSummary\n ref={summary.ref}\n headingLevel={(headingLevel + 1) as 2 | 3 | 4}\n // The message about the whole form is read with it.\n aria-describedby={\n serverErrors.form ? formMessageId : undefined\n }\n errors={summary.listed.map((field) => ({\n field,\n message: errorFor(field) ?? '',\n targetId: fieldIds[field],\n }))}\n />\n\n {serverErrors.form && (\n <AuthFormMessage\n ref={formMessage}\n id={formMessageId}\n announce={formMessageAnnounce}\n >\n {serverErrors.form}\n </AuthFormMessage>\n )}\n\n {email ? (\n <FormField\n id={emailFieldId}\n label={t('resetPasswordForm.email')}\n data-field=\"email\"\n >\n {/* Read-only, and not posted: it has no name. `username`\n is what password managers pair with\n `new-password`, so the new password is saved under\n this address. */}\n <EmailInput\n ref={emailField.ref}\n name=\"\"\n readOnly\n autoComplete=\"username\"\n // An address reads left to right in any language.\n dir=\"ltr\"\n disableBuiltInValidation\n onChange={emailField.quiet()}\n />\n </FormField>\n ) : null}\n\n <FormField\n id={passwordFieldId}\n label={labels.password}\n required\n error={errorFor('password')}\n announceError={!summary.lists('password')}\n data-field=\"password\"\n >\n {/* PasswordInput names itself \"password\" when given no\n name; an empty one keeps a field out of the post. */}\n <PasswordInput\n ref={control.password}\n name={name('password')}\n autoComplete=\"new-password\"\n requirements={requirements}\n value={values.password}\n onChange={(event) => set('password', event.target.value)}\n onBlur={() => leave('password')}\n />\n </FormField>\n\n <FormField\n id={passwordConfirmFieldId}\n label={labels.passwordConfirm}\n required\n error={errorFor('passwordConfirm')}\n announceError={!summary.lists('passwordConfirm')}\n data-field=\"passwordConfirm\"\n >\n <PasswordInput\n ref={control.passwordConfirm}\n name={name('passwordConfirm')}\n autoComplete=\"new-password\"\n value={values.passwordConfirm}\n onChange={(event) =>\n set('passwordConfirm', event.target.value)\n }\n onBlur={() => {\n leave('passwordConfirm');\n match.confirmProps.onBlur();\n }}\n />\n </FormField>\n\n <ExtraFields fields={extraFields} />\n\n <Button\n type=\"submit\"\n intent=\"primary\"\n size=\"lg\"\n loading={busy}\n className=\"ds:w-full\"\n data-action=\"submit\"\n >\n {submitLabel ?? t('resetPasswordForm.submit')}\n </Button>\n </form>\n {/* Outside the form: a screen reader may hold back what\n changes inside a busy region. */}\n <AuthFormStatus busy={busy} />\n </>\n )}\n </Card.Body>\n </Card>\n </div>\n );\n },\n);\nResetPasswordForm.displayName = 'ResetPasswordForm';\n"],"names":["PLATFORM_PASSWORD_POLICY","RESET_PASSWORD_FORM_FIELDS","resetPasswordFormVariants","cva","resultVariants","LTR_ISOLATE","POP_ISOLATE","RESULT_HEADING","hasMessage","errors","focusTargetFor","messages","field","ResetPasswordForm","forwardRef","state","title","description","headingLevel","showLogo","logoMotion","children","email","loginHref","requestHref","submitLabel","submitting","onSubmit","fieldNames","action","hiddenFields","className","rest","ref","t","useTranslation","titleId","useId","resultTitleId","resultDescriptionId","emailFieldId","passwordFieldId","passwordConfirmFieldId","formMessageId","native","fieldIds","values","setValues","useState","touched","setTouched","submitted","setSubmitted","serverErrors","setServerErrors","set","value","previous","leave","emptyPasswords","requirements","usePasswordRequirements","match","useFieldMatch","invalid","rule","due","ownErrors","errorFor","controls","useRef","control","useMemo","node","formMessage","result","emailInput","emailField","useStateBackedInput","openedWithFieldMessage","summary","useErrorSummary","formMessageAnnounce","useFormMessageAnnounce","focusRequest","setFocusRequest","target","useEffect","_a","_b","clearOnResult","wasForm","submit","useRegistrationSubmit","answer","next","busy","formAction","useFormAction","extraFields","useExtraFields","name","safe","href","isSafeHref","loginLink","requestLink","showingForm","labels","resultHeading","jsx","Card","AuthCardHeading","jsxs","Fragment","Alert","Button","ErrorSummary","AuthFormMessage","FormField","EmailInput","PasswordInput","event","ExtraFields","AuthFormStatus"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reset-password-form-C93SH0Tx.js","sources":["../../src/patterns/_shared/password-policy.ts","../../src/patterns/reset-password-form/reset-password-form.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* The platform's password rule, as the live checklist shows it. */\n/* */\n/* The platform's rule is written in ASCII (`[A-Z]`, `[a-z]`, `\\d`), so */\n/* the checklist counts the same characters and never shows green for a */\n/* password the server will refuse. Shipped with the patterns that */\n/* import it; not an export of its own. */\n/* -------------------------------------------------------------------- */\n\nimport type { UsePasswordRequirementsOptions } from '../../components/password-input';\n\nexport const PLATFORM_PASSWORD_POLICY: UsePasswordRequirementsOptions = {\n minLength: 12,\n uppercase: true,\n lowercase: true,\n digit: true,\n charset: 'ascii',\n};\n","/* -------------------------------------------------------------------- */\n/* ResetPasswordForm — the card the reset email opens, where the person */\n/* chooses a new password. */\n/* */\n/* Two password fields with the live rules checklist, then one of two */\n/* results in place of the form: the password has changed, or the link */\n/* has expired. The host checks the link and the code, sets the state */\n/* and owns every server rule; the card checks what a browser can check, */\n/* then hands the values to `onSubmit` or posts a real form. A password */\n/* is never put in an attribute. */\n/* -------------------------------------------------------------------- */\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { cva } from 'class-variance-authority';\nimport { Alert } from '../../components/alert';\nimport { Button } from '../../components/button';\nimport { Card } from '../../components/card';\nimport { EmailInput } from '../../components/email-input';\nimport { ErrorSummary } from '../../components/error-summary';\nimport { FormField } from '../../components/form-field';\nimport {\n PasswordInput,\n usePasswordRequirements,\n} from '../../components/password-input';\nimport { isSafeHref } from '../../components/_shared/safe-href';\nimport { useStateBackedInput } from '../../components/_shared/use-state-backed-input';\nimport { useFieldMatch } from '../../hooks/use-field-match';\nimport {\n AuthCardHeading,\n AuthFormMessage,\n AuthFormStatus,\n ExtraFields,\n useExtraFields,\n useFormAction,\n} from '../_shared/auth-form-parts';\nimport type { BrandMarkMotion } from '../_shared/brand-mark';\nimport { PLATFORM_PASSWORD_POLICY } from '../_shared/password-policy';\nimport {\n useErrorSummary,\n useFormMessageAnnounce,\n} from '../_shared/use-error-summary';\nimport { useRegistrationSubmit } from '../registration-form/use-registration-submit';\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/** The form's two fields. */\nexport const RESET_PASSWORD_FORM_FIELDS = [\n 'password',\n 'passwordConfirm',\n] as const;\n\nexport type ResetPasswordFormField =\n (typeof RESET_PASSWORD_FORM_FIELDS)[number];\n\n/** What the card shows: the form, the result after a good change, or the\n * result for a link that no longer works. */\nexport type ResetPasswordFormState = 'form' | 'done' | 'expired';\n\n/** What `onSubmit` receives once both passwords have passed their checks. */\nexport interface ResetPasswordFormValues {\n password: string;\n passwordConfirm: string;\n}\n\n/** Messages from the server, already translated: one for each field, plus\n * `form` for a message about the whole form. */\nexport type ResetPasswordFormErrors = Partial<\n Record<ResetPasswordFormField | 'form', string>\n>;\n\n/** The host's own form field names, for a native form post. They are\n * needed: a field without a name is not posted. */\nexport type ResetPasswordFormFieldNames = Record<\n ResetPasswordFormField,\n string\n>;\n\nexport interface ResetPasswordFormProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'title' | 'onSubmit'\n> {\n /**\n * What the card shows. Default `form`. `done` and `expired` replace the\n * form and take focus when they appear.\n */\n state?: ResetPasswordFormState;\n /** Replaces the form's heading \"Choose a new password\". Pass it\n * translated. The result states have their own heading. */\n title?: string;\n /** Replaces the line under the form's heading (\"Enter a new password\n * for your account.\"). Pass it translated. */\n description?: string;\n /**\n * The heading's level. Default 1: this page usually has no other\n * heading. Pass 2 on a page that has its own `h1`.\n */\n headingLevel?: 1 | 2 | 3;\n /** Shows the Alfadocs mark above the heading. Default true. */\n showLogo?: boolean;\n /** How the mark arrives when the card opens. Default `assemble`. */\n logoMotion?: BrandMarkMotion;\n /**\n * Host content between the heading and the fields, such as a notice. It\n * sits outside the form, so nothing in it is posted.\n */\n children?: ReactNode;\n /**\n * The address the password is for. In the form it shows read-only, and\n * is the username a password manager saves the new password under. In\n * the `done` state it is named in the line under the result.\n */\n email?: string;\n /**\n * The `done` state's action, \"Sign in\". A path on the same site, or an\n * address written in full. Anything else shows no button.\n */\n loginHref?: string;\n /**\n * The `expired` state's action, \"Ask for a new link\". A path on the\n * same site, or an address written in full. Anything else shows no\n * button.\n */\n requestHref?: string;\n /**\n * Messages from the server, already translated. A field's message shows\n * on the field until the user changes it; `form` shows above the\n * fields. When messages arrive, focus moves to the error summary when a\n * field has one, or to the message above the fields when only that\n * says something. Passwords the server refused are emptied.\n */\n errors?: ResetPasswordFormErrors;\n /** Replaces the button's \"Change password\". Pass it translated. */\n submitLabel?: string;\n /**\n * True while the host sends the passwords: the button shows a spinner\n * and a second submit is ignored. Set it inside `onSubmit`, before\n * anything is awaited.\n */\n submitting?: boolean;\n /** For React hosts: called with both passwords once they have passed\n * their checks. Not called when `fieldNames` is set. */\n onSubmit?: (values: ResetPasswordFormValues) => void;\n /**\n * For a native form post: the host's own field names, e.g.\n * `{ password: 'lost_password_reset[password1]', passwordConfirm:\n * 'lost_password_reset[password2]' }`. When set, the inputs carry those\n * names and the browser posts the form.\n */\n fieldNames?: ResetPasswordFormFieldNames;\n /**\n * Where a native form post goes. Default: the current address. Only a\n * path, or an address on the page's own origin, is accepted: another\n * origin is never sent what the user typed. Does nothing without\n * `fieldNames`.\n */\n action?: string;\n /**\n * Extra hidden inputs posted with a native form, by name: a CSRF token,\n * e.g. `{ 'lost_password_reset[_token]': token }`. Not used with\n * `onSubmit`. A name that is also in `fieldNames` is left out.\n */\n hiddenFields?: Readonly<Record<string, string>>;\n}\n\n/* -------------------------------------------------------------------- */\n/* Styles */\n/* -------------------------------------------------------------------- */\n\n// 400px at most, in px: a host whose root font size is not 16px would\n// stretch or squeeze a width in rem.\nconst resetPasswordFormVariants = cva(\n 'ds:mx-auto ds:w-full ds:max-w-[400px] ds:text-start ds:text-[var(--foreground)]',\n);\n\n// The result takes focus when it appears, so it shows a ring.\nconst resultVariants = cva(\n 'ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] 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\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\ntype FocusTarget = ResetPasswordFormField | 'form';\n\n/** Unicode isolates: they keep an address's order inside any sentence. */\nconst LTR_ISOLATE = '\\u2066';\nconst POP_ISOLATE = '\\u2069';\n\n/** The result's heading, by the level of the card's own. */\nconst RESULT_HEADING = { 1: 'h2', 2: 'h3', 3: 'h4' } as const;\n\nconst hasMessage = (errors: ResetPasswordFormErrors): boolean =>\n Object.values(errors).some(Boolean);\n\nconst focusTargetFor = (\n messages: ResetPasswordFormErrors,\n): FocusTarget | undefined =>\n RESET_PASSWORD_FORM_FIELDS.find((field) => messages[field]) ??\n (messages.form ? 'form' : undefined);\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\n/**\n * The card for the page the reset email opens. Mount it on the\n * reset-password page, inside `SignedOutFrame` or the host's own chrome.\n */\nexport const ResetPasswordForm = forwardRef<\n HTMLDivElement,\n ResetPasswordFormProps\n>(\n (\n {\n state = 'form',\n title,\n description,\n headingLevel = 1,\n showLogo = true,\n logoMotion = 'assemble',\n children,\n email,\n loginHref,\n requestHref,\n errors,\n submitLabel,\n submitting = false,\n onSubmit,\n fieldNames,\n action,\n hiddenFields,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const titleId = useId();\n const resultTitleId = useId();\n const resultDescriptionId = useId();\n const emailFieldId = useId();\n const passwordFieldId = useId();\n const passwordConfirmFieldId = useId();\n const formMessageId = useId();\n const native = fieldNames !== undefined;\n const fieldIds: Record<ResetPasswordFormField, string> = {\n password: passwordFieldId,\n passwordConfirm: passwordConfirmFieldId,\n };\n\n /* ------------------------------ State ----------------------------- */\n\n // Passwords always start empty and are held in state only: the inputs\n // get them as a DOM property, never as an attribute.\n const [values, setValues] = useState<ResetPasswordFormValues>({\n password: '',\n passwordConfirm: '',\n });\n const [touched, setTouched] = useState<\n Partial<Record<ResetPasswordFormField, boolean>>\n >({});\n const [submitted, setSubmitted] = useState(false);\n const [serverErrors, setServerErrors] = useState<ResetPasswordFormErrors>(\n () => errors ?? {},\n );\n\n const set = (field: ResetPasswordFormField, value: string) => {\n setValues((previous) => ({ ...previous, [field]: value }));\n // The server's message was about the old value.\n setServerErrors((previous) =>\n previous[field] ? { ...previous, [field]: undefined } : previous,\n );\n };\n const leave = (field: ResetPasswordFormField) =>\n setTouched((previous) =>\n previous[field] ? previous : { ...previous, [field]: true },\n );\n const emptyPasswords = () => {\n setValues({ password: '', passwordConfirm: '' });\n setTouched({});\n };\n\n /* ---------------------------- Validation -------------------------- */\n\n const requirements = usePasswordRequirements(PLATFORM_PASSWORD_POLICY);\n const match = useFieldMatch({\n value: values.password,\n confirmValue: values.passwordConfirm,\n kind: 'password',\n submitted,\n });\n\n /** What stops a submit, whether or not the message shows yet. */\n const invalid: Record<ResetPasswordFormField, boolean> = {\n password:\n values.password === '' ||\n !requirements.every((rule) => rule.test(values.password)),\n passwordConfirm: values.passwordConfirm === '' || !match.matches,\n };\n // A message shows once the user has left the field or tried to submit.\n const due = (field: ResetPasswordFormField) =>\n submitted || touched[field] === true;\n const ownErrors: Record<ResetPasswordFormField, string | undefined> = {\n password:\n due('password') && invalid.password\n ? values.password === ''\n ? t('resetPasswordForm.passwordRequired')\n : t('resetPasswordForm.passwordInvalid')\n : undefined,\n passwordConfirm:\n values.passwordConfirm === ''\n ? due('passwordConfirm')\n ? t('resetPasswordForm.passwordConfirmRequired')\n : undefined\n : match.error,\n };\n const errorFor = (field: ResetPasswordFormField) =>\n serverErrors[field] ?? ownErrors[field];\n\n /* ------------------------------ Focus ----------------------------- */\n\n const controls = useRef<\n Partial<Record<ResetPasswordFormField, HTMLElement | null>>\n >({});\n const control = useMemo(\n () => ({\n password: (node: HTMLElement | null) => {\n controls.current.password = node;\n },\n passwordConfirm: (node: HTMLElement | null) => {\n controls.current.passwordConfirm = node;\n },\n }),\n [],\n );\n const formMessage = useRef<HTMLDivElement>(null);\n const result = useRef<HTMLDivElement>(null);\n\n // The address stays out of the input's `value` attribute, where a\n // copied outerHTML or a session replay could read it.\n const emailInput = useRef<HTMLInputElement>(null);\n const emailField = useStateBackedInput(email ?? '', emailInput);\n\n // Every field with a message after a refused submit, or after a server\n // answer, is listed at the top of the form, and focus goes there. A\n // page the server rendered after a failed post opens that way too.\n const [openedWithFieldMessage] = useState(() =>\n RESET_PASSWORD_FORM_FIELDS.some((field) => Boolean(errors?.[field])),\n );\n const summary = useErrorSummary(\n RESET_PASSWORD_FORM_FIELDS,\n errorFor,\n openedWithFieldMessage,\n );\n const formMessageAnnounce = useFormMessageAnnounce(\n serverErrors.form,\n summary.listed.length > 0,\n );\n\n // A new object for each request, so asking twice for the same place\n // moves focus twice.\n const [focusRequest, setFocusRequest] = useState<{\n target: FocusTarget;\n } | null>(() => {\n const target = openedWithFieldMessage\n ? undefined\n : focusTargetFor(errors ?? {});\n return target ? { target } : null;\n });\n useEffect(() => {\n if (!focusRequest) return;\n if (focusRequest.target === 'form') formMessage.current?.focus();\n else controls.current[focusRequest.target]?.focus();\n }, [focusRequest]);\n\n // The form, and the button that had focus, are gone once a result\n // shows. Move focus to it. Not on a page that opens on a result: that\n // one is read from the top like any other.\n // A password does not wait in memory behind a result.\n const clearOnResult = state !== 'form';\n useEffect(() => {\n if (clearOnResult) {\n setValues({ password: '', passwordConfirm: '' });\n setTouched({});\n }\n }, [clearOnResult]);\n const wasForm = useRef(state === 'form');\n useEffect(() => {\n if (state !== 'form' && wasForm.current) result.current?.focus();\n wasForm.current = state === 'form';\n }, [state]);\n\n /* ------------------------------ Submit ---------------------------- */\n\n const submit = useRegistrationSubmit<ResetPasswordFormField>({\n errors,\n submitting,\n native,\n captcha: false,\n firstProblem: () =>\n RESET_PASSWORD_FORM_FIELDS.find((field) => invalid[field]),\n onRefused: (field) => {\n setSubmitted(true);\n summary.open(() => setFocusRequest({ target: field }));\n },\n // Only a React host gets here: a native post is the browser's own.\n deliver: () =>\n onSubmit?.({\n password: values.password,\n passwordConfirm: values.passwordConfirm,\n }),\n onAnswer: (answer) => {\n const next: ResetPasswordFormErrors = answer;\n setServerErrors(next);\n if (!hasMessage(next)) return;\n // Never keep a password the server did not accept.\n emptyPasswords();\n setSubmitted(false);\n summary.open(() => {\n const target = focusTargetFor(next);\n if (target) setFocusRequest({ target });\n });\n },\n // The page came back from the browser's cache as it was left: the\n // passwords must not come back with it.\n onRestore: emptyPasswords,\n });\n const { busy } = submit;\n\n /* ------------------------------ Render ---------------------------- */\n\n const formAction = useFormAction('ResetPasswordForm', native, action);\n const extraFields = useExtraFields(\n 'ResetPasswordForm',\n native,\n fieldNames,\n hiddenFields,\n );\n const name = (field: ResetPasswordFormField) =>\n native ? fieldNames[field] : field;\n const safe = (href: string | undefined) =>\n href && isSafeHref(href) ? href : undefined;\n const loginLink = safe(loginHref);\n const requestLink = safe(requestHref);\n const showingForm = state === 'form';\n const labels: Record<ResetPasswordFormField, string> = {\n password: t('resetPasswordForm.password'),\n passwordConfirm: t('resetPasswordForm.passwordConfirm'),\n };\n const resultHeading = RESULT_HEADING[headingLevel];\n\n return (\n <div\n ref={ref}\n {...rest}\n data-component=\"reset-password-form\"\n data-state={state}\n className={resetPasswordFormVariants({ className })}\n >\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]\">\n <AuthCardHeading\n id={titleId}\n title={\n showingForm\n ? (title ?? t('resetPasswordForm.title'))\n : t('resetPasswordForm.pageTitle')\n }\n description={\n showingForm\n ? (description ?? t('resetPasswordForm.description'))\n : undefined\n }\n level={headingLevel}\n showLogo={showLogo}\n logoMotion={logoMotion}\n />\n\n {children ? (\n <div\n data-slot=\"host-content\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n {children}\n </div>\n ) : null}\n\n {state === 'done' && (\n <>\n <Alert\n ref={result}\n variant=\"success\"\n tabIndex={-1}\n aria-labelledby={resultTitleId}\n aria-describedby={resultDescriptionId}\n data-slot=\"result\"\n className={resultVariants()}\n >\n <Alert.Title id={resultTitleId} as={resultHeading}>\n {t('resetPasswordForm.doneTitle')}\n </Alert.Title>\n <Alert.Description id={resultDescriptionId}>\n {email\n ? t('resetPasswordForm.doneDescriptionEmail', {\n // Isolated left to right, so the address keeps its\n // order inside a right-to-left sentence.\n email: `${LTR_ISOLATE}${email}${POP_ISOLATE}`,\n })\n : t('resetPasswordForm.doneDescription')}\n </Alert.Description>\n </Alert>\n {loginLink && (\n <Button\n asChild\n intent=\"primary\"\n size=\"lg\"\n className=\"ds:w-full\"\n >\n <a href={loginLink} data-action=\"login\">\n {t('resetPasswordForm.signIn')}\n </a>\n </Button>\n )}\n </>\n )}\n\n {state === 'expired' && (\n <>\n <Alert\n ref={result}\n variant=\"warning\"\n // Focus moves here: an assertive alert as well would read\n // it twice, and over a page that opens on it.\n announce=\"polite\"\n tabIndex={-1}\n aria-labelledby={resultTitleId}\n aria-describedby={resultDescriptionId}\n data-slot=\"result\"\n className={resultVariants()}\n >\n <Alert.Title id={resultTitleId} as={resultHeading}>\n {t('resetPasswordForm.expiredTitle')}\n </Alert.Title>\n <Alert.Description id={resultDescriptionId}>\n {t('resetPasswordForm.expiredDescription')}\n </Alert.Description>\n </Alert>\n {requestLink && (\n <Button\n asChild\n intent=\"primary\"\n size=\"lg\"\n className=\"ds:w-full\"\n >\n <a href={requestLink} data-action=\"request-link\">\n {t('resetPasswordForm.requestLink')}\n </a>\n </Button>\n )}\n </>\n )}\n\n {showingForm && (\n <>\n <form\n noValidate\n method=\"post\"\n action={formAction}\n aria-labelledby={titleId}\n aria-busy={busy || undefined}\n onSubmit={submit.handleSubmit}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n <ErrorSummary\n ref={summary.ref}\n headingLevel={(headingLevel + 1) as 2 | 3 | 4}\n // The message about the whole form is read with it.\n aria-describedby={\n serverErrors.form ? formMessageId : undefined\n }\n errors={summary.listed.map((field) => ({\n field,\n message: errorFor(field) ?? '',\n targetId: fieldIds[field],\n }))}\n />\n\n {serverErrors.form && (\n <AuthFormMessage\n ref={formMessage}\n id={formMessageId}\n announce={formMessageAnnounce}\n >\n {serverErrors.form}\n </AuthFormMessage>\n )}\n\n {email ? (\n <FormField\n id={emailFieldId}\n label={t('resetPasswordForm.email')}\n data-field=\"email\"\n >\n {/* Read-only, and not posted: it has no name. `username`\n is what password managers pair with\n `new-password`, so the new password is saved under\n this address. */}\n <EmailInput\n ref={emailField.ref}\n name=\"\"\n readOnly\n autoComplete=\"username\"\n // An address reads left to right in any language.\n dir=\"ltr\"\n disableBuiltInValidation\n onChange={emailField.quiet()}\n />\n </FormField>\n ) : null}\n\n <FormField\n id={passwordFieldId}\n label={labels.password}\n required\n error={errorFor('password')}\n announceError={!summary.lists('password')}\n data-field=\"password\"\n >\n {/* PasswordInput names itself \"password\" when given no\n name; an empty one keeps a field out of the post. */}\n <PasswordInput\n ref={control.password}\n name={name('password')}\n autoComplete=\"new-password\"\n requirements={requirements}\n value={values.password}\n onChange={(event) => set('password', event.target.value)}\n onBlur={() => leave('password')}\n />\n </FormField>\n\n <FormField\n id={passwordConfirmFieldId}\n label={labels.passwordConfirm}\n required\n error={errorFor('passwordConfirm')}\n announceError={!summary.lists('passwordConfirm')}\n data-field=\"passwordConfirm\"\n >\n <PasswordInput\n ref={control.passwordConfirm}\n name={name('passwordConfirm')}\n autoComplete=\"new-password\"\n value={values.passwordConfirm}\n onChange={(event) =>\n set('passwordConfirm', event.target.value)\n }\n onBlur={() => {\n leave('passwordConfirm');\n match.confirmProps.onBlur();\n }}\n />\n </FormField>\n\n <ExtraFields fields={extraFields} />\n\n <Button\n type=\"submit\"\n intent=\"primary\"\n size=\"lg\"\n loading={busy}\n className=\"ds:w-full\"\n data-action=\"submit\"\n >\n {submitLabel ?? t('resetPasswordForm.submit')}\n </Button>\n </form>\n {/* Outside the form: a screen reader may hold back what\n changes inside a busy region. */}\n <AuthFormStatus busy={busy} />\n </>\n )}\n </Card.Body>\n </Card>\n </div>\n );\n },\n);\nResetPasswordForm.displayName = 'ResetPasswordForm';\n"],"names":["PLATFORM_PASSWORD_POLICY","RESET_PASSWORD_FORM_FIELDS","resetPasswordFormVariants","cva","resultVariants","LTR_ISOLATE","POP_ISOLATE","RESULT_HEADING","hasMessage","errors","focusTargetFor","messages","field","ResetPasswordForm","forwardRef","state","title","description","headingLevel","showLogo","logoMotion","children","email","loginHref","requestHref","submitLabel","submitting","onSubmit","fieldNames","action","hiddenFields","className","rest","ref","t","useTranslation","titleId","useId","resultTitleId","resultDescriptionId","emailFieldId","passwordFieldId","passwordConfirmFieldId","formMessageId","native","fieldIds","values","setValues","useState","touched","setTouched","submitted","setSubmitted","serverErrors","setServerErrors","set","value","previous","leave","emptyPasswords","requirements","usePasswordRequirements","match","useFieldMatch","invalid","rule","due","ownErrors","errorFor","controls","useRef","control","useMemo","node","formMessage","result","emailInput","emailField","useStateBackedInput","openedWithFieldMessage","summary","useErrorSummary","formMessageAnnounce","useFormMessageAnnounce","focusRequest","setFocusRequest","target","useEffect","_a","_b","clearOnResult","wasForm","submit","useRegistrationSubmit","answer","next","busy","formAction","useFormAction","extraFields","useExtraFields","name","safe","href","isSafeHref","loginLink","requestLink","showingForm","labels","resultHeading","jsx","Card","AuthCardHeading","jsxs","Fragment","Alert","Button","ErrorSummary","AuthFormMessage","FormField","EmailInput","PasswordInput","event","ExtraFields","AuthFormStatus"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { c as U } from "./index-D2ZczOXr.js";
|
|
|
4
4
|
import { useTranslation as O } from "react-i18next";
|
|
5
5
|
import { u as Z } from "./registry-DvAUVLHh.js";
|
|
6
6
|
import { R as N } from "./rating-CGYpg5dj.js";
|
|
7
|
-
import { C as T } from "./card-
|
|
7
|
+
import { C as T } from "./card-DVO3x8E-.js";
|
|
8
8
|
import { C as ee, b as te, a as se } from "./collapsible-DVPM46bq.js";
|
|
9
9
|
import { D as o } from "./description-list-BxzDN9o2.js";
|
|
10
10
|
import { E as ae } from "./empty-state-DwJ5YNzz.js";
|
|
@@ -592,4 +592,4 @@ export {
|
|
|
592
592
|
fe as R,
|
|
593
593
|
oe as r
|
|
594
594
|
};
|
|
595
|
-
//# sourceMappingURL=reviews-panel-
|
|
595
|
+
//# sourceMappingURL=reviews-panel-CfJPEkH7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviews-panel-B03auSpQ.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 /**\n * Language of this review's own text (comment and practice reply), as a\n * BCP 47 tag such as `\"it\"`. Sets `lang` on that text so a screen reader on\n * a page in another language pronounces it correctly. Overrides the\n * panel-level `contentLang`. Leave it out and no `lang` is written.\n * The pseudonym, date, rating and the kit's labels keep the page language.\n */\n contentLang?: 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 * Default language of every review's own text, as a BCP 47 tag. A review's\n * own `contentLang` wins. Leave both out and no `lang` is written.\n */\n contentLang?: string;\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 true\n * Show the review count on each star filter chip. Pass `false` when the\n * per-star counts are not known (no server histogram): the chips stay\n * usable but show no number, and their accessible names drop the count.\n */\n showStarCounts?: 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 showCounts: boolean;\n onChange: (next: ReviewFilter) => void;\n}\n\nfunction FilterBar({\n filter,\n histogram,\n totalCount,\n showCounts,\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 = showCounts\n ? isAll\n ? t('reviewsPanel.filter.allLabel', { count })\n : t('reviewsPanel.filter.starsLabel', { stars, count })\n : isAll\n ? t('reviewsPanel.filter.allShort')\n : t('rating.label', { value: stars, max: 5 });\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 {showCounts ? (\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 ) : null}\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 /** Resolved language of the review's text (item override, else panel default). */\n textLang?: string;\n}\n\nfunction ReviewCard({ review, locale, textLang }: 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 aria-controls={contentId}\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(--review-details-foreground)] ds:min-w-[var(--min-target-size)] ds:min-h-[var(--min-target-size)] 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 lang={textLang}\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 lang={textLang}\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 contentLang,\n filter: filterProp,\n onFilterChange,\n introSlot,\n headerActionSlot,\n 'aria-label': ariaLabel,\n id,\n className,\n hideFilter = false,\n showStarCounts = true,\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 showCounts={showStarCounts}\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\n review={review}\n locale={locale}\n textLang={review.contentLang ?? contentLang}\n />\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","showCounts","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","textLang","triggerId","contentId","open","setOpen","breakdownHasValues","v","hasDetails","formattedDate","useMemo","parsed","pseudonym","footerAndDetails","Collapsible","Card","CollapsibleTrigger","ChevronDown","CollapsibleContent","DescriptionList","_b","_c","_d","PanelSkeleton","hideFilter","Skeleton","_","rootVariants","ReviewsPanel","forwardRef","aggregate","contentLang","filterProp","onFilterChange","introSlot","headerActionSlot","id","className","showStarCounts","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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"}
|
|
1
|
+
{"version":3,"file":"reviews-panel-CfJPEkH7.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 /**\n * Language of this review's own text (comment and practice reply), as a\n * BCP 47 tag such as `\"it\"`. Sets `lang` on that text so a screen reader on\n * a page in another language pronounces it correctly. Overrides the\n * panel-level `contentLang`. Leave it out and no `lang` is written.\n * The pseudonym, date, rating and the kit's labels keep the page language.\n */\n contentLang?: 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 * Default language of every review's own text, as a BCP 47 tag. A review's\n * own `contentLang` wins. Leave both out and no `lang` is written.\n */\n contentLang?: string;\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 true\n * Show the review count on each star filter chip. Pass `false` when the\n * per-star counts are not known (no server histogram): the chips stay\n * usable but show no number, and their accessible names drop the count.\n */\n showStarCounts?: 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 showCounts: boolean;\n onChange: (next: ReviewFilter) => void;\n}\n\nfunction FilterBar({\n filter,\n histogram,\n totalCount,\n showCounts,\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 = showCounts\n ? isAll\n ? t('reviewsPanel.filter.allLabel', { count })\n : t('reviewsPanel.filter.starsLabel', { stars, count })\n : isAll\n ? t('reviewsPanel.filter.allShort')\n : t('rating.label', { value: stars, max: 5 });\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 {showCounts ? (\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 ) : null}\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 /** Resolved language of the review's text (item override, else panel default). */\n textLang?: string;\n}\n\nfunction ReviewCard({ review, locale, textLang }: 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 aria-controls={contentId}\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(--review-details-foreground)] ds:min-w-[var(--min-target-size)] ds:min-h-[var(--min-target-size)] 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 lang={textLang}\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 lang={textLang}\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 contentLang,\n filter: filterProp,\n onFilterChange,\n introSlot,\n headerActionSlot,\n 'aria-label': ariaLabel,\n id,\n className,\n hideFilter = false,\n showStarCounts = true,\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 showCounts={showStarCounts}\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\n review={review}\n locale={locale}\n textLang={review.contentLang ?? contentLang}\n />\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","showCounts","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","textLang","triggerId","contentId","open","setOpen","breakdownHasValues","v","hasDetails","formattedDate","useMemo","parsed","pseudonym","footerAndDetails","Collapsible","Card","CollapsibleTrigger","ChevronDown","CollapsibleContent","DescriptionList","_b","_c","_d","PanelSkeleton","hideFilter","Skeleton","_","rootVariants","ReviewsPanel","forwardRef","aggregate","contentLang","filterProp","onFilterChange","introSlot","headerActionSlot","id","className","showStarCounts","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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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as s, jsxs as r } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef as f } from "react";
|
|
3
3
|
import { c as u } from "./index-D2ZczOXr.js";
|
|
4
|
-
import { C as e } from "./card-
|
|
4
|
+
import { C as e } from "./card-DVO3x8E-.js";
|
|
5
5
|
const v = u("ds:flex ds:flex-col", {
|
|
6
6
|
variants: {
|
|
7
7
|
gap: {
|
|
@@ -57,4 +57,4 @@ x.displayName = "SectionCard";
|
|
|
57
57
|
export {
|
|
58
58
|
x as S
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=section-card-
|
|
60
|
+
//# sourceMappingURL=section-card-DFr2wGal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-card-
|
|
1
|
+
{"version":3,"file":"section-card-DFr2wGal.js","sources":["../../src/components/section-card/section-card.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Card } from '../card/card';\n\n/* -------------------------------------------------------------------- */\n/* SectionCard */\n/* */\n/* One labelled block inside a panel or a page column: a quiet heading, */\n/* an optional action on the inline end, and a stacked body. It is the */\n/* rhythm a rail panel is built from — patient, appointment, notes, */\n/* treatments — each its own card so a reader can skim the headings. */\n/* */\n/* It exists because that shape was being re-authored per surface: a */\n/* `Card` + `Card.Body` + a muted `type-label` heading + a flex row for */\n/* the action. Re-authored, the heading level drifts, the gap drifts, and */\n/* the action sometimes pushes the heading off its baseline. */\n/* */\n/* The heading is QUIET by design (`type-label`, muted) — it labels a */\n/* block, it does not title a page. The content is what the reader is */\n/* looking at; the heading only says which block they are in. Pass a */\n/* `headingLevel` that continues the surrounding document outline rather */\n/* than accepting the default blindly. */\n/* -------------------------------------------------------------------- */\n\nconst bodyVariants = cva('ds:flex ds:flex-col', {\n variants: {\n gap: {\n sm: 'ds:gap-[var(--spacing-xs)]',\n md: 'ds:gap-[var(--spacing-sm)]',\n lg: 'ds:gap-[var(--spacing-md)]',\n },\n },\n defaultVariants: { gap: 'md' },\n});\n\nexport interface SectionCardProps\n extends\n Omit<HTMLAttributes<HTMLElement>, 'title'>,\n VariantProps<typeof bodyVariants> {\n /**\n * The block's label. Already translated upstream. Omit for an unlabelled\n * block — the identity card at the top of a panel, say, whose content is\n * self-evident.\n */\n title?: ReactNode;\n /** `id` applied to the heading so a caller can wire `aria-labelledby`. */\n headingId?: string;\n /**\n * Semantic heading level. Default `3` — a panel's own `PanelHeader` is\n * usually the `h2`. Set it to continue the real outline of the surface.\n */\n headingLevel?: 2 | 3 | 4;\n /** Rendered inline-end of the heading row: a count, a link, an icon button. */\n headerAction?: ReactNode;\n /** Card surface. Default `elevated`, the rail-panel treatment. */\n variant?: 'default' | 'outlined' | 'elevated';\n /** Space between the body's children. Default `md`. */\n gap?: 'sm' | 'md' | 'lg';\n children: ReactNode;\n}\n\nexport const SectionCard = forwardRef<HTMLElement, SectionCardProps>(\n (\n {\n title,\n headingId,\n headingLevel = 3,\n headerAction,\n variant = 'elevated',\n gap,\n className,\n children,\n ...rest\n },\n ref,\n ) => {\n const Heading = `h${headingLevel}` as const satisfies 'h2' | 'h3' | 'h4';\n\n return (\n <Card\n ref={ref}\n variant={variant}\n data-component=\"section-card\"\n className={className}\n {...rest}\n >\n <Card.Body className={bodyVariants({ gap })}>\n {/* The row renders only when there is something to put in it, so an\n unlabelled card carries no empty band above its content. */}\n {title != null || headerAction != null ? (\n <div className=\"ds:flex ds:min-w-0 ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n {title != null ? (\n <Heading\n id={headingId}\n className=\"ds:m-0 ds:min-w-0 type-label ds:text-[color:var(--muted-foreground)]\"\n >\n {title}\n </Heading>\n ) : (\n // Keeps the action on the inline end when there is no heading.\n <span />\n )}\n {headerAction}\n </div>\n ) : null}\n {children}\n </Card.Body>\n </Card>\n );\n },\n);\n\nSectionCard.displayName = 'SectionCard';\n"],"names":["bodyVariants","cva","SectionCard","forwardRef","title","headingId","headingLevel","headerAction","variant","gap","className","children","rest","ref","Heading","jsx","Card","jsxs"],"mappings":";;;;AAwBA,MAAMA,IAAeC,EAAI,uBAAuB;AAAA,EAC9C,UAAU;AAAA,IACR,KAAK;AAAA,MACH,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IAAA;AAAA,EACN;AAAA,EAEF,iBAAiB,EAAE,KAAK,KAAA;AAC1B,CAAC,GA4BYC,IAAcC;AAAA,EACzB,CACE;AAAA,IACE,OAAAC;AAAA,IACA,WAAAC;AAAA,IACA,cAAAC,IAAe;AAAA,IACf,cAAAC;AAAA,IACA,SAAAC,IAAU;AAAA,IACV,KAAAC;AAAA,IACA,WAAAC;AAAA,IACA,UAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,GAELC,MACG;AACH,UAAMC,IAAU,IAAIR,CAAY;AAEhC,WACE,gBAAAS;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,KAAAH;AAAA,QACA,SAAAL;AAAA,QACA,kBAAe;AAAA,QACf,WAAAE;AAAA,QACC,GAAGE;AAAA,QAEJ,UAAA,gBAAAK,EAACD,EAAK,MAAL,EAAU,WAAWhB,EAAa,EAAE,KAAAS,GAAK,GAGvC,UAAA;AAAA,UAAAL,KAAS,QAAQG,KAAgB,OAChC,gBAAAU,EAAC,OAAA,EAAI,WAAU,oFACZ,UAAA;AAAA,YAAAb,KAAS,OACR,gBAAAW;AAAA,cAACD;AAAA,cAAA;AAAA,gBACC,IAAIT;AAAA,gBACJ,WAAU;AAAA,gBAET,UAAAD;AAAA,cAAA;AAAA,YAAA;AAAA;AAAA,gCAIF,QAAA,CAAA,CAAK;AAAA;AAAA,YAEPG;AAAA,UAAA,EAAA,CACH,IACE;AAAA,UACHI;AAAA,QAAA,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEAT,EAAY,cAAc;"}
|
|
@@ -6,7 +6,7 @@ import { parsePhoneNumberFromString as Xe } from "libphonenumber-js/min";
|
|
|
6
6
|
import { i as Ye } from "./isEmail-BfVspSvV.js";
|
|
7
7
|
import { A as h } from "./alert-C4WKViaS.js";
|
|
8
8
|
import { B as pe } from "./button-DohK_Ak_.js";
|
|
9
|
-
import { C as B } from "./card-
|
|
9
|
+
import { C as B } from "./card-DVO3x8E-.js";
|
|
10
10
|
import { D as Ze } from "./date-picker-CdhXlgVI.js";
|
|
11
11
|
import { E as he } from "./email-input-DxwbREpx.js";
|
|
12
12
|
import { E as Ve } from "./error-summary-DxNhwf_G.js";
|
|
@@ -407,4 +407,4 @@ export {
|
|
|
407
407
|
q as S,
|
|
408
408
|
fn as a
|
|
409
409
|
};
|
|
410
|
-
//# sourceMappingURL=send-registration-link-form-
|
|
410
|
+
//# sourceMappingURL=send-registration-link-form-DfbTjrEK.js.map
|