@cdc/core 4.25.5-1 → 4.25.6-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 (41) hide show
  1. package/LICENSE +201 -0
  2. package/components/Alert/components/Alert.tsx +1 -1
  3. package/components/DataTable/DataTable.tsx +0 -3
  4. package/components/DataTable/DataTableStandAlone.tsx +15 -9
  5. package/components/DataTable/components/ChartHeader.tsx +6 -4
  6. package/components/DataTable/components/DataTableEditorPanel.tsx +25 -3
  7. package/components/DataTable/helpers/chartCellMatrix.tsx +13 -10
  8. package/components/DataTable/helpers/getChartCellValue.ts +42 -26
  9. package/components/DataTable/helpers/mapCellMatrix.tsx +9 -1
  10. package/components/EditorPanel/FootnotesEditor.tsx +76 -22
  11. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +1 -1
  12. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +48 -34
  13. package/components/Filters/Filters.tsx +27 -69
  14. package/components/Filters/components/Dropdown.tsx +1 -1
  15. package/components/Footnotes/Footnotes.tsx +1 -1
  16. package/components/Footnotes/FootnotesStandAlone.tsx +8 -33
  17. package/components/Layout/components/Visualization/index.tsx +2 -1
  18. package/components/Legend/Legend.Gradient.tsx +3 -2
  19. package/components/MultiSelect/MultiSelect.tsx +3 -6
  20. package/components/NestedDropdown/NestedDropdown.tsx +19 -19
  21. package/helpers/cove/number.ts +5 -3
  22. package/helpers/coveUpdateWorker.ts +4 -0
  23. package/helpers/formatConfigBeforeSave.ts +19 -32
  24. package/helpers/updateFieldFactory.ts +1 -0
  25. package/helpers/ver/4.25.4.ts +77 -0
  26. package/helpers/ver/4.25.6.ts +36 -0
  27. package/helpers/ver/4.25.7.ts +26 -0
  28. package/helpers/ver/tests/4.25.4.test.ts +66 -1
  29. package/helpers/ver/tests/4.25.6.test.ts +84 -0
  30. package/package.json +7 -5
  31. package/styles/_global.scss +0 -4
  32. package/styles/filters.scss +0 -4
  33. package/types/Axis.ts +2 -0
  34. package/types/DataSet.ts +14 -0
  35. package/types/Footnotes.ts +5 -2
  36. package/types/Table.ts +1 -0
  37. package/types/Visualization.ts +3 -12
  38. package/types/VizFilter.ts +3 -0
  39. package/components/Filters/helpers/getNewRuntime.ts +0 -35
  40. package/components/Filters/helpers/tests/getNewRuntime.test.ts +0 -82
  41. /package/helpers/{fetchRemoteData.js → fetchRemoteData.ts} +0 -0
@@ -1,75 +1,129 @@
1
+ import React, { useState } from 'react'
1
2
  import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
2
3
  import _ from 'lodash'
3
4
  import Footnotes, { Footnote } from '../../types/Footnotes'
4
5
  import { footnotesSymbols } from '../../helpers/footnoteSymbols'
5
6
  import InputSelect from '../inputs/InputSelect'
6
7
  import { TextField } from './Inputs'
8
+ import { Datasets } from '@cdc/core/types/DataSet'
9
+ import DataTransform from '../../helpers/DataTransform'
10
+ import fetchRemoteData from '../../helpers/fetchRemoteData'
11
+ import Loader from '../Loader'
12
+ import { AnyVisualization } from '../../types/Visualization'
7
13
  interface FootnotesEditorProps {
8
- config: Footnotes
9
- updateField: UpdateFieldFunc<Footnote[]>
14
+ config: AnyVisualization
15
+ updateField: UpdateFieldFunc<Footnote[] | Object>
16
+ datasets: Datasets
10
17
  }
11
18
 
12
- const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }) => {
19
+ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField, datasets }) => {
20
+ const footnotesConfig = config.footnotes || {}
21
+ const [errorMessage, setErrorMessage] = useState('')
22
+ const [loadingAPIData, setLoadingAPIData] = useState(false)
23
+ const [datasetsCache, setDatasetsCache] = useState(datasets || {})
24
+ const transform = new DataTransform()
25
+
26
+ const fetchData = async datasetKey => {
27
+ const { data, dataUrl } = datasetsCache[datasetKey]
28
+ if (!dataUrl) return data
29
+ let newData = data
30
+ const noCachedData = dataUrl && !data
31
+ const dataSetChanged = datasetKey !== footnotesConfig.dataKey
32
+ setErrorMessage('')
33
+ if (dataSetChanged || noCachedData) {
34
+ setLoadingAPIData(true)
35
+ try {
36
+ newData = await fetchRemoteData(dataUrl)
37
+ newData = transform.autoStandardize(newData)
38
+ } catch (e) {
39
+ setErrorMessage('There was an issue loading the data source. Please check the datasource URL and try again.')
40
+ }
41
+
42
+ setLoadingAPIData(false)
43
+ }
44
+ return newData
45
+ }
46
+
13
47
  const addStaticFootnote = () => {
14
- const newStaticNotes = [...(config.staticFootnotes || []), { text: 'Add Footnote Text' }]
15
- updateField(null, null, 'staticFootnotes', newStaticNotes)
48
+ const newStaticNotes = [...(footnotesConfig.staticFootnotes || []), { text: 'Add Footnote Text' }]
49
+ updateField('footnotes', null, 'staticFootnotes', newStaticNotes)
16
50
  }
17
51
 
18
52
  const updateStaticFootnote = (footnoteIndex, footnoteUpdate: Footnote) => {
19
- const footnoteCopy = _.cloneDeep(config.staticFootnotes)
53
+ const footnoteCopy = _.cloneDeep(footnotesConfig.staticFootnotes)
20
54
  footnoteCopy[footnoteIndex] = footnoteUpdate
21
- updateField(null, null, 'staticFootnotes', footnoteCopy)
55
+ updateField('footnotes', null, 'staticFootnotes', footnoteCopy)
22
56
  }
23
57
 
24
58
  const deleteStaticFootnote = footnoteIndex => {
25
- const footnoteCopy = _.cloneDeep(config.staticFootnotes)
59
+ const footnoteCopy = _.cloneDeep(footnotesConfig.staticFootnotes)
26
60
  footnoteCopy.splice(footnoteIndex, 1)
27
- updateField(null, null, 'staticFootnotes', footnoteCopy)
61
+ updateField('footnotes', null, 'staticFootnotes', footnoteCopy)
28
62
  }
29
63
 
30
64
  const getOptions = (opts: string[]) => {
31
65
  return [['', '--Select--']].concat(opts.map(key => [key, key]))
32
66
  }
33
67
 
34
- const datasets = config.datasets || {}
68
+ const dataColumns = footnotesConfig.dataKey
69
+ ? getOptions(Object.keys(datasetsCache[footnotesConfig.dataKey]?.data?.[0] || {}))
70
+ : []
71
+ const dataSetOptions = getOptions(Object.keys(datasetsCache))
72
+
73
+ const changeFootnoteDataKey = async value => {
74
+ if (value) {
75
+ if (!datasetsCache[value]) {
76
+ const newData = await fetchData(value)
77
+ setDatasetsCache({ ...datasetsCache, [value]: { ...datasetsCache[value], data: newData } })
78
+ }
79
+ } else {
80
+ updateField('footnotes', null, 'dynamicFootnotes', {})
81
+ }
35
82
 
36
- const dataColumns = config.dataKey ? getOptions(Object.keys(datasets[config.dataKey]?.data?.[0] || {})) : []
37
- const dataSetOptions = getOptions(Object.keys(datasets))
83
+ updateField('footnotes', null, 'dataKey', value)
84
+ }
38
85
  return (
39
86
  <>
87
+ {loadingAPIData && <Loader fullScreen />}
40
88
  <em>Dynamic Footnotes</em>
41
89
  <div className='row border p-2'>
42
90
  <InputSelect
43
91
  label='Select a Footnote Dataset'
44
- value={config.dataKey}
92
+ value={footnotesConfig.dataKey}
45
93
  options={dataSetOptions}
46
94
  fieldName='dataKey'
47
- updateField={updateField}
95
+ updateField={(section, subsection, fieldname, dataKey) => {
96
+ changeFootnoteDataKey(dataKey)
97
+ }}
48
98
  />
99
+ {errorMessage && <p className='text-danger'>{errorMessage}</p>}
49
100
 
50
- {config.dataKey && (
101
+ {footnotesConfig.dataKey && (
51
102
  <div className='p-3'>
52
103
  <InputSelect
53
104
  label='Footnote Symbol Column'
54
- value={config.dynamicFootnotes?.symbolColumn}
105
+ value={footnotesConfig.dynamicFootnotes?.symbolColumn}
55
106
  options={dataColumns}
56
- section='dynamicFootnotes'
107
+ section='footnotes'
108
+ subsection='dynamicFootnotes'
57
109
  fieldName='symbolColumn'
58
110
  updateField={updateField}
59
111
  />
60
112
  <InputSelect
61
113
  label='Footnote Text Column'
62
- value={config.dynamicFootnotes?.textColumn}
114
+ value={footnotesConfig.dynamicFootnotes?.textColumn}
63
115
  options={dataColumns}
64
- section='dynamicFootnotes'
116
+ section='footnotes'
117
+ subsection='dynamicFootnotes'
65
118
  fieldName='textColumn'
66
119
  updateField={updateField}
67
120
  />
68
121
  <InputSelect
69
122
  label='Footnote Order Column'
70
- value={config.dynamicFootnotes?.orderColumn}
123
+ value={footnotesConfig.dynamicFootnotes?.orderColumn}
71
124
  options={dataColumns}
72
- section='dynamicFootnotes'
125
+ section='footnotes'
126
+ subsection='dynamicFootnotes'
73
127
  fieldName='orderColumn'
74
128
  updateField={updateField}
75
129
  />
@@ -81,7 +135,7 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }
81
135
 
82
136
  <em>Static Footnotes</em>
83
137
 
84
- {config.staticFootnotes?.map((note, index) => (
138
+ {footnotesConfig.staticFootnotes?.map((note, index) => (
85
139
  <div key={index} className='row border p-2'>
86
140
  <div className='col-8'>
87
141
  <InputSelect
@@ -267,7 +267,7 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
267
267
  const orderedSubGroupValues = subGrouping.valuesLookup[groupName].orderedValues
268
268
  return (
269
269
  <div key={`group-subgroup-values-${groupName}-${i}`}>
270
- <span className='font-weight-bold'>{groupName}</span>
270
+ <span className='font-weight-bold fw-bold'>{groupName}</span>
271
271
  <FilterOrder
272
272
  key={`subgroup-values-${groupName}-${i}`}
273
273
  orderedValues={orderedSubGroupValues}
@@ -19,9 +19,10 @@ type VizFilterProps = {
19
19
  config: Visualization
20
20
  updateField: UpdateFieldFunc<string | VizFilter[] | VizFilter>
21
21
  rawData: Object[]
22
+ hasFootnotes?: boolean
22
23
  }
23
24
 
24
- const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawData }) => {
25
+ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawData, hasFootnotes }) => {
25
26
  const openControls = useState({})
26
27
  const dataColumns = useMemo(() => {
27
28
  return _.uniq(_.flatten(rawData?.map(row => Object.keys(row))))
@@ -171,20 +172,6 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
171
172
  options={filterStyleOptions}
172
173
  />
173
174
 
174
- <Select
175
- value={filter.defaultValue}
176
- options={
177
- filter.resetLabel
178
- ? [filter.resetLabel, ...config.filters?.[filterIndex].values]
179
- : config.filters?.[filterIndex].values
180
- }
181
- updateField={(_section, _subSection, _key, value) => {
182
- updateFilterDefaultValue(filterIndex, value)
183
- }}
184
- label='Filter Default Value'
185
- initial='Select'
186
- />
187
-
188
175
  {filter.filterStyle !== 'nested-dropdown' ? (
189
176
  <>
190
177
  <Select
@@ -196,16 +183,21 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
196
183
  initial='- Select Option -'
197
184
  />
198
185
 
199
- <label>
200
- <span className='edit-showDropdown column-heading'>Show Filter Input</span>
201
- <input
202
- type='checkbox'
203
- checked={filter.showDropdown === undefined ? true : filter.showDropdown}
204
- onChange={e => {
205
- updateFilterProp('showDropdown', filterIndex, e.target.checked)
186
+ {filter.columnName && (
187
+ <Select
188
+ value={filter.defaultValue}
189
+ options={
190
+ filter.resetLabel
191
+ ? [filter.resetLabel, ...config.filters?.[filterIndex].values]
192
+ : config.filters?.[filterIndex].values
193
+ }
194
+ updateField={(_section, _subSection, _key, value) => {
195
+ updateFilterDefaultValue(filterIndex, value)
206
196
  }}
197
+ label='Filter Default Value'
198
+ initial='Select'
207
199
  />
208
- </label>
200
+ )}
209
201
 
210
202
  <label>
211
203
  <span className='edit-label column-heading'>Label</span>
@@ -240,17 +232,6 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
240
232
  />
241
233
  )}
242
234
 
243
- <label>
244
- <span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
245
- <input
246
- type='text'
247
- value={filter.setByQueryParameter}
248
- onChange={e => {
249
- updateFilterProp('setByQueryParameter', filterIndex, e.target.value)
250
- }}
251
- />
252
- </label>
253
-
254
235
  <Select
255
236
  value={filter.order || 'asc'}
256
237
  fieldName='order'
@@ -278,6 +259,27 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
278
259
  options={dataColumns}
279
260
  />
280
261
  )}
262
+ <label>
263
+ <span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
264
+ <input
265
+ type='text'
266
+ value={filter.setByQueryParameter}
267
+ onChange={e => {
268
+ updateFilterProp('setByQueryParameter', filterIndex, e.target.value)
269
+ }}
270
+ />
271
+ </label>
272
+
273
+ <label>
274
+ <input
275
+ type='checkbox'
276
+ checked={filter.showDropdown === undefined ? true : filter.showDropdown}
277
+ onChange={e => {
278
+ updateFilterProp('showDropdown', filterIndex, e.target.checked)
279
+ }}
280
+ />
281
+ <span className='edit-showDropdown column-heading'>Show Filter</span>
282
+ </label>
281
283
  </>
282
284
  ) : (
283
285
  <NestedDropdownEditor
@@ -291,6 +293,18 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
291
293
  updateFilterStyle={updateFilterStyle}
292
294
  />
293
295
  )}
296
+ {hasFootnotes && (
297
+ <label>
298
+ <input
299
+ type='checkbox'
300
+ checked={!!filter.filterFootnotes}
301
+ onChange={e => {
302
+ updateFilterProp('filterFootnotes', filterIndex, e.target.checked)
303
+ }}
304
+ />
305
+ <span className='edit-showDropdown column-heading'>Filter Footnotes</span>
306
+ </label>
307
+ )}
294
308
  <label>
295
309
  <span className='edit-label column-heading'>
296
310
  Filter Parents{' '}
@@ -1,5 +1,6 @@
1
1
  import { useState, useEffect, useMemo, useRef, useId } from 'react'
2
2
  import _ from 'lodash'
3
+ import parse from 'html-react-parser'
3
4
 
4
5
  // CDC
5
6
  import Button from '../elements/Button'
@@ -13,8 +14,6 @@ import { getNestedOptions } from './helpers/getNestedOptions'
13
14
  import { getWrappingStatuses } from './helpers/filterWrapping'
14
15
  import { handleSorting } from './helpers/handleSorting'
15
16
  import { getChangedFilters } from './helpers/getChangedFilters'
16
- import { getNewRuntime } from './helpers/getNewRuntime'
17
- import { filterVizData } from '../../helpers/filterVizData'
18
17
  import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
19
18
  import { applyQueuedActive } from './helpers/applyQueuedActive'
20
19
  import Tabs from './components/Tabs'
@@ -40,25 +39,19 @@ const BUTTON_TEXT = {
40
39
  }
41
40
 
42
41
  type FilterProps = {
43
- filteredData: Object[]
44
- dimensions: DimensionsType
42
+ dimensions?: DimensionsType
45
43
  config: Visualization
46
- // function for updating the runtime filters
47
- setFilteredData: Function
48
- // updating function for setting fitlerBehavior
49
- setConfig: Function
44
+ setFilters: Function
50
45
  standaloneMap?: boolean
51
46
  excludedData?: Object[]
52
- getUniqueValues: Function
47
+ getUniqueValues?: Function
53
48
  }
54
49
 
55
50
  const Filters: React.FC<FilterProps> = ({
56
51
  config: visualizationConfig,
57
- filteredData,
58
52
  dimensions,
59
53
  standaloneMap,
60
- setConfig,
61
- setFilteredData,
54
+ setFilters,
62
55
  excludedData,
63
56
  getUniqueValues
64
57
  }) => {
@@ -82,7 +75,7 @@ const Filters: React.FC<FilterProps> = ({
82
75
  // end of Handle Wrapping Filters
83
76
 
84
77
  const initialActiveFilters = useMemo(() => {
85
- if (!filteredData) return []
78
+ //if (!filteredData) return []
86
79
  return filters.map(filter => filter.active)
87
80
  }, [])
88
81
 
@@ -92,36 +85,10 @@ const Filters: React.FC<FilterProps> = ({
92
85
  }, [filters])
93
86
 
94
87
  const changeFilterActive = (index, value) => {
95
- let newFilters = standaloneMap ? [...filteredData] : [...visualizationConfig.filters]
88
+ if (filterBehavior === 'Apply Button') setShowApplyButton(true)
96
89
 
97
- newFilters = getChangedFilters(newFilters, index, value, visualizationConfig.filterBehavior)
98
- if (visualizationConfig.filterBehavior === 'Apply Button') setShowApplyButton(true)
99
-
100
- if (!visualizationConfig.dynamicSeries) {
101
- const _newFilters = addValuesToFilters(newFilters, excludedData)
102
- setConfig({
103
- ...visualizationConfig,
104
- filters: _newFilters
105
- })
106
- }
107
-
108
- if (visualizationConfig.filterBehavior === 'Filter Change') {
109
- if (standaloneMap) {
110
- setFilteredData(newFilters)
111
- } else {
112
- const newFilteredData = filterVizData(newFilters, excludedData)
113
- setFilteredData(newFilteredData)
114
-
115
- if (visualizationConfig.dynamicSeries) {
116
- const runtime = getNewRuntime(visualizationConfig, newFilteredData)
117
- setConfig({
118
- ...visualizationConfig,
119
- filters: newFilters,
120
- runtime
121
- })
122
- }
123
- }
124
- }
90
+ const newFilters = getChangedFilters([...filters], index, value, filterBehavior)
91
+ setFilters(newFilters)
125
92
  }
126
93
 
127
94
  const handleApplyButton = newFilters => {
@@ -140,19 +107,13 @@ const Filters: React.FC<FilterProps> = ({
140
107
  updateQueryString(queryParams)
141
108
  }
142
109
 
143
- setConfig({ ...visualizationConfig, filters: newFilters })
144
-
145
- if (standaloneMap) {
146
- setFilteredData(newFilters, excludedData)
147
- } else {
148
- setFilteredData(filterVizData(newFilters, excludedData))
149
- }
110
+ setFilters(newFilters)
150
111
 
151
112
  setShowApplyButton(false)
152
113
  }
153
114
 
154
115
  const handleReset = e => {
155
- let newFilters = [...visualizationConfig.filters]
116
+ let newFilters = [...filters]
156
117
  e.preventDefault()
157
118
 
158
119
  // reset to first item in values array.
@@ -175,18 +136,12 @@ const Filters: React.FC<FilterProps> = ({
175
136
  updateQueryString(queryParams)
176
137
  }
177
138
 
178
- setConfig({ ...visualizationConfig, filters: newFilters })
179
-
180
- if (standaloneMap) {
181
- setFilteredData(newFilters, excludedData)
182
- } else {
183
- setFilteredData(filterVizData(newFilters, excludedData))
184
- }
139
+ setFilters(newFilters)
185
140
  }
186
141
 
187
142
  const mobileFilterStyle = useMemo(() => {
188
- if (!dimensions) false
189
- const [width] = dimensions || []
143
+ if (!dimensions) return false
144
+ const [width] = dimensions
190
145
  const isMobile = Number(width) < 768
191
146
  const isTabSimple = filters?.some(filter => filter.filterStyle === VIZ_FILTER_STYLE.tabSimple)
192
147
 
@@ -195,13 +150,12 @@ const Filters: React.FC<FilterProps> = ({
195
150
 
196
151
  const vizFiltersWithValues = useMemo(() => {
197
152
  // Here charts is using config.filters where maps is using a runtime value
198
- let vizfilters = standaloneMap ? filteredData : filters
199
- if (!vizfilters) return []
200
- if (vizfilters.fromHash) delete vizfilters.fromHash // support for Maps config
201
- return addValuesToFilters(vizfilters as VizFilter[], visualizationConfig.data)
202
- }, [filters, filteredData])
153
+ if (!filters) return []
154
+ if (filters.fromHash) delete filters.fromHash // support for Maps config
155
+ return addValuesToFilters(filters as VizFilter[], visualizationConfig.data)
156
+ }, [filters])
203
157
 
204
- if (visualizationConfig?.filters?.length === 0) return
158
+ if (visualizationConfig?.filters?.length === 0) return <></>
205
159
 
206
160
  const getClasses = () => {
207
161
  const { visualizationType, legend } = visualizationConfig || {}
@@ -220,13 +174,14 @@ const Filters: React.FC<FilterProps> = ({
220
174
  return (
221
175
  <section className={getClasses().join(' ')}>
222
176
  {visualizationConfig.filterIntro && (
223
- <p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
177
+ <p className='filters-section__intro-text mb-3'>{parse(visualizationConfig.filterIntro)}</p>
224
178
  )}
225
179
  <div className='d-flex flex-wrap w-100 filters-section__wrapper align-items-end'>
226
180
  <>
227
181
  {vizFiltersWithValues.map((singleFilter: VizFilter, outerIndex) => {
228
182
  if (singleFilter.showDropdown === false) return
229
183
  const { label, filterStyle, columnName } = singleFilter as VizFilter
184
+ const [nestedActiveGroup, nestedActiveSubGroup] = getNestedGroup(singleFilter)
230
185
 
231
186
  handleSorting(singleFilter)
232
187
 
@@ -239,7 +194,6 @@ const Filters: React.FC<FilterProps> = ({
239
194
  const { isDropdown } = wrappingFilters[columnName] || {}
240
195
  const showDefaultDropdown =
241
196
  ((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
242
- const [nestedActiveGroup, nestedActiveSubGroup] = getNestedGroup(singleFilter)
243
197
  const hideLabelMargin = singleFilter.filterStyle === 'tab-simple' && !showDefaultDropdown
244
198
  return (
245
199
  <div
@@ -249,7 +203,7 @@ const Filters: React.FC<FilterProps> = ({
249
203
  >
250
204
  {label && (
251
205
  <label
252
- className={`font-weight-bold mb-${hideLabelMargin ? '0' : '2'}`}
206
+ className={`font-weight-bold fw-bold mb-${hideLabelMargin ? '0' : '2'}`}
253
207
  htmlFor={`filter-${outerIndex}`}
254
208
  >
255
209
  {label}
@@ -284,7 +238,11 @@ const Filters: React.FC<FilterProps> = ({
284
238
  <MultiSelect
285
239
  options={singleFilter.values.map(v => ({ value: v, label: v }))}
286
240
  fieldName={outerIndex}
287
- updateField={(_section, _subSection, fieldName, value) => changeFilterActive(fieldName, value)}
241
+ updateField={(_section, _subSection, fieldName, value) => {
242
+ const defaultSelection = singleFilter.defaultValue || [singleFilter.values[0]]
243
+ const selection = value?.length ? value : defaultSelection
244
+ changeFilterActive(fieldName, selection)
245
+ }}
288
246
  selected={singleFilter.active as string[]}
289
247
  limit={(singleFilter as MultiSelectFilter).selectLimit || 5}
290
248
  />
@@ -18,7 +18,7 @@ const Dropdown: React.FC<DropdownProps> = ({ index: outerIndex, label, filter, c
18
18
  name={label}
19
19
  aria-label={`Filter by ${label}`}
20
20
  className={`cove-form-select ${DROPDOWN_STYLES}`}
21
- style={{ backgroundColor: 'white !important' }}
21
+ style={{ backgroundColor: 'white' }}
22
22
  data-index='0'
23
23
  value={queuedActive || active}
24
24
  onChange={e => {
@@ -11,7 +11,7 @@ const Footnotes: React.FC<FootnotesProps> = ({ footnotes }) => {
11
11
  <ul className='cove-footnotes'>
12
12
  {footnotes.map((note, i) => {
13
13
  return (
14
- <li key={note.symbol + i} className='mb-1'>
14
+ <li key={`${note.symbol || 'footnote-'}${i}`} className='mb-1'>
15
15
  {note.symbol && <span className='me-1'>{note.symbol}</span>}
16
16
  {note.text}
17
17
  </li>
@@ -1,45 +1,20 @@
1
- import EditorWrapper from '../EditorWrapper'
2
1
  import Footnotes from './Footnotes'
3
- import FootnotesEditor from '../EditorPanel/FootnotesEditor'
4
- import { ViewPort } from '../../types/ViewPort'
5
- import FootnotesConfig, { Footnote } from '../../types/Footnotes'
2
+ import FootnotesConfig from '../../types/Footnotes'
6
3
  import _ from 'lodash'
7
4
  import { useMemo } from 'react'
8
- import { updateFieldFactory } from '../../helpers/updateFieldFactory'
5
+ import { filterVizData } from '../../helpers/filterVizData'
6
+ import { VizFilter } from '../../types/VizFilter'
9
7
 
10
8
  type StandAloneProps = {
11
- isEditor?: boolean
12
- visualizationKey: string
13
9
  config: FootnotesConfig
14
- updateConfig?: (config: FootnotesConfig) => void
15
- viewport?: ViewPort
10
+ filters?: VizFilter[]
16
11
  }
17
12
 
18
- const FootnotesStandAlone: React.FC<StandAloneProps> = ({
19
- visualizationKey,
20
- config,
21
- viewport,
22
- isEditor,
23
- updateConfig
24
- }) => {
25
- const updateField = updateFieldFactory<Footnote[]>(config, updateConfig)
26
- if (isEditor)
27
- return (
28
- <EditorWrapper
29
- component={FootnotesStandAlone}
30
- visualizationKey={visualizationKey}
31
- visualizationConfig={config}
32
- updateConfig={updateConfig}
33
- type={'Footnotes'}
34
- viewport={viewport}
35
- >
36
- <FootnotesEditor key={visualizationKey} config={config} updateField={updateField} />
37
- </EditorWrapper>
38
- )
39
-
13
+ const FootnotesStandAlone: React.FC<StandAloneProps> = ({ config, filters }) => {
14
+ if (!config) return null
40
15
  // get the api footnotes from the config
41
16
  const apiFootnotes = useMemo(() => {
42
- const configData = config.formattedData || config.data
17
+ const configData = filterVizData(filters, config.data)
43
18
  if (configData && config.dataKey && config.dynamicFootnotes) {
44
19
  const { symbolColumn, textColumn, orderColumn } = config.dynamicFootnotes
45
20
  const _data = configData.map(row => _.pick(row, [symbolColumn, textColumn, orderColumn]))
@@ -47,7 +22,7 @@ const FootnotesStandAlone: React.FC<StandAloneProps> = ({
47
22
  return _data.map(row => ({ symbol: row[symbolColumn], text: row[textColumn] }))
48
23
  }
49
24
  return []
50
- }, [config.dynamicFootnotes, config.formattedData, config.data])
25
+ }, [config.dynamicFootnotes, config.data, filters])
51
26
 
52
27
  // get static footnotes from the config.footnotes
53
28
  const staticFootnotes = config.staticFootnotes || []
@@ -2,6 +2,7 @@
2
2
  import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
3
3
  import React, { forwardRef } from 'react'
4
4
  import { Config as DataBiteConfig } from '@cdc/data-bite/src/types/Config'
5
+ import { Config as DataTableConfig } from '@cdc/data-table/src/types/Config'
5
6
  import './visualizations.scss'
6
7
  import { Config as WaffleChartConfig } from '@cdc/waffle-chart/src/types/Config'
7
8
  import { MarkupIncludeConfig } from '@cdc/core/types/MarkupInclude'
@@ -10,7 +11,7 @@ import { MapConfig } from '@cdc/map/src/types/MapConfig'
10
11
 
11
12
  type VisualizationWrapper = {
12
13
  children: React.ReactNode
13
- config: ChartConfig | DataBiteConfig | WaffleChartConfig | MarkupIncludeConfig | DashboardFilters | MapConfig
14
+ config: ChartConfig | DataBiteConfig | WaffleChartConfig | MarkupIncludeConfig | DashboardFilters | MapConfig | DataTableConfig
14
15
  currentViewport?: string
15
16
  imageId?: string
16
17
  isEditor: boolean
@@ -98,7 +98,8 @@ const LegendGradient = ({
98
98
 
99
99
  if (style === 'gradient') {
100
100
  return (
101
- <svg className={'w-100 overflow-visible'} height={newHeight}>
101
+ // TODO: figure out why bootstrap 'overflow: visible' is not working consistently
102
+ <svg className={'w-100 overflow-visible'} height={newHeight} style={{ overflow: 'visible' }} width={width}>
102
103
  {/* background border*/}
103
104
  <rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill={BORDER_COLOR} />
104
105
  {/* Define the gradient */}
@@ -161,7 +162,7 @@ const LegendGradient = ({
161
162
  x2={xPosition + separatorSize / 2}
162
163
  y1={-3}
163
164
  y2={boxHeight + MARGIN + 3}
164
- stroke={'var(--colors-gray-cool-40'}
165
+ stroke={'var(--colors-gray-cool-40,#8d9297)'}
165
166
  strokeWidth={1}
166
167
  strokeDasharray='5,3'
167
168
  strokeDashoffset={1}
@@ -36,9 +36,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
36
36
  tooltip,
37
37
  loading
38
38
  }) => {
39
- const preselectedItems = useMemo(() => options.filter(opt => selected.includes(opt.value)).slice(0, limit), [options])
40
- const [selectedItems, setSelectedItems] = useState<Option[]>()
41
- const items = selectedItems || preselectedItems
39
+ const items = useMemo(() => options.filter(opt => selected.includes(opt.value)).slice(0, limit), [options])
42
40
  const [expanded, setExpanded] = useState(false)
43
41
  const multiSelectRef = useRef(null)
44
42
 
@@ -68,14 +66,12 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
68
66
  if (e && e.type === 'keyup' && e.key !== 'Enter') return
69
67
  if (limit && items?.length >= limit) return
70
68
  const newItems = [...items, option]
71
- setSelectedItems(newItems)
72
69
  update(newItems)
73
70
  }
74
71
 
75
72
  const handleItemRemove = (option: Option, e = null) => {
76
73
  if (e && e.type === 'keyup' && e.key !== 'Enter') return
77
74
  const newItems = items.filter(item => item.value !== option.value)
78
- setSelectedItems(newItems)
79
75
  update(newItems)
80
76
  }
81
77
 
@@ -99,6 +95,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
99
95
  items.map(item => (
100
96
  <div key={item.value} aria-labelledby={label ? multiID + label : undefined}>
101
97
  {item.label}
98
+
102
99
  <button
103
100
  aria-label='Remove'
104
101
  onClick={e => {
@@ -114,7 +111,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
114
111
  </div>
115
112
  ))
116
113
  ) : (
117
- <span className='pl-1 pt-1'>{loading ? 'Loading...' : '- Select -'}</span>
114
+ <span className='ps-1 pt-1'>{loading ? 'Loading...' : '- Select -'}</span>
118
115
  )}
119
116
  <button
120
117
  aria-label={expanded ? 'Collapse' : 'Expand'}