@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,46 @@
1
+ import React from 'react';
2
+
3
+ import { useConfirmAction } from './ConfirmAction.hooks';
4
+ import { ConfirmActionDialog } from './ConfirmAction.parts';
5
+ import type { ConfirmActionProps } from './ConfirmAction.types';
6
+
7
+ /**
8
+ * Wraps any trigger in a confirmation popup.
9
+ *
10
+ * The trigger is a render prop rather than a fixed element, so the same guard
11
+ * covers a button, a dropdown item, an icon or a table row action without this
12
+ * component knowing what any of them look like. The guarded handler runs ONLY
13
+ * after the operator confirms:
14
+ *
15
+ * ```tsx
16
+ * <ConfirmAction
17
+ * title="Excluir a categoria?"
18
+ * entityName={row.name}
19
+ * description="Ela vai para a lixeira e pode ser restaurada de lá."
20
+ * confirmText="Excluir"
21
+ * onConfirm={() => deleteCategoryAction({ tenantSlug, id: row.id })}
22
+ * >
23
+ * {(request) => <Button color="danger" onClick={request}>Excluir</Button>}
24
+ * </ConfirmAction>
25
+ * ```
26
+ *
27
+ * For the common case — an existing button whose `onClick` is already the
28
+ * destructive write — reach for {@link withConfirmation} instead, which needs
29
+ * no restructuring of the call site.
30
+ */
31
+ export function ConfirmAction({
32
+ onConfirm,
33
+ children,
34
+ ...options
35
+ }: ConfirmActionProps): React.ReactElement {
36
+ const state = useConfirmAction(onConfirm, options.errorText);
37
+
38
+ return (
39
+ <>
40
+ {children(state.request, { pending: state.pending })}
41
+ <ConfirmActionDialog state={state} {...options} />
42
+ </>
43
+ );
44
+ }
45
+
46
+ ConfirmAction.displayName = 'ConfirmAction';
@@ -0,0 +1,111 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+
3
+ /**
4
+ * How much the confirmation should shout. `destructive` paints the dialog and
5
+ * its confirm button in the error colour and moves the initial focus onto
6
+ * Cancel, so a stray Enter cannot complete a delete.
7
+ */
8
+ export type ConfirmTone = 'destructive' | 'default';
9
+
10
+ /** Which button owns focus when the dialog opens. */
11
+ export type ConfirmInitialFocus = 'confirm' | 'cancel';
12
+
13
+ /**
14
+ * Everything the confirmation popup needs to say. `title` and `confirmText` are
15
+ * required on purpose: a confirm whose button reads "OK" tells the operator
16
+ * nothing about what is about to happen, so the verb is never defaulted.
17
+ */
18
+ export interface ConfirmOptions {
19
+ /** The question, e.g. `Excluir a categoria?`. */
20
+ title: string;
21
+ /**
22
+ * The consequence, in the operator's words. Prefer saying whether the action
23
+ * can be undone — the recycle bin recovers a soft delete, nothing recovers a
24
+ * purge. Falls back to a neutral sentence built from `entityName`.
25
+ */
26
+ description?: ReactNode;
27
+ /** The specific row being acted on, echoed into the dialog so it names a thing. */
28
+ entityName?: string;
29
+ /** The confirm button's label. Use the VERB ("Excluir"), never "OK". */
30
+ confirmText: string;
31
+ /** The cancel button's label. Defaults to `Cancelar`. */
32
+ cancelText?: string;
33
+ /**
34
+ * When set, the confirm button stays disabled until the operator types this
35
+ * exact string. For the handful of actions with no recovery path at all
36
+ * (permanent purge, suspending a live store), a click is too cheap.
37
+ */
38
+ typeToConfirm?: string;
39
+ /** Label above the type-to-confirm field. Defaults to a sentence naming the text. */
40
+ typeToConfirmLabel?: string;
41
+ /** Defaults to `destructive` — this component exists for destructive actions. */
42
+ tone?: ConfirmTone;
43
+ /** Defaults to `cancel` for the destructive tone, `confirm` otherwise. */
44
+ initialFocus?: ConfirmInitialFocus;
45
+ /** Shown inside the dialog when the action rejects; the dialog stays open. */
46
+ errorText?: string;
47
+ /** Root test id; the dialog's parts derive theirs from it. */
48
+ dataTestId?: string;
49
+ }
50
+
51
+ /**
52
+ * The action being guarded. A returned promise keeps the dialog open in its
53
+ * pending state until it settles, so the operator sees the write finish and
54
+ * cannot fire it twice.
55
+ *
56
+ * It takes no arguments deliberately: the click that opened the popup is long
57
+ * over by the time the operator confirms, and holding its synthetic event
58
+ * across that gap hands the handler a target that may no longer be in the
59
+ * document.
60
+ */
61
+ export type ConfirmHandler = () => void | Promise<unknown>;
62
+
63
+ /** The confirmation state machine returned by `useConfirmAction`. */
64
+ export interface UseConfirmActionResult {
65
+ /** True while the popup is open. */
66
+ open: boolean;
67
+ /** True while the confirmed action is in flight. */
68
+ pending: boolean;
69
+ /** The failure message from the last attempt, or null. */
70
+ error: string | null;
71
+ /** Open the popup (this is what the trigger's click does). */
72
+ request: () => void;
73
+ /** Dismiss without running anything. No-op while pending. */
74
+ cancel: () => void;
75
+ /** Run the guarded action. No-op while pending. */
76
+ confirm: () => void;
77
+ }
78
+
79
+ /**
80
+ * `<ConfirmAction>`'s props: the options, the guarded action, and a render prop
81
+ * for the trigger. The render prop receives the opener so any element can be
82
+ * the trigger — a button, a menu item, an icon.
83
+ */
84
+ export interface ConfirmActionProps extends ConfirmOptions {
85
+ onConfirm: ConfirmHandler;
86
+ children: (request: () => void, state: { pending: boolean }) => ReactNode;
87
+ }
88
+
89
+ /**
90
+ * The minimum a component must accept to be wrappable by `withConfirmation`:
91
+ * some click handler. The signature is deliberately at its widest so that any
92
+ * button-shaped component qualifies — `MouseEventHandler<HTMLButtonElement>`,
93
+ * `MouseEventHandler<HTMLAnchorElement>` and a bare `() => void` all satisfy it,
94
+ * where naming one concrete element type would exclude the others.
95
+ */
96
+ export interface ConfirmableProps {
97
+ onClick?: (...args: never[]) => unknown;
98
+ }
99
+
100
+ /**
101
+ * The prop `withConfirmation` adds to the wrapped component. Pass the options
102
+ * to guard the click; pass `false` (or nothing, when the HOC has no defaults)
103
+ * to fall through to the plain component — the escape hatch for the same button
104
+ * used in a context where the action is harmless.
105
+ */
106
+ export interface WithConfirmationProps {
107
+ confirm?: Partial<ConfirmOptions> | false;
108
+ }
109
+
110
+ /** The component `withConfirmation` returns. */
111
+ export type ConfirmableComponent<P> = ComponentType<P & WithConfirmationProps>;
@@ -0,0 +1,18 @@
1
+ import { Button } from '../../form/Button';
2
+ import type { ButtonProps } from '../../form/Button/Button.types';
3
+
4
+ import { withConfirmation } from './with-confirmation';
5
+ import type { ConfirmableComponent } from './ConfirmAction.types';
6
+
7
+ /**
8
+ * The library `Button`, already wrapped — the shape most call sites want.
9
+ *
10
+ * Adding the guard to an existing button is then an import swap plus the
11
+ * `confirm` prop; the button's own props (colour, size, icon) are untouched.
12
+ * The baked-in defaults are only the wording and tone every destructive
13
+ * confirmation shares, and each instance can still override them.
14
+ */
15
+ export const ConfirmButton: ConfirmableComponent<ButtonProps> = withConfirmation<ButtonProps>(
16
+ Button,
17
+ { cancelText: 'Cancelar', tone: 'destructive' },
18
+ );
@@ -0,0 +1,288 @@
1
+ /**
2
+ * The safety contract, asserted at the surface an operator actually touches.
3
+ *
4
+ * Every case here is a way the guard could fail OPEN — the write leaving before
5
+ * anyone confirmed it, or leaving twice. A confirmation that merely renders is
6
+ * worth nothing; what matters is that nothing happens until the confirm button
7
+ * is pressed, and that it then happens exactly once.
8
+ */
9
+ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
10
+ import { afterEach, describe, expect, it, vi } from 'vitest';
11
+
12
+ import { Button } from '../../../form/Button';
13
+ import { ConfirmAction, withConfirmation } from '../index';
14
+
15
+ afterEach(cleanup);
16
+
17
+ const OPTIONS = {
18
+ title: 'Excluir a categoria?',
19
+ description: 'Ela vai para a lixeira e pode ser restaurada de lá.',
20
+ confirmText: 'Excluir',
21
+ };
22
+
23
+ /** The dialog's buttons, by the test ids `AlertDialog` derives from ours. */
24
+ const confirmButton = (): HTMLElement => screen.getByTestId('confirm-action-confirm-button');
25
+ const cancelButton = (): HTMLElement => screen.getByTestId('confirm-action-cancel-button');
26
+ const trigger = (): HTMLElement => screen.getByRole('button', { name: 'Excluir' });
27
+
28
+ function renderConfirmAction(
29
+ onConfirm: () => void | Promise<unknown>,
30
+ options: Record<string, unknown> = {},
31
+ ): void {
32
+ render(
33
+ <ConfirmAction {...OPTIONS} {...options} onConfirm={onConfirm}>
34
+ {(request) => (
35
+ <button type="button" onClick={request}>
36
+ Excluir
37
+ </button>
38
+ )}
39
+ </ConfirmAction>,
40
+ );
41
+ }
42
+
43
+ describe('ConfirmAction', () => {
44
+ it('opens the popup without running the action', () => {
45
+ const onConfirm = vi.fn();
46
+ renderConfirmAction(onConfirm);
47
+
48
+ fireEvent.click(trigger());
49
+
50
+ expect(screen.getByText('Excluir a categoria?')).toBeInTheDocument();
51
+ expect(onConfirm).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('runs nothing when the operator cancels', async () => {
55
+ const onConfirm = vi.fn();
56
+ renderConfirmAction(onConfirm);
57
+
58
+ fireEvent.click(trigger());
59
+ fireEvent.click(cancelButton());
60
+
61
+ expect(onConfirm).not.toHaveBeenCalled();
62
+ await waitFor(() => {
63
+ expect(screen.queryByText('Excluir a categoria?')).not.toBeInTheDocument();
64
+ });
65
+ });
66
+
67
+ it('runs the action once the operator confirms', async () => {
68
+ const onConfirm = vi.fn();
69
+ renderConfirmAction(onConfirm);
70
+
71
+ fireEvent.click(trigger());
72
+ fireEvent.click(confirmButton());
73
+
74
+ expect(onConfirm).toHaveBeenCalledTimes(1);
75
+ await waitFor(() => {
76
+ expect(screen.queryByText('Excluir a categoria?')).not.toBeInTheDocument();
77
+ });
78
+ });
79
+
80
+ it('sends one write when the confirm button is double-clicked', () => {
81
+ // The write is held open so both clicks land inside the in-flight window —
82
+ // the exact race a bare onClick would turn into two DELETEs. The resolver
83
+ // lives on a container because the flakiness gate forbids reassigning a
84
+ // closed-over binding from inside a stub.
85
+ const inFlight: { resolve: () => void } = { resolve: () => undefined };
86
+ const onConfirm = vi.fn(
87
+ () =>
88
+ new Promise<void>((resolve) => {
89
+ inFlight.resolve = resolve;
90
+ }),
91
+ );
92
+ renderConfirmAction(onConfirm);
93
+
94
+ fireEvent.click(trigger());
95
+ fireEvent.click(confirmButton());
96
+ fireEvent.click(confirmButton());
97
+
98
+ expect(onConfirm).toHaveBeenCalledTimes(1);
99
+ inFlight.resolve();
100
+ });
101
+
102
+ it('disables the confirm while the write is in flight', async () => {
103
+ const inFlight: { resolve: () => void } = { resolve: () => undefined };
104
+ const onConfirm = vi.fn(
105
+ () =>
106
+ new Promise<void>((resolve) => {
107
+ inFlight.resolve = resolve;
108
+ }),
109
+ );
110
+ renderConfirmAction(onConfirm);
111
+
112
+ fireEvent.click(trigger());
113
+ fireEvent.click(confirmButton());
114
+
115
+ await waitFor(() => {
116
+ expect(confirmButton()).toBeDisabled();
117
+ });
118
+ inFlight.resolve();
119
+ });
120
+
121
+ it('keeps the popup open, carrying the error, when the action fails', async () => {
122
+ // The server's sentence is the one the operator can act on, so a rejection
123
+ // carrying a message beats the generic fallback.
124
+ const onConfirm = vi.fn(() =>
125
+ Promise.reject(new Error('Esta categoria ainda tem produtos vinculados.')),
126
+ );
127
+ renderConfirmAction(onConfirm);
128
+
129
+ fireEvent.click(trigger());
130
+ fireEvent.click(confirmButton());
131
+
132
+ await waitFor(() => {
133
+ expect(screen.getByTestId('confirm-action-error')).toHaveTextContent(
134
+ 'Esta categoria ainda tem produtos vinculados.',
135
+ );
136
+ });
137
+ expect(screen.getByText('Excluir a categoria?')).toBeInTheDocument();
138
+ });
139
+
140
+ it('falls back to the configured wording when the rejection says nothing', async () => {
141
+ const onConfirm = vi.fn(() => Promise.reject(new Error('')));
142
+ renderConfirmAction(onConfirm, { errorText: 'Não foi possível excluir a categoria.' });
143
+
144
+ fireEvent.click(trigger());
145
+ fireEvent.click(confirmButton());
146
+
147
+ await waitFor(() => {
148
+ expect(screen.getByTestId('confirm-action-error')).toHaveTextContent(
149
+ 'Não foi possível excluir a categoria.',
150
+ );
151
+ });
152
+ });
153
+
154
+ it('recovers from a failure: a second confirm can still run', async () => {
155
+ // `busy` must be released on the failure path too, or the popup is left
156
+ // holding an error it can never be retried out of.
157
+ const attempts = { count: 0 };
158
+ const onConfirm = vi.fn(() => {
159
+ attempts.count += 1;
160
+ return attempts.count === 1 ? Promise.reject(new Error('rede')) : Promise.resolve();
161
+ });
162
+ renderConfirmAction(onConfirm);
163
+
164
+ fireEvent.click(trigger());
165
+ fireEvent.click(confirmButton());
166
+ await waitFor(() => {
167
+ expect(screen.getByTestId('confirm-action-error')).toBeInTheDocument();
168
+ });
169
+
170
+ fireEvent.click(confirmButton());
171
+ expect(onConfirm).toHaveBeenCalledTimes(2);
172
+ await waitFor(() => {
173
+ expect(screen.queryByText('Excluir a categoria?')).not.toBeInTheDocument();
174
+ });
175
+ });
176
+
177
+ it('announces as an alertdialog named by its title and described by its body', () => {
178
+ // `alertdialog`, not `dialog`: this interrupts what the operator was doing
179
+ // and will not go away until answered, which is what makes a screen reader
180
+ // read the message out on open instead of just the dialog's name. MUI
181
+ // hardcodes role="dialog", so the override has to keep working.
182
+ renderConfirmAction(vi.fn());
183
+ fireEvent.click(trigger());
184
+
185
+ const dialog = screen.getByRole('alertdialog');
186
+ expect(dialog).toHaveAccessibleName('Excluir a categoria?');
187
+ expect(dialog).toHaveAccessibleDescription(
188
+ /Ela vai para a lixeira e pode ser restaurada de lá./,
189
+ );
190
+ });
191
+
192
+ it('opens with focus on Cancel, so a stray Enter cannot delete', async () => {
193
+ renderConfirmAction(vi.fn());
194
+
195
+ fireEvent.click(trigger());
196
+
197
+ await waitFor(() => {
198
+ expect(cancelButton()).toHaveFocus();
199
+ });
200
+ });
201
+
202
+ it('holds the confirm disabled until the exact text is typed', async () => {
203
+ const onConfirm = vi.fn();
204
+ renderConfirmAction(onConfirm, { typeToConfirm: 'Bebidas' });
205
+
206
+ fireEvent.click(trigger());
207
+ const field = screen.getByTestId('confirm-action-type-to-confirm');
208
+ expect(confirmButton()).toBeDisabled();
209
+
210
+ fireEvent.change(field, { target: { value: 'Bebida' } });
211
+ expect(confirmButton()).toBeDisabled();
212
+
213
+ fireEvent.change(field, { target: { value: 'Bebidas' } });
214
+ expect(confirmButton()).toBeEnabled();
215
+
216
+ fireEvent.click(confirmButton());
217
+ expect(onConfirm).toHaveBeenCalledTimes(1);
218
+ await waitFor(() => {
219
+ expect(screen.queryByText('Excluir a categoria?')).not.toBeInTheDocument();
220
+ });
221
+ });
222
+
223
+ it('starts each open from an empty type-to-confirm field', async () => {
224
+ renderConfirmAction(vi.fn(), { typeToConfirm: 'Bebidas' });
225
+
226
+ fireEvent.click(trigger());
227
+ fireEvent.change(screen.getByTestId('confirm-action-type-to-confirm'), {
228
+ target: { value: 'Bebidas' },
229
+ });
230
+ fireEvent.click(cancelButton());
231
+ await waitFor(() => {
232
+ expect(screen.queryByText('Excluir a categoria?')).not.toBeInTheDocument();
233
+ });
234
+
235
+ fireEvent.click(trigger());
236
+ expect(screen.getByTestId('confirm-action-type-to-confirm')).toHaveValue('');
237
+ expect(confirmButton()).toBeDisabled();
238
+ });
239
+ });
240
+
241
+ describe('withConfirmation', () => {
242
+ const ConfirmButton = withConfirmation(Button);
243
+
244
+ it("guards the wrapped component's own onClick", async () => {
245
+ const onClick = vi.fn();
246
+ render(
247
+ <ConfirmButton onClick={onClick} confirm={OPTIONS}>
248
+ Excluir
249
+ </ConfirmButton>,
250
+ );
251
+
252
+ fireEvent.click(trigger());
253
+ expect(onClick).not.toHaveBeenCalled();
254
+
255
+ fireEvent.click(confirmButton());
256
+ expect(onClick).toHaveBeenCalledTimes(1);
257
+ await waitFor(() => {
258
+ expect(screen.queryByText('Excluir a categoria?')).not.toBeInTheDocument();
259
+ });
260
+ });
261
+
262
+ it('falls through to the plain component when confirm is false', () => {
263
+ const onClick = vi.fn();
264
+ render(
265
+ <ConfirmButton onClick={onClick} confirm={false}>
266
+ Excluir
267
+ </ConfirmButton>,
268
+ );
269
+
270
+ fireEvent.click(trigger());
271
+
272
+ expect(onClick).toHaveBeenCalledTimes(1);
273
+ // A COUNT rather than an absence assertion: the popup was never rendered
274
+ // here, so there is no prior presence to wait for — which is exactly the
275
+ // distinction the flakiness rule exists to enforce.
276
+ expect(screen.queryAllByTestId('confirm-action')).toHaveLength(0);
277
+ });
278
+
279
+ it('does not leak its own confirm prop onto the DOM node', () => {
280
+ render(
281
+ <ConfirmButton onClick={vi.fn()} confirm={OPTIONS}>
282
+ Excluir
283
+ </ConfirmButton>,
284
+ );
285
+
286
+ expect(trigger()).not.toHaveAttribute('confirm');
287
+ });
288
+ });
@@ -0,0 +1,16 @@
1
+ export { ConfirmAction } from './ConfirmAction';
2
+ export { ConfirmButton } from './ConfirmButton';
3
+ export { DEFAULT_ERROR_TEXT, useConfirmAction } from './ConfirmAction.hooks';
4
+ export { ConfirmActionDialog } from './ConfirmAction.parts';
5
+ export { withConfirmation } from './with-confirmation';
6
+ export type {
7
+ ConfirmActionProps,
8
+ ConfirmHandler,
9
+ ConfirmInitialFocus,
10
+ ConfirmOptions,
11
+ ConfirmTone,
12
+ ConfirmableComponent,
13
+ ConfirmableProps,
14
+ UseConfirmActionResult,
15
+ WithConfirmationProps,
16
+ } from './ConfirmAction.types';
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import type { ComponentType } from 'react';
3
+
4
+ import { useConfirmAction } from './ConfirmAction.hooks';
5
+ import { ConfirmActionDialog } from './ConfirmAction.parts';
6
+ import type {
7
+ ConfirmHandler,
8
+ ConfirmOptions,
9
+ ConfirmableComponent,
10
+ ConfirmableProps,
11
+ WithConfirmationProps,
12
+ } from './ConfirmAction.types';
13
+
14
+ /**
15
+ * Merge the HOC's baked-in defaults with the per-instance `confirm` prop.
16
+ * Returns null when the instance opts out (`confirm={false}`) or when neither
17
+ * side supplies the two fields a confirmation cannot be written without — the
18
+ * question and the verb. A null result means "render the plain component",
19
+ * which keeps the wrapped export usable in harmless contexts.
20
+ */
21
+ function resolveOptions(
22
+ defaults: Partial<ConfirmOptions> | undefined,
23
+ instance: Partial<ConfirmOptions> | false | undefined,
24
+ ): ConfirmOptions | null {
25
+ if (instance === false) return null;
26
+ const merged = { ...defaults, ...instance };
27
+ if (!merged.title || !merged.confirmText) return null;
28
+ return { ...merged, title: merged.title, confirmText: merged.confirmText };
29
+ }
30
+
31
+ /**
32
+ * Adapt the wrapped component's `onClick` into a {@link ConfirmHandler}.
33
+ *
34
+ * The click argument is dropped (see `ConfirmHandler`) and the return value is
35
+ * narrowed: a promise is handed back so the popup can hold its pending state
36
+ * until the write settles, anything else becomes `undefined`. A component with
37
+ * no `onClick` has nothing to guard, so confirming is a no-op.
38
+ */
39
+ function useGuardedHandler(onClick: ConfirmableProps['onClick']): ConfirmHandler {
40
+ return React.useCallback((): void | Promise<unknown> => {
41
+ const result = onClick?.();
42
+ return result instanceof Promise ? result : undefined;
43
+ }, [onClick]);
44
+ }
45
+
46
+ /**
47
+ * Give any click-driven component a confirmation popup, without restructuring
48
+ * the call site.
49
+ *
50
+ * The wrapped component's own `onClick` becomes the GUARDED action: clicking
51
+ * now opens the popup, and the handler runs only if the operator confirms. It
52
+ * inherits the whole contract from `useConfirmAction` — cancel sends nothing, a
53
+ * returned promise holds the popup in its pending state so a double-click
54
+ * cannot fire two writes, and a rejection keeps the popup open carrying the
55
+ * error.
56
+ *
57
+ * ```tsx
58
+ * const ConfirmButton = withConfirmation(Button);
59
+ *
60
+ * <ConfirmButton
61
+ * color="danger"
62
+ * onClick={() => deleteCategoryAction({ tenantSlug, id: row.id })}
63
+ * confirm={{
64
+ * title: "Excluir a categoria?",
65
+ * entityName: row.name,
66
+ * description: "Ela vai para a lixeira e pode ser restaurada de lá.",
67
+ * confirmText: "Excluir",
68
+ * }}
69
+ * >
70
+ * Excluir
71
+ * </ConfirmButton>
72
+ * ```
73
+ *
74
+ * Bake the wording in once when the same guard repeats — `withConfirmation(Button,
75
+ * { confirmText: "Excluir" })` — and let each instance override what differs.
76
+ * `confirm={false}` falls through to the plain component.
77
+ *
78
+ * Refs are not forwarded: the HOC renders a fragment (trigger + popup), so there
79
+ * is no single node to hand back. Wrap a component that needs a ref at its own
80
+ * call site with {@link ConfirmAction} instead.
81
+ */
82
+ export function withConfirmation<P extends ConfirmableProps>(
83
+ Wrapped: ComponentType<P>,
84
+ defaults?: Partial<ConfirmOptions>,
85
+ ): ConfirmableComponent<P> {
86
+ function WithConfirmation(props: P & WithConfirmationProps): React.ReactElement {
87
+ const { confirm, onClick } = props;
88
+ const options = resolveOptions(defaults, confirm);
89
+ const state = useConfirmAction(useGuardedHandler(onClick), options?.errorText);
90
+
91
+ // `confirm` is ours and must not reach the DOM — an unknown attribute on a
92
+ // <button> is a React warning at every render. The cast is the standard HOC
93
+ // one: TypeScript cannot prove `Omit<P & X, 'confirm'>` is still a `P`.
94
+ const { confirm: _confirm, ...rest } = props;
95
+ const forwarded = rest as unknown as P;
96
+
97
+ if (!options) return <Wrapped {...forwarded} />;
98
+
99
+ return (
100
+ <>
101
+ <Wrapped {...forwarded} onClick={state.request} />
102
+ <ConfirmActionDialog state={state} {...options} />
103
+ </>
104
+ );
105
+ }
106
+
107
+ WithConfirmation.displayName = `withConfirmation(${
108
+ Wrapped.displayName ?? Wrapped.name ?? 'Component'
109
+ })`;
110
+
111
+ return WithConfirmation;
112
+ }
@@ -1,6 +1,7 @@
1
1
  import type { SxProps, Theme } from '@mui/material';
2
2
  import { alpha, keyframes } from '@mui/material';
3
3
 
4
+ import { dynamicViewportHeight } from '../../../utils/viewport';
4
5
  import type { DialogProps } from './Dialog.types';
5
6
 
6
7
  /**
@@ -133,7 +134,8 @@ export function variantStylesOf(theme: Theme, opts: VariantStyleOptions): SxProp
133
134
  borderRadius: 0,
134
135
  margin: 0,
135
136
  width: '100vw',
136
- height: '100vh',
137
+ // `100dvh`, not `100vh` — see `dynamicViewportHeight`.
138
+ ...dynamicViewportHeight('height'),
137
139
  maxWidth: 'none',
138
140
  maxHeight: 'none',
139
141
  ...decorations,
@@ -144,7 +146,7 @@ export function variantStylesOf(theme: Theme, opts: VariantStyleOptions): SxProp
144
146
  borderRadius: `${radius}px 0 0 ${radius}px`,
145
147
  margin: 0,
146
148
  width: maxWidthOf(opts.size),
147
- height: '100vh',
149
+ ...dynamicViewportHeight('height'),
148
150
  maxHeight: 'none',
149
151
  position: 'absolute' as const,
150
152
  right: 0,
@@ -1,4 +1,4 @@
1
- import { Close as CloseIcon } from '@mui/icons-material';
1
+ import CloseIcon from '@mui/icons-material/Close';
2
2
  import {
3
3
  Backdrop,
4
4
  Box,
@@ -199,6 +199,30 @@ export const DialogHeader: React.FC<DialogHeaderProps> = ({
199
199
  );
200
200
  };
201
201
 
202
+ /**
203
+ * Top padding for a body that sits directly under the title.
204
+ *
205
+ * MUI zeroes it there (`.MuiDialogTitle-root + &` on a non-`dividers` content),
206
+ * assuming the title's own bottom padding is the whole gap. That assumption
207
+ * breaks for a body opening with an outlined field: a filled field floats its
208
+ * label ABOVE the input's border box, and `DialogContent` is `overflow-y: auto`
209
+ * — a clipping box — so the label gets sliced in half (FUT-544: "Tipo" in the
210
+ * "Novo insumo" dialog).
211
+ *
212
+ * Reserve the overhang rather than restore the full 24px: the title already
213
+ * contributes its bottom padding, so a shrunk label's `translate(…, -9px)` is
214
+ * all that is missing. 12px clears it and leaves the gap visually tight.
215
+ */
216
+ const TITLED_BODY_PADDING_TOP = 1.5;
217
+
218
+ /**
219
+ * `.MuiDialogTitle-root + &` is `(0,2,0)`-specific and a plain `sx` entry is
220
+ * `(0,1,0)`, so the padding above only survives the cascade by naming the class
221
+ * again — `(0,3,0)`. Scoped to the title-following case so an untitled dialog
222
+ * keeps MUI's roomier default.
223
+ */
224
+ const TITLED_BODY_SELECTOR = '.MuiDialogTitle-root + &.MuiDialogContent-root';
225
+
202
226
  export const DialogContent: React.FC<DialogContentProps> = ({
203
227
  children,
204
228
  dividers = false,
@@ -211,6 +235,7 @@ export const DialogContent: React.FC<DialogContentProps> = ({
211
235
  dividers={dividers}
212
236
  sx={{
213
237
  padding: dense ? 1.5 : 3,
238
+ [TITLED_BODY_SELECTOR]: { paddingTop: TITLED_BODY_PADDING_TOP },
214
239
  '&.MuiDialogContent-dividers': {
215
240
  borderTop: dividers ? '1px solid' : 'none',
216
241
  borderBottom: dividers ? '1px solid' : 'none',