@cdc/core 4.26.1 → 4.26.3
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/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/LICENSE +201 -0
- package/_stories/Gallery.Charts.stories.tsx +35 -42
- package/_stories/Gallery.DataBite.stories.tsx +15 -8
- package/_stories/Gallery.Maps.stories.tsx +37 -28
- package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +5 -4
- package/_stories/PageBRFSS.stories.tsx +21 -16
- package/_stories/PageCancerRegistries.stories.tsx +15 -15
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
- package/_stories/PageMaternalMortality.stories.tsx +5 -4
- package/_stories/PageOralHealth.stories.tsx +15 -10
- package/_stories/PageRespiratory.stories.tsx +4 -4
- package/_stories/PageSmokingTobacco.stories.tsx +15 -10
- package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
- package/_stories/PageWastewater.stories.tsx +44 -30
- package/_stories/VegaImport.stories.tsx +401 -0
- package/_stories/vega-fixtures/bars-with-line.json +444 -0
- package/_stories/vega-fixtures/bars.json +58 -0
- package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
- package/_stories/vega-fixtures/combo.json +68 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
- package/_stories/vega-fixtures/horizontal-bar.json +427 -0
- package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
- package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
- package/_stories/vega-fixtures/lines.json +227 -0
- package/_stories/vega-fixtures/measles-bars.json +348 -0
- package/_stories/vega-fixtures/measles-map.json +11101 -0
- package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
- package/_stories/vega-fixtures/multi-dataset.json +255 -0
- package/_stories/vega-fixtures/no-data.json +14 -0
- package/_stories/vega-fixtures/pie-chart.json +94 -0
- package/_stories/vega-fixtures/repeat-spec.json +47 -0
- package/_stories/vega-fixtures/stacked-area.json +222 -0
- package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
- package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
- package/_stories/vega-fixtures/stacked-bars.json +212 -0
- package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
- package/_stories/vega-fixtures/warning-combo.json +59 -0
- package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
- package/assets/callout-flag.svg +7 -0
- package/assets/icon-chart-area.svg +1 -0
- package/assets/icon-chart-radar.svg +23 -0
- package/assets/logo2.svg +31 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
- package/components/Alert/components/Alert.styles.css +2 -2
- package/components/ComboBox/combobox.styles.css +48 -48
- package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/DataTable.tsx +46 -18
- package/components/DataTable/DataTableStandAlone.tsx +1 -0
- package/components/DataTable/components/ChartHeader.tsx +21 -12
- package/components/DataTable/components/MapHeader.tsx +34 -28
- package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
- package/components/DataTable/data-table.css +50 -52
- package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
- package/components/DataTable/helpers/getChartCellValue.ts +10 -7
- package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
- package/components/DownloadButton.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +38 -31
- package/components/EditorPanel/CustomSortOrder.tsx +94 -0
- package/components/EditorPanel/DataTableEditor.tsx +139 -23
- package/components/EditorPanel/EditorPanel.styles.css +71 -71
- package/components/EditorPanel/EditorPanel.tsx +3 -8
- package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
- package/components/EditorPanel/FootnotesEditor.tsx +2 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
- package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
- package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
- package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
- package/components/EditorPanel/sections/VisualSection.tsx +11 -0
- package/components/EditorWrapper/editor-wrapper.style.css +1 -1
- package/components/Filters/Filters.tsx +3 -5
- package/components/Filters/components/Tabs.tsx +19 -7
- package/{styles → components/Filters}/filters.scss +3 -3
- package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
- package/components/Layout/components/Responsive.tsx +14 -6
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
- package/components/Layout/components/Visualization/index.tsx +50 -38
- package/components/Layout/components/Visualization/visualizations.scss +232 -15
- package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
- package/components/Layout/components/VisualizationContainer.tsx +37 -0
- package/components/Layout/components/VisualizationContent.test.tsx +182 -0
- package/components/Layout/components/VisualizationContent.tsx +75 -0
- package/components/Layout/index.tsx +5 -5
- package/components/Layout/styles/editor-utils.scss +3 -3
- package/components/Layout/styles/editor.scss +4 -4
- package/components/Legend/Legend.Gradient.tsx +7 -1
- package/components/Loader/loader.styles.css +2 -2
- package/components/Loading.jsx +1 -1
- package/components/MediaControls.tsx +10 -3
- package/components/MultiSelect/multiselect.styles.css +19 -19
- package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
- package/components/PaletteSelector/PaletteSelector.css +15 -15
- package/components/RichTooltip/richTooltip.css +6 -6
- package/components/Table/table.styles.css +2 -2
- package/components/Waiting.tsx +1 -1
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -1
- package/components/elements/Button.jsx +1 -1
- package/components/elements/Card.jsx +1 -1
- package/{styles/v2/components → components/elements}/button.scss +9 -8
- package/components/inputs/InputCheckbox.jsx +1 -1
- package/components/inputs/InputSelect.tsx +1 -1
- package/components/inputs/InputText.jsx +1 -1
- package/components/inputs/InputToggle.tsx +1 -1
- package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
- package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
- package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
- package/{styles → components}/loading.scss +1 -1
- package/components/managers/DataDesigner.tsx +1 -1
- package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +1 -1
- package/components/ui/LoadSpin.jsx +1 -1
- package/components/ui/Modal.jsx +1 -1
- package/components/ui/Overlay.jsx +1 -1
- package/components/ui/Title/index.test.tsx +34 -0
- package/components/ui/Title/index.tsx +24 -7
- package/components/ui/Title/title.styles.css +119 -25
- package/components/ui/Tooltip.tsx +1 -1
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/{styles/v2/components → components/ui}/accordion.scss +3 -3
- package/components/ui/accordion.styles.css +11 -11
- package/{styles/v2/components → components/ui}/modal.scss +2 -2
- package/{styles/v2/components → components/ui}/overlay.scss +6 -6
- package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
- package/{styles → components}/waiting.scss +9 -3
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +285 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2530 -3901
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +111 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/backfillDefaults.ts +35 -0
- package/helpers/constants.ts +12 -0
- package/helpers/cove/date.ts +64 -3
- package/helpers/cove/number.ts +29 -15
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +14 -8
- package/helpers/displayDataAsText.ts +1 -1
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +169 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +17 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/extractDataAndMetadata.ts +20 -0
- package/helpers/fetchRemoteData.ts +14 -8
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/labelHash.ts +9 -0
- package/helpers/markupProcessor.ts +56 -38
- package/helpers/metrics/types.ts +3 -0
- package/helpers/palettes/colorDistributions.ts +1 -1
- package/helpers/palettes/utils.ts +12 -12
- package/helpers/parseCsvWithQuotes.ts +15 -14
- package/helpers/prepareScreenshot.ts +33 -10
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/abbreviateNumber.test.ts +59 -0
- package/helpers/tests/backfillDefaults.test.ts +253 -0
- package/helpers/tests/date.test.ts +110 -0
- package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
- package/helpers/tests/markupProcessor.test.ts +315 -124
- package/helpers/tests/number.test.ts +42 -0
- package/helpers/tests/prepareScreenshot.test.ts +28 -28
- package/helpers/tests/testStandaloneBuild.ts +36 -26
- package/helpers/tests/useDataVizClasses.test.ts +66 -0
- package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
- package/helpers/useDataVizClasses.ts +13 -7
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.24.4.ts +24 -0
- package/helpers/ver/4.26.1.ts +1 -1
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/4.26.3.ts +44 -0
- package/helpers/ver/4.26.4.ts +31 -0
- package/helpers/ver/tests/4.26.1.test.ts +105 -0
- package/helpers/ver/tests/4.26.2.test.ts +298 -0
- package/helpers/ver/tests/4.26.3.test.ts +168 -0
- package/helpers/ver/tests/4.26.4.test.ts +88 -0
- package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +27 -32
- package/styles/_global.scss +7 -7
- package/styles/_reset.scss +2 -2
- package/styles/{v2/base → base}/_file-selector.scss +4 -4
- package/styles/{v2/base → base}/_general.scss +2 -4
- package/styles/{v2/base → base}/index.scss +1 -1
- package/styles/base.scss +107 -165
- package/styles/cove-main.scss +3 -6
- package/styles/layout/_component.scss +110 -0
- package/styles/{v2/layout → layout}/_data-table.scss +7 -7
- package/styles/layout/_wrapper-padding.scss +27 -0
- package/styles/{v2/main.scss → main.scss} +3 -1
- package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
- package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
- package/styles/{v2/utils → utils}/_grid.scss +8 -3
- package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
- package/styles/{v2/utils → utils}/index.scss +2 -1
- package/types/Annotation.ts +10 -11
- package/types/Axis.ts +2 -0
- package/types/ComponentStyles.ts +1 -0
- package/types/ConfigureData.ts +1 -0
- package/types/General.ts +2 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/MarkupVariable.ts +2 -1
- package/types/Palette.ts +22 -0
- package/types/Table.ts +9 -0
- package/types/Visualization.ts +7 -0
- package/_stories/StoryRenderingTests.stories.tsx +0 -164
- package/helpers/embedCodeGenerator.ts +0 -109
- package/styles/_common-components.css +0 -73
- package/styles/_variables.scss +0 -63
- package/styles/v2/layout/_component.scss +0 -21
- package/styles/v2/utils/_variables.scss +0 -9
- package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
- /package/{styles/v2/components → components/ui}/icon.scss +0 -0
- /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
- /package/styles/{v2/base → base}/_heading.scss +0 -0
- /package/styles/{v2/base → base}/_reset.scss +0 -0
- /package/styles/{v2/layout → layout}/_alert.scss +0 -0
- /package/styles/{v2/layout → layout}/_progression.scss +0 -0
- /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
- /package/styles/{v2/layout → layout}/index.scss +0 -0
- /package/styles/{v2/themes → themes}/index.scss +0 -0
- /package/styles/{v2/utils → utils}/_align.scss +0 -0
- /package/styles/{v2/utils → utils}/_animations.scss +0 -0
- /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
- /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
|
@@ -10,21 +10,23 @@ import FieldSetWrapper from './FieldSetWrapper'
|
|
|
10
10
|
import { useDataColumns } from '../../hooks/useDataColumns'
|
|
11
11
|
import Alert from '../Alert/components/Alert'
|
|
12
12
|
|
|
13
|
-
interface ColumnsEditorProps {
|
|
14
|
-
config: Partial<Visualization>
|
|
15
|
-
updateField: UpdateFieldFunc<string | boolean | string[] | number | Column | Record<string, Partial<Column>>>
|
|
16
|
-
deleteColumn: (colName: string) => void
|
|
17
|
-
|
|
13
|
+
interface ColumnsEditorProps {
|
|
14
|
+
config: Partial<Visualization>
|
|
15
|
+
updateField: UpdateFieldFunc<string | boolean | string[] | number | Column | Record<string, Partial<Column>>>
|
|
16
|
+
deleteColumn: (colName: string) => void
|
|
17
|
+
hiddenColumnNames?: string[]
|
|
18
|
+
}
|
|
18
19
|
|
|
19
20
|
type OpenControls = [Record<string, boolean>, Function] // useState type
|
|
20
21
|
|
|
21
|
-
const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenControls }> = ({
|
|
22
|
-
config,
|
|
23
|
-
deleteColumn,
|
|
24
|
-
updateField,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenControls }> = ({
|
|
23
|
+
config,
|
|
24
|
+
deleteColumn,
|
|
25
|
+
updateField,
|
|
26
|
+
hiddenColumnNames = [],
|
|
27
|
+
colKey,
|
|
28
|
+
controls
|
|
29
|
+
}) => {
|
|
28
30
|
const [openControls, setOpenControls] = controls
|
|
29
31
|
|
|
30
32
|
const editColumn = (key, value) => {
|
|
@@ -58,17 +60,18 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
|
|
|
58
60
|
const allColumns = useDataColumns(config.data)
|
|
59
61
|
|
|
60
62
|
// Filter out groupBy and already configured columns
|
|
61
|
-
const availableColumns = useMemo(() => {
|
|
62
|
-
const configuredColumns = Object.values(config.columns).map(col => col.name)
|
|
63
|
-
const cols = allColumns.filter(key => {
|
|
64
|
-
if (config.table.groupBy === key) return false
|
|
65
|
-
if (configuredColumns.includes(key)) return false
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const availableColumns = useMemo(() => {
|
|
64
|
+
const configuredColumns = Object.values(config.columns).map(col => col.name)
|
|
65
|
+
const cols = allColumns.filter(key => {
|
|
66
|
+
if (config.table.groupBy === key) return false
|
|
67
|
+
if (configuredColumns.includes(key)) return false
|
|
68
|
+
if (hiddenColumnNames.includes(key)) return false
|
|
69
|
+
return true
|
|
70
|
+
})
|
|
71
|
+
// Add current column name if it exists
|
|
72
|
+
if (config.columns[colKey]?.name) cols.push(config.columns[colKey].name)
|
|
73
|
+
return cols
|
|
74
|
+
}, [allColumns, config.table.groupBy, config.columns, colKey, hiddenColumnNames])
|
|
72
75
|
|
|
73
76
|
const colName = config.columns[colKey]?.name
|
|
74
77
|
|
|
@@ -264,9 +267,12 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
|
|
|
264
267
|
)
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, deleteColumn }) => {
|
|
268
|
-
const openControls = useState({})
|
|
269
|
-
const additionalColumns = Object.keys(config.columns)
|
|
270
|
+
const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, deleteColumn, hiddenColumnNames = [] }) => {
|
|
271
|
+
const openControls = useState({})
|
|
272
|
+
const additionalColumns = Object.keys(config.columns).filter(colKey => {
|
|
273
|
+
const columnName = config.columns[colKey]?.name || colKey
|
|
274
|
+
return !hiddenColumnNames.includes(columnName)
|
|
275
|
+
})
|
|
270
276
|
|
|
271
277
|
// just adds a new column but not set to any data yet
|
|
272
278
|
const addColumnConfig = number => {
|
|
@@ -312,11 +318,12 @@ const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, dele
|
|
|
312
318
|
key={val + i}
|
|
313
319
|
controls={openControls}
|
|
314
320
|
config={config}
|
|
315
|
-
deleteColumn={deleteColumn}
|
|
316
|
-
updateField={updateField}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
deleteColumn={deleteColumn}
|
|
322
|
+
updateField={updateField}
|
|
323
|
+
hiddenColumnNames={hiddenColumnNames}
|
|
324
|
+
colKey={val}
|
|
325
|
+
/>
|
|
326
|
+
))}
|
|
320
327
|
<button
|
|
321
328
|
className={'btn btn-primary'}
|
|
322
329
|
onClick={event => {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react'
|
|
2
|
+
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
3
|
+
import _ from 'lodash'
|
|
4
|
+
|
|
5
|
+
type CustomSortOrderProps = {
|
|
6
|
+
column: string
|
|
7
|
+
data: Record<string, any>[]
|
|
8
|
+
customOrder?: string[]
|
|
9
|
+
updateField: Function
|
|
10
|
+
/** Optional transform for display labels (e.g. displayGeoName for maps) */
|
|
11
|
+
displayTransform?: (value: string) => string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Editor component for drag-and-drop custom sort ordering.
|
|
16
|
+
* Shows unique values from the selected column and allows reordering via DnD.
|
|
17
|
+
*/
|
|
18
|
+
const CustomSortOrder: React.FC<CustomSortOrderProps> = ({
|
|
19
|
+
column,
|
|
20
|
+
data,
|
|
21
|
+
customOrder,
|
|
22
|
+
updateField,
|
|
23
|
+
displayTransform
|
|
24
|
+
}) => {
|
|
25
|
+
// Compute unique values from the selected column
|
|
26
|
+
const uniqueValues = useMemo(() => {
|
|
27
|
+
if (!column || !data?.length) return []
|
|
28
|
+
const values = data.map(row => String(row[column] ?? '')).filter(v => v !== '')
|
|
29
|
+
return _.uniq(values)
|
|
30
|
+
}, [column, data])
|
|
31
|
+
|
|
32
|
+
// Use customOrder if set, otherwise fall back to natural unique order
|
|
33
|
+
const orderedValues = useMemo(() => {
|
|
34
|
+
if (customOrder?.length) {
|
|
35
|
+
// Include any new values from data that aren't in customOrder
|
|
36
|
+
const extra = uniqueValues.filter(v => !customOrder.includes(v))
|
|
37
|
+
return [...customOrder.filter(v => uniqueValues.includes(v)), ...extra]
|
|
38
|
+
}
|
|
39
|
+
return uniqueValues
|
|
40
|
+
}, [customOrder, uniqueValues])
|
|
41
|
+
|
|
42
|
+
const handleDragEnd = useCallback(
|
|
43
|
+
({ source, destination }) => {
|
|
44
|
+
if (!destination || source.index === destination.index) return
|
|
45
|
+
const reordered = [...orderedValues]
|
|
46
|
+
const [moved] = reordered.splice(source.index, 1)
|
|
47
|
+
reordered.splice(destination.index, 0, moved)
|
|
48
|
+
updateField('table', 'defaultSort', 'customOrder', reordered)
|
|
49
|
+
},
|
|
50
|
+
[orderedValues, updateField]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if (!orderedValues.length) return null
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div>
|
|
57
|
+
<DragDropContext onDragEnd={handleDragEnd}>
|
|
58
|
+
<Droppable droppableId='custom_sort_order'>
|
|
59
|
+
{provided => (
|
|
60
|
+
<ul
|
|
61
|
+
{...provided.droppableProps}
|
|
62
|
+
className='sort-list'
|
|
63
|
+
ref={provided.innerRef}
|
|
64
|
+
style={{ marginTop: '0.5em', paddingLeft: 0, listStyle: 'none' }}
|
|
65
|
+
>
|
|
66
|
+
{orderedValues.map((value, index) => (
|
|
67
|
+
<Draggable key={value} draggableId={`customSort-${value}`} index={index}>
|
|
68
|
+
{(provided, snapshot) => (
|
|
69
|
+
<li style={{ marginBottom: '2px' }}>
|
|
70
|
+
<div
|
|
71
|
+
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
72
|
+
style={{
|
|
73
|
+
...provided.draggableProps.style
|
|
74
|
+
}}
|
|
75
|
+
ref={provided.innerRef}
|
|
76
|
+
{...provided.draggableProps}
|
|
77
|
+
{...provided.dragHandleProps}
|
|
78
|
+
>
|
|
79
|
+
{displayTransform ? displayTransform(value) : value}
|
|
80
|
+
</div>
|
|
81
|
+
</li>
|
|
82
|
+
)}
|
|
83
|
+
</Draggable>
|
|
84
|
+
))}
|
|
85
|
+
{provided.placeholder}
|
|
86
|
+
</ul>
|
|
87
|
+
)}
|
|
88
|
+
</Droppable>
|
|
89
|
+
</DragDropContext>
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default CustomSortOrder
|
|
@@ -7,6 +7,7 @@ import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
|
7
7
|
import { Visualization } from '../../types/Visualization'
|
|
8
8
|
import _ from 'lodash'
|
|
9
9
|
import { Column } from '../../types/Column'
|
|
10
|
+
import CustomSortOrder from './CustomSortOrder'
|
|
10
11
|
|
|
11
12
|
interface DataTableProps {
|
|
12
13
|
config: Partial<Visualization>
|
|
@@ -27,7 +28,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
27
28
|
}, [config.columns])
|
|
28
29
|
|
|
29
30
|
const groupPivotColumns = useMemo(() => {
|
|
30
|
-
const columns: string[] = config.data.flatMap(Object.keys)
|
|
31
|
+
const columns: string[] = (config.data ?? []).flatMap(Object.keys)
|
|
31
32
|
const cols = _.uniq(columns).filter(key => {
|
|
32
33
|
return true
|
|
33
34
|
})
|
|
@@ -39,6 +40,56 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
39
40
|
updateField('table', null, 'groupBy', value)
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
const changeSortColumn = (value: string) => {
|
|
44
|
+
if (value === '' || value === PLACEHOLDER) {
|
|
45
|
+
// Clear entire defaultSort when column is deselected
|
|
46
|
+
updateField('table', null, 'defaultSort', {})
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
// Set column and reset to ascending, clear custom order
|
|
50
|
+
updateField('table', null, 'defaultSort', { column: value, sortDirection: 'asc' })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const changeSortDirection = (value: string) => {
|
|
54
|
+
const newDefaultSort = { ...config.table.defaultSort, sortDirection: value }
|
|
55
|
+
// Clear customOrder when switching away from custom
|
|
56
|
+
if (value !== 'custom') {
|
|
57
|
+
delete newDefaultSort.customOrder
|
|
58
|
+
} else if (!newDefaultSort.customOrder?.length) {
|
|
59
|
+
// Auto-populate customOrder with unique values so the table updates immediately
|
|
60
|
+
const col = newDefaultSort.column
|
|
61
|
+
const dataCol = config.type === 'map' && config.columns?.[col]?.name ? config.columns[col].name : col
|
|
62
|
+
if (dataCol && config.data?.length) {
|
|
63
|
+
newDefaultSort.customOrder = _.uniq(config.data.map(row => String(row[dataCol] ?? '')).filter(v => v !== ''))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
updateField('table', null, 'defaultSort', newDefaultSort)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Build sort column options based on visualization type
|
|
70
|
+
// Maps use config.columns keys (geo, primary, etc.); other types use data column names
|
|
71
|
+
const sortColumnOptions = useMemo(() => {
|
|
72
|
+
if (config.type === 'map' && config.columns) {
|
|
73
|
+
return Object.keys(config.columns)
|
|
74
|
+
.filter(key => config.columns[key].dataTable !== false && config.columns[key].name)
|
|
75
|
+
.map(key => ({
|
|
76
|
+
label: config.columns[key].label || config.columns[key].name || key,
|
|
77
|
+
value: key
|
|
78
|
+
}))
|
|
79
|
+
}
|
|
80
|
+
return dataColumns.map(col => ({ label: col, value: col }))
|
|
81
|
+
}, [config.type, config.columns, dataColumns])
|
|
82
|
+
|
|
83
|
+
// For custom sort, resolve the actual data column name to get unique values
|
|
84
|
+
const customSortDataColumn = useMemo(() => {
|
|
85
|
+
const col = config.table?.defaultSort?.column
|
|
86
|
+
if (!col) return ''
|
|
87
|
+
if (config.type === 'map' && config.columns?.[col]?.name) {
|
|
88
|
+
return config.columns[col].name
|
|
89
|
+
}
|
|
90
|
+
return col
|
|
91
|
+
}, [config.type, config.columns, config.table?.defaultSort?.column])
|
|
92
|
+
|
|
42
93
|
const excludeColumns = (section, subSection, fieldName, excludedColNames: string[]) => {
|
|
43
94
|
const newColumns = _.cloneDeep(config.columns)
|
|
44
95
|
|
|
@@ -234,6 +285,49 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
234
285
|
updateField={updateField}
|
|
235
286
|
/>
|
|
236
287
|
)}
|
|
288
|
+
<Select
|
|
289
|
+
value={config.table.defaultSort?.column || ''}
|
|
290
|
+
fieldName='column'
|
|
291
|
+
section='table'
|
|
292
|
+
subsection='defaultSort'
|
|
293
|
+
label='Default Sort Column'
|
|
294
|
+
initial={PLACEHOLDER}
|
|
295
|
+
options={sortColumnOptions}
|
|
296
|
+
updateField={(_section, _subSection, _fieldName, value) => changeSortColumn(value)}
|
|
297
|
+
tooltip={
|
|
298
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
299
|
+
<Tooltip.Target>
|
|
300
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
301
|
+
</Tooltip.Target>
|
|
302
|
+
<Tooltip.Content>
|
|
303
|
+
<p>Choose a column to sort the data table by when it first loads.</p>
|
|
304
|
+
</Tooltip.Content>
|
|
305
|
+
</Tooltip>
|
|
306
|
+
}
|
|
307
|
+
/>
|
|
308
|
+
{config.table.defaultSort?.column && (
|
|
309
|
+
<Select
|
|
310
|
+
value={config.table.defaultSort?.sortDirection || 'asc'}
|
|
311
|
+
fieldName='sortDirection'
|
|
312
|
+
section='table'
|
|
313
|
+
subsection='defaultSort'
|
|
314
|
+
label='Sort Direction'
|
|
315
|
+
options={[
|
|
316
|
+
{ label: 'Ascending', value: 'asc' },
|
|
317
|
+
{ label: 'Descending', value: 'desc' },
|
|
318
|
+
{ label: 'Custom', value: 'custom' }
|
|
319
|
+
]}
|
|
320
|
+
updateField={(_section, _subSection, _fieldName, value) => changeSortDirection(value)}
|
|
321
|
+
/>
|
|
322
|
+
)}
|
|
323
|
+
{config.table.defaultSort?.column && config.table.defaultSort?.sortDirection === 'custom' && (
|
|
324
|
+
<CustomSortOrder
|
|
325
|
+
column={customSortDataColumn}
|
|
326
|
+
data={config.data}
|
|
327
|
+
customOrder={config.table.defaultSort.customOrder}
|
|
328
|
+
updateField={updateField}
|
|
329
|
+
/>
|
|
330
|
+
)}
|
|
237
331
|
<CheckBox
|
|
238
332
|
value={config.table.download}
|
|
239
333
|
fieldName='download'
|
|
@@ -259,6 +353,16 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
259
353
|
section='table'
|
|
260
354
|
updateField={updateField}
|
|
261
355
|
/>
|
|
356
|
+
<div className='ms-4 mt-2' style={{ maxWidth: 'calc(100% - 1.5rem)' }}>
|
|
357
|
+
<TextField
|
|
358
|
+
value={config.table.downloadDataLabel}
|
|
359
|
+
section='table'
|
|
360
|
+
fieldName='downloadDataLabel'
|
|
361
|
+
label='Download Data Link Text'
|
|
362
|
+
placeholder='Download Data (CSV)'
|
|
363
|
+
updateField={updateField}
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
262
366
|
</>
|
|
263
367
|
)}
|
|
264
368
|
{isDashboard && config.type !== 'table' && (
|
|
@@ -289,28 +393,40 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
289
393
|
/>
|
|
290
394
|
)}
|
|
291
395
|
{config.type !== 'table' && config.table.showDownloadImgButton && (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
<Tooltip
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
396
|
+
<>
|
|
397
|
+
<CheckBox
|
|
398
|
+
value={config.table.includeContextInDownload}
|
|
399
|
+
fieldName='includeContextInDownload'
|
|
400
|
+
className='ms-4'
|
|
401
|
+
label='Include Heading & Context'
|
|
402
|
+
section='table'
|
|
403
|
+
updateField={updateField}
|
|
404
|
+
tooltip={
|
|
405
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
406
|
+
<Tooltip.Target>
|
|
407
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
408
|
+
</Tooltip.Target>
|
|
409
|
+
<Tooltip.Content>
|
|
410
|
+
<p>
|
|
411
|
+
When enabled, the image download will include the section heading (H2 or H3) and any explanatory
|
|
412
|
+
paragraphs that appear immediately before the visualization. Be sure to test the image download on
|
|
413
|
+
the published page to ensure the correct context is included.
|
|
414
|
+
</p>
|
|
415
|
+
</Tooltip.Content>
|
|
416
|
+
</Tooltip>
|
|
417
|
+
}
|
|
418
|
+
/>
|
|
419
|
+
<div className='ms-4 mt-2' style={{ maxWidth: 'calc(100% - 1.5rem)' }}>
|
|
420
|
+
<TextField
|
|
421
|
+
value={config.table.downloadImageLabel}
|
|
422
|
+
section='table'
|
|
423
|
+
fieldName='downloadImageLabel'
|
|
424
|
+
label='Download Image Link Text'
|
|
425
|
+
placeholder={`Download ${config.type === 'map' ? 'Map' : 'Chart'} (PNG)`}
|
|
426
|
+
updateField={updateField}
|
|
427
|
+
/>
|
|
428
|
+
</div>
|
|
429
|
+
</>
|
|
314
430
|
)}
|
|
315
431
|
<label>
|
|
316
432
|
<span className='edit-label column-heading'>Table Cell Min Width</span>
|