@12-apps/ui 1.0.0 → 1.3.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 (448) hide show
  1. package/components-catalog.md +14 -0
  2. package/package.json +42 -27
  3. package/src/__tests__/data-grid.test.tsx +125 -0
  4. package/src/__tests__/package-wiring.test.ts +12 -8
  5. package/src/__tests__/social-login-button.test.tsx +5 -5
  6. package/src/__tests__/viewport.test.ts +50 -0
  7. package/src/charts/index.ts +8 -0
  8. package/src/components/data-display/Alert/Alert.md +1 -1
  9. package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
  10. package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
  11. package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
  12. package/src/components/data-display/Alert/Alert.tsx +135 -328
  13. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
  14. package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
  15. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
  16. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
  17. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
  18. package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
  19. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
  20. package/src/components/data-display/Avatar/Avatar.tsx +154 -450
  21. package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
  22. package/src/components/data-display/Badge/Badge.animations.ts +67 -0
  23. package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
  24. package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
  25. package/src/components/data-display/Badge/Badge.styles.ts +261 -0
  26. package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
  27. package/src/components/data-display/Badge/Badge.tsx +151 -557
  28. package/src/components/data-display/Badge/Badge.variants.ts +106 -0
  29. package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
  30. package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
  31. package/src/components/data-display/Banner/Banner.styles.ts +120 -0
  32. package/src/components/data-display/Banner/Banner.tsx +179 -224
  33. package/src/components/data-display/Chart/Chart.md +48 -1
  34. package/src/components/data-display/Chart/Chart.tsx +82 -500
  35. package/src/components/data-display/Chart/Chart.types.ts +2 -0
  36. package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
  37. package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
  38. package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
  39. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
  40. package/src/components/data-display/Chart/SpecChart.tsx +53 -0
  41. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
  42. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
  43. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
  44. package/src/components/data-display/Chart/chart-format.ts +26 -0
  45. package/src/components/data-display/Chart/chart-internals.ts +148 -0
  46. package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
  47. package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
  48. package/src/components/data-display/Chart/chart-spec.ts +171 -0
  49. package/src/components/data-display/Chart/index.ts +30 -0
  50. package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
  51. package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
  52. package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
  53. package/src/components/data-display/Chip/Chip.tsx +31 -94
  54. package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
  55. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
  56. package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
  57. package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
  58. package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
  59. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
  60. package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
  61. package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
  62. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
  63. package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
  64. package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
  65. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
  66. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
  67. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
  68. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
  69. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
  70. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
  71. package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
  72. package/src/components/data-display/DataViews/base-card.tsx +278 -0
  73. package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
  74. package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
  75. package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
  76. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
  77. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
  78. package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
  79. package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
  80. package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
  81. package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
  82. package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
  83. package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
  84. package/src/components/data-display/DataViews/data-views-types.ts +312 -0
  85. package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
  86. package/src/components/data-display/DataViews/index.ts +33 -0
  87. package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
  88. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
  89. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
  90. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
  91. package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
  92. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
  93. package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
  94. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
  95. package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
  96. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
  97. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
  98. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
  99. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
  100. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
  101. package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
  102. package/src/components/data-display/LazyImage/LazyImage.md +1 -1
  103. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
  104. package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
  105. package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
  106. package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
  107. package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
  108. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
  109. package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
  110. package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
  111. package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
  112. package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
  113. package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
  114. package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
  115. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
  116. package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
  117. package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
  118. package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
  119. package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
  120. package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
  121. package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
  122. package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
  123. package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
  124. package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
  125. package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
  126. package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
  127. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
  128. package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
  129. package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
  130. package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
  131. package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
  132. package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
  133. package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
  134. package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
  135. package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
  136. package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
  137. package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
  138. package/src/components/data-display/Progress/Progress.styles.ts +101 -0
  139. package/src/components/data-display/Progress/Progress.tsx +185 -213
  140. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
  141. package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
  142. package/src/components/data-display/StatCard/StatCard.md +117 -0
  143. package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
  144. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
  145. package/src/components/data-display/StatCard/StatCard.tsx +236 -0
  146. package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
  147. package/src/components/data-display/StatCard/index.ts +7 -0
  148. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
  149. package/src/components/data-display/Stepper/Stepper.tsx +1 -1
  150. package/src/components/data-display/Table/Table.helpers.ts +31 -0
  151. package/src/components/data-display/Table/Table.hooks.ts +161 -0
  152. package/src/components/data-display/Table/Table.stories.tsx +3 -1
  153. package/src/components/data-display/Table/Table.styles.ts +223 -0
  154. package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
  155. package/src/components/data-display/Table/Table.tsx +291 -748
  156. package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
  157. package/src/components/data-display/Table/TableParts.tsx +298 -0
  158. package/src/components/data-display/Table/TableStates.tsx +21 -0
  159. package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
  160. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
  161. package/src/components/data-display/Timeline/Timeline.tsx +23 -284
  162. package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
  163. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
  164. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
  165. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
  166. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
  167. package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
  168. package/src/components/data-display/index.ts +1 -0
  169. package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
  170. package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
  171. package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
  172. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
  173. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
  174. package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
  175. package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
  176. package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
  177. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
  178. package/src/components/feedback/ConfirmAction/index.ts +16 -0
  179. package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
  180. package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
  181. package/src/components/feedback/Dialog/Dialog.tsx +26 -1
  182. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
  183. package/src/components/feedback/Modal/Modal.md +1 -1
  184. package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
  185. package/src/components/feedback/Sonner/Sonner.tsx +104 -288
  186. package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
  187. package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
  188. package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
  189. package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
  190. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
  191. package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
  192. package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
  193. package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
  194. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
  195. package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
  196. package/src/components/feedback/Toast/Toast.tsx +92 -81
  197. package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
  198. package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
  199. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
  200. package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
  201. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
  202. package/src/components/feedback/index.ts +1 -0
  203. package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
  204. package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
  205. package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
  206. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
  207. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
  208. package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
  209. package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
  210. package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
  211. package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
  212. package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
  213. package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
  214. package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
  215. package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
  216. package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
  217. package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
  218. package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
  219. package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
  220. package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
  221. package/src/components/form/Button/Button.helpers.ts +36 -0
  222. package/src/components/form/Button/Button.stories.tsx +4 -1
  223. package/src/components/form/Button/Button.styles.ts +225 -0
  224. package/src/components/form/Button/Button.test.stories.tsx +25 -21
  225. package/src/components/form/Button/Button.tsx +45 -252
  226. package/src/components/form/Calendar/Calendar.dates.ts +100 -0
  227. package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
  228. package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
  229. package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
  230. package/src/components/form/Calendar/Calendar.selection.ts +156 -0
  231. package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
  232. package/src/components/form/Calendar/Calendar.tsx +63 -551
  233. package/src/components/form/Calendar/Calendar.types.ts +16 -0
  234. package/src/components/form/Calendar/CalendarDay.tsx +127 -0
  235. package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
  236. package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
  237. package/src/components/form/CepField/CepField.md +138 -0
  238. package/src/components/form/CepField/CepField.stories.tsx +138 -0
  239. package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
  240. package/src/components/form/CepField/CepField.tsx +211 -0
  241. package/src/components/form/CepField/CepField.types.ts +73 -0
  242. package/src/components/form/CepField/index.ts +2 -0
  243. package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
  244. package/src/components/form/Checkbox/Checkbox.tsx +19 -16
  245. package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
  246. package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
  247. package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
  248. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
  249. package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
  250. package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
  251. package/src/components/form/Command/Command.hooks.ts +207 -0
  252. package/src/components/form/Command/Command.parts.tsx +58 -0
  253. package/src/components/form/Command/Command.styles.ts +83 -0
  254. package/src/components/form/Command/Command.tsx +61 -270
  255. package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
  256. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
  257. package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
  258. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
  259. package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
  260. package/src/components/form/HeaderButton/index.ts +2 -0
  261. package/src/components/form/Input/Input.stories.tsx +5 -1
  262. package/src/components/form/Input/Input.styles.ts +176 -0
  263. package/src/components/form/Input/Input.test.stories.tsx +30 -23
  264. package/src/components/form/Input/Input.tsx +71 -211
  265. package/src/components/form/Input/Input.types.ts +11 -0
  266. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
  267. package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
  268. package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
  269. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
  270. package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
  271. package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
  272. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
  273. package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
  274. package/src/components/form/PhoneInput/countries.ts +54 -0
  275. package/src/components/form/PhoneInput/phone.ts +47 -0
  276. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
  277. package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
  278. package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
  279. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
  280. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
  281. package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
  282. package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
  283. package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
  284. package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
  285. package/src/components/form/Select/Select.test.stories.tsx +35 -27
  286. package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
  287. package/src/components/form/Switch/Switch.md +2 -1
  288. package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
  289. package/src/components/form/Textarea/Textarea.styles.ts +143 -0
  290. package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
  291. package/src/components/form/Textarea/Textarea.tsx +114 -535
  292. package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
  293. package/src/components/form/Toggle/Toggle.styles.ts +211 -0
  294. package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
  295. package/src/components/form/Toggle/Toggle.tsx +10 -185
  296. package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
  297. package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
  298. package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
  299. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
  300. package/src/components/form/UploadButton/UploadButton.tsx +76 -305
  301. package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
  302. package/src/components/form/index.ts +1 -0
  303. package/src/components/form/total-form/Fields/CepField.tsx +143 -0
  304. package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
  305. package/src/components/form/total-form/Fields/index.ts +4 -0
  306. package/src/components/form/total-form/FormContainer.tsx +3 -3
  307. package/src/components/form/total-form/SubmitButton.tsx +25 -2
  308. package/src/components/form/total-form/total-form.test.tsx +118 -1
  309. package/src/components/layout/Accordion/Accordion.md +1 -1
  310. package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
  311. package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
  312. package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
  313. package/src/components/layout/Accordion/Accordion.tsx +2 -75
  314. package/src/components/layout/Card/Card.helpers.ts +30 -0
  315. package/src/components/layout/Card/Card.stories.tsx +9 -11
  316. package/src/components/layout/Card/Card.styles.ts +182 -0
  317. package/src/components/layout/Card/Card.test.stories.tsx +46 -46
  318. package/src/components/layout/Card/Card.tsx +24 -241
  319. package/src/components/layout/CardGrid/CardGrid.md +96 -0
  320. package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
  321. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
  322. package/src/components/layout/CardGrid/index.ts +2 -0
  323. package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
  324. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
  325. package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
  326. package/src/components/layout/Container/Container.test.stories.tsx +11 -16
  327. package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
  328. package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
  329. package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
  330. package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
  331. package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
  332. package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
  333. package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
  334. package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
  335. package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
  336. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
  337. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
  338. package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
  339. package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
  340. package/src/components/layout/Dashboard/index.ts +2 -1
  341. package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
  342. package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
  343. package/src/components/layout/Drawer/Drawer.tsx +67 -53
  344. package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
  345. package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
  346. package/src/components/layout/Resizable/Resizable.tsx +38 -153
  347. package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
  348. package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
  349. package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
  350. package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
  351. package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
  352. package/src/components/layout/Separator/Separator.styles.ts +128 -0
  353. package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
  354. package/src/components/layout/Separator/Separator.tsx +6 -132
  355. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
  356. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
  357. package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
  358. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
  359. package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
  360. package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
  361. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
  362. package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
  363. package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
  364. package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
  365. package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
  366. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
  367. package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
  368. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
  369. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
  370. package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
  371. package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
  372. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
  373. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
  374. package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
  375. package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
  376. package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
  377. package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
  378. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
  379. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
  380. package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
  381. package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
  382. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
  383. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
  384. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
  385. package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
  386. package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
  387. package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
  388. package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
  389. package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
  390. package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
  391. package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
  392. package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
  393. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
  394. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
  395. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
  396. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
  397. package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
  398. package/src/components/navigation/Pagination/Pagination.md +2 -1
  399. package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
  400. package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
  401. package/src/components/navigation/Pagination/Pagination.tsx +178 -313
  402. package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
  403. package/src/components/navigation/Tabs/Tabs.md +3 -1
  404. package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
  405. package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
  406. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
  407. package/src/components/navigation/Tabs/Tabs.tsx +192 -361
  408. package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
  409. package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
  410. package/src/components/typography/Code/Code.test.stories.tsx +5 -5
  411. package/src/components/typography/Code/Code.tsx +74 -49
  412. package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
  413. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
  414. package/src/components/typography/Text/Text.tsx +84 -77
  415. package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
  416. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
  417. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
  418. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
  419. package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
  420. package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
  421. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
  422. package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
  423. package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
  424. package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
  425. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
  426. package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
  427. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
  428. package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
  429. package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
  430. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
  431. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
  432. package/src/components/utility/InstallPrompt/index.ts +10 -0
  433. package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
  434. package/src/components/utility/Transition/Transition.helpers.ts +40 -0
  435. package/src/components/utility/Transition/Transition.tsx +17 -68
  436. package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
  437. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
  438. package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
  439. package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
  440. package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
  441. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
  442. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
  443. package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
  444. package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
  445. package/src/components/utility/index.ts +1 -0
  446. package/src/mui/Link.ts +2 -0
  447. package/src/utils/index.ts +2 -1
  448. package/src/utils/viewport.ts +65 -0
@@ -118,7 +118,7 @@ export const KeyboardNavigation: Story = {
118
118
 
119
119
  // Tab to country selector
120
120
  await userEvent.tab();
121
- await expect(countrySelector).toHaveFocus();
121
+ await waitFor(() => expect(countrySelector).toHaveFocus());
122
122
 
123
123
  // Press Enter to open country menu
124
124
  await userEvent.keyboard('[Enter]');
@@ -189,12 +189,12 @@ export const FocusManagement: Story = {
189
189
 
190
190
  // Focus the input
191
191
  await userEvent.click(input);
192
- await expect(input).toHaveFocus();
192
+ await waitFor(() => expect(input).toHaveFocus());
193
193
 
194
194
  // Tab away and back
195
195
  await userEvent.tab();
196
196
  await userEvent.tab({ shift: true });
197
- await expect(input).toHaveFocus();
197
+ await waitFor(() => expect(input).toHaveFocus());
198
198
  },
199
199
  };
200
200
 
@@ -226,7 +226,7 @@ export const ResponsiveDesign: Story = {
226
226
  // Test input functionality on mobile
227
227
  await userEvent.clear(input);
228
228
  await userEvent.click(input);
229
- await expect(input).toHaveFocus();
229
+ await waitFor(() => expect(input).toHaveFocus());
230
230
 
231
231
  // Type phone number on mobile viewport
232
232
  await userEvent.type(input, '5551234567');
@@ -1,4 +1,5 @@
1
- import { ArrowDropDown,Phone as PhoneIcon } from '@mui/icons-material';
1
+ import ArrowDropDown from '@mui/icons-material/ArrowDropDown';
2
+ import PhoneIcon from '@mui/icons-material/Phone';
2
3
  import {
3
4
  alpha,
4
5
  Box,
@@ -10,65 +11,15 @@ import {
10
11
  styled,
11
12
  TextField,
12
13
  Typography,
13
- useTheme,
14
- } from '@mui/material';
15
- import type { CountryCode } from 'libphonenumber-js';
16
- import { parsePhoneNumber } from 'libphonenumber-js';
14
+ useTheme } from '@mui/material';
17
15
  import type { FC} from 'react';
18
- import React, { useEffect, useRef,useState } from 'react';
16
+ import React, { } from 'react';
19
17
 
18
+ import { countries } from './countries';
19
+ import { usePhoneInput } from './PhoneInput.hooks';
20
20
  import type { CountryData,PhoneInputProps } from './PhoneInput.types';
21
21
 
22
22
  // Country data with expanded support
23
- const countries: CountryData[] = [
24
- { code: 'US' as CountryCode, name: 'United States', dial: '+1', flag: '🇺🇸' },
25
- { code: 'GB' as CountryCode, name: 'United Kingdom', dial: '+44', flag: '🇬🇧' },
26
- { code: 'CA' as CountryCode, name: 'Canada', dial: '+1', flag: '🇨🇦' },
27
- { code: 'AU' as CountryCode, name: 'Australia', dial: '+61', flag: '🇦🇺' },
28
- { code: 'DE' as CountryCode, name: 'Germany', dial: '+49', flag: '🇩🇪' },
29
- { code: 'FR' as CountryCode, name: 'France', dial: '+33', flag: '🇫🇷' },
30
- { code: 'IT' as CountryCode, name: 'Italy', dial: '+39', flag: '🇮🇹' },
31
- { code: 'ES' as CountryCode, name: 'Spain', dial: '+34', flag: '🇪🇸' },
32
- { code: 'NL' as CountryCode, name: 'Netherlands', dial: '+31', flag: '🇳🇱' },
33
- { code: 'JP' as CountryCode, name: 'Japan', dial: '+81', flag: '🇯🇵' },
34
- { code: 'CN' as CountryCode, name: 'China', dial: '+86', flag: '🇨🇳' },
35
- { code: 'IN' as CountryCode, name: 'India', dial: '+91', flag: '🇮🇳' },
36
- { code: 'BR' as CountryCode, name: 'Brazil', dial: '+55', flag: '🇧🇷' },
37
- { code: 'MX' as CountryCode, name: 'Mexico', dial: '+52', flag: '🇲🇽' },
38
- { code: 'KR' as CountryCode, name: 'South Korea', dial: '+82', flag: '🇰🇷' },
39
- { code: 'SE' as CountryCode, name: 'Sweden', dial: '+46', flag: '🇸🇪' },
40
- { code: 'NO' as CountryCode, name: 'Norway', dial: '+47', flag: '🇳🇴' },
41
- { code: 'DK' as CountryCode, name: 'Denmark', dial: '+45', flag: '🇩🇰' },
42
- { code: 'CH' as CountryCode, name: 'Switzerland', dial: '+41', flag: '🇨🇭' },
43
- { code: 'AT' as CountryCode, name: 'Austria', dial: '+43', flag: '🇦🇹' },
44
- { code: 'BE' as CountryCode, name: 'Belgium', dial: '+32', flag: '🇧🇪' },
45
- { code: 'FI' as CountryCode, name: 'Finland', dial: '+358', flag: '🇫🇮' },
46
- { code: 'IE' as CountryCode, name: 'Ireland', dial: '+353', flag: '🇮🇪' },
47
- { code: 'PT' as CountryCode, name: 'Portugal', dial: '+351', flag: '🇵🇹' },
48
- { code: 'GR' as CountryCode, name: 'Greece', dial: '+30', flag: '🇬🇷' },
49
- { code: 'PL' as CountryCode, name: 'Poland', dial: '+48', flag: '🇵🇱' },
50
- { code: 'CZ' as CountryCode, name: 'Czech Republic', dial: '+420', flag: '🇨🇿' },
51
- { code: 'HU' as CountryCode, name: 'Hungary', dial: '+36', flag: '🇭🇺' },
52
- { code: 'RU' as CountryCode, name: 'Russia', dial: '+7', flag: '🇷🇺' },
53
- { code: 'ZA' as CountryCode, name: 'South Africa', dial: '+27', flag: '🇿🇦' },
54
- { code: 'EG' as CountryCode, name: 'Egypt', dial: '+20', flag: '🇪🇬' },
55
- { code: 'NG' as CountryCode, name: 'Nigeria', dial: '+234', flag: '🇳🇬' },
56
- { code: 'KE' as CountryCode, name: 'Kenya', dial: '+254', flag: '🇰🇪' },
57
- { code: 'AR' as CountryCode, name: 'Argentina', dial: '+54', flag: '🇦🇷' },
58
- { code: 'CL' as CountryCode, name: 'Chile', dial: '+56', flag: '🇨🇱' },
59
- { code: 'CO' as CountryCode, name: 'Colombia', dial: '+57', flag: '🇨🇴' },
60
- { code: 'PE' as CountryCode, name: 'Peru', dial: '+51', flag: '🇵🇪' },
61
- { code: 'MY' as CountryCode, name: 'Malaysia', dial: '+60', flag: '🇲🇾' },
62
- { code: 'SG' as CountryCode, name: 'Singapore', dial: '+65', flag: '🇸🇬' },
63
- { code: 'TH' as CountryCode, name: 'Thailand', dial: '+66', flag: '🇹🇭' },
64
- { code: 'VN' as CountryCode, name: 'Vietnam', dial: '+84', flag: '🇻🇳' },
65
- { code: 'PH' as CountryCode, name: 'Philippines', dial: '+63', flag: '🇵🇭' },
66
- { code: 'ID' as CountryCode, name: 'Indonesia', dial: '+62', flag: '🇮🇩' },
67
- { code: 'TR' as CountryCode, name: 'Turkey', dial: '+90', flag: '🇹🇷' },
68
- { code: 'IL' as CountryCode, name: 'Israel', dial: '+972', flag: '🇮🇱' },
69
- { code: 'AE' as CountryCode, name: 'United Arab Emirates', dial: '+971', flag: '🇦🇪' },
70
- { code: 'SA' as CountryCode, name: 'Saudi Arabia', dial: '+966', flag: '🇸🇦' },
71
- ].sort((a, b) => a.name.localeCompare(b.name));
72
23
 
73
24
  // Styled components
74
25
  const GlassTextField = styled(TextField)(({ theme }) => ({
@@ -80,22 +31,15 @@ const GlassTextField = styled(TextField)(({ theme }) => ({
80
31
  transition: theme.transitions.create(['border-color', 'box-shadow', 'background']),
81
32
  '&:hover': {
82
33
  background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.9)} 0%, ${alpha(theme.palette.background.paper, 0.7)} 100%)`,
83
- borderColor: theme.palette.primary.main,
84
- },
34
+ borderColor: theme.palette.primary.main },
85
35
  '&.Mui-focused': {
86
36
  background: theme.palette.background.paper,
87
- boxShadow: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.25)}`,
88
- },
37
+ boxShadow: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.25)}` },
89
38
  '& fieldset': {
90
- border: 'none',
91
- },
92
- },
39
+ border: 'none' } },
93
40
  '& .MuiInputLabel-root': {
94
41
  '&.Mui-focused': {
95
- color: theme.palette.primary.main,
96
- },
97
- },
98
- }));
42
+ color: theme.palette.primary.main } } }));
99
43
 
100
44
  const CountrySelector = styled(Box)(({ theme }) => ({
101
45
  display: 'flex',
@@ -105,9 +49,7 @@ const CountrySelector = styled(Box)(({ theme }) => ({
105
49
  cursor: 'pointer',
106
50
  transition: theme.transitions.create(['background-color']),
107
51
  '&:hover': {
108
- backgroundColor: alpha(theme.palette.primary.main, 0.08),
109
- },
110
- }));
52
+ backgroundColor: alpha(theme.palette.primary.main, 0.08) } }));
111
53
 
112
54
  const CountryMenu = styled(Menu)(({ theme }) => ({
113
55
  '& .MuiPaper-root': {
@@ -115,259 +57,304 @@ const CountryMenu = styled(Menu)(({ theme }) => ({
115
57
  backdropFilter: 'blur(20px)',
116
58
  WebkitBackdropFilter: 'blur(20px)',
117
59
  border: `1px solid ${alpha(theme.palette.divider, 0.18)}`,
118
- maxHeight: 400,
119
- },
120
- }));
60
+ maxHeight: 400 } }));
121
61
 
122
62
  // Helper functions with enhanced validation
123
- const formatPhoneNumber = (value: string, country: CountryCode): string => {
124
- if (!value || value.trim() === '') return value;
125
-
126
- try {
127
- const phoneNumber = parsePhoneNumber(value, country);
128
- // Format if we have a phone number object, regardless of strict validity
129
- // This allows formatting of test numbers (like 555 prefix) and partially valid numbers
130
- if (phoneNumber) {
131
- return phoneNumber.formatInternational();
132
- }
133
- } catch {
134
- // Silently handle formatting errors
135
- }
136
- return value;
137
- };
138
-
139
- const validatePhoneNumber = (value: string, country: CountryCode): boolean => {
140
- if (!value || value.trim() === '') return false;
141
-
142
- try {
143
- const phoneNumber = parsePhoneNumber(value, country);
144
- // Consider a number valid if it can be parsed and has the right format
145
- // This allows test numbers (like 555 prefix) which isPossible but not isValid
146
- return phoneNumber ? phoneNumber.isPossible() : false;
147
- } catch {
148
- // Silently handle validation errors
149
- return false;
150
- }
151
- };
152
-
153
- // Enhanced helper to detect country from number
154
- const detectCountryFromNumber = (value: string): CountryCode | undefined => {
155
- if (!value || !value.startsWith('+')) return undefined;
156
-
157
- try {
158
- const phoneNumber = parsePhoneNumber(value);
159
- return phoneNumber?.country;
160
- } catch {
161
- return undefined;
162
- }
163
- };
164
-
165
- // Main component
166
- export const PhoneInput: FC<PhoneInputProps> = ({
167
- variant = 'outlined',
168
- label = 'Phone Number',
169
- placeholder = 'Enter phone number',
170
- icon = <PhoneIcon />,
171
- defaultValue = '',
172
- countryCode: initialCountryCode = 'US',
173
- floating = false,
63
+ // The text field itself with its two adornments. Split out so PhoneInput is
64
+ // prop defaults, the hook call and composition.
65
+ const PhoneField: React.FC<{
66
+ TextFieldComponent: React.ElementType;
67
+ variant: PhoneInputProps['variant'];
68
+ label?: React.ReactNode;
69
+ placeholder?: string;
70
+ value: string;
71
+ validation: { hasError: boolean; helperText?: React.ReactNode };
72
+ disabled: boolean;
73
+ required: boolean;
74
+ fullWidth: boolean;
75
+ icon?: React.ReactNode;
76
+ isValid: boolean;
77
+ selectedCountry?: CountryData;
78
+ anchorEl: HTMLElement | null;
79
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
80
+ onFocus: () => void;
81
+ onBlur: () => void;
82
+ onCountryOpen: (event: React.MouseEvent<HTMLElement>) => void;
83
+ onCountryOpenViaKeyboard: (element: HTMLElement) => void;
84
+ }> = ({
85
+ TextFieldComponent,
86
+ variant,
87
+ label,
88
+ placeholder,
89
+ value,
90
+ validation,
91
+ disabled,
92
+ required,
93
+ fullWidth,
94
+ icon,
95
+ isValid,
96
+ selectedCountry,
97
+ anchorEl,
174
98
  onChange,
175
- helper,
176
- error = false,
177
- errorMessage,
178
- disabled = false,
179
- required = false,
180
- fullWidth = true,
181
- }) => {
182
- const theme = useTheme();
183
- const [value, setValue] = useState(defaultValue);
184
- const [selectedCountry, setSelectedCountry] = useState(
185
- countries.find((c) => c.code === initialCountryCode) || countries[0],
186
- );
187
- const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
188
- const [isValid, setIsValid] = useState(false);
189
- const [isFocused, setIsFocused] = useState(false);
190
- const onChangeRef = useRef(onChange);
191
- const isInitialMountRef = useRef(true);
99
+ onFocus,
100
+ onBlur,
101
+ onCountryOpen,
102
+ onCountryOpenViaKeyboard }) => (
103
+ <TextFieldComponent
104
+ variant={variant === 'glass' ? 'outlined' : variant}
105
+ label={label}
106
+ placeholder={placeholder}
107
+ value={value}
108
+ onChange={onChange}
109
+ onFocus={onFocus}
110
+ onBlur={onBlur}
111
+ error={validation.hasError}
112
+ helperText={validation.helperText}
113
+ disabled={disabled}
114
+ required={required}
115
+ fullWidth={fullWidth}
116
+ type="tel"
117
+ data-testid="phone-input-field"
118
+ InputProps={{
119
+ startAdornment: (
120
+ <CountryAdornment
121
+ country={selectedCountry}
122
+ menuOpen={Boolean(anchorEl)}
123
+ onOpen={onCountryOpen}
124
+ onOpenViaKeyboard={onCountryOpenViaKeyboard}
125
+ />
126
+ ),
127
+ endAdornment: <ValidityAdornment icon={icon} isValid={isValid} /> }}
128
+ sx={{
129
+ '& input': {
130
+ letterSpacing: '0.5px' } }}
131
+ />
132
+ );
192
133
 
193
- // Update ref when onChange changes
194
- useEffect(() => {
195
- onChangeRef.current = onChange;
196
- }, [onChange]);
134
+ const PHONE_INPUT_DEFAULTS = {
135
+ variant: 'outlined',
136
+ label: 'Phone Number',
137
+ placeholder: 'Enter phone number',
138
+ icon: <PhoneIcon />,
139
+ defaultValue: '',
140
+ countryCode: 'US',
141
+ floating: false,
142
+ error: false,
143
+ disabled: false,
144
+ required: false,
145
+ fullWidth: true } satisfies Partial<PhoneInputProps>;
197
146
 
198
- useEffect(() => {
199
- if (!selectedCountry) return;
147
+ // Drops explicitly-undefined props before the merge, so `prop={undefined}` falls
148
+ // back to the default exactly as a destructuring default would. Eleven separate
149
+ // destructuring defaults were most of this component's branch count.
150
+ const definedProps = (props: PhoneInputProps): Partial<PhoneInputProps> =>
151
+ Object.fromEntries(
152
+ Object.entries(props).filter(([, value]) => value !== undefined),
153
+ ) as Partial<PhoneInputProps>;
200
154
 
201
- const valid = validatePhoneNumber(value, selectedCountry.code);
202
- setIsValid(valid);
155
+ const CountryAdornment: React.FC<{
156
+ country?: CountryData;
157
+ menuOpen: boolean;
158
+ onOpen: (event: React.MouseEvent<HTMLElement>) => void;
159
+ onOpenViaKeyboard: (element: HTMLElement) => void;
160
+ }> = ({ country, menuOpen, onOpen, onOpenViaKeyboard }) => (
161
+ <InputAdornment position="start" data-testid="phone-input-start-adornment">
162
+ <CountrySelector
163
+ onClick={onOpen}
164
+ role="button"
165
+ aria-label="Select country"
166
+ aria-expanded={menuOpen}
167
+ tabIndex={0}
168
+ data-testid="country-selector"
169
+ onKeyDown={(e) => {
170
+ if (e.key !== 'Enter' && e.key !== ' ') return;
171
+ e.preventDefault();
172
+ onOpenViaKeyboard(e.currentTarget as HTMLElement);
173
+ }}
174
+ >
175
+ <Typography variant="h6" component="span" sx={{ mr: 0.5 }} data-testid="country-flag">
176
+ {country?.flag}
177
+ </Typography>
178
+ <Typography
179
+ variant="body2"
180
+ color="text.secondary"
181
+ sx={{ mr: 0.5 }}
182
+ data-testid="country-dial-code"
183
+ >
184
+ {country?.dial}
185
+ </Typography>
186
+ <ArrowDropDown fontSize="small" data-testid="country-dropdown-icon" />
187
+ </CountrySelector>
188
+ </InputAdornment>
189
+ );
203
190
 
204
- // Skip onChange call on initial mount
205
- if (isInitialMountRef.current) {
206
- isInitialMountRef.current = false;
207
- return;
208
- }
191
+ // The trailing icon turns green once the number validates.
192
+ const ValidityAdornment: React.FC<{ icon?: React.ReactNode; isValid: boolean }> = ({
193
+ icon,
194
+ isValid }) => {
195
+ const theme = useTheme();
209
196
 
210
- // Call onChange for all value/country changes after initial mount
211
- if (onChangeRef.current) {
212
- onChangeRef.current(value, valid, selectedCountry.code);
213
- }
214
- }, [value, selectedCountry]);
197
+ if (!icon) return null;
215
198
 
216
- const handleCountryClick = (event: React.MouseEvent<HTMLElement>) => {
217
- setAnchorEl(event.currentTarget);
218
- };
199
+ return (
200
+ <InputAdornment position="end" data-testid="phone-input-end-adornment">
201
+ <Box
202
+ sx={{
203
+ color: isValid ? theme.palette.success.main : theme.palette.text.secondary,
204
+ transition: 'color 0.3s ease' }}
205
+ data-testid="phone-input-icon"
206
+ >
207
+ {icon}
208
+ </Box>
209
+ </InputAdornment>
210
+ );
211
+ };
219
212
 
220
- const handleCountryClose = () => {
221
- setAnchorEl(null);
222
- };
213
+ const CountryPicker: React.FC<{
214
+ anchorEl: HTMLElement | null;
215
+ selectedCode?: CountryData['code'];
216
+ onClose: () => void;
217
+ onSelect: (country: CountryData) => void;
218
+ }> = ({ anchorEl, selectedCode, onClose, onSelect }) => {
219
+ const theme = useTheme();
223
220
 
224
- const handleCountrySelect = (country: CountryData) => {
225
- setSelectedCountry(country);
226
- handleCountryClose();
221
+ return (
222
+ <CountryMenu
223
+ anchorEl={anchorEl}
224
+ open={Boolean(anchorEl)}
225
+ onClose={onClose}
226
+ anchorOrigin={{
227
+ vertical: 'bottom',
228
+ horizontal: 'left' }}
229
+ transformOrigin={{
230
+ vertical: 'top',
231
+ horizontal: 'left' }}
232
+ data-testid="country-menu"
233
+ >
234
+ {countries.map((country) => (
235
+ <MenuItem
236
+ key={country.code}
237
+ onClick={() => onSelect(country)}
238
+ selected={country.code === selectedCode}
239
+ data-testid={`country-option-${country.code}`}
240
+ sx={{
241
+ '&:hover': {
242
+ background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.1)} 0%, ${alpha(theme.palette.primary.main, 0.05)} 100%)` } }}
243
+ >
244
+ <ListItemIcon sx={{ minWidth: 36 }}>
245
+ <Typography variant="h5" component="span">
246
+ {country.flag}
247
+ </Typography>
248
+ </ListItemIcon>
249
+ <ListItemText
250
+ primary={country.name}
251
+ secondary={country.dial}
252
+ primaryTypographyProps={{ fontSize: '0.9rem' }}
253
+ secondaryTypographyProps={{ fontSize: '0.8rem' }}
254
+ />
255
+ </MenuItem>
256
+ ))}
257
+ </CountryMenu>
258
+ );
259
+ };
227
260
 
228
- // Reformat number with new country code
229
- if (value) {
230
- const formatted = formatPhoneNumber(value, country.code);
231
- setValue(formatted);
232
- }
233
- };
261
+ // The field shows an error either because the caller says so, or because what
262
+ // has been typed does not validate and the user has moved on.
263
+ const resolveValidation = ({
264
+ error,
265
+ errorMessage,
266
+ isValid,
267
+ value,
268
+ isFocused,
269
+ helper,
270
+ countryName }: {
271
+ error: boolean;
272
+ errorMessage?: string;
273
+ isValid: boolean;
274
+ value: string;
275
+ isFocused: boolean;
276
+ helper?: React.ReactNode;
277
+ countryName?: string;
278
+ }) => {
279
+ const looksInvalid = !isValid && value !== '' && !isFocused;
234
280
 
235
- const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
236
- const newValue = event.target.value;
237
- setValue(newValue);
281
+ return {
282
+ hasError: error || looksInvalid,
283
+ helperText:
284
+ errorMessage ||
285
+ (looksInvalid ? `Invalid phone number for ${countryName || 'selected country'}` : helper) };
286
+ };
238
287
 
239
- // Auto-detect country if user types international format
240
- if (newValue.startsWith('+')) {
241
- const detectedCountry = detectCountryFromNumber(newValue);
242
- if (detectedCountry) {
243
- const detectedCountryData = countries.find((c) => c.code === detectedCountry);
244
- if (detectedCountryData && detectedCountryData.code !== selectedCountry?.code) {
245
- setSelectedCountry(detectedCountryData);
246
- }
247
- }
248
- }
249
- };
288
+ export const PhoneInput: FC<PhoneInputProps> = (props) => {
289
+ const {
290
+ variant,
291
+ label,
292
+ placeholder,
293
+ icon,
294
+ defaultValue,
295
+ countryCode: initialCountryCode,
296
+ floating,
297
+ onChange,
298
+ helper,
299
+ error,
300
+ errorMessage,
301
+ disabled,
302
+ required,
303
+ fullWidth } = { ...PHONE_INPUT_DEFAULTS, ...definedProps(props) };
304
+ const {
305
+ value,
306
+ selectedCountry,
307
+ anchorEl,
308
+ isValid,
309
+ isFocused,
310
+ setIsFocused,
311
+ setAnchorEl,
312
+ handleCountryClick,
313
+ handleCountryClose,
314
+ handleCountrySelect,
315
+ handleChange,
316
+ handleBlur } = usePhoneInput({ defaultValue, initialCountryCode, onChange });
250
317
 
251
- const handleBlur = () => {
252
- setIsFocused(false);
253
- if (value && selectedCountry) {
254
- const formatted = formatPhoneNumber(value, selectedCountry.code);
255
- setValue(formatted);
256
- }
257
- };
318
+ const validation = resolveValidation({
319
+ error,
320
+ errorMessage,
321
+ isValid,
322
+ value,
323
+ isFocused,
324
+ helper,
325
+ countryName: selectedCountry?.name });
258
326
 
259
327
  const TextFieldComponent = variant === 'glass' ? GlassTextField : TextField;
260
328
 
261
329
  return (
262
330
  <Box data-testid="phone-input-container">
263
- <TextFieldComponent
264
- variant={variant === 'glass' ? 'outlined' : variant}
331
+ <PhoneField
332
+ TextFieldComponent={TextFieldComponent}
333
+ variant={variant}
265
334
  label={floating ? undefined : label}
266
335
  placeholder={placeholder}
267
336
  value={value}
268
- onChange={handleChange}
269
- onFocus={() => setIsFocused(true)}
270
- onBlur={handleBlur}
271
- error={error || (!isValid && value !== '' && !isFocused)}
272
- helperText={
273
- errorMessage ||
274
- (!isValid && value !== '' && !isFocused
275
- ? `Invalid phone number for ${selectedCountry?.name || 'selected country'}`
276
- : helper)
277
- }
337
+ validation={validation}
278
338
  disabled={disabled}
279
339
  required={required}
280
340
  fullWidth={fullWidth}
281
- type="tel"
282
- data-testid="phone-input-field"
283
- InputProps={{
284
- startAdornment: (
285
- <InputAdornment position="start" data-testid="phone-input-start-adornment">
286
- <CountrySelector
287
- onClick={handleCountryClick}
288
- role="button"
289
- aria-label="Select country"
290
- aria-expanded={Boolean(anchorEl)}
291
- tabIndex={0}
292
- data-testid="country-selector"
293
- onKeyDown={(e) => {
294
- if (e.key === 'Enter' || e.key === ' ') {
295
- e.preventDefault();
296
- setAnchorEl(e.currentTarget as HTMLElement);
297
- }
298
- }}
299
- >
300
- <Typography variant="h6" component="span" sx={{ mr: 0.5 }} data-testid="country-flag">
301
- {selectedCountry?.flag}
302
- </Typography>
303
- <Typography variant="body2" color="text.secondary" sx={{ mr: 0.5 }} data-testid="country-dial-code">
304
- {selectedCountry?.dial}
305
- </Typography>
306
- <ArrowDropDown fontSize="small" data-testid="country-dropdown-icon" />
307
- </CountrySelector>
308
- </InputAdornment>
309
- ),
310
- endAdornment: icon && (
311
- <InputAdornment position="end" data-testid="phone-input-end-adornment">
312
- <Box
313
- sx={{
314
- color: isValid ? theme.palette.success.main : theme.palette.text.secondary,
315
- transition: 'color 0.3s ease',
316
- }}
317
- data-testid="phone-input-icon"
318
- >
319
- {icon}
320
- </Box>
321
- </InputAdornment>
322
- ),
323
- }}
324
- sx={{
325
- '& input': {
326
- letterSpacing: '0.5px',
327
- },
328
- }}
341
+ icon={icon}
342
+ isValid={isValid}
343
+ selectedCountry={selectedCountry}
344
+ anchorEl={anchorEl}
345
+ onChange={handleChange}
346
+ onFocus={() => setIsFocused(true)}
347
+ onBlur={handleBlur}
348
+ onCountryOpen={handleCountryClick}
349
+ onCountryOpenViaKeyboard={setAnchorEl}
329
350
  />
330
351
 
331
- <CountryMenu
352
+ <CountryPicker
332
353
  anchorEl={anchorEl}
333
- open={Boolean(anchorEl)}
354
+ selectedCode={selectedCountry?.code}
334
355
  onClose={handleCountryClose}
335
- anchorOrigin={{
336
- vertical: 'bottom',
337
- horizontal: 'left',
338
- }}
339
- transformOrigin={{
340
- vertical: 'top',
341
- horizontal: 'left',
342
- }}
343
- data-testid="country-menu"
344
- >
345
- {countries.map((country) => (
346
- <MenuItem
347
- key={country.code}
348
- onClick={() => handleCountrySelect(country)}
349
- selected={country.code === selectedCountry?.code}
350
- data-testid={`country-option-${country.code}`}
351
- sx={{
352
- '&:hover': {
353
- background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.1)} 0%, ${alpha(theme.palette.primary.main, 0.05)} 100%)`,
354
- },
355
- }}
356
- >
357
- <ListItemIcon sx={{ minWidth: 36 }}>
358
- <Typography variant="h5" component="span">
359
- {country.flag}
360
- </Typography>
361
- </ListItemIcon>
362
- <ListItemText
363
- primary={country.name}
364
- secondary={country.dial}
365
- primaryTypographyProps={{ fontSize: '0.9rem' }}
366
- secondaryTypographyProps={{ fontSize: '0.8rem' }}
367
- />
368
- </MenuItem>
369
- ))}
370
- </CountryMenu>
356
+ onSelect={handleCountrySelect}
357
+ />
371
358
  </Box>
372
359
  );
373
360
  };