@cdc/chart 4.26.3 → 4.26.4

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 (128) hide show
  1. package/CONFIG.md +169 -0
  2. package/README.md +42 -17
  3. package/dist/cdcchart.js +34725 -33730
  4. package/examples/feature/bar/additional-column-tooltip.json +3 -3
  5. package/examples/feature/deviation/planet-deviation-config.json +2 -2
  6. package/examples/feature/paired-bar/paired-bar-example.json +2 -2
  7. package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
  8. package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
  9. package/examples/feature/tests-covid/covid-example-config.json +2 -2
  10. package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
  11. package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
  12. package/examples/metadata-variables.json +1 -1
  13. package/examples/minimal-example.json +24 -0
  14. package/examples/private/test2.json +35012 -0
  15. package/package.json +4 -4
  16. package/src/CdcChartComponent.tsx +9 -6
  17. package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
  18. package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
  19. package/src/_stories/{Chart.SmallMultiples.stories.tsx → Chart.SmallMultiples.smoke.stories.tsx} +17 -1
  20. package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
  21. package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
  22. package/src/_stories/ChartBar.Editor.stories.tsx +88 -85
  23. package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
  24. package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
  25. package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
  26. package/src/_stories/_mock/paired-bar-abbr.json +1 -1
  27. package/src/_stories/_mock/paired-bar.json +1 -1
  28. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
  30. package/src/components/Axis/BottomAxis.tsx +1 -1
  31. package/src/components/Axis/LeftAxis.tsx +2 -4
  32. package/src/components/Axis/LeftAxisGridlines.tsx +1 -3
  33. package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
  34. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
  35. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
  36. package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
  37. package/src/components/BarChart/components/BarChart.tsx +2 -6
  38. package/src/components/BarChart/helpers/useBarChart.ts +1 -1
  39. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
  40. package/src/components/EditorPanel/EditorPanel.tsx +633 -908
  41. package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
  42. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +216 -165
  43. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +168 -116
  44. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +154 -109
  45. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  46. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +12 -26
  47. package/src/components/EditorPanel/useEditorPermissions.ts +1 -10
  48. package/src/components/Forecasting/Forecasting.tsx +2 -2
  49. package/src/components/HorizonChart/HorizonChart.tsx +10 -2
  50. package/src/components/HoverLine/HoverLine.tsx +6 -8
  51. package/src/components/Legend/Legend.Component.tsx +4 -4
  52. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
  53. package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
  54. package/src/components/Legend/helpers/index.ts +0 -2
  55. package/src/components/LineChart/LineChartProps.ts +1 -0
  56. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +3 -2
  57. package/src/components/LineChart/index.tsx +11 -4
  58. package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
  59. package/src/components/LinearChart/tests/LinearChart.test.tsx +147 -0
  60. package/src/components/LinearChart.tsx +98 -41
  61. package/src/components/PairedBarChart.jsx +2 -2
  62. package/src/components/PieChart/PieChart.tsx +11 -9
  63. package/src/components/Regions/components/Regions.tsx +30 -20
  64. package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
  65. package/src/components/SmallMultiples/SmallMultipleTile.tsx +1 -0
  66. package/src/components/Sparkline/components/SparkLine.tsx +1 -1
  67. package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
  68. package/src/data/initial-state.js +1 -0
  69. package/src/helpers/calculateLeftYAxisWidth.ts +193 -0
  70. package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
  71. package/src/helpers/tooltipHelpers.ts +176 -0
  72. package/src/hooks/useHighlightedBars.ts +9 -16
  73. package/src/hooks/useProgrammaticTooltip.ts +16 -2
  74. package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
  75. package/src/hooks/useTooltip.tsx +167 -38
  76. package/src/test/CdcChart.test.jsx +24 -0
  77. package/src/types/ChartConfig.ts +1 -0
  78. package/LICENSE +0 -201
  79. package/examples/chart-regression-2.json +0 -2360
  80. package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
  81. package/examples/feature/__data__/city-temperature.json +0 -2198
  82. package/examples/feature/bar/Barchart_with_negative.json +0 -34
  83. package/examples/feature/boxplot/box-plot-data.json +0 -71
  84. package/examples/feature/boxplot/boxplot-data.json +0 -156
  85. package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
  86. package/examples/feature/forest-plot/log.json +0 -26
  87. package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
  88. package/examples/region-issue.json +0 -2065
  89. package/examples/xaxis.json +0 -493
  90. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  91. /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
  92. /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
  93. /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
  94. /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
  95. /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
  96. /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
  97. /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
  98. /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
  99. /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
  100. /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
  101. /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
  102. /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
  103. /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
  104. /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
  105. /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
  106. /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
  107. /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
  108. /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
  109. /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
  110. /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
  111. /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
  112. /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
  113. /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
  114. /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
  115. /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
  116. /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
  117. /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
  118. /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
  119. /package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +0 -0
  120. /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
  121. /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
  122. /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
  123. /package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +0 -0
  124. /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
  125. /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
  126. /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
  127. /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
  128. /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { useContext, FC } from 'react'
2
+ import { Draggable } from '@hello-pangea/dnd'
2
3
  import {
3
4
  Accordion,
4
5
  AccordionItem,
@@ -6,6 +7,7 @@ import {
6
7
  AccordionItemPanel,
7
8
  AccordionItemButton
8
9
  } from 'react-accessible-accordion'
10
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
9
11
  import Tooltip from '@cdc/core/components/ui/Tooltip'
10
12
  import Icon from '@cdc/core/components/ui/Icon'
11
13
  import Button from '@cdc/core/components/elements/Button'
@@ -153,6 +155,21 @@ const PanelPatternSettings: FC<PanelProps> = props => {
153
155
  const fieldOptions = getFieldOptions()
154
156
  const currentPatterns: Record<string, LegendPattern> = legendCfg.patterns || {}
155
157
 
158
+ const handlePatternReorder = (sourceIndex: number, destinationIndex: number) => {
159
+ const patternEntries = Object.entries(currentPatterns)
160
+ const [movedPattern] = patternEntries.splice(sourceIndex, 1)
161
+ patternEntries.splice(destinationIndex, 0, movedPattern)
162
+
163
+ const newPatterns = Object.fromEntries(patternEntries)
164
+ updateConfig({
165
+ ...config,
166
+ legend: {
167
+ ...config.legend,
168
+ patterns: newPatterns
169
+ }
170
+ })
171
+ }
172
+
156
173
  // Check if all patterns pass contrast requirements
157
174
  const checkPatternContrasts = () => {
158
175
  return Object.values(currentPatterns).every(pattern => pattern.contrastCheck !== false)
@@ -308,130 +325,165 @@ const PanelPatternSettings: FC<PanelProps> = props => {
308
325
  message='Pattern colors must comply with <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
309
326
  showCloseButton={false}
310
327
  />
311
- <br />
312
328
  </>
313
329
  )}
314
330
 
315
- {/* Individual Pattern Configurations */}
316
- {Object.entries(currentPatterns).map(([patternKey, pattern], index) => {
317
- const p: LegendPattern = pattern || {}
318
- const domPatternKey = `${sanitizeToSvgId(patternKey)}-${index}`
319
-
320
- return (
321
- <Accordion allowZeroExpanded key={`pattern-accordion-${index}`}>
322
- <AccordionItem>
323
- <AccordionItemHeading>
324
- <AccordionItemButton>
325
- {p.dataKey && p.dataValue
326
- ? `${p.dataKey}: ${p.dataValue}`
327
- : p.dataValue
328
- ? `All Series: ${p.dataValue}`
329
- : `Pattern ${index + 1}`}
330
- </AccordionItemButton>
331
- </AccordionItemHeading>
332
- <AccordionItemPanel>
333
- {p.contrastCheck ?? true ? (
334
- <Alert type='success' message='This pattern passes contrast checks' showCloseButton={false} />
335
- ) : (
336
- <Alert
337
- type='danger'
338
- message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
339
- showCloseButton={false}
340
- />
341
- )}
342
-
343
- <Select
344
- label='Data Key:'
345
- value={p.dataKey || ''}
346
- options={fieldOptions}
347
- initial='Select Data Key'
348
- fieldName={`pattern-datakey-${domPatternKey}`}
349
- updateField={(section, subsection, fieldName, value) =>
350
- handlePatternUpdate(patternKey, 'dataKey', value)
351
- }
352
- />
353
-
354
- <label htmlFor={`pattern-datavalue-${domPatternKey}`}>
355
- Data Value:
356
- <input
357
- type='text'
358
- id={`pattern-datavalue-${domPatternKey}`}
359
- value={p.dataValue || ''}
360
- onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
361
- placeholder='Enter data value'
362
- />
363
- </label>
364
-
365
- <label htmlFor={`pattern-label-${domPatternKey}`}>
366
- Label (optional):
367
- <input
368
- type='text'
369
- id={`pattern-label-${domPatternKey}`}
370
- value={p.label || ''}
371
- onChange={e => handlePatternUpdate(patternKey, 'label', e.target.value)}
372
- />
373
- </label>
374
-
375
- <Select
376
- label='Pattern Type:'
377
- value={p.shape || 'circles'}
378
- options={patternTypes}
379
- fieldName={`pattern-type-${domPatternKey}`}
380
- updateField={(section, subsection, fieldName, value) =>
381
- handlePatternUpdate(patternKey, 'shape', value)
382
- }
383
- />
384
-
385
- <Select
386
- label='Pattern Size:'
387
- value={getPatternSizeText(p.patternSize || 8)}
388
- options={patternSizes}
389
- fieldName={`pattern-size-${domPatternKey}`}
390
- updateField={(section, subsection, fieldName, value) =>
391
- handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(value))
392
- }
393
- />
394
-
395
- <div className='mt-3'>
396
- <label htmlFor={`pattern-color-${domPatternKey}`}>
397
- Pattern Color
398
- <Tooltip style={{ textTransform: 'none' }}>
399
- <Tooltip.Target>
400
- <Icon
401
- display='question'
402
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
331
+ <GroupedList
332
+ items={Object.entries(currentPatterns)}
333
+ label='Pattern'
334
+ droppableId='chart-patterns-order'
335
+ onDragEnd={({ source, destination }) => {
336
+ if (!destination || source.index === destination.index) return
337
+ handlePatternReorder(source.index, destination.index)
338
+ }}
339
+ renderItem={([patternKey, pattern], index) => {
340
+ const p: LegendPattern = pattern || {}
341
+ const domPatternKey = `${sanitizeToSvgId(patternKey)}-${index}`
342
+
343
+ return (
344
+ <Draggable
345
+ key={`pattern-${patternKey}-${index}`}
346
+ draggableId={`pattern-${patternKey}-${index}`}
347
+ index={index}
348
+ >
349
+ {(provided, snapshot) => (
350
+ <div
351
+ ref={provided.innerRef}
352
+ {...provided.draggableProps}
353
+ {...provided.dragHandleProps}
354
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
355
+ style={provided.draggableProps.style}
356
+ >
357
+ <Accordion allowZeroExpanded preExpanded={['panel']}>
358
+ <AccordionItem uuid='panel' className='series-item series-item--chart'>
359
+ <AccordionItemHeading className='series-item__title'>
360
+ <AccordionItemButton className='accordion__button'>
361
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
362
+ {p.dataKey && p.dataValue
363
+ ? `${p.dataKey}: ${p.dataValue}`
364
+ : p.dataValue
365
+ ? `All Series: ${p.dataValue}`
366
+ : `Pattern ${index + 1}`}
367
+ </AccordionItemButton>
368
+ </AccordionItemHeading>
369
+ <AccordionItemPanel>
370
+ <div className='series-item__panel-actions'>
371
+ <Button
372
+ type='button'
373
+ variant='danger'
374
+ size='sm'
375
+ className='grouped-list__remove'
376
+ onClick={() => handleRemovePattern(patternKey)}
377
+ >
378
+ Remove Pattern
379
+ </Button>
380
+ </div>
381
+ {p.contrastCheck ?? true ? (
382
+ <Alert
383
+ type='success'
384
+ message='This pattern passes contrast checks'
385
+ showCloseButton={false}
386
+ />
387
+ ) : (
388
+ <Alert
389
+ type='danger'
390
+ message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
391
+ showCloseButton={false}
392
+ />
393
+ )}
394
+
395
+ <Select
396
+ label='Data Key:'
397
+ value={p.dataKey || ''}
398
+ options={fieldOptions}
399
+ initial='Select Data Key'
400
+ fieldName={`pattern-datakey-${domPatternKey}`}
401
+ updateField={(section, subsection, fieldName, value) =>
402
+ handlePatternUpdate(patternKey, 'dataKey', value)
403
+ }
404
+ />
405
+
406
+ <label htmlFor={`pattern-datavalue-${domPatternKey}`}>
407
+ Data Value:
408
+ <input
409
+ type='text'
410
+ id={`pattern-datavalue-${domPatternKey}`}
411
+ value={p.dataValue || ''}
412
+ onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
413
+ placeholder='Enter data value'
414
+ />
415
+ </label>
416
+
417
+ <label htmlFor={`pattern-label-${domPatternKey}`}>
418
+ Label (optional):
419
+ <input
420
+ type='text'
421
+ id={`pattern-label-${domPatternKey}`}
422
+ value={p.label || ''}
423
+ onChange={e => handlePatternUpdate(patternKey, 'label', e.target.value)}
424
+ />
425
+ </label>
426
+
427
+ <Select
428
+ label='Pattern Type:'
429
+ value={p.shape || 'circles'}
430
+ options={patternTypes}
431
+ fieldName={`pattern-type-${domPatternKey}`}
432
+ updateField={(section, subsection, fieldName, value) =>
433
+ handlePatternUpdate(patternKey, 'shape', value)
434
+ }
403
435
  />
404
- </Tooltip.Target>
405
- <Tooltip.Content>
406
- <p>
407
- If this setting is used, it is the responsibility of the visualization author to verify the
408
- visualization colors meet WCAG 3:1 contrast ratios.
409
- </p>
410
- </Tooltip.Content>
411
- </Tooltip>
412
- <input
413
- type='text'
414
- value={p.color || ''}
415
- id={`pattern-color-${domPatternKey}`}
416
- onChange={e => handlePatternUpdate(patternKey, 'color', e.target.value)}
417
- placeholder='#666666'
418
- />
419
- </label>
420
- </div>
421
436
 
422
- <Button onClick={() => handleRemovePattern(patternKey)} className='btn btn-danger'>
423
- Remove Pattern
424
- </Button>
425
- </AccordionItemPanel>
426
- </AccordionItem>
427
- </Accordion>
428
- )
429
- })}
437
+ <Select
438
+ label='Pattern Size:'
439
+ value={getPatternSizeText(p.patternSize || 8)}
440
+ options={patternSizes}
441
+ fieldName={`pattern-size-${domPatternKey}`}
442
+ updateField={(section, subsection, fieldName, value) =>
443
+ handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(value))
444
+ }
445
+ />
446
+
447
+ <div className='mt-3'>
448
+ <label htmlFor={`pattern-color-${domPatternKey}`}>
449
+ Pattern Color
450
+ <Tooltip style={{ textTransform: 'none' }}>
451
+ <Tooltip.Target>
452
+ <Icon
453
+ display='question'
454
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
455
+ />
456
+ </Tooltip.Target>
457
+ <Tooltip.Content>
458
+ <p>
459
+ If this setting is used, it is the responsibility of the visualization author to
460
+ verify the visualization colors meet WCAG 3:1 contrast ratios.
461
+ </p>
462
+ </Tooltip.Content>
463
+ </Tooltip>
464
+ <input
465
+ type='text'
466
+ value={p.color || ''}
467
+ id={`pattern-color-${domPatternKey}`}
468
+ onChange={e => handlePatternUpdate(patternKey, 'color', e.target.value)}
469
+ placeholder='#666666'
470
+ />
471
+ </label>
472
+ </div>
473
+ </AccordionItemPanel>
474
+ </AccordionItem>
475
+ </Accordion>
476
+ </div>
477
+ )}
478
+ </Draggable>
479
+ )
480
+ }}
481
+ />
430
482
 
431
483
  {/* Add Pattern Button */}
432
- <button className='btn btn-primary full-width mt-2' onClick={handleAddPattern}>
484
+ <Button variant='editor-primary' onClick={handleAddPattern}>
433
485
  Add Pattern
434
- </button>
486
+ </Button>
435
487
 
436
488
  {Object.keys(currentPatterns).length === 0 && (
437
489
  <p style={{ color: '#666', fontStyle: 'italic' }}>
@@ -1,15 +1,19 @@
1
1
  import { memo, useContext } from 'react'
2
+ import { Draggable } from '@hello-pangea/dnd'
2
3
  import { useEditorPermissions } from '../../useEditorPermissions.js'
3
4
  import {
5
+ Accordion,
4
6
  AccordionItem,
5
7
  AccordionItemHeading,
6
8
  AccordionItemPanel,
7
9
  AccordionItemButton
8
10
  } from 'react-accessible-accordion'
9
11
  import { type ChartConfig } from '../../../../types/ChartConfig.js'
12
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
10
13
  import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
11
14
  import Tooltip from '@cdc/core/components/ui/Tooltip'
12
15
  import Icon from '@cdc/core/components/ui/Icon'
16
+ import Button from '@cdc/core/components/elements/Button'
13
17
  import { type ChartContext } from '../../../../types/ChartContext.js'
14
18
  import { type PanelProps } from '../PanelProps.js'
15
19
  import ConfigContext from '../../../../ConfigContext.js'
@@ -53,129 +57,170 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
53
57
  updateConfig({ ...config, regions })
54
58
  }
55
59
 
60
+ const moveRegion = (sourceIndex, destinationIndex) => {
61
+ const regions = config.regions ? [...config.regions] : []
62
+ const [movedRegion] = regions.splice(sourceIndex, 1)
63
+ regions.splice(destinationIndex, 0, movedRegion)
64
+ updateConfig({ ...config, regions })
65
+ }
66
+
56
67
  const fromOptions = ['Fixed', 'Previous Days']
57
68
  const toOptions = ['Last Date', 'Fixed']
58
69
 
59
70
  return (
60
71
  <>
61
- {config.regions &&
62
- config.regions.map(({ label, color, from, to, background, range = 'Custom' }, i) => (
63
- <div className='edit-block' key={`region-${i}`}>
64
- <button
65
- type='button'
66
- className='btn btn-danger remove-column'
67
- onClick={event => {
68
- event.preventDefault()
69
- removeColumn(i)
70
- }}
71
- >
72
- Remove
73
- </button>
74
- <TextField value={label} label='Region Label' fieldName='label' i={i} updateField={updateField} />
75
- <div className='two-col-inputs'>
76
- <TextField
77
- value={color}
78
- label='Text Color'
79
- fieldName='color'
80
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
81
- />
82
- <TextField
83
- value={background}
84
- label='Background'
85
- fieldName='background'
86
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
87
- />
88
- </div>
89
-
90
- <Select
91
- value={config.regions[i].fromType ?? 'Fixed'}
92
- label='Minimum Region Type'
93
- initial={'Select'}
94
- required={true}
95
- onChange={e => {
96
- if (e.target.value !== '' && e.target.value !== 'Select') {
97
- const newRegions = [...config.regions]
98
- newRegions[i].fromType = e.target.value
99
- updateConfig({
100
- ...config,
101
- regions: newRegions
102
- })
103
- }
104
- e.target.value = ''
105
- }}
106
- options={fromOptions}
107
- />
108
-
109
- {(config.regions[i].fromType === 'Fixed' ||
110
- config.regions[i].fromType === 'Previous Days' ||
111
- !config.regions[i].fromType) && (
112
- <>
113
- <TextField
114
- value={from}
115
- label={
116
- config.regions[i].fromType === 'Fixed' || !config.regions[i]?.fromType
117
- ? 'From Value'
118
- : 'Previous Number of Days'
119
- }
120
- fieldName='from'
121
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
122
- tooltip={
123
- <Tooltip style={{ textTransform: 'none' }}>
124
- <Tooltip.Target>
125
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
126
- </Tooltip.Target>
127
- <Tooltip.Content>
128
- <p>
129
- When using categorical (linear scale) match the data set value. When using date (linear / date
130
- time scale) match the x-axis value.
131
- </p>
132
- </Tooltip.Content>
133
- </Tooltip>
134
- }
135
- />
136
- </>
137
- )}
138
-
139
- <Select
140
- value={config.regions[i].toType ?? 'Fixed'}
141
- label='Maximum Region Type'
142
- initial={'Select'}
143
- required={true}
144
- onChange={e => {
145
- if (e.target.value !== '' && e.target.value !== 'Select') {
146
- const newRegions = [...config.regions]
147
- newRegions[i].toType = e.target.value
148
- updateConfig({
149
- ...config,
150
- regions: newRegions
151
- })
152
- }
153
- e.target.value = ''
154
- }}
155
- options={toOptions}
156
- />
157
-
158
- {(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) && (
159
- <TextField
160
- value={to}
161
- label='To Value'
162
- fieldName='to'
163
- updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
164
- />
72
+ <GroupedList
73
+ items={config.regions}
74
+ label='Regions'
75
+ droppableId='chart-regions-order'
76
+ onDragEnd={({ source, destination }) => {
77
+ if (!destination || source.index === destination.index) return
78
+ moveRegion(source.index, destination.index)
79
+ }}
80
+ renderItem={({ label, color, from, to, background, range = 'Custom' }, i) => (
81
+ <Draggable key={`region-${i}`} draggableId={`region-${i}`} index={i}>
82
+ {(provided, snapshot) => (
83
+ <div
84
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
85
+ style={provided.draggableProps.style}
86
+ ref={provided.innerRef}
87
+ {...provided.draggableProps}
88
+ {...provided.dragHandleProps}
89
+ >
90
+ <Accordion allowZeroExpanded preExpanded={['panel']}>
91
+ <AccordionItem uuid='panel' className='series-item series-item--chart'>
92
+ <AccordionItemHeading className='series-item__title'>
93
+ <AccordionItemButton className='accordion__button'>
94
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
95
+ {label || `Region ${i + 1}`}
96
+ </AccordionItemButton>
97
+ </AccordionItemHeading>
98
+ <AccordionItemPanel>
99
+ <div className='series-item__panel-actions'>
100
+ <Button
101
+ type='button'
102
+ variant='danger'
103
+ size='sm'
104
+ className='grouped-list__remove'
105
+ onClick={event => {
106
+ event.preventDefault()
107
+ removeColumn(i)
108
+ }}
109
+ >
110
+ Remove
111
+ </Button>
112
+ </div>
113
+ <TextField value={label} label='Region Label' fieldName='label' i={i} updateField={updateField} />
114
+ <div className='two-col-inputs'>
115
+ <TextField
116
+ value={color}
117
+ label='Text Color'
118
+ fieldName='color'
119
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
120
+ />
121
+ <TextField
122
+ value={background}
123
+ label='Background'
124
+ fieldName='background'
125
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
126
+ />
127
+ </div>
128
+
129
+ <Select
130
+ value={config.regions[i].fromType ?? 'Fixed'}
131
+ label='Minimum Region Type'
132
+ initial={'Select'}
133
+ required={true}
134
+ onChange={e => {
135
+ if (e.target.value !== '' && e.target.value !== 'Select') {
136
+ const newRegions = [...config.regions]
137
+ newRegions[i].fromType = e.target.value
138
+ updateConfig({
139
+ ...config,
140
+ regions: newRegions
141
+ })
142
+ }
143
+ e.target.value = ''
144
+ }}
145
+ options={fromOptions}
146
+ />
147
+
148
+ {(config.regions[i].fromType === 'Fixed' ||
149
+ config.regions[i].fromType === 'Previous Days' ||
150
+ !config.regions[i].fromType) && (
151
+ <>
152
+ <TextField
153
+ value={from}
154
+ label={
155
+ config.regions[i].fromType === 'Fixed' || !config.regions[i]?.fromType
156
+ ? 'From Value'
157
+ : 'Previous Number of Days'
158
+ }
159
+ fieldName='from'
160
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
161
+ tooltip={
162
+ <Tooltip style={{ textTransform: 'none' }}>
163
+ <Tooltip.Target>
164
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
165
+ </Tooltip.Target>
166
+ <Tooltip.Content>
167
+ <p>
168
+ When using categorical (linear scale) match the data set value. When using date
169
+ (linear / date time scale) match the x-axis value.
170
+ </p>
171
+ </Tooltip.Content>
172
+ </Tooltip>
173
+ }
174
+ />
175
+ </>
176
+ )}
177
+
178
+ <Select
179
+ value={config.regions[i].toType ?? 'Fixed'}
180
+ label='Maximum Region Type'
181
+ initial={'Select'}
182
+ required={true}
183
+ onChange={e => {
184
+ if (e.target.value !== '' && e.target.value !== 'Select') {
185
+ const newRegions = [...config.regions]
186
+ newRegions[i].toType = e.target.value
187
+ updateConfig({
188
+ ...config,
189
+ regions: newRegions
190
+ })
191
+ }
192
+ e.target.value = ''
193
+ }}
194
+ options={toOptions}
195
+ />
196
+
197
+ {(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) && (
198
+ <TextField
199
+ value={to}
200
+ label='To Value'
201
+ fieldName='to'
202
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
203
+ />
204
+ )}
205
+ </AccordionItemPanel>
206
+ </AccordionItem>
207
+ </Accordion>
208
+ </div>
165
209
  )}
166
- </div>
167
- ))}
210
+ </Draggable>
211
+ )}
212
+ />
168
213
  {!config.regions && <p style={{ textAlign: 'center' }}>There are currently no regions.</p>}
169
- <button
214
+ <Button
170
215
  type='button'
171
- className='btn btn-primary full-width'
216
+ variant='editor-primary'
172
217
  onClick={e => {
173
218
  e.preventDefault()
174
219
  addColumn()
175
220
  }}
176
221
  >
177
222
  Add Region
178
- </button>
223
+ </Button>
179
224
  </>
180
225
  )
181
226
  })
@@ -115,16 +115,16 @@ const SankeySettings: React.FC<PanelProps> = props => {
115
115
  </div>
116
116
  ))}
117
117
  {data?.storyNodeText?.length < 3 && (
118
- <button
118
+ <Button
119
119
  type='button'
120
- className='btn btn-primary full-width'
120
+ variant='editor-primary'
121
121
  onClick={e => {
122
122
  e.preventDefault()
123
123
  addStoryNode()
124
124
  }}
125
125
  >
126
126
  Add StoryNode
127
- </button>
127
+ </Button>
128
128
  )}
129
129
  {config.data?.[0]?.tooltips?.length > 0 && (
130
130
  <CheckBox