@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,140 @@
1
+ "use client";
2
+
3
+ import { ConfirmButton } from "../../feedback/ConfirmAction";
4
+ import { Dialog, DialogContent } from "../../feedback/Dialog";
5
+ import { Button } from "../../form/Button";
6
+ import { Box } from "../../../mui/Box";
7
+ import { Stack } from "../../../mui/Stack";
8
+ import { Text } from "../../typography/Text";
9
+
10
+ import type { SavedViewSummary } from "./data-views-types";
11
+
12
+ interface ManageViewsDialogProps {
13
+ open: boolean;
14
+ onClose: () => void;
15
+ views: SavedViewSummary[];
16
+ onEdit: (view: SavedViewSummary) => void;
17
+ onDelete: (view: SavedViewSummary) => void;
18
+ testIdPrefix?: string;
19
+ }
20
+
21
+ function tags(view: SavedViewSummary): string[] {
22
+ const list: string[] = [];
23
+ if (view.isDefault) list.push("Padrão");
24
+ if (view.pinned) list.push("Fixada");
25
+ if (view.shared) list.push("Compartilhada");
26
+ if (!view.isOwner) list.push("De outro usuário");
27
+ return list;
28
+ }
29
+
30
+ /**
31
+ * "Gerenciar visões" dialog (FUT-89): lists every view the user can see with its
32
+ * flags, and owner-only edit/delete. Non-owned shared views are listed read-only.
33
+ */
34
+ /** One saved view: its name/description/tags, and the owner-only actions. */
35
+ function ManageViewRow({
36
+ view,
37
+ onEdit,
38
+ onDelete,
39
+ testIdPrefix,
40
+ }: {
41
+ view: SavedViewSummary;
42
+ onEdit: (view: SavedViewSummary) => void;
43
+ onDelete: (view: SavedViewSummary) => void;
44
+ testIdPrefix: string;
45
+ }): React.JSX.Element {
46
+ return (
47
+ <Stack
48
+ direction="row"
49
+ spacing={1}
50
+ sx={{ alignItems: "center", justifyContent: "space-between" }}
51
+ data-testid={`${testIdPrefix}-manage-row-${view.id}`}
52
+ >
53
+ <Box sx={{ minWidth: 0 }}>
54
+ <Text variant="body" size="sm" as="span" weight="semibold">
55
+ {view.name}
56
+ </Text>
57
+ {view.description && (
58
+ <Text variant="caption" as="p" color="secondary">
59
+ {view.description}
60
+ </Text>
61
+ )}
62
+ {tags(view).length > 0 && (
63
+ <Text variant="caption" as="p" color="secondary">
64
+ {tags(view).join(" · ")}
65
+ </Text>
66
+ )}
67
+ </Box>
68
+ {view.isOwner && (
69
+ <Stack direction="row" spacing={1} sx={{ flex: "0 0 auto" }}>
70
+ <Button
71
+ variant="text"
72
+ size="sm"
73
+ onClick={() => onEdit(view)}
74
+ dataTestId={`${testIdPrefix}-manage-edit-${view.id}`}
75
+ >
76
+ Editar
77
+ </Button>
78
+ {/* Confirm-gated (FUT-546): a saved view is a filter set somebody
79
+ built by hand, and there is no undo behind this. */}
80
+ <ConfirmButton
81
+ variant="text"
82
+ size="sm"
83
+ color="danger"
84
+ onClick={() => onDelete(view)}
85
+ confirm={{
86
+ title: 'Excluir a visão salva?',
87
+ entityName: view.name,
88
+ description:
89
+ 'Os filtros e colunas guardados nela são perdidos. Não é possível restaurar.',
90
+ confirmText: 'Excluir',
91
+ }}
92
+ dataTestId={`${testIdPrefix}-manage-delete-${view.id}`}
93
+ >
94
+ Excluir
95
+ </ConfirmButton>
96
+ </Stack>
97
+ )}
98
+ </Stack>
99
+ );
100
+ }
101
+
102
+ export function ManageViewsDialog({
103
+ open,
104
+ onClose,
105
+ views,
106
+ onEdit,
107
+ onDelete,
108
+ testIdPrefix = "views",
109
+ }: ManageViewsDialogProps): React.JSX.Element {
110
+ return (
111
+ <Dialog
112
+ open={open}
113
+ onClose={onClose}
114
+ title="Gerenciar visões"
115
+ size="md"
116
+ showCloseButton
117
+ dataTestId={`${testIdPrefix}-manage-dialog`}
118
+ >
119
+ <DialogContent>
120
+ <Stack spacing={1.5} data-testid={`${testIdPrefix}-manage-list`}>
121
+ {views.length === 0 ? (
122
+ <Text variant="body" as="p">
123
+ Nenhuma visão salva.
124
+ </Text>
125
+ ) : (
126
+ views.map((view) => (
127
+ <ManageViewRow
128
+ key={view.id}
129
+ view={view}
130
+ onEdit={onEdit}
131
+ onDelete={onDelete}
132
+ testIdPrefix={testIdPrefix}
133
+ />
134
+ ))
135
+ )}
136
+ </Stack>
137
+ </DialogContent>
138
+ </Dialog>
139
+ );
140
+ }
@@ -0,0 +1,289 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ import { Dialog, DialogContent } from "../../feedback/Dialog";
6
+ import { Button } from "../../form/Button";
7
+ import { Input } from "../../form/Input";
8
+ import { Switch } from "../../form/Switch";
9
+ import { Box } from "../../../mui/Box";
10
+ import { Stack } from "../../../mui/Stack";
11
+ import { Text } from "../../typography/Text";
12
+
13
+ import { describeViewState } from "./data-views-preview";
14
+ import type {
15
+ DataViewColumn,
16
+ DataViewState,
17
+ FilterFieldConfig,
18
+ SavedViewSummary,
19
+ } from "./data-views-types";
20
+
21
+ /** The values the modal collects (the current view state is captured by the parent). */
22
+ export interface SaveViewPayload {
23
+ name: string;
24
+ description: string;
25
+ shared: boolean;
26
+ pinned: boolean;
27
+ isDefault: boolean;
28
+ }
29
+
30
+ interface SaveViewModalProps<T extends Record<string, unknown>> {
31
+ open: boolean;
32
+ onClose: () => void;
33
+ /** The state being saved (drives the preview). */
34
+ currentState: DataViewState;
35
+ fields: FilterFieldConfig<T>[];
36
+ columns: DataViewColumn<T>[];
37
+ /** When set, the modal edits this view (seeds the form + becomes "Salvar alterações"). */
38
+ editing?: SavedViewSummary | null;
39
+ onSave: (payload: SaveViewPayload) => Promise<void> | void;
40
+ testIdPrefix?: string;
41
+ }
42
+
43
+ function PreviewList({ title, items, testId }: { title: string; items: string[]; testId: string }): React.JSX.Element {
44
+ return (
45
+ <Box data-testid={testId}>
46
+ <Text variant="caption" as="span" weight="semibold">
47
+ {title}
48
+ </Text>
49
+ {items.length === 0 ? (
50
+ <Text variant="caption" as="p" color="secondary">
51
+ —
52
+ </Text>
53
+ ) : (
54
+ <Stack component="ul" spacing={0.25} sx={{ m: 0, pl: 2 }}>
55
+ {items.map((item) => (
56
+ <Text key={item} variant="caption" as="li">
57
+ {item}
58
+ </Text>
59
+ ))}
60
+ </Stack>
61
+ )}
62
+ </Box>
63
+ );
64
+ }
65
+
66
+ interface FlagsSwitchesProps {
67
+ pinned: boolean;
68
+ shared: boolean;
69
+ isDefault: boolean;
70
+ setPinned: (value: boolean) => void;
71
+ setShared: (value: boolean) => void;
72
+ setIsDefault: (value: boolean) => void;
73
+ testIdPrefix: string;
74
+ }
75
+
76
+ function FlagsSwitches({
77
+ pinned,
78
+ shared,
79
+ isDefault,
80
+ setPinned,
81
+ setShared,
82
+ setIsDefault,
83
+ testIdPrefix,
84
+ }: FlagsSwitchesProps): React.JSX.Element {
85
+ return (
86
+ <Stack spacing={0.5}>
87
+ <Switch
88
+ size="sm"
89
+ label="Fixar na barra lateral"
90
+ checked={pinned}
91
+ onChange={(_event, value) => setPinned(value)}
92
+ dataTestId={`${testIdPrefix}-save-pinned`}
93
+ />
94
+ <Switch
95
+ size="sm"
96
+ label="Compartilhar com a equipe"
97
+ checked={shared}
98
+ onChange={(_event, value) => setShared(value)}
99
+ dataTestId={`${testIdPrefix}-save-shared`}
100
+ />
101
+ <Switch
102
+ size="sm"
103
+ label="Definir como padrão"
104
+ checked={isDefault}
105
+ onChange={(_event, value) => setIsDefault(value)}
106
+ dataTestId={`${testIdPrefix}-save-default`}
107
+ />
108
+ </Stack>
109
+ );
110
+ }
111
+
112
+ interface PreviewBoxProps {
113
+ preview: { filters: string[]; columns: string[]; sort: string[] };
114
+ testIdPrefix: string;
115
+ }
116
+
117
+ function PreviewBox({ preview, testIdPrefix }: PreviewBoxProps): React.JSX.Element {
118
+ return (
119
+ <Box sx={{ p: 1.5, borderRadius: 1, border: (theme) => `1px solid ${theme.palette.divider}` }}>
120
+ <Text variant="caption" as="p" weight="semibold">
121
+ Prévia
122
+ </Text>
123
+ <Stack spacing={1} sx={{ mt: 0.5 }}>
124
+ <PreviewList title="Filtros" items={preview.filters} testId={`${testIdPrefix}-preview-filters`} />
125
+ <PreviewList title="Colunas" items={preview.columns} testId={`${testIdPrefix}-preview-columns`} />
126
+ <PreviewList title="Ordenação" items={preview.sort} testId={`${testIdPrefix}-preview-sort`} />
127
+ </Stack>
128
+ </Box>
129
+ );
130
+ }
131
+
132
+ interface SaveViewFormState {
133
+ name: string;
134
+ description: string;
135
+ pinned: boolean;
136
+ shared: boolean;
137
+ isDefault: boolean;
138
+ canSave: boolean;
139
+ error: string | null;
140
+ setName: (value: string) => void;
141
+ setDescription: (value: string) => void;
142
+ setPinned: (value: boolean) => void;
143
+ setShared: (value: boolean) => void;
144
+ setIsDefault: (value: boolean) => void;
145
+ submit: () => Promise<void>;
146
+ }
147
+
148
+ /** The initial form values, seeded from an edited view (or blank for create). */
149
+ function seedForm(editing: SavedViewSummary | null | undefined): SaveViewPayload {
150
+ if (!editing) {
151
+ return { name: "", description: "", pinned: false, shared: false, isDefault: false };
152
+ }
153
+ return {
154
+ name: editing.name,
155
+ description: editing.description ?? "",
156
+ pinned: editing.pinned,
157
+ shared: editing.shared,
158
+ isDefault: editing.isDefault,
159
+ };
160
+ }
161
+
162
+ /** Local form state + submit for {@link SaveViewModal}, seeded from an edited view. */
163
+ function useSaveViewForm(
164
+ editing: SavedViewSummary | null | undefined,
165
+ onSave: (payload: SaveViewPayload) => Promise<void> | void,
166
+ ): SaveViewFormState {
167
+ const initial = seedForm(editing);
168
+ const [name, setName] = useState(initial.name);
169
+ const [description, setDescription] = useState(initial.description);
170
+ const [pinned, setPinned] = useState(initial.pinned);
171
+ const [shared, setShared] = useState(initial.shared);
172
+ const [isDefault, setIsDefault] = useState(initial.isDefault);
173
+ const [saving, setSaving] = useState(false);
174
+ const [error, setError] = useState<string | null>(null);
175
+
176
+ const canSave = name.trim().length > 0 && !saving;
177
+
178
+ async function submit(): Promise<void> {
179
+ setSaving(true);
180
+ setError(null);
181
+ // A rejected save (e.g. duplicate name) is caught here so the modal stays
182
+ // open and shows the reason; the finally always re-enables the button.
183
+ try {
184
+ await onSave({ name: name.trim(), description: description.trim(), shared, pinned, isDefault });
185
+ } catch (cause) {
186
+ setError(cause instanceof Error ? cause.message : "Não foi possível salvar a visão.");
187
+ } finally {
188
+ setSaving(false);
189
+ }
190
+ }
191
+
192
+ return {
193
+ name,
194
+ description,
195
+ pinned,
196
+ shared,
197
+ isDefault,
198
+ canSave,
199
+ error,
200
+ setName,
201
+ setDescription,
202
+ setPinned,
203
+ setShared,
204
+ setIsDefault,
205
+ submit,
206
+ };
207
+ }
208
+
209
+ /**
210
+ * The "Salvar visão" modal (FUT-89): captures a name + description and the pin /
211
+ * share / default flags, and previews the filters, visible columns, and sort the
212
+ * view will store. Used for both create and edit.
213
+ */
214
+ export function SaveViewModal<T extends Record<string, unknown>>({
215
+ open,
216
+ onClose,
217
+ currentState,
218
+ fields,
219
+ columns,
220
+ editing,
221
+ onSave,
222
+ testIdPrefix = "view",
223
+ }: SaveViewModalProps<T>): React.JSX.Element {
224
+ const form = useSaveViewForm(editing, onSave);
225
+ const preview = describeViewState(currentState, fields, columns);
226
+
227
+ return (
228
+ <Dialog
229
+ open={open}
230
+ onClose={onClose}
231
+ title={editing ? "Editar visão" : "Salvar visão"}
232
+ size="sm"
233
+ showCloseButton
234
+ dataTestId={`${testIdPrefix}-save-modal`}
235
+ >
236
+ <DialogContent>
237
+ <Stack spacing={2} data-testid={`${testIdPrefix}-save-form`}>
238
+ <Input
239
+ size="sm"
240
+ fullWidth
241
+ label="Nome"
242
+ value={form.name}
243
+ onChange={(event) => form.setName(event.target.value)}
244
+ data-testid={`${testIdPrefix}-save-name`}
245
+ />
246
+ <Input
247
+ size="sm"
248
+ fullWidth
249
+ label="Descrição (opcional)"
250
+ value={form.description}
251
+ onChange={(event) => form.setDescription(event.target.value)}
252
+ data-testid={`${testIdPrefix}-save-description`}
253
+ />
254
+
255
+ <FlagsSwitches
256
+ pinned={form.pinned}
257
+ shared={form.shared}
258
+ isDefault={form.isDefault}
259
+ setPinned={form.setPinned}
260
+ setShared={form.setShared}
261
+ setIsDefault={form.setIsDefault}
262
+ testIdPrefix={testIdPrefix}
263
+ />
264
+
265
+ <PreviewBox preview={preview} testIdPrefix={testIdPrefix} />
266
+
267
+ {form.error && (
268
+ <Text variant="caption" color="danger" as="p" data-testid={`${testIdPrefix}-save-error`}>
269
+ {form.error}
270
+ </Text>
271
+ )}
272
+
273
+ <Stack direction="row" spacing={2}>
274
+ <Button
275
+ onClick={() => void form.submit()}
276
+ disabled={!form.canSave}
277
+ dataTestId={`${testIdPrefix}-save-submit`}
278
+ >
279
+ {editing ? "Salvar alterações" : "Salvar visão"}
280
+ </Button>
281
+ <Button variant="text" onClick={onClose} dataTestId={`${testIdPrefix}-save-cancel`}>
282
+ Cancelar
283
+ </Button>
284
+ </Stack>
285
+ </Stack>
286
+ </DialogContent>
287
+ </Dialog>
288
+ );
289
+ }
@@ -0,0 +1,271 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import { ThemeProvider, createTheme } from "../../../../mui/styles";
4
+
5
+ import { DataViewsGrid } from "../DataViewsGrid";
6
+ import type { DataViewColumn, FilterFieldConfig } from "../data-views-types";
7
+
8
+ interface Row extends Record<string, unknown> {
9
+ id: string;
10
+ name: string;
11
+ status: string;
12
+ }
13
+
14
+ const rows: Row[] = [
15
+ { id: "1", name: "Ana", status: "ACTIVE" },
16
+ { id: "2", name: "Bruno", status: "INACTIVE" },
17
+ { id: "3", name: "Carla", status: "ACTIVE" },
18
+ ];
19
+
20
+ const columns: DataViewColumn<Row>[] = [
21
+ { id: "name", header: "Nome", accessor: "name", searchable: true },
22
+ { id: "status", header: "Status", accessor: "status", searchable: true },
23
+ { id: "actions", header: "", type: "actions", hideable: false, cell: () => null },
24
+ ];
25
+
26
+ const fields: FilterFieldConfig<Row>[] = [
27
+ {
28
+ id: "status",
29
+ label: "Status",
30
+ options: [
31
+ { value: "ACTIVE", label: "Ativo" },
32
+ { value: "INACTIVE", label: "Inativo" },
33
+ ],
34
+ },
35
+ ];
36
+
37
+ function renderGrid(props: Partial<React.ComponentProps<typeof DataViewsGrid<Row>>> = {}) {
38
+ return render(
39
+ <ThemeProvider theme={createTheme()}>
40
+ <DataViewsGrid<Row>
41
+ rows={rows}
42
+ columns={columns}
43
+ fields={fields}
44
+ getRowId={(row) => row.id}
45
+ dataTestId="people"
46
+ testIdPrefix="people"
47
+ {...props}
48
+ />
49
+ </ThemeProvider>,
50
+ );
51
+ }
52
+
53
+ const counter = (): string => screen.getByTestId("people-counter").textContent ?? "";
54
+
55
+ /** The panel keeps its fields mounted (visibility-hidden when closed), so they
56
+ * stay interactable in tests. Query the input directly — `getByRole` would skip
57
+ * the hidden panel. */
58
+ function statusCheckbox(value: string): HTMLInputElement {
59
+ const input = screen.getByTestId(`people-filter-status-${value}`).querySelector("input");
60
+ if (!input) throw new Error(`checkbox input for ${value} not found`);
61
+ return input;
62
+ }
63
+
64
+ describe("DataViewsGrid", () => {
65
+ beforeEach(() => window.localStorage.clear());
66
+
67
+ it("renders every row and a matched/total counter", () => {
68
+ renderGrid();
69
+ expect(screen.getByText("Ana")).toBeInTheDocument();
70
+ expect(screen.getByText("Bruno")).toBeInTheDocument();
71
+ expect(counter()).toContain("3 de 3");
72
+ });
73
+
74
+ it("narrows via the keyword search (commit on blur) and updates the counter", async () => {
75
+ renderGrid();
76
+ const search = screen.getByTestId("people-search-all");
77
+ fireEvent.change(search, { target: { value: "car" } });
78
+ fireEvent.blur(search);
79
+ await waitFor(() => expect(screen.queryByText("Ana")).not.toBeInTheDocument());
80
+ expect(screen.getByText("Carla")).toBeInTheDocument();
81
+ expect(counter()).toContain("1 de 3");
82
+ });
83
+
84
+ it("applies a checkbox filter field and updates the counter", async () => {
85
+ renderGrid();
86
+ fireEvent.click(statusCheckbox("INACTIVE"));
87
+ await waitFor(() => expect(screen.queryByText("Ana")).not.toBeInTheDocument());
88
+ expect(screen.getByText("Bruno")).toBeInTheDocument();
89
+ expect(counter()).toContain("1 de 3");
90
+ });
91
+
92
+ it("reports state changes and re-applies a supplied view state", async () => {
93
+ const onStateChange = vi.fn();
94
+ const { rerender } = renderGrid({ onStateChange });
95
+ expect(onStateChange).toHaveBeenCalled();
96
+
97
+ rerender(
98
+ <ThemeProvider theme={createTheme()}>
99
+ <DataViewsGrid<Row>
100
+ rows={rows}
101
+ columns={columns}
102
+ fields={fields}
103
+ getRowId={(row) => row.id}
104
+ dataTestId="people"
105
+ testIdPrefix="people"
106
+ onStateChange={onStateChange}
107
+ appliedState={{ search: "", pills: { status: ["ACTIVE"] }, sortBy: [], visibleColumns: ["name", "status", "actions"] }}
108
+ />
109
+ </ThemeProvider>,
110
+ );
111
+
112
+ await waitFor(() => expect(screen.queryByText("Bruno")).not.toBeInTheDocument());
113
+ expect(counter()).toContain("2 de 3");
114
+ });
115
+
116
+ it("syncState re-applies the URL controls on change while preserving hidden columns", async () => {
117
+ // A stable appliedState hides the `status` column; only `syncState` changes
118
+ // between renders (as a URL back/forward would), so the appliedState effect
119
+ // never re-fires and we isolate the syncState merge.
120
+ const applied = { search: "", pills: {}, sortBy: [], visibleColumns: ["name", "actions"] };
121
+ const { rerender } = renderGrid({
122
+ appliedState: applied,
123
+ syncState: { search: "", pills: {}, ranges: {}, sortBy: [] },
124
+ });
125
+ // `name` header present (evidence the grid rendered) but `status` is hidden.
126
+ expect(screen.getByRole("columnheader", { name: "Nome" })).toBeInTheDocument();
127
+ await waitFor(() =>
128
+ expect(screen.queryByRole("columnheader", { name: "Status" })).not.toBeInTheDocument(),
129
+ );
130
+ expect(counter()).toContain("3 de 3");
131
+
132
+ // A new syncState reference applies a status filter (e.g. browser back/forward).
133
+ rerender(
134
+ <ThemeProvider theme={createTheme()}>
135
+ <DataViewsGrid<Row>
136
+ rows={rows}
137
+ columns={columns}
138
+ fields={fields}
139
+ getRowId={(row) => row.id}
140
+ dataTestId="people"
141
+ testIdPrefix="people"
142
+ appliedState={applied}
143
+ syncState={{ search: "", pills: { status: ["ACTIVE"] }, ranges: {}, sortBy: [] }}
144
+ />
145
+ </ThemeProvider>,
146
+ );
147
+
148
+ // The filter re-applied (Bruno is INACTIVE)...
149
+ await waitFor(() => expect(screen.queryByText("Bruno")).not.toBeInTheDocument());
150
+ expect(counter()).toContain("2 de 3");
151
+ // ...and the user's hidden `status` column was NOT resurrected by the re-sync.
152
+ await waitFor(() =>
153
+ expect(screen.queryByRole("columnheader", { name: "Status" })).not.toBeInTheDocument(),
154
+ );
155
+ });
156
+
157
+ it("the funnel toggle opens/closes the filter panel", async () => {
158
+ renderGrid();
159
+ const funnel = screen.getByTestId("people-filters-toggle");
160
+ expect(funnel).toHaveAttribute("aria-expanded", "false");
161
+ fireEvent.click(funnel);
162
+ await waitFor(() => expect(funnel).toHaveAttribute("aria-expanded", "true"));
163
+ // Counter stays visible in the toolbar regardless of panel state.
164
+ expect(screen.getByTestId("people-counter")).toBeInTheDocument();
165
+ fireEvent.click(funnel);
166
+ await waitFor(() => expect(funnel).toHaveAttribute("aria-expanded", "false"));
167
+ });
168
+
169
+ it("selects all rows, shows the count, and renders the bulk action", async () => {
170
+ const onBulk = vi.fn();
171
+ renderGrid({
172
+ bulkActions: (selected, clear) => (
173
+ <button
174
+ type="button"
175
+ data-testid="people-bulk"
176
+ onClick={() => {
177
+ onBulk(selected.length);
178
+ clear();
179
+ }}
180
+ >
181
+ Bulk {selected.length}
182
+ </button>
183
+ ),
184
+ });
185
+ // No selection: no count indicator, no bulk action.
186
+ await waitFor(() => expect(screen.queryByTestId("selected-count-indicator")).not.toBeInTheDocument());
187
+
188
+ fireEvent.click(screen.getByTestId("people-select-all"));
189
+ await waitFor(() =>
190
+ expect(screen.getByTestId("selected-count-indicator")).toHaveTextContent("3 items selected"),
191
+ );
192
+ const bulk = screen.getByTestId("people-bulk");
193
+ fireEvent.click(bulk);
194
+ expect(onBulk).toHaveBeenCalledWith(3);
195
+ // Bulk action clears the selection.
196
+ await waitFor(() => expect(screen.queryByTestId("selected-count-indicator")).not.toBeInTheDocument());
197
+ });
198
+
199
+ it("drives the row kebab AND the bulk menu from one rowActions list", async () => {
200
+ const onArchive = vi.fn();
201
+ const onEdit = vi.fn();
202
+ renderGrid({
203
+ // No actions column here so the grid appends its auto kebab from rowActions.
204
+ columns: [
205
+ { id: "name", header: "Nome", accessor: "name", searchable: true },
206
+ { id: "status", header: "Status", accessor: "status" },
207
+ ],
208
+ rowActions: [
209
+ // `bulk: false` → appears in the kebab but NOT the bulk menu.
210
+ { id: "edit", label: "Editar", bulk: false, onSelect: (r) => onEdit(r.length) },
211
+ { id: "archive", label: "Arquivar", color: "error", onSelect: (r) => onArchive(r.length) },
212
+ ],
213
+ });
214
+
215
+ // Per-row kebab: both actions present; Editar acts on the single row.
216
+ fireEvent.click(screen.getByTestId("people-actions-1"));
217
+ expect(screen.getByText("Editar")).toBeInTheDocument();
218
+ expect(screen.getByText("Arquivar")).toBeInTheDocument();
219
+ fireEvent.click(screen.getByText("Editar"));
220
+ expect(onEdit).toHaveBeenCalledWith(1);
221
+
222
+ // Select all → the bulk "Ações" menu shows only the bulk-eligible action.
223
+ fireEvent.click(screen.getByTestId("people-select-all"));
224
+ await waitFor(() => expect(screen.getByTestId("people-bulk-actions")).toBeInTheDocument());
225
+ fireEvent.click(screen.getByTestId("people-bulk-actions"));
226
+ await waitFor(() => expect(screen.getByText("Arquivar")).toBeInTheDocument());
227
+ // "Editar" is single-item only, so it must NOT appear in the bulk menu.
228
+ await waitFor(() => expect(screen.queryByText("Editar")).not.toBeInTheDocument());
229
+ fireEvent.click(screen.getByText("Arquivar"));
230
+ expect(onArchive).toHaveBeenCalledWith(3);
231
+ });
232
+
233
+ it("re-emits visible rows on a same-id content change, but not on a content-equal rerender", async () => {
234
+ // Local seed (not the shared module `rows`) so this test owns its data.
235
+ const seed: Row[] = [
236
+ { id: "1", name: "Ana", status: "ACTIVE" },
237
+ { id: "2", name: "Bruno", status: "INACTIVE" },
238
+ ];
239
+ const onVisibleRowsChange = vi.fn<(rows: Row[]) => void>();
240
+ const { rerender } = renderGrid({ onVisibleRowsChange, rows: seed });
241
+ await waitFor(() => expect(onVisibleRowsChange).toHaveBeenCalled());
242
+ const afterInitial = onVisibleRowsChange.mock.calls.length;
243
+
244
+ const rerenderWith = (next: Row[]): void =>
245
+ rerender(
246
+ <ThemeProvider theme={createTheme()}>
247
+ <DataViewsGrid<Row>
248
+ rows={next}
249
+ columns={columns}
250
+ fields={fields}
251
+ getRowId={(row) => row.id}
252
+ dataTestId="people"
253
+ testIdPrefix="people"
254
+ onVisibleRowsChange={onVisibleRowsChange}
255
+ />
256
+ </ThemeProvider>,
257
+ );
258
+
259
+ // Fresh array + row refs, identical values → no re-emit (guards the loop).
260
+ rerenderWith(seed.map((row) => ({ ...row })));
261
+ expect(onVisibleRowsChange.mock.calls.length).toBe(afterInitial);
262
+
263
+ // Same ids, changed values → must re-emit so the export isn't stale.
264
+ rerenderWith(seed.map((row) => (row.id === "1" ? { ...row, name: "Ana Editada" } : { ...row })));
265
+ await waitFor(() =>
266
+ expect(onVisibleRowsChange.mock.calls.length).toBeGreaterThan(afterInitial),
267
+ );
268
+ const emitted = onVisibleRowsChange.mock.calls.at(-1)?.[0] ?? [];
269
+ expect(emitted.find((row) => row.id === "1")?.name).toBe("Ana Editada");
270
+ });
271
+ });