@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,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CSV export modifiers for chart widgets
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Flattens object array data into CSV-ready rows.
|
|
7
|
+
* Used by bar, pie, histogram, and timeseries widgets.
|
|
8
|
+
*
|
|
9
|
+
* @param data - Array of series, where each series is an array of data objects
|
|
10
|
+
* @returns CSV rows with headers and values
|
|
11
|
+
*/
|
|
12
|
+
export function flattenObjectArrayToCSV<T extends Record<string, unknown>>(
|
|
13
|
+
data: T[][],
|
|
14
|
+
): string[][] {
|
|
15
|
+
const rows: string[][] = []
|
|
16
|
+
|
|
17
|
+
// Add headers from first data point if available
|
|
18
|
+
if (data.length > 0 && (data[0]?.length ?? 0) > 0) {
|
|
19
|
+
const firstDataPoint = data?.[0]?.[0] ?? {}
|
|
20
|
+
const headers = Object.keys(firstDataPoint)
|
|
21
|
+
rows.push(headers)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Add data rows from all series
|
|
25
|
+
data.forEach((series) => {
|
|
26
|
+
series.forEach((dataPoint) => {
|
|
27
|
+
const values = Object.values(dataPoint).map((v) => String(v))
|
|
28
|
+
rows.push(values)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return rows
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates CSV rows for scatterplot data.
|
|
37
|
+
* Scatterplot uses array format [x, y] instead of objects.
|
|
38
|
+
*
|
|
39
|
+
* @param data - Array of series, where each series is an array of [x, y] tuples
|
|
40
|
+
* @returns CSV rows with ['x', 'y'] headers
|
|
41
|
+
*/
|
|
42
|
+
export function scatterplotDataToCSV(data: number[][][]): string[][] {
|
|
43
|
+
const rows: string[][] = []
|
|
44
|
+
|
|
45
|
+
// Add headers
|
|
46
|
+
rows.push(['x', 'y'])
|
|
47
|
+
|
|
48
|
+
// Add data rows from all series
|
|
49
|
+
data.forEach((series) => {
|
|
50
|
+
series.forEach((dataPoint) => {
|
|
51
|
+
rows.push([String(dataPoint[0]), String(dataPoint[1])])
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return rows
|
|
56
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for chart widget configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { createChartWidgetConfig } from './config-factory'
|
|
6
|
+
export type {
|
|
7
|
+
ChartWidgetBaseConfig,
|
|
8
|
+
CreateChartWidgetConfigParams,
|
|
9
|
+
} from './config-factory'
|
|
10
|
+
|
|
11
|
+
export { flattenObjectArrayToCSV, scatterplotDataToCSV } from './csv-modifiers'
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
buildLegendConfig,
|
|
15
|
+
buildGridConfig,
|
|
16
|
+
createTooltipPositioner,
|
|
17
|
+
createAxisLabelFormatter,
|
|
18
|
+
createTooltipFormatter,
|
|
19
|
+
applyXAxisFormatter,
|
|
20
|
+
applyYAxisFormatter,
|
|
21
|
+
} from './option-builders'
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { Theme } from '@mui/material'
|
|
2
|
+
import type { LegendComponentOption } from 'echarts'
|
|
3
|
+
import type {
|
|
4
|
+
CallbackDataParams,
|
|
5
|
+
TopLevelFormatterParams,
|
|
6
|
+
} from 'node_modules/echarts/types/dist/shared'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shared EChart configuration builders for chart widgets
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds standard legend configuration for chart widgets
|
|
14
|
+
*
|
|
15
|
+
* @param hasLegend - Whether to show the legend
|
|
16
|
+
* @returns Legend configuration object
|
|
17
|
+
*/
|
|
18
|
+
export function buildLegendConfig(hasLegend: boolean): LegendComponentOption {
|
|
19
|
+
return {
|
|
20
|
+
show: hasLegend,
|
|
21
|
+
icon: 'circle' as const,
|
|
22
|
+
left: 0,
|
|
23
|
+
bottom: 0,
|
|
24
|
+
orient: 'horizontal',
|
|
25
|
+
type: 'scroll',
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Builds standard grid configuration with legend-aware spacing
|
|
31
|
+
*
|
|
32
|
+
* @param hasLegend - Whether the chart has a legend
|
|
33
|
+
* @param theme - MUI theme for spacing
|
|
34
|
+
* @param additionalConfig - Additional grid configuration to merge
|
|
35
|
+
* @returns Grid configuration object
|
|
36
|
+
*/
|
|
37
|
+
export function buildGridConfig(hasLegend: boolean, theme: Theme) {
|
|
38
|
+
return {
|
|
39
|
+
...(!hasLegend && { bottom: parseInt(theme.spacing(3)) }),
|
|
40
|
+
...(hasLegend && { bottom: parseInt(theme.spacing(7)) }),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a tooltip position calculator that handles overflow
|
|
46
|
+
* Used by bar, histogram, and scatterplot widgets
|
|
47
|
+
*
|
|
48
|
+
* @param theme - MUI theme for spacing
|
|
49
|
+
* @returns Tooltip position function
|
|
50
|
+
*/
|
|
51
|
+
export function createTooltipPositioner(theme: Theme) {
|
|
52
|
+
return function (
|
|
53
|
+
point: [number, number],
|
|
54
|
+
_params: unknown,
|
|
55
|
+
_dom: unknown,
|
|
56
|
+
_rect: unknown,
|
|
57
|
+
size: { contentSize: [number, number]; viewSize: [number, number] },
|
|
58
|
+
) {
|
|
59
|
+
const position = { top: parseInt(theme.spacing(0.5)) } as Record<
|
|
60
|
+
string,
|
|
61
|
+
number
|
|
62
|
+
>
|
|
63
|
+
|
|
64
|
+
// Position tooltip left or right based on available space
|
|
65
|
+
if (size.contentSize[0] < size.viewSize[0] - point[0]) {
|
|
66
|
+
position.left = point[0]
|
|
67
|
+
} else {
|
|
68
|
+
position.right = size.viewSize[0] - point[0]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return position
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates an axis label formatter for ECharts
|
|
77
|
+
* Used to format numeric axis labels with a widget formatter
|
|
78
|
+
*
|
|
79
|
+
* @param formatter - Optional formatter function from widget config
|
|
80
|
+
* @returns Axis label formatter function or undefined
|
|
81
|
+
*/
|
|
82
|
+
export function createAxisLabelFormatter(
|
|
83
|
+
formatter?: (value: number) => string,
|
|
84
|
+
) {
|
|
85
|
+
if (!formatter) return undefined
|
|
86
|
+
return (value: number) => formatter(value)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Applies formatter to xAxis configuration
|
|
91
|
+
* Only applies to single axis objects (not arrays) with type 'value'
|
|
92
|
+
*
|
|
93
|
+
* @param xAxis - Existing xAxis configuration
|
|
94
|
+
* @param formatter - Optional formatter function from widget config
|
|
95
|
+
* @returns Updated xAxis configuration or undefined if no changes needed
|
|
96
|
+
*/
|
|
97
|
+
export function applyXAxisFormatter(
|
|
98
|
+
xAxis: unknown,
|
|
99
|
+
formatter?: (value: number) => string,
|
|
100
|
+
) {
|
|
101
|
+
let axisFormatter = createAxisLabelFormatter(formatter)
|
|
102
|
+
|
|
103
|
+
const xAxisIsObject = xAxis && !Array.isArray(xAxis)
|
|
104
|
+
const xAxisTyped = xAxis as { type?: string; axisLabel?: unknown }
|
|
105
|
+
|
|
106
|
+
if (!xAxisIsObject || xAxisTyped.type !== 'value') {
|
|
107
|
+
axisFormatter = undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
...xAxisTyped,
|
|
112
|
+
axisLabel: {
|
|
113
|
+
...(typeof xAxisTyped.axisLabel === 'object' && xAxisTyped.axisLabel
|
|
114
|
+
? xAxisTyped.axisLabel
|
|
115
|
+
: {}),
|
|
116
|
+
formatter: axisFormatter,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Applies formatter to yAxis configuration
|
|
123
|
+
* Only applies to single axis objects (not arrays) with type 'value'
|
|
124
|
+
*
|
|
125
|
+
* @param yAxis - Existing yAxis configuration
|
|
126
|
+
* @param formatter - Optional formatter function from widget config
|
|
127
|
+
* @returns Updated yAxis configuration or undefined if no changes needed
|
|
128
|
+
*/
|
|
129
|
+
export function applyYAxisFormatter(
|
|
130
|
+
yAxis: unknown,
|
|
131
|
+
formatter?: (value: number) => string,
|
|
132
|
+
) {
|
|
133
|
+
let axisFormatter = createAxisLabelFormatter(formatter)
|
|
134
|
+
|
|
135
|
+
const yAxisIsObject = yAxis && !Array.isArray(yAxis)
|
|
136
|
+
const yAxisTyped = yAxis as { type?: string; axisLabel?: unknown }
|
|
137
|
+
|
|
138
|
+
if (!yAxisIsObject || yAxisTyped.type !== 'value') {
|
|
139
|
+
axisFormatter = undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...yAxisTyped,
|
|
144
|
+
axisLabel: {
|
|
145
|
+
...(typeof yAxisTyped.axisLabel === 'object' && yAxisTyped.axisLabel
|
|
146
|
+
? yAxisTyped.axisLabel
|
|
147
|
+
: {}),
|
|
148
|
+
formatter: axisFormatter,
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates a tooltip formatter for ECharts
|
|
155
|
+
* Formats numeric values in tooltip using widget formatter
|
|
156
|
+
* Handles both axis trigger (array) and item trigger (object) modes
|
|
157
|
+
*
|
|
158
|
+
* @param formatter - Optional formatter function from widget config
|
|
159
|
+
* @returns Tooltip formatter function or undefined
|
|
160
|
+
*/
|
|
161
|
+
export function createTooltipFormatter(
|
|
162
|
+
callback: (
|
|
163
|
+
item: CallbackDataParams,
|
|
164
|
+
items: CallbackDataParams[],
|
|
165
|
+
) => {
|
|
166
|
+
name: string
|
|
167
|
+
seriesName: string
|
|
168
|
+
marker: string
|
|
169
|
+
value: string | number
|
|
170
|
+
},
|
|
171
|
+
) {
|
|
172
|
+
return (params: TopLevelFormatterParams) => {
|
|
173
|
+
// Handle both array (axis trigger) and object (item trigger)
|
|
174
|
+
const items = Array.isArray(params) ? params : [params]
|
|
175
|
+
|
|
176
|
+
const tooltip = (name: string, callback: string) =>
|
|
177
|
+
`<div style="margin: 0px 0 0;line-height:1;">${name ? `<div style="font-size:11px;color:#FFFFFF;font-weight:400;line-height:1; margin-bottom: 10px">${name}</div>` : ''}<div style="margin: 0;line-height:1;">${callback}</div><div style="clear:both"></div></div>`
|
|
178
|
+
|
|
179
|
+
const values = items.map((item) => {
|
|
180
|
+
const { name, seriesName, marker, value } = callback(item, items)
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
seriesName,
|
|
184
|
+
marker,
|
|
185
|
+
value,
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const name = values[0]?.name ?? ''
|
|
190
|
+
// Show margin if name exists or there are multiple items
|
|
191
|
+
const showMargin = name || items.length > 1
|
|
192
|
+
const marginStyle = showMargin
|
|
193
|
+
? 'margin: 10px 0 0;line-height:1;'
|
|
194
|
+
: 'margin: 0;line-height:1;'
|
|
195
|
+
|
|
196
|
+
const formattedValues = values.map(
|
|
197
|
+
({ seriesName, marker, value }) =>
|
|
198
|
+
`<div style="${marginStyle}"><div style="margin: 0px 0 0;line-height:1;">${marker}${seriesName ? `<span style="font-size:11px;color:#FFFFFF;font-weight:400;margin-left:2px;margin-right:10px">${seriesName}</span>` : ''}<span style="float:right;margin-left:10px;font-size:11px;color:#FFFFFF;font-weight:900">${value}</span></div></div>`,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return tooltip(name, formattedValues.join(''))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Theme } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base skeleton styles shared across all chart widgets
|
|
5
|
+
*/
|
|
6
|
+
export const baseSkeletonStyles = {
|
|
7
|
+
graph: {
|
|
8
|
+
/**
|
|
9
|
+
* Common container style for chart widget skeletons
|
|
10
|
+
*/
|
|
11
|
+
container: {
|
|
12
|
+
display: 'flex',
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
justifyContent: 'space-between',
|
|
15
|
+
flexDirection: 'column',
|
|
16
|
+
gap: ({ spacing }: Theme) => spacing(1),
|
|
17
|
+
height: ({ spacing }: Theme) => spacing(38),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
} as const
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const ChangeColumnIcon = () => (
|
|
2
|
+
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 18 18'>
|
|
3
|
+
<path
|
|
4
|
+
fill='currentColor'
|
|
5
|
+
fillRule='evenodd'
|
|
6
|
+
d='M14.25 2.25H3.75c-.825 0-1.5.675-1.5 1.5v10.5c0 .825.675 1.5 1.5 1.5h10.5c.825 0 1.5-.675 1.5-1.5V3.75c0-.825-.675-1.5-1.5-1.5m-3.75 12h-3V3.75h3zM3.75 3.75H6v10.5H3.75zM12 14.25V3.75h2.25v10.5z'
|
|
7
|
+
clipRule='evenodd'
|
|
8
|
+
/>
|
|
9
|
+
</svg>
|
|
10
|
+
)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { ChangeColumn } from './change-column'
|
|
4
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
+
import type { TableColumn } from '../../table/types'
|
|
6
|
+
|
|
7
|
+
// Mock @dnd-kit with minimal implementation for testing
|
|
8
|
+
vi.mock('@dnd-kit/core', async () => {
|
|
9
|
+
const actual = await vi.importActual('@dnd-kit/core')
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
useSensor: () => ({}),
|
|
13
|
+
useSensors: () => [],
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('ChangeColumn', () => {
|
|
18
|
+
const widgetId = 'test-change-column-widget'
|
|
19
|
+
|
|
20
|
+
const mockColumns: TableColumn[] = [
|
|
21
|
+
{ id: 'name', label: 'Name' },
|
|
22
|
+
{ id: 'country', label: 'Country' },
|
|
23
|
+
{ id: 'population', label: 'Population' },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
useWidgetStore.getState().clearWidgets()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('returns null when widget has no columns', () => {
|
|
31
|
+
const { container } = render(<ChangeColumn id={widgetId} />)
|
|
32
|
+
expect(container.firstChild).toBeNull()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('returns null when widget has fewer than 2 columns', () => {
|
|
36
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
37
|
+
columns: [{ id: 'name', label: 'Name' }],
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const { container } = render(<ChangeColumn id={widgetId} />)
|
|
41
|
+
expect(container.firstChild).toBeNull()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('renders change column button when widget has 2 or more columns', () => {
|
|
45
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
46
|
+
columns: mockColumns,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
render(<ChangeColumn id={widgetId} />)
|
|
50
|
+
|
|
51
|
+
const button = screen.getByRole('button')
|
|
52
|
+
expect(button).toBeTruthy()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('shows default tooltip label', () => {
|
|
56
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
57
|
+
columns: mockColumns,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
render(<ChangeColumn id={widgetId} />)
|
|
61
|
+
|
|
62
|
+
const button = screen.getByRole('button', { name: 'Change column' })
|
|
63
|
+
expect(button).toBeTruthy()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('opens menu on button click', () => {
|
|
67
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
68
|
+
columns: mockColumns,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
render(<ChangeColumn id={widgetId} />)
|
|
72
|
+
|
|
73
|
+
const button = screen.getByRole('button')
|
|
74
|
+
fireEvent.click(button)
|
|
75
|
+
|
|
76
|
+
// Menu should be open with all columns for reordering
|
|
77
|
+
expect(screen.getByRole('menu')).toBeTruthy()
|
|
78
|
+
expect(screen.getByRole('menuitem', { name: /Name/i })).toBeTruthy()
|
|
79
|
+
expect(screen.getByRole('menuitem', { name: /Country/i })).toBeTruthy()
|
|
80
|
+
expect(screen.getByRole('menuitem', { name: /Population/i })).toBeTruthy()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('displays all columns in menu for drag-and-drop reordering', () => {
|
|
84
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
85
|
+
columns: mockColumns,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
render(<ChangeColumn id={widgetId} />)
|
|
89
|
+
|
|
90
|
+
const button = screen.getByRole('button')
|
|
91
|
+
fireEvent.click(button)
|
|
92
|
+
|
|
93
|
+
// All three columns should be visible
|
|
94
|
+
const menuItems = screen.getAllByRole('menuitem')
|
|
95
|
+
expect(menuItems).toHaveLength(3)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('uses custom labels when provided', () => {
|
|
99
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
100
|
+
columns: mockColumns,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const customLabels = {
|
|
104
|
+
tooltip: 'Reorder columns',
|
|
105
|
+
ariaLabel: 'Reorder table columns',
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
render(<ChangeColumn id={widgetId} labels={customLabels} />)
|
|
109
|
+
|
|
110
|
+
const button = screen.getByRole('button', { name: 'Reorder table columns' })
|
|
111
|
+
expect(button).toBeTruthy()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('works with exactly 2 columns', () => {
|
|
115
|
+
const twoColumns: TableColumn[] = [
|
|
116
|
+
{ id: 'first', label: 'First' },
|
|
117
|
+
{ id: 'second', label: 'Second' },
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
121
|
+
columns: twoColumns,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
render(<ChangeColumn id={widgetId} />)
|
|
125
|
+
|
|
126
|
+
const button = screen.getByRole('button')
|
|
127
|
+
fireEvent.click(button)
|
|
128
|
+
|
|
129
|
+
// Both columns should be available for reordering
|
|
130
|
+
expect(screen.getByRole('menuitem', { name: /First/i })).toBeTruthy()
|
|
131
|
+
expect(screen.getByRole('menuitem', { name: /Second/i })).toBeTruthy()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('sets data-active attribute when menu is open', () => {
|
|
135
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
136
|
+
columns: mockColumns,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
render(<ChangeColumn id={widgetId} />)
|
|
140
|
+
|
|
141
|
+
const button = screen.getByRole('button')
|
|
142
|
+
expect(button.getAttribute('data-active')).toBe('false')
|
|
143
|
+
|
|
144
|
+
fireEvent.click(button)
|
|
145
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('menu items are keyboard focusable', () => {
|
|
149
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
150
|
+
columns: mockColumns,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
render(<ChangeColumn id={widgetId} />)
|
|
154
|
+
|
|
155
|
+
const button = screen.getByRole('button')
|
|
156
|
+
fireEvent.click(button)
|
|
157
|
+
|
|
158
|
+
const menuItems = screen.getAllByRole('menuitem')
|
|
159
|
+
menuItems.forEach((item) => {
|
|
160
|
+
expect(item.getAttribute('tabindex')).toBe('0')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DndContext,
|
|
3
|
+
closestCenter,
|
|
4
|
+
KeyboardSensor,
|
|
5
|
+
PointerSensor,
|
|
6
|
+
useSensor,
|
|
7
|
+
useSensors,
|
|
8
|
+
type DragEndEvent,
|
|
9
|
+
} from '@dnd-kit/core'
|
|
10
|
+
import {
|
|
11
|
+
arrayMove,
|
|
12
|
+
SortableContext,
|
|
13
|
+
sortableKeyboardCoordinates,
|
|
14
|
+
verticalListSortingStrategy,
|
|
15
|
+
} from '@dnd-kit/sortable'
|
|
16
|
+
import { IconButton, Menu, SvgIcon } from '@mui/material'
|
|
17
|
+
import { useCallback, useMemo, useState, type MouseEvent } from 'react'
|
|
18
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
19
|
+
import type { ChangeColumnProps } from './types'
|
|
20
|
+
import { actionButtonStyles } from '../shared/styles'
|
|
21
|
+
import { Tooltip } from '../../../components'
|
|
22
|
+
import type { TableWidgetState } from '../../table/types'
|
|
23
|
+
import { ChangeColumnIcon } from './change-column-icon'
|
|
24
|
+
import { SortableColumnItem } from './sortable-column-item'
|
|
25
|
+
import { useShallow } from 'zustand/shallow'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Widget action to reorder columns in a table widget via drag-and-drop.
|
|
29
|
+
*
|
|
30
|
+
* This action reads the columns from the widget store and allows users to
|
|
31
|
+
* drag and drop columns to reorder them. All columns are displayed and
|
|
32
|
+
* can be reordered.
|
|
33
|
+
*
|
|
34
|
+
* Returns null if there are fewer than 2 columns.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <ChangeColumn id="my-table-widget" />
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function ChangeColumn({
|
|
42
|
+
id,
|
|
43
|
+
labels,
|
|
44
|
+
Icon,
|
|
45
|
+
IconButtonProps,
|
|
46
|
+
MenuProps,
|
|
47
|
+
}: ChangeColumnProps) {
|
|
48
|
+
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
|
|
49
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
50
|
+
const columns = useWidgetStore(
|
|
51
|
+
useShallow((state) => state.getWidget<TableWidgetState>(id)?.columns),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const sensors = useSensors(
|
|
55
|
+
useSensor(PointerSensor),
|
|
56
|
+
useSensor(KeyboardSensor, {
|
|
57
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
58
|
+
}),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const columnIds = useMemo(
|
|
62
|
+
() => columns?.map((col) => col.id) ?? [],
|
|
63
|
+
[columns],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const handleToggle = useCallback((event: MouseEvent<HTMLElement>) => {
|
|
67
|
+
event.stopPropagation()
|
|
68
|
+
setAnchorEl(event.currentTarget)
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
const handleClose = useCallback(() => {
|
|
72
|
+
setAnchorEl(null)
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
75
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
76
|
+
const { active, over } = event
|
|
77
|
+
if (!over || active.id === over.id || !columns) return
|
|
78
|
+
const oldIndex = columns.findIndex((col) => col.id === active.id)
|
|
79
|
+
const newIndex = columns.findIndex((col) => col.id === over.id)
|
|
80
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
81
|
+
const newColumns = arrayMove(columns, oldIndex, newIndex)
|
|
82
|
+
setWidget(id, { columns: newColumns })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Return null if there are fewer than 2 columns
|
|
87
|
+
if (!columns || columns.length < 2) {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const tooltipLabel = labels?.tooltip ?? 'Change column'
|
|
92
|
+
const isOpen = Boolean(anchorEl)
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<Tooltip title={tooltipLabel}>
|
|
97
|
+
<IconButton
|
|
98
|
+
size='small'
|
|
99
|
+
aria-label={labels?.ariaLabel ?? tooltipLabel}
|
|
100
|
+
aria-controls={isOpen ? 'change-column-menu' : undefined}
|
|
101
|
+
aria-haspopup='true'
|
|
102
|
+
aria-expanded={isOpen ? 'true' : undefined}
|
|
103
|
+
onClick={handleToggle}
|
|
104
|
+
data-active={isOpen}
|
|
105
|
+
sx={actionButtonStyles.trigger}
|
|
106
|
+
{...IconButtonProps}
|
|
107
|
+
>
|
|
108
|
+
{Icon ?? (
|
|
109
|
+
<SvgIcon>
|
|
110
|
+
<ChangeColumnIcon />
|
|
111
|
+
</SvgIcon>
|
|
112
|
+
)}
|
|
113
|
+
</IconButton>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
<DndContext
|
|
116
|
+
sensors={sensors}
|
|
117
|
+
collisionDetection={closestCenter}
|
|
118
|
+
onDragEnd={handleDragEnd}
|
|
119
|
+
>
|
|
120
|
+
<Menu
|
|
121
|
+
id='change-column-menu'
|
|
122
|
+
anchorEl={anchorEl}
|
|
123
|
+
open={isOpen}
|
|
124
|
+
onClose={handleClose}
|
|
125
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
|
126
|
+
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
|
127
|
+
{...MenuProps}
|
|
128
|
+
>
|
|
129
|
+
<SortableContext
|
|
130
|
+
items={columnIds}
|
|
131
|
+
strategy={verticalListSortingStrategy}
|
|
132
|
+
>
|
|
133
|
+
{columns.map((column) => (
|
|
134
|
+
<SortableColumnItem key={column.id} column={column} />
|
|
135
|
+
))}
|
|
136
|
+
</SortableContext>
|
|
137
|
+
</Menu>
|
|
138
|
+
</DndContext>
|
|
139
|
+
</>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useSortable } from '@dnd-kit/sortable'
|
|
2
|
+
import { CSS } from '@dnd-kit/utilities'
|
|
3
|
+
import { ListItemText, MenuItem } from '@mui/material'
|
|
4
|
+
import type { TableColumn } from '../../table/types'
|
|
5
|
+
|
|
6
|
+
export interface SortableColumnItemProps {
|
|
7
|
+
column: TableColumn
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A draggable menu item for column reordering.
|
|
12
|
+
* Uses @dnd-kit/sortable for drag-and-drop functionality.
|
|
13
|
+
*/
|
|
14
|
+
export function SortableColumnItem({ column }: SortableColumnItemProps) {
|
|
15
|
+
const {
|
|
16
|
+
attributes,
|
|
17
|
+
listeners,
|
|
18
|
+
setNodeRef,
|
|
19
|
+
transform,
|
|
20
|
+
transition,
|
|
21
|
+
isDragging,
|
|
22
|
+
} = useSortable({ id: column.id })
|
|
23
|
+
|
|
24
|
+
const style = {
|
|
25
|
+
transform: CSS.Transform.toString(transform),
|
|
26
|
+
transition,
|
|
27
|
+
opacity: isDragging ? 0.5 : 1,
|
|
28
|
+
cursor: isDragging ? 'grabbing' : 'grab',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<MenuItem
|
|
33
|
+
ref={setNodeRef}
|
|
34
|
+
style={style}
|
|
35
|
+
{...attributes}
|
|
36
|
+
{...listeners}
|
|
37
|
+
role='menuitem'
|
|
38
|
+
tabIndex={0}
|
|
39
|
+
sx={{
|
|
40
|
+
'&:focus-visible': {
|
|
41
|
+
outline: (theme) => `2px solid ${theme.palette.primary.main}`,
|
|
42
|
+
outlineOffset: -2,
|
|
43
|
+
},
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<ListItemText>{column.label}</ListItemText>
|
|
47
|
+
</MenuItem>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IconButtonProps, MenuProps } from '@mui/material'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface ChangeColumnProps {
|
|
5
|
+
/** Widget ID to update column configuration in the widget store */
|
|
6
|
+
id: string
|
|
7
|
+
/** Custom labels for the action */
|
|
8
|
+
labels?: {
|
|
9
|
+
/** Tooltip label */
|
|
10
|
+
tooltip?: string
|
|
11
|
+
/** Accessibility label */
|
|
12
|
+
ariaLabel?: string
|
|
13
|
+
}
|
|
14
|
+
/** Props passed to the IconButton component */
|
|
15
|
+
IconButtonProps?: IconButtonProps
|
|
16
|
+
/** Props passed to the Menu component */
|
|
17
|
+
MenuProps?: Partial<MenuProps>
|
|
18
|
+
/** Custom icon to display */
|
|
19
|
+
Icon?: ReactNode
|
|
20
|
+
}
|