@cdc/core 4.23.10 → 4.24.1

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 (74) hide show
  1. package/LICENSE +201 -0
  2. package/assets/icon-deviation-bar.svg +1 -0
  3. package/components/DataTable/DataTable.tsx +223 -0
  4. package/components/DataTable/components/BoxplotHeader.tsx +16 -0
  5. package/components/DataTable/components/CellAnchor.tsx +44 -0
  6. package/components/DataTable/components/ChartHeader.tsx +103 -0
  7. package/components/DataTable/components/ExpandCollapse.tsx +21 -0
  8. package/components/DataTable/components/Icons.tsx +10 -0
  9. package/components/DataTable/components/MapHeader.tsx +56 -0
  10. package/components/DataTable/components/SkipNav.tsx +7 -0
  11. package/components/DataTable/helpers/boxplotCellMatrix.tsx +64 -0
  12. package/components/DataTable/helpers/chartCellMatrix.tsx +92 -0
  13. package/components/DataTable/helpers/customColumns.ts +25 -0
  14. package/components/DataTable/helpers/customSort.ts +55 -0
  15. package/components/DataTable/helpers/getChartCellValue.ts +56 -0
  16. package/components/DataTable/helpers/getDataSeriesColumns.ts +29 -0
  17. package/components/DataTable/helpers/getSeriesName.ts +26 -0
  18. package/components/DataTable/helpers/mapCellMatrix.tsx +56 -0
  19. package/components/DataTable/helpers/regionCellMatrix.tsx +13 -0
  20. package/components/DataTable/helpers/standardizeState.js +76 -0
  21. package/components/DataTable/index.ts +1 -0
  22. package/components/DataTable/types/TableConfig.ts +52 -0
  23. package/components/DownloadButton.tsx +29 -0
  24. package/components/EditorPanel/DataTableEditor.tsx +133 -0
  25. package/components/EditorPanel/Inputs.tsx +150 -0
  26. package/components/Filters.jsx +3 -3
  27. package/components/LegendCircle.jsx +2 -2
  28. package/components/MediaControls.jsx +1 -1
  29. package/components/MultiSelect/MultiSelect.tsx +95 -0
  30. package/components/MultiSelect/index.ts +1 -0
  31. package/components/MultiSelect/multiselect.styles.css +50 -0
  32. package/components/Table/Table.tsx +69 -0
  33. package/components/Table/components/Cell.tsx +9 -0
  34. package/components/Table/components/GroupRow.tsx +20 -0
  35. package/components/Table/components/Row.tsx +26 -0
  36. package/components/Table/index.ts +1 -0
  37. package/components/Table/types/CellMatrix.ts +4 -0
  38. package/components/Table/types/RowType.ts +5 -0
  39. package/components/_stories/DataTable.stories.tsx +103 -0
  40. package/components/_stories/EditorPanel.stories.tsx +53 -0
  41. package/components/_stories/Inputs.stories.tsx +37 -0
  42. package/components/_stories/MultiSelect.stories.tsx +24 -0
  43. package/components/_stories/Table.stories.tsx +53 -0
  44. package/components/_stories/_mocks/dashboard_no_filter.json +121 -0
  45. package/components/_stories/_mocks/example-city-state.json +808 -0
  46. package/components/_stories/_mocks/row_type.json +42 -0
  47. package/components/_stories/styles.scss +9 -0
  48. package/components/inputs/{InputSelect.jsx → InputSelect.tsx} +15 -5
  49. package/components/managers/{DataDesigner.jsx → DataDesigner.tsx} +103 -94
  50. package/components/ui/{Icon.jsx → Icon.tsx} +3 -3
  51. package/components/ui/Title/Title.scss +95 -0
  52. package/components/ui/Title/index.tsx +34 -0
  53. package/components/ui/_stories/Title.stories.tsx +21 -0
  54. package/helpers/DataTransform.ts +75 -20
  55. package/helpers/cove/string.ts +11 -0
  56. package/helpers/fetchRemoteData.js +1 -1
  57. package/helpers/getFileExtension.ts +28 -5
  58. package/package.json +2 -2
  59. package/styles/_data-table.scss +3 -0
  60. package/styles/heading-colors.scss +0 -3
  61. package/styles/v2/layout/_component.scss +0 -11
  62. package/types/Axis.ts +41 -0
  63. package/types/Color.ts +5 -0
  64. package/types/Column.ts +15 -0
  65. package/types/ComponentStyles.ts +7 -0
  66. package/types/ComponentThemes.ts +13 -0
  67. package/types/EditorColumnProperties.ts +8 -0
  68. package/types/FilterBehavior.ts +1 -0
  69. package/types/Runtime.ts +29 -0
  70. package/types/Series.ts +1 -0
  71. package/types/Table.ts +18 -0
  72. package/types/UpdateFieldFunc.ts +1 -0
  73. package/types/Visualization.ts +21 -0
  74. package/components/DataTable.jsx +0 -754
@@ -0,0 +1,42 @@
1
+ [
2
+ {
3
+ "type": "row_group",
4
+ "overall": "",
5
+ "male": "",
6
+ "female": "",
7
+ "row_type": "row_group",
8
+ "nullColumn": null
9
+ },
10
+ {
11
+ "type": "row_group_total",
12
+ "overall": "100",
13
+ "male": "50",
14
+ "female": "50",
15
+ "row_type": "row_group_total",
16
+ "nullColumn": null
17
+ },
18
+ {
19
+ "type": "regular",
20
+ "overall": "50",
21
+ "male": "25",
22
+ "female": "25",
23
+ "row_type": null,
24
+ "nullColumn": null
25
+ },
26
+ {
27
+ "type": "regular",
28
+ "overall": "50",
29
+ "male": "25",
30
+ "female": "25",
31
+ "row_type": null,
32
+ "nullColumn": null
33
+ },
34
+ {
35
+ "type": "total",
36
+ "overall": "100",
37
+ "male": "50",
38
+ "female": "50",
39
+ "row_type": "total",
40
+ "nullColumn": null
41
+ }
42
+ ]
@@ -0,0 +1,9 @@
1
+ @import '../../styles/_variables';
2
+ @import '../../styles/_mixins';
3
+ @import '../../styles/_data-table';
4
+ @import '../../styles/_global.scss';
5
+
6
+ .visually-hidden {
7
+ position: fixed;
8
+ left: -10000px;
9
+ }
@@ -1,9 +1,19 @@
1
- import React, { memo } from 'react'
2
-
3
1
  import '../../styles/v2/components/input/index.scss'
4
2
 
5
- const InputSelect = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes }) => {
6
- let optionsJsx = ''
3
+ interface InputProps {
4
+ label?
5
+ value?
6
+ options: string[] | { [key: string]: string }
7
+ fieldName
8
+ section?
9
+ subsection?
10
+ required?
11
+ updateField
12
+ initial?
13
+ }
14
+
15
+ const InputSelect = ({ label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes }: InputProps) => {
16
+ let optionsJsx = []
7
17
 
8
18
  if (Array.isArray(options)) {
9
19
  //Handle basic array
@@ -48,6 +58,6 @@ const InputSelect = memo(({ label, value, options, fieldName, section = null, su
48
58
  </select>
49
59
  </label>
50
60
  )
51
- })
61
+ }
52
62
 
53
63
  export default InputSelect
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import { useEffect } from 'react'
2
2
 
3
3
  import Button from '../elements/Button'
4
4
  import Card from '../elements/Card'
@@ -6,77 +6,109 @@ import Card from '../elements/Card'
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
8
 
9
- const DataDesigner = props => {
9
+ 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
14
+ config?: any // can be one of many different types of chart configs that aren't fully established yet
15
+ setConfig?: (newConfig: any) => void
16
+ }
17
+
18
+ const DataDesigner = (props: DataDesignerProps) => {
10
19
  const { configureData, updateDescriptionProp, visualizationKey, dataKey, config, setConfig } = props
20
+ const hasDataOrientation = config?.visualizationType !== 'Forest Plot'
21
+ const hasMultipleSeries = config?.visualizationType !== 'Forest Plot'
22
+ const hasRowSelection = config?.visualizationType !== 'Forest Plot'
23
+
24
+ useEffect(() => {
25
+ if (config?.visualizationType === 'Forest Plot') {
26
+ // needed to properly set data up?
27
+ setConfig({
28
+ ...config,
29
+ dataDescription: {
30
+ series: true,
31
+ horizontal: false,
32
+ singleRow: true
33
+ }
34
+ })
35
+ }
36
+ }, [])
11
37
 
12
38
  return (
13
39
  <div className='cove-data-designer__container'>
14
40
  <div className='mb-2'>
15
41
  <div className='cove-heading--3 fw-bold mb-1'>Describe Data</div>
16
- <div className='cove-heading--3 fw-normal mb-1'>Data Orientation</div>
17
- <div className='grid grid-gap-2 mb-4'>
18
- <div className='column'>
19
- <button
20
- className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === false ? ' active' : '')}
21
- onClick={() => {
22
- updateDescriptionProp(visualizationKey, dataKey, 'horizontal', false)
23
- }}
24
- >
25
- <Card>
26
- <strong className='cove-heading--3'>Vertical</strong>
27
- <p className='mb-1'>
28
- Values for map geography or chart date/category axis are contained in a single <em>column</em>.
29
- </p>
30
- {DATA_TABLE_VERTICAL}
31
- </Card>
32
- </button>
33
- </div>
34
- <div className='column'>
35
- <button
36
- className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === true ? ' active' : '')}
37
- onClick={() => {
38
- updateDescriptionProp(visualizationKey, dataKey, 'horizontal', true)
39
- }}
40
- >
41
- <Card>
42
- <strong className='cove-heading--3'>Horizontal</strong>
43
- <p className='mb-1'>
44
- Values for map geography or chart date/category axis are contained in a single <em>row</em>
45
- </p>
46
- {DATA_TABLE_HORIZONTAL}
47
- </Card>
48
- </button>
49
- </div>
50
- </div>
42
+ {hasDataOrientation && (
43
+ <>
44
+ <div className='cove-heading--3 fw-normal mb-1'>Data Orientation</div>
45
+ <div className='grid grid-gap-2 mb-4'>
46
+ <div className='column'>
47
+ <button
48
+ className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === false ? ' active' : '')}
49
+ onClick={() => {
50
+ updateDescriptionProp(visualizationKey, dataKey, 'horizontal', false)
51
+ }}
52
+ >
53
+ <Card>
54
+ <strong className='cove-heading--3'>Vertical</strong>
55
+ <p className='mb-1'>
56
+ Values for map geography or chart date/category axis are contained in a single <em>column</em>.
57
+ </p>
58
+ {DATA_TABLE_VERTICAL}
59
+ </Card>
60
+ </button>
61
+ </div>
62
+ <div className='column'>
63
+ <button
64
+ className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === true ? ' active' : '')}
65
+ onClick={() => {
66
+ updateDescriptionProp(visualizationKey, dataKey, 'horizontal', true)
67
+ }}
68
+ >
69
+ <Card>
70
+ <strong className='cove-heading--3'>Horizontal</strong>
71
+ <p className='mb-1'>
72
+ Values for map geography or chart date/category axis are contained in a single <em>row</em>
73
+ </p>
74
+ {DATA_TABLE_HORIZONTAL}
75
+ </Card>
76
+ </button>
77
+ </div>
78
+ </div>
79
+ </>
80
+ )}
51
81
  </div>
52
82
  {configureData.dataDescription && (
53
83
  <>
54
- <div className='mb-2'>
55
- <div className='mb-1'>Are there multiple series represented in your data?</div>
56
- <div>
57
- <Button
58
- style={{ backgroundColor: '#00345d' }}
59
- hoverStyle={{ backgroundColor: '#015daa' }}
60
- className='mr-1'
61
- onClick={() => {
62
- updateDescriptionProp(visualizationKey, dataKey, 'series', true)
63
- }}
64
- active={configureData.dataDescription.series === true}
65
- >
66
- Yes
67
- </Button>
68
- <Button
69
- style={{ backgroundColor: '#00345d' }}
70
- hoverStyle={{ backgroundColor: '#015daa' }}
71
- onClick={() => {
72
- updateDescriptionProp(visualizationKey, dataKey, 'series', false)
73
- }}
74
- active={configureData.dataDescription.series === false}
75
- >
76
- No
77
- </Button>
84
+ {hasMultipleSeries && (
85
+ <div className='mb-2'>
86
+ <div className='mb-1'>Are there multiple series represented in your data?</div>
87
+ <div>
88
+ <Button
89
+ style={{ backgroundColor: '#00345d' }}
90
+ hoverStyle={{ backgroundColor: '#015daa' }}
91
+ className='mr-1'
92
+ onClick={() => {
93
+ updateDescriptionProp(visualizationKey, dataKey, 'series', true)
94
+ }}
95
+ active={configureData.dataDescription.series === true}
96
+ >
97
+ Yes
98
+ </Button>
99
+ <Button
100
+ style={{ backgroundColor: '#00345d' }}
101
+ hoverStyle={{ backgroundColor: '#015daa' }}
102
+ onClick={() => {
103
+ updateDescriptionProp(visualizationKey, dataKey, 'series', false)
104
+ }}
105
+ active={configureData.dataDescription.series === false}
106
+ >
107
+ No
108
+ </Button>
109
+ </div>
78
110
  </div>
79
- </div>
111
+ )}
80
112
  {configureData.dataDescription.horizontal === true && configureData.dataDescription.series === true && (
81
113
  <div className='mb-2'>
82
114
  <div className='mb-1'>Which property in the dataset represents which series the row is describing?</div>
@@ -95,7 +127,7 @@ const DataDesigner = props => {
95
127
  </select>
96
128
  </div>
97
129
  )}
98
- {configureData.dataDescription.horizontal === false && configureData.dataDescription.series === true && (
130
+ {configureData.dataDescription.horizontal === false && configureData.dataDescription.series === true && hasRowSelection && (
99
131
  <>
100
132
  <div className='mb-2'>
101
133
  <div className='mb-1'>Are the series values in your data represented in a single row, or across multiple rows?</div>
@@ -166,16 +198,16 @@ const DataDesigner = props => {
166
198
  </div>
167
199
  <div className='mb-2'>
168
200
  <div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
169
- {configureData.dataDescription.valueKeys && configureData.dataDescription.valueKeys.length > 0 && (
201
+ {configureData.dataDescription.valueKeysTallSupport && configureData.dataDescription.valueKeysTallSupport.length > 0 && (
170
202
  <ul className='value-list'>
171
- {configureData.dataDescription.valueKeys.map((valueKey, index) => (
203
+ {configureData.dataDescription.valueKeysTallSupport.map((valueKey, index) => (
172
204
  <li key={`value-keys-list-${index}`}>
173
205
  {valueKey}
174
206
  <button
175
207
  onClick={() => {
176
- let newValueKeys = configureData.dataDescription.valueKeys
208
+ let newValueKeys = configureData.dataDescription.valueKeysTallSupport
177
209
  newValueKeys.splice(index, 1)
178
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', newValueKeys)
210
+ updateDescriptionProp(visualizationKey, dataKey, 'valueKeysTallSupport', newValueKeys)
179
211
  }}
180
212
  >
181
213
  X
@@ -186,14 +218,14 @@ const DataDesigner = props => {
186
218
  )}
187
219
  <select
188
220
  onChange={e => {
189
- if (e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)) {
190
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', [...(configureData.dataDescription.valueKeys || []), e.target.value])
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])
191
223
  }
192
224
  }}
193
225
  >
194
226
  <option value=''>Choose an option</option>
195
227
  {Object.keys(configureData.data[0])
196
- .filter(value => !configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(value) === -1)
228
+ .filter(value => !configureData.dataDescription.valueKeysTallSupport || configureData.dataDescription.valueKeysTallSupport.indexOf(value) === -1)
197
229
  .map((value, index) => (
198
230
  <option value={value} key={`value-keys-option-${index}`}>
199
231
  {value}
@@ -247,7 +279,7 @@ const DataDesigner = props => {
247
279
  {config?.visualizationType === 'Forest Plot' && (
248
280
  <>
249
281
  <div className='mb-2'>
250
- <div className='mb-1'>Which column represents the date/category column?</div>
282
+ <div className='mb-1'>Which column represents the Study ID?</div>
251
283
  <select
252
284
  onChange={e => {
253
285
  setConfig({
@@ -337,29 +369,6 @@ const DataDesigner = props => {
337
369
  ))}
338
370
  </select>
339
371
  </div>
340
-
341
- <div className='mb-2'>
342
- <div className='mb-1'>Which shape do you want to use in your forest plot?</div>
343
- <select
344
- onChange={e => {
345
- setConfig({
346
- ...config,
347
- forestPlot: {
348
- ...config.forestPlot,
349
- shape: e.target.value
350
- }
351
- })
352
- }}
353
- defaultValue={'Select'}
354
- >
355
- <option value=''>Choose an option</option>
356
- {['text', 'circle', 'square', 'diamond'].map((value, index) => (
357
- <option value={value} key={index}>
358
- {value}
359
- </option>
360
- ))}
361
- </select>
362
- </div>
363
372
  </>
364
373
  )}
365
374
  </>
@@ -69,17 +69,17 @@ const iconHash = {
69
69
 
70
70
  export const ICON_TYPES = Object.keys(iconHash)
71
71
 
72
- const Icon = ({ display = '', base, alt = '', size, color, style, ...attributes }) => {
72
+ const Icon = ({ display = '', base = undefined, alt = '', size = undefined, color = undefined, style = undefined, ...attributes }) => {
73
73
  const IconObj = iconHash[display] || null
74
74
 
75
75
  const filteredAttrs = { ...attributes }
76
76
  delete filteredAttrs.className
77
77
 
78
78
  const styles = {
79
- ...style,
80
79
  color: color ? color : null,
81
80
  width: size ? size + 'px' : null,
82
- cursor: display === 'move' ? 'move' : 'default'
81
+ cursor: display === 'move' ? 'move' : 'default',
82
+ ...style
83
83
  }
84
84
 
85
85
  return (
@@ -0,0 +1,95 @@
1
+ .cove-component__header {
2
+ position: relative;
3
+ padding: 0.6em 0.8em;
4
+ margin: 0;
5
+ color: #fff;
6
+ font-size: 1.1em;
7
+
8
+ border-top-left-radius: 3px;
9
+ border-top-right-radius: 3px;
10
+
11
+ &.mb-0 {
12
+ margin-bottom: 0 !important;
13
+ }
14
+
15
+ em {
16
+ font-style: italic;
17
+ }
18
+
19
+ strong {
20
+ font-weight: bold;
21
+ }
22
+
23
+ &:not(:empty) {
24
+ padding: 0.6em 0.8em;
25
+ border-bottom-width: 4px;
26
+ border-bottom-style: solid;
27
+ }
28
+
29
+ &.bite-header {
30
+ margin-bottom: 0 !important;
31
+ }
32
+
33
+ // THEMES
34
+ &.theme-purple {
35
+ background: #712177;
36
+ border-bottom-color: #b890bb;
37
+ }
38
+ &.theme-brown {
39
+ background: #705043;
40
+ border-bottom-color: #ad907b;
41
+ }
42
+ &.theme-teal {
43
+ background: #00695c;
44
+ border-bottom-color: #4ebaaa;
45
+ }
46
+ &.theme-pink {
47
+ background: #af4448;
48
+ border-bottom-color: #e57373;
49
+ }
50
+ &.theme-orange {
51
+ background: #bb4d00;
52
+ border-bottom-color: #ffad42;
53
+ }
54
+ &.theme-slate {
55
+ background: #29434e;
56
+ border-bottom-color: #7e9ba5;
57
+ }
58
+ &.theme-indigo {
59
+ background: #26418f;
60
+ border-bottom-color: #92a6dd;
61
+ }
62
+ &.theme-cyan {
63
+ background: #007b91;
64
+ border-bottom-color: #65b0bd;
65
+ }
66
+ &.theme-green {
67
+ background: #4b830d;
68
+ border-bottom-color: #84bc49;
69
+ }
70
+ &.theme-amber {
71
+ background: #fbab18;
72
+ border-bottom-color: #ffd54f;
73
+ }
74
+ &.theme-blue {
75
+ background: #005eaa;
76
+ border-bottom-color: #88c3ea;
77
+ }
78
+
79
+ &.mb-0 {
80
+ margin-bottom: 0 !important;
81
+ }
82
+ }
83
+
84
+ // add additional space below title if needed
85
+ .map-title.cove-component__header,
86
+ .type-chart .cove-component__header {
87
+ &:not(:empty) {
88
+ margin: 0 0 1rem 0 !important;
89
+ }
90
+ }
91
+
92
+ // fix sparkline bottom margin gap
93
+ .type-sparkline.type-chart .cove-component__header {
94
+ margin: 0px !important;
95
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+ import parse from 'html-react-parser'
3
+ import './Title.scss'
4
+
5
+ type HeaderProps = {
6
+ title?: string
7
+ isDashboard?: boolean
8
+ superTitle?: string
9
+ classes?: string[]
10
+ style?: React.CSSProperties
11
+ showTitle?: boolean
12
+ ariaLevel?: number
13
+ }
14
+
15
+ const Title = (props: HeaderProps) => {
16
+ const { isDashboard, title, superTitle, classes = [], showTitle = true, ariaLevel = 2 } = props
17
+
18
+ // standard classes every vis should have
19
+ const updatedClasses = ['cove-component__header', 'component__header', ...classes]
20
+
21
+ return (
22
+ title &&
23
+ showTitle && (
24
+ <header className={updatedClasses.join(' ')} aria-hidden='true' style={props.style} aria-level={ariaLevel}>
25
+ {superTitle && <sup>{parse(superTitle)}</sup>}
26
+ <div>
27
+ {parse(title)} {isDashboard}
28
+ </div>
29
+ </header>
30
+ )
31
+ )
32
+ }
33
+
34
+ export default Title
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+
3
+ import Title from '../Title'
4
+
5
+ const meta: Meta = {
6
+ title: 'Components/Atoms/Title',
7
+ component: Title
8
+ }
9
+
10
+ type Story = StoryObj
11
+
12
+ export const Primary: Story = {
13
+ args: {
14
+ title: 'My Title',
15
+ superTitle: 'My super title',
16
+ isDashboard: false,
17
+ classes: ['theme-blue']
18
+ }
19
+ }
20
+
21
+ export default meta