@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
@@ -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 */}
@@ -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 meta: Meta<typeof Icon> = {
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: Icon,
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
@@ -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 (url, visualizationType = '') {
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,13 @@
1
+ export const isSolrCsv = dataUrl => {
2
+ if (dataUrl.includes('wt=csv')) {
3
+ return true
4
+ }
5
+ return false
6
+ }
7
+
8
+ export const isSolrJson = dataUrl => {
9
+ if (dataUrl?.includes('wt=json')) {
10
+ return true
11
+ }
12
+ return false
13
+ }
@@ -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
- resetButton: ['legend-container__reset-button', 'btn', 'clear'],
69
- description: ['legend-container__description']
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.1",
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": "echo \"Error: run tests from root\" && exit 1"
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": "a352a3f74f4b681191e3244061dbb3621f36eec3"
34
+ "gitHead": "9c7ef7ca74f2d2a1e04d923b133fe0fc557a62bf"
34
35
  }
@@ -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
  }