Skip to content

Commit 1f49b26

Browse files
authored
Enhancement: standardize editor panel (#2462)
* Add BaseEditorPanel Refactor and Common Hooks - Creas BaseEditorPanel component to @cdc/core - Update all EditorPanel implementations to extend from BaseEditorPanel - Remove redundant styles and logic from individual EditorPanel components - Simplify imports and exports for EditorPanel components across packages - Create hooks for common data handling patterns - Improved state management in Header component * Enhance Editor Panel: Standardize filter dropdowns and improve accessibility labels * remove log * updates for getDataColumns * Rename getColumns to getNonPivotColumns for clarity in pivotData helper
1 parent 92b639b commit 1f49b26

File tree

39 files changed

+6834
-6482
lines changed

39 files changed

+6834
-6482
lines changed

packages/chart/src/_stories/ChartBar.Editor.stories.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2565,9 +2565,15 @@ export const BarFiltersTests: Story = {
25652565
'Apply Filter Value - Chart data visually filtered to show only Q2',
25662566
getChartDataState,
25672567
async () => {
2568-
// Find the "Filter Default Value" dropdown - this sets filter.active which actually filters the data
2569-
const filterDefaultValueSelect = canvas.getByLabelText(/filter default value/i) as HTMLSelectElement
2570-
2568+
// Find all "Filter Default Value (category)" dropdowns
2569+
const filterDefaultValueSelects = canvas.getAllByLabelText(
2570+
/filter default value \(category\)/i
2571+
) as HTMLSelectElement[]
2572+
// Select the dropdown that contains Q2 as an option
2573+
const filterDefaultValueSelect = filterDefaultValueSelects.find(select =>
2574+
Array.from(select.options).some(opt => opt.value === 'Q2')
2575+
)
2576+
if (!filterDefaultValueSelect) throw new Error('Could not find filter default value dropdown for Q2')
25712577
// Select Q2 to filter the chart to only show Q2 data (different from current state)
25722578
await userEvent.selectOptions(filterDefaultValueSelect, 'Q2')
25732579
},

packages/chart/src/components/EditorPanel/EditorPanel.tsx

Lines changed: 2704 additions & 2660 deletions
Large diffs are not rendered by default.

packages/chart/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,24 @@ import WarningImage from '../../../../images/warning.svg'
88

99
// contexts
1010
import ConfigContext from '../../../../ConfigContext'
11+
import { useEditorPanelContext } from '../../EditorPanelContext'
1112

1213
// types
1314
import { type ChartContext } from '../../../../types/ChartContext'
1415
import { type PanelProps } from '../PanelProps'
1516

16-
import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
17+
import {
18+
AccordionItem,
19+
AccordionItemHeading,
20+
AccordionItemPanel,
21+
AccordionItemButton
22+
} from 'react-accessible-accordion'
1723

1824
const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
1925
const { config, rawData: unfilteredData, updateConfig } = useContext<ChartContext>(ConfigContext)
26+
const { getColumns } = useEditorPanelContext()
2027
if (config.visualizationType !== 'Forest Plot') return
2128

22-
// todo: get from editor context?
2329
const enforceRestrictions = updatedConfig => {
2430
if (updatedConfig.orientation === 'horizontal') {
2531
updatedConfig.labels = false
@@ -33,31 +39,6 @@ const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
3339
}
3440
}
3541

36-
// todo: get from editor context?
37-
const getColumns = (filter = true) => {
38-
let columns = {}
39-
unfilteredData.forEach(row => {
40-
Object.keys(row).forEach(columnName => (columns[columnName] = true))
41-
})
42-
43-
if (filter) {
44-
Object.keys(columns).forEach(key => {
45-
if (
46-
(config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
47-
(config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))
48-
/*
49-
TODO: Resolve errors when config keys exist, but have no value
50-
Proposal: (((confidenceUpper && confidenceLower) || confidenceUpper || confidenceLower) && Object.keys(config.confidenceKeys).includes(key))
51-
*/
52-
) {
53-
delete columns[key]
54-
}
55-
})
56-
}
57-
58-
return Object.keys(columns)
59-
}
60-
6142
// todo: editor context?
6243
const updateField = (section, subsection, fieldName, newValue) => {
6344
if (section === 'boxplot' && subsection === 'legend') {
@@ -151,7 +132,9 @@ const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
151132
<AccordionItemHeading>
152133
<AccordionItemButton>
153134
{name}
154-
{(!config.forestPlot.estimateField || !config.forestPlot.upper || !config.forestPlot.lower) && <WarningImage width='25' className='warning-icon' />}
135+
{(!config.forestPlot.estimateField || !config.forestPlot.upper || !config.forestPlot.lower) && (
136+
<WarningImage width='25' className='warning-icon' />
137+
)}
155138
</AccordionItemButton>
156139
</AccordionItemHeading>
157140
<AccordionItemPanel>
@@ -201,14 +184,22 @@ const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
201184
<Tooltip.Content>
202185
<p>
203186
Linear - Typically used for continuous outcomes. Line of no effect is positioned on 0 (zero) <br />
204-
<br /> Logarithmic - Typically used for binary outcomes such as risk ratios and odds ratios. Line of no effect is positioned on 1.
187+
<br /> Logarithmic - Typically used for binary outcomes such as risk ratios and odds ratios. Line of
188+
no effect is positioned on 1.
205189
</p>
206190
</Tooltip.Content>
207191
</Tooltip>
208192
}
209193
/>
210194

211-
<TextField type='text' value={config.forestPlot?.title || ''} updateField={updateField} section='forestPlot' fieldName='title' label='Plot Title' />
195+
<TextField
196+
type='text'
197+
value={config.forestPlot?.title || ''}
198+
updateField={updateField}
199+
section='forestPlot'
200+
fieldName='title'
201+
label='Plot Title'
202+
/>
212203

213204
<br />
214205
<hr />
@@ -317,7 +308,14 @@ const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
317308
</span>
318309
</label>
319310

320-
<CheckBox value={config.forestPlot?.lineOfNoEffect?.show || false} section='forestPlot' subsection='lineOfNoEffect' fieldName='show' label='Show Line of No Effect' updateField={updateField} />
311+
<CheckBox
312+
value={config.forestPlot?.lineOfNoEffect?.show || false}
313+
section='forestPlot'
314+
subsection='lineOfNoEffect'
315+
fieldName='show'
316+
label='Show Line of No Effect'
317+
updateField={updateField}
318+
/>
321319

322320
<br />
323321
<hr />
@@ -400,13 +398,37 @@ const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
400398
/>
401399
</label>
402400

403-
<TextField type='number' min={20} max={45} value={config.forestPlot.rowHeight ? config.forestPlot.rowHeight : 10} updateField={updateField} section='forestPlot' fieldName='rowHeight' label='Row Height' placeholder='10' />
401+
<TextField
402+
type='number'
403+
min={20}
404+
max={45}
405+
value={config.forestPlot.rowHeight ? config.forestPlot.rowHeight : 10}
406+
updateField={updateField}
407+
section='forestPlot'
408+
fieldName='rowHeight'
409+
label='Row Height'
410+
placeholder='10'
411+
/>
404412
<br />
405413
<hr />
406414
<br />
407415
<h4>Labels Settings</h4>
408-
<TextField type='text' value={config.forestPlot?.leftLabel || ''} updateField={updateField} section='forestPlot' fieldName='leftLabel' label='Left Label' />
409-
<TextField type='text' value={config.forestPlot?.rightLabel || ''} updateField={updateField} section='forestPlot' fieldName='rightLabel' label='Right Label' />
416+
<TextField
417+
type='text'
418+
value={config.forestPlot?.leftLabel || ''}
419+
updateField={updateField}
420+
section='forestPlot'
421+
fieldName='leftLabel'
422+
label='Left Label'
423+
/>
424+
<TextField
425+
type='text'
426+
value={config.forestPlot?.rightLabel || ''}
427+
updateField={updateField}
428+
section='forestPlot'
429+
fieldName='rightLabel'
430+
label='Right Label'
431+
/>
410432

411433
<br />
412434
<hr />

packages/chart/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext, FC } from 'react'
1+
import { useContext, FC, useMemo } from 'react'
22
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
33
import {
44
AccordionItem,
@@ -11,6 +11,7 @@ import {
1111
import { TextField, Select, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
1212
import Tooltip from '@cdc/core/components/ui/Tooltip'
1313
import Icon from '@cdc/core/components/ui/Icon'
14+
import { useDataColumns } from '@cdc/core/hooks/useDataColumns'
1415

1516
// contexts
1617
import { ChartContext } from './../../../../types/ChartContext.js'
@@ -25,30 +26,30 @@ const PanelSmallMultiples: FC<PanelProps> = props => {
2526
const { updateField } = useEditorPanelContext()
2627
const { visSupportsSmallMultiples } = useEditorPermissions()
2728

28-
const getColumns = (filter = true) => {
29-
let columns = {}
30-
rawData?.forEach(row => {
31-
Object.keys(row).forEach(columnName => (columns[columnName] = true))
32-
})
33-
34-
if (filter) {
35-
const { lower, upper } = config.confidenceKeys || {}
36-
Object.keys(columns).forEach(key => {
37-
if (
38-
(config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
39-
(config.confidenceKeys &&
40-
Object.keys(config.confidenceKeys).includes(key) &&
41-
((lower && upper) || lower || upper) &&
42-
key !== lower &&
43-
key !== upper)
44-
) {
45-
delete columns[key]
46-
}
47-
})
48-
}
29+
// Extract column names from data with memoization (replaces getColumns)
30+
const allColumns = useDataColumns(rawData)
4931

50-
return Object.keys(columns)
51-
}
32+
// Filter out series columns and confidence key columns (except lower and upper)
33+
const filteredColumns = useMemo(() => {
34+
const { lower, upper } = config.confidenceKeys || {}
35+
return allColumns.filter(key => {
36+
// Filter out series columns
37+
if (config.series && config.series.some(series => series.dataKey === key)) {
38+
return false
39+
}
40+
// Filter out confidence key columns (except lower and upper)
41+
if (
42+
config.confidenceKeys &&
43+
Object.keys(config.confidenceKeys).includes(key) &&
44+
((lower && upper) || lower || upper) &&
45+
key !== lower &&
46+
key !== upper
47+
) {
48+
return false
49+
}
50+
return true
51+
})
52+
}, [allColumns, config.series, config.confidenceKeys])
5253

5354
return (
5455
<>
@@ -91,7 +92,7 @@ const PanelSmallMultiples: FC<PanelProps> = props => {
9192
label='Tile By Column'
9293
initial='Select Column'
9394
updateField={updateField}
94-
options={getColumns()}
95+
options={filteredColumns}
9596
/>
9697
)}
9798

@@ -184,6 +185,8 @@ const PanelSmallMultiples: FC<PanelProps> = props => {
184185
value={currentOrderType}
185186
options={tileOrderOptions}
186187
label='Tile Order'
188+
fieldName='tileOrderType'
189+
section='smallMultiples'
187190
updateField={(_section, _subsection, _fieldName, value) => {
188191
handleOrderTypeChange(value)
189192
}}
@@ -244,6 +247,8 @@ const PanelSmallMultiples: FC<PanelProps> = props => {
244247
}
245248
]}
246249
label='Color Mode'
250+
fieldName='colorMode'
251+
section='smallMultiples'
247252
updateField={(_section, _subsection, _fieldName, value) => {
248253
updateConfig({
249254
...config,

packages/core/components/EditorPanel/ColumnsEditor.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { Visualization } from '../../types/Visualization'
55
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
66
import { Column } from '../../types/Column'
77
import _ from 'lodash'
8-
import React, { useState } from 'react'
8+
import React, { useState, useMemo } from 'react'
99
import FieldSetWrapper from './FieldSetWrapper'
10+
import { useDataColumns } from '../../hooks/useDataColumns'
1011

1112
interface ColumnsEditorProps {
1213
config: Partial<Visualization>
@@ -52,19 +53,21 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
5253
updateField(null, null, 'columns', newColumns)
5354
}
5455

55-
const getColumns = () => {
56-
const columns: string[] = config.data.flatMap(row => {
57-
return Object.keys(row).map(columnName => columnName)
58-
})
56+
// Extract column names from data with memoization (replaces getColumns)
57+
const allColumns = useDataColumns(config.data)
58+
59+
// Filter out groupBy and already configured columns
60+
const availableColumns = useMemo(() => {
5961
const configuredColumns = Object.values(config.columns).map(col => col.name)
60-
const cols = _.uniq(columns).filter(key => {
62+
const cols = allColumns.filter(key => {
6163
if (config.table.groupBy === key) return false
6264
if (configuredColumns.includes(key)) return false
6365
return true
6466
})
67+
// Add current column name if it exists
6568
if (config.columns[colKey]?.name) cols.push(config.columns[colKey].name)
6669
return cols
67-
}
70+
}, [allColumns, config.table.groupBy, config.columns, colKey])
6871

6972
const colName = config.columns[colKey]?.name
7073

@@ -82,7 +85,7 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
8285
fieldName='name'
8386
section={'columns'}
8487
initial={'-Select-'}
85-
options={getColumns()}
88+
options={availableColumns}
8689
updateField={(_section, _subsection, _fieldName, value) => changeName(value)}
8790
/>
8891
{config.type !== 'table' && (

packages/core/components/EditorPanel/EditorPanel.styles.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,19 @@
421421
.editor-toggle svg path {
422422
fill: currentColor;
423423
}
424+
425+
/* Shared styles consolidated from package-specific EditorPanel.styles.css files */
426+
/* Previously duplicated in: data-bite, filtered-text, markup-include */
427+
.editor-panel .condition-section :is(label) {
428+
font-size: 1em;
429+
}
430+
431+
:is(span).edit-label {
432+
margin-bottom: 0.3em;
433+
display: block;
434+
}
435+
436+
.react-tooltip {
437+
position: absolute;
438+
width: 250px;
439+
}

0 commit comments

Comments
 (0)