@cdc/core 4.25.3 → 4.25.6-1
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/assets/icon-close.svg +1 -1
- package/components/Alert/components/Alert.tsx +1 -1
- package/components/DataTable/DataTable.tsx +18 -16
- package/components/DataTable/DataTableStandAlone.tsx +15 -9
- package/components/DataTable/components/CellAnchor.tsx +1 -1
- package/components/DataTable/components/ChartHeader.tsx +8 -5
- package/components/DataTable/components/DataTableEditorPanel.tsx +25 -3
- package/components/DataTable/components/MapHeader.tsx +1 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +14 -10
- package/components/DataTable/helpers/getChartCellValue.ts +42 -26
- package/components/DataTable/helpers/mapCellMatrix.tsx +25 -7
- package/components/DownloadButton.tsx +17 -2
- package/components/EditorPanel/DataTableEditor.tsx +1 -1
- package/components/EditorPanel/FootnotesEditor.tsx +76 -22
- package/components/EditorPanel/Inputs.tsx +12 -4
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +3 -2
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +51 -35
- package/components/Filters/Filters.tsx +158 -461
- package/components/Filters/components/Dropdown.tsx +39 -0
- package/components/Filters/components/Tabs.tsx +82 -0
- package/components/Filters/helpers/getChangedFilters.ts +31 -0
- package/components/Filters/helpers/getNestedOptions.ts +2 -2
- package/components/Filters/helpers/handleSorting.ts +2 -2
- package/components/Filters/helpers/tests/getChangedFilters.test.ts +92 -0
- package/components/Filters/helpers/tests/getNestedOptions.test.ts +31 -0
- package/components/Filters/index.ts +1 -1
- package/components/Footnotes/Footnotes.tsx +1 -1
- package/components/Footnotes/FootnotesStandAlone.tsx +8 -33
- package/components/Layout/components/Visualization/index.tsx +4 -3
- package/components/Legend/Legend.Gradient.tsx +68 -24
- package/components/MultiSelect/MultiSelect.tsx +3 -6
- package/components/MultiSelect/multiselect.styles.css +2 -0
- package/components/NestedDropdown/NestedDropdown.tsx +21 -21
- package/components/RichTooltip/RichTooltip.tsx +37 -0
- package/components/RichTooltip/richTooltip.css +16 -0
- package/components/Table/Table.tsx +142 -142
- package/components/Table/components/Row.tsx +1 -1
- package/components/Table/table.styles.css +10 -0
- package/components/_stories/DataTable.stories.tsx +9 -2
- package/components/_stories/Table.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -4
- package/components/ui/Accordion.jsx +8 -1
- package/components/ui/Title/index.tsx +4 -1
- package/components/ui/Title/{Title.scss → title.styles.css} +0 -2
- package/components/ui/_stories/Colors.stories.mdx +220 -0
- package/components/ui/_stories/IconGallery.stories.mdx +14 -0
- package/components/ui/_stories/Title.stories.tsx +29 -4
- package/components/ui/accordion.styles.css +3 -0
- package/data/colorPalettes.js +0 -1
- package/dist/cove-main.css +3 -8
- package/dist/cove-main.css.map +1 -1
- package/helpers/constants.ts +6 -0
- package/helpers/cove/accessibility.ts +7 -8
- package/helpers/cove/number.ts +5 -3
- package/helpers/coveUpdateWorker.ts +9 -1
- package/helpers/filterOrderOptions.ts +17 -0
- package/helpers/formatConfigBeforeSave.ts +19 -32
- package/helpers/isNumber.ts +20 -0
- package/helpers/isRightAlignedTableValue.js +1 -1
- package/helpers/pivotData.ts +16 -11
- package/helpers/tests/pivotData.test.ts +74 -0
- package/helpers/updateFieldFactory.ts +1 -0
- package/helpers/ver/4.25.3.ts +25 -2
- package/helpers/ver/4.25.4.ts +110 -0
- package/helpers/ver/4.25.6.ts +36 -0
- package/helpers/ver/4.25.7.ts +26 -0
- package/helpers/ver/tests/4.25.4.test.ts +89 -0
- package/helpers/ver/tests/4.25.6.test.ts +84 -0
- package/helpers/viewports.ts +4 -0
- package/package.json +7 -6
- package/styles/_global-variables.scss +3 -0
- package/styles/_global.scss +0 -4
- package/styles/_reset.scss +0 -6
- package/styles/filters.scss +0 -4
- package/styles/v2/main.scss +0 -5
- package/types/Axis.ts +2 -0
- package/types/DataSet.ts +14 -0
- package/types/Footnotes.ts +5 -2
- package/types/General.ts +1 -0
- package/types/Legend.ts +1 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +3 -12
- package/types/VizFilter.ts +3 -0
- package/components/ui/_stories/Colors.stories.tsx +0 -92
- package/components/ui/_stories/Icon.stories.tsx +0 -29
- package/helpers/cove/fontSettings.ts +0 -2
- package/helpers/isNumber.js +0 -24
- package/helpers/isNumberLog.js +0 -18
- /package/helpers/{fetchRemoteData.js → fetchRemoteData.ts} +0 -0
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo, useRef, useId } from 'react'
|
|
2
2
|
import _ from 'lodash'
|
|
3
|
+
import parse from 'html-react-parser'
|
|
3
4
|
|
|
4
5
|
// CDC
|
|
5
6
|
import Button from '../elements/Button'
|
|
6
|
-
import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
|
|
7
7
|
import MultiSelect from '../MultiSelect'
|
|
8
8
|
import { Visualization } from '../../types/Visualization'
|
|
9
|
-
import { MultiSelectFilter,
|
|
10
|
-
import { filterVizData } from '../../helpers/filterVizData'
|
|
9
|
+
import { MultiSelectFilter, VizFilter } from '../../types/VizFilter'
|
|
11
10
|
import { addValuesToFilters } from '../../helpers/addValuesToFilters'
|
|
12
11
|
import { DimensionsType } from '../../types/Dimensions'
|
|
13
12
|
import NestedDropdown from '../NestedDropdown'
|
|
14
13
|
import { getNestedOptions } from './helpers/getNestedOptions'
|
|
15
|
-
import { applyQueuedActive } from './helpers/applyQueuedActive'
|
|
16
|
-
import { handleSorting } from './helpers/handleSorting'
|
|
17
14
|
import { getWrappingStatuses } from './helpers/filterWrapping'
|
|
15
|
+
import { handleSorting } from './helpers/handleSorting'
|
|
16
|
+
import { getChangedFilters } from './helpers/getChangedFilters'
|
|
17
|
+
import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
|
|
18
|
+
import { applyQueuedActive } from './helpers/applyQueuedActive'
|
|
19
|
+
import Tabs from './components/Tabs'
|
|
20
|
+
import Dropdown from './components/Dropdown'
|
|
18
21
|
|
|
19
22
|
export const VIZ_FILTER_STYLE = {
|
|
20
23
|
dropdown: 'dropdown',
|
|
@@ -26,169 +29,66 @@ export const VIZ_FILTER_STYLE = {
|
|
|
26
29
|
multiSelect: 'multi-select'
|
|
27
30
|
} as const
|
|
28
31
|
|
|
29
|
-
export const DROPDOWN_STYLES = 'py-2 ps-2 w-100 d-block'
|
|
30
|
-
|
|
31
32
|
export type VizFilterStyle = (typeof VIZ_FILTER_STYLE)[keyof typeof VIZ_FILTER_STYLE]
|
|
32
33
|
|
|
33
34
|
export const filterStyleOptions = Object.values(VIZ_FILTER_STYLE)
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
label: 'Descending Alphanumeric',
|
|
42
|
-
value: 'desc'
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
label: 'Custom',
|
|
46
|
-
value: 'cust'
|
|
47
|
-
},
|
|
48
|
-
{ label: 'Order By Data Column', value: 'column' }
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
export const useFilters = props => {
|
|
52
|
-
const [showApplyButton, setShowApplyButton] = useState(false)
|
|
36
|
+
const BUTTON_TEXT = {
|
|
37
|
+
apply: 'Apply',
|
|
38
|
+
resetText: 'Clear Filters'
|
|
39
|
+
}
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
getUniqueValues,
|
|
63
|
-
standaloneMap
|
|
64
|
-
} = props
|
|
65
|
-
const { data } = visualizationConfig
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Re-orders a filter based on two indices and updates the runtime filters array and filters state
|
|
69
|
-
* @param {number} idx1 - The index of the original position of the filter value.
|
|
70
|
-
* @param {number} idx2 - The index of the new position for the filter value.
|
|
71
|
-
* @param {number} filterIndex - The index of the filter item within the array of filter items.
|
|
72
|
-
* @param {object} filter - The filter item itself, which contains an array of filter values.
|
|
73
|
-
* @return {void} None. This function only updates the state of the component.
|
|
74
|
-
*
|
|
75
|
-
* @modifies {object} - The filter object passed in as a parameter
|
|
76
|
-
* @modifies {array} - The filteredData state if visualizationConfig.type equals 'map'
|
|
77
|
-
* @modifies {object} - The visualizationConfig state
|
|
78
|
-
*/
|
|
79
|
-
const handleFilterOrder = (idx1, idx2, filterIndex, filter) => {
|
|
80
|
-
// Create a shallow copy of the filter values array & update position of the values
|
|
81
|
-
const updatedValues = [...filter.values]
|
|
82
|
-
const [movedItem] = updatedValues.splice(idx1, 1)
|
|
83
|
-
updatedValues.splice(idx2, 0, movedItem)
|
|
84
|
-
|
|
85
|
-
const filtersCopy = !standaloneMap ? [...visualizationConfig.filters] : [...filteredData]
|
|
86
|
-
const filterItem = { ...filtersCopy[filterIndex] }
|
|
87
|
-
|
|
88
|
-
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
89
|
-
filterItem.values = updatedValues
|
|
90
|
-
filterItem.orderedValues = updatedValues
|
|
91
|
-
if (!filterItem.active) filterItem.active = filterItem.defaultValue ? filterItem.defaultValue : updatedValues[0]
|
|
92
|
-
filterItem.order = 'cust'
|
|
93
|
-
|
|
94
|
-
// Update the filters
|
|
95
|
-
filtersCopy[filterIndex] = filterItem
|
|
96
|
-
|
|
97
|
-
if (standaloneMap) {
|
|
98
|
-
setFilteredData(filtersCopy)
|
|
99
|
-
}
|
|
41
|
+
type FilterProps = {
|
|
42
|
+
dimensions?: DimensionsType
|
|
43
|
+
config: Visualization
|
|
44
|
+
setFilters: Function
|
|
45
|
+
standaloneMap?: boolean
|
|
46
|
+
excludedData?: Object[]
|
|
47
|
+
getUniqueValues?: Function
|
|
48
|
+
}
|
|
100
49
|
|
|
101
|
-
|
|
102
|
-
|
|
50
|
+
const Filters: React.FC<FilterProps> = ({
|
|
51
|
+
config: visualizationConfig,
|
|
52
|
+
dimensions,
|
|
53
|
+
standaloneMap,
|
|
54
|
+
setFilters,
|
|
55
|
+
excludedData,
|
|
56
|
+
getUniqueValues
|
|
57
|
+
}) => {
|
|
58
|
+
const { filters, general, theme, filterBehavior } = visualizationConfig
|
|
59
|
+
const [showApplyButton, setShowApplyButton] = useState(false)
|
|
60
|
+
// Handle Wrapping Filters
|
|
61
|
+
const [wrappingFilters, setWrappingFilters] = useState<
|
|
62
|
+
Record<string, { highestWrappedWidth: number; isDropdown: boolean }>
|
|
63
|
+
>({})
|
|
103
64
|
|
|
104
|
-
const
|
|
105
|
-
let newFilters = standaloneMap ? [...filteredData] : [...visualizationConfig.filters]
|
|
106
|
-
|
|
107
|
-
const newFilter = newFilters[index]
|
|
108
|
-
if (visualizationConfig.filterBehavior === 'Apply Button') {
|
|
109
|
-
newFilter.queuedActive = value
|
|
110
|
-
setShowApplyButton(true)
|
|
111
|
-
} else {
|
|
112
|
-
if (newFilter.filterStyle !== 'nested-dropdown') {
|
|
113
|
-
newFilter.active = value
|
|
114
|
-
} else {
|
|
115
|
-
newFilter.active = value[0]
|
|
116
|
-
newFilter.subGrouping.active = value[1]
|
|
117
|
-
}
|
|
65
|
+
const wrappingFilterRefs = useRef({})
|
|
118
66
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
queryParams[newFilter.setByQueryParameter] = newFilter.active
|
|
122
|
-
updateQueryString(queryParams)
|
|
123
|
-
}
|
|
124
|
-
if (
|
|
125
|
-
newFilter?.subGrouping?.setByQueryParameter &&
|
|
126
|
-
queryParams[newFilter?.subGrouping?.setByQueryParameter] !== newFilter?.subGrouping.active
|
|
127
|
-
) {
|
|
128
|
-
queryParams[newFilter?.subGrouping?.setByQueryParameter] = newFilter.subGrouping.active
|
|
129
|
-
updateQueryString(queryParams)
|
|
130
|
-
}
|
|
131
|
-
setFilteredData(newFilters)
|
|
132
|
-
}
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const filterWrappingStatusesToUpdate = getWrappingStatuses(wrappingFilterRefs, wrappingFilters, filters)
|
|
133
69
|
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
...visualizationConfig,
|
|
138
|
-
filters: newFilters
|
|
139
|
-
})
|
|
70
|
+
if (filterWrappingStatusesToUpdate.length) {
|
|
71
|
+
const validStatuses = filterWrappingStatusesToUpdate.filter(Boolean) as [string, any][]
|
|
72
|
+
setWrappingFilters({ ...wrappingFilters, ...Object.fromEntries(validStatuses) })
|
|
140
73
|
}
|
|
74
|
+
}, [filters, dimensions?.[0]])
|
|
75
|
+
// end of Handle Wrapping Filters
|
|
141
76
|
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
77
|
+
const initialActiveFilters = useMemo(() => {
|
|
78
|
+
//if (!filteredData) return []
|
|
79
|
+
return filters.map(filter => filter.active)
|
|
80
|
+
}, [])
|
|
146
81
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (visualizationConfig.dynamicSeries) {
|
|
153
|
-
const runtime = visualizationConfig.runtime || {}
|
|
154
|
-
runtime.series = []
|
|
155
|
-
runtime.seriesLabels = {}
|
|
156
|
-
runtime.seriesLabelsAll = []
|
|
157
|
-
|
|
158
|
-
if (newFilteredData && newFilteredData.length && newFilteredData.length > 0) {
|
|
159
|
-
Object.keys(newFilteredData[0]).forEach(seriesKey => {
|
|
160
|
-
if (
|
|
161
|
-
seriesKey !== visualizationConfig.xAxis.dataKey &&
|
|
162
|
-
newFilteredData[0][seriesKey] &&
|
|
163
|
-
(!visualizationConfig.filters ||
|
|
164
|
-
visualizationConfig.filters?.filter(filter => filter.columnName === seriesKey).length === 0) &&
|
|
165
|
-
(!visualizationConfig.columns || Object.keys(visualizationConfig.columns).indexOf(seriesKey) === -1)
|
|
166
|
-
) {
|
|
167
|
-
runtime.series.push({
|
|
168
|
-
dataKey: seriesKey,
|
|
169
|
-
type: visualizationConfig.dynamicSeriesType,
|
|
170
|
-
lineType: visualizationConfig.dynamicSeriesLineType,
|
|
171
|
-
tooltip: true
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
}
|
|
82
|
+
const initialFiltersActive = useMemo(() => {
|
|
83
|
+
const activeFilters = filters.map(filter => filter.active)
|
|
84
|
+
return initialActiveFilters.every(filter => activeFilters.includes(filter))
|
|
85
|
+
}, [filters])
|
|
176
86
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
})
|
|
183
|
-
: []
|
|
184
|
-
|
|
185
|
-
setConfig({
|
|
186
|
-
...visualizationConfig,
|
|
187
|
-
filters: newFilters,
|
|
188
|
-
runtime
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
}
|
|
87
|
+
const changeFilterActive = (index, value) => {
|
|
88
|
+
if (filterBehavior === 'Apply Button') setShowApplyButton(true)
|
|
89
|
+
|
|
90
|
+
const newFilters = getChangedFilters([...filters], index, value, filterBehavior)
|
|
91
|
+
setFilters(newFilters)
|
|
192
92
|
}
|
|
193
93
|
|
|
194
94
|
const handleApplyButton = newFilters => {
|
|
@@ -207,19 +107,13 @@ export const useFilters = props => {
|
|
|
207
107
|
updateQueryString(queryParams)
|
|
208
108
|
}
|
|
209
109
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (standaloneMap) {
|
|
213
|
-
setFilteredData(newFilters, excludedData)
|
|
214
|
-
} else {
|
|
215
|
-
setFilteredData(filterVizData(newFilters, excludedData))
|
|
216
|
-
}
|
|
110
|
+
setFilters(newFilters)
|
|
217
111
|
|
|
218
112
|
setShowApplyButton(false)
|
|
219
113
|
}
|
|
220
114
|
|
|
221
115
|
const handleReset = e => {
|
|
222
|
-
let newFilters = [...
|
|
116
|
+
let newFilters = [...filters]
|
|
223
117
|
e.preventDefault()
|
|
224
118
|
|
|
225
119
|
// reset to first item in values array.
|
|
@@ -227,7 +121,7 @@ export const useFilters = props => {
|
|
|
227
121
|
const queryParams = getQueryParams()
|
|
228
122
|
newFilters.forEach((filter, i) => {
|
|
229
123
|
if (!filter.values || filter.values.length === 0) {
|
|
230
|
-
filter.values = getUniqueValues(data, filter.columnName)
|
|
124
|
+
filter.values = getUniqueValues(visualizationConfig.data, filter.columnName)
|
|
231
125
|
}
|
|
232
126
|
|
|
233
127
|
newFilters[i].active = handleSorting(filter).values[0]
|
|
@@ -242,308 +136,26 @@ export const useFilters = props => {
|
|
|
242
136
|
updateQueryString(queryParams)
|
|
243
137
|
}
|
|
244
138
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (standaloneMap) {
|
|
248
|
-
setFilteredData(newFilters, excludedData)
|
|
249
|
-
} else {
|
|
250
|
-
setFilteredData(filterVizData(newFilters, excludedData))
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const filterConstants = {
|
|
255
|
-
buttonText: 'Apply',
|
|
256
|
-
resetText: 'Clear Filters'
|
|
139
|
+
setFilters(newFilters)
|
|
257
140
|
}
|
|
258
141
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
handleApplyButton,
|
|
262
|
-
changeFilterActive,
|
|
263
|
-
showApplyButton,
|
|
264
|
-
handleReset,
|
|
265
|
-
filterConstants,
|
|
266
|
-
filterStyleOptions,
|
|
267
|
-
filterOrderOptions,
|
|
268
|
-
handleFilterOrder,
|
|
269
|
-
handleSorting
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
type FilterProps = {
|
|
274
|
-
filteredData: Object[]
|
|
275
|
-
dimensions: DimensionsType
|
|
276
|
-
config: Visualization
|
|
277
|
-
// function for updating the runtime filters
|
|
278
|
-
setFilteredData: Function
|
|
279
|
-
// updating function for setting fitlerBehavior
|
|
280
|
-
setConfig: Function
|
|
281
|
-
standaloneMap?: boolean
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const Filters = (props: FilterProps) => {
|
|
285
|
-
const { config: visualizationConfig, filteredData, dimensions, standaloneMap } = props
|
|
286
|
-
const { filters, general, theme, filterBehavior } = visualizationConfig
|
|
287
|
-
const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
|
|
288
|
-
const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
|
|
289
|
-
const [wrappingFilters, setWrappingFilters] = useState({})
|
|
290
|
-
const [initialActiveFilters, setInitialActiveFilters] = useState([])
|
|
291
|
-
|
|
292
|
-
useEffect(() => {
|
|
293
|
-
if (!filteredData) return
|
|
294
|
-
|
|
295
|
-
setInitialActiveFilters(filters.map(filter => filter.active))
|
|
296
|
-
}, [])
|
|
297
|
-
|
|
298
|
-
const activeFilters = filters.map(filter => filter.active)
|
|
299
|
-
const initialFiltersActive = initialActiveFilters.every((filter, i) => activeFilters.includes(filter))
|
|
300
|
-
const id = useId()
|
|
301
|
-
|
|
302
|
-
const wrappingFilterRefs = useRef({})
|
|
303
|
-
const filterWrappingStatusesToUpdate = getWrappingStatuses(wrappingFilterRefs, wrappingFilters, filters)
|
|
304
|
-
|
|
305
|
-
if (filterWrappingStatusesToUpdate.length) {
|
|
306
|
-
const validStatuses = filterWrappingStatusesToUpdate.filter(Boolean) as [string, any][]
|
|
307
|
-
setWrappingFilters({ ...wrappingFilters, ...Object.fromEntries(validStatuses) })
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// useFilters hook provides data and logic for handling various filter functions
|
|
311
|
-
// prettier-ignore
|
|
312
|
-
const {
|
|
313
|
-
handleApplyButton,
|
|
314
|
-
changeFilterActive,
|
|
315
|
-
showApplyButton,
|
|
316
|
-
handleReset,
|
|
317
|
-
filterConstants,
|
|
318
|
-
handleSorting
|
|
319
|
-
} = useFilters(props)
|
|
320
|
-
|
|
321
|
-
useEffect(() => {
|
|
322
|
-
if (!dimensions) return
|
|
142
|
+
const mobileFilterStyle = useMemo(() => {
|
|
143
|
+
if (!dimensions) return false
|
|
323
144
|
const [width] = dimensions
|
|
324
|
-
|
|
325
145
|
const isMobile = Number(width) < 768
|
|
326
146
|
const isTabSimple = filters?.some(filter => filter.filterStyle === VIZ_FILTER_STYLE.tabSimple)
|
|
327
147
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
setMobileFilterStyle(defaultToMobile)
|
|
331
|
-
}, [dimensions])
|
|
332
|
-
|
|
333
|
-
useEffect(() => {
|
|
334
|
-
const noLongerTabSimple = Object.keys(wrappingFilters).filter(columnName => {
|
|
335
|
-
const filter = filters.find(filter => filter.columnName === columnName)
|
|
336
|
-
if (!filter) return false
|
|
337
|
-
return filter.filterStyle !== VIZ_FILTER_STYLE.tabSimple
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
if (!noLongerTabSimple.length) return
|
|
341
|
-
|
|
342
|
-
setWrappingFilters(
|
|
343
|
-
Object.fromEntries(
|
|
344
|
-
Object.entries(wrappingFilters).filter(([columnName]) => !noLongerTabSimple.includes(columnName))
|
|
345
|
-
)
|
|
346
|
-
)
|
|
347
|
-
}, [filters])
|
|
348
|
-
|
|
349
|
-
useEffect(() => {
|
|
350
|
-
if (selectedFilter) {
|
|
351
|
-
const el = document.getElementById(selectedFilter.id)
|
|
352
|
-
if (el) el.focus()
|
|
353
|
-
}
|
|
354
|
-
}, [changeFilterActive, selectedFilter])
|
|
355
|
-
|
|
356
|
-
const TabBar = props => {
|
|
357
|
-
const { filter: singleFilter, index: outerIndex } = props
|
|
358
|
-
return (
|
|
359
|
-
<section className='single-filters__tab-bar'>
|
|
360
|
-
{singleFilter.values.map((filter, index) => {
|
|
361
|
-
const buttonClassList = ['button__tab-bar', singleFilter.active === filter ? 'button__tab-bar--active' : '']
|
|
362
|
-
return (
|
|
363
|
-
<button
|
|
364
|
-
id={`${filter}-${outerIndex}-${index}-${id}`}
|
|
365
|
-
className={buttonClassList.join(' ')}
|
|
366
|
-
key={filter}
|
|
367
|
-
onClick={e => {
|
|
368
|
-
changeFilterActive(outerIndex, filter)
|
|
369
|
-
setSelectedFilter(e.target)
|
|
370
|
-
}}
|
|
371
|
-
onKeyDown={e => {
|
|
372
|
-
if (e.keyCode === 13) {
|
|
373
|
-
changeFilterActive(outerIndex, filter)
|
|
374
|
-
setSelectedFilter(e.target)
|
|
375
|
-
}
|
|
376
|
-
}}
|
|
377
|
-
>
|
|
378
|
-
{filter}
|
|
379
|
-
</button>
|
|
380
|
-
)
|
|
381
|
-
})}
|
|
382
|
-
</section>
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const Dropdown = props => {
|
|
387
|
-
const { index: outerIndex, label, active, filters } = props
|
|
388
|
-
return (
|
|
389
|
-
<select
|
|
390
|
-
id={`filter-${outerIndex}`}
|
|
391
|
-
name={label}
|
|
392
|
-
aria-label={`Filter by ${label}`}
|
|
393
|
-
className={`cove-form-select ${DROPDOWN_STYLES}`}
|
|
394
|
-
data-index='0'
|
|
395
|
-
value={active}
|
|
396
|
-
onChange={e => {
|
|
397
|
-
changeFilterActive(outerIndex, e.target.value)
|
|
398
|
-
}}
|
|
399
|
-
>
|
|
400
|
-
{filters}
|
|
401
|
-
</select>
|
|
402
|
-
)
|
|
403
|
-
}
|
|
148
|
+
return isMobile && filters?.length && !isTabSimple
|
|
149
|
+
}, [dimensions?.[0]])
|
|
404
150
|
|
|
405
151
|
const vizFiltersWithValues = useMemo(() => {
|
|
406
152
|
// Here charts is using config.filters where maps is using a runtime value
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}, [filters, filteredData])
|
|
412
|
-
|
|
413
|
-
// Resolve Filter Styles
|
|
414
|
-
const Style = () => {
|
|
415
|
-
return vizFiltersWithValues.map((singleFilter: VizFilter, outerIndex) => {
|
|
416
|
-
if (singleFilter.showDropdown === false) return
|
|
417
|
-
|
|
418
|
-
const DropdownOptions = []
|
|
419
|
-
const Pills = []
|
|
420
|
-
const Tabs = []
|
|
421
|
-
const isTabSimple = singleFilter.filterStyle === 'tab-simple'
|
|
422
|
-
|
|
423
|
-
const { active, queuedActive, label, filterStyle, columnName } = singleFilter as VizFilter
|
|
424
|
-
const { isDropdown } = wrappingFilters[columnName] || {}
|
|
425
|
-
|
|
426
|
-
handleSorting(singleFilter)
|
|
427
|
-
singleFilter.values?.forEach((filterOption, index) => {
|
|
428
|
-
const isActive = active === filterOption
|
|
429
|
-
|
|
430
|
-
const pillClassList = ['pill', isActive ? 'pill--active' : null, theme && theme]
|
|
431
|
-
const tabClassList = ['tab', isActive && 'tab--active', theme && theme, isTabSimple && 'tab--simple']
|
|
432
|
-
|
|
433
|
-
Pills.push(
|
|
434
|
-
<div className='pill__wrapper' key={`pill-${index}`}>
|
|
435
|
-
<button
|
|
436
|
-
id={`${filterOption}-${outerIndex}-${index}-${id}`}
|
|
437
|
-
className={pillClassList.join(' ')}
|
|
438
|
-
onKeyDown={e => {
|
|
439
|
-
if (e.keyCode === 13) {
|
|
440
|
-
changeFilterActive(outerIndex, filterOption)
|
|
441
|
-
setSelectedFilter(e.target)
|
|
442
|
-
}
|
|
443
|
-
}}
|
|
444
|
-
onClick={e => {
|
|
445
|
-
changeFilterActive(outerIndex, filterOption)
|
|
446
|
-
setSelectedFilter(e.target)
|
|
447
|
-
}}
|
|
448
|
-
name={label}
|
|
449
|
-
>
|
|
450
|
-
{filterOption}
|
|
451
|
-
</button>
|
|
452
|
-
</div>
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
DropdownOptions.push(
|
|
456
|
-
<option key={index} value={filterOption} aria-label={filterOption}>
|
|
457
|
-
{singleFilter.labels && singleFilter.labels[filterOption]
|
|
458
|
-
? singleFilter.labels[filterOption]
|
|
459
|
-
: filterOption}
|
|
460
|
-
</option>
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
Tabs.push(
|
|
464
|
-
<button
|
|
465
|
-
id={`${filterOption}-${outerIndex}-${index}-${id}`}
|
|
466
|
-
className={tabClassList.join(' ')}
|
|
467
|
-
onClick={e => {
|
|
468
|
-
changeFilterActive(outerIndex, filterOption)
|
|
469
|
-
setSelectedFilter(e.target)
|
|
470
|
-
}}
|
|
471
|
-
onKeyDown={e => {
|
|
472
|
-
if (e.keyCode === 13) {
|
|
473
|
-
changeFilterActive(outerIndex, filterOption)
|
|
474
|
-
setSelectedFilter(e.target)
|
|
475
|
-
}
|
|
476
|
-
}}
|
|
477
|
-
>
|
|
478
|
-
{filterOption}
|
|
479
|
-
</button>
|
|
480
|
-
)
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
const classList = [
|
|
484
|
-
'single-filters',
|
|
485
|
-
'form-group',
|
|
486
|
-
mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
|
|
487
|
-
]
|
|
488
|
-
const mobileExempt = ['nested-dropdown', 'multi-select', VIZ_FILTER_STYLE.tabSimple].includes(filterStyle)
|
|
489
|
-
const showDefaultDropdown = ((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
|
|
490
|
-
const [nestedActiveGroup, nestedActiveSubGroup] = useMemo<string[]>(() => {
|
|
491
|
-
if (filterStyle !== 'nested-dropdown') return []
|
|
492
|
-
return (singleFilter.queuedActive || [singleFilter.active, singleFilter.subGrouping?.active]) as [
|
|
493
|
-
string,
|
|
494
|
-
string
|
|
495
|
-
]
|
|
496
|
-
}, [singleFilter])
|
|
497
|
-
const hideLabelMargin = isTabSimple && !showDefaultDropdown
|
|
498
|
-
return (
|
|
499
|
-
<div className={classList.join(' ')} key={outerIndex} ref={el => (wrappingFilterRefs.current[columnName] = el)}>
|
|
500
|
-
<>
|
|
501
|
-
{label && (
|
|
502
|
-
<label className={`font-weight-bold mb-${hideLabelMargin ? '0' : '2'}`} htmlFor={`filter-${outerIndex}`}>
|
|
503
|
-
{label}
|
|
504
|
-
</label>
|
|
505
|
-
)}
|
|
506
|
-
{filterStyle === 'tab' && !mobileFilterStyle && Tabs}
|
|
507
|
-
{filterStyle === 'tab-simple' && !showDefaultDropdown && (
|
|
508
|
-
<div className='tab-simple-container d-flex w-100'>{Tabs}</div>
|
|
509
|
-
)}
|
|
510
|
-
{filterStyle === 'pill' && !mobileFilterStyle && Pills}
|
|
511
|
-
{filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
|
|
512
|
-
{filterStyle === 'multi-select' && (
|
|
513
|
-
<MultiSelect
|
|
514
|
-
options={singleFilter.values.map(v => ({ value: v, label: v }))}
|
|
515
|
-
fieldName={outerIndex}
|
|
516
|
-
updateField={(_section, _subSection, fieldName, value) => changeFilterActive(fieldName, value)}
|
|
517
|
-
selected={singleFilter.active as string[]}
|
|
518
|
-
limit={(singleFilter as MultiSelectFilter).selectLimit || 5}
|
|
519
|
-
/>
|
|
520
|
-
)}
|
|
521
|
-
{filterStyle === 'nested-dropdown' && (
|
|
522
|
-
<NestedDropdown
|
|
523
|
-
activeGroup={nestedActiveGroup}
|
|
524
|
-
activeSubGroup={nestedActiveSubGroup}
|
|
525
|
-
filterIndex={outerIndex}
|
|
526
|
-
options={getNestedOptions(singleFilter)}
|
|
527
|
-
listLabel={label}
|
|
528
|
-
handleSelectedItems={value => changeFilterActive(outerIndex, value)}
|
|
529
|
-
/>
|
|
530
|
-
)}
|
|
531
|
-
{showDefaultDropdown && (
|
|
532
|
-
<Dropdown
|
|
533
|
-
filter={singleFilter}
|
|
534
|
-
index={outerIndex}
|
|
535
|
-
label={label}
|
|
536
|
-
active={queuedActive || active}
|
|
537
|
-
filters={DropdownOptions}
|
|
538
|
-
/>
|
|
539
|
-
)}
|
|
540
|
-
</>
|
|
541
|
-
</div>
|
|
542
|
-
)
|
|
543
|
-
})
|
|
544
|
-
}
|
|
153
|
+
if (!filters) return []
|
|
154
|
+
if (filters.fromHash) delete filters.fromHash // support for Maps config
|
|
155
|
+
return addValuesToFilters(filters as VizFilter[], visualizationConfig.data)
|
|
156
|
+
}, [filters])
|
|
545
157
|
|
|
546
|
-
if (visualizationConfig?.filters?.length === 0) return
|
|
158
|
+
if (visualizationConfig?.filters?.length === 0) return <></>
|
|
547
159
|
|
|
548
160
|
const getClasses = () => {
|
|
549
161
|
const { visualizationType, legend } = visualizationConfig || {}
|
|
@@ -554,15 +166,100 @@ const Filters = (props: FilterProps) => {
|
|
|
554
166
|
return [baseClass, conditionalClass, legendClass, 'w-100'].filter(Boolean)
|
|
555
167
|
}
|
|
556
168
|
|
|
169
|
+
const getNestedGroup = (singleFilter: VizFilter): string[] => {
|
|
170
|
+
if (singleFilter.filterStyle !== 'nested-dropdown') return []
|
|
171
|
+
return (singleFilter.queuedActive || [singleFilter.active, singleFilter.subGrouping?.active]) as [string, string]
|
|
172
|
+
}
|
|
173
|
+
|
|
557
174
|
return (
|
|
558
175
|
<section className={getClasses().join(' ')}>
|
|
559
176
|
{visualizationConfig.filterIntro && (
|
|
560
|
-
<p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
|
|
177
|
+
<p className='filters-section__intro-text mb-3'>{parse(visualizationConfig.filterIntro)}</p>
|
|
561
178
|
)}
|
|
562
179
|
<div className='d-flex flex-wrap w-100 filters-section__wrapper align-items-end'>
|
|
563
|
-
{' '}
|
|
564
180
|
<>
|
|
565
|
-
|
|
181
|
+
{vizFiltersWithValues.map((singleFilter: VizFilter, outerIndex) => {
|
|
182
|
+
if (singleFilter.showDropdown === false) return
|
|
183
|
+
const { label, filterStyle, columnName } = singleFilter as VizFilter
|
|
184
|
+
const [nestedActiveGroup, nestedActiveSubGroup] = getNestedGroup(singleFilter)
|
|
185
|
+
|
|
186
|
+
handleSorting(singleFilter)
|
|
187
|
+
|
|
188
|
+
const classList = [
|
|
189
|
+
'single-filters',
|
|
190
|
+
'form-group',
|
|
191
|
+
mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
|
|
192
|
+
]
|
|
193
|
+
const mobileExempt = ['nested-dropdown', 'multi-select', VIZ_FILTER_STYLE.tabSimple].includes(filterStyle)
|
|
194
|
+
const { isDropdown } = wrappingFilters[columnName] || {}
|
|
195
|
+
const showDefaultDropdown =
|
|
196
|
+
((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
|
|
197
|
+
const hideLabelMargin = singleFilter.filterStyle === 'tab-simple' && !showDefaultDropdown
|
|
198
|
+
return (
|
|
199
|
+
<div
|
|
200
|
+
className={classList.join(' ')}
|
|
201
|
+
key={outerIndex}
|
|
202
|
+
ref={el => (wrappingFilterRefs.current[columnName] = el)}
|
|
203
|
+
>
|
|
204
|
+
{label && (
|
|
205
|
+
<label
|
|
206
|
+
className={`font-weight-bold fw-bold mb-${hideLabelMargin ? '0' : '2'}`}
|
|
207
|
+
htmlFor={`filter-${outerIndex}`}
|
|
208
|
+
>
|
|
209
|
+
{label}
|
|
210
|
+
</label>
|
|
211
|
+
)}
|
|
212
|
+
{showDefaultDropdown && (
|
|
213
|
+
<Dropdown
|
|
214
|
+
filter={singleFilter}
|
|
215
|
+
index={outerIndex}
|
|
216
|
+
label={label}
|
|
217
|
+
changeFilterActive={changeFilterActive}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
{['tab', 'tab bar', 'pill'].includes(filterStyle) && !mobileFilterStyle && (
|
|
221
|
+
<Tabs
|
|
222
|
+
filter={singleFilter}
|
|
223
|
+
index={outerIndex}
|
|
224
|
+
changeFilterActive={changeFilterActive}
|
|
225
|
+
theme={theme}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
228
|
+
{filterStyle === 'tab-simple' && !showDefaultDropdown && (
|
|
229
|
+
<Tabs
|
|
230
|
+
filter={singleFilter}
|
|
231
|
+
index={outerIndex}
|
|
232
|
+
changeFilterActive={changeFilterActive}
|
|
233
|
+
theme={theme}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{filterStyle === 'multi-select' && (
|
|
238
|
+
<MultiSelect
|
|
239
|
+
options={singleFilter.values.map(v => ({ value: v, label: v }))}
|
|
240
|
+
fieldName={outerIndex}
|
|
241
|
+
updateField={(_section, _subSection, fieldName, value) => {
|
|
242
|
+
const defaultSelection = singleFilter.defaultValue || [singleFilter.values[0]]
|
|
243
|
+
const selection = value?.length ? value : defaultSelection
|
|
244
|
+
changeFilterActive(fieldName, selection)
|
|
245
|
+
}}
|
|
246
|
+
selected={singleFilter.active as string[]}
|
|
247
|
+
limit={(singleFilter as MultiSelectFilter).selectLimit || 5}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
{filterStyle === 'nested-dropdown' && (
|
|
251
|
+
<NestedDropdown
|
|
252
|
+
activeGroup={nestedActiveGroup}
|
|
253
|
+
activeSubGroup={nestedActiveSubGroup}
|
|
254
|
+
filterIndex={outerIndex}
|
|
255
|
+
options={getNestedOptions(singleFilter)}
|
|
256
|
+
listLabel={label}
|
|
257
|
+
handleSelectedItems={value => changeFilterActive(outerIndex, value)}
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
})}
|
|
566
263
|
{filterBehavior === 'Apply Button' ? (
|
|
567
264
|
<div className='filters-section__buttons'>
|
|
568
265
|
<Button
|
|
@@ -572,10 +269,10 @@ const Filters = (props: FilterProps) => {
|
|
|
572
269
|
disabled={!showApplyButton}
|
|
573
270
|
className={[general?.headerColor ? general.headerColor : theme, 'apply', 'me-2'].join(' ')}
|
|
574
271
|
>
|
|
575
|
-
{
|
|
272
|
+
{BUTTON_TEXT.apply}
|
|
576
273
|
</Button>
|
|
577
274
|
<Button secondary disabled={initialFiltersActive} onClick={handleReset}>
|
|
578
|
-
{
|
|
275
|
+
{BUTTON_TEXT.resetText}
|
|
579
276
|
</Button>
|
|
580
277
|
</div>
|
|
581
278
|
) : (
|