@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.
- package/assets/icon-command.svg +3 -0
- package/assets/icon-rotate-left.svg +3 -0
- package/assets/icon-sankey.svg +1 -0
- package/assets/icon-table.svg +1 -0
- package/components/AdvancedEditor.jsx +9 -0
- package/components/DataTable/DataTable.tsx +37 -13
- 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/ExpandCollapse.tsx +22 -16
- package/components/DataTable/components/MapHeader.tsx +10 -5
- package/components/DataTable/helpers/chartCellMatrix.tsx +2 -2
- package/components/DataTable/helpers/customColumns.ts +4 -2
- package/components/DataTable/helpers/getChartCellValue.ts +4 -2
- package/components/DataTable/helpers/getDataSeriesColumns.ts +9 -1
- package/components/DataTable/helpers/mapCellMatrix.tsx +2 -2
- package/components/DataTable/types/TableConfig.ts +7 -7
- package/components/EditorPanel/ColumnsEditor.tsx +312 -0
- package/components/EditorPanel/DataTableEditor.tsx +42 -27
- package/components/Filters.jsx +35 -17
- package/components/Layout/components/Responsive.tsx +184 -0
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +47 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +902 -0
- package/components/Layout/components/Sidebar/index.tsx +3 -0
- package/components/Layout/components/Visualization/index.tsx +79 -0
- package/components/Layout/components/Visualization/visualizations.scss +33 -0
- package/components/Layout/index.tsx +11 -0
- package/components/Layout/styles/editor-grid-view.scss +156 -0
- package/components/Layout/styles/editor-utils.scss +197 -0
- package/components/Layout/styles/editor.scss +144 -0
- package/components/LegendCircle.jsx +4 -3
- package/components/MediaControls.jsx +1 -1
- 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 +8 -6
- package/components/Table/components/Row.tsx +6 -2
- package/components/Table/types/RowType.ts +3 -0
- package/components/Waiting.jsx +11 -1
- package/components/_stories/MultiSelect.stories.tsx +10 -1
- package/components/_stories/NestedDropdown.stories.tsx +58 -0
- package/components/_stories/styles.scss +1 -0
- package/components/createBarElement.jsx +120 -0
- package/components/elements/ScreenReaderText.tsx +8 -0
- package/components/elements/SkipTo.tsx +46 -0
- package/components/managers/DataDesigner.tsx +18 -18
- package/components/ui/Icon.tsx +9 -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/data/colorPalettes.js +1 -6
- package/helpers/cove/accessibility.ts +23 -0
- package/helpers/cove/date.ts +19 -0
- package/helpers/{coveUpdateWorker.js → coveUpdateWorker.ts} +9 -5
- package/helpers/isDomainExternal.js +14 -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 +7 -7
- package/helpers/ver/4.24.3.ts +56 -0
- package/package.json +4 -3
- package/styles/_data-table.scss +8 -13
- package/styles/_global.scss +7 -4
- package/styles/_variables.scss +3 -0
- package/styles/base.scss +4 -14
- package/styles/v2/base/index.scss +1 -1
- package/styles/v2/components/ui/tooltip.scss +0 -21
- package/types/Axis.ts +3 -0
- package/types/BaseVisualizationType.ts +1 -0
- package/types/ConfidenceInterval.ts +1 -0
- package/types/ConfigureData.ts +8 -0
- package/types/DataDescription.ts +9 -0
- package/types/Legend.ts +18 -0
- package/types/Region.ts +10 -0
- package/types/Runtime.ts +2 -0
- package/types/Table.ts +2 -1
- package/types/UpdateFieldFunc.ts +1 -1
- package/types/Visualization.ts +19 -10
- package/components/DataTable/components/SkipNav.tsx +0 -7
- package/helpers/cove/date.js +0 -9
- 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
|
|
@@ -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?:
|
|
11
|
-
updateDescriptionProp
|
|
12
|
-
visualizationKey
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
259
|
+
updateDescriptionProp('ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
|
|
260
260
|
}
|
|
261
261
|
e.target.value = ''
|
|
262
262
|
}}
|
package/components/ui/Icon.tsx
CHANGED
|
@@ -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:
|
|
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(' ')}
|
|
24
|
+
<header className={updatedClasses.join(' ')} style={props.style}>
|
|
25
25
|
{superTitle && <sup>{parse(superTitle)}</sup>}
|
|
26
|
-
<
|
|
26
|
+
<h2>
|
|
27
27
|
{parse(title)} {isDashboard}
|
|
28
|
-
</
|
|
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 */}
|
package/data/colorPalettes.js
CHANGED
|
@@ -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
|
|
3
|
+
import update_4_24_3 from './ver/4.24.3'
|
|
4
4
|
|
|
5
5
|
// 4.23.6 ------------------------------------------------------
|
|
6
|
-
const coveUpdateWorker =
|
|
6
|
+
export const coveUpdateWorker = config => {
|
|
7
7
|
let genConfig = config
|
|
8
8
|
|
|
9
|
-
// v4.
|
|
10
|
-
genConfig =
|
|
9
|
+
// v4.24.3
|
|
10
|
+
genConfig = update_4_24_3(genConfig)
|
|
11
11
|
|
|
12
12
|
return genConfig
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
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
|
+
}
|