@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
@@ -8,6 +8,7 @@ import {
8
8
  } from '@mui/material';
9
9
  import React from 'react';
10
10
 
11
+ import { transitionDuration, transitionEasing } from './Transition.helpers';
11
12
  import type { CustomTransitionProps } from './Transition.types';
12
13
 
13
14
  export const Transition: React.FC<CustomTransitionProps> = ({
@@ -22,58 +23,10 @@ export const Transition: React.FC<CustomTransitionProps> = ({
22
23
  }) => {
23
24
  const theme = useTheme();
24
25
 
25
- const getDuration = () => {
26
- if (typeof duration === 'number') {
27
- return duration;
28
- }
29
-
30
- if (typeof duration === 'object') {
31
- return duration;
32
- }
33
-
34
- // Default durations based on variant
35
- switch (variant) {
36
- case 'fade':
37
- return theme.transitions.duration.shorter;
38
- case 'slide':
39
- return theme.transitions.duration.enteringScreen;
40
- case 'scale':
41
- case 'grow':
42
- case 'zoom':
43
- return theme.transitions.duration.shorter;
44
- case 'collapse':
45
- return theme.transitions.duration.standard;
46
- default:
47
- return theme.transitions.duration.standard;
48
- }
49
- };
50
-
51
- const getEasing = () => {
52
- if (typeof easing === 'string') {
53
- return easing;
54
- }
55
-
56
- if (typeof easing === 'object') {
57
- return easing;
58
- }
59
-
60
- // Default easing based on variant
61
- switch (variant) {
62
- case 'slide':
63
- return theme.transitions.easing.easeOut;
64
- case 'scale':
65
- case 'grow':
66
- case 'zoom':
67
- return theme.transitions.easing.easeInOut;
68
- default:
69
- return theme.transitions.easing.easeInOut;
70
- }
71
- };
72
-
73
26
  const transitionProps = {
74
27
  in: inProp,
75
- timeout: getDuration(),
76
- easing: getEasing(),
28
+ timeout: transitionDuration(theme, variant, duration),
29
+ easing: transitionEasing(theme, variant, easing),
77
30
  unmountOnExit: true,
78
31
  style: {
79
32
  transitionDelay: `${delay}ms`,
@@ -81,22 +34,26 @@ export const Transition: React.FC<CustomTransitionProps> = ({
81
34
  ...props,
82
35
  };
83
36
 
37
+ // Every variant wraps the same three-deep structure; only the MUI transition
38
+ // component differs.
39
+ const content = (
40
+ <div data-testid="transition-element">
41
+ <div data-testid="transition-content">{children}</div>
42
+ </div>
43
+ );
44
+
84
45
  switch (variant) {
85
46
  case 'fade':
86
47
  return (
87
48
  <Fade {...transitionProps} data-testid="transition-wrapper">
88
- <div data-testid="transition-element">
89
- <div data-testid="transition-content">{children}</div>
90
- </div>
49
+ {content}
91
50
  </Fade>
92
51
  );
93
52
 
94
53
  case 'slide':
95
54
  return (
96
55
  <Slide direction={direction} {...transitionProps} data-testid="transition-wrapper">
97
- <div data-testid="transition-element">
98
- <div data-testid="transition-content">{children}</div>
99
- </div>
56
+ {content}
100
57
  </Slide>
101
58
  );
102
59
 
@@ -104,36 +61,28 @@ export const Transition: React.FC<CustomTransitionProps> = ({
104
61
  case 'grow':
105
62
  return (
106
63
  <Grow {...transitionProps} data-testid="transition-wrapper">
107
- <div data-testid="transition-element">
108
- <div data-testid="transition-content">{children}</div>
109
- </div>
64
+ {content}
110
65
  </Grow>
111
66
  );
112
67
 
113
68
  case 'collapse':
114
69
  return (
115
70
  <Collapse {...transitionProps} data-testid="transition-wrapper">
116
- <div data-testid="transition-element">
117
- <div data-testid="transition-content">{children}</div>
118
- </div>
71
+ {content}
119
72
  </Collapse>
120
73
  );
121
74
 
122
75
  case 'zoom':
123
76
  return (
124
77
  <Zoom {...transitionProps} data-testid="transition-wrapper">
125
- <div data-testid="transition-element">
126
- <div data-testid="transition-content">{children}</div>
127
- </div>
78
+ {content}
128
79
  </Zoom>
129
80
  );
130
81
 
131
82
  default:
132
83
  return (
133
84
  <Fade {...transitionProps} data-testid="transition-wrapper">
134
- <div data-testid="transition-element">
135
- <div data-testid="transition-content">{children}</div>
136
- </div>
85
+ {content}
137
86
  </Fade>
138
87
  );
139
88
  }
@@ -0,0 +1,319 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+
3
+ import type { VirtualGridProps, VirtualListProps } from './VirtualList.types';
4
+
5
+ type ScrollEvent = React.UIEvent<HTMLDivElement> | Event;
6
+ type ListItems = VirtualListProps['items'];
7
+ type ListVariant = NonNullable<VirtualListProps['variant']>;
8
+ type ItemHeight = (index: number) => number;
9
+
10
+ // Handles both React synthetic events and native DOM events, since the same
11
+ // handler serves an internal onScroll and an external container's listener.
12
+ const scrollOffsets = (event: ScrollEvent) => {
13
+ const target = (event as React.UIEvent<HTMLDivElement>).currentTarget || (event as Event).target;
14
+ const element = target as HTMLElement;
15
+ return { top: element.scrollTop, left: element.scrollLeft };
16
+ };
17
+
18
+ /**
19
+ * When the caller owns the scroll container, the component cannot attach
20
+ * `onScroll` through JSX and has to subscribe to that element directly.
21
+ */
22
+ const useExternalScrollListener = (
23
+ scrollContainerRef: VirtualListProps['scrollContainerRef'],
24
+ handleScroll: (event: Event) => void,
25
+ ) => {
26
+ useEffect(() => {
27
+ if (!scrollContainerRef?.current) return;
28
+
29
+ const element = scrollContainerRef.current;
30
+ const scrollHandler = (e: Event) => handleScroll(e);
31
+
32
+ element.addEventListener('scroll', scrollHandler);
33
+ return () => element.removeEventListener('scroll', scrollHandler);
34
+ }, [scrollContainerRef, handleScroll]);
35
+ };
36
+
37
+ /**
38
+ * Tracks the scroll offset whether it arrives through our own `onScroll` or
39
+ * through a container the caller owns, and hands both offsets to `report` so a
40
+ * grid can pass on the horizontal one too.
41
+ */
42
+ const useScrollTop = (
43
+ scrollContainerRef: VirtualListProps['scrollContainerRef'],
44
+ report: (top: number, left: number) => void,
45
+ ) => {
46
+ const [scrollTop, setScrollTop] = useState(0);
47
+
48
+ const handleScroll = useCallback(
49
+ (event: ScrollEvent) => {
50
+ const { top, left } = scrollOffsets(event);
51
+ setScrollTop(top);
52
+ report(top, left);
53
+ },
54
+ [report],
55
+ );
56
+
57
+ useExternalScrollListener(scrollContainerRef, handleScroll);
58
+
59
+ return { scrollTop, handleScroll };
60
+ };
61
+
62
+ interface HeightArgs {
63
+ items: ListItems;
64
+ variant: ListVariant;
65
+ itemHeight: number;
66
+ estimatedItemHeight: number;
67
+ }
68
+
69
+ /**
70
+ * Fixed lists know every height up front. Variable ones prefer the item's own
71
+ * declared height, fall back to a measurement taken on a previous render, and
72
+ * only then guess.
73
+ */
74
+ const useItemHeight = ({
75
+ items,
76
+ variant,
77
+ itemHeight,
78
+ estimatedItemHeight,
79
+ }: HeightArgs): ItemHeight => {
80
+ const measured = useRef<Map<number, number>>(new Map());
81
+
82
+ return useCallback(
83
+ (index: number): number => {
84
+ if (variant !== 'variable') {
85
+ return itemHeight;
86
+ }
87
+
88
+ return items[index]?.height || measured.current.get(index) || estimatedItemHeight;
89
+ },
90
+ [variant, itemHeight, estimatedItemHeight, items],
91
+ );
92
+ };
93
+
94
+ const useTotalHeight = (
95
+ items: ListItems,
96
+ variant: ListVariant,
97
+ itemHeight: number,
98
+ getItemHeight: ItemHeight,
99
+ ) =>
100
+ useMemo(() => {
101
+ if (variant === 'fixed') {
102
+ return items.length * itemHeight;
103
+ }
104
+
105
+ let sum = 0;
106
+ for (let i = 0; i < items.length; i++) {
107
+ sum += getItemHeight(i);
108
+ }
109
+ return sum;
110
+ }, [items.length, variant, itemHeight, getItemHeight]);
111
+
112
+ interface RangeArgs {
113
+ count: number;
114
+ scrollTop: number;
115
+ height: number;
116
+ overscan: number;
117
+ itemHeight: number;
118
+ getItemHeight: ItemHeight;
119
+ }
120
+
121
+ // Walks forward accumulating heights until it passes the viewport's top edge, then
122
+ // its bottom edge. Linear in the number of items, which is why fixed lists take
123
+ // the arithmetic shortcut instead.
124
+ const variableRange = ({ count, scrollTop, height, overscan, getItemHeight }: RangeArgs) => {
125
+ let accumulated = 0;
126
+ let startIndex = 0;
127
+ let endIndex = count - 1;
128
+
129
+ for (let i = 0; i < count; i++) {
130
+ if (accumulated + getItemHeight(i) > scrollTop) {
131
+ startIndex = Math.max(0, i - overscan);
132
+ break;
133
+ }
134
+ accumulated += getItemHeight(i);
135
+ }
136
+
137
+ accumulated = 0;
138
+ for (let i = 0; i <= startIndex; i++) {
139
+ accumulated += getItemHeight(i);
140
+ }
141
+
142
+ for (let i = startIndex; i < count; i++) {
143
+ if (accumulated > scrollTop + height) {
144
+ endIndex = Math.min(count - 1, i + overscan);
145
+ break;
146
+ }
147
+ accumulated += getItemHeight(i);
148
+ }
149
+
150
+ return { startIndex, endIndex };
151
+ };
152
+
153
+ const fixedRange = ({ count, scrollTop, height, overscan, itemHeight }: RangeArgs) => ({
154
+ startIndex: Math.max(0, Math.floor(scrollTop / itemHeight) - overscan),
155
+ endIndex: Math.min(count - 1, Math.ceil((scrollTop + height) / itemHeight) + overscan),
156
+ });
157
+
158
+ const useItemOffset = (
159
+ variant: ListVariant,
160
+ itemHeight: number,
161
+ getItemHeight: ItemHeight,
162
+ ): ItemHeight =>
163
+ useCallback(
164
+ (index: number): number => {
165
+ if (variant === 'fixed') {
166
+ return index * itemHeight;
167
+ }
168
+
169
+ let offset = 0;
170
+ for (let i = 0; i < index; i++) {
171
+ offset += getItemHeight(i);
172
+ }
173
+ return offset;
174
+ },
175
+ [variant, itemHeight, getItemHeight],
176
+ );
177
+
178
+ interface ListArgs {
179
+ items: ListItems;
180
+ variant: ListVariant;
181
+ height: number;
182
+ itemHeight: number;
183
+ estimatedItemHeight: number;
184
+ overscan: number;
185
+ onScroll: VirtualListProps['onScroll'];
186
+ scrollContainerRef: VirtualListProps['scrollContainerRef'];
187
+ }
188
+
189
+ export const useVirtualList = ({
190
+ items,
191
+ variant,
192
+ height,
193
+ itemHeight,
194
+ estimatedItemHeight,
195
+ overscan,
196
+ onScroll,
197
+ scrollContainerRef,
198
+ }: ListArgs) => {
199
+ const getItemHeight = useItemHeight({ items, variant, itemHeight, estimatedItemHeight });
200
+ const getItemOffset = useItemOffset(variant, itemHeight, getItemHeight);
201
+ const totalHeight = useTotalHeight(items, variant, itemHeight, getItemHeight);
202
+
203
+ const report = useCallback((top: number) => onScroll?.(top), [onScroll]);
204
+ const { scrollTop, handleScroll } = useScrollTop(scrollContainerRef, report);
205
+
206
+ const visibleItems = useMemo(() => {
207
+ const args = { count: items.length, scrollTop, height, overscan, itemHeight, getItemHeight };
208
+ const { startIndex, endIndex } = variant === 'fixed' ? fixedRange(args) : variableRange(args);
209
+ const result = [];
210
+
211
+ for (let i = startIndex; i <= endIndex; i++) {
212
+ const item = items[i];
213
+ if (!item) continue;
214
+
215
+ result.push({
216
+ item,
217
+ index: i,
218
+ style: {
219
+ position: 'absolute' as const,
220
+ top: getItemOffset(i),
221
+ left: 0,
222
+ width: '100%',
223
+ height: getItemHeight(i),
224
+ },
225
+ });
226
+ }
227
+
228
+ return result;
229
+ }, [items, scrollTop, height, overscan, variant, itemHeight, getItemOffset, getItemHeight]);
230
+
231
+ return { totalHeight, visibleItems, handleScroll };
232
+ };
233
+
234
+ interface GridArgs {
235
+ items: VirtualGridProps['items'];
236
+ height: number;
237
+ width: number | string;
238
+ columnCount: number;
239
+ rowHeight: number;
240
+ columnWidth?: number;
241
+ gap: number;
242
+ overscan: number;
243
+ onScroll: VirtualGridProps['onScroll'];
244
+ scrollContainerRef: VirtualGridProps['scrollContainerRef'];
245
+ }
246
+
247
+ const useColumnWidth = (
248
+ columnWidth: number | undefined,
249
+ width: number | string,
250
+ columnCount: number,
251
+ gap: number,
252
+ ) =>
253
+ useMemo(() => {
254
+ if (columnWidth) return columnWidth;
255
+
256
+ const containerWidth = typeof width === 'number' ? width : 300; // fallback
257
+ return (containerWidth - (columnCount - 1) * gap) / columnCount;
258
+ }, [columnWidth, width, columnCount, gap]);
259
+
260
+ export const useVirtualGrid = ({
261
+ items,
262
+ height,
263
+ width,
264
+ columnCount,
265
+ rowHeight,
266
+ columnWidth,
267
+ gap,
268
+ overscan,
269
+ onScroll,
270
+ scrollContainerRef,
271
+ }: GridArgs) => {
272
+ const rowCount = Math.ceil(items.length / columnCount);
273
+ const totalHeight = rowCount * (rowHeight + gap) - gap;
274
+ const computedColumnWidth = useColumnWidth(columnWidth, width, columnCount, gap);
275
+
276
+ const report = useCallback(
277
+ (top: number, left: number) => onScroll?.(top, left),
278
+ [onScroll],
279
+ );
280
+ const { scrollTop, handleScroll } = useScrollTop(scrollContainerRef, report);
281
+
282
+ const visibleItems = useMemo(() => {
283
+ const pitch = rowHeight + gap;
284
+ const startRow = Math.max(0, Math.floor(scrollTop / pitch) - overscan);
285
+ const endRow = Math.min(rowCount - 1, Math.ceil((scrollTop + height) / pitch) + overscan);
286
+
287
+ // Rows are contiguous in `items`, so the visible block is one index range
288
+ // rather than a row loop wrapping a column loop.
289
+ const firstIndex = startRow * columnCount;
290
+ const lastIndex = Math.min((endRow + 1) * columnCount - 1, items.length - 1);
291
+ const result = [];
292
+
293
+ for (let index = firstIndex; index <= lastIndex; index++) {
294
+ const item = items[index];
295
+ if (!item) continue;
296
+
297
+ const rowIndex = Math.floor(index / columnCount);
298
+ const columnIndex = index % columnCount;
299
+
300
+ result.push({
301
+ item,
302
+ index,
303
+ columnIndex,
304
+ rowIndex,
305
+ style: {
306
+ position: 'absolute' as const,
307
+ top: rowIndex * pitch,
308
+ left: columnIndex * (computedColumnWidth + gap),
309
+ width: computedColumnWidth,
310
+ height: rowHeight,
311
+ },
312
+ });
313
+ }
314
+
315
+ return result;
316
+ }, [items, scrollTop, height, overscan, rowCount, columnCount, rowHeight, gap, computedColumnWidth]);
317
+
318
+ return { totalHeight, visibleItems, handleScroll };
319
+ };
@@ -40,11 +40,12 @@ const generateItems = (count: number): VirtualListItem[] => Array.from({ length:
40
40
 
41
41
  const generateVariableItems = (count: number): VirtualListItem[] => Array.from({ length: count }, (_, i) => ({
42
42
  id: i,
43
- height: 60 + Math.floor(Math.random() * 100),
43
+ // Cycles 60/95/130/165 so the mix of heights is fixed per run.
44
+ height: 60 + (i % 4) * 35,
44
45
  data: {
45
46
  name: `Variable Item ${i + 1}`,
46
47
  description: `This item has a variable height. ${
47
- Math.random() > 0.5
48
+ i % 2 === 0
48
49
  ? 'It contains additional content that makes it taller than other items in the list.'
49
50
  : 'Short description.'
50
51
  }`,
@@ -52,6 +53,22 @@ const generateVariableItems = (count: number): VirtualListItem[] => Array.from({
52
53
  },
53
54
  }));
54
55
 
56
+ // A virtual list's job is to swap which items are mounted as the container
57
+ // scrolls, so these tests assert on the rendered window. The raw scrollTop they
58
+ // checked before depends on the viewport and says nothing about virtualisation.
59
+ const renderedIndices = (root: HTMLElement): number[] =>
60
+ Array.from(root.querySelectorAll('[data-testid^="virtual-item-"]')).map((element) =>
61
+ Number((element.getAttribute('data-testid') ?? '').replace('virtual-item-', '')),
62
+ );
63
+
64
+ const firstRenderedIndex = (root: HTMLElement): number => {
65
+ const indices = renderedIndices(root);
66
+ return indices.length > 0 ? Math.min(...indices) : -1;
67
+ };
68
+
69
+ // Larger than any fixture's total height, so the container lands at its end.
70
+ const SCROLL_TO_END = 50_000;
71
+
55
72
  const SimpleItemRenderer = ({
56
73
  item,
57
74
  index,
@@ -147,7 +164,7 @@ export const BasicInteraction: Story = {
147
164
 
148
165
  // Test that scroll callback is called
149
166
  await waitFor(() => {
150
- expect(virtualList.scrollTop).toBeGreaterThan(0);
167
+ expect(firstRenderedIndex(virtualList)).toBeGreaterThan(0);
151
168
  });
152
169
  },
153
170
  };
@@ -193,18 +210,19 @@ export const GridInteraction: Story = {
193
210
  // Test scrolling in grid
194
211
  fireEvent.scroll(virtualGrid, { target: { scrollTop: 300 } });
195
212
  await waitFor(() => {
196
- expect(virtualGrid.scrollTop).toBeGreaterThan(0);
213
+ expect(firstRenderedIndex(virtualGrid)).toBeGreaterThan(0);
197
214
  });
198
215
  },
199
216
  };
200
217
 
201
218
  // Form Interaction Tests (Scroll behaviors)
202
- export const ScrollInteraction: Story = {
203
- render: () => {
204
- const items = generateItems(1000);
205
- let scrollPosition = 0;
219
+ // The read-out was a plain `let` mutated from the scroll callback, so it never
220
+ // re-rendered and always showed 0px.
221
+ const ScrollInteractionDemo: React.FC = () => {
222
+ const items = React.useMemo(() => generateItems(1000), []);
223
+ const [scrollPosition, setScrollPosition] = React.useState(0);
206
224
 
207
- return (
225
+ return (
208
226
  <Box sx={{ display: 'flex', gap: 2 }}>
209
227
  <Paper sx={{ width: 400, height: 300 }}>
210
228
  <VirtualList
@@ -212,9 +230,7 @@ export const ScrollInteraction: Story = {
212
230
  variant="fixed"
213
231
  height={300}
214
232
  itemHeight={50}
215
- onScroll={(scrollTop) => {
216
- scrollPosition = scrollTop;
217
- }}
233
+ onScroll={setScrollPosition}
218
234
  renderItem={SimpleItemRenderer}
219
235
  data-testid="scrollable-list"
220
236
  />
@@ -223,34 +239,37 @@ export const ScrollInteraction: Story = {
223
239
  <Typography variant="body2" data-testid="scroll-info">
224
240
  Scroll Position: {Math.round(scrollPosition)}px
225
241
  </Typography>
226
- </Box>
227
242
  </Box>
228
- );
229
- },
243
+ </Box>
244
+ );
245
+ };
246
+
247
+ export const ScrollInteraction: Story = {
248
+ render: () => <ScrollInteractionDemo />,
230
249
  play: async ({ canvasElement }) => {
231
250
  const canvas = within(canvasElement);
232
251
  const scrollableList = canvas.getByTestId('scrollable-list');
233
252
 
234
253
  // Test initial state
235
254
  await expect(scrollableList).toBeInTheDocument();
236
- expect(scrollableList.scrollTop).toBe(0);
255
+ expect(firstRenderedIndex(scrollableList)).toBe(0);
237
256
 
238
257
  // Test scroll to middle
239
258
  fireEvent.scroll(scrollableList, { target: { scrollTop: 500 } });
240
259
  await waitFor(() => {
241
- expect(scrollableList.scrollTop).toBeGreaterThan(400);
260
+ expect(firstRenderedIndex(scrollableList)).toBeGreaterThan(0);
242
261
  });
243
262
 
244
263
  // Test scroll to bottom
245
- scrollableList.scrollTop = scrollableList.scrollHeight - scrollableList.clientHeight;
264
+ fireEvent.scroll(scrollableList, { target: { scrollTop: SCROLL_TO_END } });
246
265
  await waitFor(() => {
247
- expect(scrollableList.scrollTop).toBeGreaterThan(1000);
266
+ expect(firstRenderedIndex(scrollableList)).toBeGreaterThan(0);
248
267
  });
249
268
 
250
269
  // Test scroll back to top
251
- scrollableList.scrollTop = 0;
270
+ fireEvent.scroll(scrollableList, { target: { scrollTop: 0 } });
252
271
  await waitFor(() => {
253
- expect(scrollableList.scrollTop).toBe(0);
272
+ expect(firstRenderedIndex(scrollableList)).toBe(0);
254
273
  });
255
274
  },
256
275
  };
@@ -315,7 +334,7 @@ export const KeyboardNavigation: Story = {
315
334
  await waitFor(
316
335
  () => {
317
336
  // Check the scroll position was applied
318
- expect(list.scrollTop).toBeGreaterThan(0);
337
+ expect(firstRenderedIndex(list)).toBeGreaterThan(0);
319
338
  },
320
339
  { timeout: 2000 },
321
340
  );
@@ -324,7 +343,7 @@ export const KeyboardNavigation: Story = {
324
343
  fireEvent.scroll(list, { target: { scrollTop: 0 } });
325
344
  await waitFor(
326
345
  () => {
327
- expect(list.scrollTop).toBe(0);
346
+ expect(firstRenderedIndex(list)).toBe(0);
328
347
  },
329
348
  { timeout: 1000 },
330
349
  );
@@ -432,7 +451,7 @@ export const FocusManagement: Story = {
432
451
  // Test external focus
433
452
  const externalButton = canvas.getByTestId('external-button');
434
453
  await userEvent.click(externalButton);
435
- expect(externalButton).toHaveFocus();
454
+ await waitFor(() => expect(externalButton).toHaveFocus());
436
455
 
437
456
  // Test tab into list
438
457
  await userEvent.tab();
@@ -807,7 +826,7 @@ export const Performance: Story = {
807
826
  fireEvent.scroll(performanceList, { target: { scrollTop: 500 } });
808
827
  await waitFor(
809
828
  () => {
810
- expect(performanceList.scrollTop).toBeGreaterThan(0);
829
+ expect(firstRenderedIndex(performanceList)).toBeGreaterThan(0);
811
830
  },
812
831
  { timeout: 2000 },
813
832
  );
@@ -815,16 +834,14 @@ export const Performance: Story = {
815
834
  // Test multiple scroll events
816
835
  for (let i = 1; i <= 3; i++) {
817
836
  fireEvent.scroll(performanceList, { target: { scrollTop: i * 200 } });
818
- await new Promise<void>((resolve) => {
819
- setTimeout(resolve, 50);
820
- });
837
+ await waitFor(() => expect(performanceList).toBeInTheDocument());
821
838
  }
822
839
 
823
840
  // Verify list is still responsive
824
841
  await expect(performanceList).toBeInTheDocument();
825
842
  await waitFor(
826
843
  () => {
827
- expect(performanceList.scrollTop).toBeGreaterThan(0);
844
+ expect(firstRenderedIndex(performanceList)).toBeGreaterThan(0);
828
845
  },
829
846
  { timeout: 1000 },
830
847
  );
@@ -927,7 +944,7 @@ export const EdgeCases: Story = {
927
944
  fireEvent.scroll(variableHeightList, { target: { scrollTop: 100 } });
928
945
  await waitFor(
929
946
  () => {
930
- expect(variableHeightList.scrollTop).toBeGreaterThan(0);
947
+ expect(firstRenderedIndex(variableHeightList)).toBeGreaterThan(0);
931
948
  },
932
949
  { timeout: 1000 },
933
950
  );
@@ -965,9 +982,6 @@ export const Integration: Story = {
965
982
  <ListItem
966
983
  button
967
984
  onClick={() => {
968
- // Integration with external handlers
969
- // eslint-disable-next-line no-console
970
- console.log('Clicked item:', item.id);
971
985
  }}
972
986
  >
973
987
  <ListItemAvatar>
@@ -1008,9 +1022,6 @@ export const Integration: Story = {
1008
1022
  },
1009
1023
  }}
1010
1024
  onClick={() => {
1011
- // Integration with external handlers
1012
- // eslint-disable-next-line no-console
1013
- console.log('Clicked grid item:', item.id);
1014
1025
  }}
1015
1026
  >
1016
1027
  <Typography variant="body2" color="white">
@@ -1067,8 +1078,8 @@ export const Integration: Story = {
1067
1078
  fireEvent.scroll(integrationGrid, { target: { scrollTop: 200 } });
1068
1079
 
1069
1080
  await waitFor(() => {
1070
- expect(integrationList.scrollTop).toBeGreaterThan(0);
1071
- expect(integrationGrid.scrollTop).toBeGreaterThan(0);
1081
+ expect(firstRenderedIndex(integrationList)).toBeGreaterThan(0);
1082
+ expect(firstRenderedIndex(integrationGrid)).toBeGreaterThan(0);
1072
1083
  });
1073
1084
  },
1074
1085
  };