@12-apps/ui 1.0.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/components-catalog.md +14 -0
  2. package/package.json +42 -27
  3. package/src/__tests__/data-grid.test.tsx +125 -0
  4. package/src/__tests__/package-wiring.test.ts +12 -8
  5. package/src/__tests__/social-login-button.test.tsx +5 -5
  6. package/src/__tests__/viewport.test.ts +50 -0
  7. package/src/charts/index.ts +8 -0
  8. package/src/components/data-display/Alert/Alert.md +1 -1
  9. package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
  10. package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
  11. package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
  12. package/src/components/data-display/Alert/Alert.tsx +135 -328
  13. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
  14. package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
  15. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
  16. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
  17. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
  18. package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
  19. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
  20. package/src/components/data-display/Avatar/Avatar.tsx +154 -450
  21. package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
  22. package/src/components/data-display/Badge/Badge.animations.ts +67 -0
  23. package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
  24. package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
  25. package/src/components/data-display/Badge/Badge.styles.ts +261 -0
  26. package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
  27. package/src/components/data-display/Badge/Badge.tsx +151 -557
  28. package/src/components/data-display/Badge/Badge.variants.ts +106 -0
  29. package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
  30. package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
  31. package/src/components/data-display/Banner/Banner.styles.ts +120 -0
  32. package/src/components/data-display/Banner/Banner.tsx +179 -224
  33. package/src/components/data-display/Chart/Chart.md +48 -1
  34. package/src/components/data-display/Chart/Chart.tsx +82 -500
  35. package/src/components/data-display/Chart/Chart.types.ts +2 -0
  36. package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
  37. package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
  38. package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
  39. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
  40. package/src/components/data-display/Chart/SpecChart.tsx +53 -0
  41. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
  42. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
  43. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
  44. package/src/components/data-display/Chart/chart-format.ts +26 -0
  45. package/src/components/data-display/Chart/chart-internals.ts +148 -0
  46. package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
  47. package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
  48. package/src/components/data-display/Chart/chart-spec.ts +171 -0
  49. package/src/components/data-display/Chart/index.ts +30 -0
  50. package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
  51. package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
  52. package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
  53. package/src/components/data-display/Chip/Chip.tsx +31 -94
  54. package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
  55. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
  56. package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
  57. package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
  58. package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
  59. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
  60. package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
  61. package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
  62. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
  63. package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
  64. package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
  65. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
  66. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
  67. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
  68. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
  69. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
  70. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
  71. package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
  72. package/src/components/data-display/DataViews/base-card.tsx +278 -0
  73. package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
  74. package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
  75. package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
  76. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
  77. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
  78. package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
  79. package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
  80. package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
  81. package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
  82. package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
  83. package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
  84. package/src/components/data-display/DataViews/data-views-types.ts +312 -0
  85. package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
  86. package/src/components/data-display/DataViews/index.ts +33 -0
  87. package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
  88. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
  89. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
  90. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
  91. package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
  92. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
  93. package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
  94. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
  95. package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
  96. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
  97. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
  98. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
  99. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
  100. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
  101. package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
  102. package/src/components/data-display/LazyImage/LazyImage.md +1 -1
  103. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
  104. package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
  105. package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
  106. package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
  107. package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
  108. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
  109. package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
  110. package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
  111. package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
  112. package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
  113. package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
  114. package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
  115. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
  116. package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
  117. package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
  118. package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
  119. package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
  120. package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
  121. package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
  122. package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
  123. package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
  124. package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
  125. package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
  126. package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
  127. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
  128. package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
  129. package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
  130. package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
  131. package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
  132. package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
  133. package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
  134. package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
  135. package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
  136. package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
  137. package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
  138. package/src/components/data-display/Progress/Progress.styles.ts +101 -0
  139. package/src/components/data-display/Progress/Progress.tsx +185 -213
  140. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
  141. package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
  142. package/src/components/data-display/StatCard/StatCard.md +117 -0
  143. package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
  144. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
  145. package/src/components/data-display/StatCard/StatCard.tsx +236 -0
  146. package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
  147. package/src/components/data-display/StatCard/index.ts +7 -0
  148. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
  149. package/src/components/data-display/Stepper/Stepper.tsx +1 -1
  150. package/src/components/data-display/Table/Table.helpers.ts +31 -0
  151. package/src/components/data-display/Table/Table.hooks.ts +161 -0
  152. package/src/components/data-display/Table/Table.stories.tsx +3 -1
  153. package/src/components/data-display/Table/Table.styles.ts +223 -0
  154. package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
  155. package/src/components/data-display/Table/Table.tsx +291 -748
  156. package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
  157. package/src/components/data-display/Table/TableParts.tsx +298 -0
  158. package/src/components/data-display/Table/TableStates.tsx +21 -0
  159. package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
  160. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
  161. package/src/components/data-display/Timeline/Timeline.tsx +23 -284
  162. package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
  163. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
  164. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
  165. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
  166. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
  167. package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
  168. package/src/components/data-display/index.ts +1 -0
  169. package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
  170. package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
  171. package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
  172. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
  173. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
  174. package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
  175. package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
  176. package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
  177. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
  178. package/src/components/feedback/ConfirmAction/index.ts +16 -0
  179. package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
  180. package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
  181. package/src/components/feedback/Dialog/Dialog.tsx +26 -1
  182. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
  183. package/src/components/feedback/Modal/Modal.md +1 -1
  184. package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
  185. package/src/components/feedback/Sonner/Sonner.tsx +104 -288
  186. package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
  187. package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
  188. package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
  189. package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
  190. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
  191. package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
  192. package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
  193. package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
  194. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
  195. package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
  196. package/src/components/feedback/Toast/Toast.tsx +92 -81
  197. package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
  198. package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
  199. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
  200. package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
  201. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
  202. package/src/components/feedback/index.ts +1 -0
  203. package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
  204. package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
  205. package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
  206. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
  207. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
  208. package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
  209. package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
  210. package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
  211. package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
  212. package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
  213. package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
  214. package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
  215. package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
  216. package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
  217. package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
  218. package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
  219. package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
  220. package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
  221. package/src/components/form/Button/Button.helpers.ts +36 -0
  222. package/src/components/form/Button/Button.stories.tsx +4 -1
  223. package/src/components/form/Button/Button.styles.ts +225 -0
  224. package/src/components/form/Button/Button.test.stories.tsx +25 -21
  225. package/src/components/form/Button/Button.tsx +45 -252
  226. package/src/components/form/Calendar/Calendar.dates.ts +100 -0
  227. package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
  228. package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
  229. package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
  230. package/src/components/form/Calendar/Calendar.selection.ts +156 -0
  231. package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
  232. package/src/components/form/Calendar/Calendar.tsx +63 -551
  233. package/src/components/form/Calendar/Calendar.types.ts +16 -0
  234. package/src/components/form/Calendar/CalendarDay.tsx +127 -0
  235. package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
  236. package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
  237. package/src/components/form/CepField/CepField.md +138 -0
  238. package/src/components/form/CepField/CepField.stories.tsx +138 -0
  239. package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
  240. package/src/components/form/CepField/CepField.tsx +211 -0
  241. package/src/components/form/CepField/CepField.types.ts +73 -0
  242. package/src/components/form/CepField/index.ts +2 -0
  243. package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
  244. package/src/components/form/Checkbox/Checkbox.tsx +19 -16
  245. package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
  246. package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
  247. package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
  248. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
  249. package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
  250. package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
  251. package/src/components/form/Command/Command.hooks.ts +207 -0
  252. package/src/components/form/Command/Command.parts.tsx +58 -0
  253. package/src/components/form/Command/Command.styles.ts +83 -0
  254. package/src/components/form/Command/Command.tsx +61 -270
  255. package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
  256. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
  257. package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
  258. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
  259. package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
  260. package/src/components/form/HeaderButton/index.ts +2 -0
  261. package/src/components/form/Input/Input.stories.tsx +5 -1
  262. package/src/components/form/Input/Input.styles.ts +176 -0
  263. package/src/components/form/Input/Input.test.stories.tsx +30 -23
  264. package/src/components/form/Input/Input.tsx +71 -211
  265. package/src/components/form/Input/Input.types.ts +11 -0
  266. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
  267. package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
  268. package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
  269. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
  270. package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
  271. package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
  272. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
  273. package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
  274. package/src/components/form/PhoneInput/countries.ts +54 -0
  275. package/src/components/form/PhoneInput/phone.ts +47 -0
  276. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
  277. package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
  278. package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
  279. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
  280. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
  281. package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
  282. package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
  283. package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
  284. package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
  285. package/src/components/form/Select/Select.test.stories.tsx +35 -27
  286. package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
  287. package/src/components/form/Switch/Switch.md +2 -1
  288. package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
  289. package/src/components/form/Textarea/Textarea.styles.ts +143 -0
  290. package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
  291. package/src/components/form/Textarea/Textarea.tsx +114 -535
  292. package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
  293. package/src/components/form/Toggle/Toggle.styles.ts +211 -0
  294. package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
  295. package/src/components/form/Toggle/Toggle.tsx +10 -185
  296. package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
  297. package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
  298. package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
  299. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
  300. package/src/components/form/UploadButton/UploadButton.tsx +76 -305
  301. package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
  302. package/src/components/form/index.ts +1 -0
  303. package/src/components/form/total-form/Fields/CepField.tsx +143 -0
  304. package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
  305. package/src/components/form/total-form/Fields/index.ts +4 -0
  306. package/src/components/form/total-form/FormContainer.tsx +3 -3
  307. package/src/components/form/total-form/SubmitButton.tsx +25 -2
  308. package/src/components/form/total-form/total-form.test.tsx +118 -1
  309. package/src/components/layout/Accordion/Accordion.md +1 -1
  310. package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
  311. package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
  312. package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
  313. package/src/components/layout/Accordion/Accordion.tsx +2 -75
  314. package/src/components/layout/Card/Card.helpers.ts +30 -0
  315. package/src/components/layout/Card/Card.stories.tsx +9 -11
  316. package/src/components/layout/Card/Card.styles.ts +182 -0
  317. package/src/components/layout/Card/Card.test.stories.tsx +46 -46
  318. package/src/components/layout/Card/Card.tsx +24 -241
  319. package/src/components/layout/CardGrid/CardGrid.md +96 -0
  320. package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
  321. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
  322. package/src/components/layout/CardGrid/index.ts +2 -0
  323. package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
  324. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
  325. package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
  326. package/src/components/layout/Container/Container.test.stories.tsx +11 -16
  327. package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
  328. package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
  329. package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
  330. package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
  331. package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
  332. package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
  333. package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
  334. package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
  335. package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
  336. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
  337. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
  338. package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
  339. package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
  340. package/src/components/layout/Dashboard/index.ts +2 -1
  341. package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
  342. package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
  343. package/src/components/layout/Drawer/Drawer.tsx +67 -53
  344. package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
  345. package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
  346. package/src/components/layout/Resizable/Resizable.tsx +38 -153
  347. package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
  348. package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
  349. package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
  350. package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
  351. package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
  352. package/src/components/layout/Separator/Separator.styles.ts +128 -0
  353. package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
  354. package/src/components/layout/Separator/Separator.tsx +6 -132
  355. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
  356. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
  357. package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
  358. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
  359. package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
  360. package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
  361. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
  362. package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
  363. package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
  364. package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
  365. package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
  366. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
  367. package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
  368. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
  369. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
  370. package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
  371. package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
  372. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
  373. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
  374. package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
  375. package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
  376. package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
  377. package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
  378. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
  379. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
  380. package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
  381. package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
  382. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
  383. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
  384. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
  385. package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
  386. package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
  387. package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
  388. package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
  389. package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
  390. package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
  391. package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
  392. package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
  393. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
  394. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
  395. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
  396. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
  397. package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
  398. package/src/components/navigation/Pagination/Pagination.md +2 -1
  399. package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
  400. package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
  401. package/src/components/navigation/Pagination/Pagination.tsx +178 -313
  402. package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
  403. package/src/components/navigation/Tabs/Tabs.md +3 -1
  404. package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
  405. package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
  406. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
  407. package/src/components/navigation/Tabs/Tabs.tsx +192 -361
  408. package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
  409. package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
  410. package/src/components/typography/Code/Code.test.stories.tsx +5 -5
  411. package/src/components/typography/Code/Code.tsx +74 -49
  412. package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
  413. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
  414. package/src/components/typography/Text/Text.tsx +84 -77
  415. package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
  416. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
  417. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
  418. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
  419. package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
  420. package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
  421. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
  422. package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
  423. package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
  424. package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
  425. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
  426. package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
  427. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
  428. package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
  429. package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
  430. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
  431. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
  432. package/src/components/utility/InstallPrompt/index.ts +10 -0
  433. package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
  434. package/src/components/utility/Transition/Transition.helpers.ts +40 -0
  435. package/src/components/utility/Transition/Transition.tsx +17 -68
  436. package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
  437. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
  438. package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
  439. package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
  440. package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
  441. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
  442. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
  443. package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
  444. package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
  445. package/src/components/utility/index.ts +1 -0
  446. package/src/mui/Link.ts +2 -0
  447. package/src/utils/index.ts +2 -1
  448. package/src/utils/viewport.ts +65 -0
@@ -0,0 +1,182 @@
1
+ import { alpha, keyframes } from '@mui/material';
2
+ import type { CSSObject, Theme } from '@mui/material';
3
+
4
+ import type { CardProps, CardVariant } from './Card.types';
5
+
6
+ type BorderRadius = NonNullable<CardProps['borderRadius']>;
7
+
8
+ // Define pulse animation
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
+ const borderRadiusFor = (theme: Theme, radius: BorderRadius): number | string => {
25
+ switch (radius) {
26
+ case 'none':
27
+ return 0;
28
+ case 'sm':
29
+ return theme.spacing(0.5);
30
+ case 'lg':
31
+ return theme.spacing(2);
32
+ case 'xl':
33
+ return theme.spacing(3);
34
+ case 'full':
35
+ return '50%';
36
+ default:
37
+ return theme.spacing(1);
38
+ }
39
+ };
40
+
41
+ const pulseSurface = (theme: Theme): CSSObject => ({
42
+ position: 'relative',
43
+ overflow: 'visible',
44
+ '&::after': {
45
+ content: '""',
46
+ position: 'absolute',
47
+ top: '0',
48
+ left: '0',
49
+ right: '0',
50
+ bottom: '0',
51
+ borderRadius: 'inherit',
52
+ backgroundColor: theme.palette.primary.main,
53
+ opacity: 0.3,
54
+ animation: `${pulseAnimation} 2s infinite`,
55
+ pointerEvents: 'none',
56
+ zIndex: -1,
57
+ },
58
+ });
59
+
60
+ const neumorphicShadow = (theme: Theme, lifted: boolean): string => {
61
+ const spread = lifted ? 12 : 8;
62
+ const blur = spread * 2;
63
+
64
+ return theme.palette.mode === 'dark'
65
+ ? `${spread}px ${spread}px ${blur}px ${alpha(theme.palette.common.black, 0.3)}, -${spread}px -${spread}px ${blur}px ${alpha(theme.palette.common.white, 0.1)}`
66
+ : `${spread}px ${spread}px ${blur}px ${alpha(theme.palette.grey[400], lifted ? 0.3 : 0.2)}, -${spread}px -${spread}px ${blur}px ${alpha(theme.palette.common.white, lifted ? 0.9 : 0.8)}`;
67
+ };
68
+
69
+ const sectionBackground = (theme: Theme, lifted: boolean): string => {
70
+ if (theme.palette.mode === 'dark') {
71
+ return lifted ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.2)';
72
+ }
73
+ return lifted ? 'rgba(0, 0, 0, 0.04)' : 'rgba(0, 0, 0, 0.02)';
74
+ };
75
+
76
+ interface Surface {
77
+ surface: CSSObject;
78
+ hover: CSSObject;
79
+ }
80
+
81
+ /**
82
+ * Each variant contributes a resting surface and whatever it changes on hover.
83
+ * Only `interactive` cards actually move on hover, so the hover half of every
84
+ * variant reads back its resting value when `interactive` is off.
85
+ */
86
+ const VARIANT_SURFACES: Record<CardVariant, (theme: Theme, lifted: boolean) => Surface> = {
87
+ elevated: (_theme, lifted) => ({
88
+ surface: { elevation: 4 } as CSSObject,
89
+ hover: { elevation: lifted ? 8 : 4 } as CSSObject,
90
+ }),
91
+ outlined: (theme, lifted) => ({
92
+ surface: { border: `1px solid ${theme.palette.divider}`, boxShadow: 'none' },
93
+ hover: { borderColor: lifted ? theme.palette.primary.main : theme.palette.divider },
94
+ }),
95
+ glass: (theme, lifted) => ({
96
+ surface: {
97
+ backgroundColor: alpha(theme.palette.background.paper, 0.1),
98
+ backdropFilter: 'blur(20px)',
99
+ border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
100
+ boxShadow: `0 8px 32px ${alpha(theme.palette.common.black, 0.1)}`,
101
+ },
102
+ hover: {
103
+ backgroundColor: alpha(theme.palette.background.paper, lifted ? 0.15 : 0.1),
104
+ border: `1px solid ${alpha(theme.palette.primary.main, 0.3)}`,
105
+ },
106
+ }),
107
+ gradient: (theme, lifted) => ({
108
+ surface: {
109
+ background: cardGradient(theme, false),
110
+ color: theme.palette.primary.contrastText,
111
+ boxShadow: `0 4px 20px ${alpha(theme.palette.primary.main, 0.3)}`,
112
+ },
113
+ hover: { background: cardGradient(theme, lifted) },
114
+ }),
115
+ neumorphic: (theme, lifted) => ({
116
+ surface: {
117
+ backgroundColor:
118
+ theme.palette.mode === 'dark' ? theme.palette.grey[800] : theme.palette.grey[100],
119
+ boxShadow: neumorphicShadow(theme, false),
120
+ border: 'none',
121
+ },
122
+ hover: { boxShadow: neumorphicShadow(theme, lifted) },
123
+ }),
124
+ section: (theme, lifted) => ({
125
+ surface: {
126
+ backgroundColor: sectionBackground(theme, false),
127
+ border: 'none',
128
+ boxShadow: 'none',
129
+ },
130
+ hover: { backgroundColor: sectionBackground(theme, lifted) },
131
+ }),
132
+ };
133
+
134
+ const cardGradient = (theme: Theme, dark: boolean): string =>
135
+ dark
136
+ ? `linear-gradient(135deg, ${theme.palette.primary.dark}, ${theme.palette.secondary.dark})`
137
+ : `linear-gradient(135deg, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`;
138
+
139
+ // An unrecognised variant gets the shared base only — no interactive, glow or
140
+ // pulse decoration, as before.
141
+ const variantSurface = (
142
+ theme: Theme,
143
+ variant: CardVariant,
144
+ interactive: boolean,
145
+ ): Surface | null => VARIANT_SURFACES[variant]?.(theme, interactive) ?? null;
146
+
147
+ interface CardStyleArgs {
148
+ variant: CardVariant;
149
+ interactive: boolean;
150
+ glow: boolean;
151
+ pulse: boolean;
152
+ borderRadius: BorderRadius;
153
+ }
154
+
155
+ export const cardStyles = (
156
+ theme: Theme,
157
+ { variant, interactive, glow, pulse, borderRadius }: CardStyleArgs,
158
+ ): CSSObject => {
159
+ const base: CSSObject = {
160
+ borderRadius: borderRadiusFor(theme, borderRadius),
161
+ transition: theme.transitions.create(
162
+ ['box-shadow', 'transform', 'border-color', 'background-color'],
163
+ { duration: theme.transitions.duration.standard },
164
+ ),
165
+ };
166
+
167
+ const chosen = variantSurface(theme, variant, interactive);
168
+ if (!chosen) return base;
169
+
170
+ return {
171
+ ...base,
172
+ ...(interactive && { cursor: 'pointer', '&:active': { transform: 'translateY(0)' } }),
173
+ ...(glow && { boxShadow: `0 0 20px ${alpha(theme.palette.primary.main, 0.3)}` }),
174
+ ...(pulse && pulseSurface(theme)),
175
+ ...chosen.surface,
176
+ '&:hover': {
177
+ ...(interactive && { transform: 'translateY(-2px)' }),
178
+ ...(glow && { boxShadow: `0 0 30px ${alpha(theme.palette.primary.main, 0.4)}` }),
179
+ ...chosen.hover,
180
+ },
181
+ };
182
+ };
@@ -1,4 +1,6 @@
1
- import { Favorite, MoreVert, Share } from '@mui/icons-material';
1
+ import Favorite from '@mui/icons-material/Favorite';
2
+ import MoreVert from '@mui/icons-material/MoreVert';
3
+ import Share from '@mui/icons-material/Share';
2
4
  import { Avatar, Box,Button, Chip, IconButton, Stack, Typography } from '@mui/material';
3
5
  import type { Meta, StoryObj } from '@storybook/react-vite';
4
6
  import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
@@ -538,19 +540,21 @@ export const KeyboardNavigationTest: Story = {
538
540
  const firstCard = canvas.getByTestId('interactive-card-1');
539
541
  const secondCard = canvas.getByTestId('interactive-card-2');
540
542
 
541
- // Focus first card
542
- firstCard.focus();
543
- await expect(firstCard).toHaveFocus();
543
+ // Tab in from the body rather than calling .focus() — that made the first
544
+ // assertion a tautology about the DOM honouring focus(), not about this
545
+ // card being the first tab stop.
546
+ await userEvent.tab();
547
+ await waitFor(() => expect(firstCard).toHaveFocus());
544
548
 
545
549
  // Tab to next card
546
550
  await userEvent.tab();
547
- await expect(secondCard).toHaveFocus();
551
+ await waitFor(() => expect(secondCard).toHaveFocus());
548
552
  });
549
553
 
550
554
  await step('Tab navigation backward', async () => {
551
555
  await userEvent.tab({ shift: true });
552
556
  const firstCard = canvas.getByTestId('interactive-card-1');
553
- await expect(firstCard).toHaveFocus();
557
+ await waitFor(() => expect(firstCard).toHaveFocus());
554
558
  });
555
559
  },
556
560
  };
@@ -813,10 +817,15 @@ export const ScreenReaderTest: Story = {
813
817
 
814
818
  await step('Keyboard activation', async () => {
815
819
  const accessibleCard = canvas.getByTestId('accessible-card');
816
- accessibleCard.focus();
820
+
821
+ // Tab in rather than calling .focus(), so this covers the card actually
822
+ // being reachable by keyboard, and assert focus survives Enter — pressing
823
+ // Enter on a card must not send focus somewhere else.
824
+ await userEvent.tab();
825
+ await waitFor(() => expect(accessibleCard).toHaveFocus());
826
+
817
827
  await userEvent.keyboard('{Enter}');
818
- // Card should respond to Enter key when focused
819
- await expect(accessibleCard).toHaveFocus();
828
+ await waitFor(() => expect(accessibleCard).toHaveFocus());
820
829
  });
821
830
  },
822
831
  };
@@ -847,23 +856,26 @@ export const FocusManagementTest: Story = {
847
856
  const card1 = canvas.getByTestId('focus-card-1');
848
857
  const innerButton1 = canvas.getByTestId('inner-button-1');
849
858
 
850
- // Focus card
851
- card1.focus();
852
- // In testing environment, focus might immediately move to the button
853
- // Check that either the card or its inner button has focus
854
- const hasFocus = document.activeElement === card1 || document.activeElement === innerButton1;
855
- await expect(hasFocus).toBeTruthy();
859
+ // Tab in from the body. contains() is true for the node itself, so this
860
+ // holds whether the card is its own tab stop or focus goes straight to the
861
+ // button inside it.
862
+ await userEvent.tab();
863
+ await waitFor(() => expect(card1.contains(document.activeElement)).toBeTruthy());
856
864
 
857
- // If card has focus, tab to inner element
858
- if (document.activeElement === card1) {
865
+ // Whether the card is a tab stop of its own is a Card implementation
866
+ // detail, so step forward only if focus is not already on the button. The
867
+ // previous version wrapped the assertion itself in that condition, which
868
+ // meant one of its two paths asserted nothing at all.
869
+ if (document.activeElement !== innerButton1) {
859
870
  await userEvent.tab();
860
- await expect(innerButton1).toHaveFocus();
861
871
  }
872
+ await waitFor(() => expect(innerButton1).toHaveFocus());
862
873
  });
863
874
 
864
875
  await step('Focus visibility', async () => {
865
876
  const card1 = canvas.getByTestId('focus-card-1');
866
- card1.focus();
877
+ // Focus is already inside card 1 from the previous step.
878
+ await waitFor(() => expect(card1.contains(document.activeElement)).toBeTruthy());
867
879
 
868
880
  // Check for focus ring (outline)
869
881
  const styles = window.getComputedStyle(card1);
@@ -873,21 +885,15 @@ export const FocusManagementTest: Story = {
873
885
  await step('Focus restoration', async () => {
874
886
  const card1 = canvas.getByTestId('focus-card-1');
875
887
  const card2 = canvas.getByTestId('focus-card-2');
876
- const innerButton2 = canvas.getByTestId('inner-button-2');
877
-
878
- // Focus on second card or its button
879
- card2.focus();
880
- // Check that focus moved to second card area
881
- const secondCardHasFocus =
882
- document.activeElement === card2 || document.activeElement === innerButton2;
883
- await expect(secondCardHasFocus).toBeTruthy();
884
-
885
- // Focus back on first card
886
- card1.focus();
887
- // Check that focus moved back to first card area
888
- const firstCardArea = canvas.getByTestId('focus-card-1');
889
- const focusIsInFirstCard = firstCardArea.contains(document.activeElement);
890
- await expect(focusIsInFirstCard).toBeTruthy();
888
+
889
+ // Continue the journey forward out of card 1 and into card 2...
890
+ await userEvent.tab();
891
+ await waitFor(() => expect(card2.contains(document.activeElement)).toBeTruthy());
892
+
893
+ // ...then shift-tab back, which is the restoration this step is named for.
894
+ // Calling .focus() on each card in turn only proved that focus() works.
895
+ await userEvent.tab({ shift: true });
896
+ await waitFor(() => expect(card1.contains(document.activeElement)).toBeTruthy());
891
897
  });
892
898
  },
893
899
  };
@@ -1011,7 +1017,6 @@ export const PerformanceTest: Story = {
1011
1017
  },
1012
1018
  play: async ({ canvasElement, step }) => {
1013
1019
  const canvas = within(canvasElement);
1014
- const startTime = Date.now();
1015
1020
 
1016
1021
  await step('Render performance', async () => {
1017
1022
  const container = canvas.getByTestId('performance-container');
@@ -1021,25 +1026,20 @@ export const PerformanceTest: Story = {
1021
1026
  const cards = canvas.getAllByTestId(/^perf-card-/);
1022
1027
  await expect(cards).toHaveLength(50);
1023
1028
 
1024
- const renderTime = Date.now() - startTime;
1025
- // Performance metric logged to test report
1026
-
1027
- // Expect reasonable render time (under 1000ms)
1028
- await expect(renderTime).toBeLessThan(1000);
1029
+ // No wall-clock budget here: the queries above run against DOM that has
1030
+ // already rendered, so a duration around them measures query time, and any
1031
+ // millisecond threshold is really a bet on how loaded the CI runner is.
1029
1032
  });
1030
1033
 
1031
1034
  await step('Interaction performance', async () => {
1032
1035
  const interactiveCard = canvas.getByTestId('perf-card-0');
1033
- const interactionStart = Date.now();
1034
1036
 
1035
1037
  await userEvent.hover(interactiveCard);
1036
1038
  await userEvent.unhover(interactiveCard);
1037
1039
 
1038
- const interactionTime = Date.now() - interactionStart;
1039
- // Interaction metric logged to test report
1040
-
1041
- // Expect quick interaction (under 100ms)
1042
- await expect(interactionTime).toBeLessThan(100);
1040
+ // The card survives a hover/unhover cycle. Timing it was a runner-speed
1041
+ // assertion, not a component one.
1042
+ await expect(interactiveCard).toBeInTheDocument();
1043
1043
  });
1044
1044
 
1045
1045
  await step('Memory efficiency', async () => {
@@ -1,5 +1,4 @@
1
1
  import {
2
- alpha,
3
2
  Box,
4
3
  Card as MuiCard,
5
4
  CardActions as MuiCardActions,
@@ -7,11 +6,12 @@ import {
7
6
  CardHeader as MuiCardHeader,
8
7
  CardMedia as MuiCardMedia,
9
8
  CircularProgress,
10
- keyframes,
11
9
  useTheme,
12
10
  } from '@mui/material';
13
11
  import React from 'react';
14
12
 
13
+ import { resolveCardProps } from './Card.helpers';
14
+ import { cardStyles } from './Card.styles';
15
15
  import type {
16
16
  CardActionsProps,
17
17
  CardContentProps,
@@ -20,31 +20,15 @@ import type {
20
20
  CardProps,
21
21
  } from './Card.types';
22
22
 
23
- // Define pulse animation
24
- const pulseAnimation = keyframes`
25
- 0% {
26
- box-shadow: 0 0 0 0 currentColor;
27
- opacity: 1;
28
- }
29
- 70% {
30
- box-shadow: 0 0 0 15px currentColor;
31
- opacity: 0;
32
- }
33
- 100% {
34
- box-shadow: 0 0 0 0 currentColor;
35
- opacity: 0;
36
- }
37
- `;
38
-
39
- export const Card: React.FC<CardProps> = (props) => {
23
+ export const Card: React.FC<CardProps> = (rawProps) => {
40
24
  const {
41
25
  children,
42
- variant = 'elevated',
43
- interactive = false,
44
- glow = false,
45
- pulse = false,
46
- borderRadius = 'md',
47
- loading = false,
26
+ variant,
27
+ interactive,
28
+ glow,
29
+ pulse,
30
+ borderRadius,
31
+ loading,
48
32
  onClick,
49
33
  onFocus,
50
34
  onBlur,
@@ -52,231 +36,30 @@ export const Card: React.FC<CardProps> = (props) => {
52
36
  dataTestId,
53
37
  // These props are reserved for future implementation but need to be extracted
54
38
  // to prevent them from being passed to the underlying MuiCard
55
- expandable,
56
- expanded,
57
- onExpandToggle,
58
- entranceAnimation,
59
- animationDelay,
60
- skeleton,
61
- hoverScale,
39
+ expandable: _expandable,
40
+ expanded: _expanded,
41
+ onExpandToggle: _onExpandToggle,
42
+ entranceAnimation: _entranceAnimation,
43
+ animationDelay: _animationDelay,
44
+ skeleton: _skeleton,
45
+ hoverScale: _hoverScale,
62
46
  ...restProps
63
- } = props;
64
-
65
- // Void unused props to satisfy linter
66
- void expandable;
67
- void expanded;
68
- void onExpandToggle;
69
- void entranceAnimation;
70
- void animationDelay;
71
- void skeleton;
72
- void hoverScale;
47
+ } = resolveCardProps(rawProps);
73
48
 
74
49
  const theme = useTheme();
75
50
 
76
- const getBorderRadius = () => {
77
- switch (borderRadius) {
78
- case 'none':
79
- return 0;
80
- case 'sm':
81
- return theme.spacing(0.5);
82
- case 'md':
83
- return theme.spacing(1);
84
- case 'lg':
85
- return theme.spacing(2);
86
- case 'xl':
87
- return theme.spacing(3);
88
- case 'full':
89
- return '50%';
90
- default:
91
- return theme.spacing(1);
92
- }
93
- };
94
-
95
- const getVariantStyles = () => {
96
- const baseStyles = {
97
- borderRadius: getBorderRadius(),
98
- transition: theme.transitions.create(
99
- ['box-shadow', 'transform', 'border-color', 'background-color'],
100
- {
101
- duration: theme.transitions.duration.standard,
102
- },
103
- ),
104
- };
105
-
106
- const interactiveStyles = interactive
107
- ? {
108
- cursor: 'pointer',
109
- '&:hover': {
110
- transform: 'translateY(-2px)',
111
- },
112
- '&:active': {
113
- transform: 'translateY(0)',
114
- },
115
- }
116
- : {};
117
-
118
- const glowStyles = glow
119
- ? {
120
- boxShadow: `0 0 20px ${alpha(theme.palette.primary.main, 0.3)}`,
121
- '&:hover': {
122
- boxShadow: `0 0 30px ${alpha(theme.palette.primary.main, 0.4)}`,
123
- },
124
- }
125
- : {};
126
-
127
- const pulseStyles = pulse
128
- ? {
129
- position: 'relative',
130
- overflow: 'visible',
131
- '&::after': {
132
- content: '""',
133
- position: 'absolute',
134
- top: '0',
135
- left: '0',
136
- right: '0',
137
- bottom: '0',
138
- borderRadius: 'inherit',
139
- backgroundColor: theme.palette.primary.main,
140
- opacity: 0.3,
141
- animation: `${pulseAnimation} 2s infinite`,
142
- pointerEvents: 'none',
143
- zIndex: -1,
144
- },
145
- }
146
- : {};
147
-
148
- switch (variant) {
149
- case 'elevated':
150
- return {
151
- ...baseStyles,
152
- ...interactiveStyles,
153
- ...glowStyles,
154
- ...pulseStyles,
155
- elevation: 4,
156
- '&:hover': {
157
- ...interactiveStyles['&:hover'],
158
- ...glowStyles['&:hover'],
159
- elevation: interactive ? 8 : 4,
160
- },
161
- };
162
-
163
- case 'outlined':
164
- return {
165
- ...baseStyles,
166
- ...interactiveStyles,
167
- ...glowStyles,
168
- ...pulseStyles,
169
- border: `1px solid ${theme.palette.divider}`,
170
- boxShadow: 'none',
171
- '&:hover': {
172
- ...interactiveStyles['&:hover'],
173
- ...glowStyles['&:hover'],
174
- borderColor: interactive ? theme.palette.primary.main : theme.palette.divider,
175
- },
176
- };
177
-
178
- case 'glass':
179
- return {
180
- ...baseStyles,
181
- ...interactiveStyles,
182
- ...glowStyles,
183
- ...pulseStyles,
184
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
185
- backdropFilter: 'blur(20px)',
186
- border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
187
- boxShadow: `0 8px 32px ${alpha(theme.palette.common.black, 0.1)}`,
188
- '&:hover': {
189
- ...interactiveStyles['&:hover'],
190
- ...glowStyles['&:hover'],
191
- backgroundColor: interactive
192
- ? alpha(theme.palette.background.paper, 0.15)
193
- : alpha(theme.palette.background.paper, 0.1),
194
- border: `1px solid ${alpha(theme.palette.primary.main, 0.3)}`,
195
- },
196
- };
197
-
198
- case 'gradient':
199
- return {
200
- ...baseStyles,
201
- ...interactiveStyles,
202
- ...glowStyles,
203
- ...pulseStyles,
204
- background: `linear-gradient(135deg, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`,
205
- color: theme.palette.primary.contrastText,
206
- boxShadow: `0 4px 20px ${alpha(theme.palette.primary.main, 0.3)}`,
207
- '&:hover': {
208
- ...interactiveStyles['&:hover'],
209
- ...glowStyles['&:hover'],
210
- background: interactive
211
- ? `linear-gradient(135deg, ${theme.palette.primary.dark}, ${theme.palette.secondary.dark})`
212
- : `linear-gradient(135deg, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`,
213
- },
214
- };
215
-
216
- case 'neumorphic': {
217
- const neumorphicBg =
218
- theme.palette.mode === 'dark' ? theme.palette.grey[800] : theme.palette.grey[100];
219
-
220
- return {
221
- ...baseStyles,
222
- ...interactiveStyles,
223
- ...pulseStyles,
224
- backgroundColor: neumorphicBg,
225
- boxShadow:
226
- theme.palette.mode === 'dark'
227
- ? `8px 8px 16px ${alpha(theme.palette.common.black, 0.3)}, -8px -8px 16px ${alpha(theme.palette.common.white, 0.1)}`
228
- : `8px 8px 16px ${alpha(theme.palette.grey[400], 0.2)}, -8px -8px 16px ${alpha(theme.palette.common.white, 0.8)}`,
229
- border: 'none',
230
- '&:hover': {
231
- ...interactiveStyles['&:hover'],
232
- ...glowStyles['&:hover'],
233
- boxShadow: interactive
234
- ? theme.palette.mode === 'dark'
235
- ? `12px 12px 24px ${alpha(theme.palette.common.black, 0.3)}, -12px -12px 24px ${alpha(theme.palette.common.white, 0.1)}`
236
- : `12px 12px 24px ${alpha(theme.palette.grey[400], 0.3)}, -12px -12px 24px ${alpha(theme.palette.common.white, 0.9)}`
237
- : theme.palette.mode === 'dark'
238
- ? `8px 8px 16px ${alpha(theme.palette.common.black, 0.3)}, -8px -8px 16px ${alpha(theme.palette.common.white, 0.1)}`
239
- : `8px 8px 16px ${alpha(theme.palette.grey[400], 0.2)}, -8px -8px 16px ${alpha(theme.palette.common.white, 0.8)}`,
240
- },
241
- };
242
- }
243
-
244
- case 'section':
245
- return {
246
- ...baseStyles,
247
- ...interactiveStyles,
248
- ...glowStyles,
249
- ...pulseStyles,
250
- backgroundColor:
251
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.02)',
252
- border: 'none',
253
- boxShadow: 'none',
254
- '&:hover': {
255
- ...interactiveStyles['&:hover'],
256
- ...glowStyles['&:hover'],
257
- backgroundColor: interactive
258
- ? theme.palette.mode === 'dark'
259
- ? 'rgba(0, 0, 0, 0.25)'
260
- : 'rgba(0, 0, 0, 0.04)'
261
- : theme.palette.mode === 'dark'
262
- ? 'rgba(0, 0, 0, 0.2)'
263
- : 'rgba(0, 0, 0, 0.02)',
264
- },
265
- };
266
-
267
- default:
268
- return baseStyles;
269
- }
270
- };
51
+ // A loading card is inert: no handlers fire and pointer events are off, so a
52
+ // half-rendered card cannot be clicked through.
53
+ const idle = !loading;
271
54
 
272
55
  return (
273
56
  <MuiCard
274
57
  data-testid={dataTestId || 'card'}
275
- onClick={!loading ? onClick : undefined}
276
- onFocus={!loading ? onFocus : undefined}
277
- onBlur={!loading ? onBlur : undefined}
58
+ onClick={idle ? onClick : undefined}
59
+ onFocus={idle ? onFocus : undefined}
60
+ onBlur={idle ? onBlur : undefined}
278
61
  sx={{
279
- ...getVariantStyles(),
62
+ ...cardStyles(theme, { variant, interactive, glow, pulse, borderRadius }),
280
63
  position: 'relative',
281
64
  opacity: loading ? 0.6 : 1,
282
65
  pointerEvents: loading ? 'none' : 'auto',