@12-apps/ui 1.0.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/components-catalog.md +14 -0
  2. package/package.json +42 -27
  3. package/src/__tests__/data-grid.test.tsx +125 -0
  4. package/src/__tests__/package-wiring.test.ts +12 -8
  5. package/src/__tests__/social-login-button.test.tsx +5 -5
  6. package/src/__tests__/viewport.test.ts +50 -0
  7. package/src/charts/index.ts +8 -0
  8. package/src/components/data-display/Alert/Alert.md +1 -1
  9. package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
  10. package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
  11. package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
  12. package/src/components/data-display/Alert/Alert.tsx +135 -328
  13. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
  14. package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
  15. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
  16. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
  17. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
  18. package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
  19. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
  20. package/src/components/data-display/Avatar/Avatar.tsx +154 -450
  21. package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
  22. package/src/components/data-display/Badge/Badge.animations.ts +67 -0
  23. package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
  24. package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
  25. package/src/components/data-display/Badge/Badge.styles.ts +261 -0
  26. package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
  27. package/src/components/data-display/Badge/Badge.tsx +151 -557
  28. package/src/components/data-display/Badge/Badge.variants.ts +106 -0
  29. package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
  30. package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
  31. package/src/components/data-display/Banner/Banner.styles.ts +120 -0
  32. package/src/components/data-display/Banner/Banner.tsx +179 -224
  33. package/src/components/data-display/Chart/Chart.md +48 -1
  34. package/src/components/data-display/Chart/Chart.tsx +82 -500
  35. package/src/components/data-display/Chart/Chart.types.ts +2 -0
  36. package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
  37. package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
  38. package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
  39. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
  40. package/src/components/data-display/Chart/SpecChart.tsx +53 -0
  41. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
  42. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
  43. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
  44. package/src/components/data-display/Chart/chart-format.ts +26 -0
  45. package/src/components/data-display/Chart/chart-internals.ts +148 -0
  46. package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
  47. package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
  48. package/src/components/data-display/Chart/chart-spec.ts +171 -0
  49. package/src/components/data-display/Chart/index.ts +30 -0
  50. package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
  51. package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
  52. package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
  53. package/src/components/data-display/Chip/Chip.tsx +31 -94
  54. package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
  55. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
  56. package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
  57. package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
  58. package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
  59. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
  60. package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
  61. package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
  62. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
  63. package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
  64. package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
  65. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
  66. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
  67. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
  68. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
  69. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
  70. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
  71. package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
  72. package/src/components/data-display/DataViews/base-card.tsx +278 -0
  73. package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
  74. package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
  75. package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
  76. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
  77. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
  78. package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
  79. package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
  80. package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
  81. package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
  82. package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
  83. package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
  84. package/src/components/data-display/DataViews/data-views-types.ts +312 -0
  85. package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
  86. package/src/components/data-display/DataViews/index.ts +33 -0
  87. package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
  88. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
  89. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
  90. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
  91. package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
  92. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
  93. package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
  94. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
  95. package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
  96. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
  97. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
  98. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
  99. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
  100. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
  101. package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
  102. package/src/components/data-display/LazyImage/LazyImage.md +1 -1
  103. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
  104. package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
  105. package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
  106. package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
  107. package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
  108. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
  109. package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
  110. package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
  111. package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
  112. package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
  113. package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
  114. package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
  115. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
  116. package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
  117. package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
  118. package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
  119. package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
  120. package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
  121. package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
  122. package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
  123. package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
  124. package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
  125. package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
  126. package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
  127. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
  128. package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
  129. package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
  130. package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
  131. package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
  132. package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
  133. package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
  134. package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
  135. package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
  136. package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
  137. package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
  138. package/src/components/data-display/Progress/Progress.styles.ts +101 -0
  139. package/src/components/data-display/Progress/Progress.tsx +185 -213
  140. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
  141. package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
  142. package/src/components/data-display/StatCard/StatCard.md +117 -0
  143. package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
  144. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
  145. package/src/components/data-display/StatCard/StatCard.tsx +236 -0
  146. package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
  147. package/src/components/data-display/StatCard/index.ts +7 -0
  148. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
  149. package/src/components/data-display/Stepper/Stepper.tsx +1 -1
  150. package/src/components/data-display/Table/Table.helpers.ts +31 -0
  151. package/src/components/data-display/Table/Table.hooks.ts +161 -0
  152. package/src/components/data-display/Table/Table.stories.tsx +3 -1
  153. package/src/components/data-display/Table/Table.styles.ts +223 -0
  154. package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
  155. package/src/components/data-display/Table/Table.tsx +291 -748
  156. package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
  157. package/src/components/data-display/Table/TableParts.tsx +298 -0
  158. package/src/components/data-display/Table/TableStates.tsx +21 -0
  159. package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
  160. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
  161. package/src/components/data-display/Timeline/Timeline.tsx +23 -284
  162. package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
  163. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
  164. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
  165. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
  166. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
  167. package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
  168. package/src/components/data-display/index.ts +1 -0
  169. package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
  170. package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
  171. package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
  172. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
  173. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
  174. package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
  175. package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
  176. package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
  177. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
  178. package/src/components/feedback/ConfirmAction/index.ts +16 -0
  179. package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
  180. package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
  181. package/src/components/feedback/Dialog/Dialog.tsx +26 -1
  182. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
  183. package/src/components/feedback/Modal/Modal.md +1 -1
  184. package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
  185. package/src/components/feedback/Sonner/Sonner.tsx +104 -288
  186. package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
  187. package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
  188. package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
  189. package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
  190. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
  191. package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
  192. package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
  193. package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
  194. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
  195. package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
  196. package/src/components/feedback/Toast/Toast.tsx +92 -81
  197. package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
  198. package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
  199. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
  200. package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
  201. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
  202. package/src/components/feedback/index.ts +1 -0
  203. package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
  204. package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
  205. package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
  206. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
  207. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
  208. package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
  209. package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
  210. package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
  211. package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
  212. package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
  213. package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
  214. package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
  215. package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
  216. package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
  217. package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
  218. package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
  219. package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
  220. package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
  221. package/src/components/form/Button/Button.helpers.ts +36 -0
  222. package/src/components/form/Button/Button.stories.tsx +4 -1
  223. package/src/components/form/Button/Button.styles.ts +225 -0
  224. package/src/components/form/Button/Button.test.stories.tsx +25 -21
  225. package/src/components/form/Button/Button.tsx +45 -252
  226. package/src/components/form/Calendar/Calendar.dates.ts +100 -0
  227. package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
  228. package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
  229. package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
  230. package/src/components/form/Calendar/Calendar.selection.ts +156 -0
  231. package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
  232. package/src/components/form/Calendar/Calendar.tsx +63 -551
  233. package/src/components/form/Calendar/Calendar.types.ts +16 -0
  234. package/src/components/form/Calendar/CalendarDay.tsx +127 -0
  235. package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
  236. package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
  237. package/src/components/form/CepField/CepField.md +138 -0
  238. package/src/components/form/CepField/CepField.stories.tsx +138 -0
  239. package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
  240. package/src/components/form/CepField/CepField.tsx +211 -0
  241. package/src/components/form/CepField/CepField.types.ts +73 -0
  242. package/src/components/form/CepField/index.ts +2 -0
  243. package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
  244. package/src/components/form/Checkbox/Checkbox.tsx +19 -16
  245. package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
  246. package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
  247. package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
  248. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
  249. package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
  250. package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
  251. package/src/components/form/Command/Command.hooks.ts +207 -0
  252. package/src/components/form/Command/Command.parts.tsx +58 -0
  253. package/src/components/form/Command/Command.styles.ts +83 -0
  254. package/src/components/form/Command/Command.tsx +61 -270
  255. package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
  256. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
  257. package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
  258. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
  259. package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
  260. package/src/components/form/HeaderButton/index.ts +2 -0
  261. package/src/components/form/Input/Input.stories.tsx +5 -1
  262. package/src/components/form/Input/Input.styles.ts +176 -0
  263. package/src/components/form/Input/Input.test.stories.tsx +30 -23
  264. package/src/components/form/Input/Input.tsx +71 -211
  265. package/src/components/form/Input/Input.types.ts +11 -0
  266. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
  267. package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
  268. package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
  269. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
  270. package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
  271. package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
  272. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
  273. package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
  274. package/src/components/form/PhoneInput/countries.ts +54 -0
  275. package/src/components/form/PhoneInput/phone.ts +47 -0
  276. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
  277. package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
  278. package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
  279. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
  280. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
  281. package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
  282. package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
  283. package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
  284. package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
  285. package/src/components/form/Select/Select.test.stories.tsx +35 -27
  286. package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
  287. package/src/components/form/Switch/Switch.md +2 -1
  288. package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
  289. package/src/components/form/Textarea/Textarea.styles.ts +143 -0
  290. package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
  291. package/src/components/form/Textarea/Textarea.tsx +114 -535
  292. package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
  293. package/src/components/form/Toggle/Toggle.styles.ts +211 -0
  294. package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
  295. package/src/components/form/Toggle/Toggle.tsx +10 -185
  296. package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
  297. package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
  298. package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
  299. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
  300. package/src/components/form/UploadButton/UploadButton.tsx +76 -305
  301. package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
  302. package/src/components/form/index.ts +1 -0
  303. package/src/components/form/total-form/Fields/CepField.tsx +143 -0
  304. package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
  305. package/src/components/form/total-form/Fields/index.ts +4 -0
  306. package/src/components/form/total-form/FormContainer.tsx +3 -3
  307. package/src/components/form/total-form/SubmitButton.tsx +25 -2
  308. package/src/components/form/total-form/total-form.test.tsx +118 -1
  309. package/src/components/layout/Accordion/Accordion.md +1 -1
  310. package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
  311. package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
  312. package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
  313. package/src/components/layout/Accordion/Accordion.tsx +2 -75
  314. package/src/components/layout/Card/Card.helpers.ts +30 -0
  315. package/src/components/layout/Card/Card.stories.tsx +9 -11
  316. package/src/components/layout/Card/Card.styles.ts +182 -0
  317. package/src/components/layout/Card/Card.test.stories.tsx +46 -46
  318. package/src/components/layout/Card/Card.tsx +24 -241
  319. package/src/components/layout/CardGrid/CardGrid.md +96 -0
  320. package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
  321. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
  322. package/src/components/layout/CardGrid/index.ts +2 -0
  323. package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
  324. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
  325. package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
  326. package/src/components/layout/Container/Container.test.stories.tsx +11 -16
  327. package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
  328. package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
  329. package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
  330. package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
  331. package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
  332. package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
  333. package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
  334. package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
  335. package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
  336. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
  337. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
  338. package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
  339. package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
  340. package/src/components/layout/Dashboard/index.ts +2 -1
  341. package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
  342. package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
  343. package/src/components/layout/Drawer/Drawer.tsx +67 -53
  344. package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
  345. package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
  346. package/src/components/layout/Resizable/Resizable.tsx +38 -153
  347. package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
  348. package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
  349. package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
  350. package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
  351. package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
  352. package/src/components/layout/Separator/Separator.styles.ts +128 -0
  353. package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
  354. package/src/components/layout/Separator/Separator.tsx +6 -132
  355. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
  356. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
  357. package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
  358. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
  359. package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
  360. package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
  361. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
  362. package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
  363. package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
  364. package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
  365. package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
  366. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
  367. package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
  368. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
  369. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
  370. package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
  371. package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
  372. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
  373. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
  374. package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
  375. package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
  376. package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
  377. package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
  378. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
  379. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
  380. package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
  381. package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
  382. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
  383. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
  384. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
  385. package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
  386. package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
  387. package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
  388. package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
  389. package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
  390. package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
  391. package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
  392. package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
  393. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
  394. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
  395. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
  396. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
  397. package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
  398. package/src/components/navigation/Pagination/Pagination.md +2 -1
  399. package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
  400. package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
  401. package/src/components/navigation/Pagination/Pagination.tsx +178 -313
  402. package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
  403. package/src/components/navigation/Tabs/Tabs.md +3 -1
  404. package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
  405. package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
  406. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
  407. package/src/components/navigation/Tabs/Tabs.tsx +192 -361
  408. package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
  409. package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
  410. package/src/components/typography/Code/Code.test.stories.tsx +5 -5
  411. package/src/components/typography/Code/Code.tsx +74 -49
  412. package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
  413. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
  414. package/src/components/typography/Text/Text.tsx +84 -77
  415. package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
  416. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
  417. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
  418. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
  419. package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
  420. package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
  421. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
  422. package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
  423. package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
  424. package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
  425. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
  426. package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
  427. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
  428. package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
  429. package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
  430. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
  431. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
  432. package/src/components/utility/InstallPrompt/index.ts +10 -0
  433. package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
  434. package/src/components/utility/Transition/Transition.helpers.ts +40 -0
  435. package/src/components/utility/Transition/Transition.tsx +17 -68
  436. package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
  437. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
  438. package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
  439. package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
  440. package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
  441. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
  442. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
  443. package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
  444. package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
  445. package/src/components/utility/index.ts +1 -0
  446. package/src/mui/Link.ts +2 -0
  447. package/src/utils/index.ts +2 -1
  448. package/src/utils/viewport.ts +65 -0
@@ -1,5 +1,11 @@
1
1
  import type { ReactNode } from 'react';
2
2
 
3
+ /** A modal's position in the stack: the top panel, the one behind it, or hidden. */
4
+ export type ModalPanelRole = 'primary' | 'secondary' | 'background';
5
+
6
+ /** The breakpoint the panel's width is capped at, or `false` for uncapped. */
7
+ export type PanelMaxWidth = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
8
+
3
9
  export interface StackedModalProps {
4
10
  /** Controls the visibility of the modal */
5
11
  open: boolean;
@@ -9,6 +15,14 @@ export interface StackedModalProps {
9
15
  glass?: boolean;
10
16
  /** Title displayed in the modal navigation bar */
11
17
  navigationTitle?: string | ReactNode;
18
+ /**
19
+ * Drops the header's ✕ so the navigation bar is a breadcrumb only. For panels
20
+ * whose CONTENT owns the dismiss (a sticky action bar with its own close):
21
+ * two ✕ a few pixels apart are the same action twice with nothing to tell
22
+ * them apart. Never hides the BACK arrow of a stacked panel — that one is a
23
+ * different action, and it is the only way out of depth ≥2.
24
+ */
25
+ hideClose?: boolean;
12
26
  /** Modal content */
13
27
  children?: ReactNode;
14
28
  /** Actions to display in the modal header (desktop) or footer (mobile) */
@@ -26,7 +40,7 @@ export interface StackedModalProps {
26
40
  /** Make modal full screen */
27
41
  fullScreen?: boolean;
28
42
  /** Maximum width of the modal */
29
- maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
43
+ maxWidth?: PanelMaxWidth;
30
44
  /** Disable the backdrop click behavior */
31
45
  disableBackdrop?: boolean;
32
46
  /** Disable focus trap functionality */
@@ -49,7 +63,7 @@ export interface ModalInfo {
49
63
  /** Z-index value for stacking */
50
64
  zIndex: number;
51
65
  /** Role in the modal stack */
52
- role: 'primary' | 'secondary' | 'background';
66
+ role: ModalPanelRole;
53
67
  }
54
68
 
55
69
  export interface ModalStackContextValue {
@@ -66,5 +80,5 @@ export interface ModalStackContextValue {
66
80
  /** Check if a modal is in the stack */
67
81
  isModalInStack: (modalId: string) => boolean;
68
82
  /** Get the role of a modal in the stack */
69
- getModalRole: (modalId: string) => 'primary' | 'secondary' | 'background' | null;
83
+ getModalRole: (modalId: string) => ModalPanelRole | null;
70
84
  }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * StackedModal on iOS: the slide-over panel is `position: fixed` and full-height,
3
+ * so it must be sized against the DYNAMIC viewport. With `100vh` the panel runs
4
+ * underneath the mobile browser toolbar, which leaves the tail of the scroll area
5
+ * — the "Criar produto" submit row — permanently out of reach: overscrolling to
6
+ * it rubber-bands and springs straight back.
7
+ */
8
+ import { cleanup, render, screen } from "@testing-library/react";
9
+ import { afterEach, describe, expect, it, vi } from "vitest";
10
+
11
+ import { StackedModal, StackedModalProvider } from "../index";
12
+
13
+ /**
14
+ * Answers `max-width` media queries with `true` so `useMediaQuery` reports a
15
+ * phone — jsdom otherwise matches nothing and the modal always renders in its
16
+ * desktop shape (actions in the header, no bottom action bar).
17
+ */
18
+ function stubPhoneViewport(): void {
19
+ vi.stubGlobal(
20
+ "matchMedia",
21
+ (query: string): MediaQueryList =>
22
+ ({
23
+ matches: query.includes("max-width"),
24
+ media: query,
25
+ onchange: null,
26
+ addListener: () => undefined,
27
+ removeListener: () => undefined,
28
+ addEventListener: () => undefined,
29
+ removeEventListener: () => undefined,
30
+ dispatchEvent: () => false,
31
+ }) as MediaQueryList,
32
+ );
33
+ }
34
+
35
+ /** All CSS emotion has injected into the document, as one searchable string. */
36
+ function injectedCss(): string {
37
+ return Array.from(document.querySelectorAll("style"))
38
+ .map((tag) => {
39
+ // Emotion writes rules through the CSSOM in "speedy" mode, leaving the
40
+ // tag's text empty — read both shapes so this holds either way.
41
+ const fromRules = Array.from(tag.sheet?.cssRules ?? [])
42
+ .map((rule) => rule.cssText)
43
+ .join("");
44
+ return `${tag.textContent ?? ""}${fromRules}`;
45
+ })
46
+ .join("");
47
+ }
48
+
49
+ function renderModal(actions?: React.ReactNode): void {
50
+ render(
51
+ <StackedModalProvider>
52
+ <StackedModal
53
+ open
54
+ onClose={() => undefined}
55
+ navigationTitle="Novo produto"
56
+ modalId="viewport-spec"
57
+ dataTestId="viewport-modal"
58
+ actions={actions}
59
+ >
60
+ <div>corpo</div>
61
+ </StackedModal>
62
+ </StackedModalProvider>,
63
+ );
64
+ }
65
+
66
+ afterEach(() => {
67
+ cleanup();
68
+ vi.unstubAllGlobals();
69
+ });
70
+
71
+ describe("StackedModal viewport sizing", () => {
72
+ it("pins the panel to the dynamic viewport instead of a bare 100vh", async () => {
73
+ renderModal();
74
+ await screen.findByTestId("viewport-modal-header");
75
+
76
+ const css = injectedCss();
77
+ expect(css).toContain("100dvh");
78
+ // The dvh sizing must be guarded so browsers without dynamic viewport units
79
+ // still get the 100vh fallback rather than no height at all.
80
+ expect(css).toContain("@supports (height: 100dvh)");
81
+ expect(css).toContain("100vh");
82
+ });
83
+
84
+ it("lets the scroll area own its overscroll so it can't rubber-band the panel", async () => {
85
+ renderModal();
86
+ const content = await screen.findByTestId("viewport-modal-content");
87
+
88
+ expect(injectedCss()).toContain("overscroll-behavior:contain");
89
+ expect(content).toBeInTheDocument();
90
+ });
91
+
92
+ it("clears the home indicator under the mobile action bar", async () => {
93
+ stubPhoneViewport();
94
+ renderModal(<button type="button">Criar</button>);
95
+
96
+ // On a phone the actions move out of the header into a pinned bottom bar…
97
+ const footer = await screen.findByTestId("viewport-modal-footer");
98
+ expect(footer).toHaveTextContent("Criar");
99
+ // …which pads past its own 16px by the home-indicator inset.
100
+ expect(injectedCss()).toContain(
101
+ "max(16px, calc(16px + env(safe-area-inset-bottom)))",
102
+ );
103
+ });
104
+ });
@@ -0,0 +1,119 @@
1
+ import type { FC, ReactNode } from 'react';
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
3
+ import type { ModalInfo, ModalPanelRole, ModalStackContextValue } from './StackedModal.types';
4
+
5
+ /** MUI Dialog default z-index; each panel in the stack sits 10 above the last. */
6
+ const BASE_Z_INDEX = 1300;
7
+ const Z_INDEX_STEP = 10;
8
+
9
+ const noop = (): void => {
10
+ /** do nothing */
11
+ };
12
+
13
+ const ModalStackContext = createContext<ModalStackContextValue>({
14
+ stack: [],
15
+ pushModal: noop,
16
+ popModal: noop,
17
+ clearStack: noop,
18
+ currentDepth: 0,
19
+ isModalInStack: () => false,
20
+ getModalRole: () => null,
21
+ });
22
+
23
+ export const useModalStack = (): ModalStackContextValue => {
24
+ const context = useContext(ModalStackContext);
25
+ if (!context) {
26
+ throw new Error('useModalStack must be used within a ModalStackProvider');
27
+ }
28
+ return context;
29
+ };
30
+
31
+ /** Top of the stack drives the view; the one behind it recedes; the rest are hidden. */
32
+ const roleAt = (index: number, depth: number): ModalPanelRole => {
33
+ if (index === depth - 1) return 'primary';
34
+ if (index === depth - 2) return 'secondary';
35
+ return 'background';
36
+ };
37
+
38
+ /** Re-derive every role from stack position — the one place roles are assigned. */
39
+ const withRoles = (stack: ModalInfo[]): ModalInfo[] =>
40
+ stack.map((modal, index) => ({ ...modal, role: roleAt(index, stack.length) }));
41
+
42
+ /**
43
+ * Locks body scroll while any modal is open, restoring whatever the page had
44
+ * before. Owned by the provider rather than each modal, so nested panels can't
45
+ * fight over the same style.
46
+ */
47
+ const useBodyScrollLock = (locked: boolean): void => {
48
+ const originalOverflow = useRef<string>('');
49
+
50
+ useEffect(() => {
51
+ const restore = (): void => {
52
+ if (!originalOverflow.current) return;
53
+ document.body.style.overflow = originalOverflow.current;
54
+ originalOverflow.current = '';
55
+ };
56
+
57
+ if (locked) {
58
+ if (!originalOverflow.current) {
59
+ originalOverflow.current = document.body.style.overflow || '';
60
+ }
61
+ document.body.style.overflow = 'hidden';
62
+ } else {
63
+ restore();
64
+ }
65
+
66
+ return restore;
67
+ }, [locked]);
68
+ };
69
+
70
+ export const ModalStackProvider: FC<{ children: ReactNode }> = ({ children }) => {
71
+ const [stack, setStack] = useState<ModalInfo[]>([]);
72
+
73
+ useBodyScrollLock(stack.length > 0);
74
+
75
+ const pushModal = useCallback((modalId: string) => {
76
+ setStack((prev) => {
77
+ if (prev.some((m) => m.id === modalId)) return prev;
78
+ const entry: ModalInfo = {
79
+ id: modalId,
80
+ zIndex: BASE_Z_INDEX + prev.length * Z_INDEX_STEP,
81
+ role: 'primary',
82
+ };
83
+ return withRoles([...prev, entry]);
84
+ });
85
+ }, []);
86
+
87
+ const popModal = useCallback((modalId?: string) => {
88
+ setStack((prev) =>
89
+ withRoles(modalId ? prev.filter((m) => m.id !== modalId) : prev.slice(0, -1)),
90
+ );
91
+ }, []);
92
+
93
+ const clearStack = useCallback(() => setStack([]), []);
94
+
95
+ const isModalInStack = useCallback(
96
+ (modalId: string) => stack.some((m) => m.id === modalId),
97
+ [stack],
98
+ );
99
+
100
+ const getModalRole = useCallback(
101
+ (modalId: string) => stack.find((m) => m.id === modalId)?.role ?? null,
102
+ [stack],
103
+ );
104
+
105
+ const value = useMemo(
106
+ () => ({
107
+ stack,
108
+ pushModal,
109
+ popModal,
110
+ clearStack,
111
+ currentDepth: stack.length,
112
+ isModalInStack,
113
+ getModalRole,
114
+ }),
115
+ [stack, pushModal, popModal, clearStack, isModalInStack, getModalRole],
116
+ );
117
+
118
+ return <ModalStackContext.Provider value={value}>{children}</ModalStackContext.Provider>;
119
+ };
@@ -1,10 +1,9 @@
1
- import {
2
- CheckCircle as SuccessIcon,
3
- Close as CloseIcon,
4
- Error as ErrorIcon,
5
- Info as InfoIcon,
6
- Warning as WarningIcon,
7
- } from '@mui/icons-material';
1
+ import SuccessIcon from '@mui/icons-material/CheckCircle';
2
+ import CloseIcon from '@mui/icons-material/Close';
3
+ import ErrorIcon from '@mui/icons-material/Error';
4
+ import InfoIcon from '@mui/icons-material/Info';
5
+ import WarningIcon from '@mui/icons-material/Warning';
6
+ import type { Theme } from '@mui/material';
8
7
  import {
9
8
  Alert,
10
9
  alpha,
@@ -23,6 +22,46 @@ import type { ToastContainerProps, ToastContextType, ToastItem,ToastProps } from
23
22
 
24
23
  const ToastContext = createContext<ToastContextType | null>(null);
25
24
 
25
+ type PromiseToastOptions<T> = {
26
+ loading: string;
27
+ success: string | ((data: T) => string);
28
+ error: string | ((error: unknown) => string);
29
+ };
30
+
31
+ // success/error may be a literal or a formatter over the settled value.
32
+ const resolveToastMessage = <T,>(message: string | ((value: T) => string), value: T): string =>
33
+ typeof message === 'function' ? message(value) : message;
34
+
35
+ // Shows a persistent "loading" toast for the life of a promise, then swaps it
36
+ // for a success or error one. Lives outside the provider so the provider body
37
+ // stays readable; it takes add/remove rather than reaching for context.
38
+ const trackPromiseWithToasts = async <T,>(
39
+ promiseToResolve: Promise<T>,
40
+ options: PromiseToastOptions<T>,
41
+ addToast: (toast: Omit<ToastProps, 'id'>) => string,
42
+ removeToast: (id: string) => void,
43
+ ): Promise<T> => {
44
+ const toastId = addToast({
45
+ message: options.loading,
46
+ variant: 'promise',
47
+ persistent: true,
48
+ });
49
+
50
+ try {
51
+ const data = await promiseToResolve;
52
+
53
+ removeToast(toastId);
54
+ addToast({ message: resolveToastMessage(options.success, data), variant: 'success' });
55
+
56
+ return data;
57
+ } catch (error) {
58
+ removeToast(toastId);
59
+ addToast({ message: resolveToastMessage(options.error, error), variant: 'error' });
60
+
61
+ throw error;
62
+ }
63
+ };
64
+
26
65
  export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
27
66
  const [toasts, setToasts] = useState<ToastItem[]>([]);
28
67
 
@@ -58,40 +97,8 @@ export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ childre
58
97
  }, []);
59
98
 
60
99
  const promise = useCallback(
61
- async <T,>(
62
- promiseToResolve: Promise<T>,
63
- options: {
64
- loading: string;
65
- success: string | ((data: T) => string);
66
- error: string | ((error: unknown) => string);
67
- },
68
- ): Promise<T> => {
69
- const toastId = addToast({
70
- message: options.loading,
71
- variant: 'promise',
72
- persistent: true,
73
- });
74
-
75
- try {
76
- const data = await promiseToResolve;
77
-
78
- removeToast(toastId);
79
- addToast({
80
- message: typeof options.success === 'function' ? options.success(data) : options.success,
81
- variant: 'success',
82
- });
83
-
84
- return data;
85
- } catch (error) {
86
- removeToast(toastId);
87
- addToast({
88
- message: typeof options.error === 'function' ? options.error(error) : options.error,
89
- variant: 'error',
90
- });
91
-
92
- throw error;
93
- }
94
- },
100
+ <T,>(promiseToResolve: Promise<T>, options: PromiseToastOptions<T>): Promise<T> =>
101
+ trackPromiseWithToasts(promiseToResolve, options, addToast, removeToast),
95
102
  [addToast, removeToast],
96
103
  );
97
104
 
@@ -114,6 +121,47 @@ export const useToast = (): ToastContextType => {
114
121
  return context;
115
122
  };
116
123
 
124
+ const renderVariantIcon = (variant: ToastProps['variant']) => {
125
+ switch (variant) {
126
+ case 'success':
127
+ return <SuccessIcon />;
128
+ case 'error':
129
+ return <ErrorIcon />;
130
+ case 'warning':
131
+ return <WarningIcon />;
132
+ case 'info':
133
+ return <InfoIcon />;
134
+ case 'promise':
135
+ return <CircularProgress size={20} />;
136
+ default:
137
+ return null;
138
+ }
139
+ };
140
+
141
+ // 'default' and 'promise' have no MUI severity of their own; both read as info.
142
+ const toAlertSeverity = (variant: ToastProps['variant']) =>
143
+ variant === 'default' || variant === 'promise' ? 'info' : variant;
144
+
145
+ const buildToastStyles = (theme: Theme, glass: boolean) => {
146
+ const baseStyles = {
147
+ borderRadius: theme.spacing(1.5),
148
+ transition: theme.transitions.create(['background-color', 'backdrop-filter'], {
149
+ duration: theme.transitions.duration.standard,
150
+ }),
151
+ };
152
+
153
+ if (glass) {
154
+ return {
155
+ ...baseStyles,
156
+ backgroundColor: alpha(theme.palette.background.paper, 0.1),
157
+ backdropFilter: 'blur(20px)',
158
+ border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
159
+ };
160
+ }
161
+
162
+ return baseStyles;
163
+ };
164
+
117
165
  export const Toast: React.FC<ToastProps> = ({
118
166
  id = '',
119
167
  message,
@@ -126,43 +174,6 @@ export const Toast: React.FC<ToastProps> = ({
126
174
  }) => {
127
175
  const theme = useTheme();
128
176
 
129
- const getIcon = () => {
130
- switch (variant) {
131
- case 'success':
132
- return <SuccessIcon />;
133
- case 'error':
134
- return <ErrorIcon />;
135
- case 'warning':
136
- return <WarningIcon />;
137
- case 'info':
138
- return <InfoIcon />;
139
- case 'promise':
140
- return <CircularProgress size={20} />;
141
- default:
142
- return null;
143
- }
144
- };
145
-
146
- const getVariantStyles = () => {
147
- const baseStyles = {
148
- borderRadius: theme.spacing(1.5),
149
- transition: theme.transitions.create(['background-color', 'backdrop-filter'], {
150
- duration: theme.transitions.duration.standard,
151
- }),
152
- };
153
-
154
- if (glass) {
155
- return {
156
- ...baseStyles,
157
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
158
- backdropFilter: 'blur(20px)',
159
- border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
160
- };
161
- }
162
-
163
- return baseStyles;
164
- };
165
-
166
177
  const handleClose = () => {
167
178
  if (onClose && id) {
168
179
  onClose(id);
@@ -172,8 +183,8 @@ export const Toast: React.FC<ToastProps> = ({
172
183
  return (
173
184
  <Alert
174
185
  data-testid={dataTestId}
175
- icon={getIcon()}
176
- severity={variant === 'default' || variant === 'promise' ? 'info' : variant}
186
+ icon={renderVariantIcon(variant)}
187
+ severity={toAlertSeverity(variant)}
177
188
  onClose={closable ? handleClose : undefined}
178
189
  action={
179
190
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -200,7 +211,7 @@ export const Toast: React.FC<ToastProps> = ({
200
211
  )}
201
212
  </Box>
202
213
  }
203
- sx={getVariantStyles()}
214
+ sx={buildToastStyles(theme, glass)}
204
215
  >
205
216
  <Typography data-testid={`${dataTestId}-message`} variant="body2">{message}</Typography>
206
217
  </Alert>
@@ -0,0 +1,65 @@
1
+ const DEFAULT_PADDING = 16;
2
+
3
+ export const getElementBounds = (selector: string): globalThis.DOMRect | null =>
4
+ document.querySelector(selector)?.getBoundingClientRect() ?? null;
5
+
6
+ // With placement 'auto', the side with the most free space around the target
7
+ // wins.
8
+ const pickPlacement = (targetBounds: globalThis.DOMRect): string => {
9
+ const spaces = {
10
+ top: targetBounds.top,
11
+ bottom: window.innerHeight - targetBounds.bottom,
12
+ left: targetBounds.left,
13
+ right: window.innerWidth - targetBounds.right,
14
+ };
15
+
16
+ return Object.entries(spaces).reduce((a, b) => (b[1] > a[1] ? b : a))[0];
17
+ };
18
+
19
+ const offsetFor = (
20
+ placement: string,
21
+ targetBounds: globalThis.DOMRect,
22
+ tooltipBounds: globalThis.DOMRect,
23
+ padding: number,
24
+ ): { top: number; left: number } => {
25
+ const centredLeft = targetBounds.left + targetBounds.width / 2 - tooltipBounds.width / 2;
26
+ const centredTop = targetBounds.top + targetBounds.height / 2 - tooltipBounds.height / 2;
27
+
28
+ switch (placement) {
29
+ case 'top':
30
+ return { top: targetBounds.top - tooltipBounds.height - padding, left: centredLeft };
31
+ case 'bottom':
32
+ return { top: targetBounds.bottom + padding, left: centredLeft };
33
+ case 'left':
34
+ return { top: centredTop, left: targetBounds.left - tooltipBounds.width - padding };
35
+ case 'right':
36
+ return { top: centredTop, left: targetBounds.right + padding };
37
+ default:
38
+ return { top: 0, left: 0 };
39
+ }
40
+ };
41
+
42
+ // Keeps the tooltip fully on screen, whatever the chosen side works out to.
43
+ const clampToViewport = (
44
+ position: { top: number; left: number },
45
+ tooltipBounds: globalThis.DOMRect,
46
+ padding: number,
47
+ ) => ({
48
+ top: Math.max(padding, Math.min(position.top, window.innerHeight - tooltipBounds.height - padding)),
49
+ left: Math.max(padding, Math.min(position.left, window.innerWidth - tooltipBounds.width - padding)),
50
+ });
51
+
52
+ export const calculateTooltipPosition = (
53
+ targetBounds: globalThis.DOMRect,
54
+ tooltipBounds: globalThis.DOMRect,
55
+ placement: string,
56
+ padding = DEFAULT_PADDING,
57
+ ) => {
58
+ const finalPlacement = placement === 'auto' ? pickPlacement(targetBounds) : placement;
59
+ const offset = offsetFor(finalPlacement, targetBounds, tooltipBounds, padding);
60
+
61
+ return {
62
+ position: clampToViewport(offset, tooltipBounds, padding),
63
+ placement: finalPlacement,
64
+ };
65
+ };