@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.
- package/LICENSE +201 -0
- package/assets/icon-combo-chart.svg +1 -0
- package/assets/icon-epi-chart.svg +27 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +17 -13
- package/components/Alert/components/Alert.tsx +34 -8
- package/components/BlurStrokeText.tsx +44 -0
- package/components/DataTable/DataTable.tsx +62 -36
- package/components/DataTable/DataTableStandAlone.tsx +37 -6
- package/components/DataTable/components/ChartHeader.tsx +31 -26
- package/components/DataTable/components/MapHeader.tsx +19 -10
- package/components/DataTable/components/SortIcon/index.tsx +25 -0
- package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
- package/{styles/_data-table.scss → components/DataTable/data-table.css} +250 -298
- package/components/DataTable/helpers/boxplotCellMatrix.tsx +14 -13
- package/components/DataTable/helpers/customSort.ts +11 -15
- package/components/DataTable/helpers/getChartCellValue.ts +23 -5
- package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
- package/components/DataTable/helpers/getNewSortBy.ts +35 -0
- package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
- package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
- package/components/EditorPanel/ColumnsEditor.tsx +81 -36
- package/components/EditorPanel/DataTableEditor.tsx +149 -43
- package/components/EditorPanel/FieldSetWrapper.tsx +2 -2
- package/components/EditorPanel/Inputs.tsx +68 -20
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +30 -55
- package/components/{Filters.tsx → Filters/Filters.tsx} +60 -43
- package/components/Filters/helpers/applyQueuedActive.ts +12 -0
- package/components/Filters/helpers/getNestedOptions.ts +29 -0
- package/components/Filters/helpers/handleSorting.ts +18 -0
- package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
- package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
- package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
- package/components/Filters/index.ts +5 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -7
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/Legend/Legend.Gradient.tsx +44 -36
- package/components/Loader/Loader.tsx +33 -0
- package/components/Loader/index.ts +1 -0
- package/components/Loader/loader.styles.css +13 -0
- package/components/MultiSelect/MultiSelect.tsx +85 -62
- package/components/MultiSelect/multiselect.styles.css +10 -7
- package/components/NestedDropdown/NestedDropdown.tsx +118 -56
- package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
- package/components/NestedDropdown/nesteddropdown.styles.css +22 -13
- package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
- package/components/Table/Table.tsx +102 -34
- package/components/Table/components/GroupRow.tsx +1 -1
- package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
- package/components/_stories/DataTable.stories.tsx +14 -0
- package/components/_stories/Filters.stories.tsx +57 -0
- package/components/_stories/NestedDropdown.stories.tsx +22 -46
- package/components/_stories/_mocks/DataTable/no-data.json +108 -0
- package/components/_stories/_mocks/nested-dropdown.json +30 -0
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Icon.tsx +19 -6
- package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
- package/data/colorPalettes.js +107 -10
- package/dist/cove-main.css +6080 -0
- package/dist/cove-main.css.map +1 -0
- package/helpers/DataTransform.ts +2 -1
- package/helpers/addValuesToFilters.ts +8 -3
- package/helpers/cove/{number.js → number.ts} +62 -27
- package/helpers/coveUpdateWorker.ts +6 -7
- package/helpers/fetchRemoteData.js +32 -37
- package/helpers/formatConfigBeforeSave.ts +17 -1
- package/helpers/gatherQueryParams.ts +12 -2
- package/helpers/pivotData.ts +52 -11
- package/helpers/queryStringUtils.ts +6 -0
- package/helpers/tests/gatherQueryParams.test.ts +34 -0
- package/helpers/tests/pivotData.test.ts +50 -0
- package/helpers/useDataVizClasses.ts +42 -20
- package/helpers/ver/4.24.10.ts +47 -0
- package/helpers/ver/4.24.9.ts +0 -3
- package/helpers/ver/tests/4.24.10.test.ts +45 -0
- package/helpers/viewports.ts +9 -0
- package/package.json +7 -3
- package/styles/_button-section.scss +5 -1
- package/styles/_global-variables.scss +20 -2
- package/styles/_global.scss +22 -30
- package/styles/_reset.scss +2 -26
- package/styles/base.scss +0 -1
- package/styles/cove-main.scss +6 -0
- package/styles/filters.scss +6 -26
- package/styles/v2/base/_reset.scss +0 -7
- package/styles/v2/components/editor.scss +0 -4
- package/styles/v2/components/icon.scss +1 -1
- package/styles/v2/components/ui/tooltip.scss +42 -40
- package/styles/v2/layout/_component.scss +0 -6
- package/styles/v2/layout/index.scss +0 -1
- package/types/Axis.ts +4 -0
- package/types/BoxPlot.ts +5 -3
- package/types/Color.ts +1 -1
- package/types/General.ts +1 -0
- package/types/Legend.ts +1 -2
- package/types/MarkupInclude.ts +1 -0
- package/types/Runtime.ts +3 -1
- package/types/Series.ts +8 -1
- package/types/Table.ts +3 -2
- package/types/Visualization.ts +19 -8
- package/types/VizFilter.ts +2 -1
- package/components/DataTable/components/Icons.tsx +0 -10
- package/components/_stories/EditorPanel.stories.tsx +0 -54
- package/components/_stories/Layout.Debug.stories.tsx +0 -91
- package/components/ui/Select.jsx +0 -30
- package/helpers/getGradientLegendWidth.ts +0 -15
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useMemo } from 'react'
|
|
1
|
+
import { useState, useEffect, useRef, useMemo, useId } from 'react'
|
|
2
2
|
import './nesteddropdown.styles.css'
|
|
3
3
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
4
|
-
import {
|
|
4
|
+
import { filterSearchTerm, NestedOptions, ValueTextPair } from './nestedDropdownHelpers'
|
|
5
5
|
|
|
6
6
|
const Options: React.FC<{
|
|
7
|
-
|
|
7
|
+
subOptions: ValueTextPair[]
|
|
8
|
+
filterIndex: number
|
|
8
9
|
label: string
|
|
9
10
|
handleSubGroupSelect: Function
|
|
10
11
|
userSelectedLabel: string
|
|
11
12
|
userSearchTerm: string
|
|
12
|
-
}> = ({
|
|
13
|
+
}> = ({ subOptions, filterIndex, label, handleSubGroupSelect, userSelectedLabel, userSearchTerm }) => {
|
|
13
14
|
const [isTierOneExpanded, setIsTierOneExpanded] = useState(true)
|
|
14
15
|
const checkMark = <>✔</>
|
|
15
16
|
|
|
@@ -18,7 +19,7 @@ const Options: React.FC<{
|
|
|
18
19
|
}, [userSearchTerm])
|
|
19
20
|
|
|
20
21
|
const handleGroupClick = e => {
|
|
21
|
-
const leaveExpanded = e.target.className ===
|
|
22
|
+
const leaveExpanded = e.target.className === `selectable-item-${filterIndex}` ? true : !isTierOneExpanded
|
|
22
23
|
setIsTierOneExpanded(leaveExpanded)
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -26,38 +27,61 @@ const Options: React.FC<{
|
|
|
26
27
|
const currentItem = e.target
|
|
27
28
|
if (e.key === 'ArrowRight') setIsTierOneExpanded(true)
|
|
28
29
|
else if (e.key === 'ArrowLeft') {
|
|
29
|
-
if (currentItem.className ===
|
|
30
|
+
if (currentItem.className === `selectable-item-${filterIndex}`) currentItem.parentNode.parentNode.focus()
|
|
30
31
|
setIsTierOneExpanded(false)
|
|
31
32
|
} else if (e.key === 'Enter') {
|
|
32
|
-
currentItem.className ===
|
|
33
|
+
currentItem.className === `selectable-item-${filterIndex}`
|
|
34
|
+
? handleSubGroupSelect(currentItem.dataset.value)
|
|
35
|
+
: setIsTierOneExpanded(!isTierOneExpanded)
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
return (
|
|
37
40
|
<>
|
|
38
|
-
<li
|
|
41
|
+
<li
|
|
42
|
+
role='treeitem'
|
|
43
|
+
key={label}
|
|
44
|
+
tabIndex={0}
|
|
45
|
+
aria-label={label}
|
|
46
|
+
onClick={handleGroupClick}
|
|
47
|
+
onKeyUp={handleKeyUp}
|
|
48
|
+
className={`nested-dropdown-group-${filterIndex}`}
|
|
49
|
+
>
|
|
39
50
|
<span className={'font-weight-bold'}>{label} </span>
|
|
40
51
|
{
|
|
41
52
|
<span className='list-arrow' aria-hidden='true'>
|
|
42
|
-
{isTierOneExpanded ?
|
|
53
|
+
{isTierOneExpanded ? (
|
|
54
|
+
<Icon display='caretFilledUp' alt='arrow pointing up' />
|
|
55
|
+
) : (
|
|
56
|
+
<Icon display='caretFilledDown' alt='arrow pointing down' />
|
|
57
|
+
)}
|
|
43
58
|
</span>
|
|
44
59
|
}
|
|
45
|
-
<ul
|
|
46
|
-
{
|
|
47
|
-
|
|
60
|
+
<ul
|
|
61
|
+
aria-expanded={isTierOneExpanded}
|
|
62
|
+
role='group'
|
|
63
|
+
tabIndex={-1}
|
|
64
|
+
aria-labelledby={label}
|
|
65
|
+
className={isTierOneExpanded ? '' : 'hide'}
|
|
66
|
+
>
|
|
67
|
+
{subOptions.map(subGroup => {
|
|
68
|
+
const [value, text] = subGroup
|
|
69
|
+
const subGroupText = text || value
|
|
70
|
+
|
|
71
|
+
const regionID = label + value
|
|
48
72
|
const isSelected = regionID === userSelectedLabel
|
|
49
73
|
|
|
50
74
|
return (
|
|
51
75
|
<li
|
|
52
76
|
key={regionID}
|
|
53
|
-
className=
|
|
77
|
+
className={`selectable-item-${filterIndex}`}
|
|
54
78
|
tabIndex={0}
|
|
55
79
|
role='treeitem'
|
|
56
80
|
aria-label={regionID}
|
|
57
81
|
aria-selected={isSelected}
|
|
58
|
-
data-value={
|
|
82
|
+
data-value={value}
|
|
59
83
|
onClick={e => {
|
|
60
|
-
handleSubGroupSelect(
|
|
84
|
+
handleSubGroupSelect(value)
|
|
61
85
|
}}
|
|
62
86
|
>
|
|
63
87
|
{isSelected ? (
|
|
@@ -68,7 +92,7 @@ const Options: React.FC<{
|
|
|
68
92
|
''
|
|
69
93
|
)}
|
|
70
94
|
|
|
71
|
-
{
|
|
95
|
+
{subGroupText}
|
|
72
96
|
</li>
|
|
73
97
|
)
|
|
74
98
|
})}
|
|
@@ -78,44 +102,47 @@ const Options: React.FC<{
|
|
|
78
102
|
)
|
|
79
103
|
}
|
|
80
104
|
|
|
81
|
-
|
|
105
|
+
type NestedDropdownProps = {
|
|
106
|
+
activeGroup: string
|
|
107
|
+
activeSubGroup?: string
|
|
108
|
+
filterIndex: number
|
|
82
109
|
isEditor?: boolean
|
|
83
|
-
|
|
110
|
+
isUrlFilter?: boolean
|
|
84
111
|
listLabel: string
|
|
85
|
-
handleSelectedItems:
|
|
112
|
+
handleSelectedItems: ([group, subgroup]: [string, string]) => void
|
|
113
|
+
options: NestedOptions
|
|
114
|
+
subGroupingActive?: string
|
|
86
115
|
}
|
|
87
116
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
})
|
|
100
|
-
}, [currentFilter, currentFilter.subGrouping])
|
|
101
|
-
const groupFilterActive = currentFilter.active
|
|
102
|
-
const subGroupFilterActive = currentFilter.subGrouping?.active ?? ''
|
|
117
|
+
const NestedDropdown: React.FC<NestedDropdownProps> = ({
|
|
118
|
+
options,
|
|
119
|
+
activeGroup,
|
|
120
|
+
activeSubGroup,
|
|
121
|
+
filterIndex,
|
|
122
|
+
listLabel,
|
|
123
|
+
handleSelectedItems
|
|
124
|
+
}) => {
|
|
125
|
+
const dropdownId = useId()
|
|
126
|
+
const groupFilterActive = activeGroup
|
|
127
|
+
const subGroupFilterActive = activeSubGroup || ''
|
|
103
128
|
|
|
104
129
|
const [userSearchTerm, setUserSearchTerm] = useState('')
|
|
105
|
-
const [inputValue, setInputValue] = useState(
|
|
130
|
+
const [inputValue, setInputValue] = useState(
|
|
131
|
+
subGroupFilterActive !== '' ? `${groupFilterActive} - ${subGroupFilterActive}` : ''
|
|
132
|
+
)
|
|
106
133
|
const [inputHasFocus, setInputHasFocus] = useState(false)
|
|
107
134
|
const [isListOpened, setIsListOpened] = useState(false)
|
|
108
135
|
|
|
109
136
|
const searchInput = useRef(null)
|
|
110
137
|
const searchDropdown = useRef(null)
|
|
111
138
|
|
|
112
|
-
const chooseSelectedSubGroup = (tierOne: string, tierTwo: string) => {
|
|
139
|
+
const chooseSelectedSubGroup = (tierOne: string | number, tierTwo: string | number) => {
|
|
113
140
|
searchInput.current.focus()
|
|
114
141
|
const selectedItemValue = `${tierOne} - ${tierTwo}`
|
|
115
142
|
setUserSearchTerm('')
|
|
116
143
|
setIsListOpened(false)
|
|
117
144
|
setInputValue(selectedItemValue)
|
|
118
|
-
handleSelectedItems([tierOne, tierTwo])
|
|
145
|
+
handleSelectedItems([String(tierOne), String(tierTwo)])
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
const handleKeyUp = e => {
|
|
@@ -127,7 +154,7 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
|
|
|
127
154
|
setIsListOpened(true)
|
|
128
155
|
// Move focus from Input to top of dropdown
|
|
129
156
|
Dropdown.firstChild.focus()
|
|
130
|
-
} else if (className ===
|
|
157
|
+
} else if (className === `selectable-item-${filterIndex}`) {
|
|
131
158
|
// Move focus to next item on list: next Tier Two item or the next Tier One or SearchInput
|
|
132
159
|
const itemToFocusOnAfterKeyUp = nextSibling ?? parentNode.parentNode.nextSibling ?? searchInput.current
|
|
133
160
|
itemToFocusOnAfterKeyUp.focus()
|
|
@@ -152,13 +179,14 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
|
|
|
152
179
|
// Move focus to last item of the last collapsed Tier Two in dropdown
|
|
153
180
|
Dropdown.lastChild.lastChild.lastChild.focus()
|
|
154
181
|
}
|
|
155
|
-
} else if (className ===
|
|
182
|
+
} else if (className === `selectable-item-${filterIndex}`) {
|
|
156
183
|
// Move focus to previous Tier Two or Move focus to current Tier One
|
|
157
184
|
const itemToFocusOnAfterKeyUp = previousSibling ?? parentNode.parentNode
|
|
158
185
|
itemToFocusOnAfterKeyUp.focus()
|
|
159
186
|
} else if (previousSibling) {
|
|
160
187
|
// 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 =
|
|
188
|
+
const itemToFocusOnAfterKeyUp =
|
|
189
|
+
previousSibling.lastChild.className === 'hide' ? previousSibling : previousSibling.lastChild.lastChild
|
|
162
190
|
itemToFocusOnAfterKeyUp.focus()
|
|
163
191
|
} else {
|
|
164
192
|
// Move focus from top of the dropdown to Input
|
|
@@ -190,11 +218,9 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
|
|
|
190
218
|
}
|
|
191
219
|
}
|
|
192
220
|
|
|
193
|
-
const filterOptions
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return optsMemo.filter(([tierOne, tierTwo]) => tierOne.match(newRegex) || tierTwo.some(value => String(value).match(newRegex)))
|
|
197
|
-
}, [userSearchTerm])
|
|
221
|
+
const filterOptions = useMemo(() => {
|
|
222
|
+
return filterSearchTerm(userSearchTerm, options)
|
|
223
|
+
}, [userSearchTerm, options])
|
|
198
224
|
|
|
199
225
|
const handleSearchTermChange = e => {
|
|
200
226
|
const newSearchTerm = e.target.value
|
|
@@ -203,11 +229,35 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
|
|
|
203
229
|
setInputValue(newSearchTerm)
|
|
204
230
|
}
|
|
205
231
|
|
|
232
|
+
const handleOnBlur = e => {
|
|
233
|
+
if (
|
|
234
|
+
e.relatedTarget === null ||
|
|
235
|
+
![
|
|
236
|
+
`nested-dropdown-${filterIndex}`,
|
|
237
|
+
`nested-dropdown-group-${filterIndex}`,
|
|
238
|
+
`selectable-item-${filterIndex}`
|
|
239
|
+
].includes(e.relatedTarget.className)
|
|
240
|
+
) {
|
|
241
|
+
setInputHasFocus(false)
|
|
242
|
+
setIsListOpened(false)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
206
246
|
return (
|
|
207
247
|
<>
|
|
208
|
-
|
|
248
|
+
{listLabel && (
|
|
249
|
+
<label className='text-capitalize font-weight-bold' htmlFor={dropdownId}>
|
|
250
|
+
{listLabel}
|
|
251
|
+
</label>
|
|
252
|
+
)}
|
|
253
|
+
<div
|
|
254
|
+
id={dropdownId}
|
|
255
|
+
className={`nested-dropdown nested-dropdown-${filterIndex} ${isListOpened ? 'open-filter' : ''}`}
|
|
256
|
+
onKeyUp={handleKeyUp}
|
|
257
|
+
>
|
|
209
258
|
<div className='nested-dropdown-input-container' aria-label='searchInput' role='textbox'>
|
|
210
259
|
<input
|
|
260
|
+
id={`nested-dropdown-${filterIndex}`}
|
|
211
261
|
className='search-input'
|
|
212
262
|
ref={searchInput}
|
|
213
263
|
aria-label='searchInput'
|
|
@@ -216,26 +266,38 @@ const NestedDropdown: React.FC<NestedDropdownProps> = ({ currentFilter, listLabe
|
|
|
216
266
|
tabIndex={0}
|
|
217
267
|
value={inputValue}
|
|
218
268
|
onChange={handleSearchTermChange}
|
|
219
|
-
placeholder={'Select
|
|
269
|
+
placeholder={'- Select -'}
|
|
220
270
|
onClick={() => {
|
|
221
271
|
if (inputHasFocus) setIsListOpened(!isListOpened)
|
|
222
272
|
}}
|
|
223
273
|
onFocus={() => setInputHasFocus(true)}
|
|
224
|
-
onBlur={
|
|
274
|
+
onBlur={e => handleOnBlur(e)}
|
|
225
275
|
/>
|
|
226
276
|
<span className='list-arrow' aria-hidden={true}>
|
|
227
|
-
|
|
277
|
+
<Icon display='caretDown' />
|
|
228
278
|
</span>
|
|
229
279
|
</div>
|
|
230
|
-
<ul
|
|
231
|
-
|
|
232
|
-
|
|
280
|
+
<ul
|
|
281
|
+
role='tree'
|
|
282
|
+
key={listLabel}
|
|
283
|
+
tabIndex={-1}
|
|
284
|
+
aria-labelledby='main-nested-dropdown'
|
|
285
|
+
aria-expanded={isListOpened}
|
|
286
|
+
ref={searchDropdown}
|
|
287
|
+
className={`main-nested-dropdown-container ${isListOpened ? '' : 'hide'}`}
|
|
288
|
+
>
|
|
289
|
+
{filterOptions.length
|
|
290
|
+
? filterOptions.map(([group, subgroup], index) => {
|
|
291
|
+
const [groupValue, groupText] = group
|
|
292
|
+
const groupTextValue = String(groupText || groupValue)
|
|
233
293
|
return (
|
|
234
294
|
<Options
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
295
|
+
key={groupTextValue + '_' + index}
|
|
296
|
+
subOptions={subgroup}
|
|
297
|
+
filterIndex={filterIndex}
|
|
298
|
+
label={groupTextValue}
|
|
299
|
+
handleSubGroupSelect={subGroupValue => {
|
|
300
|
+
chooseSelectedSubGroup(groupValue, subGroupValue)
|
|
239
301
|
}}
|
|
240
302
|
userSelectedLabel={groupFilterActive + subGroupFilterActive}
|
|
241
303
|
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,5 +1,12 @@
|
|
|
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
|
+
[class^='nested-dropdown-group-'] {
|
|
3
10
|
list-style: none;
|
|
4
11
|
}
|
|
5
12
|
|
|
@@ -8,19 +15,19 @@
|
|
|
8
15
|
position: relative;
|
|
9
16
|
display: inline-block;
|
|
10
17
|
width: 100%;
|
|
18
|
+
padding: 0;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
.main-nested-dropdown-container,
|
|
14
22
|
.nested-dropdown-input-container {
|
|
15
|
-
padding: 7px 15px;
|
|
16
|
-
background: #fff;
|
|
17
23
|
border: 1px solid var(--lightGray);
|
|
18
|
-
|
|
19
|
-
width: 325px;
|
|
24
|
+
min-width: 200px;
|
|
20
25
|
cursor: pointer;
|
|
26
|
+
padding: 0.375rem 0.75rem;
|
|
27
|
+
font-size: 1em;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
[class^='selectable-item-'] {
|
|
24
31
|
list-style: none;
|
|
25
32
|
padding-left: 20px;
|
|
26
33
|
position: relative;
|
|
@@ -38,15 +45,16 @@
|
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
.nested-dropdown-input-container {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 100%;
|
|
50
|
+
border-radius: 0.25rem;
|
|
41
51
|
position: relative;
|
|
42
52
|
& > span.list-arrow {
|
|
53
|
+
color: var(--mediumGray);
|
|
43
54
|
position: absolute;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
padding: 9px;
|
|
48
|
-
background: #fff;
|
|
49
|
-
pointer-events: none;
|
|
55
|
+
top: 9px;
|
|
56
|
+
right: 1px;
|
|
57
|
+
float: right;
|
|
50
58
|
}
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -55,7 +63,8 @@
|
|
|
55
63
|
overflow-y: scroll;
|
|
56
64
|
position: absolute;
|
|
57
65
|
z-index: 3;
|
|
58
|
-
width:
|
|
66
|
+
min-width: 200px;
|
|
67
|
+
background: white;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
.hide {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { filterSearchTerm, NestedOptions } from '../nestedDropdownHelpers'
|
|
3
|
+
|
|
4
|
+
describe('filterSearchTerm()', () => {
|
|
5
|
+
const optsMemo: NestedOptions = [
|
|
6
|
+
[['Fruits'], [['Apple'], ['Cantaloupe'], ['Orange'], ['Tomato']]],
|
|
7
|
+
[['Vegetables'], [['Spinach'], ['Broccoli'], ['Carrot'], ['Tomato']]],
|
|
8
|
+
[
|
|
9
|
+
['Fungi', 'Mushrooms'],
|
|
10
|
+
[['Portobello', 'Mushroom 1'], ['Morel, Mushroom 2'], ['Chanterelle', 'Mushroom 3']]
|
|
11
|
+
]
|
|
12
|
+
]
|
|
13
|
+
it('Return the original OptsMemo when there is no search term', () => {
|
|
14
|
+
const result = filterSearchTerm('', optsMemo)
|
|
15
|
+
expect(result).toEqual(optsMemo)
|
|
16
|
+
const result2 = filterSearchTerm(undefined, optsMemo)
|
|
17
|
+
expect(result2).toEqual(optsMemo)
|
|
18
|
+
})
|
|
19
|
+
it('Return the empty array if there is no match to search term', () => {
|
|
20
|
+
const result = filterSearchTerm('Pizza', optsMemo)
|
|
21
|
+
expect(result).toEqual([])
|
|
22
|
+
})
|
|
23
|
+
it('Filter out the top level groupings', () => {
|
|
24
|
+
const result = filterSearchTerm('', optsMemo)
|
|
25
|
+
expect(result).toEqual(optsMemo)
|
|
26
|
+
const result2 = filterSearchTerm('fruit', optsMemo)
|
|
27
|
+
const expectedResult2: NestedOptions = [[['Fruits'], [['Apple'], ['Cantaloupe'], ['Orange'], ['Tomato']]]]
|
|
28
|
+
expect(result2).toEqual(expectedResult2)
|
|
29
|
+
const result3 = filterSearchTerm('VEG', optsMemo)
|
|
30
|
+
const expectedResult3: NestedOptions = [[['Vegetables'], [['Spinach'], ['Broccoli'], ['Carrot'], ['Tomato']]]]
|
|
31
|
+
expect(result3).toEqual(expectedResult3)
|
|
32
|
+
const result4 = filterSearchTerm('mush', optsMemo)
|
|
33
|
+
const expectedResult4: NestedOptions = [
|
|
34
|
+
[
|
|
35
|
+
['Fungi', 'Mushrooms'],
|
|
36
|
+
[['Portobello', 'Mushroom 1'], ['Morel, Mushroom 2'], ['Chanterelle', 'Mushroom 3']]
|
|
37
|
+
]
|
|
38
|
+
]
|
|
39
|
+
expect(result4).toEqual(expectedResult4)
|
|
40
|
+
})
|
|
41
|
+
it('Filter out the subvalues', () => {
|
|
42
|
+
const result = filterSearchTerm('ap', optsMemo)
|
|
43
|
+
const expectedResult: NestedOptions = [[['Fruits'], [['Apple']]]]
|
|
44
|
+
expect(result).toEqual(expectedResult)
|
|
45
|
+
const result2 = filterSearchTerm('ca', optsMemo)
|
|
46
|
+
const expectedResult2: NestedOptions = [
|
|
47
|
+
[['Fruits'], [['Cantaloupe']]],
|
|
48
|
+
[['Vegetables'], [['Carrot']]]
|
|
49
|
+
]
|
|
50
|
+
expect(result2).toEqual(expectedResult2)
|
|
51
|
+
const result3 = filterSearchTerm('tomato', optsMemo)
|
|
52
|
+
const expectedResult3: NestedOptions = [
|
|
53
|
+
[['Fruits'], [['Tomato']]],
|
|
54
|
+
[['Vegetables'], [['Tomato']]]
|
|
55
|
+
]
|
|
56
|
+
expect(result3).toEqual(expectedResult3)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -4,9 +4,11 @@ import GroupRow from './components/GroupRow'
|
|
|
4
4
|
import { CellMatrix, GroupCellMatrix } from './types/CellMatrix'
|
|
5
5
|
import { RowType } from './types/RowType'
|
|
6
6
|
import { PreliminaryDataItem } from '@cdc/chart/src/types/ChartConfig'
|
|
7
|
+
import _ from 'lodash'
|
|
7
8
|
|
|
8
9
|
type TableProps = {
|
|
9
10
|
childrenMatrix: CellMatrix | GroupCellMatrix
|
|
11
|
+
noData?: boolean
|
|
10
12
|
tableName: string
|
|
11
13
|
caption: string
|
|
12
14
|
stickyHeader?: boolean
|
|
@@ -27,46 +29,112 @@ type TableProps = {
|
|
|
27
29
|
|
|
28
30
|
type Position = 'sticky'
|
|
29
31
|
|
|
30
|
-
const Table = ({
|
|
32
|
+
const Table = ({
|
|
33
|
+
childrenMatrix,
|
|
34
|
+
noData,
|
|
35
|
+
tableName,
|
|
36
|
+
caption,
|
|
37
|
+
stickyHeader,
|
|
38
|
+
headContent,
|
|
39
|
+
tableOptions,
|
|
40
|
+
wrapColumns,
|
|
41
|
+
hasRowType,
|
|
42
|
+
fontSize,
|
|
43
|
+
viewport,
|
|
44
|
+
preliminaryData
|
|
45
|
+
}: TableProps) => {
|
|
31
46
|
const headStyle = stickyHeader ? { position: 'sticky' as Position, top: 0, zIndex: 2 } : {}
|
|
32
47
|
const isGroupedMatrix = !Array.isArray(childrenMatrix)
|
|
33
48
|
|
|
34
49
|
return (
|
|
35
50
|
<table {...tableOptions}>
|
|
36
51
|
<caption className='visually-hidden'>{caption}</caption>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
{noData ? (
|
|
53
|
+
<tr>
|
|
54
|
+
<td className='py-5 text-center'>No Data</td>
|
|
55
|
+
</tr>
|
|
56
|
+
) : (
|
|
57
|
+
<>
|
|
58
|
+
<thead style={headStyle}>{headContent}</thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
{isGroupedMatrix
|
|
61
|
+
? Object.keys(childrenMatrix).flatMap(groupName => {
|
|
62
|
+
let colSpan = 0
|
|
63
|
+
const rows = childrenMatrix[groupName].map((row, i) => {
|
|
64
|
+
colSpan = row.length
|
|
65
|
+
const key = `${tableName}-${groupName}-row-${i}`
|
|
66
|
+
return (
|
|
67
|
+
<Row
|
|
68
|
+
preliminaryData={preliminaryData}
|
|
69
|
+
key={key}
|
|
70
|
+
rowKey={key}
|
|
71
|
+
childRow={row}
|
|
72
|
+
wrapColumns={wrapColumns}
|
|
73
|
+
cellMinWidth={tableOptions.cellMinWidth}
|
|
74
|
+
fontSize={fontSize}
|
|
75
|
+
viewport={viewport}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
return [<GroupRow label={groupName} colSpan={colSpan} key={`${tableName}-${groupName}`} />, ...rows]
|
|
80
|
+
})
|
|
81
|
+
: childrenMatrix.map((childRow, i) => {
|
|
82
|
+
let childRowCopy = [...childRow]
|
|
83
|
+
let rowType = undefined
|
|
84
|
+
if (hasRowType) rowType = childRowCopy.shift()
|
|
85
|
+
const key = `${tableName}-row-${i}`
|
|
86
|
+
if (rowType === undefined) {
|
|
87
|
+
return (
|
|
88
|
+
<Row
|
|
89
|
+
preliminaryData={preliminaryData}
|
|
90
|
+
key={key}
|
|
91
|
+
rowKey={key}
|
|
92
|
+
childRow={childRow}
|
|
93
|
+
wrapColumns={wrapColumns}
|
|
94
|
+
cellMinWidth={tableOptions.cellMinWidth}
|
|
95
|
+
fontSize={fontSize}
|
|
96
|
+
viewport={viewport}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
} else {
|
|
100
|
+
switch (rowType) {
|
|
101
|
+
case RowType.row_group:
|
|
102
|
+
return <GroupRow label={childRowCopy[0]} colSpan={childRowCopy.length} key={key} />
|
|
103
|
+
case RowType.total:
|
|
104
|
+
return (
|
|
105
|
+
<Row
|
|
106
|
+
preliminaryData={preliminaryData}
|
|
107
|
+
key={key}
|
|
108
|
+
rowKey={key}
|
|
109
|
+
childRow={childRowCopy}
|
|
110
|
+
isTotal={true}
|
|
111
|
+
wrapColumns={wrapColumns}
|
|
112
|
+
cellMinWidth={tableOptions.cellMinWidth}
|
|
113
|
+
fontSize={fontSize}
|
|
114
|
+
viewport={viewport}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
case RowType.row_group_total:
|
|
118
|
+
return <GroupRow label={childRowCopy[0]} colSpan={1} key={key} data={childRowCopy.slice(1)} />
|
|
119
|
+
default:
|
|
120
|
+
return (
|
|
121
|
+
<Row
|
|
122
|
+
preliminaryData={preliminaryData}
|
|
123
|
+
key={key}
|
|
124
|
+
rowKey={key}
|
|
125
|
+
childRow={childRowCopy}
|
|
126
|
+
wrapColumns={wrapColumns}
|
|
127
|
+
cellMinWidth={tableOptions.cellMinWidth}
|
|
128
|
+
fontSize={fontSize}
|
|
129
|
+
viewport={viewport}
|
|
130
|
+
/>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
})}
|
|
135
|
+
</tbody>
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
70
138
|
</table>
|
|
71
139
|
)
|
|
72
140
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { a } from 'vitest/dist/suite-IbNSsUWN'
|
|
2
|
+
import { BlurStrokeText } from '../BlurStrokeText'
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof BlurStrokeText> = {
|
|
6
|
+
title: 'Components/Atoms/BlurStrokeText',
|
|
7
|
+
component: BlurStrokeText
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default meta
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof BlurStrokeText>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: args => (
|
|
16
|
+
<svg width='300' height='100' style={{ backgroundColor: '#464646' }}>
|
|
17
|
+
<BlurStrokeText {...args}>A feathered stroke option</BlurStrokeText>
|
|
18
|
+
</svg>
|
|
19
|
+
),
|
|
20
|
+
args: {
|
|
21
|
+
fontSize: 15,
|
|
22
|
+
y: 50,
|
|
23
|
+
x: 50,
|
|
24
|
+
blurRadius: 1,
|
|
25
|
+
disableStroke: false
|
|
26
|
+
}
|
|
27
|
+
}
|