@12-apps/ui 1.0.0 → 1.3.1

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
@@ -1,10 +1,5 @@
1
- import {
2
- CheckCircle as SuccessIcon,
3
- Security as SecurityIcon,
4
- Warning as WarningIcon,
5
- Check as CheckIcon,
6
- Close as CloseIcon
7
- } from '@mui/icons-material';
1
+ import CheckIcon from '@mui/icons-material/Check';
2
+ import CloseIcon from '@mui/icons-material/Close';
8
3
 
9
4
  import {
10
5
  alpha,
@@ -16,11 +11,19 @@ import {
16
11
  Stack,
17
12
  styled,
18
13
  Typography,
19
- useTheme,
20
- } from '@mui/material';
14
+ useTheme } from '@mui/material';
21
15
  import type { FC} from 'react';
22
16
  import React, { useMemo } from 'react';
23
17
 
18
+ import type { RequirementChecks } from './PasswordStrength.helpers';
19
+ import {
20
+ buildSuggestions,
21
+ calculatePasswordStrength,
22
+ defaultRequirements,
23
+ evaluateRequirements,
24
+ getStrengthIcon,
25
+ getStrengthLabel,
26
+ SuggestionsList } from './PasswordStrength.helpers';
24
27
  import type { PasswordRequirements, PasswordStrengthProps } from './PasswordStrength.types';
25
28
 
26
29
  interface RequirementIconProps {
@@ -35,14 +38,6 @@ interface RequirementIconProps {
35
38
  };
36
39
 
37
40
  // Default requirements
38
- const defaultRequirements: PasswordRequirements = {
39
- minLength: 8,
40
- uppercase: true,
41
- lowercase: true,
42
- numbers: true,
43
- special: true,
44
- };
45
-
46
41
  // Animation keyframes
47
42
  const pulseAnimation = keyframes`
48
43
  0%, 100% {
@@ -66,8 +61,7 @@ const slideInAnimation = keyframes`
66
61
 
67
62
  // Styled components
68
63
  const StrengthContainer = styled(Box, {
69
- shouldForwardProp: (prop) => prop !== 'animated',
70
- })<{ animated?: boolean }>(({ theme, animated }) => ({
64
+ shouldForwardProp: (prop) => prop !== 'animated' })<{ animated?: boolean }>(({ theme, animated }) => ({
71
65
  width: '100%',
72
66
  padding: theme.spacing(2),
73
67
  background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.8)} 0%, ${alpha(theme.palette.background.paper, 0.6)} 100%)`,
@@ -75,12 +69,10 @@ const StrengthContainer = styled(Box, {
75
69
  WebkitBackdropFilter: 'blur(10px)',
76
70
  border: `1px solid ${alpha(theme.palette.divider, 0.18)}`,
77
71
  borderRadius: theme.shape.borderRadius * 2,
78
- transition: animated ? 'all 0.3s ease' : 'none',
79
- }));
72
+ transition: animated ? 'all 0.3s ease' : 'none' }));
80
73
 
81
74
  const StrengthBar = styled(LinearProgress, {
82
- shouldForwardProp: (prop) => prop !== 'strength' && prop !== 'animated',
83
- })<{ strength: number; animated?: boolean }>(({ theme, strength, animated }) => {
75
+ shouldForwardProp: (prop) => prop !== 'strength' && prop !== 'animated' })<{ strength: number; animated?: boolean }>(({ theme, strength, animated }) => {
84
76
  const getColor = () => {
85
77
  if (strength <= 20) return theme.palette.error.main;
86
78
  if (strength <= 40) return theme.palette.warning.main;
@@ -102,14 +94,11 @@ const StrengthBar = styled(LinearProgress, {
102
94
  borderRadius: 4,
103
95
  background: getGradient(),
104
96
  transition: animated ? 'all 0.3s ease' : 'none',
105
- boxShadow: `0 2px 8px ${alpha(getColor(), 0.3)}`,
106
- },
107
- };
97
+ boxShadow: `0 2px 8px ${alpha(getColor(), 0.3)}` } };
108
98
  });
109
99
 
110
100
  const RequirementItem = styled(Box, {
111
- shouldForwardProp: (prop) => prop !== 'met' && prop !== 'animated',
112
- })<{ met: boolean; animated?: boolean }>(({ theme, met, animated }) => ({
101
+ shouldForwardProp: (prop) => prop !== 'met' && prop !== 'animated' })<{ met: boolean; animated?: boolean }>(({ theme, met, animated }) => ({
113
102
  display: 'flex',
114
103
  alignItems: 'center',
115
104
  gap: theme.spacing(1),
@@ -122,13 +111,10 @@ const RequirementItem = styled(Box, {
122
111
  animation: animated ? `${slideInAnimation} 0.3s ease` : 'none',
123
112
  '& svg': {
124
113
  fontSize: '1rem',
125
- color: met ? theme.palette.success.main : theme.palette.text.disabled,
126
- },
127
- }));
114
+ color: met ? theme.palette.success.main : theme.palette.text.disabled } }));
128
115
 
129
116
  const StrengthLabel = styled(Chip, {
130
- shouldForwardProp: (prop) => prop !== 'strength' && prop !== 'animated',
131
- })<{ strength: number; animated?: boolean }>(({ theme, strength, animated }) => {
117
+ shouldForwardProp: (prop) => prop !== 'strength' && prop !== 'animated' })<{ strength: number; animated?: boolean }>(({ theme, strength, animated }) => {
132
118
  const getColor = () => {
133
119
  if (strength <= 20) return theme.palette.error;
134
120
  if (strength <= 40) return theme.palette.warning;
@@ -144,19 +130,16 @@ const StrengthLabel = styled(Chip, {
144
130
  color: palette.main,
145
131
  border: `1px solid ${alpha(palette.main, 0.3)}`,
146
132
  fontWeight: 600,
147
- animation: animated ? `${pulseAnimation} 2s ease infinite` : 'none',
148
- };
133
+ animation: animated ? `${pulseAnimation} 2s ease infinite` : 'none' };
149
134
  });
150
135
 
151
136
  const StepsContainer = styled(Box)(({ theme }) => ({
152
137
  display: 'flex',
153
138
  gap: theme.spacing(1),
154
- marginTop: theme.spacing(2),
155
- }));
139
+ marginTop: theme.spacing(2) }));
156
140
 
157
141
  const Step = styled(Box, {
158
- shouldForwardProp: (prop) => prop !== 'active' && prop !== 'completed' && prop !== 'animated',
159
- })<{ active: boolean; completed: boolean; animated?: boolean }>(
142
+ shouldForwardProp: (prop) => prop !== 'active' && prop !== 'completed' && prop !== 'animated' })<{ active: boolean; completed: boolean; animated?: boolean }>(
160
143
  ({ theme, active, completed, animated }) => ({
161
144
  flex: 1,
162
145
  height: 6,
@@ -167,56 +150,176 @@ const Step = styled(Box, {
167
150
  ? `linear-gradient(90deg, ${theme.palette.warning.main} 0%, ${theme.palette.warning.light} 100%)`
168
151
  : alpha(theme.palette.action.disabled, 0.2),
169
152
  transition: animated ? 'all 0.3s ease' : 'none',
170
- boxShadow: completed ? `0 2px 8px ${alpha(theme.palette.success.main, 0.3)}` : 'none',
171
- }),
153
+ boxShadow: completed ? `0 2px 8px ${alpha(theme.palette.success.main, 0.3)}` : 'none' }),
172
154
  );
173
155
 
174
- // Helper functions
175
- const calculatePasswordStrength = (
176
- password: string,
177
- requirements: PasswordRequirements,
178
- ): number => {
179
- if (!password) return 0;
180
-
181
- let strength = 0;
182
- const checks = {
183
- length: password.length >= (requirements.minLength || 8),
184
- uppercase: !requirements.uppercase || /[A-Z]/.test(password),
185
- lowercase: !requirements.lowercase || /[a-z]/.test(password),
186
- numbers: !requirements.numbers || /\d/.test(password),
187
- special: !requirements.special || /[!@#$%^&*(),.?":{}|<>]/.test(password),
188
- };
156
+ // The three indicator variants, out of the component body so the render keeps
157
+ // only its own layout branches.
158
+ const StrengthIndicator: React.FC<{
159
+ variant: PasswordStrengthProps['variant'];
160
+ strength: number;
161
+ animated: boolean;
162
+ }> = ({ variant, strength, animated }) => {
163
+ const theme = useTheme();
189
164
 
190
- // Base strength from length
191
- strength += Math.min(password.length * 3, 30);
165
+ switch (variant) {
166
+ case 'circular':
167
+ return (
168
+ <Box sx={{ position: 'relative', display: 'inline-flex' }}>
169
+ <Box
170
+ sx={{
171
+ width: 80,
172
+ height: 80,
173
+ borderRadius: '50%',
174
+ background: `conic-gradient(
175
+ ${theme.palette.success.main} 0deg ${strength * 3.6}deg,
176
+ ${alpha(theme.palette.action.disabled, 0.1)} ${strength * 3.6}deg 360deg
177
+ )`,
178
+ display: 'flex',
179
+ alignItems: 'center',
180
+ justifyContent: 'center',
181
+ boxShadow: `0 4px 12px ${alpha(theme.palette.success.main, strength / 200)}` }}
182
+ >
183
+ <Box
184
+ sx={{
185
+ width: 64,
186
+ height: 64,
187
+ borderRadius: '50%',
188
+ background: theme.palette.background.paper,
189
+ display: 'flex',
190
+ alignItems: 'center',
191
+ justifyContent: 'center',
192
+ flexDirection: 'column' }}
193
+ >
194
+ <Typography variant="h6" fontWeight="bold">
195
+ {strength}%
196
+ </Typography>
197
+ </Box>
198
+ </Box>
199
+ </Box>
200
+ );
201
+
202
+ case 'steps': {
203
+ const steps = 5;
204
+ const activeStep = Math.ceil((strength / 100) * steps);
205
+ return (
206
+ <StepsContainer>
207
+ {Array.from({ length: steps }, (_, i) => (
208
+ <Step key={i} active={i === activeStep - 1} completed={i < activeStep - 1} animated={animated} />
209
+ ))}
210
+ </StepsContainer>
211
+ );
212
+ }
192
213
 
193
- // Add points for meeting requirements
194
- if (checks.length) strength += 20;
195
- if (checks.uppercase) strength += 10;
196
- if (checks.lowercase) strength += 10;
197
- if (checks.numbers) strength += 15;
198
- if (checks.special) strength += 15;
214
+ default:
215
+ return <StrengthBar variant="determinate" value={strength} strength={strength} animated={animated} />;
216
+ }
217
+ };
199
218
 
200
- // Bonus for complexity
201
- if (password.length > 12) strength += 10;
202
- if (password.length > 16) strength += 10;
219
+ const StrengthHeading: React.FC<{
220
+ strength: number;
221
+ hasValue: boolean;
222
+ animated: boolean;
223
+ }> = ({ strength, hasValue, animated }) => (
224
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
225
+ <Typography variant="body2" color="text.secondary">
226
+ Password Strength
227
+ </Typography>
228
+ {hasValue && (
229
+ <Fade in={true}>
230
+ <StrengthLabel
231
+ label={getStrengthLabel(strength)}
232
+ icon={getStrengthIcon(strength)}
233
+ size="small"
234
+ strength={strength}
235
+ animated={animated}
236
+ />
237
+ </Fade>
238
+ )}
239
+ </Box>
240
+ );
203
241
 
204
- return Math.min(strength, 100);
205
- };
242
+ // One row per enabled requirement. Previously five near-identical JSX blocks,
243
+ // each with its own `requirements.x &&` guard.
244
+ const RequirementsList: React.FC<{
245
+ requirements: PasswordRequirements;
246
+ checks: RequirementChecks;
247
+ animated: boolean;
248
+ }> = ({ requirements, checks, animated }) => {
249
+ const rows: ReadonlyArray<[unknown, keyof RequirementChecks, React.ReactNode]> = [
250
+ [requirements.minLength, 'length', `At least ${requirements.minLength} characters`],
251
+ [requirements.uppercase, 'uppercase', 'One uppercase letter'],
252
+ [requirements.lowercase, 'lowercase', 'One lowercase letter'],
253
+ [requirements.numbers, 'numbers', 'One number'],
254
+ [requirements.special, 'special', 'One special character'],
255
+ ];
206
256
 
207
- const getStrengthLabel = (strength: number): string => {
208
- if (strength <= 20) return 'Very Weak';
209
- if (strength <= 40) return 'Weak';
210
- if (strength <= 60) return 'Fair';
211
- if (strength <= 80) return 'Good';
212
- return 'Strong';
257
+ return (
258
+ <Stack spacing={1}>
259
+ <Typography variant="caption" color="text.secondary" fontWeight="medium">
260
+ Requirements:
261
+ </Typography>
262
+ {rows
263
+ .filter(([enabled]) => Boolean(enabled))
264
+ .map(([, key, label]) => (
265
+ <RequirementItem key={key} met={checks[key]} animated={animated}>
266
+ <RequirementIcon met={checks[key]} />
267
+ <Typography variant="caption">{label}</Typography>
268
+ </RequirementItem>
269
+ ))}
270
+ </Stack>
271
+ );
213
272
  };
214
273
 
215
- const getStrengthIcon = (strength: number) => {
216
- if (strength <= 40) return <WarningIcon />;
217
- if (strength <= 80) return <SecurityIcon />;
218
- return <SuccessIcon />;
219
- };
274
+ // The visible sections. Kept apart from PasswordStrength itself so the exported
275
+ // component is just prop defaults plus the three memos, and every display guard
276
+ // lives in one place.
277
+ const PasswordStrengthBody: React.FC<{
278
+ value: string;
279
+ strength: number;
280
+ requirements: PasswordRequirements;
281
+ requirementChecks: RequirementChecks;
282
+ suggestions: string[];
283
+ showStrengthLabel: boolean;
284
+ showRequirements: boolean;
285
+ showSuggestions: boolean;
286
+ variant: PasswordStrengthProps['variant'];
287
+ animated: boolean;
288
+ }> = ({
289
+ value,
290
+ strength,
291
+ requirements,
292
+ requirementChecks,
293
+ suggestions,
294
+ showStrengthLabel,
295
+ showRequirements,
296
+ showSuggestions,
297
+ variant,
298
+ animated }) => (
299
+ <Stack spacing={2}>
300
+ {showStrengthLabel && (
301
+ <StrengthHeading strength={strength} hasValue={value.length > 0} animated={animated} />
302
+ )}
303
+
304
+ <StrengthIndicator variant={variant} strength={strength} animated={animated} />
305
+
306
+ {showRequirements && (
307
+ <Fade in={value.length > 0}>
308
+ <RequirementsList
309
+ requirements={requirements}
310
+ checks={requirementChecks}
311
+ animated={animated}
312
+ />
313
+ </Fade>
314
+ )}
315
+
316
+ {showSuggestions && suggestions.length > 0 && value.length > 0 && (
317
+ <Fade in={true}>
318
+ <SuggestionsList suggestions={suggestions} />
319
+ </Fade>
320
+ )}
321
+ </Stack>
322
+ );
220
323
 
221
324
  // Main component
222
325
  export const PasswordStrength: FC<PasswordStrengthProps> = ({
@@ -227,190 +330,34 @@ export const PasswordStrength: FC<PasswordStrengthProps> = ({
227
330
  showSuggestions = false,
228
331
  variant = 'linear',
229
332
  animated = true,
230
- 'data-testid': dataTestId,
231
- }) => {
232
- const theme = useTheme();
333
+ 'data-testid': dataTestId }) => {
233
334
 
234
335
  const strength = useMemo(
235
336
  () => calculatePasswordStrength(value, requirements),
236
337
  [value, requirements],
237
338
  );
238
339
 
239
- const requirementChecks = useMemo(() => {
240
- if (!value)
241
- return {
242
- length: false,
243
- uppercase: false,
244
- lowercase: false,
245
- numbers: false,
246
- special: false,
247
- };
248
-
249
- return {
250
- length: value.length >= (requirements.minLength || 8),
251
- uppercase: !requirements.uppercase || /[A-Z]/.test(value),
252
- lowercase: !requirements.lowercase || /[a-z]/.test(value),
253
- numbers: !requirements.numbers || /\d/.test(value),
254
- special: !requirements.special || /[!@#$%^&*(),.?":{}|<>]/.test(value),
255
- };
256
- }, [value, requirements]);
257
-
258
- const suggestions = useMemo(() => {
259
- const tips = [];
260
- if (!requirementChecks.length) tips.push('Use at least 8 characters');
261
- if (!requirementChecks.uppercase) tips.push('Add uppercase letters');
262
- if (!requirementChecks.lowercase) tips.push('Add lowercase letters');
263
- if (!requirementChecks.numbers) tips.push('Include numbers');
264
- if (!requirementChecks.special) tips.push('Add special characters');
265
- return tips;
266
- }, [requirementChecks]);
267
-
268
- const renderStrengthIndicator = () => {
269
- switch (variant) {
270
- case 'circular':
271
- return (
272
- <Box sx={{ position: 'relative', display: 'inline-flex' }}>
273
- <Box
274
- sx={{
275
- width: 80,
276
- height: 80,
277
- borderRadius: '50%',
278
- background: `conic-gradient(
279
- ${theme.palette.success.main} 0deg ${strength * 3.6}deg,
280
- ${alpha(theme.palette.action.disabled, 0.1)} ${strength * 3.6}deg 360deg
281
- )`,
282
- display: 'flex',
283
- alignItems: 'center',
284
- justifyContent: 'center',
285
- boxShadow: `0 4px 12px ${alpha(theme.palette.success.main, strength / 200)}`,
286
- }}
287
- >
288
- <Box
289
- sx={{
290
- width: 64,
291
- height: 64,
292
- borderRadius: '50%',
293
- background: theme.palette.background.paper,
294
- display: 'flex',
295
- alignItems: 'center',
296
- justifyContent: 'center',
297
- flexDirection: 'column',
298
- }}
299
- >
300
- <Typography variant="h6" fontWeight="bold">
301
- {strength}%
302
- </Typography>
303
- </Box>
304
- </Box>
305
- </Box>
306
- );
307
-
308
- case 'steps': {
309
- const steps = 5;
310
- const activeStep = Math.ceil((strength / 100) * steps);
311
- return (
312
- <StepsContainer>
313
- {Array.from({ length: steps }, (_, i) => (
314
- <Step key={i} active={i === activeStep - 1} completed={i < activeStep - 1} animated={animated} />
315
- ))}
316
- </StepsContainer>
317
- );
318
- }
319
-
320
- default:
321
- return <StrengthBar variant="determinate" value={strength} strength={strength} animated={animated} />;
322
- }
323
- };
340
+ const requirementChecks = useMemo(
341
+ () => evaluateRequirements(value, requirements),
342
+ [value, requirements],
343
+ );
344
+
345
+ const suggestions = useMemo(() => buildSuggestions(requirementChecks), [requirementChecks]);
324
346
 
325
347
  return (
326
348
  <StrengthContainer animated={animated} data-testid={dataTestId}>
327
- <Stack spacing={2}>
328
- {showStrengthLabel && (
329
- <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
330
- <Typography variant="body2" color="text.secondary">
331
- Password Strength
332
- </Typography>
333
- {value.length > 0 && (
334
- <Fade in={true}>
335
- <StrengthLabel
336
- label={getStrengthLabel(strength)}
337
- icon={getStrengthIcon(strength)}
338
- size="small"
339
- strength={strength}
340
- animated={animated}
341
- />
342
- </Fade>
343
- )}
344
- </Box>
345
- )}
346
-
347
- {renderStrengthIndicator()}
348
-
349
- {showRequirements && (
350
- <Fade in={value.length > 0}>
351
- <Stack spacing={1}>
352
- <Typography variant="caption" color="text.secondary" fontWeight="medium">
353
- Requirements:
354
- </Typography>
355
- {requirements.minLength && (
356
- <RequirementItem met={requirementChecks.length} animated={animated}>
357
- <RequirementIcon met={requirementChecks.length} />
358
- <Typography variant="caption">
359
- At least {requirements.minLength} characters
360
- </Typography>
361
- </RequirementItem>
362
- )}
363
- {requirements.uppercase && (
364
- <RequirementItem met={requirementChecks.uppercase} animated={animated}>
365
- <RequirementIcon met={requirementChecks.uppercase} />
366
- <Typography variant="caption">One uppercase letter</Typography>
367
- </RequirementItem>
368
- )}
369
- {requirements.lowercase && (
370
- <RequirementItem met={requirementChecks.lowercase} animated={animated}>
371
- <RequirementIcon met={requirementChecks.lowercase} />
372
- <Typography variant="caption">One lowercase letter</Typography>
373
- </RequirementItem>
374
- )}
375
- {requirements.numbers && (
376
- <RequirementItem met={requirementChecks.numbers} animated={animated}>
377
- <RequirementIcon met={requirementChecks.numbers} />
378
- <Typography variant="caption">One number</Typography>
379
- </RequirementItem>
380
- )}
381
- {requirements.special && (
382
- <RequirementItem met={requirementChecks.special} animated={animated}>
383
- <RequirementIcon met={requirementChecks.special} />
384
- <Typography variant="caption">One special character</Typography>
385
- </RequirementItem>
386
- )}
387
- </Stack>
388
- </Fade>
389
- )}
390
-
391
- {showSuggestions && suggestions.length > 0 && value.length > 0 && (
392
- <Fade in={true}>
393
- <Box>
394
- <Typography variant="caption" color="text.secondary" fontWeight="medium">
395
- Suggestions:
396
- </Typography>
397
- <Box sx={{ mt: 1 }}>
398
- {suggestions.map((tip, index) => (
399
- <Typography
400
- key={index}
401
- variant="caption"
402
- display="block"
403
- color="warning.main"
404
- sx={{ ml: 2 }}
405
- >
406
- • {tip}
407
- </Typography>
408
- ))}
409
- </Box>
410
- </Box>
411
- </Fade>
412
- )}
413
- </Stack>
349
+ <PasswordStrengthBody
350
+ value={value}
351
+ strength={strength}
352
+ requirements={requirements}
353
+ requirementChecks={requirementChecks}
354
+ suggestions={suggestions}
355
+ showStrengthLabel={showStrengthLabel}
356
+ showRequirements={showRequirements}
357
+ showSuggestions={showSuggestions}
358
+ variant={variant}
359
+ animated={animated}
360
+ />
414
361
  </StrengthContainer>
415
362
  );
416
363
  };
@@ -0,0 +1,134 @@
1
+ import type { CountryCode } from 'libphonenumber-js';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+
4
+ import { countries } from './countries';
5
+ import { detectCountryFromNumber, formatPhoneNumber, validatePhoneNumber } from './phone';
6
+ import type { CountryData } from './PhoneInput.types';
7
+
8
+ // Value, selected country, menu anchor and validity, plus the handlers that move
9
+ // between them. Kept out of the component so its body is markup.
10
+ export const usePhoneInput = ({
11
+ defaultValue,
12
+ initialCountryCode,
13
+ onChange,
14
+ }: {
15
+ defaultValue: string;
16
+ initialCountryCode: CountryCode;
17
+ onChange?: (value: string, isValid: boolean, country: CountryCode) => void;
18
+ }) => {
19
+ const [value, setValue] = useState(defaultValue);
20
+ const [selectedCountry, setSelectedCountry] = useState(
21
+ countries.find((c) => c.code === initialCountryCode) || countries[0],
22
+ );
23
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
24
+ const [isValid, setIsValid] = useState(false);
25
+ const [isFocused, setIsFocused] = useState(false);
26
+ const onChangeRef = useRef(onChange);
27
+ const isInitialMountRef = useRef(true);
28
+
29
+ // Update ref when onChange changes
30
+ useEffect(() => {
31
+ onChangeRef.current = onChange;
32
+ }, [onChange]);
33
+
34
+ useEffect(() => {
35
+ if (!selectedCountry) return;
36
+
37
+ const valid = validatePhoneNumber(value, selectedCountry.code);
38
+ setIsValid(valid);
39
+
40
+ // Skip onChange call on initial mount
41
+ if (isInitialMountRef.current) {
42
+ isInitialMountRef.current = false;
43
+ return;
44
+ }
45
+
46
+ // Call onChange for all value/country changes after initial mount
47
+ if (onChangeRef.current) {
48
+ onChangeRef.current(value, valid, selectedCountry.code);
49
+ }
50
+ }, [value, selectedCountry]);
51
+
52
+ const handlers = usePhoneHandlers({
53
+ value,
54
+ setValue,
55
+ selectedCountry,
56
+ setSelectedCountry,
57
+ setAnchorEl,
58
+ setIsFocused,
59
+ });
60
+
61
+ return {
62
+ value,
63
+ selectedCountry,
64
+ anchorEl,
65
+ isValid,
66
+ isFocused,
67
+ setIsFocused,
68
+ setAnchorEl,
69
+ ...handlers,
70
+ };
71
+ };
72
+
73
+ // Opening and closing the country menu, picking a country, typing, and
74
+ // reformatting on blur.
75
+ const usePhoneHandlers = ({
76
+ value,
77
+ setValue,
78
+ selectedCountry,
79
+ setSelectedCountry,
80
+ setAnchorEl,
81
+ setIsFocused,
82
+ }: {
83
+ value: string;
84
+ setValue: React.Dispatch<React.SetStateAction<string>>;
85
+ selectedCountry?: CountryData;
86
+ setSelectedCountry: React.Dispatch<React.SetStateAction<CountryData | undefined>>;
87
+ setAnchorEl: React.Dispatch<React.SetStateAction<null | HTMLElement>>;
88
+ setIsFocused: React.Dispatch<React.SetStateAction<boolean>>;
89
+ }) => {
90
+ const handleCountryClick = (event: React.MouseEvent<HTMLElement>) => {
91
+ setAnchorEl(event.currentTarget);
92
+ };
93
+
94
+ const handleCountryClose = () => {
95
+ setAnchorEl(null);
96
+ };
97
+
98
+ const handleCountrySelect = (country: CountryData) => {
99
+ setSelectedCountry(country);
100
+ handleCountryClose();
101
+
102
+ // Reformat number with new country code
103
+ if (value) {
104
+ const formatted = formatPhoneNumber(value, country.code);
105
+ setValue(formatted);
106
+ }
107
+ };
108
+
109
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
110
+ const newValue = event.target.value;
111
+ setValue(newValue);
112
+
113
+ // Auto-detect country if user types international format
114
+ if (newValue.startsWith('+')) {
115
+ const detectedCountry = detectCountryFromNumber(newValue);
116
+ if (detectedCountry) {
117
+ const detectedCountryData = countries.find((c) => c.code === detectedCountry);
118
+ if (detectedCountryData && detectedCountryData.code !== selectedCountry?.code) {
119
+ setSelectedCountry(detectedCountryData);
120
+ }
121
+ }
122
+ }
123
+ };
124
+
125
+ const handleBlur = () => {
126
+ setIsFocused(false);
127
+ if (value && selectedCountry) {
128
+ const formatted = formatPhoneNumber(value, selectedCountry.code);
129
+ setValue(formatted);
130
+ }
131
+ };
132
+
133
+ return { handleCountryClick, handleCountryClose, handleCountrySelect, handleChange, handleBlur };
134
+ };