@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,167 @@
1
+ import PaidIcon from '@mui/icons-material/Paid';
2
+ import ReceiptLongIcon from '@mui/icons-material/ReceiptLong';
3
+ import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
4
+ import TrendingUpIcon from '@mui/icons-material/TrendingUp';
5
+ import { Box } from '@mui/material';
6
+ import type { Meta, StoryObj } from '@storybook/react-vite';
7
+
8
+ import { StatCard } from './StatCard';
9
+
10
+ const meta: Meta<typeof StatCard> = {
11
+ title: 'DataDisplay/StatCard',
12
+ component: StatCard,
13
+ parameters: {
14
+ layout: 'centered',
15
+ docs: {
16
+ description: {
17
+ component:
18
+ 'KPI tile: one labelled figure with an optional period-over-period delta, icon, hint and loading state. Values arrive pre-formatted — the tile never formats money or percentages itself.',
19
+ },
20
+ },
21
+ },
22
+ tags: ['autodocs'],
23
+ argTypes: {
24
+ label: { control: 'text' },
25
+ value: { control: 'text' },
26
+ hint: { control: 'text' },
27
+ loading: { control: 'boolean' },
28
+ delta: { control: false },
29
+ icon: { control: false },
30
+ className: { control: 'text' },
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+ type Story = StoryObj<typeof meta>;
36
+
37
+ export const Default: Story = {
38
+ args: {
39
+ label: 'Receita',
40
+ value: 'R$ 12.480,00',
41
+ },
42
+ };
43
+
44
+ export const WithPositiveDelta: Story = {
45
+ args: {
46
+ label: 'Receita',
47
+ value: 'R$ 12.480,00',
48
+ delta: { value: '+12,5%', direction: 'up', label: 'vs. período anterior' },
49
+ },
50
+ };
51
+
52
+ export const WithNegativeDelta: Story = {
53
+ args: {
54
+ label: 'Pedidos',
55
+ value: '184',
56
+ delta: { value: '-8,2%', direction: 'down', label: 'vs. período anterior' },
57
+ },
58
+ };
59
+
60
+ export const WithNeutralDelta: Story = {
61
+ args: {
62
+ label: 'Margem',
63
+ value: '62,0%',
64
+ delta: { value: '0,0%', direction: 'neutral', label: 'vs. período anterior' },
65
+ },
66
+ };
67
+
68
+ /**
69
+ * "Lower is better" metrics separate the ARROW from the MEANING: cost rose
70
+ * (`direction: 'up'`) but that reads badly (`tone: 'negative'`).
71
+ */
72
+ export const InvertedTone: Story = {
73
+ args: {
74
+ label: 'Custo (CMV)',
75
+ value: 'R$ 4.900,00',
76
+ delta: {
77
+ value: '+9,4%',
78
+ direction: 'up',
79
+ tone: 'negative',
80
+ label: 'vs. período anterior',
81
+ },
82
+ },
83
+ };
84
+
85
+ export const WithIcon: Story = {
86
+ args: {
87
+ label: 'Lucro bruto',
88
+ value: 'R$ 7.580,00',
89
+ icon: <TrendingUpIcon fontSize="small" />,
90
+ delta: { value: '+14,0%', direction: 'up' },
91
+ },
92
+ };
93
+
94
+ export const WithHint: Story = {
95
+ args: {
96
+ label: 'Receita',
97
+ value: 'R$ 12.480,00',
98
+ hint: 'Últimos 30 dias',
99
+ },
100
+ };
101
+
102
+ export const Loading: Story = {
103
+ args: {
104
+ label: 'Receita',
105
+ value: 'R$ 12.480,00',
106
+ delta: { value: '+12,5%', direction: 'up' },
107
+ loading: true,
108
+ },
109
+ };
110
+
111
+ export const LongValueOverflow: Story = {
112
+ args: {
113
+ label: 'Receita acumulada do exercício corrente',
114
+ value: 'R$ 1.284.930.577,25',
115
+ delta: { value: '+1.204,75%', direction: 'up', label: 'vs. período anterior' },
116
+ },
117
+ decorators: [
118
+ (Story) => (
119
+ <Box sx={{ width: 220 }}>
120
+ <Story />
121
+ </Box>
122
+ ),
123
+ ],
124
+ };
125
+
126
+ /** The dashboard shape: a responsive grid of tiles sharing one range. */
127
+ export const KpiGrid: Story = {
128
+ render: () => (
129
+ <Box
130
+ sx={{
131
+ display: 'grid',
132
+ gap: 2,
133
+ gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(4, 1fr)' },
134
+ width: { xs: 280, sm: 520, md: 880 },
135
+ }}
136
+ >
137
+ <StatCard
138
+ label="Receita"
139
+ value="R$ 12.480,00"
140
+ icon={<PaidIcon fontSize="small" />}
141
+ delta={{ value: '+12,5%', direction: 'up', label: 'vs. período anterior' }}
142
+ dataTestId="kpi-revenue"
143
+ />
144
+ <StatCard
145
+ label="Custo (CMV)"
146
+ value="R$ 4.900,00"
147
+ icon={<ReceiptLongIcon fontSize="small" />}
148
+ delta={{ value: '+9,4%', direction: 'up', tone: 'negative', label: 'vs. período anterior' }}
149
+ dataTestId="kpi-cogs"
150
+ />
151
+ <StatCard
152
+ label="Lucro bruto"
153
+ value="R$ 7.580,00"
154
+ icon={<TrendingUpIcon fontSize="small" />}
155
+ delta={{ value: '+14,0%', direction: 'up', label: 'vs. período anterior' }}
156
+ dataTestId="kpi-profit"
157
+ />
158
+ <StatCard
159
+ label="Pedidos"
160
+ value="184"
161
+ icon={<ShoppingCartIcon fontSize="small" />}
162
+ delta={{ value: '-8,2%', direction: 'down', label: 'vs. período anterior' }}
163
+ dataTestId="kpi-orders"
164
+ />
165
+ </Box>
166
+ ),
167
+ };
@@ -0,0 +1,240 @@
1
+ import TrendingUpIcon from '@mui/icons-material/TrendingUp';
2
+ import { Box } from '@mui/material';
3
+ import type { Meta, StoryObj } from '@storybook/react-vite';
4
+ import { expect, waitFor, within } from 'storybook/test';
5
+
6
+ import { StatCard } from './StatCard';
7
+
8
+ const meta: Meta<typeof StatCard> = {
9
+ title: 'DataDisplay/StatCard/Tests',
10
+ component: StatCard,
11
+ parameters: {
12
+ layout: 'padded',
13
+ chromatic: { disableSnapshot: false },
14
+ },
15
+ tags: ['autodocs', 'test', 'component:StatCard'],
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const BasicInteraction: Story = {
22
+ name: '🧪 Renders label and value',
23
+ args: {
24
+ label: 'Receita',
25
+ value: 'R$ 12.480,00',
26
+ dataTestId: 'kpi-revenue',
27
+ },
28
+ play: async ({ canvasElement, step }) => {
29
+ const canvas = within(canvasElement);
30
+
31
+ await step('Tile root is present', async () => {
32
+ await expect(canvas.getByTestId('kpi-revenue')).toBeInTheDocument();
33
+ });
34
+
35
+ await step('Label and value render their text', async () => {
36
+ await expect(canvas.getByTestId('kpi-revenue-label')).toHaveTextContent('Receita');
37
+ await expect(canvas.getByTestId('kpi-revenue-value')).toHaveTextContent('R$ 12.480,00');
38
+ });
39
+
40
+ await step('No delta renders when none is passed', async () => {
41
+ await waitFor(() =>
42
+ expect(canvas.queryByTestId('kpi-revenue-delta')).not.toBeInTheDocument(),
43
+ );
44
+ });
45
+ },
46
+ };
47
+
48
+ export const ScreenReaderTest: Story = {
49
+ name: '🧪 Screen Reader — labelled group and delta sentence',
50
+ args: {
51
+ label: 'Receita',
52
+ value: 'R$ 12.480,00',
53
+ delta: { value: '+12,5%', direction: 'up', label: 'vs. período anterior' },
54
+ dataTestId: 'kpi-revenue',
55
+ },
56
+ play: async ({ canvasElement, step }) => {
57
+ const canvas = within(canvasElement);
58
+
59
+ await step('The tile is a group labelled by its own label element', async () => {
60
+ const group = canvas.getByRole('group', { name: 'Receita' });
61
+ await expect(group).toBe(canvas.getByTestId('kpi-revenue'));
62
+ });
63
+
64
+ await step('The delta exposes a direction-aware sentence', async () => {
65
+ await expect(
66
+ canvas.getByLabelText('Increase of +12,5% vs. período anterior'),
67
+ ).toBeInTheDocument();
68
+ });
69
+ },
70
+ };
71
+
72
+ export const VisualStateUpDelta: Story = {
73
+ name: '🧪 Visual State — upward delta reads positive',
74
+ args: {
75
+ label: 'Lucro bruto',
76
+ value: 'R$ 7.580,00',
77
+ icon: <TrendingUpIcon fontSize="small" />,
78
+ delta: { value: '+14,0%', direction: 'up' },
79
+ dataTestId: 'kpi-profit',
80
+ },
81
+ play: async ({ canvasElement, step }) => {
82
+ const canvas = within(canvasElement);
83
+
84
+ await step('Direction and tone are exposed on the delta row', async () => {
85
+ const delta = canvas.getByTestId('kpi-profit-delta');
86
+ await expect(delta).toHaveAttribute('data-direction', 'up');
87
+ await expect(delta).toHaveAttribute('data-tone', 'positive');
88
+ });
89
+
90
+ await step('The leading icon renders and is hidden from assistive tech', async () => {
91
+ await expect(canvas.getByTestId('kpi-profit-icon')).toHaveAttribute('aria-hidden', 'true');
92
+ });
93
+ },
94
+ };
95
+
96
+ export const VisualStateDownDelta: Story = {
97
+ name: '🧪 Visual State — downward delta reads negative',
98
+ args: {
99
+ label: 'Pedidos',
100
+ value: '184',
101
+ delta: { value: '-8,2%', direction: 'down' },
102
+ dataTestId: 'kpi-orders',
103
+ },
104
+ play: async ({ canvasElement, step }) => {
105
+ const canvas = within(canvasElement);
106
+
107
+ await step('Direction and tone are exposed on the delta row', async () => {
108
+ const delta = canvas.getByTestId('kpi-orders-delta');
109
+ await expect(delta).toHaveAttribute('data-direction', 'down');
110
+ await expect(delta).toHaveAttribute('data-tone', 'negative');
111
+ });
112
+
113
+ await step('The generated sentence says "Decrease"', async () => {
114
+ await expect(canvas.getByLabelText('Decrease of -8,2%')).toBeInTheDocument();
115
+ });
116
+ },
117
+ };
118
+
119
+ export const EdgeCaseInvertedTone: Story = {
120
+ name: '🧪 Edge Case — rising cost keeps the up arrow but reads negative',
121
+ args: {
122
+ label: 'Custo (CMV)',
123
+ value: 'R$ 4.900,00',
124
+ delta: { value: '+9,4%', direction: 'up', tone: 'negative' },
125
+ dataTestId: 'kpi-cogs',
126
+ },
127
+ play: async ({ canvasElement, step }) => {
128
+ const canvas = within(canvasElement);
129
+
130
+ await step('Arrow direction and semantic tone are independent', async () => {
131
+ const delta = canvas.getByTestId('kpi-cogs-delta');
132
+ await expect(delta).toHaveAttribute('data-direction', 'up');
133
+ await expect(delta).toHaveAttribute('data-tone', 'negative');
134
+ });
135
+ },
136
+ };
137
+
138
+ export const EdgeCaseLocalizedAria: Story = {
139
+ name: '🧪 Edge Case — ariaLabel overrides the English sentence',
140
+ args: {
141
+ label: 'Receita',
142
+ value: 'R$ 12.480,00',
143
+ delta: {
144
+ value: '+12,5%',
145
+ direction: 'up',
146
+ ariaLabel: 'Aumento de 12,5% em relação ao período anterior',
147
+ },
148
+ dataTestId: 'kpi-revenue',
149
+ },
150
+ play: async ({ canvasElement, step }) => {
151
+ const canvas = within(canvasElement);
152
+
153
+ await step('The override wins over the generated text', async () => {
154
+ await expect(
155
+ canvas.getByLabelText('Aumento de 12,5% em relação ao período anterior'),
156
+ ).toBeInTheDocument();
157
+ await waitFor(() =>
158
+ expect(canvas.queryByLabelText('Increase of +12,5%')).not.toBeInTheDocument(),
159
+ );
160
+ });
161
+ },
162
+ };
163
+
164
+ export const LoadingStateTest: Story = {
165
+ name: '🧪 Loading State — skeletons replace value and delta',
166
+ args: {
167
+ label: 'Receita',
168
+ value: 'R$ 12.480,00',
169
+ delta: { value: '+12,5%', direction: 'up' },
170
+ loading: true,
171
+ dataTestId: 'kpi-revenue',
172
+ },
173
+ play: async ({ canvasElement, step }) => {
174
+ const canvas = within(canvasElement);
175
+
176
+ await step('The tile announces itself as busy', async () => {
177
+ await expect(canvas.getByTestId('kpi-revenue')).toHaveAttribute('aria-busy', 'true');
178
+ });
179
+
180
+ await step('Value and delta are replaced by skeletons', async () => {
181
+ await expect(canvas.getByTestId('kpi-revenue-value-skeleton')).toBeInTheDocument();
182
+ await expect(canvas.getByTestId('kpi-revenue-delta-skeleton')).toBeInTheDocument();
183
+ await waitFor(() => {
184
+ expect(canvas.queryByTestId('kpi-revenue-value')).not.toBeInTheDocument();
185
+ expect(canvas.queryByTestId('kpi-revenue-delta')).not.toBeInTheDocument();
186
+ });
187
+ });
188
+
189
+ await step('The label survives the loading swap (no reflow)', async () => {
190
+ await expect(canvas.getByTestId('kpi-revenue-label')).toHaveTextContent('Receita');
191
+ });
192
+ },
193
+ };
194
+
195
+ export const ResponsiveDesign: Story = {
196
+ name: '🧪 Responsive Design — tiles stack in a narrow grid',
197
+ parameters: { viewport: { defaultViewport: 'mobile1' } },
198
+ render: () => (
199
+ <Box
200
+ sx={{
201
+ display: 'grid',
202
+ gap: 2,
203
+ gridTemplateColumns: { xs: '1fr', md: 'repeat(2, 1fr)' },
204
+ }}
205
+ >
206
+ <StatCard label="Receita" value="R$ 12.480,00" dataTestId="kpi-revenue" />
207
+ <StatCard label="Pedidos" value="184" dataTestId="kpi-orders" />
208
+ </Box>
209
+ ),
210
+ play: async ({ canvasElement, step }) => {
211
+ const canvas = within(canvasElement);
212
+
213
+ await step('Both tiles render inside the grid', async () => {
214
+ await expect(canvas.getByTestId('kpi-revenue')).toBeVisible();
215
+ await expect(canvas.getByTestId('kpi-orders')).toBeVisible();
216
+ });
217
+ },
218
+ };
219
+
220
+ export const IntegrationHintAndIcon: Story = {
221
+ name: '🧪 Integration — icon, hint and delta compose',
222
+ args: {
223
+ label: 'Margem',
224
+ value: '62,0%',
225
+ hint: 'Últimos 30 dias',
226
+ icon: <TrendingUpIcon fontSize="small" />,
227
+ delta: { value: '+1,4 p.p.', direction: 'up', label: 'vs. período anterior' },
228
+ dataTestId: 'kpi-margin',
229
+ },
230
+ play: async ({ canvasElement, step }) => {
231
+ const canvas = within(canvasElement);
232
+
233
+ await step('Every optional slot renders together', async () => {
234
+ await expect(canvas.getByTestId('kpi-margin-icon')).toBeInTheDocument();
235
+ await expect(canvas.getByTestId('kpi-margin-hint')).toHaveTextContent('Últimos 30 dias');
236
+ await expect(canvas.getByTestId('kpi-margin-delta')).toHaveAttribute('data-direction', 'up');
237
+ await expect(canvas.getByTestId('kpi-margin-value')).toHaveTextContent('62,0%');
238
+ });
239
+ },
240
+ };
@@ -0,0 +1,236 @@
1
+ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
2
+ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
3
+ import RemoveIcon from '@mui/icons-material/Remove';
4
+ import { Box, Stack, Typography, useTheme, type Theme } from '@mui/material';
5
+ import React from 'react';
6
+
7
+ import { Skeleton } from '../../layout/Skeleton/Skeleton';
8
+
9
+ import type {
10
+ StatCardDelta,
11
+ StatCardDeltaDirection,
12
+ StatCardDeltaTone,
13
+ StatCardProps,
14
+ } from './StatCard.types';
15
+
16
+ /** The arrow each direction renders (decorative — the text carries the meaning). */
17
+ const DIRECTION_ICON: Record<StatCardDeltaDirection, typeof ArrowUpwardIcon> = {
18
+ up: ArrowUpwardIcon,
19
+ down: ArrowDownwardIcon,
20
+ neutral: RemoveIcon,
21
+ };
22
+
23
+ /** How each direction reads to a screen reader when no `ariaLabel` is given. */
24
+ const DIRECTION_WORD: Record<StatCardDeltaDirection, string> = {
25
+ up: 'Increase of',
26
+ down: 'Decrease of',
27
+ neutral: 'Change of',
28
+ };
29
+
30
+ /** A direction's natural tone, used when the caller doesn't override it. */
31
+ const IMPLIED_TONE: Record<StatCardDeltaDirection, StatCardDeltaTone> = {
32
+ up: 'positive',
33
+ down: 'negative',
34
+ neutral: 'neutral',
35
+ };
36
+
37
+ /** Resolve a tone to its semantic palette colour (never a hardcoded hex). */
38
+ function toneColor(tone: StatCardDeltaTone, theme: Theme): string {
39
+ if (tone === 'positive') return theme.palette.success.main;
40
+ if (tone === 'negative') return theme.palette.error.main;
41
+ return theme.palette.text.secondary;
42
+ }
43
+
44
+ /** The screen-reader sentence for a delta: caller override, else generated. */
45
+ function deltaAriaLabel(delta: StatCardDelta): string {
46
+ if (delta.ariaLabel) return delta.ariaLabel;
47
+ const suffix = delta.label ? ` ${delta.label}` : '';
48
+ return `${DIRECTION_WORD[delta.direction]} ${delta.value}${suffix}`;
49
+ }
50
+
51
+ /** The delta row: a decorative arrow plus the pre-formatted change and context. */
52
+ function DeltaRow({ delta, testId }: { delta: StatCardDelta; testId: string }): React.JSX.Element {
53
+ const theme = useTheme();
54
+ const tone = delta.tone ?? IMPLIED_TONE[delta.direction];
55
+ const Icon = DIRECTION_ICON[delta.direction];
56
+ const color = toneColor(tone, theme);
57
+
58
+ return (
59
+ <Stack
60
+ direction="row"
61
+ spacing={0.5}
62
+ sx={{ alignItems: 'center', flexWrap: 'wrap' }}
63
+ data-testid={testId}
64
+ data-direction={delta.direction}
65
+ data-tone={tone}
66
+ >
67
+ {/* The arrow duplicates what the aria-label already says, so it is hidden
68
+ from assistive tech rather than read twice. */}
69
+ <Icon aria-hidden="true" sx={{ fontSize: '1rem', color }} />
70
+ <Typography
71
+ variant="body2"
72
+ component="span"
73
+ aria-label={deltaAriaLabel(delta)}
74
+ sx={{ color, fontWeight: theme.typography.fontWeightMedium }}
75
+ >
76
+ {delta.value}
77
+ </Typography>
78
+ {delta.label && (
79
+ <Typography variant="caption" component="span" sx={{ color: theme.palette.text.secondary }}>
80
+ {delta.label}
81
+ </Typography>
82
+ )}
83
+ </Stack>
84
+ );
85
+ }
86
+
87
+ /** The label row: the optional decorative icon plus the metric's name. */
88
+ function LabelRow({
89
+ label,
90
+ icon,
91
+ labelId,
92
+ testId,
93
+ }: Pick<StatCardProps, 'label' | 'icon'> & {
94
+ labelId: string;
95
+ testId: string;
96
+ }): React.JSX.Element {
97
+ const theme = useTheme();
98
+ return (
99
+ <Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
100
+ {icon && (
101
+ <Box
102
+ aria-hidden="true"
103
+ data-testid={`${testId}-icon`}
104
+ sx={{ display: 'flex', color: theme.palette.text.secondary }}
105
+ >
106
+ {icon}
107
+ </Box>
108
+ )}
109
+ <Typography
110
+ id={labelId}
111
+ variant="caption"
112
+ component="p"
113
+ data-testid={`${testId}-label`}
114
+ sx={{ color: theme.palette.text.secondary }}
115
+ >
116
+ {label}
117
+ </Typography>
118
+ </Stack>
119
+ );
120
+ }
121
+
122
+ /** The figure itself, or its skeleton while the tile is loading. */
123
+ function ValueRow({
124
+ value,
125
+ loading,
126
+ testId,
127
+ }: Pick<StatCardProps, 'value' | 'loading'> & { testId: string }): React.JSX.Element {
128
+ const theme = useTheme();
129
+ if (loading) {
130
+ return (
131
+ <Skeleton
132
+ variant="rectangular"
133
+ height={28}
134
+ width="60%"
135
+ borderRadius={4}
136
+ data-testid={`${testId}-value-skeleton`}
137
+ />
138
+ );
139
+ }
140
+ return (
141
+ <Typography
142
+ variant="h6"
143
+ component="p"
144
+ data-testid={`${testId}-value`}
145
+ sx={{
146
+ fontWeight: theme.typography.fontWeightMedium,
147
+ color: theme.palette.text.primary,
148
+ overflowWrap: 'anywhere',
149
+ }}
150
+ >
151
+ {value}
152
+ </Typography>
153
+ );
154
+ }
155
+
156
+ /** The delta slot: its skeleton while loading, the row once resolved, else nothing. */
157
+ function DeltaSlot({
158
+ delta,
159
+ loading,
160
+ testId,
161
+ }: {
162
+ delta?: StatCardDelta;
163
+ loading: boolean;
164
+ testId: string;
165
+ }): React.JSX.Element | null {
166
+ if (!delta) return null;
167
+ if (loading) {
168
+ return (
169
+ <Skeleton
170
+ variant="rectangular"
171
+ height={16}
172
+ width="40%"
173
+ borderRadius={4}
174
+ data-testid={`${testId}-delta-skeleton`}
175
+ />
176
+ );
177
+ }
178
+ return <DeltaRow delta={delta} testId={`${testId}-delta`} />;
179
+ }
180
+
181
+ /**
182
+ * StatCard (KPI tile) — one labelled figure with an optional period-over-period
183
+ * delta, leading icon, hint line and loading state. The single shared tile for
184
+ * dashboards and summary bands, so surfaces stop hand-rolling the pattern.
185
+ *
186
+ * The tile is presentation only: `value` and `delta.value` arrive ALREADY
187
+ * formatted, so locale/currency rules stay with the data owner. Colour comes
188
+ * from the MUI palette (`success` / `error` / `text.secondary`), and the delta's
189
+ * meaning is separable from its arrow via {@link StatCardDelta.tone} — a rise in
190
+ * cost can render "up" while reading as negative.
191
+ */
192
+ export const StatCard: React.FC<StatCardProps> = React.memo(
193
+ ({ label, value, delta, icon, hint, loading = false, className, dataTestId }) => {
194
+ const theme = useTheme();
195
+ const labelId = React.useId();
196
+ const testId = dataTestId || 'stat-card';
197
+
198
+ return (
199
+ <Box
200
+ role="group"
201
+ aria-labelledby={labelId}
202
+ aria-busy={loading || undefined}
203
+ className={className}
204
+ data-testid={testId}
205
+ sx={{
206
+ display: 'flex',
207
+ flexDirection: 'column',
208
+ gap: theme.spacing(0.5),
209
+ padding: theme.spacing(2),
210
+ borderRadius: theme.shape.borderRadius / 4,
211
+ border: `1px solid ${theme.palette.divider}`,
212
+ backgroundColor: theme.palette.background.paper,
213
+ minWidth: 0,
214
+ }}
215
+ >
216
+ <LabelRow label={label} icon={icon} labelId={labelId} testId={testId} />
217
+ <ValueRow value={value} loading={loading} testId={testId} />
218
+ <DeltaSlot delta={delta} loading={loading} testId={testId} />
219
+ {hint && (
220
+ <Typography
221
+ variant="caption"
222
+ component="p"
223
+ data-testid={`${testId}-hint`}
224
+ sx={{ color: theme.palette.text.secondary }}
225
+ >
226
+ {hint}
227
+ </Typography>
228
+ )}
229
+ </Box>
230
+ );
231
+ },
232
+ );
233
+
234
+ StatCard.displayName = 'StatCard';
235
+
236
+ export default StatCard;
@@ -0,0 +1,62 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ /** Which way a metric moved over the comparison window. */
4
+ export type StatCardDeltaDirection = 'up' | 'down' | 'neutral';
5
+
6
+ /**
7
+ * How the movement should READ to the user. Direction and meaning are separate
8
+ * concerns: revenue going up is good, cost going up is bad — so a consumer can
9
+ * override the tone without lying about the arrow.
10
+ */
11
+ export type StatCardDeltaTone = 'positive' | 'negative' | 'neutral';
12
+
13
+ /** The optional period-over-period change shown beneath a stat's value. */
14
+ export interface StatCardDelta {
15
+ /**
16
+ * Pre-formatted change, e.g. `"+12,5%"` or `"-R$ 320,00"`. Formatting
17
+ * (locale, currency, precision) belongs to the caller, exactly as {@link
18
+ * StatCardProps.value} does.
19
+ */
20
+ value: string;
21
+ /** The arrow/geometry: which way the metric moved. */
22
+ direction: StatCardDeltaDirection;
23
+ /**
24
+ * The semantic colour. Defaults to the direction's natural reading
25
+ * (`up` → positive, `down` → negative, `neutral` → neutral); pass it
26
+ * explicitly for "lower is better" metrics such as cost.
27
+ */
28
+ tone?: StatCardDeltaTone;
29
+ /** Optional context for the comparison, e.g. `"vs. período anterior"`. */
30
+ label?: string;
31
+ /**
32
+ * Overrides the generated screen-reader sentence (which is English by
33
+ * default) — pass a localized string when the surrounding app is not English.
34
+ */
35
+ ariaLabel?: string;
36
+ }
37
+
38
+ /** Props for the {@link StatCard} KPI tile. */
39
+ export interface StatCardProps {
40
+ /** What the figure measures, e.g. `"Receita"`. */
41
+ label: string;
42
+ /**
43
+ * The figure itself, ALREADY formatted (`"R$ 1.250,00"`, `"60,0%"`, `12`).
44
+ * The tile never formats: money/percent/locale rules live with the caller.
45
+ */
46
+ value: ReactNode;
47
+ /** Optional period-over-period change rendered under the value. */
48
+ delta?: StatCardDelta;
49
+ /** Optional leading icon rendered beside the label. */
50
+ icon?: ReactNode;
51
+ /** Optional clarifying line under the value, e.g. the range being measured. */
52
+ hint?: string;
53
+ /**
54
+ * While true the value/delta are replaced by skeletons and the tile is
55
+ * announced as busy — the label stays, so the grid never reflows on load.
56
+ */
57
+ loading?: boolean;
58
+ /** Custom className for the tile root. */
59
+ className?: string;
60
+ /** Test id for the tile root; child test ids are derived from it. */
61
+ dataTestId?: string;
62
+ }