@carto/ps-react-ui 4.3.3 → 4.3.4

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 (297) 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/widgets/actions.js +1 -1
  8. package/dist/widgets/bar.js +1 -1
  9. package/dist/widgets/category.js +1 -1
  10. package/dist/widgets/formula.js +1 -1
  11. package/dist/widgets/histogram.js +1 -1
  12. package/dist/widgets/markdown.js +1 -1
  13. package/dist/widgets/pie.js +1 -1
  14. package/dist/widgets/scatterplot.js +1 -1
  15. package/dist/widgets/spread.js +1 -1
  16. package/dist/widgets/table.js +1 -1
  17. package/dist/widgets/timeseries.js +1 -1
  18. package/dist/widgets/toolbar-actions.js.map +1 -1
  19. package/dist/widgets/wrapper.js +1 -1
  20. package/package.json +8 -3
  21. package/src/components/basemaps/basemaps.test.tsx +196 -0
  22. package/src/components/basemaps/basemaps.tsx +128 -0
  23. package/src/components/basemaps/const.ts +13 -0
  24. package/src/components/basemaps/group-wrapper.test.tsx +38 -0
  25. package/src/components/basemaps/group-wrapper.tsx +28 -0
  26. package/src/components/basemaps/group.test.tsx +52 -0
  27. package/src/components/basemaps/group.tsx +42 -0
  28. package/src/components/basemaps/header.test.tsx +54 -0
  29. package/src/components/basemaps/header.tsx +36 -0
  30. package/src/components/basemaps/styles.ts +76 -0
  31. package/src/components/basemaps/types.ts +30 -0
  32. package/src/components/common-types.ts +1 -0
  33. package/src/components/geolocation-controls/const.ts +6 -0
  34. package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
  35. package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
  36. package/src/components/geolocation-controls/types.ts +17 -0
  37. package/src/components/index.ts +64 -0
  38. package/src/components/lasso-tool/chip.tsx +37 -0
  39. package/src/components/lasso-tool/const.tsx +70 -0
  40. package/src/components/lasso-tool/icons.tsx +91 -0
  41. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
  42. package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
  43. package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
  44. package/src/components/lasso-tool/lasso-tool.tsx +479 -0
  45. package/src/components/lasso-tool/styles.ts +143 -0
  46. package/src/components/lasso-tool/types.ts +114 -0
  47. package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
  48. package/src/components/list-data/list-data-skeleton.tsx +40 -0
  49. package/src/components/list-data/list-data.test.tsx +94 -0
  50. package/src/components/list-data/list-data.tsx +106 -0
  51. package/src/components/list-data/styles.ts +37 -0
  52. package/src/components/list-data/types.ts +25 -0
  53. package/src/components/measurement-tools/const.tsx +108 -0
  54. package/src/components/measurement-tools/icons.tsx +54 -0
  55. package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
  56. package/src/components/measurement-tools/measurement-tools.tsx +443 -0
  57. package/src/components/measurement-tools/styles.ts +91 -0
  58. package/src/components/measurement-tools/types.ts +77 -0
  59. package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
  60. package/src/components/no-data-alert/no-data-alert.tsx +59 -0
  61. package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
  62. package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
  63. package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
  64. package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
  65. package/src/components/tooltip/tooltip.test.tsx +86 -0
  66. package/src/components/tooltip/tooltip.tsx +30 -0
  67. package/src/components/types.ts +1 -0
  68. package/src/components/zoom-controls/styles.ts +27 -0
  69. package/src/components/zoom-controls/types.ts +19 -0
  70. package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
  71. package/src/components/zoom-controls/zoom-controls.tsx +114 -0
  72. package/src/hooks/index.ts +2 -0
  73. package/src/hooks/use-debounce.ts +55 -0
  74. package/src/hooks/use-skeleton.test.tsx +32 -0
  75. package/src/hooks/use-skeleton.ts +19 -0
  76. package/src/hooks/use-widget-ref.ts +33 -0
  77. package/src/widgets/README.md +277 -0
  78. package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
  79. package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
  80. package/src/widgets/_shared/chart-config/index.ts +21 -0
  81. package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
  82. package/src/widgets/_shared/skeleton/index.ts +5 -0
  83. package/src/widgets/_shared/skeleton/styles.ts +20 -0
  84. package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
  85. package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
  86. package/src/widgets/actions/change-column/change-column.tsx +141 -0
  87. package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
  88. package/src/widgets/actions/change-column/types.ts +20 -0
  89. package/src/widgets/actions/download/download.test.tsx +322 -0
  90. package/src/widgets/actions/download/download.tsx +118 -0
  91. package/src/widgets/actions/download/exports.test.tsx +275 -0
  92. package/src/widgets/actions/download/exports.tsx +103 -0
  93. package/src/widgets/actions/download/types.ts +21 -0
  94. package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
  95. package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
  96. package/src/widgets/actions/fullscreen/styles.ts +17 -0
  97. package/src/widgets/actions/fullscreen/types.ts +27 -0
  98. package/src/widgets/actions/index.ts +51 -0
  99. package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
  100. package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
  101. package/src/widgets/actions/lock-selection/types.ts +41 -0
  102. package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
  103. package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
  104. package/src/widgets/actions/relative-data/style.ts +9 -0
  105. package/src/widgets/actions/relative-data/types.ts +31 -0
  106. package/src/widgets/actions/relative-data/utils.test.ts +223 -0
  107. package/src/widgets/actions/relative-data/utils.ts +58 -0
  108. package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
  109. package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
  110. package/src/widgets/actions/searcher/searcher.tsx +205 -0
  111. package/src/widgets/actions/searcher/types.ts +72 -0
  112. package/src/widgets/actions/shared/styles.ts +12 -0
  113. package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
  114. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
  115. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
  116. package/src/widgets/actions/stack-toggle/types.ts +29 -0
  117. package/src/widgets/actions/zoom-toggle/index.ts +2 -0
  118. package/src/widgets/actions/zoom-toggle/style.ts +14 -0
  119. package/src/widgets/actions/zoom-toggle/types.ts +44 -0
  120. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
  121. package/src/widgets/bar/config.ts +122 -0
  122. package/src/widgets/bar/index.ts +9 -0
  123. package/src/widgets/bar/skeleton.tsx +60 -0
  124. package/src/widgets/bar/style.ts +33 -0
  125. package/src/widgets/bar/types.ts +16 -0
  126. package/src/widgets/category/category-ui.test.tsx +399 -0
  127. package/src/widgets/category/category-ui.tsx +156 -0
  128. package/src/widgets/category/components/category-bar.tsx +28 -0
  129. package/src/widgets/category/components/category-legend.tsx +30 -0
  130. package/src/widgets/category/components/category-row-multi.tsx +50 -0
  131. package/src/widgets/category/components/category-row-other.tsx +23 -0
  132. package/src/widgets/category/components/category-row-single.tsx +47 -0
  133. package/src/widgets/category/components/index.ts +14 -0
  134. package/src/widgets/category/config.ts +85 -0
  135. package/src/widgets/category/index.ts +30 -0
  136. package/src/widgets/category/skeleton.tsx +24 -0
  137. package/src/widgets/category/style.ts +133 -0
  138. package/src/widgets/category/types.ts +40 -0
  139. package/src/widgets/echart/const.ts +1 -0
  140. package/src/widgets/echart/echart-ui.test.tsx +519 -0
  141. package/src/widgets/echart/echart-ui.tsx +80 -0
  142. package/src/widgets/echart/echart.test.tsx +537 -0
  143. package/src/widgets/echart/echart.tsx +60 -0
  144. package/src/widgets/echart/index.ts +16 -0
  145. package/src/widgets/echart/options.ts +53 -0
  146. package/src/widgets/echart/types.ts +41 -0
  147. package/src/widgets/echart/utils.ts +169 -0
  148. package/src/widgets/error/error.test.tsx +331 -0
  149. package/src/widgets/error/error.tsx +40 -0
  150. package/src/widgets/error/index.ts +2 -0
  151. package/src/widgets/error/types.ts +14 -0
  152. package/src/widgets/formula/components/item.test.tsx +249 -0
  153. package/src/widgets/formula/components/item.tsx +18 -0
  154. package/src/widgets/formula/components/prefix.test.tsx +341 -0
  155. package/src/widgets/formula/components/prefix.tsx +18 -0
  156. package/src/widgets/formula/components/row.test.tsx +364 -0
  157. package/src/widgets/formula/components/row.tsx +21 -0
  158. package/src/widgets/formula/components/series.tsx +34 -0
  159. package/src/widgets/formula/components/suffix.test.tsx +383 -0
  160. package/src/widgets/formula/components/suffix.tsx +28 -0
  161. package/src/widgets/formula/components/value.test.tsx +329 -0
  162. package/src/widgets/formula/components/value.tsx +29 -0
  163. package/src/widgets/formula/config.ts +27 -0
  164. package/src/widgets/formula/formula-ui.test.tsx +399 -0
  165. package/src/widgets/formula/formula-ui.tsx +27 -0
  166. package/src/widgets/formula/index.ts +24 -0
  167. package/src/widgets/formula/serializer.test.tsx +144 -0
  168. package/src/widgets/formula/serializer.ts +28 -0
  169. package/src/widgets/formula/skeleton.tsx +10 -0
  170. package/src/widgets/formula/style.ts +23 -0
  171. package/src/widgets/formula/types.ts +50 -0
  172. package/src/widgets/histogram/config.ts +143 -0
  173. package/src/widgets/histogram/index.ts +8 -0
  174. package/src/widgets/histogram/skeleton.tsx +52 -0
  175. package/src/widgets/histogram/style.ts +8 -0
  176. package/src/widgets/histogram/types.ts +17 -0
  177. package/src/widgets/index.ts +25 -0
  178. package/src/widgets/loader/index.ts +4 -0
  179. package/src/widgets/loader/loader.tsx +70 -0
  180. package/src/widgets/loader/types.ts +11 -0
  181. package/src/widgets/loader/utils.test.ts +112 -0
  182. package/src/widgets/loader/utils.ts +35 -0
  183. package/src/widgets/markdown/config.ts +18 -0
  184. package/src/widgets/markdown/index.ts +14 -0
  185. package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
  186. package/src/widgets/markdown/markdown-ui.tsx +52 -0
  187. package/src/widgets/markdown/markdown.tsx +20 -0
  188. package/src/widgets/markdown/skeleton.tsx +12 -0
  189. package/src/widgets/markdown/style.ts +28 -0
  190. package/src/widgets/markdown/types.ts +28 -0
  191. package/src/widgets/no-data/index.ts +2 -0
  192. package/src/widgets/no-data/no-data.test.tsx +447 -0
  193. package/src/widgets/no-data/no-data.tsx +116 -0
  194. package/src/widgets/no-data/style.ts +18 -0
  195. package/src/widgets/no-data/types.ts +72 -0
  196. package/src/widgets/note/index.ts +2 -0
  197. package/src/widgets/note/note.test.tsx +391 -0
  198. package/src/widgets/note/note.tsx +114 -0
  199. package/src/widgets/note/style.ts +29 -0
  200. package/src/widgets/note/types.ts +9 -0
  201. package/src/widgets/pie/config.ts +177 -0
  202. package/src/widgets/pie/index.ts +8 -0
  203. package/src/widgets/pie/skeleton.tsx +70 -0
  204. package/src/widgets/pie/style.ts +8 -0
  205. package/src/widgets/pie/types.ts +16 -0
  206. package/src/widgets/range/components/range-item.tsx +213 -0
  207. package/src/widgets/range/config.ts +10 -0
  208. package/src/widgets/range/index.ts +16 -0
  209. package/src/widgets/range/range-ui.test.tsx +203 -0
  210. package/src/widgets/range/range-ui.tsx +11 -0
  211. package/src/widgets/range/serializer.test.ts +70 -0
  212. package/src/widgets/range/serializer.ts +27 -0
  213. package/src/widgets/range/skeleton.tsx +14 -0
  214. package/src/widgets/range/style.ts +37 -0
  215. package/src/widgets/range/types.ts +39 -0
  216. package/src/widgets/scatterplot/config.ts +138 -0
  217. package/src/widgets/scatterplot/index.ts +8 -0
  218. package/src/widgets/scatterplot/skeleton.tsx +59 -0
  219. package/src/widgets/scatterplot/style.ts +21 -0
  220. package/src/widgets/scatterplot/types.ts +17 -0
  221. package/src/widgets/selection-summary/index.ts +6 -0
  222. package/src/widgets/selection-summary/selection-summary.tsx +46 -0
  223. package/src/widgets/selection-summary/style.ts +10 -0
  224. package/src/widgets/selection-summary/types.ts +14 -0
  225. package/src/widgets/skeleton-loader/index.ts +2 -0
  226. package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
  227. package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
  228. package/src/widgets/skeleton-loader/types.ts +8 -0
  229. package/src/widgets/spread/components/max-value.tsx +29 -0
  230. package/src/widgets/spread/components/min-value.tsx +29 -0
  231. package/src/widgets/spread/components/separator.tsx +6 -0
  232. package/src/widgets/spread/config.ts +34 -0
  233. package/src/widgets/spread/index.ts +23 -0
  234. package/src/widgets/spread/skeleton.tsx +10 -0
  235. package/src/widgets/spread/spread-ui.test.tsx +368 -0
  236. package/src/widgets/spread/spread-ui.tsx +29 -0
  237. package/src/widgets/spread/style.ts +22 -0
  238. package/src/widgets/spread/types.ts +47 -0
  239. package/src/widgets/stores/index.ts +9 -0
  240. package/src/widgets/stores/types.ts +192 -0
  241. package/src/widgets/stores/widget-store.test.ts +601 -0
  242. package/src/widgets/stores/widget-store.ts +239 -0
  243. package/src/widgets/subheader/index.ts +3 -0
  244. package/src/widgets/subheader/style.ts +20 -0
  245. package/src/widgets/subheader/subheader.test.tsx +45 -0
  246. package/src/widgets/subheader/subheader.tsx +16 -0
  247. package/src/widgets/subheader/types.ts +11 -0
  248. package/src/widgets/table/components/cell-header.tsx +58 -0
  249. package/src/widgets/table/components/cell.tsx +80 -0
  250. package/src/widgets/table/components/index.ts +4 -0
  251. package/src/widgets/table/components/pagination-actions.tsx +67 -0
  252. package/src/widgets/table/components/pagination.tsx +41 -0
  253. package/src/widgets/table/components/row.tsx +60 -0
  254. package/src/widgets/table/config.ts +71 -0
  255. package/src/widgets/table/helpers.test.ts +244 -0
  256. package/src/widgets/table/helpers.ts +107 -0
  257. package/src/widgets/table/hooks/index.ts +7 -0
  258. package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
  259. package/src/widgets/table/hooks/use-pagination.ts +155 -0
  260. package/src/widgets/table/hooks/use-selection.test.ts +504 -0
  261. package/src/widgets/table/hooks/use-selection.ts +189 -0
  262. package/src/widgets/table/hooks/use-sort.test.ts +296 -0
  263. package/src/widgets/table/hooks/use-sort.ts +138 -0
  264. package/src/widgets/table/index.ts +53 -0
  265. package/src/widgets/table/serializer.ts +54 -0
  266. package/src/widgets/table/skeleton.tsx +48 -0
  267. package/src/widgets/table/style.ts +34 -0
  268. package/src/widgets/table/table-ui.tsx +64 -0
  269. package/src/widgets/table/types.ts +223 -0
  270. package/src/widgets/timeseries/config.ts +135 -0
  271. package/src/widgets/timeseries/index.ts +8 -0
  272. package/src/widgets/timeseries/skeleton.tsx +55 -0
  273. package/src/widgets/timeseries/style.ts +36 -0
  274. package/src/widgets/timeseries/types.ts +17 -0
  275. package/src/widgets/toolbar-actions/index.ts +6 -0
  276. package/src/widgets/toolbar-actions/styles.ts +38 -0
  277. package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
  278. package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
  279. package/src/widgets/toolbar-actions/types.ts +60 -0
  280. package/src/widgets/wrapper/components/actions.test.tsx +101 -0
  281. package/src/widgets/wrapper/components/actions.tsx +30 -0
  282. package/src/widgets/wrapper/components/options.test.tsx +323 -0
  283. package/src/widgets/wrapper/components/options.tsx +73 -0
  284. package/src/widgets/wrapper/components/title.test.tsx +126 -0
  285. package/src/widgets/wrapper/components/title.tsx +32 -0
  286. package/src/widgets/wrapper/index.ts +16 -0
  287. package/src/widgets/wrapper/styles.ts +98 -0
  288. package/src/widgets/wrapper/types.ts +55 -0
  289. package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
  290. package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
  291. package/src/widgets/wrapper/wrapper.test.tsx +365 -0
  292. package/src/widgets/wrapper/wrapper.tsx +50 -0
  293. package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
  294. package/dist/types/common/common.d.ts +0 -3
  295. package/dist/types/common/index.d.ts +0 -26
  296. package/dist/types/common/lasso-tools.d.ts +0 -36
  297. package/dist/types/common/measurement-tools.d.ts +0 -65
@@ -0,0 +1,77 @@
1
+ import type { Paper, TooltipProps } from '@mui/material'
2
+ import type { ComponentProps } from 'react'
3
+ import type {
4
+ MeasurementMode,
5
+ MeasurementModes,
6
+ MeasurementOptionsMode,
7
+ MeasurementOptionsUnit,
8
+ MeasurementUnit,
9
+ MeasurementUnitImperialDistance,
10
+ MeasurementUnitMetricDistance,
11
+ MeasurementUnits,
12
+ MeasurementModesMapping,
13
+ MeasurementUnitsMapping,
14
+ } from '../types'
15
+
16
+ interface Labels {
17
+ action?: {
18
+ tooltip?: {
19
+ active?: string
20
+ inactive?: string
21
+ }
22
+ }
23
+ chip?: {
24
+ tooltip?: {
25
+ active?: string
26
+ inactive?: string
27
+ }
28
+ }
29
+ options?: {
30
+ mode?: {
31
+ title?: string
32
+ options?: MeasurementOptionsMode
33
+ }
34
+ units?: {
35
+ title?: string
36
+ modal?: {
37
+ title?: string
38
+ subtitle?: string
39
+ apply?: string
40
+ options?: {
41
+ metric: {
42
+ title: string
43
+ options: MeasurementOptionsUnit<MeasurementUnitMetricDistance>
44
+ }
45
+ imperial: {
46
+ title: string
47
+ options: MeasurementOptionsUnit<MeasurementUnitImperialDistance>
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ interface ActionProps {
56
+ TooltipProps?: Omit<TooltipProps, 'title'>
57
+ }
58
+
59
+ export interface MeasurementToolsComponentProps<
60
+ T extends string = MeasurementMode,
61
+ U extends string = MeasurementUnit,
62
+ > {
63
+ enabled: boolean
64
+ value: string
65
+ labels?: Labels
66
+ modesMapping?: MeasurementModesMapping<T>
67
+ unitsMapping?: MeasurementUnitsMapping<U>
68
+ actionProps?: ActionProps
69
+ modeSelected?: T
70
+ modes: Partial<MeasurementModes<T>>
71
+ units: MeasurementUnits<T, U>
72
+ unitSelected?: string
73
+ PaperProps?: ComponentProps<typeof Paper>
74
+ onActionToggle: (data: boolean) => void
75
+ onChangeMode: (mode?: T) => void
76
+ onChangeUnit: (unit: string) => void
77
+ }
@@ -0,0 +1,31 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { render, within } from '@testing-library/react'
3
+ import { NoDataAlert } from './no-data-alert'
4
+
5
+ describe('NoDataAlert', () => {
6
+ test('with default props', () => {
7
+ const { container } = render(<NoDataAlert />)
8
+
9
+ const title = within(container).queryByText('No data available')
10
+ const body = within(container).queryByText(
11
+ 'There are no results for the combination of filters applied to your data. Try tweaking your filters, or zoom and pan the map to adjust the Map View.',
12
+ )
13
+
14
+ expect(title).toBeTruthy()
15
+ expect(body).toBeTruthy()
16
+ })
17
+
18
+ test('with custom title', () => {
19
+ const { container } = render(<NoDataAlert title='Custom title' />)
20
+
21
+ const title = within(container).queryByText('Custom title')
22
+ expect(title).toBeTruthy()
23
+ })
24
+
25
+ test('with custom body', () => {
26
+ const { container } = render(<NoDataAlert body='Custom body' />)
27
+
28
+ const body = within(container).queryByText('Custom body')
29
+ expect(body).toBeTruthy()
30
+ })
31
+ })
@@ -0,0 +1,59 @@
1
+ import {
2
+ Alert,
3
+ Box,
4
+ Typography,
5
+ type AlertProps,
6
+ type TypographyProps,
7
+ } from '@mui/material'
8
+
9
+ interface NoDataAlertProps {
10
+ title?: string
11
+ body?: string
12
+ severity?: AlertProps['severity']
13
+ }
14
+
15
+ export function NoDataAlert({
16
+ title = 'No data available',
17
+ body = 'There are no results for the combination of filters applied to your data. Try tweaking your filters, or zoom and pan the map to adjust the Map View.',
18
+ severity,
19
+ }: NoDataAlertProps) {
20
+ if (severity) {
21
+ return (
22
+ <Alert title={title} severity={severity}>
23
+ <AlertBody>{body}</AlertBody>
24
+ </Alert>
25
+ )
26
+ }
27
+
28
+ return (
29
+ <Box>
30
+ {title && <Typography variant='body2'>{title}</Typography>}
31
+ <AlertBody color='textSecondary'>{body}</AlertBody>
32
+ </Box>
33
+ )
34
+ }
35
+
36
+ // Aux
37
+ interface AlertBodyProps {
38
+ color?: TypographyProps['color']
39
+ children: React.ReactNode
40
+ }
41
+
42
+ function AlertBody({ color, children }: AlertBodyProps) {
43
+ if (children) {
44
+ return (
45
+ <Box mt={0.5}>
46
+ <Typography
47
+ component='div'
48
+ variant='caption'
49
+ color={color ?? 'inherit'}
50
+ style={{ fontWeight: 'normal' }}
51
+ >
52
+ {children}
53
+ </Typography>
54
+ </Box>
55
+ )
56
+ }
57
+
58
+ return <Box mt={-1} />
59
+ }
@@ -0,0 +1,91 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import { describe, test, expect, vi, beforeEach } from 'vitest'
3
+ import { ResponsiveDrawer } from './responsive-drawer'
4
+ import { ThemeProvider } from '@mui/material'
5
+ import { createTheme } from '@mui/material/styles'
6
+
7
+ // Create a basic theme for testing
8
+ const theme = createTheme()
9
+
10
+ describe('ResponsiveDrawer', () => {
11
+ const mockOnChangeCollapsed = vi.fn()
12
+
13
+ const defaultProps = {
14
+ collapsed: false,
15
+ onChangeCollapsed: mockOnChangeCollapsed,
16
+ position: 'top-left' as const,
17
+ isMobile: false,
18
+ children: <div data-testid='drawer-content'>Test Content</div>,
19
+ }
20
+
21
+ beforeEach(() => {
22
+ mockOnChangeCollapsed.mockReset()
23
+ })
24
+
25
+ test('renders in desktop mode when isMobile is false', () => {
26
+ const { container } = render(
27
+ <ThemeProvider theme={theme}>
28
+ <ResponsiveDrawer ref={null} {...defaultProps} />
29
+ </ThemeProvider>,
30
+ )
31
+
32
+ // Check if content is rendered
33
+ const content = screen.getByTestId('drawer-content')
34
+ expect(content).toBeDefined()
35
+
36
+ // Make sure we're not using the mobile Drawer (no presentation role)
37
+ const presentationElements = container.querySelectorAll(
38
+ '[role="presentation"]',
39
+ )
40
+ expect(presentationElements.length).toBe(0)
41
+ })
42
+
43
+ test('renders in mobile mode when isMobile is true', () => {
44
+ render(
45
+ <ThemeProvider theme={theme}>
46
+ <ResponsiveDrawer ref={null} {...defaultProps} isMobile={true} />
47
+ </ThemeProvider>,
48
+ )
49
+
50
+ // Check if content is rendered
51
+ const content = screen.getByTestId('drawer-content')
52
+ expect(content).toBeDefined()
53
+
54
+ // Mobile mode uses MUI Drawer which has a 'presentation' role
55
+ const presentationElements = document.querySelectorAll(
56
+ '[role="presentation"]',
57
+ )
58
+ expect(presentationElements.length).toBeGreaterThan(0)
59
+ })
60
+
61
+ test('hides content when collapsed is true in desktop mode', () => {
62
+ render(
63
+ <ThemeProvider theme={theme}>
64
+ <ResponsiveDrawer ref={null} {...defaultProps} collapsed={true} />
65
+ </ThemeProvider>,
66
+ )
67
+
68
+ // When collapsed, the Popover should be closed and content should not be in the document
69
+ const content = screen.queryByTestId('drawer-content')
70
+ expect(content).toBeNull()
71
+ })
72
+
73
+ test('closes drawer when clicking outside in mobile mode', () => {
74
+ const { container } = render(
75
+ <ThemeProvider theme={theme}>
76
+ <ResponsiveDrawer ref={null} {...defaultProps} isMobile={true} />
77
+ </ThemeProvider>,
78
+ )
79
+
80
+ // Find and click the backdrop to close the drawer
81
+ const backdrop = container.querySelector('[role="presentation"] > div')
82
+ expect(backdrop).toBeDefined()
83
+ if (backdrop) {
84
+ fireEvent.click(backdrop)
85
+
86
+ // onChangeCollapsed should be called to close the drawer
87
+ expect(mockOnChangeCollapsed).toHaveBeenCalledTimes(1)
88
+ expect(mockOnChangeCollapsed).toHaveBeenCalledWith(true)
89
+ }
90
+ })
91
+ })
@@ -0,0 +1,53 @@
1
+ import { Drawer, Popover, type PopoverProps } from '@mui/material'
2
+ import type { ReactNode } from 'react'
3
+
4
+ export function ResponsiveDrawer<
5
+ T extends {
6
+ collapsed: boolean
7
+ onChangeCollapsed: (collapsed: boolean) => void
8
+ position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
9
+ slotProps?: PopoverProps['slotProps']
10
+ },
11
+ >({
12
+ ref,
13
+ children,
14
+ collapsed,
15
+ isMobile = false,
16
+ position,
17
+ sx,
18
+ slotProps,
19
+ onChangeCollapsed,
20
+ }: Required<Pick<T, 'collapsed' | 'onChangeCollapsed' | 'position'>> &
21
+ Pick<PopoverProps, 'slotProps' | 'sx'> & {
22
+ ref: HTMLElement | null
23
+ isMobile: boolean
24
+ children: ReactNode
25
+ }) {
26
+ if (isMobile) {
27
+ return (
28
+ <Drawer anchor='bottom' open={!collapsed} onClose={onChangeCollapsed}>
29
+ {children}
30
+ </Drawer>
31
+ )
32
+ }
33
+
34
+ return (
35
+ <Popover
36
+ sx={sx}
37
+ anchorEl={ref}
38
+ open={!collapsed}
39
+ onClose={() => onChangeCollapsed(true)}
40
+ anchorOrigin={{
41
+ vertical: position.startsWith('bottom') ? 'bottom' : 'top',
42
+ horizontal: position.endsWith('right') ? 'left' : 'right',
43
+ }}
44
+ transformOrigin={{
45
+ vertical: position.startsWith('bottom') ? 'bottom' : 'top',
46
+ horizontal: position.endsWith('right') ? 'right' : 'left',
47
+ }}
48
+ slotProps={slotProps}
49
+ >
50
+ {children}
51
+ </Popover>
52
+ )
53
+ }
@@ -0,0 +1,168 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import * as React from 'react'
4
+ import { SmartTooltip } from './smart-tooltip'
5
+
6
+ // Mock the useLayoutEffect hook for testing overflow scenarios
7
+ vi.mock('react', async () => {
8
+ const actual = await vi.importActual('react')
9
+ return {
10
+ ...actual,
11
+ useLayoutEffect: (callback: () => void) => {
12
+ callback()
13
+ },
14
+ }
15
+ })
16
+
17
+ // Setup timers for useLayoutEffect timeout
18
+ beforeEach(() => {
19
+ vi.useFakeTimers()
20
+ })
21
+
22
+ afterEach(() => {
23
+ vi.restoreAllMocks()
24
+ vi.useRealTimers()
25
+ })
26
+
27
+ describe('SmartTooltip', () => {
28
+ const defaultProps = {
29
+ title: 'Test Label',
30
+ children: ({ ref }: { ref: React.Ref<HTMLElement> }) => (
31
+ <div ref={ref as React.Ref<HTMLDivElement>} data-testid='test-child'>
32
+ Content
33
+ </div>
34
+ ),
35
+ }
36
+
37
+ test('renders correctly with child element', () => {
38
+ render(<SmartTooltip {...defaultProps} />)
39
+ const child = screen.getByTestId('test-child')
40
+ expect(child).toBeTruthy()
41
+ expect(child.textContent).toBe('Content')
42
+ })
43
+
44
+ test('passes ref to children function', () => {
45
+ const childrenSpy = vi
46
+ .fn()
47
+ .mockReturnValue(<div data-testid='test-child'>Content</div>)
48
+ render(<SmartTooltip title='Test Label'>{childrenSpy}</SmartTooltip>)
49
+
50
+ expect(childrenSpy).toHaveBeenCalledTimes(1)
51
+ // Just check that a ref was passed
52
+ expect(childrenSpy).toHaveBeenCalled()
53
+ })
54
+
55
+ test('handles undefined title', () => {
56
+ render(
57
+ <SmartTooltip title={undefined}>
58
+ {({ ref }) => (
59
+ <div ref={ref as React.Ref<HTMLDivElement>} data-testid='test-child'>
60
+ Content
61
+ </div>
62
+ )}
63
+ </SmartTooltip>,
64
+ )
65
+ const child = screen.getByTestId('test-child')
66
+ expect(child).toBeTruthy()
67
+ })
68
+
69
+ test('updates isOverflowing state when dependencies change', () => {
70
+ const { rerender } = render(
71
+ <SmartTooltip title='Test Label' dependencies={[1]}>
72
+ {({ ref }) => (
73
+ <div ref={ref as React.Ref<HTMLDivElement>} data-testid='test-child'>
74
+ Content
75
+ </div>
76
+ )}
77
+ </SmartTooltip>,
78
+ )
79
+
80
+ // Re-render with different dependencies to trigger useLayoutEffect
81
+ rerender(
82
+ <SmartTooltip title='Test Label' dependencies={[2]}>
83
+ {({ ref }) => (
84
+ <div ref={ref as React.Ref<HTMLDivElement>} data-testid='test-child'>
85
+ Content
86
+ </div>
87
+ )}
88
+ </SmartTooltip>,
89
+ )
90
+
91
+ expect(screen.getByTestId('test-child')).toBeTruthy()
92
+ })
93
+
94
+ test('tooltip behavior with overflowing content', () => {
95
+ // Mock the ref.current properties to simulate overflow
96
+ vi.spyOn(React, 'useRef').mockReturnValue({
97
+ current: {
98
+ scrollWidth: 100,
99
+ clientWidth: 50,
100
+ scrollHeight: 100,
101
+ clientHeight: 50,
102
+ },
103
+ } as React.RefObject<HTMLDivElement>)
104
+
105
+ render(<SmartTooltip {...defaultProps} />)
106
+
107
+ // Force the timeout in useLayoutEffect to execute
108
+ vi.runAllTimers()
109
+
110
+ // Verify the tooltip has the correct title
111
+ const tooltipWrapper = screen.getByTestId('test-child')
112
+ expect(tooltipWrapper).toBeTruthy()
113
+ })
114
+
115
+ test('tooltip behavior with non-overflowing content', () => {
116
+ // Mock the ref.current properties to simulate no overflow
117
+ vi.spyOn(React, 'useRef').mockReturnValue({
118
+ current: {
119
+ scrollWidth: 50,
120
+ clientWidth: 100,
121
+ scrollHeight: 50,
122
+ clientHeight: 100,
123
+ },
124
+ } as React.RefObject<HTMLDivElement>)
125
+
126
+ render(<SmartTooltip {...defaultProps} />)
127
+
128
+ // Force the timeout in useLayoutEffect to execute
129
+ vi.runAllTimers()
130
+
131
+ // Verify the tooltip doesn't have a title (since there's no overflow)
132
+ const tooltipWrapper = screen.getByTestId('test-child')
133
+ expect(tooltipWrapper).toBeTruthy()
134
+ // Check that the title attribute is not set since content is not overflowing
135
+ expect(tooltipWrapper?.getAttribute('title')).toBeFalsy()
136
+ })
137
+
138
+ test('accepts custom timeout parameter', () => {
139
+ const customTimeout = 1000
140
+
141
+ // Spy on setTimeout to verify it's called with the custom timeout
142
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
143
+
144
+ render(<SmartTooltip {...defaultProps} timeout={customTimeout} />)
145
+
146
+ expect(setTimeoutSpy).toHaveBeenCalledWith(
147
+ expect.any(Function),
148
+ customTimeout,
149
+ )
150
+
151
+ setTimeoutSpy.mockRestore()
152
+ })
153
+
154
+ test('passes TooltipProps to the Tooltip component', () => {
155
+ render(
156
+ <SmartTooltip
157
+ {...defaultProps}
158
+ TooltipProps={{
159
+ placement: 'bottom',
160
+ arrow: true,
161
+ }}
162
+ />,
163
+ )
164
+
165
+ const tooltipWrapper = screen.getByTestId('test-child')
166
+ expect(tooltipWrapper).toBeTruthy()
167
+ })
168
+ })
@@ -0,0 +1,40 @@
1
+ import type { TooltipProps } from '@mui/material'
2
+ import { useLayoutEffect, useRef, useState, type JSX, type Ref } from 'react'
3
+ import { Tooltip } from '../tooltip/tooltip'
4
+
5
+ export function SmartTooltip<T extends HTMLElement>({
6
+ title,
7
+ dependencies = [],
8
+ timeout = 500,
9
+ TooltipProps,
10
+ children,
11
+ }: {
12
+ title: string | undefined
13
+ dependencies?: unknown[]
14
+ timeout?: number
15
+ children: (props: { ref: Ref<T> }) => JSX.Element
16
+ TooltipProps?: Partial<TooltipProps>
17
+ }) {
18
+ const ref = useRef<T>(null)
19
+ const [isOverflowing, setIsOverflowing] = useState(false)
20
+
21
+ useLayoutEffect(() => {
22
+ const timerId = setTimeout(() => {
23
+ if (ref.current) {
24
+ const isOverflowing =
25
+ ref.current.scrollWidth > ref.current.clientWidth ||
26
+ ref.current.scrollHeight > ref.current.clientHeight
27
+
28
+ setIsOverflowing(isOverflowing)
29
+ }
30
+ }, timeout)
31
+
32
+ return () => clearTimeout(timerId)
33
+ }, [dependencies, timeout])
34
+
35
+ return (
36
+ <Tooltip title={isOverflowing && title} {...TooltipProps}>
37
+ {children({ ref })}
38
+ </Tooltip>
39
+ )
40
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { Tooltip, setTooltipEnterDelay } from './tooltip'
4
+
5
+ describe('Tooltip', () => {
6
+ test('renders children correctly', () => {
7
+ render(
8
+ <Tooltip title='Test tooltip'>
9
+ <button>Hover me</button>
10
+ </Tooltip>,
11
+ )
12
+
13
+ expect(screen.getByText('Hover me')).toBeTruthy()
14
+ })
15
+
16
+ test('uses default enterNextDelay of 1000ms', () => {
17
+ render(
18
+ <Tooltip title='Test tooltip'>
19
+ <button>Hover me</button>
20
+ </Tooltip>,
21
+ )
22
+
23
+ // Since we're wrapping MUI Tooltip and not altering its behavior much,
24
+ // and MUI applies most props internally, we can't easily test the
25
+ // enterNextDelay prop directly. Instead, we verify the component renders.
26
+ expect(screen.getByText('Hover me')).toBeTruthy()
27
+ })
28
+
29
+ test('accepts custom enterNextDelay', () => {
30
+ render(
31
+ <Tooltip title='Test tooltip' enterNextDelay={500}>
32
+ <button>Hover me</button>
33
+ </Tooltip>,
34
+ )
35
+
36
+ // Similar to above test, we mainly ensure the component renders correctly
37
+ expect(screen.getByText('Hover me')).toBeTruthy()
38
+ })
39
+
40
+ test('passes additional props to MUI Tooltip', () => {
41
+ render(
42
+ <Tooltip
43
+ title='Test tooltip'
44
+ placement='bottom'
45
+ arrow={true}
46
+ data-testid='custom-tooltip'
47
+ >
48
+ <button>Hover me</button>
49
+ </Tooltip>,
50
+ )
51
+
52
+ // Verify that our wrapper properly passes through props
53
+ const tooltipWrapper = screen
54
+ .getByText('Hover me')
55
+ .closest('[data-testid="custom-tooltip"]')
56
+ expect(tooltipWrapper).toBeTruthy()
57
+ })
58
+
59
+ test('setTooltipEnterDelay function sets the delay', () => {
60
+ const delay = 700
61
+ setTooltipEnterDelay(delay)
62
+
63
+ // We can't test the internal implementation, but we can ensure that
64
+ // the function can be called without errors.
65
+ expect(() => setTooltipEnterDelay(delay)).not.toThrow()
66
+ })
67
+
68
+ test('setTooltipEnterDelay updates the default enter delay', () => {
69
+ const newDelay = 2000
70
+ setTooltipEnterDelay(newDelay)
71
+
72
+ render(
73
+ <Tooltip title='Test tooltip'>
74
+ <button>Hover me</button>
75
+ </Tooltip>,
76
+ )
77
+
78
+ // After setting a new delay, the component should use that as the default
79
+ // We can verify by checking the props passed to the rendered component
80
+ const tooltipElement = screen.getByText('Hover me').parentElement
81
+ expect(tooltipElement).toBeTruthy()
82
+
83
+ // Reset the delay for other tests
84
+ setTooltipEnterDelay(1000)
85
+ })
86
+ })
@@ -0,0 +1,30 @@
1
+ import { Tooltip as MuiTooltip, type TooltipProps } from '@mui/material'
2
+
3
+ let tooltipEnterDelay = 500
4
+
5
+ /**
6
+ * A wrapper around MUI's Tooltip component with enterNextDelay set to 1000ms by default.
7
+ *
8
+ * This component ensures consistent tooltip behavior across the application.
9
+ *
10
+ * @example
11
+ * <Tooltip title="This is a tooltip">
12
+ * <Button>Hover me</Button>
13
+ * </Tooltip>
14
+ */
15
+ export function Tooltip({
16
+ children,
17
+ title,
18
+ enterNextDelay = tooltipEnterDelay,
19
+ ...props
20
+ }: TooltipProps) {
21
+ return (
22
+ <MuiTooltip title={title} enterNextDelay={enterNextDelay} {...props}>
23
+ {children}
24
+ </MuiTooltip>
25
+ )
26
+ }
27
+
28
+ export function setTooltipEnterDelay(delay: number) {
29
+ tooltipEnterDelay = delay
30
+ }
@@ -0,0 +1 @@
1
+ export * from './common-types'
@@ -0,0 +1,27 @@
1
+ import type { SxProps, Theme } from '@mui/material'
2
+
3
+ export const styles = {
4
+ zoom: {
5
+ position: 'relative',
6
+ height: ({ spacing }) => spacing(4),
7
+ aspectRatio: 1,
8
+ display: 'flex',
9
+ alignItems: 'center',
10
+ justifyContent: 'center',
11
+ },
12
+ paper: {
13
+ display: 'flex',
14
+ backgroundColor: ({ palette }) => palette.background.paper,
15
+ width: 'auto',
16
+ overflow: 'hidden',
17
+ boxShadow: ({ shadows }) => shadows[1],
18
+ borderRadius: ({ spacing }) => spacing(0.5),
19
+
20
+ button: {
21
+ borderRadius: 0,
22
+ },
23
+ },
24
+ circularProgress: {
25
+ position: 'absolute',
26
+ },
27
+ } satisfies Record<string, SxProps<Theme>>
@@ -0,0 +1,19 @@
1
+ import type { Paper } from '@mui/material'
2
+ import type { ComponentProps, ElementType } from 'react'
3
+
4
+ export interface ZoomControlProps {
5
+ zoom: number
6
+ direction?: 'vertical' | 'horizontal'
7
+ reverse?: boolean
8
+ disabled?: boolean
9
+ isLoading?: boolean
10
+ maxZoom?: number
11
+ minZoom?: number
12
+ PaperProps?: ComponentProps<typeof Paper>
13
+ ResetViewProps?: {
14
+ Icon: ElementType
15
+ }
16
+ showZoom?: boolean
17
+ onChange: (zoom: number) => void
18
+ onReset?: () => void
19
+ }