@cdc/core 4.24.2 → 4.24.4

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 (84) hide show
  1. package/assets/icon-command.svg +3 -0
  2. package/assets/icon-rotate-left.svg +3 -0
  3. package/assets/icon-sankey.svg +1 -0
  4. package/assets/icon-table.svg +1 -0
  5. package/components/AdvancedEditor.jsx +9 -0
  6. package/components/DataTable/DataTable.tsx +37 -13
  7. package/components/DataTable/DataTableStandAlone.tsx +15 -0
  8. package/components/DataTable/components/CellAnchor.tsx +3 -1
  9. package/components/DataTable/components/ChartHeader.tsx +48 -12
  10. package/components/DataTable/components/DataTableEditorPanel.tsx +42 -0
  11. package/components/DataTable/components/ExpandCollapse.tsx +22 -16
  12. package/components/DataTable/components/MapHeader.tsx +10 -5
  13. package/components/DataTable/helpers/chartCellMatrix.tsx +2 -2
  14. package/components/DataTable/helpers/customColumns.ts +4 -2
  15. package/components/DataTable/helpers/getChartCellValue.ts +4 -2
  16. package/components/DataTable/helpers/getDataSeriesColumns.ts +9 -1
  17. package/components/DataTable/helpers/mapCellMatrix.tsx +2 -2
  18. package/components/DataTable/types/TableConfig.ts +7 -7
  19. package/components/EditorPanel/ColumnsEditor.tsx +312 -0
  20. package/components/EditorPanel/DataTableEditor.tsx +42 -27
  21. package/components/Filters.jsx +35 -17
  22. package/components/Layout/components/Responsive.tsx +184 -0
  23. package/components/Layout/components/Sidebar/components/Sidebar.tsx +47 -0
  24. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +902 -0
  25. package/components/Layout/components/Sidebar/index.tsx +3 -0
  26. package/components/Layout/components/Visualization/index.tsx +79 -0
  27. package/components/Layout/components/Visualization/visualizations.scss +33 -0
  28. package/components/Layout/index.tsx +11 -0
  29. package/components/Layout/styles/editor-grid-view.scss +156 -0
  30. package/components/Layout/styles/editor-utils.scss +197 -0
  31. package/components/Layout/styles/editor.scss +144 -0
  32. package/components/LegendCircle.jsx +4 -3
  33. package/components/MediaControls.jsx +1 -1
  34. package/components/MultiSelect/MultiSelect.tsx +39 -20
  35. package/components/MultiSelect/multiselect.styles.css +44 -27
  36. package/components/NestedDropdown/NestedDropdown.tsx +257 -0
  37. package/components/NestedDropdown/index.ts +1 -0
  38. package/components/NestedDropdown/nesteddropdown.styles.css +70 -0
  39. package/components/Table/Table.tsx +8 -6
  40. package/components/Table/components/Row.tsx +6 -2
  41. package/components/Table/types/RowType.ts +3 -0
  42. package/components/Waiting.jsx +11 -1
  43. package/components/_stories/MultiSelect.stories.tsx +10 -1
  44. package/components/_stories/NestedDropdown.stories.tsx +58 -0
  45. package/components/_stories/styles.scss +1 -0
  46. package/components/createBarElement.jsx +120 -0
  47. package/components/elements/ScreenReaderText.tsx +8 -0
  48. package/components/elements/SkipTo.tsx +46 -0
  49. package/components/managers/DataDesigner.tsx +18 -18
  50. package/components/ui/Icon.tsx +9 -1
  51. package/components/ui/Title/Title.scss +7 -1
  52. package/components/ui/Title/index.tsx +3 -3
  53. package/components/ui/Tooltip.jsx +1 -1
  54. package/data/colorPalettes.js +1 -6
  55. package/helpers/cove/accessibility.ts +23 -0
  56. package/helpers/cove/date.ts +19 -0
  57. package/helpers/{coveUpdateWorker.js → coveUpdateWorker.ts} +9 -5
  58. package/helpers/isDomainExternal.js +14 -0
  59. package/helpers/queryStringUtils.js +26 -0
  60. package/helpers/tests/updateFieldFactory.test.ts +89 -0
  61. package/helpers/updateFieldFactory.ts +38 -0
  62. package/helpers/useDataVizClasses.js +7 -7
  63. package/helpers/ver/4.24.3.ts +56 -0
  64. package/package.json +4 -3
  65. package/styles/_data-table.scss +8 -13
  66. package/styles/_global.scss +7 -4
  67. package/styles/_variables.scss +3 -0
  68. package/styles/base.scss +4 -14
  69. package/styles/v2/base/index.scss +1 -1
  70. package/styles/v2/components/ui/tooltip.scss +0 -21
  71. package/types/Axis.ts +3 -0
  72. package/types/BaseVisualizationType.ts +1 -0
  73. package/types/ConfidenceInterval.ts +1 -0
  74. package/types/ConfigureData.ts +8 -0
  75. package/types/DataDescription.ts +9 -0
  76. package/types/Legend.ts +18 -0
  77. package/types/Region.ts +10 -0
  78. package/types/Runtime.ts +2 -0
  79. package/types/Table.ts +2 -1
  80. package/types/UpdateFieldFunc.ts +1 -1
  81. package/types/Visualization.ts +19 -10
  82. package/components/DataTable/components/SkipNav.tsx +0 -7
  83. package/helpers/cove/date.js +0 -9
  84. package/helpers/ver/4.23.js +0 -10
@@ -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
@@ -1,3 +1,4 @@
1
+ @import '../../styles/base.scss';
1
2
  @import '../../styles/_variables';
2
3
  @import '../../styles/_mixins';
3
4
  @import '../../styles/_data-table';
@@ -0,0 +1,120 @@
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 adjustedWidth = Math.max(0, width);
5
+ const adjustedHeight = Math.max(0, height);
6
+
7
+ const isHorizontal = config.orientation === 'horizontal'
8
+ const isRounded = config.barStyle === 'rounded'
9
+ const isStacked = config.visualizationSubType === 'stacked'
10
+ const tipRounding = config.tipRounding
11
+ const comboBarSeriesCount = config.visualizationType === 'Combo' && config.runtime?.barSeriesKeys?.length
12
+ const barSeriesCount = config.runtime.seriesKeys.length
13
+ const isolateSeriesCount = config.visualizationType === 'Bar' && config.legend.axisAlign && seriesHighlight?.length ? seriesHighlight?.length : 0
14
+ const stackCount = comboBarSeriesCount ? comboBarSeriesCount : isolateSeriesCount ? isolateSeriesCount : barSeriesCount
15
+
16
+ let radius = config.roundingStyle === 'standard' ? 8 : config.roundingStyle === 'shallow' ? 5 : config.roundingStyle === 'finger' ? 15 : 0
17
+ if (radius > adjustedWidth / 2 || radius > adjustedHeight / 2) {
18
+ radius = Math.min(adjustedWidth / 2, adjustedHeight / 2)
19
+ }
20
+
21
+ const roundTop = () => {
22
+ return `M${x},${y + adjustedHeight}
23
+ L${x},${y + radius}
24
+ Q${x},${y} ${x + radius},${y}
25
+ L${x + adjustedWidth - radius},${y}
26
+ Q${x + adjustedWidth},${y} ${x + adjustedWidth},${y + radius}
27
+ L${x + adjustedWidth},${y + adjustedHeight}
28
+ L${x},${y + adjustedHeight}`
29
+ }
30
+
31
+ const roundRight = () => {
32
+ return `M${x},${y + adjustedHeight}
33
+ L${x},${y}
34
+ L${x + adjustedWidth - radius},${y}
35
+ Q${x + adjustedWidth},${y} ${x + adjustedWidth},${y + radius}
36
+ L${x + adjustedWidth},${y + adjustedHeight - radius}
37
+ Q${x + adjustedWidth},${y + adjustedHeight} ${x + adjustedWidth - radius},${y + adjustedHeight}
38
+ L${x},${y + adjustedHeight}`
39
+ }
40
+
41
+ const roundBottom = () => {
42
+ return `M${x + radius},${y + adjustedHeight}
43
+ Q${x},${y + adjustedHeight} ${x},${y + adjustedHeight - radius}
44
+ L${x},${y}
45
+ L${x + adjustedWidth},${y}
46
+ L${x + adjustedWidth},${y + adjustedHeight - radius}
47
+ Q${x + adjustedWidth},${y + adjustedHeight} ${x + adjustedWidth - radius},${y + adjustedHeight}
48
+ L${x + radius},${y + adjustedHeight}`
49
+ }
50
+
51
+ const roundLeft = () => {
52
+ return `M${x + radius},${y + adjustedHeight}
53
+ Q${x},${y + adjustedHeight} ${x},${y + adjustedHeight - radius}
54
+ L${x},${y + radius}
55
+ Q${x},${y} ${x + radius},${y}
56
+ L${x + adjustedWidth},${y}
57
+ L${x + adjustedWidth},${y + adjustedHeight}
58
+ L${x + radius},${y + adjustedHeight}`
59
+ }
60
+
61
+ const roundFull = () => {
62
+ return `M${x + radius},${y + adjustedHeight}
63
+ Q${x},${y + adjustedHeight} ${x},${y + adjustedHeight - radius}
64
+ L${x},${y + radius}
65
+ Q${x},${y} ${x + radius},${y}
66
+ L${x + adjustedWidth - radius},${y}
67
+ Q${x + adjustedWidth},${y} ${x + adjustedWidth},${y + radius}
68
+ L${x + adjustedWidth},${y + adjustedHeight - radius}
69
+ Q${x + adjustedWidth},${y + adjustedHeight} ${x + adjustedWidth - radius},${y + adjustedHeight}
70
+ L${x + radius},${y + adjustedHeight}`
71
+ }
72
+
73
+ const nonRounded = () => {
74
+ return `M${x},${y}
75
+ L${x + adjustedWidth},${y}
76
+ L${x + adjustedWidth},${y + adjustedHeight}
77
+ L${x},${y + adjustedHeight}
78
+ L${x},${y}`
79
+ }
80
+
81
+ let path
82
+ if (index === undefined || index === null || !isRounded) {
83
+ path = nonRounded()
84
+ } else {
85
+ path = nonRounded()
86
+
87
+ if ((isStacked && index + 1 === stackCount) || !isStacked) {
88
+ path = isHorizontal ? roundRight() : roundTop()
89
+ }
90
+ if (!isStacked && index === -1) {
91
+ path = isHorizontal ? roundLeft() : roundBottom()
92
+ }
93
+ if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
94
+ path = isHorizontal ? roundLeft() : roundBottom()
95
+ }
96
+ if (tipRounding === 'full' && ((isStacked && index === 0 && stackCount === 1) || !isStacked)) {
97
+ path = roundFull()
98
+ }
99
+ }
100
+
101
+ return (
102
+ <path
103
+ id={id}
104
+ className={className}
105
+ d={path}
106
+ fill={background}
107
+ stroke={borderColor}
108
+ strokeWidth={borderWidth}
109
+ onMouseOver={onMouseOver}
110
+ onMouseLeave={onMouseLeave}
111
+ onClick={onClick}
112
+ data-tooltip-html={tooltipHtml}
113
+ data-tooltip-id={tooltipId}
114
+ style={{
115
+ transition: 'all 0.2s linear',
116
+ ...styleOverrides
117
+ }}
118
+ ></path>
119
+ )
120
+ }
@@ -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,46 @@
1
+ import { useId } from 'react'
2
+
3
+ type SkipToProps = {
4
+ // id to skip to
5
+ skipId: string
6
+ // focusable text output, screen reader message
7
+ skipMessage: string
8
+ }
9
+
10
+ const SkipTo: React.FC<SkipToProps> = ({ skipId, skipMessage }) => {
11
+ const accessibleId = useId()
12
+ const handleOnClick = () => {
13
+ // Navigate to the specific part of the page
14
+ const targetElement = document.getElementById(skipId)
15
+ if (targetElement) {
16
+ targetElement.scrollIntoView()
17
+ targetElement.setAttribute('tabIndex', '-1')
18
+ targetElement.focus()
19
+ // Setup to remove tabIndex on blur to maintain document flow
20
+ const blurListener = () => {
21
+ targetElement.removeAttribute('tabIndex')
22
+ targetElement.removeEventListener('blur', blurListener)
23
+ }
24
+ targetElement.addEventListener('blur', blurListener)
25
+ }
26
+ }
27
+ return (
28
+ <div
29
+ tabIndex={0}
30
+ id={`skip-nav--${accessibleId}`}
31
+ className='cdcdataviz-sr-only-focusable'
32
+ onClick={handleOnClick}
33
+ onKeyDown={e => {
34
+ if (e.key === 'Enter' || e.key === ' ') {
35
+ handleOnClick()
36
+ }
37
+ }}
38
+ role='link'
39
+ aria-label={skipMessage}
40
+ >
41
+ {skipMessage}
42
+ </div>
43
+ )
44
+ }
45
+
46
+ export default SkipTo
@@ -5,18 +5,18 @@ import Card from '../elements/Card'
5
5
 
6
6
  import { DATA_TABLE_VERTICAL, DATA_TABLE_HORIZONTAL, DATA_TABLE_SINGLE_ROW, DATA_TABLE_MULTI_ROW } from '../../templates/dataDesignerTables'
7
7
  import '../../styles/v2/components/data-designer.scss'
8
+ import { ConfigureData } from '../../types/ConfigureData'
8
9
 
9
10
  type DataDesignerProps = {
10
- configureData?: any // todo: add description here when understood better
11
- updateDescriptionProp?: (vizKey: string, dataKey: string, propName: string, value: any) => void // used to update data description fields
12
- visualizationKey?: any // todo: add description here when understood better
13
- dataKey?: string // appears to be the data file name, ie valid-data.csv
11
+ configureData?: ConfigureData
12
+ updateDescriptionProp: (propName: string, value: any) => void // used to update data description fields
13
+ visualizationKey: string
14
14
  config?: any // can be one of many different types of chart configs that aren't fully established yet
15
15
  setConfig?: (newConfig: any) => void
16
16
  }
17
17
 
18
18
  const DataDesigner = (props: DataDesignerProps) => {
19
- const { configureData, updateDescriptionProp, visualizationKey, dataKey, config, setConfig } = props
19
+ const { configureData, updateDescriptionProp, config, setConfig } = props
20
20
  const hasDataOrientation = config?.visualizationType !== 'Forest Plot'
21
21
  const hasMultipleSeries = config?.visualizationType !== 'Forest Plot'
22
22
  const hasRowSelection = config?.visualizationType !== 'Forest Plot'
@@ -47,7 +47,7 @@ const DataDesigner = (props: DataDesignerProps) => {
47
47
  <button
48
48
  className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === false ? ' active' : '')}
49
49
  onClick={() => {
50
- updateDescriptionProp(visualizationKey, dataKey, 'horizontal', false)
50
+ updateDescriptionProp('horizontal', false)
51
51
  }}
52
52
  >
53
53
  <Card>
@@ -63,7 +63,7 @@ const DataDesigner = (props: DataDesignerProps) => {
63
63
  <button
64
64
  className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === true ? ' active' : '')}
65
65
  onClick={() => {
66
- updateDescriptionProp(visualizationKey, dataKey, 'horizontal', true)
66
+ updateDescriptionProp('horizontal', true)
67
67
  }}
68
68
  >
69
69
  <Card>
@@ -90,7 +90,7 @@ const DataDesigner = (props: DataDesignerProps) => {
90
90
  hoverStyle={{ backgroundColor: '#015daa' }}
91
91
  className='mr-1'
92
92
  onClick={() => {
93
- updateDescriptionProp(visualizationKey, dataKey, 'series', true)
93
+ updateDescriptionProp('series', true)
94
94
  }}
95
95
  active={configureData.dataDescription.series === true}
96
96
  >
@@ -100,7 +100,7 @@ const DataDesigner = (props: DataDesignerProps) => {
100
100
  style={{ backgroundColor: '#00345d' }}
101
101
  hoverStyle={{ backgroundColor: '#015daa' }}
102
102
  onClick={() => {
103
- updateDescriptionProp(visualizationKey, dataKey, 'series', false)
103
+ updateDescriptionProp('series', false)
104
104
  }}
105
105
  active={configureData.dataDescription.series === false}
106
106
  >
@@ -114,7 +114,7 @@ const DataDesigner = (props: DataDesignerProps) => {
114
114
  <div className='mb-1'>Which property in the dataset represents which series the row is describing?</div>
115
115
  <select
116
116
  onChange={e => {
117
- updateDescriptionProp(visualizationKey, dataKey, 'seriesKey', e.target.value)
117
+ updateDescriptionProp('seriesKey', e.target.value)
118
118
  }}
119
119
  defaultValue={configureData.dataDescription.seriesKey}
120
120
  >
@@ -136,7 +136,7 @@ const DataDesigner = (props: DataDesignerProps) => {
136
136
  <button
137
137
  className={'cove-data-designer__button' + (configureData.dataDescription.singleRow === true ? ' active' : '')}
138
138
  onClick={() => {
139
- updateDescriptionProp(visualizationKey, dataKey, 'singleRow', true)
139
+ updateDescriptionProp('singleRow', true)
140
140
  }}
141
141
  >
142
142
  <Card>
@@ -150,7 +150,7 @@ const DataDesigner = (props: DataDesignerProps) => {
150
150
  <button
151
151
  className={'cove-data-designer__button' + (configureData.dataDescription.singleRow === false ? ' active' : '')}
152
152
  onClick={() => {
153
- updateDescriptionProp(visualizationKey, dataKey, 'singleRow', false)
153
+ updateDescriptionProp('singleRow', false)
154
154
  }}
155
155
  >
156
156
  <Card>
@@ -168,7 +168,7 @@ const DataDesigner = (props: DataDesignerProps) => {
168
168
  <div className='mb-1'>Which property in the dataset represents which series the row is describing?</div>
169
169
  <select
170
170
  onChange={e => {
171
- updateDescriptionProp(visualizationKey, dataKey, 'seriesKey', e.target.value)
171
+ updateDescriptionProp('seriesKey', e.target.value)
172
172
  }}
173
173
  defaultValue={configureData.dataDescription.seriesKey}
174
174
  >
@@ -184,7 +184,7 @@ const DataDesigner = (props: DataDesignerProps) => {
184
184
  <div className='mb-1'>Which property in the dataset represents the values for the category/date axis or map geography?</div>
185
185
  <select
186
186
  onChange={e => {
187
- updateDescriptionProp(visualizationKey, dataKey, 'xKey', e.target.value)
187
+ updateDescriptionProp('xKey', e.target.value)
188
188
  }}
189
189
  defaultValue={configureData.dataDescription.xKey}
190
190
  >
@@ -207,7 +207,7 @@ const DataDesigner = (props: DataDesignerProps) => {
207
207
  onClick={() => {
208
208
  let newValueKeys = configureData.dataDescription.valueKeysTallSupport
209
209
  newValueKeys.splice(index, 1)
210
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeysTallSupport', newValueKeys)
210
+ updateDescriptionProp('valueKeysTallSupport', newValueKeys)
211
211
  }}
212
212
  >
213
213
  X
@@ -219,7 +219,7 @@ const DataDesigner = (props: DataDesignerProps) => {
219
219
  <select
220
220
  onChange={e => {
221
221
  if (e.target.value && (!configureData.dataDescription.valueKeysTallSupport || configureData.dataDescription.valueKeysTallSupport.indexOf(e.target.value) === -1)) {
222
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeysTallSupport', [...(configureData.dataDescription.valueKeysTallSupport || []), e.target.value])
222
+ updateDescriptionProp('valueKeysTallSupport', [...(configureData.dataDescription.valueKeysTallSupport || []), e.target.value])
223
223
  }
224
224
  }}
225
225
  >
@@ -244,7 +244,7 @@ const DataDesigner = (props: DataDesignerProps) => {
244
244
  onClick={() => {
245
245
  let newIgnoredKeys = configureData.dataDescription.ignoredKeys
246
246
  newIgnoredKeys.splice(index, 1)
247
- updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', newIgnoredKeys)
247
+ updateDescriptionProp('ignoredKeys', newIgnoredKeys)
248
248
  }}
249
249
  >
250
250
  X
@@ -256,7 +256,7 @@ const DataDesigner = (props: DataDesignerProps) => {
256
256
  <select
257
257
  onChange={e => {
258
258
  if (e.target.value) {
259
- updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
259
+ updateDescriptionProp('ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
260
260
  }
261
261
  e.target.value = ''
262
262
  }}
@@ -31,6 +31,10 @@ 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'
36
+ import iconRotateLeft from '../../assets/icon-rotate-left.svg'
37
+ import iconCommand from '../../assets/icon-command.svg'
34
38
 
35
39
  import '../../styles/v2/components/icon.scss'
36
40
 
@@ -64,7 +68,11 @@ const iconHash = {
64
68
  plus: iconPlus,
65
69
  minus: iconMinus,
66
70
  'filtered-text': iconText,
67
- 'filter-dropdowns': iconDropdowns
71
+ 'filter-dropdowns': iconDropdowns,
72
+ table: iconTable,
73
+ sankey: iconSankey,
74
+ rotateLeft: iconRotateLeft,
75
+ command: iconCommand
68
76
  }
69
77
 
70
78
  export const ICON_TYPES = Object.keys(iconHash)
@@ -2,9 +2,15 @@
2
2
  position: relative;
3
3
  padding: 0.6em 0.8em;
4
4
  margin: 0;
5
- color: #fff;
5
+ color: var(--white);
6
6
  font-size: 1.1em;
7
7
 
8
+ h2 {
9
+ font-size: 1.1rem;
10
+ color: var(--white);
11
+ margin: 0;
12
+ }
13
+
8
14
  border-top-left-radius: 3px;
9
15
  border-top-right-radius: 3px;
10
16
 
@@ -21,11 +21,11 @@ const Title = (props: HeaderProps) => {
21
21
  return (
22
22
  title &&
23
23
  showTitle && (
24
- <header className={updatedClasses.join(' ')} aria-hidden='true' style={props.style} aria-level={ariaLevel}>
24
+ <header className={updatedClasses.join(' ')} style={props.style}>
25
25
  {superTitle && <sup>{parse(superTitle)}</sup>}
26
- <div>
26
+ <h2>
27
27
  {parse(title)} {isDashboard}
28
- </div>
28
+ </h2>
29
29
  </header>
30
30
  )
31
31
  )
@@ -26,7 +26,7 @@ const Tooltip = ({ place = 'top', trigger = 'hover', float = false, shadow = tru
26
26
 
27
27
  return (
28
28
  <span className='cove-tooltip' style={style} {...attributes}>
29
- <a id={uid} className='cove-tooltip--target' data-tooltip-float={float} data-tooltip-place={place} data-tooltip-events={generateTriggerEvent()}>
29
+ <a id={uid} role='dialog' tabIndex={0} className='cove-tooltip--target' data-tooltip-float={float} data-tooltip-place={place} data-tooltip-events={generateTriggerEvent()}>
30
30
  {tooltipTargetChildren ? tooltipTargetChildren.props.children : null}
31
31
  </a>
32
32
  {/* prettier-ignore */}
@@ -11,17 +11,12 @@ const colorPalettesMap = {
11
11
  greenblue: ['#f7fcf0', '#e0f3db', '#ccebc5', '#a8ddb5', '#7bccc4', '#4eb3d3', '#267BA6', '#0868ac', '#084081'],
12
12
  yellowpurple: ['#FFF0B0', '#F5CC76', '#EDAE4B', '#E3683C', '#BF2A48', '#6D2059', '#8F0C4B', '#310958', '#0E0943'],
13
13
  qualitative1: ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#6a3d9a', '#cab2d6', '#E31A90', '#15017A', '#C2C0FC'],
14
-
15
14
  qualitative2: ['#7fc97f', '#beaed4', '#ff9', '#386cb0', '#f0027f', '#bf5b17', '#666', '#fedab8'],
16
-
17
15
  qualitative3: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666'],
18
-
19
16
  qualitative4: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ff3', '#a65628', '#f781bf'],
20
-
17
+ // qualitative9 doesn't appear to be used anywhere...
21
18
  qualitative9: ['#497d0c', '#84BC49', '#88c3ea', '#fcad90', '#f26b4f', '#c31b1f', '#c31b1f'],
22
-
23
19
  'sequential-blue-2(MPX)': ['#F5FEFF', '#E1FBFF', '#C0F2FD', '#94E2ED', '#5EBAD4', '#3695BE', '#2273A0', '#0E5181', '#093460'],
24
-
25
20
  'sequential-orange(MPX)': ['#FFFDF0', '#FFF7DC', '#FFE9C2', '#FFD097', '#F7A866', '#EB7723', '#B95117', '#853209', '#661F00']
26
21
  }
27
22
 
@@ -0,0 +1,23 @@
1
+ import chroma from 'chroma-js'
2
+
3
+ /**
4
+ * WCAG 2.0 Standard requires 4.5:1 contrast ratio
5
+ * https://www.w3.org/TR/WCAG20-TECHS/G18.html
6
+ *
7
+ * !important - DO NOT CHANGE.
8
+ */
9
+ export const WCAG_CONTRAST_RATIO = 4.5
10
+
11
+ export const getContrastColor = (textColor: string, bgColor: string) => {
12
+ if (chroma.contrast(textColor, bgColor) < WCAG_CONTRAST_RATIO) {
13
+ switch (textColor) {
14
+ case '#FFF':
15
+ return '#000'
16
+ case '#000':
17
+ return '#FFF'
18
+ default:
19
+ return textColor
20
+ }
21
+ }
22
+ return textColor
23
+ }
@@ -0,0 +1,19 @@
1
+ import { timeFormat, timeParse } from 'd3-time-format'
2
+ import { type Axis } from '@cdc/core/types/Axis'
3
+
4
+ export function formatDate(format = undefined, date) {
5
+ return timeFormat(format)(date)
6
+ }
7
+
8
+ export function parseDate(format = undefined, dateString) {
9
+ return timeParse(format)(dateString) || new Date()
10
+ }
11
+
12
+ export const isDateScale = (axis: Axis) => {
13
+ try {
14
+ if (!axis) throw new Error('COVE: No axis passed to isDateScale')
15
+ return ['date', 'date-time'].includes(axis.type)
16
+ } catch ({ message }) {
17
+ console.warn(message) // eslint-disable-line
18
+ }
19
+ }
@@ -1,15 +1,19 @@
1
1
  // If config key names or position in the config have been changed with a version change,
2
2
  // process those config entries and format old values into new
3
- import update_4_23 from './ver/4.23'
3
+ import update_4_24_3 from './ver/4.24.3'
4
4
 
5
5
  // 4.23.6 ------------------------------------------------------
6
- const coveUpdateWorker = async config => {
6
+ export const coveUpdateWorker = config => {
7
7
  let genConfig = config
8
8
 
9
- // v4.23
10
- genConfig = await update_4_23(genConfig)
9
+ // v4.24.3
10
+ genConfig = update_4_24_3(genConfig)
11
11
 
12
12
  return genConfig
13
13
  }
14
14
 
15
- export default coveUpdateWorker
15
+ const asyncWorker = async config => {
16
+ return await coveUpdateWorker(config)
17
+ }
18
+
19
+ export default asyncWorker
@@ -0,0 +1,14 @@
1
+ export default function isDomainExternal(domain) {
2
+ const internalDomains = ['cdc.gov', 'localhost', 'facebook.com', 'twitter.com', 'linkedin.com', 'pinterest.com', 'youtube.com', 'youtube-nocookie.com', 'plus.google.com', 'instagram.com', 'flickr.com', 'tumblr.com', 'cdc.sharepoint.com', 'vaccines.gov', 'vacunas.gov']
3
+
4
+ const hostname = new URL(domain, window.location.origin).hostname
5
+ let external = true
6
+
7
+ internalDomains.forEach(internalDomain => {
8
+ if (hostname.indexOf(internalDomain) !== -1 && hostname.indexOf(internalDomain) === hostname.length - internalDomain.length) {
9
+ external = false
10
+ }
11
+ })
12
+
13
+ return external
14
+ }
@@ -0,0 +1,26 @@
1
+ export function getQueryStringFilterValue(filter) {
2
+ const urlParams = new URLSearchParams(window.location.search)
3
+ if(filter.setByQueryParameter){ // Only check the query string if the filter is supposed to be set by QS param
4
+ const filterValue = urlParams.get(filter.setByQueryParameter)
5
+ if(filterValue && filter.values){
6
+ for(let i = 0; i < filter.values.length; i++){
7
+ if(filter.values[i] && filter.values[i].toLowerCase() === filterValue.toLowerCase()){
8
+ return filter.values[i]
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ export function getQueryParams(filter) {
16
+ const queryParams = {};
17
+ for (const [key, value] of (new URLSearchParams(window.location.search)).entries()) {
18
+ queryParams[key] = value
19
+ }
20
+ return queryParams;
21
+ }
22
+
23
+ export function updateQueryString(queryParams) {
24
+ const updateUrl = `${window.location.origin}${window.location.pathname}?${Object.keys(queryParams).map(queryParam => `${queryParam}=${encodeURIComponent(queryParams[queryParam])}`).join('&')}`;
25
+ window.history.pushState({path: updateUrl}, '', updateUrl);
26
+ }