@12-apps/ui 1.0.0 → 1.3.0

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
@@ -54,47 +54,65 @@ export interface DescriptionItemProps {
54
54
  * />
55
55
  * ```
56
56
  */
57
+ // Vertical stacks the label above the value; horizontal sets them side by side
58
+ // with the label sized to its content.
59
+ const containerSx = (isHorizontal: boolean) => ({
60
+ display: 'flex',
61
+ flexDirection: isHorizontal ? ('row' as const) : ('column' as const),
62
+ alignItems: isHorizontal ? 'center' : 'flex-start',
63
+ gap: isHorizontal ? 1 : 0,
64
+ });
65
+
66
+ const labelStyle = (isHorizontal: boolean) => ({
67
+ textTransform: 'uppercase' as const,
68
+ marginBottom: isHorizontal ? 0 : '0.25rem',
69
+ minWidth: isHorizontal ? ('fit-content' as const) : undefined,
70
+ flexShrink: isHorizontal ? 0 : undefined,
71
+ });
72
+
73
+ const makeTestId =
74
+ (dataTestId?: string) =>
75
+ (suffix: string): string =>
76
+ dataTestId ? `${dataTestId}-${suffix}` : `description-item-${suffix}`;
77
+
57
78
  export const DescriptionItem: React.FC<DescriptionItemProps> = ({
58
79
  label,
59
80
  value,
60
81
  orientation = 'vertical',
61
82
  className,
62
83
  'data-testid': dataTestId,
63
- }) => (
64
- <Box
65
- className={className}
66
- sx={{
67
- display: 'flex',
68
- flexDirection: orientation === 'vertical' ? 'column' : 'row',
69
- alignItems: orientation === 'horizontal' ? 'center' : 'flex-start',
70
- gap: orientation === 'horizontal' ? 1 : 0,
71
- }}
72
- data-testid={dataTestId || 'description-item'}
73
- >
74
- <Text
75
- variant="caption"
76
- size="xs"
77
- color="secondary"
78
- style={{
79
- textTransform: 'uppercase',
80
- marginBottom: orientation === 'vertical' ? '0.25rem' : 0,
81
- minWidth: orientation === 'horizontal' ? 'fit-content' : undefined,
82
- flexShrink: orientation === 'horizontal' ? 0 : undefined,
83
- }}
84
- data-testid={dataTestId ? `${dataTestId}-label` : 'description-item-label'}
84
+ }) => {
85
+ const isHorizontal = orientation === 'horizontal';
86
+ const testId = makeTestId(dataTestId);
87
+
88
+ return (
89
+ <Box
90
+ className={className}
91
+ sx={containerSx(isHorizontal)}
92
+ data-testid={dataTestId || 'description-item'}
85
93
  >
86
- {label}
87
- </Text>
88
- <Box data-testid={dataTestId ? `${dataTestId}-value` : 'description-item-value'}>
89
- {typeof value === 'string' || typeof value === 'number' ? (
90
- <Text variant="body" size="sm" weight="semibold">
91
- {value}
92
- </Text>
93
- ) : (
94
- value
95
- )}
94
+ <Text
95
+ variant="caption"
96
+ size="xs"
97
+ color="secondary"
98
+ style={labelStyle(isHorizontal)}
99
+ data-testid={testId('label')}
100
+ >
101
+ {label}
102
+ </Text>
103
+ <Box data-testid={testId('value')}>
104
+ {/* Plain text gets the component's own typography; anything else is
105
+ rendered as given. */}
106
+ {typeof value === 'string' || typeof value === 'number' ? (
107
+ <Text variant="body" size="sm" weight="semibold">
108
+ {value}
109
+ </Text>
110
+ ) : (
111
+ value
112
+ )}
113
+ </Box>
96
114
  </Box>
97
- </Box>
98
- );
115
+ );
116
+ };
99
117
 
100
118
  DescriptionItem.displayName = 'DescriptionItem';
@@ -133,7 +133,7 @@ export const KeyboardNavigation: Story = {
133
133
  // Tab to primary button
134
134
  await userEvent.tab();
135
135
  const primaryButton = canvas.getByRole('button', { name: 'Primary' });
136
- await expect(primaryButton).toHaveFocus();
136
+ await waitFor(() => expect(primaryButton).toHaveFocus());
137
137
 
138
138
  // Press Enter to activate
139
139
  await userEvent.keyboard('{Enter}');
@@ -142,12 +142,12 @@ export const KeyboardNavigation: Story = {
142
142
  // Tab to secondary button
143
143
  await userEvent.tab();
144
144
  const secondaryButton = canvas.getByRole('button', { name: 'Secondary' });
145
- await expect(secondaryButton).toHaveFocus();
145
+ await waitFor(() => expect(secondaryButton).toHaveFocus());
146
146
 
147
147
  // Tab to help link
148
148
  await userEvent.tab();
149
149
  const helpLink = canvas.getByRole('link', { name: 'Help' });
150
- await expect(helpLink).toHaveFocus();
150
+ await waitFor(() => expect(helpLink).toHaveFocus());
151
151
  },
152
152
  };
153
153
 
@@ -203,14 +203,14 @@ export const FocusManagement: Story = {
203
203
 
204
204
  // Test focus indicators are visible
205
205
  await userEvent.tab();
206
- await expect(button).toHaveFocus();
206
+ await waitFor(() => expect(button).toHaveFocus());
207
207
 
208
208
  await userEvent.tab();
209
- await expect(link).toHaveFocus();
209
+ await waitFor(() => expect(link).toHaveFocus());
210
210
 
211
211
  // Test focus trap doesn't exist (buttons should be focusable)
212
212
  await userEvent.tab();
213
- await expect(link).not.toHaveFocus();
213
+ await waitFor(() => expect(link).not.toHaveFocus());
214
214
  },
215
215
  };
216
216
 
@@ -12,23 +12,211 @@ import React from 'react';
12
12
 
13
13
  import type { EmptyStateProps } from './EmptyState.types';
14
14
 
15
- export const EmptyState: React.FC<EmptyStateProps> = React.memo(({
16
- variant = 'default',
17
- title,
18
- description,
19
- illustration,
20
- primaryAction,
21
- secondaryAction,
22
- helpLink,
23
- onRefresh,
24
- refreshLabel = 'Refresh',
25
- onCreate,
26
- createLabel = 'Create New',
27
- className,
28
- dataTestId,
29
- }) => {
15
+ type EmptyStateVariant = NonNullable<EmptyStateProps['variant']>;
16
+
17
+ const makeTestId =
18
+ (dataTestId?: string) =>
19
+ (suffix: string): string =>
20
+ dataTestId ? `${dataTestId}-${suffix}` : `empty-state-${suffix}`;
21
+
22
+ interface ActionSpec {
23
+ key: string;
24
+ variant: 'contained' | 'outlined';
25
+ onClick: () => void;
26
+ label: string;
27
+ icon?: React.ReactNode;
28
+ }
29
+
30
+ // The four buttons are one button with different props. Listing them keeps their
31
+ // order — primary, create, secondary, refresh — visible in one place.
32
+ const actionsOf = (
33
+ props: EmptyStateProps,
34
+ refreshLabel: string,
35
+ createLabel: string,
36
+ ): ActionSpec[] => {
37
+ const { primaryAction, secondaryAction, onCreate, onRefresh } = props;
38
+ const specs: ActionSpec[] = [];
39
+
40
+ if (primaryAction) {
41
+ specs.push({
42
+ key: 'primary-action',
43
+ variant: 'contained',
44
+ onClick: primaryAction.onClick,
45
+ label: primaryAction.label,
46
+ });
47
+ }
48
+ if (onCreate) {
49
+ specs.push({
50
+ key: 'create-button',
51
+ variant: 'contained',
52
+ onClick: onCreate,
53
+ label: createLabel,
54
+ icon: <AddIcon />,
55
+ });
56
+ }
57
+ if (secondaryAction) {
58
+ specs.push({
59
+ key: 'secondary-action',
60
+ variant: 'outlined',
61
+ onClick: secondaryAction.onClick,
62
+ label: secondaryAction.label,
63
+ });
64
+ }
65
+ if (onRefresh) {
66
+ specs.push({
67
+ key: 'refresh-button',
68
+ variant: 'outlined',
69
+ onClick: onRefresh,
70
+ label: refreshLabel,
71
+ icon: <RefreshIcon />,
72
+ });
73
+ }
74
+
75
+ return specs;
76
+ };
77
+
78
+ const Illustration: React.FC<{
79
+ illustration: React.ReactNode;
80
+ variant: EmptyStateVariant;
81
+ testId: string;
82
+ }> = ({ illustration, variant, testId }) => (
83
+ <Box
84
+ data-testid={testId}
85
+ sx={{
86
+ maxWidth: variant === 'illustrated' ? 240 : 120,
87
+ width: '100%',
88
+ height: 'auto',
89
+ opacity: variant === 'minimal' ? 0.6 : 0.8,
90
+ display: variant === 'minimal' ? 'none' : 'block',
91
+ }}
92
+ >
93
+ {illustration}
94
+ </Box>
95
+ );
96
+
97
+ const Actions: React.FC<{
98
+ specs: ActionSpec[];
99
+ testId: (suffix: string) => string;
100
+ }> = ({ specs, testId }) => {
101
+ const theme = useTheme();
102
+
103
+ return (
104
+ <Stack
105
+ direction={{ xs: 'column', sm: 'row' }}
106
+ spacing={2}
107
+ alignItems="center"
108
+ sx={{ mt: theme.spacing(2) }}
109
+ >
110
+ {specs.map((spec) => (
111
+ <Button
112
+ key={spec.key}
113
+ variant={spec.variant}
114
+ onClick={spec.onClick}
115
+ startIcon={spec.icon}
116
+ data-testid={testId(spec.key)}
117
+ sx={{ minWidth: 120 }}
118
+ >
119
+ {spec.label}
120
+ </Button>
121
+ ))}
122
+ </Stack>
123
+ );
124
+ };
125
+
126
+ const HelpLink: React.FC<{
127
+ helpLink: NonNullable<EmptyStateProps['helpLink']>;
128
+ testId: string;
129
+ }> = ({ helpLink, testId }) => {
130
+ const theme = useTheme();
131
+
132
+ return (
133
+ <Link
134
+ href={helpLink.href}
135
+ target={helpLink.external ? '_blank' : undefined}
136
+ rel={helpLink.external ? 'noopener noreferrer' : undefined}
137
+ data-testid={testId}
138
+ sx={{
139
+ mt: theme.spacing(1),
140
+ color: theme.palette.primary.main,
141
+ textDecoration: 'none',
142
+ '&:hover': {
143
+ textDecoration: 'underline',
144
+ },
145
+ }}
146
+ >
147
+ {helpLink.label}
148
+ {helpLink.external && ' ↗'}
149
+ </Link>
150
+ );
151
+ };
152
+
153
+ const Content: React.FC<{
154
+ title: string;
155
+ description?: string;
156
+ titleId: string;
157
+ testId: (suffix: string) => string;
158
+ actions: ActionSpec[];
159
+ showActions: boolean;
160
+ helpLink?: EmptyStateProps['helpLink'];
161
+ }> = ({ title, description, titleId, testId, actions, showActions, helpLink }) => {
162
+ const theme = useTheme();
163
+
164
+ return (
165
+ <Stack spacing={2} alignItems="center">
166
+ <Typography
167
+ id={titleId}
168
+ variant="h6"
169
+ component="h3"
170
+ data-testid={testId('title')}
171
+ sx={{
172
+ fontWeight: theme.typography.fontWeightMedium,
173
+ color: theme.palette.text.primary,
174
+ maxWidth: 400,
175
+ }}
176
+ >
177
+ {title}
178
+ </Typography>
179
+
180
+ {description && (
181
+ <Typography
182
+ variant="body2"
183
+ color="text.secondary"
184
+ data-testid={testId('description')}
185
+ sx={{
186
+ maxWidth: 480,
187
+ lineHeight: 1.6,
188
+ }}
189
+ >
190
+ {description}
191
+ </Typography>
192
+ )}
193
+
194
+ {showActions && <Actions specs={actions} testId={testId} />}
195
+
196
+ {helpLink && <HelpLink helpLink={helpLink} testId={testId('help-link')} />}
197
+ </Stack>
198
+ );
199
+ };
200
+
201
+ export const EmptyState: React.FC<EmptyStateProps> = React.memo((props) => {
202
+ const {
203
+ variant = 'default',
204
+ title,
205
+ description,
206
+ illustration,
207
+ helpLink,
208
+ refreshLabel = 'Refresh',
209
+ createLabel = 'Create New',
210
+ className,
211
+ dataTestId,
212
+ } = props;
30
213
  const theme = useTheme();
31
214
  const titleId = React.useId();
215
+ const testId = makeTestId(dataTestId);
216
+
217
+ const actions = actionsOf(props, refreshLabel, createLabel);
218
+ // The `action` variant reserves the action row even when nothing fills it.
219
+ const showActions = variant === 'action' || actions.length > 0;
32
220
 
33
221
  return (
34
222
  <Box
@@ -49,132 +237,27 @@ export const EmptyState: React.FC<EmptyStateProps> = React.memo(({
49
237
  >
50
238
  {/* Illustration */}
51
239
  {illustration && (
52
- <Box
53
- data-testid={dataTestId ? `${dataTestId}-icon` : 'empty-state-icon'}
54
- sx={{
55
- maxWidth: variant === 'illustrated' ? 240 : 120,
56
- width: '100%',
57
- height: 'auto',
58
- opacity: variant === 'minimal' ? 0.6 : 0.8,
59
- display: variant === 'minimal' ? 'none' : 'block',
60
- }}
61
- >
62
- {illustration}
63
- </Box>
240
+ <Illustration
241
+ illustration={illustration}
242
+ variant={variant}
243
+ testId={testId('icon')}
244
+ />
64
245
  )}
65
246
 
66
247
  {/* Content */}
67
- <Stack spacing={2} alignItems="center">
68
- <Typography
69
- id={titleId}
70
- variant="h6"
71
- component="h3"
72
- data-testid={dataTestId ? `${dataTestId}-title` : 'empty-state-title'}
73
- sx={{
74
- fontWeight: theme.typography.fontWeightMedium,
75
- color: theme.palette.text.primary,
76
- maxWidth: 400,
77
- }}
78
- >
79
- {title}
80
- </Typography>
81
-
82
- {description && (
83
- <Typography
84
- variant="body2"
85
- color="text.secondary"
86
- data-testid={dataTestId ? `${dataTestId}-description` : 'empty-state-description'}
87
- sx={{
88
- maxWidth: 480,
89
- lineHeight: 1.6,
90
- }}
91
- >
92
- {description}
93
- </Typography>
94
- )}
95
-
96
- {/* Actions */}
97
- {(variant === 'action' || primaryAction || secondaryAction || onCreate || onRefresh) && (
98
- <Stack
99
- direction={{ xs: 'column', sm: 'row' }}
100
- spacing={2}
101
- alignItems="center"
102
- sx={{ mt: theme.spacing(2) }}
103
- >
104
- {primaryAction && (
105
- <Button
106
- variant="contained"
107
- onClick={primaryAction.onClick}
108
- data-testid={dataTestId ? `${dataTestId}-primary-action` : 'empty-state-primary-action'}
109
- sx={{ minWidth: 120 }}
110
- >
111
- {primaryAction.label}
112
- </Button>
113
- )}
114
-
115
- {onCreate && (
116
- <Button
117
- variant="contained"
118
- onClick={onCreate}
119
- startIcon={<AddIcon />}
120
- data-testid={dataTestId ? `${dataTestId}-create-button` : 'empty-state-create-button'}
121
- sx={{ minWidth: 120 }}
122
- >
123
- {createLabel}
124
- </Button>
125
- )}
126
-
127
- {secondaryAction && (
128
- <Button
129
- variant="outlined"
130
- onClick={secondaryAction.onClick}
131
- data-testid={dataTestId ? `${dataTestId}-secondary-action` : 'empty-state-secondary-action'}
132
- sx={{ minWidth: 120 }}
133
- >
134
- {secondaryAction.label}
135
- </Button>
136
- )}
137
-
138
- {onRefresh && (
139
- <Button
140
- variant="outlined"
141
- onClick={onRefresh}
142
- startIcon={<RefreshIcon />}
143
- data-testid={dataTestId ? `${dataTestId}-refresh-button` : 'empty-state-refresh-button'}
144
- sx={{ minWidth: 120 }}
145
- >
146
- {refreshLabel}
147
- </Button>
148
- )}
149
-
150
- </Stack>
151
- )}
152
-
153
- {/* Help Link */}
154
- {helpLink && (
155
- <Link
156
- href={helpLink.href}
157
- target={helpLink.external ? '_blank' : undefined}
158
- rel={helpLink.external ? 'noopener noreferrer' : undefined}
159
- data-testid={dataTestId ? `${dataTestId}-help-link` : 'empty-state-help-link'}
160
- sx={{
161
- mt: theme.spacing(1),
162
- color: theme.palette.primary.main,
163
- textDecoration: 'none',
164
- '&:hover': {
165
- textDecoration: 'underline',
166
- },
167
- }}
168
- >
169
- {helpLink.label}
170
- {helpLink.external && ' ↗'}
171
- </Link>
172
- )}
173
- </Stack>
248
+ <Content
249
+ title={title}
250
+ description={description}
251
+ titleId={titleId}
252
+ testId={testId}
253
+ actions={actions}
254
+ showActions={showActions}
255
+ helpLink={helpLink}
256
+ />
174
257
  </Box>
175
258
  );
176
259
  });
177
260
 
178
261
  EmptyState.displayName = 'EmptyState';
179
262
 
180
- export default EmptyState;
263
+ export default EmptyState;
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { expect, fn, userEvent, within } from 'storybook/test';
2
+ import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
3
3
 
4
4
  import { ErrorState } from './ErrorState';
5
5
 
@@ -122,7 +122,7 @@ export const KeyboardNavigation: Story = {
122
122
  await step('Focus retry button via Tab', async () => {
123
123
  await userEvent.tab();
124
124
  const retryButton = canvas.getByTestId('error-state-retry-button');
125
- await expect(retryButton).toHaveFocus();
125
+ await waitFor(() => expect(retryButton).toHaveFocus());
126
126
  });
127
127
 
128
128
  await step('Activate button with Enter key', async () => {
@@ -2,9 +2,87 @@ import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
2
2
  import RefreshIcon from '@mui/icons-material/Refresh';
3
3
  import WarningAmberIcon from '@mui/icons-material/WarningAmber';
4
4
  import { Box, Button, Stack, Typography, useTheme } from '@mui/material';
5
+ import type { Theme } from '@mui/material';
5
6
  import React from 'react';
6
7
 
7
- import type { ErrorStateProps } from './ErrorState.types';
8
+ import type { ErrorStateProps, ErrorStateSeverity } from './ErrorState.types';
9
+
10
+ const makeTestId =
11
+ (dataTestId?: string) =>
12
+ (suffix: string): string =>
13
+ dataTestId ? `${dataTestId}-${suffix}` : `error-state-${suffix}`;
14
+
15
+ const paletteFor = (theme: Theme, severity: ErrorStateSeverity) =>
16
+ severity === 'error' ? theme.palette.error : theme.palette.warning;
17
+
18
+ const ErrorIcon: React.FC<{
19
+ severity: ErrorStateSeverity;
20
+ icon?: React.ReactNode;
21
+ testId: string;
22
+ }> = ({ severity, icon, testId }) => {
23
+ const theme = useTheme();
24
+ const color = paletteFor(theme, severity);
25
+ const Fallback = severity === 'error' ? ErrorOutlineIcon : WarningAmberIcon;
26
+
27
+ return (
28
+ <Box
29
+ data-testid={testId}
30
+ sx={{
31
+ display: 'flex',
32
+ alignItems: 'center',
33
+ justifyContent: 'center',
34
+ width: 80,
35
+ height: 80,
36
+ borderRadius: '50%',
37
+ backgroundColor: color.light,
38
+ opacity: 0.9,
39
+ }}
40
+ >
41
+ {icon || <Fallback sx={{ fontSize: 48, color: color.main }} />}
42
+ </Box>
43
+ );
44
+ };
45
+
46
+ const ErrorText: React.FC<{
47
+ title?: string;
48
+ message: string;
49
+ titleId: string;
50
+ testId: (suffix: string) => string;
51
+ }> = ({ title, message, titleId, testId }) => {
52
+ const theme = useTheme();
53
+
54
+ return (
55
+ <Stack spacing={1} alignItems="center">
56
+ {title && (
57
+ <Typography
58
+ id={titleId}
59
+ variant="h6"
60
+ component="h3"
61
+ data-testid={testId('title')}
62
+ sx={{
63
+ fontWeight: theme.typography.fontWeightMedium,
64
+ color: theme.palette.text.primary,
65
+ }}
66
+ >
67
+ {title}
68
+ </Typography>
69
+ )}
70
+
71
+ <Typography
72
+ id={`${titleId}-message`}
73
+ variant="body2"
74
+ color="text.secondary"
75
+ data-testid={testId('message')}
76
+ sx={{
77
+ maxWidth: 400,
78
+ lineHeight: 1.6,
79
+ }}
80
+ >
81
+ {message}
82
+ </Typography>
83
+ </Stack>
84
+ );
85
+ };
8
86
 
9
87
  export const ErrorState: React.FC<ErrorStateProps> = React.memo(
10
88
  ({
@@ -19,15 +97,7 @@ export const ErrorState: React.FC<ErrorStateProps> = React.memo(
19
97
  }) => {
20
98
  const theme = useTheme();
21
99
  const titleId = React.useId();
22
-
23
- const color = severity === 'error' ? theme.palette.error : theme.palette.warning;
24
-
25
- const defaultIcon =
26
- severity === 'error' ? (
27
- <ErrorOutlineIcon sx={{ fontSize: 48, color: color.main }} />
28
- ) : (
29
- <WarningAmberIcon sx={{ fontSize: 48, color: color.main }} />
30
- );
100
+ const testId = makeTestId(dataTestId);
31
101
 
32
102
  return (
33
103
  <Box
@@ -47,51 +117,9 @@ export const ErrorState: React.FC<ErrorStateProps> = React.memo(
47
117
  gap: theme.spacing(2),
48
118
  }}
49
119
  >
50
- <Box
51
- data-testid={dataTestId ? `${dataTestId}-icon` : 'error-state-icon'}
52
- sx={{
53
- display: 'flex',
54
- alignItems: 'center',
55
- justifyContent: 'center',
56
- width: 80,
57
- height: 80,
58
- borderRadius: '50%',
59
- backgroundColor: color.light,
60
- opacity: 0.9,
61
- }}
62
- >
63
- {icon || defaultIcon}
64
- </Box>
65
-
66
- <Stack spacing={1} alignItems="center">
67
- {title && (
68
- <Typography
69
- id={titleId}
70
- variant="h6"
71
- component="h3"
72
- data-testid={dataTestId ? `${dataTestId}-title` : 'error-state-title'}
73
- sx={{
74
- fontWeight: theme.typography.fontWeightMedium,
75
- color: theme.palette.text.primary,
76
- }}
77
- >
78
- {title}
79
- </Typography>
80
- )}
120
+ <ErrorIcon severity={severity} icon={icon} testId={testId('icon')} />
81
121
 
82
- <Typography
83
- id={`${titleId}-message`}
84
- variant="body2"
85
- color="text.secondary"
86
- data-testid={dataTestId ? `${dataTestId}-message` : 'error-state-message'}
87
- sx={{
88
- maxWidth: 400,
89
- lineHeight: 1.6,
90
- }}
91
- >
92
- {message}
93
- </Typography>
94
- </Stack>
122
+ <ErrorText title={title} message={message} titleId={titleId} testId={testId} />
95
123
 
96
124
  {onRetry && (
97
125
  <Button
@@ -99,7 +127,7 @@ export const ErrorState: React.FC<ErrorStateProps> = React.memo(
99
127
  color={severity}
100
128
  onClick={onRetry}
101
129
  startIcon={<RefreshIcon />}
102
- data-testid={dataTestId ? `${dataTestId}-retry-button` : 'error-state-retry-button'}
130
+ data-testid={testId('retry-button')}
103
131
  sx={{
104
132
  mt: theme.spacing(1),
105
133
  minWidth: 120,