@12-apps/ui 6.93.0 → 6.93.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/components-catalog.md +9 -2
  2. package/components-guidelines.md +30 -0
  3. package/dist/_internal/components/data-display/AlertDialog/AlertDialog.js +2 -2
  4. package/dist/_internal/components/data-display/AlertDialog/AlertDialog.parts.js +9 -3
  5. package/dist/_internal/components/data-display/AsyncStateContainer/AsyncStateContainer.js +3 -3
  6. package/dist/_internal/components/data-display/Avatar/Avatar.js +2 -2
  7. package/dist/_internal/components/data-display/Badge/Badge.js +2 -2
  8. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +7 -7
  9. package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +7 -7
  10. package/dist/_internal/components/data-display/Carousel/Carousel.js +3 -3
  11. package/dist/_internal/components/data-display/Chart/Chart.js +4 -4
  12. package/dist/_internal/components/data-display/Chart/SpecChart.js +7 -7
  13. package/dist/_internal/components/data-display/Chart/chart-context.js +2 -2
  14. package/dist/_internal/components/data-display/Chart/chart-renderers.js +3 -3
  15. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +31 -31
  16. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +42 -42
  17. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +7 -7
  18. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +2 -2
  19. package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +2 -2
  20. package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +2 -2
  21. package/dist/_internal/components/data-display/DataViews/base-list-card.js +5 -5
  22. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +3 -3
  23. package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +6 -6
  24. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +5 -5
  25. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +26 -26
  26. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +8 -8
  27. package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +6 -6
  28. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +7 -7
  29. package/dist/_internal/components/data-display/DataViews/data-views-range-bounds.js +3 -3
  30. package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +5 -5
  31. package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +2 -2
  32. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +7 -7
  33. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +8 -8
  34. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +6 -6
  35. package/dist/_internal/components/data-display/DataViews/list-card-rails.js +2 -2
  36. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +3 -3
  37. package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.js +25 -0
  38. package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.js.map +1 -0
  39. package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.styles.js +23 -0
  40. package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.styles.js.map +1 -0
  41. package/dist/_internal/components/data-display/DayRuler/DayRuler.js +14 -12
  42. package/dist/_internal/components/data-display/DayRuler/DayRuler.keyboard.js +3 -3
  43. package/dist/_internal/components/data-display/DayRuler/DayRuler.metrics.js +1 -1
  44. package/dist/_internal/components/data-display/DayRuler/DayRuler.parts.js +12 -15
  45. package/dist/_internal/components/data-display/DayRuler/DayRuler.styles.js +3 -11
  46. package/dist/_internal/components/data-display/DayRuler/day-ruler-model.js +5 -1
  47. package/dist/_internal/components/data-display/DayRuler/useRulerDrag.js +3 -3
  48. package/dist/_internal/components/data-display/DayRuler/useTextFits.js +4 -3
  49. package/dist/_internal/components/data-display/ErrorState/ErrorState.js +2 -2
  50. package/dist/_internal/components/data-display/ErrorState/ErrorState.metrics.js +3 -1
  51. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.js +6 -6
  52. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.parts.js +5 -5
  53. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.styles.js +2 -2
  54. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleNameCell.js +3 -3
  55. package/dist/_internal/components/data-display/ScheduleGrid/schedule-name.styles.js +2 -2
  56. package/dist/_internal/components/data-display/ScheduleGrid/schedule-tones.js +3 -1
  57. package/dist/_internal/components/data-display/index.js +41 -39
  58. package/dist/_internal/components/feedback/ConfirmAction/ConfirmAction.js +4 -4
  59. package/dist/_internal/components/feedback/ConfirmAction/ConfirmAction.parts.js +3 -3
  60. package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +5 -5
  61. package/dist/_internal/components/feedback/ConfirmAction/with-confirmation.js +4 -4
  62. package/dist/_internal/components/feedback/index.js +6 -6
  63. package/dist/_internal/components/index.js +52 -50
  64. package/dist/_internal/components/navigation/SectionNav/SectionNav.bar.js +6 -6
  65. package/dist/_internal/components/navigation/SectionNav/SectionNav.js +9 -9
  66. package/dist/_internal/components/navigation/SectionNav/SectionNav.parts.js +3 -3
  67. package/dist/_internal/components/navigation/SectionNav/SectionNav.primary.js +4 -4
  68. package/dist/_internal/components/navigation/SectionNav/SectionNav.rail.js +5 -5
  69. package/dist/_internal/components/navigation/SectionNav/SectionNav.railCreate.js +4 -4
  70. package/dist/_internal/components/navigation/SectionNav/SectionNav.sheet.js +4 -4
  71. package/dist/_internal/components/navigation/index.js +9 -9
  72. package/dist/_internal/user-avatar/user-avatar.js +3 -3
  73. package/dist/charts.js +13 -13
  74. package/dist/{chunk-LDCZPZPD.js → chunk-24HHUCWB.js} +2 -2
  75. package/dist/{chunk-7ZQ3KQ7J.js → chunk-273OLFJD.js} +16 -6
  76. package/dist/chunk-273OLFJD.js.map +1 -0
  77. package/dist/{chunk-FAUNQ3FS.js → chunk-2M3W6ACW.js} +3 -3
  78. package/dist/{chunk-ALOEOGQV.js → chunk-3TZUZXY5.js} +2 -2
  79. package/dist/{chunk-4HK4FVMV.js → chunk-3UAHDLKH.js} +7 -1
  80. package/dist/{chunk-4HK4FVMV.js.map → chunk-3UAHDLKH.js.map} +1 -1
  81. package/dist/{chunk-F5H3WDG7.js → chunk-4E2RQOSQ.js} +2 -2
  82. package/dist/{chunk-JIEVKHOH.js → chunk-4J62V5OE.js} +6 -6
  83. package/dist/{chunk-OJZLFFG4.js → chunk-5AZEDQXX.js} +59 -70
  84. package/dist/chunk-5AZEDQXX.js.map +1 -0
  85. package/dist/{chunk-CZVAVX5L.js → chunk-6ATXOJVQ.js} +2 -2
  86. package/dist/{chunk-E2UOEI7N.js → chunk-6LGKJVOA.js} +2 -2
  87. package/dist/{chunk-XBWW3QSI.js → chunk-7WE432RQ.js} +9 -9
  88. package/dist/{chunk-5357QUVE.js → chunk-AMLHEKSV.js} +5 -5
  89. package/dist/{chunk-HSVSU7I2.js → chunk-ASEHJIIV.js} +3 -3
  90. package/dist/{chunk-FYUNROLW.js → chunk-BZYLTGOE.js} +2 -2
  91. package/dist/{chunk-TZ44F6W7.js → chunk-CGS2LQE5.js} +2 -2
  92. package/dist/{chunk-KPUUHAK6.js → chunk-CWXX5JR3.js} +11 -9
  93. package/dist/chunk-CWXX5JR3.js.map +1 -0
  94. package/dist/{chunk-6HXNFNOB.js → chunk-EE6DJPB7.js} +4 -4
  95. package/dist/{chunk-OM6ZT64U.js → chunk-F2UP5ZF4.js} +3 -3
  96. package/dist/{chunk-P53ANKZV.js → chunk-F7LBTMCA.js} +4 -4
  97. package/dist/{chunk-6HPVCDVU.js → chunk-FGZIEZEP.js} +69 -112
  98. package/dist/chunk-FGZIEZEP.js.map +1 -0
  99. package/dist/{chunk-IWSTMVCA.js → chunk-FKS2PNJF.js} +8 -8
  100. package/dist/{chunk-3WYEENUH.js → chunk-FPNVOB52.js} +7 -7
  101. package/dist/{chunk-E73C73UD.js → chunk-FYSS2PYA.js} +3 -3
  102. package/dist/{chunk-N557OEZR.js → chunk-G6NMSBSQ.js} +2 -2
  103. package/dist/{chunk-A5OERY43.js → chunk-GARM5SX6.js} +2 -2
  104. package/dist/{chunk-WJMSND5Q.js → chunk-GXGHEOWC.js} +2 -2
  105. package/dist/{chunk-U2SAW6DH.js → chunk-GY53SDVL.js} +4 -4
  106. package/dist/{chunk-MFX4ASX5.js → chunk-HWZETMO4.js} +3 -3
  107. package/dist/{chunk-HBH7V3IP.js → chunk-I6IFUFLW.js} +5 -3
  108. package/dist/chunk-I6IFUFLW.js.map +1 -0
  109. package/dist/{chunk-MM3QESLI.js → chunk-ILJ3FXIC.js} +2 -2
  110. package/dist/{chunk-K6VEN5VN.js → chunk-K3PMMW23.js} +4 -4
  111. package/dist/{chunk-T5RFH2T5.js → chunk-KOGJBAFI.js} +2 -2
  112. package/dist/{chunk-COK65GX2.js → chunk-KURE5R7Z.js} +12 -2
  113. package/dist/chunk-KURE5R7Z.js.map +1 -0
  114. package/dist/{chunk-MAYAJXO6.js → chunk-KXDLLS62.js} +5 -5
  115. package/dist/{chunk-LE5JYNR7.js → chunk-L5CAHQIN.js} +23 -2
  116. package/dist/chunk-L5CAHQIN.js.map +1 -0
  117. package/dist/{chunk-DBI6AMNY.js → chunk-LKWXU6NH.js} +4 -4
  118. package/dist/chunk-MPVH2GM5.js +83 -0
  119. package/dist/chunk-MPVH2GM5.js.map +1 -0
  120. package/dist/{chunk-6DO672AG.js → chunk-N5PVD4J5.js} +7 -7
  121. package/dist/{chunk-ZYZPSBNQ.js → chunk-OGSTMHT6.js} +22 -7
  122. package/dist/chunk-OGSTMHT6.js.map +1 -0
  123. package/dist/{chunk-TLGGVE56.js → chunk-OTVOQHZB.js} +3 -1
  124. package/dist/{chunk-TLGGVE56.js.map → chunk-OTVOQHZB.js.map} +1 -1
  125. package/dist/{chunk-7F6D73NL.js → chunk-RLTGD5WJ.js} +2 -2
  126. package/dist/{chunk-R52N5UCS.js → chunk-SB2SEGR6.js} +2 -2
  127. package/dist/{chunk-Z2WZDVGG.js → chunk-SI5SKX52.js} +2 -2
  128. package/dist/{chunk-RYJ6XGFA.js → chunk-SJAOQGQI.js} +2 -2
  129. package/dist/{chunk-D7R6SRJB.js → chunk-SKQPUAMK.js} +3 -3
  130. package/dist/{chunk-AMIUZRDQ.js → chunk-SNWCT6JN.js} +3 -3
  131. package/dist/{chunk-7W5X3KQ2.js → chunk-SW7JCF4Z.js} +2 -2
  132. package/dist/{chunk-BNBWUXT3.js → chunk-TE6HAWDM.js} +2 -2
  133. package/dist/{chunk-JG733RMP.js → chunk-TZC6KIK2.js} +5 -5
  134. package/dist/{chunk-4QGKR65P.js → chunk-U4Z6GZDW.js} +5 -5
  135. package/dist/{chunk-XFZDO2KB.js → chunk-UIIHUWOV.js} +13 -13
  136. package/dist/{chunk-L4V7FZJI.js → chunk-UKUGSU3Y.js} +2 -2
  137. package/dist/{chunk-VJIJCLUX.js → chunk-UOKXYV3U.js} +2 -2
  138. package/dist/{chunk-K7IDOTSU.js → chunk-VNMD6BA4.js} +2 -2
  139. package/dist/chunk-WG7CAT5D.js +65 -0
  140. package/dist/chunk-WG7CAT5D.js.map +1 -0
  141. package/dist/chunk-WSSD7H5E.js +52 -0
  142. package/dist/chunk-WSSD7H5E.js.map +1 -0
  143. package/dist/{chunk-DTZOP4ZR.js → chunk-XCUHGPPZ.js} +2 -2
  144. package/dist/{chunk-3YFDGFEK.js → chunk-Y7BOP7VZ.js} +2 -2
  145. package/dist/{chunk-XNAI7QGU.js → chunk-ZM6FJCWS.js} +4 -4
  146. package/dist/{chunk-FC7UCFFA.js → chunk-ZPWPA6FR.js} +8 -8
  147. package/dist/data-display/AlertDialog.js +2 -2
  148. package/dist/data-display/AsyncStateContainer.js +3 -3
  149. package/dist/data-display/Avatar.js +2 -2
  150. package/dist/data-display/Badge.js +2 -2
  151. package/dist/data-display/CardKit.js +10 -10
  152. package/dist/data-display/Carousel.js +3 -3
  153. package/dist/data-display/Chart.js +13 -13
  154. package/dist/data-display/DataViews.js +56 -56
  155. package/dist/data-display/DayRuler.js +14 -12
  156. package/dist/data-display/ErrorState.js +2 -2
  157. package/dist/data-display/ScheduleGrid.js +6 -6
  158. package/dist/feedback/ConfirmAction.js +6 -6
  159. package/dist/native/data-display/ErrorState.js.map +1 -1
  160. package/dist/navigation/SectionNav.js +9 -9
  161. package/dist/types/components/data-display/AlertDialog/AlertDialog.d.ts.map +1 -1
  162. package/dist/types/components/data-display/AlertDialog/AlertDialog.parts.d.ts +11 -0
  163. package/dist/types/components/data-display/AlertDialog/AlertDialog.parts.d.ts.map +1 -1
  164. package/dist/types/components/data-display/DayRuler/DayRuler.d.ts.map +1 -1
  165. package/dist/types/components/data-display/DayRuler/DayRuler.footer.d.ts +16 -0
  166. package/dist/types/components/data-display/DayRuler/DayRuler.footer.d.ts.map +1 -0
  167. package/dist/types/components/data-display/DayRuler/DayRuler.footer.styles.d.ts +18 -0
  168. package/dist/types/components/data-display/DayRuler/DayRuler.footer.styles.d.ts.map +1 -0
  169. package/dist/types/components/data-display/DayRuler/DayRuler.metrics.d.ts +15 -5
  170. package/dist/types/components/data-display/DayRuler/DayRuler.metrics.d.ts.map +1 -1
  171. package/dist/types/components/data-display/DayRuler/DayRuler.parts.d.ts +13 -14
  172. package/dist/types/components/data-display/DayRuler/DayRuler.parts.d.ts.map +1 -1
  173. package/dist/types/components/data-display/DayRuler/DayRuler.styles.d.ts +20 -23
  174. package/dist/types/components/data-display/DayRuler/DayRuler.styles.d.ts.map +1 -1
  175. package/dist/types/components/data-display/DayRuler/DayRuler.types.d.ts +13 -3
  176. package/dist/types/components/data-display/DayRuler/DayRuler.types.d.ts.map +1 -1
  177. package/dist/types/components/data-display/DayRuler/day-ruler-model.d.ts +27 -0
  178. package/dist/types/components/data-display/DayRuler/day-ruler-model.d.ts.map +1 -1
  179. package/dist/types/components/data-display/DayRuler/useTextFits.d.ts +11 -9
  180. package/dist/types/components/data-display/DayRuler/useTextFits.d.ts.map +1 -1
  181. package/dist/types/components/data-display/ErrorState/ErrorState.d.ts.map +1 -1
  182. package/dist/types/components/data-display/ErrorState/ErrorState.metrics.d.ts +5 -0
  183. package/dist/types/components/data-display/ErrorState/ErrorState.metrics.d.ts.map +1 -1
  184. package/dist/types/components/data-display/ScheduleGrid/schedule-tones.d.ts +8 -0
  185. package/dist/types/components/data-display/ScheduleGrid/schedule-tones.d.ts.map +1 -1
  186. package/dist/types-native/components/data-display/ErrorState/ErrorState.metrics.d.ts +5 -0
  187. package/dist/types-native/components/data-display/ErrorState/ErrorState.metrics.d.ts.map +1 -1
  188. package/dist/user-avatar.js +3 -3
  189. package/package.json +1 -1
  190. package/src/components/data-display/AlertDialog/AlertDialog.md +25 -1
  191. package/src/components/data-display/AlertDialog/AlertDialog.parts.tsx +28 -0
  192. package/src/components/data-display/AlertDialog/AlertDialog.tsx +34 -5
  193. package/src/components/data-display/DayRuler/DayRuler.footer.styles.ts +54 -0
  194. package/src/components/data-display/DayRuler/DayRuler.footer.tsx +123 -0
  195. package/src/components/data-display/DayRuler/DayRuler.md +37 -13
  196. package/src/components/data-display/DayRuler/DayRuler.metrics.ts +15 -5
  197. package/src/components/data-display/DayRuler/DayRuler.parts.tsx +86 -155
  198. package/src/components/data-display/DayRuler/DayRuler.styles.ts +89 -83
  199. package/src/components/data-display/DayRuler/DayRuler.tsx +3 -2
  200. package/src/components/data-display/DayRuler/DayRuler.types.ts +13 -3
  201. package/src/components/data-display/DayRuler/day-ruler-model.ts +45 -0
  202. package/src/components/data-display/DayRuler/useTextFits.ts +68 -25
  203. package/src/components/data-display/ErrorState/ErrorState.metrics.ts +5 -0
  204. package/src/components/data-display/ErrorState/ErrorState.tsx +2 -0
  205. package/src/components/data-display/ScheduleGrid/schedule-tones.ts +13 -0
  206. package/src/components/feedback/ConfirmAction/ConfirmAction.md +10 -0
  207. package/dist/chunk-6HPVCDVU.js.map +0 -1
  208. package/dist/chunk-7ZQ3KQ7J.js.map +0 -1
  209. package/dist/chunk-COK65GX2.js.map +0 -1
  210. package/dist/chunk-HBH7V3IP.js.map +0 -1
  211. package/dist/chunk-KPUUHAK6.js.map +0 -1
  212. package/dist/chunk-LE5JYNR7.js.map +0 -1
  213. package/dist/chunk-OJZLFFG4.js.map +0 -1
  214. package/dist/chunk-YMYJO2CZ.js +0 -32
  215. package/dist/chunk-YMYJO2CZ.js.map +0 -1
  216. package/dist/chunk-ZYZPSBNQ.js.map +0 -1
  217. /package/dist/{chunk-LDCZPZPD.js.map → chunk-24HHUCWB.js.map} +0 -0
  218. /package/dist/{chunk-FAUNQ3FS.js.map → chunk-2M3W6ACW.js.map} +0 -0
  219. /package/dist/{chunk-ALOEOGQV.js.map → chunk-3TZUZXY5.js.map} +0 -0
  220. /package/dist/{chunk-F5H3WDG7.js.map → chunk-4E2RQOSQ.js.map} +0 -0
  221. /package/dist/{chunk-JIEVKHOH.js.map → chunk-4J62V5OE.js.map} +0 -0
  222. /package/dist/{chunk-CZVAVX5L.js.map → chunk-6ATXOJVQ.js.map} +0 -0
  223. /package/dist/{chunk-E2UOEI7N.js.map → chunk-6LGKJVOA.js.map} +0 -0
  224. /package/dist/{chunk-XBWW3QSI.js.map → chunk-7WE432RQ.js.map} +0 -0
  225. /package/dist/{chunk-5357QUVE.js.map → chunk-AMLHEKSV.js.map} +0 -0
  226. /package/dist/{chunk-HSVSU7I2.js.map → chunk-ASEHJIIV.js.map} +0 -0
  227. /package/dist/{chunk-FYUNROLW.js.map → chunk-BZYLTGOE.js.map} +0 -0
  228. /package/dist/{chunk-TZ44F6W7.js.map → chunk-CGS2LQE5.js.map} +0 -0
  229. /package/dist/{chunk-6HXNFNOB.js.map → chunk-EE6DJPB7.js.map} +0 -0
  230. /package/dist/{chunk-OM6ZT64U.js.map → chunk-F2UP5ZF4.js.map} +0 -0
  231. /package/dist/{chunk-P53ANKZV.js.map → chunk-F7LBTMCA.js.map} +0 -0
  232. /package/dist/{chunk-IWSTMVCA.js.map → chunk-FKS2PNJF.js.map} +0 -0
  233. /package/dist/{chunk-3WYEENUH.js.map → chunk-FPNVOB52.js.map} +0 -0
  234. /package/dist/{chunk-E73C73UD.js.map → chunk-FYSS2PYA.js.map} +0 -0
  235. /package/dist/{chunk-N557OEZR.js.map → chunk-G6NMSBSQ.js.map} +0 -0
  236. /package/dist/{chunk-A5OERY43.js.map → chunk-GARM5SX6.js.map} +0 -0
  237. /package/dist/{chunk-WJMSND5Q.js.map → chunk-GXGHEOWC.js.map} +0 -0
  238. /package/dist/{chunk-U2SAW6DH.js.map → chunk-GY53SDVL.js.map} +0 -0
  239. /package/dist/{chunk-MFX4ASX5.js.map → chunk-HWZETMO4.js.map} +0 -0
  240. /package/dist/{chunk-MM3QESLI.js.map → chunk-ILJ3FXIC.js.map} +0 -0
  241. /package/dist/{chunk-K6VEN5VN.js.map → chunk-K3PMMW23.js.map} +0 -0
  242. /package/dist/{chunk-T5RFH2T5.js.map → chunk-KOGJBAFI.js.map} +0 -0
  243. /package/dist/{chunk-MAYAJXO6.js.map → chunk-KXDLLS62.js.map} +0 -0
  244. /package/dist/{chunk-DBI6AMNY.js.map → chunk-LKWXU6NH.js.map} +0 -0
  245. /package/dist/{chunk-6DO672AG.js.map → chunk-N5PVD4J5.js.map} +0 -0
  246. /package/dist/{chunk-7F6D73NL.js.map → chunk-RLTGD5WJ.js.map} +0 -0
  247. /package/dist/{chunk-R52N5UCS.js.map → chunk-SB2SEGR6.js.map} +0 -0
  248. /package/dist/{chunk-Z2WZDVGG.js.map → chunk-SI5SKX52.js.map} +0 -0
  249. /package/dist/{chunk-RYJ6XGFA.js.map → chunk-SJAOQGQI.js.map} +0 -0
  250. /package/dist/{chunk-D7R6SRJB.js.map → chunk-SKQPUAMK.js.map} +0 -0
  251. /package/dist/{chunk-AMIUZRDQ.js.map → chunk-SNWCT6JN.js.map} +0 -0
  252. /package/dist/{chunk-7W5X3KQ2.js.map → chunk-SW7JCF4Z.js.map} +0 -0
  253. /package/dist/{chunk-BNBWUXT3.js.map → chunk-TE6HAWDM.js.map} +0 -0
  254. /package/dist/{chunk-JG733RMP.js.map → chunk-TZC6KIK2.js.map} +0 -0
  255. /package/dist/{chunk-4QGKR65P.js.map → chunk-U4Z6GZDW.js.map} +0 -0
  256. /package/dist/{chunk-XFZDO2KB.js.map → chunk-UIIHUWOV.js.map} +0 -0
  257. /package/dist/{chunk-L4V7FZJI.js.map → chunk-UKUGSU3Y.js.map} +0 -0
  258. /package/dist/{chunk-VJIJCLUX.js.map → chunk-UOKXYV3U.js.map} +0 -0
  259. /package/dist/{chunk-K7IDOTSU.js.map → chunk-VNMD6BA4.js.map} +0 -0
  260. /package/dist/{chunk-DTZOP4ZR.js.map → chunk-XCUHGPPZ.js.map} +0 -0
  261. /package/dist/{chunk-3YFDGFEK.js.map → chunk-Y7BOP7VZ.js.map} +0 -0
  262. /package/dist/{chunk-XNAI7QGU.js.map → chunk-ZM6FJCWS.js.map} +0 -0
  263. /package/dist/{chunk-FC7UCFFA.js.map → chunk-ZPWPA6FR.js.map} +0 -0
@@ -0,0 +1,123 @@
1
+ import Box from '@mui/material/Box/index.js';
2
+ import type * as React from 'react';
3
+
4
+ import { SR_ONLY_SX } from '../ScheduleGrid/ScheduleGrid.styles';
5
+ import type { RulerScale } from './DayRuler.keyboard';
6
+ import { coverageLabelSx, coverageSx, lineSx } from './DayRuler.styles';
7
+ import { footerCellSx, footerNoteSx, trendSx } from './DayRuler.footer.styles';
8
+ import type { DayRulerFooterCell, DayRulerFooterRow, RulerHour } from './DayRuler.types';
9
+ import { formatHour } from './day-ruler-model';
10
+
11
+ /** The points of a trend over the hours' centres, in a 100 × 40 box: the highest value near the top, `null` breaks nothing (it is skipped). */
12
+ export function trendPoints(values: readonly (number | null)[], hours: number): string {
13
+ const present = values.slice(0, hours).filter((value): value is number => value !== null);
14
+ const max = Math.max(0, ...present);
15
+ return values
16
+ .slice(0, hours)
17
+ .flatMap((value, index) => {
18
+ if (value === null) return [];
19
+ const x = ((index + 0.5) * 100) / hours;
20
+ const y = max > 0 ? 38 - (value / max) * 34 : 38;
21
+ return [`${x.toFixed(2)},${y.toFixed(2)}`];
22
+ })
23
+ .join(' ');
24
+ }
25
+
26
+ /** One hour's default words: "20:00 · 4"; just the hour when it says nothing. */
27
+ const hourWords = (hour: RulerHour, text: string): string => (text ? `${formatHour(hour)} · ${text}` : formatHour(hour));
28
+
29
+ /** What a reader hears for a marked hour: the host's `title`, else the hour, its count and that it is short. */
30
+ function markedWords(cell: DayRulerFooterCell, hour: RulerHour, marked: string): string {
31
+ return cell.title ?? `${hourWords(hour, cell.text)} · ${marked}`;
32
+ }
33
+
34
+ interface FooterContext {
35
+ hours: number;
36
+ from: RulerHour;
37
+ /** `labels.footerMarked`. */
38
+ marked: string;
39
+ }
40
+
41
+ function FooterCells({ cells, ctx }: { cells: readonly DayRulerFooterCell[]; ctx: FooterContext }): React.JSX.Element {
42
+ return (
43
+ <Box sx={coverageSx(ctx.hours)}>
44
+ {Array.from({ length: ctx.hours }, (_, index) => {
45
+ const cell = cells[index] ?? { text: '' };
46
+ const hour = ctx.from + index;
47
+ return (
48
+ <Box
49
+ key={index}
50
+ component="span"
51
+ sx={footerCellSx(Boolean(cell.marked))}
52
+ title={cell.marked ? markedWords(cell, hour, ctx.marked) : (cell.title ?? hourWords(hour, cell.text))}
53
+ data-marked={cell.marked ? '' : undefined}
54
+ >
55
+ {/* A marked hour is said in words too, not only by its outline and a tooltip touch never shows. */}
56
+ {cell.marked ? (
57
+ <>
58
+ <span aria-hidden>{cell.text}</span>
59
+ <Box component="span" sx={SR_ONLY_SX}>
60
+ {markedWords(cell, hour, ctx.marked)}
61
+ </Box>
62
+ </>
63
+ ) : (
64
+ cell.text
65
+ )}
66
+ </Box>
67
+ );
68
+ })}
69
+ </Box>
70
+ );
71
+ }
72
+
73
+ /** A trend's figures in words, for the reader the drawn line does not reach: "11:00 4 · 12:00 6". */
74
+ function trendWords(values: readonly (number | null)[], ctx: FooterContext): string {
75
+ return values
76
+ .slice(0, ctx.hours)
77
+ .flatMap((value, index) => (value === null ? [] : [`${formatHour(ctx.from + index)} ${Math.round(value * 10) / 10}`]))
78
+ .join(' · ');
79
+ }
80
+
81
+ function FooterFigures({ row, ctx }: { row: DayRulerFooterRow; ctx: FooterContext }): React.JSX.Element {
82
+ if (row.trend) {
83
+ return (
84
+ <Box component="span" sx={{ display: 'block', minWidth: 0 }}>
85
+ <Box component="svg" viewBox="0 0 100 40" preserveAspectRatio="none" sx={trendSx} aria-hidden>
86
+ <polyline fill="none" stroke="currentColor" strokeWidth={1.5} vectorEffect="non-scaling-stroke" points={trendPoints(row.trend, ctx.hours)} />
87
+ </Box>
88
+ <Box component="span" sx={SR_ONLY_SX}>
89
+ {trendWords(row.trend, ctx)}
90
+ </Box>
91
+ </Box>
92
+ );
93
+ }
94
+ if (row.note !== undefined) {
95
+ return (
96
+ <Box component="span" sx={footerNoteSx(Boolean(row.marked))} data-marked={row.marked ? '' : undefined}>
97
+ {row.note}
98
+ </Box>
99
+ );
100
+ }
101
+ return <FooterCells cells={row.cells ?? []} ctx={ctx} />;
102
+ }
103
+
104
+ /**
105
+ * A group's lines under its rows (FUT-3882): counts per hour, a trend, or a
106
+ * sentence, on the tracks' columns. `marked` is `labels.footerMarked`.
107
+ */
108
+ export function GroupFooter(props: { rows: readonly DayRulerFooterRow[]; scale: RulerScale; testId: string; marked: string }): React.JSX.Element {
109
+ const { rows, scale, testId } = props;
110
+ const ctx: FooterContext = { hours: scale.to - scale.from, from: scale.from, marked: props.marked };
111
+ return (
112
+ <>
113
+ {rows.map((row) => (
114
+ <Box key={row.id} sx={lineSx} data-testid={`${testId}-footer-${row.id}`}>
115
+ <Box component="span" sx={coverageLabelSx}>
116
+ {row.label}
117
+ </Box>
118
+ <FooterFigures row={row} ctx={ctx} />
119
+ </Box>
120
+ ))}
121
+ </>
122
+ );
123
+ }
@@ -9,10 +9,11 @@ gestures; hours, limits and refusals are the host's.
9
9
  Na escala, por hora [ ][ ][5][5][5] … [1][1][1][ ][ ]
10
10
  06:00 10:00 … 22:00 02:00
11
11
  ENTREGAS
12
- Luiz entrega |··········[↻ 20:39–00:00]···| [Iniciar agora]
13
- atrasado · era 20:39 ┃ agora
14
- Anderson Paiva | sem escala · desde 08:26 | [Encerrar turno]
15
- | ━━━━━━━━━━━━━━━━━━━━━━━━━┃|
12
+ Luiz entrega |··········[↻ 20:39–00:00]···|
13
+ atrasado · era 20:39 | ┃ agora|
14
+ [Iniciar agora]
15
+ Anderson Paiva | sem escala · desde 08:26 |
16
+ [Encerrar turno] | ━━━━━━━━━━━━━━━━━━━━━━━━━┃|
16
17
  ```
17
18
 
18
19
  Times are **hours from the day's midnight**, fractions included: `20.75` is 20:45 and a value past
@@ -42,7 +43,7 @@ Times are **hours from the day's midnight**, fractions included: `20.75` is 20:4
42
43
 
43
44
  | Prop | Type | Description |
44
45
  |---|---|---|
45
- | `rows` | `DayRulerRow[]` | `id`, `label`, `meta?`, `metaTone?`, `nameAdornment?` (node beside the name — a "Freela" tag; it moves under the name when there is no room for both), `chips?` (node — one line, a chip that does not fit shrinks and cuts its label), `actions?` (node — "Encerrar turno", "Iniciar agora"), `blocks` (`id`, `start`, `end`, `label`, `shortLabel?`, `tone`, `movable?`, `resizable?`, `selected?`, `title?`, `breakSpan?` — `{ start, end }` in ruler hours, a meal break drawn as a hatched gap inside the block: it travels with a moved block and is cut to its ends while one is dragged; a span that falls outside the block, is empty or is reversed (`end <= start`) draws nothing; its words are the host's, in `label` / `title`), `actual?` (`start`, `end`), `actualLabel?`, `group?` (a new value starts a new heading), `onLabelClick?`, `labelAccessible?`. |
46
+ | `rows` | `DayRulerRow[]` | `id`, `label`, `meta?`, `metaTone?`, `nameAdornment?` (node beside the name — a "Freela" tag; it moves under the name when there is no room for both), `chips?` (node — one line, a chip that does not fit shrinks and cuts its label), `actions?` (node — "Encerrar turno", "Iniciar agora"; under the name on a computer, beside it when stacked — never a column of their own), `blocks` (`id`, `start`, `end`, `label` (drawn whole or not at all — see Layout), `shortLabel?`, `tone`, `movable?`, `resizable?`, `selected?`, `title?`, `breakSpan?` — `{ start, end }` in ruler hours, a meal break drawn as a hatched gap inside the block: it travels with a moved block and is cut to its ends while one is dragged; a span that falls outside the block, is empty or is reversed (`end <= start`) draws nothing; its words are the host's, in `label` / `title`), `actual?` (`start`, `end`), `actualLabel?`, `group?` (a new value starts a new heading), `onLabelClick?`, `labelAccessible?`. |
46
47
  | `from` / `to` | `number` (default 6 / 26) | The ruler's span. |
47
48
  | `now` | `number?` | The "agora" line, when on the ruler. |
48
49
  | `coverage` | `number[]?` | People per hour, index 0 = `from…from+1`. |
@@ -57,27 +58,50 @@ Times are **hours from the day's midnight**, fractions included: `20.75` is 20:4
57
58
  | `loading` / `error` / `empty` | `boolean` / node / node | Skeleton rows with `aria-busy`; the host's error; the host's empty sentence. |
58
59
  | `aria-label` | `string?` | The region's name. |
59
60
  | `labels` | `Partial<DayRulerLabels>` | `createTrack(row)`, `moved(block, range)`, `resized(block, range)`, `refused`, `loading`, `footerMarked`. Default: the English pack; pt-BR: `DAY_RULER_LABELS['pt-BR']` from `@12-apps/ui/data-display/DayRuler`. |
60
- | `dataTestId` | `string?` (default `day-ruler`) | Parts: `-coverage`, `-axis`, `-footer-${footerRowId}`, `-row-${rowId}`, `-track-${rowId}`, `-block-${blockId}`, `-handle-l/-r-${blockId}`, `-break-${blockId}`, `-actual-${rowId}`, `-now`, `-preview`, `-live`, `-loading`. |
61
+ | `dataTestId` | `string?` (default `day-ruler`) | Parts: `-coverage`, `-axis`, `-footer-${footerRowId}`, `-row-${rowId}`, `-actions-${rowId}` (only on a row with actions), `-track-${rowId}`, `-block-${blockId}`, `-handle-l/-r-${blockId}`, `-break-${blockId}`, `-actual-${rowId}`, `-now`, `-preview`, `-live`, `-loading`. |
61
62
 
62
63
  Tones are `ScheduleGrid`'s; on the ruler a `plan` block is a dashed outline over a faint ground.
63
64
 
64
65
  ## Layout
65
66
 
66
- - **Computer:** name 220px · track (`minmax(0, 1fr)`) · actions 220px (none when no row has
67
- `actions`), 16px gap; a hairline between rows. Track 40px tall with a line every hour; blocks 24px at +4px; the actual bar 6px
68
- at +31px; the now line the track's full height.
67
+ - **Computer:** name 220px · track (`minmax(0, 1fr)`), 16px gap; a hairline between rows. A
68
+ row's `actions` sit in the name's column, under the name, meta and chips (8px above them) —
69
+ **never a column of their own** (FUT-3831). A column reserved for the one row with an "Iniciar
70
+ agora" used to leave every other row a blank 236px band; a column per row (or per group) would
71
+ have given those rows a wider track and put their hours out of line with the axis. So every
72
+ track, with actions or without, is the same width and the hours line up down the whole ruler.
73
+ Track 40px tall with a line every hour; blocks 24px at +4px; the actual bar 6px at +31px; the
74
+ now line the track's full height.
75
+ - **The now line is drawn UNDER the blocks** (FUT-3831): it shows above and below each bar, where
76
+ it overhangs the track, and never through a block's label — it used to run through a late
77
+ block's "07:30–1|3:00". On a block whose fill is not opaque (the dashed `plan`, the `away` /
78
+ `uncovered` outline) the label takes an opaque ground of its own (the fill flattened over the
79
+ cell's ground), so the line may show through the bar but not through its text.
69
80
  - **Stacked (a container under 800px: a phone, a tablet in portrait)** — a container query, not a
70
81
  media query: name, meta, chips and the actions on top, the track under them at full width, 30px
71
82
  tall with a line every `tickHours`; the coverage row (its label above the counts) and the hour
72
83
  axis span the same full width. **Under 600px** (a phone-sized pane) there is no coverage row and
73
- no axis. Every bound is "from that width up". A block whose range does not fit it, by pixels,
74
- says only its `shortLabel` (its start), never a cut "20:39–00:". The 20px blocks and the 24px name buttons
75
- answer a 40px finger (an invisible hit area), the look unchanged.
84
+ no axis. Every bound is "from that width up". The queries read the ruler's own named container
85
+ (`day-ruler`).
86
+ - **A block's text is drawn whole or not at all, at every width** (FUT-3831). By pixels: the
87
+ `label` when it fits the bar; else the `shortLabel` (its start, "20:39"); else **nothing** — the
88
+ bar stays empty rather than showing a cut "20:" or "20:39–00:". 12px is the type's floor, so a
89
+ text is never drawn smaller to squeeze in. Whatever the bar shows, its tooltip (`title`, default
90
+ `label`) and its accessible name still carry the whole `label`.
91
+ - **Hit band (stacked):** the 20px bars and the 24px name buttons answer a 40px finger, the look
92
+ unchanged. A bar's invisible band is 40px tall and **at least 40px wide, centred on the bar** —
93
+ a 1-hour turno at 320 is a 16px bar (FUT-3831). Where two bars are closer than that, each band
94
+ stops at the **midpoint of the gap** between them, so neither takes a tap meant for the other;
95
+ bars that touch or overlap give each other nothing past their own edges, and no band reaches
96
+ past the track's ends. A press in the band is a press on the block: a tap opens it, a mouse drag
97
+ moves it (a drag-create starts only on track the bands leave free). The end handles sit above
98
+ the band, so a press on a bar's end still resizes it.
76
99
 
77
100
  ## Accessibility
78
101
 
79
102
  Rows are a `list` per group. Blocks are native buttons with `aria-keyshortcuts`; the `selected`
80
- block is `aria-current="true"`. An interactive
103
+ block is `aria-current="true"`. A block that shows less than its `label` (its start, or nothing)
104
+ is named by the whole `label` (`aria-label`). An interactive
81
105
  track is a focusable `group` named `labels.createTrack(row)`; Enter or Space creates.
82
106
 
83
107
  ## Related
@@ -3,14 +3,18 @@
3
3
  * 1280 and the phone rows), in px. The web reads them through `rem`/`sxRem`.
4
4
  */
5
5
  export const DAY_RULER_METRICS = {
6
- /** The name and the actions columns on a computer. */
6
+ /** The name column on a computer; a row's actions sit in it, under the name (FUT-3831). */
7
7
  sideColumn: 220,
8
8
  gap: 16,
9
+ /** Between the name (with its meta and chips) and the row's actions under it, on a computer. */
10
+ actionsTop: 8,
9
11
  /**
10
12
  * Below this container width a row stacks: name and actions on top, the
11
- * track under them. Two 220px side columns, their gaps and a track of at
12
- * least 320px (where the hour labels every 4 hours do not collide): 792,
13
- * rounded up.
13
+ * track under them. Measured when the actions had a 220px column of their
14
+ * own (two 220px side columns, their gaps and a track of at least 320px,
15
+ * where the hour labels every 4 hours do not collide: 792, rounded up); kept
16
+ * when they moved under the name (FUT-3831) so the bound every host was
17
+ * checked at did not move.
14
18
  */
15
19
  compactBelow: 800,
16
20
  /**
@@ -21,6 +25,12 @@ export const DAY_RULER_METRICS = {
21
25
  phoneBelow: 600,
22
26
  rowPaddingY: 8,
23
27
  track: { height: 40, compactHeight: 30, radius: 8, compactRadius: 6 },
28
+ /**
29
+ * `hitArea`: on a narrow ruler the invisible hit band round a bar is this
30
+ * tall and at least this wide, centred on the bar and cut at the midpoint to
31
+ * a neighbour (FUT-3831). `font` is the floor: a text that does not fit at
32
+ * it is not drawn, never drawn smaller.
33
+ */
24
34
  block: { top: 4, height: 24, compactTop: 3, compactHeight: 20, radius: 6, paddingX: 6, font: 12, hitArea: 40 },
25
35
  /** The end handles' hit area. */
26
36
  handle: 10,
@@ -37,7 +47,7 @@ export const DAY_RULER_METRICS = {
37
47
  ringOffset: 1,
38
48
  /** The loading row's name bar. */
39
49
  skeletonNameRadius: 4,
40
- /** Where a block's text cannot be measured, one shorter than this share of the ruler says only its start on a narrow ruler. */
50
+ /** Where a block's text cannot be measured, one shorter than this share of the ruler says only its start. */
41
51
  narrowShare: 0.3,
42
52
  /** The shortest block a resize leaves, in hours. */
43
53
  minHours: 0.5,
@@ -1,13 +1,12 @@
1
1
  import Box from '@mui/material/Box/index.js';
2
2
  import type * as React from 'react';
3
- import { useRef } from 'react';
3
+ import { Fragment, isValidElement, useRef } from 'react';
4
4
 
5
5
  import { ScheduleNameCell } from '../ScheduleGrid/ScheduleNameCell';
6
6
  import type { RulerAbilities, RulerScale } from './DayRuler.keyboard';
7
7
  import { DAY_RULER_METRICS as M } from './DayRuler.metrics';
8
- import { useTextFits } from './useTextFits';
9
- import { formatHour, formatRange, percentOf, ticksOf } from './day-ruler-model';
10
- import { SR_ONLY_SX } from '../ScheduleGrid/ScheduleGrid.styles';
8
+ import { useTextFit } from './useTextFits';
9
+ import { formatHour, formatRange, hitBoundsOf, percentOf, ticksOf, type BlockTextFit } from './day-ruler-model';
11
10
  import {
12
11
  actionsSx,
13
12
  actualLabelSx,
@@ -18,18 +17,16 @@ import {
18
17
  coverageCellSx,
19
18
  coverageLabelSx,
20
19
  coverageSx,
21
- footerCellSx,
22
- footerNoteSx,
23
20
  handleSx,
24
21
  lineSx,
25
22
  nowSx,
26
23
  rowSx,
27
24
  trackSx,
28
- trendSx,
29
25
  whoSx,
26
+ type HitBounds,
30
27
  type Span,
31
28
  } from './DayRuler.styles';
32
- import type { DayRulerBlock, DayRulerFooterCell, DayRulerFooterRow, DayRulerRow, RulerHour } from './DayRuler.types';
29
+ import type { DayRulerBlock, DayRulerRow, RulerHour } from './DayRuler.types';
33
30
  import type { RulerDrag, RulerDragApi } from './useRulerDrag';
34
31
 
35
32
  export const spanOf = (start: RulerHour, end: RulerHour, scale: RulerScale): Span => {
@@ -71,6 +68,8 @@ interface BlockProps {
71
68
  span: Span;
72
69
  /** The hours it is drawn at (its own, or a drag's). */
73
70
  at: { start: RulerHour; end: RulerHour };
71
+ /** How far its hit band may reach on a narrow ruler, in % of the track (the midpoints to its neighbours). */
72
+ hit: HitBounds;
74
73
  text: string;
75
74
  ctx: RowContext;
76
75
  testId: string;
@@ -98,25 +97,39 @@ function BreakBand({ block, at, testId }: { block: DayRulerBlock; at: BlockProps
98
97
  return <Box component="span" aria-hidden sx={breakSx(gap)} data-dr-break="" data-testid={testId} />;
99
98
  }
100
99
 
101
- function RulerBlock({ row, block, span, at, text, ctx, testId }: BlockProps): React.JSX.Element {
100
+ /** Where nothing can be measured, a block shorter than the prototype's 30% of the ruler says its start. */
101
+ function fallbackFit(block: DayRulerBlock, scale: RulerScale): BlockTextFit {
102
+ return (block.end - block.start) / (scale.to - scale.from) >= M.narrowShare ? 'full' : 'short';
103
+ }
104
+
105
+ /**
106
+ * What the bar's fit puts on it: `data-fit` for the styles, and — while the bar
107
+ * shows less than its full text — that full text as its name, so the name still
108
+ * says all of it.
109
+ */
110
+ function fitProps(fit: BlockTextFit, text: string): { 'aria-label'?: string; 'data-fit'?: BlockTextFit } {
111
+ return fit === 'full' ? {} : { 'aria-label': text, 'data-fit': fit };
112
+ }
113
+
114
+ function RulerBlock({ row, block, span, at, hit, text, ctx, testId }: BlockProps): React.JSX.Element {
102
115
  const { can, dragApi, scale } = ctx;
103
116
  const movable = can.move && Boolean(block.movable);
104
117
  const resizable = can.resize && Boolean(block.resizable);
105
118
  const ref = useRef<HTMLButtonElement | null>(null);
106
- // Short by pixels; where nothing can be measured, by the prototype's 30% rule.
107
- const fits = useTextFits(ref, text, (block.end - block.start) / (scale.to - scale.from) >= M.narrowShare);
119
+ const short = block.shortLabel ?? text;
120
+ const fit = useTextFit(ref, text, short, fallbackFit(block, scale));
108
121
  const press = (mode: 'move' | 'start' | 'end') => (event: React.PointerEvent<HTMLElement>) =>
109
122
  dragApi.startBlock(event, row.id, block, mode);
110
123
  return (
111
124
  <Box
112
125
  component="button"
113
126
  type="button"
114
- sx={blockSx(block.tone, span, { selected: Boolean(block.selected), movable })}
127
+ sx={blockSx(block.tone, span, { selected: Boolean(block.selected), movable, hit })}
115
128
  title={block.title ?? block.label}
129
+ {...fitProps(fit, text)}
116
130
  aria-keyshortcuts={shortcutsOf(movable, resizable)}
117
131
  aria-current={block.selected ? 'true' : undefined}
118
132
  ref={ref}
119
- data-narrow={fits ? undefined : ''}
120
133
  data-tone={block.tone}
121
134
  data-testid={testId}
122
135
  onPointerDown={movable ? press('move') : undefined}
@@ -131,7 +144,7 @@ function RulerBlock({ row, block, span, at, text, ctx, testId }: BlockProps): Re
131
144
  {text}
132
145
  </span>
133
146
  <span data-dr-text="short" title={text}>
134
- {block.shortLabel ?? text}
147
+ {short}
135
148
  </span>
136
149
  {resizable ? <Handle side="r" onPointerDown={press('end')} testId={`${ctx.testId}-handle-r-${block.id}`} /> : null}
137
150
  </Box>
@@ -141,39 +154,51 @@ function RulerBlock({ row, block, span, at, text, ctx, testId }: BlockProps): Re
141
154
  /** A row's blocks, each where a drag has it now (with its live hours) or where it is. */
142
155
  function Blocks({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {
143
156
  const mine = ctx.drag && ctx.drag.rowId === row.id ? ctx.drag : null;
157
+ const drawn = row.blocks.map((block) => {
158
+ const live = mine?.blockId === block.id ? mine : null;
159
+ const start = live ? live.start : block.start;
160
+ const end = live ? live.end : block.end;
161
+ return { block, live, start, end, span: spanOf(start, end, ctx.scale) };
162
+ });
163
+ const preview = mine && mine.mode === 'create' ? previewSpan(mine, ctx.scale) : null;
164
+ // Every bar drawn on the row, the create's preview included, bounds its neighbours' hit bands.
165
+ const hits = hitBoundsOf([...drawn.map((d) => d.span), ...(preview ? [preview] : [])]);
144
166
  return (
145
167
  <>
146
- {row.blocks.map((block) => {
147
- const live = mine?.blockId === block.id ? mine : null;
148
- const start = live ? live.start : block.start;
149
- const end = live ? live.end : block.end;
150
- return (
151
- <RulerBlock
152
- key={block.id}
153
- row={row}
154
- block={block}
155
- span={spanOf(start, end, ctx.scale)}
156
- at={{ start, end }}
157
- text={live?.moved ? formatRange(start, end) : block.label}
158
- ctx={ctx}
159
- testId={`${ctx.testId}-block-${block.id}`}
160
- />
161
- );
162
- })}
163
- {mine && mine.mode === 'create' ? <CreatePreview row={row} drag={mine} ctx={ctx} /> : null}
168
+ {drawn.map(({ block, live, start, end, span }, index) => (
169
+ <RulerBlock
170
+ key={block.id}
171
+ row={row}
172
+ block={block}
173
+ span={span}
174
+ at={{ start, end }}
175
+ hit={hits[index]!}
176
+ text={live?.moved ? formatRange(start, end) : block.label}
177
+ ctx={ctx}
178
+ testId={`${ctx.testId}-block-${block.id}`}
179
+ />
180
+ ))}
181
+ {mine && preview ? <CreatePreview row={row} drag={mine} span={preview} hit={hits[drawn.length]!} ctx={ctx} /> : null}
164
182
  </>
165
183
  );
166
184
  }
167
185
 
186
+ /** Where a create drag's preview is drawn: at least one snap long. */
187
+ function previewSpan(drag: RulerDrag, scale: RulerScale): Span {
188
+ return spanOf(drag.start, Math.max(drag.end, drag.start + scale.snap), scale);
189
+ }
190
+
168
191
  /** The block a create drag is drawing, with its live hours. */
169
- function CreatePreview({ row, drag, ctx }: { row: DayRulerRow; drag: RulerDrag; ctx: RowContext }): React.JSX.Element {
192
+ function CreatePreview(props: { row: DayRulerRow; drag: RulerDrag; span: Span; hit: HitBounds; ctx: RowContext }): React.JSX.Element {
193
+ const { row, drag, span, hit, ctx } = props;
170
194
  const range = formatRange(drag.start, drag.end);
171
195
  return (
172
196
  <RulerBlock
173
197
  row={row}
174
198
  block={{ id: '\u0000new', start: drag.start, end: drag.end, label: range, tone: 'plan' }}
175
- span={spanOf(drag.start, Math.max(drag.end, drag.start + ctx.scale.snap), ctx.scale)}
199
+ span={span}
176
200
  at={{ start: drag.start, end: drag.end }}
201
+ hit={hit}
177
202
  text={range}
178
203
  ctx={ctx}
179
204
  testId={`${ctx.testId}-preview`}
@@ -197,7 +222,7 @@ function Actual({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.
197
222
  );
198
223
  }
199
224
 
200
- /** A row's track: the actual bar, its blocks (or the one being dragged, live) and the now line. */
225
+ /** A row's track: the actual bar, the now line under its blocks (or the one being dragged, live). */
201
226
  function Track({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {
202
227
  const { scale, now, can, dragApi } = ctx;
203
228
  const showNow = now !== undefined && now >= scale.from && now <= scale.to;
@@ -217,19 +242,42 @@ function Track({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.E
217
242
  }}
218
243
  >
219
244
  <Actual row={row} ctx={ctx} />
220
- <Blocks row={row} ctx={ctx} />
245
+ {/* Before the blocks, so it paints under them and never through a label. */}
221
246
  {showNow ? <Box sx={nowSx(percentOf(now, scale.from, scale.to))} data-testid={`${ctx.testId}-now`} /> : null}
247
+ <Blocks row={row} ctx={ctx} />
222
248
  </Box>
223
249
  );
224
250
  }
225
251
 
226
- /** One person: name, meta and chips · the track · the row's actions. */
252
+ /**
253
+ * Whether a node draws anything: nothing, a boolean, an empty string, an empty
254
+ * array and an empty fragment do not — the actions slot (and its 8px above)
255
+ * is rendered only for content.
256
+ */
257
+ export function hasContent(node: React.ReactNode): boolean {
258
+ if (node === null || node === undefined || typeof node === 'boolean' || node === '') return false;
259
+ if (Array.isArray(node)) return node.some((child) => hasContent(child as React.ReactNode));
260
+ if (isValidElement<{ children?: React.ReactNode }>(node) && node.type === Fragment) return hasContent(node.props.children);
261
+ return true;
262
+ }
263
+
264
+ /**
265
+ * One person: name, meta and chips, the row's actions under them · the track.
266
+ * The actions live in the name's column, never a column of their own: a
267
+ * column reserved for one row's "Iniciar agora" left every other row a blank
268
+ * band, and a per-row one would have cut the hours out of line (FUT-3831).
269
+ */
227
270
  export function RulerRow({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {
271
+ const hasActions = hasContent(row.actions);
228
272
  return (
229
273
  <Box sx={rowSx} data-testid={`${ctx.testId}-row-${row.id}`} role="listitem">
230
274
  <ScheduleNameCell row={row} sx={whoSx} sizes={{ name: M.name, meta: M.meta, chipsTop: 0.75 }} />
231
275
  <Track row={row} ctx={ctx} />
232
- <Box sx={actionsSx}>{row.actions}</Box>
276
+ {hasActions ? (
277
+ <Box sx={actionsSx} data-testid={`${ctx.testId}-actions-${row.id}`}>
278
+ {row.actions}
279
+ </Box>
280
+ ) : null}
233
281
  </Box>
234
282
  );
235
283
  }
@@ -253,126 +301,10 @@ export function Coverage(props: { coverage: readonly number[]; label?: string; s
253
301
  );
254
302
  })}
255
303
  </Box>
256
- <span />
257
304
  </Box>
258
305
  );
259
306
  }
260
307
 
261
- /** The points of a trend over the hours' centres, in a 100 × 40 box: the highest value near the top, `null` breaks nothing (it is skipped). */
262
- export function trendPoints(values: readonly (number | null)[], hours: number): string {
263
- const present = values.slice(0, hours).filter((value): value is number => value !== null);
264
- const max = Math.max(0, ...present);
265
- return values
266
- .slice(0, hours)
267
- .flatMap((value, index) => {
268
- if (value === null) return [];
269
- const x = ((index + 0.5) * 100) / hours;
270
- const y = max > 0 ? 38 - (value / max) * 34 : 38;
271
- return [`${x.toFixed(2)},${y.toFixed(2)}`];
272
- })
273
- .join(' ');
274
- }
275
-
276
- /** One hour's default words: "20:00 · 4"; just the hour when it says nothing. */
277
- const hourWords = (hour: RulerHour, text: string): string => (text ? `${formatHour(hour)} · ${text}` : formatHour(hour));
278
-
279
- /** What a reader hears for a marked hour: the host's `title`, else the hour, its count and that it is short. */
280
- function markedWords(cell: DayRulerFooterCell, hour: RulerHour, marked: string): string {
281
- return cell.title ?? `${hourWords(hour, cell.text)} · ${marked}`;
282
- }
283
-
284
- interface FooterContext {
285
- hours: number;
286
- from: RulerHour;
287
- /** `labels.footerMarked`. */
288
- marked: string;
289
- }
290
-
291
- function FooterCells({ cells, ctx }: { cells: readonly DayRulerFooterCell[]; ctx: FooterContext }): React.JSX.Element {
292
- return (
293
- <Box sx={coverageSx(ctx.hours)}>
294
- {Array.from({ length: ctx.hours }, (_, index) => {
295
- const cell = cells[index] ?? { text: '' };
296
- const hour = ctx.from + index;
297
- return (
298
- <Box
299
- key={index}
300
- component="span"
301
- sx={footerCellSx(Boolean(cell.marked))}
302
- title={cell.marked ? markedWords(cell, hour, ctx.marked) : (cell.title ?? hourWords(hour, cell.text))}
303
- data-marked={cell.marked ? '' : undefined}
304
- >
305
- {/* A marked hour is said in words too, not only by its outline and a tooltip touch never shows. */}
306
- {cell.marked ? (
307
- <>
308
- <span aria-hidden>{cell.text}</span>
309
- <Box component="span" sx={SR_ONLY_SX}>
310
- {markedWords(cell, hour, ctx.marked)}
311
- </Box>
312
- </>
313
- ) : (
314
- cell.text
315
- )}
316
- </Box>
317
- );
318
- })}
319
- </Box>
320
- );
321
- }
322
-
323
- /** A trend's figures in words, for the reader the drawn line does not reach: "11:00 4 · 12:00 6". */
324
- function trendWords(values: readonly (number | null)[], ctx: FooterContext): string {
325
- return values
326
- .slice(0, ctx.hours)
327
- .flatMap((value, index) => (value === null ? [] : [`${formatHour(ctx.from + index)} ${Math.round(value * 10) / 10}`]))
328
- .join(' · ');
329
- }
330
-
331
- function FooterFigures({ row, ctx }: { row: DayRulerFooterRow; ctx: FooterContext }): React.JSX.Element {
332
- if (row.trend) {
333
- return (
334
- <Box component="span" sx={{ display: 'block', minWidth: 0 }}>
335
- <Box component="svg" viewBox="0 0 100 40" preserveAspectRatio="none" sx={trendSx} aria-hidden>
336
- <polyline fill="none" stroke="currentColor" strokeWidth={1.5} vectorEffect="non-scaling-stroke" points={trendPoints(row.trend, ctx.hours)} />
337
- </Box>
338
- <Box component="span" sx={SR_ONLY_SX}>
339
- {trendWords(row.trend, ctx)}
340
- </Box>
341
- </Box>
342
- );
343
- }
344
- if (row.note !== undefined) {
345
- return (
346
- <Box component="span" sx={footerNoteSx(Boolean(row.marked))} data-marked={row.marked ? '' : undefined}>
347
- {row.note}
348
- </Box>
349
- );
350
- }
351
- return <FooterCells cells={row.cells ?? []} ctx={ctx} />;
352
- }
353
-
354
- /**
355
- * A group's lines under its rows (FUT-3882): counts per hour, a trend, or a
356
- * sentence, on the tracks' columns. `marked` is `labels.footerMarked`.
357
- */
358
- export function GroupFooter(props: { rows: readonly DayRulerFooterRow[]; scale: RulerScale; testId: string; marked: string }): React.JSX.Element {
359
- const { rows, scale, testId } = props;
360
- const ctx: FooterContext = { hours: scale.to - scale.from, from: scale.from, marked: props.marked };
361
- return (
362
- <>
363
- {rows.map((row) => (
364
- <Box key={row.id} sx={lineSx} data-testid={`${testId}-footer-${row.id}`}>
365
- <Box component="span" sx={coverageLabelSx}>
366
- {row.label}
367
- </Box>
368
- <FooterFigures row={row} ctx={ctx} />
369
- <span />
370
- </Box>
371
- ))}
372
- </>
373
- );
374
- }
375
-
376
308
  /** The hour labels under the coverage row. */
377
309
  export function Axis({ scale, testId }: { scale: RulerScale; testId: string }): React.JSX.Element {
378
310
  return (
@@ -385,7 +317,6 @@ export function Axis({ scale, testId }: { scale: RulerScale; testId: string }):
385
317
  </span>
386
318
  ))}
387
319
  </Box>
388
- <span />
389
320
  </Box>
390
321
  );
391
322
  }