@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.
- package/dist/components.js +3 -3
- package/dist/components.js.map +1 -1
- package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
- package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
- package/dist/types/components/common-types.d.ts +41 -0
- package/dist/types/components/types.d.ts +1 -1
- package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
- package/dist/types/widgets/echart/types.d.ts +4 -0
- package/dist/widgets/actions.js +1 -1
- package/dist/widgets/bar.js +1 -1
- package/dist/widgets/category.js +1 -1
- package/dist/widgets/echart.js +96 -85
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +1 -1
- package/dist/widgets/histogram.js +1 -1
- package/dist/widgets/markdown.js +1 -1
- package/dist/widgets/pie.js +1 -1
- package/dist/widgets/scatterplot.js +1 -1
- package/dist/widgets/spread.js +1 -1
- package/dist/widgets/table.js +1 -1
- package/dist/widgets/timeseries.js +1 -1
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/dist/widgets/wrapper.js +1 -1
- package/package.json +8 -3
- package/src/components/basemaps/basemaps.test.tsx +196 -0
- package/src/components/basemaps/basemaps.tsx +128 -0
- package/src/components/basemaps/const.ts +13 -0
- package/src/components/basemaps/group-wrapper.test.tsx +38 -0
- package/src/components/basemaps/group-wrapper.tsx +28 -0
- package/src/components/basemaps/group.test.tsx +52 -0
- package/src/components/basemaps/group.tsx +42 -0
- package/src/components/basemaps/header.test.tsx +54 -0
- package/src/components/basemaps/header.tsx +36 -0
- package/src/components/basemaps/styles.ts +76 -0
- package/src/components/basemaps/types.ts +30 -0
- package/src/components/common-types.ts +1 -0
- package/src/components/geolocation-controls/const.ts +6 -0
- package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
- package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
- package/src/components/geolocation-controls/types.ts +17 -0
- package/src/components/index.ts +64 -0
- package/src/components/lasso-tool/chip.tsx +37 -0
- package/src/components/lasso-tool/const.tsx +70 -0
- package/src/components/lasso-tool/icons.tsx +91 -0
- package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
- package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
- package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
- package/src/components/lasso-tool/lasso-tool.tsx +479 -0
- package/src/components/lasso-tool/styles.ts +143 -0
- package/src/components/lasso-tool/types.ts +114 -0
- package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
- package/src/components/list-data/list-data-skeleton.tsx +40 -0
- package/src/components/list-data/list-data.test.tsx +94 -0
- package/src/components/list-data/list-data.tsx +106 -0
- package/src/components/list-data/styles.ts +37 -0
- package/src/components/list-data/types.ts +25 -0
- package/src/components/measurement-tools/const.tsx +108 -0
- package/src/components/measurement-tools/icons.tsx +54 -0
- package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
- package/src/components/measurement-tools/measurement-tools.tsx +443 -0
- package/src/components/measurement-tools/styles.ts +91 -0
- package/src/components/measurement-tools/types.ts +77 -0
- package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
- package/src/components/no-data-alert/no-data-alert.tsx +59 -0
- package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
- package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
- package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
- package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
- package/src/components/tooltip/tooltip.test.tsx +86 -0
- package/src/components/tooltip/tooltip.tsx +30 -0
- package/src/components/types.ts +1 -0
- package/src/components/zoom-controls/styles.ts +27 -0
- package/src/components/zoom-controls/types.ts +19 -0
- package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
- package/src/components/zoom-controls/zoom-controls.tsx +114 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-debounce.ts +55 -0
- package/src/hooks/use-skeleton.test.tsx +32 -0
- package/src/hooks/use-skeleton.ts +19 -0
- package/src/hooks/use-widget-ref.ts +33 -0
- package/src/widgets/README.md +277 -0
- package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
- package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
- package/src/widgets/_shared/chart-config/index.ts +21 -0
- package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
- package/src/widgets/_shared/skeleton/index.ts +5 -0
- package/src/widgets/_shared/skeleton/styles.ts +20 -0
- package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
- package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
- package/src/widgets/actions/change-column/change-column.tsx +141 -0
- package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
- package/src/widgets/actions/change-column/types.ts +20 -0
- package/src/widgets/actions/download/download.test.tsx +322 -0
- package/src/widgets/actions/download/download.tsx +118 -0
- package/src/widgets/actions/download/exports.test.tsx +275 -0
- package/src/widgets/actions/download/exports.tsx +103 -0
- package/src/widgets/actions/download/types.ts +21 -0
- package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
- package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
- package/src/widgets/actions/fullscreen/styles.ts +17 -0
- package/src/widgets/actions/fullscreen/types.ts +27 -0
- package/src/widgets/actions/index.ts +51 -0
- package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
- package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
- package/src/widgets/actions/lock-selection/types.ts +41 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
- package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
- package/src/widgets/actions/relative-data/style.ts +9 -0
- package/src/widgets/actions/relative-data/types.ts +31 -0
- package/src/widgets/actions/relative-data/utils.test.ts +223 -0
- package/src/widgets/actions/relative-data/utils.ts +58 -0
- package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
- package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
- package/src/widgets/actions/searcher/searcher.tsx +205 -0
- package/src/widgets/actions/searcher/types.ts +72 -0
- package/src/widgets/actions/shared/styles.ts +12 -0
- package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
- package/src/widgets/actions/stack-toggle/types.ts +29 -0
- package/src/widgets/actions/zoom-toggle/index.ts +2 -0
- package/src/widgets/actions/zoom-toggle/style.ts +14 -0
- package/src/widgets/actions/zoom-toggle/types.ts +44 -0
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
- package/src/widgets/bar/config.ts +122 -0
- package/src/widgets/bar/index.ts +9 -0
- package/src/widgets/bar/skeleton.tsx +60 -0
- package/src/widgets/bar/style.ts +33 -0
- package/src/widgets/bar/types.ts +16 -0
- package/src/widgets/category/category-ui.test.tsx +399 -0
- package/src/widgets/category/category-ui.tsx +156 -0
- package/src/widgets/category/components/category-bar.tsx +28 -0
- package/src/widgets/category/components/category-legend.tsx +30 -0
- package/src/widgets/category/components/category-row-multi.tsx +50 -0
- package/src/widgets/category/components/category-row-other.tsx +23 -0
- package/src/widgets/category/components/category-row-single.tsx +47 -0
- package/src/widgets/category/components/index.ts +14 -0
- package/src/widgets/category/config.ts +85 -0
- package/src/widgets/category/index.ts +30 -0
- package/src/widgets/category/skeleton.tsx +24 -0
- package/src/widgets/category/style.ts +133 -0
- package/src/widgets/category/types.ts +40 -0
- package/src/widgets/echart/const.ts +1 -0
- package/src/widgets/echart/echart-ui.test.tsx +537 -0
- package/src/widgets/echart/echart-ui.tsx +92 -0
- package/src/widgets/echart/echart.test.tsx +562 -0
- package/src/widgets/echart/echart.tsx +68 -0
- package/src/widgets/echart/index.ts +16 -0
- package/src/widgets/echart/options.ts +53 -0
- package/src/widgets/echart/types.ts +45 -0
- package/src/widgets/echart/utils.ts +169 -0
- package/src/widgets/error/error.test.tsx +331 -0
- package/src/widgets/error/error.tsx +40 -0
- package/src/widgets/error/index.ts +2 -0
- package/src/widgets/error/types.ts +14 -0
- package/src/widgets/formula/components/item.test.tsx +249 -0
- package/src/widgets/formula/components/item.tsx +18 -0
- package/src/widgets/formula/components/prefix.test.tsx +341 -0
- package/src/widgets/formula/components/prefix.tsx +18 -0
- package/src/widgets/formula/components/row.test.tsx +364 -0
- package/src/widgets/formula/components/row.tsx +21 -0
- package/src/widgets/formula/components/series.tsx +34 -0
- package/src/widgets/formula/components/suffix.test.tsx +383 -0
- package/src/widgets/formula/components/suffix.tsx +28 -0
- package/src/widgets/formula/components/value.test.tsx +329 -0
- package/src/widgets/formula/components/value.tsx +29 -0
- package/src/widgets/formula/config.ts +27 -0
- package/src/widgets/formula/formula-ui.test.tsx +399 -0
- package/src/widgets/formula/formula-ui.tsx +27 -0
- package/src/widgets/formula/index.ts +24 -0
- package/src/widgets/formula/serializer.test.tsx +144 -0
- package/src/widgets/formula/serializer.ts +28 -0
- package/src/widgets/formula/skeleton.tsx +10 -0
- package/src/widgets/formula/style.ts +23 -0
- package/src/widgets/formula/types.ts +50 -0
- package/src/widgets/histogram/config.ts +143 -0
- package/src/widgets/histogram/index.ts +8 -0
- package/src/widgets/histogram/skeleton.tsx +52 -0
- package/src/widgets/histogram/style.ts +8 -0
- package/src/widgets/histogram/types.ts +17 -0
- package/src/widgets/index.ts +25 -0
- package/src/widgets/loader/index.ts +4 -0
- package/src/widgets/loader/loader.tsx +70 -0
- package/src/widgets/loader/types.ts +11 -0
- package/src/widgets/loader/utils.test.ts +112 -0
- package/src/widgets/loader/utils.ts +35 -0
- package/src/widgets/markdown/config.ts +18 -0
- package/src/widgets/markdown/index.ts +14 -0
- package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
- package/src/widgets/markdown/markdown-ui.tsx +52 -0
- package/src/widgets/markdown/markdown.tsx +20 -0
- package/src/widgets/markdown/skeleton.tsx +12 -0
- package/src/widgets/markdown/style.ts +28 -0
- package/src/widgets/markdown/types.ts +28 -0
- package/src/widgets/no-data/index.ts +2 -0
- package/src/widgets/no-data/no-data.test.tsx +447 -0
- package/src/widgets/no-data/no-data.tsx +116 -0
- package/src/widgets/no-data/style.ts +18 -0
- package/src/widgets/no-data/types.ts +72 -0
- package/src/widgets/note/index.ts +2 -0
- package/src/widgets/note/note.test.tsx +391 -0
- package/src/widgets/note/note.tsx +114 -0
- package/src/widgets/note/style.ts +29 -0
- package/src/widgets/note/types.ts +9 -0
- package/src/widgets/pie/config.ts +177 -0
- package/src/widgets/pie/index.ts +8 -0
- package/src/widgets/pie/skeleton.tsx +70 -0
- package/src/widgets/pie/style.ts +8 -0
- package/src/widgets/pie/types.ts +16 -0
- package/src/widgets/range/components/range-item.tsx +213 -0
- package/src/widgets/range/config.ts +10 -0
- package/src/widgets/range/index.ts +16 -0
- package/src/widgets/range/range-ui.test.tsx +203 -0
- package/src/widgets/range/range-ui.tsx +11 -0
- package/src/widgets/range/serializer.test.ts +70 -0
- package/src/widgets/range/serializer.ts +27 -0
- package/src/widgets/range/skeleton.tsx +14 -0
- package/src/widgets/range/style.ts +37 -0
- package/src/widgets/range/types.ts +39 -0
- package/src/widgets/scatterplot/config.ts +138 -0
- package/src/widgets/scatterplot/index.ts +8 -0
- package/src/widgets/scatterplot/skeleton.tsx +59 -0
- package/src/widgets/scatterplot/style.ts +21 -0
- package/src/widgets/scatterplot/types.ts +17 -0
- package/src/widgets/selection-summary/index.ts +6 -0
- package/src/widgets/selection-summary/selection-summary.tsx +46 -0
- package/src/widgets/selection-summary/style.ts +10 -0
- package/src/widgets/selection-summary/types.ts +14 -0
- package/src/widgets/skeleton-loader/index.ts +2 -0
- package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
- package/src/widgets/skeleton-loader/types.ts +8 -0
- package/src/widgets/spread/components/max-value.tsx +29 -0
- package/src/widgets/spread/components/min-value.tsx +29 -0
- package/src/widgets/spread/components/separator.tsx +6 -0
- package/src/widgets/spread/config.ts +34 -0
- package/src/widgets/spread/index.ts +23 -0
- package/src/widgets/spread/skeleton.tsx +10 -0
- package/src/widgets/spread/spread-ui.test.tsx +368 -0
- package/src/widgets/spread/spread-ui.tsx +29 -0
- package/src/widgets/spread/style.ts +22 -0
- package/src/widgets/spread/types.ts +47 -0
- package/src/widgets/stores/index.ts +9 -0
- package/src/widgets/stores/types.ts +192 -0
- package/src/widgets/stores/widget-store.test.ts +601 -0
- package/src/widgets/stores/widget-store.ts +239 -0
- package/src/widgets/subheader/index.ts +3 -0
- package/src/widgets/subheader/style.ts +20 -0
- package/src/widgets/subheader/subheader.test.tsx +45 -0
- package/src/widgets/subheader/subheader.tsx +16 -0
- package/src/widgets/subheader/types.ts +11 -0
- package/src/widgets/table/components/cell-header.tsx +58 -0
- package/src/widgets/table/components/cell.tsx +80 -0
- package/src/widgets/table/components/index.ts +4 -0
- package/src/widgets/table/components/pagination-actions.tsx +67 -0
- package/src/widgets/table/components/pagination.tsx +41 -0
- package/src/widgets/table/components/row.tsx +60 -0
- package/src/widgets/table/config.ts +71 -0
- package/src/widgets/table/helpers.test.ts +244 -0
- package/src/widgets/table/helpers.ts +107 -0
- package/src/widgets/table/hooks/index.ts +7 -0
- package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
- package/src/widgets/table/hooks/use-pagination.ts +155 -0
- package/src/widgets/table/hooks/use-selection.test.ts +504 -0
- package/src/widgets/table/hooks/use-selection.ts +189 -0
- package/src/widgets/table/hooks/use-sort.test.ts +296 -0
- package/src/widgets/table/hooks/use-sort.ts +138 -0
- package/src/widgets/table/index.ts +53 -0
- package/src/widgets/table/serializer.ts +54 -0
- package/src/widgets/table/skeleton.tsx +48 -0
- package/src/widgets/table/style.ts +34 -0
- package/src/widgets/table/table-ui.tsx +64 -0
- package/src/widgets/table/types.ts +223 -0
- package/src/widgets/timeseries/config.ts +135 -0
- package/src/widgets/timeseries/index.ts +8 -0
- package/src/widgets/timeseries/skeleton.tsx +55 -0
- package/src/widgets/timeseries/style.ts +36 -0
- package/src/widgets/timeseries/types.ts +17 -0
- package/src/widgets/toolbar-actions/index.ts +6 -0
- package/src/widgets/toolbar-actions/styles.ts +38 -0
- package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
- package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
- package/src/widgets/toolbar-actions/types.ts +60 -0
- package/src/widgets/wrapper/components/actions.test.tsx +101 -0
- package/src/widgets/wrapper/components/actions.tsx +30 -0
- package/src/widgets/wrapper/components/options.test.tsx +323 -0
- package/src/widgets/wrapper/components/options.tsx +73 -0
- package/src/widgets/wrapper/components/title.test.tsx +126 -0
- package/src/widgets/wrapper/components/title.tsx +32 -0
- package/src/widgets/wrapper/index.ts +16 -0
- package/src/widgets/wrapper/styles.ts +98 -0
- package/src/widgets/wrapper/types.ts +55 -0
- package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
- package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
- package/src/widgets/wrapper/wrapper.test.tsx +365 -0
- package/src/widgets/wrapper/wrapper.tsx +50 -0
- package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
- package/dist/types/common/common.d.ts +0 -3
- package/dist/types/common/index.d.ts +0 -26
- package/dist/types/common/lasso-tools.d.ts +0 -36
- package/dist/types/common/measurement-tools.d.ts +0 -65
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SxProps, Theme } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared styles for widget action buttons
|
|
5
|
+
*/
|
|
6
|
+
export const actionButtonStyles = {
|
|
7
|
+
trigger: {
|
|
8
|
+
'&[data-active="true"]': {
|
|
9
|
+
background: (theme: Theme) => theme.palette.primary.relatedLight,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
} satisfies Record<string, SxProps<Theme>>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const GroupedBarChartIcon = () => (
|
|
2
|
+
<svg
|
|
3
|
+
width='24'
|
|
4
|
+
height='24'
|
|
5
|
+
viewBox='0 0 24 24'
|
|
6
|
+
fill='none'
|
|
7
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d='M4 20V8H8V20H4ZM9 20V13H13V20H9ZM16 20V4H20V20H16Z'
|
|
11
|
+
fill='currentColor'
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
14
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
3
|
+
import { StackToggle } from './stack-toggle'
|
|
4
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
+
import { DEFAULT_STACK_GROUP } from '../../echart/const'
|
|
6
|
+
|
|
7
|
+
describe('StackToggle', () => {
|
|
8
|
+
const widgetId = 'test-stack-widget'
|
|
9
|
+
|
|
10
|
+
// Helper to set up widget with multiple series (required for component to render)
|
|
11
|
+
function setupMultiSeriesWidget(overrides = {}) {
|
|
12
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
13
|
+
option: {
|
|
14
|
+
series: [
|
|
15
|
+
{ name: 'Series 1', type: 'bar' },
|
|
16
|
+
{ name: 'Series 2', type: 'bar' },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
...overrides,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
useWidgetStore.getState().clearWidgets()
|
|
25
|
+
setupMultiSeriesWidget()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('renders stack toggle button', () => {
|
|
29
|
+
render(<StackToggle id={widgetId} />)
|
|
30
|
+
|
|
31
|
+
const button = screen.getByRole('button')
|
|
32
|
+
expect(button).toBeTruthy()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('shows stacked tooltip when in unstacked mode', () => {
|
|
36
|
+
render(<StackToggle id={widgetId} />)
|
|
37
|
+
|
|
38
|
+
const button = screen.getByRole('button', { name: 'Enable stacking' })
|
|
39
|
+
expect(button).toBeTruthy()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('toggles to stacked mode and updates widget store', () => {
|
|
43
|
+
render(<StackToggle id={widgetId} />)
|
|
44
|
+
|
|
45
|
+
const button = screen.getByRole('button')
|
|
46
|
+
fireEvent.click(button)
|
|
47
|
+
|
|
48
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
49
|
+
expect((widget as { isStacked?: boolean })?.isStacked).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('toggles back to unstacked mode', () => {
|
|
53
|
+
render(<StackToggle id={widgetId} />)
|
|
54
|
+
|
|
55
|
+
const button = screen.getByRole('button')
|
|
56
|
+
|
|
57
|
+
// Toggle to stacked
|
|
58
|
+
fireEvent.click(button)
|
|
59
|
+
|
|
60
|
+
// Toggle back to unstacked
|
|
61
|
+
fireEvent.click(button)
|
|
62
|
+
|
|
63
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
64
|
+
expect((widget as { isStacked?: boolean })?.isStacked).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('has active state when in stacked mode', () => {
|
|
68
|
+
render(<StackToggle id={widgetId} />)
|
|
69
|
+
|
|
70
|
+
const button = screen.getByRole('button')
|
|
71
|
+
|
|
72
|
+
expect(button.getAttribute('data-active')).toBe('false')
|
|
73
|
+
|
|
74
|
+
fireEvent.click(button)
|
|
75
|
+
|
|
76
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('starts in stacked mode when defaultIsStacked is true', () => {
|
|
80
|
+
render(<StackToggle id={widgetId} defaultIsStacked />)
|
|
81
|
+
|
|
82
|
+
const button = screen.getByRole('button')
|
|
83
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
84
|
+
|
|
85
|
+
// Should show unstacked tooltip when in stacked mode
|
|
86
|
+
expect(button.getAttribute('aria-label')).toBe('Disable stacking')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('uses custom labels when provided', () => {
|
|
90
|
+
const customLabels = {
|
|
91
|
+
stacked: 'Stack bars',
|
|
92
|
+
unstacked: 'Unstack bars',
|
|
93
|
+
ariaLabel: 'Toggle bar stacking',
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
render(<StackToggle id={widgetId} labels={customLabels} />)
|
|
97
|
+
|
|
98
|
+
const button = screen.getByRole('button', { name: 'Toggle bar stacking' })
|
|
99
|
+
expect(button).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('initializes store with default values on mount', () => {
|
|
103
|
+
render(<StackToggle id={widgetId} />)
|
|
104
|
+
|
|
105
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
106
|
+
expect((widget as { isStacked?: boolean })?.isStacked).toBe(false)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('initializes store with stacked values when defaultIsStacked is true', () => {
|
|
110
|
+
render(<StackToggle id={widgetId} defaultIsStacked />)
|
|
111
|
+
|
|
112
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
113
|
+
expect((widget as { isStacked?: boolean })?.isStacked).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('updates EChart series options when toggling to stacked', () => {
|
|
117
|
+
// setupMultiSeriesWidget already ran in beforeEach
|
|
118
|
+
render(<StackToggle id={widgetId} />)
|
|
119
|
+
const button = screen.getByRole('button')
|
|
120
|
+
fireEvent.click(button)
|
|
121
|
+
|
|
122
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
123
|
+
const series = (widget as { option?: { series?: { stack?: string }[] } })
|
|
124
|
+
?.option?.series?.[0]
|
|
125
|
+
expect(series?.stack).toBe(DEFAULT_STACK_GROUP)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('removes stack from EChart series when toggling to unstacked', () => {
|
|
129
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
130
|
+
option: {
|
|
131
|
+
series: [
|
|
132
|
+
{ name: 'Series 1', type: 'bar', stack: DEFAULT_STACK_GROUP },
|
|
133
|
+
{ name: 'Series 2', type: 'bar', stack: DEFAULT_STACK_GROUP },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
render(<StackToggle id={widgetId} />)
|
|
139
|
+
const button = screen.getByRole('button')
|
|
140
|
+
|
|
141
|
+
// First click unstacks (since series has stack, it starts stacked)
|
|
142
|
+
fireEvent.click(button)
|
|
143
|
+
|
|
144
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
145
|
+
const series = (widget as { option?: { series?: { stack?: string }[] } })
|
|
146
|
+
?.option?.series?.[0]
|
|
147
|
+
expect(series?.stack).toBeUndefined()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('updates multiple series when toggling stack', () => {
|
|
151
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
152
|
+
option: {
|
|
153
|
+
series: [
|
|
154
|
+
{ name: 'Series 1', type: 'bar' },
|
|
155
|
+
{ name: 'Series 2', type: 'bar' },
|
|
156
|
+
{ name: 'Series 3', type: 'bar' },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
render(<StackToggle id={widgetId} />)
|
|
162
|
+
const button = screen.getByRole('button')
|
|
163
|
+
fireEvent.click(button)
|
|
164
|
+
|
|
165
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
166
|
+
const seriesArray = (
|
|
167
|
+
widget as { option?: { series?: { stack?: string }[] } }
|
|
168
|
+
)?.option?.series
|
|
169
|
+
|
|
170
|
+
expect(seriesArray).toHaveLength(3)
|
|
171
|
+
seriesArray?.forEach((s) => {
|
|
172
|
+
expect(s.stack).toBe(DEFAULT_STACK_GROUP)
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('uses default stack group when re-stacking after unstacking', () => {
|
|
177
|
+
const customStackGroup = 'custom-group'
|
|
178
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
179
|
+
option: {
|
|
180
|
+
series: [
|
|
181
|
+
{ name: 'Series 1', type: 'bar', stack: customStackGroup },
|
|
182
|
+
{ name: 'Series 2', type: 'bar', stack: customStackGroup },
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
render(<StackToggle id={widgetId} />)
|
|
188
|
+
const button = screen.getByRole('button')
|
|
189
|
+
|
|
190
|
+
// Toggle off then on - re-stacking uses default stack group
|
|
191
|
+
fireEvent.click(button) // unstacked
|
|
192
|
+
fireEvent.click(button) // stacked again
|
|
193
|
+
|
|
194
|
+
void waitFor(() => {
|
|
195
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
196
|
+
const series = (widget as { option?: { series?: { stack?: string }[] } })
|
|
197
|
+
?.option?.series?.[0]
|
|
198
|
+
expect(series?.stack).toBe(customStackGroup)
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test('detects existing stack in series and defaults to stacked mode', () => {
|
|
203
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
204
|
+
option: {
|
|
205
|
+
series: [
|
|
206
|
+
{ name: 'Series 1', type: 'bar', stack: 'existing-stack' },
|
|
207
|
+
{ name: 'Series 2', type: 'bar', stack: 'existing-stack' },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
render(<StackToggle id={widgetId} />)
|
|
213
|
+
|
|
214
|
+
const button = screen.getByRole('button')
|
|
215
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
216
|
+
expect(button.getAttribute('aria-label')).toBe('Disable stacking')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('returns null for single series (stacking not applicable)', () => {
|
|
220
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
221
|
+
option: {
|
|
222
|
+
series: { name: 'Single Series', type: 'bar' },
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const { container } = render(<StackToggle id={widgetId} />)
|
|
227
|
+
// Component returns null when there's only one series
|
|
228
|
+
expect(container.firstChild).toBeNull()
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
test('returns null when widget has no option defined', () => {
|
|
232
|
+
// Clear the widget set up in beforeEach and create one without option
|
|
233
|
+
useWidgetStore.getState().clearWidgets()
|
|
234
|
+
useWidgetStore.getState().setWidget(widgetId, {})
|
|
235
|
+
|
|
236
|
+
const { container } = render(<StackToggle id={widgetId} />)
|
|
237
|
+
// Component returns null when there's no series data
|
|
238
|
+
expect(container.firstChild).toBeNull()
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('preserves other option properties when updating series', () => {
|
|
242
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
243
|
+
option: {
|
|
244
|
+
title: { text: 'My Chart' },
|
|
245
|
+
xAxis: { type: 'category' },
|
|
246
|
+
series: [
|
|
247
|
+
{ name: 'Series 1', type: 'bar' },
|
|
248
|
+
{ name: 'Series 2', type: 'bar' },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
render(<StackToggle id={widgetId} />)
|
|
254
|
+
const button = screen.getByRole('button')
|
|
255
|
+
fireEvent.click(button)
|
|
256
|
+
|
|
257
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
258
|
+
const option = (
|
|
259
|
+
widget as {
|
|
260
|
+
option?: {
|
|
261
|
+
title?: { text?: string }
|
|
262
|
+
xAxis?: { type?: string }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
)?.option
|
|
266
|
+
|
|
267
|
+
expect(option?.title?.text).toBe('My Chart')
|
|
268
|
+
expect(option?.xAxis?.type).toBe('category')
|
|
269
|
+
})
|
|
270
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { IconButton } from '@mui/material'
|
|
2
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
3
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
4
|
+
import type { StackToggleProps, StackToggleState } from './types'
|
|
5
|
+
import { actionButtonStyles } from '../shared/styles'
|
|
6
|
+
import { Tooltip } from '../../../components'
|
|
7
|
+
import { GroupedBarChartIcon } from './grouped-bar-chart-icon'
|
|
8
|
+
import { getEChartStackConfig } from '../../echart/utils'
|
|
9
|
+
import { DEFAULT_STACK_GROUP } from '../../echart/const'
|
|
10
|
+
import type { EchartWidgetState } from '../../echart/types'
|
|
11
|
+
import { useShallow } from 'zustand/shallow'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Widget action to toggle stacking behavior in ECharts bar and histogram widgets.
|
|
15
|
+
*
|
|
16
|
+
* Stores the stack state in the widget store and updates the ECharts option
|
|
17
|
+
* using getEChartStackConfig to apply stacking to all series.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <StackToggle
|
|
22
|
+
* id="my-widget"
|
|
23
|
+
* defaultIsStacked={false}
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function StackToggle({
|
|
28
|
+
id,
|
|
29
|
+
defaultIsStacked = false,
|
|
30
|
+
labels,
|
|
31
|
+
Icon,
|
|
32
|
+
IconButtonProps,
|
|
33
|
+
}: StackToggleProps) {
|
|
34
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
35
|
+
const storeIsStacked = useWidgetStore(
|
|
36
|
+
useShallow((state) => state.getWidget<StackToggleState>(id)?.isStacked),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const getWidget = useWidgetStore((state) => state.getWidget)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Checks if there are multiple series in the chart
|
|
43
|
+
*/
|
|
44
|
+
const hasMultiSeries = useMemo(() => {
|
|
45
|
+
const option = getWidget<EchartWidgetState>(id)?.option
|
|
46
|
+
if (!option) return false
|
|
47
|
+
|
|
48
|
+
const series = Array.isArray(option.series)
|
|
49
|
+
? option.series
|
|
50
|
+
: [option.series]
|
|
51
|
+
|
|
52
|
+
return series.length > 1
|
|
53
|
+
}, [getWidget, id])
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Checks if any series in the option has a stack property defined
|
|
57
|
+
*/
|
|
58
|
+
const hasStackInSeries = useMemo(() => {
|
|
59
|
+
const option = getWidget<EchartWidgetState>(id)?.option
|
|
60
|
+
if (!option) return false
|
|
61
|
+
|
|
62
|
+
const series = Array.isArray(option.series)
|
|
63
|
+
? option.series
|
|
64
|
+
: [option.series]
|
|
65
|
+
|
|
66
|
+
return series.some((s) => (s as { stack?: string })?.stack)
|
|
67
|
+
}, [getWidget, id])
|
|
68
|
+
|
|
69
|
+
// If series already has stack defined, default to stacked=true
|
|
70
|
+
const effectiveDefaultIsStacked = hasStackInSeries || defaultIsStacked
|
|
71
|
+
const isStacked = storeIsStacked ?? effectiveDefaultIsStacked
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Updates the ECharts option with the stack configuration
|
|
75
|
+
* Preserves existing stack group names from series, falling back to DEFAULT_STACK_GROUP
|
|
76
|
+
*/
|
|
77
|
+
const updateOptions = useCallback(
|
|
78
|
+
(stacked: boolean) => {
|
|
79
|
+
const option = getWidget<EchartWidgetState>(id)?.option
|
|
80
|
+
|
|
81
|
+
if (!option) return
|
|
82
|
+
|
|
83
|
+
const series = Array.isArray(option.series)
|
|
84
|
+
? option.series
|
|
85
|
+
: [option.series]
|
|
86
|
+
|
|
87
|
+
const updatedSeries = series.map((s) => {
|
|
88
|
+
// Extract existing stack group from series, fallback to default
|
|
89
|
+
const existingStack = (s as { stack?: string })?.stack
|
|
90
|
+
const stackGroup =
|
|
91
|
+
typeof existingStack === 'string'
|
|
92
|
+
? existingStack
|
|
93
|
+
: DEFAULT_STACK_GROUP
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...s,
|
|
97
|
+
...getEChartStackConfig(stacked, stackGroup),
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
setWidget(id, {
|
|
102
|
+
option: {
|
|
103
|
+
...option,
|
|
104
|
+
series: updatedSeries,
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
},
|
|
108
|
+
[getWidget, id, setWidget],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Initialize store with default value only if not already configured
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setWidget(id, { isStacked: effectiveDefaultIsStacked })
|
|
114
|
+
}, [effectiveDefaultIsStacked, id, setWidget])
|
|
115
|
+
|
|
116
|
+
const handleToggle = useCallback(() => {
|
|
117
|
+
const newIsStacked = !isStacked
|
|
118
|
+
|
|
119
|
+
setWidget(id, { isStacked: newIsStacked })
|
|
120
|
+
updateOptions(newIsStacked)
|
|
121
|
+
}, [isStacked, id, setWidget, updateOptions])
|
|
122
|
+
|
|
123
|
+
const tooltipLabel = isStacked
|
|
124
|
+
? (labels?.unstacked ?? 'Disable stacking')
|
|
125
|
+
: (labels?.stacked ?? 'Enable stacking')
|
|
126
|
+
|
|
127
|
+
// Early return if there's only one series - stacking doesn't apply
|
|
128
|
+
if (!hasMultiSeries) {
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Tooltip title={tooltipLabel}>
|
|
134
|
+
<IconButton
|
|
135
|
+
size='small'
|
|
136
|
+
aria-label={labels?.ariaLabel ?? tooltipLabel}
|
|
137
|
+
onClick={handleToggle}
|
|
138
|
+
sx={actionButtonStyles.trigger}
|
|
139
|
+
data-active={isStacked}
|
|
140
|
+
{...IconButtonProps}
|
|
141
|
+
>
|
|
142
|
+
{Icon ?? <GroupedBarChartIcon />}
|
|
143
|
+
</IconButton>
|
|
144
|
+
</Tooltip>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IconButtonProps } from '@mui/material'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
4
|
+
|
|
5
|
+
export interface StackToggleProps {
|
|
6
|
+
/** Widget ID to update stack configuration in the widget store */
|
|
7
|
+
id: string
|
|
8
|
+
/** Initial toggle state - when true, starts with stacking enabled */
|
|
9
|
+
defaultIsStacked?: boolean
|
|
10
|
+
/** Custom labels for the action */
|
|
11
|
+
labels?: {
|
|
12
|
+
/** Tooltip when stacking is disabled (button will enable stacking) */
|
|
13
|
+
stacked?: string
|
|
14
|
+
/** Tooltip when stacking is enabled (button will disable stacking) */
|
|
15
|
+
unstacked?: string
|
|
16
|
+
/** Accessibility label */
|
|
17
|
+
ariaLabel?: string
|
|
18
|
+
}
|
|
19
|
+
/** Props passed to the IconButton component */
|
|
20
|
+
IconButtonProps?: IconButtonProps
|
|
21
|
+
/** Custom icon to display */
|
|
22
|
+
Icon?: ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type StackToggleState<T = unknown> = BaseWidgetState<
|
|
26
|
+
T & {
|
|
27
|
+
isStacked?: boolean
|
|
28
|
+
}
|
|
29
|
+
>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SxProps, Theme } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
export const styles = {
|
|
4
|
+
container: {
|
|
5
|
+
display: 'flex',
|
|
6
|
+
alignItems: 'center',
|
|
7
|
+
gap: ({ spacing }) => spacing(0.25),
|
|
8
|
+
},
|
|
9
|
+
trigger: {
|
|
10
|
+
'&[data-active="true"]': {
|
|
11
|
+
background: (theme: Theme) => theme.palette.primary.relatedLight,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
} satisfies Record<string, SxProps<Theme>>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { IconButtonProps } from '@mui/material'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* State stored in widget store for zoom functionality
|
|
7
|
+
*/
|
|
8
|
+
export interface ZoomConfig {
|
|
9
|
+
zoom?: boolean
|
|
10
|
+
/** Start percentage for zoom range (0-100) */
|
|
11
|
+
zoomStart?: number
|
|
12
|
+
/** End percentage for zoom range (0-100) */
|
|
13
|
+
zoomEnd?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ZoomState<T = unknown> = BaseWidgetState<T & ZoomConfig>
|
|
17
|
+
|
|
18
|
+
export interface ZoomToggleProps {
|
|
19
|
+
/** Widget ID to update zoom state in the widget store */
|
|
20
|
+
id: string
|
|
21
|
+
/** Initial zoom state - when true, zoom is enabled */
|
|
22
|
+
defaultZoom?: boolean
|
|
23
|
+
/** Initial start percentage for zoom range (0-100) */
|
|
24
|
+
defaultZoomStart?: number
|
|
25
|
+
/** Initial end percentage for zoom range (0-100) */
|
|
26
|
+
defaultZoomEnd?: number
|
|
27
|
+
/** Custom labels for the action */
|
|
28
|
+
labels?: {
|
|
29
|
+
/** Tooltip when zoom is disabled (button will enable zoom) */
|
|
30
|
+
enable?: string
|
|
31
|
+
/** Tooltip when zoom is enabled (button will disable zoom) */
|
|
32
|
+
disable?: string
|
|
33
|
+
/** Tooltip for reset button */
|
|
34
|
+
reset?: string
|
|
35
|
+
/** Accessibility label */
|
|
36
|
+
ariaLabel?: string
|
|
37
|
+
}
|
|
38
|
+
/** Props passed to the IconButton component */
|
|
39
|
+
IconButtonProps?: IconButtonProps
|
|
40
|
+
/** Custom icon to display for zoom toggle */
|
|
41
|
+
Icon?: ReactNode
|
|
42
|
+
/** Custom icon to display for reset button */
|
|
43
|
+
ResetIcon?: ReactNode
|
|
44
|
+
}
|