@cdc/chart 4.25.10 → 4.25.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +36258 -34658
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/private/DEV-11825.json +573 -0
  6. package/examples/private/na.json +913 -0
  7. package/examples/private/test-data.csv +28 -0
  8. package/index.html +2 -121
  9. package/package.json +4 -4
  10. package/src/CdcChart.tsx +8 -11
  11. package/src/CdcChartComponent.tsx +256 -87
  12. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  13. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  14. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  15. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  16. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  17. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  18. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  19. package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
  20. package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
  21. package/src/_stories/ChartEditor.stories.tsx +1 -2
  22. package/src/_stories/_mock/combo.json +451 -0
  23. package/src/_stories/_mock/editor-test-configs.json +376 -0
  24. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  25. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  26. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  27. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  28. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  29. package/src/_stories/_mock/pie_config.json +257 -62
  30. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  31. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  32. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  33. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  34. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  35. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  36. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
  37. package/src/components/AreaChart/index.tsx +1 -2
  38. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -4
  39. package/src/components/BarChart/components/BarChart.Vertical.tsx +3 -2
  40. package/src/components/BoxPlot/helpers/index.ts +3 -3
  41. package/src/components/Brush/BrushChart.tsx +1 -1
  42. package/src/components/EditorPanel/EditorPanel.tsx +199 -190
  43. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  44. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +102 -55
  46. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  47. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
  48. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +75 -21
  49. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  50. package/src/components/EditorPanel/editor-panel.scss +0 -20
  51. package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
  52. package/src/components/Forecasting/Forecasting.tsx +139 -21
  53. package/src/components/Legend/Legend.Component.tsx +16 -9
  54. package/src/components/Legend/helpers/createFormatLabels.tsx +181 -181
  55. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  56. package/src/components/LineChart/LineChartProps.ts +0 -3
  57. package/src/components/LineChart/helpers.ts +1 -1
  58. package/src/components/LineChart/index.tsx +36 -13
  59. package/src/components/LinearChart.tsx +75 -80
  60. package/src/components/Regions/components/Regions.tsx +3 -24
  61. package/src/components/Sankey/types/index.ts +1 -1
  62. package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
  63. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  64. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  65. package/src/components/SmallMultiples/index.ts +2 -0
  66. package/src/data/initial-state.js +13 -1
  67. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  68. package/src/helpers/getColorScale.ts +10 -0
  69. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
  70. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  71. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  72. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  73. package/src/hooks/useScales.ts +88 -34
  74. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  75. package/src/hooks/useTooltip.tsx +60 -15
  76. package/src/scss/main.scss +1 -80
  77. package/src/store/chart.actions.ts +2 -0
  78. package/src/store/chart.reducer.ts +4 -0
  79. package/src/types/ChartConfig.ts +24 -6
  80. package/src/types/ChartContext.ts +3 -0
  81. package/src/_stories/_mock/pie_data.json +0 -218
  82. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  83. package/src/helpers/sort.ts +0 -7
  84. package/src/hooks/useActiveElement.js +0 -19
  85. package/src/hooks/useChartClasses.js +0 -41
@@ -4,6 +4,7 @@ import ConfigContext from '../../../../ConfigContext.js'
4
4
  // CDC Core
5
5
  import Accordion from '@cdc/core/components/ui/Accordion'
6
6
  import Button from '@cdc/core/components/elements/Button'
7
+ import { CheckBox, Select } from '@cdc/core/components/EditorPanel/Inputs'
7
8
  import _ from 'lodash'
8
9
 
9
10
  // types
@@ -14,6 +15,29 @@ import './../panels.scss'
14
15
  const PanelAnnotate: React.FC<PanelProps> = props => {
15
16
  const { updateConfig, config, svgRef } = useContext(ConfigContext)
16
17
 
18
+ const updateField = (section, subsection, fieldName, value) => {
19
+ if (subsection) {
20
+ updateConfig({
21
+ ...config,
22
+ [section]: {
23
+ ...config[section],
24
+ [subsection]: {
25
+ ...config[section][subsection],
26
+ [fieldName]: value
27
+ }
28
+ }
29
+ })
30
+ } else {
31
+ updateConfig({
32
+ ...config,
33
+ [section]: {
34
+ ...config[section],
35
+ [fieldName]: value
36
+ }
37
+ })
38
+ }
39
+ }
40
+
17
41
  const handleAnnotationUpdate = (value, property, index) => {
18
42
  const svgContainer = document.querySelector('.chart-container > svg')?.getBoundingClientRect()
19
43
  const newSvgDims = [svgContainer?.width, svgContainer?.height]
@@ -67,8 +91,8 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
67
91
  config.xAxis.type === 'date'
68
92
  ? new Date(config?.data?.[0]?.[config.xAxis.dataKey]).getTime()
69
93
  : config.xAxis.type === 'categorical'
70
- ? '1/15/2016'
71
- : '',
94
+ ? '1/15/2016'
95
+ : '',
72
96
  yKey: '',
73
97
  dx: 20,
74
98
  dy: -20,
@@ -96,22 +120,14 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
96
120
  return (
97
121
  <Accordion key={props.name}>
98
122
  <Accordion.Section title={props.name} key={props.name}>
99
- <label key={`key-1`}>
100
- Show Annotation Dropdown
101
- <input
102
- type='checkbox'
103
- checked={config?.general?.showAnnotationDropdown || false}
104
- onChange={e => {
105
- updateConfig({
106
- ...config,
107
- general: {
108
- ...config.general,
109
- showAnnotationDropdown: e.target.checked
110
- }
111
- })
112
- }}
113
- />
114
- </label>
123
+ <CheckBox
124
+ value={config?.general?.showAnnotationDropdown || false}
125
+ section='general'
126
+ subsection={null}
127
+ fieldName='showAnnotationDropdown'
128
+ label='Show Annotation Dropdown'
129
+ updateField={updateField}
130
+ />
115
131
 
116
132
  {config.general.showAnnotationDropdown && (
117
133
  <label key={`key-2`}>
@@ -164,61 +180,53 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
164
180
  />
165
181
  </label>
166
182
 
167
- <label>
168
- Edit Subject
169
- <input
170
- type='checkbox'
171
- checked={config?.annotations[index]?.edit?.subject || false}
172
- onChange={e => {
173
- const updatedAnnotations = _.cloneDeep(config?.annotations)
174
- updatedAnnotations[index].edit.subject = e.target.checked
175
- updateConfig({
176
- ...config,
177
- annotations: updatedAnnotations
178
- })
179
- }}
180
- />
181
- </label>
182
- <label>
183
- Edit Label
184
- <input
185
- type='checkbox'
186
- checked={config?.annotations[index]?.edit?.label || false}
187
- onChange={e => {
188
- const updatedAnnotations = _.cloneDeep(config?.annotations)
189
- updatedAnnotations[index].edit.label = e.target.checked
190
- updateConfig({
191
- ...config,
192
- annotations: updatedAnnotations
193
- })
194
- }}
195
- />
196
- </label>
183
+ <CheckBox
184
+ value={config?.annotations[index]?.edit?.subject || false}
185
+ section='annotations'
186
+ subsection={null}
187
+ fieldName={`${index}.edit.subject`}
188
+ label='Edit Subject'
189
+ updateField={(section, subsection, fieldName, value) => {
190
+ const updatedAnnotations = _.cloneDeep(config?.annotations)
191
+ updatedAnnotations[index].edit.subject = value
192
+ updateConfig({
193
+ ...config,
194
+ annotations: updatedAnnotations
195
+ })
196
+ }}
197
+ />
198
+ <CheckBox
199
+ value={config?.annotations[index]?.edit?.label || false}
200
+ section='annotations'
201
+ subsection={null}
202
+ fieldName={`${index}.edit.label`}
203
+ label='Edit Label'
204
+ updateField={(section, subsection, fieldName, value) => {
205
+ const updatedAnnotations = _.cloneDeep(config?.annotations)
206
+ updatedAnnotations[index].edit.label = value
207
+ updateConfig({
208
+ ...config,
209
+ annotations: updatedAnnotations
210
+ })
211
+ }}
212
+ />
197
213
 
198
- <label>
199
- Connection Type:
200
- <select
201
- key='annotation-connection-type'
202
- onChange={e => {
203
- const updatedAnnotations = _.cloneDeep(config?.annotations)
204
- updatedAnnotations[index].connectionType = e.target.value
205
- updateConfig({
206
- ...config,
207
- annotations: updatedAnnotations
208
- })
209
- }}
210
- value={config?.annotations[index]?.connectionType}
211
- >
212
- <option key='select' value='select'>
213
- Select
214
- </option>
215
- {['curve', 'line', 'elbow', 'none'].map((side, index) => (
216
- <option key={side} value={side}>
217
- {side}
218
- </option>
219
- ))}
220
- </select>
221
- </label>
214
+ <Select
215
+ label='Connection Type:'
216
+ value={config?.annotations[index]?.connectionType}
217
+ options={['Select', 'curve', 'line', 'elbow', 'none']}
218
+ section='annotations'
219
+ subsection={null}
220
+ fieldName='connectionType'
221
+ updateField={(section, subsection, fieldName, value) => {
222
+ const updatedAnnotations = _.cloneDeep(config?.annotations)
223
+ updatedAnnotations[index].connectionType = value
224
+ updateConfig({
225
+ ...config,
226
+ annotations: updatedAnnotations
227
+ })
228
+ }}
229
+ />
222
230
 
223
231
  {annotation.connectionType === 'curve' && (
224
232
  <>
@@ -243,45 +251,22 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
243
251
  </>
244
252
  )}
245
253
 
246
- {/* <label>
247
- Connection Location:
248
- <select
249
- onChange={e => {
250
- const updatedAnnotations = _.cloneDeep(config?.annotations)
251
- updatedAnnotations[index].connectionLocation = e.target.value
252
- updateConfig({
253
- ...config,
254
- annotations: updatedAnnotations
255
- })
256
- }}
257
- >
258
- {['auto', 'left', 'top', 'bottom', 'right'].map((side, index) => (
259
- <option key={side} value={side}>
260
- {side}
261
- </option>
262
- ))}
263
- </select>
264
- </label> */}
265
-
266
- <label>
267
- Marker
268
- <select
269
- key='annotation-marker'
270
- value={annotation.marker}
271
- onChange={e => {
272
- const updatedAnnotations = _.cloneDeep(config?.annotations)
273
- updatedAnnotations[index].marker = e.target.value
274
- updateConfig({
275
- ...config,
276
- annotations: updatedAnnotations
277
- })
278
- }}
279
- >
280
- {['arrow', 'circle'].map((column, columnIndex) => {
281
- return <option key={`col-${columnIndex}`}>{column}</option>
282
- })}
283
- </select>
284
- </label>
254
+ <Select
255
+ label='Marker'
256
+ value={annotation.marker}
257
+ options={['arrow', 'circle']}
258
+ section='annotations'
259
+ subsection={null}
260
+ fieldName='marker'
261
+ updateField={(section, subsection, fieldName, value) => {
262
+ const updatedAnnotations = _.cloneDeep(config?.annotations)
263
+ updatedAnnotations[index].marker = value
264
+ updateConfig({
265
+ ...config,
266
+ annotations: updatedAnnotations
267
+ })
268
+ }}
269
+ />
285
270
 
286
271
  <Button className='btn btn-danger full-width' onClick={() => handleRemoveAnnotation(index)}>
287
272
  Delete Annotation
@@ -21,7 +21,7 @@ import ConfigContext from '../../../../ConfigContext.js'
21
21
  import { PanelProps } from '../PanelProps'
22
22
 
23
23
  const PanelGeneral: FC<PanelProps> = props => {
24
- const { config } = useContext(ConfigContext)
24
+ const { config, updateConfig } = useContext(ConfigContext)
25
25
  const { updateField } = useEditorPanelContext()
26
26
  const {
27
27
  enabledChartTypes,
@@ -62,6 +62,24 @@ const PanelGeneral: FC<PanelProps> = props => {
62
62
  label='Chart Type'
63
63
  updateField={updateField}
64
64
  options={enabledChartTypes}
65
+ onChange={event => {
66
+ const newVisType = event.target.value
67
+
68
+ updateField(null, null, 'visualizationType', newVisType)
69
+
70
+ if (newVisType === 'Forecasting' && config.xAxis.type === 'categorical') {
71
+ updateConfig({
72
+ ...config,
73
+ visualizationType: newVisType,
74
+ xAxis: {
75
+ ...config.xAxis,
76
+ type: 'date',
77
+ dateParseFormat: config.xAxis.dateParseFormat || '%Y-%m-%d',
78
+ dateDisplayFormat: config.xAxis.dateDisplayFormat || '%Y-%m-%d'
79
+ }
80
+ })
81
+ }
82
+ }}
65
83
  />
66
84
  )}
67
85
  {visSupportsChartHeight() && config.orientation === 'vertical' && (
@@ -10,6 +10,7 @@ import Tooltip from '@cdc/core/components/ui/Tooltip'
10
10
  import Icon from '@cdc/core/components/ui/Icon'
11
11
  import Button from '@cdc/core/components/elements/Button'
12
12
  import Alert from '@cdc/core/components/Alert'
13
+ import { Select } from '@cdc/core/components/EditorPanel/Inputs'
13
14
  import ConfigContext from '../../../../ConfigContext'
14
15
  import { ChartContext } from '../../../../types/ChartContext'
15
16
  import { PanelProps } from '../PanelProps'
@@ -89,17 +90,22 @@ const PanelPatternSettings: FC<PanelProps> = props => {
89
90
  }
90
91
 
91
92
  // Checks contrast and logs warning if needed
92
- const checkAndLogContrast = (fill: string, patternColor: string, dataValue: string, dataKey: string): boolean => {
93
- if (!fill || !patternColor) return true // Default to true if colors are missing
93
+ const checkAndLogContrast = (
94
+ patternColor: string,
95
+ backgroundColor: string,
96
+ dataValue: string,
97
+ dataKey: string
98
+ ): boolean => {
99
+ if (!backgroundColor || !patternColor) return true // Default to true if colors are missing
94
100
 
95
- const contrastCheck = checkColorContrast(fill, patternColor)
101
+ const contrastCheck = checkColorContrast(patternColor, backgroundColor)
96
102
 
97
103
  if (!contrastCheck) {
98
104
  console.error(
99
105
  `COVE: pattern contrast check failed for ${dataValue} in ${dataKey} with:
100
106
  pattern color: ${patternColor}
101
- background color: ${fill}
102
- contrast: ${getColorContrast(fill, patternColor)}`
107
+ background color: ${backgroundColor}
108
+ contrast: ${getColorContrast(patternColor, backgroundColor)}`
103
109
  )
104
110
  }
105
111
 
@@ -108,7 +114,9 @@ const PanelPatternSettings: FC<PanelProps> = props => {
108
114
 
109
115
  // Perform contrast check for a specific pattern against actual bar colors
110
116
  const performContrastCheck = (patternKey: string, patternColor: string) => {
111
- if (!patternColor || patternColor === '') return true
117
+ if (!patternColor || patternColor === '') {
118
+ return true
119
+ }
112
120
 
113
121
  // Get the actual bar colors that the pattern will be overlaid on
114
122
  let seriesColors: string[] = []
@@ -138,8 +146,8 @@ const PanelPatternSettings: FC<PanelProps> = props => {
138
146
  const contrastResults: Array<{ color: string; passes: boolean; ratio: number | false }> = []
139
147
 
140
148
  seriesColors.forEach((barColor, index) => {
141
- const contrastPasses = checkAndLogContrast(barColor, patternColor, patternKey, `series-${index}`)
142
- const contrastRatio = getColorContrast(barColor, patternColor)
149
+ const contrastPasses = checkAndLogContrast(patternColor, barColor, patternKey, `series-${index}`)
150
+ const contrastRatio = getColorContrast(patternColor, barColor)
143
151
 
144
152
  contrastResults.push({
145
153
  color: barColor,
@@ -184,26 +192,48 @@ const PanelPatternSettings: FC<PanelProps> = props => {
184
192
  }
185
193
  }
186
194
 
187
- updateConfig({
195
+ const updatedConfig = {
188
196
  ...config,
189
197
  legend: {
190
198
  ...(config.legend || {}),
191
199
  patterns: newPatterns
200
+ },
201
+ runtime: {
202
+ ...config.runtime
192
203
  }
193
- })
204
+ }
205
+
206
+ // Check if all patterns pass and set error message
207
+ const allPatternsPass = Object.values(newPatterns).every((p: any) => p.contrastCheck !== false)
208
+ updatedConfig.runtime.editorErrorMessage = allPatternsPass
209
+ ? ''
210
+ : 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
211
+
212
+ updateConfig(updatedConfig)
194
213
  }
195
214
 
196
215
  const handleRemovePattern = (patternKey: string) => {
197
216
  const newPatterns = { ...(legendCfg.patterns || {}) }
198
217
  delete newPatterns[patternKey]
199
218
 
200
- updateConfig({
219
+ const updatedConfig = {
201
220
  ...config,
202
221
  legend: {
203
222
  ...(config.legend || {}),
204
223
  patterns: newPatterns
224
+ },
225
+ runtime: {
226
+ ...config.runtime
205
227
  }
206
- })
228
+ }
229
+
230
+ // Check if all remaining patterns pass and clear error message if needed
231
+ const allPatternsPass = Object.values(newPatterns).every((p: any) => p.contrastCheck !== false)
232
+ if (allPatternsPass || Object.keys(newPatterns).length === 0) {
233
+ updatedConfig.runtime.editorErrorMessage = ''
234
+ }
235
+
236
+ updateConfig(updatedConfig)
207
237
  }
208
238
 
209
239
  const handlePatternKeyChange = (oldKey: string, newKey: string) => {
@@ -228,15 +258,33 @@ const PanelPatternSettings: FC<PanelProps> = props => {
228
258
  })
229
259
  }
230
260
 
261
+ const reviewColorContrast = (updatedConfig: any, patternKey: string) => {
262
+ // Re-check the contrast for the updated pattern
263
+ const pattern = updatedConfig.legend.patterns[patternKey]
264
+
265
+ if (pattern?.color) {
266
+ pattern.contrastCheck = performContrastCheck(patternKey, pattern.color)
267
+ }
268
+
269
+ // Update error message based on whether all patterns pass contrast checks
270
+ const allPatterns = Object.values(updatedConfig.legend.patterns || {})
271
+
272
+ const allPatternsPass = allPatterns.every((p: any) => p.contrastCheck !== false)
273
+
274
+ const errorMsg = allPatternsPass ? '' : 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
275
+ // Set error message AFTER spreading runtime to avoid it being overwritten
276
+ updatedConfig.runtime.editorErrorMessage = errorMsg
277
+ }
278
+
231
279
  const handlePatternUpdate = (patternKey: string, field: string, value: any) => {
232
280
  const updatedPattern = {
233
281
  ...(legendCfg.patterns?.[patternKey] || {}),
234
282
  [field]: value
235
283
  }
236
284
 
237
- // Perform contrast check if color is being updated
238
- if (field === 'color') {
239
- updatedPattern.contrastCheck = performContrastCheck(patternKey, value)
285
+ // Clear dataValue if dataKey is being cleared or set to 'Select'
286
+ if (field === 'dataKey' && (value === 'Select' || value === '')) {
287
+ updatedPattern.dataValue = ''
240
288
  }
241
289
 
242
290
  const newPatterns = {
@@ -244,13 +292,23 @@ const PanelPatternSettings: FC<PanelProps> = props => {
244
292
  [patternKey]: updatedPattern
245
293
  }
246
294
 
247
- updateConfig({
295
+ const updatedConfig = {
248
296
  ...config,
249
297
  legend: {
250
298
  ...(config.legend || {}),
251
299
  patterns: newPatterns
300
+ },
301
+ runtime: {
302
+ ...config.runtime
252
303
  }
253
- })
304
+ }
305
+
306
+ // Perform contrast check whenever color changes (even if cleared)
307
+ if (field === 'color') {
308
+ reviewColorContrast(updatedConfig, patternKey)
309
+ }
310
+
311
+ updateConfig(updatedConfig)
254
312
  }
255
313
 
256
314
  return (
@@ -294,19 +352,16 @@ const PanelPatternSettings: FC<PanelProps> = props => {
294
352
  />
295
353
  )}
296
354
 
297
- <label htmlFor={`pattern-datakey-${patternKey}`}>Data Key:</label>
298
- <select
299
- id={`pattern-datakey-${patternKey}`}
300
- value={p.dataKey || 'Select'}
301
- onChange={e => handlePatternUpdate(patternKey, 'dataKey', e.target.value)}
302
- >
303
- <option value='Select'>Select Data Key</option>
304
- {fieldOptions.map((option, index) => (
305
- <option value={option.value} key={index}>
306
- {option.label}
307
- </option>
308
- ))}
309
- </select>
355
+ <Select
356
+ label='Data Key:'
357
+ value={p.dataKey || ''}
358
+ options={fieldOptions}
359
+ initial='Select Data Key'
360
+ fieldName={`pattern-datakey-${patternKey}`}
361
+ updateField={(section, subsection, fieldName, value) =>
362
+ handlePatternUpdate(patternKey, 'dataKey', value)
363
+ }
364
+ />
310
365
 
311
366
  {p.dataKey && (
312
367
  <>
@@ -333,33 +388,25 @@ const PanelPatternSettings: FC<PanelProps> = props => {
333
388
  />
334
389
  </label>
335
390
 
336
- <label htmlFor={`pattern-type-${patternKey}`}>Pattern Type:</label>
337
- <select
338
- id={`pattern-type-${patternKey}`}
391
+ <Select
392
+ label='Pattern Type:'
339
393
  value={p.shape || 'circles'}
340
- onChange={e => handlePatternUpdate(patternKey, 'shape', e.target.value)}
341
- >
342
- {patternTypes.map((patternType, index) => (
343
- <option value={patternType.value} key={index}>
344
- {patternType.label}
345
- </option>
346
- ))}
347
- </select>
348
-
349
- <label htmlFor={`pattern-size-${patternKey}`}>Pattern Size:</label>
350
- <select
351
- id={`pattern-size-${patternKey}`}
394
+ options={patternTypes}
395
+ fieldName={`pattern-type-${patternKey}`}
396
+ updateField={(section, subsection, fieldName, value) =>
397
+ handlePatternUpdate(patternKey, 'shape', value)
398
+ }
399
+ />
400
+
401
+ <Select
402
+ label='Pattern Size:'
352
403
  value={getPatternSizeText(p.patternSize || 8)}
353
- onChange={e =>
354
- handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(e.target.value))
404
+ options={patternSizes}
405
+ fieldName={`pattern-size-${patternKey}`}
406
+ updateField={(section, subsection, fieldName, value) =>
407
+ handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(value))
355
408
  }
356
- >
357
- {patternSizes.map((size, index) => (
358
- <option value={size.value} key={index}>
359
- {size.label}
360
- </option>
361
- ))}
362
- </select>
409
+ />
363
410
 
364
411
  <div className='mt-3'>
365
412
  <label htmlFor={`pattern-color-${patternKey}`}>
@@ -380,7 +427,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
380
427
  </Tooltip>
381
428
  <input
382
429
  type='text'
383
- value={p.color || '#666666'}
430
+ value={p.color || ''}
384
431
  id={`pattern-color-${patternKey}`}
385
432
  onChange={e => handlePatternUpdate(patternKey, 'color', e.target.value)}
386
433
  placeholder='#666666'