@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.
Files changed (71) hide show
  1. package/assets/icon-sankey.svg +1 -0
  2. package/assets/icon-table.svg +1 -0
  3. package/components/DataTable/DataTable.tsx +44 -15
  4. package/components/DataTable/DataTableStandAlone.tsx +15 -0
  5. package/components/DataTable/components/CellAnchor.tsx +3 -1
  6. package/components/DataTable/components/ChartHeader.tsx +48 -12
  7. package/components/DataTable/components/DataTableEditorPanel.tsx +42 -0
  8. package/components/DataTable/components/MapHeader.tsx +10 -5
  9. package/components/DataTable/helpers/customColumns.ts +4 -2
  10. package/components/DataTable/helpers/customSort.ts +9 -0
  11. package/components/DataTable/helpers/getChartCellValue.ts +5 -3
  12. package/components/DataTable/helpers/getDataSeriesColumns.ts +10 -2
  13. package/components/DataTable/helpers/getSeriesName.ts +15 -20
  14. package/components/DataTable/helpers/mapCellMatrix.tsx +4 -0
  15. package/components/DataTable/types/TableConfig.ts +12 -37
  16. package/components/EditorPanel/ColumnsEditor.tsx +311 -0
  17. package/components/EditorPanel/DataTableEditor.tsx +27 -28
  18. package/components/Filters.jsx +35 -16
  19. package/components/MultiSelect/MultiSelect.tsx +39 -20
  20. package/components/MultiSelect/multiselect.styles.css +44 -27
  21. package/components/NestedDropdown/NestedDropdown.tsx +257 -0
  22. package/components/NestedDropdown/index.ts +1 -0
  23. package/components/NestedDropdown/nesteddropdown.styles.css +70 -0
  24. package/components/Table/Table.tsx +1 -1
  25. package/components/_stories/MultiSelect.stories.tsx +10 -1
  26. package/components/_stories/NestedDropdown.stories.tsx +58 -0
  27. package/components/createBarElement.jsx +117 -0
  28. package/components/elements/ScreenReaderText.tsx +8 -0
  29. package/components/elements/SkipTo.tsx +14 -0
  30. package/components/ui/Icon.tsx +5 -1
  31. package/components/ui/Title/Title.scss +7 -1
  32. package/components/ui/Title/index.tsx +3 -3
  33. package/components/ui/Tooltip.jsx +1 -1
  34. package/components/ui/_stories/Colors.stories.tsx +92 -0
  35. package/components/ui/_stories/Icon.stories.tsx +17 -10
  36. package/data/colorPalettes.js +1 -6
  37. package/helpers/cove/accessibility.ts +23 -0
  38. package/helpers/cove/date.ts +19 -0
  39. package/helpers/coveUpdateWorker.js +4 -0
  40. package/helpers/fetchRemoteData.js +5 -5
  41. package/helpers/getViewport.ts +23 -0
  42. package/helpers/isDomainExternal.js +14 -0
  43. package/helpers/isSolr.js +13 -0
  44. package/helpers/queryStringUtils.js +26 -0
  45. package/helpers/tests/updateFieldFactory.test.ts +89 -0
  46. package/helpers/updateFieldFactory.ts +38 -0
  47. package/helpers/useDataVizClasses.js +2 -2
  48. package/helpers/ver/4.24.3.js +25 -0
  49. package/helpers/withDevTools.ts +50 -0
  50. package/package.json +4 -3
  51. package/styles/_data-table.scss +2 -20
  52. package/styles/_global-variables.scss +75 -0
  53. package/styles/base.scss +97 -69
  54. package/types/Action.ts +1 -0
  55. package/types/Axis.ts +3 -0
  56. package/types/BaseVisualizationType.ts +1 -0
  57. package/types/BoxPlot.ts +21 -0
  58. package/types/Column.ts +1 -0
  59. package/types/ConfidenceInterval.ts +1 -0
  60. package/types/General.ts +9 -0
  61. package/types/Legend.ts +18 -0
  62. package/types/Region.ts +10 -0
  63. package/types/Runtime.ts +3 -1
  64. package/types/Table.ts +5 -2
  65. package/types/UpdateFieldFunc.ts +1 -1
  66. package/types/ViewPort.ts +2 -0
  67. package/types/Visualization.ts +23 -5
  68. package/types/WCMSProps.ts +11 -0
  69. package/components/DataTable/components/SkipNav.tsx +0 -7
  70. package/helpers/cove/date.js +0 -9
  71. package/helpers/getViewport.js +0 -21
@@ -1,39 +1,56 @@
1
1
  .cove-multiselect {
2
2
  position: relative;
3
- .selected {
4
- border: 1px solid #ccc;
5
- padding: 5px;
6
- min-height: 40px;
7
- :is(button) {
8
- border: none;
9
- background: none;
10
- }
11
- :is(div) {
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
- padding: 0 0 0 5px;
14
- margin-right: 5px;
15
- margin-bottom: 2px;
16
- background: #ccc;
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
- .expand {
20
- padding: 0 5px;
21
- border-radius: 5px;
22
- background: #ccc;
23
- float: right;
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 #ccc;
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
- z-index: 1;
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-left: 10px;
61
+ padding: 0 10px;
45
62
  &:hover {
46
- background: #ccc;
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 = <>&#10004;</>
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: 999 } : {}
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
@@ -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)