@cdc/map 4.23.11 → 4.24.2

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 (60) hide show
  1. package/dist/cdcmap.js +52413 -46767
  2. package/examples/default-patterns.json +581 -0
  3. package/examples/default-usa.json +159 -57
  4. package/examples/test.json +0 -9614
  5. package/examples/zika.json +1194 -0
  6. package/index.html +17 -6
  7. package/package.json +3 -3
  8. package/src/CdcMap.tsx +39 -31
  9. package/src/components/CityList.jsx +34 -23
  10. package/src/components/{EditorPanel.jsx → EditorPanel/components/EditorPanel.tsx} +31 -64
  11. package/src/components/{HexShapeSettings.jsx → EditorPanel/components/HexShapeSettings.tsx} +2 -51
  12. package/src/components/EditorPanel/components/Inputs.tsx +59 -0
  13. package/src/components/EditorPanel/components/Panel.PatternSettings.tsx +140 -0
  14. package/src/components/EditorPanel/components/Panels.tsx +7 -0
  15. package/src/components/EditorPanel/index.tsx +3 -0
  16. package/src/components/Geo.jsx +4 -2
  17. package/src/components/Legend/components/Legend.tsx +183 -0
  18. package/src/components/Legend/components/LegendItem.Hex.tsx +49 -0
  19. package/src/components/Legend/components/index.scss +235 -0
  20. package/src/components/Legend/index.tsx +3 -0
  21. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +129 -0
  22. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +66 -0
  23. package/src/components/UsaMap/components/Territory/index.tsx +9 -0
  24. package/src/components/{CountyMap.jsx → UsaMap/components/UsaMap.County.tsx} +9 -9
  25. package/src/components/{UsaRegionMap.jsx → UsaMap/components/UsaMap.Region.tsx} +1 -3
  26. package/src/components/{SingleStateMap.jsx → UsaMap/components/UsaMap.SingleState.tsx} +8 -10
  27. package/src/components/{UsaMap.jsx → UsaMap/components/UsaMap.State.tsx} +55 -127
  28. package/src/components/UsaMap/index.tsx +13 -0
  29. package/src/components/{WorldMap.jsx → WorldMap/components/WorldMap.jsx} +20 -14
  30. package/src/components/WorldMap/data/world-topo-guiana-update.json +1 -0
  31. package/src/components/WorldMap/data/world-topo-old.json +1 -0
  32. package/src/components/WorldMap/data/world-topo-recent.json +39194 -0
  33. package/src/components/WorldMap/data/world-topo.json +1 -0
  34. package/src/components/WorldMap/index.tsx +3 -0
  35. package/src/context.ts +2 -1
  36. package/src/data/initial-state.js +5 -3
  37. package/src/data/supported-geos.js +21 -1
  38. package/src/hooks/useTooltip.ts +4 -4
  39. package/src/scss/editor-panel.scss +2 -3
  40. package/src/scss/main.scss +11 -1
  41. package/src/scss/map.scss +22 -12
  42. package/src/types/MapConfig.ts +149 -0
  43. package/src/types/MapContext.ts +45 -0
  44. package/src/types/runtimeLegend.ts +1 -0
  45. package/examples/world-geocode-data.json +0 -18
  46. package/examples/world-geocode.json +0 -108
  47. package/src/components/Sidebar.tsx +0 -142
  48. package/src/data/abbreviations.js +0 -57
  49. package/src/data/feature-test.json +0 -73
  50. package/src/data/newtest.json +0 -1
  51. package/src/data/state-abbreviations.js +0 -60
  52. package/src/data/supported-cities.csv +0 -165
  53. package/src/data/test.json +0 -1
  54. package/src/data/world-topo.json +0 -1
  55. package/src/scss/sidebar.scss +0 -230
  56. /package/src/{data → components/UsaMap/data}/cb_2019_us_county_20m.json +0 -0
  57. /package/src/{data → components/UsaMap/data}/us-hex-topo.json +0 -0
  58. /package/src/{data → components/UsaMap/data}/us-regions-topo-2.json +0 -0
  59. /package/src/{data → components/UsaMap/data}/us-regions-topo.json +0 -0
  60. /package/src/{data → components/UsaMap/data}/us-topo.json +0 -0
@@ -0,0 +1,59 @@
1
+ import { memo, useState, useEffect } from 'react'
2
+ import { useDebounce } from 'use-debounce'
3
+
4
+ // todo: look into combining these with core
5
+ const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
6
+ <label className='checkbox column-heading'>
7
+ <input
8
+ type='checkbox'
9
+ name={fieldName}
10
+ checked={value}
11
+ onChange={e => {
12
+ updateField(section, subsection, fieldName, !value)
13
+ }}
14
+ {...attributes}
15
+ />
16
+ <span className='edit-label'>
17
+ {label}
18
+ {tooltip}
19
+ </span>
20
+ </label>
21
+ ))
22
+
23
+ const TextField = ({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', tooltip, ...attributes }) => {
24
+ const [value, setValue] = useState(stateValue)
25
+
26
+ const [debouncedValue] = useDebounce(value, 500)
27
+
28
+ useEffect(() => {
29
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
30
+ updateField(section, subsection, fieldName, debouncedValue)
31
+ }
32
+ }, [debouncedValue]) // eslint-disable-line
33
+
34
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
35
+
36
+ const onChange = e => setValue(e.target.value)
37
+
38
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
39
+
40
+ if ('textarea' === type) {
41
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
42
+ }
43
+
44
+ if ('number' === type) {
45
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
46
+ }
47
+
48
+ return (
49
+ <label>
50
+ <span className='edit-label column-heading'>
51
+ {label}
52
+ {tooltip}
53
+ </span>
54
+ {formElement}
55
+ </label>
56
+ )
57
+ }
58
+
59
+ export { CheckBox, TextField }
@@ -0,0 +1,140 @@
1
+ import { useContext } from 'react'
2
+ import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
3
+ import ConfigContext from '../../../context'
4
+ import { type MapContext } from '../../../types/MapContext'
5
+ import Button from '@cdc/core/components/elements/Button'
6
+
7
+ type PanelProps = {
8
+ name: string
9
+ }
10
+
11
+ const PatternSettings = ({ name }: PanelProps) => {
12
+ const { state, setState } = useContext<MapContext>(ConfigContext)
13
+ const defaultPattern = 'circles'
14
+ const patternTypes = ['circles', 'waves', 'lines']
15
+
16
+ const {
17
+ map: { patterns },
18
+ data
19
+ } = state
20
+
21
+ /** Updates the map config with a new pattern item */
22
+ const handleAddGeoPattern = () => {
23
+ let patterns = [...state.map.patterns]
24
+ patterns.push({ dataKey: '', pattern: defaultPattern })
25
+ setState({
26
+ ...state,
27
+ map: {
28
+ ...state.map,
29
+ patterns
30
+ }
31
+ })
32
+ }
33
+
34
+ /** Updates the map pattern at a given index */
35
+ const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label') => {
36
+ const updatedPatterns = [...state.map.patterns]
37
+ updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value }
38
+
39
+ setState({
40
+ ...state,
41
+ map: {
42
+ ...state.map,
43
+ patterns: updatedPatterns
44
+ }
45
+ })
46
+ }
47
+
48
+ const handleRemovePattern = index => {
49
+ const updatedPatterns = state.map.patterns.filter((pattern, i) => i !== index)
50
+
51
+ setState({
52
+ ...state,
53
+ map: {
54
+ ...state.map,
55
+ patterns: updatedPatterns
56
+ }
57
+ })
58
+ }
59
+
60
+ return (
61
+ <AccordionItem>
62
+ <AccordionItemHeading>
63
+ <AccordionItemButton>{name}</AccordionItemButton>
64
+ </AccordionItemHeading>
65
+ <AccordionItemPanel>
66
+ {patterns &&
67
+ patterns.map((pattern, patternIndex) => {
68
+ const dataValueOptions = [...new Set(data?.map(d => d?.[pattern?.dataKey]))]
69
+ const dataKeyOptions = Object.keys(data[0])
70
+ dataValueOptions.unshift('Select')
71
+ dataKeyOptions.unshift('Select')
72
+
73
+ dataValueOptions.sort()
74
+ dataKeyOptions.sort()
75
+
76
+ return (
77
+ <Accordion allowZeroExpanded>
78
+ <AccordionItem>
79
+ <AccordionItemHeading>
80
+ <AccordionItemButton>{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}</AccordionItemButton>
81
+ </AccordionItemHeading>
82
+ <AccordionItemPanel>
83
+ <>
84
+ <label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
85
+ <select id={`pattern-dataKey--${patternIndex}`} value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}>
86
+ {/* TODO: sort these? */}
87
+ {dataKeyOptions.map((d, index) => {
88
+ return (
89
+ <option value={d} key={index}>
90
+ {d}
91
+ </option>
92
+ )
93
+ })}
94
+ </select>
95
+
96
+ <label htmlFor={`pattern-dataValue--${patternIndex}`}>
97
+ Data Value:
98
+ <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')} id={`pattern-dataValue--${patternIndex}`} value={pattern.dataValue === '' ? '' : pattern.dataValue} />
99
+ </label>
100
+
101
+ <label htmlFor={`pattern-label--${patternIndex}`}>
102
+ Label (optional):
103
+ <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')} id={`pattern-dataValue--${patternIndex}`} value={pattern.label === '' ? '' : pattern.label} />
104
+ </label>
105
+
106
+ <label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
107
+ <select id={`pattern-type--${patternIndex}`} value={pattern?.pattern} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}>
108
+ {patternTypes.map((patternName, index) => (
109
+ <option value={patternName} key={index}>
110
+ {patternName}
111
+ </option>
112
+ ))}
113
+ </select>
114
+
115
+ <label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
116
+ <select id={`pattern-size--${patternIndex}`} value={pattern?.size} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}>
117
+ {['small', 'medium', 'large'].map((size, index) => (
118
+ <option value={size} key={index}>
119
+ {size}
120
+ </option>
121
+ ))}
122
+ </select>
123
+ <Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn--remove warn'>
124
+ Remove Pattern
125
+ </Button>
126
+ </>
127
+ </AccordionItemPanel>
128
+ </AccordionItem>
129
+ </Accordion>
130
+ )
131
+ })}
132
+ <button className='btn full-width' onClick={handleAddGeoPattern}>
133
+ Add Geo Pattern
134
+ </button>
135
+ </AccordionItemPanel>
136
+ </AccordionItem>
137
+ )
138
+ }
139
+
140
+ export default PatternSettings
@@ -0,0 +1,7 @@
1
+ import PatternSettings from './Panel.PatternSettings'
2
+
3
+ const Panels = {
4
+ PatternSettings
5
+ }
6
+
7
+ export default Panels
@@ -0,0 +1,3 @@
1
+ import EditorPanel from './components/EditorPanel'
2
+
3
+ export default EditorPanel
@@ -1,9 +1,11 @@
1
1
  import React, { memo } from 'react'
2
2
 
3
3
  const Geo = ({ path, styles, stroke, strokeWidth, ...props }) => {
4
+ const { className, ...restProps } = props
5
+ const geoClassName = String(props.additionalData?.name)?.toLowerCase()?.replaceAll(' ', '') || 'country'
4
6
  return (
5
- <g className='geo-group' css={styles} {...props}>
6
- <path tabIndex={-1} className='single-geo' stroke={stroke} strokeWidth={strokeWidth} d={path} />
7
+ <g className={`geo-group ${geoClassName}`} style={styles} {...restProps}>
8
+ <path tabIndex={-1} className={`single-geo ${geoClassName}`} stroke={stroke} strokeWidth={strokeWidth} d={path} />
7
9
  </g>
8
10
  )
9
11
  }
@@ -0,0 +1,183 @@
1
+ //TODO: Move legends to core
2
+ import { useContext } from 'react'
3
+ import parse from 'html-react-parser'
4
+ import chroma from 'chroma-js'
5
+
6
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
+ import LegendCircle from '@cdc/core/components/LegendCircle'
8
+ import LegendItemHex from './LegendItem.Hex'
9
+
10
+ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
11
+ import ConfigContext from '../../../context'
12
+ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
13
+ import './index.scss'
14
+
15
+ const Legend = () => {
16
+ // prettier-ignore
17
+ const {
18
+ displayDataAsText,
19
+ resetLegendToggles,
20
+ runtimeFilters,
21
+ runtimeLegend,
22
+ setAccessibleStatus,
23
+ setRuntimeLegend,
24
+ state,
25
+ viewport,
26
+ } = useContext(ConfigContext)
27
+
28
+ const { legend } = state
29
+
30
+ // Toggles if a legend is active and being applied to the map and data table.
31
+ const toggleLegendActive = (i, legendLabel) => {
32
+ const newValue = !runtimeLegend[i].disabled
33
+
34
+ runtimeLegend[i].disabled = newValue // Toggle!
35
+
36
+ let newLegend = [...runtimeLegend]
37
+
38
+ newLegend[i].disabled = newValue
39
+
40
+ const disabledAmt = runtimeLegend.disabledAmt ?? 0
41
+
42
+ newLegend['disabledAmt'] = newValue ? disabledAmt + 1 : disabledAmt - 1
43
+
44
+ setRuntimeLegend(newLegend)
45
+
46
+ setAccessibleStatus(`Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`)
47
+ }
48
+
49
+ const legendList = () => {
50
+ let legendItems
51
+
52
+ legendItems = runtimeLegend.map((entry, idx) => {
53
+ const entryMax = displayDataAsText(entry.max, 'primary')
54
+
55
+ const entryMin = displayDataAsText(entry.min, 'primary')
56
+
57
+ let formattedText = `${entryMin}${entryMax !== entryMin ? ` - ${entryMax}` : ''}`
58
+
59
+ // If interval, add some formatting
60
+ if (legend.type === 'equalinterval' && idx !== runtimeLegend.length - 1) {
61
+ formattedText = `${entryMin} - < ${entryMax}`
62
+ }
63
+
64
+ const { disabled } = entry
65
+
66
+ if (legend.type === 'category') {
67
+ formattedText = displayDataAsText(entry.value, 'primary')
68
+ }
69
+
70
+ if (entry.max === 0 && entry.min === 0) {
71
+ formattedText = '0'
72
+ }
73
+
74
+ let legendLabel = formattedText
75
+
76
+ if (entry.hasOwnProperty('special')) {
77
+ legendLabel = entry.label || entry.value
78
+ }
79
+
80
+ const handleListItemClass = () => {
81
+ let classes = ['legend-container__li']
82
+ if (disabled) classes.push('legend-container__li--disabled')
83
+ if (entry.hasOwnProperty('special')) classes.push('legend-container__li--special-class')
84
+ return classes
85
+ }
86
+
87
+ return (
88
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
89
+ <li
90
+ className={handleListItemClass().join(' ')}
91
+ key={idx}
92
+ title={`Legend item ${legendLabel} - Click to disable`}
93
+ onClick={() => {
94
+ toggleLegendActive(idx, legendLabel)
95
+ }}
96
+ >
97
+ <LegendCircle fill={entry.color} /> <span className='label'>{legendLabel}</span>
98
+ </li>
99
+ )
100
+ })
101
+
102
+ if (state.map.patterns) {
103
+ // loop over map patterns
104
+ state.map.patterns.map((patternData, patternDataIndex) => {
105
+ const { pattern, dataKey, size } = patternData
106
+ let defaultPatternColor = 'black'
107
+ const sizes = {
108
+ small: '8',
109
+ medium: '10',
110
+ large: '12'
111
+ }
112
+
113
+ const legendSize = 16
114
+
115
+ legendItems.push(
116
+ <>
117
+ <li className={`legend-container__li legend-container__li--geo-pattern`}>
118
+ <span className='legend-item' style={{ border: 'unset' }}>
119
+ <svg width={legendSize} height={legendSize}>
120
+ {pattern === 'waves' && <PatternWaves id={`${dataKey}--${patternDataIndex}`} height={sizes[size] ?? 10} width={sizes[size] ?? 10} fill={defaultPatternColor} />}
121
+ {pattern === 'circles' && <PatternCircles id={`${dataKey}--${patternDataIndex}`} height={sizes[size] ?? 10} width={sizes[size] ?? 10} fill={defaultPatternColor} />}
122
+ {pattern === 'lines' && <PatternLines id={`${dataKey}--${patternDataIndex}`} height={sizes[size] ?? 6} width={sizes[size] ?? 10} stroke={defaultPatternColor} strokeWidth={2} orientation={['diagonalRightToLeft']} />}
123
+ <circle id={dataKey} fill={`url(#${dataKey}--${patternDataIndex})`} r={legendSize / 2} cx={legendSize / 2} cy={legendSize / 2} stroke='#0000004d' strokeWidth={1} />
124
+ </svg>
125
+ </span>
126
+ <p style={{ lineHeight: '22.4px' }}>{patternData.label || patternData.dataValue || ''}</p>
127
+ </li>
128
+ </>
129
+ )
130
+ })
131
+ }
132
+
133
+ return legendItems
134
+ }
135
+
136
+ const { legendClasses } = useDataVizClasses(state, viewport)
137
+
138
+ const handleReset = e => {
139
+ e.preventDefault()
140
+ resetLegendToggles()
141
+ setAccessibleStatus('Legend has been reset, please reference the data table to see updated values.')
142
+ }
143
+
144
+ return (
145
+ <ErrorBoundary component='Sidebar'>
146
+ <div className='legends'>
147
+ <aside id='legend' className={legendClasses.aside.join(' ') || ''} role='region' aria-label='Legend'>
148
+ <section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
149
+ {runtimeLegend.disabledAmt > 0 && (
150
+ <button onClick={handleReset} className={legendClasses.resetButton.join(' ') || ''}>
151
+ Clear
152
+ </button>
153
+ )}
154
+ {legend.title && <span className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</span>}
155
+ {legend.dynamicDescription === false && legend.description && <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>}
156
+ {legend.dynamicDescription === true &&
157
+ runtimeFilters.map((filter, idx) => {
158
+ const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
159
+
160
+ // Do we have a custom description for this?
161
+ const desc = legend.descriptions[lookupStr] || ''
162
+
163
+ if (desc.length > 0) {
164
+ return (
165
+ <p key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
166
+ {desc}
167
+ </p>
168
+ )
169
+ }
170
+ return true
171
+ })}
172
+ <ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
173
+ {legendList()}
174
+ </ul>
175
+ </section>
176
+ </aside>
177
+ {state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && <LegendItemHex state={state} runtimeLegend={runtimeLegend} viewport={viewport} />}
178
+ </div>
179
+ </ErrorBoundary>
180
+ )
181
+ }
182
+
183
+ export default Legend
@@ -0,0 +1,49 @@
1
+ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
2
+ import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react-icons/ai'
3
+ import parse from 'html-react-parser'
4
+
5
+ const LegendItemHex = props => {
6
+ const { state, viewport } = props
7
+
8
+ const getItemShape = shape => {
9
+ switch (shape) {
10
+ case 'Arrow Down':
11
+ return <AiOutlineArrowDown />
12
+ case 'Arrow Up':
13
+ return <AiOutlineArrowUp />
14
+ case 'Arrow Right':
15
+ return <AiOutlineArrowRight />
16
+ default:
17
+ return
18
+ }
19
+ }
20
+
21
+ const { legendClasses } = useDataVizClasses(state, viewport)
22
+
23
+ // TODO: create core legend for reusability
24
+ return (
25
+ state.hexMap.type === 'shapes' &&
26
+ state.hexMap.shapeGroups.map((shapeGroup, shapeGroupIndex) => {
27
+ return (
28
+ <aside id='legend' className={legendClasses.aside.join(' ')} role='region' aria-label='Legend' tabIndex={0}>
29
+ <section className={legendClasses.section.join(' ')} aria-label='Map Legend'>
30
+ {shapeGroup.legendTitle && <span className={legendClasses.title.join(' ')}>{parse(shapeGroup.legendTitle)}</span>}
31
+ {shapeGroup.legendDescription && <p className={legendClasses.description.join(' ')}>{parse(shapeGroup.legendDescription)}</p>}
32
+
33
+ <ul className={legendClasses.ul.join(' ')} aria-label='Legend items' style={{ listStyle: 'none' }}>
34
+ {shapeGroup.items.map((item, itemIndex) => {
35
+ return (
36
+ <li className={legendClasses.li.join(' ')} key={`hex-legend-item-${itemIndex}`}>
37
+ {getItemShape(item.shape)} {item.value}
38
+ </li>
39
+ )
40
+ })}
41
+ </ul>
42
+ </section>
43
+ </aside>
44
+ )
45
+ })
46
+ )
47
+ }
48
+
49
+ export default LegendItemHex
@@ -0,0 +1,235 @@
1
+ @import '@cdc/core/styles/base';
2
+ @import '@cdc/core/styles/heading-colors';
3
+
4
+ .cdc-map-inner-container {
5
+ .map-container.world aside.side {
6
+ border-top: 0;
7
+ }
8
+ @include breakpointClass(md) {
9
+ .map-container.world aside.side {
10
+ border-top: $lightGray 1px solid;
11
+ position: absolute;
12
+ box-shadow: rgba(0, 0, 0, 0.2) 0 10px 18px;
13
+ }
14
+ }
15
+
16
+ aside {
17
+ background-color: #fff;
18
+ z-index: 6;
19
+ border-top: $lightGray 1px solid;
20
+ @include breakpointClass(md) {
21
+ &.bottom {
22
+ border: $lightGray 1px solid;
23
+ }
24
+ &.side {
25
+ z-index: 1;
26
+ box-sizing: content-box;
27
+ max-width: 450px;
28
+ margin-top: 2em;
29
+ margin-bottom: 2em;
30
+ align-self: flex-start;
31
+ z-index: 4;
32
+ right: 1em;
33
+ border: $lightGray 1px solid;
34
+ top: 2em;
35
+ right: 1em;
36
+
37
+ ul.vertical-sorted {
38
+ column-count: 2;
39
+ column-fill: balance;
40
+ }
41
+
42
+ ul:not(.vertical-sorted) {
43
+ column-count: initial;
44
+ column-fill: initial;
45
+ display: flex;
46
+ flex-direction: row;
47
+ flex-wrap: wrap;
48
+ }
49
+ }
50
+
51
+ &.bottom {
52
+ ul.legend-container__ul.vertical-sorted {
53
+ display: block;
54
+ column-count: 2;
55
+ column-fill: balance;
56
+ }
57
+
58
+ ul.legend-container__ul {
59
+ display: flex;
60
+ flex-direction: row;
61
+ flex-wrap: wrap;
62
+
63
+ li {
64
+ width: 50%;
65
+ }
66
+ }
67
+
68
+ ul.single-row {
69
+ display: block;
70
+ column-count: initial;
71
+ column-fill: auto;
72
+ }
73
+ }
74
+ }
75
+
76
+ .legend-container {
77
+ padding: 1em;
78
+ position: relative;
79
+ .legend-container__title {
80
+ font-size: 1.3em;
81
+ padding-bottom: 0;
82
+ display: inline-block;
83
+ }
84
+ .legend-container__title + p,
85
+ .legend-container__title + ul,
86
+ p + ul,
87
+ p + p {
88
+ padding-top: 1em;
89
+ }
90
+ .legend-container__reset-button {
91
+ font-size: 0.75em;
92
+ color: rgba(0, 0, 0, 0.6);
93
+ position: absolute;
94
+ right: 1em;
95
+ background: rgba(0, 0, 0, 0.1);
96
+ text-transform: uppercase;
97
+ transition: 0.2s all;
98
+ padding: 0.2em 0.5em;
99
+ border: rgba(0, 0, 0, 0.2) 1px solid;
100
+ &:hover {
101
+ text-decoration: none;
102
+ background: rgba(0, 0, 0, 0.15);
103
+ transition: 0.2s all;
104
+ }
105
+ }
106
+ p {
107
+ line-height: 1.4em;
108
+ }
109
+ .legend-container__ul {
110
+ list-style: none;
111
+ padding-top: 1em;
112
+ button {
113
+ font-size: unset;
114
+ background: transparent;
115
+ }
116
+
117
+ &.vertical-sorted {
118
+ flex-direction: column;
119
+ }
120
+
121
+ &:not(.vertical-sorted, .legend-container__ul--single-column) {
122
+ width: 100%;
123
+ @include breakpoint(sm) {
124
+ .legend-container__li {
125
+ width: 50%;
126
+ }
127
+ }
128
+ }
129
+ .legend-container__li {
130
+ flex-shrink: 0;
131
+ display: inline-block;
132
+ padding-right: 1em;
133
+ padding-bottom: 1em;
134
+ vertical-align: middle;
135
+ transition: 0.1s opacity;
136
+ display: flex;
137
+ cursor: pointer;
138
+ flex-grow: 1;
139
+
140
+ &.legend-container__li--disabled {
141
+ opacity: 0.4;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ .bottom .legend-container__ul--single-column:not(.vertical-sorted) {
148
+ display: inline-block;
149
+
150
+ @include breakpoint(md) {
151
+ display: flex;
152
+ }
153
+
154
+ .legend-container__li {
155
+ width: 100%;
156
+ }
157
+ }
158
+
159
+ &.side .legend-container .legend-container__ul--single-column {
160
+ @include breakpointClass(md) {
161
+ width: 25%;
162
+ min-width: 200px;
163
+ .legend-section ul {
164
+ flex-direction: column;
165
+ li {
166
+ width: 100%;
167
+ &:nth-last-of-type(-n + 2) {
168
+ padding-bottom: 1em;
169
+ }
170
+ &:last-child {
171
+ padding-bottom: 0;
172
+ }
173
+ }
174
+ }
175
+ }
176
+ li {
177
+ width: 100%;
178
+ }
179
+ }
180
+
181
+ &.bottom.single-row {
182
+ width: 100%;
183
+ .legend-container ul {
184
+ flex-direction: row;
185
+ align-items: baseline;
186
+ justify-content: flex-start;
187
+ flex-wrap: wrap;
188
+ li {
189
+ justify-items: center;
190
+ line-break: loose;
191
+ align-items: center;
192
+ width: auto;
193
+ padding-right: 1em;
194
+ padding-bottom: 1em;
195
+ display: inline-block;
196
+ & > span {
197
+ margin: 0 !important;
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ @include breakpointClass(sm) {
204
+ .legend-container ul {
205
+ align-items: flex-start;
206
+ justify-content: space-between;
207
+ li {
208
+ flex-grow: 1;
209
+ padding-right: 0.5em;
210
+ }
211
+ }
212
+ }
213
+
214
+ .filters-section {
215
+ padding: 0 1em 1em;
216
+ .heading-3 {
217
+ font-weight: bold;
218
+ margin-bottom: 0.5em;
219
+ }
220
+ form {
221
+ margin-top: 0.5em;
222
+ line-height: 2em;
223
+ display: flex;
224
+ align-items: flex-end;
225
+ section + section {
226
+ margin-left: 0.75em;
227
+ }
228
+ select {
229
+ display: block;
230
+ font-size: 1em;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }