@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
@@ -0,0 +1,281 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import React, { useState } from 'react';
3
+ import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
4
+
5
+ import { CepField } from './CepField';
6
+ import type { CepAddress } from './CepField.types';
7
+
8
+ const ADDRESS: CepAddress = {
9
+ cep: '01310100',
10
+ street: 'Avenida Paulista',
11
+ neighborhood: 'Bela Vista',
12
+ city: 'São Paulo',
13
+ state: 'SP',
14
+ };
15
+
16
+ const meta: Meta<typeof CepField> = {
17
+ title: 'Form/CepField/Tests',
18
+ component: CepField,
19
+ parameters: {
20
+ layout: 'centered',
21
+ chromatic: { disableSnapshot: false },
22
+ docs: {
23
+ description: { component: 'Test stories for CepField masking, lookup and race-safety' },
24
+ },
25
+ },
26
+ tags: ['autodocs', 'test'],
27
+ };
28
+
29
+ export default meta;
30
+ export type Story = StoryObj<typeof meta>;
31
+
32
+ /** Controlled harness — the field is controlled, so tests need real state. */
33
+ function Harness({
34
+ onLookup,
35
+ onResolved,
36
+ onNotFound,
37
+ debounceMs = 0,
38
+ initial = '',
39
+ disabled = false,
40
+ }: {
41
+ onLookup?: (cep: string) => Promise<CepAddress | null>;
42
+ onResolved?: (address: CepAddress) => void;
43
+ onNotFound?: () => void;
44
+ debounceMs?: number;
45
+ initial?: string;
46
+ disabled?: boolean;
47
+ }): React.ReactElement {
48
+ const [value, setValue] = useState(initial);
49
+ return (
50
+ <CepField
51
+ value={value}
52
+ onChange={setValue}
53
+ onLookup={onLookup}
54
+ onResolved={onResolved}
55
+ onNotFound={onNotFound}
56
+ debounceMs={debounceMs}
57
+ disabled={disabled}
58
+ />
59
+ );
60
+ }
61
+
62
+ // 1. Masking
63
+
64
+ export const MasksWhileTyping: Story = {
65
+ render: () => <Harness />,
66
+ play: async ({ canvasElement }) => {
67
+ const canvas = within(canvasElement);
68
+ const input = canvas.getByTestId('cep-field-postalCode');
69
+
70
+ await userEvent.type(input, '01310100');
71
+ await expect(input).toHaveValue('01310-100');
72
+ },
73
+ };
74
+
75
+ export const StripsNonDigitsAndCaps: Story = {
76
+ render: () => <Harness />,
77
+ play: async ({ canvasElement }) => {
78
+ const canvas = within(canvasElement);
79
+ const input = canvas.getByTestId('cep-field-postalCode');
80
+
81
+ await userEvent.type(input, 'ab01310100999');
82
+ await expect(input).toHaveValue('01310-100');
83
+ },
84
+ };
85
+
86
+ // 2. Lookup
87
+
88
+ export const ResolvesACompleteCep: Story = {
89
+ render: function ResolvesStory() {
90
+ const onResolved = fn();
91
+ return (
92
+ <Harness onLookup={async () => ADDRESS} onResolved={onResolved} />
93
+ );
94
+ },
95
+ play: async ({ canvasElement }) => {
96
+ const canvas = within(canvasElement);
97
+ const input = canvas.getByTestId('cep-field-postalCode');
98
+
99
+ await userEvent.type(input, '01310100');
100
+ await waitFor(() => expect(canvas.getByTestId('cep-field-postalCode-found')).toBeInTheDocument());
101
+ },
102
+ };
103
+
104
+ export const DoesNotLookUpAnIncompleteCep: Story = {
105
+ render: function IncompleteStory() {
106
+ const lookup = fn(async () => ADDRESS);
107
+ return <Harness onLookup={lookup} />;
108
+ },
109
+ play: async ({ canvasElement }) => {
110
+ const canvas = within(canvasElement);
111
+ const input = canvas.getByTestId('cep-field-postalCode');
112
+
113
+ await userEvent.type(input, '0131');
114
+ // Presence evidence first: the field really did take the partial value…
115
+ await expect(input).toHaveValue('0131');
116
+ // …and no status line ever appears, because an incomplete CEP is never
117
+ // queried. waitFor gives the (absent) debounce a chance to fire first.
118
+ await waitFor(() => {
119
+ expect(canvas.queryByTestId('cep-field-postalCode-loading')).toBeNull();
120
+ expect(canvas.queryByTestId('cep-field-postalCode-found')).toBeNull();
121
+ });
122
+ },
123
+ };
124
+
125
+ export const ReportsAnUnknownCep: Story = {
126
+ render: () => <Harness onLookup={async () => null} />,
127
+ play: async ({ canvasElement }) => {
128
+ const canvas = within(canvasElement);
129
+ const input = canvas.getByTestId('cep-field-postalCode');
130
+
131
+ await userEvent.type(input, '00000000');
132
+ await waitFor(() =>
133
+ expect(canvas.getByTestId('cep-field-postalCode-notfound')).toBeInTheDocument(),
134
+ );
135
+ },
136
+ };
137
+
138
+ /**
139
+ * A miss must be REPORTED, not just displayed: callers that auto-filled from a
140
+ * previous CEP rely on `onNotFound` to undo it, so the form never shows one
141
+ * CEP's street under another's.
142
+ */
143
+ export const ReportsAMissToTheCaller: Story = {
144
+ render: function ReportsMissStory() {
145
+ const [missed, setMissed] = useState(false);
146
+ return (
147
+ <div>
148
+ <Harness onLookup={async () => null} onNotFound={() => setMissed(true)} />
149
+ <span data-testid="miss-flag">{missed ? 'missed' : 'clean'}</span>
150
+ </div>
151
+ );
152
+ },
153
+ play: async ({ canvasElement }) => {
154
+ const canvas = within(canvasElement);
155
+ const input = canvas.getByTestId('cep-field-postalCode');
156
+
157
+ await userEvent.type(input, '00000000');
158
+ await waitFor(() => expect(canvas.getByTestId('miss-flag')).toHaveTextContent('missed'));
159
+ },
160
+ };
161
+
162
+ /** …and a resolved CEP must NOT report a miss. */
163
+ export const DoesNotReportAMissOnSuccess: Story = {
164
+ render: function NoMissStory() {
165
+ const [missed, setMissed] = useState(false);
166
+ return (
167
+ <div>
168
+ <Harness onLookup={async () => ADDRESS} onNotFound={() => setMissed(true)} />
169
+ <span data-testid="miss-flag">{missed ? 'missed' : 'clean'}</span>
170
+ </div>
171
+ );
172
+ },
173
+ play: async ({ canvasElement }) => {
174
+ const canvas = within(canvasElement);
175
+ const input = canvas.getByTestId('cep-field-postalCode');
176
+
177
+ await userEvent.type(input, '01310100');
178
+ await waitFor(() => expect(canvas.getByTestId('cep-field-postalCode-found')).toBeInTheDocument());
179
+ await expect(canvas.getByTestId('miss-flag')).toHaveTextContent('clean');
180
+ },
181
+ };
182
+
183
+ /** A lookup that throws is indistinguishable from "not found" — never a crash. */
184
+ export const SurvivesAThrowingLookup: Story = {
185
+ render: () => (
186
+ <Harness
187
+ onLookup={async () => {
188
+ throw new Error('provider down');
189
+ }}
190
+ />
191
+ ),
192
+ play: async ({ canvasElement }) => {
193
+ const canvas = within(canvasElement);
194
+ const input = canvas.getByTestId('cep-field-postalCode');
195
+
196
+ await userEvent.type(input, '01310100');
197
+ await waitFor(() =>
198
+ expect(canvas.getByTestId('cep-field-postalCode-notfound')).toBeInTheDocument(),
199
+ );
200
+ },
201
+ };
202
+
203
+ /**
204
+ * …including a lookup that throws BEFORE returning a promise, which is what a
205
+ * misconfigured adapter does. Awaiting the call and catching on its result
206
+ * would miss this one entirely and surface it as an unhandled rejection.
207
+ */
208
+ export const SurvivesASynchronousThrow: Story = {
209
+ render: () => (
210
+ <Harness
211
+ onLookup={() => {
212
+ throw new Error('adapter misconfigured');
213
+ }}
214
+ />
215
+ ),
216
+ play: async ({ canvasElement }) => {
217
+ const canvas = within(canvasElement);
218
+ const input = canvas.getByTestId('cep-field-postalCode');
219
+
220
+ await userEvent.type(input, '01310100');
221
+ await waitFor(() =>
222
+ expect(canvas.getByTestId('cep-field-postalCode-notfound')).toBeInTheDocument(),
223
+ );
224
+ },
225
+ };
226
+
227
+ // 3. State
228
+
229
+ export const DisabledSuppressesLookup: Story = {
230
+ render: () => <Harness initial="01310-100" disabled onLookup={async () => ADDRESS} />,
231
+ play: async ({ canvasElement }) => {
232
+ const canvas = within(canvasElement);
233
+ const input = canvas.getByTestId('cep-field-postalCode');
234
+
235
+ await expect(input).toBeDisabled();
236
+ await expect(input).toHaveValue('01310-100');
237
+ // A complete CEP, but disabled suppresses the lookup entirely.
238
+ await waitFor(() => expect(canvas.queryByTestId('cep-field-postalCode-found')).toBeNull());
239
+ },
240
+ };
241
+
242
+ export const ClearsStatusWhenEditedBackToIncomplete: Story = {
243
+ render: () => <Harness onLookup={async () => ADDRESS} />,
244
+ play: async ({ canvasElement }) => {
245
+ const canvas = within(canvasElement);
246
+ const input = canvas.getByTestId('cep-field-postalCode');
247
+
248
+ await userEvent.type(input, '01310100');
249
+ await waitFor(() => expect(canvas.getByTestId('cep-field-postalCode-found')).toBeInTheDocument());
250
+
251
+ await userEvent.type(input, '{backspace}');
252
+ await waitFor(() => expect(canvas.queryByTestId('cep-field-postalCode-found')).toBeNull());
253
+ },
254
+ };
255
+
256
+ // 4. Accessibility
257
+
258
+ export const ExposesAccessibleStatus: Story = {
259
+ render: () => <Harness onLookup={async () => ADDRESS} />,
260
+ play: async ({ canvasElement }) => {
261
+ const canvas = within(canvasElement);
262
+ const input = canvas.getByTestId('cep-field-postalCode');
263
+
264
+ await expect(input).toHaveAttribute('aria-describedby', 'cep-field-postalCode-status');
265
+ await userEvent.type(input, '01310100');
266
+ await waitFor(() => expect(canvas.getByRole('status')).toBeInTheDocument());
267
+ },
268
+ };
269
+
270
+ export const RendersACallerError: Story = {
271
+ render: function ErrorStory() {
272
+ const [value, setValue] = useState('123');
273
+ return <CepField value={value} onChange={setValue} error="CEP inválido." />;
274
+ },
275
+ play: async ({ canvasElement }) => {
276
+ const canvas = within(canvasElement);
277
+ await expect(canvas.getByTestId('cep-field-postalCode-message')).toHaveTextContent(
278
+ 'CEP inválido.',
279
+ );
280
+ },
281
+ };
@@ -0,0 +1,211 @@
1
+ 'use client';
2
+
3
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
4
+
5
+ import { cepDigits, formatCep, isValidCep } from '@12-apps/forms-core';
6
+
7
+ import { FormControl, FormLabel, FormMessage } from '../Form';
8
+ import { Input } from '../Input';
9
+ import type { CepAddress, CepFieldProps, CepLookupStatus } from './CepField.types';
10
+
11
+ /** What the user is told for each non-idle outcome. */
12
+ const STATUS_TEXT: Record<Exclude<CepLookupStatus, 'idle'>, string> = {
13
+ loading: 'Buscando endereço…',
14
+ found: 'Endereço preenchido pelo CEP.',
15
+ notfound: 'CEP não encontrado — preencha o endereço manualmente.',
16
+ };
17
+
18
+ /** Everything {@link useCepLookup} needs, so the hook signature stays flat. */
19
+ interface LookupOptions {
20
+ digits: string;
21
+ complete: boolean;
22
+ disabled: boolean;
23
+ debounceMs: number;
24
+ onLookup?: (cep: string) => Promise<CepAddress | null>;
25
+ onResolved?: (address: CepAddress) => void;
26
+ onNotFound?: () => void;
27
+ }
28
+
29
+ /**
30
+ * Debounced, race-safe CEP resolution.
31
+ *
32
+ * Two guards earn their keep here: a monotonic sequence id, bumped
33
+ * synchronously on every new query, so a slow response for a since-edited CEP
34
+ * can never land; and a "last queried" marker, so re-rendering (or the caller
35
+ * re-masking the same value) never re-hits the provider.
36
+ */
37
+ function useCepLookup({
38
+ digits,
39
+ complete,
40
+ disabled,
41
+ debounceMs,
42
+ onLookup,
43
+ onResolved,
44
+ onNotFound,
45
+ }: LookupOptions): CepLookupStatus {
46
+ const [status, setStatus] = useState<CepLookupStatus>('idle');
47
+ const lastQueriedRef = useRef('');
48
+ const seqRef = useRef(0);
49
+ // Latest callbacks, read at call time — keeps them out of the effect deps so
50
+ // an inline arrow from the caller doesn't re-trigger the lookup every render.
51
+ const lookupRef = useRef(onLookup);
52
+ const resolvedRef = useRef(onResolved);
53
+ const notFoundRef = useRef(onNotFound);
54
+ lookupRef.current = onLookup;
55
+ resolvedRef.current = onResolved;
56
+ notFoundRef.current = onNotFound;
57
+
58
+ useEffect(() => {
59
+ if (!complete || disabled || !lookupRef.current) {
60
+ // Editing away from a complete CEP drops any stale status and forgets the
61
+ // last query, so re-typing the same CEP resolves again.
62
+ seqRef.current += 1;
63
+ setStatus('idle');
64
+ lastQueriedRef.current = '';
65
+ return;
66
+ }
67
+ if (lastQueriedRef.current === digits) return;
68
+
69
+ const mySeq = (seqRef.current += 1);
70
+ const handle = setTimeout(() => {
71
+ lastQueriedRef.current = digits;
72
+ setStatus('loading');
73
+ void (async () => {
74
+ // A failing lookup is treated exactly like "not found" — the user types
75
+ // the address instead. Never surfaced as a crash. Going through
76
+ // `Promise.resolve().then` rather than calling directly covers the two
77
+ // failure modes an `await … .catch()` misses: a lookup that throws
78
+ // SYNCHRONOUSLY, and one that returns something other than a promise.
79
+ const address = await Promise.resolve()
80
+ .then(() => lookupRef.current?.(digits))
81
+ .catch(() => null);
82
+ if (seqRef.current !== mySeq) return;
83
+ setStatus(address ? 'found' : 'notfound');
84
+ // A miss is reported too, so a caller that filled from a PREVIOUS CEP
85
+ // can undo it rather than leaving that address under this CEP.
86
+ if (address) resolvedRef.current?.(address);
87
+ else notFoundRef.current?.();
88
+ })();
89
+ }, debounceMs);
90
+
91
+ return () => clearTimeout(handle);
92
+ }, [digits, complete, disabled, debounceMs]);
93
+
94
+ return status;
95
+ }
96
+
97
+ /**
98
+ * The lookup outcome as a live region, so screen readers announce it without
99
+ * stealing focus. Silent while idle, and while the caller is showing its own
100
+ * validation error (which would otherwise stack two messages).
101
+ */
102
+ function CepStatus({
103
+ status,
104
+ testId,
105
+ suppressed,
106
+ }: {
107
+ status: CepLookupStatus;
108
+ testId: string;
109
+ suppressed: boolean;
110
+ }): React.ReactElement {
111
+ const visible = !suppressed && status !== 'idle';
112
+ return (
113
+ <span id={`${testId}-status`} role="status" aria-live="polite">
114
+ {visible && (
115
+ <FormMessage error={status === 'notfound'} dataTestId={`${testId}-${status}`}>
116
+ {STATUS_TEXT[status as Exclude<CepLookupStatus, 'idle'>]}
117
+ </FormMessage>
118
+ )}
119
+ </span>
120
+ );
121
+ }
122
+
123
+ /**
124
+ * A CEP input that fills the rest of the address in.
125
+ *
126
+ * The pattern every Brazilian form has used for twenty years: type eight
127
+ * digits, and logradouro/bairro/cidade/UF arrive on their own. This is the
128
+ * reusable half — masking, validation, debouncing, race-safety and status —
129
+ * with the actual lookup injected via `onLookup`, so the same component serves
130
+ * a supplier form, a store-location form or a delivery checkout without
131
+ * knowing anything about providers or auth.
132
+ *
133
+ * Resolution is best-effort by design. A provider outage, an unknown CEP or a
134
+ * timeout leaves the field exactly as usable as a plain text input — the user
135
+ * types the address themselves. Autofill never blocks.
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * <CepField
140
+ * value={cep}
141
+ * onChange={setCep}
142
+ * onLookup={(c) => fetch(`/api/…/lookup/cep/${c}`).then((r) => r.json()).then((b) => b.data)}
143
+ * onResolved={(a) => { setStreet(a.street ?? ''); setCity(a.city ?? ''); }}
144
+ * />
145
+ * ```
146
+ */
147
+ export function CepField({
148
+ value,
149
+ onChange,
150
+ onLookup,
151
+ onResolved,
152
+ onNotFound,
153
+ label = 'CEP',
154
+ placeholder = '00000-000',
155
+ error,
156
+ disabled = false,
157
+ debounceMs = 500,
158
+ name = 'postalCode',
159
+ dataTestId,
160
+ }: CepFieldProps): React.ReactElement {
161
+ const testId = dataTestId ?? `cep-field-${name}`;
162
+
163
+ const status = useCepLookup({
164
+ digits: cepDigits(value),
165
+ complete: isValidCep(value),
166
+ disabled,
167
+ debounceMs,
168
+ onLookup,
169
+ onResolved,
170
+ onNotFound,
171
+ });
172
+
173
+ const handleChange = useCallback(
174
+ (event: React.ChangeEvent<HTMLInputElement>) => onChange(formatCep(event.target.value)),
175
+ [onChange],
176
+ );
177
+
178
+ return (
179
+ <FormControl fullWidth>
180
+ {label && (
181
+ <FormLabel htmlFor={name} error={Boolean(error)}>
182
+ {label}
183
+ </FormLabel>
184
+ )}
185
+ <Input
186
+ id={name}
187
+ name={name}
188
+ type="text"
189
+ inputMode="numeric"
190
+ autoComplete="postal-code"
191
+ placeholder={placeholder}
192
+ value={value}
193
+ error={Boolean(error)}
194
+ disabled={disabled}
195
+ loading={status === 'loading'}
196
+ onChange={handleChange}
197
+ data-testid={testId}
198
+ aria-describedby={`${testId}-status`}
199
+ aria-busy={status === 'loading'}
200
+ />
201
+ {error && (
202
+ <FormMessage error dataTestId={`${testId}-message`}>
203
+ {error}
204
+ </FormMessage>
205
+ )}
206
+ <CepStatus status={status} testId={testId} suppressed={Boolean(error)} />
207
+ </FormControl>
208
+ );
209
+ }
210
+
211
+ export default CepField;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Types for {@link CepField} — the reusable CEP auto-completer.
3
+ */
4
+
5
+ /**
6
+ * A resolved CEP.
7
+ *
8
+ * Structurally identical to what the app's `/lookup/cep` endpoint returns, but
9
+ * declared here so the component stays independent of any particular backend:
10
+ * anything that can produce this shape can drive the field.
11
+ *
12
+ * `número` and `complemento` are deliberately absent — no CEP database holds
13
+ * them, so they are always the user's to type.
14
+ */
15
+ export interface CepAddress {
16
+ /** The CEP that resolved, bare digits. */
17
+ cep: string;
18
+ /** Logradouro (street name, no number). */
19
+ street: string | null;
20
+ /** Bairro. */
21
+ neighborhood: string | null;
22
+ /** Município. */
23
+ city: string | null;
24
+ /** UF. */
25
+ state: string | null;
26
+ }
27
+
28
+ /** What the field is currently doing, surfaced as a status line. */
29
+ export type CepLookupStatus = 'idle' | 'loading' | 'found' | 'notfound';
30
+
31
+ export interface CepFieldProps {
32
+ /** Current field value. Masked (`01310-100`) or bare — the field re-masks. */
33
+ value: string;
34
+ /**
35
+ * Called on every keystroke with the MASKED value, so the caller's state and
36
+ * what the user sees never diverge.
37
+ */
38
+ onChange: (masked: string) => void;
39
+ /**
40
+ * Resolve a complete CEP to an address, or `null` when it is unknown.
41
+ *
42
+ * Injected rather than hard-wired: the component knows nothing about
43
+ * providers, transport or auth. Omit it to get a masked, validated CEP input
44
+ * with no lookup at all.
45
+ */
46
+ onLookup?: (cep: string) => Promise<CepAddress | null>;
47
+ /**
48
+ * Called once a CEP resolves — this is where the caller fills the address
49
+ * fields. Never called for an incomplete, unchanged or unknown CEP.
50
+ */
51
+ onResolved?: (address: CepAddress) => void;
52
+ /**
53
+ * Called when a COMPLETE CEP fails to resolve (unknown, or every provider
54
+ * down). The counterpart to `onResolved`: a caller that auto-filled from a
55
+ * previous CEP should undo that here, so the form never shows one CEP's
56
+ * street under a different CEP.
57
+ */
58
+ onNotFound?: () => void;
59
+ /** Visible label. Defaults to `CEP`. */
60
+ label?: string;
61
+ /** Placeholder. Defaults to `00000-000`. */
62
+ placeholder?: string;
63
+ /** Validation error from the caller's form, shown below the input. */
64
+ error?: string;
65
+ /** Disables the input and suppresses lookups. */
66
+ disabled?: boolean;
67
+ /** Milliseconds to wait after the last keystroke before looking up. */
68
+ debounceMs?: number;
69
+ /** Field name / input id. Defaults to `postalCode`. */
70
+ name?: string;
71
+ /** Test id for the input. Defaults to `cep-field-<name>`. */
72
+ dataTestId?: string;
73
+ }
@@ -0,0 +1,2 @@
1
+ export { CepField } from './CepField';
2
+ export type { CepAddress, CepFieldProps, CepLookupStatus } from './CepField.types';
@@ -0,0 +1,28 @@
1
+ import type { CheckboxProps } from './Checkbox.types';
2
+
3
+ type CheckboxDefaultedKeys = 'variant' | 'ripple' | 'glow' | 'pulse';
4
+
5
+ type ResolvedCheckboxProps = CheckboxProps &
6
+ Required<Pick<CheckboxProps, CheckboxDefaultedKeys>>;
7
+
8
+ const CHECKBOX_DEFAULTS: Pick<CheckboxProps, CheckboxDefaultedKeys> = {
9
+ variant: 'default',
10
+ ripple: true,
11
+ glow: false,
12
+ pulse: false,
13
+ };
14
+
15
+ // Strips explicitly-undefined props before the merge, so `ripple={undefined}` still
16
+ // falls back to the default exactly as a destructuring default would.
17
+ const definedProps = (props: CheckboxProps): Partial<CheckboxProps> =>
18
+ Object.fromEntries(
19
+ Object.entries(props).filter(([, value]) => value !== undefined),
20
+ ) as Partial<CheckboxProps>;
21
+
22
+ export const resolveCheckboxProps = (props: CheckboxProps): ResolvedCheckboxProps =>
23
+ ({ ...CHECKBOX_DEFAULTS, ...definedProps(props) }) as ResolvedCheckboxProps;
24
+
25
+ export const makeTestId =
26
+ (dataTestId?: string) =>
27
+ (suffix: string): string =>
28
+ dataTestId ? `${dataTestId}-${suffix}` : `checkbox-${suffix}`;
@@ -8,6 +8,7 @@ import {
8
8
  styled} from '@mui/material';
9
9
  import React from 'react';
10
10
 
11
+ import { makeTestId, resolveCheckboxProps } from './Checkbox.helpers';
11
12
  import type { CheckboxProps } from './Checkbox.types';
12
13
 
13
14
  const pulse = keyframes`
@@ -99,21 +100,23 @@ const StyledFormHelperText = styled(FormHelperText)(({ theme }) => ({
99
100
  }));
100
101
 
101
102
  export const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
102
- ({
103
- variant = 'default',
104
- label,
105
- error,
106
- helperText,
107
- loading,
108
- ripple = true,
109
- glow = false,
110
- pulse = false,
111
- disabled,
112
- 'data-testid': dataTestId,
113
- ...props
114
- }, ref) => {
103
+ (rawProps, ref) => {
104
+ const {
105
+ variant,
106
+ label,
107
+ error,
108
+ helperText,
109
+ loading,
110
+ ripple,
111
+ glow,
112
+ pulse,
113
+ disabled,
114
+ 'data-testid': dataTestId,
115
+ ...props
116
+ } = resolveCheckboxProps(rawProps);
115
117
  const isDisabled = disabled || loading;
116
-
118
+ const testId = makeTestId(dataTestId);
119
+
117
120
  const checkbox = (
118
121
  <div style={{ position: 'relative', display: 'inline-flex' }}>
119
122
  <StyledCheckbox
@@ -144,7 +147,7 @@ export const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
144
147
 
145
148
  if (label) {
146
149
  return (
147
- <div data-testid={dataTestId ? `${dataTestId}-container` : 'checkbox-container'}>
150
+ <div data-testid={testId('container')}>
148
151
  <StyledFormControlLabel
149
152
  control={checkbox}
150
153
  label={label}
@@ -154,7 +157,7 @@ export const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
154
157
  {helperText && (
155
158
  <StyledFormHelperText
156
159
  error={error}
157
- data-testid={dataTestId ? `${dataTestId}-helper` : 'checkbox-helper'}
160
+ data-testid={testId('helper')}
158
161
  >
159
162
  {helperText}
160
163
  </StyledFormHelperText>