@12-apps/ui 1.0.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/components-catalog.md +14 -0
  2. package/package.json +42 -27
  3. package/src/__tests__/data-grid.test.tsx +125 -0
  4. package/src/__tests__/package-wiring.test.ts +12 -8
  5. package/src/__tests__/social-login-button.test.tsx +5 -5
  6. package/src/__tests__/viewport.test.ts +50 -0
  7. package/src/charts/index.ts +8 -0
  8. package/src/components/data-display/Alert/Alert.md +1 -1
  9. package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
  10. package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
  11. package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
  12. package/src/components/data-display/Alert/Alert.tsx +135 -328
  13. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
  14. package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
  15. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
  16. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
  17. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
  18. package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
  19. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
  20. package/src/components/data-display/Avatar/Avatar.tsx +154 -450
  21. package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
  22. package/src/components/data-display/Badge/Badge.animations.ts +67 -0
  23. package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
  24. package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
  25. package/src/components/data-display/Badge/Badge.styles.ts +261 -0
  26. package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
  27. package/src/components/data-display/Badge/Badge.tsx +151 -557
  28. package/src/components/data-display/Badge/Badge.variants.ts +106 -0
  29. package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
  30. package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
  31. package/src/components/data-display/Banner/Banner.styles.ts +120 -0
  32. package/src/components/data-display/Banner/Banner.tsx +179 -224
  33. package/src/components/data-display/Chart/Chart.md +48 -1
  34. package/src/components/data-display/Chart/Chart.tsx +82 -500
  35. package/src/components/data-display/Chart/Chart.types.ts +2 -0
  36. package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
  37. package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
  38. package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
  39. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
  40. package/src/components/data-display/Chart/SpecChart.tsx +53 -0
  41. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
  42. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
  43. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
  44. package/src/components/data-display/Chart/chart-format.ts +26 -0
  45. package/src/components/data-display/Chart/chart-internals.ts +148 -0
  46. package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
  47. package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
  48. package/src/components/data-display/Chart/chart-spec.ts +171 -0
  49. package/src/components/data-display/Chart/index.ts +30 -0
  50. package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
  51. package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
  52. package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
  53. package/src/components/data-display/Chip/Chip.tsx +31 -94
  54. package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
  55. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
  56. package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
  57. package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
  58. package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
  59. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
  60. package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
  61. package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
  62. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
  63. package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
  64. package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
  65. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
  66. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
  67. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
  68. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
  69. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
  70. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
  71. package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
  72. package/src/components/data-display/DataViews/base-card.tsx +278 -0
  73. package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
  74. package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
  75. package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
  76. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
  77. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
  78. package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
  79. package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
  80. package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
  81. package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
  82. package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
  83. package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
  84. package/src/components/data-display/DataViews/data-views-types.ts +312 -0
  85. package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
  86. package/src/components/data-display/DataViews/index.ts +33 -0
  87. package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
  88. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
  89. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
  90. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
  91. package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
  92. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
  93. package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
  94. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
  95. package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
  96. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
  97. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
  98. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
  99. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
  100. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
  101. package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
  102. package/src/components/data-display/LazyImage/LazyImage.md +1 -1
  103. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
  104. package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
  105. package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
  106. package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
  107. package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
  108. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
  109. package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
  110. package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
  111. package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
  112. package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
  113. package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
  114. package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
  115. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
  116. package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
  117. package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
  118. package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
  119. package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
  120. package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
  121. package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
  122. package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
  123. package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
  124. package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
  125. package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
  126. package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
  127. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
  128. package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
  129. package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
  130. package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
  131. package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
  132. package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
  133. package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
  134. package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
  135. package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
  136. package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
  137. package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
  138. package/src/components/data-display/Progress/Progress.styles.ts +101 -0
  139. package/src/components/data-display/Progress/Progress.tsx +185 -213
  140. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
  141. package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
  142. package/src/components/data-display/StatCard/StatCard.md +117 -0
  143. package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
  144. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
  145. package/src/components/data-display/StatCard/StatCard.tsx +236 -0
  146. package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
  147. package/src/components/data-display/StatCard/index.ts +7 -0
  148. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
  149. package/src/components/data-display/Stepper/Stepper.tsx +1 -1
  150. package/src/components/data-display/Table/Table.helpers.ts +31 -0
  151. package/src/components/data-display/Table/Table.hooks.ts +161 -0
  152. package/src/components/data-display/Table/Table.stories.tsx +3 -1
  153. package/src/components/data-display/Table/Table.styles.ts +223 -0
  154. package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
  155. package/src/components/data-display/Table/Table.tsx +291 -748
  156. package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
  157. package/src/components/data-display/Table/TableParts.tsx +298 -0
  158. package/src/components/data-display/Table/TableStates.tsx +21 -0
  159. package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
  160. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
  161. package/src/components/data-display/Timeline/Timeline.tsx +23 -284
  162. package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
  163. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
  164. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
  165. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
  166. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
  167. package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
  168. package/src/components/data-display/index.ts +1 -0
  169. package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
  170. package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
  171. package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
  172. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
  173. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
  174. package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
  175. package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
  176. package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
  177. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
  178. package/src/components/feedback/ConfirmAction/index.ts +16 -0
  179. package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
  180. package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
  181. package/src/components/feedback/Dialog/Dialog.tsx +26 -1
  182. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
  183. package/src/components/feedback/Modal/Modal.md +1 -1
  184. package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
  185. package/src/components/feedback/Sonner/Sonner.tsx +104 -288
  186. package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
  187. package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
  188. package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
  189. package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
  190. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
  191. package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
  192. package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
  193. package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
  194. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
  195. package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
  196. package/src/components/feedback/Toast/Toast.tsx +92 -81
  197. package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
  198. package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
  199. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
  200. package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
  201. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
  202. package/src/components/feedback/index.ts +1 -0
  203. package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
  204. package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
  205. package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
  206. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
  207. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
  208. package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
  209. package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
  210. package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
  211. package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
  212. package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
  213. package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
  214. package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
  215. package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
  216. package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
  217. package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
  218. package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
  219. package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
  220. package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
  221. package/src/components/form/Button/Button.helpers.ts +36 -0
  222. package/src/components/form/Button/Button.stories.tsx +4 -1
  223. package/src/components/form/Button/Button.styles.ts +225 -0
  224. package/src/components/form/Button/Button.test.stories.tsx +25 -21
  225. package/src/components/form/Button/Button.tsx +45 -252
  226. package/src/components/form/Calendar/Calendar.dates.ts +100 -0
  227. package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
  228. package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
  229. package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
  230. package/src/components/form/Calendar/Calendar.selection.ts +156 -0
  231. package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
  232. package/src/components/form/Calendar/Calendar.tsx +63 -551
  233. package/src/components/form/Calendar/Calendar.types.ts +16 -0
  234. package/src/components/form/Calendar/CalendarDay.tsx +127 -0
  235. package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
  236. package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
  237. package/src/components/form/CepField/CepField.md +138 -0
  238. package/src/components/form/CepField/CepField.stories.tsx +138 -0
  239. package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
  240. package/src/components/form/CepField/CepField.tsx +211 -0
  241. package/src/components/form/CepField/CepField.types.ts +73 -0
  242. package/src/components/form/CepField/index.ts +2 -0
  243. package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
  244. package/src/components/form/Checkbox/Checkbox.tsx +19 -16
  245. package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
  246. package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
  247. package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
  248. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
  249. package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
  250. package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
  251. package/src/components/form/Command/Command.hooks.ts +207 -0
  252. package/src/components/form/Command/Command.parts.tsx +58 -0
  253. package/src/components/form/Command/Command.styles.ts +83 -0
  254. package/src/components/form/Command/Command.tsx +61 -270
  255. package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
  256. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
  257. package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
  258. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
  259. package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
  260. package/src/components/form/HeaderButton/index.ts +2 -0
  261. package/src/components/form/Input/Input.stories.tsx +5 -1
  262. package/src/components/form/Input/Input.styles.ts +176 -0
  263. package/src/components/form/Input/Input.test.stories.tsx +30 -23
  264. package/src/components/form/Input/Input.tsx +71 -211
  265. package/src/components/form/Input/Input.types.ts +11 -0
  266. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
  267. package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
  268. package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
  269. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
  270. package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
  271. package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
  272. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
  273. package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
  274. package/src/components/form/PhoneInput/countries.ts +54 -0
  275. package/src/components/form/PhoneInput/phone.ts +47 -0
  276. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
  277. package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
  278. package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
  279. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
  280. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
  281. package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
  282. package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
  283. package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
  284. package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
  285. package/src/components/form/Select/Select.test.stories.tsx +35 -27
  286. package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
  287. package/src/components/form/Switch/Switch.md +2 -1
  288. package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
  289. package/src/components/form/Textarea/Textarea.styles.ts +143 -0
  290. package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
  291. package/src/components/form/Textarea/Textarea.tsx +114 -535
  292. package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
  293. package/src/components/form/Toggle/Toggle.styles.ts +211 -0
  294. package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
  295. package/src/components/form/Toggle/Toggle.tsx +10 -185
  296. package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
  297. package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
  298. package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
  299. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
  300. package/src/components/form/UploadButton/UploadButton.tsx +76 -305
  301. package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
  302. package/src/components/form/index.ts +1 -0
  303. package/src/components/form/total-form/Fields/CepField.tsx +143 -0
  304. package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
  305. package/src/components/form/total-form/Fields/index.ts +4 -0
  306. package/src/components/form/total-form/FormContainer.tsx +3 -3
  307. package/src/components/form/total-form/SubmitButton.tsx +25 -2
  308. package/src/components/form/total-form/total-form.test.tsx +118 -1
  309. package/src/components/layout/Accordion/Accordion.md +1 -1
  310. package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
  311. package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
  312. package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
  313. package/src/components/layout/Accordion/Accordion.tsx +2 -75
  314. package/src/components/layout/Card/Card.helpers.ts +30 -0
  315. package/src/components/layout/Card/Card.stories.tsx +9 -11
  316. package/src/components/layout/Card/Card.styles.ts +182 -0
  317. package/src/components/layout/Card/Card.test.stories.tsx +46 -46
  318. package/src/components/layout/Card/Card.tsx +24 -241
  319. package/src/components/layout/CardGrid/CardGrid.md +96 -0
  320. package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
  321. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
  322. package/src/components/layout/CardGrid/index.ts +2 -0
  323. package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
  324. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
  325. package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
  326. package/src/components/layout/Container/Container.test.stories.tsx +11 -16
  327. package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
  328. package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
  329. package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
  330. package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
  331. package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
  332. package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
  333. package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
  334. package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
  335. package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
  336. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
  337. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
  338. package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
  339. package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
  340. package/src/components/layout/Dashboard/index.ts +2 -1
  341. package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
  342. package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
  343. package/src/components/layout/Drawer/Drawer.tsx +67 -53
  344. package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
  345. package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
  346. package/src/components/layout/Resizable/Resizable.tsx +38 -153
  347. package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
  348. package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
  349. package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
  350. package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
  351. package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
  352. package/src/components/layout/Separator/Separator.styles.ts +128 -0
  353. package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
  354. package/src/components/layout/Separator/Separator.tsx +6 -132
  355. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
  356. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
  357. package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
  358. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
  359. package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
  360. package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
  361. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
  362. package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
  363. package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
  364. package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
  365. package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
  366. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
  367. package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
  368. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
  369. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
  370. package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
  371. package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
  372. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
  373. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
  374. package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
  375. package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
  376. package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
  377. package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
  378. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
  379. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
  380. package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
  381. package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
  382. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
  383. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
  384. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
  385. package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
  386. package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
  387. package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
  388. package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
  389. package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
  390. package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
  391. package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
  392. package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
  393. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
  394. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
  395. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
  396. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
  397. package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
  398. package/src/components/navigation/Pagination/Pagination.md +2 -1
  399. package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
  400. package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
  401. package/src/components/navigation/Pagination/Pagination.tsx +178 -313
  402. package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
  403. package/src/components/navigation/Tabs/Tabs.md +3 -1
  404. package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
  405. package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
  406. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
  407. package/src/components/navigation/Tabs/Tabs.tsx +192 -361
  408. package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
  409. package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
  410. package/src/components/typography/Code/Code.test.stories.tsx +5 -5
  411. package/src/components/typography/Code/Code.tsx +74 -49
  412. package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
  413. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
  414. package/src/components/typography/Text/Text.tsx +84 -77
  415. package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
  416. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
  417. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
  418. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
  419. package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
  420. package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
  421. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
  422. package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
  423. package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
  424. package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
  425. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
  426. package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
  427. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
  428. package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
  429. package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
  430. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
  431. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
  432. package/src/components/utility/InstallPrompt/index.ts +10 -0
  433. package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
  434. package/src/components/utility/Transition/Transition.helpers.ts +40 -0
  435. package/src/components/utility/Transition/Transition.tsx +17 -68
  436. package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
  437. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
  438. package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
  439. package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
  440. package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
  441. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
  442. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
  443. package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
  444. package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
  445. package/src/components/utility/index.ts +1 -0
  446. package/src/mui/Link.ts +2 -0
  447. package/src/utils/index.ts +2 -1
  448. package/src/utils/viewport.ts +65 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * The DataGrid's cell and row renderers.
3
+ *
4
+ * Components rather than closures over the grid's scope: as inline `render*`
5
+ * functions they could reach every piece of the component's state, which is how
6
+ * the body grew past 700 lines. Each one now states exactly what it needs.
7
+ */
8
+ import ArrowDownward from '@mui/icons-material/ArrowDownward';
9
+ import ArrowUpward from '@mui/icons-material/ArrowUpward';
10
+ import ExpandLess from '@mui/icons-material/ExpandLess';
11
+ import ExpandMore from '@mui/icons-material/ExpandMore';
12
+ import UnfoldMore from '@mui/icons-material/UnfoldMore';
13
+ import { Button, Checkbox, IconButton, TableCell, TableRow, Typography, useTheme } from '@mui/material';
14
+ import React from 'react';
15
+
16
+ import { cellValue } from './DataGrid.rows';
17
+ import type { DataGridModel } from './DataGrid.model';
18
+ import type { DataGridProps, GridColumn, GridSort } from './DataGrid.types';
19
+
20
+ /** The glyph for a column's current sort state — unsorted included. */
21
+ function SortIcon({ dir }: { dir: GridSort['dir'] | undefined }): React.JSX.Element {
22
+ if (!dir) return <UnfoldMore fontSize="small" sx={{ opacity: 0.5 }} />;
23
+ return dir === 'asc' ? <ArrowUpward fontSize="small" /> : <ArrowDownward fontSize="small" />;
24
+ }
25
+
26
+ /** `aria-sort`'s three values, which are not the same words as `SortDirection`. */
27
+ function ariaSort(dir: GridSort['dir'] | undefined): 'none' | 'ascending' | 'descending' {
28
+ if (!dir) return 'none';
29
+ return dir === 'asc' ? 'ascending' : 'descending';
30
+ }
31
+
32
+ export interface HeaderCellProps<T extends Record<string, unknown>> {
33
+ column: GridColumn<T>;
34
+ sort: GridSort | undefined;
35
+ sortable: boolean;
36
+ stickyHeader: boolean;
37
+ headerHeight: number;
38
+ onSort: (columnId: string) => void;
39
+ }
40
+
41
+ export function HeaderCell<T extends Record<string, unknown>>({
42
+ column,
43
+ sort,
44
+ sortable,
45
+ stickyHeader,
46
+ headerHeight,
47
+ onSort,
48
+ }: HeaderCellProps<T>): React.JSX.Element {
49
+ const theme = useTheme();
50
+ const content = column.headerCell ? column.headerCell(column) : column.header;
51
+ return (
52
+ <TableCell
53
+ data-slot="header-cell"
54
+ data-sort={sort?.dir ?? 'none'}
55
+ sx={{
56
+ position: stickyHeader ? 'sticky' : 'static',
57
+ top: 0,
58
+ zIndex: 1,
59
+ backgroundColor: theme.palette.background.paper,
60
+ borderBottom: `1px solid ${theme.palette.divider}`,
61
+ height: headerHeight,
62
+ minWidth: column.minWidth,
63
+ maxWidth: column.maxWidth,
64
+ width: column.width,
65
+ }}
66
+ role="columnheader"
67
+ aria-sort={ariaSort(sort?.dir)}
68
+ >
69
+ {sortable ? (
70
+ <Button
71
+ onClick={() => onSort(column.id)}
72
+ sx={{
73
+ justifyContent: 'flex-start',
74
+ textTransform: 'none',
75
+ fontWeight: 500,
76
+ color: 'inherit',
77
+ p: 0,
78
+ minWidth: 0,
79
+ '&:hover': { backgroundColor: 'transparent', opacity: 0.8 },
80
+ }}
81
+ endIcon={<SortIcon dir={sort?.dir} />}
82
+ aria-label={column.ariaLabel ?? `Sort by ${String(column.header)}`}
83
+ >
84
+ {content}
85
+ </Button>
86
+ ) : (
87
+ <Typography variant="subtitle2" fontWeight={500}>
88
+ {content}
89
+ </Typography>
90
+ )}
91
+ </TableCell>
92
+ );
93
+ }
94
+
95
+ export interface DataCellProps<T extends Record<string, unknown>> {
96
+ row: T;
97
+ rowIndex: number;
98
+ rowId: string | number;
99
+ column: GridColumn<T>;
100
+ rowHeight: number;
101
+ editing: NonNullable<DataGridProps<T>['editing']>;
102
+ model: Pick<DataGridModel<T>, 'editingCell' | 'setEditingCell'>;
103
+ }
104
+
105
+ export function DataCell<T extends Record<string, unknown>>({
106
+ row,
107
+ rowIndex,
108
+ rowId,
109
+ column,
110
+ rowHeight,
111
+ editing,
112
+ model,
113
+ }: DataCellProps<T>): React.JSX.Element {
114
+ const value = cellValue(row, column);
115
+ const isEditing =
116
+ model.editingCell?.rowId === rowId && model.editingCell?.colId === column.id;
117
+ const content = column.cell
118
+ ? column.cell({ value, row, rowIndex, col: column })
119
+ : String(value ?? '');
120
+ return (
121
+ <TableCell
122
+ data-slot="cell"
123
+ data-col-id={column.id}
124
+ data-editing={isEditing}
125
+ sx={{
126
+ minWidth: column.minWidth,
127
+ maxWidth: column.maxWidth,
128
+ width: column.width,
129
+ height: rowHeight,
130
+ }}
131
+ role="gridcell"
132
+ tabIndex={-1}
133
+ >
134
+ {isEditing && column.editor
135
+ ? column.editor({
136
+ value,
137
+ row,
138
+ onChange: () => undefined,
139
+ commit: () => {
140
+ editing.onEditCommit?.({ rowId, colId: column.id, value, row });
141
+ model.setEditingCell(null);
142
+ },
143
+ cancel: () => model.setEditingCell(null),
144
+ })
145
+ : content}
146
+ </TableCell>
147
+ );
148
+ }
149
+
150
+ export interface GridRowProps<T extends Record<string, unknown>> {
151
+ row: T;
152
+ index: number;
153
+ rowId: string | number;
154
+ isSelected: boolean;
155
+ isExpanded: boolean;
156
+ totalColumns: number;
157
+ model: DataGridModel<T>;
158
+ props: DataGridProps<T>;
159
+ }
160
+
161
+ /** The leading checkbox / chevron cells, which exist only for enabled features. */
162
+ function RowControls<T extends Record<string, unknown>>({
163
+ index,
164
+ rowId,
165
+ isSelected,
166
+ isExpanded,
167
+ model,
168
+ props,
169
+ }: GridRowProps<T>): React.JSX.Element {
170
+ const { selection = { mode: 'none' }, expansion } = props;
171
+ return (
172
+ <>
173
+ {selection.mode !== 'none' && (
174
+ <TableCell padding="checkbox">
175
+ <Checkbox
176
+ checked={isSelected}
177
+ onChange={(event) => model.onToggleRow(rowId, event.target.checked)}
178
+ inputProps={{ 'aria-label': `Select row ${index + 1}` }}
179
+ />
180
+ </TableCell>
181
+ )}
182
+ {expansion && (
183
+ <TableCell padding="checkbox">
184
+ <IconButton
185
+ size="small"
186
+ onClick={() => model.onToggleExpansion(rowId)}
187
+ aria-label={isExpanded ? 'Collapse row' : 'Expand row'}
188
+ >
189
+ {isExpanded ? <ExpandLess /> : <ExpandMore />}
190
+ </IconButton>
191
+ </TableCell>
192
+ )}
193
+ </>
194
+ );
195
+ }
196
+
197
+ export function GridRow<T extends Record<string, unknown>>(
198
+ rowProps: GridRowProps<T>,
199
+ ): React.JSX.Element {
200
+ const { row, index, rowId, isSelected, isExpanded, totalColumns, model, props } = rowProps;
201
+ const theme = useTheme();
202
+ const expansion = props.expansion;
203
+ return (
204
+ <React.Fragment>
205
+ <TableRow
206
+ data-slot="row"
207
+ data-selected={isSelected}
208
+ data-expanded={isExpanded}
209
+ sx={{
210
+ '&:hover': { backgroundColor: theme.palette.action.hover },
211
+ ...(isSelected && { backgroundColor: theme.palette.action.selected }),
212
+ height: model.rowHeight,
213
+ }}
214
+ role="row"
215
+ aria-selected={isSelected}
216
+ >
217
+ <RowControls {...rowProps} />
218
+ {model.visibleColumns.map((column) => (
219
+ <DataCell
220
+ key={column.id}
221
+ row={row}
222
+ rowIndex={index}
223
+ rowId={rowId}
224
+ column={column}
225
+ rowHeight={model.rowHeight}
226
+ editing={props.editing ?? { mode: 'none' }}
227
+ model={model}
228
+ />
229
+ ))}
230
+ </TableRow>
231
+ {expansion && isExpanded && (
232
+ <TableRow>
233
+ <TableCell colSpan={totalColumns}>{expansion.render(row, index)}</TableCell>
234
+ </TableRow>
235
+ )}
236
+ </React.Fragment>
237
+ );
238
+ }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * The DataGrid's STATE, in one hook.
3
+ *
4
+ * The component used to hold all of it inline, which made its body 700 lines
5
+ * long and put the row pipeline, the virtualization window and the JSX in one
6
+ * scope where any of them could reach any other. Split out, each piece is a
7
+ * hook with named inputs and the render is left with nothing to do but render.
8
+ *
9
+ * Controlled/uncontrolled is resolved in ONE place per feature: a caller that
10
+ * passes `sorting.sortBy` owns the sort, and a caller that does not gets the
11
+ * internal state — the component never holds a second, divergent copy.
12
+ */
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
+ import type React from 'react';
15
+
16
+ import { processRows } from './DataGrid.rows';
17
+ import type {
18
+ DataGridProps,
19
+ GridColumn,
20
+ GridFilter,
21
+ GridSort,
22
+ SortDirection,
23
+ } from './DataGrid.types';
24
+
25
+ const DEFAULT_ROW_HEIGHT = 52;
26
+ const DEFAULT_PAGE_SIZE = 50;
27
+ /** Rows rendered on either side of the window, so a fast scroll stays filled. */
28
+ const OVERSCAN_COUNT = 20;
29
+ /** Below this many rows, virtualization costs more than it saves. */
30
+ const VIRTUALIZATION_THRESHOLD = 100;
31
+ /** The initial (and reset) window size, before a scroll has measured anything. */
32
+ const INITIAL_WINDOW = 50;
33
+ /** Re-window only past this much drift, so a scroll is not one setState per pixel. */
34
+ const WINDOW_DRIFT = 5;
35
+
36
+ /** Which id is being edited, or `null`. */
37
+ export interface EditingCell {
38
+ rowId: string | number;
39
+ colId: string;
40
+ }
41
+
42
+ /** The visible slice of a virtualized body. */
43
+ export interface VisibleRange {
44
+ start: number;
45
+ end: number;
46
+ }
47
+
48
+ /** Everything the render needs, and nothing it has to derive. */
49
+ export interface DataGridModel<T extends Record<string, unknown>> {
50
+ containerRef: React.RefObject<HTMLDivElement | null>;
51
+ rowHeight: number;
52
+ visibleColumns: GridColumn<T>[];
53
+ processedRows: T[];
54
+ visibleRows: T[];
55
+ visibleRange: VisibleRange;
56
+ virtualized: boolean;
57
+ sortBy: readonly GridSort[];
58
+ selectedRowIds: Array<string | number>;
59
+ selectedRowIdsSet: ReadonlySet<string | number>;
60
+ expandedRowIdsSet: ReadonlySet<string | number>;
61
+ editingCell: EditingCell | null;
62
+ setEditingCell: (cell: EditingCell | null) => void;
63
+ setSelectedRowIds: (ids: Array<string | number>) => void;
64
+ onSort: (columnId: string) => void;
65
+ onToggleRow: (rowId: string | number, isSelected: boolean) => void;
66
+ onToggleExpansion: (rowId: string | number) => void;
67
+ onScroll: (event: React.UIEvent<HTMLDivElement>) => void;
68
+ }
69
+
70
+ /** The row height a density implies. */
71
+ function densityHeight(base: number, density: DataGridProps['density']): number {
72
+ if (density === 'compact') return Math.max(base * 0.8, 32);
73
+ if (density === 'spacious') return base * 1.2;
74
+ return base;
75
+ }
76
+
77
+ /** The rows this render should show, with every client-mode step resolved. */
78
+ function useProcessedRows<T extends Record<string, unknown>>(
79
+ props: DataGridProps<T>,
80
+ columnMap: ReadonlyMap<string, GridColumn<T>>,
81
+ sortBy: readonly GridSort[],
82
+ filters: readonly GridFilter[],
83
+ ): T[] {
84
+ const { rows, filtering = {}, sorting = {}, pagination, virtualizeRows = true } = props;
85
+ const pageIndex = pagination?.pageIndex ?? 0;
86
+ const pageSize = pagination?.pageSize ?? DEFAULT_PAGE_SIZE;
87
+ return useMemo(
88
+ () =>
89
+ processRows({
90
+ rows,
91
+ columnMap,
92
+ filters,
93
+ sortBy,
94
+ needsFiltering: filtering.mode === 'client' && filters.length > 0,
95
+ needsSorting: sorting.mode === 'client' && sortBy.length > 0,
96
+ // Virtualization already slices; doing both would page a page.
97
+ needsPagination: pagination?.mode === 'client' && !virtualizeRows,
98
+ pageIndex,
99
+ pageSize,
100
+ }),
101
+ [
102
+ rows,
103
+ columnMap,
104
+ filters,
105
+ sortBy,
106
+ filtering.mode,
107
+ sorting.mode,
108
+ pagination?.mode,
109
+ virtualizeRows,
110
+ pageIndex,
111
+ pageSize,
112
+ ],
113
+ );
114
+ }
115
+
116
+ /**
117
+ * The virtualization window and the scroll handler that moves it.
118
+ *
119
+ * Throttled through `requestAnimationFrame`, and only committed once the window
120
+ * has drifted meaningfully — a `setState` per scroll event re-renders every
121
+ * visible row for a movement the eye cannot see.
122
+ */
123
+ function useRowWindow(
124
+ rowCount: number,
125
+ rowHeight: number,
126
+ enabled: boolean,
127
+ ): { range: VisibleRange; onScroll: (event: React.UIEvent<HTMLDivElement>) => void } {
128
+ const [range, setRange] = useState<VisibleRange>({ start: 0, end: INITIAL_WINDOW });
129
+ const rafRef = useRef<number | null>(null);
130
+
131
+ useEffect(() => {
132
+ if (rowCount < range.end) setRange({ start: 0, end: Math.min(INITIAL_WINDOW, rowCount) });
133
+ }, [rowCount, range.end]);
134
+
135
+ useEffect(
136
+ () => () => {
137
+ if (rafRef.current !== null) window.cancelAnimationFrame(rafRef.current);
138
+ },
139
+ [],
140
+ );
141
+
142
+ const onScroll = useCallback(
143
+ (event: React.UIEvent<HTMLDivElement>) => {
144
+ if (!enabled) return;
145
+ const { scrollTop, clientHeight } = event.currentTarget;
146
+ if (rafRef.current !== null) window.cancelAnimationFrame(rafRef.current);
147
+ rafRef.current = window.requestAnimationFrame(() => {
148
+ const first = Math.floor(scrollTop / rowHeight);
149
+ const visible = Math.ceil(clientHeight / rowHeight);
150
+ const start = Math.max(0, first - OVERSCAN_COUNT);
151
+ const end = Math.min(first + visible + OVERSCAN_COUNT, rowCount);
152
+ setRange((current) =>
153
+ Math.abs(start - current.start) > WINDOW_DRIFT ||
154
+ Math.abs(end - current.end) > WINDOW_DRIFT
155
+ ? { start, end }
156
+ : current,
157
+ );
158
+ rafRef.current = null;
159
+ });
160
+ },
161
+ [enabled, rowHeight, rowCount],
162
+ );
163
+
164
+ return { range, onScroll };
165
+ }
166
+
167
+ /** The next `sortBy` for a header press: asc → desc → unsorted. */
168
+ function nextSortBy(current: readonly GridSort[], columnId: string): GridSort[] {
169
+ const existing = current.find((sort) => sort.id === columnId);
170
+ let dir: SortDirection = 'asc';
171
+ if (existing?.dir === 'asc') dir = 'desc';
172
+ else if (existing?.dir === 'desc') dir = null;
173
+ return dir ? [{ id: columnId, dir }] : current.filter((sort) => sort.id !== columnId);
174
+ }
175
+
176
+ /** Selection + expansion, each resolved once against its controlled prop. */
177
+ function useRowFlags<T extends Record<string, unknown>>(
178
+ props: DataGridProps<T>,
179
+ ): Pick<
180
+ DataGridModel<T>,
181
+ | 'selectedRowIds'
182
+ | 'selectedRowIdsSet'
183
+ | 'expandedRowIdsSet'
184
+ | 'setSelectedRowIds'
185
+ | 'onToggleRow'
186
+ | 'onToggleExpansion'
187
+ > {
188
+ const { selection = { mode: 'none' }, expansion } = props;
189
+ const [internalSelected, setInternalSelected] = useState(selection.defaultSelectedRowIds ?? []);
190
+ const [internalExpanded, setInternalExpanded] = useState(expansion?.defaultExpandedRowIds ?? []);
191
+ const selectedRowIds = selection.selectedRowIds ?? internalSelected;
192
+ const expandedRowIds = expansion?.expandedRowIds ?? internalExpanded;
193
+
194
+ const commitSelected = useCallback(
195
+ (ids: Array<string | number>) => {
196
+ if (selection.onChangeSelected) selection.onChangeSelected(ids);
197
+ else setInternalSelected(ids);
198
+ },
199
+ [selection],
200
+ );
201
+
202
+ return {
203
+ selectedRowIds,
204
+ selectedRowIdsSet: useMemo(() => new Set(selectedRowIds), [selectedRowIds]),
205
+ expandedRowIdsSet: useMemo(() => new Set(expandedRowIds), [expandedRowIds]),
206
+ setSelectedRowIds: commitSelected,
207
+ onToggleRow: (rowId, isSelected) => {
208
+ if (selection.mode === 'single') commitSelected(isSelected ? [rowId] : []);
209
+ else if (selection.mode === 'multi') {
210
+ commitSelected(
211
+ isSelected
212
+ ? [...selectedRowIds, rowId]
213
+ : selectedRowIds.filter((id) => id !== rowId),
214
+ );
215
+ }
216
+ },
217
+ onToggleExpansion: (rowId) => {
218
+ if (!expansion) return;
219
+ const next = expandedRowIds.includes(rowId)
220
+ ? expandedRowIds.filter((id) => id !== rowId)
221
+ : [...expandedRowIds, rowId];
222
+ if (expansion.onChangeExpanded) expansion.onChangeExpanded(next);
223
+ else setInternalExpanded(next);
224
+ },
225
+ };
226
+ }
227
+
228
+ /**
229
+ * A header press, routed to whoever owns the sort: the server (which must
230
+ * re-query — sorting the current page locally would reorder one page of a
231
+ * dataset and call it sorted), the caller's `onChangeSortBy`, or internal state.
232
+ */
233
+ function useSortHandler<T extends Record<string, unknown>>(
234
+ props: DataGridProps<T>,
235
+ sortBy: readonly GridSort[],
236
+ filters: readonly GridFilter[],
237
+ setInternalSortBy: (next: GridSort[]) => void,
238
+ ): (columnId: string) => void {
239
+ const { sorting = {}, pagination, onRequestData } = props;
240
+ return useCallback(
241
+ (columnId: string) => {
242
+ const next = nextSortBy(sortBy, columnId);
243
+ if (sorting.mode === 'server' && onRequestData) {
244
+ onRequestData({
245
+ pageIndex: pagination?.pageIndex ?? 0,
246
+ pageSize: pagination?.pageSize ?? DEFAULT_PAGE_SIZE,
247
+ sortBy: next,
248
+ filters: [...filters],
249
+ });
250
+ return;
251
+ }
252
+ if (sorting.onChangeSortBy) sorting.onChangeSortBy(next);
253
+ else setInternalSortBy(next);
254
+ },
255
+ [sortBy, sorting, onRequestData, pagination, filters, setInternalSortBy],
256
+ );
257
+ }
258
+
259
+ /**
260
+ * Sort and filter, each resolved ONCE against its controlled prop: a caller
261
+ * that passes `sorting.sortBy` owns the sort, and one that does not gets the
262
+ * internal state. The grid never holds a second, divergent copy.
263
+ */
264
+ function useSortAndFilter<T extends Record<string, unknown>>(
265
+ props: DataGridProps<T>,
266
+ ): {
267
+ sortBy: readonly GridSort[];
268
+ filters: readonly GridFilter[];
269
+ setInternalSortBy: (next: GridSort[]) => void;
270
+ } {
271
+ const { sorting = {}, filtering = {} } = props;
272
+ const [internalSortBy, setInternalSortBy] = useState<GridSort[]>(sorting.defaultSortBy ?? []);
273
+ const [internalFilters] = useState<GridFilter[]>(filtering.defaultFilters ?? []);
274
+ return {
275
+ sortBy: sorting.sortBy ?? internalSortBy,
276
+ filters: filtering.filters ?? internalFilters,
277
+ setInternalSortBy,
278
+ };
279
+ }
280
+
281
+ export function useDataGridModel<T extends Record<string, unknown>>(
282
+ props: DataGridProps<T>,
283
+ ): DataGridModel<T> {
284
+ const { columns } = props;
285
+ const containerRef = useRef<HTMLDivElement>(null);
286
+ const [editingCell, setEditingCell] = useState<EditingCell | null>(null);
287
+ const { sortBy, filters, setInternalSortBy } = useSortAndFilter(props);
288
+ const rowHeight = densityHeight(props.rowHeight ?? DEFAULT_ROW_HEIGHT, props.density);
289
+ const columnMap = useMemo(
290
+ () => new Map(columns.map((column) => [column.id, column])),
291
+ [columns],
292
+ );
293
+ const visibleColumns = useMemo(() => columns.filter((column) => !column.hidden), [columns]);
294
+ const processedRows = useProcessedRows(props, columnMap, sortBy, filters);
295
+ const virtualized =
296
+ (props.virtualizeRows ?? true) && processedRows.length > VIRTUALIZATION_THRESHOLD;
297
+ const { range, onScroll } = useRowWindow(processedRows.length, rowHeight, virtualized);
298
+ const onSort = useSortHandler(props, sortBy, filters, setInternalSortBy);
299
+
300
+ return {
301
+ containerRef,
302
+ rowHeight,
303
+ visibleColumns,
304
+ processedRows,
305
+ visibleRows: virtualized ? processedRows.slice(range.start, range.end) : processedRows,
306
+ visibleRange: range,
307
+ virtualized,
308
+ sortBy,
309
+ editingCell,
310
+ setEditingCell,
311
+ onSort,
312
+ onScroll,
313
+ ...useRowFlags(props),
314
+ };
315
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The DataGrid's CLIENT-MODE row pipeline: filter, sort, paginate.
3
+ *
4
+ * Lifted out of the component so it is a pure function of its inputs — no
5
+ * hooks, no theme, no refs — which is the only way the comparator can be read
6
+ * (and tested) at all. Server modes skip every step here: the rows arrived
7
+ * already filtered, sorted and paged, and re-doing any of it locally would make
8
+ * the grid disagree with the page count the server just sent.
9
+ */
10
+ import type { GridColumn, GridFilter, GridSort } from './DataGrid.types';
11
+
12
+ /** The value a column reads out of a row: accessor fn, accessor key, or id. */
13
+ export function cellValue<T extends Record<string, unknown>>(
14
+ row: T,
15
+ column: GridColumn<T>,
16
+ ): unknown {
17
+ const { accessor } = column;
18
+ if (typeof accessor === 'function') return accessor(row);
19
+ if (accessor !== undefined) return row[accessor];
20
+ return row[column.id];
21
+ }
22
+
23
+ /**
24
+ * Order two cell values, with absent ones sorting first.
25
+ *
26
+ * Numbers compare as numbers and everything else as a string — the grid has no
27
+ * per-column comparator, and stringifying a number would sort 10 before 9.
28
+ */
29
+ function compareValues(a: unknown, b: unknown): number {
30
+ if (a == null && b == null) return 0;
31
+ if (a == null) return -1;
32
+ if (b == null) return 1;
33
+ const left = typeof a === 'number' ? a : String(a);
34
+ const right = typeof b === 'number' ? b : String(b);
35
+ if (left < right) return -1;
36
+ return left > right ? 1 : 0;
37
+ }
38
+
39
+ /**
40
+ * A comparator over the whole `sortBy` list — earlier entries win, later ones
41
+ * break their ties. A sort entry naming an unknown column, or carrying a `null`
42
+ * direction, is skipped rather than treated as ascending.
43
+ */
44
+ function makeComparator<T extends Record<string, unknown>>(
45
+ sortBy: readonly GridSort[],
46
+ columnMap: ReadonlyMap<string, GridColumn<T>>,
47
+ ): (a: T, b: T) => number {
48
+ return (a, b) => {
49
+ for (const sort of sortBy) {
50
+ const column = columnMap.get(sort.id);
51
+ if (!column || !sort.dir) continue;
52
+ const comparison = compareValues(cellValue(a, column), cellValue(b, column));
53
+ if (comparison !== 0) return sort.dir === 'desc' ? -comparison : comparison;
54
+ }
55
+ return 0;
56
+ };
57
+ }
58
+
59
+ /** Does `row` satisfy one filter? An unknown column or empty value matches all. */
60
+ function matchesFilter<T extends Record<string, unknown>>(
61
+ row: T,
62
+ filter: GridFilter,
63
+ columnMap: ReadonlyMap<string, GridColumn<T>>,
64
+ ): boolean {
65
+ const column = columnMap.get(filter.id);
66
+ if (!column || !filter.value) return true;
67
+ const value = cellValue(row, column);
68
+ if (column.filterPredicate) return column.filterPredicate(value, filter.value, row);
69
+ return String(value ?? '')
70
+ .toLowerCase()
71
+ .includes(String(filter.value).toLowerCase());
72
+ }
73
+
74
+ /** Which client-mode steps this render actually needs. */
75
+ interface RowPipeline<T extends Record<string, unknown>> {
76
+ rows: T[];
77
+ columnMap: ReadonlyMap<string, GridColumn<T>>;
78
+ filters: readonly GridFilter[];
79
+ sortBy: readonly GridSort[];
80
+ needsFiltering: boolean;
81
+ needsSorting: boolean;
82
+ needsPagination: boolean;
83
+ pageIndex: number;
84
+ pageSize: number;
85
+ }
86
+
87
+ /**
88
+ * Run the pipeline, copying the array only when a step actually has to modify
89
+ * it. The identity of the untouched array is load-bearing: the grid's memos and
90
+ * every consumer's `rows === prev` check depend on a no-op pipeline returning
91
+ * exactly what it was given.
92
+ */
93
+ export function processRows<T extends Record<string, unknown>>(pipeline: RowPipeline<T>): T[] {
94
+ const { rows, columnMap, needsFiltering, needsSorting, needsPagination } = pipeline;
95
+ if (!needsFiltering && !needsSorting && !needsPagination) return rows;
96
+
97
+ let result = rows;
98
+ if (needsFiltering) {
99
+ result = result.filter((row) =>
100
+ pipeline.filters.every((filter) => matchesFilter(row, filter, columnMap)),
101
+ );
102
+ }
103
+ if (needsSorting) {
104
+ // Copy only here: `Array#sort` is in-place and the caller owns `rows`.
105
+ result = [...result].sort(makeComparator(pipeline.sortBy, columnMap));
106
+ }
107
+ if (needsPagination) {
108
+ const start = pipeline.pageIndex * pipeline.pageSize;
109
+ result = result.slice(start, start + pipeline.pageSize);
110
+ }
111
+ return result;
112
+ }