@carto/ps-react-ui 4.3.3 → 4.3.5

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 (301) hide show
  1. package/dist/components.js +3 -3
  2. package/dist/components.js.map +1 -1
  3. package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
  4. package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
  5. package/dist/types/components/common-types.d.ts +41 -0
  6. package/dist/types/components/types.d.ts +1 -1
  7. package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
  8. package/dist/types/widgets/echart/types.d.ts +4 -0
  9. package/dist/widgets/actions.js +1 -1
  10. package/dist/widgets/bar.js +1 -1
  11. package/dist/widgets/category.js +1 -1
  12. package/dist/widgets/echart.js +96 -85
  13. package/dist/widgets/echart.js.map +1 -1
  14. package/dist/widgets/formula.js +1 -1
  15. package/dist/widgets/histogram.js +1 -1
  16. package/dist/widgets/markdown.js +1 -1
  17. package/dist/widgets/pie.js +1 -1
  18. package/dist/widgets/scatterplot.js +1 -1
  19. package/dist/widgets/spread.js +1 -1
  20. package/dist/widgets/table.js +1 -1
  21. package/dist/widgets/timeseries.js +1 -1
  22. package/dist/widgets/toolbar-actions.js.map +1 -1
  23. package/dist/widgets/wrapper.js +1 -1
  24. package/package.json +8 -3
  25. package/src/components/basemaps/basemaps.test.tsx +196 -0
  26. package/src/components/basemaps/basemaps.tsx +128 -0
  27. package/src/components/basemaps/const.ts +13 -0
  28. package/src/components/basemaps/group-wrapper.test.tsx +38 -0
  29. package/src/components/basemaps/group-wrapper.tsx +28 -0
  30. package/src/components/basemaps/group.test.tsx +52 -0
  31. package/src/components/basemaps/group.tsx +42 -0
  32. package/src/components/basemaps/header.test.tsx +54 -0
  33. package/src/components/basemaps/header.tsx +36 -0
  34. package/src/components/basemaps/styles.ts +76 -0
  35. package/src/components/basemaps/types.ts +30 -0
  36. package/src/components/common-types.ts +1 -0
  37. package/src/components/geolocation-controls/const.ts +6 -0
  38. package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
  39. package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
  40. package/src/components/geolocation-controls/types.ts +17 -0
  41. package/src/components/index.ts +64 -0
  42. package/src/components/lasso-tool/chip.tsx +37 -0
  43. package/src/components/lasso-tool/const.tsx +70 -0
  44. package/src/components/lasso-tool/icons.tsx +91 -0
  45. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
  46. package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
  47. package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
  48. package/src/components/lasso-tool/lasso-tool.tsx +479 -0
  49. package/src/components/lasso-tool/styles.ts +143 -0
  50. package/src/components/lasso-tool/types.ts +114 -0
  51. package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
  52. package/src/components/list-data/list-data-skeleton.tsx +40 -0
  53. package/src/components/list-data/list-data.test.tsx +94 -0
  54. package/src/components/list-data/list-data.tsx +106 -0
  55. package/src/components/list-data/styles.ts +37 -0
  56. package/src/components/list-data/types.ts +25 -0
  57. package/src/components/measurement-tools/const.tsx +108 -0
  58. package/src/components/measurement-tools/icons.tsx +54 -0
  59. package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
  60. package/src/components/measurement-tools/measurement-tools.tsx +443 -0
  61. package/src/components/measurement-tools/styles.ts +91 -0
  62. package/src/components/measurement-tools/types.ts +77 -0
  63. package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
  64. package/src/components/no-data-alert/no-data-alert.tsx +59 -0
  65. package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
  66. package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
  67. package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
  68. package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
  69. package/src/components/tooltip/tooltip.test.tsx +86 -0
  70. package/src/components/tooltip/tooltip.tsx +30 -0
  71. package/src/components/types.ts +1 -0
  72. package/src/components/zoom-controls/styles.ts +27 -0
  73. package/src/components/zoom-controls/types.ts +19 -0
  74. package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
  75. package/src/components/zoom-controls/zoom-controls.tsx +114 -0
  76. package/src/hooks/index.ts +2 -0
  77. package/src/hooks/use-debounce.ts +55 -0
  78. package/src/hooks/use-skeleton.test.tsx +32 -0
  79. package/src/hooks/use-skeleton.ts +19 -0
  80. package/src/hooks/use-widget-ref.ts +33 -0
  81. package/src/widgets/README.md +277 -0
  82. package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
  83. package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
  84. package/src/widgets/_shared/chart-config/index.ts +21 -0
  85. package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
  86. package/src/widgets/_shared/skeleton/index.ts +5 -0
  87. package/src/widgets/_shared/skeleton/styles.ts +20 -0
  88. package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
  89. package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
  90. package/src/widgets/actions/change-column/change-column.tsx +141 -0
  91. package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
  92. package/src/widgets/actions/change-column/types.ts +20 -0
  93. package/src/widgets/actions/download/download.test.tsx +322 -0
  94. package/src/widgets/actions/download/download.tsx +118 -0
  95. package/src/widgets/actions/download/exports.test.tsx +275 -0
  96. package/src/widgets/actions/download/exports.tsx +103 -0
  97. package/src/widgets/actions/download/types.ts +21 -0
  98. package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
  99. package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
  100. package/src/widgets/actions/fullscreen/styles.ts +17 -0
  101. package/src/widgets/actions/fullscreen/types.ts +27 -0
  102. package/src/widgets/actions/index.ts +51 -0
  103. package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
  104. package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
  105. package/src/widgets/actions/lock-selection/types.ts +41 -0
  106. package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
  107. package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
  108. package/src/widgets/actions/relative-data/style.ts +9 -0
  109. package/src/widgets/actions/relative-data/types.ts +31 -0
  110. package/src/widgets/actions/relative-data/utils.test.ts +223 -0
  111. package/src/widgets/actions/relative-data/utils.ts +58 -0
  112. package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
  113. package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
  114. package/src/widgets/actions/searcher/searcher.tsx +205 -0
  115. package/src/widgets/actions/searcher/types.ts +72 -0
  116. package/src/widgets/actions/shared/styles.ts +12 -0
  117. package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
  118. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
  119. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
  120. package/src/widgets/actions/stack-toggle/types.ts +29 -0
  121. package/src/widgets/actions/zoom-toggle/index.ts +2 -0
  122. package/src/widgets/actions/zoom-toggle/style.ts +14 -0
  123. package/src/widgets/actions/zoom-toggle/types.ts +44 -0
  124. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
  125. package/src/widgets/bar/config.ts +122 -0
  126. package/src/widgets/bar/index.ts +9 -0
  127. package/src/widgets/bar/skeleton.tsx +60 -0
  128. package/src/widgets/bar/style.ts +33 -0
  129. package/src/widgets/bar/types.ts +16 -0
  130. package/src/widgets/category/category-ui.test.tsx +399 -0
  131. package/src/widgets/category/category-ui.tsx +156 -0
  132. package/src/widgets/category/components/category-bar.tsx +28 -0
  133. package/src/widgets/category/components/category-legend.tsx +30 -0
  134. package/src/widgets/category/components/category-row-multi.tsx +50 -0
  135. package/src/widgets/category/components/category-row-other.tsx +23 -0
  136. package/src/widgets/category/components/category-row-single.tsx +47 -0
  137. package/src/widgets/category/components/index.ts +14 -0
  138. package/src/widgets/category/config.ts +85 -0
  139. package/src/widgets/category/index.ts +30 -0
  140. package/src/widgets/category/skeleton.tsx +24 -0
  141. package/src/widgets/category/style.ts +133 -0
  142. package/src/widgets/category/types.ts +40 -0
  143. package/src/widgets/echart/const.ts +1 -0
  144. package/src/widgets/echart/echart-ui.test.tsx +537 -0
  145. package/src/widgets/echart/echart-ui.tsx +92 -0
  146. package/src/widgets/echart/echart.test.tsx +562 -0
  147. package/src/widgets/echart/echart.tsx +68 -0
  148. package/src/widgets/echart/index.ts +16 -0
  149. package/src/widgets/echart/options.ts +53 -0
  150. package/src/widgets/echart/types.ts +45 -0
  151. package/src/widgets/echart/utils.ts +169 -0
  152. package/src/widgets/error/error.test.tsx +331 -0
  153. package/src/widgets/error/error.tsx +40 -0
  154. package/src/widgets/error/index.ts +2 -0
  155. package/src/widgets/error/types.ts +14 -0
  156. package/src/widgets/formula/components/item.test.tsx +249 -0
  157. package/src/widgets/formula/components/item.tsx +18 -0
  158. package/src/widgets/formula/components/prefix.test.tsx +341 -0
  159. package/src/widgets/formula/components/prefix.tsx +18 -0
  160. package/src/widgets/formula/components/row.test.tsx +364 -0
  161. package/src/widgets/formula/components/row.tsx +21 -0
  162. package/src/widgets/formula/components/series.tsx +34 -0
  163. package/src/widgets/formula/components/suffix.test.tsx +383 -0
  164. package/src/widgets/formula/components/suffix.tsx +28 -0
  165. package/src/widgets/formula/components/value.test.tsx +329 -0
  166. package/src/widgets/formula/components/value.tsx +29 -0
  167. package/src/widgets/formula/config.ts +27 -0
  168. package/src/widgets/formula/formula-ui.test.tsx +399 -0
  169. package/src/widgets/formula/formula-ui.tsx +27 -0
  170. package/src/widgets/formula/index.ts +24 -0
  171. package/src/widgets/formula/serializer.test.tsx +144 -0
  172. package/src/widgets/formula/serializer.ts +28 -0
  173. package/src/widgets/formula/skeleton.tsx +10 -0
  174. package/src/widgets/formula/style.ts +23 -0
  175. package/src/widgets/formula/types.ts +50 -0
  176. package/src/widgets/histogram/config.ts +143 -0
  177. package/src/widgets/histogram/index.ts +8 -0
  178. package/src/widgets/histogram/skeleton.tsx +52 -0
  179. package/src/widgets/histogram/style.ts +8 -0
  180. package/src/widgets/histogram/types.ts +17 -0
  181. package/src/widgets/index.ts +25 -0
  182. package/src/widgets/loader/index.ts +4 -0
  183. package/src/widgets/loader/loader.tsx +70 -0
  184. package/src/widgets/loader/types.ts +11 -0
  185. package/src/widgets/loader/utils.test.ts +112 -0
  186. package/src/widgets/loader/utils.ts +35 -0
  187. package/src/widgets/markdown/config.ts +18 -0
  188. package/src/widgets/markdown/index.ts +14 -0
  189. package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
  190. package/src/widgets/markdown/markdown-ui.tsx +52 -0
  191. package/src/widgets/markdown/markdown.tsx +20 -0
  192. package/src/widgets/markdown/skeleton.tsx +12 -0
  193. package/src/widgets/markdown/style.ts +28 -0
  194. package/src/widgets/markdown/types.ts +28 -0
  195. package/src/widgets/no-data/index.ts +2 -0
  196. package/src/widgets/no-data/no-data.test.tsx +447 -0
  197. package/src/widgets/no-data/no-data.tsx +116 -0
  198. package/src/widgets/no-data/style.ts +18 -0
  199. package/src/widgets/no-data/types.ts +72 -0
  200. package/src/widgets/note/index.ts +2 -0
  201. package/src/widgets/note/note.test.tsx +391 -0
  202. package/src/widgets/note/note.tsx +114 -0
  203. package/src/widgets/note/style.ts +29 -0
  204. package/src/widgets/note/types.ts +9 -0
  205. package/src/widgets/pie/config.ts +177 -0
  206. package/src/widgets/pie/index.ts +8 -0
  207. package/src/widgets/pie/skeleton.tsx +70 -0
  208. package/src/widgets/pie/style.ts +8 -0
  209. package/src/widgets/pie/types.ts +16 -0
  210. package/src/widgets/range/components/range-item.tsx +213 -0
  211. package/src/widgets/range/config.ts +10 -0
  212. package/src/widgets/range/index.ts +16 -0
  213. package/src/widgets/range/range-ui.test.tsx +203 -0
  214. package/src/widgets/range/range-ui.tsx +11 -0
  215. package/src/widgets/range/serializer.test.ts +70 -0
  216. package/src/widgets/range/serializer.ts +27 -0
  217. package/src/widgets/range/skeleton.tsx +14 -0
  218. package/src/widgets/range/style.ts +37 -0
  219. package/src/widgets/range/types.ts +39 -0
  220. package/src/widgets/scatterplot/config.ts +138 -0
  221. package/src/widgets/scatterplot/index.ts +8 -0
  222. package/src/widgets/scatterplot/skeleton.tsx +59 -0
  223. package/src/widgets/scatterplot/style.ts +21 -0
  224. package/src/widgets/scatterplot/types.ts +17 -0
  225. package/src/widgets/selection-summary/index.ts +6 -0
  226. package/src/widgets/selection-summary/selection-summary.tsx +46 -0
  227. package/src/widgets/selection-summary/style.ts +10 -0
  228. package/src/widgets/selection-summary/types.ts +14 -0
  229. package/src/widgets/skeleton-loader/index.ts +2 -0
  230. package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
  231. package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
  232. package/src/widgets/skeleton-loader/types.ts +8 -0
  233. package/src/widgets/spread/components/max-value.tsx +29 -0
  234. package/src/widgets/spread/components/min-value.tsx +29 -0
  235. package/src/widgets/spread/components/separator.tsx +6 -0
  236. package/src/widgets/spread/config.ts +34 -0
  237. package/src/widgets/spread/index.ts +23 -0
  238. package/src/widgets/spread/skeleton.tsx +10 -0
  239. package/src/widgets/spread/spread-ui.test.tsx +368 -0
  240. package/src/widgets/spread/spread-ui.tsx +29 -0
  241. package/src/widgets/spread/style.ts +22 -0
  242. package/src/widgets/spread/types.ts +47 -0
  243. package/src/widgets/stores/index.ts +9 -0
  244. package/src/widgets/stores/types.ts +192 -0
  245. package/src/widgets/stores/widget-store.test.ts +601 -0
  246. package/src/widgets/stores/widget-store.ts +239 -0
  247. package/src/widgets/subheader/index.ts +3 -0
  248. package/src/widgets/subheader/style.ts +20 -0
  249. package/src/widgets/subheader/subheader.test.tsx +45 -0
  250. package/src/widgets/subheader/subheader.tsx +16 -0
  251. package/src/widgets/subheader/types.ts +11 -0
  252. package/src/widgets/table/components/cell-header.tsx +58 -0
  253. package/src/widgets/table/components/cell.tsx +80 -0
  254. package/src/widgets/table/components/index.ts +4 -0
  255. package/src/widgets/table/components/pagination-actions.tsx +67 -0
  256. package/src/widgets/table/components/pagination.tsx +41 -0
  257. package/src/widgets/table/components/row.tsx +60 -0
  258. package/src/widgets/table/config.ts +71 -0
  259. package/src/widgets/table/helpers.test.ts +244 -0
  260. package/src/widgets/table/helpers.ts +107 -0
  261. package/src/widgets/table/hooks/index.ts +7 -0
  262. package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
  263. package/src/widgets/table/hooks/use-pagination.ts +155 -0
  264. package/src/widgets/table/hooks/use-selection.test.ts +504 -0
  265. package/src/widgets/table/hooks/use-selection.ts +189 -0
  266. package/src/widgets/table/hooks/use-sort.test.ts +296 -0
  267. package/src/widgets/table/hooks/use-sort.ts +138 -0
  268. package/src/widgets/table/index.ts +53 -0
  269. package/src/widgets/table/serializer.ts +54 -0
  270. package/src/widgets/table/skeleton.tsx +48 -0
  271. package/src/widgets/table/style.ts +34 -0
  272. package/src/widgets/table/table-ui.tsx +64 -0
  273. package/src/widgets/table/types.ts +223 -0
  274. package/src/widgets/timeseries/config.ts +135 -0
  275. package/src/widgets/timeseries/index.ts +8 -0
  276. package/src/widgets/timeseries/skeleton.tsx +55 -0
  277. package/src/widgets/timeseries/style.ts +36 -0
  278. package/src/widgets/timeseries/types.ts +17 -0
  279. package/src/widgets/toolbar-actions/index.ts +6 -0
  280. package/src/widgets/toolbar-actions/styles.ts +38 -0
  281. package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
  282. package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
  283. package/src/widgets/toolbar-actions/types.ts +60 -0
  284. package/src/widgets/wrapper/components/actions.test.tsx +101 -0
  285. package/src/widgets/wrapper/components/actions.tsx +30 -0
  286. package/src/widgets/wrapper/components/options.test.tsx +323 -0
  287. package/src/widgets/wrapper/components/options.tsx +73 -0
  288. package/src/widgets/wrapper/components/title.test.tsx +126 -0
  289. package/src/widgets/wrapper/components/title.tsx +32 -0
  290. package/src/widgets/wrapper/index.ts +16 -0
  291. package/src/widgets/wrapper/styles.ts +98 -0
  292. package/src/widgets/wrapper/types.ts +55 -0
  293. package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
  294. package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
  295. package/src/widgets/wrapper/wrapper.test.tsx +365 -0
  296. package/src/widgets/wrapper/wrapper.tsx +50 -0
  297. package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
  298. package/dist/types/common/common.d.ts +0 -3
  299. package/dist/types/common/index.d.ts +0 -26
  300. package/dist/types/common/lasso-tools.d.ts +0 -36
  301. package/dist/types/common/measurement-tools.d.ts +0 -65
@@ -0,0 +1,73 @@
1
+ import { MoreVert } from '@mui/icons-material'
2
+ import {
3
+ IconButton,
4
+ Menu,
5
+ MenuItem,
6
+ ListItemIcon,
7
+ ListItemText,
8
+ } from '@mui/material'
9
+ import type { WrapperOptionsProps } from '../types'
10
+ import { useState } from 'react'
11
+ import { styles } from '../styles'
12
+
13
+ export function Options({ labels, options = [] }: WrapperOptionsProps) {
14
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
15
+
16
+ const handleOptionAction = (
17
+ e: React.MouseEvent<HTMLElement>,
18
+ option: NonNullable<WrapperOptionsProps['options']>[number],
19
+ ) => {
20
+ e.stopPropagation()
21
+ option.onClick()
22
+ setAnchorEl(null)
23
+ }
24
+
25
+ return (
26
+ <>
27
+ <IconButton
28
+ sx={styles.options.trigger}
29
+ size='small'
30
+ aria-label={labels?.title ?? 'Options'}
31
+ aria-controls='options-menu'
32
+ aria-haspopup='true'
33
+ onClick={(e) => {
34
+ e.stopPropagation()
35
+ setAnchorEl(e.currentTarget)
36
+ }}
37
+ >
38
+ <MoreVert />
39
+ </IconButton>
40
+ <Menu
41
+ variant='menu'
42
+ elevation={8}
43
+ anchorOrigin={{
44
+ vertical: 'top',
45
+ horizontal: 'right',
46
+ }}
47
+ transformOrigin={{
48
+ vertical: 'top',
49
+ horizontal: 'right',
50
+ }}
51
+ anchorEl={anchorEl}
52
+ open={Boolean(anchorEl)}
53
+ onClose={() => setAnchorEl(null)}
54
+ MenuListProps={{
55
+ sx: {
56
+ paddingBottom: 0,
57
+ },
58
+ }}
59
+ >
60
+ {options.map((option, idx) => (
61
+ <MenuItem
62
+ key={`${option.label}-${idx}`}
63
+ disabled={option.disabled}
64
+ onClick={(e) => handleOptionAction(e, option)}
65
+ >
66
+ {option.icon && <ListItemIcon>{option.icon}</ListItemIcon>}
67
+ <ListItemText>{option.label}</ListItemText>
68
+ </MenuItem>
69
+ ))}
70
+ </Menu>
71
+ </>
72
+ )
73
+ }
@@ -0,0 +1,126 @@
1
+ import { describe, test, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { Title } from './title'
4
+
5
+ describe('Title', () => {
6
+ test('renders with string children', () => {
7
+ render(<Title>Test Title</Title>)
8
+ expect(screen.getByText('Test Title')).toBeTruthy()
9
+ })
10
+
11
+ test('renders with ReactNode children', () => {
12
+ render(
13
+ <Title>
14
+ <span>Complex</span> <strong>Title</strong>
15
+ </Title>,
16
+ )
17
+ expect(screen.getByText('Complex')).toBeTruthy()
18
+ expect(screen.getByText('Title')).toBeTruthy()
19
+ })
20
+
21
+ test('renders with custom label', () => {
22
+ render(<Title label='Custom Label'>Display Text</Title>)
23
+ // The component should render the children
24
+ expect(screen.getByText('Display Text')).toBeTruthy()
25
+ })
26
+
27
+ test('uses children as tooltip when children is string and no label', () => {
28
+ render(<Title>String Title</Title>)
29
+ expect(screen.getByText('String Title')).toBeTruthy()
30
+ })
31
+
32
+ test('uses empty string tooltip when children is not string and no label', () => {
33
+ render(
34
+ <Title>
35
+ <div>Complex Title</div>
36
+ </Title>,
37
+ )
38
+ expect(screen.getByText('Complex Title')).toBeTruthy()
39
+ })
40
+
41
+ test('applies correct typography variant', () => {
42
+ render(<Title>Test Title</Title>)
43
+ const typography = screen.getByText('Test Title')
44
+ expect(typography.tagName).toBe('H6') // subtitle1 renders as h6
45
+ })
46
+
47
+ test('wraps content in SmartTooltip', () => {
48
+ render(<Title>Tooltip Title</Title>)
49
+ expect(screen.getByText('Tooltip Title')).toBeTruthy()
50
+ })
51
+
52
+ test('handles empty string children', () => {
53
+ render(<Title>{''}</Title>)
54
+ // Should render but be empty
55
+ const { container } = render(<Title>{''}</Title>)
56
+ expect(container).toBeTruthy()
57
+ })
58
+
59
+ test('handles numeric children', () => {
60
+ render(<Title>{123}</Title>)
61
+ expect(screen.getByText('123')).toBeTruthy()
62
+ })
63
+
64
+ test('renders with multiple children', () => {
65
+ render(
66
+ <Title>
67
+ <span>Part 1</span>
68
+ <span>Part 2</span>
69
+ <span>Part 3</span>
70
+ </Title>,
71
+ )
72
+ expect(screen.getByText('Part 1')).toBeTruthy()
73
+ expect(screen.getByText('Part 2')).toBeTruthy()
74
+ expect(screen.getByText('Part 3')).toBeTruthy()
75
+ })
76
+
77
+ test('prefers label over string children for tooltip', () => {
78
+ render(<Title label='Tooltip Text'>Display Text</Title>)
79
+ expect(screen.getByText('Display Text')).toBeTruthy()
80
+ })
81
+
82
+ test('renders with undefined label and string children', () => {
83
+ render(<Title label={undefined}>String Children</Title>)
84
+ expect(screen.getByText('String Children')).toBeTruthy()
85
+ })
86
+
87
+ test('handles complex nested structure', () => {
88
+ render(
89
+ <Title>
90
+ <div>
91
+ <span>Nested</span>
92
+ <div>Content</div>
93
+ </div>
94
+ </Title>,
95
+ )
96
+ expect(screen.getByText('Nested')).toBeTruthy()
97
+ expect(screen.getByText('Content')).toBeTruthy()
98
+ })
99
+
100
+ test('ref is passed to Typography component', () => {
101
+ const { container } = render(<Title>Title with Ref</Title>)
102
+ expect(screen.getByText('Title with Ref')).toBeTruthy()
103
+ // The Typography component should be in the DOM
104
+ const typography = container.querySelector('[class*="MuiTypography"]')
105
+ expect(typography).toBeTruthy()
106
+ })
107
+
108
+ test('renders with textTransform none', () => {
109
+ render(<Title>Title Text</Title>)
110
+ expect(screen.getByText('Title Text')).toBeTruthy()
111
+ })
112
+
113
+ test('handles boolean children', () => {
114
+ render(<Title>{true}</Title>)
115
+ // Boolean children don't render text
116
+ const { container } = render(<Title>{false}</Title>)
117
+ expect(container).toBeTruthy()
118
+ })
119
+
120
+ test('handles null children', () => {
121
+ render(<Title>{null}</Title>)
122
+ // Null children don't render
123
+ const { container } = render(<Title>{null}</Title>)
124
+ expect(container).toBeTruthy()
125
+ })
126
+ })
@@ -0,0 +1,32 @@
1
+ import { Box, Typography } from '@mui/material'
2
+ import { styles } from '../styles'
3
+ import { SmartTooltip } from '../../../components'
4
+ import type { ReactNode } from 'react'
5
+ import type { WrapperOptionsProps } from '../types'
6
+
7
+ export function Title({
8
+ label,
9
+ children,
10
+ }: {
11
+ children: ReactNode
12
+ label?: NonNullable<WrapperOptionsProps['labels']>['title']
13
+ }) {
14
+ return (
15
+ <SmartTooltip
16
+ title={label ?? (typeof children === 'string' ? children : '')}
17
+ >
18
+ {({ ref }) => (
19
+ <Box sx={styles.title.container}>
20
+ <Typography
21
+ sx={styles.title.text}
22
+ ref={ref}
23
+ variant='subtitle1'
24
+ textTransform='none'
25
+ >
26
+ {children}
27
+ </Typography>
28
+ </Box>
29
+ )}
30
+ </SmartTooltip>
31
+ )
32
+ }
@@ -0,0 +1,16 @@
1
+ // Subcomponents for advanced stories
2
+ export { Actions } from './components/actions'
3
+ export { Options } from './components/options'
4
+ export { Title } from './components/title'
5
+
6
+ export { WidgetWrapper } from './wrapper'
7
+ export { WrapperUI } from './wrapper-ui'
8
+
9
+ export type {
10
+ WrapperActionsProps,
11
+ WrapperOption,
12
+ WrapperOptionsProps,
13
+ WrapperProps,
14
+ WrapperState,
15
+ WrapperUIProps,
16
+ } from './types'
@@ -0,0 +1,98 @@
1
+ import type { SxProps, Theme } from '@mui/material'
2
+
3
+ export const styles = {
4
+ root: {
5
+ '.Mui-disabled .MuiAccordionSummary-expandIconWrapper': {
6
+ display: 'none',
7
+ },
8
+ '.widget-wrapper-actions > *': {
9
+ opacity: 1,
10
+ transition: ({ transitions }) =>
11
+ transitions.create('opacity', {
12
+ duration: transitions.duration.standard,
13
+ easing: transitions.easing.easeInOut,
14
+ }),
15
+
16
+ '@media (hover: hover)': {
17
+ opacity: 0,
18
+ },
19
+
20
+ '&:has(.active)': {
21
+ opacity: 1,
22
+ },
23
+ },
24
+ '&:hover': {
25
+ '& .widget-wrapper-actions > *': {
26
+ opacity: 1,
27
+ },
28
+ },
29
+
30
+ '&.collapsed .widget-wrapper-actions > *': {
31
+ opacity: 0,
32
+ pointerEvents: 'none',
33
+
34
+ '&:has(.active)': {
35
+ opacity: 1,
36
+ },
37
+ },
38
+ },
39
+ summary: {
40
+ minHeight: ({ spacing }) => spacing(7),
41
+ '& .MuiAccordionSummary-content': {
42
+ gap: ({ spacing }) => spacing(0.5),
43
+ paddingInlineEnd: ({ spacing }) => spacing(0.75),
44
+ },
45
+ },
46
+ loading: {
47
+ height: ({ spacing }) => spacing(0.35),
48
+ left: 0,
49
+ position: 'absolute',
50
+ top: 0,
51
+ width: '100%',
52
+ zIndex: 1,
53
+ },
54
+ title: {
55
+ wrapper: {
56
+ flexGrow: 1,
57
+ flexShrink: 1,
58
+ minWidth: 0,
59
+ display: 'flex',
60
+ justifyContent: 'center',
61
+ flexDirection: 'column',
62
+ },
63
+ container: {
64
+ display: 'flex',
65
+ alignItems: 'center',
66
+ flexGrow: 1,
67
+ minHeight: ({ spacing }) => spacing(3),
68
+ },
69
+ text: {
70
+ wordBreak: 'break-word',
71
+ overflow: 'hidden',
72
+ display: '-webkit-box',
73
+ WebkitLineClamp: 2,
74
+ WebkitBoxOrient: 'vertical',
75
+ },
76
+ },
77
+ actions: {
78
+ display: 'flex',
79
+ gap: ({ spacing }) => spacing(0.5),
80
+ alignItems: 'center',
81
+ flexShrink: 0,
82
+ justifyContent: 'flex-end',
83
+ '& > div': {
84
+ pointerEvents: 'auto',
85
+ },
86
+ },
87
+ options: {
88
+ trigger: {
89
+ pointerEvents: 'auto',
90
+ },
91
+ },
92
+ detail: {
93
+ display: 'flex',
94
+ flexDirection: 'column',
95
+ gap: ({ spacing }) => spacing(1),
96
+ paddingTop: ({ spacing }) => spacing(0.5),
97
+ },
98
+ } satisfies Record<string, SxProps<Theme>>
@@ -0,0 +1,55 @@
1
+ import type { ReactNode, SyntheticEvent } from 'react'
2
+ import type { SxProps, Theme } from '@mui/material'
3
+ import type { BaseWidgetState } from '../stores/types'
4
+
5
+ export interface WrapperProps
6
+ extends Pick<
7
+ WrapperUIProps,
8
+ 'sx' | 'actions' | 'options' | 'labels' | 'children' | 'onChangeCollapsed'
9
+ >,
10
+ Pick<WrapperState, 'disabled' | 'title'> {
11
+ id: WrapperState['id']
12
+ defaultCollapsed?: boolean
13
+ }
14
+
15
+ export type WrapperState<T = unknown> = BaseWidgetState<
16
+ T & {
17
+ title: string
18
+ disabled?: boolean
19
+ collapsed?: boolean
20
+ isLoading?: boolean
21
+ }
22
+ >
23
+
24
+ export interface WrapperUIProps
25
+ extends WrapperActionsProps,
26
+ Omit<WrapperOptionsProps, 'labels' | ''> {
27
+ id: WrapperState['id']
28
+ children: ReactNode
29
+ sx?: SxProps<Theme>
30
+ labels?: {
31
+ options?: WrapperOptionsProps['labels']
32
+ }
33
+ onChangeCollapsed?: (
34
+ event: SyntheticEvent<Element, Event>,
35
+ collapsed: boolean,
36
+ ) => void
37
+ }
38
+
39
+ export interface WrapperActionsProps {
40
+ actions?: ReactNode[]
41
+ }
42
+
43
+ export interface WrapperOptionsProps {
44
+ labels?: {
45
+ title: string
46
+ }
47
+ options?: WrapperOption[]
48
+ }
49
+
50
+ export interface WrapperOption {
51
+ label: string
52
+ disabled?: boolean
53
+ icon?: ReactNode
54
+ onClick: () => void
55
+ }
@@ -0,0 +1,232 @@
1
+ import { describe, test, expect, vi, beforeEach } from 'vitest'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { WrapperUI } from './wrapper-ui'
4
+ import { useWidgetStore } from '../stores/widget-store'
5
+ import type { WrapperState } from './types'
6
+
7
+ describe('WrapperUI', () => {
8
+ const widgetId = 'test-wrapper-ui'
9
+
10
+ const defaultProps = {
11
+ id: widgetId,
12
+ children: <div>Test Content</div>,
13
+ }
14
+
15
+ beforeEach(() => {
16
+ useWidgetStore.getState().clearWidgets()
17
+ // Pre-set the widget in the store with default values
18
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
19
+ title: 'Wrapper Widget',
20
+ collapsed: false,
21
+ disabled: false,
22
+ isFetching: false,
23
+ })
24
+ })
25
+
26
+ test('renders correctly with default props', () => {
27
+ render(<WrapperUI {...defaultProps} />)
28
+ expect(screen.getByText('Test Content')).toBeTruthy()
29
+ expect(screen.getByText('Wrapper Widget')).toBeTruthy()
30
+ })
31
+
32
+ test('renders with custom title from store', () => {
33
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
34
+ title: 'Custom Title',
35
+ })
36
+ render(<WrapperUI {...defaultProps} />)
37
+ expect(screen.getByText('Custom Title')).toBeTruthy()
38
+ })
39
+
40
+ test('renders in expanded state by default', () => {
41
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
42
+ collapsed: false,
43
+ })
44
+ render(<WrapperUI {...defaultProps} />)
45
+ const accordion = screen.getByRole('region')
46
+ expect(accordion).toBeTruthy()
47
+ })
48
+
49
+ test('renders in collapsed state', () => {
50
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
51
+ collapsed: true,
52
+ })
53
+ render(<WrapperUI {...defaultProps} />)
54
+ // When collapsed, AccordionDetails is not visible
55
+ const accordion = screen.getByRole('button')
56
+ expect(accordion).toBeTruthy()
57
+ })
58
+
59
+ test('renders loading indicator when isFetching is true', () => {
60
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
61
+ isFetching: true,
62
+ })
63
+ render(<WrapperUI {...defaultProps} />)
64
+ const progressBar = screen.getByRole('progressbar')
65
+ expect(progressBar).toBeTruthy()
66
+ })
67
+
68
+ test('does not render loading indicator when isFetching is false', () => {
69
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
70
+ isFetching: false,
71
+ })
72
+ render(<WrapperUI {...defaultProps} />)
73
+ const progressBar = screen.queryByRole('progressbar')
74
+ expect(progressBar).toBeNull()
75
+ })
76
+
77
+ test('renders actions when provided and not collapsed', () => {
78
+ const actions = [
79
+ <button key='action1'>Action 1</button>,
80
+ <button key='action2'>Action 2</button>,
81
+ ]
82
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
83
+ collapsed: false,
84
+ })
85
+ render(<WrapperUI {...defaultProps} actions={actions} />)
86
+ expect(screen.getByText('Action 1')).toBeTruthy()
87
+ expect(screen.getByText('Action 2')).toBeTruthy()
88
+ })
89
+
90
+ test('renders actions even when collapsed', () => {
91
+ const actions = [<button key='action1'>Action 1</button>]
92
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
93
+ collapsed: true,
94
+ })
95
+ render(<WrapperUI {...defaultProps} actions={actions} />)
96
+ // Actions are rendered in the AccordionSummary which is always visible
97
+ expect(screen.getByText('Action 1')).toBeTruthy()
98
+ })
99
+
100
+ test('does not render actions when array is empty', () => {
101
+ render(<WrapperUI {...defaultProps} actions={[]} />)
102
+ // No actions should be rendered
103
+ expect(screen.getByText('Test Content')).toBeTruthy()
104
+ })
105
+
106
+ test('renders options when provided', () => {
107
+ const options = [
108
+ {
109
+ label: 'Option 1',
110
+ onClick: vi.fn(),
111
+ },
112
+ ]
113
+ render(<WrapperUI {...defaultProps} options={options} />)
114
+ const optionsButton = screen.getByLabelText('Options')
115
+ expect(optionsButton).toBeTruthy()
116
+ })
117
+
118
+ test('renders options with custom label', () => {
119
+ const options = [
120
+ {
121
+ label: 'Option 1',
122
+ onClick: vi.fn(),
123
+ },
124
+ ]
125
+ const labels = {
126
+ options: {
127
+ title: 'Custom Options Label',
128
+ },
129
+ }
130
+ render(<WrapperUI {...defaultProps} options={options} labels={labels} />)
131
+ const optionsButton = screen.getByLabelText('Custom Options Label')
132
+ expect(optionsButton).toBeTruthy()
133
+ })
134
+
135
+ test('does not render options when array is empty', () => {
136
+ render(<WrapperUI {...defaultProps} options={[]} />)
137
+ const optionsButton = screen.queryByLabelText('Options')
138
+ expect(optionsButton).toBeNull()
139
+ })
140
+
141
+ test('handles onChangeCollapsed callback', () => {
142
+ const onChangeCollapsed = vi.fn()
143
+ render(
144
+ <WrapperUI {...defaultProps} onChangeCollapsed={onChangeCollapsed} />,
145
+ )
146
+
147
+ const accordion = screen.getByRole('button')
148
+ fireEvent.click(accordion)
149
+
150
+ expect(onChangeCollapsed).toHaveBeenCalledTimes(1)
151
+ })
152
+
153
+ test('renders disabled state', () => {
154
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
155
+ disabled: true,
156
+ })
157
+ render(<WrapperUI {...defaultProps} />)
158
+ const accordion = screen.getByRole('button')
159
+ expect(accordion.classList.contains('Mui-disabled')).toBe(true)
160
+ })
161
+
162
+ test('applies custom sx styles', () => {
163
+ const sx = { backgroundColor: 'red' }
164
+ render(<WrapperUI {...defaultProps} sx={sx} />)
165
+ // Component should render (we can't easily test sx application without jest-dom)
166
+ expect(screen.getByText('Test Content')).toBeTruthy()
167
+ })
168
+
169
+ test('renders children correctly', () => {
170
+ render(
171
+ <WrapperUI {...defaultProps}>
172
+ <div>Child 1</div>
173
+ <div>Child 2</div>
174
+ </WrapperUI>,
175
+ )
176
+ expect(screen.getByText('Child 1')).toBeTruthy()
177
+ expect(screen.getByText('Child 2')).toBeTruthy()
178
+ })
179
+
180
+ test('renders with all props combined', () => {
181
+ const actions = [<button key='action1'>Action 1</button>]
182
+ const options = [
183
+ {
184
+ label: 'Option 1',
185
+ onClick: vi.fn(),
186
+ },
187
+ ]
188
+ const labels = {
189
+ options: {
190
+ title: 'Custom Options',
191
+ },
192
+ }
193
+ const onChangeCollapsed = vi.fn()
194
+
195
+ useWidgetStore.getState().setWidget<WrapperState>(widgetId, {
196
+ title: 'Full Test',
197
+ collapsed: false,
198
+ disabled: false,
199
+ isFetching: true,
200
+ })
201
+
202
+ render(
203
+ <WrapperUI
204
+ {...defaultProps}
205
+ actions={actions}
206
+ options={options}
207
+ labels={labels}
208
+ sx={{ margin: 2 }}
209
+ onChangeCollapsed={onChangeCollapsed}
210
+ />,
211
+ )
212
+
213
+ expect(screen.getByText('Full Test')).toBeTruthy()
214
+ expect(screen.getByText('Action 1')).toBeTruthy()
215
+ expect(screen.getByLabelText('Custom Options')).toBeTruthy()
216
+ expect(screen.getByRole('progressbar')).toBeTruthy()
217
+ expect(screen.getByText('Test Content')).toBeTruthy()
218
+ })
219
+
220
+ test('handles collapse toggle multiple times', () => {
221
+ const onChangeCollapsed = vi.fn()
222
+ render(
223
+ <WrapperUI {...defaultProps} onChangeCollapsed={onChangeCollapsed} />,
224
+ )
225
+
226
+ const accordion = screen.getByRole('button')
227
+ fireEvent.click(accordion)
228
+ fireEvent.click(accordion)
229
+
230
+ expect(onChangeCollapsed).toHaveBeenCalledTimes(2)
231
+ })
232
+ })
@@ -0,0 +1,57 @@
1
+ import {
2
+ Accordion,
3
+ AccordionDetails,
4
+ AccordionSummary,
5
+ LinearProgress,
6
+ } from '@mui/material'
7
+ import { styles } from './styles'
8
+ import { Title } from './components/title'
9
+ import type { WrapperState, WrapperUIProps } from './types'
10
+ import { Options } from './components/options'
11
+ import { Actions } from './components/actions'
12
+ import { useWidgetStore } from '../stores/widget-store'
13
+ import { useShallow } from 'zustand/shallow'
14
+
15
+ export function WrapperUI({
16
+ children,
17
+ id,
18
+ actions = [],
19
+ sx,
20
+ labels,
21
+ options = [],
22
+ onChangeCollapsed,
23
+ }: WrapperUIProps) {
24
+ const widget = useWidgetStore(
25
+ useShallow((state) => {
26
+ const widget = state.getWidget<WrapperState>(id)
27
+ return {
28
+ title: widget?.title,
29
+ collapsed: widget?.collapsed,
30
+ disabled: widget?.disabled,
31
+ isFetching: widget?.isFetching,
32
+ }
33
+ }),
34
+ )
35
+
36
+ return (
37
+ <Accordion
38
+ className={widget?.collapsed ? 'collapsed' : ''}
39
+ sx={{ ...styles.root, ...sx }}
40
+ expanded={!widget?.collapsed}
41
+ onChange={onChangeCollapsed}
42
+ disabled={widget?.disabled}
43
+ >
44
+ <AccordionSummary sx={styles.summary}>
45
+ {widget?.isFetching && (
46
+ <LinearProgress sx={styles.loading} color='primary' />
47
+ )}
48
+ <Title>{widget?.title}</Title>
49
+ {!!actions.length && <Actions actions={actions} />}
50
+ {!!options.length && (
51
+ <Options labels={labels?.options} options={options} />
52
+ )}
53
+ </AccordionSummary>
54
+ <AccordionDetails sx={styles.detail}>{children}</AccordionDetails>
55
+ </Accordion>
56
+ )
57
+ }