@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.
Files changed (89) hide show
  1. package/assets/icon-close.svg +1 -1
  2. package/components/Alert/components/Alert.tsx +1 -1
  3. package/components/DataTable/DataTable.tsx +18 -16
  4. package/components/DataTable/DataTableStandAlone.tsx +15 -9
  5. package/components/DataTable/components/CellAnchor.tsx +1 -1
  6. package/components/DataTable/components/ChartHeader.tsx +8 -5
  7. package/components/DataTable/components/DataTableEditorPanel.tsx +25 -3
  8. package/components/DataTable/components/MapHeader.tsx +1 -0
  9. package/components/DataTable/helpers/chartCellMatrix.tsx +14 -10
  10. package/components/DataTable/helpers/getChartCellValue.ts +42 -26
  11. package/components/DataTable/helpers/mapCellMatrix.tsx +25 -7
  12. package/components/DownloadButton.tsx +17 -2
  13. package/components/EditorPanel/DataTableEditor.tsx +1 -1
  14. package/components/EditorPanel/FootnotesEditor.tsx +76 -22
  15. package/components/EditorPanel/Inputs.tsx +12 -4
  16. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +3 -2
  17. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +51 -35
  18. package/components/Filters/Filters.tsx +158 -461
  19. package/components/Filters/components/Dropdown.tsx +39 -0
  20. package/components/Filters/components/Tabs.tsx +82 -0
  21. package/components/Filters/helpers/getChangedFilters.ts +31 -0
  22. package/components/Filters/helpers/getNestedOptions.ts +2 -2
  23. package/components/Filters/helpers/handleSorting.ts +2 -2
  24. package/components/Filters/helpers/tests/getChangedFilters.test.ts +92 -0
  25. package/components/Filters/helpers/tests/getNestedOptions.test.ts +31 -0
  26. package/components/Filters/index.ts +1 -1
  27. package/components/Footnotes/Footnotes.tsx +1 -1
  28. package/components/Footnotes/FootnotesStandAlone.tsx +8 -33
  29. package/components/Layout/components/Visualization/index.tsx +4 -3
  30. package/components/Legend/Legend.Gradient.tsx +68 -24
  31. package/components/MultiSelect/MultiSelect.tsx +3 -6
  32. package/components/MultiSelect/multiselect.styles.css +2 -0
  33. package/components/NestedDropdown/NestedDropdown.tsx +21 -21
  34. package/components/RichTooltip/RichTooltip.tsx +37 -0
  35. package/components/RichTooltip/richTooltip.css +16 -0
  36. package/components/Table/Table.tsx +142 -142
  37. package/components/Table/components/Row.tsx +1 -1
  38. package/components/Table/table.styles.css +10 -0
  39. package/components/_stories/DataTable.stories.tsx +9 -2
  40. package/components/_stories/Table.stories.tsx +1 -1
  41. package/components/_stories/styles.scss +0 -4
  42. package/components/ui/Accordion.jsx +8 -1
  43. package/components/ui/Title/index.tsx +4 -1
  44. package/components/ui/Title/{Title.scss → title.styles.css} +0 -2
  45. package/components/ui/_stories/Colors.stories.mdx +220 -0
  46. package/components/ui/_stories/IconGallery.stories.mdx +14 -0
  47. package/components/ui/_stories/Title.stories.tsx +29 -4
  48. package/components/ui/accordion.styles.css +3 -0
  49. package/data/colorPalettes.js +0 -1
  50. package/dist/cove-main.css +3 -8
  51. package/dist/cove-main.css.map +1 -1
  52. package/helpers/constants.ts +6 -0
  53. package/helpers/cove/accessibility.ts +7 -8
  54. package/helpers/cove/number.ts +5 -3
  55. package/helpers/coveUpdateWorker.ts +9 -1
  56. package/helpers/filterOrderOptions.ts +17 -0
  57. package/helpers/formatConfigBeforeSave.ts +19 -32
  58. package/helpers/isNumber.ts +20 -0
  59. package/helpers/isRightAlignedTableValue.js +1 -1
  60. package/helpers/pivotData.ts +16 -11
  61. package/helpers/tests/pivotData.test.ts +74 -0
  62. package/helpers/updateFieldFactory.ts +1 -0
  63. package/helpers/ver/4.25.3.ts +25 -2
  64. package/helpers/ver/4.25.4.ts +110 -0
  65. package/helpers/ver/4.25.6.ts +36 -0
  66. package/helpers/ver/4.25.7.ts +26 -0
  67. package/helpers/ver/tests/4.25.4.test.ts +89 -0
  68. package/helpers/ver/tests/4.25.6.test.ts +84 -0
  69. package/helpers/viewports.ts +4 -0
  70. package/package.json +7 -6
  71. package/styles/_global-variables.scss +3 -0
  72. package/styles/_global.scss +0 -4
  73. package/styles/_reset.scss +0 -6
  74. package/styles/filters.scss +0 -4
  75. package/styles/v2/main.scss +0 -5
  76. package/types/Axis.ts +2 -0
  77. package/types/DataSet.ts +14 -0
  78. package/types/Footnotes.ts +5 -2
  79. package/types/General.ts +1 -0
  80. package/types/Legend.ts +1 -0
  81. package/types/Table.ts +1 -0
  82. package/types/Visualization.ts +3 -12
  83. package/types/VizFilter.ts +3 -0
  84. package/components/ui/_stories/Colors.stories.tsx +0 -92
  85. package/components/ui/_stories/Icon.stories.tsx +0 -29
  86. package/helpers/cove/fontSettings.ts +0 -2
  87. package/helpers/isNumber.js +0 -24
  88. package/helpers/isNumberLog.js +0 -18
  89. /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, OrderBy, VizFilter } from '../../types/VizFilter'
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
- export const filterOrderOptions: { label: string; value: OrderBy }[] = [
36
- {
37
- label: 'Ascending Alphanumeric',
38
- value: 'asc'
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
- // Deconstructing: notice, adding more descriptive visualizationConfig name over config
55
- // visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
56
- const {
57
- config: visualizationConfig,
58
- setConfig,
59
- filteredData,
60
- setFilteredData,
61
- excludedData,
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
- setConfig({ ...visualizationConfig, filters: filtersCopy })
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 changeFilterActive = (index, value) => {
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
- const queryParams = getQueryParams()
120
- if (newFilter.setByQueryParameter && queryParams[newFilter.setByQueryParameter] !== newFilter.active) {
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 (!visualizationConfig.dynamicSeries) {
135
- newFilters = addValuesToFilters(newFilters, excludedData)
136
- setConfig({
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
- // Used for setting active filter, fromHash breaks the filteredData functionality.
143
- if (standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
144
- setFilteredData(newFilters)
145
- }
77
+ const initialActiveFilters = useMemo(() => {
78
+ //if (!filteredData) return []
79
+ return filters.map(filter => filter.active)
80
+ }, [])
146
81
 
147
- // If we're on a chart and not using the apply button
148
- if (!standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
149
- const newFilteredData = filterVizData(newFilters, excludedData)
150
- setFilteredData(newFilteredData)
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
- runtime.seriesKeys = runtime.series
178
- ? runtime.series.map(series => {
179
- runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
180
- runtime.seriesLabelsAll.push(series.name || series.dataKey)
181
- return series.dataKey
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
- setConfig({ ...visualizationConfig, filters: newFilters })
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 = [...visualizationConfig.filters]
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
- setConfig({ ...visualizationConfig, filters: newFilters })
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
- // prettier-ignore
260
- return {
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
- const defaultToMobile = isMobile && filters?.length && !isTabSimple
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
- let vizfilters = standaloneMap ? filteredData : filters
408
- if (!vizfilters) return []
409
- if (vizfilters.fromHash) delete vizfilters.fromHash // support for Maps config
410
- return addValuesToFilters(vizfilters as VizFilter[], visualizationConfig.data)
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
- <Style />
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
- {filterConstants.buttonText}
272
+ {BUTTON_TEXT.apply}
576
273
  </Button>
577
274
  <Button secondary disabled={initialFiltersActive} onClick={handleReset}>
578
- {filterConstants.resetText}
275
+ {BUTTON_TEXT.resetText}
579
276
  </Button>
580
277
  </div>
581
278
  ) : (