@cdc/core 4.24.9 → 4.24.10

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 (77) 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/BlurStrokeText.tsx +44 -0
  5. package/components/DataTable/DataTable.tsx +51 -35
  6. package/components/DataTable/DataTableStandAlone.tsx +37 -6
  7. package/components/DataTable/components/ChartHeader.tsx +31 -26
  8. package/components/DataTable/components/MapHeader.tsx +19 -10
  9. package/components/DataTable/components/SortIcon/index.tsx +25 -0
  10. package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
  11. package/{styles/_data-table.scss → components/DataTable/data-table.css} +268 -298
  12. package/components/DataTable/helpers/customSort.ts +11 -15
  13. package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
  14. package/components/DataTable/helpers/getNewSortBy.ts +35 -0
  15. package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
  16. package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
  17. package/components/EditorPanel/DataTableEditor.tsx +132 -26
  18. package/components/EditorPanel/Inputs.tsx +42 -4
  19. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
  20. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +1 -1
  21. package/components/{Filters.tsx → Filters/Filters.tsx} +48 -39
  22. package/components/Filters/helpers/applyQueuedActive.ts +12 -0
  23. package/components/Filters/helpers/getNestedOptions.ts +29 -0
  24. package/components/Filters/helpers/handleSorting.ts +18 -0
  25. package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
  26. package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
  27. package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
  28. package/components/Filters/index.ts +5 -0
  29. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -3
  30. package/components/Legend/Legend.Gradient.tsx +2 -9
  31. package/components/Loader/Loader.tsx +33 -0
  32. package/components/Loader/index.ts +1 -0
  33. package/components/Loader/loader.styles.css +13 -0
  34. package/components/NestedDropdown/NestedDropdown.tsx +90 -48
  35. package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
  36. package/components/NestedDropdown/nesteddropdown.styles.css +7 -0
  37. package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
  38. package/components/Table/components/GroupRow.tsx +1 -1
  39. package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
  40. package/components/_stories/NestedDropdown.stories.tsx +22 -46
  41. package/components/_stories/_mocks/nested-dropdown.json +30 -0
  42. package/components/_stories/styles.scss +0 -1
  43. package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
  44. package/data/colorPalettes.js +107 -10
  45. package/dist/cove-main.css +6114 -0
  46. package/dist/cove-main.css.map +1 -0
  47. package/helpers/addValuesToFilters.ts +8 -3
  48. package/helpers/cove/number.js +46 -25
  49. package/helpers/coveUpdateWorker.ts +6 -7
  50. package/helpers/formatConfigBeforeSave.ts +16 -1
  51. package/helpers/gatherQueryParams.ts +12 -2
  52. package/helpers/pivotData.ts +52 -11
  53. package/helpers/tests/gatherQueryParams.test.ts +34 -0
  54. package/helpers/tests/pivotData.test.ts +50 -0
  55. package/helpers/ver/4.24.10.ts +47 -0
  56. package/helpers/ver/4.24.9.ts +0 -3
  57. package/helpers/ver/tests/4.24.10.test.ts +45 -0
  58. package/helpers/viewports.ts +9 -0
  59. package/package.json +7 -3
  60. package/styles/_button-section.scss +4 -0
  61. package/styles/_global-variables.scss +19 -1
  62. package/styles/_global.scss +1 -8
  63. package/styles/_reset.scss +2 -15
  64. package/styles/base.scss +0 -1
  65. package/styles/cove-main.scss +6 -0
  66. package/styles/filters.scss +6 -4
  67. package/styles/v2/components/ui/tooltip.scss +42 -40
  68. package/styles/v2/layout/_component.scss +0 -6
  69. package/styles/v2/layout/index.scss +0 -1
  70. package/types/Axis.ts +2 -0
  71. package/types/General.ts +1 -0
  72. package/types/Table.ts +2 -1
  73. package/types/Visualization.ts +13 -1
  74. package/types/VizFilter.ts +2 -1
  75. package/components/DataTable/components/Icons.tsx +0 -10
  76. package/components/_stories/EditorPanel.stories.tsx +0 -54
  77. package/components/_stories/Layout.Debug.stories.tsx +0 -91
@@ -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
+ })
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getNestedOptions } from '../getNestedOptions'
3
+ import { SubGrouping } from '../../../../types/VizFilter'
4
+ import { NestedOptions } from '../../../NestedDropdown/nestedDropdownHelpers'
5
+
6
+ describe('getNestedOptions', () => {
7
+ it('should return nested options when orderedValues is not provided', () => {
8
+ const params = {
9
+ values: ['value1', 'value2'],
10
+ subGrouping: null
11
+ }
12
+ const expectedOutput: NestedOptions = [
13
+ [['value1'], []],
14
+ [['value2'], []]
15
+ ]
16
+ expect(getNestedOptions(params)).toEqual(expectedOutput)
17
+ })
18
+
19
+ it('should return nested options when orderedValues is provided', () => {
20
+ const params = {
21
+ orderedValues: ['value2', 'value1'],
22
+ values: ['value1', 'value2'],
23
+ subGrouping: null
24
+ }
25
+ const expectedOutput: NestedOptions = [
26
+ [['value2'], []],
27
+ [['value1'], []]
28
+ ]
29
+ expect(getNestedOptions(params)).toEqual(expectedOutput)
30
+ })
31
+
32
+ it('should return nested options when subGrouping is not provided', () => {
33
+ const params = {
34
+ values: ['value1', 'value2'],
35
+ subGrouping: null
36
+ }
37
+ const expectedOutput: NestedOptions = [
38
+ [['value1'], []],
39
+ [['value2'], []]
40
+ ]
41
+ expect(getNestedOptions(params)).toEqual(expectedOutput)
42
+ })
43
+
44
+ it('should return nested options when subGrouping is provided with nested values', () => {
45
+ const subGrouping: SubGrouping = {
46
+ valuesLookup: {
47
+ value1: {
48
+ orderedValues: ['subValue2', 'subValue1'],
49
+ values: ['subValue1', 'subValue2']
50
+ },
51
+ value2: {
52
+ orderedValues: null,
53
+ values: ['subValue3']
54
+ }
55
+ }
56
+ }
57
+ const params = {
58
+ orderedValues: ['value1', 'value2'],
59
+ values: ['value1', 'value2'],
60
+ subGrouping
61
+ }
62
+ const expectedOutput: NestedOptions = [
63
+ [['value1'], [['subValue2'], ['subValue1']]],
64
+ [['value2'], [['subValue3']]]
65
+ ]
66
+ expect(getNestedOptions(params)).toEqual(expectedOutput)
67
+ })
68
+
69
+ it('should return nested options when subGrouping is provided without orderedValues', () => {
70
+ const subGrouping: SubGrouping = {
71
+ valuesLookup: {
72
+ value1: {
73
+ orderedValues: null,
74
+ values: ['subValue1', 'subValue2']
75
+ },
76
+ value2: {
77
+ orderedValues: null,
78
+ values: ['subValue3']
79
+ }
80
+ }
81
+ }
82
+ const params = {
83
+ orderedValues: ['value1', 'value2'],
84
+ values: ['value1', 'value2'],
85
+ subGrouping
86
+ }
87
+ const expectedOutput: NestedOptions = [
88
+ [['value1'], [['subValue1'], ['subValue2']]],
89
+ [['value2'], [['subValue3']]]
90
+ ]
91
+ expect(getNestedOptions(params)).toEqual(expectedOutput)
92
+ })
93
+ })
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { handleSorting } from '../handleSorting'
3
+ import _ from 'lodash'
4
+
5
+ describe('handleSorting', () => {
6
+ it('should use orderedValues when order is "cust" and filterStyle is not "nested-dropdown"', () => {
7
+ const singleFilter = {
8
+ values: ['value3', 'value1', 'value2'],
9
+ orderedValues: ['value1', 'value2', 'value3'],
10
+ order: 'cust',
11
+ filterStyle: 'someOtherStyle'
12
+ }
13
+
14
+ const result = handleSorting(singleFilter)
15
+
16
+ expect(result.values).toEqual(['value1', 'value2', 'value3'])
17
+ })
18
+
19
+ it('should sort values in ascending order by default', () => {
20
+ const singleFilter = {
21
+ values: ['value3', 'value1', 'value2'],
22
+ order: 'asc',
23
+ filterStyle: 'someOtherStyle'
24
+ }
25
+
26
+ const result = handleSorting(singleFilter)
27
+
28
+ expect(result.values).toEqual(['value1', 'value2', 'value3'])
29
+ })
30
+
31
+ it('should sort values in descending order when order is "desc"', () => {
32
+ const singleFilter = {
33
+ values: ['value3', 'value1', 'value2'],
34
+ order: 'desc',
35
+ filterStyle: 'someOtherStyle'
36
+ }
37
+
38
+ const result = handleSorting(singleFilter)
39
+
40
+ expect(result.values).toEqual(['value3', 'value2', 'value1'])
41
+ })
42
+
43
+ it('should not use orderedValues when filterStyle is "nested-dropdown"', () => {
44
+ const singleFilter = {
45
+ values: ['value3', 'value1', 'value2'],
46
+ orderedValues: ['value1', 'value2', 'value3'],
47
+ order: 'cust',
48
+ filterStyle: 'nested-dropdown'
49
+ }
50
+
51
+ const result = handleSorting(singleFilter)
52
+
53
+ expect(result.values).toEqual(['value1', 'value2', 'value3'])
54
+ })
55
+
56
+ it('should handle empty orderedValues when order is "cust"', () => {
57
+ const singleFilter = {
58
+ values: ['value3', 'value1', 'value2'],
59
+ orderedValues: [],
60
+ order: 'cust',
61
+ filterStyle: 'someOtherStyle'
62
+ }
63
+
64
+ const result = handleSorting(singleFilter)
65
+
66
+ expect(result.values).toEqual(['value3', 'value1', 'value2'])
67
+ })
68
+ })
@@ -0,0 +1,5 @@
1
+ export { default } from './Filters'
2
+
3
+ export { filterOrderOptions, filterStyleOptions, useFilters } from './Filters'
4
+
5
+ export { handleSorting } from './helpers/handleSorting'
@@ -1,5 +1,3 @@
1
- @import '@cdc/core/styles/v2/themes/_color-definitions.scss';
2
-
3
1
  .cdc-editor .configure .type-dashboard .sidebar {
4
2
  top: 0;
5
3
  }
@@ -210,7 +208,7 @@
210
208
 
211
209
  svg {
212
210
  width: 60px;
213
- color: $blue;
211
+ color: var(--blue);
214
212
  margin-right: 1rem;
215
213
  height: 60px; // IE11
216
214
  path {
@@ -3,6 +3,7 @@ import { Text } from '@visx/text'
3
3
  import { type ViewportSize, type MapConfig } from '@cdc/map/src/types/MapConfig'
4
4
  import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
5
5
  import { getGradientLegendWidth } from '@cdc/core/helpers/getGradientLegendWidth'
6
+ import { getTextWidth } from '../../helpers/getTextWidth'
6
7
  import { DimensionsType } from '../../types/Dimensions'
7
8
 
8
9
  type CombinedConfig = MapConfig | ChartConfig
@@ -13,17 +14,9 @@ interface GradientProps {
13
14
  config: CombinedConfig
14
15
  dimensions: DimensionsType
15
16
  currentViewport: ViewportSize
16
- getTextWidth: (text: string, font: string) => string
17
17
  }
18
18
 
19
- const LegendGradient = ({
20
- labels,
21
- colors,
22
- config,
23
- dimensions,
24
- currentViewport,
25
- getTextWidth
26
- }: GradientProps): JSX.Element => {
19
+ const LegendGradient = ({ labels, colors, config, dimensions, currentViewport }: GradientProps): JSX.Element => {
27
20
  let [width] = dimensions
28
21
 
29
22
  const legendWidth = getGradientLegendWidth(width, currentViewport)
@@ -0,0 +1,33 @@
1
+ import React, { useEffect, useRef } from 'react'
2
+ import './loader.styles.css'
3
+
4
+ type LoaderProps = {
5
+ fullScreen?: boolean
6
+ }
7
+
8
+ const Spinner = () => (
9
+ <div className='spinner-border text-primary' role='status'>
10
+ <span className='sr-only'>Loading...</span>
11
+ </div>
12
+ )
13
+
14
+ const Loader: React.FC<LoaderProps> = ({ fullScreen = false }) => {
15
+ const backgroundRef = useRef(null)
16
+
17
+ useEffect(() => {
18
+ if (backgroundRef?.current) {
19
+ const backgroundHeight = backgroundRef.current.parentElement.clientHeight
20
+ backgroundRef.current.style.height = `${backgroundHeight}px`
21
+ }
22
+ }, [])
23
+
24
+ return fullScreen ? (
25
+ <div ref={backgroundRef} className='cove-loader fullscreen'>
26
+ <Spinner />
27
+ </div>
28
+ ) : (
29
+ <Spinner />
30
+ )
31
+ }
32
+
33
+ export default Loader
@@ -0,0 +1 @@
1
+ export { default } from './Loader'
@@ -0,0 +1,13 @@
1
+ .cove-loader {
2
+ &.fullscreen {
3
+ background: rgba(255, 255, 255, 0.8);
4
+ position: absolute;
5
+ width: 100%;
6
+ display: flex;
7
+ justify-content: center;
8
+ z-index: 100;
9
+ & > * {
10
+ margin-top: 40vh;
11
+ }
12
+ }
13
+ }
@@ -1,15 +1,15 @@
1
1
  import { useState, useEffect, useRef, useMemo } from 'react'
2
2
  import './nesteddropdown.styles.css'
3
3
  import Icon from '@cdc/core/components/ui/Icon'
4
- import { VizFilter } from '../../types/VizFilter'
4
+ import { filterSearchTerm, NestedOptions, ValueTextPair } from './nestedDropdownHelpers'
5
5
 
6
6
  const Options: React.FC<{
7
- currentOptions: (string | number)[]
7
+ subOptions: ValueTextPair[]
8
8
  label: string
9
9
  handleSubGroupSelect: Function
10
10
  userSelectedLabel: string
11
11
  userSearchTerm: string
12
- }> = ({ currentOptions = [], label, handleSubGroupSelect, userSelectedLabel, userSearchTerm }) => {
12
+ }> = ({ subOptions, label, handleSubGroupSelect, userSelectedLabel, userSearchTerm }) => {
13
13
  const [isTierOneExpanded, setIsTierOneExpanded] = useState(true)
14
14
  const checkMark = <>&#10004;</>
15
15
 
@@ -29,22 +29,45 @@ const Options: React.FC<{
29
29
  if (currentItem.className === 'selectable-item') currentItem.parentNode.parentNode.focus()
30
30
  setIsTierOneExpanded(false)
31
31
  } else if (e.key === 'Enter') {
32
- currentItem.className === 'selectable-item' ? handleSubGroupSelect(currentItem.dataset.value) : setIsTierOneExpanded(!isTierOneExpanded)
32
+ currentItem.className === 'selectable-item'
33
+ ? handleSubGroupSelect(currentItem.dataset.value)
34
+ : setIsTierOneExpanded(!isTierOneExpanded)
33
35
  }
34
36
  }
35
37
 
36
38
  return (
37
39
  <>
38
- <li role='treeitem' key={label} tabIndex={0} aria-label={label} onClick={handleGroupClick} onKeyUp={handleKeyUp} className='nested-dropdown-group'>
40
+ <li
41
+ role='treeitem'
42
+ key={label}
43
+ tabIndex={0}
44
+ aria-label={label}
45
+ onClick={handleGroupClick}
46
+ onKeyUp={handleKeyUp}
47
+ className='nested-dropdown-group'
48
+ >
39
49
  <span className={'font-weight-bold'}>{label} </span>
40
50
  {
41
51
  <span className='list-arrow' aria-hidden='true'>
42
- {isTierOneExpanded ? <Icon display='caretFilledUp' /> : <Icon display='caretFilledDown' />}
52
+ {isTierOneExpanded ? (
53
+ <Icon display='caretFilledUp' alt='arrow pointing up' />
54
+ ) : (
55
+ <Icon display='caretFilledDown' alt='arrow pointing down' />
56
+ )}
43
57
  </span>
44
58
  }
45
- <ul aria-expanded={isTierOneExpanded} role='group' tabIndex={-1} aria-labelledby={label} className={isTierOneExpanded ? '' : 'hide'}>
46
- {currentOptions.map((tierTwo, tierTwoIndex) => {
47
- const regionID = label + tierTwo
59
+ <ul
60
+ aria-expanded={isTierOneExpanded}
61
+ role='group'
62
+ tabIndex={-1}
63
+ aria-labelledby={label}
64
+ className={isTierOneExpanded ? '' : 'hide'}
65
+ >
66
+ {subOptions.map(subGroup => {
67
+ const [value, text] = subGroup
68
+ const subGroupText = text || value
69
+
70
+ const regionID = label + value
48
71
  const isSelected = regionID === userSelectedLabel
49
72
 
50
73
  return (
@@ -55,9 +78,9 @@ const Options: React.FC<{
55
78
  role='treeitem'
56
79
  aria-label={regionID}
57
80
  aria-selected={isSelected}
58
- data-value={tierTwo}
81
+ data-value={value}
59
82
  onClick={e => {
60
- handleSubGroupSelect(tierTwo)
83
+ handleSubGroupSelect(value)
61
84
  }}
62
85
  >
63
86
  {isSelected ? (
@@ -68,7 +91,7 @@ const Options: React.FC<{
68
91
  ''
69
92
  )}
70
93
 
71
- {tierTwo}
94
+ {subGroupText}
72
95
  </li>
73
96
  )
74
97
  })}
@@ -78,44 +101,44 @@ const Options: React.FC<{
78
101
  )
79
102
  }
80
103
 
81
- interface NestedDropdownProps {
104
+ type NestedDropdownProps = {
105
+ activeGroup: string
106
+ activeSubGroup?: string
82
107
  isEditor?: boolean
83
- currentFilter: VizFilter
108
+ isUrlFilter?: boolean
84
109
  listLabel: string
85
- handleSelectedItems: Function
110
+ handleSelectedItems: ([group, subgroup]: [string, string]) => void
111
+ options: NestedOptions
112
+ subGroupingActive?: string
86
113
  }
87
114
 
88
- type OptionsMemo = [string, (string | number)[]][]
89
-
90
- const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabel, handleSelectedItems }) => {
91
- const optsMemo: OptionsMemo = useMemo(() => {
92
- // keep custom ordered value order
93
- const values = currentFilter.orderedValues?.filter(value => currentFilter.values.includes(value)) || currentFilter.values
94
- return values.map(value => {
95
- if (!currentFilter.subGrouping) return [value, []]
96
- const { orderedValues, values } = currentFilter.subGrouping.valuesLookup[value]
97
- const subFilterValues = orderedValues?.filter(value => values.includes(value)) || values
98
- return [value, subFilterValues]
99
- })
100
- }, [currentFilter, currentFilter.subGrouping])
101
- const groupFilterActive = currentFilter.active
102
- const subGroupFilterActive = currentFilter.subGrouping?.active ?? ''
115
+ const NestedDropdown: React.FC<NestedDropdownProps> = ({
116
+ options,
117
+ activeGroup,
118
+ activeSubGroup,
119
+ listLabel,
120
+ handleSelectedItems
121
+ }) => {
122
+ const groupFilterActive = activeGroup
123
+ const subGroupFilterActive = activeSubGroup || ''
103
124
 
104
125
  const [userSearchTerm, setUserSearchTerm] = useState('')
105
- const [inputValue, setInputValue] = useState(subGroupFilterActive !== '' ? `${groupFilterActive} - ${subGroupFilterActive}` : 'Select an Option')
126
+ const [inputValue, setInputValue] = useState(
127
+ subGroupFilterActive !== '' ? `${groupFilterActive} - ${subGroupFilterActive}` : ''
128
+ )
106
129
  const [inputHasFocus, setInputHasFocus] = useState(false)
107
130
  const [isListOpened, setIsListOpened] = useState(false)
108
131
 
109
132
  const searchInput = useRef(null)
110
133
  const searchDropdown = useRef(null)
111
134
 
112
- const chooseSelectedSubGroup = (tierOne: string, tierTwo: string) => {
135
+ const chooseSelectedSubGroup = (tierOne: string | number, tierTwo: string | number) => {
113
136
  searchInput.current.focus()
114
137
  const selectedItemValue = `${tierOne} - ${tierTwo}`
115
138
  setUserSearchTerm('')
116
139
  setIsListOpened(false)
117
140
  setInputValue(selectedItemValue)
118
- handleSelectedItems([tierOne, tierTwo])
141
+ handleSelectedItems([String(tierOne), String(tierTwo)])
119
142
  }
120
143
 
121
144
  const handleKeyUp = e => {
@@ -158,7 +181,8 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
158
181
  itemToFocusOnAfterKeyUp.focus()
159
182
  } else if (previousSibling) {
160
183
  // Move focus to previous collapsed Tier One or Move focus from Tier One to the last of the previous Tier Two's items
161
- const itemToFocusOnAfterKeyUp = previousSibling.lastChild.className === 'hide' ? previousSibling : previousSibling.lastChild.lastChild
184
+ const itemToFocusOnAfterKeyUp =
185
+ previousSibling.lastChild.className === 'hide' ? previousSibling : previousSibling.lastChild.lastChild
162
186
  itemToFocusOnAfterKeyUp.focus()
163
187
  } else {
164
188
  // Move focus from top of the dropdown to Input
@@ -191,10 +215,8 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
191
215
  }
192
216
 
193
217
  const filterOptions: OptionsMemo = useMemo(() => {
194
- if (!userSearchTerm) return optsMemo
195
- const newRegex = new RegExp(`^${userSearchTerm}`, 'i')
196
- return optsMemo.filter(([tierOne, tierTwo]) => tierOne.match(newRegex) || tierTwo.some(value => String(value).match(newRegex)))
197
- }, [userSearchTerm])
218
+ return filterSearchTerm(userSearchTerm, options)
219
+ }, [userSearchTerm, options])
198
220
 
199
221
  const handleSearchTermChange = e => {
200
222
  const newSearchTerm = e.target.value
@@ -205,7 +227,12 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
205
227
 
206
228
  return (
207
229
  <>
208
- <div id='nested-dropdown-container' className='nested-dropdown' onKeyUp={handleKeyUp}>
230
+ {listLabel && <span className='edit-label column-heading'>{listLabel}</span>}
231
+ <div
232
+ id='nested-dropdown-container'
233
+ className={`nested-dropdown ${isListOpened ? 'open-filter' : ''}`}
234
+ onKeyUp={handleKeyUp}
235
+ >
209
236
  <div className='nested-dropdown-input-container' aria-label='searchInput' role='textbox'>
210
237
  <input
211
238
  className='search-input'
@@ -216,7 +243,7 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
216
243
  tabIndex={0}
217
244
  value={inputValue}
218
245
  onChange={handleSearchTermChange}
219
- placeholder={'Select or type to search'}
246
+ placeholder={'Select an Option'}
220
247
  onClick={() => {
221
248
  if (inputHasFocus) setIsListOpened(!isListOpened)
222
249
  }}
@@ -224,18 +251,33 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
224
251
  onBlur={() => setInputHasFocus(false)}
225
252
  />
226
253
  <span className='list-arrow' aria-hidden={true}>
227
- {isListOpened ? <Icon display='caretFilledUp' /> : <Icon display='caretFilledDown' />}
254
+ {isListOpened ? (
255
+ <Icon display='caretFilledUp' alt='arrow pointing up' />
256
+ ) : (
257
+ <Icon display='caretFilledDown' alt='arrow pointing down' />
258
+ )}
228
259
  </span>
229
260
  </div>
230
- <ul role='tree' key={listLabel} tabIndex={-1} aria-labelledby='main-nested-dropdown' aria-expanded={isListOpened} ref={searchDropdown} className={`main-nested-dropdown-container ${isListOpened ? '' : 'hide'}`}>
231
- {filterOptions?.length
232
- ? filterOptions.map(([groupName, options]) => {
261
+ <ul
262
+ role='tree'
263
+ key={listLabel}
264
+ tabIndex={-1}
265
+ aria-labelledby='main-nested-dropdown'
266
+ aria-expanded={isListOpened}
267
+ ref={searchDropdown}
268
+ className={`main-nested-dropdown-container ${isListOpened ? '' : 'hide'}`}
269
+ >
270
+ {filterOptions.length
271
+ ? filterOptions.map(([group, subgroup], index) => {
272
+ const [groupValue, groupText] = group
273
+ const groupTextValue = String(groupText || groupValue)
233
274
  return (
234
275
  <Options
235
- currentOptions={options}
236
- label={groupName}
237
- handleSubGroupSelect={(subGroupValue: string) => {
238
- chooseSelectedSubGroup(groupName, subGroupValue)
276
+ key={groupTextValue + '_' + index}
277
+ subOptions={subgroup}
278
+ label={groupTextValue}
279
+ handleSubGroupSelect={subGroupValue => {
280
+ chooseSelectedSubGroup(groupValue, subGroupValue)
239
281
  }}
240
282
  userSelectedLabel={groupFilterActive + subGroupFilterActive}
241
283
  userSearchTerm={userSearchTerm}
@@ -0,0 +1,34 @@
1
+ import _ from 'lodash'
2
+
3
+ export type ValueTextPair = [string | number, string | number | undefined] | [string | number] // [value, text]
4
+
5
+ export type NestedOptions = Array<[ValueTextPair, ValueTextPair[]]>
6
+
7
+ export const filterSearchTerm = (userSearchTerm: string, optsMemo: NestedOptions): NestedOptions => {
8
+ if (userSearchTerm === undefined || userSearchTerm === '') return optsMemo || ([] as NestedOptions)
9
+ const newRegex = new RegExp(`^${userSearchTerm}`, 'i')
10
+ const newOptsMemoTierOneFiltered = optsMemo.filter(([group, subGroups]) => {
11
+ const [groupValue, groupText] = group
12
+ const _groupText = String(groupText || groupValue)
13
+ return (
14
+ _groupText.match(newRegex) ||
15
+ subGroups.some(([value, text]) => {
16
+ const subGroupText = String(text || value)
17
+ return subGroupText.match(newRegex)
18
+ })
19
+ )
20
+ })
21
+
22
+ const filterOptions: NestedOptions = newOptsMemoTierOneFiltered.map(([group, subGroups]) => {
23
+ const [groupValue, groupText] = group
24
+ const _groupText = String(groupText || groupValue)
25
+ if (_groupText.match(newRegex)) return [group, subGroups]
26
+ const newOptions = subGroups.filter(([value, text]) => {
27
+ const subGroupText = text || value
28
+ return String(subGroupText).match(newRegex)
29
+ })
30
+ return [group, newOptions]
31
+ })
32
+
33
+ return filterOptions
34
+ }
@@ -1,3 +1,10 @@
1
+ .cdc-dashboard-inner-container.cove-component__content.cove-dashboard-filters-container:has(
2
+ > .nested-dropdown.open-filter
3
+ )
4
+ :has(> .nested-dropdown-container) {
5
+ z-index: 9;
6
+ }
7
+
1
8
  .nested-dropdown {
2
9
  .nested-dropdown-group {
3
10
  list-style: none;