@12-apps/ui 6.52.0 → 6.53.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 (270) hide show
  1. package/components-catalog.md +1 -1
  2. package/dist/_internal/components/data-display/Avatar/Avatar.js +3 -3
  3. package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
  4. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +1 -1
  5. package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +1 -1
  6. package/dist/_internal/components/data-display/Chart/SpecChart.js +3 -3
  7. package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
  8. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +24 -24
  9. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +26 -26
  10. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +1 -1
  11. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +4 -4
  12. package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +3 -3
  13. package/dist/_internal/components/data-display/DataViews/base-list-card.js +6 -6
  14. package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +6 -6
  15. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +2 -2
  16. package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +9 -9
  17. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +3 -3
  18. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +21 -21
  19. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +11 -11
  20. package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +9 -9
  21. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +10 -10
  22. package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +2 -2
  23. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +6 -6
  24. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +2 -2
  25. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +5 -5
  26. package/dist/_internal/components/data-display/DataViews/list-card-rails.js +2 -2
  27. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
  28. package/dist/_internal/components/data-display/FleetMap/FleetBody.js +4 -4
  29. package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +3 -3
  30. package/dist/_internal/components/data-display/FleetMap/FleetMap.fixtures.js +2 -2
  31. package/dist/_internal/components/data-display/FleetMap/FleetMap.js +5 -5
  32. package/dist/_internal/components/data-display/HoverCard/HoverCard.js +2 -2
  33. package/dist/_internal/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.js +3 -1
  34. package/dist/_internal/components/data-display/InteractiveTooltip/InteractiveTooltip.js +2 -2
  35. package/dist/_internal/components/data-display/Lightbox/Lightbox.js +3 -3
  36. package/dist/_internal/components/data-display/MapPreview/MapPreview.js +2 -2
  37. package/dist/_internal/components/data-display/Progress/Progress.js +2 -2
  38. package/dist/_internal/components/data-display/Sheet/Sheet.hooks.js +2 -2
  39. package/dist/_internal/components/data-display/Sheet/Sheet.js +4 -4
  40. package/dist/_internal/components/data-display/index.js +27 -27
  41. package/dist/_internal/components/feedback/index.js +3 -3
  42. package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.hooks.js +2 -2
  43. package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.js +5 -5
  44. package/dist/_internal/components/form/Autocomplete/Autocomplete.js +2 -2
  45. package/dist/_internal/components/form/Calendar/Calendar.hooks.js +2 -2
  46. package/dist/_internal/components/form/Calendar/Calendar.js +3 -3
  47. package/dist/_internal/components/form/CategorySelect/CategoryPanel.js +1 -1
  48. package/dist/_internal/components/form/CategorySelect/CategorySelect.js +4 -4
  49. package/dist/_internal/components/form/Command/Command.js +3 -3
  50. package/dist/_internal/components/form/DateRangePicker/DateRangePicker.js +5 -5
  51. package/dist/_internal/components/form/UploadButton/UploadButton.js +2 -2
  52. package/dist/_internal/components/form/index.js +22 -22
  53. package/dist/_internal/components/form/total-form/Fields/CategoryField.js +5 -5
  54. package/dist/_internal/components/form/total-form/Fields/index.js +9 -9
  55. package/dist/_internal/components/index.js +67 -66
  56. package/dist/_internal/components/layout/ScrollArea/ScrollArea.js +2 -2
  57. package/dist/_internal/components/layout/SettingCard/SettingCard.js +9 -4
  58. package/dist/_internal/components/layout/SettingCard/SettingCard.metrics.js +1 -1
  59. package/dist/_internal/components/layout/SettingCard/SettingCard.parts.js +3 -3
  60. package/dist/_internal/components/layout/SettingCard/SettingCard.styles.js +6 -2
  61. package/dist/_internal/components/layout/SettingCard/SettingCard.summary.js +63 -0
  62. package/dist/_internal/components/layout/SettingCard/SettingCard.summary.js.map +1 -0
  63. package/dist/_internal/components/layout/SettingCard/SettingGrid.js +4 -4
  64. package/dist/_internal/components/layout/SettingCard/SettingGroup.js +10 -5
  65. package/dist/_internal/components/layout/SettingCard/SettingSwitch.js +9 -4
  66. package/dist/_internal/components/layout/SettingCard/SettingToggle.js +10 -5
  67. package/dist/_internal/components/layout/index.js +18 -13
  68. package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +5 -5
  69. package/dist/_internal/components/navigation/index.js +5 -5
  70. package/dist/_internal/en-US.data-display.js +2 -2
  71. package/dist/_internal/en-US.layout.js +1 -1
  72. package/dist/_internal/pt-BR.data-display.js +2 -2
  73. package/dist/_internal/pt-BR.layout.js +1 -1
  74. package/dist/_internal/user-avatar/user-avatar.js +4 -4
  75. package/dist/charts.js +6 -6
  76. package/dist/{chunk-GXQDWHPN.js → chunk-2CJZLL5Y.js} +2 -2
  77. package/dist/{chunk-57VL5UX7.js → chunk-2JTZWQ25.js} +6 -4
  78. package/dist/chunk-2JTZWQ25.js.map +1 -0
  79. package/dist/{chunk-STTULW2K.js → chunk-2KVWOTEQ.js} +5 -5
  80. package/dist/{chunk-ISW5K72V.js → chunk-2ZRZTGWP.js} +5 -5
  81. package/dist/{chunk-33L26P4J.js → chunk-3ZKGNEP2.js} +5 -3
  82. package/dist/{chunk-33L26P4J.js.map → chunk-3ZKGNEP2.js.map} +1 -1
  83. package/dist/{chunk-2OTBMU5Z.js → chunk-44LYXLW7.js} +7 -7
  84. package/dist/{chunk-LYVTJLCF.js → chunk-56JF5PZK.js} +4 -4
  85. package/dist/{chunk-5KGABAQB.js → chunk-5752357K.js} +4 -4
  86. package/dist/{chunk-SPZAEMGW.js → chunk-5WX6U34F.js} +4 -4
  87. package/dist/{chunk-QHXMNSOZ.js → chunk-6L6VXRLJ.js} +2 -2
  88. package/dist/{chunk-VVFASSFY.js → chunk-6VGB4OX2.js} +3 -3
  89. package/dist/{chunk-FRP7NM6X.js → chunk-6WZUKV55.js} +2 -2
  90. package/dist/{chunk-QEINKMMX.js → chunk-7C763T3K.js} +2 -2
  91. package/dist/{chunk-PARTZ7XI.js → chunk-7JKDB5J5.js} +4 -4
  92. package/dist/{chunk-3U7WKBMH.js → chunk-AA2X5GOT.js} +5 -3
  93. package/dist/{chunk-3U7WKBMH.js.map → chunk-AA2X5GOT.js.map} +1 -1
  94. package/dist/{chunk-QYV7KLEN.js → chunk-ASGSC5UA.js} +5 -5
  95. package/dist/{chunk-7GQ2ZZBQ.js → chunk-BCTRC6AR.js} +2 -2
  96. package/dist/{chunk-XSHWEUDB.js → chunk-BE5Z36XZ.js} +4 -4
  97. package/dist/{chunk-IC7GHPMK.js → chunk-BFRVMCQM.js} +7 -2
  98. package/dist/chunk-BFRVMCQM.js.map +1 -0
  99. package/dist/{chunk-PM7SI4YT.js → chunk-BJOKL5PQ.js} +5 -5
  100. package/dist/{chunk-PGPPN63I.js → chunk-EMEXXAQI.js} +2 -2
  101. package/dist/{chunk-R6SBP5M5.js → chunk-GOWBYHYW.js} +4 -4
  102. package/dist/{chunk-LZQQ4JXY.js → chunk-HGVPPJKE.js} +8 -8
  103. package/dist/{chunk-XNBX3M35.js → chunk-HLCEKWFS.js} +4 -4
  104. package/dist/{chunk-4MGFUPQT.js → chunk-HOJL2GKW.js} +4 -4
  105. package/dist/{chunk-V5ZLUJV7.js → chunk-HRMB72E4.js} +2 -2
  106. package/dist/{chunk-CSDVRHUX.js → chunk-HWBKMUMV.js} +4 -4
  107. package/dist/{chunk-KN3WYEH2.js → chunk-IA6F7DU6.js} +4 -4
  108. package/dist/{chunk-BE5FGUJS.js → chunk-IM5SR7G2.js} +2 -2
  109. package/dist/{chunk-PJVDIUT2.js → chunk-IRNKHHQM.js} +3 -3
  110. package/dist/{chunk-4XET5MWR.js → chunk-KKE4SHRS.js} +2 -2
  111. package/dist/{chunk-ZTTFLIPH.js → chunk-L624DFJV.js} +2 -2
  112. package/dist/{chunk-OYT7YXQL.js → chunk-MGPN5TZE.js} +5 -5
  113. package/dist/{chunk-ERY6D2OV.js → chunk-MWPXKZ7O.js} +4 -4
  114. package/dist/{chunk-SCCE6VWY.js → chunk-N2Z7NHD5.js} +2 -2
  115. package/dist/chunk-N4R6T5K7.js +95 -0
  116. package/dist/chunk-N4R6T5K7.js.map +1 -0
  117. package/dist/{chunk-GNHXFJRR.js → chunk-N6EEY7V3.js} +2 -2
  118. package/dist/{chunk-OEBHWQSX.js → chunk-OESAYTNG.js} +9 -4
  119. package/dist/chunk-OESAYTNG.js.map +1 -0
  120. package/dist/{chunk-G5XHAN56.js → chunk-OT3KFZRE.js} +2 -2
  121. package/dist/{chunk-XAUKQZJK.js → chunk-QX4LMVL2.js} +2 -2
  122. package/dist/{chunk-S5UY5U73.js → chunk-QYJ25FWM.js} +20 -2
  123. package/dist/{chunk-S5UY5U73.js.map → chunk-QYJ25FWM.js.map} +1 -1
  124. package/dist/{chunk-6PYX6V3R.js → chunk-RJXVVLBO.js} +4 -4
  125. package/dist/{chunk-FRCBJLV5.js → chunk-RKKYSSAV.js} +5 -5
  126. package/dist/{chunk-WF7BZT6V.js → chunk-RR6BIHFX.js} +2 -2
  127. package/dist/{chunk-NGAU6PNB.js → chunk-RRVQVQF7.js} +5 -5
  128. package/dist/{chunk-F33YKDO2.js → chunk-TEST2NBS.js} +5 -5
  129. package/dist/{chunk-NKJSYZEU.js → chunk-V32626FV.js} +4 -4
  130. package/dist/{chunk-MLDA44NW.js → chunk-VGXSPUTD.js} +3 -3
  131. package/dist/{chunk-CTXF22BU.js → chunk-VODKYDC4.js} +5 -5
  132. package/dist/{chunk-DWJH3KXN.js → chunk-WAKGVAOB.js} +4 -4
  133. package/dist/{chunk-3DO5AJCG.js → chunk-WE2LNTR6.js} +12 -5
  134. package/dist/chunk-WE2LNTR6.js.map +1 -0
  135. package/dist/{chunk-SZA44EFA.js → chunk-WOEVBEGJ.js} +4 -4
  136. package/dist/{chunk-EOTV5BLM.js → chunk-WOP2DRMC.js} +2 -2
  137. package/dist/{chunk-E6JRPGMN.js → chunk-X3DVZNZH.js} +2 -2
  138. package/dist/{chunk-MLQYIJTO.js → chunk-XM7QCNZQ.js} +9 -3
  139. package/dist/chunk-XM7QCNZQ.js.map +1 -0
  140. package/dist/{chunk-P35CTP6L.js → chunk-Y3WB66XT.js} +7 -7
  141. package/dist/{chunk-DI46ZEWP.js → chunk-Y5QDXG5N.js} +20 -4
  142. package/dist/chunk-Y5QDXG5N.js.map +1 -0
  143. package/dist/{chunk-TNQDSTIA.js → chunk-Y6C4MYZE.js} +7 -7
  144. package/dist/{chunk-TBZKUZNW.js → chunk-ZPWU7Z6O.js} +3 -3
  145. package/dist/{chunk-QQSRZUT4.js → chunk-ZXEDKRRT.js} +4 -4
  146. package/dist/data-display/Avatar.js +3 -3
  147. package/dist/data-display/CardKit.js +8 -8
  148. package/dist/data-display/Chart.js +6 -6
  149. package/dist/data-display/DataViews.js +35 -35
  150. package/dist/data-display/FleetMap.js +5 -5
  151. package/dist/data-display/HoverCard.js +2 -2
  152. package/dist/data-display/InteractiveTooltip.js +2 -2
  153. package/dist/data-display/Lightbox.js +3 -3
  154. package/dist/data-display/MapPreview.js +2 -2
  155. package/dist/data-display/Progress.js +2 -2
  156. package/dist/data-display/Sheet.js +4 -4
  157. package/dist/en-US.js +2 -2
  158. package/dist/feedback/ConfirmAction.js +3 -3
  159. package/dist/form/AddressAutocomplete.js +5 -5
  160. package/dist/form/Autocomplete.js +2 -2
  161. package/dist/form/Calendar.js +3 -3
  162. package/dist/form/CategorySelect.js +4 -4
  163. package/dist/form/Command.js +3 -3
  164. package/dist/form/DateRangePicker.js +10 -10
  165. package/dist/form/UploadButton.js +2 -2
  166. package/dist/form/total-form.js +7 -7
  167. package/dist/layout/ContentToolbar.js +3 -3
  168. package/dist/layout/ScrollArea.js +2 -2
  169. package/dist/layout/SettingCard.js +13 -8
  170. package/dist/locales.js +4 -4
  171. package/dist/native/feedback/Dialog.js +86 -40
  172. package/dist/native/feedback/Dialog.js.map +1 -1
  173. package/dist/navigation/AppHeader.js +5 -5
  174. package/dist/pt-BR.js +2 -2
  175. package/dist/types/components/data-display/InteractiveTooltip/InteractiveTooltip.d.ts.map +1 -1
  176. package/dist/types/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.d.ts +18 -0
  177. package/dist/types/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.d.ts.map +1 -1
  178. package/dist/types/components/layout/SettingCard/SettingCard.d.ts.map +1 -1
  179. package/dist/types/components/layout/SettingCard/SettingCard.metrics.d.ts +5 -0
  180. package/dist/types/components/layout/SettingCard/SettingCard.metrics.d.ts.map +1 -1
  181. package/dist/types/components/layout/SettingCard/SettingCard.parts.d.ts +2 -0
  182. package/dist/types/components/layout/SettingCard/SettingCard.parts.d.ts.map +1 -1
  183. package/dist/types/components/layout/SettingCard/SettingCard.styles.d.ts +9 -2
  184. package/dist/types/components/layout/SettingCard/SettingCard.styles.d.ts.map +1 -1
  185. package/dist/types/components/layout/SettingCard/SettingCard.summary.d.ts +33 -0
  186. package/dist/types/components/layout/SettingCard/SettingCard.summary.d.ts.map +1 -0
  187. package/dist/types/components/layout/SettingCard/SettingSwitch.d.ts.map +1 -1
  188. package/dist/types/copy/layout.d.ts +8 -0
  189. package/dist/types/copy/layout.d.ts.map +1 -1
  190. package/dist/types/en-US.layout.d.ts.map +1 -1
  191. package/dist/types/pt-BR.layout.d.ts.map +1 -1
  192. package/dist/types-native/components/feedback/Dialog/Dialog.look.native.d.ts +12 -0
  193. package/dist/types-native/components/feedback/Dialog/Dialog.look.native.d.ts.map +1 -1
  194. package/dist/types-native/components/feedback/Dialog/Dialog.native.d.ts.map +1 -1
  195. package/dist/types-native/platform/keyboard-overlap.native.d.ts +36 -0
  196. package/dist/types-native/platform/keyboard-overlap.native.d.ts.map +1 -0
  197. package/dist/user-avatar.js +4 -4
  198. package/package.json +1 -1
  199. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +36 -0
  200. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +7 -2
  201. package/src/components/feedback/Dialog/Dialog.look.native.ts +17 -0
  202. package/src/components/feedback/Dialog/Dialog.native.tsx +12 -2
  203. package/src/components/layout/SettingCard/SettingCard.md +1 -1
  204. package/src/components/layout/SettingCard/SettingCard.metrics.ts +5 -0
  205. package/src/components/layout/SettingCard/SettingCard.parts.tsx +4 -0
  206. package/src/components/layout/SettingCard/SettingCard.styles.ts +17 -5
  207. package/src/components/layout/SettingCard/SettingCard.summary.tsx +100 -0
  208. package/src/components/layout/SettingCard/SettingCard.tsx +6 -2
  209. package/src/components/layout/SettingCard/SettingSwitch.tsx +11 -2
  210. package/src/components/layout/SettingCard/SettingToggle.md +1 -1
  211. package/src/copy/layout.ts +8 -0
  212. package/src/en-US.layout.ts +2 -0
  213. package/src/platform/keyboard-overlap.native.ts +77 -0
  214. package/src/pt-BR.layout.ts +2 -0
  215. package/dist/chunk-3DO5AJCG.js.map +0 -1
  216. package/dist/chunk-57VL5UX7.js.map +0 -1
  217. package/dist/chunk-DI46ZEWP.js.map +0 -1
  218. package/dist/chunk-IC7GHPMK.js.map +0 -1
  219. package/dist/chunk-MLQYIJTO.js.map +0 -1
  220. package/dist/chunk-OEBHWQSX.js.map +0 -1
  221. /package/dist/{chunk-GXQDWHPN.js.map → chunk-2CJZLL5Y.js.map} +0 -0
  222. /package/dist/{chunk-STTULW2K.js.map → chunk-2KVWOTEQ.js.map} +0 -0
  223. /package/dist/{chunk-ISW5K72V.js.map → chunk-2ZRZTGWP.js.map} +0 -0
  224. /package/dist/{chunk-2OTBMU5Z.js.map → chunk-44LYXLW7.js.map} +0 -0
  225. /package/dist/{chunk-LYVTJLCF.js.map → chunk-56JF5PZK.js.map} +0 -0
  226. /package/dist/{chunk-5KGABAQB.js.map → chunk-5752357K.js.map} +0 -0
  227. /package/dist/{chunk-SPZAEMGW.js.map → chunk-5WX6U34F.js.map} +0 -0
  228. /package/dist/{chunk-QHXMNSOZ.js.map → chunk-6L6VXRLJ.js.map} +0 -0
  229. /package/dist/{chunk-VVFASSFY.js.map → chunk-6VGB4OX2.js.map} +0 -0
  230. /package/dist/{chunk-FRP7NM6X.js.map → chunk-6WZUKV55.js.map} +0 -0
  231. /package/dist/{chunk-QEINKMMX.js.map → chunk-7C763T3K.js.map} +0 -0
  232. /package/dist/{chunk-PARTZ7XI.js.map → chunk-7JKDB5J5.js.map} +0 -0
  233. /package/dist/{chunk-QYV7KLEN.js.map → chunk-ASGSC5UA.js.map} +0 -0
  234. /package/dist/{chunk-7GQ2ZZBQ.js.map → chunk-BCTRC6AR.js.map} +0 -0
  235. /package/dist/{chunk-XSHWEUDB.js.map → chunk-BE5Z36XZ.js.map} +0 -0
  236. /package/dist/{chunk-PM7SI4YT.js.map → chunk-BJOKL5PQ.js.map} +0 -0
  237. /package/dist/{chunk-PGPPN63I.js.map → chunk-EMEXXAQI.js.map} +0 -0
  238. /package/dist/{chunk-R6SBP5M5.js.map → chunk-GOWBYHYW.js.map} +0 -0
  239. /package/dist/{chunk-LZQQ4JXY.js.map → chunk-HGVPPJKE.js.map} +0 -0
  240. /package/dist/{chunk-XNBX3M35.js.map → chunk-HLCEKWFS.js.map} +0 -0
  241. /package/dist/{chunk-4MGFUPQT.js.map → chunk-HOJL2GKW.js.map} +0 -0
  242. /package/dist/{chunk-V5ZLUJV7.js.map → chunk-HRMB72E4.js.map} +0 -0
  243. /package/dist/{chunk-CSDVRHUX.js.map → chunk-HWBKMUMV.js.map} +0 -0
  244. /package/dist/{chunk-KN3WYEH2.js.map → chunk-IA6F7DU6.js.map} +0 -0
  245. /package/dist/{chunk-BE5FGUJS.js.map → chunk-IM5SR7G2.js.map} +0 -0
  246. /package/dist/{chunk-PJVDIUT2.js.map → chunk-IRNKHHQM.js.map} +0 -0
  247. /package/dist/{chunk-4XET5MWR.js.map → chunk-KKE4SHRS.js.map} +0 -0
  248. /package/dist/{chunk-ZTTFLIPH.js.map → chunk-L624DFJV.js.map} +0 -0
  249. /package/dist/{chunk-OYT7YXQL.js.map → chunk-MGPN5TZE.js.map} +0 -0
  250. /package/dist/{chunk-ERY6D2OV.js.map → chunk-MWPXKZ7O.js.map} +0 -0
  251. /package/dist/{chunk-SCCE6VWY.js.map → chunk-N2Z7NHD5.js.map} +0 -0
  252. /package/dist/{chunk-GNHXFJRR.js.map → chunk-N6EEY7V3.js.map} +0 -0
  253. /package/dist/{chunk-G5XHAN56.js.map → chunk-OT3KFZRE.js.map} +0 -0
  254. /package/dist/{chunk-XAUKQZJK.js.map → chunk-QX4LMVL2.js.map} +0 -0
  255. /package/dist/{chunk-6PYX6V3R.js.map → chunk-RJXVVLBO.js.map} +0 -0
  256. /package/dist/{chunk-FRCBJLV5.js.map → chunk-RKKYSSAV.js.map} +0 -0
  257. /package/dist/{chunk-WF7BZT6V.js.map → chunk-RR6BIHFX.js.map} +0 -0
  258. /package/dist/{chunk-NGAU6PNB.js.map → chunk-RRVQVQF7.js.map} +0 -0
  259. /package/dist/{chunk-F33YKDO2.js.map → chunk-TEST2NBS.js.map} +0 -0
  260. /package/dist/{chunk-NKJSYZEU.js.map → chunk-V32626FV.js.map} +0 -0
  261. /package/dist/{chunk-MLDA44NW.js.map → chunk-VGXSPUTD.js.map} +0 -0
  262. /package/dist/{chunk-CTXF22BU.js.map → chunk-VODKYDC4.js.map} +0 -0
  263. /package/dist/{chunk-DWJH3KXN.js.map → chunk-WAKGVAOB.js.map} +0 -0
  264. /package/dist/{chunk-SZA44EFA.js.map → chunk-WOEVBEGJ.js.map} +0 -0
  265. /package/dist/{chunk-EOTV5BLM.js.map → chunk-WOP2DRMC.js.map} +0 -0
  266. /package/dist/{chunk-E6JRPGMN.js.map → chunk-X3DVZNZH.js.map} +0 -0
  267. /package/dist/{chunk-P35CTP6L.js.map → chunk-Y3WB66XT.js.map} +0 -0
  268. /package/dist/{chunk-TNQDSTIA.js.map → chunk-Y6C4MYZE.js.map} +0 -0
  269. /package/dist/{chunk-TBZKUZNW.js.map → chunk-ZPWU7Z6O.js.map} +0 -0
  270. /package/dist/{chunk-QQSRZUT4.js.map → chunk-ZXEDKRRT.js.map} +0 -0
@@ -29,6 +29,18 @@ export interface DialogLook {
29
29
  /** The paper's corner radius, for the pulse ring to follow. */
30
30
  radius: number;
31
31
  }
32
+ /**
33
+ * The overlay's bottom inset while the soft keyboard covers `overlap` dp of it
34
+ * (FUT-3021), or nothing while it covers none.
35
+ *
36
+ * The inset grows by exactly the overlap, on top of the margin the variant
37
+ * already keeps, so the space the paper may fill — its `maxHeight: '100%'`, or
38
+ * the `height: '100%'` of `fullscreen` and `drawer` — shrinks by the same
39
+ * amount and ends where the keyboard starts. A centred paper re-centres in what
40
+ * is left, and a body taller than that scrolls inside `DialogContent` with the
41
+ * actions still on screen.
42
+ */
43
+ export declare function dialogKeyboardLift(theme: UiTheme, variant: DialogVariant, overlap: number): ViewStyle | null;
32
44
  /**
33
45
  * Everything the dialog paints, decided once per render.
34
46
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.look.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.look.native.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAanF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;;;;;GAQG;AAEH,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAE/E;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAIxD;AAYD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AA0DD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,GAAG,UAAU,CAyBxE"}
1
+ {"version":3,"file":"Dialog.look.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.look.native.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAanF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;;;;;GAQG;AAEH,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAE/E;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAIxD;AAYD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AA0DD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAI5G;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,GAAG,UAAU,CAyBxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAiBzD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAwDlF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmE/D;yBAnEe,MAAM"}
1
+ {"version":3,"file":"Dialog.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAkBzD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAwDlF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA4E/D;yBA5Ee,MAAM"}
@@ -0,0 +1,36 @@
1
+ import { type LayoutChangeEvent } from 'react-native';
2
+ /**
3
+ * HOW FAR THE SOFT KEYBOARD COVERS A FULL-WINDOW VIEW, AND NOTHING MORE.
4
+ *
5
+ * React Native's `Modal` asks Android for `SOFT_INPUT_ADJUST_RESIZE`, and under
6
+ * edge-to-edge that asks for nothing: the window keeps its full height and the
7
+ * keyboard is drawn over it. iOS never resized a modal at all. So an overlay
8
+ * that fills the modal has its bottom under the keyboard, and whatever sits
9
+ * there — a dialog's actions — is hidden until the keyboard goes away.
10
+ *
11
+ * This reads the keyboard's own top edge from the `Keyboard` events and the
12
+ * view's own bottom edge from its layout, and answers the difference. Reading
13
+ * the OVERLAP rather than the keyboard's height is what keeps it right when the
14
+ * window DID resize (a host that is not edge-to-edge): the view then already
15
+ * ends at the keyboard, the overlap is zero, and nothing is lifted twice. No
16
+ * soft-input mode is set here, deliberately — a second one would fight the one
17
+ * `Modal` already asks for.
18
+ *
19
+ * The view's layout `y` is relative to its parent, so the view is expected to
20
+ * be the root of a window that starts at the top of the screen — which a
21
+ * `transparent` modal is, on iOS and under Android edge-to-edge alike.
22
+ */
23
+ export declare function keyboardOverlap(viewBottom: number | null, keyboardTop: number | null): number;
24
+ export interface KeyboardOverlap {
25
+ /** How many dp of the view the keyboard covers; `0` while it is down. */
26
+ overlap: number;
27
+ /** Goes on the view being measured. */
28
+ onLayout: (event: LayoutChangeEvent) => void;
29
+ }
30
+ /**
31
+ * The keyboard's overlap with the view `onLayout` is put on. Android re-sends
32
+ * `keyboardDidShow` when the keyboard changes height while it stays up (an
33
+ * emoji panel, a suggestion strip), so the overlap follows those too.
34
+ */
35
+ export declare function useKeyboardOverlap(): KeyboardOverlap;
36
+ //# sourceMappingURL=keyboard-overlap.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard-overlap.native.d.ts","sourceRoot":"","sources":["../../../src/platform/keyboard-overlap.native.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAG7F;AAQD,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAuBpD"}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  UserAvatar,
3
3
  UserMenu
4
- } from "./chunk-EOTV5BLM.js";
4
+ } from "./chunk-WOP2DRMC.js";
5
5
  import "./chunk-UDQSIRAP.js";
6
- import "./chunk-PGPPN63I.js";
7
- import "./chunk-TNQDSTIA.js";
6
+ import "./chunk-EMEXXAQI.js";
7
+ import "./chunk-Y6C4MYZE.js";
8
+ import "./chunk-MTEQEPNY.js";
8
9
  import "./chunk-6ZZGK5DT.js";
9
10
  import "./chunk-3ZVUNSFF.js";
10
- import "./chunk-MTEQEPNY.js";
11
11
  import "./chunk-VUHLNYAE.js";
12
12
  import "./chunk-2GSIEDGN.js";
13
13
  import "./chunk-WN5PNP6F.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.52.0",
3
+ "version": "6.53.0",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -97,3 +97,39 @@ export const usePinnedTooltip = ({ clickable, onPin, onUnpin }: PinOptions) => {
97
97
 
98
98
  return { isPinned, isControlledOpen, wrapperRef, handleClick };
99
99
  };
100
+
101
+ type OnOpen = NonNullable<InteractiveTooltipProps['onOpen']>;
102
+ type OnClose = NonNullable<InteractiveTooltipProps['onClose']>;
103
+
104
+ /**
105
+ * The hover half of the tooltip's open state, so the MUI Tooltip is controlled
106
+ * from its first render (FUT-3013). It used to receive `open={undefined}` while
107
+ * hovering and a boolean once pinned, which MUI reports as a component switching
108
+ * from uncontrolled to controlled on the first click. The caller's own
109
+ * onOpen/onClose still run.
110
+ *
111
+ * Unpinning also closes it: the hover listeners are off while pinned, so a
112
+ * hover state left `true` from before the pin would otherwise keep the tooltip
113
+ * open after the click outside that unpinned it.
114
+ */
115
+ export const useHoverOpen = (
116
+ isPinned: boolean,
117
+ onOpen?: OnOpen,
118
+ onClose?: OnClose,
119
+ ): { open: boolean; onOpen: OnOpen; onClose: OnClose } => {
120
+ const [open, setOpen] = useState(false);
121
+ useEffect(() => {
122
+ if (!isPinned) setOpen(false);
123
+ }, [isPinned]);
124
+ return {
125
+ open,
126
+ onOpen: (event) => {
127
+ setOpen(true);
128
+ onOpen?.(event);
129
+ },
130
+ onClose: (event) => {
131
+ setOpen(false);
132
+ onClose?.(event);
133
+ },
134
+ };
135
+ };
@@ -2,7 +2,7 @@ import MuiTooltip from '@mui/material/Tooltip/index.js';
2
2
  import { styled } from '@mui/material/styles/index.js';
3
3
  import React from 'react';
4
4
 
5
- import { resolveTooltipProps, usePinnedTooltip } from './InteractiveTooltip.hooks';
5
+ import { resolveTooltipProps, useHoverOpen, usePinnedTooltip } from './InteractiveTooltip.hooks';
6
6
  import {
7
7
  arrowColor,
8
8
  emphasisStyles,
@@ -58,6 +58,8 @@ export const InteractiveTooltip = React.forwardRef<HTMLDivElement, InteractiveTo
58
58
  onUnpin,
59
59
  className,
60
60
  children,
61
+ onOpen,
62
+ onClose,
61
63
  ...props
62
64
  } = resolveTooltipProps(rawProps);
63
65
 
@@ -66,6 +68,7 @@ export const InteractiveTooltip = React.forwardRef<HTMLDivElement, InteractiveTo
66
68
  onPin,
67
69
  onUnpin,
68
70
  });
71
+ const hover = useHoverOpen(isPinned, onOpen, onClose);
69
72
 
70
73
  // Determine which content to show
71
74
  const tooltipContent = isPinned ? pinnedContent : hoverContent;
@@ -94,7 +97,9 @@ export const InteractiveTooltip = React.forwardRef<HTMLDivElement, InteractiveTo
94
97
  glow={glow}
95
98
  pulse={pulse}
96
99
  title={tooltipContent}
97
- open={isPinned ? isControlledOpen : undefined}
100
+ open={isPinned ? isControlledOpen : hover.open}
101
+ onOpen={hover.onOpen}
102
+ onClose={hover.onClose}
98
103
  enterDelay={isPinned ? 0 : 100}
99
104
  leaveDelay={0}
100
105
  disableHoverListener={isPinned}
@@ -120,6 +120,23 @@ function overlayFor(theme: UiTheme, variant: DialogVariant): ViewStyle {
120
120
  return centredOverlay(theme);
121
121
  }
122
122
 
123
+ /**
124
+ * The overlay's bottom inset while the soft keyboard covers `overlap` dp of it
125
+ * (FUT-3021), or nothing while it covers none.
126
+ *
127
+ * The inset grows by exactly the overlap, on top of the margin the variant
128
+ * already keeps, so the space the paper may fill — its `maxHeight: '100%'`, or
129
+ * the `height: '100%'` of `fullscreen` and `drawer` — shrinks by the same
130
+ * amount and ends where the keyboard starts. A centred paper re-centres in what
131
+ * is left, and a body taller than that scrolls inside `DialogContent` with the
132
+ * actions still on screen.
133
+ */
134
+ export function dialogKeyboardLift(theme: UiTheme, variant: DialogVariant, overlap: number): ViewStyle | null {
135
+ if (overlap <= 0) return null;
136
+ const margin = variant === 'fullscreen' || variant === 'drawer' ? 0 : theme.spacing(DIALOG_MARGIN_UNITS);
137
+ return { paddingBottom: margin + overlap };
138
+ }
139
+
123
140
  /**
124
141
  * Everything the dialog paints, decided once per render.
125
142
  *
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import { Modal, Pressable, ScrollView, StyleSheet, View } from 'react-native';
3
3
 
4
4
  import { hasSpacingSlot, withDialogDefaults } from './Dialog.helpers';
5
- import { dialogBackdrop, dialogLook } from './Dialog.look.native';
5
+ import { dialogBackdrop, dialogKeyboardLift, dialogLook } from './Dialog.look.native';
6
6
  import { DIALOG_BODY_PADDING_UNITS, DIALOG_PULSE } from './Dialog.metrics';
7
7
  import type { DialogProps } from './Dialog.types.native';
8
8
  import {
@@ -12,6 +12,7 @@ import {
12
12
  DialogTitledContext,
13
13
  dialogBodyTextStyle,
14
14
  } from './DialogParts.native';
15
+ import { useKeyboardOverlap } from '../../../platform/keyboard-overlap.native';
15
16
  import { PulseRing } from '../../../platform/pulse-ring.native';
16
17
  import { resolveTestId, withoutTestIdProps } from '../../../platform/test-id';
17
18
  import { renderTextChildren } from '../../../platform/text-children';
@@ -106,9 +107,18 @@ export function Dialog(rawProps: DialogProps): React.JSX.Element {
106
107
  // backdrop and Escape (which `Modal` routes through `onRequestClose`).
107
108
  const dismiss = persistent ? undefined : onClose;
108
109
 
110
+ // Edge-to-edge defeats the `SOFT_INPUT_ADJUST_RESIZE` that `Modal` asks for,
111
+ // so the window never shrinks for the keyboard and nothing else moves the
112
+ // paper: the overlay measures how much of it the keyboard covers and takes
113
+ // that off the paper's room itself (FUT-3021).
114
+ const keyboard = useKeyboardOverlap();
115
+
109
116
  return (
110
117
  <Modal visible={open} transparent onRequestClose={dismiss} {...modalProps}>
111
- <View style={look.overlay}>
118
+ <View
119
+ style={[look.overlay, dialogKeyboardLift(theme, variant, keyboard.overlap)]}
120
+ onLayout={keyboard.onLayout}
121
+ >
112
122
  <Pressable
113
123
  aria-hidden
114
124
  tabIndex={-1}
@@ -51,7 +51,7 @@ const [draft, setDraft] = useState<number | null>(saved);
51
51
  | `title` | `string` | **required** | The setting's name. A heading, and the accessible name of the card's region. |
52
52
  | `onSave` | `() => void \| Promise<unknown>` | **required** | Persist the draft. See *Saving* below. |
53
53
  | `children` | `ReactNode` | **required** | The form. Rendered only while open. |
54
- | `summary` | `ReactNode` | — | The one line a closed card shows. Ends in an ellipsis rather than wrapping. |
54
+ | `summary` | `ReactNode` | — | What a closed card shows under its title: two lines at most, then an ellipsis. While it is cut, an [i] beside the title shows the whole text (hover, or a click/tap that pins it). |
55
55
  | `status` | `{ label: string; color?: ColorValue }` | — | The pill beside the title. `label` is always shown, so colour never carries the meaning alone. |
56
56
  | `learnMore` | `ReactNode` | — | Longer explanation behind a disclosure in the open card. Omit for no disclosure. |
57
57
  | `onCancel` | `() => void` | — | The card closed without saving. Reset the host's draft here. |
@@ -13,6 +13,11 @@ export const SETTING_CARD = {
13
13
  groupRuleWidth: 2,
14
14
  /** The narrowest a grid column gets by default. */
15
15
  gridMinColumnWidth: 320,
16
+ /** The widest the [i]'s full-summary tooltip gets. */
17
+ infoMaxWidth: 360,
18
+ /** The [i]'s keyboard focus ring: its width, and its gap from the glyph. */
19
+ infoFocusRingWidth: 2,
20
+ infoFocusRingOffset: 2,
16
21
  } as const;
17
22
 
18
23
  /** The grid's default gap, in theme SPACING units (not px): what `sx`'s `gap: 2` means. */
@@ -28,6 +28,8 @@ interface HeaderProps {
28
28
  trailing?: React.ReactNode;
29
29
  /** Renders the title as a `<label>` for this control instead of a heading. */
30
30
  labelFor?: string;
31
+ /** Beside the title, after the status pill — the [i] while the summary is cut. */
32
+ info?: React.ReactNode;
31
33
  dataTestId?: string;
32
34
  }
33
35
 
@@ -46,6 +48,7 @@ export const SettingHeader: React.FC<HeaderProps> = ({
46
48
  status,
47
49
  trailing,
48
50
  labelFor,
51
+ info,
49
52
  dataTestId,
50
53
  }) => {
51
54
  const theme = useTheme();
@@ -75,6 +78,7 @@ export const SettingHeader: React.FC<HeaderProps> = ({
75
78
  dataTestId={partTestId(dataTestId, 'status')}
76
79
  />
77
80
  )}
81
+ {info}
78
82
  </Box>
79
83
  {trailing}
80
84
  </Box>
@@ -78,17 +78,29 @@ export const titleStyles = (theme: Theme): CSSObject => ({
78
78
  overflowWrap: 'anywhere',
79
79
  });
80
80
 
81
- /** The one line a closed card shows. Long values end in an ellipsis, not a second line. */
81
+ /** The most lines a card's summary shows before the rest goes behind the [i]. */
82
+ export const SUMMARY_MAX_LINES = 2;
83
+
84
+ /**
85
+ * Two lines at most, cut with an ellipsis. Visual only: the whole sentence stays
86
+ * in the DOM, so a switch that is `aria-describedby` it still reads all of it.
87
+ */
88
+ export const clampStyles: CSSObject = {
89
+ display: '-webkit-box',
90
+ WebkitLineClamp: SUMMARY_MAX_LINES,
91
+ WebkitBoxOrient: 'vertical',
92
+ overflow: 'hidden',
93
+ };
94
+
95
+ /** A closed card's value: two lines at most, the rest behind the [i]. */
82
96
  export const summaryStyles = (theme: Theme): CSSObject => ({
83
97
  ...theme.typography.body2,
84
98
  color: theme.palette.text.secondary,
85
99
  margin: 0,
86
- overflow: 'hidden',
87
- textOverflow: 'ellipsis',
88
- whiteSpace: 'nowrap',
100
+ ...clampStyles,
89
101
  });
90
102
 
91
- /** A switch card's explanation, which may wrap: it is read, not scanned. */
103
+ /** A switch card's explanation; `SettingSwitchBlock` clamps it (see `clampStyles`). */
92
104
  export const descriptionStyles = (theme: Theme): CSSObject => ({
93
105
  ...theme.typography.body2,
94
106
  color: theme.palette.text.secondary,
@@ -0,0 +1,100 @@
1
+ import Box from '@mui/material/Box/index.js';
2
+ import { useTheme } from '@mui/material/styles/index.js';
3
+ import React, { useCallback, useLayoutEffect, useState } from 'react';
4
+
5
+ import { Icon } from '../../../icons';
6
+ import { rem } from '../../../tokens/relative';
7
+ import { InteractiveTooltip } from '../../data-display/InteractiveTooltip';
8
+
9
+ import { SETTING_CARD } from './SettingCard.metrics';
10
+ import { partTestId } from './SettingCard.parts';
11
+
12
+ /**
13
+ * Whether a clamped element is hiding text — measured, never guessed from the
14
+ * string's length, because what fits depends on the width the card is GIVEN.
15
+ * Re-measured whenever the element resizes, so a card that widens drops its [i].
16
+ */
17
+ export const useClamped = (content?: unknown): { ref: (node: HTMLElement | null) => void; clamped: boolean } => {
18
+ const [node, setNode] = useState<HTMLElement | null>(null);
19
+ const [clamped, setClamped] = useState(false);
20
+ const ref = useCallback((next: HTMLElement | null) => setNode(next), []);
21
+
22
+ useLayoutEffect(() => {
23
+ if (!node) return undefined;
24
+ const measure = (): void => setClamped(node.scrollHeight > node.clientHeight + 1);
25
+ measure();
26
+ if (typeof ResizeObserver === 'undefined') return undefined;
27
+ const observer = new ResizeObserver(measure);
28
+ observer.observe(node);
29
+ return () => observer.disconnect();
30
+ // `content` too: a summary that grows from two lines to three inside the
31
+ // same clamped height fires no resize, so the text changing re-measures.
32
+ }, [node, content]);
33
+
34
+ return { ref, clamped };
35
+ };
36
+
37
+ /**
38
+ * The [i] beside a card's title while its summary is cut: hover shows the whole
39
+ * text, a click or tap pins it — so it works on a phone, where there is no hover.
40
+ */
41
+ export const SummaryInfo: React.FC<{ label: string; summary: React.ReactNode; dataTestId?: string }> = ({
42
+ label,
43
+ summary,
44
+ dataTestId,
45
+ }) => {
46
+ const theme = useTheme();
47
+ return (
48
+ <InteractiveTooltip
49
+ variant="light"
50
+ size="sm"
51
+ maxWidth={SETTING_CARD.infoMaxWidth}
52
+ hoverContent={summary}
53
+ pinnedContent={summary}
54
+ dataTestId={partTestId(dataTestId, 'info')}
55
+ >
56
+ <Box
57
+ component="button"
58
+ type="button"
59
+ aria-label={label}
60
+ sx={{
61
+ display: 'inline-flex',
62
+ alignItems: 'center',
63
+ justifyContent: 'center',
64
+ padding: 0,
65
+ border: 0,
66
+ background: 'none',
67
+ color: theme.palette.text.secondary,
68
+ cursor: 'pointer',
69
+ borderRadius: '50%',
70
+ '&:focus-visible': {
71
+ outline: `${rem(theme, SETTING_CARD.infoFocusRingWidth)} solid ${theme.palette.primary.main}`,
72
+ outlineOffset: rem(theme, SETTING_CARD.infoFocusRingOffset),
73
+ },
74
+ }}
75
+ >
76
+ <Icon name="InfoOutlined" size="xs" />
77
+ </Box>
78
+ </InteractiveTooltip>
79
+ );
80
+ };
81
+
82
+ /**
83
+ * The summary's measuring ref, and the [i] for the header while it is cut —
84
+ * `null` when there is no summary, it fits, or `show` is false (an open card).
85
+ */
86
+ export const useSummaryInfo = ({
87
+ summary,
88
+ show = true,
89
+ label,
90
+ dataTestId,
91
+ }: {
92
+ summary: React.ReactNode;
93
+ show?: boolean;
94
+ label: string;
95
+ dataTestId?: string;
96
+ }): { ref: (node: HTMLElement | null) => void; info: React.ReactNode } => {
97
+ const { ref, clamped } = useClamped(summary);
98
+ const visible = show && clamped && summary != null;
99
+ return { ref, info: visible ? <SummaryInfo label={label} summary={summary} dataTestId={dataTestId} /> : null };
100
+ };
@@ -7,6 +7,7 @@ import { Button } from '../../form/Button';
7
7
  import { useSettingCard } from './SettingCard.hooks';
8
8
  import { LearnMore, PendingSpinner, SaveError, SettingHeader, partTestId } from './SettingCard.parts';
9
9
  import { SETTING_CARD_OPEN_ATTR, actionsStyles, summaryStyles, surfaceStyles, withCallerSx } from './SettingCard.styles';
10
+ import { useSummaryInfo } from './SettingCard.summary';
10
11
  import type { SettingCardCopy, SettingCardProps } from './SettingCard.types';
11
12
 
12
13
  interface OpenBodyProps {
@@ -82,7 +83,8 @@ const OpenBody: React.FC<OpenBodyProps> = ({
82
83
  /**
83
84
  * A setting, summarised while closed and edited in place.
84
85
  *
85
- * Closed: title, status pill, a one-line summary and Edit. Edit opens the card
86
+ * Closed: title, status pill, a summary of at most two lines (an [i] beside the
87
+ * title holds the rest) and Edit. Edit opens the card
86
88
  * where it is — no dialog, no route — around the host's form, a "learn more"
87
89
  * disclosure and Cancel / Save. Save may be async: the card holds its saving
88
90
  * state while the promise is pending, closes when it resolves, and stays open
@@ -134,6 +136,7 @@ export const SettingCard = forwardRef<HTMLElement, SettingCardProps>(function Se
134
136
  const titleId = `${baseId}-title`;
135
137
  const card = useSettingCard(props);
136
138
  const { open } = card;
139
+ const clamp = useSummaryInfo({ summary, show: !open, label: copy.moreInfo ?? title, dataTestId });
137
140
 
138
141
  return (
139
142
  <Box
@@ -153,6 +156,7 @@ export const SettingCard = forwardRef<HTMLElement, SettingCardProps>(function Se
153
156
  headingLevel={headingLevel}
154
157
  icon={icon}
155
158
  status={status}
159
+ info={clamp.info}
156
160
  trailing={
157
161
  open ? null : (
158
162
  <EditTrigger
@@ -169,7 +173,7 @@ export const SettingCard = forwardRef<HTMLElement, SettingCardProps>(function Se
169
173
  dataTestId={dataTestId}
170
174
  />
171
175
  {!open && summary != null && (
172
- <Box component="p" sx={summaryStyles} data-testid={partTestId(dataTestId, 'summary')}>
176
+ <Box component="p" ref={clamp.ref} sx={summaryStyles} data-testid={partTestId(dataTestId, 'summary')}>
173
177
  {summary}
174
178
  </Box>
175
179
  )}
@@ -7,7 +7,8 @@ import { SR_ONLY_SX } from '../../form/Label/Label.styles';
7
7
 
8
8
  import { useAsyncSwitch } from './SettingCard.hooks';
9
9
  import { PendingSpinner, SaveError, SettingHeader, partTestId } from './SettingCard.parts';
10
- import { descriptionStyles } from './SettingCard.styles';
10
+ import { clampStyles, descriptionStyles } from './SettingCard.styles';
11
+ import { useSummaryInfo } from './SettingCard.summary';
11
12
  import type { SettingToggleProps } from './SettingCard.types';
12
13
 
13
14
  /** Everything the switch-driven cards share, minus the surface each draws around it. */
@@ -46,6 +47,7 @@ export const SettingSwitchBlock: React.FC<
46
47
  SettingSwitchBlockProps & { ids: SettingSwitchIds; state: ReturnType<typeof useAsyncSwitch> }
47
48
  > = ({ title, headingLevel = 'h3', icon, summary, copy, disabled, dataTestId, ids, state }) => {
48
49
  const theme = useTheme();
50
+ const clamp = useSummaryInfo({ summary, label: copy.moreInfo ?? title, dataTestId });
49
51
  const { shown, saving, error, flip } = state;
50
52
  const describedBy = [summary != null ? ids.summaryId : null, error ? ids.errorId : null]
51
53
  .filter(Boolean)
@@ -76,10 +78,17 @@ export const SettingSwitchBlock: React.FC<
76
78
  icon={icon}
77
79
  labelFor={ids.inputId}
78
80
  trailing={control}
81
+ info={clamp.info}
79
82
  dataTestId={dataTestId}
80
83
  />
81
84
  {summary != null && (
82
- <Box component="p" id={ids.summaryId} sx={descriptionStyles(theme)} data-testid={partTestId(dataTestId, 'summary')}>
85
+ <Box
86
+ component="p"
87
+ ref={clamp.ref}
88
+ id={ids.summaryId}
89
+ sx={{ ...descriptionStyles(theme), ...clampStyles }}
90
+ data-testid={partTestId(dataTestId, 'summary')}
91
+ >
83
92
  {summary}
84
93
  </Box>
85
94
  )}
@@ -28,7 +28,7 @@ import { SettingToggle } from '@12-apps/ui/layout/SettingCard';
28
28
  | `title` | `string` | **required** | The switch's `<label>` — its accessible name, and a tap target far larger than the track. |
29
29
  | `checked` | `boolean` | **required** | The persisted value. |
30
30
  | `onChange` | `(checked: boolean) => void \| Promise<unknown>` | **required** | Persist a flip. See below. |
31
- | `summary` | `ReactNode` | — | Explanation under the title; the switch is `aria-describedby` it. |
31
+ | `summary` | `ReactNode` | — | Explanation under the title; the switch is `aria-describedby` it. Two lines at most — while it is cut, an [i] beside the title shows the rest. The whole text stays in the DOM, so the description a screen reader hears is complete. |
32
32
  | `variant` | `'card' \| 'row'` | `'card'` | `row` drops the surface — for a dependent row inside a [`SettingGroup`](./SettingGroup.md). |
33
33
  | `disabled` | `boolean` | `false` | Disables the switch. Also disabled inside a `SettingGroup` whose main switch is off. |
34
34
  | `formatError` | `(error: unknown) => string \| undefined` | — | The sentence for a rejected save; `undefined` falls back to `copy.saveFailed`. |
@@ -33,6 +33,12 @@ export interface SettingCardCopy {
33
33
  learnMore: string;
34
34
  /** Shown when the save rejected and the host's `formatError` gave no sentence of its own. */
35
35
  saveFailed: string;
36
+ /**
37
+ * The accessible name of the [i] beside the title while the summary is cut to
38
+ * two lines. Optional so a host's own copy object keeps compiling; without it
39
+ * the [i] is named after the card's title.
40
+ */
41
+ moreInfo?: string;
36
42
  }
37
43
 
38
44
  /**
@@ -44,4 +50,6 @@ export interface SettingSwitchCopy {
44
50
  saving: string;
45
51
  /** Shown when the save rejected and the host's `formatError` gave no sentence of its own. */
46
52
  saveFailed: string;
53
+ /** The [i]'s accessible name — see `SettingCardCopy.moreInfo`. */
54
+ moreInfo?: string;
47
55
  }
@@ -23,9 +23,11 @@ export const EN_US_SETTING_CARD_COPY: SettingCardCopy = {
23
23
  saving: "Saving…",
24
24
  learnMore: "Learn more",
25
25
  saveFailed: "Could not save. Please try again.",
26
+ moreInfo: "Show the full text",
26
27
  };
27
28
 
28
29
  export const EN_US_SETTING_SWITCH_COPY: SettingSwitchCopy = {
29
30
  saving: "Saving…",
30
31
  saveFailed: "Could not save. Please try again.",
32
+ moreInfo: "Show the full text",
31
33
  };
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Keyboard,
4
+ Platform,
5
+ type KeyboardEvent,
6
+ type KeyboardEventName,
7
+ type LayoutChangeEvent,
8
+ } from 'react-native';
9
+
10
+ /**
11
+ * HOW FAR THE SOFT KEYBOARD COVERS A FULL-WINDOW VIEW, AND NOTHING MORE.
12
+ *
13
+ * React Native's `Modal` asks Android for `SOFT_INPUT_ADJUST_RESIZE`, and under
14
+ * edge-to-edge that asks for nothing: the window keeps its full height and the
15
+ * keyboard is drawn over it. iOS never resized a modal at all. So an overlay
16
+ * that fills the modal has its bottom under the keyboard, and whatever sits
17
+ * there — a dialog's actions — is hidden until the keyboard goes away.
18
+ *
19
+ * This reads the keyboard's own top edge from the `Keyboard` events and the
20
+ * view's own bottom edge from its layout, and answers the difference. Reading
21
+ * the OVERLAP rather than the keyboard's height is what keeps it right when the
22
+ * window DID resize (a host that is not edge-to-edge): the view then already
23
+ * ends at the keyboard, the overlap is zero, and nothing is lifted twice. No
24
+ * soft-input mode is set here, deliberately — a second one would fight the one
25
+ * `Modal` already asks for.
26
+ *
27
+ * The view's layout `y` is relative to its parent, so the view is expected to
28
+ * be the root of a window that starts at the top of the screen — which a
29
+ * `transparent` modal is, on iOS and under Android edge-to-edge alike.
30
+ */
31
+ export function keyboardOverlap(viewBottom: number | null, keyboardTop: number | null): number {
32
+ if (viewBottom === null || keyboardTop === null) return 0;
33
+ return Math.max(0, viewBottom - keyboardTop);
34
+ }
35
+
36
+ /** iOS announces the keyboard before it moves; Android only once it has. */
37
+ const KEYBOARD_EVENTS: { show: KeyboardEventName; hide: KeyboardEventName } =
38
+ Platform.OS === 'ios'
39
+ ? { show: 'keyboardWillShow', hide: 'keyboardWillHide' }
40
+ : { show: 'keyboardDidShow', hide: 'keyboardDidHide' };
41
+
42
+ export interface KeyboardOverlap {
43
+ /** How many dp of the view the keyboard covers; `0` while it is down. */
44
+ overlap: number;
45
+ /** Goes on the view being measured. */
46
+ onLayout: (event: LayoutChangeEvent) => void;
47
+ }
48
+
49
+ /**
50
+ * The keyboard's overlap with the view `onLayout` is put on. Android re-sends
51
+ * `keyboardDidShow` when the keyboard changes height while it stays up (an
52
+ * emoji panel, a suggestion strip), so the overlap follows those too.
53
+ */
54
+ export function useKeyboardOverlap(): KeyboardOverlap {
55
+ const [viewBottom, setViewBottom] = React.useState<number | null>(null);
56
+ const [keyboardTop, setKeyboardTop] = React.useState<number | null>(null);
57
+
58
+ React.useEffect(() => {
59
+ const shown = Keyboard.addListener(KEYBOARD_EVENTS.show, (event: KeyboardEvent) => {
60
+ setKeyboardTop(event.endCoordinates.screenY);
61
+ });
62
+ const hidden = Keyboard.addListener(KEYBOARD_EVENTS.hide, () => {
63
+ setKeyboardTop(null);
64
+ });
65
+ return () => {
66
+ shown.remove();
67
+ hidden.remove();
68
+ };
69
+ }, []);
70
+
71
+ const onLayout = React.useCallback((event: LayoutChangeEvent) => {
72
+ const { y, height } = event.nativeEvent.layout;
73
+ setViewBottom(y + height);
74
+ }, []);
75
+
76
+ return { overlap: keyboardOverlap(viewBottom, keyboardTop), onLayout };
77
+ }
@@ -23,9 +23,11 @@ export const PT_BR_SETTING_CARD_COPY: SettingCardCopy = {
23
23
  saving: "Salvando…",
24
24
  learnMore: "Saiba mais",
25
25
  saveFailed: "Não foi possível salvar. Tente novamente.",
26
+ moreInfo: "Ver o texto inteiro",
26
27
  };
27
28
 
28
29
  export const PT_BR_SETTING_SWITCH_COPY: SettingSwitchCopy = {
29
30
  saving: "Salvando…",
30
31
  saveFailed: "Não foi possível salvar. Tente novamente.",
32
+ moreInfo: "Ver o texto inteiro",
31
33
  };