@cdc/map 4.25.8 → 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 (137) hide show
  1. package/.claude/agents/typescript-organizer.md +118 -0
  2. package/.claude/settings.local.json +30 -0
  3. package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
  4. package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
  5. package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
  6. package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
  7. package/dist/cdcmap.js +56991 -53706
  8. package/examples/example-city-state.json +9 -1
  9. package/examples/multi-country-centering.json +45 -0
  10. package/examples/private/c.json +290 -0
  11. package/examples/private/canvas-city-hover.json +787 -0
  12. package/examples/private/colors-2.json +221 -0
  13. package/examples/private/colors.json +221 -0
  14. package/examples/private/d.json +345 -0
  15. package/examples/private/g.json +1 -0
  16. package/examples/private/h.json +105911 -0
  17. package/examples/private/measles-data.json +378 -0
  18. package/examples/private/measles.json +211 -0
  19. package/examples/private/north-dakota.json +1132 -0
  20. package/examples/private/state-with-pattern.json +883 -0
  21. package/index.html +36 -34
  22. package/package.json +26 -5
  23. package/src/CdcMap.tsx +23 -8
  24. package/src/CdcMapComponent.tsx +238 -308
  25. package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
  26. package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
  27. package/src/_stories/CdcMap.Editor.stories.tsx +3371 -0
  28. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  29. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  30. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  31. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  32. package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
  33. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  34. package/src/_stories/CdcMap.stories.tsx +37 -9
  35. package/src/_stories/GoogleMap.stories.tsx +2 -2
  36. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  37. package/src/_stories/_mock/column-wrap-test.json +265 -0
  38. package/src/_stories/_mock/equal-number.json +1109 -0
  39. package/src/_stories/_mock/multi-country-hide.json +78 -0
  40. package/src/_stories/_mock/multi-country.json +95 -0
  41. package/src/_stories/_mock/multi-state.json +887 -20403
  42. package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
  43. package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
  44. package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
  45. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  46. package/src/_stories/_mock/usa-state-gradient.json +2 -4
  47. package/src/components/BubbleList.tsx +17 -13
  48. package/src/components/CityList.tsx +85 -107
  49. package/src/components/EditorPanel/components/EditorPanel.tsx +787 -709
  50. package/src/components/EditorPanel/components/HexShapeSettings.tsx +58 -95
  51. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +34 -42
  52. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +354 -0
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/Geo.tsx +22 -3
  55. package/src/components/Legend/components/Legend.tsx +76 -40
  56. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  57. package/src/components/Legend/components/index.scss +1 -1
  58. package/src/components/MapContainer.tsx +52 -0
  59. package/src/components/MapControls.tsx +44 -0
  60. package/src/components/NavigationMenu.tsx +27 -15
  61. package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
  62. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  63. package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
  64. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
  65. package/src/components/SmallMultiples/index.tsx +3 -0
  66. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +36 -4
  67. package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
  68. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
  69. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +23 -4
  70. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +6 -0
  71. package/src/components/UsaMap/components/UsaMap.County.tsx +123 -37
  72. package/src/components/UsaMap/components/UsaMap.Region.tsx +36 -5
  73. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +30 -10
  74. package/src/components/UsaMap/components/UsaMap.State.tsx +53 -12
  75. package/src/components/UsaMap/helpers/map.ts +4 -4
  76. package/src/components/UsaMap/helpers/shapes.ts +9 -6
  77. package/src/components/WorldMap/WorldMap.tsx +193 -35
  78. package/src/components/ZoomControls.tsx +6 -9
  79. package/src/context/LegendMemoContext.tsx +30 -0
  80. package/src/context.ts +1 -40
  81. package/src/data/initial-state.js +153 -130
  82. package/src/data/supported-geos.js +25 -78
  83. package/src/helpers/addUIDs.ts +13 -2
  84. package/src/helpers/applyColorToLegend.ts +140 -20
  85. package/src/helpers/applyLegendToRow.ts +10 -6
  86. package/src/helpers/componentHelpers.ts +8 -0
  87. package/src/helpers/constants.ts +12 -14
  88. package/src/helpers/dataTableHelpers.ts +6 -0
  89. package/src/helpers/displayGeoName.ts +18 -3
  90. package/src/helpers/generateRuntimeLegend.ts +44 -10
  91. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  92. package/src/helpers/getColumnNames.ts +1 -1
  93. package/src/helpers/getCountriesPicked.ts +103 -0
  94. package/src/helpers/getMapContainerClasses.ts +7 -0
  95. package/src/helpers/getPatternForRow.ts +33 -0
  96. package/src/helpers/getStatesPicked.ts +8 -5
  97. package/src/helpers/index.ts +3 -3
  98. package/src/helpers/isLegendItemDisabled.ts +16 -0
  99. package/src/helpers/mapObserverHelpers.ts +40 -0
  100. package/src/helpers/resetLegendToggles.ts +3 -2
  101. package/src/helpers/smallMultiplesHelpers.ts +359 -0
  102. package/src/helpers/tests/titleCase.test.ts +76 -0
  103. package/src/helpers/titleCase.ts +13 -13
  104. package/src/helpers/toggleLegendActive.ts +6 -11
  105. package/src/helpers/urlDataHelpers.ts +70 -0
  106. package/src/hooks/useCountryZoom.tsx +241 -0
  107. package/src/hooks/useGeoClickHandler.ts +36 -2
  108. package/src/hooks/useLegendMemo.ts +17 -0
  109. package/src/hooks/useMapLayers.tsx +5 -4
  110. package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
  111. package/src/hooks/useResizeObserver.ts +5 -2
  112. package/src/hooks/useStateZoom.tsx +30 -8
  113. package/src/hooks/useSynchronizedGeographies.ts +56 -0
  114. package/src/hooks/useTooltip.ts +1 -2
  115. package/src/index.jsx +1 -2
  116. package/src/scss/editor-panel.scss +4 -440
  117. package/src/scss/main.scss +1 -1
  118. package/src/scss/map.scss +12 -15
  119. package/src/store/map.actions.ts +7 -7
  120. package/src/store/map.reducer.ts +17 -6
  121. package/src/test/CdcMap.test.jsx +11 -0
  122. package/src/types/MapConfig.ts +46 -18
  123. package/src/types/MapContext.ts +6 -7
  124. package/src/types/runtimeLegend.ts +17 -1
  125. package/vite.config.js +2 -7
  126. package/vitest.config.ts +16 -0
  127. package/src/components/DataTable.tsx +0 -385
  128. package/src/components/EditorPanel/components/Inputs.tsx +0 -59
  129. package/src/coreStyles_map.scss +0 -3
  130. package/src/helpers/colorDistributions.ts +0 -12
  131. package/src/helpers/generateColorsArray.ts +0 -14
  132. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  133. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
  134. package/src/hooks/useActiveElement.ts +0 -19
  135. package/src/scss/mixins.scss +0 -47
  136. package/src/types/Annotations.ts +0 -24
  137. /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
@@ -1,4 +1,6 @@
1
- import React, { useContext, useEffect, useState } from 'react'
1
+ import React, { useContext, useEffect, useState, useMemo, useRef } from 'react'
2
+ import { filterColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
3
+ import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
2
4
 
3
5
  // Third Party
4
6
  import {
@@ -12,12 +14,14 @@ import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
12
14
  import { useDebounce } from 'use-debounce'
13
15
  import _ from 'lodash'
14
16
  import { Tooltip as ReactTooltip } from 'react-tooltip'
17
+ import 'react-tooltip/dist/react-tooltip.css'
15
18
  import Panels from './Panels'
16
19
  import Layout from '@cdc/core/components/Layout'
17
20
 
18
21
  // Data
19
- import colorPalettes from '@cdc/core/data/colorPalettes'
20
- import { supportedStatesFipsCodes } from '../../../data/supported-geos.js'
22
+ import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
23
+ import { supportedStatesFipsCodes, supportedCountries } from '../../../data/supported-geos.js'
24
+ import { getSupportedCountryOptions } from '../../../helpers/getCountriesPicked'
21
25
 
22
26
  // Components - Core
23
27
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
@@ -26,6 +30,7 @@ import Icon from '@cdc/core/components/ui/Icon'
26
30
  import InputToggle from '@cdc/core/components/inputs/InputToggle'
27
31
  import Tooltip from '@cdc/core/components/ui/Tooltip'
28
32
  import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
33
+ import PanelMarkup from '@cdc/core/components/EditorPanel/components/PanelMarkup'
29
34
 
30
35
  // Assets
31
36
  import UsaGraphic from '@cdc/core/assets/icon-map-usa.svg'
@@ -43,12 +48,23 @@ import { MapContext } from '../../../types/MapContext.js'
43
48
  import Alert from '@cdc/core/components/Alert'
44
49
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
45
50
  import { CheckBox, Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
51
+ import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
46
52
  import useColumnsRequiredChecker from '../../../hooks/useColumnsRequiredChecker'
47
- import { addUIDs, HEADER_COLORS } from '../../../helpers'
53
+ import { addUIDs } from '../../../helpers'
54
+ import generateRuntimeData from '../../../helpers/generateRuntimeData'
55
+
56
+ import '@cdc/core/styles/v2/components/editor.scss'
48
57
  import './editorPanel.styles.css'
49
58
  import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
50
59
  import { Datasets } from '@cdc/core/types/DataSet'
51
60
  import MultiSelect from '@cdc/core/components/MultiSelect'
61
+ import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
62
+ import { isV1Palette, getCurrentPaletteName, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
63
+ import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
64
+ import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
65
+ import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
66
+ import PaletteConversionModal from '@cdc/core/components/PaletteConversionModal'
67
+ import { CustomColorsEditor } from '@cdc/core/components/CustomColorsEditor'
52
68
 
53
69
  type MapEditorPanelProps = {
54
70
  datasets?: Datasets
@@ -65,30 +81,48 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
65
81
  setConfig,
66
82
  config,
67
83
  tooltipId,
68
- runtimeData,
69
- setRuntimeData
84
+ runtimeData
70
85
  } = useContext<MapContext>(ConfigContext)
71
86
 
72
87
  const { columnsRequiredChecker } = useColumnsRequiredChecker()
73
88
  const dispatch = useContext(MapDispatchContext)
74
89
  const { general, columns, legend, table, tooltips } = config
75
- const columnsInData = config?.data?.[0] ? Object.keys(config.data[0]) : []
90
+
91
+ // Get columns from data with fallback to datasets (for dashboard context)
92
+ const columnsInData = useMemo(() => {
93
+ // First try config.data
94
+ if (config?.data?.[0]) {
95
+ return Object.keys(config.data[0])
96
+ }
97
+
98
+ // Fallback to datasets using config.dataKey (for dashboard visualizations)
99
+ if (datasets && config?.dataKey) {
100
+ const assignedDataset = datasets[config.dataKey]
101
+ if (assignedDataset?.data?.[0]) {
102
+ return Object.keys(assignedDataset.data[0])
103
+ }
104
+ }
105
+
106
+ return []
107
+ }, [
108
+ config?.data?.length > 0 ? JSON.stringify(Object.keys(config.data[0])) : null,
109
+ datasets?.[config?.dataKey as string]?.data?.length,
110
+ config?.dataKey
111
+ ])
76
112
 
77
113
  const [loadedDefault, setLoadedDefault] = useState(false)
78
114
  const [displayPanel, setDisplayPanel] = useState(true)
79
115
  const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
116
+ const [showConversionModal, setShowConversionModal] = useState(false)
117
+ const [pendingPaletteSelection, setPendingPaletteSelection] = useState<{
118
+ palette: string
119
+ action: () => void
120
+ } | null>(null)
80
121
 
81
122
  const {
82
123
  MapLayerHandlers: { handleMapLayer, handleAddLayer, handleRemoveLayer }
83
124
  } = useMapLayers(config, setConfig, false, tooltipId)
84
125
 
85
- useEffect(() => {
86
- // Pass up to Editor if needed
87
- if (setParentConfig) {
88
- setParentConfig(convertStateToConfig())
89
- }
90
- }, [config])
91
-
92
126
  const categoryMove = (idx1, idx2) => {
93
127
  let categoryValuesOrder = getCategoryValuesOrder()
94
128
  let [movedItem] = categoryValuesOrder.splice(idx1, 1)
@@ -690,8 +724,27 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
690
724
  })
691
725
 
692
726
  if (config) {
693
- const newData = generateRuntimeData(config)
694
- setRuntimeData(newData)
727
+ const newData = generateRuntimeData(config, [], 0, legend.type === 'category')
728
+ dispatch({ type: 'SET_RUNTIME_DATA', payload: newData })
729
+ }
730
+ break
731
+ case 'chooseCountry':
732
+ let countryData = value.map(countryName => ({
733
+ iso: Object.keys(supportedCountries).find(key => supportedCountries[key][0] === countryName),
734
+ name: countryName
735
+ }))
736
+
737
+ setConfig({
738
+ ...config,
739
+ general: {
740
+ ...config.general,
741
+ countriesPicked: countryData
742
+ }
743
+ })
744
+
745
+ if (config) {
746
+ const newData = generateRuntimeData(config, [], 0, legend.type === 'category')
747
+ dispatch({ type: 'SET_RUNTIME_DATA', payload: newData })
695
748
  }
696
749
  break
697
750
  case 'classificationType':
@@ -868,7 +921,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
868
921
  }
869
922
 
870
923
  const convertStateToConfig = () => {
871
- let strippedState = _.cloneDeep(config) // Deep copy
924
+ let strippedState = cloneConfig(config) // Deep copy
872
925
 
873
926
  // Strip ref
874
927
  delete strippedState['']
@@ -900,58 +953,89 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
900
953
 
901
954
  const isReversed = config.general.palette.isReversed
902
955
 
903
- function filterColorPalettes() {
904
- let sequential = []
905
- let nonSequential = []
906
- let accessibleColors = []
907
- for (let paletteName in colorPalettes) {
908
- if (!isReversed) {
909
- if (paletteName.includes('qualitative') && !paletteName.endsWith('reverse')) {
910
- nonSequential.push(paletteName)
911
- }
912
- if (paletteName.includes('colorblindsafe') && !paletteName.endsWith('reverse')) {
913
- accessibleColors.push(paletteName)
914
- }
915
- if (
916
- !paletteName.includes('qualitative') &&
917
- !paletteName.includes('colorblindsafe') &&
918
- !paletteName.endsWith('reverse')
919
- ) {
920
- sequential.push(paletteName)
921
- }
922
- }
923
- if (isReversed) {
924
- if (paletteName.includes('qualitative') && paletteName.endsWith('reverse')) {
925
- nonSequential.push(paletteName)
926
- }
927
- if (paletteName.includes('colorblindsafe') && paletteName.endsWith('reverse')) {
928
- accessibleColors.push(paletteName)
929
- }
930
- if (
931
- !paletteName.includes('qualitative') &&
932
- !paletteName.includes('colorblindsafe') &&
933
- paletteName.endsWith('reverse')
934
- ) {
935
- sequential.push(paletteName)
956
+ const { sequential, nonSequential, accessibleColors } = useMemo(
957
+ () => filterColorPalettes({ config, isReversed, colorPalettes }),
958
+ [isReversed, colorPalettes, config.general.palette.version]
959
+ )
960
+
961
+ // Helper function to handle palette selection with conversion prompt
962
+ const handlePaletteSelection = (palette: string) => {
963
+ const isV1PaletteConfig = isV1Palette(config)
964
+
965
+ const executeSelection = () => {
966
+ const _newConfig = _.cloneDeep(config)
967
+
968
+ // If v2 migration is disabled, use the original palette name and keep v1 version
969
+ if (!USE_V2_MIGRATION) {
970
+ _newConfig.general.palette.name = palette
971
+ _newConfig.general.palette.version = '1.0'
972
+ } else {
973
+ // V2 migration logic
974
+ _newConfig.general.palette.name = palette
975
+ ? migratePaletteWithMap(palette, paletteMigrationMap, false)
976
+ : undefined
977
+ if (isV1PaletteConfig) {
978
+ _newConfig.general.palette.version = '2.0'
936
979
  }
937
980
  }
981
+ setConfig(_newConfig)
982
+ }
983
+
984
+ if (isV1PaletteConfig) {
985
+ setPendingPaletteSelection({ palette, action: executeSelection })
986
+ setShowConversionModal(true)
987
+ } else {
988
+ executeSelection()
989
+ }
990
+ }
991
+
992
+ // Modal handlers
993
+ const handleConversionConfirm = () => {
994
+ if (pendingPaletteSelection) {
995
+ pendingPaletteSelection.action()
996
+ }
997
+ setShowConversionModal(false)
998
+ setPendingPaletteSelection(null)
999
+ }
1000
+
1001
+ const handleConversionCancel = () => {
1002
+ setShowConversionModal(false)
1003
+ setPendingPaletteSelection(null)
1004
+ }
1005
+
1006
+ const handleReturnToV1 = () => {
1007
+ if (pendingPaletteSelection) {
1008
+ const _newConfig = cloneConfig(config)
1009
+ _newConfig.general.palette.name = pendingPaletteSelection.palette
1010
+ _newConfig.general.palette.version = '1.0'
1011
+ setConfig(_newConfig)
938
1012
  }
1013
+ setShowConversionModal(false)
1014
+ setPendingPaletteSelection(null)
1015
+ }
1016
+
1017
+ // Helper function to determine if a palette should be marked as selected
1018
+ const getPaletteClassName = (palette: string) => {
1019
+ const currentPaletteName = config.general.palette.name || ''
939
1020
 
940
- return [sequential, nonSequential, accessibleColors]
1021
+ // Direct comparison since the UI filters palettes by version
1022
+ // When v1 is selected, UI shows v1 palettes; when v2 is selected, UI shows v2 palettes
1023
+ return currentPaletteName === palette ? 'selected' : ''
941
1024
  }
942
- const [sequential, nonSequential, accessibleColors] = filterColorPalettes()
1025
+
1026
+ const configStringRef = useRef<string>()
943
1027
 
944
1028
  useEffect(() => {
945
1029
  setLoadedDefault(config.defaultData)
946
1030
  columnsRequiredChecker()
947
1031
  }, [config])
948
1032
 
949
- useEffect(() => {
950
- const newConfig = convertStateToConfig()
951
- if (isEditor && setParentConfig) {
952
- setParentConfig(newConfig)
953
- }
954
- }, [config])
1033
+ // Only update parent when config content actually changes (not just reference)
1034
+ const configString = JSON.stringify(convertStateToConfig())
1035
+ if (isEditor && setParentConfig && configStringRef.current !== configString) {
1036
+ configStringRef.current = configString
1037
+ setParentConfig(JSON.parse(configString))
1038
+ }
955
1039
 
956
1040
  const columnsOptions = [
957
1041
  <option value='' key={'Select Option'}>
@@ -1014,6 +1098,16 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1014
1098
  return options
1015
1099
  }
1016
1100
 
1101
+ const CountryOptionList = () => {
1102
+ const countryOptions = getSupportedCountryOptions()
1103
+
1104
+ return countryOptions.map(({ value, label }) => (
1105
+ <option key={value} value={label}>
1106
+ {label}
1107
+ </option>
1108
+ ))
1109
+ }
1110
+
1017
1111
  const filterValueOptionList = []
1018
1112
 
1019
1113
  if (runtimeFilters.length > 0) {
@@ -1220,6 +1314,34 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1220
1314
  />
1221
1315
  </label>
1222
1316
  )}
1317
+ {/* Country Selection for World Maps */}
1318
+ {config.general.geoType === 'world' && (
1319
+ <>
1320
+ <label>
1321
+ <span>Countries Selector</span>
1322
+ <MultiSelect
1323
+ selected={(config.general.countriesPicked || []).map(country => country.name)}
1324
+ options={CountryOptionList().map(option => ({
1325
+ value: option.props.value,
1326
+ label: option.props.children
1327
+ }))}
1328
+ fieldName={'countriesPicked'}
1329
+ updateField={(_, __, ___, selectedOptions) => {
1330
+ handleEditorChanges('chooseCountry', selectedOptions)
1331
+ }}
1332
+ />
1333
+ </label>
1334
+ {config.general.countriesPicked && config.general.countriesPicked.length > 0 && (
1335
+ <CheckBox
1336
+ value={config.general.hideUnselectedCountries || false}
1337
+ fieldName='hideUnselectedCountries'
1338
+ label='Hide Unselected Countries'
1339
+ updateField={updateField}
1340
+ section='general'
1341
+ />
1342
+ )}
1343
+ </>
1344
+ )}
1223
1345
  {/* Type */}
1224
1346
  <Select
1225
1347
  label={
@@ -1263,7 +1385,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1263
1385
  { value: '_blank', label: 'New Window' }
1264
1386
  ]}
1265
1387
  onChange={event => {
1266
- const _newConfig = _.cloneDeep(config)
1388
+ const _newConfig = cloneConfig(config)
1267
1389
  _newConfig.general.navigationTarget = event.target.value
1268
1390
  setConfig(_newConfig)
1269
1391
  }}
@@ -1297,37 +1419,35 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1297
1419
 
1298
1420
  {/* Display as Hex */}
1299
1421
  {general.geoType === 'us' && general.type !== 'navigation' && general.type !== 'bubble' && (
1300
- <label className='checkbox mt-4'>
1301
- <input
1302
- type='checkbox'
1303
- checked={config.general.displayAsHex}
1304
- onChange={event => {
1305
- const _newConfig = _.cloneDeep(config)
1306
- _newConfig.general.displayAsHex = event.target.checked
1307
- setConfig(_newConfig)
1308
- }}
1309
- />
1310
- <span className='edit-label'>Display As Hex Map</span>
1311
- </label>
1422
+ <CheckBox
1423
+ value={config.general.displayAsHex}
1424
+ section='general'
1425
+ subsection={null}
1426
+ fieldName='displayAsHex'
1427
+ label='Display As Hex Map'
1428
+ updateField={updateField}
1429
+ className=''
1430
+ />
1312
1431
  )}
1313
1432
 
1314
1433
  {/* Shapes on Hex */}
1315
- <label className='checkbox mt-4'>
1316
- <input
1317
- type='checkbox'
1318
- checked={config.hexMap.type === 'shapes'}
1319
- onChange={event => {
1320
- setConfig({
1321
- ...config,
1322
- hexMap: {
1323
- ...config.hexMap,
1324
- type: event.target.checked ? 'shapes' : 'standard'
1325
- }
1326
- })
1327
- }}
1328
- />
1329
- <span className='edit-label'>Display Shapes on Hex Map</span>
1330
- </label>
1434
+ <CheckBox
1435
+ value={config.hexMap.type === 'shapes'}
1436
+ section='hexMap'
1437
+ subsection={null}
1438
+ fieldName='type'
1439
+ label='Display Shapes on Hex Map'
1440
+ updateField={updateField}
1441
+ onChange={event => {
1442
+ setConfig({
1443
+ ...config,
1444
+ hexMap: {
1445
+ ...config.hexMap,
1446
+ type: event.target.checked ? 'shapes' : 'standard'
1447
+ }
1448
+ })
1449
+ }}
1450
+ />
1331
1451
  <HexSetting.ShapeColumns columnsOptions={columnsOptions} />
1332
1452
 
1333
1453
  {'us' === config.general.geoType &&
@@ -1353,18 +1473,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1353
1473
  )}
1354
1474
 
1355
1475
  {'us' === config.general.geoType && (
1356
- <label className='checkbox'>
1357
- <input
1358
- type='checkbox'
1359
- checked={general.territoriesAlwaysShow || false}
1360
- onChange={event => {
1361
- const _newConfig = _.cloneDeep(config)
1362
- _newConfig.general.territoriesAlwaysShow = event.target.checked
1363
- setConfig(_newConfig)
1364
- }}
1365
- />
1366
- <span className='edit-label'>Show All Territories</span>
1367
- </label>
1476
+ <CheckBox
1477
+ value={general.territoriesAlwaysShow || false}
1478
+ section='general'
1479
+ subsection={null}
1480
+ fieldName='territoriesAlwaysShow'
1481
+ label='Show All Territories'
1482
+ updateField={updateField}
1483
+ />
1368
1484
  )}
1369
1485
  </AccordionItemPanel>
1370
1486
  </AccordionItem>
@@ -1397,18 +1513,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1397
1513
  </Tooltip>
1398
1514
  }
1399
1515
  />
1400
- <label className='checkbox'>
1401
- <input
1402
- type='checkbox'
1403
- checked={config.general.showTitle || false}
1404
- onChange={event => {
1405
- const _newConfig = _.cloneDeep(config)
1406
- _newConfig.general.showTitle = event.target.checked
1407
- setConfig(_newConfig)
1408
- }}
1409
- />
1410
- <span className='edit-label'>Show Title</span>
1411
- </label>
1516
+ <CheckBox
1517
+ value={config.general.showTitle || false}
1518
+ section='general'
1519
+ subsection={null}
1520
+ fieldName='showTitle'
1521
+ label='Show Title'
1522
+ updateField={updateField}
1523
+ />
1412
1524
  <TextField
1413
1525
  value={general.superTitle || ''}
1414
1526
  updateField={updateField}
@@ -1518,41 +1630,28 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1518
1630
  options={columnsOptions.map(c => c.key)}
1519
1631
  onChange={event => {
1520
1632
  editColumn('geo', 'name', event.target.value)
1521
- checkConfigurationNeeded(config)
1522
1633
  }}
1523
1634
  />
1524
1635
  </label>
1525
1636
  {config.general.type === 'us-geocode' && (
1526
- <label className='checkbox'>
1527
- <input
1528
- type='checkbox'
1529
- checked={config.general.convertFipsCodes}
1530
- onChange={event => {
1531
- setConfig({
1532
- ...config,
1533
- general: {
1534
- ...config.general,
1535
- convertFipsCodes: event.target.checked
1536
- }
1537
- })
1538
- }}
1539
- />
1540
- <span className='edit-label'>Convert FIPS Codes to Geography Name</span>
1541
- </label>
1637
+ <CheckBox
1638
+ value={config.general.convertFipsCodes}
1639
+ section='general'
1640
+ subsection={null}
1641
+ fieldName='convertFipsCodes'
1642
+ label='Convert FIPS Codes to Geography Name'
1643
+ updateField={updateField}
1644
+ />
1542
1645
  )}
1543
1646
 
1544
- <label className='checkbox'>
1545
- <input
1546
- type='checkbox'
1547
- checked={config.general.hideGeoColumnInTooltip || false}
1548
- onChange={event => {
1549
- const _newConfig = _.cloneDeep(config)
1550
- _newConfig.general.hideGeoColumnInTooltip = event.target.checked
1551
- setConfig(_newConfig)
1552
- }}
1553
- />
1554
- <span className='edit-label'>Hide Geography Column Name in Tooltip</span>
1555
- </label>
1647
+ <CheckBox
1648
+ value={config.general.hideGeoColumnInTooltip || false}
1649
+ section='general'
1650
+ subsection={null}
1651
+ fieldName='hideGeoColumnInTooltip'
1652
+ label='Hide Geography Column Name in Tooltip'
1653
+ updateField={updateField}
1654
+ />
1556
1655
  <TextField
1557
1656
  value={config.general.geoLabelOverride}
1558
1657
  section='general'
@@ -1579,11 +1678,10 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1579
1678
  value={columns.primary.name}
1580
1679
  options={columnsOptions.map(c => c.key)}
1581
1680
  onChange={event => {
1582
- const _state = _.cloneDeep(config)
1681
+ const _state = cloneConfig(config)
1583
1682
  _state.columns.primary.name = event.target.value
1584
1683
  _state.columns.primary.label = event.target.value
1585
1684
  setConfig(_state)
1586
- checkConfigurationNeeded(_state)
1587
1685
  }}
1588
1686
  tooltip={
1589
1687
  <Tooltip style={{ textTransform: 'none' }}>
@@ -1596,15 +1694,18 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1596
1694
  </Tooltip>
1597
1695
  }
1598
1696
  />
1599
- <label className='checkbox'>
1600
- <input
1601
- type='checkbox'
1602
- checked={config.general.hidePrimaryColumnInTooltip || false}
1697
+ <label>
1698
+ <CheckBox
1699
+ value={config.general.hidePrimaryColumnInTooltip || false}
1700
+ section='general'
1701
+ subsection={null}
1702
+ fieldName='hidePrimaryColumnInTooltip'
1703
+ label='Hide Data Column Name in Tooltip'
1704
+ updateField={updateField}
1603
1705
  onChange={event => {
1604
1706
  handleEditorChanges('hidePrimaryColumnInTooltip', event.target.checked)
1605
1707
  }}
1606
1708
  />
1607
- <span className='edit-label'>Hide Data Column Name in Tooltip</span>
1608
1709
  </label>
1609
1710
  <TextField
1610
1711
  value={columns.primary.label}
@@ -1653,42 +1754,30 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1653
1754
  min={0}
1654
1755
  />
1655
1756
  </li>
1656
- <li>
1657
- <label className='checkbox'>
1658
- <input
1659
- type='checkbox'
1660
- checked={config.columns.primary.useCommas}
1661
- onChange={event => {
1662
- editColumn('primary', 'useCommas', event.target.checked)
1663
- }}
1664
- />
1665
- <span className='edit-label'>Add Commas to Numbers</span>
1666
- </label>
1667
- </li>
1668
- <li>
1669
- <label className='checkbox'>
1670
- <input
1671
- type='checkbox'
1672
- checked={config.columns.primary.dataTable || false}
1673
- onChange={event => {
1674
- editColumn('primary', 'dataTable', event.target.checked)
1675
- }}
1676
- />
1677
- <span className='edit-label'>Show in Data Table</span>
1678
- </label>
1679
- </li>
1680
- <li>
1681
- <label className='checkbox'>
1682
- <input
1683
- type='checkbox'
1684
- checked={config.columns.primary.tooltip || false}
1685
- onChange={event => {
1686
- editColumn('primary', 'tooltip', event.target.checked)
1687
- }}
1688
- />
1689
- <span className='edit-label'>Show in Tooltips</span>
1690
- </label>
1691
- </li>
1757
+ <CheckBox
1758
+ value={config.columns.primary.useCommas}
1759
+ section='columns'
1760
+ subsection='primary'
1761
+ fieldName='useCommas'
1762
+ label='Add Commas to Numbers'
1763
+ updateField={updateField}
1764
+ />
1765
+ <CheckBox
1766
+ value={config.columns.primary.dataTable || false}
1767
+ section='columns'
1768
+ subsection='primary'
1769
+ fieldName='dataTable'
1770
+ label='Show in Data Table'
1771
+ updateField={updateField}
1772
+ />
1773
+ <CheckBox
1774
+ value={config.columns.primary.tooltip || false}
1775
+ section='columns'
1776
+ subsection='primary'
1777
+ fieldName='tooltip'
1778
+ label='Show in Tooltips'
1779
+ updateField={updateField}
1780
+ />
1692
1781
  </ul>
1693
1782
  </fieldset>
1694
1783
  )}
@@ -1707,14 +1796,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1707
1796
  </Tooltip.Content>
1708
1797
  </Tooltip>
1709
1798
  </span>
1710
- <select
1711
- value={config.columns.categorical ? config.columns.categorical.name : columnsOptions[0]}
1799
+ <Select
1800
+ label=''
1801
+ value={config.columns.categorical ? config.columns.categorical.name : columnsOptions[0]?.key}
1802
+ options={columnsOptions.map(c => c.key)}
1712
1803
  onChange={event => {
1713
1804
  editColumn('categorical', 'name', event.target.value)
1714
1805
  }}
1715
- >
1716
- {columnsOptions}
1717
- </select>
1806
+ />
1718
1807
  </label>
1719
1808
  </fieldset>
1720
1809
  )}
@@ -1937,42 +2026,39 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1937
2026
  updateField={updateField}
1938
2027
  />
1939
2028
  </li>
1940
- <li>
1941
- <label className='checkbox'>
1942
- <input
1943
- type='checkbox'
1944
- checked={config.columns[val].useCommas}
1945
- onChange={event => {
1946
- editColumn(val, 'useCommas', event.target.checked)
1947
- }}
1948
- />
1949
- <span className='edit-label'>Add Commas to Numbers</span>
1950
- </label>
1951
- </li>
1952
- <li>
1953
- <label className='checkbox'>
1954
- <input
1955
- type='checkbox'
1956
- checked={config.columns[val].dataTable}
1957
- onChange={event => {
1958
- editColumn(val, 'dataTable', event.target.checked)
1959
- }}
1960
- />
1961
- <span className='edit-label'>Show in Data Table</span>
1962
- </label>
1963
- </li>
1964
- <li>
1965
- <label className='checkbox'>
1966
- <input
1967
- type='checkbox'
1968
- checked={config.columns[val].tooltip}
1969
- onChange={event => {
1970
- editColumn(val, 'tooltip', event.target.checked)
1971
- }}
1972
- />
1973
- <span className='edit-label'>Show in Tooltips</span>
1974
- </label>
1975
- </li>
2029
+ <CheckBox
2030
+ value={config.columns[val].useCommas}
2031
+ section='columns'
2032
+ subsection={val}
2033
+ fieldName='useCommas'
2034
+ label='Add Commas to Numbers'
2035
+ updateField={updateField}
2036
+ onChange={event => {
2037
+ editColumn(val, 'useCommas', event.target.checked)
2038
+ }}
2039
+ />
2040
+ <CheckBox
2041
+ value={config.columns[val].dataTable}
2042
+ section='columns'
2043
+ subsection={val}
2044
+ fieldName='dataTable'
2045
+ label='Show in Data Table'
2046
+ updateField={updateField}
2047
+ onChange={event => {
2048
+ editColumn(val, 'dataTable', event.target.checked)
2049
+ }}
2050
+ />
2051
+ <CheckBox
2052
+ value={config.columns[val].tooltip}
2053
+ section='columns'
2054
+ subsection={val}
2055
+ fieldName='tooltip'
2056
+ label='Show in Tooltips'
2057
+ updateField={updateField}
2058
+ onChange={event => {
2059
+ editColumn(val, 'tooltip', event.target.checked)
2060
+ }}
2061
+ />
1976
2062
  </ul>
1977
2063
  </fieldset>
1978
2064
  ))}
@@ -2083,7 +2169,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2083
2169
  messages = []
2084
2170
  }
2085
2171
 
2086
- const _newConfig = _.cloneDeep(config)
2172
+ const _newConfig = cloneConfig(config)
2087
2173
  _newConfig.legend.type = event.target.value
2088
2174
  _newConfig.runtime.editorErrorMessage = messages
2089
2175
  setConfig(_newConfig)
@@ -2091,18 +2177,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2091
2177
  />
2092
2178
  )}
2093
2179
  {'navigation' !== config.general.type && (
2094
- <label className='checkbox'>
2095
- <input
2096
- type='checkbox'
2097
- checked={config.general.showSidebar || false}
2098
- onChange={event => {
2099
- const _newConfig = _.cloneDeep(config)
2100
- _newConfig.general.showSidebar = event.target.checked
2101
- setConfig(_newConfig)
2102
- }}
2103
- />
2104
- <span className='edit-label'>Show Legend</span>
2105
- </label>
2180
+ <CheckBox
2181
+ value={config.general.showSidebar || false}
2182
+ section='general'
2183
+ subsection={null}
2184
+ fieldName='showSidebar'
2185
+ label='Show Legend'
2186
+ updateField={updateField}
2187
+ />
2106
2188
  )}
2107
2189
  {'navigation' !== config.general.type && (
2108
2190
  <>
@@ -2159,18 +2241,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2159
2241
  />
2160
2242
  )}
2161
2243
  {'navigation' !== config.general.type && config.legend.style === 'gradient' && (
2162
- <label>
2163
- <span className='edit-label'>Gradient Style</span>
2164
- <select
2165
- value={legend.subStyle || ''}
2166
- onChange={event => {
2167
- handleEditorChanges('legendSubStyle', event.target.value)
2168
- }}
2169
- >
2170
- <option value='linear blocks'>linear blocks</option>
2171
- <option value='smooth'>smooth</option>
2172
- </select>
2173
- </label>
2244
+ <Select
2245
+ label='Gradient Style'
2246
+ value={legend.subStyle || ''}
2247
+ options={['linear blocks', 'smooth']}
2248
+ onChange={event => {
2249
+ handleEditorChanges('legendSubStyle', event.target.value)
2250
+ }}
2251
+ />
2174
2252
  )}
2175
2253
  {allowLegendSeparators && (
2176
2254
  <TextField
@@ -2208,61 +2286,66 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2208
2286
  </label>
2209
2287
  )}
2210
2288
  {
2211
- <label className='checkbox'>
2212
- <input
2213
- type='checkbox'
2214
- checked={legend.hideBorder}
2215
- onChange={event => {
2216
- handleEditorChanges('legendBorder', event.target.checked)
2217
- }}
2218
- />
2219
- <span className='edit-label column-heading'>Hide Legend Box</span>
2220
- <Tooltip style={{ textTransform: 'none' }}>
2221
- <Tooltip.Target>
2222
- <Icon
2223
- display='question'
2224
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2225
- />
2226
- </Tooltip.Target>
2227
- <Tooltip.Content>
2228
- <p> Default option for top and bottom legends is ‘No Box.’</p>
2229
- </Tooltip.Content>
2230
- </Tooltip>
2231
- </label>
2289
+ <CheckBox
2290
+ value={legend.hideBorder}
2291
+ section='legend'
2292
+ subsection={null}
2293
+ fieldName='hideBorder'
2294
+ label='Hide Legend Box'
2295
+ updateField={updateField}
2296
+ onChange={event => {
2297
+ handleEditorChanges('legendBorder', event.target.checked)
2298
+ }}
2299
+ tooltip={
2300
+ <Tooltip style={{ textTransform: 'none' }}>
2301
+ <Tooltip.Target>
2302
+ <Icon
2303
+ display='question'
2304
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2305
+ />
2306
+ </Tooltip.Target>
2307
+ <Tooltip.Content>
2308
+ <p> Default option for top and bottom legends is 'No Box.'</p>
2309
+ </Tooltip.Content>
2310
+ </Tooltip>
2311
+ }
2312
+ />
2232
2313
  }
2233
2314
  {'side' === legend.position && (
2234
- <label className='checkbox'>
2235
- <input
2236
- type='checkbox'
2237
- checked={legend.singleColumn}
2238
- onChange={event => {
2239
- const _newConfig = _.cloneDeep(config)
2240
- _newConfig.legend.singleColumn = event.target.checked
2241
- _newConfig.legend.singleRow = false
2242
- _newConfig.legend.verticalSorted = false
2315
+ <CheckBox
2316
+ value={legend.singleColumn}
2317
+ section='legend'
2318
+ subsection={null}
2319
+ fieldName='singleColumn'
2320
+ label='Single Column Legend'
2321
+ updateField={updateField}
2322
+ onChange={event => {
2323
+ const _newConfig = cloneConfig(config)
2324
+ _newConfig.legend.singleColumn = event.target.checked
2325
+ _newConfig.legend.singleRow = false
2326
+ _newConfig.legend.verticalSorted = false
2243
2327
 
2244
- setConfig(_newConfig)
2245
- }}
2246
- />
2247
- <span className='edit-label'>Single Column Legend</span>
2248
- </label>
2328
+ setConfig(_newConfig)
2329
+ }}
2330
+ />
2249
2331
  )}
2250
2332
  {'side' !== legend.position && legend.style !== 'gradient' && (
2251
- <label className='checkbox'>
2252
- <input
2253
- type='checkbox'
2254
- checked={legend.singleRow}
2255
- onChange={event => {
2256
- const _newConfig = _.cloneDeep(config)
2257
- _newConfig.legend.singleRow = event.target.checked
2258
- _newConfig.legend.singleColumn = false
2259
- _newConfig.legend.verticalSorted = false
2333
+ <CheckBox
2334
+ value={legend.singleRow}
2335
+ section='legend'
2336
+ subsection={null}
2337
+ fieldName='singleRow'
2338
+ label='Single Row Legend'
2339
+ updateField={updateField}
2340
+ onChange={event => {
2341
+ const _newConfig = cloneConfig(config)
2342
+ _newConfig.legend.singleRow = event.target.checked
2343
+ _newConfig.legend.singleColumn = false
2344
+ _newConfig.legend.verticalSorted = false
2260
2345
 
2261
- setConfig(_newConfig)
2262
- }}
2263
- />
2264
- <span className='edit-label'>Single Row Legend</span>
2265
- </label>
2346
+ setConfig(_newConfig)
2347
+ }}
2348
+ />
2266
2349
  )}
2267
2350
 
2268
2351
  {'navigation' !== config.general.type && config.legend.type === 'category' && (
@@ -2271,53 +2354,46 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2271
2354
  value={legend.groupBy || ''}
2272
2355
  options={columnsOptions.map(c => c.key)}
2273
2356
  onChange={event => {
2274
- const _newConfig = _.cloneDeep(config)
2357
+ const _newConfig = cloneConfig(config)
2275
2358
  _newConfig.legend.groupBy = event.target.value
2276
2359
  setConfig(_newConfig)
2277
2360
  }}
2278
2361
  />
2279
2362
  )}
2280
2363
  {config.legend.style !== 'gradient' && (
2281
- <label className='checkbox'>
2282
- <input
2283
- type='checkbox'
2284
- checked={legend.verticalSorted}
2285
- onChange={event => {
2286
- const _newConfig = _.cloneDeep(config)
2287
- _newConfig.legend.verticalSorted = event.target.checked
2288
- setConfig(_newConfig)
2289
- }}
2290
- />
2291
- <span className='edit-label'>Vertical sorted legend</span>
2292
- </label>
2364
+ <CheckBox
2365
+ value={legend.verticalSorted}
2366
+ section='legend'
2367
+ subsection={null}
2368
+ fieldName='verticalSorted'
2369
+ label='Vertical sorted legend'
2370
+ updateField={updateField}
2371
+ />
2293
2372
  )}
2294
2373
 
2295
2374
  {/* always show */}
2296
2375
  {
2297
- <label className='checkbox'>
2298
- <input
2299
- type='checkbox'
2300
- checked={legend.showSpecialClassesLast}
2301
- onChange={event => {
2302
- handleEditorChanges('legendShowSpecialClassesLast', event.target.checked)
2303
- }}
2304
- />
2305
- <span className='edit-label'>Show Special Classes Last</span>
2306
- </label>
2376
+ <CheckBox
2377
+ value={legend.showSpecialClassesLast}
2378
+ section='legend'
2379
+ subsection={null}
2380
+ fieldName='showSpecialClassesLast'
2381
+ label='Show Special Classes Last'
2382
+ updateField={updateField}
2383
+ onChange={event => {
2384
+ handleEditorChanges('legendShowSpecialClassesLast', event.target.checked)
2385
+ }}
2386
+ />
2307
2387
  }
2308
2388
  {'category' !== legend.type && (
2309
- <label className='checkbox'>
2310
- <input
2311
- type='checkbox'
2312
- checked={legend.separateZero || false}
2313
- onChange={event => {
2314
- const _newConfig = _.cloneDeep(config)
2315
- _newConfig.legend.separateZero = event.target.checked
2316
- return setConfig(_newConfig)
2317
- }}
2318
- />
2319
- <span className='edit-label column-heading'>
2320
- Separate Zero
2389
+ <CheckBox
2390
+ value={legend.separateZero || false}
2391
+ section='legend'
2392
+ subsection={null}
2393
+ fieldName='separateZero'
2394
+ label='Separate Zero'
2395
+ updateField={updateField}
2396
+ tooltip={
2321
2397
  <Tooltip style={{ textTransform: 'none' }}>
2322
2398
  <Tooltip.Target>
2323
2399
  <Icon
@@ -2329,35 +2405,33 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2329
2405
  <p>For numeric data, you can separate the zero value as its own data class.</p>
2330
2406
  </Tooltip.Content>
2331
2407
  </Tooltip>
2332
- </span>
2333
- </label>
2408
+ }
2409
+ />
2334
2410
  )}
2335
2411
 
2336
2412
  {/* Temp Checkbox */}
2337
2413
  {config.legend.type === 'equalnumber' && (
2338
- <label className='checkbox'>
2339
- <input
2340
- type='checkbox'
2341
- checked={config.general.equalNumberOptIn}
2342
- onChange={event => {
2343
- const _newConfig = _.clone(config)
2344
- _newConfig.general.equalNumberOptIn = event.target.checked
2345
- setConfig(_newConfig)
2346
- }}
2347
- />
2348
- <span className='edit-label column-heading'>Use new quantile legend</span>
2349
- <Tooltip style={{ textTransform: 'none' }}>
2350
- <Tooltip.Target>
2351
- <Icon
2352
- display='question'
2353
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2354
- />
2355
- </Tooltip.Target>
2356
- <Tooltip.Content>
2357
- <p>This prevents numbers from being used in more than one category (ie. 0-1, 1-2, 2-3) </p>
2358
- </Tooltip.Content>
2359
- </Tooltip>
2360
- </label>
2414
+ <CheckBox
2415
+ value={config.general.equalNumberOptIn}
2416
+ section='general'
2417
+ subsection={null}
2418
+ fieldName='equalNumberOptIn'
2419
+ label='Use new quantile legend'
2420
+ updateField={updateField}
2421
+ tooltip={
2422
+ <Tooltip style={{ textTransform: 'none' }}>
2423
+ <Tooltip.Target>
2424
+ <Icon
2425
+ display='question'
2426
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2427
+ />
2428
+ </Tooltip.Target>
2429
+ <Tooltip.Content>
2430
+ <p>This prevents numbers from being used in more than one category (ie. 0-1, 1-2, 2-3) </p>
2431
+ </Tooltip.Content>
2432
+ </Tooltip>
2433
+ }
2434
+ />
2361
2435
  )}
2362
2436
 
2363
2437
  {'category' !== legend.type && (
@@ -2470,59 +2544,59 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2470
2544
  </React.Fragment>
2471
2545
  )}
2472
2546
  {config.filters.length > 0 && (
2473
- <label className='checkbox'>
2474
- <input
2475
- type='checkbox'
2476
- checked={legend.dynamicDescription}
2547
+ <label className='checkbox column-heading'>
2548
+ <CheckBox
2549
+ value={legend.dynamicDescription}
2550
+ section='legend'
2551
+ subsection={null}
2552
+ fieldName='dynamicDescription'
2553
+ label='Dynamic Legend Description'
2554
+ updateField={updateField}
2477
2555
  onChange={() => {
2478
2556
  handleEditorChanges('dynamicDescription', filterValueOptionList[0])
2479
2557
  }}
2480
2558
  />
2481
- <span className='edit-label column-heading'>
2482
- Dynamic Legend Description
2483
- <Tooltip style={{ textTransform: 'none' }}>
2484
- <Tooltip.Target>
2485
- <Icon
2486
- display='question'
2487
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2488
- />
2489
- </Tooltip.Target>
2490
- <Tooltip.Content>
2491
- <p>
2492
- Check this option if the map has multiple filter controls and you want to specify a
2493
- description for each filter selection.
2494
- </p>
2495
- </Tooltip.Content>
2496
- </Tooltip>
2497
- </span>
2498
- </label>
2499
- )}
2500
- {(config.filters.length > 0 || config.general.type === 'bubble' || config.general.geoType === 'us') && (
2501
- <label className='checkbox'>
2502
- <input
2503
- type='checkbox'
2504
- checked={legend.unified}
2505
- onChange={event => handleEditorChanges('unifiedLegend', event.target.checked)}
2506
- />
2507
- <span className='edit-label column-heading'>
2508
- Unified Legend
2509
- <Tooltip style={{ textTransform: 'none' }}>
2510
- <Tooltip.Target>
2511
- <Icon
2512
- display='question'
2513
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2514
- />
2515
- </Tooltip.Target>
2516
- <Tooltip.Content>
2517
- <p>
2518
- For a map with filters, check this option if you want the high and low values in the legend
2519
- to be based on <em>all</em> mapped values.
2520
- </p>
2521
- </Tooltip.Content>
2522
- </Tooltip>
2523
- </span>
2559
+ <Tooltip style={{ textTransform: 'none' }}>
2560
+ <Tooltip.Target>
2561
+ <Icon
2562
+ display='question'
2563
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2564
+ />
2565
+ </Tooltip.Target>
2566
+ <Tooltip.Content>
2567
+ <p>
2568
+ Check this option if the map has multiple filter controls and you want to specify a
2569
+ description for each filter selection.
2570
+ </p>
2571
+ </Tooltip.Content>
2572
+ </Tooltip>
2524
2573
  </label>
2525
2574
  )}
2575
+ <span className='d-flex mt-2'>
2576
+ <CheckBox
2577
+ value={legend.unified}
2578
+ section='legend'
2579
+ subsection={null}
2580
+ fieldName='unified'
2581
+ label='Unified Legend'
2582
+ updateField={updateField}
2583
+ onChange={event => handleEditorChanges('unifiedLegend', event.target.checked)}
2584
+ />
2585
+ <Tooltip style={{ textTransform: 'none' }}>
2586
+ <Tooltip.Target>
2587
+ <Icon
2588
+ display='question'
2589
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2590
+ />
2591
+ </Tooltip.Target>
2592
+ <Tooltip.Content>
2593
+ <p>
2594
+ Check this option if you want the high and low values in the legend to be based on <em>all</em>{' '}
2595
+ mapped values (useful for maps with filters or small multiples).
2596
+ </p>
2597
+ </Tooltip.Content>
2598
+ </Tooltip>
2599
+ </span>
2526
2600
  </AccordionItemPanel>
2527
2601
  </AccordionItem>
2528
2602
  )}
@@ -2579,32 +2653,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2579
2653
  </Tooltip>
2580
2654
  }
2581
2655
  />
2582
- <label className='checkbox'>
2583
- <input
2584
- type='checkbox'
2585
- checked={config.table.wrapColumns}
2586
- onChange={event => {
2587
- setConfig({
2588
- ...config,
2589
- table: {
2590
- ...config.table,
2591
- wrapColumns: event.target.checked
2592
- }
2593
- })
2594
- }}
2595
- />
2596
- <span className='edit-label column-heading'>WRAP DATA TABLE COLUMNS</span>
2597
- </label>
2598
- <label className='checkbox'>
2599
- <input
2600
- type='checkbox'
2601
- checked={config.table.forceDisplay !== undefined ? config.table.forceDisplay : !isDashboard}
2602
- onChange={event => {
2603
- handleEditorChanges('showDataTable', event.target.checked)
2604
- }}
2605
- />
2606
- <span className='edit-label column-heading'>
2607
- Show Data Table
2656
+ <CheckBox
2657
+ value={config.table.wrapColumns}
2658
+ section='table'
2659
+ subsection={null}
2660
+ fieldName='wrapColumns'
2661
+ label='WRAP DATA TABLE COLUMNS'
2662
+ updateField={updateField}
2663
+ className='column-heading'
2664
+ />
2665
+ <CheckBox
2666
+ value={config.table.forceDisplay !== undefined ? config.table.forceDisplay : !isDashboard}
2667
+ section='table'
2668
+ subsection={null}
2669
+ fieldName='forceDisplay'
2670
+ label='Show Data Table'
2671
+ updateField={updateField}
2672
+ onChange={event => {
2673
+ handleEditorChanges('showDataTable', event.target.checked)
2674
+ }}
2675
+ tooltip={
2608
2676
  <Tooltip style={{ textTransform: 'none' }}>
2609
2677
  <Tooltip.Target>
2610
2678
  <Icon
@@ -2619,24 +2687,17 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2619
2687
  </p>
2620
2688
  </Tooltip.Content>
2621
2689
  </Tooltip>
2622
- </span>
2623
- </label>
2624
- <label className='checkbox'>
2625
- <input
2626
- type='checkbox'
2627
- checked={config.table.showNonGeoData}
2628
- onChange={event => {
2629
- setConfig({
2630
- ...config,
2631
- table: {
2632
- ...config.table,
2633
- showNonGeoData: event.target.checked
2634
- }
2635
- })
2636
- }}
2637
- />
2638
- <span className='edit-label column-heading'>
2639
- Show Non Geographic Data
2690
+ }
2691
+ />
2692
+
2693
+ <CheckBox
2694
+ value={config.table.showNonGeoData}
2695
+ section='table'
2696
+ subsection={null}
2697
+ fieldName='showNonGeoData'
2698
+ label='Show Non Geographic Data'
2699
+ updateField={updateField}
2700
+ tooltip={
2640
2701
  <Tooltip style={{ textTransform: 'none' }}>
2641
2702
  <Tooltip.Target>
2642
2703
  <Icon
@@ -2648,8 +2709,9 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2648
2709
  <p>Show any data not associated with a geographic location</p>
2649
2710
  </Tooltip.Content>
2650
2711
  </Tooltip>
2651
- </span>
2652
- </label>
2712
+ }
2713
+ />
2714
+
2653
2715
  <TextField
2654
2716
  value={table.indexLabel || ''}
2655
2717
  updateField={updateField}
@@ -2690,16 +2752,17 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2690
2752
  }
2691
2753
  type='textarea'
2692
2754
  />
2693
- <label className='checkbox'>
2694
- <input
2695
- type='checkbox'
2696
- checked={config.table.limitHeight}
2697
- onChange={event => {
2698
- handleEditorChanges('limitDataTableHeight', event.target.checked)
2699
- }}
2700
- />
2701
- <span className='edit-label'>Limit Table Height</span>
2702
- </label>
2755
+ <CheckBox
2756
+ value={config.table.limitHeight}
2757
+ section='table'
2758
+ subsection={null}
2759
+ fieldName='limitHeight'
2760
+ label='Limit Table Height'
2761
+ updateField={updateField}
2762
+ onChange={event => {
2763
+ handleEditorChanges('limitDataTableHeight', event.target.checked)
2764
+ }}
2765
+ />
2703
2766
  {config.table.limitHeight && (
2704
2767
  <TextField
2705
2768
  value={table.height}
@@ -2725,16 +2788,17 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2725
2788
  max='500'
2726
2789
  />
2727
2790
 
2728
- <label className='checkbox'>
2729
- <input
2730
- type='checkbox'
2731
- checked={config.table.expanded || false}
2732
- onChange={event => {
2733
- handleEditorChanges('expandDataTable', event.target.checked)
2734
- }}
2735
- />
2736
- <span className='edit-label'>Map loads with data table expanded</span>
2737
- </label>
2791
+ <CheckBox
2792
+ value={config.table.expanded || false}
2793
+ section='table'
2794
+ subsection={null}
2795
+ fieldName='expanded'
2796
+ label='Map loads with data table expanded'
2797
+ updateField={updateField}
2798
+ onChange={event => {
2799
+ handleEditorChanges('expandDataTable', event.target.checked)
2800
+ }}
2801
+ />
2738
2802
  <CheckBox
2739
2803
  value={config.table.download}
2740
2804
  fieldName='download'
@@ -2744,17 +2808,18 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2744
2808
  />
2745
2809
  {config.table.download && (
2746
2810
  <>
2747
- <label className='checkbox'>
2748
- <input
2749
- type='checkbox'
2750
- className='ms-4'
2751
- checked={config.table.showDownloadLinkBelow}
2752
- onChange={event => {
2753
- handleEditorChanges('toggleDownloadLinkBelow', event.target.checked)
2754
- }}
2755
- />
2756
- <span className='edit-label'>Show Link Below Table</span>
2757
- </label>
2811
+ <CheckBox
2812
+ value={config.table.showDownloadLinkBelow}
2813
+ section='table'
2814
+ subsection={null}
2815
+ fieldName='showDownloadLinkBelow'
2816
+ label='Show Link Below Table'
2817
+ updateField={updateField}
2818
+ className='ms-4'
2819
+ onChange={event => {
2820
+ handleEditorChanges('toggleDownloadLinkBelow', event.target.checked)
2821
+ }}
2822
+ />
2758
2823
  <CheckBox
2759
2824
  value={config.table.downloadVisibleDataOnly}
2760
2825
  fieldName='downloadVisibleDataOnly'
@@ -2766,53 +2831,47 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2766
2831
  </>
2767
2832
  )}
2768
2833
  {isDashboard && (
2769
- <label className='checkbox'>
2770
- <input
2771
- type='checkbox'
2772
- checked={config.table.showDataTableLink}
2773
- onChange={event => {
2774
- const _newConfig = _.cloneDeep(config)
2775
- _newConfig.table.showDataTableLink = event.target.checked
2776
- setConfig(_newConfig)
2777
- }}
2778
- />
2779
- <span className='edit-label'>Show Data Table Name & Link</span>
2780
- </label>
2834
+ <CheckBox
2835
+ value={config.table.showDataTableLink}
2836
+ section='table'
2837
+ subsection={null}
2838
+ fieldName='showDataTableLink'
2839
+ label='Show Data Table Name & Link'
2840
+ updateField={updateField}
2841
+ />
2781
2842
  )}
2782
2843
  {isLoadedFromUrl && (
2783
- <label className='checkbox'>
2784
- <input
2785
- type='checkbox'
2786
- checked={config.table.showDownloadUrl}
2787
- onChange={event => {
2788
- const _newConfig = _.cloneDeep(config)
2789
- _newConfig.table.showDownloadUrl = event.target.checked
2790
- setConfig(_newConfig)
2791
- }}
2792
- />
2793
- <span className='edit-label'>Show URL to Automatically Updated Data</span>
2794
- </label>
2795
- )}
2796
- <label className='checkbox'>
2797
- <input
2798
- type='checkbox'
2799
- checked={config.table.showFullGeoNameInCSV}
2800
- onChange={event => {
2801
- handleEditorChanges('toggleShowFullGeoNameInCSV', event.target.checked)
2802
- }}
2803
- />
2804
- <span className='edit-label'>Include Full Geo Name in CSV Download</span>
2805
- </label>
2806
- <label className='checkbox'>
2807
- <input
2808
- type='checkbox'
2809
- checked={config.general.showDownloadImgButton}
2810
- onChange={event => {
2811
- handleEditorChanges('toggleDownloadImgButton', event.target.checked)
2812
- }}
2844
+ <CheckBox
2845
+ value={config.table.showDownloadUrl}
2846
+ section='table'
2847
+ subsection={null}
2848
+ fieldName='showDownloadUrl'
2849
+ label='Show URL to Automatically Updated Data'
2850
+ updateField={updateField}
2813
2851
  />
2814
- <span className='edit-label'>Enable Image Download</span>
2815
- </label>
2852
+ )}
2853
+ <CheckBox
2854
+ value={config.table.showFullGeoNameInCSV}
2855
+ section='table'
2856
+ subsection={null}
2857
+ fieldName='showFullGeoNameInCSV'
2858
+ label='Include Full Geo Name in CSV Download'
2859
+ updateField={updateField}
2860
+ onChange={event => {
2861
+ handleEditorChanges('toggleShowFullGeoNameInCSV', event.target.checked)
2862
+ }}
2863
+ />
2864
+ <CheckBox
2865
+ value={config.general.showDownloadImgButton}
2866
+ section='general'
2867
+ subsection={null}
2868
+ fieldName='showDownloadImgButton'
2869
+ label='Enable Image Download'
2870
+ updateField={updateField}
2871
+ onChange={event => {
2872
+ handleEditorChanges('toggleDownloadImgButton', event.target.checked)
2873
+ }}
2874
+ />
2816
2875
 
2817
2876
  {/* <label className='checkbox'>
2818
2877
  <input
@@ -2877,47 +2936,32 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2877
2936
  <AccordionItemButton>Visual</AccordionItemButton>
2878
2937
  </AccordionItemHeading>
2879
2938
  <AccordionItemPanel>
2880
- <label>
2881
- <span className='edit-label'>Header Theme</span>
2882
- <ul className='color-palette'>
2883
- {HEADER_COLORS.map(palette => {
2884
- return (
2885
- <li
2886
- title={palette}
2887
- key={palette}
2888
- onClick={() => {
2889
- handleEditorChanges('headerColor', palette)
2890
- }}
2891
- className={config.general.headerColor === palette ? 'selected ' + palette : palette}
2892
- ></li>
2893
- )
2894
- })}
2895
- </ul>
2896
- </label>
2897
- <label className='checkbox'>
2898
- <input
2899
- type='checkbox'
2900
- checked={config.general.showTitle || false}
2901
- onChange={event => {
2902
- handleEditorChanges('showTitle', event.target.checked)
2903
- }}
2904
- />
2905
- <span className='edit-label'>Show Title</span>
2906
- </label>
2939
+ <HeaderThemeSelector
2940
+ selectedTheme={config.general.headerColor}
2941
+ onThemeSelect={palette => handleEditorChanges('headerColor', palette)}
2942
+ label='Header Theme'
2943
+ />
2944
+ <CheckBox
2945
+ value={config.general.showTitle || false}
2946
+ section='general'
2947
+ subsection={null}
2948
+ fieldName='showTitle'
2949
+ label='Show Title'
2950
+ updateField={updateField}
2951
+ onChange={event => {
2952
+ handleEditorChanges('showTitle', event.target.checked)
2953
+ }}
2954
+ />
2907
2955
 
2908
2956
  {'navigation' === config.general.type && (
2909
- <label className='checkbox'>
2910
- <input
2911
- type='checkbox'
2912
- checked={config.general.fullBorder || false}
2913
- onChange={event => {
2914
- const _newConfig = _.cloneDeep(config)
2915
- _newConfig.general.fullBorder = event.target.checked
2916
- setConfig(_newConfig)
2917
- }}
2918
- />
2919
- <span className='edit-label'>Add border around map</span>
2920
- </label>
2957
+ <CheckBox
2958
+ value={config.general.fullBorder || false}
2959
+ section='general'
2960
+ subsection={null}
2961
+ fieldName='fullBorder'
2962
+ label='Add border around map'
2963
+ updateField={updateField}
2964
+ />
2921
2965
  )}
2922
2966
  <Select
2923
2967
  label='Geo Border Color'
@@ -2933,6 +2977,15 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2933
2977
  <label>
2934
2978
  <span className='edit-label'>Map Color Palette</span>
2935
2979
  </label>
2980
+ <div className='mb-2'>
2981
+ <small className='text-muted'>
2982
+ Review color contrasts{' '}
2983
+ <a href='https://webaim.org/resources/contrastchecker/' target='_blank' rel='noopener noreferrer'>
2984
+ here
2985
+ </a>
2986
+ </small>
2987
+ </div>
2988
+ <DeveloperPaletteRollback config={config} updateConfig={setConfig} />
2936
2989
  <InputToggle
2937
2990
  type='3d'
2938
2991
  section='general'
@@ -2941,127 +2994,136 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2941
2994
  size='small'
2942
2995
  label='Use selected palette in reverse order'
2943
2996
  onClick={() => {
2944
- const _state = _.cloneDeep(config)
2997
+ const _state = cloneConfig(config)
2998
+ const currentPaletteName = config.general.palette?.name || ''
2945
2999
  _state.general.palette.isReversed = !_state.general.palette.isReversed
2946
3000
  let paletteName = ''
2947
- if (_state.general.palette.isReversed && !config.color.endsWith('reverse')) {
2948
- paletteName = config.color + 'reverse'
3001
+ if (_state.general.palette.isReversed && !currentPaletteName.endsWith('reverse')) {
3002
+ paletteName = currentPaletteName + 'reverse'
2949
3003
  }
2950
- if (!_state.general.palette.isReversed && config.color.endsWith('reverse')) {
2951
- paletteName = config.color.slice(0, -7)
3004
+ if (!_state.general.palette.isReversed && currentPaletteName.endsWith('reverse')) {
3005
+ paletteName = currentPaletteName.slice(0, -7)
2952
3006
  }
2953
3007
  if (paletteName) {
2954
- _state.color = paletteName
3008
+ _state.general.palette.name = paletteName
2955
3009
  }
2956
3010
  setConfig(_state)
2957
3011
  }}
2958
3012
  value={config.general.palette.isReversed}
2959
3013
  />
2960
3014
  <span>Sequential</span>
2961
- <ul className='color-palette'>
2962
- {sequential.map(palette => {
2963
- const colorOne = {
2964
- backgroundColor: colorPalettes[palette][2]
2965
- }
2966
-
2967
- const colorTwo = {
2968
- backgroundColor: colorPalettes[palette][4]
2969
- }
2970
-
2971
- const colorThree = {
2972
- backgroundColor: colorPalettes[palette][6]
2973
- }
2974
-
2975
- return (
2976
- <li
2977
- title={palette}
2978
- key={palette}
2979
- onClick={() => {
2980
- const _newConfig = _.cloneDeep(config)
2981
- _newConfig.color = palette
2982
- setConfig(_newConfig)
2983
- }}
2984
- className={config.color === palette ? 'selected' : ''}
2985
- >
2986
- <span style={colorOne}></span>
2987
- <span style={colorTwo}></span>
2988
- <span style={colorThree}></span>
2989
- </li>
2990
- )
2991
- })}
2992
- </ul>
3015
+ <PaletteSelector
3016
+ palettes={sequential}
3017
+ colorPalettes={colorPalettes}
3018
+ config={config}
3019
+ onPaletteSelect={handlePaletteSelection}
3020
+ selectedPalette={getCurrentPaletteName(config)}
3021
+ colorIndices={[2, 3, 5]}
3022
+ className='color-palette'
3023
+ element='button'
3024
+ getItemClassName={getPaletteClassName}
3025
+ />
2993
3026
  <span>Non-Sequential</span>
2994
- <ul className='color-palette'>
2995
- {nonSequential.map(palette => {
2996
- const colorOne = {
2997
- backgroundColor: colorPalettes[palette][2]
2998
- }
2999
-
3000
- const colorTwo = {
3001
- backgroundColor: colorPalettes[palette][4]
3027
+ <PaletteSelector
3028
+ palettes={nonSequential}
3029
+ colorPalettes={colorPalettes}
3030
+ config={config}
3031
+ onPaletteSelect={handlePaletteSelection}
3032
+ selectedPalette={getCurrentPaletteName(config)}
3033
+ colorIndices={[2, 3, 5]}
3034
+ className='color-palette'
3035
+ element='button'
3036
+ getItemClassName={getPaletteClassName}
3037
+ minColorsForFilter={(_, paletteAccessor, config) => {
3038
+ if (paletteAccessor.length <= 8 && config.general.geoType === 'us-region') {
3039
+ return false
3002
3040
  }
3003
-
3004
- const colorThree = {
3005
- backgroundColor: colorPalettes[palette][6]
3006
- }
3007
-
3008
- // hide palettes with too few colors for region maps
3009
- if (colorPalettes[palette].length <= 8 && config.general.geoType === 'us-region') {
3010
- return ''
3011
- }
3012
- return (
3013
- <li
3014
- title={palette}
3015
- key={palette}
3016
- onClick={() => {
3017
- const _newConfig = _.cloneDeep(config)
3018
- _newConfig.color = palette
3019
- setConfig(_newConfig)
3020
- }}
3021
- className={config.color === palette ? 'selected' : ''}
3022
- >
3023
- <span style={colorOne}></span>
3024
- <span style={colorTwo}></span>
3025
- <span style={colorThree}></span>
3026
- </li>
3027
- )
3028
- })}
3029
- </ul>
3041
+ return true
3042
+ }}
3043
+ />
3030
3044
  <span>Colorblind Safe</span>
3031
- <ul className='color-palette'>
3032
- {accessibleColors.map(palette => {
3033
- const colorOne = {
3034
- backgroundColor: colorPalettes[palette][2]
3035
- }
3036
-
3037
- const colorTwo = {
3038
- backgroundColor: colorPalettes[palette][4]
3045
+ <PaletteSelector
3046
+ palettes={accessibleColors}
3047
+ colorPalettes={colorPalettes}
3048
+ config={config}
3049
+ onPaletteSelect={handlePaletteSelection}
3050
+ selectedPalette={getCurrentPaletteName(config)}
3051
+ colorIndices={[2, 3, 5]}
3052
+ className='color-palette'
3053
+ element='button'
3054
+ getItemClassName={getPaletteClassName}
3055
+ minColorsForFilter={(_, paletteAccessor, config) => {
3056
+ if (paletteAccessor.length <= 8 && config.general.geoType === 'us-region') {
3057
+ return false
3039
3058
  }
3059
+ return true
3060
+ }}
3061
+ />
3040
3062
 
3041
- const colorThree = {
3042
- backgroundColor: colorPalettes[palette][6]
3043
- }
3063
+ {isCoveDeveloperMode() && (
3064
+ <>
3065
+ <div className='mt-3'>
3066
+ <label className='checkbox'>
3067
+ <input
3068
+ type='checkbox'
3069
+ checked={!!config.general.palette.customColorsOrdered}
3070
+ onChange={e => {
3071
+ const _state = cloneConfig(config)
3072
+ if (e.target.checked) {
3073
+ // Extract actual colors from runtime legend if available
3074
+ if (runtimeLegend?.items && runtimeLegend.items.length > 0) {
3075
+ const extractedColors = []
3076
+ for (const item of runtimeLegend.items) {
3077
+ // Skip special classes (like "No Data")
3078
+ if (item.special) continue
3079
+ // Add the color if it exists and hasn't been added yet
3080
+ if (item.color && !extractedColors.includes(item.color)) {
3081
+ extractedColors.push(item.color)
3082
+ }
3083
+ }
3084
+ _state.general.palette.customColorsOrdered =
3085
+ extractedColors.length > 0
3086
+ ? extractedColors
3087
+ : ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
3088
+ } else {
3089
+ // Fallback to default colors if runtime legend not available
3090
+ _state.general.palette.customColorsOrdered = ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
3091
+ }
3092
+ } else {
3093
+ // Remove custom colors and revert to default palette
3094
+ delete _state.general.palette.customColorsOrdered
3095
+ delete _state.general.palette.customColors
3096
+ // Set default palette if none exists
3097
+ if (!_state.general.palette.name) {
3098
+ _state.general.palette.name = 'sequential_blue_green'
3099
+ _state.general.palette.version = '2.0'
3100
+ }
3101
+ }
3102
+ setConfig(_state)
3103
+ }}
3104
+ />
3105
+ Use Custom Colors
3106
+ </label>
3107
+ </div>
3108
+
3109
+ {config.general.palette.customColorsOrdered && (
3110
+ <div className='mt-2'>
3111
+ <CustomColorsEditor
3112
+ colors={config.general.palette.customColorsOrdered}
3113
+ onChange={newColors => {
3114
+ const _state = cloneConfig(config)
3115
+ _state.general.palette.customColorsOrdered = newColors
3116
+ setConfig(_state)
3117
+ }}
3118
+ label='Custom Color Order'
3119
+ minColors={1}
3120
+ maxColors={20}
3121
+ />
3122
+ </div>
3123
+ )}
3124
+ </>
3125
+ )}
3044
3126
 
3045
- // hide palettes with too few colors for region maps
3046
- if (colorPalettes[palette].length <= 8 && config.general.geoType === 'us-region') {
3047
- return ''
3048
- }
3049
- return (
3050
- <li
3051
- title={palette}
3052
- key={palette}
3053
- onClick={() => {
3054
- handleEditorChanges('color', palette)
3055
- }}
3056
- className={config.color === palette ? 'selected' : ''}
3057
- >
3058
- <span style={colorOne}></span>
3059
- <span style={colorTwo}></span>
3060
- <span style={colorThree}></span>
3061
- </li>
3062
- )
3063
- })}
3064
- </ul>
3065
3127
  <label>
3066
3128
  Geocode Settings
3067
3129
  <TextField
@@ -3116,7 +3178,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3116
3178
  type='checkbox'
3117
3179
  checked={config.general.allowMapZoom}
3118
3180
  onChange={event => {
3119
- const _newConfig = _.cloneDeep(config)
3181
+ const _newConfig = cloneConfig(config)
3120
3182
  _newConfig.general.allowMapZoom = event.target.checked
3121
3183
  _newConfig.mapPosition.coordinates = config.general.geoType === 'world' ? [0, 30] : [0, 0]
3122
3184
  _newConfig.mapPosition.zoom = 1
@@ -3132,7 +3194,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3132
3194
  type='checkbox'
3133
3195
  checked={config.visual.extraBubbleBorder}
3134
3196
  onChange={event => {
3135
- const _newConfig = _.cloneDeep(config)
3197
+ const _newConfig = cloneConfig(config)
3136
3198
  _newConfig.visual.extraBubbleBorder = event.target.checked
3137
3199
  setConfig(_newConfig)
3138
3200
  }}
@@ -3144,22 +3206,21 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3144
3206
  config.general.geoType === 'us-county' ||
3145
3207
  config.general.geoType === 'world') && (
3146
3208
  <>
3147
- <label>
3148
- <span className='edit-label'>Default City Style</span>
3149
- <select
3150
- value={config.visual.cityStyle || false}
3151
- onChange={event => {
3152
- handleEditorChanges('handleCityStyle', event.target.value)
3153
- }}
3154
- >
3155
- <option value='circle'>Circle</option>
3156
- <option value='pin'>Pin</option>
3157
- <option value='square'>Square</option>
3158
- <option value='triangle'>Triangle</option>
3159
- <option value='diamond'>Diamond</option>
3160
- <option value='star'>Star</option>
3161
- </select>
3162
- </label>
3209
+ <Select
3210
+ label='Default City Style'
3211
+ value={config.visual.cityStyle || 'circle'}
3212
+ options={[
3213
+ { value: 'circle', label: 'Circle' },
3214
+ { value: 'pin', label: 'Pin' },
3215
+ { value: 'square', label: 'Square' },
3216
+ { value: 'triangle', label: 'Triangle' },
3217
+ { value: 'diamond', label: 'Diamond' },
3218
+ { value: 'star', label: 'Star' }
3219
+ ]}
3220
+ onChange={event => {
3221
+ handleEditorChanges('handleCityStyle', event.target.value)
3222
+ }}
3223
+ />
3163
3224
  <TextField
3164
3225
  value={config.visual.cityStyleLabel}
3165
3226
  section='visual'
@@ -3195,17 +3256,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3195
3256
  Remove
3196
3257
  </button>
3197
3258
  <p>City Style {i + 1}</p>
3198
- <label>
3199
- <span className='edit-label column-heading'>Column with configuration value</span>
3200
- <select
3201
- value={column}
3202
- onChange={e => {
3203
- editCityStyles('update', i, 'column', e.target.value)
3204
- }}
3205
- >
3206
- {columnsOptions}
3207
- </select>
3208
- </label>
3259
+ <Select
3260
+ label='Column with configuration value'
3261
+ value={column}
3262
+ options={columnsOptions.map(c => c.key)}
3263
+ onChange={e => {
3264
+ editCityStyles('update', i, 'column', e.target.value)
3265
+ }}
3266
+ />
3209
3267
  <label>
3210
3268
  <span className='edit-label column-heading'>Value to Trigger</span>
3211
3269
  <input
@@ -3216,17 +3274,19 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3216
3274
  }}
3217
3275
  ></input>
3218
3276
  </label>
3219
- <label>
3220
- <span className='edit-label column-heading'>Shape</span>
3221
- <select
3222
- value={shape}
3223
- onChange={e => {
3224
- editCityStyles('update', i, 'shape', e.target.value)
3225
- }}
3226
- >
3227
- {getCityStyleOptions('value')}
3228
- </select>
3229
- </label>
3277
+ <Select
3278
+ label='Shape'
3279
+ value={shape}
3280
+ options={[
3281
+ { value: '', label: '- Select Option -' },
3282
+ ...['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
3283
+ .filter(val => String(config.visual.cityStyle).toLowerCase() !== val.toLowerCase())
3284
+ .map(val => ({ value: val, label: val }))
3285
+ ]}
3286
+ onChange={e => {
3287
+ editCityStyles('update', i, 'shape', e.target.value)
3288
+ }}
3289
+ />
3230
3290
  <label>
3231
3291
  <span className='edit-label column-heading'>Label</span>
3232
3292
  <input
@@ -3371,9 +3431,27 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3371
3431
  </AccordionItem>
3372
3432
  {config.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
3373
3433
  {config.general.geoType !== 'us-county' && <Panels.Annotate name='Text Annotations' />}
3434
+ <PanelMarkup
3435
+ name='Markup Variables'
3436
+ markupVariables={config.markupVariables || []}
3437
+ data={config.data || []}
3438
+ enableMarkupVariables={config.enableMarkupVariables || false}
3439
+ onMarkupVariablesChange={variables => setConfig({ ...config, markupVariables: variables })}
3440
+ onToggleEnable={enabled => setConfig({ ...config, enableMarkupVariables: enabled })}
3441
+ />
3442
+ <Panels.SmallMultiples name='Small Multiples' />
3374
3443
  </Accordion>
3375
3444
  <AdvancedEditor loadConfig={setConfig} config={config} convertStateToConfig={convertStateToConfig} />
3376
3445
  </Layout.Sidebar>
3446
+
3447
+ {showConversionModal && (
3448
+ <PaletteConversionModal
3449
+ onConfirm={handleConversionConfirm}
3450
+ onCancel={handleConversionCancel}
3451
+ onReturnToV1={handleReturnToV1}
3452
+ paletteName={pendingPaletteSelection?.palette}
3453
+ />
3454
+ )}
3377
3455
  </ErrorBoundary>
3378
3456
  )
3379
3457
  }