@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,182 @@
1
+ import RecentIcon from '@mui/icons-material/History';
2
+ import { Box, Divider, List, ListItem, ListItemIcon, ListItemText, Typography } from '@mui/material';
3
+ import { alpha, styled } from '@mui/material/styles';
4
+ import React from 'react';
5
+
6
+ import { ShortcutChip } from './ShortcutChip';
7
+ import type { PaletteCommand } from './CommandPalette.types';
8
+
9
+ const ResultsList = styled(List)(({ theme }) => ({
10
+ maxHeight: 400,
11
+ overflowY: 'auto',
12
+ padding: theme.spacing(1),
13
+ '&::-webkit-scrollbar': {
14
+ width: 8,
15
+ },
16
+ '&::-webkit-scrollbar-track': {
17
+ background: alpha(theme.palette.action.disabled, 0.1),
18
+ },
19
+ '&::-webkit-scrollbar-thumb': {
20
+ background: alpha(theme.palette.primary.main, 0.3),
21
+ borderRadius: 4,
22
+ '&:hover': {
23
+ background: alpha(theme.palette.primary.main, 0.5),
24
+ },
25
+ },
26
+ }));
27
+
28
+ const CommandItem = styled(ListItem)<{ selected?: boolean }>(({ theme, selected }) => ({
29
+ borderRadius: theme.shape.borderRadius,
30
+ marginBottom: theme.spacing(0.5),
31
+ transition: theme.transitions.create(['background-color', 'transform']),
32
+ cursor: 'pointer',
33
+ ...(selected && {
34
+ backgroundColor: alpha(theme.palette.primary.main, 0.12),
35
+ transform: 'translateX(4px)',
36
+ '& .MuiListItemIcon-root': {
37
+ color: theme.palette.primary.main,
38
+ },
39
+ }),
40
+ '&:hover': {
41
+ backgroundColor: alpha(theme.palette.primary.main, 0.08),
42
+ },
43
+ }));
44
+
45
+ const CategoryLabel = styled(Typography)(({ theme }) => ({
46
+ fontSize: '0.75rem',
47
+ fontWeight: 600,
48
+ color: theme.palette.text.secondary,
49
+ textTransform: 'uppercase',
50
+ letterSpacing: '0.1em',
51
+ padding: theme.spacing(1, 2, 0.5, 2),
52
+ marginTop: theme.spacing(1),
53
+ }));
54
+
55
+ const NoResults = styled(Box)(({ theme }) => ({
56
+ padding: theme.spacing(4, 2),
57
+ textAlign: 'center',
58
+ color: theme.palette.text.secondary,
59
+ }));
60
+
61
+ const CommandRow: React.FC<{
62
+ command: PaletteCommand;
63
+ index: number;
64
+ selected: boolean;
65
+ dataTestId?: string;
66
+ onSelect: (index: number) => void;
67
+ onExecute: (command: PaletteCommand) => void;
68
+ }> = ({ command, index, selected, dataTestId, onSelect, onExecute }) => (
69
+ <CommandItem
70
+ data-index={index}
71
+ data-testid={`${dataTestId}-item-${command.id}`}
72
+ selected={selected}
73
+ onClick={() => onExecute(command)}
74
+ onMouseEnter={() => onSelect(index)}
75
+ >
76
+ {command.icon && <ListItemIcon sx={{ minWidth: 40 }}>{command.icon}</ListItemIcon>}
77
+ <ListItemText
78
+ primary={command.label}
79
+ secondary={command.description}
80
+ primaryTypographyProps={{ fontSize: '0.9rem', fontWeight: selected ? 500 : 400 }}
81
+ secondaryTypographyProps={{ fontSize: '0.75rem' }}
82
+ />
83
+ {command.shortcut && <ShortcutChip label={command.shortcut} size="small" />}
84
+ </CommandItem>
85
+ );
86
+
87
+ const slug = (value: string) => value.toLowerCase().replace(/\s+/g, '-');
88
+
89
+ // The list, its recent section and the empty state. The running commandIndex
90
+ // has to stay consistent across both sections, so it is threaded through here
91
+ // rather than mutated from inside the render as it was before.
92
+ export const PaletteResults: React.FC<{
93
+ listRef: React.Ref<globalThis.HTMLUListElement>;
94
+ dataTestId?: string;
95
+ searchQuery: string;
96
+ showRecent?: boolean;
97
+ commands: PaletteCommand[];
98
+ recentCommandIds: string[];
99
+ filteredCommands: PaletteCommand[];
100
+ groupedCommands: Record<string, PaletteCommand[]>;
101
+ selectedIndex: number;
102
+ onSelect: (index: number) => void;
103
+ onExecute: (command: PaletteCommand) => void;
104
+ }> = ({
105
+ listRef,
106
+ dataTestId,
107
+ searchQuery,
108
+ showRecent,
109
+ commands,
110
+ recentCommandIds,
111
+ filteredCommands,
112
+ groupedCommands,
113
+ selectedIndex,
114
+ onSelect,
115
+ onExecute,
116
+ }) => {
117
+ if (filteredCommands.length === 0) {
118
+ return (
119
+ <NoResults data-testid={`${dataTestId}-no-results`}>
120
+ <Typography variant="body2" color="text.secondary">
121
+ No commands found for "{searchQuery}"
122
+ </Typography>
123
+ <Typography variant="caption" color="text.disabled" sx={{ mt: 1 }}>
124
+ Try a different search term
125
+ </Typography>
126
+ </NoResults>
127
+ );
128
+ }
129
+
130
+ const showRecentSection = !searchQuery && showRecent && recentCommandIds.length > 0;
131
+ const recent = showRecentSection
132
+ ? (recentCommandIds
133
+ .map((id) => commands.find((command) => command.id === id))
134
+ .filter(Boolean) as PaletteCommand[])
135
+ : [];
136
+
137
+ // One running index across both sections, so keyboard selection lines up with
138
+ // what is on screen.
139
+ let commandIndex = 0;
140
+ const row = (command: PaletteCommand) => {
141
+ const index = commandIndex++;
142
+ return (
143
+ <CommandRow
144
+ key={command.id}
145
+ command={command}
146
+ index={index}
147
+ selected={index === selectedIndex}
148
+ dataTestId={dataTestId}
149
+ onSelect={onSelect}
150
+ onExecute={onExecute}
151
+ />
152
+ );
153
+ };
154
+
155
+ const showCategoryLabels = Object.keys(groupedCommands).length > 1;
156
+
157
+ return (
158
+ <ResultsList ref={listRef} data-testid={`${dataTestId}-list`}>
159
+ {showRecentSection && (
160
+ <>
161
+ <CategoryLabel data-testid={`${dataTestId}-group-recent`}>
162
+ <RecentIcon sx={{ fontSize: 14, mr: 0.5, verticalAlign: 'middle' }} />
163
+ Recent
164
+ </CategoryLabel>
165
+ {recent.map(row)}
166
+ {filteredCommands.length > recentCommandIds.length && <Divider sx={{ my: 1 }} />}
167
+ </>
168
+ )}
169
+
170
+ {Object.entries(groupedCommands).map(([category, categoryCommands]) => (
171
+ <Box key={category} data-testid={`${dataTestId}-group-${slug(category)}`}>
172
+ {showCategoryLabels && (
173
+ <CategoryLabel data-testid={`${dataTestId}-group-label-${slug(category)}`}>
174
+ {category}
175
+ </CategoryLabel>
176
+ )}
177
+ {categoryCommands.map(row)}
178
+ </Box>
179
+ ))}
180
+ </ResultsList>
181
+ );
182
+ };
@@ -0,0 +1,18 @@
1
+ import type { ChipProps } from '@mui/material';
2
+ import { Chip } from '@mui/material';
3
+ import React from 'react';
4
+
5
+ // A plain component rather than styled(Chip): a styled component's inferred type
6
+ // cannot be named across a module boundary in this package (TS2742), and both
7
+ // the results list and the palette footer need this one.
8
+ export const ShortcutChip: React.FC<ChipProps> = (props) => (
9
+ <Chip
10
+ {...props}
11
+ sx={{
12
+ height: 20,
13
+ fontSize: '0.7rem',
14
+ fontFamily: 'monospace',
15
+ ...props.sx,
16
+ }}
17
+ />
18
+ );
@@ -18,7 +18,9 @@ A right-click context menu component with multiple visual variants and customiza
18
18
 
19
19
  ```tsx
20
20
  import { ContextMenu } from '@app-services-monitoring/ui';
21
- import { ContentCopy, Edit, Delete } from '@mui/icons-material';
21
+ import ContentCopy from '@mui/icons-material/ContentCopy';
22
+ import Edit from '@mui/icons-material/Edit';
23
+ import Delete from '@mui/icons-material/Delete';
22
24
 
23
25
  function MyComponent() {
24
26
  const menuItems = [
@@ -1,4 +1,7 @@
1
- import { ContentCopy, Edit, Folder, Star } from '@mui/icons-material';
1
+ import ContentCopy from '@mui/icons-material/ContentCopy';
2
+ import Edit from '@mui/icons-material/Edit';
3
+ import Folder from '@mui/icons-material/Folder';
4
+ import Star from '@mui/icons-material/Star';
2
5
  import { Box, Card, CardContent,Paper, Typography } from '@mui/material';
3
6
  import type { Meta, StoryObj } from '@storybook/react-vite';
4
7
 
@@ -1,14 +1,12 @@
1
- import {
2
- Archive,
3
- ContentCopy,
4
- ContentCut,
5
- ContentPaste,
6
- Delete,
7
- Edit,
8
- Settings,
9
- Share,
10
- Star,
11
- } from '@mui/icons-material';
1
+ import Archive from '@mui/icons-material/Archive';
2
+ import ContentCopy from '@mui/icons-material/ContentCopy';
3
+ import ContentCut from '@mui/icons-material/ContentCut';
4
+ import ContentPaste from '@mui/icons-material/ContentPaste';
5
+ import Delete from '@mui/icons-material/Delete';
6
+ import Edit from '@mui/icons-material/Edit';
7
+ import Settings from '@mui/icons-material/Settings';
8
+ import Share from '@mui/icons-material/Share';
9
+ import Star from '@mui/icons-material/Star';
12
10
  import { Box, Paper, Typography } from '@mui/material';
13
11
  import type { Meta, StoryObj } from '@storybook/react-vite';
14
12
  import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
@@ -333,7 +331,7 @@ export const FocusManagement: Story = {
333
331
 
334
332
  // Test tab navigation skips disabled items
335
333
  await userEvent.tab();
336
- expect(document.activeElement).not.toBe(disabledItem);
334
+ await waitFor(() => expect(document.activeElement).not.toBe(disabledItem));
337
335
 
338
336
  // Close menu
339
337
  await userEvent.keyboard('{Escape}');
@@ -400,16 +398,12 @@ export const ResponsiveDesign: Story = {
400
398
  expect(body.getByText('Copy')).toBeInTheDocument();
401
399
  });
402
400
 
403
- // Verify menu is positioned correctly (doesn't overflow viewport)
401
+ // The menu opens and is visible. Overflow-against-the-viewport is deliberately
402
+ // NOT asserted here: it reads window/document dimensions, so the result depends
403
+ // on the runner's screen size rather than on the component. That belongs in a
404
+ // visual or e2e check pinned to a fixed viewport.
404
405
  const menu = body.getByRole('menu');
405
- const menuRect = menu.getBoundingClientRect();
406
- const viewportWidth = window.innerWidth;
407
- const viewportHeight = window.innerHeight;
408
-
409
- expect(menuRect.right).toBeLessThanOrEqual(viewportWidth);
410
- expect(menuRect.bottom).toBeLessThanOrEqual(viewportHeight);
411
- expect(menuRect.left).toBeGreaterThanOrEqual(0);
412
- expect(menuRect.top).toBeGreaterThanOrEqual(0);
406
+ expect(menu).toBeVisible();
413
407
 
414
408
  // Close menu
415
409
  await userEvent.keyboard('{Escape}');
@@ -567,7 +561,7 @@ export const VisualStates: Story = {
567
561
  // Menu should not open when disabled
568
562
  await new Promise((resolve) => window.setTimeout(resolve, 100));
569
563
  const bodyForDisabled = within(document.body);
570
- expect(bodyForDisabled.queryByText('Copy')).not.toBeInTheDocument();
564
+ await waitFor(() => expect(bodyForDisabled.queryByText('Copy')).not.toBeInTheDocument());
571
565
  },
572
566
  };
573
567
 
@@ -841,9 +835,10 @@ export const Integration: Story = {
841
835
  expect(body.getByText('Edit')).toBeInTheDocument();
842
836
  });
843
837
 
844
- // Verify second menu is open and first is closed
838
+ // Verify second menu is open and first is closed. The 'Copy' item was present
839
+ // before this interaction, so its removal is awaited rather than asserted flat.
845
840
  expect(body.getByText('Edit')).toBeInTheDocument();
846
- expect(body.queryByText('Copy')).not.toBeInTheDocument();
841
+ await waitFor(() => expect(body.queryByText('Copy')).not.toBeInTheDocument());
847
842
 
848
843
  await userEvent.keyboard('{Escape}');
849
844
  },
@@ -7,6 +7,7 @@ import {
7
7
  MenuItem,
8
8
  Typography,
9
9
  } from '@mui/material';
10
+ import type { CSSObject, Theme } from '@mui/material';
10
11
  import { styled } from '@mui/material/styles';
11
12
  import React, { cloneElement, isValidElement,useState } from 'react';
12
13
 
@@ -58,6 +59,22 @@ const StyledMenu = styled(Menu, {
58
59
  },
59
60
  }));
60
61
 
62
+ // `color` names a palette key (`success`, `warning`, …) that MUI's Theme type does
63
+ // not index, so it is read structurally and falls back to primary.
64
+ const paletteColor = (theme: Theme, color: string): string =>
65
+ (theme.palette as unknown as Record<string, { main?: string }>)[color]?.main ||
66
+ theme.palette.primary.main;
67
+
68
+ const accentStyles = (theme: Theme, color: string): CSSObject => {
69
+ const main = paletteColor(theme, color);
70
+
71
+ return {
72
+ color: main,
73
+ '& .MuiListItemIcon-root': { color: main },
74
+ '&:hover': { backgroundColor: alpha(main, 0.08) },
75
+ };
76
+ };
77
+
61
78
  const StyledMenuItem = styled(MenuItem, {
62
79
  shouldForwardProp: (prop) => !['color', 'dangerous'].includes(prop as string),
63
80
  })<{ color?: string; dangerous?: boolean }>(({ theme, color, dangerous }) => ({
@@ -76,25 +93,7 @@ const StyledMenuItem = styled(MenuItem, {
76
93
  },
77
94
  }),
78
95
 
79
- ...(color &&
80
- color !== 'default' &&
81
- !dangerous && {
82
- color:
83
- (theme.palette as unknown as Record<string, { main?: string }>)[color]?.main ||
84
- theme.palette.primary.main,
85
- '& .MuiListItemIcon-root': {
86
- color:
87
- (theme.palette as unknown as Record<string, { main?: string }>)[color]?.main ||
88
- theme.palette.primary.main,
89
- },
90
- '&:hover': {
91
- backgroundColor: alpha(
92
- (theme.palette as unknown as Record<string, { main?: string }>)[color]?.main ||
93
- theme.palette.primary.main,
94
- 0.08,
95
- ),
96
- },
97
- }),
96
+ ...(color && color !== 'default' && !dangerous ? accentStyles(theme, color) : {}),
98
97
  }));
99
98
 
100
99
  const MenuHeader = styled(Typography)(({ theme }) => ({
@@ -145,6 +144,53 @@ const renderMenuItem = (
145
144
  );
146
145
  };
147
146
 
147
+ interface TriggerOptions {
148
+ disabled: boolean;
149
+ triggerClassName?: string;
150
+ triggerStyle?: React.CSSProperties;
151
+ }
152
+
153
+ // The trigger is whatever the caller passed as children. A single element is
154
+ // cloned, so the handler and cursor land on the caller's own node; anything else
155
+ // gets wrapped in a div that can carry them.
156
+ const buildTrigger = (
157
+ children: React.ReactNode,
158
+ onContextMenu: (event: React.MouseEvent) => void,
159
+ { disabled, triggerClassName, triggerStyle }: TriggerOptions,
160
+ ) => {
161
+ const cursor = disabled ? 'default' : 'context-menu';
162
+
163
+ if (!isValidElement(children)) {
164
+ return (
165
+ <div
166
+ onContextMenu={onContextMenu}
167
+ className={triggerClassName}
168
+ style={{ ...triggerStyle, cursor }}
169
+ >
170
+ {children}
171
+ </div>
172
+ );
173
+ }
174
+
175
+ const element = children as React.ReactElement<{
176
+ onContextMenu?: (event: React.MouseEvent) => void;
177
+ className?: string;
178
+ style?: React.CSSProperties;
179
+ }>;
180
+
181
+ return cloneElement(element, {
182
+ onContextMenu,
183
+ className: [
184
+ element.props.className,
185
+ triggerClassName,
186
+ !disabled ? 'context-menu-trigger' : '',
187
+ ]
188
+ .filter(Boolean)
189
+ .join(' '),
190
+ style: { ...element.props.style, ...triggerStyle, cursor },
191
+ });
192
+ };
193
+
148
194
  export const ContextMenu = React.forwardRef<HTMLDivElement, ContextMenuProps>(
149
195
  (
150
196
  {
@@ -193,41 +239,11 @@ export const ContextMenu = React.forwardRef<HTMLDivElement, ContextMenuProps>(
193
239
  handleClose();
194
240
  };
195
241
 
196
- const triggerElement = isValidElement(children) ? (
197
- cloneElement(
198
- children as React.ReactElement<{
199
- onContextMenu?: (event: React.MouseEvent) => void;
200
- className?: string;
201
- style?: React.CSSProperties;
202
- }>,
203
- {
204
- onContextMenu: handleContextMenu,
205
- className: [
206
- (children as React.ReactElement<{ className?: string }>).props.className,
207
- triggerClassName,
208
- !disabled ? 'context-menu-trigger' : '',
209
- ]
210
- .filter(Boolean)
211
- .join(' '),
212
- style: {
213
- ...(children as React.ReactElement<{ style?: React.CSSProperties }>).props.style,
214
- ...triggerStyle,
215
- cursor: disabled ? 'default' : 'context-menu',
216
- },
217
- }
218
- )
219
- ) : (
220
- <div
221
- onContextMenu={handleContextMenu}
222
- className={triggerClassName}
223
- style={{
224
- ...triggerStyle,
225
- cursor: disabled ? 'default' : 'context-menu',
226
- }}
227
- >
228
- {children}
229
- </div>
230
- );
242
+ const triggerElement = buildTrigger(children, handleContextMenu, {
243
+ disabled,
244
+ triggerClassName,
245
+ triggerStyle,
246
+ });
231
247
 
232
248
  return (
233
249
  <>
@@ -19,7 +19,9 @@ A click-triggered dropdown menu component with multiple variants, nested items s
19
19
 
20
20
  ```tsx
21
21
  import { DropdownMenu } from '@app-services-monitoring/ui';
22
- import { Edit, Delete, Share } from '@mui/icons-material';
22
+ import Edit from '@mui/icons-material/Edit';
23
+ import Delete from '@mui/icons-material/Delete';
24
+ import Share from '@mui/icons-material/Share';
23
25
  import { Button } from '@mui/material';
24
26
 
25
27
  function MyComponent() {
@@ -1,22 +1,20 @@
1
- import {
2
- Add,
3
- Check,
4
- Close,
5
- Delete,
6
- Download,
7
- Edit,
8
- FileCopy,
9
- Logout,
10
- MoreVert,
11
- Person,
12
- Print,
13
- Refresh,
14
- Remove,
15
- Save,
16
- Settings,
17
- Share,
18
- Upload,
19
- } from '@mui/icons-material';
1
+ import Add from '@mui/icons-material/Add';
2
+ import Check from '@mui/icons-material/Check';
3
+ import Close from '@mui/icons-material/Close';
4
+ import Delete from '@mui/icons-material/Delete';
5
+ import Download from '@mui/icons-material/Download';
6
+ import Edit from '@mui/icons-material/Edit';
7
+ import FileCopy from '@mui/icons-material/FileCopy';
8
+ import Logout from '@mui/icons-material/Logout';
9
+ import MoreVert from '@mui/icons-material/MoreVert';
10
+ import Person from '@mui/icons-material/Person';
11
+ import Print from '@mui/icons-material/Print';
12
+ import Refresh from '@mui/icons-material/Refresh';
13
+ import Remove from '@mui/icons-material/Remove';
14
+ import Save from '@mui/icons-material/Save';
15
+ import Settings from '@mui/icons-material/Settings';
16
+ import Share from '@mui/icons-material/Share';
17
+ import Upload from '@mui/icons-material/Upload';
20
18
  import { Box, IconButton } from '@mui/material';
21
19
  import type { Meta, StoryObj } from '@storybook/react-vite';
22
20
 
@@ -1,14 +1,12 @@
1
- import {
2
- Add,
3
- Check,
4
- Close,
5
- Delete,
6
- Edit,
7
- FileCopy,
8
- Save,
9
- Settings,
10
- Share,
11
- } from '@mui/icons-material';
1
+ import Add from '@mui/icons-material/Add';
2
+ import Check from '@mui/icons-material/Check';
3
+ import Close from '@mui/icons-material/Close';
4
+ import Delete from '@mui/icons-material/Delete';
5
+ import Edit from '@mui/icons-material/Edit';
6
+ import FileCopy from '@mui/icons-material/FileCopy';
7
+ import Save from '@mui/icons-material/Save';
8
+ import Settings from '@mui/icons-material/Settings';
9
+ import Share from '@mui/icons-material/Share';
12
10
  import { Box } from '@mui/material';
13
11
  import type { Meta, StoryObj } from '@storybook/react-vite';
14
12
  import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
@@ -126,7 +124,10 @@ export const KeyboardNavigation: Story = {
126
124
 
127
125
  // Focus and open menu with keyboard
128
126
  const trigger = await canvas.findByRole('button', { name: /keyboard menu/i });
129
- trigger.focus();
127
+ // Tab to the trigger — clicking would open the menu, which is what the Enter
128
+ // key below is meant to prove.
129
+ await userEvent.tab();
130
+ await waitFor(() => expect(trigger).toHaveFocus());
130
131
 
131
132
  // Open with Enter key
132
133
  await userEvent.keyboard('{Enter}');
@@ -218,9 +219,10 @@ export const FocusManagement: Story = {
218
219
  // Get trigger button
219
220
  const trigger = await canvas.findByRole('button', { name: /focus test/i });
220
221
 
221
- // Store initial focus
222
- trigger.focus();
223
- expect(document.activeElement).toBe(trigger);
222
+ // Store initial focus — reached by keyboard so the "focus returns to the
223
+ // trigger on close" check below starts from a real user position.
224
+ await userEvent.tab();
225
+ await waitFor(() => expect(document.activeElement).toBe(trigger));
224
226
 
225
227
  // Open menu
226
228
  await userEvent.click(trigger);
@@ -384,10 +386,8 @@ export const VisualStates: Story = {
384
386
  name: (n) => n.trim() === 'Enabled', // disambiguates from "Also Enabled"
385
387
  });
386
388
 
387
- // Focus instead of hover (hover CSS isn’t reliable in JSDOM)
388
- enabledLi.focus();
389
- await expect(enabledLi).toHaveFocus();
390
-
389
+ // The click below is what this story is about; it does not need the item
390
+ // pre-focused, and hover CSS isn’t reliable in JSDOM either way.
391
391
  // Click should invoke the spy
392
392
  await userEvent.click(enabledLi);
393
393
  await waitFor(() => expect(itemsWithDisabled[0].onClick).toHaveBeenCalled());
@@ -414,17 +414,16 @@ export const Performance: Story = {
414
414
  const canvas = within(canvasElement);
415
415
  const trigger = await canvas.findByRole('button', { name: /large menu/i });
416
416
 
417
- const t0 = Date.now();
418
417
  await userEvent.click(trigger);
419
418
 
419
+ // waitFor already bounds how long the menu may take to appear. Timing the
420
+ // open with a wall-clock budget on top of that measures the runner's load,
421
+ // not the component — the "allow headless/VM jitter" note it carried was the
422
+ // tell — so the budget assertion is gone and the open itself is the check.
420
423
  await waitFor(() => {
421
424
  expect(document.querySelector('[role="menu"]')).toBeInTheDocument();
422
425
  });
423
426
 
424
- const openTime = Date.now() - t0;
425
- // Allow headless/VM jitter
426
- expect(openTime).toBeLessThan(2500);
427
-
428
427
  // Grab the UL (role="menu") and its Paper container (actual scroll container)
429
428
  const menu = document.querySelector('[role="menu"]') as HTMLElement;
430
429
  const paper =
@@ -518,10 +517,11 @@ export const EdgeCases: Story = {
518
517
  const menu = document.querySelector('[role="menu"]');
519
518
  expect(menu).toBeInTheDocument();
520
519
 
521
- // Check that long text doesn't break layout
520
+ // Check that long text doesn't break layout. Width is only asserted to be
521
+ // non-zero: comparing it against the window width makes the result depend
522
+ // on the runner's screen rather than on the menu.
522
523
  const menuRect = menu?.getBoundingClientRect();
523
524
  expect(menuRect?.width).toBeGreaterThan(0);
524
- expect(menuRect?.width).toBeLessThan(window.innerWidth);
525
525
  });
526
526
  },
527
527
  };