@cdc/core 4.24.9 → 4.24.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.
Files changed (106) hide show
  1. package/LICENSE +201 -0
  2. package/assets/icon-combo-chart.svg +1 -0
  3. package/assets/icon-epi-chart.svg +27 -0
  4. package/components/AdvancedEditor/AdvancedEditor.tsx +17 -13
  5. package/components/Alert/components/Alert.tsx +34 -8
  6. package/components/BlurStrokeText.tsx +44 -0
  7. package/components/DataTable/DataTable.tsx +62 -36
  8. package/components/DataTable/DataTableStandAlone.tsx +37 -6
  9. package/components/DataTable/components/ChartHeader.tsx +31 -26
  10. package/components/DataTable/components/MapHeader.tsx +19 -10
  11. package/components/DataTable/components/SortIcon/index.tsx +25 -0
  12. package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
  13. package/{styles/_data-table.scss → components/DataTable/data-table.css} +250 -298
  14. package/components/DataTable/helpers/boxplotCellMatrix.tsx +14 -13
  15. package/components/DataTable/helpers/customSort.ts +11 -15
  16. package/components/DataTable/helpers/getChartCellValue.ts +23 -5
  17. package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
  18. package/components/DataTable/helpers/getNewSortBy.ts +35 -0
  19. package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
  20. package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
  21. package/components/EditorPanel/ColumnsEditor.tsx +81 -36
  22. package/components/EditorPanel/DataTableEditor.tsx +149 -43
  23. package/components/EditorPanel/FieldSetWrapper.tsx +2 -2
  24. package/components/EditorPanel/Inputs.tsx +68 -20
  25. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
  26. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +30 -55
  27. package/components/{Filters.tsx → Filters/Filters.tsx} +60 -43
  28. package/components/Filters/helpers/applyQueuedActive.ts +12 -0
  29. package/components/Filters/helpers/getNestedOptions.ts +29 -0
  30. package/components/Filters/helpers/handleSorting.ts +18 -0
  31. package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
  32. package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
  33. package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
  34. package/components/Filters/index.ts +5 -0
  35. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -7
  36. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  37. package/components/Legend/Legend.Gradient.tsx +44 -36
  38. package/components/Loader/Loader.tsx +33 -0
  39. package/components/Loader/index.ts +1 -0
  40. package/components/Loader/loader.styles.css +13 -0
  41. package/components/MultiSelect/MultiSelect.tsx +85 -62
  42. package/components/MultiSelect/multiselect.styles.css +10 -7
  43. package/components/NestedDropdown/NestedDropdown.tsx +118 -56
  44. package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
  45. package/components/NestedDropdown/nesteddropdown.styles.css +22 -13
  46. package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
  47. package/components/Table/Table.tsx +102 -34
  48. package/components/Table/components/GroupRow.tsx +1 -1
  49. package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
  50. package/components/_stories/DataTable.stories.tsx +14 -0
  51. package/components/_stories/Filters.stories.tsx +57 -0
  52. package/components/_stories/NestedDropdown.stories.tsx +22 -46
  53. package/components/_stories/_mocks/DataTable/no-data.json +108 -0
  54. package/components/_stories/_mocks/nested-dropdown.json +30 -0
  55. package/components/_stories/styles.scss +0 -1
  56. package/components/ui/Icon.tsx +19 -6
  57. package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
  58. package/data/colorPalettes.js +107 -10
  59. package/dist/cove-main.css +6080 -0
  60. package/dist/cove-main.css.map +1 -0
  61. package/helpers/DataTransform.ts +2 -1
  62. package/helpers/addValuesToFilters.ts +8 -3
  63. package/helpers/cove/{number.js → number.ts} +62 -27
  64. package/helpers/coveUpdateWorker.ts +6 -7
  65. package/helpers/fetchRemoteData.js +32 -37
  66. package/helpers/formatConfigBeforeSave.ts +17 -1
  67. package/helpers/gatherQueryParams.ts +12 -2
  68. package/helpers/pivotData.ts +52 -11
  69. package/helpers/queryStringUtils.ts +6 -0
  70. package/helpers/tests/gatherQueryParams.test.ts +34 -0
  71. package/helpers/tests/pivotData.test.ts +50 -0
  72. package/helpers/useDataVizClasses.ts +42 -20
  73. package/helpers/ver/4.24.10.ts +47 -0
  74. package/helpers/ver/4.24.9.ts +0 -3
  75. package/helpers/ver/tests/4.24.10.test.ts +45 -0
  76. package/helpers/viewports.ts +9 -0
  77. package/package.json +7 -3
  78. package/styles/_button-section.scss +5 -1
  79. package/styles/_global-variables.scss +20 -2
  80. package/styles/_global.scss +22 -30
  81. package/styles/_reset.scss +2 -26
  82. package/styles/base.scss +0 -1
  83. package/styles/cove-main.scss +6 -0
  84. package/styles/filters.scss +6 -26
  85. package/styles/v2/base/_reset.scss +0 -7
  86. package/styles/v2/components/editor.scss +0 -4
  87. package/styles/v2/components/icon.scss +1 -1
  88. package/styles/v2/components/ui/tooltip.scss +42 -40
  89. package/styles/v2/layout/_component.scss +0 -6
  90. package/styles/v2/layout/index.scss +0 -1
  91. package/types/Axis.ts +4 -0
  92. package/types/BoxPlot.ts +5 -3
  93. package/types/Color.ts +1 -1
  94. package/types/General.ts +1 -0
  95. package/types/Legend.ts +1 -2
  96. package/types/MarkupInclude.ts +1 -0
  97. package/types/Runtime.ts +3 -1
  98. package/types/Series.ts +8 -1
  99. package/types/Table.ts +3 -2
  100. package/types/Visualization.ts +19 -8
  101. package/types/VizFilter.ts +2 -1
  102. package/components/DataTable/components/Icons.tsx +0 -10
  103. package/components/_stories/EditorPanel.stories.tsx +0 -54
  104. package/components/_stories/Layout.Debug.stories.tsx +0 -91
  105. package/components/ui/Select.jsx +0 -30
  106. package/helpers/getGradientLegendWidth.ts +0 -15
@@ -17,6 +17,7 @@ export type TextFieldProps = {
17
17
  value: string | number
18
18
  type?: 'text' | 'number' | 'textarea' | 'date'
19
19
  min?: number
20
+ maxLength?: number
20
21
  max?: number
21
22
  i?: number
22
23
  id?: string
@@ -27,20 +28,24 @@ export type CheckboxProps = {
27
28
  min?: number
28
29
  i?: number
29
30
  className?: string
30
- } & Input
31
-
32
- export type SelectProps = {
33
- value?: string
34
- options?: string[]
35
- required?: boolean
36
- initial?: string
37
-
38
- // all other props
39
- [x: string]: any
40
- } & Input
31
+ } & Input &
32
+ Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'>
41
33
 
42
34
  const TextField = memo((props: TextFieldProps) => {
43
- const { display = true, label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'text', i = null, min = null, ...attributes } = props
35
+ const {
36
+ display = true,
37
+ label,
38
+ tooltip,
39
+ section = null,
40
+ subsection = null,
41
+ fieldName,
42
+ updateField,
43
+ value: stateValue,
44
+ type = 'text',
45
+ i = null,
46
+ min = null,
47
+ ...attributes
48
+ } = props
44
49
  const [value, setValue] = useState(stateValue)
45
50
  const [debouncedValue] = useDebounce(value, 500)
46
51
 
@@ -93,7 +98,17 @@ const TextField = memo((props: TextFieldProps) => {
93
98
  })
94
99
 
95
100
  const CheckBox = memo((props: CheckboxProps) => {
96
- const { display = true, label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes } = props
101
+ const {
102
+ display = true,
103
+ label,
104
+ value,
105
+ fieldName,
106
+ section = null,
107
+ subsection = null,
108
+ tooltip,
109
+ updateField,
110
+ ...attributes
111
+ } = props
97
112
  if (!display) {
98
113
  return <></>
99
114
  }
@@ -116,13 +131,46 @@ const CheckBox = memo((props: CheckboxProps) => {
116
131
  )
117
132
  })
118
133
 
134
+ export type SelectProps = {
135
+ value?: string
136
+ options?: string[] | { label: string; value: string }[]
137
+ required?: boolean
138
+ initial?: string
139
+
140
+ // all other props
141
+ [x: string]: any
142
+ } & Input
143
+
119
144
  const Select = memo((props: SelectProps) => {
120
- const { display = true, label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes } = props
121
- let optionsJsx = options.map((optionName, index) => (
122
- <option value={optionName} key={index}>
123
- {optionName}
124
- </option>
125
- ))
145
+ const {
146
+ display = true,
147
+ label,
148
+ value,
149
+ options,
150
+ fieldName,
151
+ section = null,
152
+ subsection = null,
153
+ required = false,
154
+ tooltip,
155
+ updateField,
156
+ initial: initialValue,
157
+ ...attributes
158
+ } = props
159
+ const optionsJsx = options.map((option, index) => {
160
+ if (typeof option === 'string') {
161
+ return (
162
+ <option value={option} key={index}>
163
+ {option}
164
+ </option>
165
+ )
166
+ } else {
167
+ return (
168
+ <option value={option.value} key={index}>
169
+ {option.label}
170
+ </option>
171
+ )
172
+ }
173
+ })
126
174
 
127
175
  if (initialValue) {
128
176
  optionsJsx.unshift(
@@ -142,7 +190,7 @@ const Select = memo((props: SelectProps) => {
142
190
  {tooltip}
143
191
  </span>
144
192
  <select
145
- className={required && !value ? 'warning' : ''}
193
+ className={`cove-form-select ${required && !value ? 'warning' : ''}`}
146
194
  name={fieldName}
147
195
  value={value}
148
196
  onChange={event => {
@@ -3,6 +3,7 @@ import { SubGrouping, VizFilter, OrderBy } from '../../../types/VizFilter'
3
3
  import { filterOrderOptions, handleSorting } from '../../Filters'
4
4
  import FilterOrder from './components/FilterOrder'
5
5
  import { Visualization } from '../../../types/Visualization'
6
+ import { useMemo } from 'react'
6
7
 
7
8
  type NestedDropdownEditorProps = {
8
9
  config: Visualization
@@ -121,6 +122,25 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
121
122
 
122
123
  const columnNameOptions = dataColumns.filter(columnName => !listOfUsedColumnNames.includes(columnName))
123
124
 
125
+ const useParameters = useMemo(() => {
126
+ const filter = config.filters[filterIndex]
127
+ return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
128
+ }, [config, filterIndex])
129
+
130
+ const handleParametersCheckboxClick = e => {
131
+ const updatedFilters = config.filters
132
+ const { checked } = e.target
133
+ const groupColumnName = checked ? filter.columnName : ''
134
+ const subGroupColumnName = checked ? subGrouping.columnName : ''
135
+ updatedFilters[filterIndex] = {
136
+ ...config.filters[filterIndex],
137
+ setByQueryParameter: groupColumnName,
138
+ subGrouping: { ...subGrouping, setByQueryParameter: subGroupColumnName }
139
+ }
140
+
141
+ updateField(null, null, 'filters', updatedFilters)
142
+ }
143
+
124
144
  return (
125
145
  <div className='nesteddropdown-editor'>
126
146
  <label>
@@ -175,15 +195,13 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
175
195
  <label>
176
196
  <input
177
197
  type='checkbox'
178
- checked={!!filter.setByQueryParameter}
198
+ checked={useParameters}
179
199
  aria-label='Create query parameters'
180
- onChange={e => {
181
- updateGroupingFilterProp('setByQueryParameter', filter.columnName)
182
- updateSubGroupingFilterProperty({ ...subGrouping, setByQueryParameter: subGrouping.columnName })
183
- }}
200
+ disabled={!filter.columnName || !subGrouping?.columnName}
201
+ onChange={e => handleParametersCheckboxClick(e)}
184
202
  />
185
203
  <span> Create query parameters</span>
186
- {!!filter.setByQueryParameter && (
204
+ {useParameters && (
187
205
  <>
188
206
  <span className='edit-label column-heading mt-2'>
189
207
  Grouping: Default Value Set By Query String Parameter
@@ -200,7 +218,7 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
200
218
  </span>
201
219
  <input
202
220
  type='text'
203
- value={subGrouping.setByQueryParameter}
221
+ value={subGrouping?.setByQueryParameter}
204
222
  onChange={e => {
205
223
  const setByQueryParameter = e.target.value
206
224
  updateSubGroupingFilterProperty({ ...subGrouping, setByQueryParameter })
@@ -140,43 +140,24 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
140
140
  controls={openControls}
141
141
  deleteField={() => removeFilter(filterIndex)}
142
142
  >
143
- <label>
144
- <span className='edit-label column-heading'>Filter Style</span>
145
-
146
- <select
147
- value={filter.filterStyle}
148
- onChange={e => {
149
- updateFilterStyle(filterIndex, e.target.value)
150
- }}
151
- >
152
- {filterStyleOptions.map((item, index) => {
153
- return (
154
- <option key={`filter-style-${index}`} value={item}>
155
- {item}
156
- </option>
157
- )
158
- })}
159
- </select>
160
- </label>
143
+ <Select
144
+ value={filter.filterStyle}
145
+ fieldName='filterStyle'
146
+ label='Filter Style'
147
+ updateField={(_section, _subsection, _field, value) => updateFilterStyle(filterIndex, value)}
148
+ options={filterStyleOptions}
149
+ />
161
150
 
162
151
  {filter.filterStyle !== 'nested-dropdown' ? (
163
152
  <>
164
- <label>
165
- <span className='edit-label column-heading'>Filter</span>
166
- <select
167
- value={filter.columnName}
168
- onChange={e => {
169
- handleNameChange(filterIndex, e.target.value)
170
- }}
171
- >
172
- <option value=''>- Select Option -</option>
173
- {dataColumns.map((dataKey, filterIndex) => (
174
- <option value={dataKey} key={filterIndex}>
175
- {dataKey}
176
- </option>
177
- ))}
178
- </select>
179
- </label>
153
+ <Select
154
+ value={filter.columnName}
155
+ fieldName='columnName'
156
+ label='Filter'
157
+ updateField={(_section, _subsection, _field, value) => handleNameChange(filterIndex, value)}
158
+ options={dataColumns}
159
+ initial='- Select Option -'
160
+ />
180
161
 
181
162
  <label>
182
163
  <span className='edit-showDropdown column-heading'>Show Filter Input</span>
@@ -233,27 +214,21 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
233
214
  />
234
215
  </label>
235
216
 
236
- <label>
237
- <span className='edit-filterOrder column-heading'>Filter Order</span>
238
- <select
239
- value={filter.order ? filter.order : 'asc'}
240
- onChange={e => updateFilterProp('order', filterIndex, e.target.value)}
241
- >
242
- {filterOrderOptions.map((option, index) => {
243
- return (
244
- <option value={option.value} key={`filter-${index}`}>
245
- {option.label}
246
- </option>
247
- )
248
- })}
249
- </select>
250
- {filter.order === 'cust' && (
251
- <FilterOrder
252
- orderedValues={filter.orderedValues || filter.values}
253
- handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
254
- />
255
- )}
256
- </label>
217
+ <Select
218
+ value={filter.order || 'asc'}
219
+ fieldName='order'
220
+ label='Filter Order'
221
+ updateField={(_section, _subSection, _field, value) =>
222
+ updateFilterProp('order', filterIndex, value)
223
+ }
224
+ options={filterOrderOptions}
225
+ />
226
+ {filter.order === 'cust' && (
227
+ <FilterOrder
228
+ orderedValues={filter.orderedValues || filter.values}
229
+ handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
230
+ />
231
+ )}
257
232
  </>
258
233
  ) : (
259
234
  <NestedDropdownEditor
@@ -2,18 +2,32 @@ import { useState, useEffect, useMemo } from 'react'
2
2
  import { useId } from 'react'
3
3
 
4
4
  // CDC
5
- import Button from './elements/Button'
6
- import { getQueryParams, updateQueryString } from '../helpers/queryStringUtils'
7
- import MultiSelect from './MultiSelect'
8
- import { Visualization } from '../types/Visualization'
9
- import { MultiSelectFilter, OrderBy, VizFilter } from '../types/VizFilter'
10
- import { filterVizData } from '../helpers/filterVizData'
11
- import { addValuesToFilters } from '../helpers/addValuesToFilters'
12
- import { DimensionsType } from '../types/Dimensions'
13
- import NestedDropdown from './NestedDropdown'
5
+ import Button from '../elements/Button'
6
+ import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
7
+ import MultiSelect from '../MultiSelect'
8
+ import { Visualization } from '../../types/Visualization'
9
+ import { MultiSelectFilter, OrderBy, VizFilter } from '../../types/VizFilter'
10
+ import { filterVizData } from '../../helpers/filterVizData'
11
+ import { addValuesToFilters } from '../../helpers/addValuesToFilters'
12
+ import { DimensionsType } from '../../types/Dimensions'
13
+ import NestedDropdown from '../NestedDropdown'
14
14
  import _ from 'lodash'
15
+ import { getNestedOptions } from './helpers/getNestedOptions'
16
+ import { applyQueuedActive } from './helpers/applyQueuedActive'
17
+ import { handleSorting } from './helpers/handleSorting'
15
18
 
16
- export const filterStyleOptions = ['dropdown', 'nested-dropdown', 'pill', 'tab', 'tab bar', 'multi-select']
19
+ export const VIZ_FILTER_STYLE = {
20
+ dropdown: 'dropdown',
21
+ nestedDropdown: 'nested-dropdown',
22
+ pill: 'pill',
23
+ tab: 'tab',
24
+ tabBar: 'tab bar',
25
+ multiSelect: 'multi-select'
26
+ } as const
27
+
28
+ export type VizFilterStyle = (typeof VIZ_FILTER_STYLE)[keyof typeof VIZ_FILTER_STYLE]
29
+
30
+ export const filterStyleOptions = Object.values(VIZ_FILTER_STYLE)
17
31
 
18
32
  export const filterOrderOptions: { label: string; value: OrderBy }[] = [
19
33
  {
@@ -30,23 +44,6 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
30
44
  }
31
45
  ]
32
46
 
33
- export const handleSorting = singleFilter => {
34
- const singleFilterValues = _.cloneDeep(singleFilter.values)
35
- if (singleFilter.order === 'cust' && singleFilter.filterStyle !== 'nested-dropdown') {
36
- singleFilter.values = singleFilter.orderedValues?.length ? singleFilter.orderedValues : singleFilterValues
37
- return singleFilter
38
- }
39
-
40
- const sort = (a, b) => {
41
- const asc = singleFilter.order !== 'desc'
42
- return (asc ? a : b).toString().localeCompare((asc ? b : a).toString(), 'en', { numeric: true })
43
- }
44
-
45
- singleFilter.values = singleFilterValues.sort(sort)
46
-
47
- return singleFilter
48
- }
49
-
50
47
  const hasStandardFilterBehavior = ['chart', 'table']
51
48
 
52
49
  export const useFilters = props => {
@@ -116,6 +113,14 @@ export const useFilters = props => {
116
113
  queryParams[newFilter.setByQueryParameter] = newFilter.active
117
114
  updateQueryString(queryParams)
118
115
  }
116
+ if (
117
+ newFilter?.subGrouping?.setByQueryParameter &&
118
+ queryParams[newFilter?.subGrouping?.setByQueryParameter] !== newFilter?.subGrouping.active
119
+ ) {
120
+ queryParams[newFilter?.subGrouping?.setByQueryParameter] = newFilter.subGrouping.active
121
+ updateQueryString(queryParams)
122
+ }
123
+ setFilteredData(newFilters[index])
119
124
  }
120
125
 
121
126
  if (!visualizationConfig.dynamicSeries) {
@@ -186,8 +191,7 @@ export const useFilters = props => {
186
191
  const queryParams = getQueryParams()
187
192
  newFilters.forEach(newFilter => {
188
193
  if (newFilter.queuedActive) {
189
- newFilter.active = newFilter.queuedActive
190
- delete newFilter.queuedActive
194
+ applyQueuedActive(newFilter)
191
195
  if (newFilter.setByQueryParameter && queryParams[newFilter.setByQueryParameter] !== newFilter.active) {
192
196
  queryParams[newFilter.setByQueryParameter] = newFilter.active
193
197
  needsQueryUpdate = true
@@ -347,7 +351,7 @@ const Filters = (props: FilterProps) => {
347
351
  id={`filter-${outerIndex}`}
348
352
  name={label}
349
353
  aria-label={`Filter by ${label}`}
350
- className='filter-select'
354
+ className='cove-form-select'
351
355
  data-index='0'
352
356
  value={active}
353
357
  onChange={e => {
@@ -379,7 +383,6 @@ const Filters = (props: FilterProps) => {
379
383
  const { active, queuedActive, label, filterStyle } = singleFilter as VizFilter
380
384
 
381
385
  handleSorting(singleFilter)
382
-
383
386
  singleFilter.values?.forEach((filterOption, index) => {
384
387
  const pillClassList = ['pill', active === filterOption ? 'pill--active' : null, theme && theme]
385
388
  const tabClassList = ['tab', active === filterOption && 'tab--active', theme && theme]
@@ -436,6 +439,7 @@ const Filters = (props: FilterProps) => {
436
439
 
437
440
  const classList = [
438
441
  'single-filters',
442
+ 'form-group mr-3',
439
443
  mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
440
444
  ]
441
445
  const mobileExempt = ['nested-dropdown', 'multi-select'].includes(filterStyle)
@@ -443,7 +447,11 @@ const Filters = (props: FilterProps) => {
443
447
  return (
444
448
  <div className={classList.join(' ')} key={outerIndex}>
445
449
  <>
446
- {label && <label htmlFor={`filter-${outerIndex}`}>{label}</label>}
450
+ {label && (
451
+ <label className='text-capitalize font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
452
+ {label}
453
+ </label>
454
+ )}
447
455
  {filterStyle === 'tab' && !mobileFilterStyle && Tabs}
448
456
  {filterStyle === 'pill' && !mobileFilterStyle && Pills}
449
457
  {filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
@@ -458,7 +466,10 @@ const Filters = (props: FilterProps) => {
458
466
  )}
459
467
  {filterStyle === 'nested-dropdown' && (
460
468
  <NestedDropdown
461
- currentFilter={singleFilter}
469
+ activeGroup={(singleFilter.active as string) || (singleFilter.queuedActive || [])[0]}
470
+ activeSubGroup={(singleFilter.subGrouping?.active as string) || (singleFilter.queuedActive || [])[1]}
471
+ filterIndex={outerIndex}
472
+ options={getNestedOptions(singleFilter)}
462
473
  listLabel={label}
463
474
  handleSelectedItems={value => changeFilterActive(outerIndex, value)}
464
475
  />
@@ -479,26 +490,32 @@ const Filters = (props: FilterProps) => {
479
490
  }
480
491
 
481
492
  if (visualizationConfig?.filters?.length === 0) return
482
- const filterSectionClassList = [
483
- `filters-section legend_${visualizationConfig?.legend?.hide ? 'hidden' : 'visible'}_${
484
- visualizationConfig?.legend?.position || ''
485
- }`,
486
- type === 'map' ? general.headerColor : visualizationConfig?.visualizationType === 'Spark Line' ? null : theme
487
- ]
493
+
494
+ const getClasses = () => {
495
+ const { visualizationType, legend } = visualizationConfig || {}
496
+ const baseClass = 'filters-section'
497
+ const conditionalClass = type === 'map' ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
498
+ const legendClass = legend && !legend.hide && legend.position === 'top' ? 'mb-0' : null
499
+
500
+ return [baseClass, conditionalClass, legendClass].filter(Boolean)
501
+ }
502
+
488
503
  return (
489
- <section className={filterSectionClassList.join(' ')}>
504
+ <section className={getClasses().join(' ')}>
490
505
  <p className='filters-section__intro-text'>
491
- {filters?.some(f => f.active && f.showDropdown) ? filterConstants.introText : ''}{' '}
506
+ {filters?.some(filter => filter.active && filter.columnName) ? filterConstants.introText : ''}{' '}
492
507
  {visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
493
508
  </p>
494
- <div className='filters-section__wrapper'>
509
+ <div className='d-flex flex-wrap w-100 filters-section__wrapper'>
495
510
  {' '}
496
511
  <>
497
512
  <Style />
498
513
  {filterBehavior === 'Apply Button' ? (
499
514
  <div className='filters-section__buttons'>
500
515
  <Button
501
- onClick={() => handleApplyButton(filters)}
516
+ onClick={e => {
517
+ handleApplyButton(filters)
518
+ }}
502
519
  disabled={!showApplyButton}
503
520
  className={[general?.headerColor ? general.headerColor : theme, 'apply'].join(' ')}
504
521
  >
@@ -0,0 +1,12 @@
1
+ import { VIZ_FILTER_STYLE } from '../Filters'
2
+ import { SharedFilter } from '@cdc/dashboard/src/types/SharedFilter'
3
+
4
+ export const applyQueuedActive = (sharedFilter: SharedFilter) => {
5
+ if (sharedFilter.filterStyle === VIZ_FILTER_STYLE.nestedDropdown) {
6
+ sharedFilter.active = sharedFilter.queuedActive[0]
7
+ sharedFilter.subGrouping.active = sharedFilter.queuedActive[1]
8
+ } else {
9
+ sharedFilter.active = sharedFilter.queuedActive
10
+ }
11
+ delete sharedFilter.queuedActive
12
+ }
@@ -0,0 +1,29 @@
1
+ import { SubGrouping } from '../../../types/VizFilter'
2
+ import { NestedOptions, ValueTextPair } from '../../NestedDropdown/nestedDropdownHelpers'
3
+
4
+ type GetOptionsMemoParams = {
5
+ orderedValues?: string[]
6
+ values: string[]
7
+ subGrouping: SubGrouping
8
+ }
9
+
10
+ export const getNestedOptions = ({ orderedValues, values, subGrouping }: GetOptionsMemoParams): NestedOptions => {
11
+ // keep custom ordered value order
12
+ const filteredValues = orderedValues?.length
13
+ ? orderedValues.filter(orderedValue => values.includes(orderedValue))
14
+ : values
15
+ const v: NestedOptions = filteredValues.map<[ValueTextPair, ValueTextPair[]]>(value => {
16
+ if (!subGrouping) return [[value], []]
17
+ const { orderedValues, values: filteredSubValues } = subGrouping.valuesLookup[value]
18
+ // keep custom subFilter order
19
+ const subFilterValues =
20
+ orderedValues?.filter(orderedValue => filteredSubValues.includes(orderedValue)) || filteredSubValues
21
+ const structuredNestedDropdownData: [ValueTextPair, ValueTextPair[]] = [
22
+ [value],
23
+ subFilterValues.map(subValue => [subValue])
24
+ ]
25
+ return structuredNestedDropdownData
26
+ })
27
+
28
+ return v
29
+ }
@@ -0,0 +1,18 @@
1
+ import _ from 'lodash'
2
+
3
+ export const handleSorting = singleFilter => {
4
+ const singleFilterValues = _.cloneDeep(singleFilter.values)
5
+ if (singleFilter.order === 'cust' && singleFilter.filterStyle !== 'nested-dropdown') {
6
+ singleFilter.values = singleFilter.orderedValues?.length ? singleFilter.orderedValues : singleFilterValues
7
+ return singleFilter
8
+ }
9
+
10
+ const sort = (a, b) => {
11
+ const asc = singleFilter.order !== 'desc'
12
+ return String(asc ? a : b).localeCompare(String(asc ? b : a), 'en', { numeric: true })
13
+ }
14
+
15
+ singleFilter.values = singleFilterValues.sort(sort)
16
+
17
+ return singleFilter
18
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { applyQueuedActive } from '../applyQueuedActive'
3
+ import { VIZ_FILTER_STYLE } from '../../Filters'
4
+ import { SharedFilter } from '@cdc/dashboard/src/types/SharedFilter'
5
+
6
+ describe('applyQueuedActive', () => {
7
+ it('should apply queuedActive to active and subGrouping.active for nestedDropdown filter style', () => {
8
+ const sharedFilter: SharedFilter = {
9
+ filterStyle: VIZ_FILTER_STYLE.nestedDropdown,
10
+ queuedActive: ['activeValue', 'subActiveValue'],
11
+ active: null,
12
+ subGrouping: {
13
+ active: null
14
+ }
15
+ }
16
+
17
+ applyQueuedActive(sharedFilter)
18
+
19
+ expect(sharedFilter.active).toBe('activeValue')
20
+ expect(sharedFilter.subGrouping.active).toBe('subActiveValue')
21
+ expect(sharedFilter.queuedActive).toBeUndefined()
22
+ })
23
+
24
+ it('should apply queuedActive to active for non-nestedDropdown filter style', () => {
25
+ const sharedFilter: SharedFilter = {
26
+ filterStyle: 'someOtherStyle',
27
+ queuedActive: 'activeValue',
28
+ active: null
29
+ }
30
+
31
+ applyQueuedActive(sharedFilter)
32
+
33
+ expect(sharedFilter.active).toBe('activeValue')
34
+ expect(sharedFilter.queuedActive).toBeUndefined()
35
+ })
36
+
37
+ it('should handle empty queuedActive for non-nestedDropdown filter style', () => {
38
+ const sharedFilter: SharedFilter = {
39
+ filterStyle: 'someOtherStyle',
40
+ queuedActive: null,
41
+ active: null
42
+ }
43
+
44
+ applyQueuedActive(sharedFilter)
45
+
46
+ expect(sharedFilter.active).toBeNull()
47
+ expect(sharedFilter.queuedActive).toBeUndefined()
48
+ })
49
+ })