@cdc/core 4.24.11 → 4.24.12-2

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 (39) hide show
  1. package/components/Alert/components/Alert.tsx +9 -4
  2. package/components/DataTable/DataTable.tsx +19 -8
  3. package/components/DataTable/DataTableStandAlone.tsx +3 -3
  4. package/components/DataTable/components/ExpandCollapse.tsx +1 -1
  5. package/components/DataTable/components/SortIcon/sort-icon.css +15 -0
  6. package/components/DataTable/helpers/boxplotCellMatrix.tsx +6 -2
  7. package/components/DataTable/helpers/getChartCellValue.ts +11 -11
  8. package/components/EditorPanel/DataTableEditor.tsx +29 -23
  9. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +25 -1
  10. package/components/Filters/Filters.tsx +31 -35
  11. package/components/Filters/helpers/handleSorting.ts +5 -0
  12. package/components/Footnotes/FootnotesStandAlone.tsx +17 -4
  13. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  14. package/components/Legend/Legend.Gradient.tsx +1 -1
  15. package/components/Loader/Loader.tsx +10 -5
  16. package/components/MultiSelect/MultiSelect.tsx +84 -84
  17. package/components/MultiSelect/multiselect.styles.css +10 -0
  18. package/components/NestedDropdown/NestedDropdown.tsx +21 -18
  19. package/components/NestedDropdown/nesteddropdown.styles.css +11 -0
  20. package/components/Table/components/Row.tsx +1 -1
  21. package/components/inputs/{InputToggle.jsx → InputToggle.tsx} +35 -29
  22. package/dist/cove-main.css +6 -3
  23. package/dist/cove-main.css.map +1 -1
  24. package/helpers/addValuesToFilters.ts +22 -8
  25. package/helpers/coveUpdateWorker.ts +1 -1
  26. package/helpers/filterVizData.ts +2 -2
  27. package/helpers/formatConfigBeforeSave.ts +15 -0
  28. package/helpers/gatherQueryParams.ts +2 -3
  29. package/helpers/queryStringUtils.ts +10 -1
  30. package/helpers/tests/addValuesToFilters.test.ts +6 -1
  31. package/helpers/useDataVizClasses.ts +2 -1
  32. package/helpers/ver/4.24.10.ts +12 -0
  33. package/helpers/ver/versionNeedsUpdate.ts +2 -0
  34. package/helpers/viewports.ts +8 -7
  35. package/package.json +2 -2
  36. package/styles/_global-variables.scss +8 -3
  37. package/styles/_reset.scss +0 -1
  38. package/types/Version.ts +1 -0
  39. package/types/VizFilter.ts +2 -1
@@ -20,6 +20,8 @@ type AlertProps = {
20
20
  autoDismiss?: boolean
21
21
  // set seconds until autoDismiss, default is 5 seconds
22
22
  secondsBeforeDismiss?: number
23
+ // provide option for non dismissable alert
24
+ showCloseButton?: boolean
23
25
  }
24
26
 
25
27
  const Alert: React.FC<AlertProps> = ({
@@ -29,7 +31,8 @@ const Alert: React.FC<AlertProps> = ({
29
31
  heading,
30
32
  onDismiss,
31
33
  autoDismiss,
32
- secondsBeforeDismiss = 5
34
+ secondsBeforeDismiss = 5,
35
+ showCloseButton = true
33
36
  }) => {
34
37
  // sanitize the text for setting dangerouslySetInnerHTML
35
38
 
@@ -55,9 +58,11 @@ const Alert: React.FC<AlertProps> = ({
55
58
  {type === 'info' && <Icon display='info' size={iconSize} />}
56
59
  <span dangerouslySetInnerHTML={sanitizedData()} />
57
60
  </div>
58
- <button type='button' className='close pl-5' aria-label='Close' onClick={() => onDismiss()}>
59
- X
60
- </button>
61
+ {showCloseButton && (
62
+ <button type='button' className='close pl-5' aria-label='Close' onClick={() => onDismiss()}>
63
+ X
64
+ </button>
65
+ )}
61
66
  </div>
62
67
  )
63
68
  }
@@ -202,7 +202,7 @@ const DataTable = (props: DataTableProps) => {
202
202
  : config.visualizationType === 'Pie'
203
203
  ? [config.yAxis.dataKey]
204
204
  : config.visualizationType === 'Box Plot'
205
- ? Object.entries(config.boxplot.tableData[0])
205
+ ? config?.boxplot?.plots?.[0] ? Object.entries(config.boxplot.plots[0]) : []
206
206
  : config.runtime?.seriesKeys),
207
207
  [config.runtime?.seriesKeys]) // eslint-disable-line
208
208
 
@@ -244,17 +244,28 @@ const DataTable = (props: DataTableProps) => {
244
244
  </MediaControls.Section>
245
245
  )
246
246
  }
247
+ const getClassNames = (): string => {
248
+ const classes = ['data-table-container']
249
+ const isBrushActive = config?.brush?.active && config.legend?.position !== 'bottom'
250
+
251
+ if (isBrushActive) {
252
+ classes.push('brush-active')
253
+ }
254
+
255
+ classes.push(viewport)
256
+
257
+ const downloadLinkClass = !config.table.showDownloadLinkBelow ? 'download-link-above' : ''
258
+ if (downloadLinkClass) {
259
+ classes.push(downloadLinkClass)
260
+ }
261
+
262
+ return classes.join(' ')
263
+ }
247
264
 
248
265
  return (
249
266
  <ErrorBoundary component='DataTable'>
250
267
  {!config.table.showDownloadLinkBelow && <TableMediaControls />}
251
- <section
252
- id={tabbingId.replace('#', '')}
253
- className={`data-table-container ${viewport} ${
254
- !config.table.showDownloadLinkBelow ? 'download-link-above' : ''
255
- }`}
256
- aria-label={accessibilityLabel}
257
- >
268
+ <section id={tabbingId.replace('#', '')} className={getClassNames()} aria-label={accessibilityLabel}>
258
269
  <SkipTo skipId={skipId} skipMessage='Skip Data Table' />
259
270
  {config.table.collapsible !== false && (
260
271
  <ExpandCollapse
@@ -6,6 +6,7 @@ import DataTableEditorPanel from './components/DataTableEditorPanel'
6
6
  import Filters from '../Filters'
7
7
  import { TableConfig } from './types/TableConfig'
8
8
  import { filterVizData } from '../../helpers/filterVizData'
9
+ import { addValuesToFilters } from '../../helpers/addValuesToFilters'
9
10
 
10
11
  type StandAloneProps = {
11
12
  visualizationKey: string
@@ -28,9 +29,8 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
28
29
 
29
30
  useEffect(() => {
30
31
  // when using editor changes to filter should update the data
31
- setFilteredData(
32
- filterVizData(config.filters, config?.formattedData?.length > 0 ? config.formattedData : config.data)
33
- )
32
+ const filters = addValuesToFilters(config.filters, config.data)
33
+ setFilteredData(filterVizData(filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
34
34
  }, [config.filters])
35
35
 
36
36
  if (isEditor)
@@ -2,7 +2,7 @@ import Icon from '../../ui/Icon'
2
2
  import { fontSizes } from '../../../helpers/cove/fontSettings'
3
3
 
4
4
  const ExpandCollapse = ({ expanded, setExpanded, tableTitle, fontSize, viewport }) => {
5
- const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[fontSize]}px`
5
+ const titleFontSize = ['xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[fontSize]}px`
6
6
  return (
7
7
  <div
8
8
  style={{ fontSize: titleFontSize }}
@@ -19,3 +19,18 @@
19
19
  top: 0.5rem;
20
20
  }
21
21
  }
22
+
23
+ @media (max-width: 576px) {
24
+ .sort-icon {
25
+ :is(svg) {
26
+ width: 0.9rem;
27
+ height: 0.9rem;
28
+ }
29
+ .up {
30
+ bottom: 0.3rem;
31
+ }
32
+ .down {
33
+ top: 0.3rem;
34
+ }
35
+ }
36
+ }
@@ -18,7 +18,7 @@ const boxplotCellMatrix = ({ rows, config }): CellMatrix => {
18
18
  values: labels.values,
19
19
  columnCount: labels.count,
20
20
  columnSd: 'Standard Deviation',
21
- nonOutlierValues: 'Non Outliers',
21
+ columnNonOutliers: 'Non Outliers',
22
22
  columnLowerBounds: labels.lowerBounds,
23
23
  columnUpperBounds: labels.upperBounds
24
24
  }
@@ -36,8 +36,12 @@ const boxplotCellMatrix = ({ rows, config }): CellMatrix => {
36
36
  if (Number(rowid) === 5) return plot.columnCount
37
37
  if (Number(rowid) === 6) return plot.columnSd
38
38
  if (Number(rowid) === 7) return plot.columnMean
39
- if (Number(rowid) === 8) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
39
+ if (Number(rowid) === 8) return plot.columnIqr
40
40
  if (Number(rowid) === 9) return plot.values.length > 0 ? plot.values.toString() : '-'
41
+ if (Number(rowid) === 10) return plot.columnLowerBounds
42
+ if (Number(rowid) === 11) return plot.columnUpperBounds
43
+ if (Number(rowid) === 12) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
44
+ if (Number(rowid) === 13) return plot.columnNonOutliers.length > 0 ? plot.columnNonOutliers.toString() : '-'
41
45
  return <p>-</p>
42
46
  }
43
47
  // get list of data keys for each row
@@ -29,18 +29,18 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
29
29
 
30
30
  const rowObj = runtimeData[row]
31
31
  let cellValue // placeholder for formatting below
32
- let labelValue = rowObj[column] // just raw X axis string
32
+ const labelValue = rowObj[column] // just raw X axis string
33
33
  if (column === config.xAxis?.dataKey) {
34
- // not the prettiest, but helper functions work nicely here.
35
- cellValue =
36
- config.xAxis?.type === 'date'
37
- ? formatDate(
38
- config.table?.dateDisplayFormat || config.xAxis?.dateDisplayFormat,
39
- parseDate(config.xAxis?.dateParseFormat, labelValue)
40
- )
41
- : labelValue
42
- cellValue =
43
- config.xAxis?.type === 'continuous' ? formatNumber(runtimeData[row][column], 'bottom', false, config) : labelValue
34
+ const { type, dateDisplayFormat, dateParseFormat } = config.xAxis || {}
35
+ const dateFormat = config.table?.dateDisplayFormat || dateDisplayFormat
36
+
37
+ if (type === 'date' || type === 'date-time') {
38
+ cellValue = formatDate(dateFormat, parseDate(dateParseFormat, labelValue))
39
+ } else if (type === 'continuous') {
40
+ cellValue = formatNumber(runtimeData[row][column], 'bottom', false, config)
41
+ } else {
42
+ cellValue = labelValue
43
+ }
44
44
  } else {
45
45
  let resolvedAxis = 'left'
46
46
  let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
@@ -205,15 +205,18 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
205
205
  />
206
206
  )}
207
207
  {config?.visualizationType !== 'Sankey' && (
208
- <MultiSelect
209
- key={excludedColumns.join('') + 'excluded'}
210
- options={dataColumns.map(c => ({ label: c, value: c }))}
211
- selected={excludedColumns}
212
- fieldName='dataTable'
213
- label='Exclude Columns'
214
- section='columns'
215
- updateField={excludeColumns}
216
- />
208
+ <label>
209
+ <span className='edit-label column-heading mt-1'>Exclude Columns </span>
210
+ <MultiSelect
211
+ key={excludedColumns.join('') + 'excluded'}
212
+ options={dataColumns.map(c => ({ label: c, value: c }))}
213
+ selected={excludedColumns}
214
+ label={'Exclude Columns'}
215
+ fieldName='dataTable'
216
+ section='columns'
217
+ updateField={excludeColumns}
218
+ />
219
+ </label>
217
220
  )}
218
221
  <CheckBox
219
222
  value={config.table.collapsible}
@@ -323,18 +326,9 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
323
326
  updateField={updateField}
324
327
  />
325
328
  {config.table.pivot?.columnName && (
326
- <MultiSelect
327
- key={config.table.pivot?.columnName}
328
- options={groupPivotColumns
329
- .filter(col => col !== config.table.pivot?.columnName && col !== config.table.groupBy)
330
- .map(c => ({ label: c, value: c }))}
331
- selected={config.table.pivot?.valueColumns}
332
- label='Pivot Value Column(s) '
333
- section='table'
334
- subsection='pivot'
335
- fieldName='valueColumns'
336
- updateField={updateField}
337
- tooltip={
329
+ <label>
330
+ <span className='edit-label column-heading mt-1'>
331
+ Pivot Value Column(s)
338
332
  <Tooltip style={{ textTransform: 'none' }}>
339
333
  <Tooltip.Target>
340
334
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -343,8 +337,20 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
343
337
  <p>The column(s) whos values will be pivoted under the column selected as the Filter.</p>
344
338
  </Tooltip.Content>
345
339
  </Tooltip>
346
- }
347
- />
340
+ </span>
341
+ <MultiSelect
342
+ key={config.table.pivot?.columnName}
343
+ options={groupPivotColumns
344
+ .filter(col => col !== config.table.pivot?.columnName && col !== config.table.groupBy)
345
+ .map(c => ({ label: c, value: c }))}
346
+ selected={config.table.pivot?.valueColumns}
347
+ label='Pivot Value Column(s) '
348
+ section='table'
349
+ subsection='pivot'
350
+ fieldName='valueColumns'
351
+ updateField={updateField}
352
+ />
353
+ </label>
348
354
  )}
349
355
  </>
350
356
  )
@@ -67,7 +67,12 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
67
67
 
68
68
  const addNewFilter = () => {
69
69
  const filters = config.filters ? [...config.filters] : []
70
- const newVizFilter: VizFilter = { values: [], filterStyle: 'dropdown', id: Date.now() } as VizFilter
70
+ const newVizFilter: VizFilter = {
71
+ values: [],
72
+ filterStyle: 'dropdown',
73
+ id: Date.now(),
74
+ showDropdown: true
75
+ } as VizFilter
71
76
  filters.push(newVizFilter)
72
77
  updateField(null, null, 'filters', filters)
73
78
  }
@@ -125,6 +130,13 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
125
130
  </Tooltip>
126
131
  }
127
132
  />
133
+ <TextField
134
+ type='textarea'
135
+ label='Filter intro text'
136
+ value={config.filterIntro}
137
+ updateField={updateField}
138
+ fieldName='filterIntro'
139
+ />
128
140
  <br />
129
141
  <ul className='filters-list'>
130
142
  {/* Whether filters should apply onChange or Apply Button */}
@@ -229,6 +241,17 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
229
241
  handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
230
242
  />
231
243
  )}
244
+ {filter.order === 'column' && (
245
+ <Select
246
+ value={filter.orderColumn}
247
+ fieldName='orderColumn'
248
+ label='Order Column'
249
+ updateField={(_section, _subSection, _field, value) =>
250
+ updateFilterProp('orderColumn', filterIndex, value)
251
+ }
252
+ options={dataColumns}
253
+ />
254
+ )}
232
255
  </>
233
256
  ) : (
234
257
  <NestedDropdownEditor
@@ -262,6 +285,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
262
285
  updateFilterProp('parents', filterIndex, value)
263
286
  }}
264
287
  options={getParentFilterOptions(filterIndex)}
288
+ selected={config.filters[filterIndex].parents}
265
289
  />
266
290
  </label>
267
291
  </FieldSetWrapper>
@@ -41,18 +41,25 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
41
41
  {
42
42
  label: 'Custom',
43
43
  value: 'cust'
44
- }
44
+ },
45
+ { label: 'Order By Data Column', value: 'column' }
45
46
  ]
46
47
 
47
- const hasStandardFilterBehavior = ['chart', 'table']
48
-
49
48
  export const useFilters = props => {
50
49
  const [showApplyButton, setShowApplyButton] = useState(false)
51
50
 
52
51
  // Desconstructing: notice, adding more descriptive visualizationConfig name over config
53
52
  // visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
54
- const { config: visualizationConfig, setConfig, filteredData, setFilteredData, excludedData, getUniqueValues } = props
55
- const { type, data } = visualizationConfig
53
+ const {
54
+ config: visualizationConfig,
55
+ setConfig,
56
+ filteredData,
57
+ setFilteredData,
58
+ excludedData,
59
+ getUniqueValues,
60
+ standaloneMap
61
+ } = props
62
+ const { data } = visualizationConfig
56
63
 
57
64
  /**
58
65
  * Re-orders a filter based on two indices and updates the runtime filters array and filters state
@@ -72,9 +79,7 @@ export const useFilters = props => {
72
79
  const [movedItem] = updatedValues.splice(idx1, 1)
73
80
  updatedValues.splice(idx2, 0, movedItem)
74
81
 
75
- const filtersCopy = hasStandardFilterBehavior.includes(visualizationConfig.type)
76
- ? [...visualizationConfig.filters]
77
- : [...filteredData]
82
+ const filtersCopy = !standaloneMap ? [...visualizationConfig.filters] : [...filteredData]
78
83
  const filterItem = { ...filtersCopy[filterIndex] }
79
84
 
80
85
  // Overwrite filterItem.values since thats what we map through in the editor panel
@@ -86,7 +91,7 @@ export const useFilters = props => {
86
91
  // Update the filters
87
92
  filtersCopy[filterIndex] = filterItem
88
93
 
89
- if (visualizationConfig.type === 'map') {
94
+ if (standaloneMap) {
90
95
  setFilteredData(filtersCopy)
91
96
  }
92
97
 
@@ -94,7 +99,7 @@ export const useFilters = props => {
94
99
  }
95
100
 
96
101
  const changeFilterActive = (index, value) => {
97
- let newFilters = visualizationConfig.type === 'map' ? [...filteredData] : [...visualizationConfig.filters]
102
+ let newFilters = standaloneMap ? [...filteredData] : [...visualizationConfig.filters]
98
103
 
99
104
  if (visualizationConfig.filterBehavior === 'Apply Button') {
100
105
  newFilters[index].queuedActive = value
@@ -120,7 +125,7 @@ export const useFilters = props => {
120
125
  queryParams[newFilter?.subGrouping?.setByQueryParameter] = newFilter.subGrouping.active
121
126
  updateQueryString(queryParams)
122
127
  }
123
- setFilteredData(newFilters[index])
128
+ setFilteredData(newFilters)
124
129
  }
125
130
 
126
131
  if (!visualizationConfig.dynamicSeries) {
@@ -132,15 +137,12 @@ export const useFilters = props => {
132
137
  }
133
138
 
134
139
  // Used for setting active filter, fromHash breaks the filteredData functionality.
135
- if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
140
+ if (standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
136
141
  setFilteredData(newFilters)
137
142
  }
138
143
 
139
144
  // If we're on a chart and not using the apply button
140
- if (
141
- hasStandardFilterBehavior.includes(visualizationConfig.type) &&
142
- visualizationConfig.filterBehavior === 'Filter Change'
143
- ) {
145
+ if (!standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
144
146
  const newFilteredData = filterVizData(newFilters, excludedData)
145
147
  setFilteredData(newFilteredData)
146
148
 
@@ -204,11 +206,9 @@ export const useFilters = props => {
204
206
 
205
207
  setConfig({ ...visualizationConfig, filters: newFilters })
206
208
 
207
- if (type === 'map') {
209
+ if (standaloneMap) {
208
210
  setFilteredData(newFilters, excludedData)
209
- }
210
-
211
- if (hasStandardFilterBehavior.includes(visualizationConfig.type)) {
211
+ } else {
212
212
  setFilteredData(filterVizData(newFilters, excludedData))
213
213
  }
214
214
 
@@ -240,7 +240,7 @@ export const useFilters = props => {
240
240
 
241
241
  setConfig({ ...visualizationConfig, filters: newFilters })
242
242
 
243
- if (type === 'map') {
243
+ if (standaloneMap) {
244
244
  setFilteredData(newFilters, excludedData)
245
245
  } else {
246
246
  setFilteredData(filterVizData(newFilters, excludedData))
@@ -249,9 +249,7 @@ export const useFilters = props => {
249
249
 
250
250
  const filterConstants = {
251
251
  buttonText: 'Apply Filters',
252
- resetText: 'Reset All',
253
- introText: `Make a selection from the filters to change the visualization information.`,
254
- applyText: 'Select the apply button to update the visualization information.'
252
+ resetText: 'Reset All'
255
253
  }
256
254
 
257
255
  // prettier-ignore
@@ -276,13 +274,12 @@ type FilterProps = {
276
274
  setFilteredData: Function
277
275
  // updating function for setting fitlerBehavior
278
276
  setConfig: Function
279
- // exclusions
280
- exclusions: any[]
277
+ standaloneMap?: boolean
281
278
  }
282
279
 
283
280
  const Filters = (props: FilterProps) => {
284
- const { config: visualizationConfig, filteredData, dimensions } = props
285
- const { filters, type, general, theme, filterBehavior } = visualizationConfig
281
+ const { config: visualizationConfig, filteredData, dimensions, standaloneMap } = props
282
+ const { filters, general, theme, filterBehavior } = visualizationConfig
286
283
  const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
287
284
  const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
288
285
  const id = useId()
@@ -365,7 +362,7 @@ const Filters = (props: FilterProps) => {
365
362
 
366
363
  const vizFiltersWithValues = useMemo(() => {
367
364
  // Here charts is using config.filters where maps is using a runtime value
368
- let vizfilters = type === 'map' ? filteredData : filters
365
+ let vizfilters = standaloneMap ? filteredData : filters
369
366
  if (!vizfilters) return []
370
367
  if (vizfilters.fromHash) delete vizfilters.fromHash // support for Maps config
371
368
  return addValuesToFilters(vizfilters as VizFilter[], visualizationConfig.data)
@@ -448,7 +445,7 @@ const Filters = (props: FilterProps) => {
448
445
  <div className={classList.join(' ')} key={outerIndex}>
449
446
  <>
450
447
  {label && (
451
- <label className='text-capitalize font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
448
+ <label className='font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
452
449
  {label}
453
450
  </label>
454
451
  )}
@@ -494,7 +491,7 @@ const Filters = (props: FilterProps) => {
494
491
  const getClasses = () => {
495
492
  const { visualizationType, legend } = visualizationConfig || {}
496
493
  const baseClass = 'filters-section'
497
- const conditionalClass = type === 'map' ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
494
+ const conditionalClass = standaloneMap ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
498
495
  const legendClass = legend && !legend.hide && legend.position === 'top' ? 'mb-0' : null
499
496
 
500
497
  return [baseClass, conditionalClass, legendClass].filter(Boolean)
@@ -502,10 +499,9 @@ const Filters = (props: FilterProps) => {
502
499
 
503
500
  return (
504
501
  <section className={getClasses().join(' ')}>
505
- <p className='filters-section__intro-text'>
506
- {filters?.some(filter => filter.active && filter.columnName) ? filterConstants.introText : ''}{' '}
507
- {visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
508
- </p>
502
+ {visualizationConfig.filterIntro && (
503
+ <p className='filters-section__intro-text'>{visualizationConfig.filterIntro}</p>
504
+ )}
509
505
  <div className='d-flex flex-wrap w-100 filters-section__wrapper'>
510
506
  {' '}
511
507
  <>
@@ -7,6 +7,11 @@ export const handleSorting = singleFilter => {
7
7
  return singleFilter
8
8
  }
9
9
 
10
+ if (singleFilter.order === 'column') {
11
+ // sorting is done in the generateValuesForFilter function
12
+ return singleFilter
13
+ }
14
+
10
15
  const sort = (a, b) => {
11
16
  const asc = singleFilter.order !== 'desc'
12
17
  return String(asc ? a : b).localeCompare(String(asc ? b : a), 'en', { numeric: true })
@@ -15,20 +15,33 @@ type StandAloneProps = {
15
15
  viewport?: ViewPort
16
16
  }
17
17
 
18
- const FootnotesStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, config, viewport, isEditor, updateConfig }) => {
18
+ const FootnotesStandAlone: React.FC<StandAloneProps> = ({
19
+ visualizationKey,
20
+ config,
21
+ viewport,
22
+ isEditor,
23
+ updateConfig
24
+ }) => {
19
25
  const updateField = updateFieldFactory<Footnote[]>(config, updateConfig)
20
26
  if (isEditor)
21
27
  return (
22
- <EditorWrapper component={FootnotesStandAlone} visualizationKey={visualizationKey} visualizationConfig={config} updateConfig={updateConfig} type={'Footnotes'} viewport={viewport}>
28
+ <EditorWrapper
29
+ component={FootnotesStandAlone}
30
+ visualizationKey={visualizationKey}
31
+ visualizationConfig={config}
32
+ updateConfig={updateConfig}
33
+ type={'Footnotes'}
34
+ viewport={viewport}
35
+ >
23
36
  <FootnotesEditor key={visualizationKey} config={config} updateField={updateField} />
24
37
  </EditorWrapper>
25
38
  )
26
39
 
27
40
  // get the api footnotes from the config
28
41
  const apiFootnotes = useMemo(() => {
29
- if (config.dataKey && config.dynamicFootnotes) {
42
+ const configData = config.formattedData || config.data
43
+ if (configData && config.dataKey && config.dynamicFootnotes) {
30
44
  const { symbolColumn, textColumn, orderColumn } = config.dynamicFootnotes
31
- const configData = config.formattedData || config.data
32
45
  const _data = configData.map(row => _.pick(row, [symbolColumn, textColumn, orderColumn]))
33
46
  _data.sort((a, b) => a[orderColumn] - b[orderColumn])
34
47
  return _data.map(row => ({ symbol: row[symbolColumn], text: row[textColumn] }))
@@ -1,6 +1,6 @@
1
1
  .cdc-open-viz-module {
2
2
  .cdc-chart-inner-container .cove-component__content {
3
- padding: 25px 0px !important;
3
+ padding: 25px 15px 25px 0 !important;
4
4
  }
5
5
  &.isEditor {
6
6
  overflow: auto;
@@ -38,7 +38,7 @@ const LegendGradient = ({
38
38
 
39
39
  const numTicks = colors?.length
40
40
 
41
- const longestLabel = labels && labels.length > 0 ? labels.reduce((a, b) => (a.length > b.length ? a : b)) : ''
41
+ const longestLabel = (labels || []).reduce((a: string, b) => (a.length > String(b).length ? a : b), '')
42
42
  const boxHeight = 20
43
43
  let height = 50
44
44
 
@@ -1,17 +1,22 @@
1
1
  import React, { useEffect, useRef } from 'react'
2
2
  import './loader.styles.css'
3
3
 
4
+ // these coorespond to bootstrap classes
5
+ // https://getbootstrap.com/docs/4.2/components/spinners/
6
+ type SpinnerType = 'text-primary' | 'text-secondary'
7
+
4
8
  type LoaderProps = {
5
9
  fullScreen?: boolean
10
+ spinnerType?: SpinnerType
6
11
  }
7
12
 
8
- const Spinner = () => (
9
- <div className='spinner-border text-primary' role='status'>
13
+ const Spinner = ({ spinnerType }: { spinnerType: SpinnerType }) => (
14
+ <div className={`spinner-border ${spinnerType}`} role='status'>
10
15
  <span className='sr-only'>Loading...</span>
11
16
  </div>
12
17
  )
13
18
 
14
- const Loader: React.FC<LoaderProps> = ({ fullScreen = false }) => {
19
+ const Loader: React.FC<LoaderProps> = ({ fullScreen = false, spinnerType }) => {
15
20
  const backgroundRef = useRef(null)
16
21
 
17
22
  useEffect(() => {
@@ -23,10 +28,10 @@ const Loader: React.FC<LoaderProps> = ({ fullScreen = false }) => {
23
28
 
24
29
  return fullScreen ? (
25
30
  <div ref={backgroundRef} className='cove-loader fullscreen'>
26
- <Spinner />
31
+ <Spinner spinnerType={spinnerType || 'text-primary'} />
27
32
  </div>
28
33
  ) : (
29
- <Spinner />
34
+ <Spinner spinnerType={spinnerType || 'text-primary'} />
30
35
  )
31
36
  }
32
37