@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
|
@@ -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 */}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
const COLORS = [
|
|
4
|
+
['baseColor', '#333'],
|
|
5
|
+
['blue', '#005eaa'],
|
|
6
|
+
['lightestGray', '#f2f2f2'],
|
|
7
|
+
['lightGray', '#c7c7c7'],
|
|
8
|
+
['mediumGray', '#565656'],
|
|
9
|
+
['darkGray', '#333'],
|
|
10
|
+
['red', '#d8000c'],
|
|
11
|
+
['white', '#fff'],
|
|
12
|
+
|
|
13
|
+
['primary', '#005eaa'],
|
|
14
|
+
['secondary', '#88c3ea'],
|
|
15
|
+
['tertiary', '#c0e9ff'],
|
|
16
|
+
['quaternary', '#edf9ff'],
|
|
17
|
+
|
|
18
|
+
['purple-primary', '#712177'],
|
|
19
|
+
['purple-secondary', '#b890bb'],
|
|
20
|
+
['purple-tertiary', '#e3d3e4'],
|
|
21
|
+
['purple-quaternary', '#f7f2f7'],
|
|
22
|
+
|
|
23
|
+
['brown-primary', '#705043'],
|
|
24
|
+
['brown-secondary', '#ad907b'],
|
|
25
|
+
['brown-tertiary', '#d7ccc8'],
|
|
26
|
+
['brown-quaternary', '#f2ebe8'],
|
|
27
|
+
|
|
28
|
+
['teal-primary', '#00695c'],
|
|
29
|
+
['teal-secondary', '#4ebaaa'],
|
|
30
|
+
['teal-tertiary', '#ceece7'],
|
|
31
|
+
['teal-quaternary', '#ebf7f5'],
|
|
32
|
+
|
|
33
|
+
['pink-primary', '#af4448'],
|
|
34
|
+
['pink-secondary', '#e57373'],
|
|
35
|
+
['pink-tertiary', '#ffc2c2'],
|
|
36
|
+
['pink-quaternary', '#ffe7e7'],
|
|
37
|
+
|
|
38
|
+
['orange-primary', '#bb4d00'],
|
|
39
|
+
['orange-secondary', '#ffad42'],
|
|
40
|
+
['orange-tertiary', '#ffe97d'],
|
|
41
|
+
['orange-quaternary', '#fff4cf'],
|
|
42
|
+
|
|
43
|
+
['slate-primary', '#29434e'],
|
|
44
|
+
['slate-secondary', '#7e9ba5'],
|
|
45
|
+
['slate-tertiary', '#b6c6d2'],
|
|
46
|
+
['slate-quaternary', '#e2e8ed'],
|
|
47
|
+
|
|
48
|
+
['indigo-primary', '#26418f'],
|
|
49
|
+
['indigo-secondary', '#92a6dd'],
|
|
50
|
+
['indigo-tertiary', '#dee8ff'],
|
|
51
|
+
['indigo-quaternary', '#f2f6ff'],
|
|
52
|
+
|
|
53
|
+
['cyan-primary', '#006778'],
|
|
54
|
+
['cyan-secondary', '#65b0bd'],
|
|
55
|
+
['cyan-tertiary', '#cce5e9'],
|
|
56
|
+
['cyan-quaternary', '#ebf5f6'],
|
|
57
|
+
|
|
58
|
+
['green-primary', '#4b830d'],
|
|
59
|
+
['green-secondary', '#84bc49'],
|
|
60
|
+
['green-tertiary', '#dcedc8'],
|
|
61
|
+
['green-quaternary', '#f1f8e9'],
|
|
62
|
+
|
|
63
|
+
['amber-primary', '#fbab18'],
|
|
64
|
+
['amber-secondary', '#ffd54f'],
|
|
65
|
+
['amber-tertiary', '#ffecb3'],
|
|
66
|
+
['amber-quaternary', '#fff7e1']
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const ColorArray = () => {
|
|
70
|
+
return (
|
|
71
|
+
<table className='table'>
|
|
72
|
+
{COLORS.map(([name, hex]) => (
|
|
73
|
+
<tr>
|
|
74
|
+
<td style={{ background: hex, width: '60px' }}></td>
|
|
75
|
+
<td>{name}</td>
|
|
76
|
+
<td>{hex}</td>
|
|
77
|
+
</tr>
|
|
78
|
+
))}
|
|
79
|
+
</table>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const meta: Meta<typeof ColorArray> = {
|
|
84
|
+
title: 'Components/Atoms/Colors',
|
|
85
|
+
component: ColorArray
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type Story = StoryObj<typeof ColorArray>
|
|
89
|
+
|
|
90
|
+
export const Primary: Story = {}
|
|
91
|
+
|
|
92
|
+
export default meta
|
|
@@ -3,20 +3,27 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|
|
3
3
|
|
|
4
4
|
import Icon, { ICON_TYPES } from '../Icon'
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const IconArray = () => {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
{ICON_TYPES.map(name => (
|
|
10
|
+
<div>
|
|
11
|
+
<span>
|
|
12
|
+
<Icon display={name} /> {name}{' '}
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
))}
|
|
16
|
+
</>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const meta: Meta<typeof IconArray> = {
|
|
7
21
|
title: 'Components/Atoms/Icon',
|
|
8
|
-
component:
|
|
9
|
-
parameters: {
|
|
10
|
-
display: ICON_TYPES
|
|
11
|
-
}
|
|
22
|
+
component: IconArray
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
type Story = StoryObj<typeof Icon>
|
|
15
26
|
|
|
16
|
-
export const Primary: Story = {
|
|
17
|
-
args: {
|
|
18
|
-
display: 'question'
|
|
19
|
-
}
|
|
20
|
-
}
|
|
27
|
+
export const Primary: Story = {}
|
|
21
28
|
|
|
22
29
|
export default meta
|
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,6 +1,7 @@
|
|
|
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
3
|
import update_4_23 from './ver/4.23'
|
|
4
|
+
import update_4_24_3 from './ver/4.24.3'
|
|
4
5
|
|
|
5
6
|
// 4.23.6 ------------------------------------------------------
|
|
6
7
|
const coveUpdateWorker = async config => {
|
|
@@ -9,6 +10,9 @@ const coveUpdateWorker = async config => {
|
|
|
9
10
|
// v4.23
|
|
10
11
|
genConfig = await update_4_23(genConfig)
|
|
11
12
|
|
|
13
|
+
// v4.24.3
|
|
14
|
+
genConfig = await update_4_24_3(genConfig)
|
|
15
|
+
|
|
12
16
|
return genConfig
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import Papa from 'papaparse'
|
|
2
|
+
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
2
3
|
|
|
3
|
-
export default async function (
|
|
4
|
+
export default async function (_url, visualizationType = '') {
|
|
5
|
+
let url = new URL(_url, window.location.origin)
|
|
4
6
|
try {
|
|
5
|
-
url = new URL(url, window.location.origin)
|
|
6
|
-
|
|
7
7
|
const path = url.pathname
|
|
8
8
|
const regex = /(?:\.([^.]+))?$/
|
|
9
9
|
const ext = regex.exec(path)[1]
|
|
10
10
|
|
|
11
|
-
if ('csv' === ext) {
|
|
11
|
+
if ('csv' === ext || isSolrCsv(_url)) {
|
|
12
12
|
return await fetch(url.href)
|
|
13
13
|
.then(response => response.text())
|
|
14
14
|
.then(responseText => {
|
|
@@ -30,7 +30,7 @@ export default async function (url, visualizationType = '') {
|
|
|
30
30
|
return parsedCsv.data
|
|
31
31
|
})
|
|
32
32
|
} else {
|
|
33
|
-
return await fetch(url.href).then(response => response.json())
|
|
33
|
+
return await fetch(isSolrCsv(_url) ? _url : url.href).then(response => response.json())
|
|
34
34
|
}
|
|
35
35
|
} catch {
|
|
36
36
|
// If we can't parse it, still attempt to fetch it
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ViewPort } from '../types/ViewPort'
|
|
2
|
+
|
|
3
|
+
export const viewports = {
|
|
4
|
+
lg: 1200,
|
|
5
|
+
md: 992,
|
|
6
|
+
sm: 768,
|
|
7
|
+
xs: 576,
|
|
8
|
+
xxs: 350
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function getViewport(size): ViewPort {
|
|
12
|
+
let result: ViewPort = 'lg'
|
|
13
|
+
|
|
14
|
+
if (size > 1200) return result
|
|
15
|
+
|
|
16
|
+
for (let viewport in viewports) {
|
|
17
|
+
if (size <= viewports[viewport]) {
|
|
18
|
+
result = viewport as ViewPort
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result
|
|
23
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { updateFieldFactory } from '../updateFieldFactory'
|
|
2
|
+
|
|
3
|
+
describe('updateFieldFactory', () => {
|
|
4
|
+
it('should update the top level field when section and subsection are null', () => {
|
|
5
|
+
const config = { filterColumn: 'oldValue', filterValue: 'oldValue' }
|
|
6
|
+
let updatedConfig = {}
|
|
7
|
+
const updateConfig = newConfig => {
|
|
8
|
+
updatedConfig = newConfig
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const updateField = updateFieldFactory(config, updateConfig)
|
|
12
|
+
updateField(null, null, 'filterColumn', 'newValue')
|
|
13
|
+
|
|
14
|
+
expect(updatedConfig).toEqual({ filterColumn: 'newValue', filterValue: '' })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should update the second level when section is not null and subsection is null', () => {
|
|
18
|
+
const config = { section: { filterColumn: 'oldValue', filterValue: 'oldValue' } }
|
|
19
|
+
let updatedConfig = {}
|
|
20
|
+
const updateConfig = newConfig => {
|
|
21
|
+
updatedConfig = newConfig
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const updateField = updateFieldFactory(config, updateConfig)
|
|
25
|
+
updateField('section', null, 'filterColumn', 'newValue')
|
|
26
|
+
|
|
27
|
+
expect(updatedConfig).toEqual({ section: { filterColumn: 'newValue', filterValue: 'oldValue' } })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should add to the second level when section is an array and subsection is null', () => {
|
|
31
|
+
const config = { section: ['oldValue'] }
|
|
32
|
+
let updatedConfig = {}
|
|
33
|
+
const updateConfig = newConfig => {
|
|
34
|
+
updatedConfig = newConfig
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const updateField = updateFieldFactory(config, updateConfig)
|
|
38
|
+
updateField('section', null, 'thisParamDoesNotMatter', 'newValue')
|
|
39
|
+
|
|
40
|
+
expect(updatedConfig).toEqual({ section: ['oldValue', 'newValue'] })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should update the third level when section and subsection are not null', () => {
|
|
44
|
+
const config = { section: { subsection: { filterColumn: 'oldValue', filterValue: 'oldValue' } } }
|
|
45
|
+
let updatedConfig = {}
|
|
46
|
+
const updateConfig = newConfig => {
|
|
47
|
+
updatedConfig = newConfig
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const updateField = updateFieldFactory(config, updateConfig)
|
|
51
|
+
updateField('section', 'subsection', 'filterColumn', 'newValue')
|
|
52
|
+
|
|
53
|
+
expect(updatedConfig).toEqual({ section: { subsection: { filterColumn: 'newValue', filterValue: 'oldValue' } } })
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should update the third level when section is an array', () => {
|
|
57
|
+
const config = { section: [{ filterColumn: 'oldValue', filterValue: 'oldValue' }] }
|
|
58
|
+
let updatedConfig = {}
|
|
59
|
+
const updateConfig = newConfig => {
|
|
60
|
+
updatedConfig = newConfig
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const updateField = updateFieldFactory(config, updateConfig)
|
|
64
|
+
updateField('section', 0, 'filterColumn', 'newValue')
|
|
65
|
+
|
|
66
|
+
expect(updatedConfig).toEqual({ section: [{ filterColumn: 'newValue', filterValue: 'oldValue' }] })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should update the second level when newValue is a string and legacy is true', () => {
|
|
70
|
+
// test for legacy use case
|
|
71
|
+
// puts the newValue in two places for some reason
|
|
72
|
+
const config = { section: { subsection: 'oldValue' } }
|
|
73
|
+
let updatedConfig = {}
|
|
74
|
+
const updateConfig = newConfig => {
|
|
75
|
+
updatedConfig = newConfig
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const updateField = updateFieldFactory(config, updateConfig, true)
|
|
79
|
+
updateField('section', 'subsection', 'filterColumn', 'newValue')
|
|
80
|
+
|
|
81
|
+
expect(updatedConfig).toEqual({ section: { subsection: 'newValue', filterColumn: 'newValue' } })
|
|
82
|
+
|
|
83
|
+
updateField('section', null, 'filterColumn', 'newValue2')
|
|
84
|
+
expect(updatedConfig).toEqual({ section: { subsection: 'oldValue', filterColumn: 'newValue2' } })
|
|
85
|
+
|
|
86
|
+
updateField('section', 'subsection', null, 'newValue2')
|
|
87
|
+
expect(updatedConfig).toEqual({ section: { subsection: 'newValue2', null: 'newValue2' } })
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { UpdateFieldFunc } from '../types/UpdateFieldFunc'
|
|
2
|
+
|
|
3
|
+
export const updateFieldFactory =
|
|
4
|
+
(config, updateConfig, legacy = false): UpdateFieldFunc<any> =>
|
|
5
|
+
(section, subsection, fieldName, newValue) => {
|
|
6
|
+
// Top level
|
|
7
|
+
if (null === section && null === subsection) {
|
|
8
|
+
const updatedConfig = { ...config, [fieldName]: newValue }
|
|
9
|
+
|
|
10
|
+
if ('filterColumn' === fieldName) {
|
|
11
|
+
updatedConfig.filterValue = ''
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
updateConfig(updatedConfig)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const isArray = Array.isArray(config[section])
|
|
19
|
+
|
|
20
|
+
let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
|
|
21
|
+
|
|
22
|
+
if (null !== subsection) {
|
|
23
|
+
if (isArray) {
|
|
24
|
+
sectionValue = [...config[section]]
|
|
25
|
+
sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
|
|
26
|
+
} else if (typeof newValue === 'string' && legacy) {
|
|
27
|
+
// supports data-bite, filter-text, markup-include, and waffle-chart.
|
|
28
|
+
// i'm not sure what the use case for this could be considering you should be able to
|
|
29
|
+
// update by not passing the subsection. Therefore I'm labeling it as legacy.
|
|
30
|
+
sectionValue[subsection] = newValue
|
|
31
|
+
} else {
|
|
32
|
+
sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const updatedConfig = { ...config, [section]: sectionValue }
|
|
36
|
+
|
|
37
|
+
updateConfig(updatedConfig)
|
|
38
|
+
}
|
|
@@ -65,8 +65,8 @@ export default function useDataVizClasses(config, viewport = null) {
|
|
|
65
65
|
ul: getUlClasses(),
|
|
66
66
|
li: ['single-legend-item', 'legend-container__li'],
|
|
67
67
|
title: ['legend-container__title'],
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
description: ['legend-container__description'],
|
|
69
|
+
div: [legend?.position === 'bottom' && legend?.singleRow ? 'shape-container single-row' : 'shape-container']
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return { innerContainerClasses, contentClasses, barBorderClass, lineDatapointClass, sparkLineStyles, legendClasses }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const update_4_24_3 = async config => {
|
|
2
|
+
const ver = '4.24.3'
|
|
3
|
+
|
|
4
|
+
let newConfig = { ...config }
|
|
5
|
+
|
|
6
|
+
newConfig.validated = ver
|
|
7
|
+
|
|
8
|
+
if (newConfig.type === 'chart') {
|
|
9
|
+
if (newConfig.xAxis.sortDates) {
|
|
10
|
+
newConfig.xAxis.type = 'date-time'
|
|
11
|
+
}
|
|
12
|
+
newConfig.table.download = true
|
|
13
|
+
|
|
14
|
+
delete newConfig.xAxis.sortDates
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (newConfig.type === 'map') {
|
|
18
|
+
newConfig.table.download = true
|
|
19
|
+
newConfig.general.showDownloadButton = true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return newConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default update_4_24_3
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// This package hooks into the Redux Dev Tools extension
|
|
2
|
+
// It works as a wrapper for any reducer function
|
|
3
|
+
|
|
4
|
+
// Based on: https://github.com/MacKentoch/react-bootstrap-webpack-starter/blob/master/front/src/contexts/withDevTools/index.ts
|
|
5
|
+
|
|
6
|
+
// types
|
|
7
|
+
type DevToolsMessageType = 'DISPATCH' | string
|
|
8
|
+
|
|
9
|
+
type DevToolsMessagePayload = {
|
|
10
|
+
type?: string
|
|
11
|
+
state?: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type DevToolsMessage = {
|
|
15
|
+
type?: DevToolsMessageType
|
|
16
|
+
payload?: DevToolsMessagePayload
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Action = {
|
|
20
|
+
type: string
|
|
21
|
+
payload?: any
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// the only method the devToolsWrapper hooks into is .send()
|
|
25
|
+
// all other type definitions are kept for reference.
|
|
26
|
+
// it's possible there's a DevTools type in the Redux library
|
|
27
|
+
// however this allows us to avoid another dependency as this project
|
|
28
|
+
// doesn't use Redux.
|
|
29
|
+
type DevTools = {
|
|
30
|
+
init: () => void
|
|
31
|
+
connect: () => any
|
|
32
|
+
subscribe: (message: DevToolsMessage) => any
|
|
33
|
+
send: (action: Action, newState: any) => any
|
|
34
|
+
unsubscribe: () => any
|
|
35
|
+
dispatch: (action: Action) => any
|
|
36
|
+
disconnect: () => any
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// constants
|
|
40
|
+
const withDevTools = typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION__
|
|
41
|
+
const devTools: DevTools = !withDevTools ? null : (window as any).__REDUX_DEVTOOLS_EXTENSION__.connect()
|
|
42
|
+
export const devToolsStore = !withDevTools ? null : devTools
|
|
43
|
+
|
|
44
|
+
export const devToolsWrapper =
|
|
45
|
+
<StateType, ActionTypes>(_reducer: (s: StateType, a: ActionTypes) => StateType) =>
|
|
46
|
+
(state: StateType, action: ActionTypes): StateType => {
|
|
47
|
+
const newState = _reducer(state, action)
|
|
48
|
+
devToolsStore?.send(action as Action, newState)
|
|
49
|
+
return newState
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/core",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.3",
|
|
4
4
|
"description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
|
|
5
5
|
"moduleName": "CdcCore",
|
|
6
6
|
"main": "dist/cdccore",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "vitest watch --reporter verbose --globals true",
|
|
9
|
+
"test:ui": "vitest --ui"
|
|
9
10
|
},
|
|
10
11
|
"repository": {
|
|
11
12
|
"type": "git",
|
|
@@ -30,5 +31,5 @@
|
|
|
30
31
|
"react": "^18.2.0",
|
|
31
32
|
"react-dom": "^18.2.0"
|
|
32
33
|
},
|
|
33
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "9c7ef7ca74f2d2a1e04d923b133fe0fc557a62bf"
|
|
34
35
|
}
|
package/styles/_data-table.scss
CHANGED
|
@@ -31,10 +31,6 @@ div.data-table-heading {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
table.horizontal {
|
|
34
|
-
tr {
|
|
35
|
-
display: flex;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
34
|
th,
|
|
39
35
|
td {
|
|
40
36
|
min-width: 200px;
|
|
@@ -42,7 +38,7 @@ table.horizontal {
|
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
table.data-table {
|
|
45
|
-
width: 100%;
|
|
41
|
+
min-width: 100%;
|
|
46
42
|
background: #fff;
|
|
47
43
|
position: relative;
|
|
48
44
|
border: none;
|
|
@@ -51,7 +47,6 @@ table.data-table {
|
|
|
51
47
|
overflow: auto;
|
|
52
48
|
appearance: none;
|
|
53
49
|
table-layout: fixed;
|
|
54
|
-
display: grid;
|
|
55
50
|
* {
|
|
56
51
|
box-sizing: border-box;
|
|
57
52
|
}
|
|
@@ -107,18 +102,6 @@ table.data-table {
|
|
|
107
102
|
background-size: 10px 5px;
|
|
108
103
|
}
|
|
109
104
|
|
|
110
|
-
/* doesnt work
|
|
111
|
-
th.sort-asc,
|
|
112
|
-
td.sort-asc {
|
|
113
|
-
background-image: url(../assets/icon-caret-filled-up.svg);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
th.sort-desc,
|
|
117
|
-
td.sort-desc {
|
|
118
|
-
background-image: url(../assets/icon-caret-filled-down.svg);
|
|
119
|
-
}
|
|
120
|
-
*/
|
|
121
|
-
|
|
122
105
|
// format the white triangle sort icon in data table headers
|
|
123
106
|
.sort-icon {
|
|
124
107
|
fill: white;
|
|
@@ -138,7 +121,6 @@ table.data-table {
|
|
|
138
121
|
}
|
|
139
122
|
|
|
140
123
|
tbody {
|
|
141
|
-
display: table-cell;
|
|
142
124
|
tr {
|
|
143
125
|
width: 100%;
|
|
144
126
|
&:hover {
|
|
@@ -162,6 +144,7 @@ table.data-table {
|
|
|
162
144
|
|
|
163
145
|
th,
|
|
164
146
|
td {
|
|
147
|
+
width: 1% !important;
|
|
165
148
|
white-space: nowrap;
|
|
166
149
|
text-overflow: ellipsis;
|
|
167
150
|
overflow: hidden;
|
|
@@ -170,7 +153,6 @@ table.data-table {
|
|
|
170
153
|
}
|
|
171
154
|
}
|
|
172
155
|
tr {
|
|
173
|
-
display: flex;
|
|
174
156
|
& > * {
|
|
175
157
|
width: 100%;
|
|
176
158
|
}
|