@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,225 @@
1
+ import { alpha, keyframes } from '@mui/material';
2
+ import type { CSSObject, Theme } from '@mui/material';
3
+
4
+ // Define pulse animation globally
5
+ const pulseAnimation = keyframes`
6
+ 0% {
7
+ box-shadow: 0 0 0 0 currentColor;
8
+ opacity: 1;
9
+ }
10
+ 70% {
11
+ box-shadow: 0 0 0 15px currentColor;
12
+ opacity: 0;
13
+ }
14
+ 100% {
15
+ box-shadow: 0 0 0 0 currentColor;
16
+ opacity: 0;
17
+ }
18
+ `;
19
+
20
+ interface ColorPalette {
21
+ main: string;
22
+ dark: string;
23
+ light: string;
24
+ contrastText: string;
25
+ }
26
+
27
+ const shade = (
28
+ palette: ColorPalette | undefined,
29
+ key: 'dark' | 'light',
30
+ fallback: ColorPalette,
31
+ ): string => palette?.[key] || palette?.main || fallback[key];
32
+
33
+ /** `neutral` has no MUI palette entry, so its four shades come from grey. */
34
+ const neutralPalette = (theme: Theme): ColorPalette => ({
35
+ main: theme.palette.grey?.[700] || '#616161',
36
+ dark: theme.palette.grey?.[800] || '#424242',
37
+ light: theme.palette.grey?.[500] || '#9e9e9e',
38
+ contrastText: '#fff',
39
+ });
40
+
41
+ export const getColorFromTheme = (theme: Theme, color: string): ColorPalette => {
42
+ if (color === 'neutral') return neutralPalette(theme);
43
+
44
+ const colorMap: Record<string, ColorPalette> = {
45
+ primary: theme.palette.primary as ColorPalette,
46
+ secondary: theme.palette.secondary as ColorPalette,
47
+ success: theme.palette.success as ColorPalette,
48
+ warning: theme.palette.warning as ColorPalette,
49
+ danger: theme.palette.error as ColorPalette,
50
+ };
51
+
52
+ const fallback = theme.palette.primary as ColorPalette;
53
+ const palette = colorMap[color] || fallback;
54
+
55
+ // Ensure palette has required properties
56
+ return {
57
+ main: palette?.main || fallback.main,
58
+ dark: shade(palette, 'dark', fallback),
59
+ light: shade(palette, 'light', fallback),
60
+ contrastText: palette?.contrastText || '#fff',
61
+ };
62
+ };
63
+
64
+ export const SIZE_MAP: Record<string, CSSObject> = {
65
+ xs: { padding: '2px 8px', fontSize: '0.75rem' },
66
+ sm: { padding: '6px 12px', fontSize: '0.875rem' },
67
+ md: { padding: '8px 16px', fontSize: '1rem' },
68
+ lg: { padding: '10px 20px', fontSize: '1.125rem' },
69
+ xl: { padding: '12px 24px', fontSize: '1.25rem' },
70
+ };
71
+
72
+ /**
73
+ * MUI centres icons with a negative margin that fights our own padding, so the
74
+ * flex alignment is restated here rather than fought with `!important`.
75
+ */
76
+ export const iconAlignmentStyles = (theme: Theme): CSSObject => ({
77
+ '& .MuiButton-startIcon, & .MuiButton-endIcon': {
78
+ display: 'inline-flex',
79
+ alignItems: 'center',
80
+ justifyContent: 'center',
81
+ margin: 0,
82
+ verticalAlign: 'middle',
83
+ '& > *': {
84
+ display: 'inline-flex',
85
+ alignItems: 'center',
86
+ justifyContent: 'center',
87
+ lineHeight: 0,
88
+ },
89
+ },
90
+ '& .MuiButton-startIcon': {
91
+ marginRight: theme.spacing(0.5),
92
+ },
93
+ '& .MuiButton-endIcon': {
94
+ marginLeft: theme.spacing(0.5),
95
+ },
96
+ });
97
+
98
+ // Gradients run between two related hues; the named colours pick a specific pair,
99
+ // anything else runs from its own main to its own dark.
100
+ const GRADIENT_PAIRS: Record<string, (theme: Theme) => [string, string]> = {
101
+ primary: (theme) => [theme.palette.primary.main, theme.palette.secondary.main],
102
+ secondary: (theme) => [theme.palette.secondary.main, theme.palette.primary.main],
103
+ success: (theme) => [theme.palette.success.light, theme.palette.success.dark],
104
+ warning: (theme) => [theme.palette.warning.light, theme.palette.warning.dark],
105
+ danger: (theme) => [theme.palette.error.light, theme.palette.error.dark],
106
+ };
107
+
108
+ const gradientFor = (theme: Theme, color: string, palette: ColorPalette): string => {
109
+ const [from, to] = GRADIENT_PAIRS[color]?.(theme) ?? [palette.main, palette.dark];
110
+ return `linear-gradient(135deg, ${from} 0%, ${to} 100%)`;
111
+ };
112
+
113
+ const VARIANT_STYLES: Record<
114
+ string,
115
+ (theme: Theme, palette: ColorPalette, color: string) => CSSObject
116
+ > = {
117
+ solid: (theme, palette) => ({
118
+ backgroundColor: palette.main,
119
+ color: palette.contrastText || '#fff',
120
+ '&:hover': {
121
+ backgroundColor: palette.dark,
122
+ transform: 'translateY(-2px)',
123
+ boxShadow: theme.shadows[8],
124
+ },
125
+ }),
126
+ outline: (_theme, palette) => ({
127
+ backgroundColor: 'transparent',
128
+ color: palette.main,
129
+ border: `1px solid ${palette.main}`,
130
+ '&:hover': {
131
+ backgroundColor: alpha(palette.main, 0.1),
132
+ borderColor: palette.dark,
133
+ },
134
+ }),
135
+ ghost: (_theme, palette) => ({
136
+ backgroundColor: 'transparent',
137
+ color: palette.main,
138
+ '&:hover': {
139
+ backgroundColor: alpha(palette.main, 0.1),
140
+ },
141
+ }),
142
+ text: (_theme, palette) => ({
143
+ backgroundColor: 'transparent',
144
+ color: palette.main,
145
+ border: `none`,
146
+ '&:hover': {
147
+ backgroundColor: alpha(palette.main, 0.1),
148
+ },
149
+ '&.active': {
150
+ backgroundColor: alpha(palette.main, 0.1),
151
+ color: palette.main,
152
+ },
153
+ }),
154
+ glass: (theme, palette) => ({
155
+ backgroundColor: alpha(theme.palette.background.paper, 0.1),
156
+ backdropFilter: 'blur(20px)',
157
+ border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
158
+ color: palette.main,
159
+ '&:hover': {
160
+ backgroundColor: alpha(theme.palette.background.paper, 0.2),
161
+ transform: 'translateY(-2px)',
162
+ },
163
+ }),
164
+ gradient: (theme, palette, color) => ({
165
+ background: gradientFor(theme, color, palette),
166
+ color: '#fff',
167
+ '&:hover': {
168
+ filter: 'brightness(1.1)',
169
+ transform: 'translateY(-2px)',
170
+ boxShadow: theme.shadows[12],
171
+ },
172
+ }),
173
+ };
174
+
175
+ export const buttonVariantStyles = (
176
+ theme: Theme,
177
+ variant: string | undefined,
178
+ palette: ColorPalette,
179
+ color: string,
180
+ ): CSSObject => VARIANT_STYLES[variant ?? '']?.(theme, palette, color) ?? {};
181
+
182
+ // Glow effect - applied with !important to override variant shadows
183
+ const glowStyles = (palette: ColorPalette): CSSObject => ({
184
+ boxShadow: `0 0 20px 5px ${alpha(palette.main, 0.6)}, 0 0 40px 10px ${alpha(palette.main, 0.3)} !important`,
185
+ filter: 'brightness(1.05)',
186
+ '&:hover': {
187
+ boxShadow: `0 0 25px 8px ${alpha(palette.main, 0.7)}, 0 0 50px 15px ${alpha(palette.main, 0.4)} !important`,
188
+ filter: 'brightness(1.1)',
189
+ transform: 'translateY(-2px) scale(1.02)',
190
+ },
191
+ });
192
+
193
+ // Pulse animation using pseudo-element
194
+ const pulseStyles = (palette: ColorPalette): CSSObject => ({
195
+ position: 'relative',
196
+ overflow: 'visible',
197
+ '&::after': {
198
+ content: '""',
199
+ position: 'absolute',
200
+ top: '50%',
201
+ left: '50%',
202
+ width: '100%',
203
+ height: '100%',
204
+ borderRadius: 'inherit',
205
+ transform: 'translate(-50%, -50%)',
206
+ backgroundColor: palette.main,
207
+ opacity: 0.3,
208
+ animation: `${pulseAnimation} 2s infinite`,
209
+ pointerEvents: 'none',
210
+ zIndex: -1,
211
+ },
212
+ });
213
+
214
+ /**
215
+ * glow and pulse are independent flags. The three combinations used to be spelled
216
+ * out one by one, but each is just the union of whichever flags are set.
217
+ */
218
+ export const buttonEmphasisStyles = (
219
+ palette: ColorPalette,
220
+ glow?: boolean,
221
+ pulse?: boolean,
222
+ ): CSSObject => ({
223
+ ...(glow ? glowStyles(palette) : {}),
224
+ ...(pulse ? pulseStyles(palette) : {}),
225
+ });
@@ -1,4 +1,5 @@
1
- import { Delete,Save } from '@mui/icons-material';
1
+ import Delete from '@mui/icons-material/Delete';
2
+ import Save from '@mui/icons-material/Save';
2
3
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
4
  import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
4
5
 
@@ -132,22 +133,26 @@ export const KeyboardNavigation: Story = {
132
133
  play: async ({ canvasElement, step, args }) => {
133
134
  const canvas = within(canvasElement);
134
135
 
136
+ // The story renders this single button, so one tab reaches it. Focus then
137
+ // stays put across the activation steps — pressing Enter or Space on a
138
+ // button does not move it — which is what makes this one keyboard journey
139
+ // rather than three re-focused fragments.
135
140
  await step('Focus via Tab navigation', async () => {
136
141
  const button = canvas.getByTestId('keyboard-button');
137
- button.focus();
138
- await expect(button).toHaveFocus();
142
+ await userEvent.tab();
143
+ await waitFor(() => expect(button).toHaveFocus());
139
144
  });
140
145
 
141
146
  await step('Enter key activation', async () => {
142
147
  const button = canvas.getByTestId('keyboard-button');
143
- button.focus();
148
+ await waitFor(() => expect(button).toHaveFocus());
144
149
  await userEvent.keyboard('{Enter}');
145
150
  await expect(args.onClick).toHaveBeenCalled();
146
151
  });
147
152
 
148
153
  await step('Space key activation', async () => {
149
154
  const button = canvas.getByTestId('keyboard-button');
150
- button.focus();
155
+ await waitFor(() => expect(button).toHaveFocus());
151
156
  await userEvent.keyboard(' ');
152
157
  await expect(args.onClick).toHaveBeenCalled();
153
158
  });
@@ -225,8 +230,10 @@ export const DisabledAccessibility: Story = {
225
230
  });
226
231
 
227
232
  await step('Verify keyboard interaction is prevented', async () => {
228
- const button = canvas.getByTestId('disabled-button');
229
- button.focus();
233
+ // A disabled button is not tabbable and cannot take focus, so keyboard
234
+ // input never reaches it. The previous version called .focus() on it,
235
+ // which is a no-op on a disabled element — the step passed for the wrong
236
+ // reason. Sending the keys with focus left on the body is the real check.
230
237
  await userEvent.keyboard('{Enter}');
231
238
  await userEvent.keyboard(' ');
232
239
  await expect(args.onClick).not.toHaveBeenCalled();
@@ -441,16 +448,12 @@ export const PerformanceTest: Story = {
441
448
  play: async ({ canvasElement, step }) => {
442
449
  const canvas = within(canvasElement);
443
450
 
444
- await step('Measure render time', async () => {
445
- const startTime = Date.now();
451
+ await step('All buttons rendered', async () => {
452
+ // The timing assertion that used to live here bracketed getAllByTestId,
453
+ // which queries DOM that is already rendered — it measured query time, not
454
+ // render time, and a 100ms wall-clock budget is exactly what a loaded CI
455
+ // runner blows through. The count is the part that actually held.
446
456
  const buttons = canvas.getAllByTestId(/perf-button-/);
447
- const endTime = Date.now();
448
-
449
- const renderTime = endTime - startTime;
450
- // Performance measurement for testing - no console output
451
-
452
- // Assert reasonable render time (adjust threshold as needed)
453
- await expect(renderTime).toBeLessThan(100);
454
457
  await expect(buttons.length).toBe(50);
455
458
  });
456
459
 
@@ -519,13 +522,14 @@ export const LoadingWithIcon: Story = {
519
522
  const button = canvas.getByTestId('loading-icon-button');
520
523
  await expect(button).toBeInTheDocument();
521
524
 
522
- // Icon should not be visible when loading
523
- const icon = canvas.queryByTestId('delete-icon');
524
- await expect(icon).not.toBeInTheDocument();
525
-
526
- // Loading spinner should be visible
525
+ // Assert the positive condition first: the spinner being present is what
526
+ // establishes that the loading state actually rendered. Without it the
527
+ // absence check below would also pass on a button that rendered nothing.
527
528
  const spinner = canvas.getByRole('progressbar');
528
529
  await expect(spinner).toBeInTheDocument();
530
+
531
+ // Icon should not be visible when loading
532
+ await waitFor(() => expect(canvas.queryByTestId('delete-icon')).not.toBeInTheDocument());
529
533
  });
530
534
  },
531
535
  };
@@ -1,62 +1,17 @@
1
- import { alpha, Button as MuiButton, CircularProgress, keyframes } from '@mui/material';
2
- import type { Theme } from '@mui/material/styles';
1
+ import { Button as MuiButton, CircularProgress } from '@mui/material';
3
2
  import { styled } from '@mui/material/styles';
4
3
  import * as React from 'react';
5
4
 
5
+ import { resolveButtonProps } from './Button.helpers';
6
+ import {
7
+ buttonEmphasisStyles,
8
+ buttonVariantStyles,
9
+ getColorFromTheme,
10
+ iconAlignmentStyles,
11
+ SIZE_MAP,
12
+ } from './Button.styles';
6
13
  import type { ButtonProps } from './Button.types';
7
14
 
8
- // Define pulse animation globally
9
- const pulseAnimation = keyframes`
10
- 0% {
11
- box-shadow: 0 0 0 0 currentColor;
12
- opacity: 1;
13
- }
14
- 70% {
15
- box-shadow: 0 0 0 15px currentColor;
16
- opacity: 0;
17
- }
18
- 100% {
19
- box-shadow: 0 0 0 0 currentColor;
20
- opacity: 0;
21
- }
22
- `;
23
-
24
- interface ColorPalette {
25
- main: string;
26
- dark: string;
27
- light: string;
28
- contrastText: string;
29
- }
30
-
31
- const getColorFromTheme = (theme: Theme, color: string): ColorPalette => {
32
- if (color === 'neutral') {
33
- return {
34
- main: theme.palette.grey?.[700] || '#616161',
35
- dark: theme.palette.grey?.[800] || '#424242',
36
- light: theme.palette.grey?.[500] || '#9e9e9e',
37
- contrastText: '#fff',
38
- };
39
- }
40
-
41
- const colorMap: Record<string, ColorPalette> = {
42
- primary: theme.palette.primary as ColorPalette,
43
- secondary: theme.palette.secondary as ColorPalette,
44
- success: theme.palette.success as ColorPalette,
45
- warning: theme.palette.warning as ColorPalette,
46
- danger: theme.palette.error as ColorPalette,
47
- };
48
-
49
- const palette = colorMap[color] || theme.palette.primary;
50
-
51
- // Ensure palette has required properties
52
- return {
53
- main: palette?.main || theme.palette.primary.main,
54
- dark: palette?.dark || palette?.main || theme.palette.primary.dark,
55
- light: palette?.light || palette?.main || theme.palette.primary.light,
56
- contrastText: palette?.contrastText || '#fff',
57
- };
58
- };
59
-
60
15
  const StyledButton = styled(MuiButton, {
61
16
  shouldForwardProp: (prop) =>
62
17
  ['glow', 'pulse', 'loading', 'customVariant', 'customColor', 'ripple'].indexOf(
@@ -78,188 +33,39 @@ const StyledButton = styled(MuiButton, {
78
33
  transition: 'all 0.3s ease',
79
34
  position: 'relative',
80
35
  overflow: 'hidden',
81
-
82
- // Icon alignment fixes
83
- '& .MuiButton-startIcon, & .MuiButton-endIcon': {
84
- display: 'inline-flex',
85
- alignItems: 'center',
86
- justifyContent: 'center',
87
- margin: 0,
88
- verticalAlign: 'middle',
89
- '& > *': {
90
- display: 'inline-flex',
91
- alignItems: 'center',
92
- justifyContent: 'center',
93
- lineHeight: 0,
94
- },
95
- },
96
- '& .MuiButton-startIcon': {
97
- marginRight: theme.spacing(0.5),
98
- },
99
- '& .MuiButton-endIcon': {
100
- marginLeft: theme.spacing(0.5),
101
- },
102
-
103
- // Variant styles
104
- ...(customVariant === 'solid' && {
105
- backgroundColor: colorPalette.main,
106
- color: colorPalette.contrastText || '#fff',
107
- '&:hover': {
108
- backgroundColor: colorPalette.dark,
109
- transform: 'translateY(-2px)',
110
- boxShadow: theme.shadows[8],
111
- },
112
- }),
113
-
114
- ...(customVariant === 'outline' && {
115
- backgroundColor: 'transparent',
116
- color: colorPalette.main,
117
- border: `1px solid ${colorPalette.main}`,
118
- '&:hover': {
119
- backgroundColor: alpha(colorPalette.main, 0.1),
120
- borderColor: colorPalette.dark,
121
- },
122
- }),
123
-
124
- ...(customVariant === 'ghost' && {
125
- backgroundColor: 'transparent',
126
- color: colorPalette.main,
127
- '&:hover': {
128
- backgroundColor: alpha(colorPalette.main, 0.1),
129
- },
130
- }),
131
-
132
- ...(customVariant === 'text' && {
133
- backgroundColor: 'transparent',
134
- color: colorPalette.main,
135
- border: `none`,
136
- '&:hover': {
137
- backgroundColor: alpha(colorPalette.main, 0.1),
138
- },
139
- '&.active': {
140
- backgroundColor: alpha(colorPalette.main, 0.1),
141
- color: colorPalette.main,
142
- },
143
- }),
144
-
145
- ...(customVariant === 'glass' && {
146
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
147
- backdropFilter: 'blur(20px)',
148
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
149
- color: colorPalette.main,
150
- '&:hover': {
151
- backgroundColor: alpha(theme.palette.background.paper, 0.2),
152
- transform: 'translateY(-2px)',
153
- },
154
- }),
155
-
156
- ...(customVariant === 'gradient' && {
157
- background:
158
- customColor === 'primary'
159
- ? `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.secondary.main} 100%)`
160
- : customColor === 'secondary'
161
- ? `linear-gradient(135deg, ${theme.palette.secondary.main} 0%, ${theme.palette.primary.main} 100%)`
162
- : customColor === 'success'
163
- ? `linear-gradient(135deg, ${theme.palette.success.light} 0%, ${theme.palette.success.dark} 100%)`
164
- : customColor === 'warning'
165
- ? `linear-gradient(135deg, ${theme.palette.warning.light} 0%, ${theme.palette.warning.dark} 100%)`
166
- : customColor === 'danger'
167
- ? `linear-gradient(135deg, ${theme.palette.error.light} 0%, ${theme.palette.error.dark} 100%)`
168
- : `linear-gradient(135deg, ${colorPalette.main} 0%, ${colorPalette.dark} 100%)`,
169
- color: '#fff',
170
- '&:hover': {
171
- filter: 'brightness(1.1)',
172
- transform: 'translateY(-2px)',
173
- boxShadow: theme.shadows[12],
174
- },
175
- }),
176
-
177
- // Glow effect - applied with !important to override variant shadows
178
- ...(glow &&
179
- !pulse && {
180
- boxShadow: `0 0 20px 5px ${alpha(colorPalette.main, 0.6)}, 0 0 40px 10px ${alpha(colorPalette.main, 0.3)} !important`,
181
- filter: 'brightness(1.05)',
182
- '&:hover': {
183
- boxShadow: `0 0 25px 8px ${alpha(colorPalette.main, 0.7)}, 0 0 50px 15px ${alpha(colorPalette.main, 0.4)} !important`,
184
- filter: 'brightness(1.1)',
185
- transform: 'translateY(-2px) scale(1.02)',
186
- },
187
- }),
188
-
189
- // Pulse animation using pseudo-element
190
- ...(pulse &&
191
- !glow && {
192
- position: 'relative',
193
- overflow: 'visible',
194
- '&::after': {
195
- content: '""',
196
- position: 'absolute',
197
- top: '50%',
198
- left: '50%',
199
- width: '100%',
200
- height: '100%',
201
- borderRadius: 'inherit',
202
- transform: 'translate(-50%, -50%)',
203
- backgroundColor: colorPalette.main,
204
- opacity: 0.3,
205
- animation: `${pulseAnimation} 2s infinite`,
206
- pointerEvents: 'none',
207
- zIndex: -1,
208
- },
209
- }),
210
-
211
- // Both glow and pulse effects combined
212
- ...(glow &&
213
- pulse && {
214
- position: 'relative',
215
- overflow: 'visible',
216
- boxShadow: `0 0 20px 5px ${alpha(colorPalette.main, 0.6)}, 0 0 40px 10px ${alpha(colorPalette.main, 0.3)} !important`,
217
- filter: 'brightness(1.05)',
218
- '&:hover': {
219
- boxShadow: `0 0 25px 8px ${alpha(colorPalette.main, 0.7)}, 0 0 50px 15px ${alpha(colorPalette.main, 0.4)} !important`,
220
- filter: 'brightness(1.1)',
221
- transform: 'translateY(-2px) scale(1.02)',
222
- },
223
- '&::after': {
224
- content: '""',
225
- position: 'absolute',
226
- top: '50%',
227
- left: '50%',
228
- width: '100%',
229
- height: '100%',
230
- borderRadius: 'inherit',
231
- transform: 'translate(-50%, -50%)',
232
- backgroundColor: colorPalette.main,
233
- opacity: 0.3,
234
- animation: `${pulseAnimation} 2s infinite`,
235
- pointerEvents: 'none',
236
- zIndex: -1,
237
- },
238
- }),
36
+ ...iconAlignmentStyles(theme),
37
+ ...buttonVariantStyles(theme, customVariant, colorPalette, customColor),
38
+ ...buttonEmphasisStyles(colorPalette, glow, pulse),
239
39
  };
240
40
  });
241
41
 
242
- const sizeMap = {
243
- xs: { padding: '2px 8px', fontSize: '0.75rem' },
244
- sm: { padding: '6px 12px', fontSize: '0.875rem' },
245
- md: { padding: '8px 16px', fontSize: '1rem' },
246
- lg: { padding: '10px 20px', fontSize: '1.125rem' },
247
- xl: { padding: '12px 24px', fontSize: '1.25rem' },
42
+ /** Our six variants collapse onto MUI's three; only outline and text differ. */
43
+ const muiVariantFor = (variant?: string): 'outlined' | 'text' | 'contained' => {
44
+ if (variant === 'outline') return 'outlined';
45
+ return variant === 'text' ? 'text' : 'contained';
46
+ };
47
+
48
+ type MuiButtonColor = 'inherit' | 'primary' | 'secondary' | 'success' | 'warning' | 'info' | 'error';
49
+
50
+ /** `danger` and `neutral` are ours; the rest are MUI's own names already. */
51
+ const muiColorFor = (color: NonNullable<ButtonProps['color']>): MuiButtonColor => {
52
+ if (color === 'danger') return 'error';
53
+ return color === 'neutral' ? 'inherit' : color;
248
54
  };
249
55
 
250
56
  export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
251
- (
252
- {
253
- variant = 'solid',
254
- color = 'primary',
255
- size = 'md',
256
- loading = false,
57
+ (rawProps, ref) => {
58
+ const {
59
+ variant,
60
+ color,
61
+ size,
62
+ loading,
257
63
  icon,
258
- iconPosition = 'left',
259
- glow = false,
260
- pulse = false,
261
- ripple = true,
262
- active = false,
64
+ iconPosition,
65
+ glow,
66
+ pulse,
67
+ ripple,
68
+ active,
263
69
  children,
264
70
  disabled,
265
71
  onClick,
@@ -267,31 +73,22 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
267
73
  onBlur,
268
74
  dataTestId,
269
75
  ...props
270
- },
271
- ref,
272
- ) => {
273
- const muiVariant = variant ==='outline' ? 'outlined' : variant === 'text' ? 'text': 'contained';
274
- const muiColor =
275
- color === 'danger'
276
- ? 'error'
277
- : color === 'neutral'
278
- ? 'inherit'
279
- : (color);
76
+ } = resolveButtonProps(rawProps);
77
+ const testId = (suffix: string) =>
78
+ dataTestId ? `${dataTestId}-${suffix}` : `button-${suffix}`;
280
79
 
281
80
  // Wrap icon with testId if provided
282
81
  const iconWithTestId =
283
- !loading && icon ? (
284
- <span data-testid={dataTestId ? `${dataTestId}-icon` : 'button-icon'}>{icon}</span>
285
- ) : undefined;
82
+ !loading && icon ? <span data-testid={testId('icon')}>{icon}</span> : undefined;
286
83
 
287
- const activeClassName = active ? 'active' : '';
288
- const mergedClassName = [activeClassName, props.className].filter(Boolean).join(' ') || undefined;
84
+ const mergedClassName =
85
+ [active ? 'active' : '', props.className].filter(Boolean).join(' ') || undefined;
289
86
 
290
87
  return (
291
88
  <StyledButton
292
89
  ref={ref}
293
- variant={muiVariant}
294
- color={muiColor}
90
+ variant={muiVariantFor(variant)}
91
+ color={muiColorFor(color)}
295
92
  customVariant={variant}
296
93
  customColor={color}
297
94
  glow={glow}
@@ -304,17 +101,13 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
304
101
  onClick={onClick}
305
102
  onFocus={onFocus}
306
103
  onBlur={onBlur}
307
- sx={sizeMap[size]}
104
+ sx={SIZE_MAP[size]}
308
105
  data-testid={dataTestId || 'button'}
309
106
  {...props}
310
107
  className={mergedClassName}
311
108
  >
312
109
  {loading ? (
313
- <CircularProgress
314
- size={16}
315
- color="inherit"
316
- data-testid={dataTestId ? `${dataTestId}-loading` : 'button-loading'}
317
- />
110
+ <CircularProgress size={16} color="inherit" data-testid={testId('loading')} />
318
111
  ) : (
319
112
  children
320
113
  )}