@12-apps/ui 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/components-catalog.md +14 -0
  2. package/package.json +42 -27
  3. package/src/__tests__/data-grid.test.tsx +125 -0
  4. package/src/__tests__/package-wiring.test.ts +12 -8
  5. package/src/__tests__/social-login-button.test.tsx +5 -5
  6. package/src/__tests__/viewport.test.ts +50 -0
  7. package/src/charts/index.ts +8 -0
  8. package/src/components/data-display/Alert/Alert.md +1 -1
  9. package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
  10. package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
  11. package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
  12. package/src/components/data-display/Alert/Alert.tsx +135 -328
  13. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
  14. package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
  15. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
  16. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
  17. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
  18. package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
  19. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
  20. package/src/components/data-display/Avatar/Avatar.tsx +154 -450
  21. package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
  22. package/src/components/data-display/Badge/Badge.animations.ts +67 -0
  23. package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
  24. package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
  25. package/src/components/data-display/Badge/Badge.styles.ts +261 -0
  26. package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
  27. package/src/components/data-display/Badge/Badge.tsx +151 -557
  28. package/src/components/data-display/Badge/Badge.variants.ts +106 -0
  29. package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
  30. package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
  31. package/src/components/data-display/Banner/Banner.styles.ts +120 -0
  32. package/src/components/data-display/Banner/Banner.tsx +179 -224
  33. package/src/components/data-display/Chart/Chart.md +48 -1
  34. package/src/components/data-display/Chart/Chart.tsx +82 -500
  35. package/src/components/data-display/Chart/Chart.types.ts +2 -0
  36. package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
  37. package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
  38. package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
  39. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
  40. package/src/components/data-display/Chart/SpecChart.tsx +53 -0
  41. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
  42. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
  43. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
  44. package/src/components/data-display/Chart/chart-format.ts +26 -0
  45. package/src/components/data-display/Chart/chart-internals.ts +148 -0
  46. package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
  47. package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
  48. package/src/components/data-display/Chart/chart-spec.ts +171 -0
  49. package/src/components/data-display/Chart/index.ts +30 -0
  50. package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
  51. package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
  52. package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
  53. package/src/components/data-display/Chip/Chip.tsx +31 -94
  54. package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
  55. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
  56. package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
  57. package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
  58. package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
  59. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
  60. package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
  61. package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
  62. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
  63. package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
  64. package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
  65. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
  66. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
  67. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
  68. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
  69. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
  70. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
  71. package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
  72. package/src/components/data-display/DataViews/base-card.tsx +278 -0
  73. package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
  74. package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
  75. package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
  76. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
  77. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
  78. package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
  79. package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
  80. package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
  81. package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
  82. package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
  83. package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
  84. package/src/components/data-display/DataViews/data-views-types.ts +312 -0
  85. package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
  86. package/src/components/data-display/DataViews/index.ts +33 -0
  87. package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
  88. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
  89. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
  90. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
  91. package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
  92. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
  93. package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
  94. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
  95. package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
  96. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
  97. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
  98. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
  99. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
  100. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
  101. package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
  102. package/src/components/data-display/LazyImage/LazyImage.md +1 -1
  103. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
  104. package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
  105. package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
  106. package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
  107. package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
  108. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
  109. package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
  110. package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
  111. package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
  112. package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
  113. package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
  114. package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
  115. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
  116. package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
  117. package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
  118. package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
  119. package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
  120. package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
  121. package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
  122. package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
  123. package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
  124. package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
  125. package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
  126. package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
  127. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
  128. package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
  129. package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
  130. package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
  131. package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
  132. package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
  133. package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
  134. package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
  135. package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
  136. package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
  137. package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
  138. package/src/components/data-display/Progress/Progress.styles.ts +101 -0
  139. package/src/components/data-display/Progress/Progress.tsx +185 -213
  140. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
  141. package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
  142. package/src/components/data-display/StatCard/StatCard.md +117 -0
  143. package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
  144. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
  145. package/src/components/data-display/StatCard/StatCard.tsx +236 -0
  146. package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
  147. package/src/components/data-display/StatCard/index.ts +7 -0
  148. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
  149. package/src/components/data-display/Stepper/Stepper.tsx +1 -1
  150. package/src/components/data-display/Table/Table.helpers.ts +31 -0
  151. package/src/components/data-display/Table/Table.hooks.ts +161 -0
  152. package/src/components/data-display/Table/Table.stories.tsx +3 -1
  153. package/src/components/data-display/Table/Table.styles.ts +223 -0
  154. package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
  155. package/src/components/data-display/Table/Table.tsx +291 -748
  156. package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
  157. package/src/components/data-display/Table/TableParts.tsx +298 -0
  158. package/src/components/data-display/Table/TableStates.tsx +21 -0
  159. package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
  160. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
  161. package/src/components/data-display/Timeline/Timeline.tsx +23 -284
  162. package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
  163. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
  164. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
  165. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
  166. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
  167. package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
  168. package/src/components/data-display/index.ts +1 -0
  169. package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
  170. package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
  171. package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
  172. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
  173. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
  174. package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
  175. package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
  176. package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
  177. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
  178. package/src/components/feedback/ConfirmAction/index.ts +16 -0
  179. package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
  180. package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
  181. package/src/components/feedback/Dialog/Dialog.tsx +26 -1
  182. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
  183. package/src/components/feedback/Modal/Modal.md +1 -1
  184. package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
  185. package/src/components/feedback/Sonner/Sonner.tsx +104 -288
  186. package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
  187. package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
  188. package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
  189. package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
  190. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
  191. package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
  192. package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
  193. package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
  194. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
  195. package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
  196. package/src/components/feedback/Toast/Toast.tsx +92 -81
  197. package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
  198. package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
  199. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
  200. package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
  201. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
  202. package/src/components/feedback/index.ts +1 -0
  203. package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
  204. package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
  205. package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
  206. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
  207. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
  208. package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
  209. package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
  210. package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
  211. package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
  212. package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
  213. package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
  214. package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
  215. package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
  216. package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
  217. package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
  218. package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
  219. package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
  220. package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
  221. package/src/components/form/Button/Button.helpers.ts +36 -0
  222. package/src/components/form/Button/Button.stories.tsx +4 -1
  223. package/src/components/form/Button/Button.styles.ts +225 -0
  224. package/src/components/form/Button/Button.test.stories.tsx +25 -21
  225. package/src/components/form/Button/Button.tsx +45 -252
  226. package/src/components/form/Calendar/Calendar.dates.ts +100 -0
  227. package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
  228. package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
  229. package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
  230. package/src/components/form/Calendar/Calendar.selection.ts +156 -0
  231. package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
  232. package/src/components/form/Calendar/Calendar.tsx +63 -551
  233. package/src/components/form/Calendar/Calendar.types.ts +16 -0
  234. package/src/components/form/Calendar/CalendarDay.tsx +127 -0
  235. package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
  236. package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
  237. package/src/components/form/CepField/CepField.md +138 -0
  238. package/src/components/form/CepField/CepField.stories.tsx +138 -0
  239. package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
  240. package/src/components/form/CepField/CepField.tsx +211 -0
  241. package/src/components/form/CepField/CepField.types.ts +73 -0
  242. package/src/components/form/CepField/index.ts +2 -0
  243. package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
  244. package/src/components/form/Checkbox/Checkbox.tsx +19 -16
  245. package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
  246. package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
  247. package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
  248. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
  249. package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
  250. package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
  251. package/src/components/form/Command/Command.hooks.ts +207 -0
  252. package/src/components/form/Command/Command.parts.tsx +58 -0
  253. package/src/components/form/Command/Command.styles.ts +83 -0
  254. package/src/components/form/Command/Command.tsx +61 -270
  255. package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
  256. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
  257. package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
  258. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
  259. package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
  260. package/src/components/form/HeaderButton/index.ts +2 -0
  261. package/src/components/form/Input/Input.stories.tsx +5 -1
  262. package/src/components/form/Input/Input.styles.ts +176 -0
  263. package/src/components/form/Input/Input.test.stories.tsx +30 -23
  264. package/src/components/form/Input/Input.tsx +71 -211
  265. package/src/components/form/Input/Input.types.ts +11 -0
  266. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
  267. package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
  268. package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
  269. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
  270. package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
  271. package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
  272. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
  273. package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
  274. package/src/components/form/PhoneInput/countries.ts +54 -0
  275. package/src/components/form/PhoneInput/phone.ts +47 -0
  276. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
  277. package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
  278. package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
  279. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
  280. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
  281. package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
  282. package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
  283. package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
  284. package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
  285. package/src/components/form/Select/Select.test.stories.tsx +35 -27
  286. package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
  287. package/src/components/form/Switch/Switch.md +2 -1
  288. package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
  289. package/src/components/form/Textarea/Textarea.styles.ts +143 -0
  290. package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
  291. package/src/components/form/Textarea/Textarea.tsx +114 -535
  292. package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
  293. package/src/components/form/Toggle/Toggle.styles.ts +211 -0
  294. package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
  295. package/src/components/form/Toggle/Toggle.tsx +10 -185
  296. package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
  297. package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
  298. package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
  299. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
  300. package/src/components/form/UploadButton/UploadButton.tsx +76 -305
  301. package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
  302. package/src/components/form/index.ts +1 -0
  303. package/src/components/form/total-form/Fields/CepField.tsx +143 -0
  304. package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
  305. package/src/components/form/total-form/Fields/index.ts +4 -0
  306. package/src/components/form/total-form/FormContainer.tsx +3 -3
  307. package/src/components/form/total-form/SubmitButton.tsx +25 -2
  308. package/src/components/form/total-form/total-form.test.tsx +118 -1
  309. package/src/components/layout/Accordion/Accordion.md +1 -1
  310. package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
  311. package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
  312. package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
  313. package/src/components/layout/Accordion/Accordion.tsx +2 -75
  314. package/src/components/layout/Card/Card.helpers.ts +30 -0
  315. package/src/components/layout/Card/Card.stories.tsx +9 -11
  316. package/src/components/layout/Card/Card.styles.ts +182 -0
  317. package/src/components/layout/Card/Card.test.stories.tsx +46 -46
  318. package/src/components/layout/Card/Card.tsx +24 -241
  319. package/src/components/layout/CardGrid/CardGrid.md +96 -0
  320. package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
  321. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
  322. package/src/components/layout/CardGrid/index.ts +2 -0
  323. package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
  324. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
  325. package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
  326. package/src/components/layout/Container/Container.test.stories.tsx +11 -16
  327. package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
  328. package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
  329. package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
  330. package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
  331. package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
  332. package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
  333. package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
  334. package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
  335. package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
  336. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
  337. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
  338. package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
  339. package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
  340. package/src/components/layout/Dashboard/index.ts +2 -1
  341. package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
  342. package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
  343. package/src/components/layout/Drawer/Drawer.tsx +67 -53
  344. package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
  345. package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
  346. package/src/components/layout/Resizable/Resizable.tsx +38 -153
  347. package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
  348. package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
  349. package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
  350. package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
  351. package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
  352. package/src/components/layout/Separator/Separator.styles.ts +128 -0
  353. package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
  354. package/src/components/layout/Separator/Separator.tsx +6 -132
  355. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
  356. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
  357. package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
  358. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
  359. package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
  360. package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
  361. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
  362. package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
  363. package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
  364. package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
  365. package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
  366. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
  367. package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
  368. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
  369. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
  370. package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
  371. package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
  372. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
  373. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
  374. package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
  375. package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
  376. package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
  377. package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
  378. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
  379. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
  380. package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
  381. package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
  382. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
  383. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
  384. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
  385. package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
  386. package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
  387. package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
  388. package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
  389. package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
  390. package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
  391. package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
  392. package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
  393. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
  394. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
  395. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
  396. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
  397. package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
  398. package/src/components/navigation/Pagination/Pagination.md +2 -1
  399. package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
  400. package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
  401. package/src/components/navigation/Pagination/Pagination.tsx +178 -313
  402. package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
  403. package/src/components/navigation/Tabs/Tabs.md +3 -1
  404. package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
  405. package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
  406. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
  407. package/src/components/navigation/Tabs/Tabs.tsx +192 -361
  408. package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
  409. package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
  410. package/src/components/typography/Code/Code.test.stories.tsx +5 -5
  411. package/src/components/typography/Code/Code.tsx +74 -49
  412. package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
  413. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
  414. package/src/components/typography/Text/Text.tsx +84 -77
  415. package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
  416. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
  417. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
  418. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
  419. package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
  420. package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
  421. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
  422. package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
  423. package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
  424. package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
  425. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
  426. package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
  427. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
  428. package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
  429. package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
  430. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
  431. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
  432. package/src/components/utility/InstallPrompt/index.ts +10 -0
  433. package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
  434. package/src/components/utility/Transition/Transition.helpers.ts +40 -0
  435. package/src/components/utility/Transition/Transition.tsx +17 -68
  436. package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
  437. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
  438. package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
  439. package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
  440. package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
  441. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
  442. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
  443. package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
  444. package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
  445. package/src/components/utility/index.ts +1 -0
  446. package/src/mui/Link.ts +2 -0
  447. package/src/utils/index.ts +2 -1
  448. package/src/utils/viewport.ts +65 -0
@@ -1,743 +1,147 @@
1
- import {
2
- ArrowDownward,
3
- ArrowUpward,
4
- ExpandLess,
5
- ExpandMore,
6
- UnfoldMore,
7
- } from '@mui/icons-material';
8
- import {
9
- Box,
10
- Button,
11
- Checkbox,
12
- CircularProgress,
13
- IconButton,
14
- Paper,
15
- Table,
16
- TableBody,
17
- TableCell,
18
- TableContainer,
19
- TableHead,
20
- TableRow,
21
- Typography,
22
- useTheme,
23
- } from '@mui/material';
24
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
25
-
26
- import type {
27
- DataGridProps,
28
- GridColumn,
29
- GridFilter,
30
- GridSort,
31
- SortDirection,
32
- } from './DataGrid.types';
33
-
34
- const DEFAULT_ROW_HEIGHT = 52;
35
- const DEFAULT_HEADER_HEIGHT = 56;
36
- const DEFAULT_PAGE_SIZE = 50;
37
- const OVERSCAN_COUNT = 20; // Items to render before/after visible area for smooth scrolling
38
- const VIRTUALIZATION_THRESHOLD = 100; // Enable virtualization for datasets larger than this
39
-
40
- export const DataGrid = <T extends Record<string, unknown> = Record<string, unknown>>({
41
- rows,
42
- columns,
43
- getRowId = (row: T, index: number) => (row as { id?: string | number }).id ?? index,
44
- sizeMode = 'auto',
45
- density = 'comfortable',
46
- rowHeight = DEFAULT_ROW_HEIGHT,
47
- headerHeight = DEFAULT_HEADER_HEIGHT,
48
- // footerHeight = 0, // TODO: Use for sticky footer implementation
49
- selection = { mode: 'none' },
50
- pagination,
51
- sorting = { mode: 'client' },
52
- filtering = { mode: 'client' },
53
- expansion,
54
- editing = { mode: 'none' },
55
- virtualizeRows = true,
56
- virtualizeColumns = false,
57
- stickyHeader = true,
58
- // stickyFooter = false, // TODO: Implement sticky footer
59
- loading = false,
60
- error,
61
- emptyState,
62
- onRequestData,
63
- ariaLabel = 'Data grid',
64
- ariaDescription,
65
- 'data-testid': dataTestId,
66
- className,
67
- style,
68
- ...htmlProps
69
- }: DataGridProps<T>) => {
70
- const theme = useTheme();
71
- const containerRef = useRef<HTMLDivElement>(null);
72
- const [_containerHeight, setContainerHeight] = useState(400);
73
-
74
- // Internal state for uncontrolled scenarios
75
- const [internalSortBy, setInternalSortBy] = useState<GridSort[]>(
76
- sorting.defaultSortBy || []
77
- );
78
- const [internalFilters] = useState<GridFilter[]>(
79
- filtering.defaultFilters || []
80
- );
81
- const [internalSelectedRowIds, setInternalSelectedRowIds] = useState<Array<string | number>>(
82
- selection.defaultSelectedRowIds || []
83
- );
84
- const [internalExpandedRowIds, setInternalExpandedRowIds] = useState<Array<string | number>>(
85
- expansion?.defaultExpandedRowIds || []
86
- );
87
- const [internalPageIndex] = useState(0);
88
- const [editingCell, setEditingCell] = useState<{ rowId: string | number; colId: string } | null>(null);
89
-
90
- // Use controlled values if provided, otherwise use internal state
91
- const currentSortBy = sorting.sortBy || internalSortBy;
92
- const currentFilters = filtering.filters || internalFilters;
93
- const currentSelectedRowIds = selection.selectedRowIds || internalSelectedRowIds;
94
- const currentExpandedRowIds = expansion?.expandedRowIds || internalExpandedRowIds;
95
- const currentPageIndex = pagination?.pageIndex ?? internalPageIndex;
96
- const currentPageSize = pagination?.pageSize ?? DEFAULT_PAGE_SIZE;
97
-
98
- // Calculate row height based on density
99
- const actualRowHeight = useMemo(() => {
100
- const baseHeight = rowHeight;
101
- switch (density) {
102
- case 'compact':
103
- return Math.max(baseHeight * 0.8, 32);
104
- case 'spacious':
105
- return baseHeight * 1.2;
106
- default:
107
- return baseHeight;
108
- }
109
- }, [rowHeight, density]);
110
-
111
- // Pre-compute column map for O(1) lookups instead of O(n) find() calls
112
- const columnMap = useMemo(() => {
113
- const map = new Map<string, GridColumn<T>>();
114
- for (const col of columns) {
115
- map.set(col.id, col);
116
- }
117
- return map;
118
- }, [columns]);
119
-
120
- // Memoize visible columns to avoid filtering on every row render
121
- const visibleColumns = useMemo(() => columns.filter(col => !col.hidden), [columns]);
122
-
123
- // Process rows: filter, sort, paginate
124
- const processedRows = useMemo(() => {
125
- // Only copy array if we actually need to modify it
126
- const needsFiltering = filtering.mode === 'client' && currentFilters.length > 0;
127
- const needsSorting = sorting.mode === 'client' && currentSortBy.length > 0;
128
- const needsPagination = pagination?.mode === 'client' && !virtualizeRows;
129
-
130
- // If no processing needed, return original array
131
- if (!needsFiltering && !needsSorting && !needsPagination) {
132
- return rows;
133
- }
134
-
135
- let result = rows;
136
-
137
- // Client-side filtering
138
- if (needsFiltering) {
139
- result = result.filter((row) => currentFilters.every((filter) => {
140
- const column = columnMap.get(filter.id); // O(1) lookup instead of O(n)
141
- if (!column || !filter.value) return true;
142
-
143
- const value = column.accessor
144
- ? typeof column.accessor === 'function'
145
- ? column.accessor(row)
146
- : row[column.accessor]
147
- : row[column.id];
148
-
149
- if (column.filterPredicate) {
150
- return column.filterPredicate(value, filter.value, row);
151
- }
152
-
153
- // Default filtering logic based on column type
154
- const filterValue = String(filter.value).toLowerCase();
155
- const cellValue = String(value || '').toLowerCase();
156
- return cellValue.includes(filterValue);
157
- }));
158
- }
159
-
160
- // Client-side sorting - only copy array if we actually need to sort
161
- if (needsSorting) {
162
- // Create a copy only for sorting
163
- result = [...result];
164
- result.sort((a, b) => {
165
- for (const sort of currentSortBy) {
166
- const column = columnMap.get(sort.id); // O(1) lookup instead of O(n)
167
- if (!column || !sort.dir) continue;
168
-
169
- const aValue = column.accessor
170
- ? typeof column.accessor === 'function'
171
- ? column.accessor(a)
172
- : a[column.accessor]
173
- : a[column.id];
174
- const bValue = column.accessor
175
- ? typeof column.accessor === 'function'
176
- ? column.accessor(b)
177
- : b[column.accessor]
178
- : b[column.id];
179
-
180
- let comparison = 0;
181
-
182
- // Handle null/undefined values
183
- if (aValue == null && bValue == null) comparison = 0;
184
- else if (aValue == null) comparison = -1;
185
- else if (bValue == null) comparison = 1;
186
- else {
187
- // Convert to strings for comparison if not numbers
188
- const aStr = typeof aValue === 'number' ? aValue : String(aValue);
189
- const bStr = typeof bValue === 'number' ? bValue : String(bValue);
190
-
191
- if (aStr < bStr) comparison = -1;
192
- else if (aStr > bStr) comparison = 1;
193
- }
194
-
195
- if (comparison !== 0) {
196
- return sort.dir === 'desc' ? -comparison : comparison;
197
- }
198
- }
199
- return 0;
200
- });
201
- }
202
-
203
- // Client-side pagination ONLY if virtualization is disabled
204
- // When virtualization is enabled, it handles slicing
205
- if (needsPagination) {
206
- const startIndex = currentPageIndex * currentPageSize;
207
- result = result.slice(startIndex, startIndex + currentPageSize);
208
- }
209
-
210
- return result;
211
- }, [rows, columnMap, currentFilters, currentSortBy, currentPageIndex, currentPageSize, filtering.mode, sorting.mode, pagination?.mode, virtualizeRows]);
212
-
213
- // Handle container resize for virtualization
214
- useEffect(() => {
215
- const handleResize = () => {
216
- if (containerRef.current) {
217
- const height = containerRef.current.clientHeight;
218
- setContainerHeight(height);
219
- }
220
- };
221
-
222
- handleResize();
223
- window.addEventListener('resize', handleResize);
224
- return () => window.removeEventListener('resize', handleResize);
225
- }, []);
226
-
227
- // Handle sort
228
- const handleSort = useCallback((columnId: string) => {
229
- const currentSort = currentSortBy.find(s => s.id === columnId);
230
- let newDirection: SortDirection = 'asc';
231
-
232
- if (currentSort) {
233
- if (currentSort.dir === 'asc') newDirection = 'desc';
234
- else if (currentSort.dir === 'desc') newDirection = null;
235
- }
236
-
237
- const newSortBy = newDirection
238
- ? [{ id: columnId, dir: newDirection }]
239
- : currentSortBy.filter(s => s.id !== columnId);
240
-
241
- if (sorting.mode === 'server' && onRequestData) {
242
- onRequestData({
243
- pageIndex: currentPageIndex,
244
- pageSize: currentPageSize,
245
- sortBy: newSortBy,
246
- filters: currentFilters,
247
- });
248
- } else {
249
- if (sorting.onChangeSortBy) {
250
- sorting.onChangeSortBy(newSortBy);
251
- } else {
252
- setInternalSortBy(newSortBy);
253
- }
254
- }
255
- }, [currentSortBy, currentPageIndex, currentPageSize, currentFilters, sorting, onRequestData]);
256
-
257
- // Handle selection
258
- const handleRowSelection = useCallback((rowId: string | number, isSelected: boolean) => {
259
- let newSelectedIds: Array<string | number>;
260
-
261
- if (selection.mode === 'single') {
262
- newSelectedIds = isSelected ? [rowId] : [];
263
- } else if (selection.mode === 'multi') {
264
- newSelectedIds = isSelected
265
- ? [...currentSelectedRowIds, rowId]
266
- : currentSelectedRowIds.filter(id => id !== rowId);
267
- } else {
268
- return;
269
- }
270
-
271
- if (selection.onChangeSelected) {
272
- selection.onChangeSelected(newSelectedIds);
273
- } else {
274
- setInternalSelectedRowIds(newSelectedIds);
275
- }
276
- }, [selection, currentSelectedRowIds]);
277
-
278
- // Convert selection and expansion to Sets for O(1) lookups
279
- const selectedRowIdsSet = useMemo(() => new Set(currentSelectedRowIds), [currentSelectedRowIds]);
280
- const expandedRowIdsSet = useMemo(() => new Set(currentExpandedRowIds), [currentExpandedRowIds]);
281
-
282
- // Handle expansion
283
- const handleRowExpansion = useCallback((rowId: string | number) => {
284
- if (!expansion) return;
285
-
286
- const isExpanded = expandedRowIdsSet.has(rowId);
287
- const newExpandedIds = isExpanded
288
- ? currentExpandedRowIds.filter(id => id !== rowId)
289
- : [...currentExpandedRowIds, rowId];
290
-
291
- if (expansion.onChangeExpanded) {
292
- expansion.onChangeExpanded(newExpandedIds);
293
- } else {
294
- setInternalExpandedRowIds(newExpandedIds);
295
- }
296
- }, [expansion, currentExpandedRowIds, expandedRowIdsSet]);
297
-
298
- // Simple virtualization for large datasets with optimized range calculation
299
- const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 });
300
- const scrollRafRef = useRef<number | null>(null);
301
-
302
- // Reset visible range when processedRows changes size significantly
303
- useEffect(() => {
304
- if (processedRows.length < visibleRange.end) {
305
- setVisibleRange({
306
- start: 0,
307
- end: Math.min(50, processedRows.length)
308
- });
309
- }
310
- }, [processedRows.length, visibleRange.end]);
311
-
312
- const visibleRows = useMemo(() => {
313
- if (!virtualizeRows || processedRows.length <= VIRTUALIZATION_THRESHOLD) {
314
- return processedRows;
315
- }
316
- return processedRows.slice(visibleRange.start, visibleRange.end);
317
- }, [processedRows, virtualizeRows, visibleRange]);
318
-
319
- // Handle scroll for virtualization with requestAnimationFrame throttling
320
- const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
321
- if (!virtualizeRows || processedRows.length <= VIRTUALIZATION_THRESHOLD) return;
322
-
323
- // Cancel any pending animation frame
324
- if (scrollRafRef.current !== null) {
325
- window.cancelAnimationFrame(scrollRafRef.current);
326
- }
327
-
328
- // Schedule the scroll update for the next frame
329
- scrollRafRef.current = window.requestAnimationFrame(() => {
330
- const { scrollTop, clientHeight } = event.currentTarget;
331
- const itemHeight = actualRowHeight;
332
- const start = Math.floor(scrollTop / itemHeight);
333
- const visibleCount = Math.ceil(clientHeight / itemHeight);
334
-
335
- // Symmetric overscan buffers for smooth scrolling
336
- const startWithBuffer = Math.max(0, start - OVERSCAN_COUNT);
337
- const endWithBuffer = Math.min(start + visibleCount + OVERSCAN_COUNT, processedRows.length);
338
-
339
- // Only update if the change is significant (>5 items difference) to reduce state updates
340
- const startDiff = Math.abs(startWithBuffer - visibleRange.start);
341
- const endDiff = Math.abs(endWithBuffer - visibleRange.end);
342
-
343
- if (startDiff > 5 || endDiff > 5) {
344
- setVisibleRange({ start: startWithBuffer, end: endWithBuffer });
345
- }
346
-
347
- scrollRafRef.current = null;
348
- });
349
- }, [virtualizeRows, processedRows.length, actualRowHeight, visibleRange.start, visibleRange.end]);
350
-
351
- // Cleanup scroll RAF on unmount
352
- useEffect(() => {
353
- return () => {
354
- if (scrollRafRef.current !== null) {
355
- window.cancelAnimationFrame(scrollRafRef.current);
356
- }
357
- };
358
- }, []);
359
-
360
- // Render sort icon
361
- const renderSortIcon = (columnId: string) => {
362
- const sort = currentSortBy.find(s => s.id === columnId);
363
- if (!sort || !sort.dir) {
364
- return <UnfoldMore fontSize="small" sx={{ opacity: 0.5 }} />;
365
- }
366
- return sort.dir === 'asc'
367
- ? <ArrowUpward fontSize="small" />
368
- : <ArrowDownward fontSize="small" />;
1
+ /**
2
+ * `DataGrid` — the shell.
3
+ *
4
+ * Everything that is not "which of the four renders is this, and what wraps the
5
+ * table" lives beside it: the state in `DataGrid.model.ts`, the client-mode row
6
+ * pipeline in `DataGrid.rows.ts`, the cells and rows in `DataGrid.cells.tsx`,
7
+ * the head/body in `DataGrid.body.tsx`, and the loading/error/empty renders in
8
+ * `DataGrid.states.tsx`.
9
+ *
10
+ * All FOUR renders carry the caller's `data-testid`. It used to be applied only
11
+ * on the populated one, so a grid whose first paint had no rows could not be
12
+ * found by a test at all — see `DataGrid.states.tsx` for the full story.
13
+ */
14
+ import { Paper, Table, TableContainer } from '@mui/material';
15
+ import type React from 'react';
16
+
17
+ import { GridBody, GridHeader } from './DataGrid.body';
18
+ import { useDataGridModel } from './DataGrid.model';
19
+ import { DataGridEmpty, DataGridError, DataGridLoading } from './DataGrid.states';
20
+ import type { DataGridProps } from './DataGrid.types';
21
+
22
+ /**
23
+ * The chrome every render needs, with the defaults resolved ONCE.
24
+ *
25
+ * Extracted from the component body because a dozen destructuring defaults are
26
+ * a dozen branches: the gate counted them, correctly, as complexity the render
27
+ * did not need to carry.
28
+ */
29
+ function resolveChrome<T extends Record<string, unknown>>(props: DataGridProps<T>): {
30
+ dataAttrs: Record<string, unknown>;
31
+ placeholder: {
32
+ dataTestId?: string;
33
+ ariaLabel: string;
34
+ className?: string;
35
+ style?: React.CSSProperties;
36
+ htmlProps: React.HTMLAttributes<HTMLElement>;
369
37
  };
370
-
371
- // Render header cell
372
- const renderHeaderCell = (column: GridColumn<T>) => {
373
- const canSort = column.enableSort !== false && (sorting.mode === 'client' || sorting.mode === 'server');
374
- const sort = currentSortBy.find(s => s.id === column.id);
375
-
376
- const headerContent = column.headerCell ? column.headerCell(column) : column.header;
377
-
378
- return (
379
- <TableCell
380
- key={column.id}
381
- data-slot="header-cell"
382
- data-sort={sort?.dir || 'none'}
383
- sx={{
384
- position: stickyHeader ? 'sticky' : 'static',
385
- top: 0,
386
- zIndex: 1,
387
- backgroundColor: theme.palette.background.paper,
388
- borderBottom: `1px solid ${theme.palette.divider}`,
389
- height: headerHeight,
390
- minWidth: column.minWidth,
391
- maxWidth: column.maxWidth,
392
- width: column.width,
393
- }}
394
- role="columnheader"
395
- aria-sort={
396
- !sort || !sort.dir ? 'none' :
397
- sort.dir === 'asc' ? 'ascending' : 'descending'
398
- }
399
- >
400
- {canSort ? (
401
- <Button
402
- onClick={() => handleSort(column.id)}
403
- sx={{
404
- justifyContent: 'flex-start',
405
- textTransform: 'none',
406
- fontWeight: 500,
407
- color: 'inherit',
408
- p: 0,
409
- minWidth: 0,
410
- '&:hover': {
411
- backgroundColor: 'transparent',
412
- opacity: 0.8,
413
- },
414
- }}
415
- endIcon={renderSortIcon(column.id)}
416
- aria-label={column.ariaLabel || `Sort by ${column.header}`}
417
- >
418
- {headerContent}
419
- </Button>
420
- ) : (
421
- <Typography variant="subtitle2" fontWeight={500}>
422
- {headerContent}
423
- </Typography>
424
- )}
425
- </TableCell>
426
- );
38
+ } {
39
+ const { className, style, 'data-testid': dataTestId, ...rest } = props;
40
+ const stickyHeader = props.stickyHeader ?? true;
41
+ return {
42
+ dataAttrs: {
43
+ 'data-ui': 'datagrid',
44
+ 'data-density': props.density ?? 'comfortable',
45
+ 'data-size-mode': props.sizeMode ?? 'auto',
46
+ 'data-virtual-rows': props.virtualizeRows ?? true,
47
+ 'data-virtual-cols': props.virtualizeColumns ?? false,
48
+ 'data-sticky-header': stickyHeader,
49
+ },
50
+ placeholder: {
51
+ dataTestId,
52
+ ariaLabel: props.ariaLabel ?? 'Data grid',
53
+ className,
54
+ style,
55
+ // Only the DOM-safe leftovers reach the element: every grid-specific prop
56
+ // is named in `GRID_ONLY_PROPS`, so this cannot smuggle `rows` onto a div.
57
+ htmlProps: omitGridProps(rest as Record<string, unknown>),
58
+ },
427
59
  };
428
-
429
- // Render data cell
430
- const renderDataCell = (row: T, rowIndex: number, column: GridColumn<T>) => {
431
- const rowId = getRowId(row, rowIndex);
432
- const value = column.accessor
433
- ? typeof column.accessor === 'function'
434
- ? column.accessor(row)
435
- : row[column.accessor]
436
- : row[column.id];
437
-
438
- const isEditing = editingCell?.rowId === rowId && editingCell?.colId === column.id;
439
- const cellContent = column.cell
440
- ? column.cell({ value, row, rowIndex, col: column })
441
- : String(value || '');
442
-
443
- return (
444
- <TableCell
445
- key={column.id}
446
- data-slot="cell"
447
- data-col-id={column.id}
448
- data-editing={isEditing}
449
- sx={{
450
- minWidth: column.minWidth,
451
- maxWidth: column.maxWidth,
452
- width: column.width,
453
- height: actualRowHeight,
454
- }}
455
- role="gridcell"
456
- tabIndex={-1}
457
- >
458
- {isEditing && column.editor ? (
459
- column.editor({
460
- value,
461
- row,
462
- onChange: () => {
463
- // Handle cell value change
464
- },
465
- commit: () => {
466
- if (editing.onEditCommit) {
467
- editing.onEditCommit({
468
- rowId,
469
- colId: column.id,
470
- value,
471
- row,
472
- });
473
- }
474
- setEditingCell(null);
475
- },
476
- cancel: () => {
477
- setEditingCell(null);
478
- },
479
- })
480
- ) : (
481
- cellContent
482
- )}
483
- </TableCell>
484
- );
485
- };
486
-
487
- // Render row
488
- const renderRow = (row: T, index: number) => {
489
- if (!row) return null;
490
-
491
- const rowId = getRowId(row, index);
492
- // Use Set for O(1) lookup instead of Array.includes O(n)
493
- const isSelected = selectedRowIdsSet.has(rowId);
494
- const isExpanded = expandedRowIdsSet.has(rowId);
495
-
496
- return (
497
- <React.Fragment key={rowId}>
498
- <TableRow
499
- data-slot="row"
500
- data-selected={isSelected}
501
- data-expanded={isExpanded}
502
- sx={{
503
- '&:hover': {
504
- backgroundColor: theme.palette.action.hover,
505
- },
506
- ...(isSelected && {
507
- backgroundColor: theme.palette.action.selected,
508
- }),
509
- height: actualRowHeight,
510
- }}
511
- role="row"
512
- aria-selected={isSelected}
513
- >
514
- {selection.mode !== 'none' && (
515
- <TableCell padding="checkbox">
516
- <Checkbox
517
- checked={isSelected}
518
- onChange={(e) => handleRowSelection(rowId, e.target.checked)}
519
- inputProps={{
520
- 'aria-label': `Select row ${index + 1}`,
521
- }}
522
- />
523
- </TableCell>
524
- )}
525
-
526
- {expansion && (
527
- <TableCell padding="checkbox">
528
- <IconButton
529
- size="small"
530
- onClick={() => handleRowExpansion(rowId)}
531
- aria-label={isExpanded ? 'Collapse row' : 'Expand row'}
532
- >
533
- {isExpanded ? <ExpandLess /> : <ExpandMore />}
534
- </IconButton>
535
- </TableCell>
536
- )}
537
-
538
- {visibleColumns.map((column) =>
539
- renderDataCell(row, index, column)
540
- )}
541
- </TableRow>
542
-
543
- {expansion && isExpanded && (
544
- <TableRow>
545
- <TableCell colSpan={columns.length + (selection.mode !== 'none' ? 1 : 0) + (expansion ? 1 : 0)}>
546
- {expansion.render(row, index)}
547
- </TableCell>
548
- </TableRow>
549
- )}
550
- </React.Fragment>
551
- );
552
- };
553
-
554
- // Loading state
555
- if (loading) {
556
- return (
557
- <Box
558
- data-ui="datagrid"
559
- data-loading="true"
560
- sx={{
561
- display: 'flex',
562
- justifyContent: 'center',
563
- alignItems: 'center',
564
- height: 200,
565
- }}
566
- aria-label={ariaLabel}
567
- role="grid"
568
- {...htmlProps}
569
- >
570
- <CircularProgress />
571
- </Box>
572
- );
60
+ }
61
+
62
+ export const DataGrid = <T extends Record<string, unknown> = Record<string, unknown>>(
63
+ props: DataGridProps<T>,
64
+ ): React.JSX.Element => {
65
+ const model = useDataGridModel(props);
66
+ const { dataAttrs, placeholder } = resolveChrome(props);
67
+
68
+ if (props.loading) return <DataGridLoading {...placeholder} />;
69
+ if (props.error) return <DataGridError {...placeholder} error={props.error} />;
70
+ if (model.processedRows.length === 0) {
71
+ return <DataGridEmpty {...placeholder} emptyState={props.emptyState} />;
573
72
  }
574
73
 
575
- // Error state
576
- if (error) {
577
- return (
578
- <Box
579
- data-ui="datagrid"
580
- data-error="true"
581
- sx={{
582
- display: 'flex',
583
- justifyContent: 'center',
584
- alignItems: 'center',
585
- height: 200,
586
- flexDirection: 'column',
587
- gap: 2,
588
- }}
589
- aria-label={ariaLabel}
590
- role="grid"
591
- {...htmlProps}
592
- >
593
- <Typography color="error" variant="body1">
594
- {error}
595
- </Typography>
596
- </Box>
597
- );
598
- }
599
-
600
- // Empty state
601
- if (processedRows.length === 0 && !loading) {
602
- return (
603
- <Box
604
- data-ui="datagrid"
605
- sx={{
606
- display: 'flex',
607
- justifyContent: 'center',
608
- alignItems: 'center',
609
- height: 200,
610
- }}
611
- aria-label={ariaLabel}
612
- role="grid"
613
- {...htmlProps}
614
- >
615
- {emptyState || (
616
- <Typography variant="body1" color="text.secondary">
617
- No data available
618
- </Typography>
619
- )}
620
- </Box>
621
- );
622
- }
623
-
624
- // visibleColumns is already memoized above
625
- const totalColumns = visibleColumns.length +
626
- (selection.mode !== 'none' ? 1 : 0) +
627
- (expansion ? 1 : 0);
74
+ const leadingColumns = (props.selection?.mode ?? 'none') !== 'none' ? 1 : 0;
75
+ const totalColumns =
76
+ model.visibleColumns.length + leadingColumns + (props.expansion ? 1 : 0);
77
+ const section = { model, props, totalColumns };
628
78
 
629
79
  return (
630
80
  <Paper
631
- ref={containerRef}
632
- data-ui="datagrid"
633
- data-density={density}
634
- data-size-mode={sizeMode}
635
- data-virtual-rows={virtualizeRows}
636
- data-virtual-cols={virtualizeColumns}
637
- data-sticky-header={stickyHeader}
638
- className={className}
639
- style={style}
81
+ ref={model.containerRef}
82
+ {...dataAttrs}
83
+ data-testid={placeholder.dataTestId}
84
+ className={placeholder.className}
85
+ style={placeholder.style}
640
86
  role="grid"
641
- aria-label={ariaLabel}
642
- aria-description={ariaDescription}
643
- aria-rowcount={processedRows.length + 1} // +1 for header
87
+ aria-label={placeholder.ariaLabel}
88
+ aria-description={props.ariaDescription}
89
+ aria-rowcount={model.processedRows.length + 1} // +1 for the header row
644
90
  aria-colcount={totalColumns}
645
- data-testid={dataTestId}
646
- sx={{
647
- height: '100%',
648
- overflow: 'hidden',
649
- display: 'flex',
650
- flexDirection: 'column',
651
- }}
652
- {...htmlProps}
91
+ sx={{ height: '100%', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}
92
+ {...placeholder.htmlProps}
653
93
  >
654
94
  <TableContainer
655
- sx={{
656
- flex: 1,
657
- overflow: 'auto',
658
- position: 'relative',
659
- }}
660
- onScroll={handleScroll}
95
+ sx={{ flex: 1, overflow: 'auto', position: 'relative' }}
96
+ onScroll={model.onScroll}
661
97
  >
662
- <Table stickyHeader={stickyHeader}>
663
- <TableHead data-slot="header">
664
- <TableRow data-slot="header-row" role="row">
665
- {selection.mode !== 'none' && (
666
- <TableCell padding="checkbox" sx={{ position: stickyHeader ? 'sticky' : 'static', top: 0, zIndex: 1, backgroundColor: theme.palette.background.paper }}>
667
- {selection.mode === 'multi' && (
668
- <Checkbox
669
- indeterminate={currentSelectedRowIds.length > 0 && currentSelectedRowIds.length < processedRows.length}
670
- checked={processedRows.length > 0 && currentSelectedRowIds.length === processedRows.length}
671
- onChange={(e) => {
672
- const allRowIds = processedRows.map((row, index) => getRowId(row, index));
673
- const newSelected = e.target.checked ? allRowIds : [];
674
- if (selection.onChangeSelected) {
675
- selection.onChangeSelected(newSelected);
676
- } else {
677
- setInternalSelectedRowIds(newSelected);
678
- }
679
- }}
680
- inputProps={{
681
- 'aria-label': 'Select all rows',
682
- }}
683
- />
684
- )}
685
- </TableCell>
686
- )}
687
-
688
- {expansion && (
689
- <TableCell
690
- padding="checkbox"
691
- sx={{
692
- position: stickyHeader ? 'sticky' : 'static',
693
- top: 0,
694
- zIndex: 1,
695
- backgroundColor: theme.palette.background.paper,
696
- }}
697
- />
698
- )}
699
-
700
- {visibleColumns.map(renderHeaderCell)}
701
- </TableRow>
702
- </TableHead>
703
-
704
- <TableBody data-slot="body">
705
- {/* Top spacer for virtualization to maintain scroll position */}
706
- {virtualizeRows && visibleRange.start > 0 && processedRows.length > VIRTUALIZATION_THRESHOLD && (
707
- <TableRow>
708
- <TableCell
709
- colSpan={totalColumns}
710
- sx={{
711
- height: visibleRange.start * actualRowHeight,
712
- border: 'none',
713
- p: 0
714
- }}
715
- />
716
- </TableRow>
717
- )}
718
-
719
- {visibleRows.map((row, index) =>
720
- renderRow(row, virtualizeRows && processedRows.length > VIRTUALIZATION_THRESHOLD ? visibleRange.start + index : index)
721
- )}
722
-
723
- {/* Bottom spacer for virtualization */}
724
- {virtualizeRows && processedRows.length > visibleRange.end && processedRows.length > VIRTUALIZATION_THRESHOLD && (
725
- <TableRow>
726
- <TableCell
727
- colSpan={totalColumns}
728
- sx={{
729
- height: (processedRows.length - visibleRange.end) * actualRowHeight,
730
- border: 'none',
731
- p: 0
732
- }}
733
- />
734
- </TableRow>
735
- )}
736
- </TableBody>
98
+ <Table stickyHeader={props.stickyHeader ?? true}>
99
+ <GridHeader {...section} />
100
+ <GridBody {...section} />
737
101
  </Table>
738
102
  </TableContainer>
739
103
  </Paper>
740
104
  );
741
105
  };
742
106
 
743
- DataGrid.displayName = 'DataGrid';
107
+ /**
108
+ * The grid props that are NOT HTML attributes.
109
+ *
110
+ * `DataGridProps` extends `HTMLAttributes`, so the rest-spread carries the
111
+ * grid's own configuration too. React would warn (or worse, serialize an array
112
+ * of rows into an attribute) if any of it reached the DOM.
113
+ */
114
+ const GRID_ONLY_PROPS = [
115
+ 'rows',
116
+ 'columns',
117
+ 'getRowId',
118
+ 'sizeMode',
119
+ 'density',
120
+ 'rowHeight',
121
+ 'headerHeight',
122
+ 'footerHeight',
123
+ 'selection',
124
+ 'pagination',
125
+ 'sorting',
126
+ 'filtering',
127
+ 'expansion',
128
+ 'editing',
129
+ 'virtualizeRows',
130
+ 'virtualizeColumns',
131
+ 'stickyHeader',
132
+ 'stickyFooter',
133
+ 'loading',
134
+ 'error',
135
+ 'emptyState',
136
+ 'onRequestData',
137
+ 'ariaLabel',
138
+ 'ariaDescription',
139
+ ] as const;
140
+
141
+ function omitGridProps(rest: Record<string, unknown>): React.HTMLAttributes<HTMLElement> {
142
+ const html: Record<string, unknown> = { ...rest };
143
+ for (const key of GRID_ONLY_PROPS) delete html[key];
144
+ return html as React.HTMLAttributes<HTMLElement>;
145
+ }
146
+
147
+ DataGrid.displayName = 'DataGrid';