@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,45 @@
1
+ import type { EChartsOption } from 'echarts'
2
+ import type * as echarts from 'echarts'
3
+ import type { BaseWidgetState } from '../stores/types'
4
+ import type { Ref } from 'react'
5
+ import { theme as CartoTheme } from '@carto/meridian-ds/theme'
6
+
7
+ export type EchartOptionsProps = EChartsOption
8
+ export type EchartReplaceMerge = string[]
9
+
10
+ export interface EchartUIProps {
11
+ id: string
12
+ option: EchartOptionsProps
13
+ className?: string
14
+ init?: echarts.EChartsInitOpts
15
+ replaceMerge?: EchartReplaceMerge
16
+ style?: React.CSSProperties
17
+ ref?: Ref<echarts.ECharts>
18
+ onEvents?: Record<string, Parameters<echarts.ECharts['on']>[2]>
19
+ }
20
+
21
+ export interface EchartProps {
22
+ id: EchartUIProps['id']
23
+ }
24
+
25
+ export type EchartWidgetData = Record<string, string | number>[][]
26
+
27
+ export type EchartWidgetState = BaseWidgetState<{
28
+ option: EchartUIProps['option']
29
+ onEvents?: EchartUIProps['onEvents']
30
+ init?: EchartUIProps['init']
31
+ replaceMerge?: EchartReplaceMerge
32
+ }>
33
+
34
+ export interface EchartWidgetOptionProps<D> {
35
+ data?: D
36
+ theme: typeof CartoTheme
37
+ formatter?: (value: number) => string
38
+ }
39
+
40
+ export interface EchartWidgetProps {
41
+ type: string
42
+ option: EchartUIProps['option']
43
+ onEvents?: EchartUIProps['onEvents']
44
+ replaceMerge?: EchartReplaceMerge
45
+ }
@@ -0,0 +1,169 @@
1
+ import deepmerge from 'deepmerge'
2
+ import type { EchartOptionsProps } from './types'
3
+ import type { Theme } from '@mui/material'
4
+ import { DEFAULT_STACK_GROUP } from './const'
5
+
6
+ export function mergeEchartWidgetConfig<T extends EchartOptionsProps>(
7
+ ...options: [T | undefined, T | undefined]
8
+ ): T {
9
+ return deepmerge(options[0] ?? {}, options[1] ?? {}, {
10
+ customMerge: (key) => {
11
+ if (key === 'color') {
12
+ return (_, b: T['color']) => b
13
+ }
14
+ if (key === 'series') {
15
+ return (a: T['series'][], b: T['series'][]) => {
16
+ if (!a.length) return a // If there is no series in a, return a as is
17
+
18
+ const mergedSeries = b?.map((bItem, index) => {
19
+ const aItem = a?.[index] ?? {}
20
+ return deepmerge(aItem, bItem as object)
21
+ })
22
+ return mergedSeries
23
+ }
24
+ }
25
+ },
26
+ }) as T
27
+ }
28
+
29
+ export function getEChartZoomConfig(
30
+ zoom: boolean,
31
+ { start, end }: { start: number; end: number } = { start: 0, end: 100 },
32
+ {
33
+ inside = true,
34
+ xSlider = true,
35
+ ySlider = false,
36
+ showSliders = true,
37
+ xAxisLabelFormatter,
38
+ } = {} as {
39
+ inside?: boolean
40
+ xSlider?: boolean
41
+ ySlider?: boolean
42
+ showSliders?: boolean
43
+ xAxisLabelFormatter?: (value: number) => string
44
+ },
45
+ theme?: Theme,
46
+ ) {
47
+ const sliderStyles = theme ? getEChartZoomSliderStyles(theme) : {}
48
+
49
+ return {
50
+ dataZoom: [
51
+ inside && {
52
+ throttle: 0,
53
+ type: 'inside',
54
+ xAxisIndex: xSlider ? [0] : [],
55
+ yAxisIndex: ySlider ? [0] : [],
56
+ show: zoom,
57
+ zoomLock: !zoom,
58
+ start,
59
+ end,
60
+ },
61
+ inside &&
62
+ ySlider && {
63
+ throttle: 0,
64
+ type: 'inside',
65
+ show: zoom,
66
+ zoomLock: !zoom,
67
+ start,
68
+ end,
69
+ orientation: 'vertical',
70
+ },
71
+ xSlider && {
72
+ throttle: 0,
73
+ type: 'slider',
74
+ xAxisIndex: [0],
75
+ bottom: 0,
76
+ height: parseInt(theme?.spacing?.(4) ?? '32'),
77
+ show: zoom && showSliders,
78
+ zoomLock: !zoom,
79
+ start,
80
+ end,
81
+ labelFormatter: xAxisLabelFormatter,
82
+ showDetail: false,
83
+ ...sliderStyles,
84
+ brushSelect: false,
85
+ moveHandleSize: 8,
86
+ handleSize: '100%',
87
+ handleIcon:
88
+ 'image://data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSIzNiIgdmlld0JveD0iMCAwIDkgMzYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNSIgeT0iLTAuNSIgd2lkdGg9IjgiIGhlaWdodD0iMTgiIHJ4PSI0IiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAwIDI3KSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzM1OEJFNyIvPgo8cmVjdCB3aWR0aD0iMyIgaGVpZ2h0PSIyIiByeD0iMSIgdHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMyAyMykiIGZpbGw9IiMzNThCRTciLz4KPHJlY3Qgd2lkdGg9IjMiIGhlaWdodD0iMiIgcng9IjEiIHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDMgMTkpIiBmaWxsPSIjMzU4QkU3Ii8+CjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHJ4PSIxIiB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAzIDE1KSIgZmlsbD0iIzM1OEJFNyIvPgo8L3N2Zz4=',
89
+ },
90
+ ySlider && {
91
+ throttle: 0,
92
+ type: 'slider',
93
+ left: parseInt(theme?.spacing?.(6) ?? '48'),
94
+ width: parseInt(theme?.spacing?.(4) ?? '32'),
95
+ yAxisIndex: [0],
96
+ show: zoom && showSliders,
97
+ zoomLock: !zoom,
98
+ start,
99
+ end,
100
+ ...sliderStyles,
101
+ brushSelect: false,
102
+ moveHandleSize: 8,
103
+ handleSize: '100%',
104
+ handleIcon:
105
+ 'image://data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iOSIgdmlld0JveD0iMCAwIDM2IDkiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNSIgeT0iMC41IiB3aWR0aD0iOCIgaGVpZ2h0PSIxOCIgcng9IjQiIHRyYW5zZm9ybT0icm90YXRlKC05MCAwLjUgMC41KSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzM1OEJFNyIvPgo8cmVjdCB3aWR0aD0iMyIgaGVpZ2h0PSIyIiByeD0iMSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDEzIDYpIiBmaWxsPSIjMzU4QkU3Ii8+CjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSgtOTAgMTcgNikiIGZpbGw9IiMzNThCRTciLz4KPHJlY3Qgd2lkdGg9IjMiIGhlaWdodD0iMiIgcng9IjEiIHRyYW5zZm9ybT0icm90YXRlKC05MCAyMSA2KSIgZmlsbD0iIzM1OEJFNyIvPgo8L3N2Zz4=',
106
+ },
107
+ ].filter((d) => !!d),
108
+ }
109
+ }
110
+
111
+ function getEChartZoomSliderStyles(theme: Theme) {
112
+ return {
113
+ fillerColor: 'rgba(53, 139, 231, 0.25)',
114
+ borderColor: 'rgba(53, 139, 231, 0.3)',
115
+ borderWidth: 0.5,
116
+ backgroundColor: 'transparent',
117
+ borderRadius: 4,
118
+ dataBackground: {
119
+ lineStyle: {
120
+ opacity: 0,
121
+ },
122
+ areaStyle: {
123
+ opacity: 1,
124
+ color: theme.palette.secondary.main,
125
+ },
126
+ },
127
+ selectedDataBackground: {
128
+ lineStyle: {
129
+ opacity: 0,
130
+ },
131
+ areaStyle: {
132
+ opacity: 1,
133
+ color: theme.palette.secondary.main,
134
+ },
135
+ },
136
+ handleStyle: {
137
+ color: theme.palette.common.white,
138
+ borderColor: 'rgba(3, 111, 226, 0.08)',
139
+ borderWidth: 1,
140
+ shadowBlur: 3,
141
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
142
+ shadowOffsetX: 0,
143
+ shadowOffsetY: 1,
144
+ },
145
+ textStyle: {
146
+ color: theme.palette.black[60],
147
+ fontSize: parseInt(theme.typography.overlineDelicate.fontSize as string),
148
+ fontFamily: theme.typography.overlineDelicate.fontFamily,
149
+ },
150
+ } as const
151
+ }
152
+
153
+ export function getEChartBrushConfig(brush: boolean) {
154
+ return brush
155
+ ? {
156
+ brush: {
157
+ brushType: 'rect',
158
+ brushMode: 'single',
159
+ },
160
+ }
161
+ : {}
162
+ }
163
+
164
+ export function getEChartStackConfig(
165
+ stack: boolean,
166
+ stackGroup: string = DEFAULT_STACK_GROUP,
167
+ ) {
168
+ return { stack: stack ? stackGroup : undefined }
169
+ }
@@ -0,0 +1,331 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { beforeEach, describe, expect, test } from 'vitest'
3
+ import { WidgetError } from './error'
4
+ import { useWidgetStore } from '../stores/widget-store'
5
+
6
+ describe('WidgetError', () => {
7
+ const widgetId = 'test-error-widget'
8
+
9
+ beforeEach(() => {
10
+ useWidgetStore.getState().clearWidgets()
11
+ })
12
+
13
+ describe('when error exists', () => {
14
+ test('renders Error UI with default title and message when error is empty object', () => {
15
+ useWidgetStore.getState().setWidget(widgetId, {
16
+ isLoading: false,
17
+ isFetching: false,
18
+ error: {},
19
+ })
20
+
21
+ render(
22
+ <WidgetError id={widgetId}>
23
+ <div>Widget Content</div>
24
+ </WidgetError>,
25
+ )
26
+
27
+ expect(screen.getByText('Error')).toBeTruthy()
28
+ expect(
29
+ screen.getByText(
30
+ 'An error occurred while loading the widget. Please try again.',
31
+ ),
32
+ ).toBeTruthy()
33
+ expect(screen.queryByText('Widget Content')).toBeNull()
34
+ })
35
+
36
+ test('renders custom title with default message', () => {
37
+ useWidgetStore.getState().setWidget(widgetId, {
38
+ isLoading: false,
39
+ isFetching: false,
40
+ error: { title: 'Database Connection Failed' },
41
+ })
42
+
43
+ render(
44
+ <WidgetError id={widgetId}>
45
+ <div>Widget Content</div>
46
+ </WidgetError>,
47
+ )
48
+
49
+ expect(screen.getByText('Database Connection Failed')).toBeTruthy()
50
+ expect(
51
+ screen.getByText(
52
+ 'An error occurred while loading the widget. Please try again.',
53
+ ),
54
+ ).toBeTruthy()
55
+ expect(screen.queryByText('Widget Content')).toBeNull()
56
+ })
57
+
58
+ test('renders default title with custom message', () => {
59
+ useWidgetStore.getState().setWidget(widgetId, {
60
+ isLoading: false,
61
+ isFetching: false,
62
+ error: { message: 'The server returned a 500 error.' },
63
+ })
64
+
65
+ render(
66
+ <WidgetError id={widgetId}>
67
+ <div>Widget Content</div>
68
+ </WidgetError>,
69
+ )
70
+
71
+ expect(screen.getByText('Error')).toBeTruthy()
72
+ expect(screen.getByText('The server returned a 500 error.')).toBeTruthy()
73
+ expect(screen.queryByText('Widget Content')).toBeNull()
74
+ })
75
+
76
+ test('renders both custom title and message', () => {
77
+ useWidgetStore.getState().setWidget(widgetId, {
78
+ isLoading: false,
79
+ isFetching: false,
80
+ error: {
81
+ title: 'Network Error',
82
+ message:
83
+ 'Unable to connect to the API. Check your internet connection.',
84
+ },
85
+ })
86
+
87
+ render(
88
+ <WidgetError id={widgetId}>
89
+ <div>Widget Content</div>
90
+ </WidgetError>,
91
+ )
92
+
93
+ expect(screen.getByText('Network Error')).toBeTruthy()
94
+ expect(
95
+ screen.getByText(
96
+ 'Unable to connect to the API. Check your internet connection.',
97
+ ),
98
+ ).toBeTruthy()
99
+ expect(screen.queryByText('Widget Content')).toBeNull()
100
+ })
101
+
102
+ test('uses defaults when title and message are empty strings', () => {
103
+ useWidgetStore.getState().setWidget(widgetId, {
104
+ isLoading: false,
105
+ isFetching: false,
106
+ error: {
107
+ title: 'Error',
108
+ message:
109
+ 'An error occurred while loading the widget. Please try again.',
110
+ },
111
+ })
112
+
113
+ render(
114
+ <WidgetError id={widgetId}>
115
+ <div>Widget Content</div>
116
+ </WidgetError>,
117
+ )
118
+
119
+ expect(screen.getByText('Error')).toBeTruthy()
120
+ expect(
121
+ screen.getByText(
122
+ 'An error occurred while loading the widget. Please try again.',
123
+ ),
124
+ ).toBeTruthy()
125
+ expect(screen.queryByText('Widget Content')).toBeNull()
126
+ })
127
+ })
128
+
129
+ describe('when no error exists', () => {
130
+ test('renders children when error is undefined', () => {
131
+ useWidgetStore.getState().setWidget(widgetId, {
132
+ isLoading: false,
133
+ isFetching: false,
134
+ error: undefined,
135
+ })
136
+
137
+ render(
138
+ <WidgetError id={widgetId}>
139
+ <div>Widget Content</div>
140
+ </WidgetError>,
141
+ )
142
+
143
+ expect(screen.getByText('Widget Content')).toBeTruthy()
144
+ expect(screen.queryByText('Error')).toBeNull()
145
+ })
146
+
147
+ test('renders children when widget has no error property', () => {
148
+ useWidgetStore.getState().setWidget(widgetId, {
149
+ isLoading: false,
150
+ isFetching: false,
151
+ })
152
+
153
+ render(
154
+ <WidgetError id={widgetId}>
155
+ <div>Widget Content</div>
156
+ </WidgetError>,
157
+ )
158
+
159
+ expect(screen.getByText('Widget Content')).toBeTruthy()
160
+ expect(screen.queryByText('Error')).toBeNull()
161
+ })
162
+ })
163
+
164
+ describe('loading states', () => {
165
+ test('renders children when isLoading is true, even with error', () => {
166
+ useWidgetStore.getState().setWidget(widgetId, {
167
+ isLoading: true,
168
+ isFetching: false,
169
+ error: { title: 'Some error' },
170
+ })
171
+
172
+ render(
173
+ <WidgetError id={widgetId}>
174
+ <div>Widget Content</div>
175
+ </WidgetError>,
176
+ )
177
+
178
+ expect(screen.getByText('Widget Content')).toBeTruthy()
179
+ expect(screen.queryByText('Error')).toBeNull()
180
+ })
181
+
182
+ test('renders children when isFetching is true, even with error', () => {
183
+ useWidgetStore.getState().setWidget(widgetId, {
184
+ isLoading: false,
185
+ isFetching: true,
186
+ error: { title: 'Some error' },
187
+ })
188
+
189
+ render(
190
+ <WidgetError id={widgetId}>
191
+ <div>Widget Content</div>
192
+ </WidgetError>,
193
+ )
194
+
195
+ expect(screen.getByText('Widget Content')).toBeTruthy()
196
+ expect(screen.queryByText('Error')).toBeNull()
197
+ })
198
+
199
+ test('renders children when both isLoading and isFetching are true, even with error', () => {
200
+ useWidgetStore.getState().setWidget(widgetId, {
201
+ isLoading: true,
202
+ isFetching: true,
203
+ error: { title: 'Some error' },
204
+ })
205
+
206
+ render(
207
+ <WidgetError id={widgetId}>
208
+ <div>Widget Content</div>
209
+ </WidgetError>,
210
+ )
211
+
212
+ expect(screen.getByText('Widget Content')).toBeTruthy()
213
+ expect(screen.queryByText('Error')).toBeNull()
214
+ })
215
+ })
216
+
217
+ describe('reactivity', () => {
218
+ test('updates when error appears', () => {
219
+ useWidgetStore.getState().setWidget(widgetId, {
220
+ isLoading: false,
221
+ isFetching: false,
222
+ error: undefined,
223
+ })
224
+
225
+ const { rerender } = render(
226
+ <WidgetError id={widgetId}>
227
+ <div>Widget Content</div>
228
+ </WidgetError>,
229
+ )
230
+
231
+ expect(screen.getByText('Widget Content')).toBeTruthy()
232
+
233
+ // Add error
234
+ useWidgetStore.getState().setWidget(widgetId, {
235
+ isLoading: false,
236
+ isFetching: false,
237
+ error: { title: 'Failed to load' },
238
+ })
239
+
240
+ rerender(
241
+ <WidgetError id={widgetId}>
242
+ <div>Widget Content</div>
243
+ </WidgetError>,
244
+ )
245
+
246
+ expect(screen.getByText('Failed to load')).toBeTruthy()
247
+ expect(screen.queryByText('Widget Content')).toBeNull()
248
+ })
249
+
250
+ test('updates when error clears', () => {
251
+ useWidgetStore.getState().setWidget(widgetId, {
252
+ isLoading: false,
253
+ isFetching: false,
254
+ error: { title: 'Failed to load' },
255
+ })
256
+
257
+ const { rerender } = render(
258
+ <WidgetError id={widgetId}>
259
+ <div>Widget Content</div>
260
+ </WidgetError>,
261
+ )
262
+
263
+ expect(screen.getByText('Failed to load')).toBeTruthy()
264
+
265
+ // Clear error
266
+ useWidgetStore.getState().setWidget(widgetId, {
267
+ isLoading: false,
268
+ isFetching: false,
269
+ error: undefined,
270
+ })
271
+
272
+ rerender(
273
+ <WidgetError id={widgetId}>
274
+ <div>Widget Content</div>
275
+ </WidgetError>,
276
+ )
277
+
278
+ expect(screen.getByText('Widget Content')).toBeTruthy()
279
+ expect(screen.queryByText('Failed to load')).toBeNull()
280
+ })
281
+
282
+ test('transitions from loading to error to success', () => {
283
+ // Start loading
284
+ useWidgetStore.getState().setWidget(widgetId, {
285
+ isLoading: true,
286
+ isFetching: false,
287
+ error: undefined,
288
+ })
289
+
290
+ const { rerender } = render(
291
+ <WidgetError id={widgetId}>
292
+ <div>Widget Content</div>
293
+ </WidgetError>,
294
+ )
295
+
296
+ expect(screen.getByText('Widget Content')).toBeTruthy()
297
+
298
+ // Error occurs
299
+ useWidgetStore.getState().setWidget(widgetId, {
300
+ isLoading: false,
301
+ isFetching: false,
302
+ error: { title: 'Network error' },
303
+ })
304
+
305
+ rerender(
306
+ <WidgetError id={widgetId}>
307
+ <div>Widget Content</div>
308
+ </WidgetError>,
309
+ )
310
+
311
+ expect(screen.getByText('Network error')).toBeTruthy()
312
+ expect(screen.queryByText('Widget Content')).toBeNull()
313
+
314
+ // Error clears on retry
315
+ useWidgetStore.getState().setWidget(widgetId, {
316
+ isLoading: false,
317
+ isFetching: false,
318
+ error: undefined,
319
+ })
320
+
321
+ rerender(
322
+ <WidgetError id={widgetId}>
323
+ <div>Widget Content</div>
324
+ </WidgetError>,
325
+ )
326
+
327
+ expect(screen.getByText('Widget Content')).toBeTruthy()
328
+ expect(screen.queryByText('Network error')).toBeNull()
329
+ })
330
+ })
331
+ })
@@ -0,0 +1,40 @@
1
+ import { Alert, AlertTitle } from '@mui/material'
2
+ import { useWidgetStore } from '../stores/widget-store'
3
+ import { useShallow } from 'zustand/shallow'
4
+ import type { WidgetErrorProps } from './types'
5
+
6
+ export function WidgetError({ id, children }: WidgetErrorProps) {
7
+ const widget = useWidgetStore(
8
+ useShallow((state) => {
9
+ const w = state.widgets[id]
10
+ return {
11
+ isLoading: w?.isLoading,
12
+ isFetching: w?.isFetching,
13
+ error: w?.error,
14
+ }
15
+ }),
16
+ )
17
+
18
+ // Don't show error during loading/fetching states
19
+ if (widget?.isLoading || widget?.isFetching) {
20
+ return children
21
+ }
22
+
23
+ // Show error UI if error exists
24
+ if (widget?.error) {
25
+ const errorTitle = widget.error.title ?? 'Error'
26
+ const errorMessage =
27
+ widget.error.message ??
28
+ 'An error occurred while loading the widget. Please try again.'
29
+
30
+ return (
31
+ <Alert severity='error'>
32
+ <AlertTitle>{errorTitle}</AlertTitle>
33
+ {errorMessage}
34
+ </Alert>
35
+ )
36
+ }
37
+
38
+ // No error, render children
39
+ return children
40
+ }
@@ -0,0 +1,2 @@
1
+ export { WidgetError } from './error'
2
+ export type { WidgetErrorProps } from './types'
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react'
2
+ import type { WidgetState } from '../stores/types'
3
+
4
+ export interface WidgetErrorProps {
5
+ /**
6
+ * Widget ID to fetch error state from store
7
+ */
8
+ id: WidgetState['id']
9
+
10
+ /**
11
+ * Children to render when no error exists
12
+ */
13
+ children: ReactNode
14
+ }