@12-apps/ui 6.89.2 → 6.90.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.
Files changed (274) hide show
  1. package/components-catalog.md +1 -1
  2. package/dist/_internal/components/data-display/Alert/Alert.js +3 -3
  3. package/dist/_internal/components/data-display/Avatar/Avatar.js +4 -4
  4. package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
  5. package/dist/_internal/components/data-display/CardKit/card-actions-context.js +4 -4
  6. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +5 -5
  7. package/dist/_internal/components/data-display/Carousel/Carousel.chrome.js +4 -3
  8. package/dist/_internal/components/data-display/Carousel/Carousel.js +6 -5
  9. package/dist/_internal/components/data-display/Carousel/Carousel.parts.js +3 -2
  10. package/dist/_internal/components/data-display/Chart/Chart.js +3 -3
  11. package/dist/_internal/components/data-display/Chart/SpecChart.js +6 -6
  12. package/dist/_internal/components/data-display/Chart/chart-renderers.js +2 -2
  13. package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
  14. package/dist/_internal/components/data-display/DataGrid/DataGrid.js +2 -2
  15. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +48 -23
  16. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +53 -28
  17. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +1 -1
  18. package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +1 -1
  19. package/dist/_internal/components/data-display/DataViews/base-list-card.js +3 -3
  20. package/dist/_internal/components/data-display/DataViews/data-views-bar-controls.js +5 -3
  21. package/dist/_internal/components/data-display/DataViews/data-views-day-input.js +1 -1
  22. package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +31 -6
  23. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +3 -3
  24. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +47 -22
  25. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +37 -12
  26. package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +32 -7
  27. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +33 -8
  28. package/dist/_internal/components/data-display/DataViews/data-views-number-input.js +1 -1
  29. package/dist/_internal/components/data-display/DataViews/data-views-preset-chips.js +10 -3
  30. package/dist/_internal/components/data-display/DataViews/data-views-range-bounds.js +47 -0
  31. package/dist/_internal/components/data-display/DataViews/data-views-range-bounds.js.map +1 -0
  32. package/dist/_internal/components/data-display/DataViews/data-views-range-calendar.js +36 -0
  33. package/dist/_internal/components/data-display/DataViews/data-views-range-calendar.js.map +1 -0
  34. package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +32 -6
  35. package/dist/_internal/components/data-display/DataViews/data-views-range-summary.js +12 -0
  36. package/dist/_internal/components/data-display/DataViews/data-views-range-summary.js.map +1 -0
  37. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +3 -3
  38. package/dist/_internal/components/data-display/DataViews/data-views-still-anchor.js +9 -0
  39. package/dist/_internal/components/data-display/DataViews/data-views-still-anchor.js.map +1 -0
  40. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +4 -4
  41. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +2 -2
  42. package/dist/_internal/components/data-display/FleetMap/FleetBody.js +9 -8
  43. package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +8 -7
  44. package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js +2 -1
  45. package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js.map +1 -1
  46. package/dist/_internal/components/data-display/FleetMap/FleetMap.js +9 -9
  47. package/dist/_internal/components/data-display/MapPreview/MapControlBar.js +4 -3
  48. package/dist/_internal/components/data-display/MapPreview/MapLayers.js +5 -4
  49. package/dist/_internal/components/data-display/MapPreview/MapMarkers.js +4 -3
  50. package/dist/_internal/components/data-display/MapPreview/MapPreview.helpers.js +3 -2
  51. package/dist/_internal/components/data-display/MapPreview/MapPreview.js +7 -6
  52. package/dist/_internal/components/data-display/index.js +35 -35
  53. package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +4 -4
  54. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.js +6 -6
  55. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.overview.js +5 -5
  56. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.parts.js +4 -4
  57. package/dist/_internal/components/feedback/index.js +7 -7
  58. package/dist/_internal/components/form/Calendar/Calendar.js +5 -5
  59. package/dist/_internal/components/form/Calendar/CalendarDay.js +1 -1
  60. package/dist/_internal/components/form/Calendar/CalendarDayCell.js +2 -2
  61. package/dist/_internal/components/form/Calendar/CalendarMonth.js +3 -3
  62. package/dist/_internal/components/form/Calendar/CalendarSelectorsMonth.js +4 -4
  63. package/dist/_internal/components/form/DateRangePicker/DateRangePicker.hooks.js +3 -3
  64. package/dist/_internal/components/form/DateRangePicker/DateRangePicker.js +10 -10
  65. package/dist/_internal/components/form/DateRangePicker/DateRangePicker.range.js +2 -2
  66. package/dist/_internal/components/form/DateRangePicker/DateRangePickerFields.js +3 -3
  67. package/dist/_internal/components/form/index.js +24 -24
  68. package/dist/_internal/components/index.js +63 -63
  69. package/dist/_internal/en-US.data-display.js +2 -1
  70. package/dist/_internal/pt-BR.data-display.js +2 -1
  71. package/dist/_internal/user-avatar/user-avatar.js +5 -5
  72. package/dist/charts.js +11 -11
  73. package/dist/{chunk-FJ3UZ4RC.js → chunk-2FKWKLSC.js} +5 -3
  74. package/dist/chunk-2FKWKLSC.js.map +1 -0
  75. package/dist/{chunk-BO6MT2S2.js → chunk-3HZKUN5J.js} +7 -6
  76. package/dist/chunk-3HZKUN5J.js.map +1 -0
  77. package/dist/{chunk-TYXFFNPM.js → chunk-3OTGREQG.js} +10 -10
  78. package/dist/{chunk-BEHT4IHH.js → chunk-3X2MA47R.js} +2 -2
  79. package/dist/{chunk-XHIBFBNP.js → chunk-3ZRK3BRN.js} +4 -2
  80. package/dist/chunk-3ZRK3BRN.js.map +1 -0
  81. package/dist/{chunk-X5OGIT2F.js → chunk-4L54KUZV.js} +22 -4
  82. package/dist/chunk-4L54KUZV.js.map +1 -0
  83. package/dist/{chunk-TRYR424I.js → chunk-6LOWZCH7.js} +15 -1
  84. package/dist/chunk-6LOWZCH7.js.map +1 -0
  85. package/dist/{chunk-6WHIJPEF.js → chunk-6PU5P7QT.js} +2 -2
  86. package/dist/chunk-7CUZTWKW.js +52 -0
  87. package/dist/chunk-7CUZTWKW.js.map +1 -0
  88. package/dist/{chunk-JIEVKHOH.js → chunk-7YJYIMWP.js} +7 -7
  89. package/dist/{chunk-YNZUYZ2O.js → chunk-A3DDEKLV.js} +2 -2
  90. package/dist/{chunk-FEKWKWGK.js → chunk-ARGVUTTL.js} +15 -1
  91. package/dist/chunk-ARGVUTTL.js.map +1 -0
  92. package/dist/{chunk-DYHOBKPU.js → chunk-BLZDSCIZ.js} +6 -6
  93. package/dist/{chunk-E2UOEI7N.js → chunk-BV2LOTMC.js} +2 -2
  94. package/dist/{chunk-O63ZROSY.js → chunk-BZBLPXV7.js} +4 -4
  95. package/dist/{chunk-OKRITX4S.js → chunk-CJ6XXM4X.js} +4 -4
  96. package/dist/{chunk-LV6AD6XC.js → chunk-DBI6AMNY.js} +4 -4
  97. package/dist/{chunk-QRRWLLXW.js → chunk-DSBC6O46.js} +2 -2
  98. package/dist/{chunk-3YFDGFEK.js → chunk-DVX4HDNP.js} +2 -2
  99. package/dist/{chunk-CJUK32UH.js → chunk-EEO7M5GU.js} +2 -2
  100. package/dist/{chunk-7XSCEHGA.js → chunk-EOQ6EKTS.js} +7 -7
  101. package/dist/{chunk-5RSK5M6M.js → chunk-FI2CQOGC.js} +63 -126
  102. package/dist/chunk-FI2CQOGC.js.map +1 -0
  103. package/dist/{chunk-Q2ERGAQO.js → chunk-FYAW5GBF.js} +33 -6
  104. package/dist/chunk-FYAW5GBF.js.map +1 -0
  105. package/dist/{chunk-AMLHEKSV.js → chunk-HI7JPDD6.js} +5 -5
  106. package/dist/chunk-J2AVWIMM.js +60 -0
  107. package/dist/chunk-J2AVWIMM.js.map +1 -0
  108. package/dist/{chunk-7QKIL5YF.js → chunk-J2GDA64N.js} +4 -4
  109. package/dist/{chunk-BAQ3ITZV.js → chunk-JFD742C3.js} +2 -2
  110. package/dist/{chunk-OBA75Y5O.js → chunk-JHWEL4NX.js} +61 -33
  111. package/dist/chunk-JHWEL4NX.js.map +1 -0
  112. package/dist/{chunk-HQCZT6TO.js → chunk-JVOEVTCZ.js} +5 -5
  113. package/dist/{chunk-WAO7UQRS.js → chunk-KANCE4MS.js} +3 -3
  114. package/dist/{chunk-XLU5WFSY.js → chunk-KRBPWTXW.js} +2 -2
  115. package/dist/{chunk-S6D7NOV7.js → chunk-L6JAALMX.js} +3 -3
  116. package/dist/{chunk-72HOUWAW.js → chunk-L6YTVXGN.js} +5 -3
  117. package/dist/{chunk-72HOUWAW.js.map → chunk-L6YTVXGN.js.map} +1 -1
  118. package/dist/chunk-LZNUBQ5I.js +181 -0
  119. package/dist/chunk-LZNUBQ5I.js.map +1 -0
  120. package/dist/{chunk-BECKKDYG.js → chunk-M2M4IDFU.js} +2 -2
  121. package/dist/{chunk-CSGBHYW6.js → chunk-M5Y42AZ4.js} +2 -2
  122. package/dist/chunk-MICX23P4.js +122 -0
  123. package/dist/chunk-MICX23P4.js.map +1 -0
  124. package/dist/{chunk-5CP5Z3YU.js → chunk-MPWA55CF.js} +4 -4
  125. package/dist/{chunk-5KK3I42O.js → chunk-NK6O4NE4.js} +13 -10
  126. package/dist/chunk-NK6O4NE4.js.map +1 -0
  127. package/dist/{chunk-RCTHCXS6.js → chunk-PJNIJIE6.js} +2 -2
  128. package/dist/{chunk-6ATXOJVQ.js → chunk-QADTI63U.js} +4 -4
  129. package/dist/{chunk-MW7P2C2C.js → chunk-QAEO52Z4.js} +2 -2
  130. package/dist/{chunk-5S5K73I6.js → chunk-QSOCOXVL.js} +3 -3
  131. package/dist/{chunk-NQCTANF3.js → chunk-QVDEGBME.js} +7 -7
  132. package/dist/{chunk-BQBEN44R.js → chunk-RDCIST7P.js} +2 -2
  133. package/dist/{chunk-HUXAIQC5.js → chunk-RZ3P4X2I.js} +2 -2
  134. package/dist/{chunk-WJMSND5Q.js → chunk-SCXVTNLE.js} +2 -2
  135. package/dist/{chunk-K6FCSB6T.js → chunk-SK2CTB3O.js} +6 -6
  136. package/dist/{chunk-QLJU7UUI.js → chunk-T7OIVPCO.js} +38 -3
  137. package/dist/chunk-T7OIVPCO.js.map +1 -0
  138. package/dist/{chunk-U2ILXZDG.js → chunk-TB5CRHTF.js} +2 -2
  139. package/dist/{chunk-SB2SEGR6.js → chunk-TIM4PXWN.js} +3 -3
  140. package/dist/{chunk-YDIC6LSP.js → chunk-VCQFDQM3.js} +27 -7
  141. package/dist/chunk-VCQFDQM3.js.map +1 -0
  142. package/dist/chunk-VIMMEADQ.js +164 -0
  143. package/dist/chunk-VIMMEADQ.js.map +1 -0
  144. package/dist/{chunk-6D5AMANO.js → chunk-WH3LUOCQ.js} +2 -2
  145. package/dist/{chunk-OWRQOYUE.js → chunk-WWR6APJR.js} +2 -2
  146. package/dist/{chunk-5BVAUUWL.js → chunk-X37NTEVS.js} +2 -2
  147. package/dist/{chunk-WW3F7DCX.js → chunk-Z2WZDVGG.js} +2 -2
  148. package/dist/{chunk-EXVCVXFX.js → chunk-ZH6NYB6Y.js} +3 -3
  149. package/dist/data-display/Alert.js +3 -3
  150. package/dist/data-display/Avatar.js +4 -4
  151. package/dist/data-display/CardKit.js +11 -11
  152. package/dist/data-display/Carousel.js +6 -5
  153. package/dist/data-display/Chart.js +11 -11
  154. package/dist/data-display/DataGrid.js +2 -2
  155. package/dist/data-display/DataViews.js +60 -35
  156. package/dist/data-display/FleetMap.js +9 -9
  157. package/dist/data-display/MapPreview.js +7 -6
  158. package/dist/en-US.js +1 -1
  159. package/dist/feedback/NotificationStatusNotice.js +4 -4
  160. package/dist/feedback/SequenceResolver.js +6 -6
  161. package/dist/form/Calendar.js +5 -5
  162. package/dist/form/DateRangePicker.js +10 -10
  163. package/dist/locales.js +5 -5
  164. package/dist/pt-BR.js +1 -1
  165. package/dist/types/components/data-display/DataViews/data-views-bar-controls.d.ts +15 -1
  166. package/dist/types/components/data-display/DataViews/data-views-bar-controls.d.ts.map +1 -1
  167. package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +35 -0
  168. package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -1
  169. package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts +7 -1
  170. package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts.map +1 -1
  171. package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
  172. package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts +5 -3
  173. package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts.map +1 -1
  174. package/dist/types/components/data-display/DataViews/data-views-number-input.d.ts +10 -2
  175. package/dist/types/components/data-display/DataViews/data-views-number-input.d.ts.map +1 -1
  176. package/dist/types/components/data-display/DataViews/data-views-preset-chips.d.ts +10 -0
  177. package/dist/types/components/data-display/DataViews/data-views-preset-chips.d.ts.map +1 -1
  178. package/dist/types/components/data-display/DataViews/data-views-range-bounds.d.ts +35 -0
  179. package/dist/types/components/data-display/DataViews/data-views-range-bounds.d.ts.map +1 -0
  180. package/dist/types/components/data-display/DataViews/data-views-range-calendar.d.ts +35 -0
  181. package/dist/types/components/data-display/DataViews/data-views-range-calendar.d.ts.map +1 -0
  182. package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts +2 -11
  183. package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts.map +1 -1
  184. package/dist/types/components/data-display/DataViews/data-views-range-summary.d.ts +21 -0
  185. package/dist/types/components/data-display/DataViews/data-views-range-summary.d.ts.map +1 -0
  186. package/dist/types/components/data-display/DataViews/data-views-still-anchor.d.ts +45 -0
  187. package/dist/types/components/data-display/DataViews/data-views-still-anchor.d.ts.map +1 -0
  188. package/dist/types/components/data-display/LazyImage/LazyImage.hooks.d.ts +1 -1
  189. package/dist/types/components/form/Calendar/Calendar.d.ts.map +1 -1
  190. package/dist/types/components/form/Calendar/CalendarDay.d.ts +2 -0
  191. package/dist/types/components/form/Calendar/CalendarDay.d.ts.map +1 -1
  192. package/dist/types/components/form/Calendar/CalendarDayCell.d.ts +2 -0
  193. package/dist/types/components/form/Calendar/CalendarDayCell.d.ts.map +1 -1
  194. package/dist/types/components/form/Calendar/CalendarMonth.d.ts +5 -0
  195. package/dist/types/components/form/Calendar/CalendarMonth.d.ts.map +1 -1
  196. package/dist/types/en-US.data-display.d.ts.map +1 -1
  197. package/dist/types/pt-BR.data-display.d.ts.map +1 -1
  198. package/dist/user-avatar.js +5 -5
  199. package/package.json +1 -1
  200. package/src/components/data-display/DataViews/RESPONSIVE-BEHAVIOUR.md +59 -0
  201. package/src/components/data-display/DataViews/data-views-bar-controls.tsx +56 -1
  202. package/src/components/data-display/DataViews/data-views-copy.ts +36 -0
  203. package/src/components/data-display/DataViews/data-views-day-input.tsx +9 -1
  204. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +5 -3
  205. package/src/components/data-display/DataViews/data-views-more-fields.tsx +1 -0
  206. package/src/components/data-display/DataViews/data-views-more-filters.tsx +82 -29
  207. package/src/components/data-display/DataViews/data-views-number-input.tsx +19 -9
  208. package/src/components/data-display/DataViews/data-views-preset-chips.tsx +165 -16
  209. package/src/components/data-display/DataViews/data-views-range-bounds.tsx +223 -0
  210. package/src/components/data-display/DataViews/data-views-range-calendar.tsx +185 -0
  211. package/src/components/data-display/DataViews/data-views-range-pill.tsx +71 -154
  212. package/src/components/data-display/DataViews/data-views-range-summary.tsx +66 -0
  213. package/src/components/data-display/DataViews/data-views-still-anchor.ts +93 -0
  214. package/src/components/form/Calendar/Calendar.md +17 -2
  215. package/src/components/form/Calendar/Calendar.tsx +56 -3
  216. package/src/components/form/Calendar/CalendarDay.tsx +33 -3
  217. package/src/components/form/Calendar/CalendarDayCell.tsx +4 -0
  218. package/src/components/form/Calendar/CalendarMonth.tsx +27 -5
  219. package/src/en-US.data-display.ts +12 -0
  220. package/src/pt-BR.data-display.ts +12 -0
  221. package/dist/chunk-5KK3I42O.js.map +0 -1
  222. package/dist/chunk-5RSK5M6M.js.map +0 -1
  223. package/dist/chunk-7D7CJRL7.js +0 -78
  224. package/dist/chunk-7D7CJRL7.js.map +0 -1
  225. package/dist/chunk-BO6MT2S2.js.map +0 -1
  226. package/dist/chunk-FEKWKWGK.js.map +0 -1
  227. package/dist/chunk-FJ3UZ4RC.js.map +0 -1
  228. package/dist/chunk-OBA75Y5O.js.map +0 -1
  229. package/dist/chunk-Q2ERGAQO.js.map +0 -1
  230. package/dist/chunk-QLJU7UUI.js.map +0 -1
  231. package/dist/chunk-TRYR424I.js.map +0 -1
  232. package/dist/chunk-X5OGIT2F.js.map +0 -1
  233. package/dist/chunk-XHIBFBNP.js.map +0 -1
  234. package/dist/chunk-YDIC6LSP.js.map +0 -1
  235. /package/dist/{chunk-TYXFFNPM.js.map → chunk-3OTGREQG.js.map} +0 -0
  236. /package/dist/{chunk-BEHT4IHH.js.map → chunk-3X2MA47R.js.map} +0 -0
  237. /package/dist/{chunk-6WHIJPEF.js.map → chunk-6PU5P7QT.js.map} +0 -0
  238. /package/dist/{chunk-JIEVKHOH.js.map → chunk-7YJYIMWP.js.map} +0 -0
  239. /package/dist/{chunk-YNZUYZ2O.js.map → chunk-A3DDEKLV.js.map} +0 -0
  240. /package/dist/{chunk-DYHOBKPU.js.map → chunk-BLZDSCIZ.js.map} +0 -0
  241. /package/dist/{chunk-E2UOEI7N.js.map → chunk-BV2LOTMC.js.map} +0 -0
  242. /package/dist/{chunk-O63ZROSY.js.map → chunk-BZBLPXV7.js.map} +0 -0
  243. /package/dist/{chunk-OKRITX4S.js.map → chunk-CJ6XXM4X.js.map} +0 -0
  244. /package/dist/{chunk-LV6AD6XC.js.map → chunk-DBI6AMNY.js.map} +0 -0
  245. /package/dist/{chunk-QRRWLLXW.js.map → chunk-DSBC6O46.js.map} +0 -0
  246. /package/dist/{chunk-3YFDGFEK.js.map → chunk-DVX4HDNP.js.map} +0 -0
  247. /package/dist/{chunk-CJUK32UH.js.map → chunk-EEO7M5GU.js.map} +0 -0
  248. /package/dist/{chunk-7XSCEHGA.js.map → chunk-EOQ6EKTS.js.map} +0 -0
  249. /package/dist/{chunk-AMLHEKSV.js.map → chunk-HI7JPDD6.js.map} +0 -0
  250. /package/dist/{chunk-7QKIL5YF.js.map → chunk-J2GDA64N.js.map} +0 -0
  251. /package/dist/{chunk-BAQ3ITZV.js.map → chunk-JFD742C3.js.map} +0 -0
  252. /package/dist/{chunk-HQCZT6TO.js.map → chunk-JVOEVTCZ.js.map} +0 -0
  253. /package/dist/{chunk-WAO7UQRS.js.map → chunk-KANCE4MS.js.map} +0 -0
  254. /package/dist/{chunk-XLU5WFSY.js.map → chunk-KRBPWTXW.js.map} +0 -0
  255. /package/dist/{chunk-S6D7NOV7.js.map → chunk-L6JAALMX.js.map} +0 -0
  256. /package/dist/{chunk-BECKKDYG.js.map → chunk-M2M4IDFU.js.map} +0 -0
  257. /package/dist/{chunk-CSGBHYW6.js.map → chunk-M5Y42AZ4.js.map} +0 -0
  258. /package/dist/{chunk-5CP5Z3YU.js.map → chunk-MPWA55CF.js.map} +0 -0
  259. /package/dist/{chunk-RCTHCXS6.js.map → chunk-PJNIJIE6.js.map} +0 -0
  260. /package/dist/{chunk-6ATXOJVQ.js.map → chunk-QADTI63U.js.map} +0 -0
  261. /package/dist/{chunk-MW7P2C2C.js.map → chunk-QAEO52Z4.js.map} +0 -0
  262. /package/dist/{chunk-5S5K73I6.js.map → chunk-QSOCOXVL.js.map} +0 -0
  263. /package/dist/{chunk-NQCTANF3.js.map → chunk-QVDEGBME.js.map} +0 -0
  264. /package/dist/{chunk-BQBEN44R.js.map → chunk-RDCIST7P.js.map} +0 -0
  265. /package/dist/{chunk-HUXAIQC5.js.map → chunk-RZ3P4X2I.js.map} +0 -0
  266. /package/dist/{chunk-WJMSND5Q.js.map → chunk-SCXVTNLE.js.map} +0 -0
  267. /package/dist/{chunk-K6FCSB6T.js.map → chunk-SK2CTB3O.js.map} +0 -0
  268. /package/dist/{chunk-U2ILXZDG.js.map → chunk-TB5CRHTF.js.map} +0 -0
  269. /package/dist/{chunk-SB2SEGR6.js.map → chunk-TIM4PXWN.js.map} +0 -0
  270. /package/dist/{chunk-6D5AMANO.js.map → chunk-WH3LUOCQ.js.map} +0 -0
  271. /package/dist/{chunk-OWRQOYUE.js.map → chunk-WWR6APJR.js.map} +0 -0
  272. /package/dist/{chunk-5BVAUUWL.js.map → chunk-X37NTEVS.js.map} +0 -0
  273. /package/dist/{chunk-WW3F7DCX.js.map → chunk-Z2WZDVGG.js.map} +0 -0
  274. /package/dist/{chunk-EXVCVXFX.js.map → chunk-ZH6NYB6Y.js.map} +0 -0
@@ -185,6 +185,7 @@ function OverflowRange<T extends Record<string, unknown>>({
185
185
  value={value}
186
186
  onChange={onChange}
187
187
  testId={`${testIdPrefix}-more-${field.id}`}
188
+ fillRow
188
189
  />
189
190
  );
190
191
  }
@@ -8,8 +8,9 @@ import KeyboardArrowDownRoundedIcon from "@mui/icons-material/KeyboardArrowDownR
8
8
  // which. On a phone, where both are icon-only, they were indistinguishable.
9
9
  import FilterAltRoundedIcon from "@mui/icons-material/FilterAltRounded";
10
10
  import Popover from "@mui/material/Popover/index.js";
11
- import type { Theme } from "@mui/material/styles/index.js";
12
- import { useState } from "react";
11
+ import { useTheme, type Theme } from "@mui/material/styles/index.js";
12
+ import useMediaQuery from "@mui/material/useMediaQuery/index.js";
13
+
13
14
 
14
15
  import { useDataViewsCopy } from "./data-views-copy-context";
15
16
  import { Button } from "../../form/Button";
@@ -17,8 +18,9 @@ import { Box } from "../../../mui/Box";
17
18
  import { Text } from "../../typography/Text";
18
19
 
19
20
  import { fieldClearing, MoreGroup, MoreQuick, type MoreFieldProps } from "./data-views-more-fields";
21
+ import { useStillAnchor } from "./data-views-still-anchor";
20
22
  import type { OverflowField } from "./data-views-overflow";
21
- import { rem, sxRem } from "../../../tokens/relative";
23
+ import { rem, remPx, sxRem } from "../../../tokens/relative";
22
24
 
23
25
  /**
24
26
  * "MAIS" — the filter overflow: its trigger, its panel and its footer. What
@@ -151,18 +153,32 @@ function MoreHeading(): React.JSX.Element {
151
153
  function MoreFooter({
152
154
  onClearAll,
153
155
  testIdPrefix,
156
+ reserved = false,
154
157
  }: {
155
158
  onClearAll: () => void;
156
159
  testIdPrefix: string;
160
+ /**
161
+ * Hold the footer's height with nothing in it. A panel holding a day range
162
+ * keeps the row while nothing is applied, so the first tap on its calendar
163
+ * does not grow the panel by a row and move its top under the finger
164
+ * (FUT-4144 UI review).
165
+ */
166
+ reserved?: boolean;
157
167
  }): React.JSX.Element {
158
168
  const copy = useDataViewsCopy();
159
169
  return (
160
- <Box sx={{ px: 1.5, py: 1, borderTop: 1, borderColor: "divider" }}>
170
+ <Box
171
+ aria-hidden={reserved || undefined}
172
+ // Held, the row is blank — no rule over it either, or it reads as an
173
+ // empty bordered row.
174
+ sx={{ px: 1.5, py: 1, borderTop: 1, borderColor: reserved ? "transparent" : "divider", visibility: reserved ? "hidden" : undefined }}
175
+ >
161
176
  <Box
162
177
  component="button"
163
178
  type="button"
164
- onClick={onClearAll}
165
- data-testid={`${testIdPrefix}-more-clear-all`}
179
+ onClick={reserved ? undefined : onClearAll}
180
+ tabIndex={reserved ? -1 : undefined}
181
+ data-testid={reserved ? undefined : `${testIdPrefix}-more-clear-all`}
166
182
  sx={{
167
183
  width: "100%",
168
184
  border: 0,
@@ -200,16 +216,39 @@ function MoreFields<T extends Record<string, unknown>>({
200
216
  );
201
217
  }
202
218
 
203
- /** The overflow trigger + panel. Renders nothing when everything fits. */
204
- export function MoreFilters<T extends Record<string, unknown>>({
205
- fields,
206
- onOpenChange,
207
- onClearAll,
208
- anyApplied,
209
- compact = false,
210
- clearKey,
211
- ...rest
212
- }: MoreFiltersProps<T> & {
219
+ /**
220
+ * The panel is a column: heading, the fields' scroller, footer. Its height is
221
+ * the screen's, not a fixed 320px — at 390×844 that cap scrolled a day range's
222
+ * month caption and weekday row out of view after the first tap, and a typed
223
+ * day's refusal out of sight of its box (FUT-4144 UI review). MUI moves the
224
+ * panel up when it is taller than the room under its trigger.
225
+ */
226
+ const COLUMN_SX = { display: "flex", flexDirection: "column" } as const;
227
+
228
+ /**
229
+ * 320, not 300: inside 12px of padding a day range's month then has the 280px
230
+ * its seven 40px columns need and its month/year caption one row (FUT-4144).
231
+ */
232
+ const PAPER_SX = (theme: Theme) => ({
233
+ ...COLUMN_SX,
234
+ width: rem(theme, 320),
235
+ maxWidth: `calc(100vw - ${rem(theme, 32)})`,
236
+ });
237
+
238
+ /**
239
+ * Below `sm` the panel takes the screen's width less 8px each side, so a day
240
+ * range's calendar gets the 280px its seven 40px columns need (it had 264 at
241
+ * 320 and 276 at 390, so every day was a 34-36px target).
242
+ */
243
+ const PHONE_PAPER_SX = (theme: Theme) => ({
244
+ ...COLUMN_SX,
245
+ width: `calc(100vw - ${rem(theme, 16)})`,
246
+ maxWidth: `calc(100vw - ${rem(theme, 16)})`,
247
+ maxHeight: `calc(100% - ${rem(theme, 16)})`,
248
+ });
249
+
250
+ /** Props of {@link MoreFilters}. */
251
+ interface MoreFiltersPanelProps<T extends Record<string, unknown>> extends MoreFiltersProps<T> {
213
252
  /** Changes on every clear-all; the fields here are keyed on it (FUT-4144). */
214
253
  clearKey?: number;
215
254
  /** Icon only — the same rung that strips Exibir/Exportar. */
@@ -227,9 +266,23 @@ export function MoreFilters<T extends Record<string, unknown>>({
227
266
  * got as far as "1" and then had nowhere to go.
228
267
  */
229
268
  onOpenChange?: (open: boolean) => void;
230
- }): React.JSX.Element | null {
231
- const [anchor, setAnchor] = useState<HTMLElement | null>(null);
269
+ }
270
+
271
+ /** The overflow trigger + panel. Renders nothing when everything fits. */
272
+ export function MoreFilters<T extends Record<string, unknown>>({
273
+ fields,
274
+ onOpenChange,
275
+ onClearAll,
276
+ anyApplied,
277
+ compact = false,
278
+ clearKey,
279
+ ...rest
280
+ }: MoreFiltersPanelProps<T>): React.JSX.Element | null {
281
+ const still = useStillAnchor();
282
+ const theme = useTheme();
283
+ const phone = useMediaQuery(theme.breakpoints.down("sm"));
232
284
  if (fields.length === 0) return null;
285
+ const holdsDayRange = fields.some((field) => field.range?.kind === "day");
233
286
  return (
234
287
  <>
235
288
  <MoreTrigger
@@ -248,32 +301,32 @@ export function MoreFilters<T extends Record<string, unknown>>({
248
301
  }
249
302
  compact={compact}
250
303
  onOpen={(element) => {
251
- setAnchor(element);
304
+ still.open(element);
252
305
  onOpenChange?.(true);
253
306
  }}
254
307
  testIdPrefix={rest.testIdPrefix}
255
308
  />
256
309
  <Popover
257
- open={Boolean(anchor)}
258
- anchorEl={anchor}
310
+ {...still.popoverProps}
259
311
  onClose={() => {
260
- setAnchor(null);
312
+ still.close();
261
313
  onOpenChange?.(false);
262
314
  }}
263
- anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
264
- slotProps={{ paper: { sx: { width: sxRem(300), maxWidth: (theme: Theme) => `calc(100vw - ${rem(theme, 32)})` } } }}
315
+ // Below `sm`, 8px from the screen's edges instead of 16.
316
+ marginThreshold={phone ? remPx(theme, 8) : undefined}
317
+ slotProps={{ paper: { ref: still.paperRef, sx: phone ? PHONE_PAPER_SX : PAPER_SX } }}
265
318
  >
266
- <Box data-testid={`${rest.testIdPrefix}-more-panel`}>
319
+ <Box data-testid={`${rest.testIdPrefix}-more-panel`} sx={{ ...COLUMN_SX, flex: "1 1 auto", minHeight: 0 }}>
267
320
  <MoreHeading />
268
- {/* overflowX hidden, not auto: this panel is a fixed 300px and its
321
+ {/* overflowX hidden, not auto: this panel is a fixed width and its
269
322
  contents are told to shrink into it. A horizontal scrollbar here
270
323
  means something refused to, which is a bug to fix rather than a
271
324
  scrollbar to live with. */}
272
- <Box sx={{ maxHeight: sxRem(320), overflowY: "auto", overflowX: "hidden", p: 1.5, minWidth: 0 }}>
325
+ <Box sx={{ flex: "1 1 auto", minHeight: 0, overflowY: "auto", overflowX: "hidden", p: { xs: 1, sm: 1.5 }, minWidth: 0 }}>
273
326
  <MoreFields key={clearKey} fields={fields} {...rest} />
274
327
  </Box>
275
- {anyApplied && (
276
- <MoreFooter onClearAll={onClearAll} testIdPrefix={rest.testIdPrefix} />
328
+ {(anyApplied || holdsDayRange) && (
329
+ <MoreFooter onClearAll={onClearAll} testIdPrefix={rest.testIdPrefix} reserved={!anyApplied} />
277
330
  )}
278
331
  </Box>
279
332
  </Popover>
@@ -91,13 +91,8 @@ function boundAsNumber(amount: number | string | undefined): number | undefined
91
91
  * One end of a numeric range. The BOUND stays a plain `number` — nothing
92
92
  * downstream learns that the typing surface changed.
93
93
  */
94
- export function NumberBoundInput({
95
- label,
96
- value,
97
- unit,
98
- onChange,
99
- testId,
100
- }: {
94
+ /** Props of {@link NumberBoundInput}. */
95
+ interface NumberBoundInputProps {
101
96
  label: string;
102
97
  /** The applied bound. A range restored from the URL hands this over as a string. */
103
98
  value: number | string | undefined;
@@ -105,7 +100,21 @@ export function NumberBoundInput({
105
100
  unit?: string;
106
101
  onChange: (bound: number | undefined) => void;
107
102
  testId: string;
108
- }): React.JSX.Element {
103
+ /**
104
+ * Take the width of the cell it sits in rather than its own 140px, so a
105
+ * pair in a panel splits the row equally (FUT-4144 UI review round 2).
106
+ */
107
+ fill?: boolean;
108
+ }
109
+
110
+ export function NumberBoundInput({
111
+ label,
112
+ value,
113
+ unit,
114
+ onChange,
115
+ testId,
116
+ fill = false,
117
+ }: NumberBoundInputProps): React.JSX.Element {
109
118
  const applied = boundAsNumber(value);
110
119
  const [text, setText] = useState(() => formatPtBrNumber(value));
111
120
 
@@ -164,7 +173,8 @@ export function NumberBoundInput({
164
173
  }
165
174
  : undefined
166
175
  }
167
- sx={[{ width: sxRem(140) }, fieldTextFieldStyles]}
176
+ fullWidth={fill}
177
+ sx={[{ width: fill ? '100%' : sxRem(140) }, fieldTextFieldStyles]}
168
178
  />
169
179
  );
170
180
  }
@@ -11,9 +11,20 @@
11
11
  * Clicking an active preset CLEARS it. A chip that reads as pressed and does
12
12
  * nothing when pressed again is a dead control, and the alternative — hunting
13
13
  * for the pill's ✕ — is the gesture this row exists to remove.
14
+ *
15
+ * ONE LINE at every width (ADR 0190 in the host, FUT-4144): the presets are
16
+ * the options of one question, and as wrapping chips "Este ano" fell onto a
17
+ * second line at 320, 390 and 600, where it read as a second question. Where
18
+ * the row is wide enough they are a segmented control of equal parts; where it
19
+ * is not, the same question is a select.
14
20
  */
15
21
  import Box from "@mui/material/Box/index.js";
22
+ import TextField from "@mui/material/TextField/index.js";
23
+ import { useTheme } from "@mui/material/styles/index.js";
24
+ import React, { useLayoutEffect, useRef, useState, type RefObject } from "react";
16
25
 
26
+ import { EN_US_DATA_VIEWS_COPY } from "../../../en-US.data-display";
27
+ import { useDataViewsCopy } from "./data-views-copy-context";
17
28
  import { isPresetActive, resolvePreset } from "./data-views-range-presets";
18
29
  import type { RangePreset, RangeValue } from "./data-views-types";
19
30
  import { sxRem } from "../../../tokens/relative";
@@ -51,6 +62,9 @@ function PresetChip({
51
62
  fontSize: sxRem(12),
52
63
  fontWeight: 600,
53
64
  whiteSpace: "nowrap",
65
+ overflow: "hidden",
66
+ textOverflow: "ellipsis",
67
+ minWidth: 0,
54
68
  "&:hover": { borderColor: active ? "primary.main" : "text.primary" },
55
69
  }}
56
70
  >
@@ -59,6 +73,115 @@ function PresetChip({
59
73
  );
60
74
  }
61
75
 
76
+ /** The chip look, for the hidden row that measures each label at its natural width. */
77
+ const MEASURE_SX = {
78
+ display: "inline-block",
79
+ px: 1.25,
80
+ border: 1,
81
+ font: "inherit",
82
+ fontSize: sxRem(12),
83
+ fontWeight: 600,
84
+ whiteSpace: "nowrap",
85
+ } as const;
86
+
87
+ /**
88
+ * Whether `count` equal parts, each as wide as the widest label, fit `width`
89
+ * with `gap` between them. Unmeasured (`null`: the server, jsdom) counts as
90
+ * fitting, so the row renders the buttons it always rendered.
91
+ */
92
+ export function presetsFitOneLine(width: number | null, widest: number, count: number, gap: number): boolean {
93
+ if (width === null) return true;
94
+ return widest * count + gap * (count - 1) <= width;
95
+ }
96
+
97
+ /** The element's own width as it changes; `null` until measured, or where nothing can measure. */
98
+ function useElementWidth(ref: RefObject<HTMLElement | null>): number | null {
99
+ const [width, setWidth] = useState<number | null>(null);
100
+ useLayoutEffect(() => {
101
+ const element = ref.current;
102
+ if (element === null || typeof ResizeObserver === "undefined") return undefined;
103
+ const observer = new ResizeObserver((entries) => {
104
+ const measured = entries[0]?.contentRect.width;
105
+ if (measured !== undefined && measured > 0) setWidth(measured);
106
+ });
107
+ observer.observe(element);
108
+ return () => observer.disconnect();
109
+ }, [ref]);
110
+ return width;
111
+ }
112
+
113
+ /**
114
+ * The widest label at the chip's own type, padding and border, read off the
115
+ * hidden measuring row.
116
+ *
117
+ * `offsetWidth`, the LAYOUT width, never the bounding box: a popover grows in
118
+ * from 75% scale, and a box read during that transition is a quarter short —
119
+ * which judged "Acima de R$ 10" to fit a third of the "Mais" panel and cut it
120
+ * to "Acima de …" (FUT-4144 UI review round 2). Measured again when the row's
121
+ * width changes and once the web fonts have loaded, since a fallback face is
122
+ * narrower than the one the parts will be drawn in.
123
+ */
124
+ function useWidestLabel(ref: RefObject<HTMLElement | null>, labels: string, width: number | null): number {
125
+ const [widest, setWidest] = useState(0);
126
+ useLayoutEffect(() => {
127
+ const measure = (): void => {
128
+ const spans = ref.current?.querySelectorAll<HTMLElement>("[data-preset-measure]") ?? [];
129
+ // +1: `offsetWidth` rounds, so a label laid out at 80.4px reads 80 and
130
+ // would be judged to fit half a pixel short, behind an ellipsis.
131
+ setWidest(Math.max(0, ...Array.from(spans, (span) => span.offsetWidth + 1)));
132
+ };
133
+ measure();
134
+ let live = true;
135
+ void document.fonts?.ready.then(() => {
136
+ if (live) measure();
137
+ });
138
+ return () => {
139
+ live = false;
140
+ };
141
+ }, [ref, labels, width]);
142
+ return widest;
143
+ }
144
+
145
+ /** The same question as a select, for a row too narrow for its parts. */
146
+ function PresetSelect({
147
+ presets,
148
+ value,
149
+ onChange,
150
+ testId,
151
+ }: {
152
+ presets: RangePreset[];
153
+ value: RangeValue;
154
+ onChange: (range: RangeValue) => void;
155
+ testId: string;
156
+ }): React.JSX.Element {
157
+ const copy = useDataViewsCopy().filters.presetSelect ?? EN_US_DATA_VIEWS_COPY.filters.presetSelect;
158
+ const active = presets.find((preset) => isPresetActive(preset, value))?.id ?? "";
159
+ return (
160
+ <TextField
161
+ select
162
+ size="small"
163
+ fullWidth
164
+ label={copy?.label}
165
+ value={active}
166
+ onChange={(event) => {
167
+ const picked = presets.find((preset) => preset.id === event.target.value);
168
+ // "None" is the select's way of tapping the active chip again.
169
+ onChange(picked ? resolvePreset(picked) : {});
170
+ }}
171
+ SelectProps={{ native: true }}
172
+ InputLabelProps={{ shrink: true }}
173
+ inputProps={{ "data-testid": `${testId}-presets-select` }}
174
+ >
175
+ <option value="">{copy?.none}</option>
176
+ {presets.map((preset) => (
177
+ <option key={preset.id} value={preset.id}>
178
+ {preset.label}
179
+ </option>
180
+ ))}
181
+ </TextField>
182
+ );
183
+ }
184
+
62
185
  /**
63
186
  * The preset row. Renders nothing when a field declares none, so a numeric
64
187
  * range without host-configured windows keeps the popover it had.
@@ -66,6 +189,9 @@ function PresetChip({
66
189
  * Carries no outer margin — the three surfaces that host it (the pill's
67
190
  * popover, the slide-in panel, the "Mais" overflow) each sit it in a different
68
191
  * stack, and a baked-in margin would be wrong in two of them.
192
+ *
193
+ * What is measured is the row's OWN width, not the window's: the same row
194
+ * lives in a 670px popover, a 300px panel and a phone-wide sheet.
69
195
  */
70
196
  export function RangePresets({
71
197
  presets,
@@ -78,24 +204,47 @@ export function RangePresets({
78
204
  onChange: (range: RangeValue) => void;
79
205
  testId: string;
80
206
  }): React.JSX.Element | null {
207
+ const theme = useTheme();
208
+ const box = useRef<HTMLDivElement>(null);
209
+ const width = useElementWidth(box);
210
+ const widest = useWidestLabel(box, presets.map((preset) => preset.label).join("|"), width);
81
211
  if (presets.length === 0) return null;
212
+ const fits = presetsFitOneLine(width, widest, presets.length, parseFloat(theme.spacing(0.75)));
82
213
  return (
83
- <Box
84
- data-testid={`${testId}-presets`}
85
- sx={{ display: "flex", flexWrap: "wrap", gap: 0.75 }}
86
- >
87
- {presets.map((preset) => {
88
- const active = isPresetActive(preset, value);
89
- return (
90
- <PresetChip
91
- key={preset.id}
92
- preset={preset}
93
- active={active}
94
- onSelect={() => onChange(active ? {} : resolvePreset(preset))}
95
- testId={`${testId}-preset-${preset.id}`}
96
- />
97
- );
98
- })}
214
+ <Box ref={box} sx={{ position: "relative", minWidth: 0 }}>
215
+ <Box
216
+ aria-hidden="true"
217
+ sx={{ position: "absolute", top: 0, left: 0, width: "max-content", height: 0, overflow: "hidden", visibility: "hidden" }}
218
+ >
219
+ {presets.map((preset) => (
220
+ <Box key={preset.id} component="span" data-preset-measure="" sx={MEASURE_SX}>
221
+ {preset.label}
222
+ </Box>
223
+ ))}
224
+ </Box>
225
+ {fits ? (
226
+ <Box
227
+ data-testid={`${testId}-presets`}
228
+ sx={{ display: "grid", gridTemplateColumns: `repeat(${presets.length}, minmax(0, 1fr))`, gap: 0.75 }}
229
+ >
230
+ {presets.map((preset) => {
231
+ const active = isPresetActive(preset, value);
232
+ return (
233
+ <PresetChip
234
+ key={preset.id}
235
+ preset={preset}
236
+ active={active}
237
+ onSelect={() => onChange(active ? {} : resolvePreset(preset))}
238
+ testId={`${testId}-preset-${preset.id}`}
239
+ />
240
+ );
241
+ })}
242
+ </Box>
243
+ ) : (
244
+ <Box data-testid={`${testId}-presets`}>
245
+ <PresetSelect presets={presets} value={value} onChange={onChange} testId={testId} />
246
+ </Box>
247
+ )}
99
248
  </Box>
100
249
  );
101
250
  }
@@ -0,0 +1,223 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * The `De`/`Até` pair of a range filter, its preset chips, the line under it
5
+ * and — for a day range — the calendar under that (FUT-4144). Shared by the
6
+ * pill's popover, the slide-in panel and the "Mais" sheet, so the three cannot
7
+ * drift on what a bound accepts or how it clears.
8
+ */
9
+ import Box from '@mui/material/Box/index.js';
10
+ import Typography from '@mui/material/Typography/index.js';
11
+ import React, { useState } from 'react';
12
+
13
+ import { useDataViewsCopy } from './data-views-copy-context';
14
+ import { DayBoundInput } from './data-views-day-input';
15
+ import { NumberBoundInput } from './data-views-number-input';
16
+ import { RangePresets } from './data-views-preset-chips';
17
+ import { DayRangeCalendar } from './data-views-range-calendar';
18
+ import { presetsFor } from './data-views-range-presets';
19
+ import { useOutsideResetKey } from './data-views-range-reset';
20
+ import { isRangeInverted } from './data-views-range-values';
21
+ import type { RangeFieldConfig, RangeValue } from './data-views-types';
22
+ import { sxRem } from '../../../tokens/relative';
23
+
24
+ /**
25
+ * One end of the range. A day keeps its `AAAA-MM-DD` string verbatim (the form
26
+ * both the field and the backend speak) and is typed through the masked
27
+ * `dd/mm/aaaa` field rather than a native date input — see
28
+ * {@link DayBoundInput} for why. A number parses, and an unparseable/blank
29
+ * entry CLEARS the bound rather than writing `NaN`.
30
+ */
31
+ function BoundField<T extends Record<string, unknown>>({
32
+ field,
33
+ label,
34
+ value,
35
+ onChange,
36
+ testId,
37
+ fill,
38
+ }: {
39
+ field: RangeFieldConfig<T>;
40
+ label: string;
41
+ value: number | string | undefined;
42
+ onChange: (bound: number | string | undefined) => void;
43
+ testId: string;
44
+ /** A number box takes its cell's width; a day box always does. */
45
+ fill: boolean;
46
+ }): React.JSX.Element {
47
+ const copy = useDataViewsCopy();
48
+ if (field.kind === 'day') {
49
+ return (
50
+ <DayBoundInput
51
+ fill
52
+ mask={copy.filters.dayMask}
53
+ invalidMessage={copy.filters.dayInvalid}
54
+ label={label}
55
+ value={value == null ? undefined : String(value)}
56
+ onChange={onChange}
57
+ testId={testId}
58
+ />
59
+ );
60
+ }
61
+ return (
62
+ <NumberBoundInput
63
+ fill={fill}
64
+ label={label}
65
+ value={value}
66
+ unit={field.unit}
67
+ onChange={onChange}
68
+ testId={testId}
69
+ />
70
+ );
71
+ }
72
+
73
+
74
+ /** One box of the pair: an equal share of the row for a day, as it was for a number. */
75
+ function BoundCell({ grow, children }: { grow: boolean; children: React.ReactNode }): React.JSX.Element {
76
+ if (!grow) return <>{children}</>;
77
+ return <Box sx={{ flex: '1 1 0', minWidth: 0 }}>{children}</Box>;
78
+ }
79
+
80
+ /**
81
+ * The line under the pair: why an inverted window matches nothing, or — for a
82
+ * day range — that BOTH days are in the result. Saying so is FUT-668's other
83
+ * half: the bounds compare as ISO strings precisely so `até` keeps its whole
84
+ * day, and nothing on screen would otherwise tell the merchant that.
85
+ */
86
+ function RangeNote({
87
+ day,
88
+ inverted,
89
+ testId,
90
+ }: {
91
+ day: boolean;
92
+ inverted: boolean;
93
+ testId: string;
94
+ }): React.JSX.Element | null {
95
+ const copy = useDataViewsCopy();
96
+ if (inverted) {
97
+ return (
98
+ <Typography
99
+ component="p"
100
+ data-testid={`${testId}-inverted`}
101
+ sx={{ mt: 0.75, fontSize: sxRem(12), color: 'error.main' }}
102
+ >
103
+ {copy.filters.rangeInvalid}
104
+ </Typography>
105
+ );
106
+ }
107
+ if (!day) return null;
108
+ return (
109
+ <Typography
110
+ component="p"
111
+ data-testid={`${testId}-inclusive`}
112
+ // `text.secondary`, not `disabled`: the note is read, and disabled grey
113
+ // at 12px is 2.68:1 (FUT-4144 UI review).
114
+ sx={{ mt: { xs: 0.25, sm: 0.75 }, fontSize: sxRem(12), color: 'text.secondary' }}
115
+ >
116
+ {copy.filters.rangeInclusiveNote}
117
+ </Typography>
118
+ );
119
+ }
120
+
121
+ /** Props of {@link RangeBounds}. */
122
+ interface RangeBoundsProps<T extends Record<string, unknown>> {
123
+ field: RangeFieldConfig<T>;
124
+ value: RangeValue;
125
+ onChange: (range: RangeValue) => void;
126
+ testId: string;
127
+ /**
128
+ * Months the day calendar shows side by side (FUT-4144). Two where the
129
+ * popover has the room; one, with the month/year selectors, on a phone and
130
+ * in the panels.
131
+ */
132
+ calendarMonths?: 1 | 2;
133
+ /**
134
+ * Whether the day calendar is mounted. A host that keeps the pair mounted
135
+ * while hidden (the slide-in panel) passes its open state, so a closed panel
136
+ * holds no calendar of its own.
137
+ */
138
+ showCalendar?: boolean;
139
+ /**
140
+ * A NUMBER pair splits the row equally too, as a day pair always does. Set
141
+ * by the panels, whose width is the row's; the pill's popover sizes itself to
142
+ * its content and keeps the boxes' own width.
143
+ */
144
+ fillRow?: boolean;
145
+ }
146
+
147
+ /**
148
+ * The `De`/`Até` pair for a range — shared by the pill's popover and the
149
+ * slide-in panel's day fields, so the two surfaces cannot drift on what a bound
150
+ * accepts or how it clears.
151
+ */
152
+ export function RangeBounds<T extends Record<string, unknown>>({
153
+ field,
154
+ value,
155
+ onChange,
156
+ testId,
157
+ calendarMonths = 1,
158
+ showCalendar = true,
159
+ fillRow = false,
160
+ }: RangeBoundsProps<T>): React.JSX.Element {
161
+ const copy = useDataViewsCopy();
162
+ const { key, emitted } = useOutsideResetKey(value);
163
+ // Moves the calendar to a TYPED day. A typed write is the fields' own, so
164
+ // the outside key above does not see it; a tap on the calendar moves
165
+ // neither, which is what keeps it still under the finger.
166
+ const [typed, setTyped] = useState(0);
167
+ const typedChange = (range: RangeValue) => {
168
+ setTyped((n) => n + 1);
169
+ onChange(emitted(range));
170
+ };
171
+ const day = field.kind === 'day';
172
+ return (
173
+ <Box data-testid={`${testId}-panel`}>
174
+ {/* Above the inputs, not below: the presets are the answer for most
175
+ merchants, and the two date pickers are the escape hatch. */}
176
+ <Box sx={{ '&:not(:empty)': { mb: { xs: 1, sm: 1.5 } } }}>
177
+ <RangePresets presets={presetsFor(field, copy)} value={value} onChange={onChange} testId={testId} />
178
+ </Box>
179
+ {/* Top-aligned: a day that does not exist grows ITS field by a message
180
+ line, and centring would push the other box half a line down. The two
181
+ boxes are one height, so with no message this is the same row. */}
182
+ <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
183
+ {/* A day pair shares the row equally, so it spans the calendar under
184
+ it; a number pair keeps its own widths. */}
185
+ <BoundCell grow={day || fillRow}>
186
+ <BoundField
187
+ key={`min-${key}`}
188
+ field={field}
189
+ label={copy.filters.rangeStart}
190
+ value={value.min}
191
+ onChange={(min) => typedChange({ ...value, min })}
192
+ testId={`${testId}-min`}
193
+ fill={fillRow}
194
+ />
195
+ </BoundCell>
196
+ <BoundCell grow={day || fillRow}>
197
+ <BoundField
198
+ key={`max-${key}`}
199
+ field={field}
200
+ label={copy.filters.rangeEnd}
201
+ value={value.max}
202
+ onChange={(max) => typedChange({ ...value, max })}
203
+ testId={`${testId}-max`}
204
+ fill={fillRow}
205
+ />
206
+ </BoundCell>
207
+ </Box>
208
+ <RangeNote day={day} inverted={isRangeInverted(value)} testId={testId} />
209
+ {/* Under the boxes and the note, with no button to reach it: the typed
210
+ path stays as short as it was, and a period is two taps (FUT-4144). */}
211
+ {day && showCalendar && (
212
+ <DayRangeCalendar
213
+ fieldLabel={field.label}
214
+ value={value}
215
+ onChange={(range) => onChange(emitted(range))}
216
+ months={calendarMonths}
217
+ viewKey={`${key}-${typed}`}
218
+ testId={testId}
219
+ />
220
+ )}
221
+ </Box>
222
+ );
223
+ }