@cdc/map 4.22.10-alpha.1 → 4.22.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.
- package/dist/cdcmap.js +10 -10
- package/examples/private/atsdr.json +19 -29
- package/examples/private/atsdr_new.json +1 -1
- package/examples/private/bubble.json +282 -284
- package/examples/private/city-state.json +427 -427
- package/examples/private/city-state2.json +433 -433
- package/examples/private/cty-issue.json +42765 -42768
- package/examples/private/default-usa.json +2 -5
- package/examples/private/default-world-data.json +1443 -1443
- package/examples/private/default.json +965 -965
- package/examples/private/diff.json +226 -0
- package/examples/private/filters.json +1 -0
- package/examples/private/legend-issue.json +3271 -1
- package/examples/private/map-issue.json +166 -0
- package/examples/private/map-rounding-error.json +42756 -42759
- package/examples/private/mdx.json +209 -209
- package/examples/private/monkeypox.json +375 -375
- package/examples/private/regions.json +51 -51
- package/examples/private/wcmsrd-13881-data.json +2856 -2856
- package/examples/private/wcmsrd-13881.json +5818 -5822
- package/examples/private/wcmsrd-14492-data.json +291 -291
- package/examples/private/wcmsrd-14492.json +103 -113
- package/examples/private/wcmsrd-test.json +264 -267
- package/examples/private/world.json +1579 -1579
- package/examples/private/worldmap.json +1489 -1489
- package/package.json +3 -3
- package/src/CdcMap.js +231 -315
- package/src/components/BubbleList.js +199 -240
- package/src/components/CityList.js +50 -96
- package/src/components/CountyMap.js +511 -600
- package/src/components/DataTable.js +218 -253
- package/src/components/EditorPanel.js +2338 -2551
- package/src/components/Geo.js +4 -14
- package/src/components/Modal.js +13 -23
- package/src/components/NavigationMenu.js +43 -39
- package/src/components/Sidebar.js +83 -93
- package/src/components/SingleStateMap.js +95 -151
- package/src/components/UsaMap.js +165 -214
- package/src/components/UsaRegionMap.js +122 -160
- package/src/components/WorldMap.js +96 -179
- package/src/components/ZoomableGroup.js +6 -26
- package/src/data/initial-state.js +1 -0
- package/src/hooks/useActiveElement.js +13 -13
- package/src/hooks/useColorPalette.ts +66 -74
- package/src/hooks/useZoomPan.js +22 -23
- package/src/index.html +1 -2
- package/src/scss/sidebar.scss +22 -0
|
@@ -1,119 +1,94 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, memo } from 'react'
|
|
2
2
|
/** @jsx jsx */
|
|
3
3
|
import { jsx } from '@emotion/react'
|
|
4
|
-
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
5
|
-
import { geoMercator
|
|
6
|
-
import { Mercator } from '@visx/geo'
|
|
7
|
-
import { feature } from
|
|
8
|
-
import topoJSON from '../data/world-topo.json'
|
|
9
|
-
import ZoomableGroup from './ZoomableGroup'
|
|
4
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
5
|
+
import { geoMercator } from 'd3-geo'
|
|
6
|
+
import { Mercator } from '@visx/geo'
|
|
7
|
+
import { feature } from 'topojson-client'
|
|
8
|
+
import topoJSON from '../data/world-topo.json'
|
|
9
|
+
import ZoomableGroup from './ZoomableGroup'
|
|
10
10
|
import Geo from './Geo'
|
|
11
|
-
import CityList from './CityList'
|
|
12
|
-
import BubbleList from './BubbleList'
|
|
11
|
+
import CityList from './CityList'
|
|
12
|
+
import BubbleList from './BubbleList'
|
|
13
13
|
|
|
14
14
|
const { features: world } = feature(topoJSON, topoJSON.objects.countries)
|
|
15
15
|
|
|
16
16
|
let projection = geoMercator()
|
|
17
17
|
|
|
18
|
-
const WorldMap =
|
|
19
|
-
const {
|
|
20
|
-
state,
|
|
21
|
-
applyTooltipsToGeo,
|
|
22
|
-
data,
|
|
23
|
-
geoClickHandler,
|
|
24
|
-
applyLegendToRow,
|
|
25
|
-
displayGeoName,
|
|
26
|
-
supportedCountries,
|
|
27
|
-
rebuildTooltips,
|
|
28
|
-
setState,
|
|
29
|
-
setRuntimeData,
|
|
30
|
-
generateRuntimeData,
|
|
31
|
-
setFilteredCountryCode,
|
|
32
|
-
position,
|
|
33
|
-
setPosition,
|
|
34
|
-
hasZoom,
|
|
35
|
-
handleMapAriaLabels
|
|
36
|
-
} = props;
|
|
18
|
+
const WorldMap = props => {
|
|
19
|
+
const { state, applyTooltipsToGeo, data, geoClickHandler, applyLegendToRow, displayGeoName, supportedCountries, rebuildTooltips, setState, setRuntimeData, generateRuntimeData, setFilteredCountryCode, position, setPosition, hasZoom, handleMapAriaLabels } = props
|
|
37
20
|
|
|
38
|
-
// TODO Refactor - state should be set together here to avoid rerenders
|
|
39
|
-
// Resets to original data & zooms out
|
|
40
|
-
const handleReset = (state, setState, setRuntimeData, generateRuntimeData) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
const handleZoomIn = (position, setPosition) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
21
|
+
// TODO Refactor - state should be set together here to avoid rerenders
|
|
22
|
+
// Resets to original data & zooms out
|
|
23
|
+
const handleReset = (state, setState, setRuntimeData, generateRuntimeData) => {
|
|
24
|
+
let reRun = generateRuntimeData(state)
|
|
25
|
+
setRuntimeData(reRun)
|
|
26
|
+
setState({
|
|
27
|
+
...state,
|
|
28
|
+
focusedCountry: false,
|
|
29
|
+
mapPosition: { coordinates: [0, 30], zoom: 1 }
|
|
30
|
+
})
|
|
31
|
+
setFilteredCountryCode('')
|
|
32
|
+
}
|
|
33
|
+
const handleZoomIn = (position, setPosition) => {
|
|
34
|
+
if (position.zoom >= 4) return
|
|
35
|
+
setPosition(pos => ({ ...pos, zoom: pos.zoom * 1.5 }))
|
|
36
|
+
}
|
|
54
37
|
|
|
55
|
-
const handleZoomOut = (position, setPosition) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
38
|
+
const handleZoomOut = (position, setPosition) => {
|
|
39
|
+
if (position.zoom <= 1) return
|
|
40
|
+
setPosition(pos => ({ ...pos, zoom: pos.zoom / 1.5 }))
|
|
41
|
+
}
|
|
59
42
|
|
|
60
|
-
const ZoomControls = ({position, setPosition, state, setState, setRuntimeData, generateRuntimeData}) => (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
>
|
|
68
|
-
<line x1="12" y1="5" x2="12" y2="19" />
|
|
69
|
-
<line x1="5" y1="12" x2="19" y2="12" />
|
|
70
|
-
</svg>
|
|
71
|
-
</button>
|
|
72
|
-
<button onClick={() => handleZoomOut(position, setPosition)} aria-label="Zoom Out">
|
|
73
|
-
<svg
|
|
74
|
-
viewBox="0 0 24 24"
|
|
75
|
-
stroke="currentColor"
|
|
76
|
-
strokeWidth="3"
|
|
77
|
-
>
|
|
78
|
-
<line x1="5" y1="12" x2="19" y2="12" />
|
|
79
|
-
</svg>
|
|
80
|
-
</button>
|
|
81
|
-
{state.general.type === 'bubble' &&
|
|
82
|
-
<button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className="reset" aria-label="Reset Zoom and Map Filters">
|
|
83
|
-
Reset Filters
|
|
43
|
+
const ZoomControls = ({ position, setPosition, state, setState, setRuntimeData, generateRuntimeData }) => (
|
|
44
|
+
<div className='zoom-controls' data-html2canvas-ignore>
|
|
45
|
+
<button onClick={() => handleZoomIn(position, setPosition)} aria-label='Zoom In'>
|
|
46
|
+
<svg viewBox='0 0 24 24' stroke='currentColor' strokeWidth='3'>
|
|
47
|
+
<line x1='12' y1='5' x2='12' y2='19' />
|
|
48
|
+
<line x1='5' y1='12' x2='19' y2='12' />
|
|
49
|
+
</svg>
|
|
84
50
|
</button>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
51
|
+
<button onClick={() => handleZoomOut(position, setPosition)} aria-label='Zoom Out'>
|
|
52
|
+
<svg viewBox='0 0 24 24' stroke='currentColor' strokeWidth='3'>
|
|
53
|
+
<line x1='5' y1='12' x2='19' y2='12' />
|
|
54
|
+
</svg>
|
|
55
|
+
</button>
|
|
56
|
+
{state.general.type === 'bubble' && (
|
|
57
|
+
<button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className='reset' aria-label='Reset Zoom and Map Filters'>
|
|
58
|
+
Reset Filters
|
|
59
|
+
</button>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
88
63
|
|
|
89
64
|
// TODO Refactor - state should be set together here to avoid rerenders
|
|
90
65
|
const handleCircleClick = (country, state, setState, setRuntimeData, generateRuntimeData) => {
|
|
91
|
-
if(!state.general.allowMapZoom) return
|
|
66
|
+
if (!state.general.allowMapZoom) return
|
|
92
67
|
let newRuntimeData = state.data.filter(item => item[state.columns.geo.name] === country[state.columns.geo.name])
|
|
93
68
|
setFilteredCountryCode(newRuntimeData[0].uid)
|
|
94
69
|
}
|
|
95
70
|
|
|
96
|
-
useEffect(() => rebuildTooltips())
|
|
71
|
+
useEffect(() => rebuildTooltips())
|
|
97
72
|
|
|
98
|
-
const handleMoveEnd =
|
|
99
|
-
setPosition(position)
|
|
100
|
-
}
|
|
73
|
+
const handleMoveEnd = position => {
|
|
74
|
+
setPosition(position)
|
|
75
|
+
}
|
|
101
76
|
|
|
102
|
-
const constructGeoJsx =
|
|
77
|
+
const constructGeoJsx = geographies => {
|
|
103
78
|
const geosJsx = geographies.map(({ feature: geo, path }, i) => {
|
|
104
79
|
const geoKey = geo.properties.iso
|
|
105
80
|
|
|
106
|
-
if(!geoKey) return
|
|
81
|
+
if (!geoKey) return
|
|
107
82
|
|
|
108
|
-
const geoData = data[geoKey]
|
|
83
|
+
const geoData = data[geoKey]
|
|
109
84
|
|
|
110
|
-
const geoDisplayName = displayGeoName(supportedCountries[geoKey][0])
|
|
85
|
+
const geoDisplayName = displayGeoName(supportedCountries[geoKey][0])
|
|
111
86
|
|
|
112
|
-
let legendColors
|
|
87
|
+
let legendColors
|
|
113
88
|
|
|
114
89
|
// Once we receive data for this geographic item, setup variables.
|
|
115
90
|
if (geoData !== undefined) {
|
|
116
|
-
legendColors = applyLegendToRow(geoData)
|
|
91
|
+
legendColors = applyLegendToRow(geoData)
|
|
117
92
|
}
|
|
118
93
|
|
|
119
94
|
const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
|
|
@@ -123,65 +98,44 @@ const ZoomControls = ({position, setPosition, state, setState, setRuntimeData, g
|
|
|
123
98
|
cursor: 'default'
|
|
124
99
|
}
|
|
125
100
|
|
|
126
|
-
const strokeWidth = .9
|
|
101
|
+
const strokeWidth = 0.9
|
|
127
102
|
|
|
128
103
|
// If a legend applies, return it with appropriate information.
|
|
129
104
|
if (legendColors && legendColors[0] !== '#000000' && state.general.type !== 'bubble') {
|
|
130
|
-
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
105
|
+
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
106
|
+
|
|
107
|
+
styles = {
|
|
108
|
+
...styles,
|
|
109
|
+
fill: legendColors[0],
|
|
110
|
+
cursor: 'default',
|
|
111
|
+
'&:hover': {
|
|
112
|
+
fill: legendColors[1]
|
|
113
|
+
},
|
|
114
|
+
'&:active': {
|
|
115
|
+
fill: legendColors[2]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
143
118
|
|
|
144
119
|
// When to add pointer cursor
|
|
145
120
|
if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
|
|
146
121
|
styles.cursor = 'pointer'
|
|
147
122
|
}
|
|
148
123
|
|
|
149
|
-
return (
|
|
150
|
-
<Geo
|
|
151
|
-
key={i + '-geo'}
|
|
152
|
-
css={styles}
|
|
153
|
-
data-for="tooltip"
|
|
154
|
-
data-tip={tooltip}
|
|
155
|
-
path={path}
|
|
156
|
-
stroke={geoStrokeColor}
|
|
157
|
-
strokeWidth={strokeWidth}
|
|
158
|
-
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
159
|
-
/>
|
|
160
|
-
)
|
|
124
|
+
return <Geo key={i + '-geo'} css={styles} data-for='tooltip' data-tip={tooltip} path={path} stroke={geoStrokeColor} strokeWidth={strokeWidth} onClick={() => geoClickHandler(geoDisplayName, geoData)} />
|
|
161
125
|
}
|
|
162
126
|
|
|
163
127
|
// Default return state, just geo with no additional information
|
|
164
128
|
return <Geo key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} css={styles} path={path} />
|
|
165
|
-
})
|
|
129
|
+
})
|
|
166
130
|
|
|
167
131
|
// Cities
|
|
168
|
-
geosJsx.push(<CityList
|
|
169
|
-
projection={projection}
|
|
170
|
-
key="cities"
|
|
171
|
-
data={data}
|
|
172
|
-
state={state}
|
|
173
|
-
geoClickHandler={geoClickHandler}
|
|
174
|
-
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
175
|
-
displayGeoName={displayGeoName}
|
|
176
|
-
applyLegendToRow={applyLegendToRow}
|
|
177
|
-
isGeoCodeMap={state.general.type === 'us-geocode'}
|
|
178
|
-
/>)
|
|
132
|
+
geosJsx.push(<CityList projection={projection} key='cities' data={data} state={state} geoClickHandler={geoClickHandler} applyTooltipsToGeo={applyTooltipsToGeo} displayGeoName={displayGeoName} applyLegendToRow={applyLegendToRow} isGeoCodeMap={state.general.type === 'us-geocode'} />)
|
|
179
133
|
|
|
180
134
|
// Bubbles
|
|
181
|
-
if(state.general.type === 'bubble') {
|
|
135
|
+
if (state.general.type === 'bubble') {
|
|
182
136
|
geosJsx.push(
|
|
183
137
|
<BubbleList
|
|
184
|
-
key=
|
|
138
|
+
key='bubbles'
|
|
185
139
|
data={state.data}
|
|
186
140
|
runtimeData={data}
|
|
187
141
|
state={state}
|
|
@@ -189,70 +143,33 @@ const ZoomControls = ({position, setPosition, state, setState, setRuntimeData, g
|
|
|
189
143
|
applyLegendToRow={applyLegendToRow}
|
|
190
144
|
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
191
145
|
displayGeoName={displayGeoName}
|
|
192
|
-
handleCircleClick={
|
|
146
|
+
handleCircleClick={country => handleCircleClick(country, state, setState, setRuntimeData, generateRuntimeData)}
|
|
193
147
|
/>
|
|
194
148
|
)
|
|
195
149
|
}
|
|
196
150
|
|
|
197
|
-
return geosJsx
|
|
198
|
-
}
|
|
151
|
+
return geosJsx
|
|
152
|
+
}
|
|
199
153
|
|
|
200
154
|
return (
|
|
201
|
-
<ErrorBoundary component=
|
|
155
|
+
<ErrorBoundary component='WorldMap'>
|
|
202
156
|
{hasZoom ? (
|
|
203
|
-
<svg
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
>
|
|
208
|
-
<rect height={500} width={880} onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} fill="white"/>
|
|
209
|
-
<ZoomableGroup
|
|
210
|
-
zoom={position.zoom}
|
|
211
|
-
center={position.coordinates}
|
|
212
|
-
onMoveEnd={handleMoveEnd}
|
|
213
|
-
maxZoom={4}
|
|
214
|
-
projection={projection}
|
|
215
|
-
width={880}
|
|
216
|
-
height={500}
|
|
217
|
-
>
|
|
218
|
-
<Mercator
|
|
219
|
-
data={world}
|
|
220
|
-
>
|
|
221
|
-
{({ features }) => constructGeoJsx(features)}
|
|
222
|
-
</Mercator>
|
|
157
|
+
<svg viewBox='0 0 880 500' role='img' aria-label={handleMapAriaLabels(state)}>
|
|
158
|
+
<rect height={500} width={880} onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} fill='white' />
|
|
159
|
+
<ZoomableGroup zoom={position.zoom} center={position.coordinates} onMoveEnd={handleMoveEnd} maxZoom={4} projection={projection} width={880} height={500}>
|
|
160
|
+
<Mercator data={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
223
161
|
</ZoomableGroup>
|
|
224
162
|
</svg>
|
|
225
|
-
) :
|
|
226
|
-
<svg viewBox=
|
|
227
|
-
<ZoomableGroup
|
|
228
|
-
|
|
229
|
-
center={position.coordinates}
|
|
230
|
-
onMoveEnd={handleMoveEnd}
|
|
231
|
-
maxZoom={0}
|
|
232
|
-
projection={projection}
|
|
233
|
-
width={880}
|
|
234
|
-
height={500}
|
|
235
|
-
>
|
|
236
|
-
<Mercator
|
|
237
|
-
data={world}
|
|
238
|
-
>
|
|
239
|
-
{({ features }) => constructGeoJsx(features)}
|
|
240
|
-
</Mercator>
|
|
163
|
+
) : (
|
|
164
|
+
<svg viewBox='0 0 880 500'>
|
|
165
|
+
<ZoomableGroup zoom={1} center={position.coordinates} onMoveEnd={handleMoveEnd} maxZoom={0} projection={projection} width={880} height={500}>
|
|
166
|
+
<Mercator data={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
241
167
|
</ZoomableGroup>
|
|
242
168
|
</svg>
|
|
243
|
-
}
|
|
244
|
-
{(state.general.type === 'data' || state.general.type === 'bubble' && hasZoom) &&
|
|
245
|
-
<ZoomControls
|
|
246
|
-
position={position}
|
|
247
|
-
setPosition={setPosition}
|
|
248
|
-
setRuntimeData={setRuntimeData}
|
|
249
|
-
state={state}
|
|
250
|
-
setState={setState}
|
|
251
|
-
generateRuntimeData={generateRuntimeData} />
|
|
252
|
-
}
|
|
253
|
-
|
|
169
|
+
)}
|
|
170
|
+
{(state.general.type === 'data' || (state.general.type === 'bubble' && hasZoom)) && <ZoomControls position={position} setPosition={setPosition} setRuntimeData={setRuntimeData} state={state} setState={setState} generateRuntimeData={generateRuntimeData} />}
|
|
254
171
|
</ErrorBoundary>
|
|
255
|
-
)
|
|
256
|
-
}
|
|
172
|
+
)
|
|
173
|
+
}
|
|
257
174
|
|
|
258
175
|
export default memo(WorldMap)
|
|
@@ -1,28 +1,8 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import useZoomPan from '../hooks/useZoomPan'
|
|
1
3
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const ZoomableGroup = ({
|
|
6
|
-
center = [0, 0],
|
|
7
|
-
zoom = 1,
|
|
8
|
-
minZoom = 1,
|
|
9
|
-
maxZoom = 8,
|
|
10
|
-
translateExtent,
|
|
11
|
-
filterZoomEvent,
|
|
12
|
-
onMoveStart,
|
|
13
|
-
onMove,
|
|
14
|
-
onMoveEnd,
|
|
15
|
-
className,
|
|
16
|
-
projection,
|
|
17
|
-
width,
|
|
18
|
-
height,
|
|
19
|
-
...restProps
|
|
20
|
-
}) => {
|
|
21
|
-
|
|
22
|
-
const {
|
|
23
|
-
mapRef,
|
|
24
|
-
transformString,
|
|
25
|
-
} = useZoomPan({
|
|
4
|
+
const ZoomableGroup = ({ center = [0, 0], zoom = 1, minZoom = 1, maxZoom = 8, translateExtent, filterZoomEvent, onMoveStart, onMove, onMoveEnd, className, projection, width, height, ...restProps }) => {
|
|
5
|
+
const { mapRef, transformString } = useZoomPan({
|
|
26
6
|
center,
|
|
27
7
|
filterZoomEvent,
|
|
28
8
|
onMoveStart,
|
|
@@ -38,10 +18,10 @@ const ZoomableGroup = ({
|
|
|
38
18
|
|
|
39
19
|
return (
|
|
40
20
|
<g ref={mapRef}>
|
|
41
|
-
<rect width={width} height={height} fill=
|
|
21
|
+
<rect width={width} height={height} fill='transparent' />
|
|
42
22
|
<g transform={transformString} {...restProps} />
|
|
43
23
|
</g>
|
|
44
24
|
)
|
|
45
25
|
}
|
|
46
26
|
|
|
47
|
-
export default ZoomableGroup
|
|
27
|
+
export default ZoomableGroup
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {useState, useEffect} from 'react'
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
2
|
// Use for accessibility testing
|
|
3
3
|
const useActiveElement = () => {
|
|
4
|
-
|
|
4
|
+
const [active, setActive] = useState(document.activeElement)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const handleFocusIn = e => {
|
|
7
|
+
setActive(document.activeElement)
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
document.addEventListener('focusin', handleFocusIn)
|
|
12
|
+
return () => {
|
|
13
|
+
document.removeEventListener('focusin', handleFocusIn)
|
|
14
|
+
}
|
|
15
|
+
}, [])
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
return active
|
|
18
18
|
}
|
|
19
|
-
export default useActiveElement
|
|
19
|
+
export default useActiveElement
|
|
@@ -1,96 +1,88 @@
|
|
|
1
|
-
import { useEffect, useReducer } from 'react'
|
|
1
|
+
import { useEffect, useReducer } from 'react'
|
|
2
2
|
|
|
3
|
-
// constants
|
|
4
|
-
const SEQUENTIAL = 'SEQUENTIAL'
|
|
5
|
-
const SEQUENTIAL_REVERSE = 'SEQUENTIAL_REVERSE'
|
|
6
|
-
export const GET_PALETTE = 'GET_PALETTE'
|
|
3
|
+
// constants
|
|
4
|
+
const SEQUENTIAL = 'SEQUENTIAL'
|
|
5
|
+
const SEQUENTIAL_REVERSE = 'SEQUENTIAL_REVERSE'
|
|
6
|
+
export const GET_PALETTE = 'GET_PALETTE'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
// types & interfaces
|
|
8
|
+
// types & interfaces
|
|
10
9
|
interface State {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
payload: Palettes;
|
|
22
|
-
paletteName?:string
|
|
23
|
-
};
|
|
10
|
+
readonly filteredPallets: string[]
|
|
11
|
+
readonly filteredQualitative: string[]
|
|
12
|
+
readonly isPaletteReversed: boolean
|
|
13
|
+
paletteName: string | undefined
|
|
14
|
+
}
|
|
15
|
+
interface Action<Palettes> {
|
|
16
|
+
type: 'SEQUENTIAL' | 'SEQUENTIAL_REVERSE' | 'GET_PALETTE'
|
|
17
|
+
payload: Palettes
|
|
18
|
+
paletteName?: string
|
|
19
|
+
}
|
|
24
20
|
|
|
25
21
|
// create initial state
|
|
26
|
-
const initialState:State = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
const initialState: State = {
|
|
23
|
+
filteredPallets: [],
|
|
24
|
+
isPaletteReversed: false,
|
|
25
|
+
filteredQualitative: [],
|
|
26
|
+
paletteName: undefined
|
|
27
|
+
}
|
|
32
28
|
|
|
33
29
|
// create reducer function to handle multiple states & manupilate with each state
|
|
34
|
-
function reducer<T>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let
|
|
38
|
-
let
|
|
39
|
-
|
|
30
|
+
function reducer<T>(state: State, action: Action<T>): State {
|
|
31
|
+
// <T> refers to generic type
|
|
32
|
+
const palletNamesArr: string[] = Object.keys(action.payload) // action.payload === colorPalettes object
|
|
33
|
+
let reverseRegex = new RegExp('reverse$') // matches a string that ends in with "reverse".
|
|
34
|
+
let qualitativeRegex = new RegExp('^qualitative') //matches any string that starts with "qualitative".
|
|
35
|
+
let paletteName: string = ''
|
|
36
|
+
switch (action.type) {
|
|
40
37
|
case GET_PALETTE:
|
|
41
38
|
// this case runs first time when page loads and then every time when color.state changes.It is mounted insude of useEffect on Editors Panel
|
|
42
39
|
// action.palletName is a string type and equals to state.color which is inisde Editors Panel.
|
|
43
|
-
return {...state,paletteName:action.paletteName}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
return { ...state, paletteName: action.paletteName }
|
|
41
|
+
case SEQUENTIAL:
|
|
42
|
+
paletteName = String(state.paletteName).endsWith('reverse') ? String(state.paletteName).substring(0, state.paletteName.length - 7) : String(state.paletteName)
|
|
43
|
+
const qualitative: string[] = palletNamesArr.filter((name: string) => name.match(qualitativeRegex) && !name.match(reverseRegex) && !name.includes('qualitative9'))
|
|
44
|
+
const sequential: string[] = palletNamesArr.filter((name: string) => !name.match(qualitativeRegex) && !name.match(reverseRegex))
|
|
45
|
+
|
|
46
|
+
return { ...state, filteredPallets: sequential, filteredQualitative: qualitative, paletteName: paletteName, isPaletteReversed: false }
|
|
47
|
+
|
|
48
|
+
case SEQUENTIAL_REVERSE:
|
|
49
|
+
paletteName = state.paletteName && String(state.paletteName).concat('reverse')
|
|
50
|
+
const qualitativeReverse: string[] = palletNamesArr.filter((name: string) => name.match(qualitativeRegex) && name.match(reverseRegex))
|
|
51
|
+
const sequentialReverse: string[] = palletNamesArr.filter((name: string) => !name.match(qualitativeRegex) && name.match(reverseRegex))
|
|
52
|
+
|
|
53
|
+
return { ...state, filteredQualitative: qualitativeReverse, filteredPallets: sequentialReverse, paletteName: paletteName, isPaletteReversed: true }
|
|
54
|
+
default:
|
|
55
|
+
return state
|
|
58
56
|
}
|
|
59
|
-
}
|
|
57
|
+
}
|
|
60
58
|
|
|
61
59
|
interface Keyable {
|
|
62
|
-
color:string
|
|
63
|
-
general:{
|
|
64
|
-
palette:{
|
|
65
|
-
isReversed:boolean
|
|
60
|
+
color: string
|
|
61
|
+
general: {
|
|
62
|
+
palette: {
|
|
63
|
+
isReversed: boolean
|
|
66
64
|
}
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
export function useColorPalette<T,Y extends Keyable>(colorPalettes:T,configState:Y){
|
|
71
|
-
const [state, dispatch] = useReducer(reducer, initialState)
|
|
72
|
-
const {paletteName,isPaletteReversed,filteredPallets,filteredQualitative} = state
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
export function useColorPalette<T, Y extends Keyable>(colorPalettes: T, configState: Y) {
|
|
69
|
+
const [state, dispatch] = useReducer(reducer, initialState)
|
|
70
|
+
const { paletteName, isPaletteReversed, filteredPallets, filteredQualitative } = state
|
|
76
71
|
|
|
77
72
|
useEffect(() => {
|
|
78
|
-
|
|
79
|
-
}, [])
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
useEffect(()=>{
|
|
83
|
-
if(configState.general.palette.isReversed){
|
|
84
|
-
dispatch({ type: "SEQUENTIAL_REVERSE", payload: colorPalettes });
|
|
85
|
-
}
|
|
86
|
-
return ()=> dispatch({ type: "SEQUENTIAL", payload: colorPalettes });
|
|
87
|
-
|
|
88
|
-
},[configState.general.palette.isReversed,dispatch,colorPalettes])
|
|
73
|
+
dispatch({ type: SEQUENTIAL, payload: colorPalettes })
|
|
74
|
+
}, [])
|
|
89
75
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (configState.general.palette.isReversed) {
|
|
78
|
+
dispatch({ type: 'SEQUENTIAL_REVERSE', payload: colorPalettes })
|
|
79
|
+
}
|
|
80
|
+
return () => dispatch({ type: 'SEQUENTIAL', payload: colorPalettes })
|
|
81
|
+
}, [configState.general.palette.isReversed, dispatch, colorPalettes])
|
|
93
82
|
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (configState.color) dispatch({ type: GET_PALETTE, payload: colorPalettes, paletteName: configState.color })
|
|
85
|
+
}, [dispatch, configState.color])
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
}
|
|
87
|
+
return { paletteName, isPaletteReversed, filteredPallets, filteredQualitative, dispatch }
|
|
88
|
+
}
|