@12-apps/ui 6.89.2 → 6.91.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/components-catalog.md +2 -2
- package/dist/_internal/components/data-display/Alert/Alert.js +3 -3
- package/dist/_internal/components/data-display/AsyncStateContainer/AsyncStateContainer.js +3 -3
- package/dist/_internal/components/data-display/Avatar/Avatar.js +3 -3
- package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
- package/dist/_internal/components/data-display/CardKit/card-actions-context.js +4 -4
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +5 -5
- package/dist/_internal/components/data-display/Carousel/Carousel.chrome.js +4 -3
- package/dist/_internal/components/data-display/Carousel/Carousel.js +5 -4
- package/dist/_internal/components/data-display/Carousel/Carousel.parts.js +3 -2
- package/dist/_internal/components/data-display/Chart/Chart.js +5 -5
- package/dist/_internal/components/data-display/Chart/SpecChart.js +8 -8
- package/dist/_internal/components/data-display/Chart/chart-context.js +2 -2
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +4 -4
- package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
- package/dist/_internal/components/data-display/Chip/Chip.js +3 -3
- package/dist/_internal/components/data-display/Chip/Chip.styles.js +2 -2
- package/dist/_internal/components/data-display/DataGrid/DataGrid.js +2 -2
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +56 -31
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +64 -39
- package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +6 -6
- package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +7 -7
- package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +5 -5
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-bar-controls.js +5 -3
- package/dist/_internal/components/data-display/DataViews/data-views-day-input.js +1 -1
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +31 -6
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +8 -8
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +53 -28
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +36 -11
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +32 -7
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +33 -8
- package/dist/_internal/components/data-display/DataViews/data-views-number-input.js +1 -1
- package/dist/_internal/components/data-display/DataViews/data-views-preset-chips.js +10 -3
- package/dist/_internal/components/data-display/DataViews/data-views-range-bounds.js +47 -0
- package/dist/_internal/components/data-display/DataViews/data-views-range-calendar.js +36 -0
- package/dist/_internal/components/data-display/DataViews/data-views-range-calendar.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +32 -6
- package/dist/_internal/components/data-display/DataViews/data-views-range-summary.js +12 -0
- package/dist/_internal/components/data-display/DataViews/data-views-range-summary.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-still-anchor.js +9 -0
- package/dist/_internal/components/data-display/DataViews/data-views-still-anchor.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +2 -2
- package/dist/_internal/components/data-display/DataViews/list-card-rails.js +4 -4
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
- package/dist/_internal/components/data-display/EmptyState/EmptyState.js +2 -2
- package/dist/_internal/components/data-display/FleetMap/FleetBody.js +13 -12
- package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +12 -11
- package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js +2 -1
- package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js.map +1 -1
- package/dist/_internal/components/data-display/FleetMap/FleetMap.js +15 -15
- package/dist/_internal/components/data-display/InteractiveTooltip/InteractiveTooltip.js +2 -2
- package/dist/_internal/components/data-display/Lightbox/Lightbox.js +7 -7
- package/dist/_internal/components/data-display/Lightbox/LightboxControls.js +2 -2
- package/dist/_internal/components/data-display/Lightbox/LightboxOverlay.js +3 -3
- package/dist/_internal/components/data-display/Lightbox/LightboxThumbnails.js +2 -2
- package/dist/_internal/components/data-display/MapPreview/MapControlBar.js +4 -3
- package/dist/_internal/components/data-display/MapPreview/MapLayers.js +6 -5
- package/dist/_internal/components/data-display/MapPreview/MapMarkers.js +5 -4
- package/dist/_internal/components/data-display/MapPreview/MapPreview.helpers.js +3 -2
- package/dist/_internal/components/data-display/MapPreview/MapPreview.js +11 -10
- package/dist/_internal/components/data-display/RegionMap/RegionMap.hooks.js +2 -1
- package/dist/_internal/components/data-display/RegionMap/RegionMap.js +4 -3
- package/dist/_internal/components/data-display/RegionMap/RegionMap.view.js +19 -0
- package/dist/_internal/components/data-display/RegionMap/RegionMap.view.js.map +1 -0
- package/dist/_internal/components/data-display/RegionMap/RegionMapDrawing.js +1 -1
- package/dist/_internal/components/data-display/SectionOnboarding/SectionOnboarding.js +3 -3
- package/dist/_internal/components/data-display/index.js +67 -61
- package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +4 -4
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.js +6 -6
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.overview.js +5 -5
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.parts.js +4 -4
- package/dist/_internal/components/feedback/index.js +7 -7
- package/dist/_internal/components/form/Calendar/Calendar.js +5 -5
- package/dist/_internal/components/form/Calendar/CalendarDay.js +1 -1
- package/dist/_internal/components/form/Calendar/CalendarDayCell.js +2 -2
- package/dist/_internal/components/form/Calendar/CalendarMonth.js +3 -3
- package/dist/_internal/components/form/Calendar/CalendarSelectorsMonth.js +4 -4
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.hooks.js +3 -3
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.js +10 -10
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.range.js +2 -2
- package/dist/_internal/components/form/DateRangePicker/DateRangePickerFields.js +3 -3
- package/dist/_internal/components/form/index.js +24 -24
- package/dist/_internal/components/index.js +102 -96
- package/dist/_internal/components/layout/SettingCard/SettingCard.js +8 -8
- package/dist/_internal/components/layout/SettingCard/SettingCard.parts.js +4 -4
- package/dist/_internal/components/layout/SettingCard/SettingCard.summary.js +7 -7
- package/dist/_internal/components/layout/SettingCard/SettingGrid.js +5 -5
- package/dist/_internal/components/layout/SettingCard/SettingGroup.js +9 -9
- package/dist/_internal/components/layout/SettingCard/SettingSwitch.js +8 -8
- package/dist/_internal/components/layout/SettingCard/SettingToggle.js +9 -9
- package/dist/_internal/components/layout/index.js +12 -12
- package/dist/_internal/en-US.data-display.js +2 -1
- package/dist/_internal/pt-BR.data-display.js +2 -1
- package/dist/_internal/user-avatar/user-avatar.js +4 -4
- package/dist/charts.js +11 -11
- package/dist/{chunk-FJ3UZ4RC.js → chunk-2FKWKLSC.js} +5 -3
- package/dist/chunk-2FKWKLSC.js.map +1 -0
- package/dist/{chunk-K2VDRODL.js → chunk-3IDL7NK7.js} +2 -2
- package/dist/{chunk-6ATXOJVQ.js → chunk-3JZ3KEQQ.js} +4 -4
- package/dist/{chunk-XHIBFBNP.js → chunk-3ZRK3BRN.js} +4 -2
- package/dist/chunk-3ZRK3BRN.js.map +1 -0
- package/dist/{chunk-EOIRU62W.js → chunk-42D7KBDH.js} +2 -2
- package/dist/{chunk-LXIPBHNJ.js → chunk-42TOCPXB.js} +5 -5
- package/dist/{chunk-WAO7UQRS.js → chunk-4KWDNGMA.js} +3 -3
- package/dist/{chunk-X5OGIT2F.js → chunk-4L54KUZV.js} +22 -4
- package/dist/chunk-4L54KUZV.js.map +1 -0
- package/dist/{chunk-JIEVKHOH.js → chunk-4QR7LM5F.js} +2 -2
- package/dist/{chunk-WJMSND5Q.js → chunk-5E2CRRD4.js} +5 -5
- package/dist/{chunk-ZDAAHCIS.js → chunk-5HADIIWN.js} +3 -3
- package/dist/{chunk-BP3ZKIPE.js → chunk-64UNVW6D.js} +4 -4
- package/dist/{chunk-TRYR424I.js → chunk-6LOWZCH7.js} +15 -1
- package/dist/chunk-6LOWZCH7.js.map +1 -0
- package/dist/{chunk-6WHIJPEF.js → chunk-6PU5P7QT.js} +2 -2
- package/dist/{chunk-7F6D73NL.js → chunk-76DX2IMH.js} +2 -2
- package/dist/chunk-7CUZTWKW.js +52 -0
- package/dist/chunk-7CUZTWKW.js.map +1 -0
- package/dist/{chunk-5RSK5M6M.js → chunk-7NP2JHGM.js} +63 -126
- package/dist/chunk-7NP2JHGM.js.map +1 -0
- package/dist/{chunk-YNZUYZ2O.js → chunk-A3DDEKLV.js} +2 -2
- package/dist/{chunk-FEKWKWGK.js → chunk-ARGVUTTL.js} +15 -1
- package/dist/chunk-ARGVUTTL.js.map +1 -0
- package/dist/{chunk-BMTIBQQC.js → chunk-BBBHPK2Q.js} +6 -6
- package/dist/{chunk-DYHOBKPU.js → chunk-BLZDSCIZ.js} +6 -6
- package/dist/{chunk-EUR2ORTN.js → chunk-CSJ7WNXU.js} +2 -2
- package/dist/{chunk-OWRQOYUE.js → chunk-DA3D5X5Z.js} +5 -5
- package/dist/{chunk-LV6AD6XC.js → chunk-DBI6AMNY.js} +4 -4
- package/dist/{chunk-3YFDGFEK.js → chunk-DVX4HDNP.js} +2 -2
- package/dist/chunk-DY2I6LMX.js +1 -0
- package/dist/chunk-DY2I6LMX.js.map +1 -0
- package/dist/{chunk-QKOJ4637.js → chunk-E6K2UZGR.js} +3 -3
- package/dist/{chunk-7XSCEHGA.js → chunk-EOQ6EKTS.js} +7 -7
- package/dist/chunk-FORAVDJF.js +164 -0
- package/dist/chunk-FORAVDJF.js.map +1 -0
- package/dist/chunk-FWAY2SAI.js +72 -0
- package/dist/chunk-FWAY2SAI.js.map +1 -0
- package/dist/{chunk-Q2ERGAQO.js → chunk-FYAW5GBF.js} +33 -6
- package/dist/chunk-FYAW5GBF.js.map +1 -0
- package/dist/{chunk-GMRQPNG3.js → chunk-G2NVBCF7.js} +20 -25
- package/dist/chunk-G2NVBCF7.js.map +1 -0
- package/dist/{chunk-Z5WUSI47.js → chunk-G3JFESKD.js} +6 -6
- package/dist/{chunk-3EIRRLCQ.js → chunk-HC6OZ7QK.js} +2 -2
- package/dist/{chunk-SKIAWOUC.js → chunk-HOPRZEBM.js} +3 -3
- package/dist/{chunk-OBA75Y5O.js → chunk-HS662RLW.js} +61 -33
- package/dist/chunk-HS662RLW.js.map +1 -0
- package/dist/{chunk-O63ZROSY.js → chunk-IWSTMVCA.js} +7 -7
- package/dist/chunk-J2AVWIMM.js +60 -0
- package/dist/chunk-J2AVWIMM.js.map +1 -0
- package/dist/{chunk-7QKIL5YF.js → chunk-J2GDA64N.js} +4 -4
- package/dist/{chunk-TZC6KIK2.js → chunk-JG733RMP.js} +5 -5
- package/dist/{chunk-CW5Z67NA.js → chunk-JH2XLVWM.js} +2 -2
- package/dist/{chunk-TYXFFNPM.js → chunk-JKZPGCN7.js} +11 -11
- package/dist/{chunk-QRRWLLXW.js → chunk-JN2IANXD.js} +3 -3
- package/dist/{chunk-HQCZT6TO.js → chunk-JVOEVTCZ.js} +5 -5
- package/dist/{chunk-GBB23QLL.js → chunk-KJKD6Y6V.js} +3 -3
- package/dist/{chunk-XLU5WFSY.js → chunk-L3PDY7CJ.js} +2 -2
- package/dist/{chunk-S6D7NOV7.js → chunk-L6JAALMX.js} +3 -3
- package/dist/{chunk-72HOUWAW.js → chunk-L6YTVXGN.js} +5 -3
- package/dist/{chunk-72HOUWAW.js.map → chunk-L6YTVXGN.js.map} +1 -1
- package/dist/chunk-LZNUBQ5I.js +181 -0
- package/dist/chunk-LZNUBQ5I.js.map +1 -0
- package/dist/{chunk-BECKKDYG.js → chunk-M2M4IDFU.js} +2 -2
- package/dist/{chunk-AMLHEKSV.js → chunk-M3XJVD5G.js} +5 -5
- package/dist/{chunk-CSGBHYW6.js → chunk-M5Y42AZ4.js} +2 -2
- package/dist/chunk-MICX23P4.js +122 -0
- package/dist/chunk-MICX23P4.js.map +1 -0
- package/dist/{chunk-5CP5Z3YU.js → chunk-MPWA55CF.js} +4 -4
- package/dist/{chunk-CJUK32UH.js → chunk-MYTV353T.js} +5 -5
- package/dist/{chunk-BO6MT2S2.js → chunk-MZAYIG2I.js} +7 -6
- package/dist/chunk-MZAYIG2I.js.map +1 -0
- package/dist/{chunk-AKE6KVQG.js → chunk-NGXL4R54.js} +5 -1
- package/dist/chunk-NGXL4R54.js.map +1 -0
- package/dist/{chunk-BEHT4IHH.js → chunk-NKEFH3RS.js} +6 -6
- package/dist/{chunk-NI67M453.js → chunk-PGANINQD.js} +5 -5
- package/dist/{chunk-RCTHCXS6.js → chunk-PJNIJIE6.js} +2 -2
- package/dist/{chunk-E2UOEI7N.js → chunk-Q3SILZTQ.js} +2 -2
- package/dist/{chunk-SB2SEGR6.js → chunk-QHIWV55K.js} +3 -3
- package/dist/{chunk-DHAXUVMR.js → chunk-QN7FVZQD.js} +4 -4
- package/dist/{chunk-5S5K73I6.js → chunk-QSOCOXVL.js} +3 -3
- package/dist/{chunk-NQCTANF3.js → chunk-QVDEGBME.js} +7 -7
- package/dist/{chunk-BQBEN44R.js → chunk-RDCIST7P.js} +2 -2
- package/dist/{chunk-HUXAIQC5.js → chunk-RZ3P4X2I.js} +2 -2
- package/dist/{chunk-QLJU7UUI.js → chunk-T7OIVPCO.js} +38 -3
- package/dist/chunk-T7OIVPCO.js.map +1 -0
- package/dist/{chunk-U2ILXZDG.js → chunk-TB5CRHTF.js} +2 -2
- package/dist/{chunk-X2GWR75Q.js → chunk-TSEBMVYY.js} +3 -3
- package/dist/{chunk-OKRITX4S.js → chunk-U2AEM3K4.js} +7 -7
- package/dist/{chunk-EXVCVXFX.js → chunk-U2QXQ7D4.js} +6 -6
- package/dist/{chunk-K6FCSB6T.js → chunk-U2SAW6DH.js} +2 -2
- package/dist/{chunk-DAHSKQBV.js → chunk-UMOHHWXY.js} +2 -2
- package/dist/{chunk-YDIC6LSP.js → chunk-VCQFDQM3.js} +27 -7
- package/dist/chunk-VCQFDQM3.js.map +1 -0
- package/dist/{chunk-P772HCHI.js → chunk-W676GY42.js} +4 -4
- package/dist/{chunk-BAQ3ITZV.js → chunk-WCF56ZCD.js} +2 -2
- package/dist/{chunk-6D5AMANO.js → chunk-WH3LUOCQ.js} +2 -2
- package/dist/{chunk-T2G4LDU5.js → chunk-WLLUARTC.js} +2 -2
- package/dist/{chunk-5BVAUUWL.js → chunk-X37NTEVS.js} +2 -2
- package/dist/{chunk-5KK3I42O.js → chunk-XILMDEGZ.js} +13 -10
- package/dist/chunk-XILMDEGZ.js.map +1 -0
- package/dist/{chunk-MW7P2C2C.js → chunk-YGQM6GFQ.js} +2 -2
- package/dist/{chunk-WW3F7DCX.js → chunk-Z2WZDVGG.js} +2 -2
- package/dist/{chunk-WNNA6YYE.js → chunk-Z7T4EFEC.js} +7 -4
- package/dist/chunk-Z7T4EFEC.js.map +1 -0
- package/dist/{chunk-LOLYBFYY.js → chunk-ZCNR4DN5.js} +5 -5
- package/dist/{chunk-COKPPU42.js → chunk-ZIHSCBMN.js} +12 -12
- package/dist/data-display/Alert.js +3 -3
- package/dist/data-display/AsyncStateContainer.js +3 -3
- package/dist/data-display/Avatar.js +3 -3
- package/dist/data-display/CardKit.js +15 -15
- package/dist/data-display/Carousel.js +5 -4
- package/dist/data-display/Chart.js +11 -11
- package/dist/data-display/Chip.js +3 -3
- package/dist/data-display/DataGrid.js +2 -2
- package/dist/data-display/DataViews.js +75 -50
- package/dist/data-display/DayRuler.js +4 -4
- package/dist/data-display/EmptyState.js +2 -2
- package/dist/data-display/FleetMap.js +15 -15
- package/dist/data-display/InteractiveTooltip.js +2 -2
- package/dist/data-display/Lightbox.js +7 -7
- package/dist/data-display/MapPreview.js +11 -10
- package/dist/data-display/RegionMap.js +11 -5
- package/dist/data-display/SectionOnboarding.js +3 -3
- package/dist/en-US.js +1 -1
- package/dist/feedback/NotificationStatusNotice.js +4 -4
- package/dist/feedback/SequenceResolver.js +6 -6
- package/dist/form/Calendar.js +5 -5
- package/dist/form/DateRangePicker.js +10 -10
- package/dist/layout/SettingCard.js +12 -12
- package/dist/locales.js +5 -5
- package/dist/pt-BR.js +1 -1
- package/dist/types/components/data-display/DataViews/data-views-bar-controls.d.ts +15 -1
- package/dist/types/components/data-display/DataViews/data-views-bar-controls.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +35 -0
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts +7 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts +5 -3
- package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-number-input.d.ts +10 -2
- package/dist/types/components/data-display/DataViews/data-views-number-input.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-preset-chips.d.ts +10 -0
- package/dist/types/components/data-display/DataViews/data-views-preset-chips.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-bounds.d.ts +35 -0
- package/dist/types/components/data-display/DataViews/data-views-range-bounds.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-range-calendar.d.ts +35 -0
- package/dist/types/components/data-display/DataViews/data-views-range-calendar.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts +2 -11
- package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-summary.d.ts +21 -0
- package/dist/types/components/data-display/DataViews/data-views-range-summary.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-still-anchor.d.ts +45 -0
- package/dist/types/components/data-display/DataViews/data-views-still-anchor.d.ts.map +1 -0
- package/dist/types/components/data-display/LazyImage/LazyImage.hooks.d.ts +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.d.ts +4 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.hooks.d.ts +4 -2
- package/dist/types/components/data-display/RegionMap/RegionMap.hooks.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.types.d.ts +40 -0
- package/dist/types/components/data-display/RegionMap/RegionMap.types.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.view.d.ts +36 -0
- package/dist/types/components/data-display/RegionMap/RegionMap.view.d.ts.map +1 -0
- package/dist/types/components/data-display/RegionMap/RegionMapDrawing.d.ts +4 -1
- package/dist/types/components/data-display/RegionMap/RegionMapDrawing.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/index.d.ts +3 -2
- package/dist/types/components/data-display/RegionMap/index.d.ts.map +1 -1
- package/dist/types/components/form/Calendar/Calendar.d.ts.map +1 -1
- package/dist/types/components/form/Calendar/CalendarDay.d.ts +2 -0
- package/dist/types/components/form/Calendar/CalendarDay.d.ts.map +1 -1
- package/dist/types/components/form/Calendar/CalendarDayCell.d.ts +2 -0
- package/dist/types/components/form/Calendar/CalendarDayCell.d.ts.map +1 -1
- package/dist/types/components/form/Calendar/CalendarMonth.d.ts +5 -0
- package/dist/types/components/form/Calendar/CalendarMonth.d.ts.map +1 -1
- package/dist/types/en-US.data-display.d.ts.map +1 -1
- package/dist/types/pt-BR.data-display.d.ts.map +1 -1
- package/dist/user-avatar.js +4 -4
- package/package.json +1 -1
- package/src/components/data-display/DataViews/RESPONSIVE-BEHAVIOUR.md +59 -0
- package/src/components/data-display/DataViews/data-views-bar-controls.tsx +56 -1
- package/src/components/data-display/DataViews/data-views-copy.ts +36 -0
- package/src/components/data-display/DataViews/data-views-day-input.tsx +9 -1
- package/src/components/data-display/DataViews/data-views-inline-bar.tsx +5 -3
- package/src/components/data-display/DataViews/data-views-more-fields.tsx +1 -0
- package/src/components/data-display/DataViews/data-views-more-filters.tsx +82 -29
- package/src/components/data-display/DataViews/data-views-number-input.tsx +19 -9
- package/src/components/data-display/DataViews/data-views-preset-chips.tsx +165 -16
- package/src/components/data-display/DataViews/data-views-range-bounds.tsx +223 -0
- package/src/components/data-display/DataViews/data-views-range-calendar.tsx +185 -0
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +71 -154
- package/src/components/data-display/DataViews/data-views-range-summary.tsx +66 -0
- package/src/components/data-display/DataViews/data-views-still-anchor.ts +93 -0
- package/src/components/data-display/RegionMap/RegionMap.hooks.ts +24 -33
- package/src/components/data-display/RegionMap/RegionMap.md +27 -0
- package/src/components/data-display/RegionMap/RegionMap.tsx +7 -1
- package/src/components/data-display/RegionMap/RegionMap.types.ts +41 -0
- package/src/components/data-display/RegionMap/RegionMap.view.ts +105 -0
- package/src/components/data-display/RegionMap/RegionMapDrawing.tsx +11 -0
- package/src/components/data-display/RegionMap/index.ts +5 -1
- package/src/components/data-display/RegionMap/tests.md +10 -2
- package/src/components/form/Calendar/Calendar.md +17 -2
- package/src/components/form/Calendar/Calendar.tsx +56 -3
- package/src/components/form/Calendar/CalendarDay.tsx +33 -3
- package/src/components/form/Calendar/CalendarDayCell.tsx +4 -0
- package/src/components/form/Calendar/CalendarMonth.tsx +27 -5
- package/src/en-US.data-display.ts +12 -0
- package/src/pt-BR.data-display.ts +12 -0
- package/dist/chunk-5KK3I42O.js.map +0 -1
- package/dist/chunk-5RSK5M6M.js.map +0 -1
- package/dist/chunk-7D7CJRL7.js +0 -78
- package/dist/chunk-7D7CJRL7.js.map +0 -1
- package/dist/chunk-ACFKPE2T.js +0 -1
- package/dist/chunk-AKE6KVQG.js.map +0 -1
- package/dist/chunk-BO6MT2S2.js.map +0 -1
- package/dist/chunk-FEKWKWGK.js.map +0 -1
- package/dist/chunk-FJ3UZ4RC.js.map +0 -1
- package/dist/chunk-GMRQPNG3.js.map +0 -1
- package/dist/chunk-OBA75Y5O.js.map +0 -1
- package/dist/chunk-Q2ERGAQO.js.map +0 -1
- package/dist/chunk-QLJU7UUI.js.map +0 -1
- package/dist/chunk-TRYR424I.js.map +0 -1
- package/dist/chunk-WNNA6YYE.js.map +0 -1
- package/dist/chunk-X5OGIT2F.js.map +0 -1
- package/dist/chunk-XHIBFBNP.js.map +0 -1
- package/dist/chunk-YDIC6LSP.js.map +0 -1
- /package/dist/{chunk-ACFKPE2T.js.map → _internal/components/data-display/DataViews/data-views-range-bounds.js.map} +0 -0
- /package/dist/{chunk-K2VDRODL.js.map → chunk-3IDL7NK7.js.map} +0 -0
- /package/dist/{chunk-6ATXOJVQ.js.map → chunk-3JZ3KEQQ.js.map} +0 -0
- /package/dist/{chunk-EOIRU62W.js.map → chunk-42D7KBDH.js.map} +0 -0
- /package/dist/{chunk-LXIPBHNJ.js.map → chunk-42TOCPXB.js.map} +0 -0
- /package/dist/{chunk-WAO7UQRS.js.map → chunk-4KWDNGMA.js.map} +0 -0
- /package/dist/{chunk-JIEVKHOH.js.map → chunk-4QR7LM5F.js.map} +0 -0
- /package/dist/{chunk-WJMSND5Q.js.map → chunk-5E2CRRD4.js.map} +0 -0
- /package/dist/{chunk-ZDAAHCIS.js.map → chunk-5HADIIWN.js.map} +0 -0
- /package/dist/{chunk-BP3ZKIPE.js.map → chunk-64UNVW6D.js.map} +0 -0
- /package/dist/{chunk-6WHIJPEF.js.map → chunk-6PU5P7QT.js.map} +0 -0
- /package/dist/{chunk-7F6D73NL.js.map → chunk-76DX2IMH.js.map} +0 -0
- /package/dist/{chunk-YNZUYZ2O.js.map → chunk-A3DDEKLV.js.map} +0 -0
- /package/dist/{chunk-BMTIBQQC.js.map → chunk-BBBHPK2Q.js.map} +0 -0
- /package/dist/{chunk-DYHOBKPU.js.map → chunk-BLZDSCIZ.js.map} +0 -0
- /package/dist/{chunk-EUR2ORTN.js.map → chunk-CSJ7WNXU.js.map} +0 -0
- /package/dist/{chunk-OWRQOYUE.js.map → chunk-DA3D5X5Z.js.map} +0 -0
- /package/dist/{chunk-LV6AD6XC.js.map → chunk-DBI6AMNY.js.map} +0 -0
- /package/dist/{chunk-3YFDGFEK.js.map → chunk-DVX4HDNP.js.map} +0 -0
- /package/dist/{chunk-QKOJ4637.js.map → chunk-E6K2UZGR.js.map} +0 -0
- /package/dist/{chunk-7XSCEHGA.js.map → chunk-EOQ6EKTS.js.map} +0 -0
- /package/dist/{chunk-Z5WUSI47.js.map → chunk-G3JFESKD.js.map} +0 -0
- /package/dist/{chunk-3EIRRLCQ.js.map → chunk-HC6OZ7QK.js.map} +0 -0
- /package/dist/{chunk-SKIAWOUC.js.map → chunk-HOPRZEBM.js.map} +0 -0
- /package/dist/{chunk-O63ZROSY.js.map → chunk-IWSTMVCA.js.map} +0 -0
- /package/dist/{chunk-7QKIL5YF.js.map → chunk-J2GDA64N.js.map} +0 -0
- /package/dist/{chunk-TZC6KIK2.js.map → chunk-JG733RMP.js.map} +0 -0
- /package/dist/{chunk-CW5Z67NA.js.map → chunk-JH2XLVWM.js.map} +0 -0
- /package/dist/{chunk-TYXFFNPM.js.map → chunk-JKZPGCN7.js.map} +0 -0
- /package/dist/{chunk-QRRWLLXW.js.map → chunk-JN2IANXD.js.map} +0 -0
- /package/dist/{chunk-HQCZT6TO.js.map → chunk-JVOEVTCZ.js.map} +0 -0
- /package/dist/{chunk-GBB23QLL.js.map → chunk-KJKD6Y6V.js.map} +0 -0
- /package/dist/{chunk-XLU5WFSY.js.map → chunk-L3PDY7CJ.js.map} +0 -0
- /package/dist/{chunk-S6D7NOV7.js.map → chunk-L6JAALMX.js.map} +0 -0
- /package/dist/{chunk-BECKKDYG.js.map → chunk-M2M4IDFU.js.map} +0 -0
- /package/dist/{chunk-AMLHEKSV.js.map → chunk-M3XJVD5G.js.map} +0 -0
- /package/dist/{chunk-CSGBHYW6.js.map → chunk-M5Y42AZ4.js.map} +0 -0
- /package/dist/{chunk-5CP5Z3YU.js.map → chunk-MPWA55CF.js.map} +0 -0
- /package/dist/{chunk-CJUK32UH.js.map → chunk-MYTV353T.js.map} +0 -0
- /package/dist/{chunk-BEHT4IHH.js.map → chunk-NKEFH3RS.js.map} +0 -0
- /package/dist/{chunk-NI67M453.js.map → chunk-PGANINQD.js.map} +0 -0
- /package/dist/{chunk-RCTHCXS6.js.map → chunk-PJNIJIE6.js.map} +0 -0
- /package/dist/{chunk-E2UOEI7N.js.map → chunk-Q3SILZTQ.js.map} +0 -0
- /package/dist/{chunk-SB2SEGR6.js.map → chunk-QHIWV55K.js.map} +0 -0
- /package/dist/{chunk-DHAXUVMR.js.map → chunk-QN7FVZQD.js.map} +0 -0
- /package/dist/{chunk-5S5K73I6.js.map → chunk-QSOCOXVL.js.map} +0 -0
- /package/dist/{chunk-NQCTANF3.js.map → chunk-QVDEGBME.js.map} +0 -0
- /package/dist/{chunk-BQBEN44R.js.map → chunk-RDCIST7P.js.map} +0 -0
- /package/dist/{chunk-HUXAIQC5.js.map → chunk-RZ3P4X2I.js.map} +0 -0
- /package/dist/{chunk-U2ILXZDG.js.map → chunk-TB5CRHTF.js.map} +0 -0
- /package/dist/{chunk-X2GWR75Q.js.map → chunk-TSEBMVYY.js.map} +0 -0
- /package/dist/{chunk-OKRITX4S.js.map → chunk-U2AEM3K4.js.map} +0 -0
- /package/dist/{chunk-EXVCVXFX.js.map → chunk-U2QXQ7D4.js.map} +0 -0
- /package/dist/{chunk-K6FCSB6T.js.map → chunk-U2SAW6DH.js.map} +0 -0
- /package/dist/{chunk-DAHSKQBV.js.map → chunk-UMOHHWXY.js.map} +0 -0
- /package/dist/{chunk-P772HCHI.js.map → chunk-W676GY42.js.map} +0 -0
- /package/dist/{chunk-BAQ3ITZV.js.map → chunk-WCF56ZCD.js.map} +0 -0
- /package/dist/{chunk-6D5AMANO.js.map → chunk-WH3LUOCQ.js.map} +0 -0
- /package/dist/{chunk-T2G4LDU5.js.map → chunk-WLLUARTC.js.map} +0 -0
- /package/dist/{chunk-5BVAUUWL.js.map → chunk-X37NTEVS.js.map} +0 -0
- /package/dist/{chunk-MW7P2C2C.js.map → chunk-YGQM6GFQ.js.map} +0 -0
- /package/dist/{chunk-WW3F7DCX.js.map → chunk-Z2WZDVGG.js.map} +0 -0
- /package/dist/{chunk-LOLYBFYY.js.map → chunk-ZCNR4DN5.js.map} +0 -0
- /package/dist/{chunk-COKPPU42.js.map → chunk-ZIHSCBMN.js.map} +0 -0
|
@@ -6,16 +6,19 @@ import {
|
|
|
6
6
|
geometryCache,
|
|
7
7
|
hitRegion,
|
|
8
8
|
projectionAt,
|
|
9
|
-
viewBoxFor,
|
|
10
9
|
type DrawnRegion,
|
|
11
10
|
type PlaneBox,
|
|
11
|
+
type Projection,
|
|
12
12
|
} from './RegionMap.helpers';
|
|
13
|
-
import type { RegionMapProps
|
|
13
|
+
import type { RegionMapProps } from './RegionMap.types';
|
|
14
|
+
import { centreOf, reachBox, viewWithin } from './RegionMap.view';
|
|
14
15
|
|
|
15
16
|
/** What the drawing needs, derived from the props. */
|
|
16
17
|
export interface RegionMapModel {
|
|
17
18
|
/** The square shown, or null when there is nothing to centre on. */
|
|
18
19
|
view: PlaneBox | null;
|
|
20
|
+
/** The projection drawn with, or null when there is nothing to centre on. */
|
|
21
|
+
projection: Projection | null;
|
|
19
22
|
drawn: DrawnRegion[];
|
|
20
23
|
/** Radii drawn, in metres; empty without circles. */
|
|
21
24
|
radii: number[];
|
|
@@ -28,15 +31,6 @@ export interface RegionMapModel {
|
|
|
28
31
|
/** A tap reaches an area this close: a share of the view (~8 px on a phone). */
|
|
29
32
|
const TOLERANCE_SHARE = 1 / 45;
|
|
30
33
|
|
|
31
|
-
/** The middle of every area, for a map with no centre of its own. */
|
|
32
|
-
function centreOf(regions: readonly RegionMapRegion[]): { latitude: number; longitude: number } | null {
|
|
33
|
-
const points = regions.flatMap((r) => r.rings.flat());
|
|
34
|
-
const lats = extent(points.map(([lat]) => lat));
|
|
35
|
-
const lngs = extent(points.map(([, lng]) => lng));
|
|
36
|
-
if (lats === null || lngs === null) return null;
|
|
37
|
-
return { latitude: (lats.min + lats.max) / 2, longitude: (lngs.min + lngs.max) / 2 };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
34
|
/** The click, in the drawing's own coordinates — or null where it cannot be read. */
|
|
41
35
|
function svgPoint(event: React.MouseEvent<SVGSVGElement>): { x: number; y: number } | null {
|
|
42
36
|
const svg = event.currentTarget;
|
|
@@ -46,16 +40,6 @@ function svgPoint(event: React.MouseEvent<SVGSVGElement>): { x: number; y: numbe
|
|
|
46
40
|
return { x: point.x, y: point.y };
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
/** The view: the outermost circle when there is one, else every area's box. */
|
|
50
|
-
function viewOf(outermostMetres: number | null, drawable: () => (PlaneBox | null)[]): PlaneBox | null {
|
|
51
|
-
if (outermostMetres !== null) return viewBoxFor(outermostMetres, null);
|
|
52
|
-
const boxes = drawable().filter((box): box is PlaneBox => box !== null);
|
|
53
|
-
const xs = extent(boxes.flatMap((b) => [b.minX, b.minX + b.spanX]));
|
|
54
|
-
const ys = extent(boxes.flatMap((b) => [b.minY, b.minY + b.spanY]));
|
|
55
|
-
if (xs === null || ys === null) return null;
|
|
56
|
-
return viewBoxFor(null, { minX: xs.min, minY: ys.min, spanX: xs.max - xs.min, spanY: ys.max - ys.min });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
43
|
/** Which area a click or tap means — see `useRegionMap`. */
|
|
60
44
|
function tappedRegion(
|
|
61
45
|
event: React.MouseEvent<SVGSVGElement>,
|
|
@@ -74,16 +58,18 @@ function tappedRegion(
|
|
|
74
58
|
);
|
|
75
59
|
}
|
|
76
60
|
|
|
77
|
-
/** The geometry cache for a centre, or
|
|
78
|
-
function useGeometry(
|
|
79
|
-
|
|
80
|
-
|
|
61
|
+
/** The projection and geometry cache for a centre, or nulls without one. */
|
|
62
|
+
function useGeometry(origin: { latitude: number; longitude: number } | null): {
|
|
63
|
+
projection: Projection | null;
|
|
64
|
+
geometryOf: ReturnType<typeof geometryCache> | null;
|
|
65
|
+
} {
|
|
81
66
|
const lat = origin?.latitude ?? null;
|
|
82
67
|
const lng = origin?.longitude ?? null;
|
|
83
|
-
return React.useMemo(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
return React.useMemo(() => {
|
|
69
|
+
if (lat === null || lng === null) return { projection: null, geometryOf: null };
|
|
70
|
+
const projection = projectionAt({ latitude: lat, longitude: lng });
|
|
71
|
+
return { projection, geometryOf: geometryCache(projection) };
|
|
72
|
+
}, [lat, lng]);
|
|
87
73
|
}
|
|
88
74
|
|
|
89
75
|
/**
|
|
@@ -101,13 +87,18 @@ export function useRegionMap({
|
|
|
101
87
|
regions,
|
|
102
88
|
center,
|
|
103
89
|
circles = [],
|
|
90
|
+
reach,
|
|
104
91
|
onToggle,
|
|
105
|
-
}: Pick<RegionMapProps, 'regions' | 'center' | 'circles' | 'onToggle'>): RegionMapModel {
|
|
106
|
-
const geometryOf = useGeometry(center ?? centreOf(regions));
|
|
92
|
+
}: Pick<RegionMapProps, 'regions' | 'center' | 'circles' | 'reach' | 'onToggle'>): RegionMapModel {
|
|
93
|
+
const { projection, geometryOf } = useGeometry(center ?? centreOf(regions));
|
|
107
94
|
const radii = circles.map((c) => c.radiusMetres);
|
|
108
95
|
const outermostMetres = extent(radii)?.max ?? null;
|
|
109
96
|
const view =
|
|
110
|
-
|
|
97
|
+
projection === null || geometryOf === null
|
|
98
|
+
? null
|
|
99
|
+
: viewWithin(outermostMetres, reach === undefined ? null : reachBox(reach, projection), () =>
|
|
100
|
+
regions.map((r) => geometryOf(r.rings).bounds),
|
|
101
|
+
);
|
|
111
102
|
const drawn = geometryOf && view ? drawOrder(regions, geometryOf, view) : [];
|
|
112
103
|
const pointerType = React.useRef<string>('mouse');
|
|
113
104
|
|
|
@@ -121,5 +112,5 @@ export function useRegionMap({
|
|
|
121
112
|
if (hit) onToggle(hit);
|
|
122
113
|
};
|
|
123
114
|
|
|
124
|
-
return { view, drawn, radii, outermostMetres, onPointerDown, onClick };
|
|
115
|
+
return { view, projection, drawn, radii, outermostMetres, onPointerDown, onClick };
|
|
125
116
|
}
|
|
@@ -75,6 +75,27 @@ clipped at an edge — the map reads the same at 320 px and at 1600 px. The
|
|
|
75
75
|
drawing is square; `maxHeight` caps it on a wide screen, and where it sits in a
|
|
76
76
|
wider box is the host's call.
|
|
77
77
|
|
|
78
|
+
### A host can draw inside it, at its scale
|
|
79
|
+
|
|
80
|
+
A host often has something of its own to show over the areas — a band served
|
|
81
|
+
past the largest circle, a route, a marker — and laying a second SVG over the
|
|
82
|
+
drawing means copying its view math and losing its frame. So:
|
|
83
|
+
|
|
84
|
+
- `reach` (`{ radiusMetres }` or `{ bounds }`) is an extent the view must ALSO
|
|
85
|
+
fit, before the 12% margin. With circles the view stays centred on the centre
|
|
86
|
+
and grows to the reach's farthest edge, so a ring stays a circle in the
|
|
87
|
+
middle; without circles the view fits the areas and the reach together. A
|
|
88
|
+
reach no radius describes — the outlines of some areas — is a box:
|
|
89
|
+
`boundsOfRings(rings)`. Omit it and the view is exactly what it was.
|
|
90
|
+
- `overlay(layer)` draws inside the SVG with `layer.view` and
|
|
91
|
+
`layer.projection` (`projectionAt(center)`), over the excluded hatch and
|
|
92
|
+
under the circles, highlights and centre. It sits in a `pointer-events: none`
|
|
93
|
+
group, so a tap still reaches the area beneath.
|
|
94
|
+
- `regionMapView(props)` returns the same view for HTML laid over the drawing
|
|
95
|
+
(a pill on a circle, a note): a point `p` sits at `(p.x - view.minX) /
|
|
96
|
+
view.spanX` across; with circles the view is centred and its half-side is
|
|
97
|
+
`view.spanX / 2`.
|
|
98
|
+
|
|
78
99
|
## Props
|
|
79
100
|
|
|
80
101
|
| prop | type | default | what it does |
|
|
@@ -82,6 +103,8 @@ wider box is the host's call.
|
|
|
82
103
|
| `regions` | `readonly RegionMapRegion[]` | — | the areas; drawn when any part is in view |
|
|
83
104
|
| `center` | `{ latitude, longitude } \| null` | — | centre, dot and circles; `null` centres on the areas, no dot |
|
|
84
105
|
| `circles` | `readonly { radiusMetres }[]` | `[]` | dashed circles; the view fits the largest |
|
|
106
|
+
| `reach` | `{ radiusMetres } \| { bounds: { south, west, north, east } }` | — | an extent the view must also fit (margin on top) |
|
|
107
|
+
| `overlay` | `(layer: { view, projection }) => ReactNode` | — | a host layer inside the SVG, under the circles, tap-transparent |
|
|
85
108
|
| `onToggle` | `(id: string) => void` | — | a tap or a list press; omit for read-only |
|
|
86
109
|
| `highlightedIds` | `readonly string[]` | `[]` | areas to point at (a search's hits), outlined and named |
|
|
87
110
|
| `copy` | `RegionMapCopy` | — | every word |
|
|
@@ -117,6 +140,10 @@ wider box is the host's call.
|
|
|
117
140
|
`[latitude, longitude]` points; outlines usually travel this way.
|
|
118
141
|
- `hitRegion(regions, point, tolerance)` and `projectionAt(center)` — the hit
|
|
119
142
|
test and projection the component uses, for a host that draws its own layer.
|
|
143
|
+
- `regionMapView({ regions, center, circles, reach })` — the view the
|
|
144
|
+
component shows for those props, in projected metres, for HTML laid over it.
|
|
145
|
+
- `boundsOfRings(rings)` — the latitude/longitude box around some outlines,
|
|
146
|
+
for a `reach` no radius describes.
|
|
120
147
|
|
|
121
148
|
## Example
|
|
122
149
|
|
|
@@ -29,6 +29,9 @@ const NO_IDS: readonly string[] = [];
|
|
|
29
29
|
* is that path. A finger's tap takes the SMALLEST area within a few pixels'
|
|
30
30
|
* worth of metres (`hitRegion`), so a sliver beside a big area can be hit
|
|
31
31
|
* from the map; one too thin even for that is still on the list.
|
|
32
|
+
* - **The host can draw inside it.** `reach` widens the view past the largest
|
|
33
|
+
* circle and `overlay` draws a layer in the SVG at the map's own projection,
|
|
34
|
+
* tap-transparent; `regionMapView` gives the same view to HTML laid over it.
|
|
32
35
|
* - **A toggle is cheap.** Geometry is projected once per `rings` identity,
|
|
33
36
|
* and the base layer is memoised, so a tap re-renders the hatch and labels.
|
|
34
37
|
*/
|
|
@@ -36,6 +39,8 @@ export function RegionMap({
|
|
|
36
39
|
regions,
|
|
37
40
|
center,
|
|
38
41
|
circles,
|
|
42
|
+
reach,
|
|
43
|
+
overlay,
|
|
39
44
|
onToggle,
|
|
40
45
|
highlightedIds = NO_IDS,
|
|
41
46
|
copy,
|
|
@@ -46,7 +51,7 @@ export function RegionMap({
|
|
|
46
51
|
dataTestId = 'region-map',
|
|
47
52
|
}: RegionMapProps): React.JSX.Element | null {
|
|
48
53
|
const theme = useTheme();
|
|
49
|
-
const model = useRegionMap({ regions, center, circles, onToggle });
|
|
54
|
+
const model = useRegionMap({ regions, center, circles, reach, onToggle });
|
|
50
55
|
const { view } = model;
|
|
51
56
|
if (view === null && regions.length === 0) return null;
|
|
52
57
|
return (
|
|
@@ -55,6 +60,7 @@ export function RegionMap({
|
|
|
55
60
|
<>
|
|
56
61
|
<RegionMapDrawing
|
|
57
62
|
model={{ ...model, view }}
|
|
63
|
+
overlay={overlay}
|
|
58
64
|
showCenter={center !== null}
|
|
59
65
|
highlightedIds={highlightedIds}
|
|
60
66
|
maxHeight={maxHeight}
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* every word arrives through `copy`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type React from 'react';
|
|
11
|
+
|
|
12
|
+
import type { PlaneBox, Projection } from './RegionMap.helpers';
|
|
13
|
+
|
|
10
14
|
/** A latitude/longitude pair, in that order. */
|
|
11
15
|
export type RegionMapPoint = readonly [latitude: number, longitude: number];
|
|
12
16
|
|
|
@@ -50,6 +54,29 @@ export interface RegionMapCircle {
|
|
|
50
54
|
radiusMetres: number;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
/** A latitude/longitude box, by its four edges in degrees. */
|
|
58
|
+
export interface RegionMapBounds {
|
|
59
|
+
south: number;
|
|
60
|
+
west: number;
|
|
61
|
+
north: number;
|
|
62
|
+
east: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* An extent the view must fit besides its circles: a radius around the centre,
|
|
67
|
+
* or a box — e.g. the bounds of some outlines (`boundsOfRings`), for a reach
|
|
68
|
+
* no single radius describes.
|
|
69
|
+
*/
|
|
70
|
+
export type RegionMapReach = { radiusMetres: number } | { bounds: RegionMapBounds };
|
|
71
|
+
|
|
72
|
+
/** What a host overlay is drawn with: the map's own view and projection. */
|
|
73
|
+
export interface RegionMapLayer {
|
|
74
|
+
/** The SVG viewBox, in projected metres. */
|
|
75
|
+
view: PlaneBox;
|
|
76
|
+
/** The projection the map draws with — `projectionAt(center)`. */
|
|
77
|
+
projection: Projection;
|
|
78
|
+
}
|
|
79
|
+
|
|
53
80
|
/** Every word the map prints or announces. */
|
|
54
81
|
export interface RegionMapCopy {
|
|
55
82
|
/** The drawing's accessible name (`role="img"`). */
|
|
@@ -95,6 +122,20 @@ export interface RegionMapProps {
|
|
|
95
122
|
center: { latitude: number; longitude: number } | null;
|
|
96
123
|
/** Circles around the centre; the view fits the largest. */
|
|
97
124
|
circles?: readonly RegionMapCircle[];
|
|
125
|
+
/**
|
|
126
|
+
* An extent the view must ALSO fit, before its margin — something the host
|
|
127
|
+
* draws past the largest circle. With circles the view stays centred on the
|
|
128
|
+
* centre and grows to the reach's farthest edge; without, it fits the areas
|
|
129
|
+
* and the reach together. Omit it and the view is the circles' (or areas').
|
|
130
|
+
*/
|
|
131
|
+
reach?: RegionMapReach;
|
|
132
|
+
/**
|
|
133
|
+
* A host layer drawn INSIDE the drawing's SVG, over the excluded hatch and
|
|
134
|
+
* under the circles, highlights and centre, so it shares the map's view,
|
|
135
|
+
* projection and frame. It is wrapped in `pointer-events: none`: taps go
|
|
136
|
+
* through it to the areas. Return SVG elements, in projected metres.
|
|
137
|
+
*/
|
|
138
|
+
overlay?: (layer: RegionMapLayer) => React.ReactNode;
|
|
98
139
|
/**
|
|
99
140
|
* A tap on an area, or a press of its list button. Omit it and the map is
|
|
100
141
|
* read-only: nothing is tappable and the list rows are not buttons.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extent,
|
|
3
|
+
planeBounds,
|
|
4
|
+
projectionAt,
|
|
5
|
+
projectRings,
|
|
6
|
+
viewBoxFor,
|
|
7
|
+
type PlaneBox,
|
|
8
|
+
type Projection,
|
|
9
|
+
} from './RegionMap.helpers';
|
|
10
|
+
import type {
|
|
11
|
+
RegionMapBounds,
|
|
12
|
+
RegionMapCircle,
|
|
13
|
+
RegionMapReach,
|
|
14
|
+
RegionMapRegion,
|
|
15
|
+
RegionMapRing,
|
|
16
|
+
} from './RegionMap.types';
|
|
17
|
+
|
|
18
|
+
// What `RegionMap` shows: its view, given the circles, the areas and a host's
|
|
19
|
+
// `reach` — pure, so a host laying HTML over the drawing computes the same one.
|
|
20
|
+
|
|
21
|
+
/** The smallest box holding every given box, or null with none. */
|
|
22
|
+
export function unionBox(boxes: readonly (PlaneBox | null)[]): PlaneBox | null {
|
|
23
|
+
const real = boxes.filter((box): box is PlaneBox => box !== null);
|
|
24
|
+
const xs = extent(real.flatMap((b) => [b.minX, b.minX + b.spanX]));
|
|
25
|
+
const ys = extent(real.flatMap((b) => [b.minY, b.minY + b.spanY]));
|
|
26
|
+
if (xs === null || ys === null) return null;
|
|
27
|
+
return { minX: xs.min, minY: ys.min, spanX: xs.max - xs.min, spanY: ys.max - ys.min };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The latitude/longitude box around some outlines — a `reach` no radius describes. */
|
|
31
|
+
export function boundsOfRings(rings: readonly RegionMapRing[]): RegionMapBounds | null {
|
|
32
|
+
const points = rings.flat();
|
|
33
|
+
const lats = extent(points.map(([latitude]) => latitude));
|
|
34
|
+
const lngs = extent(points.map(([, longitude]) => longitude));
|
|
35
|
+
if (lats === null || lngs === null) return null;
|
|
36
|
+
return { south: lats.min, west: lngs.min, north: lats.max, east: lngs.max };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A `reach` as a box in projected metres (a radius is a square around the centre). */
|
|
40
|
+
export function reachBox(reach: RegionMapReach, projection: Projection): PlaneBox | null {
|
|
41
|
+
if ('radiusMetres' in reach) {
|
|
42
|
+
const r = reach.radiusMetres;
|
|
43
|
+
return Number.isFinite(r) && r > 0 ? { minX: -r, minY: -r, spanX: r * 2, spanY: r * 2 } : null;
|
|
44
|
+
}
|
|
45
|
+
const { south, west, north, east } = reach.bounds;
|
|
46
|
+
const a = projection.project([north, west]);
|
|
47
|
+
const b = projection.project([south, east]);
|
|
48
|
+
return unionBox([{ minX: a.x, minY: a.y, spanX: 0, spanY: 0 }, { minX: b.x, minY: b.y, spanX: 0, spanY: 0 }]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** How far the box reaches from the centre along either axis — the half-side of a centred square holding it. */
|
|
52
|
+
function farthestEdge(box: PlaneBox): number {
|
|
53
|
+
return Math.max(Math.abs(box.minX), Math.abs(box.minX + box.spanX), Math.abs(box.minY), Math.abs(box.minY + box.spanY));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The view, given what it must fit: the outermost circle and the `reach` with
|
|
58
|
+
* the margin on top, still centred on the centre; else every area and the
|
|
59
|
+
* reach together. Without a reach it is exactly `viewBoxFor`'s.
|
|
60
|
+
*/
|
|
61
|
+
export function viewWithin(
|
|
62
|
+
outermostMetres: number | null,
|
|
63
|
+
reach: PlaneBox | null,
|
|
64
|
+
areaBoxes: () => readonly (PlaneBox | null)[],
|
|
65
|
+
): PlaneBox | null {
|
|
66
|
+
if (outermostMetres !== null) {
|
|
67
|
+
return viewBoxFor(Math.max(outermostMetres, reach === null ? 0 : farthestEdge(reach)), null);
|
|
68
|
+
}
|
|
69
|
+
return viewBoxFor(null, unionBox([...areaBoxes(), reach]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The middle of every area, for a map with no centre of its own. */
|
|
73
|
+
export function centreOf(regions: readonly RegionMapRegion[]): { latitude: number; longitude: number } | null {
|
|
74
|
+
const bounds = boundsOfRings(regions.flatMap((r) => r.rings));
|
|
75
|
+
if (bounds === null) return null;
|
|
76
|
+
return { latitude: (bounds.south + bounds.north) / 2, longitude: (bounds.west + bounds.east) / 2 };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The square `RegionMap` shows for these props, in projected metres around
|
|
81
|
+
* `center` (or the areas' middle) — for a host that lays its own HTML over the
|
|
82
|
+
* drawing at the same scale: a point `p` sits at
|
|
83
|
+
* `(p.x - view.minX) / view.spanX` across. With circles the view is centred,
|
|
84
|
+
* so its half-side is `view.spanX / 2`.
|
|
85
|
+
*/
|
|
86
|
+
export function regionMapView({
|
|
87
|
+
regions,
|
|
88
|
+
center,
|
|
89
|
+
circles = [],
|
|
90
|
+
reach,
|
|
91
|
+
}: {
|
|
92
|
+
regions: readonly RegionMapRegion[];
|
|
93
|
+
center: { latitude: number; longitude: number } | null;
|
|
94
|
+
circles?: readonly RegionMapCircle[];
|
|
95
|
+
reach?: RegionMapReach;
|
|
96
|
+
}): PlaneBox | null {
|
|
97
|
+
const origin = center ?? centreOf(regions);
|
|
98
|
+
if (origin === null) return null;
|
|
99
|
+
const projection = projectionAt(origin);
|
|
100
|
+
return viewWithin(
|
|
101
|
+
extent(circles.map((c) => c.radiusMetres))?.max ?? null,
|
|
102
|
+
reach === undefined ? null : reachBox(reach, projection),
|
|
103
|
+
() => regions.map((r) => planeBounds(projectRings(r.rings, projection))),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -8,9 +8,12 @@ import { rem, remPx } from '../../../tokens/relative';
|
|
|
8
8
|
import { labelPosition, type AreaGeometry, type DrawnRegion, type PlaneBox } from './RegionMap.helpers';
|
|
9
9
|
import type { RegionMapModel } from './RegionMap.hooks';
|
|
10
10
|
import { labelOrder, placeLabels } from './RegionMap.labels';
|
|
11
|
+
import type { RegionMapLayer } from './RegionMap.types';
|
|
11
12
|
|
|
12
13
|
export interface RegionMapDrawingProps {
|
|
13
14
|
model: RegionMapModel & { view: PlaneBox };
|
|
15
|
+
/** The host's layer, drawn inside the SVG under the circles (`RegionMapProps.overlay`). */
|
|
16
|
+
overlay?: (layer: RegionMapLayer) => React.ReactNode;
|
|
14
17
|
showCenter: boolean;
|
|
15
18
|
highlightedIds: readonly string[];
|
|
16
19
|
maxHeight: string | undefined;
|
|
@@ -32,6 +35,7 @@ export interface RegionMapDrawingProps {
|
|
|
32
35
|
*/
|
|
33
36
|
export function RegionMapDrawing({
|
|
34
37
|
model,
|
|
38
|
+
overlay,
|
|
35
39
|
showCenter,
|
|
36
40
|
highlightedIds,
|
|
37
41
|
maxHeight,
|
|
@@ -78,6 +82,13 @@ export function RegionMapDrawing({
|
|
|
78
82
|
<Hatch id={hatchId} pitch={view.spanX / 70} theme={theme} />
|
|
79
83
|
<BaseAreas areas={base} testId={testId} theme={theme} />
|
|
80
84
|
<ExcludedAreas drawn={drawn} hatchId={hatchId} testId={testId} theme={theme} />
|
|
85
|
+
{overlay && model.projection && (
|
|
86
|
+
// `pointer-events` is inherited in SVG: nothing the host draws here
|
|
87
|
+
// takes a tap from the areas beneath.
|
|
88
|
+
<g pointerEvents="none" data-testid={`${testId}-overlay`}>
|
|
89
|
+
{overlay({ view, projection: model.projection })}
|
|
90
|
+
</g>
|
|
91
|
+
)}
|
|
81
92
|
<Circles radii={model.radii} theme={theme} />
|
|
82
93
|
<Highlights areas={highlighted} testId={testId} theme={theme} />
|
|
83
94
|
{showCenter && (
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export { RegionMap } from './RegionMap';
|
|
2
2
|
export { decodePolyline, hitRegion, projectionAt } from './RegionMap.helpers';
|
|
3
|
-
export
|
|
3
|
+
export { boundsOfRings, regionMapView } from './RegionMap.view';
|
|
4
|
+
export type { AreaGeometry, PlaneBox, PlanePoint, PlaneRegion, Projection } from './RegionMap.helpers';
|
|
4
5
|
export type {
|
|
6
|
+
RegionMapBounds,
|
|
5
7
|
RegionMapCircle,
|
|
6
8
|
RegionMapCopy,
|
|
9
|
+
RegionMapLayer,
|
|
7
10
|
RegionMapListMode,
|
|
8
11
|
RegionMapListSort,
|
|
9
12
|
RegionMapPoint,
|
|
10
13
|
RegionMapProps,
|
|
14
|
+
RegionMapReach,
|
|
11
15
|
RegionMapRegion,
|
|
12
16
|
RegionMapRing,
|
|
13
17
|
} from './RegionMap.types';
|
|
@@ -6,12 +6,20 @@
|
|
|
6
6
|
- [x] `__tests__/region-map-helpers.test.ts` — the geometry: even-odd with a
|
|
7
7
|
hole, distance, which area a tap means (hole, smallest, sliver within the
|
|
8
8
|
tolerance even from inside a big neighbour, nothing beyond it), the view,
|
|
9
|
-
label placement, `extent` on 200k values, the projection and the decoder
|
|
9
|
+
label placement, `extent` on 200k values, the projection and the decoder;
|
|
10
|
+
the view with a reach (none is today's view, a radius past the circle
|
|
11
|
+
grows it centred, a smaller one never shrinks it, a box off to one side
|
|
12
|
+
by its farthest edge, areas and reach together with no circle, a
|
|
13
|
+
non-positive radius ignored) and `boundsOfRings`
|
|
10
14
|
- [x] `__tests__/region-map.test.tsx` — what is drawn (areas, hatch + name for
|
|
11
15
|
excluded only, several highlights, nothing with no centre and no areas),
|
|
12
16
|
what a tap reports, read-only, and the list (collapsed by default with its
|
|
13
17
|
count, `aria-expanded`, `aria-pressed`, state in words, outside areas off
|
|
14
|
-
the list and out of the count, `listed`, sort by name, detail, `none`)
|
|
18
|
+
the list and out of the count, `listed`, sort by name, detail, `none`),
|
|
19
|
+
and the reach and overlay (viewBox unchanged without a reach, widened as
|
|
20
|
+
`regionMapView` says with one, the overlay inside the SVG in a
|
|
21
|
+
`pointer-events: none` group with the map's view and projection, a tap
|
|
22
|
+
still reaching the area beneath, no group without an overlay)
|
|
15
23
|
|
|
16
24
|
## Storybook Tests Status
|
|
17
25
|
|
|
@@ -209,11 +209,26 @@ month/year buttons rather than squeezing them (`SelectorsFitA320Sheet`).
|
|
|
209
209
|
|
|
210
210
|
## Accessibility
|
|
211
211
|
|
|
212
|
-
- Container month: `role="grid"`,
|
|
212
|
+
- Container month: `role="grid"`, named by its month and year in `locale`
|
|
213
|
+
(`aria-label="outubro de 2026"`). It used to point `aria-labelledby` at an id
|
|
214
|
+
nothing carried, so the grid had no name (FUT-4144).
|
|
213
215
|
- Weekday headers: `role="columnheader"`, short names with `abbr` for SR if needed.
|
|
214
216
|
- Day cells: `role="gridcell"`, `aria-selected` true for selected day(s). For ranges, set `aria-selected="true"` on all in range or use `aria-current="date"` for today.
|
|
217
|
+
- Each visible day is named whole, in `locale`: `aria-label="1 de outubro de 2026"`
|
|
218
|
+
(`"October 1, 2026"` in en-US). The cell's text is the bare number, which in a
|
|
219
|
+
two-month range calendar names two different days (FUT-4144).
|
|
220
|
+
- The arrows of the default caption carry `labels.previousMonth`/`nextMonth`
|
|
221
|
+
when the host passes `labels`; without `labels` they stay as they were.
|
|
215
222
|
- Disabled days: `aria-disabled="true"`.
|
|
216
|
-
-
|
|
223
|
+
- With `showOutsideDays={false}` an outside day is an inert, `aria-hidden`
|
|
224
|
+
placeholder: no button, no name, no tab stop. Visible outside days are
|
|
225
|
+
unchanged.
|
|
226
|
+
- Keyboard support (roving focus within the grid). When a NAVIGATION key is
|
|
227
|
+
pressed on a day (the reader tabbed or clicked onto it), focus follows the tab
|
|
228
|
+
stop to the new day in its own month (never its muted copy in the month
|
|
229
|
+
beside it), across a month change too. Enter, Space, Tab and Escape move no
|
|
230
|
+
day and never move focus; with focus on the container it stays there, as
|
|
231
|
+
before:
|
|
217
232
|
- Arrow keys: move by 1 day (left/right) or 1 week (up/down).
|
|
218
233
|
- Home/End: move to start/end of week.
|
|
219
234
|
- PageUp/PageDown: previous/next month (with Shift for year).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SxProps, Theme } from '@mui/material/styles/index.js';
|
|
2
2
|
import Box from '@mui/material/Box/index.js';
|
|
3
3
|
import Paper from '@mui/material/Paper/index.js';
|
|
4
|
-
import React, { useCallback, useRef } from 'react';
|
|
4
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
5
5
|
|
|
6
6
|
import { EN_US_UI_LABELS } from '../../../en-US.labels';
|
|
7
7
|
import { rem, sxRem } from '../../../tokens/relative';
|
|
@@ -9,6 +9,7 @@ import { resolveLabels } from '../../../utils/resolve-labels';
|
|
|
9
9
|
|
|
10
10
|
import { resolveCalendarProps } from './Calendar.helpers';
|
|
11
11
|
import { useCalendar } from './Calendar.hooks';
|
|
12
|
+
import { navigateFocus } from './Calendar.keyboard';
|
|
12
13
|
import { useCalendarPicker } from './Calendar.picker';
|
|
13
14
|
import type { CalendarProps, ResolvedCalendarProps } from './Calendar.types';
|
|
14
15
|
import type { DayCellContext } from './CalendarDayCell';
|
|
@@ -32,6 +33,7 @@ type CalendarState = ReturnType<typeof useCalendar>;
|
|
|
32
33
|
|
|
33
34
|
const buildDayContext = (props: ResolvedCalendarProps, calendar: CalendarState): DayCellContext => ({
|
|
34
35
|
selectionMode: props.selectionMode,
|
|
36
|
+
locale: props.locale,
|
|
35
37
|
value: props.value,
|
|
36
38
|
range: props.range,
|
|
37
39
|
showOutsideDays: props.showOutsideDays,
|
|
@@ -60,6 +62,45 @@ const useRootRef = (forwarded: React.ForwardedRef<HTMLDivElement>) => {
|
|
|
60
62
|
return { rootRef, setRoot };
|
|
61
63
|
};
|
|
62
64
|
|
|
65
|
+
/** The tab stop as a day of its OWN month — side by side, an outside copy of it comes first in the DOM. */
|
|
66
|
+
const OWN_MONTH_STOP = '[role="gridcell"][tabindex="0"][data-in-month="true"]';
|
|
67
|
+
const ANY_STOP = '[role="gridcell"][tabindex="0"]';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Keeps the keyboard's place visible when it is on a DAY.
|
|
71
|
+
*
|
|
72
|
+
* The arrows move the grid's one tab stop (`focusedDate`), and that is enough
|
|
73
|
+
* while the container holds focus. But a reader who tabbed onto a day, or
|
|
74
|
+
* clicked one, has focus on that day's button: the tab stop moved and focus
|
|
75
|
+
* stayed behind on a cell that is no longer it, so the ring sat on the old day
|
|
76
|
+
* and a month change unmounted it under the reader.
|
|
77
|
+
*
|
|
78
|
+
* So a NAVIGATION key pressed on a day notes where the tab stop was, and once
|
|
79
|
+
* it has moved, focus follows it to the day in its own month. Any other key —
|
|
80
|
+
* Enter, Tab, Escape — clears the note: Enter re-sets the same day and must
|
|
81
|
+
* leave focus alone, and a stale note would pull focus into the grid on the
|
|
82
|
+
* next mouse pick.
|
|
83
|
+
*/
|
|
84
|
+
const useFollowDayFocus = (
|
|
85
|
+
rootRef: React.RefObject<HTMLDivElement | null>,
|
|
86
|
+
focusedDate: Date,
|
|
87
|
+
firstDayOfWeek: number,
|
|
88
|
+
) => {
|
|
89
|
+
const movedFrom = useRef<number | null>(null);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const from = movedFrom.current;
|
|
92
|
+
movedFrom.current = null;
|
|
93
|
+
if (from === null || from === focusedDate.getTime()) return;
|
|
94
|
+
const root = rootRef.current;
|
|
95
|
+
(root?.querySelector<HTMLElement>(OWN_MONTH_STOP) ?? root?.querySelector<HTMLElement>(ANY_STOP))?.focus();
|
|
96
|
+
}, [rootRef, focusedDate]);
|
|
97
|
+
return (event: React.KeyboardEvent) => {
|
|
98
|
+
const onDay = (event.target as HTMLElement).getAttribute('role') === 'gridcell';
|
|
99
|
+
const moves = navigateFocus(event.key, focusedDate, { firstDayOfWeek, shiftKey: event.shiftKey }) !== null;
|
|
100
|
+
movedFrom.current = onDay && moves ? focusedDate.getTime() : null;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
63
104
|
export const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>((componentProps, ref) => {
|
|
64
105
|
const props = resolveCalendarProps(componentProps);
|
|
65
106
|
const { monthsToShow, locale, renderHeader, renderFooter, className, ariaLabel } = props;
|
|
@@ -69,6 +110,8 @@ export const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>((compone
|
|
|
69
110
|
const bounds = { minDate: props.minDate, maxDate: props.maxDate };
|
|
70
111
|
const picker = useCalendarPicker({ rootRef, bounds, ...calendar });
|
|
71
112
|
const dayContext = buildDayContext(props, calendar);
|
|
113
|
+
const noteKeyTarget = useFollowDayFocus(rootRef, calendar.focusedDate, props.firstDayOfWeek);
|
|
114
|
+
const labels = resolveLabels(EN_US_UI_LABELS.calendar, props.labels);
|
|
72
115
|
// The selectors caption always resolves to one month (see the helpers).
|
|
73
116
|
const firstMonth = calendar.monthMatrices[0];
|
|
74
117
|
|
|
@@ -81,7 +124,14 @@ export const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>((compone
|
|
|
81
124
|
tabIndex={0}
|
|
82
125
|
// The day keys belong to the days: with a month or year grid open there
|
|
83
126
|
// is no focused day on screen to move.
|
|
84
|
-
onKeyDown={
|
|
127
|
+
onKeyDown={
|
|
128
|
+
picker.view === 'days'
|
|
129
|
+
? (event) => {
|
|
130
|
+
noteKeyTarget(event);
|
|
131
|
+
calendar.handleKeyDown(event);
|
|
132
|
+
}
|
|
133
|
+
: undefined
|
|
134
|
+
}
|
|
85
135
|
data-testid="calendar-container"
|
|
86
136
|
sx={containerSx(monthsToShow)}
|
|
87
137
|
>
|
|
@@ -93,7 +143,7 @@ export const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>((compone
|
|
|
93
143
|
weekdayNames={calendar.weekdayNames}
|
|
94
144
|
dayContext={dayContext}
|
|
95
145
|
bounds={bounds}
|
|
96
|
-
labels={
|
|
146
|
+
labels={labels}
|
|
97
147
|
picker={picker}
|
|
98
148
|
onPreviousMonth={() => {
|
|
99
149
|
picker.showDays();
|
|
@@ -114,6 +164,9 @@ export const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>((compone
|
|
|
114
164
|
locale={locale}
|
|
115
165
|
weekdayNames={calendar.weekdayNames}
|
|
116
166
|
renderHeader={renderHeader}
|
|
167
|
+
// Only a host that passed `labels` gets named arrows: without
|
|
168
|
+
// them the arrows caption stays exactly as it always was.
|
|
169
|
+
labels={props.labels ? labels : undefined}
|
|
117
170
|
onPreviousMonth={calendar.goToPreviousMonth}
|
|
118
171
|
onNextMonth={calendar.goToNextMonth}
|
|
119
172
|
dayContext={dayContext}
|
|
@@ -114,8 +114,11 @@ const dayCellSx = (day: DayRenderArgs, band: DayBand, visible: boolean): SxProps
|
|
|
114
114
|
color: colorFor(day, visible),
|
|
115
115
|
opacity: !day.inCurrentMonth && visible ? 0.5 : 1,
|
|
116
116
|
fontWeight: day.today || isEndpoint(day) ? 'bold' : 'normal',
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// `nth-child`, counting every cell of the week: a hidden outside day is an
|
|
118
|
+
// inert `span` placeholder (FUT-4144), and `nth-of-type` counted only the
|
|
119
|
+
// buttons, so the caps landed mid-week in a month with hidden outside days.
|
|
120
|
+
'&:nth-child(7n + 1)': { borderStartStartRadius: BAND_RADIUS, borderEndStartRadius: BAND_RADIUS },
|
|
121
|
+
'&:nth-child(7n)': { borderStartEndRadius: BAND_RADIUS, borderEndEndRadius: BAND_RADIUS },
|
|
119
122
|
'&::before': {
|
|
120
123
|
content: '""',
|
|
121
124
|
position: 'absolute',
|
|
@@ -140,8 +143,18 @@ const dayCellSx = (day: DayRenderArgs, band: DayBand, visible: boolean): SxProps
|
|
|
140
143
|
},
|
|
141
144
|
});
|
|
142
145
|
|
|
146
|
+
/**
|
|
147
|
+
* A day's spoken name, whole: "1 de outubro de 2026". The cell's text is the
|
|
148
|
+
* bare number, which a screen reader would read as "1" — in a two-month range
|
|
149
|
+
* calendar that is two different days.
|
|
150
|
+
*/
|
|
151
|
+
const dayName = (date: Date, locale: string): string =>
|
|
152
|
+
date.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' });
|
|
153
|
+
|
|
143
154
|
export interface CalendarDayProps {
|
|
144
155
|
day: DayRenderArgs;
|
|
156
|
+
/** BCP-47 tag the day's accessible name is written in. Default: en-US. */
|
|
157
|
+
locale?: string;
|
|
145
158
|
band: DayBand;
|
|
146
159
|
focused: boolean;
|
|
147
160
|
showOutsideDays: boolean;
|
|
@@ -153,6 +166,7 @@ export interface CalendarDayProps {
|
|
|
153
166
|
|
|
154
167
|
export const CalendarDay: FC<CalendarDayProps> = ({
|
|
155
168
|
day,
|
|
169
|
+
locale = 'en-US',
|
|
156
170
|
band,
|
|
157
171
|
focused,
|
|
158
172
|
showOutsideDays,
|
|
@@ -164,6 +178,20 @@ export const CalendarDay: FC<CalendarDayProps> = ({
|
|
|
164
178
|
const visible = day.inCurrentMonth || showOutsideDays;
|
|
165
179
|
const state: DayState = { ...day, outside: !day.inCurrentMonth, focused };
|
|
166
180
|
|
|
181
|
+
// A day hidden by `showOutsideDays={false}` keeps its place in the grid and
|
|
182
|
+
// nothing else: no button to click in a blank, no name, no tab stop that
|
|
183
|
+
// cannot be seen (FUT-4144).
|
|
184
|
+
if (!visible) {
|
|
185
|
+
return (
|
|
186
|
+
<Box
|
|
187
|
+
component="span"
|
|
188
|
+
aria-hidden="true"
|
|
189
|
+
data-testid="calendar-outside-placeholder"
|
|
190
|
+
sx={{ width: COLUMN_SIZE, maxWidth: '100%', aspectRatio: CELL_RATIO, justifySelf: 'stretch' }}
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
167
195
|
return (
|
|
168
196
|
<Box
|
|
169
197
|
component="button"
|
|
@@ -172,6 +200,8 @@ export const CalendarDay: FC<CalendarDayProps> = ({
|
|
|
172
200
|
aria-selected={day.selected}
|
|
173
201
|
aria-disabled={day.disabled}
|
|
174
202
|
aria-current={day.today ? 'date' : undefined}
|
|
203
|
+
aria-label={dayName(day.date, locale)}
|
|
204
|
+
data-in-month={day.inCurrentMonth}
|
|
175
205
|
className={dayClassNames(state, dayClassName?.(day))}
|
|
176
206
|
data-testid={`calendar-date-${day.date.getDate()}`}
|
|
177
207
|
onClick={() => onSelect(day.date)}
|
|
@@ -185,7 +215,7 @@ export const CalendarDay: FC<CalendarDayProps> = ({
|
|
|
185
215
|
{day.today && (
|
|
186
216
|
<Box component="span" data-testid="calendar-today" sx={{ display: 'none' }} />
|
|
187
217
|
)}
|
|
188
|
-
{
|
|
218
|
+
{day.date.getDate()}
|
|
189
219
|
</Box>
|
|
190
220
|
);
|
|
191
221
|
};
|