@12-apps/ui 6.100.0 → 6.102.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 (371) hide show
  1. package/NATIVE.md +2 -1
  2. package/components-catalog.md +3 -2
  3. package/dist/_internal/components/data-display/Alert/Alert.content.js +2 -2
  4. package/dist/_internal/components/data-display/Alert/Alert.js +4 -4
  5. package/dist/_internal/components/data-display/Avatar/Avatar.js +2 -2
  6. package/dist/_internal/components/data-display/Badge/Badge.js +2 -2
  7. package/dist/_internal/components/data-display/CardKit/card-actions-context.js +5 -5
  8. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +6 -6
  9. package/dist/_internal/components/data-display/Carousel/Carousel.js +3 -3
  10. package/dist/_internal/components/data-display/Chart/Chart.js +4 -4
  11. package/dist/_internal/components/data-display/Chart/SpecChart.js +6 -6
  12. package/dist/_internal/components/data-display/Chart/chart-context.js +2 -2
  13. package/dist/_internal/components/data-display/Chart/chart-renderers.js +3 -3
  14. package/dist/_internal/components/data-display/DataGrid/DataGrid.js +3 -3
  15. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +45 -45
  16. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +60 -60
  17. package/dist/_internal/components/data-display/DataViews/DeleteViewDialog.js +4 -4
  18. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +4 -4
  19. package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +4 -4
  20. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +4 -4
  21. package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +4 -4
  22. package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +3 -3
  23. package/dist/_internal/components/data-display/DataViews/base-list-card.js +5 -5
  24. package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +8 -8
  25. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +1 -1
  26. package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +20 -20
  27. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +9 -9
  28. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +42 -42
  29. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +24 -24
  30. package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +21 -21
  31. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +22 -22
  32. package/dist/_internal/components/data-display/DataViews/data-views-range-bounds.js +9 -9
  33. package/dist/_internal/components/data-display/DataViews/data-views-range-calendar.js +6 -6
  34. package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +11 -11
  35. package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +2 -2
  36. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +4 -4
  37. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +10 -10
  38. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +3 -3
  39. package/dist/_internal/components/data-display/DataViews/list-card-rails.js +3 -3
  40. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
  41. package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.js +3 -3
  42. package/dist/_internal/components/data-display/DayRuler/DayRuler.js +10 -10
  43. package/dist/_internal/components/data-display/DayRuler/DayRuler.keyboard.js +2 -2
  44. package/dist/_internal/components/data-display/DayRuler/DayRuler.labels.js +2 -2
  45. package/dist/_internal/components/data-display/DayRuler/DayRuler.parts.js +3 -3
  46. package/dist/_internal/components/data-display/DayRuler/useRulerDrag.js +2 -2
  47. package/dist/_internal/components/data-display/FleetMap/FleetBody.js +10 -10
  48. package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +9 -9
  49. package/dist/_internal/components/data-display/FleetMap/FleetMap.js +12 -12
  50. package/dist/_internal/components/data-display/HoverCard/HoverCard.js +2 -2
  51. package/dist/_internal/components/data-display/Lightbox/Lightbox.js +7 -7
  52. package/dist/_internal/components/data-display/Lightbox/LightboxControls.js +2 -2
  53. package/dist/_internal/components/data-display/Lightbox/LightboxOverlay.js +3 -3
  54. package/dist/_internal/components/data-display/Lightbox/LightboxThumbnails.js +2 -2
  55. package/dist/_internal/components/data-display/MapPreview/MapLayers.js +3 -3
  56. package/dist/_internal/components/data-display/MapPreview/MapMarkers.js +2 -2
  57. package/dist/_internal/components/data-display/MapPreview/MapPreview.js +8 -8
  58. package/dist/_internal/components/data-display/Progress/Progress.js +2 -2
  59. package/dist/_internal/components/data-display/RegionMap/RegionMap.js +1 -1
  60. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.js +8 -8
  61. package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.parts.js +3 -3
  62. package/dist/_internal/components/data-display/Sheet/Sheet.js +2 -2
  63. package/dist/_internal/components/data-display/Table/Table.js +3 -3
  64. package/dist/_internal/components/data-display/index.js +72 -72
  65. package/dist/_internal/components/feedback/Dialog/Dialog.js +3 -3
  66. package/dist/_internal/components/feedback/Modal/Modal.js +2 -2
  67. package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +5 -5
  68. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.frame.js +8 -8
  69. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.header.js +4 -4
  70. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.js +16 -16
  71. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.overview.js +6 -6
  72. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.parts.js +5 -5
  73. package/dist/_internal/components/feedback/TutorialOverlay/TutorialOverlay.js +2 -2
  74. package/dist/_internal/components/feedback/UsableModal/UsableModal.js +1 -1
  75. package/dist/_internal/components/feedback/UsableModal/UsableModal.sheet.js +2 -2
  76. package/dist/_internal/components/feedback/index.js +21 -21
  77. package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.hooks.js +2 -2
  78. package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.js +7 -7
  79. package/dist/_internal/components/form/AddressAutocomplete/AddressAutocompleteField.js +2 -2
  80. package/dist/_internal/components/form/Autocomplete/Autocomplete.js +1 -1
  81. package/dist/_internal/components/form/Calendar/Calendar.js +5 -5
  82. package/dist/_internal/components/form/Calendar/CalendarSelectorsMonth.js +3 -3
  83. package/dist/_internal/components/form/CategorySelect/CategoryPanel.js +4 -4
  84. package/dist/_internal/components/form/CategorySelect/CategorySelect.js +7 -7
  85. package/dist/_internal/components/form/CodeEditor/CodeEditor.js +2 -2
  86. package/dist/_internal/components/form/DateRangePicker/DateRangePicker.js +8 -8
  87. package/dist/_internal/components/form/InputOTP/InputOTP.base.js +1 -0
  88. package/dist/_internal/components/form/InputOTP/InputOTP.base.js.map +1 -0
  89. package/dist/_internal/components/form/InputOTP/InputOTP.core.js +16 -0
  90. package/dist/_internal/components/form/InputOTP/InputOTP.core.js.map +1 -0
  91. package/dist/_internal/components/form/InputOTP/InputOTP.hooks.js +2 -1
  92. package/dist/_internal/components/form/InputOTP/InputOTP.js +5 -3
  93. package/dist/_internal/components/form/InputOTP/InputOTP.metrics.js +20 -0
  94. package/dist/_internal/components/form/InputOTP/InputOTP.metrics.js.map +1 -0
  95. package/dist/_internal/components/form/InputOTP/InputOTP.styles.js +2 -1
  96. package/dist/_internal/components/form/index.js +25 -23
  97. package/dist/_internal/components/form/total-form/Fields/CategoryField.js +8 -8
  98. package/dist/_internal/components/form/total-form/Fields/index.js +9 -9
  99. package/dist/_internal/components/index.js +120 -118
  100. package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +6 -6
  101. package/dist/_internal/components/navigation/SectionNav/SectionNav.bar.js +6 -6
  102. package/dist/_internal/components/navigation/SectionNav/SectionNav.js +9 -9
  103. package/dist/_internal/components/navigation/SectionNav/SectionNav.parts.js +3 -3
  104. package/dist/_internal/components/navigation/SectionNav/SectionNav.primary.js +4 -4
  105. package/dist/_internal/components/navigation/SectionNav/SectionNav.rail.js +5 -5
  106. package/dist/_internal/components/navigation/SectionNav/SectionNav.railCreate.js +4 -4
  107. package/dist/_internal/components/navigation/SectionNav/SectionNav.sheet.js +4 -4
  108. package/dist/_internal/components/navigation/index.js +15 -15
  109. package/dist/_internal/user-avatar/user-avatar.js +3 -3
  110. package/dist/charts.js +6 -6
  111. package/dist/{chunk-37MJZLQU.js → chunk-24OFA44S.js} +4 -4
  112. package/dist/{chunk-R775ISK6.js → chunk-2L6TANRY.js} +5 -5
  113. package/dist/{chunk-B4OZU7J4.js → chunk-423A7NUP.js} +7 -7
  114. package/dist/{chunk-LFECE4R7.js → chunk-4EDCF7TC.js} +2 -2
  115. package/dist/{chunk-HI7JPDD6.js → chunk-5357QUVE.js} +2 -2
  116. package/dist/{chunk-F7JRQ2EI.js → chunk-5YX2VQFP.js} +7 -7
  117. package/dist/{chunk-N5PVD4J5.js → chunk-6DO672AG.js} +7 -7
  118. package/dist/{chunk-PGJIWCQR.js → chunk-6EZQLNPK.js} +4 -4
  119. package/dist/{chunk-YQMJJ7AL.js → chunk-6G2RAZTS.js} +5 -5
  120. package/dist/{chunk-KNW63H63.js → chunk-6PQZIZMN.js} +5 -5
  121. package/dist/{chunk-FZZMNKVF.js → chunk-6TAPAIRR.js} +8 -8
  122. package/dist/{chunk-ERAMSYPR.js → chunk-7N4O632I.js} +2 -2
  123. package/dist/{chunk-XFOKLWJZ.js → chunk-7S5PXEO7.js} +2 -2
  124. package/dist/{chunk-UIJLDAV7.js → chunk-AA6YMOEK.js} +2 -2
  125. package/dist/{chunk-CRD2WCU7.js → chunk-AFWUQJ4P.js} +8 -8
  126. package/dist/{chunk-SNWCT6JN.js → chunk-AMIUZRDQ.js} +3 -3
  127. package/dist/{chunk-3I7KE2N6.js → chunk-BNV7P52K.js} +2 -2
  128. package/dist/{chunk-64UNVW6D.js → chunk-BP3ZKIPE.js} +4 -4
  129. package/dist/{chunk-3V7OTBUZ.js → chunk-BQ2SFA27.js} +2 -2
  130. package/dist/{chunk-Q5SCBQLY.js → chunk-BXLC3TBQ.js} +8 -8
  131. package/dist/{chunk-Z5V5E3JW.js → chunk-COKPPU42.js} +11 -11
  132. package/dist/{chunk-QADTI63U.js → chunk-CZVAVX5L.js} +4 -4
  133. package/dist/{chunk-WDEVKND3.js → chunk-D2YJPZMU.js} +9 -9
  134. package/dist/{chunk-OA6T2ME3.js → chunk-DYHOBKPU.js} +5 -5
  135. package/dist/{chunk-6WAP7BJS.js → chunk-E6SPWZRN.js} +2 -2
  136. package/dist/{chunk-UTIYB6VE.js → chunk-E73PAU4U.js} +19 -19
  137. package/dist/{chunk-XR474YYX.js → chunk-EDVAFY76.js} +2 -2
  138. package/dist/{chunk-MEMVZW64.js → chunk-EPNZQWS4.js} +3 -3
  139. package/dist/{chunk-GZITNT7O.js → chunk-FGZIEZEP.js} +11 -11
  140. package/dist/{chunk-4X2TOMK6.js → chunk-FKUVFPQZ.js} +4 -4
  141. package/dist/{chunk-UN5WU2OX.js → chunk-GDWU7WF4.js} +4 -4
  142. package/dist/{chunk-643YFWNK.js → chunk-GPWDP5FF.js} +3 -3
  143. package/dist/{chunk-XA5ES25V.js → chunk-HKQWFICM.js} +2 -2
  144. package/dist/chunk-IH6DZXTG.js +22 -0
  145. package/dist/chunk-IH6DZXTG.js.map +1 -0
  146. package/dist/{chunk-255WTBZ5.js → chunk-IWSTMVCA.js} +4 -4
  147. package/dist/{chunk-QZOQVJ5L.js → chunk-J3WJEUD2.js} +19 -5
  148. package/dist/chunk-J3WJEUD2.js.map +1 -0
  149. package/dist/{chunk-TZC6KIK2.js → chunk-JG733RMP.js} +5 -5
  150. package/dist/{chunk-7MLT66DE.js → chunk-K33X3PDT.js} +2 -2
  151. package/dist/{chunk-K3PMMW23.js → chunk-K6VEN5VN.js} +4 -4
  152. package/dist/{chunk-HOWAMSUY.js → chunk-KAWVH3AA.js} +3 -3
  153. package/dist/{chunk-IJVBLU25.js → chunk-KJZ4PINO.js} +3 -3
  154. package/dist/{chunk-REHGWN5M.js → chunk-KXDLLS62.js} +4 -4
  155. package/dist/{chunk-YQ5GQ32T.js → chunk-KZP5YTE7.js} +2 -2
  156. package/dist/{chunk-3V3Z4EML.js → chunk-L4FWEI2P.js} +5 -5
  157. package/dist/{chunk-UKUGSU3Y.js → chunk-L4V7FZJI.js} +2 -2
  158. package/dist/{chunk-W56KN53D.js → chunk-L6IDPTNX.js} +6 -6
  159. package/dist/{chunk-24HHUCWB.js → chunk-LDCZPZPD.js} +2 -2
  160. package/dist/{chunk-KV7LTOWC.js → chunk-M225SD4H.js} +13 -13
  161. package/dist/{chunk-R6L376KR.js → chunk-M6R32IBO.js} +8 -13
  162. package/dist/chunk-M6R32IBO.js.map +1 -0
  163. package/dist/{chunk-FQMLTCXT.js → chunk-MAHY7PK3.js} +4 -4
  164. package/dist/{chunk-Z2J4MRTF.js → chunk-MKNW4FRR.js} +4 -4
  165. package/dist/{chunk-ILJ3FXIC.js → chunk-MM3QESLI.js} +2 -2
  166. package/dist/{chunk-FYJL432J.js → chunk-MQPUS773.js} +6 -6
  167. package/dist/{chunk-VRSW7PIO.js → chunk-NKBSWAP3.js} +4 -4
  168. package/dist/{chunk-ULC4SCFC.js → chunk-NLJ7LL6H.js} +4 -4
  169. package/dist/{chunk-RAQD5QCM.js → chunk-NQ6JP5CC.js} +6 -6
  170. package/dist/{chunk-IFOHCY4O.js → chunk-OF3KG5EV.js} +2 -2
  171. package/dist/{chunk-W676GY42.js → chunk-P772HCHI.js} +4 -4
  172. package/dist/{chunk-CWRQGA2T.js → chunk-POIENBXE.js} +7 -7
  173. package/dist/{chunk-FW5GTAUY.js → chunk-Q4AX4FOK.js} +11 -11
  174. package/dist/{chunk-XQAFSC66.js → chunk-Q4FNCT4C.js} +2 -2
  175. package/dist/{chunk-PIELY4BJ.js → chunk-Q5Z66LKI.js} +1 -1
  176. package/dist/{chunk-4Y3QLISA.js → chunk-Q7AONB4G.js} +4 -4
  177. package/dist/{chunk-EZBBLLEU.js → chunk-QQBYZ3B3.js} +4 -4
  178. package/dist/{chunk-FT3LJZWZ.js → chunk-RWUXMBSO.js} +4 -4
  179. package/dist/{chunk-KWBMM6MJ.js → chunk-RZKKWIGQ.js} +9 -9
  180. package/dist/{chunk-RVBLOR32.js → chunk-S7CXVIEF.js} +2 -2
  181. package/dist/{chunk-WLLUARTC.js → chunk-T2G4LDU5.js} +2 -2
  182. package/dist/{chunk-OFRQMFWD.js → chunk-T5D3DYVN.js} +2 -2
  183. package/dist/{chunk-O4UXH7BM.js → chunk-TP6KZ2YR.js} +7 -7
  184. package/dist/{chunk-HAWOS4SF.js → chunk-TPDN3B5T.js} +2 -2
  185. package/dist/{chunk-54DOLCJ3.js → chunk-TXIIJW6C.js} +5 -5
  186. package/dist/{chunk-HN36UVOE.js → chunk-U4Z6GZDW.js} +4 -4
  187. package/dist/{chunk-J2IQ6FJ5.js → chunk-UKLNLWTP.js} +3 -3
  188. package/dist/{chunk-NQYC7V5N.js → chunk-VJ6ZLMQ3.js} +18 -13
  189. package/dist/chunk-VJ6ZLMQ3.js.map +1 -0
  190. package/dist/{chunk-MKHNLRVX.js → chunk-VPPD2AJN.js} +7 -7
  191. package/dist/{chunk-TIM4PXWN.js → chunk-VWRXSBT2.js} +2 -2
  192. package/dist/{chunk-KVP5A3YK.js → chunk-WDWZKTGA.js} +2 -2
  193. package/dist/{chunk-FYSS2PYA.js → chunk-WH5ZEBKS.js} +7 -7
  194. package/dist/{chunk-KAGXV3ID.js → chunk-WMMXOSZG.js} +7 -7
  195. package/dist/{chunk-4RSFYS2V.js → chunk-X6D5JEUV.js} +6 -6
  196. package/dist/{chunk-GFYDOH7D.js → chunk-XEPNAU3T.js} +2 -2
  197. package/dist/{chunk-VMACQT3T.js → chunk-XH7UDC5Y.js} +3 -3
  198. package/dist/{chunk-PDWQJFMK.js → chunk-XHMHHP7B.js} +10 -10
  199. package/dist/chunk-XHPL5VI2.js +31 -0
  200. package/dist/chunk-XHPL5VI2.js.map +1 -0
  201. package/dist/{chunk-SA3KLZDZ.js → chunk-XNAI7QGU.js} +5 -5
  202. package/dist/{chunk-7TENMQNQ.js → chunk-XZMSRHFD.js} +4 -4
  203. package/dist/{chunk-HBHBRRSW.js → chunk-YBAK2WN5.js} +7 -7
  204. package/dist/{chunk-QLKUWDMQ.js → chunk-YDJ5GAGL.js} +13 -13
  205. package/dist/{chunk-LOLYBFYY.js → chunk-ZCNR4DN5.js} +5 -5
  206. package/dist/data-display/Alert.js +4 -4
  207. package/dist/data-display/Avatar.js +2 -2
  208. package/dist/data-display/Badge.js +2 -2
  209. package/dist/data-display/CardKit.js +14 -14
  210. package/dist/data-display/Carousel.js +3 -3
  211. package/dist/data-display/Chart.js +6 -6
  212. package/dist/data-display/DataGrid.js +3 -3
  213. package/dist/data-display/DataViews.js +74 -74
  214. package/dist/data-display/DayRuler.js +12 -12
  215. package/dist/data-display/FleetMap.js +12 -12
  216. package/dist/data-display/HoverCard.js +2 -2
  217. package/dist/data-display/Lightbox.js +7 -7
  218. package/dist/data-display/MapPreview.js +8 -8
  219. package/dist/data-display/Progress.js +2 -2
  220. package/dist/data-display/RegionMap.js +4 -4
  221. package/dist/data-display/ScheduleGrid.js +10 -10
  222. package/dist/data-display/Sheet.js +2 -2
  223. package/dist/data-display/Table.js +3 -3
  224. package/dist/feedback/Dialog.js +3 -3
  225. package/dist/feedback/Modal.js +2 -2
  226. package/dist/feedback/NotificationStatusNotice.js +5 -5
  227. package/dist/feedback/SequenceResolver.js +16 -16
  228. package/dist/feedback/TutorialOverlay.js +2 -2
  229. package/dist/feedback/UsableModal.js +1 -1
  230. package/dist/form/AddressAutocomplete.js +7 -7
  231. package/dist/form/Autocomplete.js +1 -1
  232. package/dist/form/Calendar.js +5 -5
  233. package/dist/form/CategorySelect.js +7 -7
  234. package/dist/form/CodeEditor.js +2 -2
  235. package/dist/form/DateRangePicker.js +10 -10
  236. package/dist/form/InputOTP.js +5 -3
  237. package/dist/form/total-form.js +9 -9
  238. package/dist/native/form/InputOTP.js +241 -0
  239. package/dist/native/form/InputOTP.js.map +1 -0
  240. package/dist/navigation/AppHeader.js +6 -6
  241. package/dist/navigation/SectionNav.js +9 -9
  242. package/dist/types/components/form/InputOTP/InputOTP.base.d.ts +77 -0
  243. package/dist/types/components/form/InputOTP/InputOTP.base.d.ts.map +1 -0
  244. package/dist/types/components/form/InputOTP/InputOTP.core.d.ts +38 -0
  245. package/dist/types/components/form/InputOTP/InputOTP.core.d.ts.map +1 -0
  246. package/dist/types/components/form/InputOTP/InputOTP.d.ts +1 -2
  247. package/dist/types/components/form/InputOTP/InputOTP.d.ts.map +1 -1
  248. package/dist/types/components/form/InputOTP/InputOTP.hooks.d.ts.map +1 -1
  249. package/dist/types/components/form/InputOTP/InputOTP.metrics.d.ts +40 -0
  250. package/dist/types/components/form/InputOTP/InputOTP.metrics.d.ts.map +1 -0
  251. package/dist/types/components/form/InputOTP/InputOTP.styles.d.ts +2 -0
  252. package/dist/types/components/form/InputOTP/InputOTP.styles.d.ts.map +1 -1
  253. package/dist/types/components/form/InputOTP/InputOTP.types.d.ts +3 -56
  254. package/dist/types/components/form/InputOTP/InputOTP.types.d.ts.map +1 -1
  255. package/dist/types-native/components/form/InputOTP/InputOTP.base.d.ts +77 -0
  256. package/dist/types-native/components/form/InputOTP/InputOTP.base.d.ts.map +1 -0
  257. package/dist/types-native/components/form/InputOTP/InputOTP.core.d.ts +38 -0
  258. package/dist/types-native/components/form/InputOTP/InputOTP.core.d.ts.map +1 -0
  259. package/dist/types-native/components/form/InputOTP/InputOTP.hooks.native.d.ts +28 -0
  260. package/dist/types-native/components/form/InputOTP/InputOTP.hooks.native.d.ts.map +1 -0
  261. package/dist/types-native/components/form/InputOTP/InputOTP.metrics.d.ts +40 -0
  262. package/dist/types-native/components/form/InputOTP/InputOTP.metrics.d.ts.map +1 -0
  263. package/dist/types-native/components/form/InputOTP/InputOTP.native.d.ts +14 -0
  264. package/dist/types-native/components/form/InputOTP/InputOTP.native.d.ts.map +1 -0
  265. package/dist/types-native/components/form/InputOTP/InputOTP.types.native.d.ts +14 -0
  266. package/dist/types-native/components/form/InputOTP/InputOTP.types.native.d.ts.map +1 -0
  267. package/dist/types-native/components/form/InputOTP/index.native.d.ts +3 -0
  268. package/dist/types-native/components/form/InputOTP/index.native.d.ts.map +1 -0
  269. package/dist/user-avatar.js +3 -3
  270. package/package.json +5 -1
  271. package/src/components/form/InputOTP/InputOTP.base.ts +91 -0
  272. package/src/components/form/InputOTP/InputOTP.core.ts +51 -0
  273. package/src/components/form/InputOTP/InputOTP.hooks.native.ts +84 -0
  274. package/src/components/form/InputOTP/InputOTP.hooks.ts +1 -46
  275. package/src/components/form/InputOTP/InputOTP.md +35 -0
  276. package/src/components/form/InputOTP/InputOTP.metrics.ts +51 -0
  277. package/src/components/form/InputOTP/InputOTP.native.tsx +217 -0
  278. package/src/components/form/InputOTP/InputOTP.styles.ts +35 -16
  279. package/src/components/form/InputOTP/InputOTP.tsx +17 -3
  280. package/src/components/form/InputOTP/InputOTP.types.native.ts +15 -0
  281. package/src/components/form/InputOTP/InputOTP.types.ts +3 -68
  282. package/src/components/form/InputOTP/NATIVE-NOTES.md +8 -0
  283. package/src/components/form/InputOTP/index.native.ts +2 -0
  284. package/dist/chunk-NQYC7V5N.js.map +0 -1
  285. package/dist/chunk-QZOQVJ5L.js.map +0 -1
  286. package/dist/chunk-R6L376KR.js.map +0 -1
  287. /package/dist/{chunk-37MJZLQU.js.map → chunk-24OFA44S.js.map} +0 -0
  288. /package/dist/{chunk-R775ISK6.js.map → chunk-2L6TANRY.js.map} +0 -0
  289. /package/dist/{chunk-B4OZU7J4.js.map → chunk-423A7NUP.js.map} +0 -0
  290. /package/dist/{chunk-LFECE4R7.js.map → chunk-4EDCF7TC.js.map} +0 -0
  291. /package/dist/{chunk-HI7JPDD6.js.map → chunk-5357QUVE.js.map} +0 -0
  292. /package/dist/{chunk-F7JRQ2EI.js.map → chunk-5YX2VQFP.js.map} +0 -0
  293. /package/dist/{chunk-N5PVD4J5.js.map → chunk-6DO672AG.js.map} +0 -0
  294. /package/dist/{chunk-PGJIWCQR.js.map → chunk-6EZQLNPK.js.map} +0 -0
  295. /package/dist/{chunk-YQMJJ7AL.js.map → chunk-6G2RAZTS.js.map} +0 -0
  296. /package/dist/{chunk-KNW63H63.js.map → chunk-6PQZIZMN.js.map} +0 -0
  297. /package/dist/{chunk-FZZMNKVF.js.map → chunk-6TAPAIRR.js.map} +0 -0
  298. /package/dist/{chunk-ERAMSYPR.js.map → chunk-7N4O632I.js.map} +0 -0
  299. /package/dist/{chunk-XFOKLWJZ.js.map → chunk-7S5PXEO7.js.map} +0 -0
  300. /package/dist/{chunk-UIJLDAV7.js.map → chunk-AA6YMOEK.js.map} +0 -0
  301. /package/dist/{chunk-CRD2WCU7.js.map → chunk-AFWUQJ4P.js.map} +0 -0
  302. /package/dist/{chunk-SNWCT6JN.js.map → chunk-AMIUZRDQ.js.map} +0 -0
  303. /package/dist/{chunk-3I7KE2N6.js.map → chunk-BNV7P52K.js.map} +0 -0
  304. /package/dist/{chunk-64UNVW6D.js.map → chunk-BP3ZKIPE.js.map} +0 -0
  305. /package/dist/{chunk-3V7OTBUZ.js.map → chunk-BQ2SFA27.js.map} +0 -0
  306. /package/dist/{chunk-Q5SCBQLY.js.map → chunk-BXLC3TBQ.js.map} +0 -0
  307. /package/dist/{chunk-Z5V5E3JW.js.map → chunk-COKPPU42.js.map} +0 -0
  308. /package/dist/{chunk-QADTI63U.js.map → chunk-CZVAVX5L.js.map} +0 -0
  309. /package/dist/{chunk-WDEVKND3.js.map → chunk-D2YJPZMU.js.map} +0 -0
  310. /package/dist/{chunk-OA6T2ME3.js.map → chunk-DYHOBKPU.js.map} +0 -0
  311. /package/dist/{chunk-6WAP7BJS.js.map → chunk-E6SPWZRN.js.map} +0 -0
  312. /package/dist/{chunk-UTIYB6VE.js.map → chunk-E73PAU4U.js.map} +0 -0
  313. /package/dist/{chunk-XR474YYX.js.map → chunk-EDVAFY76.js.map} +0 -0
  314. /package/dist/{chunk-MEMVZW64.js.map → chunk-EPNZQWS4.js.map} +0 -0
  315. /package/dist/{chunk-GZITNT7O.js.map → chunk-FGZIEZEP.js.map} +0 -0
  316. /package/dist/{chunk-4X2TOMK6.js.map → chunk-FKUVFPQZ.js.map} +0 -0
  317. /package/dist/{chunk-UN5WU2OX.js.map → chunk-GDWU7WF4.js.map} +0 -0
  318. /package/dist/{chunk-643YFWNK.js.map → chunk-GPWDP5FF.js.map} +0 -0
  319. /package/dist/{chunk-XA5ES25V.js.map → chunk-HKQWFICM.js.map} +0 -0
  320. /package/dist/{chunk-255WTBZ5.js.map → chunk-IWSTMVCA.js.map} +0 -0
  321. /package/dist/{chunk-TZC6KIK2.js.map → chunk-JG733RMP.js.map} +0 -0
  322. /package/dist/{chunk-7MLT66DE.js.map → chunk-K33X3PDT.js.map} +0 -0
  323. /package/dist/{chunk-K3PMMW23.js.map → chunk-K6VEN5VN.js.map} +0 -0
  324. /package/dist/{chunk-HOWAMSUY.js.map → chunk-KAWVH3AA.js.map} +0 -0
  325. /package/dist/{chunk-IJVBLU25.js.map → chunk-KJZ4PINO.js.map} +0 -0
  326. /package/dist/{chunk-REHGWN5M.js.map → chunk-KXDLLS62.js.map} +0 -0
  327. /package/dist/{chunk-YQ5GQ32T.js.map → chunk-KZP5YTE7.js.map} +0 -0
  328. /package/dist/{chunk-3V3Z4EML.js.map → chunk-L4FWEI2P.js.map} +0 -0
  329. /package/dist/{chunk-UKUGSU3Y.js.map → chunk-L4V7FZJI.js.map} +0 -0
  330. /package/dist/{chunk-W56KN53D.js.map → chunk-L6IDPTNX.js.map} +0 -0
  331. /package/dist/{chunk-24HHUCWB.js.map → chunk-LDCZPZPD.js.map} +0 -0
  332. /package/dist/{chunk-KV7LTOWC.js.map → chunk-M225SD4H.js.map} +0 -0
  333. /package/dist/{chunk-FQMLTCXT.js.map → chunk-MAHY7PK3.js.map} +0 -0
  334. /package/dist/{chunk-Z2J4MRTF.js.map → chunk-MKNW4FRR.js.map} +0 -0
  335. /package/dist/{chunk-ILJ3FXIC.js.map → chunk-MM3QESLI.js.map} +0 -0
  336. /package/dist/{chunk-FYJL432J.js.map → chunk-MQPUS773.js.map} +0 -0
  337. /package/dist/{chunk-VRSW7PIO.js.map → chunk-NKBSWAP3.js.map} +0 -0
  338. /package/dist/{chunk-ULC4SCFC.js.map → chunk-NLJ7LL6H.js.map} +0 -0
  339. /package/dist/{chunk-RAQD5QCM.js.map → chunk-NQ6JP5CC.js.map} +0 -0
  340. /package/dist/{chunk-IFOHCY4O.js.map → chunk-OF3KG5EV.js.map} +0 -0
  341. /package/dist/{chunk-W676GY42.js.map → chunk-P772HCHI.js.map} +0 -0
  342. /package/dist/{chunk-CWRQGA2T.js.map → chunk-POIENBXE.js.map} +0 -0
  343. /package/dist/{chunk-FW5GTAUY.js.map → chunk-Q4AX4FOK.js.map} +0 -0
  344. /package/dist/{chunk-XQAFSC66.js.map → chunk-Q4FNCT4C.js.map} +0 -0
  345. /package/dist/{chunk-PIELY4BJ.js.map → chunk-Q5Z66LKI.js.map} +0 -0
  346. /package/dist/{chunk-4Y3QLISA.js.map → chunk-Q7AONB4G.js.map} +0 -0
  347. /package/dist/{chunk-EZBBLLEU.js.map → chunk-QQBYZ3B3.js.map} +0 -0
  348. /package/dist/{chunk-FT3LJZWZ.js.map → chunk-RWUXMBSO.js.map} +0 -0
  349. /package/dist/{chunk-KWBMM6MJ.js.map → chunk-RZKKWIGQ.js.map} +0 -0
  350. /package/dist/{chunk-RVBLOR32.js.map → chunk-S7CXVIEF.js.map} +0 -0
  351. /package/dist/{chunk-WLLUARTC.js.map → chunk-T2G4LDU5.js.map} +0 -0
  352. /package/dist/{chunk-OFRQMFWD.js.map → chunk-T5D3DYVN.js.map} +0 -0
  353. /package/dist/{chunk-O4UXH7BM.js.map → chunk-TP6KZ2YR.js.map} +0 -0
  354. /package/dist/{chunk-HAWOS4SF.js.map → chunk-TPDN3B5T.js.map} +0 -0
  355. /package/dist/{chunk-54DOLCJ3.js.map → chunk-TXIIJW6C.js.map} +0 -0
  356. /package/dist/{chunk-HN36UVOE.js.map → chunk-U4Z6GZDW.js.map} +0 -0
  357. /package/dist/{chunk-J2IQ6FJ5.js.map → chunk-UKLNLWTP.js.map} +0 -0
  358. /package/dist/{chunk-MKHNLRVX.js.map → chunk-VPPD2AJN.js.map} +0 -0
  359. /package/dist/{chunk-TIM4PXWN.js.map → chunk-VWRXSBT2.js.map} +0 -0
  360. /package/dist/{chunk-KVP5A3YK.js.map → chunk-WDWZKTGA.js.map} +0 -0
  361. /package/dist/{chunk-FYSS2PYA.js.map → chunk-WH5ZEBKS.js.map} +0 -0
  362. /package/dist/{chunk-KAGXV3ID.js.map → chunk-WMMXOSZG.js.map} +0 -0
  363. /package/dist/{chunk-4RSFYS2V.js.map → chunk-X6D5JEUV.js.map} +0 -0
  364. /package/dist/{chunk-GFYDOH7D.js.map → chunk-XEPNAU3T.js.map} +0 -0
  365. /package/dist/{chunk-VMACQT3T.js.map → chunk-XH7UDC5Y.js.map} +0 -0
  366. /package/dist/{chunk-PDWQJFMK.js.map → chunk-XHMHHP7B.js.map} +0 -0
  367. /package/dist/{chunk-SA3KLZDZ.js.map → chunk-XNAI7QGU.js.map} +0 -0
  368. /package/dist/{chunk-7TENMQNQ.js.map → chunk-XZMSRHFD.js.map} +0 -0
  369. /package/dist/{chunk-HBHBRRSW.js.map → chunk-YBAK2WN5.js.map} +0 -0
  370. /package/dist/{chunk-QLKUWDMQ.js.map → chunk-YDJ5GAGL.js.map} +0 -0
  371. /package/dist/{chunk-LOLYBFYY.js.map → chunk-ZCNR4DN5.js.map} +0 -0
@@ -0,0 +1,91 @@
1
+ import type { ColorValue, SizeValue } from '../../../tokens/vocabulary';
2
+
3
+ /**
4
+ * THE CONTRACT BOTH RENDERERS HONOUR (see `Button.base.ts`).
5
+ *
6
+ * No MUI and no react-native here: this file ships in both declaration
7
+ * outputs, and a native consumer has no `@mui/material` to resolve against.
8
+ */
9
+
10
+ export interface InputOTPBaseProps {
11
+ /**
12
+ * The variant of the OTP input
13
+ */
14
+ variant?: 'numeric' | 'alphanumeric' | 'masked';
15
+
16
+ /**
17
+ * The color theme of the OTP input
18
+ */
19
+ color?: ColorValue;
20
+
21
+ /**
22
+ * The size of the OTP input
23
+ */
24
+ size?: SizeValue;
25
+
26
+ /**
27
+ * How the boxes take the row's width (FUT-4585).
28
+ *
29
+ * Left out, each box is a square of the size's field height, and the row is
30
+ * as wide as its boxes — what every caller drew before this option.
31
+ *
32
+ * `'width'`: the row takes its parent's whole width and the boxes share it
33
+ * with a 6px gap. Each keeps the size's field height (never under 40) and
34
+ * its type, and narrows to fit: never wider than it is tall, never under
35
+ * 32. On a narrow phone that draws PORTRAIT boxes instead of a smaller size.
36
+ * The boxes share the row's outer width, so pad the parent, not the row: a
37
+ * `style` (or `sx`) setting the row's padding or gap is not accounted for.
38
+ */
39
+ fit?: 'width';
40
+
41
+ /**
42
+ * Number of OTP digits
43
+ */
44
+ length?: number;
45
+
46
+ /**
47
+ * Current value
48
+ */
49
+ value?: string;
50
+
51
+ /**
52
+ * Called when value changes
53
+ */
54
+ onChange?: (value: string) => void;
55
+
56
+ /**
57
+ * Called when OTP is complete
58
+ */
59
+ onComplete?: (value: string) => void;
60
+
61
+ /**
62
+ * Whether the input should have glass morphism effect
63
+ */
64
+ glass?: boolean;
65
+
66
+ /**
67
+ * Whether the input should have gradient effects
68
+ */
69
+ gradient?: boolean;
70
+
71
+ /**
72
+ * Whether to auto-focus first input
73
+ */
74
+ autoFocus?: boolean;
75
+
76
+ /**
77
+ * Error state
78
+ */
79
+ error?: boolean;
80
+
81
+ /**
82
+ * Disabled state
83
+ */
84
+ disabled?: boolean;
85
+
86
+ /**
87
+ * Test ID for automated testing
88
+ * Container will have this ID, and individual slots will have {dataTestId}-slot-{index}
89
+ */
90
+ dataTestId?: string;
91
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The digits' arithmetic, shared by both renderers' hooks (`InputOTP.hooks.ts`
3
+ * on the web, `InputOTP.hooks.native.ts` on React Native). Pure: no React, no
4
+ * DOM, no react-native.
5
+ */
6
+
7
+ /**
8
+ * What each variant accepts. `masked` constrains nothing — it only changes what
9
+ * is drawn — so it has no pattern here.
10
+ */
11
+ export const PATTERNS: Record<string, RegExp> = {
12
+ numeric: /^\d*$/,
13
+ alphanumeric: /^[a-zA-Z0-9]*$/,
14
+ };
15
+
16
+ /** Whether `text` is something this variant will take. */
17
+ export const isAllowed = (variant: string, text: string) => (PATTERNS[variant] ?? /.*/).test(text);
18
+
19
+ /** The digit array is always `length` long, blank-padded on the right. */
20
+ export const padTo = (digits: string[], length: number) =>
21
+ Array.from({ length }, (_, i) => digits[i] ?? '');
22
+
23
+ /**
24
+ * What a change to the numeric FIRST slot is, now that its `maxLength` is the
25
+ * code's length rather than 1 (FUT-4144).
26
+ *
27
+ * That slot carries `one-time-code`, and the phone's SMS fill (or a password
28
+ * manager) writes the WHOLE code into it as one change, not a paste — kept to
29
+ * one character, the code lost every digit but the last. So:
30
+ *
31
+ * - `overtype`: a keystroke into the already-filled slot (its old character
32
+ * plus the new one). Ignored, exactly as `maxLength={1}` refused it before.
33
+ * - `code`: anything else longer than one character, written in one go.
34
+ * Spread across the slots like a paste.
35
+ *
36
+ * Every other slot and variant keeps `maxLength={1}` and never gets here.
37
+ */
38
+ export function firstSlotFill(variant: string, index: number, inputValue: string, current: string) {
39
+ if (variant !== 'numeric' || index !== 0 || inputValue.length < 2) return null;
40
+ const overtype = inputValue.length === 2 && current !== '' && inputValue.includes(current);
41
+ return overtype ? 'overtype' : 'code';
42
+ }
43
+
44
+ /**
45
+ * The code inside a first-slot fill. A phone that INSERTS the SMS code at the
46
+ * caret rather than replacing the slot leaves the digit already there in
47
+ * front: `4` + `482913` arrives as `4482913`, which spread as-is would give
48
+ * `448291`. Longer than the code and led by that digit: the digit is dropped.
49
+ */
50
+ export const codeOfFill = (inputValue: string, current: string, length: number): string =>
51
+ inputValue.length > length && current !== '' && inputValue.startsWith(current) ? inputValue.slice(1) : inputValue;
@@ -0,0 +1,84 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import { codeOfFill, firstSlotFill, isAllowed, padTo } from './InputOTP.core';
4
+
5
+ /** What a box needs from the element behind it: to take the focus. */
6
+ interface Focusable {
7
+ focus: () => void;
8
+ }
9
+
10
+ interface NativeOtpInput {
11
+ variant: string;
12
+ length: number;
13
+ value: string;
14
+ onChange?: (value: string) => void;
15
+ onComplete?: (value: string) => void;
16
+ }
17
+
18
+ /**
19
+ * The native boxes' digits — the web hook's rules (`InputOTP.hooks.ts`) over
20
+ * React Native's events: `onChangeText` carries the box's whole new text and
21
+ * `onKeyPress` carries only the key's name, and there is no paste event at
22
+ * all. A pasted or SMS-filled code arrives as a change of the FIRST box, whose
23
+ * `maxLength` is the code's length plus one for exactly that reason, and is
24
+ * spread across the boxes by the same `firstSlotFill` / `codeOfFill` the web
25
+ * uses.
26
+ */
27
+ export const useNativeOtpDigits = ({ variant, length, value, onChange, onComplete }: NativeOtpInput) => {
28
+ const [digits, setDigits] = useState<string[]>(() => padTo([], length));
29
+ const boxRefs = useRef<(Focusable | null)[]>([]);
30
+
31
+ useEffect(() => {
32
+ setDigits(padTo(value.split('').slice(0, length), length));
33
+ }, [value, length]);
34
+
35
+ const focusBox = useCallback((index: number) => boxRefs.current[index]?.focus(), []);
36
+
37
+ const publish = useCallback(
38
+ (next: string) => {
39
+ onChange?.(next);
40
+ if (next.length === length) onComplete?.(next);
41
+ },
42
+ [length, onChange, onComplete],
43
+ );
44
+
45
+ /** A whole code laid across the boxes from the first one. */
46
+ const spread = useCallback(
47
+ (text: string) => {
48
+ const code = text.slice(0, length);
49
+ if (!code || !isAllowed(variant, code)) return;
50
+ setDigits(padTo(code.split(''), length));
51
+ publish(code);
52
+ focusBox(Math.min(code.length, length - 1));
53
+ },
54
+ [variant, length, publish, focusBox],
55
+ );
56
+
57
+ const handleChangeText = useCallback(
58
+ (index: number, text: string) => {
59
+ // The web's paste spreads a code for EVERY variant; React Native has no
60
+ // paste event, so a code arrives as a change of the first box, read by
61
+ // the numeric rule for `alphanumeric` too (`masked` shows a bullet, whose
62
+ // text no longer holds the digit it hides: it keeps one character a box).
63
+ const fill = firstSlotFill(variant === 'alphanumeric' ? 'numeric' : variant, index, text, digits[0] ?? '');
64
+ if (fill) return fill === 'code' ? spread(codeOfFill(text, digits[0] ?? '', length)) : undefined;
65
+ if (!isAllowed(variant, text)) return;
66
+ const next = [...digits];
67
+ next[index] = text.slice(-1);
68
+ setDigits(next);
69
+ publish(next.join(''));
70
+ if (text && index < length - 1) focusBox(index + 1);
71
+ },
72
+ [variant, digits, length, publish, focusBox, spread],
73
+ );
74
+
75
+ /** Backspace in a box that is already empty steps back to the one before. */
76
+ const handleKey = useCallback(
77
+ (index: number, key: string) => {
78
+ if (key === 'Backspace' && !digits[index] && index > 0) focusBox(index - 1);
79
+ },
80
+ [digits, focusBox],
81
+ );
82
+
83
+ return { digits, boxRefs, handleChangeText, handleKey };
84
+ };
@@ -1,51 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
2
 
3
-
4
- /**
5
- * What each variant accepts. `masked` constrains nothing — it only changes what
6
- * is drawn — so it has no pattern here.
7
- */
8
- const PATTERNS: Record<string, RegExp> = {
9
- numeric: /^\d*$/,
10
- alphanumeric: /^[a-zA-Z0-9]*$/,
11
- };
12
-
13
- /** Whether `text` is something this variant will take. */
14
- const isAllowed = (variant: string, text: string) => (PATTERNS[variant] ?? /.*/).test(text);
15
-
16
- /** The digit array is always `length` long, blank-padded on the right. */
17
- const padTo = (digits: string[], length: number) =>
18
- Array.from({ length }, (_, i) => digits[i] ?? '');
19
-
20
- /**
21
- * What a change to the numeric FIRST slot is, now that its `maxLength` is the
22
- * code's length rather than 1 (FUT-4144).
23
- *
24
- * That slot carries `one-time-code`, and the phone's SMS fill (or a password
25
- * manager) writes the WHOLE code into it as one change, not a paste — kept to
26
- * one character, the code lost every digit but the last. So:
27
- *
28
- * - `overtype`: a keystroke into the already-filled slot (its old character
29
- * plus the new one). Ignored, exactly as `maxLength={1}` refused it before.
30
- * - `code`: anything else longer than one character, written in one go.
31
- * Spread across the slots like a paste.
32
- *
33
- * Every other slot and variant keeps `maxLength={1}` and never gets here.
34
- */
35
- function firstSlotFill(variant: string, index: number, inputValue: string, current: string) {
36
- if (variant !== 'numeric' || index !== 0 || inputValue.length < 2) return null;
37
- const overtype = inputValue.length === 2 && current !== '' && inputValue.includes(current);
38
- return overtype ? 'overtype' : 'code';
39
- }
40
-
41
- /**
42
- * The code inside a first-slot fill. A phone that INSERTS the SMS code at the
43
- * caret rather than replacing the slot leaves the digit already there in
44
- * front: `4` + `482913` arrives as `4482913`, which spread as-is would give
45
- * `448291`. Longer than the code and led by that digit: the digit is dropped.
46
- */
47
- const codeOfFill = (inputValue: string, current: string, length: number): string =>
48
- inputValue.length > length && current !== '' && inputValue.startsWith(current) ? inputValue.slice(1) : inputValue;
3
+ import { codeOfFill, firstSlotFill, isAllowed, padTo } from './InputOTP.core';
49
4
 
50
5
  interface OtpInput {
51
6
  variant: string;
@@ -27,6 +27,7 @@ The InputOTP component provides a specialized input field for one-time passwords
27
27
  | `onComplete` | `(value: string) => void` | - | Callback when all digits are filled |
28
28
  | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'danger' \| 'neutral'` | `'primary'` | Theme color |
29
29
  | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Input size |
30
+ | `fit` | `'width'` | - | Share the row's width: each box keeps the size's height (never under 40) and type, and narrows to fit, never wider than tall nor under 32. Left out, each box is a square of the size's field height |
30
31
  | `autoFocus` | `boolean` | `false` | Auto-focus first input on mount |
31
32
  | `disabled` | `boolean` | `false` | Disable all inputs |
32
33
  | `error` | `boolean` | `false` | Show error state |
@@ -191,6 +192,40 @@ The component supports various visual customizations:
191
192
  - `lg`: Large (prominent forms)
192
193
  - `xl`: Extra large (accessibility focus)
193
194
 
195
+ ### Fitting a narrow row: `fit="width"`
196
+
197
+ A square box is as wide as its field is tall, so six `lg` boxes need 328px. On
198
+ a narrow phone the only way to fit them used to be a smaller size, whose boxes
199
+ drop under the 40px tap minimum and whose type shrinks with them. `fit="width"`
200
+ keeps the size and narrows the boxes instead (FUT-4585):
201
+
202
+ - the row takes its parent's whole width, with a 6px gap between boxes;
203
+ - each box keeps the size's field height, lifted to 40 if it is shorter, and
204
+ the size's own type;
205
+ - each box's width is the row less the gaps, divided by `length`, floored to a
206
+ whole pixel, never wider than the box is tall (a wide row keeps squares) and
207
+ never under 32.
208
+
209
+ At `size="lg"` (48 tall), a sheet with 24px of padding on each side draws:
210
+
211
+ | phone | row | box |
212
+ |---|---|---|
213
+ | 320 | 272 | 40 × 48 |
214
+ | 360 | 312 | 47 × 48 |
215
+ | 390 | 342 | 48 × 48 |
216
+
217
+ ```tsx
218
+ <InputOTP fit="width" size="lg" length={6} value={code} onChange={setCode} />
219
+ ```
220
+
221
+ The boxes share the row's **outer** width, and the row spans its parent in a
222
+ column or a row parent alike. Pad the parent, not the row: a `style` (or `sx`)
223
+ that sets the row's own padding or gap is not taken off the share.
224
+
225
+ The arithmetic is `otpFitBox` in `InputOTP.metrics.ts`, shared by both
226
+ renderers: React Native measures the row with `onLayout`, the web lets CSS flex
227
+ share it under the same bounds.
228
+
194
229
  ### Color Themes
195
230
  - `primary`: Main brand color
196
231
  - `secondary`: Alternative brand color
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The numbers both renderers read (see `NATIVE.md`, "Numbers").
3
+ *
4
+ * Each box's type size, in design px, per size step; its side is the theme's
5
+ * field height for that step (a square), its weight 600.
6
+ */
7
+ export const OTP_FONT_PX: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl', number> = {
8
+ xs: 12,
9
+ sm: 14,
10
+ md: 16,
11
+ lg: 18,
12
+ xl: 20,
13
+ };
14
+
15
+ /** The gap between two boxes: MUI's `gap: 1`, one 8px spacing unit. */
16
+ export const OTP_GAP_PX = 8;
17
+
18
+ /** The border: 1px at rest, 2px focused (MUI's focused outline). */
19
+ export const OTP_BORDER = { rest: 1, focused: 2 } as const;
20
+
21
+ /**
22
+ * `fit="width"` (FUT-4585): the gap between two boxes when they share the row's
23
+ * width — the approved prototype's 6px, a little tighter than the default 8px
24
+ * so six portrait boxes stay at least 40 wide on a 320 phone.
25
+ */
26
+ export const OTP_FIT_GAP_PX = 6;
27
+
28
+ /** `fit="width"`: no box is narrower than this, however narrow the row. */
29
+ export const OTP_FIT_MIN_WIDTH_PX = 32;
30
+
31
+ /** `fit="width"`: no box is shorter than the 40px touch floor, whatever its size. */
32
+ export const OTP_FIT_MIN_HEIGHT_PX = 40;
33
+
34
+ /**
35
+ * One box's size under `fit="width"`, for a row `rowWidth` wide holding
36
+ * `length` boxes whose size step draws a field `side` tall.
37
+ *
38
+ * The box keeps the field's height (never under {@link OTP_FIT_MIN_HEIGHT_PX})
39
+ * and takes an equal share of the row, less the gaps, for its width: floored
40
+ * to a whole pixel so six of them never overflow the row, never wider than it
41
+ * is tall (a wide row keeps squares) and never under
42
+ * {@link OTP_FIT_MIN_WIDTH_PX}. A narrow row therefore draws PORTRAIT boxes
43
+ * rather than dropping to a smaller size and its smaller type.
44
+ */
45
+ export function otpFitBox(rowWidth: number, side: number, length: number): { width: number; height: number } {
46
+ const height = Math.max(side, OTP_FIT_MIN_HEIGHT_PX);
47
+ const count = Math.max(1, Math.floor(length));
48
+ const share = Math.floor((rowWidth - (count - 1) * OTP_FIT_GAP_PX) / count);
49
+ const width = Math.min(height, Math.max(OTP_FIT_MIN_WIDTH_PX, Number.isFinite(share) ? share : height));
50
+ return { width, height };
51
+ }
@@ -0,0 +1,217 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Platform,
4
+ StyleSheet,
5
+ TextInput,
6
+ View,
7
+ type LayoutChangeEvent,
8
+ type TextInputProps,
9
+ type TextStyle,
10
+ type ViewStyle,
11
+ } from 'react-native';
12
+
13
+ import { useNativeOtpDigits } from './InputOTP.hooks.native';
14
+ import {
15
+ OTP_BORDER,
16
+ OTP_FIT_GAP_PX,
17
+ OTP_FIT_MIN_HEIGHT_PX,
18
+ OTP_FIT_MIN_WIDTH_PX,
19
+ OTP_FONT_PX,
20
+ OTP_GAP_PX,
21
+ otpFitBox,
22
+ } from './InputOTP.metrics';
23
+ import type { InputOTPProps } from './InputOTP.types.native';
24
+ import { webAria, webDisabled } from '../../../platform/aria';
25
+ import { resolveFieldEdge } from '../../../tokens/field-edge.core';
26
+ import { fieldHeightPx } from '../../../tokens/field-height.core';
27
+ import type { UiPaletteKey, UiTheme } from '../../../tokens/theme';
28
+ import type { SizeValue } from '../../../tokens/vocabulary';
29
+ import { useUiTheme } from '../../../provider/use-ui-theme.native';
30
+
31
+ const PALETTE_KEYS: readonly UiPaletteKey[] = ['primary', 'secondary', 'success', 'warning', 'info', 'danger', 'neutral'];
32
+
33
+ /** The palette slot a `color` names; anything else is `primary`, as on the web. */
34
+ function accentOf(theme: UiTheme, color: string): string {
35
+ const key = PALETTE_KEYS.find((candidate) => candidate === color) ?? 'primary';
36
+ return theme.palette[key].main;
37
+ }
38
+
39
+ /**
40
+ * The keypad and the one-time-code fill, the web's `keypadProps` in React
41
+ * Native's words: digits only for `numeric`, and on the FIRST box the hint that
42
+ * lets the phone offer the code from an SMS — `oneTimeCode` on iOS,
43
+ * `sms-otp` on Android.
44
+ */
45
+ function keypadProps(variant: string, index: number): Partial<TextInputProps> {
46
+ // A code is not prose: no capital on the first letter, no autocorrect.
47
+ if (variant !== 'numeric') return { autoCapitalize: 'none', autoCorrect: false, spellCheck: false };
48
+ const fill: Partial<TextInputProps> =
49
+ index === 0
50
+ ? Platform.select<Partial<TextInputProps>>({
51
+ ios: { textContentType: 'oneTimeCode', autoComplete: 'one-time-code' },
52
+ android: { autoComplete: 'sms-otp' },
53
+ default: { autoComplete: 'one-time-code' },
54
+ })
55
+ : {};
56
+ return { keyboardType: 'number-pad', inputMode: 'numeric', ...fill };
57
+ }
58
+
59
+ /** The first box takes a whole code in one change (see the web's `slotMaxLength`), except `masked`'s. */
60
+ const boxMaxLength = (variant: string, index: number, length: number): number =>
61
+ variant !== 'masked' && index === 0 ? length + 1 : 1;
62
+
63
+ interface BoxState {
64
+ focused: boolean;
65
+ error: boolean;
66
+ disabled: boolean;
67
+ }
68
+
69
+ /**
70
+ * A box's footprint under `fit="width"`: the measured share of the row once
71
+ * the row has been laid out, and until then a flex share bounded by the same
72
+ * limits, so the first frame already fits the row.
73
+ */
74
+ function fitFootprint(side: number, length: number, rowWidth: number | null): TextStyle {
75
+ if (rowWidth !== null) return otpFitBox(rowWidth, side, length);
76
+ const height = Math.max(side, OTP_FIT_MIN_HEIGHT_PX);
77
+ return { height, flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: OTP_FIT_MIN_WIDTH_PX, maxWidth: height };
78
+ }
79
+
80
+ /**
81
+ * `fit="width"`'s state: the row's width once laid out (the row takes its
82
+ * parent's whole width), the boxes' footprint from it, and what the row adds —
83
+ * its `onLayout` and the fit style. Without the option, nothing: the boxes keep
84
+ * their square and the row's props are exactly the default's.
85
+ *
86
+ * The share is of the row's OUTER width, so under `fit` the row's `style`
87
+ * should set no horizontal padding and no gap: pad the parent instead.
88
+ */
89
+ function useFitWidth(fit: InputOTPProps['fit'], side: number, length: number) {
90
+ const [rowWidth, setRowWidth] = React.useState<number | null>(null);
91
+ if (fit !== 'width') return { footprint: undefined, rowLayout: {}, rowStyles: [] };
92
+ return {
93
+ footprint: fitFootprint(side, length, rowWidth),
94
+ rowLayout: { onLayout: (event: LayoutChangeEvent) => setRowWidth(event.nativeEvent.layout.width) },
95
+ rowStyles: [styles.fitRow],
96
+ };
97
+ }
98
+
99
+ /** One box's paint: the web slot's square, border and type, from the same numbers. */
100
+ function boxStyle(theme: UiTheme, size: SizeValue, accent: string, state: BoxState, footprint?: TextStyle): TextStyle {
101
+ const side = fieldHeightPx(theme.fieldHeight, size);
102
+ const edge = resolveFieldEdge(theme.palette.divider, theme.palette.background.paper);
103
+ // Error and disabled outrank focus, as in MUI's outlined input (and the native `Input`).
104
+ const borderColor = state.error
105
+ ? theme.palette.danger.main
106
+ : state.disabled
107
+ ? theme.palette.action.disabled
108
+ : state.focused
109
+ ? accent
110
+ : edge;
111
+ return {
112
+ ...(footprint ?? { width: side, height: side }),
113
+ borderRadius: theme.radius.field,
114
+ borderWidth: state.focused ? OTP_BORDER.focused : OTP_BORDER.rest,
115
+ borderColor,
116
+ backgroundColor: theme.palette.background.paper,
117
+ color: state.disabled ? theme.palette.text.disabled : theme.palette.text.primary,
118
+ fontFamily: theme.typography.fontFamily,
119
+ fontSize: OTP_FONT_PX[size],
120
+ fontWeight: '600',
121
+ textAlign: 'center',
122
+ padding: 0,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * The native `InputOTP`: one row of boxes, one character each, that moves
128
+ * forward as it is typed, steps back on Backspace in an empty box, and spreads
129
+ * a code the phone fills (or a paste) across every box from the first.
130
+ *
131
+ * The same props as the web one; the row is a `group` named by `aria-label`,
132
+ * each box is `${dataTestId}-slot-${index}`. Gaps against the web:
133
+ * `NATIVE-NOTES.md`.
134
+ */
135
+ export const InputOTP = React.forwardRef<View, InputOTPProps>((props, ref) => {
136
+ const {
137
+ variant = 'numeric',
138
+ color = 'primary',
139
+ size = 'md',
140
+ length = 6,
141
+ value = '',
142
+ onChange,
143
+ onComplete,
144
+ autoFocus = false,
145
+ error = false,
146
+ disabled = false,
147
+ dataTestId,
148
+ fit,
149
+ style,
150
+ 'aria-label': ariaLabel,
151
+ } = props;
152
+
153
+ const theme = useUiTheme();
154
+ const accent = accentOf(theme, color);
155
+ const [focusedIndex, setFocusedIndex] = React.useState<number | null>(null);
156
+ const { footprint, rowLayout, rowStyles } = useFitWidth(fit, fieldHeightPx(theme.fieldHeight, size), length);
157
+ const { digits, boxRefs, handleChangeText, handleKey } = useNativeOtpDigits({
158
+ variant,
159
+ length,
160
+ value,
161
+ onChange,
162
+ onComplete,
163
+ });
164
+
165
+ return (
166
+ <View
167
+ ref={ref}
168
+ testID={dataTestId}
169
+ role="group"
170
+ aria-label={ariaLabel}
171
+ {...rowLayout}
172
+ style={[styles.row, ...rowStyles, style]}
173
+ >
174
+ {digits.map((digit, index) => (
175
+ <TextInput
176
+ key={index}
177
+ ref={(element) => {
178
+ boxRefs.current[index] = element;
179
+ }}
180
+ testID={dataTestId ? `${dataTestId}-slot-${index}` : undefined}
181
+ value={variant === 'masked' ? (digit ? '•' : '') : digit}
182
+ onChangeText={(text) => handleChangeText(index, text)}
183
+ onKeyPress={(event) => handleKey(index, event.nativeEvent.key)}
184
+ onFocus={() => setFocusedIndex(index)}
185
+ onBlur={() => setFocusedIndex((current) => (current === index ? null : current))}
186
+ autoFocus={autoFocus && index === 0}
187
+ editable={!disabled}
188
+ aria-disabled={disabled}
189
+ {...webAria({ 'aria-invalid': error })}
190
+ maxLength={boxMaxLength(variant, index, length)}
191
+ style={boxStyle(theme, size, accent, { focused: focusedIndex === index && !disabled, error, disabled }, footprint)}
192
+ {...webDisabled(disabled)}
193
+ {...keypadProps(variant, index)}
194
+ />
195
+ ))}
196
+ </View>
197
+ );
198
+ });
199
+
200
+ InputOTP.displayName = 'InputOTP';
201
+
202
+ const styles = StyleSheet.create({
203
+ row: {
204
+ flexDirection: 'row',
205
+ justifyContent: 'center',
206
+ alignItems: 'center',
207
+ gap: OTP_GAP_PX,
208
+ },
209
+ /** `fit="width"`: the parent's whole width, in a column or a row parent, shared at the prototype's gap. */
210
+ fitRow: {
211
+ // Both: `stretch` alone spans a column parent but not a row parent, where
212
+ // the row would shrink to its boxes and collapse them to their minimum.
213
+ alignSelf: 'stretch',
214
+ width: '100%',
215
+ gap: OTP_FIT_GAP_PX,
216
+ } satisfies ViewStyle,
217
+ });
@@ -7,6 +7,8 @@ import { asFieldSize, fieldHeight } from '../../../tokens/field-height';
7
7
  import { absoluteInk, controlNeutral } from '../../../tokens/ink';
8
8
  import { rem } from '../../../tokens/relative';
9
9
 
10
+ import { OTP_FIT_MIN_HEIGHT_PX, OTP_FIT_MIN_WIDTH_PX, OTP_FONT_PX } from './InputOTP.metrics';
11
+
10
12
  interface ColorPalette {
11
13
  main: string;
12
14
  dark?: string;
@@ -47,37 +49,54 @@ const getColorFromTheme = (theme: Theme, color: string): ColorPalette => {
47
49
  };
48
50
  };
49
51
 
50
- type SizeKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
51
-
52
- /** Each slot's type, in design px. Its side is the theme's field height for the size (a square). */
53
- const SIZES: Record<SizeKey, { fontPx: number }> = {
54
- xs: { fontPx: 12 },
55
- sm: { fontPx: 14 },
56
- md: { fontPx: 16 },
57
- lg: { fontPx: 18 },
58
- xl: { fontPx: 20 },
59
- };
52
+ type SizeKey = keyof typeof OTP_FONT_PX;
60
53
 
61
54
  export interface OtpSlotFlags {
62
55
  customColor?: string;
63
56
  customSize?: string;
64
57
  glass?: boolean;
65
58
  gradient?: boolean;
59
+ /** `fit="width"`: share the row's width instead of drawing a square (FUT-4585). */
60
+ fitWidth?: boolean;
61
+ }
62
+
63
+ interface SlotBox {
64
+ /** The slot's own footprint in the row. */
65
+ outer: CSSObject;
66
+ /** The outlined input inside it. */
67
+ inner: CSSObject;
66
68
  }
67
69
 
70
+ /**
71
+ * A slot's box. By default a square of the field height, inside and out.
72
+ *
73
+ * Under `fit="width"` (FUT-4585), the native `otpFitBox` in CSS: the slot keeps
74
+ * the field's height (never under the 40px touch floor) and takes an equal
75
+ * share of the row, never wider than it is tall, never under 32px; the input
76
+ * fills it.
77
+ */
78
+ const slotBox = (theme: Theme, side: string, fitWidth: boolean | undefined): SlotBox => {
79
+ if (!fitWidth) return { outer: { width: side, height: side }, inner: { width: side, height: side } };
80
+ const height = `max(${side}, ${rem(theme, OTP_FIT_MIN_HEIGHT_PX)})`;
81
+ return {
82
+ outer: { flex: '1 1 0', minWidth: rem(theme, OTP_FIT_MIN_WIDTH_PX), maxWidth: height, height },
83
+ inner: { width: '100%', height },
84
+ };
85
+ };
86
+
68
87
  export const otpSlotSx = (theme: Theme, flags: OtpSlotFlags): CSSObject => {
69
- const { customColor = 'primary', customSize = 'md', glass, gradient } = flags;
88
+ const { customColor = 'primary', customSize = 'md', glass, gradient, fitWidth } = flags;
70
89
  const palette = getColorFromTheme(theme, customColor);
71
- const { fontPx } = SIZES[customSize as SizeKey] ?? SIZES.md;
90
+ const fontPx = OTP_FONT_PX[customSize as SizeKey] ?? OTP_FONT_PX.md;
72
91
  // A slot is a field: a square of the theme's field height for its size.
73
92
  const side = fieldHeight(theme, asFieldSize(customSize));
74
93
 
94
+ const box = slotBox(theme, side, fitWidth);
95
+
75
96
  return {
76
- width: side,
77
- height: side,
97
+ ...box.outer,
78
98
  '& .MuiOutlinedInput-root': {
79
- width: side,
80
- height: side,
99
+ ...box.inner,
81
100
  fontSize: rem(theme, fontPx),
82
101
  fontWeight: 600,
83
102
  textAlign: 'center',