@cdc/map 4.25.3 → 4.25.6

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 (119) hide show
  1. package/.idea/map.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/dist/cdcmap.js +31254 -32242
  5. package/examples/hex-colors.json +3 -3
  6. package/examples/m2.json +32904 -0
  7. package/examples/private/test.json +470 -1457
  8. package/examples/private/{mmr.json → wastewatermap.json} +86 -115
  9. package/index.html +36 -63
  10. package/package.json +7 -19
  11. package/src/CdcMap.tsx +56 -1552
  12. package/src/CdcMapComponent.tsx +608 -0
  13. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +10 -0
  14. package/src/_stories/CdcMap.Legend.stories.tsx +67 -0
  15. package/src/_stories/CdcMap.Table.stories.tsx +19 -0
  16. package/src/_stories/CdcMap.stories.tsx +12 -1
  17. package/src/_stories/UsaMap.NoData.stories.tsx +4 -4
  18. package/src/_stories/_mock/default-patterns.json +8 -5
  19. package/src/_stories/_mock/legend-bins.json +428 -0
  20. package/{examples/private/default-patterns.json → src/_stories/_mock/legends/legend-tests.json} +36 -131
  21. package/src/cdcMapComponent.styles.css +9 -0
  22. package/src/components/Annotation/Annotation.Draggable.tsx +27 -26
  23. package/src/components/Annotation/AnnotationDropdown.tsx +5 -6
  24. package/src/components/BubbleList.tsx +135 -49
  25. package/src/components/CityList.tsx +89 -87
  26. package/src/components/DataTable.tsx +8 -8
  27. package/src/components/EditorPanel/components/EditorPanel.tsx +823 -885
  28. package/src/components/EditorPanel/components/Error.tsx +9 -2
  29. package/src/components/EditorPanel/components/HexShapeSettings.tsx +127 -141
  30. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +55 -86
  31. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +89 -75
  32. package/src/components/EditorPanel/components/editorPanel.styles.css +95 -0
  33. package/src/components/Geo.tsx +9 -1
  34. package/src/components/GoogleMap/components/GoogleMap.tsx +1 -1
  35. package/src/components/Legend/components/Legend.tsx +92 -87
  36. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +128 -0
  37. package/src/components/Legend/components/LegendGroup/legend.group.css +27 -0
  38. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -1
  39. package/src/components/Legend/components/index.scss +74 -17
  40. package/src/components/Modal.tsx +17 -7
  41. package/src/components/NavigationMenu.tsx +11 -9
  42. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +12 -8
  43. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +4 -4
  44. package/src/components/UsaMap/components/TerritoriesSection.tsx +33 -10
  45. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +12 -10
  46. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +12 -14
  47. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +2 -1
  48. package/src/components/UsaMap/components/UsaMap.County.tsx +138 -96
  49. package/src/components/UsaMap/components/UsaMap.Region.styles.css +72 -0
  50. package/src/components/UsaMap/components/UsaMap.Region.tsx +56 -103
  51. package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +10 -0
  52. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +65 -74
  53. package/src/components/UsaMap/components/UsaMap.State.tsx +112 -91
  54. package/src/components/UsaMap/helpers/map.ts +1 -1
  55. package/src/components/UsaMap/helpers/shapes.ts +20 -7
  56. package/src/components/WorldMap/WorldMap.tsx +64 -118
  57. package/src/components/WorldMap/worldMap.styles.css +28 -0
  58. package/src/components/ZoomControls.tsx +15 -13
  59. package/src/components/zoomControls.styles.css +53 -0
  60. package/src/context.ts +17 -9
  61. package/src/data/initial-state.js +5 -2
  62. package/src/helpers/addUIDs.ts +150 -0
  63. package/src/helpers/applyColorToLegend.ts +39 -64
  64. package/src/helpers/applyLegendToRow.ts +51 -0
  65. package/src/helpers/colorDistributions.ts +12 -0
  66. package/src/helpers/constants.ts +44 -0
  67. package/src/helpers/displayGeoName.ts +9 -2
  68. package/src/helpers/formatLegendLocation.ts +3 -2
  69. package/src/helpers/generateColorsArray.ts +2 -1
  70. package/src/helpers/generateRuntimeData.ts +78 -0
  71. package/src/helpers/generateRuntimeFilters.ts +63 -0
  72. package/src/helpers/generateRuntimeLegend.ts +566 -0
  73. package/src/helpers/generateRuntimeLegendHash.ts +16 -15
  74. package/src/helpers/getColumnNames.ts +19 -0
  75. package/src/helpers/getMapContainerClasses.ts +23 -0
  76. package/src/helpers/getStatePicked.ts +8 -0
  77. package/src/helpers/handleMapTabbing.ts +31 -0
  78. package/src/helpers/hashObj.ts +1 -1
  79. package/src/helpers/index.ts +22 -0
  80. package/src/helpers/navigationHandler.ts +3 -3
  81. package/src/helpers/resetLegendToggles.ts +13 -0
  82. package/src/helpers/setBinNumbers.ts +5 -0
  83. package/src/helpers/sortSpecialClassesLast.ts +7 -0
  84. package/src/helpers/tests/getColumnNames.test.ts +52 -0
  85. package/src/helpers/titleCase.ts +1 -1
  86. package/src/helpers/toggleLegendActive.ts +25 -0
  87. package/src/hooks/useApplyTooltipsToGeo.tsx +51 -0
  88. package/src/hooks/useColumnsRequiredChecker.ts +51 -0
  89. package/src/hooks/useGeoClickHandler.ts +45 -0
  90. package/src/hooks/useLegendSeparators.ts +26 -0
  91. package/src/hooks/useMapLayers.tsx +34 -60
  92. package/src/hooks/useModal.ts +22 -0
  93. package/src/hooks/useResizeObserver.ts +4 -5
  94. package/src/hooks/useStateZoom.tsx +52 -75
  95. package/src/hooks/useTooltip.ts +2 -3
  96. package/src/index.jsx +3 -9
  97. package/src/scss/editor-panel.scss +3 -99
  98. package/src/scss/main.scss +1 -19
  99. package/src/scss/map.scss +15 -220
  100. package/src/store/map.actions.ts +46 -0
  101. package/src/store/map.reducer.ts +96 -0
  102. package/src/types/Annotations.ts +24 -0
  103. package/src/types/MapConfig.ts +23 -3
  104. package/src/types/MapContext.ts +36 -35
  105. package/src/types/Modal.ts +1 -0
  106. package/src/types/RuntimeData.ts +3 -0
  107. package/examples/private/DEV-9644.json +0 -184
  108. package/examples/private/DEV-9989.json +0 -229
  109. package/examples/private/ardi.json +0 -180
  110. package/examples/private/colors 2.json +0 -416
  111. package/examples/private/colors.json +0 -416
  112. package/examples/private/colors.json.zip +0 -0
  113. package/examples/private/customColors.json +0 -45348
  114. package/examples/test.json +0 -183
  115. package/src/helpers/closeModal.ts +0 -9
  116. package/src/scss/btn.scss +0 -69
  117. package/src/scss/filters.scss +0 -27
  118. package/src/scss/variables.scss +0 -1
  119. /package/src/hooks/{useActiveElement.js → useActiveElement.ts} +0 -0
@@ -0,0 +1,22 @@
1
+ export { addUIDs } from './addUIDs'
2
+ export { applyColorToLegend } from './applyColorToLegend'
3
+ export { colorDistributions } from './colorDistributions'
4
+ export { displayGeoName } from './displayGeoName'
5
+ export { formatLegendLocation } from './formatLegendLocation'
6
+ export { generateColorsArray } from './generateColorsArray'
7
+ export { generateRuntimeLegendHash } from './generateRuntimeLegendHash'
8
+ export { getGeoStrokeColor, getGeoFillColor } from './colors'
9
+ export { getUniqueValues } from './getUniqueValues'
10
+ export { handleMapAriaLabels } from './handleMapAriaLabels'
11
+ export { handleMapTabbing } from './handleMapTabbing'
12
+ export { hashObj } from './hashObj'
13
+ export { indexOfIgnoreType } from './indexOfIgnoreType'
14
+ export { navigationHandler } from './navigationHandler'
15
+ export { resetLegendToggles } from './resetLegendToggles'
16
+ export { setBinNumbers } from './setBinNumbers'
17
+ export { sortSpecialClassesLast } from './sortSpecialClassesLast'
18
+ export { titleCase as toTitleCase } from './toTitleCase'
19
+ export { titleCase } from './titleCase'
20
+ export { validateFipsCodeLength } from './validateFipsCodeLength'
21
+ export { getMapContainerClasses } from './getMapContainerClasses'
22
+ export { SVG_HEIGHT, SVG_WIDTH, SVG_PADDING, SVG_VIEWBOX, HEADER_COLORS, MAX_ZOOM_LEVEL } from './constants'
@@ -9,9 +9,9 @@ export const navigationHandler = (
9
9
  return
10
10
  }
11
11
 
12
- // Abort if value is blank
13
- if (0 === urlString.length) {
14
- throw Error('Blank string passed as URL. Navigation aborted.')
12
+ // Abort if urlString is not a valid string
13
+ if (typeof urlString !== 'string' || urlString.trim().length === 0) {
14
+ throw Error('Invalid or blank URL. Navigation aborted.')
15
15
  }
16
16
 
17
17
  const urlObj = new URL(urlString, window.location.origin)
@@ -0,0 +1,13 @@
1
+ import _ from 'lodash'
2
+ export const resetLegendToggles = (runtimeLegend, setRuntimeLegend) => {
3
+ const legendCopy = _.cloneDeep(runtimeLegend)
4
+
5
+ legendCopy.items.forEach(legendItem => {
6
+ delete legendItem.disabled
7
+ })
8
+ legendCopy.disabledAmt = 0
9
+
10
+ legendCopy.runtimeDataHash = runtimeLegend.runtimeDataHash
11
+
12
+ setRuntimeLegend(legendCopy)
13
+ }
@@ -0,0 +1,5 @@
1
+ export const setBinNumbers = result => {
2
+ result.items.forEach((row, i) => {
3
+ row.bin = i // set bin number to index
4
+ })
5
+ }
@@ -0,0 +1,7 @@
1
+ export const sortSpecialClassesLast = (result, legend) => {
2
+ if (legend.showSpecialClassesLast) {
3
+ const specialRows = result.items.filter(d => d.special === true)
4
+ const otherRows = result.items.filter(d => !d.special)
5
+ result.items = [...otherRows, ...specialRows]
6
+ }
7
+ }
@@ -0,0 +1,52 @@
1
+ import { getColumnNames } from './../getColumnNames'
2
+ import { MapConfig } from '../../types/MapConfig'
3
+
4
+ // write a test that returns null if columns are not provided
5
+ describe('missing config.columns parameter', () => {
6
+ it('should return null', () => {
7
+ const columns = null
8
+ expect(getColumnNames(columns)).toBeNull()
9
+ })
10
+ })
11
+
12
+ it('returns null for undefined column names', () => {
13
+ const columns: Pick<MapConfig, 'columns'> = {
14
+ columns: {
15
+ geo: { name: undefined },
16
+ primary: { name: undefined },
17
+ latitude: { name: undefined },
18
+ longitude: { name: undefined },
19
+ categorical: { name: undefined }
20
+ }
21
+ }
22
+
23
+ const result = getColumnNames(columns)
24
+ expect(result).toEqual({
25
+ geoColumnName: null,
26
+ primaryColumnName: null,
27
+ latitudeColumnName: null,
28
+ longitudeColumnName: null,
29
+ categoricalColumnName: null
30
+ })
31
+ })
32
+
33
+ it('returns null for empty column names', () => {
34
+ const columns: Pick<MapConfig, 'columns'> = {
35
+ columns: {
36
+ geo: { name: '' },
37
+ primary: { name: '' },
38
+ latitude: { name: '' },
39
+ longitude: { name: '' },
40
+ categorical: { name: '' }
41
+ }
42
+ }
43
+
44
+ const result = getColumnNames(columns)
45
+ expect(result).toEqual({
46
+ geoColumnName: null,
47
+ primaryColumnName: null,
48
+ latitudeColumnName: null,
49
+ longitudeColumnName: null,
50
+ categoricalColumnName: null
51
+ })
52
+ })
@@ -22,7 +22,7 @@ export const titleCase = string => {
22
22
  // just return with each word uppercase
23
23
  return string
24
24
  .split(' ')
25
- .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
25
+ .map(word => (word === 'of' ? word : word.charAt(0).toUpperCase() + word.substring(1).toLowerCase()))
26
26
  .join(' ')
27
27
  }
28
28
  }
@@ -0,0 +1,25 @@
1
+ import _ from 'lodash'
2
+
3
+ export const toggleLegendActive = (
4
+ i: number,
5
+ legendLabel: string,
6
+ runtimeLegend,
7
+ setRuntimeLegend,
8
+ setAccessibleStatus: (message: string) => void
9
+ ) => {
10
+ const runtimeLegendCopy = _.cloneDeep(runtimeLegend)
11
+
12
+ // Create and toggle the new value
13
+ const newValue = !runtimeLegendCopy.items?.[i].disabled
14
+ runtimeLegendCopy.items[i].disabled = newValue
15
+
16
+ const disabledAmt = runtimeLegend.disabledAmt ?? 0
17
+
18
+ runtimeLegendCopy['disabledAmt'] = newValue ? disabledAmt + 1 : disabledAmt - 1
19
+
20
+ setRuntimeLegend(runtimeLegendCopy)
21
+
22
+ setAccessibleStatus(
23
+ `Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`
24
+ )
25
+ }
@@ -0,0 +1,51 @@
1
+ import { type ReactNode, useContext } from 'react'
2
+ import { displayGeoName, navigationHandler } from '../helpers'
3
+ import ConfigContext from '../context'
4
+ import useTooltip from './useTooltip'
5
+ import { supportedStatesFipsCodes } from './../data/supported-geos'
6
+ import parse from 'html-react-parser'
7
+ import isDomainExternal from '@cdc/core/helpers/isDomainExternal'
8
+ import ExternalIcon from './../images/external-link.svg'
9
+
10
+ const useApplyTooltipsToGeo = () => {
11
+ const { config, customNavigationHandler } = useContext(ConfigContext)
12
+ const navigationColumnName = config.columns.navigate.name
13
+ const { buildTooltip } = useTooltip({ config, displayGeoName, supportedStatesFipsCodes })
14
+
15
+ const applyTooltipsToGeo = (geoName: string, row: Object, returnType = 'string') => {
16
+ let toolTipText: string | ReactNode = buildTooltip(row, geoName, '')
17
+
18
+ // We convert the markup into JSX and add a navigation link if it's going into a modal.
19
+ if ('jsx' === returnType) {
20
+ if (typeof toolTipText === 'string') {
21
+ toolTipText = [<div key='modal-content'>{parse(toolTipText)}</div>]
22
+ }
23
+
24
+ if (config.columns.hasOwnProperty('navigate') && row[navigationColumnName]) {
25
+ // Check that toolTipText is an array before pushing to it
26
+ if (Array.isArray(toolTipText)) {
27
+ toolTipText.push(
28
+ <a
29
+ href='#'
30
+ className='navigation-link'
31
+ key='modal-navigation-link'
32
+ onClick={e => {
33
+ e.preventDefault()
34
+ navigationHandler(config.general.navigationTarget, row[navigationColumnName], customNavigationHandler)
35
+ }}
36
+ >
37
+ {config.tooltips.linkLabel}
38
+ {isDomainExternal(row[navigationColumnName]) && <ExternalIcon className='inline-icon ms-1' />}
39
+ </a>
40
+ )
41
+ }
42
+ }
43
+ }
44
+
45
+ return toolTipText
46
+ }
47
+
48
+ return { applyTooltipsToGeo }
49
+ }
50
+
51
+ export default useApplyTooltipsToGeo
@@ -0,0 +1,51 @@
1
+ import { useContext } from 'react'
2
+ import ConfigContext, { MapDispatchContext } from '../context'
3
+ import { getColumnNames } from '../helpers/getColumnNames'
4
+
5
+ const useColumnsRequiredChecker = () => {
6
+ const { config } = useContext(ConfigContext)
7
+ const dispatch = useContext(MapDispatchContext)
8
+
9
+ const columnsRequiredChecker = () => {
10
+ const { primaryColumnName, geoColumnName } = getColumnNames(config.columns)
11
+
12
+ let columnList = []
13
+
14
+ // Geo is always required
15
+ if (!geoColumnName) {
16
+ columnList.push('Geography')
17
+ }
18
+
19
+ // Primary is required if we're on a data map or a point map
20
+ if ('navigation' !== config.general.type && '' === primaryColumnName) {
21
+ columnList.push('Primary')
22
+ }
23
+
24
+ // Navigate is required for navigation maps
25
+ if ('navigation' === config.general.type && '' === config.columns.navigate.name) {
26
+ columnList.push('Navigation')
27
+ }
28
+
29
+ if (
30
+ ('us-geocode' === config.general.type || 'world-geocode' === config.general.type) &&
31
+ '' === config.columns.latitude.name
32
+ ) {
33
+ columnList.push('Latitude')
34
+ }
35
+
36
+ if (
37
+ ('us-geocode' === config.general.type || 'world-geocode' === config.general.type) &&
38
+ '' === config.columns.longitude.name
39
+ ) {
40
+ columnList.push('Longitude')
41
+ }
42
+
43
+ if (columnList.length === 0) columnList = null
44
+
45
+ dispatch({ type: 'SET_REQUIRED_COLUMNS', payload: columnList })
46
+ }
47
+
48
+ return { columnsRequiredChecker }
49
+ }
50
+
51
+ export default useColumnsRequiredChecker
@@ -0,0 +1,45 @@
1
+ import ConfigContext, { MapDispatchContext } from '../context'
2
+ import { navigationHandler } from '../helpers'
3
+ import { useContext } from 'react'
4
+
5
+ const useGeoClickHandler = () => {
6
+ const { config: state, setConfig, setSharedFilter, customNavigationHandler } = useContext(ConfigContext)
7
+ const dispatch = useContext(MapDispatchContext)
8
+
9
+ const geoClickHandler = (geoDisplayName: string, geoData: object): void => {
10
+ if (setSharedFilter) {
11
+ setSharedFilter(state.uid, geoData)
12
+ }
13
+
14
+ // If world-geocode map zoom to geo point
15
+ if (['world-geocode'].includes(state.general.type)) {
16
+ const lat = geoData[state.columns.latitude.name]
17
+ const long = geoData[state.columns.longitude.name]
18
+
19
+ setConfig({
20
+ ...state,
21
+ mapPosition: { coordinates: [long, lat], zoom: 3 }
22
+ })
23
+ }
24
+
25
+ // If modals are set, or we are on a mobile viewport, display modal
26
+ if (window.matchMedia('(any-hover: none)').matches || 'click' === state.tooltips.appearanceType) {
27
+ const modalData = {
28
+ geoName: geoDisplayName,
29
+ keyedData: geoData
30
+ }
31
+ dispatch({ type: 'SET_MODAL', payload: modalData })
32
+
33
+ return
34
+ }
35
+
36
+ // Otherwise if this item has a link specified for it, do regular navigation.
37
+ if (state.columns.navigate && geoData[state.columns.navigate.name]) {
38
+ navigationHandler(state.general.navigationTarget, geoData[state.columns.navigate.name], customNavigationHandler)
39
+ }
40
+ }
41
+
42
+ return { geoClickHandler }
43
+ }
44
+
45
+ export default useGeoClickHandler
@@ -0,0 +1,26 @@
1
+ import { clamp } from 'lodash'
2
+
3
+ // TODO: generalize this to be used in legends other than linear block gradient
4
+
5
+ const LEGEND_SEPARATOR_SIZE = 0.02
6
+ const LEGEND_SEPARATOR_SIZE_MAX = 20
7
+ const LEGEND_SEPARATOR_SIZE_MIN = 8
8
+
9
+ const useLegendSeparators = (separators, legendWidth, allowsLegendSeparators) => {
10
+ const legendSeparators = allowsLegendSeparators
11
+ ? separators?.replace(' ', '').split(',').map(Number).filter(Boolean) || []
12
+ : []
13
+ const separatorSize = clamp(legendWidth * LEGEND_SEPARATOR_SIZE, LEGEND_SEPARATOR_SIZE_MIN, LEGEND_SEPARATOR_SIZE_MAX)
14
+ const legendSeparatorsToSubtract = legendSeparators.length * separatorSize
15
+ const getTickSeparatorsAdjustment = (index: number) =>
16
+ legendSeparators.reduce((acc, separators) => (index >= separators ? acc + separatorSize : acc), 0)
17
+
18
+ return {
19
+ legendSeparators,
20
+ separatorSize,
21
+ legendSeparatorsToSubtract,
22
+ getTickSeparatorsAdjustment
23
+ }
24
+ }
25
+
26
+ export default useLegendSeparators
@@ -1,7 +1,8 @@
1
- import { useEffect, useId, useState } from 'react'
1
+ import { useEffect, useId, useState, type MouseEvent, type ChangeEvent } from 'react'
2
2
  import { feature } from 'topojson-client'
3
3
  import { Group } from '@visx/group'
4
4
  import { MapConfig } from '../types/MapConfig'
5
+ import _ from 'lodash'
5
6
 
6
7
  /**
7
8
  * This is the starting structure for adding custom geoJSON shape layers to a projection.
@@ -16,7 +17,7 @@ import { MapConfig } from '../types/MapConfig'
16
17
  * 3) Clean (ie. mapshaper -clean) and edit the shape as needed and export the new layer as geoJSON
17
18
  * 4) Save the geoJSON somewhere external.
18
19
  */
19
- export default function useMapLayers(config: MapConfig, setConfig, pathGenerator, tooltipId) {
20
+ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator: Function, tooltipId: string) {
20
21
  const [fetchedTopoJSON, setFetchedTopoJSON] = useState([])
21
22
  const geoId = useId()
22
23
 
@@ -26,65 +27,43 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
26
27
 
27
28
  useEffect(() => {
28
29
  fetchGeoJSONLayers()
29
- }, []) //eslint-disable-line
30
-
31
- useEffect(() => {
32
- fetchGeoJSONLayers()
33
- }, [config.map.layers]) //eslint-disable-line
30
+ }, [config.map.layers])
34
31
 
35
32
  useEffect(() => {
36
33
  if (pathGenerator) {
37
34
  generateCustomLayers()
38
35
  }
39
- }, [fetchedTopoJSON]) //eslint-disable-line
36
+ }, [fetchedTopoJSON])
40
37
 
41
38
  const fetchGeoJSONLayers = async () => {
42
- let geos = await getMapTopoJSONLayers()
43
- setFetchedTopoJSON(geos)
39
+ try {
40
+ const geos = await getMapTopoJSONLayers()
41
+ setFetchedTopoJSON(geos)
42
+ } catch (e) {
43
+ console.error('Error fetching GeoJSON layers:', e) // eslint-disable-line
44
+ }
44
45
  }
45
46
 
46
- /**
47
- * Removes a custom map layer from the config.
48
- * @param { Event } e Remove onclick event
49
- * @param { Integer } index index of layer to remove
50
- */
51
- const handleRemoveLayer = (e, index) => {
47
+ const handleRemoveLayer = (e: MouseEvent<HTMLButtonElement>, index: number) => {
52
48
  e.preventDefault()
53
-
54
- const updatedState = {
55
- ...config,
56
- map: {
57
- ...config.map,
58
- layers: config.map.layers.filter((layer, i) => i !== index)
59
- }
60
- }
61
-
62
- setConfig(updatedState)
49
+ const newConfig = _.cloneDeep(config)
50
+ const layers = newConfig.map.layers.filter((_layer, i) => i !== index)
51
+ newConfig.map.layers = layers
52
+ setConfig(newConfig )
63
53
  }
64
54
 
65
- /**
66
- * Adds a new custom map layer to the config
67
- * @param { Event } e Add onclick event
68
- */
69
- const handleAddLayer = e => {
55
+ const handleAddLayer = (e: Event) => {
70
56
  e.preventDefault()
71
- const updatedState = {
72
- ...config,
73
- map: {
74
- ...config.map,
75
- layers: [
76
- ...config.map.layers,
77
- {
78
- name: 'New Custom Layer',
79
- url: ''
80
- }
81
- ]
82
- }
57
+ const placeHolderLayer = {
58
+ name: 'New Custom Layer',
59
+ url: ''
83
60
  }
84
- setConfig(updatedState)
61
+ const newConfig = _.cloneDeep(config)
62
+ newConfig.map.layers.unshift(placeHolderLayer)
63
+ setConfig( newConfig )
85
64
  }
86
65
 
87
- const handleMapLayer = (e, index, layerKey) => {
66
+ const handleMapLayer = (e: ChangeEvent<HTMLInputElement>, index: number, layerKey: string) => {
88
67
  e.preventDefault()
89
68
 
90
69
  let layerValue = e.target.value
@@ -93,17 +72,10 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
93
72
  layerValue = layerValue / 100
94
73
  }
95
74
 
96
- let newLayers = [...config.map.layers] as Object[]
97
-
98
- newLayers[index][layerKey] = layerValue
75
+ let newLayers = _.cloneDeep(config.map.layers)
76
+ _.set(newLayers, `[${index}][${layerKey}]`, layerValue)
99
77
 
100
- setConfig({
101
- ...config,
102
- map: {
103
- ...config.map,
104
- layers: newLayers
105
- }
106
- })
78
+ setConfig({ ...config, map: { ...config.map, layers: newLayers } })
107
79
  }
108
80
 
109
81
  /**
@@ -117,7 +89,7 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
117
89
  for (const mapLayer of config.map.layers) {
118
90
  let newLayerItem = await fetch(mapLayer.url)
119
91
  .then(res => res.json())
120
- .catch(e => console.warn('error with newLayer item'))
92
+ .catch(e => console.warn('error with newLayer item', e)) // eslint-disable-line
121
93
  if (!newLayerItem) newLayerItem = []
122
94
  TopoJSONObjects.push(newLayerItem)
123
95
  }
@@ -127,10 +99,9 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
127
99
 
128
100
  /**
129
101
  * Updates the custom map layers based on the topojson data
130
- * @returns {void} new map layers to the config
131
102
  */
132
- const generateCustomLayers = () => {
133
- if (fetchedTopoJSON.length === 0 || !fetchedTopoJSON) return false
103
+ const generateCustomLayers = (): void => {
104
+ if (fetchedTopoJSON.length === 0 || !fetchedTopoJSON) return
134
105
  let tempArr = []
135
106
  let tempFeatureArray = []
136
107
 
@@ -149,7 +120,10 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
149
120
  // feature array for county maps
150
121
  tempFeatureArray.push(item)
151
122
  tempArr.push(
152
- <Group className={layerClasses.join(' ')} key={`customMapLayer-${item.properties.name.replace(' ', '-')}-${index}`}>
123
+ <Group
124
+ className={layerClasses.join(' ')}
125
+ key={`customMapLayer-${item.properties.name.replace(' ', '-')}-${index}`}
126
+ >
153
127
  {/* prettier-ignore */}
154
128
  <path
155
129
  d={pathGenerator(item)}
@@ -0,0 +1,22 @@
1
+ import { useContext } from 'react'
2
+ import { MapDispatchContext } from '../context'
3
+
4
+ const useModal = () => {
5
+ const dispatch = useContext(MapDispatchContext)
6
+
7
+ const closeModal = ({ target }, modal: Object) => {
8
+ if (
9
+ 'string' === typeof target.className &&
10
+ (target.className.includes('modal-close') || target.className.includes('modal-background')) &&
11
+ null !== modal
12
+ ) {
13
+ dispatch({ type: 'SET_MODAL', payload: null })
14
+ }
15
+ }
16
+
17
+ return {
18
+ closeModal
19
+ }
20
+ }
21
+
22
+ export default useModal
@@ -1,6 +1,7 @@
1
1
  import { useState, useCallback, useEffect } from 'react'
2
2
  import { type ViewPort } from '@cdc/core/types/ViewPort'
3
3
  import { type DimensionsType } from '@cdc/core/types/Dimensions'
4
+ import { EDITOR_WIDTH } from '@cdc/core/helpers/constants'
4
5
  import getViewport from '@cdc/core/helpers/getViewport'
5
6
  import ResizeObserver from 'resize-observer-polyfill'
6
7
 
@@ -12,15 +13,13 @@ export const useResizeObserver = (isEditor: boolean) => {
12
13
  const resizeObserver = new ResizeObserver(entries => {
13
14
  for (let entry of entries) {
14
15
  let { width, height } = entry.contentRect
15
- let newViewport = getViewport(entry.contentRect.width)
16
16
 
17
- let editorWidth = 350
17
+ width = isEditor ? width - EDITOR_WIDTH : width
18
+
19
+ const newViewport = getViewport(width)
18
20
 
19
21
  setCurrentViewport(newViewport)
20
22
 
21
- if (isEditor) {
22
- width = width - editorWidth
23
- }
24
23
  setDimensions([width, height])
25
24
  }
26
25
  })