@cdc/core 4.24.12 → 4.25.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 (68) hide show
  1. package/components/DataTable/DataTable.tsx +26 -36
  2. package/components/DataTable/DataTableStandAlone.tsx +3 -3
  3. package/components/DataTable/components/ChartHeader.tsx +3 -2
  4. package/components/DataTable/components/ExpandCollapse.tsx +1 -4
  5. package/components/DataTable/data-table.css +2 -7
  6. package/components/DataTable/helpers/customSort.ts +2 -2
  7. package/components/DataTable/helpers/mapCellMatrix.tsx +83 -60
  8. package/components/DataTable/types/TableConfig.ts +0 -1
  9. package/components/EditorPanel/FootnotesEditor.tsx +49 -7
  10. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +15 -2
  11. package/components/Filters/Filters.tsx +42 -36
  12. package/components/Filters/helpers/handleSorting.ts +5 -0
  13. package/components/Footnotes/Footnotes.tsx +1 -1
  14. package/components/Layout/components/Visualization/index.tsx +18 -4
  15. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  16. package/components/Legend/Legend.Gradient.tsx +1 -4
  17. package/components/Legend/index.tsx +1 -1
  18. package/components/LegendShape.tsx +2 -3
  19. package/components/MediaControls.jsx +32 -8
  20. package/components/NestedDropdown/NestedDropdown.tsx +7 -12
  21. package/components/NestedDropdown/nesteddropdown.styles.css +11 -5
  22. package/components/Table/Table.tsx +0 -6
  23. package/components/Table/components/Row.tsx +2 -5
  24. package/components/_stories/DataTable.stories.tsx +1 -2
  25. package/components/elements/Button.jsx +38 -19
  26. package/components/elements/Confirm.tsx +45 -0
  27. package/components/elements/Error.tsx +24 -0
  28. package/components/managers/DataDesigner.tsx +198 -143
  29. package/components/ui/Title/Title.scss +12 -5
  30. package/components/ui/Title/index.tsx +1 -1
  31. package/dist/cove-main.css +77 -591
  32. package/dist/cove-main.css.map +1 -1
  33. package/helpers/DataTransform.ts +55 -63
  34. package/helpers/addValuesToFilters.ts +45 -16
  35. package/helpers/cove/accessibility.ts +24 -0
  36. package/helpers/cove/fontSettings.ts +1 -1
  37. package/helpers/cove/number.ts +1 -7
  38. package/helpers/coveUpdateWorker.ts +5 -1
  39. package/helpers/displayDataAsText.ts +64 -0
  40. package/helpers/filterVizData.ts +2 -2
  41. package/helpers/formatConfigBeforeSave.ts +16 -1
  42. package/helpers/isOlderVersion.ts +20 -0
  43. package/helpers/missingRequiredSections.ts +20 -0
  44. package/helpers/tests/addValuesToFilters.test.ts +19 -1
  45. package/helpers/useDataVizClasses.ts +8 -4
  46. package/helpers/ver/4.24.10.ts +12 -0
  47. package/helpers/ver/4.24.11.ts +18 -0
  48. package/helpers/ver/4.25.1.ts +18 -0
  49. package/package.json +2 -2
  50. package/styles/_button-section.scss +2 -5
  51. package/styles/_global-variables.scss +17 -7
  52. package/styles/_global.scss +8 -12
  53. package/styles/_reset.scss +4 -5
  54. package/styles/_typography.scss +0 -20
  55. package/styles/_variables.scss +0 -3
  56. package/styles/base.scss +44 -5
  57. package/styles/cove-main.scss +1 -1
  58. package/styles/filters.scss +5 -6
  59. package/styles/v2/base/_general.scss +3 -2
  60. package/styles/v2/components/button.scss +0 -1
  61. package/styles/v2/main.scss +3 -4
  62. package/styles/v2/utils/index.scss +0 -1
  63. package/types/BoxPlot.ts +1 -0
  64. package/types/Runtime.ts +1 -0
  65. package/types/Table.ts +0 -1
  66. package/types/Version.ts +1 -1
  67. package/types/VizFilter.ts +2 -1
  68. package/styles/v2/utils/_spacers.scss +0 -31
@@ -5,25 +5,41 @@ import LoadSpin from '../ui/LoadSpin'
5
5
 
6
6
  import '../../styles/v2/components/button.scss'
7
7
 
8
- const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false, loadingText = 'Loading...', flexCenter, active = false, onClick, children, ...attributes }) => {
8
+ const Button = ({
9
+ style,
10
+ role,
11
+ hoverStyle = {},
12
+ fluid = false,
13
+ loading = false,
14
+ loadingText = 'Loading...',
15
+ flexCenter,
16
+ active = false,
17
+ onClick,
18
+ children,
19
+ ...attributes
20
+ }) => {
9
21
  const buttonRef = useRef(null)
10
22
 
11
- const [ buttonState, setButtonState ] = useState('out')
12
- const [ customStyles, setCustomStyles ] = useState({ ...style })
13
- const [ childrenWidth, setChildrenWidth ] = useState()
14
- const [ loadtextWidth, setLoadtextWidth ] = useState()
23
+ const [buttonState, setButtonState] = useState('out')
24
+ const [customStyles, setCustomStyles] = useState({ ...style })
25
+ const [childrenWidth, setChildrenWidth] = useState()
26
+ const [loadtextWidth, setLoadtextWidth] = useState()
15
27
 
16
28
  const attributesObj = {
17
29
  ...attributes,
18
30
  style: customStyles,
19
- className: 'cove-button' + (flexCenter || 'loader' === role ? ' cove-button--flex-center' : '') + (fluid ? ' fluid' : '') + (loading ? ' loading' : '') + (attributes.className ? ' ' + attributes.className : ''),
31
+ className:
32
+ 'cove-button' +
33
+ (flexCenter || 'loader' === role ? ' cove-button--flex-center' : '') +
34
+ (fluid ? ' fluid' : '') +
35
+ (loading ? ' loading' : '') +
36
+ (attributes.className ? ' ' + attributes.className : ''),
20
37
  onMouseOver: () => setButtonState('in'),
21
38
  onMouseOut: () => setButtonState('out'),
22
39
  onFocus: () => setButtonState('in'),
23
40
  onBlur: () => setButtonState('out')
24
41
  }
25
42
 
26
-
27
43
  useEffect(() => {
28
44
  if ('loader' === role && buttonRef.current) {
29
45
  //Create ghost object and text nodes for children
@@ -54,9 +70,8 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
54
70
  buttonRef.current.parentNode.removeChild(ghostSpan)
55
71
  buttonRef.current.parentNode.removeChild(ghostLoaderSpan)
56
72
  }
57
- return () => {
58
- }
59
- }, [ buttonRef, children, loadingText, role ])
73
+ return () => {}
74
+ }, [buttonRef, children, loadingText, role])
60
75
 
61
76
  useEffect(() => {
62
77
  //Adjust button styles depending on cursor, focus, and active, states
@@ -64,9 +79,10 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
64
79
 
65
80
  // If button state is out, check if its 'active'; we want to keep hover styles applied to 'active' buttons
66
81
  if (buttonState === 'out')
67
- // If button state is out, and not 'active', reset display styles back to default
68
- if (!active) setCustomStyles({ ...style })
69
- }, [ buttonState, active, style ])
82
+ if (!active)
83
+ // If button state is out, and not 'active', reset display styles back to default
84
+ setCustomStyles({ ...style })
85
+ }, [buttonState, active, style])
70
86
 
71
87
  return (
72
88
  <button
@@ -82,16 +98,19 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
82
98
  <>
83
99
  {'loader' === role && (
84
100
  <>
85
- <span className="cove-button__text" style={loading ? { width: loadtextWidth + 'px' } : { width: childrenWidth + 'px' }}>
86
- <div className="cove-button__text--loading" style={loading ? { opacity: 1 } : null}>
101
+ <span
102
+ className='cove-button__text'
103
+ style={loading ? { width: loadtextWidth + 'px' } : { width: childrenWidth + 'px' }}
104
+ >
105
+ <div className='cove-button__text--loading' style={loading ? { opacity: 1 } : null}>
87
106
  {loadingText}
88
107
  </div>
89
- <div className="cove-button__text--children" style={loading ? { opacity: 0 } : null}>
108
+ <div className='cove-button__text--children' style={loading ? { opacity: 0 } : null}>
90
109
  {children}
91
110
  </div>
92
111
  </span>
93
- <div className="cove-button__load-spin" style={loading ? { width: '28px', opacity: 1 } : null}>
94
- <LoadSpin className="ml-1" size={20}/>
112
+ <div className='cove-button__load-spin' style={loading ? { width: '28px', opacity: 1 } : null}>
113
+ <LoadSpin className='ms-1' size={20} />
95
114
  </div>
96
115
  </>
97
116
  )}
@@ -104,7 +123,7 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
104
123
 
105
124
  Button.propTypes = {
106
125
  /** Specify special role type for button */
107
- role: PropTypes.oneOf([ 'loader' ]),
126
+ role: PropTypes.oneOf(['loader']),
108
127
  /** Provide object with styles that overwrite base styles when hovered */
109
128
  hoverStyle: PropTypes.object,
110
129
  /** Enables button to stretch to the full width of the content */
@@ -0,0 +1,45 @@
1
+ import Button from './Button.jsx'
2
+ import React from 'react'
3
+ import { missingRequiredSections } from '../../helpers/missingRequiredSections.js'
4
+ const Confirm = props => {
5
+ const { updateConfig, config } = props
6
+ const confirmDone = e => {
7
+ if (e) {
8
+ e.preventDefault()
9
+ }
10
+
11
+ let newConfig = { ...config }
12
+ delete newConfig.newViz
13
+
14
+ updateConfig(newConfig)
15
+ }
16
+
17
+ const styles: React.CSSProperties = {
18
+ position: 'relative',
19
+ height: '100vh',
20
+ width: '100%',
21
+ display: 'flex',
22
+ justifyContent: 'center',
23
+ alignItems: 'center',
24
+ gridArea: 'content'
25
+ }
26
+
27
+ return (
28
+ <section className='waiting' style={styles}>
29
+ <section className='waiting-container'>
30
+ <h3>Finish Configuring</h3>
31
+ <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
32
+ <Button
33
+ className='btn btn-primary'
34
+ style={{ margin: '1em auto' }}
35
+ disabled={missingRequiredSections(config)}
36
+ onClick={e => confirmDone(e)}
37
+ >
38
+ I'm Done
39
+ </Button>
40
+ </section>
41
+ </section>
42
+ )
43
+ }
44
+
45
+ export default Confirm
@@ -0,0 +1,24 @@
1
+ import React from 'react'
2
+ const Error = ({ errorMessage }) => {
3
+ const styles: React.CSSProperties = {
4
+ position: 'absolute',
5
+ background: 'white',
6
+ zIndex: '999',
7
+ height: '100vh',
8
+ width: '100%',
9
+ display: 'flex',
10
+ justifyContent: 'center',
11
+ alignItems: 'center',
12
+ gridArea: 'content'
13
+ }
14
+ return (
15
+ <section className='waiting' style={styles}>
16
+ <section className='waiting-container'>
17
+ <h3>Error With Configuration</h3>
18
+ <p>{errorMessage}</p>
19
+ </section>
20
+ </section>
21
+ )
22
+ }
23
+
24
+ export default Error
@@ -3,7 +3,12 @@ import { useEffect } from 'react'
3
3
  import Button from '../elements/Button'
4
4
  import Card from '../elements/Card'
5
5
 
6
- import { DATA_TABLE_VERTICAL, DATA_TABLE_HORIZONTAL, DATA_TABLE_SINGLE_ROW, DATA_TABLE_MULTI_ROW } from '../../templates/dataDesignerTables'
6
+ import {
7
+ DATA_TABLE_VERTICAL,
8
+ DATA_TABLE_HORIZONTAL,
9
+ DATA_TABLE_SINGLE_ROW,
10
+ DATA_TABLE_MULTI_ROW
11
+ } from '../../templates/dataDesignerTables'
7
12
  import '../../styles/v2/components/data-designer.scss'
8
13
  import { ConfigureData } from '../../types/ConfigureData'
9
14
 
@@ -45,7 +50,12 @@ const DataDesigner = (props: DataDesignerProps) => {
45
50
  <div className='grid grid-gap-2 mb-4'>
46
51
  <div className='column'>
47
52
  <button
48
- className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === false ? ' active' : '')}
53
+ className={
54
+ 'cove-data-designer__button' +
55
+ (configureData.dataDescription && configureData.dataDescription.horizontal === false
56
+ ? ' active'
57
+ : '')
58
+ }
49
59
  onClick={() => {
50
60
  updateDescriptionProp('horizontal', false)
51
61
  }}
@@ -61,7 +71,12 @@ const DataDesigner = (props: DataDesignerProps) => {
61
71
  </div>
62
72
  <div className='column'>
63
73
  <button
64
- className={'cove-data-designer__button' + (configureData.dataDescription && configureData.dataDescription.horizontal === true ? ' active' : '')}
74
+ className={
75
+ 'cove-data-designer__button' +
76
+ (configureData.dataDescription && configureData.dataDescription.horizontal === true
77
+ ? ' active'
78
+ : '')
79
+ }
65
80
  onClick={() => {
66
81
  updateDescriptionProp('horizontal', true)
67
82
  }}
@@ -88,7 +103,7 @@ const DataDesigner = (props: DataDesignerProps) => {
88
103
  <Button
89
104
  style={{ backgroundColor: '#00345d' }}
90
105
  hoverStyle={{ backgroundColor: '#015daa' }}
91
- className='mr-1'
106
+ className='me-1'
92
107
  onClick={() => {
93
108
  updateDescriptionProp('series', true)
94
109
  }}
@@ -127,154 +142,194 @@ const DataDesigner = (props: DataDesignerProps) => {
127
142
  </select>
128
143
  </div>
129
144
  )}
130
- {configureData.dataDescription.horizontal === false && configureData.dataDescription.series === true && hasRowSelection && (
131
- <>
132
- <div className='mb-2'>
133
- <div className='mb-1'>Are the series values in your data represented in a single row, or across multiple rows?</div>
134
- <div className='grid grid-gap-2 mb-4'>
135
- <div className='column'>
136
- <button
137
- className={'cove-data-designer__button' + (configureData.dataDescription.singleRow === true ? ' active' : '')}
138
- onClick={() => {
139
- updateDescriptionProp('singleRow', true)
140
- }}
141
- >
142
- <Card>
143
- <strong className='cove-heading--3'>Single Row</strong>
144
- <p className='mb-1'>Each row contains the data for an individual series in itself.</p>
145
- {DATA_TABLE_SINGLE_ROW}
146
- </Card>
147
- </button>
145
+ {configureData.dataDescription.horizontal === false &&
146
+ configureData.dataDescription.series === true &&
147
+ hasRowSelection && (
148
+ <>
149
+ <div className='mb-2'>
150
+ <div className='mb-1'>
151
+ Are the series values in your data represented in a single row, or across multiple rows?
148
152
  </div>
149
- <div className='column'>
150
- <button
151
- className={'cove-data-designer__button' + (configureData.dataDescription.singleRow === false ? ' active' : '')}
152
- onClick={() => {
153
- updateDescriptionProp('singleRow', false)
154
- }}
155
- >
156
- <Card>
157
- <strong className='cove-heading--3'>Multiple Rows</strong>
158
- <p className='mb-1'>Each series data is broken out into multiple rows.</p>
159
- {DATA_TABLE_MULTI_ROW}
160
- </Card>
161
- </button>
153
+ <div className='grid grid-gap-2 mb-4'>
154
+ <div className='column'>
155
+ <button
156
+ className={
157
+ 'cove-data-designer__button' +
158
+ (configureData.dataDescription.singleRow === true ? ' active' : '')
159
+ }
160
+ onClick={() => {
161
+ updateDescriptionProp('singleRow', true)
162
+ }}
163
+ >
164
+ <Card>
165
+ <strong className='cove-heading--3'>Single Row</strong>
166
+ <p className='mb-1'>Each row contains the data for an individual series in itself.</p>
167
+ {DATA_TABLE_SINGLE_ROW}
168
+ </Card>
169
+ </button>
170
+ </div>
171
+ <div className='column'>
172
+ <button
173
+ className={
174
+ 'cove-data-designer__button' +
175
+ (configureData.dataDescription.singleRow === false ? ' active' : '')
176
+ }
177
+ onClick={() => {
178
+ updateDescriptionProp('singleRow', false)
179
+ }}
180
+ >
181
+ <Card>
182
+ <strong className='cove-heading--3'>Multiple Rows</strong>
183
+ <p className='mb-1'>Each series data is broken out into multiple rows.</p>
184
+ {DATA_TABLE_MULTI_ROW}
185
+ </Card>
186
+ </button>
187
+ </div>
162
188
  </div>
163
189
  </div>
164
- </div>
165
- {configureData.dataDescription.singleRow === false && (
166
- <>
167
- <div className='mb-2'>
168
- <div className='mb-1'>Which property in the dataset represents which series the row is describing?</div>
169
- <select
170
- onChange={e => {
171
- updateDescriptionProp('seriesKey', e.target.value)
172
- }}
173
- defaultValue={configureData.dataDescription.seriesKey}
174
- >
175
- <option value=''>Choose an option</option>
176
- {Object.keys(configureData.data[0]).map((value, index) => (
177
- <option value={value} key={index}>
178
- {value}
179
- </option>
180
- ))}
181
- </select>
182
- </div>
183
- <div className='mb-2'>
184
- <div className='mb-1'>Which property in the dataset represents the values for the category/date axis or map geography?</div>
185
- <select
186
- onChange={e => {
187
- updateDescriptionProp('xKey', e.target.value)
188
- }}
189
- defaultValue={configureData.dataDescription.xKey}
190
- >
191
- <option value=''>Choose an option</option>
192
- {Object.keys(configureData.data[0]).map((value, index) => (
193
- <option value={value} key={index}>
194
- {value}
195
- </option>
196
- ))}
197
- </select>
198
- </div>
199
- <div className='mb-2'>
200
- <div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
201
- {configureData.dataDescription.valueKeysTallSupport && configureData.dataDescription.valueKeysTallSupport.length > 0 && (
202
- <ul className='value-list'>
203
- {configureData.dataDescription.valueKeysTallSupport.map((valueKey, index) => (
204
- <li key={`value-keys-list-${index}`}>
205
- {valueKey}
206
- <button
207
- onClick={() => {
208
- let newValueKeys = configureData.dataDescription.valueKeysTallSupport
209
- newValueKeys.splice(index, 1)
210
- updateDescriptionProp('valueKeysTallSupport', newValueKeys)
211
- }}
212
- >
213
- X
214
- </button>
215
- </li>
216
- ))}
217
- </ul>
218
- )}
219
- <select
220
- onChange={e => {
221
- if (e.target.value && (!configureData.dataDescription.valueKeysTallSupport || configureData.dataDescription.valueKeysTallSupport.indexOf(e.target.value) === -1)) {
222
- updateDescriptionProp('valueKeysTallSupport', [...(configureData.dataDescription.valueKeysTallSupport || []), e.target.value])
223
- }
224
- }}
225
- >
226
- <option value=''>Choose an option</option>
227
- {Object.keys(configureData.data[0])
228
- .filter(value => !configureData.dataDescription.valueKeysTallSupport || configureData.dataDescription.valueKeysTallSupport.indexOf(value) === -1)
229
- .map((value, index) => (
230
- <option value={value} key={`value-keys-option-${index}`}>
190
+ {configureData.dataDescription.singleRow === false && (
191
+ <>
192
+ <div className='mb-2'>
193
+ <div className='mb-1'>
194
+ Which property in the dataset represents which series the row is describing?
195
+ </div>
196
+ <select
197
+ onChange={e => {
198
+ updateDescriptionProp('seriesKey', e.target.value)
199
+ }}
200
+ defaultValue={configureData.dataDescription.seriesKey}
201
+ >
202
+ <option value=''>Choose an option</option>
203
+ {Object.keys(configureData.data[0]).map((value, index) => (
204
+ <option value={value} key={index}>
231
205
  {value}
232
206
  </option>
233
207
  ))}
234
- </select>
235
- </div>
236
- <div className='mb-2'>
237
- <div className='mb-1'>(Optional) Which properties in the dataset should be ignored? (will not be used or treated as filters)</div>
238
- {configureData.dataDescription.ignoredKeys && configureData.dataDescription.ignoredKeys.length > 0 && (
239
- <ul className='value-list'>
240
- {configureData.dataDescription.ignoredKeys.map((ignoredKey, index) => (
241
- <li key={`value-keys-list-${index}`}>
242
- {ignoredKey}
243
- <button
244
- onClick={() => {
245
- let newIgnoredKeys = configureData.dataDescription.ignoredKeys
246
- newIgnoredKeys.splice(index, 1)
247
- updateDescriptionProp('ignoredKeys', newIgnoredKeys)
248
- }}
249
- >
250
- X
251
- </button>
252
- </li>
253
- ))}
254
- </ul>
255
- )}
256
- <select
257
- onChange={e => {
258
- if (e.target.value) {
259
- updateDescriptionProp('ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
260
- }
261
- e.target.value = ''
262
- }}
263
- >
264
- <option value=''>Choose an option</option>
265
- {Object.keys(configureData.data[0])
266
- .filter(value => !configureData.dataDescription.ignoredKeys || configureData.dataDescription.ignoredKeys.indexOf(value) === -1)
267
- .map((value, index) => (
268
- <option value={value} key={`ignored-keys-option-${index}`}>
208
+ </select>
209
+ </div>
210
+ <div className='mb-2'>
211
+ <div className='mb-1'>
212
+ Which property in the dataset represents the values for the category/date axis or map geography?
213
+ </div>
214
+ <select
215
+ onChange={e => {
216
+ updateDescriptionProp('xKey', e.target.value)
217
+ }}
218
+ defaultValue={configureData.dataDescription.xKey}
219
+ >
220
+ <option value=''>Choose an option</option>
221
+ {Object.keys(configureData.data[0]).map((value, index) => (
222
+ <option value={value} key={index}>
269
223
  {value}
270
224
  </option>
271
225
  ))}
272
- </select>
273
- </div>
274
- </>
275
- )}
276
- </>
277
- )}
226
+ </select>
227
+ </div>
228
+ <div className='mb-2'>
229
+ <div className='mb-1'>
230
+ Which properties in the dataset represent the numeric value? (all remaining properties will be
231
+ treated as filters)
232
+ </div>
233
+ {configureData.dataDescription.valueKeysTallSupport &&
234
+ configureData.dataDescription.valueKeysTallSupport.length > 0 && (
235
+ <ul className='value-list'>
236
+ {configureData.dataDescription.valueKeysTallSupport.map((valueKey, index) => (
237
+ <li key={`value-keys-list-${index}`}>
238
+ {valueKey}
239
+ <button
240
+ onClick={() => {
241
+ let newValueKeys = configureData.dataDescription.valueKeysTallSupport
242
+ newValueKeys.splice(index, 1)
243
+ updateDescriptionProp('valueKeysTallSupport', newValueKeys)
244
+ }}
245
+ >
246
+ X
247
+ </button>
248
+ </li>
249
+ ))}
250
+ </ul>
251
+ )}
252
+ <select
253
+ onChange={e => {
254
+ if (
255
+ e.target.value &&
256
+ (!configureData.dataDescription.valueKeysTallSupport ||
257
+ configureData.dataDescription.valueKeysTallSupport.indexOf(e.target.value) === -1)
258
+ ) {
259
+ updateDescriptionProp('valueKeysTallSupport', [
260
+ ...(configureData.dataDescription.valueKeysTallSupport || []),
261
+ e.target.value
262
+ ])
263
+ }
264
+ }}
265
+ >
266
+ <option value=''>Choose an option</option>
267
+ {Object.keys(configureData.data[0])
268
+ .filter(
269
+ value =>
270
+ !configureData.dataDescription.valueKeysTallSupport ||
271
+ configureData.dataDescription.valueKeysTallSupport.indexOf(value) === -1
272
+ )
273
+ .map((value, index) => (
274
+ <option value={value} key={`value-keys-option-${index}`}>
275
+ {value}
276
+ </option>
277
+ ))}
278
+ </select>
279
+ </div>
280
+ <div className='mb-2'>
281
+ <div className='mb-1'>
282
+ (Optional) Which properties in the dataset should be ignored? (will not be used or treated as
283
+ filters)
284
+ </div>
285
+ {configureData.dataDescription.ignoredKeys &&
286
+ configureData.dataDescription.ignoredKeys.length > 0 && (
287
+ <ul className='value-list'>
288
+ {configureData.dataDescription.ignoredKeys.map((ignoredKey, index) => (
289
+ <li key={`value-keys-list-${index}`}>
290
+ {ignoredKey}
291
+ <button
292
+ onClick={() => {
293
+ let newIgnoredKeys = configureData.dataDescription.ignoredKeys
294
+ newIgnoredKeys.splice(index, 1)
295
+ updateDescriptionProp('ignoredKeys', newIgnoredKeys)
296
+ }}
297
+ >
298
+ X
299
+ </button>
300
+ </li>
301
+ ))}
302
+ </ul>
303
+ )}
304
+ <select
305
+ onChange={e => {
306
+ if (e.target.value) {
307
+ updateDescriptionProp('ignoredKeys', [
308
+ ...(configureData.dataDescription.ignoredKeys || []),
309
+ e.target.value
310
+ ])
311
+ }
312
+ e.target.value = ''
313
+ }}
314
+ >
315
+ <option value=''>Choose an option</option>
316
+ {Object.keys(configureData.data[0])
317
+ .filter(
318
+ value =>
319
+ !configureData.dataDescription.ignoredKeys ||
320
+ configureData.dataDescription.ignoredKeys.indexOf(value) === -1
321
+ )
322
+ .map((value, index) => (
323
+ <option value={value} key={`ignored-keys-option-${index}`}>
324
+ {value}
325
+ </option>
326
+ ))}
327
+ </select>
328
+ </div>
329
+ </>
330
+ )}
331
+ </>
332
+ )}
278
333
 
279
334
  {config?.visualizationType === 'Forest Plot' && (
280
335
  <>
@@ -1,12 +1,20 @@
1
- .cove-component__header {
1
+ .cdc-open-viz-module header.cove-component__header {
2
2
  position: relative;
3
- padding: 0.6em 0.8em;
3
+ padding: 1rem;
4
4
  margin: 0;
5
5
  color: var(--white);
6
- font-size: 1.1em;
6
+
7
+ & > sup {
8
+ font-size: var(--superTitle-font-size);
9
+ font-family: var(--app-font-secondary);
10
+ font-weight: 500;
11
+ text-transform: uppercase;
12
+ top: 0;
13
+ }
7
14
 
8
15
  h2 {
9
- font-size: 1.1rem;
16
+ font-size: var(--title-font-size);
17
+ font-family: var(--app-font-secondary);
10
18
  color: var(--white);
11
19
  margin: 0;
12
20
  }
@@ -27,7 +35,6 @@
27
35
  }
28
36
 
29
37
  &:not(:empty) {
30
- padding: 0.6em 0.8em;
31
38
  border-bottom-width: 4px;
32
39
  border-bottom-style: solid;
33
40
  }
@@ -16,7 +16,7 @@ const Title = (props: HeaderProps) => {
16
16
  const { isDashboard, title, superTitle, classes = [], showTitle = true, ariaLevel = 2 } = props
17
17
 
18
18
  // standard classes every vis should have
19
- const updatedClasses = ['cove-component__header', 'component__header', ...classes]
19
+ const updatedClasses = ['cove-component__header', 'component__header', 'mb-3', ...classes]
20
20
 
21
21
  return (
22
22
  title &&