@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,171 @@
1
+ import type { ChartDataPoint, ChartProps } from './Chart.types';
2
+ import type { ChartSemanticColor, ChartSpec, ChartSpecSeries } from './ChartSpec.types';
3
+ import { chartValueFormatter } from './chart-format';
4
+ import {
5
+ getChartSpecRenderer,
6
+ getRegisteredChartSpecTypes,
7
+ type ChartSpecRenderContext,
8
+ } from './chart-spec-registry';
9
+
10
+ export const CHART_SEMANTIC_COLORS: readonly ChartSemanticColor[] = [
11
+ 'primary',
12
+ 'secondary',
13
+ 'success',
14
+ 'warning',
15
+ 'error',
16
+ 'info',
17
+ ];
18
+
19
+ const NUMBER_FORMATS = ['brl', 'percent', 'compact', 'integer', 'decimal'];
20
+
21
+ /** Error thrown when an unknown/invalid spec is parsed. Message is written to
22
+ * be actionable for programmatic authors (builders, LLMs). */
23
+ export class ChartSpecError extends Error {
24
+ constructor(message: string) {
25
+ super(message);
26
+ this.name = 'ChartSpecError';
27
+ }
28
+ }
29
+
30
+ function isRecord(value: unknown): value is Record<string, unknown> {
31
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
32
+ }
33
+
34
+ function assertColor(value: unknown, where: string): void {
35
+ if (value === undefined) return;
36
+ if (!CHART_SEMANTIC_COLORS.includes(value as ChartSemanticColor)) {
37
+ throw new ChartSpecError(
38
+ `${where} must be a semantic color token (${CHART_SEMANTIC_COLORS.join(', ')}), got ${JSON.stringify(value)}. Raw color values are not allowed.`,
39
+ );
40
+ }
41
+ }
42
+
43
+ function parseAxis(value: unknown): { key: string; label?: string } {
44
+ if (!isRecord(value) || typeof value.key !== 'string' || value.key.length === 0) {
45
+ throw new ChartSpecError('"xAxis" must be an object with a non-empty string "key" naming the row property for the category axis.');
46
+ }
47
+ if (value.label !== undefined && typeof value.label !== 'string') {
48
+ throw new ChartSpecError('"xAxis.label" must be a string when provided.');
49
+ }
50
+ return { key: value.key, label: value.label };
51
+ }
52
+
53
+ function parseSeries(value: unknown): ChartSpecSeries[] {
54
+ if (!Array.isArray(value) || value.length === 0) {
55
+ throw new ChartSpecError('"series" must be a non-empty array of { key, label?, color? } objects.');
56
+ }
57
+ return value.map((entry, index) => {
58
+ if (!isRecord(entry) || typeof entry.key !== 'string' || entry.key.length === 0) {
59
+ throw new ChartSpecError(`"series[${index}]" must be an object with a non-empty string "key" naming the row property holding its values.`);
60
+ }
61
+ assertColor(entry.color, `"series[${index}].color"`);
62
+ if (entry.label !== undefined && typeof entry.label !== 'string') {
63
+ throw new ChartSpecError(`"series[${index}].label" must be a string when provided.`);
64
+ }
65
+ return { key: entry.key, label: entry.label, color: entry.color as ChartSemanticColor | undefined };
66
+ });
67
+ }
68
+
69
+ function assertFieldType(
70
+ spec: Record<string, unknown>,
71
+ field: string,
72
+ expected: 'string' | 'boolean' | 'number',
73
+ ): void {
74
+ const value = spec[field];
75
+ if (value !== undefined && typeof value !== expected) {
76
+ throw new ChartSpecError(`"${field}" must be a ${expected} when provided.`);
77
+ }
78
+ }
79
+
80
+ function parseColorScheme(value: unknown): ChartSemanticColor[] | undefined {
81
+ if (value === undefined) return undefined;
82
+ if (!Array.isArray(value) || value.length === 0) {
83
+ throw new ChartSpecError('"colorScheme" must be a non-empty array of semantic color tokens when provided.');
84
+ }
85
+ value.forEach((token, index) => assertColor(token, `"colorScheme[${index}]"`));
86
+ return value as ChartSemanticColor[];
87
+ }
88
+
89
+ function parseOptionalFields(spec: Record<string, unknown>): Partial<ChartSpec> {
90
+ for (const field of ['title', 'subtitle'] as const) assertFieldType(spec, field, 'string');
91
+ for (const field of ['stacked', 'legend', 'tooltip', 'curved'] as const) {
92
+ assertFieldType(spec, field, 'boolean');
93
+ }
94
+ assertFieldType(spec, 'height', 'number');
95
+ if (spec.numberFormat !== undefined && !NUMBER_FORMATS.includes(spec.numberFormat as string)) {
96
+ throw new ChartSpecError(
97
+ `"numberFormat" must be one of ${NUMBER_FORMATS.join(', ')}, got ${JSON.stringify(spec.numberFormat)}.`,
98
+ );
99
+ }
100
+ return {
101
+ title: spec.title as string | undefined,
102
+ subtitle: spec.subtitle as string | undefined,
103
+ stacked: spec.stacked as boolean | undefined,
104
+ legend: spec.legend as boolean | undefined,
105
+ tooltip: spec.tooltip as boolean | undefined,
106
+ curved: spec.curved as boolean | undefined,
107
+ height: spec.height as number | undefined,
108
+ numberFormat: spec.numberFormat as ChartSpec['numberFormat'],
109
+ colorScheme: parseColorScheme(spec.colorScheme),
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Validate an untrusted value (e.g. JSON from storage or an LLM) into a
115
+ * `ChartSpec`. Throws {@link ChartSpecError} with an actionable message on
116
+ * the first problem found.
117
+ */
118
+ export function parseChartSpec(input: unknown): ChartSpec {
119
+ if (!isRecord(input)) {
120
+ throw new ChartSpecError('A chart spec must be a JSON object.');
121
+ }
122
+ const type = input.type;
123
+ if (typeof type !== 'string' || !getChartSpecRenderer(type)) {
124
+ throw new ChartSpecError(
125
+ `"type" must be one of the registered chart types (${getRegisteredChartSpecTypes().join(', ')}), got ${JSON.stringify(type)}.`,
126
+ );
127
+ }
128
+ const yAxis = input.yAxis;
129
+ if (yAxis !== undefined && (!isRecord(yAxis) || (yAxis.label !== undefined && typeof yAxis.label !== 'string'))) {
130
+ throw new ChartSpecError('"yAxis" must be an object with an optional string "label" when provided.');
131
+ }
132
+ const centerLabel = input.centerLabel;
133
+ return {
134
+ ...parseOptionalFields(input),
135
+ type: type as ChartSpec['type'],
136
+ xAxis: parseAxis(input.xAxis),
137
+ yAxis: yAxis as ChartSpec['yAxis'],
138
+ series: parseSeries(input.series),
139
+ centerLabel: centerLabel as ChartSpec['centerLabel'],
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Derive the prop-driven `Chart` props for a validated spec. The spec API is
145
+ * layered on top of the existing component — one rendering implementation.
146
+ */
147
+ export function specToChartProps(
148
+ spec: ChartSpec,
149
+ data: ChartDataPoint[],
150
+ ctx: ChartSpecRenderContext,
151
+ ): ChartProps {
152
+ const renderer = getChartSpecRenderer(spec.type);
153
+ if (!renderer) {
154
+ throw new ChartSpecError(
155
+ `Unknown chart type "${spec.type}". Registered types: ${getRegisteredChartSpecTypes().join(', ')}.`,
156
+ );
157
+ }
158
+ return {
159
+ data,
160
+ title: spec.title,
161
+ subtitle: spec.subtitle,
162
+ xAxisKey: spec.xAxis.key,
163
+ xAxisLabel: spec.xAxis.label,
164
+ yAxisLabel: spec.yAxis?.label,
165
+ showLegend: spec.legend ?? true,
166
+ showTooltip: spec.tooltip ?? true,
167
+ height: spec.height,
168
+ valueFormatter: chartValueFormatter(spec.numberFormat),
169
+ ...renderer(spec, ctx),
170
+ };
171
+ }
@@ -1,2 +1,32 @@
1
1
  export { Chart } from './Chart';
2
2
  export type { ChartDataPoint, ChartProps, ChartSeries } from './Chart.types';
3
+ export { SpecChart } from './SpecChart';
4
+ export type { SpecChartProps } from './SpecChart';
5
+ export type {
6
+ ChartNumberFormat,
7
+ ChartSemanticColor,
8
+ ChartSpec,
9
+ ChartSpecAxis,
10
+ ChartSpecSeries,
11
+ ChartSpecType,
12
+ } from './ChartSpec.types';
13
+ export { CHART_SEMANTIC_COLORS, ChartSpecError, parseChartSpec, specToChartProps } from './chart-spec';
14
+ export { chartValueFormatter, formatChartValue } from './chart-format';
15
+ export {
16
+ getChartSpecRenderer,
17
+ getRegisteredChartSpecTypes,
18
+ registerChartSpecRenderer,
19
+ } from './chart-spec-registry';
20
+ export type { ChartSpecRenderContext, ChartSpecRenderer } from './chart-spec-registry';
21
+ export {
22
+ ChartContainer,
23
+ ChartLegend,
24
+ ChartLegendContent,
25
+ ChartTooltip,
26
+ ChartTooltipContent,
27
+ } from './ChartComposables';
28
+ export type {
29
+ ChartContainerProps,
30
+ ChartLegendContentProps,
31
+ ChartTooltipContentProps,
32
+ } from './ChartComposables';
@@ -0,0 +1,131 @@
1
+ import { Avatar } from '@mui/material';
2
+ import type { SxProps, Theme } from '@mui/material';
3
+ import type { KeyboardEvent, ReactElement } from 'react';
4
+ import React from 'react';
5
+
6
+ import type { ChipProps } from './Chip.types';
7
+
8
+ export const makeTestId =
9
+ (dataTestId?: string) =>
10
+ (suffix: string): string =>
11
+ dataTestId ? `${dataTestId}-${suffix}` : `chip-${suffix}`;
12
+
13
+ interface KeyHandlerArgs {
14
+ disabled?: boolean;
15
+ deletable?: boolean;
16
+ selectable?: boolean;
17
+ onClick?: () => void;
18
+ onDelete?: () => void;
19
+ }
20
+
21
+ const DELETE_KEYS = new Set(['Delete', 'Backspace']);
22
+ const ACTIVATE_KEYS = new Set(['Enter', ' ']);
23
+
24
+ /**
25
+ * A chip is not a native control, so the two keyboard conventions it stands in for
26
+ * are wired by hand: Delete/Backspace removes it, Enter/Space activates it.
27
+ */
28
+ export const makeKeyDownHandler =
29
+ ({ disabled, deletable, selectable, onClick, onDelete }: KeyHandlerArgs) =>
30
+ (event: KeyboardEvent): void => {
31
+ if (disabled) return;
32
+
33
+ if (DELETE_KEYS.has(event.key) && deletable && onDelete) {
34
+ event.preventDefault();
35
+ onDelete();
36
+ return;
37
+ }
38
+
39
+ if (ACTIVATE_KEYS.has(event.key) && (onClick || selectable)) {
40
+ event.preventDefault();
41
+ onClick?.();
42
+ }
43
+ };
44
+
45
+ export const avatarFor = (
46
+ avatar: ChipProps['avatar'],
47
+ avatarSrc?: string,
48
+ ): ReactElement | undefined => {
49
+ if (avatar && React.isValidElement(avatar)) {
50
+ return avatar;
51
+ }
52
+ if (avatarSrc) {
53
+ return <Avatar src={avatarSrc} sx={{ width: 24, height: 24 }} />;
54
+ }
55
+ return undefined;
56
+ };
57
+
58
+ interface ChipStyleArgs {
59
+ variant: ChipProps['variant'];
60
+ selected?: boolean;
61
+ clickable?: boolean;
62
+ disabled?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Selection styling uses SEMANTIC palette tokens (not hardcoded rgba). A filled
67
+ * selected chip keeps MUI's solid `color.main` + contrast text (clear active
68
+ * state); an outlined selected chip gets a subtle theme-driven `action.selected`
69
+ * tint so it reads as active without muddying the fill.
70
+ */
71
+ export const chipStyles = ({
72
+ variant,
73
+ selected,
74
+ clickable,
75
+ disabled,
76
+ }: ChipStyleArgs): SxProps<Theme> => {
77
+ const lifts = clickable && !disabled;
78
+
79
+ return {
80
+ // Enhanced styling for outlined variant
81
+ ...(variant === 'outlined' && {
82
+ borderWidth: '1px',
83
+ borderStyle: 'solid',
84
+ backgroundColor: 'transparent',
85
+ }),
86
+ ...(selected &&
87
+ variant === 'outlined' && {
88
+ backgroundColor: (theme: Theme) => theme.palette.action.selected,
89
+ }),
90
+ '&:hover': {
91
+ ...(lifts && {
92
+ transform: 'translateY(-1px)',
93
+ boxShadow: (theme: Theme) =>
94
+ theme.palette.mode === 'dark'
95
+ ? '0 4px 12px rgba(0, 0, 0, 0.3)'
96
+ : '0 4px 12px rgba(0, 0, 0, 0.15)',
97
+ }),
98
+ },
99
+ '&:active': {
100
+ ...(lifts && {
101
+ transform: 'translateY(0px)',
102
+ }),
103
+ },
104
+ transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
105
+ };
106
+ };
107
+
108
+ /** `option` when the chip belongs to a selectable set, `button` when it merely acts. */
109
+ export const chipRole = (
110
+ selectable?: boolean,
111
+ onClick?: () => void,
112
+ ): 'option' | 'button' | undefined => {
113
+ if (selectable) return 'option';
114
+ return onClick ? 'button' : undefined;
115
+ };
116
+
117
+ export const isClickable = (
118
+ disabled?: boolean,
119
+ onClick?: () => void,
120
+ selectable?: boolean,
121
+ ): boolean => !disabled && (Boolean(onClick) || Boolean(selectable));
122
+
123
+ export const iconWithTestId = (
124
+ icon: ChipProps['icon'],
125
+ testId: string,
126
+ ): ReactElement | undefined =>
127
+ icon && React.isValidElement(icon)
128
+ ? React.cloneElement(icon as ReactElement, {
129
+ 'data-testid': testId,
130
+ } as Record<string, unknown>)
131
+ : undefined;
@@ -1,4 +1,5 @@
1
- import { Face, Star } from '@mui/icons-material';
1
+ import Face from '@mui/icons-material/Face';
2
+ import Star from '@mui/icons-material/Star';
2
3
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
4
 
4
5
  import { Chip } from './Chip';
@@ -1,7 +1,7 @@
1
- import { Star } from '@mui/icons-material';
1
+ import Star from '@mui/icons-material/Star';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import React from 'react';
4
- import { expect, fn, Mock, userEvent, within } from 'storybook/test';
4
+ import { expect, fn, Mock, userEvent, waitFor, within } from 'storybook/test';
5
5
 
6
6
  import { Chip } from './Chip';
7
7
 
@@ -47,9 +47,10 @@ export const KeyboardNavigation: Story = {
47
47
  const canvas = within(canvasElement);
48
48
  const chip = canvas.getByRole('option');
49
49
 
50
- // Focus the chip
51
- chip.focus();
52
- expect(chip).toHaveFocus();
50
+ // Tab to the chip rather than calling .focus() directly: the keyboard is how
51
+ // a user reaches it, and the direct call asserts nothing about the component.
52
+ await userEvent.tab();
53
+ await waitFor(() => expect(chip).toHaveFocus());
53
54
 
54
55
  // Test Enter key
55
56
  await userEvent.keyboard('{Enter}');
@@ -73,9 +74,11 @@ export const FocusManagement: Story = {
73
74
  const canvas = within(canvasElement);
74
75
  const chip = canvas.getByRole('button');
75
76
 
76
- // Test programmatic focus
77
- chip.focus();
78
- expect(chip).toHaveFocus();
77
+ // Tab rather than .focus(): the chip is the only focusable node here, so this
78
+ // proves it is keyboard-reachable — a real property of the component, unlike
79
+ // asserting that a direct .focus() call took effect.
80
+ await userEvent.tab();
81
+ await waitFor(() => expect(chip).toHaveFocus());
79
82
  },
80
83
  };
81
84
 
@@ -1,10 +1,27 @@
1
1
  import CancelIcon from '@mui/icons-material/Cancel';
2
- import { Avatar, Chip as MuiChip } from '@mui/material';
3
- import type { KeyboardEvent, ReactElement } from 'react';
4
- import React, { forwardRef } from 'react';
2
+ import { Chip as MuiChip } from '@mui/material';
3
+ import { forwardRef } from 'react';
5
4
 
5
+ import {
6
+ avatarFor,
7
+ chipRole,
8
+ chipStyles,
9
+ iconWithTestId,
10
+ isClickable,
11
+ makeKeyDownHandler,
12
+ makeTestId,
13
+ } from './Chip.helpers';
6
14
  import type { ChipProps } from './Chip.types';
7
15
 
16
+ type MuiChipColor =
17
+ | 'primary'
18
+ | 'secondary'
19
+ | 'error'
20
+ | 'info'
21
+ | 'success'
22
+ | 'warning'
23
+ | 'default';
24
+
8
25
  export const Chip = forwardRef<HTMLDivElement, ChipProps>(({
9
26
  label,
10
27
  variant = 'filled',
@@ -23,112 +40,32 @@ export const Chip = forwardRef<HTMLDivElement, ChipProps>(({
23
40
  dataTestId,
24
41
  ...props
25
42
  }, ref) => {
26
- const handleKeyDown = (event: KeyboardEvent) => {
27
- if (disabled) return;
28
-
29
- // Handle delete/backspace keys for deletion
30
- if ((event.key === 'Delete' || event.key === 'Backspace') && deletable && onDelete) {
31
- event.preventDefault();
32
- onDelete();
33
- return;
34
- }
35
-
36
- // Handle enter/space for selection
37
- if ((event.key === 'Enter' || event.key === ' ') && (onClick || selectable)) {
38
- event.preventDefault();
39
- onClick?.();
40
- }
41
- };
42
-
43
- const getAvatarComponent = (): ReactElement | undefined => {
44
- if (avatar && React.isValidElement(avatar)) {
45
- return avatar;
46
- }
47
- if (avatarSrc) {
48
- return <Avatar src={avatarSrc} sx={{ width: 24, height: 24 }} />;
49
- }
50
- return undefined;
51
- };
52
-
53
- // Determine the role based on context
54
- const role = selectable ? 'option' : (onClick ? 'button' : undefined);
55
-
56
- // Determine if clickable
57
- const clickable = !disabled && (!!onClick || selectable);
58
-
59
- // Enhance icon with test ID if present
60
- const enhancedIcon = icon && React.isValidElement(icon)
61
- ? React.cloneElement(icon as ReactElement, {
62
- 'data-testid': dataTestId ? `${dataTestId}-icon` : 'chip-icon',
63
- } as Record<string, unknown>)
64
- : undefined;
65
-
66
- // Wrap label with test ID
67
- const enhancedLabel = (
68
- <span data-testid={dataTestId ? `${dataTestId}-label` : 'chip-label'}>
69
- {label}
70
- </span>
71
- );
72
-
73
- // Custom delete icon with test ID
74
- const enhancedDeleteIcon = deletable ? (
75
- <CancelIcon data-testid={dataTestId ? `${dataTestId}-delete` : 'chip-delete'} />
76
- ) : undefined;
43
+ const testId = makeTestId(dataTestId);
44
+ const clickable = isClickable(disabled, onClick, selectable);
77
45
 
78
46
  return (
79
47
  <MuiChip
80
48
  ref={ref}
81
- label={enhancedLabel}
49
+ label={<span data-testid={testId('label')}>{label}</span>}
82
50
  variant={variant}
83
51
  size={size}
84
- color={color as 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | 'default' | undefined}
85
- avatar={getAvatarComponent()}
86
- icon={enhancedIcon}
52
+ color={color as MuiChipColor | undefined}
53
+ avatar={avatarFor(avatar, avatarSrc)}
54
+ icon={iconWithTestId(icon, testId('icon'))}
87
55
  onDelete={deletable ? onDelete : undefined}
88
- deleteIcon={enhancedDeleteIcon}
56
+ deleteIcon={deletable ? <CancelIcon data-testid={testId('delete')} /> : undefined}
89
57
  disabled={disabled}
90
58
  clickable={clickable}
91
59
  onClick={clickable ? onClick : undefined}
92
- onKeyDown={handleKeyDown}
60
+ onKeyDown={makeKeyDownHandler({ disabled, deletable, selectable, onClick, onDelete })}
93
61
  className={className}
94
- role={role}
62
+ role={chipRole(selectable, onClick)}
95
63
  aria-selected={selectable ? selected : undefined}
96
64
  data-testid={dataTestId || 'chip'}
97
- sx={{
98
- // Enhanced styling for outlined variant
99
- ...(variant === 'outlined' && {
100
- borderWidth: '1px',
101
- borderStyle: 'solid',
102
- backgroundColor: 'transparent',
103
- }),
104
- // Selection styling uses SEMANTIC palette tokens (not hardcoded rgba).
105
- // A filled selected chip keeps MUI's solid `color.main` + contrast text
106
- // (clear active state); an outlined selected chip gets a subtle
107
- // theme-driven `action.selected` tint so it reads as active without
108
- // muddying the fill.
109
- ...(selected &&
110
- variant === 'outlined' && {
111
- backgroundColor: (theme) => theme.palette.action.selected,
112
- }),
113
- '&:hover': {
114
- ...(clickable && !disabled && {
115
- transform: 'translateY(-1px)',
116
- boxShadow: (theme) =>
117
- theme.palette.mode === 'dark'
118
- ? '0 4px 12px rgba(0, 0, 0, 0.3)'
119
- : '0 4px 12px rgba(0, 0, 0, 0.15)',
120
- }),
121
- },
122
- '&:active': {
123
- ...(clickable && !disabled && {
124
- transform: 'translateY(0px)',
125
- }),
126
- },
127
- transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
128
- }}
65
+ sx={chipStyles({ variant, selected, clickable, disabled })}
129
66
  {...props}
130
67
  />
131
68
  );
132
69
  });
133
70
 
134
- Chip.displayName = 'Chip';
71
+ Chip.displayName = 'Chip';
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The DataGrid's `<thead>` and `<tbody>`.
3
+ *
4
+ * Split from the shell so each stays inside the size budget and so the
5
+ * virtualization spacers — which are pure layout, not data — sit next to the
6
+ * rows they pad rather than in the middle of the component that owns the paper.
7
+ */
8
+ import { Checkbox, TableBody, TableCell, TableHead, TableRow, useTheme } from '@mui/material';
9
+ import type React from 'react';
10
+
11
+ import { GridRow, HeaderCell } from './DataGrid.cells';
12
+ import type { DataGridModel } from './DataGrid.model';
13
+ import type { DataGridProps } from './DataGrid.types';
14
+
15
+ export interface GridSectionProps<T extends Record<string, unknown>> {
16
+ model: DataGridModel<T>;
17
+ props: DataGridProps<T>;
18
+ totalColumns: number;
19
+ }
20
+
21
+ /** The leading header cells for the selection / expansion columns. */
22
+ function HeaderControls<T extends Record<string, unknown>>({
23
+ model,
24
+ props,
25
+ }: GridSectionProps<T>): React.JSX.Element {
26
+ const theme = useTheme();
27
+ const { selection = { mode: 'none' }, expansion, stickyHeader = true } = props;
28
+ const sticky = {
29
+ position: stickyHeader ? ('sticky' as const) : ('static' as const),
30
+ top: 0,
31
+ zIndex: 1,
32
+ backgroundColor: theme.palette.background.paper,
33
+ };
34
+ const total = model.processedRows.length;
35
+ const picked = model.selectedRowIds.length;
36
+ return (
37
+ <>
38
+ {selection.mode !== 'none' && (
39
+ <TableCell padding="checkbox" sx={sticky}>
40
+ {selection.mode === 'multi' && (
41
+ <Checkbox
42
+ indeterminate={picked > 0 && picked < total}
43
+ checked={total > 0 && picked === total}
44
+ onChange={(event) =>
45
+ model.setSelectedRowIds(
46
+ event.target.checked
47
+ ? model.processedRows.map((row, index) => rowIdOf(props, row, index))
48
+ : [],
49
+ )
50
+ }
51
+ inputProps={{ 'aria-label': 'Select all rows' }}
52
+ />
53
+ )}
54
+ </TableCell>
55
+ )}
56
+ {expansion && <TableCell padding="checkbox" sx={sticky} />}
57
+ </>
58
+ );
59
+ }
60
+
61
+ /** The caller's `getRowId`, or the row's own `id`, or its index. */
62
+ export function rowIdOf<T extends Record<string, unknown>>(
63
+ props: DataGridProps<T>,
64
+ row: T,
65
+ index: number,
66
+ ): string | number {
67
+ if (props.getRowId) return props.getRowId(row, index);
68
+ const candidate = (row as { id?: string | number }).id;
69
+ return candidate ?? index;
70
+ }
71
+
72
+ export function GridHeader<T extends Record<string, unknown>>(
73
+ section: GridSectionProps<T>,
74
+ ): React.JSX.Element {
75
+ const { model, props } = section;
76
+ const { sorting = {}, stickyHeader = true, headerHeight = 56 } = props;
77
+ const sortable = sorting.mode === 'client' || sorting.mode === 'server';
78
+ return (
79
+ <TableHead data-slot="header">
80
+ <TableRow data-slot="header-row" role="row">
81
+ <HeaderControls {...section} />
82
+ {model.visibleColumns.map((column) => (
83
+ <HeaderCell
84
+ key={column.id}
85
+ column={column}
86
+ sort={model.sortBy.find((entry) => entry.id === column.id)}
87
+ sortable={column.enableSort !== false && sortable}
88
+ stickyHeader={stickyHeader}
89
+ headerHeight={headerHeight}
90
+ onSort={model.onSort}
91
+ />
92
+ ))}
93
+ </TableRow>
94
+ </TableHead>
95
+ );
96
+ }
97
+
98
+ /** An invisible row standing in for the rows the window is not rendering. */
99
+ function Spacer({ height, colSpan }: { height: number; colSpan: number }): React.JSX.Element | null {
100
+ if (height <= 0) return null;
101
+ return (
102
+ <TableRow>
103
+ <TableCell colSpan={colSpan} sx={{ height, border: 'none', p: 0 }} />
104
+ </TableRow>
105
+ );
106
+ }
107
+
108
+ export function GridBody<T extends Record<string, unknown>>(
109
+ section: GridSectionProps<T>,
110
+ ): React.JSX.Element {
111
+ const { model, props, totalColumns } = section;
112
+ const { start, end } = model.visibleRange;
113
+ const trailing = model.virtualized ? model.processedRows.length - end : 0;
114
+ return (
115
+ <TableBody data-slot="body">
116
+ {model.virtualized ? (
117
+ <Spacer height={start * model.rowHeight} colSpan={totalColumns} />
118
+ ) : null}
119
+ {model.visibleRows.map((row, offset) => {
120
+ const index = model.virtualized ? start + offset : offset;
121
+ const rowId = rowIdOf(props, row, index);
122
+ return (
123
+ <GridRow
124
+ key={rowId}
125
+ row={row}
126
+ index={index}
127
+ rowId={rowId}
128
+ isSelected={model.selectedRowIdsSet.has(rowId)}
129
+ isExpanded={model.expandedRowIdsSet.has(rowId)}
130
+ totalColumns={totalColumns}
131
+ model={model}
132
+ props={props}
133
+ />
134
+ );
135
+ })}
136
+ {model.virtualized ? (
137
+ <Spacer height={trailing * model.rowHeight} colSpan={totalColumns} />
138
+ ) : null}
139
+ </TableBody>
140
+ );
141
+ }