@12-apps/ui 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/components-catalog.md +14 -0
  2. package/package.json +42 -27
  3. package/src/__tests__/data-grid.test.tsx +125 -0
  4. package/src/__tests__/package-wiring.test.ts +12 -8
  5. package/src/__tests__/social-login-button.test.tsx +5 -5
  6. package/src/__tests__/viewport.test.ts +50 -0
  7. package/src/charts/index.ts +8 -0
  8. package/src/components/data-display/Alert/Alert.md +1 -1
  9. package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
  10. package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
  11. package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
  12. package/src/components/data-display/Alert/Alert.tsx +135 -328
  13. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
  14. package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
  15. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
  16. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
  17. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
  18. package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
  19. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
  20. package/src/components/data-display/Avatar/Avatar.tsx +154 -450
  21. package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
  22. package/src/components/data-display/Badge/Badge.animations.ts +67 -0
  23. package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
  24. package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
  25. package/src/components/data-display/Badge/Badge.styles.ts +261 -0
  26. package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
  27. package/src/components/data-display/Badge/Badge.tsx +151 -557
  28. package/src/components/data-display/Badge/Badge.variants.ts +106 -0
  29. package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
  30. package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
  31. package/src/components/data-display/Banner/Banner.styles.ts +120 -0
  32. package/src/components/data-display/Banner/Banner.tsx +179 -224
  33. package/src/components/data-display/Chart/Chart.md +48 -1
  34. package/src/components/data-display/Chart/Chart.tsx +82 -500
  35. package/src/components/data-display/Chart/Chart.types.ts +2 -0
  36. package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
  37. package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
  38. package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
  39. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
  40. package/src/components/data-display/Chart/SpecChart.tsx +53 -0
  41. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
  42. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
  43. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
  44. package/src/components/data-display/Chart/chart-format.ts +26 -0
  45. package/src/components/data-display/Chart/chart-internals.ts +148 -0
  46. package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
  47. package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
  48. package/src/components/data-display/Chart/chart-spec.ts +171 -0
  49. package/src/components/data-display/Chart/index.ts +30 -0
  50. package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
  51. package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
  52. package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
  53. package/src/components/data-display/Chip/Chip.tsx +31 -94
  54. package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
  55. package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
  56. package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
  57. package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
  58. package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
  59. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
  60. package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
  61. package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
  62. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
  63. package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
  64. package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
  65. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
  66. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
  67. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
  68. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
  69. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
  70. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
  71. package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
  72. package/src/components/data-display/DataViews/base-card.tsx +278 -0
  73. package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
  74. package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
  75. package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
  76. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
  77. package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
  78. package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
  79. package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
  80. package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
  81. package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
  82. package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
  83. package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
  84. package/src/components/data-display/DataViews/data-views-types.ts +312 -0
  85. package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
  86. package/src/components/data-display/DataViews/index.ts +33 -0
  87. package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
  88. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
  89. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
  90. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
  91. package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
  92. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
  93. package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
  94. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
  95. package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
  96. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
  97. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
  98. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
  99. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
  100. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
  101. package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
  102. package/src/components/data-display/LazyImage/LazyImage.md +1 -1
  103. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
  104. package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
  105. package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
  106. package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
  107. package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
  108. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
  109. package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
  110. package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
  111. package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
  112. package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
  113. package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
  114. package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
  115. package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
  116. package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
  117. package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
  118. package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
  119. package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
  120. package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
  121. package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
  122. package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
  123. package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
  124. package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
  125. package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
  126. package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
  127. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
  128. package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
  129. package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
  130. package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
  131. package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
  132. package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
  133. package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
  134. package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
  135. package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
  136. package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
  137. package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
  138. package/src/components/data-display/Progress/Progress.styles.ts +101 -0
  139. package/src/components/data-display/Progress/Progress.tsx +185 -213
  140. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
  141. package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
  142. package/src/components/data-display/StatCard/StatCard.md +117 -0
  143. package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
  144. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
  145. package/src/components/data-display/StatCard/StatCard.tsx +236 -0
  146. package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
  147. package/src/components/data-display/StatCard/index.ts +7 -0
  148. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
  149. package/src/components/data-display/Stepper/Stepper.tsx +1 -1
  150. package/src/components/data-display/Table/Table.helpers.ts +31 -0
  151. package/src/components/data-display/Table/Table.hooks.ts +161 -0
  152. package/src/components/data-display/Table/Table.stories.tsx +3 -1
  153. package/src/components/data-display/Table/Table.styles.ts +223 -0
  154. package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
  155. package/src/components/data-display/Table/Table.tsx +291 -748
  156. package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
  157. package/src/components/data-display/Table/TableParts.tsx +298 -0
  158. package/src/components/data-display/Table/TableStates.tsx +21 -0
  159. package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
  160. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
  161. package/src/components/data-display/Timeline/Timeline.tsx +23 -284
  162. package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
  163. package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
  164. package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
  165. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
  166. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
  167. package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
  168. package/src/components/data-display/index.ts +1 -0
  169. package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
  170. package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
  171. package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
  172. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
  173. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
  174. package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
  175. package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
  176. package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
  177. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
  178. package/src/components/feedback/ConfirmAction/index.ts +16 -0
  179. package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
  180. package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
  181. package/src/components/feedback/Dialog/Dialog.tsx +26 -1
  182. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
  183. package/src/components/feedback/Modal/Modal.md +1 -1
  184. package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
  185. package/src/components/feedback/Sonner/Sonner.tsx +104 -288
  186. package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
  187. package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
  188. package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
  189. package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
  190. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
  191. package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
  192. package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
  193. package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
  194. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
  195. package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
  196. package/src/components/feedback/Toast/Toast.tsx +92 -81
  197. package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
  198. package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
  199. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
  200. package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
  201. package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
  202. package/src/components/feedback/index.ts +1 -0
  203. package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
  204. package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
  205. package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
  206. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
  207. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
  208. package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
  209. package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
  210. package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
  211. package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
  212. package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
  213. package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
  214. package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
  215. package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
  216. package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
  217. package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
  218. package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
  219. package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
  220. package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
  221. package/src/components/form/Button/Button.helpers.ts +36 -0
  222. package/src/components/form/Button/Button.stories.tsx +4 -1
  223. package/src/components/form/Button/Button.styles.ts +225 -0
  224. package/src/components/form/Button/Button.test.stories.tsx +25 -21
  225. package/src/components/form/Button/Button.tsx +45 -252
  226. package/src/components/form/Calendar/Calendar.dates.ts +100 -0
  227. package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
  228. package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
  229. package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
  230. package/src/components/form/Calendar/Calendar.selection.ts +156 -0
  231. package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
  232. package/src/components/form/Calendar/Calendar.tsx +63 -551
  233. package/src/components/form/Calendar/Calendar.types.ts +16 -0
  234. package/src/components/form/Calendar/CalendarDay.tsx +127 -0
  235. package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
  236. package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
  237. package/src/components/form/CepField/CepField.md +138 -0
  238. package/src/components/form/CepField/CepField.stories.tsx +138 -0
  239. package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
  240. package/src/components/form/CepField/CepField.tsx +211 -0
  241. package/src/components/form/CepField/CepField.types.ts +73 -0
  242. package/src/components/form/CepField/index.ts +2 -0
  243. package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
  244. package/src/components/form/Checkbox/Checkbox.tsx +19 -16
  245. package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
  246. package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
  247. package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
  248. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
  249. package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
  250. package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
  251. package/src/components/form/Command/Command.hooks.ts +207 -0
  252. package/src/components/form/Command/Command.parts.tsx +58 -0
  253. package/src/components/form/Command/Command.styles.ts +83 -0
  254. package/src/components/form/Command/Command.tsx +61 -270
  255. package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
  256. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
  257. package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
  258. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
  259. package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
  260. package/src/components/form/HeaderButton/index.ts +2 -0
  261. package/src/components/form/Input/Input.stories.tsx +5 -1
  262. package/src/components/form/Input/Input.styles.ts +176 -0
  263. package/src/components/form/Input/Input.test.stories.tsx +30 -23
  264. package/src/components/form/Input/Input.tsx +71 -211
  265. package/src/components/form/Input/Input.types.ts +11 -0
  266. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
  267. package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
  268. package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
  269. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
  270. package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
  271. package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
  272. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
  273. package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
  274. package/src/components/form/PhoneInput/countries.ts +54 -0
  275. package/src/components/form/PhoneInput/phone.ts +47 -0
  276. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
  277. package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
  278. package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
  279. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
  280. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
  281. package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
  282. package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
  283. package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
  284. package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
  285. package/src/components/form/Select/Select.test.stories.tsx +35 -27
  286. package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
  287. package/src/components/form/Switch/Switch.md +2 -1
  288. package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
  289. package/src/components/form/Textarea/Textarea.styles.ts +143 -0
  290. package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
  291. package/src/components/form/Textarea/Textarea.tsx +114 -535
  292. package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
  293. package/src/components/form/Toggle/Toggle.styles.ts +211 -0
  294. package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
  295. package/src/components/form/Toggle/Toggle.tsx +10 -185
  296. package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
  297. package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
  298. package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
  299. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
  300. package/src/components/form/UploadButton/UploadButton.tsx +76 -305
  301. package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
  302. package/src/components/form/index.ts +1 -0
  303. package/src/components/form/total-form/Fields/CepField.tsx +143 -0
  304. package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
  305. package/src/components/form/total-form/Fields/index.ts +4 -0
  306. package/src/components/form/total-form/FormContainer.tsx +3 -3
  307. package/src/components/form/total-form/SubmitButton.tsx +25 -2
  308. package/src/components/form/total-form/total-form.test.tsx +118 -1
  309. package/src/components/layout/Accordion/Accordion.md +1 -1
  310. package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
  311. package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
  312. package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
  313. package/src/components/layout/Accordion/Accordion.tsx +2 -75
  314. package/src/components/layout/Card/Card.helpers.ts +30 -0
  315. package/src/components/layout/Card/Card.stories.tsx +9 -11
  316. package/src/components/layout/Card/Card.styles.ts +182 -0
  317. package/src/components/layout/Card/Card.test.stories.tsx +46 -46
  318. package/src/components/layout/Card/Card.tsx +24 -241
  319. package/src/components/layout/CardGrid/CardGrid.md +96 -0
  320. package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
  321. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
  322. package/src/components/layout/CardGrid/index.ts +2 -0
  323. package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
  324. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
  325. package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
  326. package/src/components/layout/Container/Container.test.stories.tsx +11 -16
  327. package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
  328. package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
  329. package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
  330. package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
  331. package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
  332. package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
  333. package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
  334. package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
  335. package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
  336. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
  337. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
  338. package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
  339. package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
  340. package/src/components/layout/Dashboard/index.ts +2 -1
  341. package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
  342. package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
  343. package/src/components/layout/Drawer/Drawer.tsx +67 -53
  344. package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
  345. package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
  346. package/src/components/layout/Resizable/Resizable.tsx +38 -153
  347. package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
  348. package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
  349. package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
  350. package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
  351. package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
  352. package/src/components/layout/Separator/Separator.styles.ts +128 -0
  353. package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
  354. package/src/components/layout/Separator/Separator.tsx +6 -132
  355. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
  356. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
  357. package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
  358. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
  359. package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
  360. package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
  361. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
  362. package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
  363. package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
  364. package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
  365. package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
  366. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
  367. package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
  368. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
  369. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
  370. package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
  371. package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
  372. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
  373. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
  374. package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
  375. package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
  376. package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
  377. package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
  378. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
  379. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
  380. package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
  381. package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
  382. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
  383. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
  384. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
  385. package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
  386. package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
  387. package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
  388. package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
  389. package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
  390. package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
  391. package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
  392. package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
  393. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
  394. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
  395. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
  396. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
  397. package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
  398. package/src/components/navigation/Pagination/Pagination.md +2 -1
  399. package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
  400. package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
  401. package/src/components/navigation/Pagination/Pagination.tsx +178 -313
  402. package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
  403. package/src/components/navigation/Tabs/Tabs.md +3 -1
  404. package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
  405. package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
  406. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
  407. package/src/components/navigation/Tabs/Tabs.tsx +192 -361
  408. package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
  409. package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
  410. package/src/components/typography/Code/Code.test.stories.tsx +5 -5
  411. package/src/components/typography/Code/Code.tsx +74 -49
  412. package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
  413. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
  414. package/src/components/typography/Text/Text.tsx +84 -77
  415. package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
  416. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
  417. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
  418. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
  419. package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
  420. package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
  421. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
  422. package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
  423. package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
  424. package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
  425. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
  426. package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
  427. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
  428. package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
  429. package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
  430. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
  431. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
  432. package/src/components/utility/InstallPrompt/index.ts +10 -0
  433. package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
  434. package/src/components/utility/Transition/Transition.helpers.ts +40 -0
  435. package/src/components/utility/Transition/Transition.tsx +17 -68
  436. package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
  437. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
  438. package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
  439. package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
  440. package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
  441. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
  442. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
  443. package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
  444. package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
  445. package/src/components/utility/index.ts +1 -0
  446. package/src/mui/Link.ts +2 -0
  447. package/src/utils/index.ts +2 -1
  448. package/src/utils/viewport.ts +65 -0
@@ -0,0 +1,96 @@
1
+ # CardGrid Component
2
+
3
+ ## Overview
4
+
5
+ `CardGrid` is a shared grid container for card layouts. It arranges its children into responsive columns using CSS `grid` + `auto-fill`, so the column count adapts to the container width without media queries or JavaScript. It exposes two layout **variants** that differ only in how leftover row space is handled.
6
+
7
+ Ported from the tabwoah card grid — the `"fixed"` variant is the original behavior verbatim; the one adaptation is that this app has no Tailwind, so the container's `display: grid`, gap, and bottom padding live in `style` instead of utility classes.
8
+
9
+ ## Features
10
+
11
+ - **Two variants**: `fixed` (steady card width, even gutters) and `fluid` (cards stretch to fill)
12
+ - **Responsive by default**: `auto-fill` recomputes the column count as the container resizes — 2-up on a phone, more columns as width grows
13
+ - **Single-card friendly**: a lone card stays left-aligned at one track's width, never stretched edge to edge
14
+ - **No JS / no media queries**: pure CSS grid
15
+ - **Ref + testid passthrough**: `containerRef` (e.g. for row-clipping) and `gridTestId`
16
+
17
+ ## Variants
18
+
19
+ | Variant | Layout | Wraps? | Card sizing |
20
+ | --------- | ----------------------------------- | ----------------- | -------------------------------------------- |
21
+ | `fixed` | `repeat(auto-fill, {cardWidth}px)` + `space-around` | wraps | Cards stay exactly `cardWidth`; column count changes |
22
+ | `fluid` | `repeat(auto-fill, minmax({cardWidth}px, 1fr))` | wraps | Cards grow to fill the row; `cardWidth` is the minimum |
23
+ | `scroll` | one row, `grid-auto-flow: column` + `overflow-x: auto`, `grid-auto-columns: min({maxCardWidth}px, calc((100% - 60px) / 2))` | no — scrolls sideways | Cards are sized from the container so 2 whole cards + a 28px peek of the third always fit, capped at `maxCardWidth` |
24
+
25
+ - Use **`fixed`** when you want cards to keep a constant size and the row to breathe with even spacing (the tabwoah default).
26
+ - Use **`fluid`** when you want cards to grow and the grid to always use the full width (e.g. a product menu that should fill edge to edge).
27
+ - Use **`scroll`** when you want a swipeable rail — a single horizontally-scrolling row of fluid cards (e.g. a "highlights"/destaque strip). Two cards plus a peek of the third show on a phone; more appear as the viewport grows, and the rest are revealed by scrolling.
28
+
29
+ **The peek is a guarantee, not a side effect.** On a phone the rail must never resolve to exactly two cards flush with the edge — that reads as "these are all the products". The track width is therefore computed _from the container_ (`(100% - 2 gaps - 28px peek) / 2`) rather than clamped to a minimum card width: a minimum is precisely what would swallow the peek on a narrow screen. `maxCardWidth` still caps the track, so wide viewports fit more cards instead of inflating them. When the rail holds two cards or fewer there is nothing to scroll to, so no peek is reserved and the cards divide the row evenly.
30
+
31
+ ## Usage
32
+
33
+ ### Fluid grid (cards grow to fill)
34
+
35
+ ```tsx
36
+ import { CardGrid } from '@12-apps/ui/layout/CardGrid';
37
+
38
+ <CardGrid variant="fluid" cardWidth={165} gridTestId="menu-grid">
39
+ {items.map((item) => (
40
+ <ProductCard key={item.id} item={item} />
41
+ ))}
42
+ </CardGrid>;
43
+ ```
44
+
45
+ At ~396px of content two 165px columns fit and each stretches to ~190px; on a
46
+ wide screen the columns multiply and keep filling the row.
47
+
48
+ ### Fixed grid (steady width, even gutters)
49
+
50
+ ```tsx
51
+ <CardGrid variant="fixed" cardWidth={230}>
52
+ {cards}
53
+ </CardGrid>
54
+ ```
55
+
56
+ Every card is exactly 230px; surplus width becomes even gutters between columns.
57
+
58
+ ### Scroll rail (fluid cards, horizontal scroll)
59
+
60
+ ```tsx
61
+ <CardGrid variant="scroll" cardWidth={165} maxCardWidth={264}>
62
+ {cards}
63
+ </CardGrid>
64
+ ```
65
+
66
+ A single row that scrolls sideways. Each card is as wide as it can be while two
67
+ whole cards plus a 28px peek of the third still fit — e.g. ~142px in a 348px
68
+ container — capped at 264px on a wide viewport.
69
+
70
+ ### With a container ref (row clipping)
71
+
72
+ ```tsx
73
+ const { containerRef, maxHeight } = useMaxRowsHeight(2, items.length);
74
+
75
+ <CardGrid cardWidth={230} containerRef={containerRef} className="clipped">
76
+ {cards}
77
+ </CardGrid>;
78
+ ```
79
+
80
+ ## Props
81
+
82
+ | Prop | Type | Default | Description |
83
+ | ------------ | ----------------------- | --------- | ------------------------------------------------------------------------------------------------- |
84
+ | cardWidth | number | - | `fixed`: the exact column width in px. `fluid`: the minimum column width (columns grow from here). `scroll`: only seeds the default `maxCardWidth` — scroll tracks have no minimum. |
85
+ | maxCardWidth | number | 1.6×cardWidth | `scroll` only: the upper bound of the fluid card width. |
86
+ | variant | 'fixed' \| 'fluid' \| 'scroll' | 'fixed' | Layout behavior (see [Variants](#variants)). |
87
+ | className | string | - | Extra classes appended to the grid container. |
88
+ | gridTestId | string | - | `data-testid` for the grid container. |
89
+ | containerRef | Ref\<HTMLDivElement\> | - | Ref forwarded to the grid container (e.g. for max-rows height clipping). |
90
+ | children | ReactNode | - | The cards to lay out. |
91
+
92
+ ## Notes
93
+
94
+ - The children control their own width: give each card `width: 100%` so it fills its grid track in both variants.
95
+ - Card **height** equalizes per row automatically when tracks are equal width (equal columns ⇒ equal-height cells).
96
+ - In tabwoah the card size is driven by a zoom slider (`useGridLayout`) that feeds a larger `cardWidth`; that control was not ported here — pass `cardWidth` directly (a fixed number or a responsive one) instead.
@@ -0,0 +1,140 @@
1
+ import { Children, type CSSProperties, type ReactNode, type Ref } from "react";
2
+
3
+ import { cn } from "../../../utils/cn";
4
+
5
+ /**
6
+ * How the grid turns surplus row space into layout:
7
+ *
8
+ * - `"fixed"` — every column is exactly `cardWidth` px; leftover row space is
9
+ * spread as even gutters (`justify-content: space-around`). Cards never resize;
10
+ * the column count changes with the viewport. This is the original tabwoah
11
+ * card-grid behavior.
12
+ * - `"fluid"` — every column is *at least* `cardWidth` px and stretches (`1fr`)
13
+ * to fill the row, so cards grow to use the whole width. A lone card stays
14
+ * left-aligned at a single track's width rather than stretching edge to edge.
15
+ * - `"scroll"` — a single row that scrolls horizontally, with fluid-width cards
16
+ * sized from the container so that exactly {@link SCROLL_VISIBLE_CARDS} cards
17
+ * plus a {@link SCROLL_PEEK_WIDTH}px sliver of the next one fit on screen (up
18
+ * to `maxCardWidth`, past which more cards simply fit). The sliver is the
19
+ * point: it makes the rail visibly continue past the fold, so the row reads as
20
+ * swipeable instead of as a finished pair of cards. Wrapping is off; the row
21
+ * overflows on the x and scroll-snaps to each card's start so a swipe lands on
22
+ * a clean card boundary.
23
+ */
24
+ export type CardGridVariant = "fixed" | "fluid" | "scroll";
25
+
26
+ /** The grid gap, in px — mirrors the `gap: 1rem` below, for the scroll math. */
27
+ const GRID_GAP = 16;
28
+
29
+ /** `scroll`: how many WHOLE cards the rail commits to showing at once. */
30
+ const SCROLL_VISIBLE_CARDS = 2;
31
+
32
+ /**
33
+ * `scroll`: px of the next card left visible past the last whole one — the
34
+ * "there's more, swipe" hint. Wide enough to read as a clipped card (rounded
35
+ * edge + a slice of its photo) rather than as a rendering glitch.
36
+ */
37
+ const SCROLL_PEEK_WIDTH = 28;
38
+
39
+ /**
40
+ * The `scroll` track width: the widest a card can be while the container still
41
+ * shows `SCROLL_VISIBLE_CARDS` of them plus the peek — or, when there is nothing
42
+ * to scroll to, while the cards still divide the row evenly. Capped at `max` so
43
+ * a desktop-width rail packs in more cards instead of inflating them.
44
+ *
45
+ * The container (`100%`) has to cover, in the overflow case: N cards, the N gaps
46
+ * that follow them (one between each pair, one before the peeked card), and the
47
+ * peek. With no overflow there are only N-1 gaps and no peek.
48
+ */
49
+ function scrollTrackWidth(count: number, max: number): string {
50
+ const tracks = Math.max(1, Math.min(count, SCROLL_VISIBLE_CARDS));
51
+ const overflows = count > tracks;
52
+ const gaps = overflows ? tracks : tracks - 1;
53
+ const reserved = gaps * GRID_GAP + (overflows ? SCROLL_PEEK_WIDTH : 0);
54
+ return `min(${max}px, calc((100% - ${reserved}px) / ${tracks}))`;
55
+ }
56
+
57
+ interface CardGridProps {
58
+ /**
59
+ * `"fixed"`: the exact column width in px. `"fluid"`: the minimum column width
60
+ * (columns grow from here). `"scroll"`: only seeds the default `maxCardWidth`
61
+ * — a scroll track has no minimum, because a minimum is exactly what would
62
+ * break the "two cards plus a peek" promise on a narrow phone.
63
+ */
64
+ cardWidth: number;
65
+ /**
66
+ * `"scroll"` only: the maximum fluid card width in px. Defaults to
67
+ * `1.6 × cardWidth`.
68
+ */
69
+ maxCardWidth?: number;
70
+ /** Layout behavior — see {@link CardGridVariant}. Defaults to `"fixed"`. */
71
+ variant?: CardGridVariant;
72
+ /** Additional classes appended to the default grid container */
73
+ className?: string;
74
+ /** `data-testid` for the grid container */
75
+ gridTestId?: string;
76
+ /** Ref forwarded to the grid container (used by `useMaxRowsHeight` clipping). */
77
+ containerRef?: Ref<HTMLDivElement>;
78
+ children: ReactNode;
79
+ }
80
+
81
+ /**
82
+ * Shared grid container for card layouts. Renders responsive columns via CSS
83
+ * grid, in one of three variants (see {@link CardGridVariant}).
84
+ *
85
+ * Ported from the tabwoah card grid (the `"fixed"` variant is verbatim). The one
86
+ * adaptation: that app is Tailwind (`className="grid gap-4 pb-0.5"`); this one
87
+ * has no Tailwind, so those three utilities (display:grid, gap 1rem,
88
+ * padding-bottom 2px) live in `style`.
89
+ */
90
+ export function CardGrid({
91
+ cardWidth,
92
+ maxCardWidth,
93
+ variant = "fixed",
94
+ className,
95
+ gridTestId,
96
+ containerRef,
97
+ children,
98
+ }: CardGridProps) {
99
+ const style: CSSProperties = {
100
+ display: "grid",
101
+ gap: "1rem",
102
+ paddingBottom: "2px",
103
+ };
104
+
105
+ if (variant === "scroll") {
106
+ const max = maxCardWidth ?? Math.round(cardWidth * 1.6);
107
+ // One row, laid out as implicit columns, that scrolls sideways. Each column
108
+ // is derived from the container so two whole cards plus a peek of the third
109
+ // always fit (see `scrollTrackWidth`). Scroll-snap makes a swipe settle on a
110
+ // card boundary instead of drifting mid-card.
111
+ style.gridAutoFlow = "column";
112
+ style.gridAutoColumns = scrollTrackWidth(Children.count(children), max);
113
+ style.overflowX = "auto";
114
+ style.overflowY = "hidden";
115
+ style.scrollSnapType = "x mandatory";
116
+ } else if (variant === "fluid") {
117
+ style.gridTemplateColumns = `repeat(auto-fill, minmax(${cardWidth}px, 1fr))`;
118
+ } else {
119
+ style.gridTemplateColumns = `repeat(auto-fill, ${cardWidth}px)`;
120
+ // `space-around` only distributes leftover width in the fixed variant; the
121
+ // fluid variant consumes it via `1fr` and scroll overflows instead.
122
+ style.justifyContent = "space-around";
123
+ }
124
+
125
+ // In the scroll variant each card is a snap target, so a swipe aligns to its
126
+ // start. The snap-align sits on a wrapper (the grid item) so callers' children
127
+ // stay untouched; the other variants render children directly.
128
+ const content =
129
+ variant === "scroll"
130
+ ? Children.map(children, (child) => (
131
+ <div style={{ scrollSnapAlign: "start" }}>{child}</div>
132
+ ))
133
+ : children;
134
+
135
+ return (
136
+ <div ref={containerRef} className={cn(className)} style={style} data-testid={gridTestId}>
137
+ {content}
138
+ </div>
139
+ );
140
+ }
@@ -0,0 +1,92 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { CardGrid } from "../CardGrid";
5
+
6
+ /** The rendered grid container's inline `grid-auto-columns` track sizing. */
7
+ function trackOf(children: number): string {
8
+ const { unmount } = render(
9
+ <CardGrid variant="scroll" cardWidth={165} maxCardWidth={190} gridTestId="rail">
10
+ {Array.from({ length: children }, (_, i) => (
11
+ <div key={i}>card {i}</div>
12
+ ))}
13
+ </CardGrid>,
14
+ );
15
+ const track = screen.getByTestId("rail").style.gridAutoColumns;
16
+ unmount();
17
+ return track;
18
+ }
19
+
20
+ describe("CardGrid scroll variant", () => {
21
+ it("sizes tracks so two cards plus a peek of the third always fit", () => {
22
+ // 2 cards + the 2 gaps that follow them + the 28px peek = the container.
23
+ expect(trackOf(3)).toBe("min(190px, calc((100% - 60px) / 2))");
24
+ });
25
+
26
+ it("keeps the peek at every rail length past the fold", () => {
27
+ expect(trackOf(7)).toBe(trackOf(3));
28
+ });
29
+
30
+ it("reserves no peek when there is nothing to scroll to", () => {
31
+ // Two cards: no overflow, so no "there's more" hint to make room for —
32
+ // they divide the row across the single gap between them.
33
+ expect(trackOf(2)).toBe("min(190px, calc((100% - 16px) / 2))");
34
+ expect(trackOf(1)).toBe("min(190px, calc((100% - 0px) / 1))");
35
+ });
36
+
37
+ it("never floors the track at cardWidth — a floor would swallow the peek", () => {
38
+ // The narrow-phone guarantee only holds if the track can shrink below the
39
+ // nominal card width; a `min-width` is exactly what broke it before.
40
+ expect(trackOf(5)).not.toContain("165px");
41
+ });
42
+
43
+ it("defaults maxCardWidth to 1.6 × cardWidth", () => {
44
+ render(
45
+ <CardGrid variant="scroll" cardWidth={100} gridTestId="default-max">
46
+ <div>a</div>
47
+ <div>b</div>
48
+ <div>c</div>
49
+ </CardGrid>,
50
+ );
51
+ expect(screen.getByTestId("default-max").style.gridAutoColumns).toContain("160px");
52
+ });
53
+
54
+ it("scrolls horizontally and snaps each card to the rail start", () => {
55
+ render(
56
+ <CardGrid variant="scroll" cardWidth={165} gridTestId="snap">
57
+ <div>a</div>
58
+ <div>b</div>
59
+ <div>c</div>
60
+ </CardGrid>,
61
+ );
62
+ const rail = screen.getByTestId("snap");
63
+ expect(rail.style.overflowX).toBe("auto");
64
+ expect(rail.style.scrollSnapType).toBe("x mandatory");
65
+ expect(rail.children).toHaveLength(3);
66
+ for (const child of rail.children) {
67
+ expect((child as HTMLElement).style.scrollSnapAlign).toBe("start");
68
+ }
69
+ });
70
+ });
71
+
72
+ describe("CardGrid wrapping variants", () => {
73
+ it("leaves the fluid and fixed track math untouched", () => {
74
+ render(
75
+ <CardGrid variant="fluid" cardWidth={165} gridTestId="fluid">
76
+ <div>a</div>
77
+ </CardGrid>,
78
+ );
79
+ render(
80
+ <CardGrid variant="fixed" cardWidth={230} gridTestId="fixed">
81
+ <div>a</div>
82
+ </CardGrid>,
83
+ );
84
+ expect(screen.getByTestId("fluid").style.gridTemplateColumns).toBe(
85
+ "repeat(auto-fill, minmax(165px, 1fr))",
86
+ );
87
+ expect(screen.getByTestId("fixed").style.gridTemplateColumns).toBe(
88
+ "repeat(auto-fill, 230px)",
89
+ );
90
+ expect(screen.getByTestId("fixed").style.justifyContent).toBe("space-around");
91
+ });
92
+ });
@@ -0,0 +1,2 @@
1
+ export { CardGrid } from "./CardGrid";
2
+ export type { CardGridVariant } from "./CardGrid";
@@ -0,0 +1,83 @@
1
+ import type { Theme } from '@mui/material';
2
+
3
+ import type { CollapsibleVariant } from './Collapsible.types';
4
+
5
+ /**
6
+ * `smooth` and `default` share their timing; only `spring` differs, stretching
7
+ * the duration and overshooting past the target before settling.
8
+ */
9
+ export const transitionSettings = (
10
+ theme: Theme,
11
+ variant: CollapsibleVariant,
12
+ duration: number,
13
+ easing?: string,
14
+ ): { duration: number; easing: string } => {
15
+ if (variant === 'spring') {
16
+ return {
17
+ duration: duration * 1.2,
18
+ easing: easing || 'cubic-bezier(0.34, 1.56, 0.64, 1)',
19
+ };
20
+ }
21
+
22
+ return { duration, easing: easing || theme.transitions.easing.easeInOut };
23
+ };
24
+
25
+ interface RegionState {
26
+ open: boolean;
27
+ disabled: boolean;
28
+ dataTestId?: string;
29
+ }
30
+
31
+ /**
32
+ * Shared by both implementations: a disabled collapsible reads as collapsed to
33
+ * assistive technology whatever its `open` prop says, because it cannot be
34
+ * operated to see the rest.
35
+ */
36
+ export const regionAttrs = ({ open, disabled, dataTestId }: RegionState) => ({
37
+ 'data-disabled': disabled,
38
+ 'data-testid': dataTestId,
39
+ role: 'region',
40
+ 'aria-expanded': open && !disabled,
41
+ 'aria-hidden': disabled || !open,
42
+ });
43
+
44
+ export const dimmedStyles = (disabled: boolean) => ({
45
+ opacity: disabled ? 0.6 : 1,
46
+ pointerEvents: (disabled ? 'none' : 'auto') as 'none' | 'auto',
47
+ });
48
+
49
+ export const triggerStyles = (
50
+ theme: Theme,
51
+ { disabled, expanded }: { disabled: boolean; expanded: boolean },
52
+ ) => {
53
+ const restingBackground = expanded ? theme.palette.action.selected : 'transparent';
54
+
55
+ return {
56
+ width: '100%',
57
+ padding: theme.spacing(1, 2),
58
+ border: 'none',
59
+ backgroundColor: restingBackground,
60
+ cursor: disabled ? 'not-allowed' : 'pointer',
61
+ display: 'flex',
62
+ alignItems: 'center',
63
+ justifyContent: 'space-between',
64
+ transition: theme.transitions.create(['background-color', 'opacity'], {
65
+ duration: theme.transitions.duration.short,
66
+ }),
67
+ opacity: disabled ? 0.6 : 1,
68
+ '&:hover': {
69
+ backgroundColor: disabled
70
+ ? 'transparent'
71
+ : expanded
72
+ ? theme.palette.action.selected
73
+ : theme.palette.action.hover,
74
+ },
75
+ '&:focus': {
76
+ outline: `2px solid ${theme.palette.primary.main}`,
77
+ outlineOffset: 2,
78
+ },
79
+ '&:active': {
80
+ backgroundColor: disabled ? 'transparent' : theme.palette.action.focus,
81
+ },
82
+ };
83
+ };
@@ -1,4 +1,5 @@
1
- import { ExpandLess,ExpandMore } from '@mui/icons-material';
1
+ import ExpandLess from '@mui/icons-material/ExpandLess';
2
+ import ExpandMore from '@mui/icons-material/ExpandMore';
2
3
  import { Box,Button, Card, Stack, Typography } from '@mui/material';
3
4
  import type { Meta, StoryObj } from '@storybook/react-vite';
4
5
  import { useState } from 'react';
@@ -105,8 +106,11 @@ export const KeyboardNavigation: Story = {
105
106
 
106
107
  await step('Focus trigger with Tab', async () => {
107
108
  const trigger = canvas.getByTestId('keyboard-trigger');
108
- trigger.focus();
109
- await expect(trigger).toHaveFocus();
109
+ // Tab rather than .focus(): the trigger is the only focusable node in this
110
+ // story, so this proves it is reachable by keyboard — which is what the
111
+ // step claims to check. Asserting that .focus() focused only tests the DOM.
112
+ await userEvent.tab();
113
+ await waitFor(() => expect(trigger).toHaveFocus());
110
114
  });
111
115
 
112
116
  await step('Verify keyboard accessibility', async () => {