@cdc/core 4.24.1 → 4.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon-sankey.svg +1 -0
- package/assets/icon-table.svg +1 -0
- package/components/DataTable/DataTable.tsx +44 -15
- package/components/DataTable/DataTableStandAlone.tsx +15 -0
- package/components/DataTable/components/CellAnchor.tsx +3 -1
- package/components/DataTable/components/ChartHeader.tsx +48 -12
- package/components/DataTable/components/DataTableEditorPanel.tsx +42 -0
- package/components/DataTable/components/MapHeader.tsx +10 -5
- package/components/DataTable/helpers/customColumns.ts +4 -2
- package/components/DataTable/helpers/customSort.ts +9 -0
- package/components/DataTable/helpers/getChartCellValue.ts +5 -3
- package/components/DataTable/helpers/getDataSeriesColumns.ts +10 -2
- package/components/DataTable/helpers/getSeriesName.ts +15 -20
- package/components/DataTable/helpers/mapCellMatrix.tsx +4 -0
- package/components/DataTable/types/TableConfig.ts +12 -37
- package/components/EditorPanel/ColumnsEditor.tsx +311 -0
- package/components/EditorPanel/DataTableEditor.tsx +27 -28
- package/components/Filters.jsx +35 -16
- package/components/MultiSelect/MultiSelect.tsx +39 -20
- package/components/MultiSelect/multiselect.styles.css +44 -27
- package/components/NestedDropdown/NestedDropdown.tsx +257 -0
- package/components/NestedDropdown/index.ts +1 -0
- package/components/NestedDropdown/nesteddropdown.styles.css +70 -0
- package/components/Table/Table.tsx +1 -1
- package/components/_stories/MultiSelect.stories.tsx +10 -1
- package/components/_stories/NestedDropdown.stories.tsx +58 -0
- package/components/createBarElement.jsx +117 -0
- package/components/elements/ScreenReaderText.tsx +8 -0
- package/components/elements/SkipTo.tsx +14 -0
- package/components/ui/Icon.tsx +5 -1
- package/components/ui/Title/Title.scss +7 -1
- package/components/ui/Title/index.tsx +3 -3
- package/components/ui/Tooltip.jsx +1 -1
- package/components/ui/_stories/Colors.stories.tsx +92 -0
- package/components/ui/_stories/Icon.stories.tsx +17 -10
- package/data/colorPalettes.js +1 -6
- package/helpers/cove/accessibility.ts +23 -0
- package/helpers/cove/date.ts +19 -0
- package/helpers/coveUpdateWorker.js +4 -0
- package/helpers/fetchRemoteData.js +5 -5
- package/helpers/getViewport.ts +23 -0
- package/helpers/isDomainExternal.js +14 -0
- package/helpers/isSolr.js +13 -0
- package/helpers/queryStringUtils.js +26 -0
- package/helpers/tests/updateFieldFactory.test.ts +89 -0
- package/helpers/updateFieldFactory.ts +38 -0
- package/helpers/useDataVizClasses.js +2 -2
- package/helpers/ver/4.24.3.js +25 -0
- package/helpers/withDevTools.ts +50 -0
- package/package.json +4 -3
- package/styles/_data-table.scss +2 -20
- package/styles/_global-variables.scss +75 -0
- package/styles/base.scss +97 -69
- package/types/Action.ts +1 -0
- package/types/Axis.ts +3 -0
- package/types/BaseVisualizationType.ts +1 -0
- package/types/BoxPlot.ts +21 -0
- package/types/Column.ts +1 -0
- package/types/ConfidenceInterval.ts +1 -0
- package/types/General.ts +9 -0
- package/types/Legend.ts +18 -0
- package/types/Region.ts +10 -0
- package/types/Runtime.ts +3 -1
- package/types/Table.ts +5 -2
- package/types/UpdateFieldFunc.ts +1 -1
- package/types/ViewPort.ts +2 -0
- package/types/Visualization.ts +23 -5
- package/types/WCMSProps.ts +11 -0
- package/components/DataTable/components/SkipNav.tsx +0 -7
- package/helpers/cove/date.js +0 -9
- package/helpers/getViewport.js +0 -21
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
.cove-multiselect {
|
|
2
2
|
position: relative;
|
|
3
|
-
.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
.cove-input__label {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
.wrapper {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
.selected {
|
|
10
|
+
border: 1px solid var(--lightGray);
|
|
11
|
+
padding: 5px;
|
|
12
|
+
min-height: 40px;
|
|
13
|
+
min-width: 200px;
|
|
12
14
|
display: inline-block;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
:is(button) {
|
|
16
|
+
border: none;
|
|
17
|
+
background: none;
|
|
18
|
+
}
|
|
19
|
+
:is(div) {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
padding: 0 0 0 5px;
|
|
22
|
+
margin-right: 5px;
|
|
23
|
+
margin-bottom: 2px;
|
|
24
|
+
background: var(--lightGray);
|
|
25
|
+
border-radius: 5px;
|
|
26
|
+
}
|
|
27
|
+
.expand {
|
|
28
|
+
padding: 0 5px;
|
|
29
|
+
border-radius: 5px;
|
|
30
|
+
background: var(--lightGray);
|
|
31
|
+
float: right;
|
|
32
|
+
}
|
|
17
33
|
border-radius: 5px;
|
|
18
34
|
}
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
.cove-tooltip__content {
|
|
36
|
+
position: absolute;
|
|
37
|
+
min-width: 120px;
|
|
38
|
+
:is(p) {
|
|
39
|
+
margin-bottom: 0;
|
|
40
|
+
}
|
|
24
41
|
}
|
|
25
|
-
border-radius: 5px;
|
|
26
42
|
}
|
|
27
43
|
.dropdown {
|
|
28
44
|
background: white;
|
|
29
45
|
position: absolute;
|
|
30
46
|
margin-top: 5px;
|
|
31
|
-
border: 1px solid
|
|
32
|
-
padding: 0;
|
|
47
|
+
border: 1px solid var(--lightGray);
|
|
48
|
+
padding-left: 0;
|
|
33
49
|
min-height: 40px;
|
|
34
|
-
overflow: scroll;
|
|
35
50
|
max-height: 200px;
|
|
36
|
-
|
|
51
|
+
overflow-y: scroll;
|
|
52
|
+
min-width: 200px;
|
|
53
|
+
z-index: 4;
|
|
37
54
|
&.hide {
|
|
38
55
|
display: none;
|
|
39
56
|
}
|
|
@@ -41,10 +58,10 @@
|
|
|
41
58
|
:is(li) {
|
|
42
59
|
cursor: pointer;
|
|
43
60
|
list-style: none;
|
|
44
|
-
padding
|
|
61
|
+
padding: 0 10px;
|
|
45
62
|
&:hover {
|
|
46
|
-
background:
|
|
47
|
-
}
|
|
63
|
+
background: var(--lightGray);
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
}
|
|
50
|
-
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useMemo } from 'react'
|
|
2
|
+
import './nesteddropdown.styles.css'
|
|
3
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
4
|
+
|
|
5
|
+
const Options: React.FC<{
|
|
6
|
+
currentOptions: (string | number)[]
|
|
7
|
+
label: string
|
|
8
|
+
handleSecondTierSelect: Function
|
|
9
|
+
userSelectedTierTwoLabel: string
|
|
10
|
+
userSearchTerm: string
|
|
11
|
+
}> = ({ currentOptions, label, handleSecondTierSelect, userSelectedTierTwoLabel, userSearchTerm }) => {
|
|
12
|
+
const [isTierOneExpanded, setIsTierOneExpanded] = useState(true)
|
|
13
|
+
|
|
14
|
+
const checkMark = <>✔</>
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setIsTierOneExpanded(userSearchTerm.length > 0 ? true : isTierOneExpanded)
|
|
18
|
+
}, [userSearchTerm])
|
|
19
|
+
|
|
20
|
+
const handleGroupClick = e => {
|
|
21
|
+
const leaveExpanded = e.target.className === 'selectable-item' ? true : !isTierOneExpanded
|
|
22
|
+
setIsTierOneExpanded(leaveExpanded)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const handleKeyUp = e => {
|
|
26
|
+
const currentItem = e.target
|
|
27
|
+
if (e.key === 'ArrowRight') setIsTierOneExpanded(true)
|
|
28
|
+
else if (e.key === 'ArrowLeft') {
|
|
29
|
+
if (currentItem.className === 'selectable-item') currentItem.parentNode.parentNode.focus()
|
|
30
|
+
setIsTierOneExpanded(false)
|
|
31
|
+
} else if (e.key === 'Enter') {
|
|
32
|
+
currentItem.className === 'selectable-item' ? handleSecondTierSelect(currentItem.dataset.value) : setIsTierOneExpanded(!isTierOneExpanded)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<li role='treeitem' key={label} tabIndex={0} aria-label={label} onClick={handleGroupClick} onKeyUp={handleKeyUp} className='nested-dropdown-group'>
|
|
39
|
+
<span id={label}>{label} </span>
|
|
40
|
+
{
|
|
41
|
+
<span className='list-arrow' aria-hidden='true'>
|
|
42
|
+
{isTierOneExpanded ? <Icon display='caretFilledUp' /> : <Icon display='caretFilledDown' />}
|
|
43
|
+
</span>
|
|
44
|
+
}
|
|
45
|
+
<ul aria-expanded={isTierOneExpanded} role='group' tabIndex={-1} aria-labelledby={label} className={isTierOneExpanded ? '' : 'hide'}>
|
|
46
|
+
{currentOptions.map(tierTwo => {
|
|
47
|
+
const regionID = label + tierTwo
|
|
48
|
+
let isSelected = regionID === userSelectedTierTwoLabel
|
|
49
|
+
return (
|
|
50
|
+
<li
|
|
51
|
+
key={regionID}
|
|
52
|
+
className='selectable-item'
|
|
53
|
+
tabIndex={0}
|
|
54
|
+
role='treeitem'
|
|
55
|
+
aria-label={regionID}
|
|
56
|
+
aria-selected={isSelected}
|
|
57
|
+
data-value={tierTwo}
|
|
58
|
+
onClick={e => {
|
|
59
|
+
handleSecondTierSelect(tierTwo)
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
{isSelected ? <span aria-hidden='true'>{checkMark}</span> : ''}
|
|
63
|
+
|
|
64
|
+
{tierTwo}
|
|
65
|
+
</li>
|
|
66
|
+
)
|
|
67
|
+
})}
|
|
68
|
+
</ul>
|
|
69
|
+
</li>
|
|
70
|
+
</>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface NestedDropdownProps {
|
|
75
|
+
data: Record<string, string | number>[]
|
|
76
|
+
tiers: [string, string] // index 0 is the parent index 1 is the child
|
|
77
|
+
listLabel: string
|
|
78
|
+
handleSelectedItems: Function
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const NestedDropdown: React.FC<NestedDropdownProps> = ({ data, tiers: [firstTierLabel, secondTierLabel], listLabel, handleSelectedItems }) => {
|
|
82
|
+
const optsMemo: Record<string, (string | number)[]> = {}
|
|
83
|
+
|
|
84
|
+
data.forEach(value => {
|
|
85
|
+
const tierOne = value[firstTierLabel]
|
|
86
|
+
const tierTwo = value[secondTierLabel]
|
|
87
|
+
if (optsMemo[tierOne]) {
|
|
88
|
+
optsMemo[tierOne].push(tierTwo)
|
|
89
|
+
} else {
|
|
90
|
+
optsMemo[tierOne] = [tierTwo]
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const [userSelectedTierTwoLabel, setUserSelectedTierTwoLabel] = useState(null)
|
|
95
|
+
const [userSearchTerm, setUserSearchTerm] = useState('')
|
|
96
|
+
const [inputValue, setInputValue] = useState('')
|
|
97
|
+
const [inputHasFocus, setInputHasFocus] = useState(false)
|
|
98
|
+
const [isListOpened, setIsListOpened] = useState(false)
|
|
99
|
+
|
|
100
|
+
const searchInput = useRef(null)
|
|
101
|
+
const searchDropdown = useRef(null)
|
|
102
|
+
|
|
103
|
+
const chooseSelectedSecondTier = (tierOne: string, tierTwo: string) => {
|
|
104
|
+
searchInput.current.focus()
|
|
105
|
+
const selectedItemValue = tierTwo
|
|
106
|
+
setUserSelectedTierTwoLabel(tierOne + tierTwo)
|
|
107
|
+
setUserSearchTerm('')
|
|
108
|
+
setIsListOpened(false)
|
|
109
|
+
setInputValue(selectedItemValue)
|
|
110
|
+
handleSelectedItems(tierOne, tierTwo)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const handleKeyUp = e => {
|
|
114
|
+
const { nodeName, className, parentNode, nextSibling, lastChild, previousSibling } = e.target
|
|
115
|
+
const Dropdown = searchDropdown.current
|
|
116
|
+
switch (e.key) {
|
|
117
|
+
case 'ArrowDown': {
|
|
118
|
+
if (nodeName === 'INPUT') {
|
|
119
|
+
setIsListOpened(true)
|
|
120
|
+
// Move focus from Input to top of dropdown
|
|
121
|
+
Dropdown.firstChild.focus()
|
|
122
|
+
} else if (className === 'selectable-item') {
|
|
123
|
+
// Move focus to next item on list: next Tier Two item or the next Tier One or SearchInput
|
|
124
|
+
const itemToFocusOnAfterKeyUp = nextSibling ?? parentNode.parentNode.nextSibling ?? searchInput.current
|
|
125
|
+
itemToFocusOnAfterKeyUp.focus()
|
|
126
|
+
} else if (lastChild.className === 'hide') {
|
|
127
|
+
// If Tier One is collapsed, move to next Tier One or move focus back to the top Input
|
|
128
|
+
const itemToFocusOnAfterKeyUp = nextSibling ?? searchInput.current
|
|
129
|
+
itemToFocusOnAfterKeyUp.focus()
|
|
130
|
+
} else {
|
|
131
|
+
// If Tier One is open, move focus to Tier Two
|
|
132
|
+
lastChild?.firstChild?.focus()
|
|
133
|
+
}
|
|
134
|
+
break
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case 'ArrowUp': {
|
|
138
|
+
if (nodeName === 'INPUT') {
|
|
139
|
+
setIsListOpened(true)
|
|
140
|
+
if (Dropdown.lastChild.lastChild.className === 'hide') {
|
|
141
|
+
// Move focus from Input textbox to the last collapsed Tier Two in dropdown
|
|
142
|
+
Dropdown.lastChild.focus()
|
|
143
|
+
} else {
|
|
144
|
+
// Move focus to last item of the last collapsed Tier Two in dropdown
|
|
145
|
+
Dropdown.lastChild.lastChild.lastChild.focus()
|
|
146
|
+
}
|
|
147
|
+
} else if (className === 'selectable-item') {
|
|
148
|
+
// Move focus to previous Tier Two or Move focus to current Tier One
|
|
149
|
+
const itemToFocusOnAfterKeyUp = previousSibling ?? parentNode.parentNode
|
|
150
|
+
itemToFocusOnAfterKeyUp.focus()
|
|
151
|
+
} else if (previousSibling) {
|
|
152
|
+
// Move focus to previous collapsed Tier One or Move focus from Tier One to the last of the previous Tier Two's items
|
|
153
|
+
const itemToFocusOnAfterKeyUp = previousSibling.lastChild.className === 'hide' ? previousSibling : previousSibling.lastChild.lastChild
|
|
154
|
+
itemToFocusOnAfterKeyUp.focus()
|
|
155
|
+
} else {
|
|
156
|
+
// Move focus from top of the dropdown to Input
|
|
157
|
+
searchInput?.current.focus()
|
|
158
|
+
}
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'ArrowLeft': {
|
|
163
|
+
if (nodeName === 'INPUT') {
|
|
164
|
+
setIsListOpened(false)
|
|
165
|
+
}
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'ArrowRight': {
|
|
170
|
+
if (nodeName === 'INPUT') {
|
|
171
|
+
setIsListOpened(true)
|
|
172
|
+
}
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case 'Escape':
|
|
177
|
+
{
|
|
178
|
+
setIsListOpened(false)
|
|
179
|
+
searchInput.current.focus()
|
|
180
|
+
}
|
|
181
|
+
break
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const filterOptions: Record<string, (string | number)[]> = useMemo(() => {
|
|
186
|
+
if (!userSearchTerm) return optsMemo
|
|
187
|
+
const newOptions: Record<string, (string | number)[]> = {}
|
|
188
|
+
const newRegex = new RegExp(`^${userSearchTerm}`, 'i')
|
|
189
|
+
for (const tierOne in optsMemo) {
|
|
190
|
+
if (tierOne.match(newRegex)) {
|
|
191
|
+
newOptions[tierOne] = [...optsMemo[tierOne]]
|
|
192
|
+
} else {
|
|
193
|
+
const newSecondTierOptions = optsMemo[tierOne].filter(tierTwo => String(tierTwo).match(newRegex))
|
|
194
|
+
if (newSecondTierOptions.length > 0) {
|
|
195
|
+
newOptions[tierOne] = newSecondTierOptions
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return newOptions
|
|
200
|
+
}, [userSearchTerm])
|
|
201
|
+
|
|
202
|
+
const filterOptionsKeys = Object.keys(filterOptions)
|
|
203
|
+
|
|
204
|
+
const handleSearchTermChange = e => {
|
|
205
|
+
const newSearchTerm = e.target.value
|
|
206
|
+
setIsListOpened(true)
|
|
207
|
+
setUserSearchTerm(newSearchTerm)
|
|
208
|
+
setInputValue(newSearchTerm)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
<div id='nested-dropdown-container' className='nested-dropdown' onKeyUp={handleKeyUp}>
|
|
214
|
+
<div className='nested-dropdown-input-container' aria-label='searchInput' role='textbox'>
|
|
215
|
+
<input
|
|
216
|
+
className='search-input'
|
|
217
|
+
ref={searchInput}
|
|
218
|
+
aria-label='searchInput'
|
|
219
|
+
aria-haspopup='true'
|
|
220
|
+
aria-hidden='false'
|
|
221
|
+
tabIndex={0}
|
|
222
|
+
value={inputValue}
|
|
223
|
+
onChange={handleSearchTermChange}
|
|
224
|
+
placeholder={'Select or type to search'}
|
|
225
|
+
onClick={() => {
|
|
226
|
+
if (inputHasFocus) setIsListOpened(!isListOpened)
|
|
227
|
+
}}
|
|
228
|
+
onFocus={() => setInputHasFocus(true)}
|
|
229
|
+
onBlur={() => setInputHasFocus(false)}
|
|
230
|
+
/>
|
|
231
|
+
<span className='list-arrow' aria-hidden={true}>
|
|
232
|
+
{isListOpened ? <Icon display='caretFilledUp' /> : <Icon display='caretFilledDown' />}
|
|
233
|
+
</span>
|
|
234
|
+
</div>
|
|
235
|
+
<ul role='tree' key={listLabel} tabIndex={-1} aria-labelledby='main-nested-dropdown' aria-expanded={isListOpened} ref={searchDropdown} className={`main-nested-dropdown-container ${isListOpened ? '' : 'hide'}`}>
|
|
236
|
+
{filterOptions && filterOptionsKeys.length > 0
|
|
237
|
+
? filterOptionsKeys.map((tierOne: string) => {
|
|
238
|
+
return (
|
|
239
|
+
<Options
|
|
240
|
+
currentOptions={filterOptions[tierOne]}
|
|
241
|
+
label={tierOne}
|
|
242
|
+
handleSecondTierSelect={(tierTwo: string) => {
|
|
243
|
+
chooseSelectedSecondTier(tierOne, tierTwo)
|
|
244
|
+
}}
|
|
245
|
+
userSelectedTierTwoLabel={userSelectedTierTwoLabel}
|
|
246
|
+
userSearchTerm={userSearchTerm}
|
|
247
|
+
/>
|
|
248
|
+
)
|
|
249
|
+
})
|
|
250
|
+
: 'There are no matching items'}
|
|
251
|
+
</ul>
|
|
252
|
+
</div>
|
|
253
|
+
</>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default NestedDropdown
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './NestedDropdown'
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
.nested-dropdown {
|
|
2
|
+
li {
|
|
3
|
+
list-style: none;
|
|
4
|
+
cursor: pointer;
|
|
5
|
+
|
|
6
|
+
&:focus {
|
|
7
|
+
border: 2px solid var(--orange);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.search-input {
|
|
12
|
+
border: none;
|
|
13
|
+
position: relative;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
width: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.main-nested-dropdown-container,
|
|
19
|
+
.nested-dropdown-input-container {
|
|
20
|
+
padding: 7px 15px;
|
|
21
|
+
background: #fff;
|
|
22
|
+
border: 1px solid var(--lightGray);
|
|
23
|
+
border-radius: 5px;
|
|
24
|
+
width: 325px;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.selectable-item:hover {
|
|
29
|
+
background-color: var(--lightGray);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
& > div {
|
|
33
|
+
position: relative;
|
|
34
|
+
& > span.list-arrow {
|
|
35
|
+
position: absolute;
|
|
36
|
+
font-size: 10px;
|
|
37
|
+
top: 4px;
|
|
38
|
+
right: 12px;
|
|
39
|
+
padding: 9px;
|
|
40
|
+
background: #fff;
|
|
41
|
+
pointer-events: none;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
& > ul {
|
|
46
|
+
max-height: 375px;
|
|
47
|
+
overflow-y: scroll;
|
|
48
|
+
position: absolute;
|
|
49
|
+
z-index: 3;
|
|
50
|
+
width: 375px;
|
|
51
|
+
|
|
52
|
+
& > li {
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
|
|
55
|
+
& > ul > li {
|
|
56
|
+
position: relative;
|
|
57
|
+
font-weight: normal;
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
& > span {
|
|
60
|
+
position: absolute;
|
|
61
|
+
left: -20px;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.hide {
|
|
68
|
+
display: none;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -24,7 +24,7 @@ type TableProps = {
|
|
|
24
24
|
type Position = 'sticky'
|
|
25
25
|
|
|
26
26
|
const Table = ({ childrenMatrix, tableName, caption, stickyHeader, headContent, tableOptions, wrapColumns, hasRowType }: TableProps) => {
|
|
27
|
-
const headStyle = stickyHeader ? { position: 'sticky' as Position, top: 0, zIndex:
|
|
27
|
+
const headStyle = stickyHeader ? { position: 'sticky' as Position, top: 0, zIndex: 2 } : {}
|
|
28
28
|
const isGroupedMatrix = !Array.isArray(childrenMatrix)
|
|
29
29
|
return (
|
|
30
30
|
<table {...tableOptions}>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react'
|
|
2
2
|
|
|
3
3
|
import MultiSelect from '../MultiSelect'
|
|
4
|
+
import { userEvent, within } from '@storybook/testing-library'
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof MultiSelect> = {
|
|
6
7
|
title: 'Components/Molecules/MultiSelect',
|
|
@@ -14,10 +15,18 @@ export const Primary: Story = {
|
|
|
14
15
|
options: [
|
|
15
16
|
{ value: '1', label: 'One' },
|
|
16
17
|
{ value: '2', label: 'Two' },
|
|
17
|
-
{ value: '3', label: 'Three' }
|
|
18
|
+
{ value: '3', label: 'Three' },
|
|
19
|
+
{ value: '4', label: 'This is a really long option' }
|
|
18
20
|
],
|
|
21
|
+
selected: ['1', '2'],
|
|
22
|
+
limit: 3,
|
|
19
23
|
label: 'MultiSelect',
|
|
20
24
|
updateField: (section, subsection, fieldName, value) => {}
|
|
25
|
+
},
|
|
26
|
+
play: async ({ canvasElement }) => {
|
|
27
|
+
const canvas = within(canvasElement)
|
|
28
|
+
const questionMark = canvas.getByRole('dialog')
|
|
29
|
+
await userEvent.hover(questionMark)
|
|
21
30
|
}
|
|
22
31
|
}
|
|
23
32
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import NestedDropdown from '../NestedDropdown'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof NestedDropdown> = {
|
|
6
|
+
title: 'Components/Molecules/NestedDropdown',
|
|
7
|
+
component: NestedDropdown
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof NestedDropdown>
|
|
11
|
+
|
|
12
|
+
export const Primary: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
data: [
|
|
15
|
+
{
|
|
16
|
+
country: 'USA',
|
|
17
|
+
region: 'Region1'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
country: 'USA',
|
|
21
|
+
region: 'Florida'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
country: 'USA',
|
|
25
|
+
region: 'Iowa'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
country: 'Country2',
|
|
29
|
+
region: 'Region1'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
country: 'Country2',
|
|
33
|
+
region: 'Region2'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
country: 'Country2',
|
|
37
|
+
region: 'Region3'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
country: 'Italy',
|
|
41
|
+
region: 'Region1'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
country: 'Italy',
|
|
45
|
+
region: 'Naples'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
country: 'Italy',
|
|
49
|
+
region: 'Region3'
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
tiers: ['country', 'region'],
|
|
53
|
+
listLabel: 'Countries of the World',
|
|
54
|
+
handleSelectedItems: console.log
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default meta
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export default function createBarElement(props) {
|
|
2
|
+
const { config, index, id, className, background, borderColor, borderWidth, width, height, x, y, onMouseOver, onMouseLeave, onClick, tooltipHtml, tooltipId, styleOverrides, seriesHighlight } = props
|
|
3
|
+
|
|
4
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
5
|
+
const isRounded = config.barStyle === 'rounded'
|
|
6
|
+
const isStacked = config.visualizationSubType === 'stacked'
|
|
7
|
+
const tipRounding = config.tipRounding
|
|
8
|
+
const comboBarSeriesCount = config.visualizationType === 'Combo' && config.runtime?.barSeriesKeys?.length
|
|
9
|
+
const barSeriesCount = config.runtime.seriesKeys.length
|
|
10
|
+
const isolateSeriesCount = config.visualizationType === 'Bar' && config.legend.axisAlign && seriesHighlight?.length ? seriesHighlight?.length : 0
|
|
11
|
+
const stackCount = comboBarSeriesCount ? comboBarSeriesCount : isolateSeriesCount ? isolateSeriesCount : barSeriesCount
|
|
12
|
+
|
|
13
|
+
let radius = config.roundingStyle === 'standard' ? 8 : config.roundingStyle === 'shallow' ? 5 : config.roundingStyle === 'finger' ? 15 : 0
|
|
14
|
+
if (radius > width / 2 || radius > height / 2) {
|
|
15
|
+
radius = Math.min(width / 2, height / 2)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const roundTop = () => {
|
|
19
|
+
return `M${x},${y + height}
|
|
20
|
+
L${x},${y + radius}
|
|
21
|
+
Q${x},${y} ${x + radius},${y}
|
|
22
|
+
L${x + width - radius},${y}
|
|
23
|
+
Q${x + width},${y} ${x + width},${y + radius}
|
|
24
|
+
L${x + width},${y + height}
|
|
25
|
+
L${x},${y + height}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const roundRight = () => {
|
|
29
|
+
return `M${x},${y + height}
|
|
30
|
+
L${x},${y}
|
|
31
|
+
L${x + width - radius},${y}
|
|
32
|
+
Q${x + width},${y} ${x + width},${y + radius}
|
|
33
|
+
L${x + width},${y + height - radius}
|
|
34
|
+
Q${x + width},${y + height} ${x + width - radius},${y + height}
|
|
35
|
+
L${x},${y + height}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const roundBottom = () => {
|
|
39
|
+
return `M${x + radius},${y + height}
|
|
40
|
+
Q${x},${y + height} ${x},${y + height - radius}
|
|
41
|
+
L${x},${y}
|
|
42
|
+
L${x + width},${y}
|
|
43
|
+
L${x + width},${y + height - radius}
|
|
44
|
+
Q${x + width},${y + height} ${x + width - radius},${y + height}
|
|
45
|
+
L${x + radius},${y + height}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const roundLeft = () => {
|
|
49
|
+
return `M${x + radius},${y + height}
|
|
50
|
+
Q${x},${y + height} ${x},${y + height - radius}
|
|
51
|
+
L${x},${y + radius}
|
|
52
|
+
Q${x},${y} ${x + radius},${y}
|
|
53
|
+
L${x + width},${y}
|
|
54
|
+
L${x + width},${y + height}
|
|
55
|
+
L${x + radius},${y + height}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const roundFull = () => {
|
|
59
|
+
return `M${x + radius},${y + height}
|
|
60
|
+
Q${x},${y + height} ${x},${y + height - radius}
|
|
61
|
+
L${x},${y + radius}
|
|
62
|
+
Q${x},${y} ${x + radius},${y}
|
|
63
|
+
L${x + width - radius},${y}
|
|
64
|
+
Q${x + width},${y} ${x + width},${y + radius}
|
|
65
|
+
L${x + width},${y + height - radius}
|
|
66
|
+
Q${x + width},${y + height} ${x + width - radius},${y + height}
|
|
67
|
+
L${x + radius},${y + height}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const nonRounded = () => {
|
|
71
|
+
return `M${x},${y}
|
|
72
|
+
L${x + width},${y}
|
|
73
|
+
L${x + width},${y + height}
|
|
74
|
+
L${x},${y + height}
|
|
75
|
+
L${x},${y}`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let path
|
|
79
|
+
if (index === undefined || index === null || !isRounded) {
|
|
80
|
+
path = nonRounded()
|
|
81
|
+
} else {
|
|
82
|
+
path = nonRounded()
|
|
83
|
+
|
|
84
|
+
if ((isStacked && index + 1 === stackCount) || !isStacked) {
|
|
85
|
+
path = isHorizontal ? roundRight() : roundTop()
|
|
86
|
+
}
|
|
87
|
+
if (!isStacked && index === -1) {
|
|
88
|
+
path = isHorizontal ? roundLeft() : roundBottom()
|
|
89
|
+
}
|
|
90
|
+
if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
|
|
91
|
+
path = isHorizontal ? roundLeft() : roundBottom()
|
|
92
|
+
}
|
|
93
|
+
if (tipRounding === 'full' && ((isStacked && index === 0 && stackCount === 1) || !isStacked)) {
|
|
94
|
+
path = roundFull()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<path
|
|
100
|
+
id={id}
|
|
101
|
+
className={className}
|
|
102
|
+
d={path}
|
|
103
|
+
fill={background}
|
|
104
|
+
stroke={borderColor}
|
|
105
|
+
strokeWidth={borderWidth}
|
|
106
|
+
onMouseOver={onMouseOver}
|
|
107
|
+
onMouseLeave={onMouseLeave}
|
|
108
|
+
onClick={onClick}
|
|
109
|
+
data-tooltip-html={tooltipHtml}
|
|
110
|
+
data-tooltip-id={tooltipId}
|
|
111
|
+
style={{
|
|
112
|
+
transition: 'all 0.2s linear',
|
|
113
|
+
...styleOverrides
|
|
114
|
+
}}
|
|
115
|
+
></path>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PropsWithChildren, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
const ScreenReaderText = (props: PropsWithChildren<{ as: keyof JSX.IntrinsicElements; children?: ReactNode }>) => {
|
|
4
|
+
const Component = props.as
|
|
5
|
+
return <Component className='cdcdataviz-sr-only'>{props.children}</Component>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default ScreenReaderText
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type SkipToProps = {
|
|
2
|
+
// id to skip to
|
|
3
|
+
skipId: string
|
|
4
|
+
// focusable text output, screen reader message
|
|
5
|
+
skipMessage: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SkipTo: React.FC<SkipToProps> = ({ skipId, skipMessage }) => (
|
|
9
|
+
<a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
|
|
10
|
+
{skipMessage}
|
|
11
|
+
</a>
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
export default SkipTo
|
package/components/ui/Icon.tsx
CHANGED
|
@@ -31,6 +31,8 @@ import iconText from '../../assets/icon-filtered-text.svg'
|
|
|
31
31
|
import iconDropdowns from '../../assets/icon-filter-dropdowns.svg'
|
|
32
32
|
import iconPlus from '../../assets/icon-plus.svg'
|
|
33
33
|
import iconMinus from '../../assets/icon-minus.svg'
|
|
34
|
+
import iconTable from '../../assets/icon-table.svg'
|
|
35
|
+
import iconSankey from '../../assets/icon-sankey.svg'
|
|
34
36
|
|
|
35
37
|
import '../../styles/v2/components/icon.scss'
|
|
36
38
|
|
|
@@ -64,7 +66,9 @@ const iconHash = {
|
|
|
64
66
|
plus: iconPlus,
|
|
65
67
|
minus: iconMinus,
|
|
66
68
|
'filtered-text': iconText,
|
|
67
|
-
'filter-dropdowns': iconDropdowns
|
|
69
|
+
'filter-dropdowns': iconDropdowns,
|
|
70
|
+
table: iconTable,
|
|
71
|
+
sankey: iconSankey
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
export const ICON_TYPES = Object.keys(iconHash)
|