@cdc/core 4.25.3-6 → 4.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/components/AdvancedEditor/AdvancedEditor.tsx +11 -9
  2. package/components/DataTable/DataTable.tsx +34 -20
  3. package/components/DataTable/components/ChartHeader.tsx +1 -1
  4. package/components/DataTable/helpers/getChartCellValue.ts +11 -5
  5. package/components/DataTable/helpers/getDataSeriesColumns.ts +7 -3
  6. package/components/DataTable/helpers/mapCellMatrix.tsx +64 -33
  7. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +80 -0
  8. package/components/EditorPanel/DataTableEditor.tsx +28 -18
  9. package/components/EditorPanel/Inputs.tsx +2 -1
  10. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +23 -0
  11. package/components/Filters/Filters.tsx +20 -8
  12. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  13. package/components/MediaControls.jsx +14 -7
  14. package/components/elements/Button.jsx +4 -2
  15. package/dist/cove-main.css +98 -151
  16. package/dist/cove-main.css.map +1 -1
  17. package/helpers/DataTransform.ts +2 -2
  18. package/helpers/addValuesToFilters.ts +1 -1
  19. package/helpers/coveUpdateWorker.ts +12 -7
  20. package/helpers/formatConfigBeforeSave.ts +30 -8
  21. package/helpers/isRightAlignedTableValue.js +5 -1
  22. package/helpers/isSolr.ts +13 -0
  23. package/helpers/labelHash.ts +21 -0
  24. package/helpers/pivotData.ts +14 -7
  25. package/helpers/tests/formatConfigBeforeSave.test.ts +68 -0
  26. package/helpers/tests/pivotData.test.ts +23 -19
  27. package/helpers/ver/4.25.3.ts +20 -0
  28. package/helpers/ver/tests/versionNeedsUpdate.test.ts +28 -0
  29. package/package.json +2 -2
  30. package/styles/_global-variables.scss +2 -1
  31. package/styles/_global.scss +18 -9
  32. package/styles/base.scss +42 -0
  33. package/styles/filters.scss +5 -11
  34. package/styles/v2/components/button.scss +48 -12
  35. package/styles/v2/themes/_color-definitions.scss +1 -4
  36. package/types/General.ts +0 -1
  37. package/types/Table.ts +2 -0
  38. package/helpers/isSolr.js +0 -13
@@ -35,20 +35,22 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
35
35
  }, [config])
36
36
 
37
37
  const typeLookup = {
38
- chart: ['Charts', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
39
- dashboard: ['Dashboard', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
40
- map: ['Maps', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html', <MapIcon />],
41
- 'markup-include': [
42
- 'Markup Include',
43
- 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html',
44
- <MarkupIncludeIcon />
45
- ]
38
+ chart: ['Charts', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
39
+ dashboard: ['Dashboard', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
40
+ map: ['Maps', 'https://www.cdc.gov/cove/index.html', <MapIcon />],
41
+ 'markup-include': ['Markup Include', 'https://www.cdc.gov/cove/index.html', <MarkupIncludeIcon />]
46
42
  }
47
43
 
48
44
  if (!config.type) return <></>
49
45
  return (
50
46
  <>
51
- <a href={typeLookup[config.type][1]} target='_blank' rel='noopener noreferrer' className='guidance-link'>
47
+ <a
48
+ href={typeLookup[config.type][1]}
49
+ target='_blank'
50
+ rel='noopener noreferrer'
51
+ className='guidance-link'
52
+ style={{ cursor: 'pointer !important' }}
53
+ >
52
54
  {typeLookup[config.type][2]}
53
55
  <div>
54
56
  <span className='heading-3'>Get Help with {typeLookup[config.type][0]}</span>
@@ -11,7 +11,7 @@ import BoxplotHeader from './components/BoxplotHeader'
11
11
  import MapHeader from './components/MapHeader'
12
12
  import SkipTo from '../elements/SkipTo'
13
13
  import ExpandCollapse from './components/ExpandCollapse'
14
- import mapCellMatrix from './helpers/mapCellMatrix'
14
+ import mapCellMatrix, { getMapRowData } from './helpers/mapCellMatrix'
15
15
  import Table from '../Table'
16
16
  import chartCellMatrix from './helpers/chartCellMatrix'
17
17
  import regionCellMatrix from './helpers/regionCellMatrix'
@@ -24,6 +24,7 @@ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
24
24
  import isRightAlignedTableValue from '@cdc/core/helpers/isRightAlignedTableValue'
25
25
  import './data-table.css'
26
26
  import _ from 'lodash'
27
+ import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
27
28
 
28
29
  export type DataTableProps = {
29
30
  applyLegendToRow?: Function
@@ -32,9 +33,9 @@ export type DataTableProps = {
32
33
  config: TableConfig
33
34
  dataConfig?: Object
34
35
  defaultSortBy?: string
35
- displayGeoName?: Function
36
+ displayGeoName?: (row: string) => string
36
37
  expandDataTable: boolean
37
- formatLegendLocation?: Function
38
+ formatLegendLocation?: (row: string) => string
38
39
  groupBy?: string
39
40
  headerColor?: string
40
41
  imageRef?: string
@@ -45,8 +46,7 @@ export type DataTableProps = {
45
46
  outerContainerRef?: Function
46
47
  rawData: Object[]
47
48
  runtimeData: Object[] & Record<string, Object>
48
- setFilteredCountryCode?: Function // used for Maps only
49
- showDownloadButton?: boolean
49
+ setFilteredCountryCode?: string // used for Maps only
50
50
  tabbingId: string
51
51
  tableTitle: string
52
52
  viewport: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
@@ -61,6 +61,7 @@ const DataTable = (props: DataTableProps) => {
61
61
  config,
62
62
  dataConfig,
63
63
  defaultSortBy,
64
+ displayGeoName,
64
65
  tableTitle,
65
66
  vizTitle,
66
67
  rawData,
@@ -75,19 +76,15 @@ const DataTable = (props: DataTableProps) => {
75
76
  } = props
76
77
  const runtimeData = useMemo(() => {
77
78
  const data = removeNullColumns(parentRuntimeData)
78
- if (config.table.pivot) {
79
+ const { columnName, valueColumns } = config.table.pivot || {}
80
+ if (columnName && valueColumns) {
79
81
  const excludeColumns = Object.values(config.columns || {})
80
82
  .filter(column => column.dataTable === false)
81
83
  .map(col => col.name)
82
- const { columnName, valueColumns } = config.table.pivot
83
- if (columnName && valueColumns) {
84
- // remove excluded columns so that they aren't included in the pivot calculation
85
- const _data = data.map(row => _.omit(row, excludeColumns))
86
- return pivotData(_data, columnName, valueColumns)
87
- }
84
+ return pivotData(data, columnName, valueColumns, excludeColumns)
88
85
  }
89
86
  return data
90
- }, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumn])
87
+ }, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumns])
91
88
 
92
89
  const [expanded, setExpanded] = useState(expandDataTable)
93
90
  const [sortBy, setSortBy] = useState<any>({
@@ -208,13 +205,10 @@ const DataTable = (props: DataTableProps) => {
208
205
  [config.runtime?.seriesKeys]) // eslint-disable-line
209
206
 
210
207
  const hasNoData = runtimeData.length === 0
211
-
212
208
  const getClassNames = (): string => {
213
209
  const classes = ['data-table-container']
214
210
 
215
- const hasDownloadLinkAbove =
216
- (config.table.download || config.general?.showDownloadButton) && !config.table.showDownloadLinkBelow
217
-
211
+ const hasDownloadLinkAbove = config.table.download && !config.table.showDownloadLinkBelow
218
212
  const isStandaloneTable = config.type === 'table'
219
213
 
220
214
  if (!hasDownloadLinkAbove && !isStandaloneTable) {
@@ -233,12 +227,32 @@ const DataTable = (props: DataTableProps) => {
233
227
 
234
228
  if (config.visualizationType !== 'Box Plot') {
235
229
  const getDownloadData = () => {
230
+ const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
231
+ const sharedFilterColumns = config.table?.sharedFilterColumns || []
232
+ const vizFilterColumns = (config.filters || []).map(filter => filter.columnName)
233
+ const filterColumns = [...sharedFilterColumns, ...vizFilterColumns]
234
+ const visibleData =
235
+ config.type === 'map'
236
+ ? getMapRowData(
237
+ rows,
238
+ columns,
239
+ config,
240
+ formatLegendLocation,
241
+ runtimeData as Record<string, Object>,
242
+ displayGeoName,
243
+ filterColumns
244
+ )
245
+ : runtimeData.map(d => {
246
+ return _.pick(d, [...filterColumns, ...dataSeriesColumns])
247
+ })
248
+ const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
249
+
236
250
  // only use fullGeoName on County maps and no other
237
251
  if (config.general?.geoType === 'us-county') {
238
252
  // Add column for full Geo name along with State
239
- return rawData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row }))
253
+ return csvData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row }))
240
254
  } else {
241
- return rawData
255
+ return csvData
242
256
  }
243
257
  }
244
258
 
@@ -276,7 +290,7 @@ const DataTable = (props: DataTableProps) => {
276
290
  : {}
277
291
 
278
292
  const TableMediaControls = ({ belowTable }) => {
279
- const hasDownloadLink = config.table.download || config.general?.showDownloadButton
293
+ const hasDownloadLink = config.table.download
280
294
  return (
281
295
  <MediaControls.Section classes={getMediaControlsClasses(belowTable, hasDownloadLink)}>
282
296
  <MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
@@ -54,7 +54,7 @@ const ChartHeader = ({
54
54
  }
55
55
 
56
56
  const ColumnHeadingText = ({ column, text, config }) => {
57
- if (text === 'pivotColumn') return ''
57
+ if (text === '_pivotedFrom') return ''
58
58
  let notApplicableText = 'Not Applicable'
59
59
  if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
60
60
  if (text === '__series__' && !config.table.indexLabel)
@@ -2,16 +2,22 @@ import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
2
2
  import { formatNumber } from '../../../helpers/cove/number'
3
3
  import { TableConfig } from '../types/TableConfig'
4
4
 
5
+ const isPivotColumn = (columnName, config) => {
6
+ const tableHasPivotColumnConfigured = config.table.pivot?.valueColumns?.length
7
+ const originalColumnNames = Object.keys(config.data?.[0] || {})
8
+ const columnIsPivot = originalColumnNames.length && !originalColumnNames.includes(columnName)
9
+ return tableHasPivotColumnConfigured && columnIsPivot
10
+ }
11
+
5
12
  // if its additional column, return formatting params
6
- const isAdditionalColumn = (column, config) => {
7
- let inthere = false
13
+ const isAdditionalColumn = (column: string, config, rowData) => {
14
+ const columnName = isPivotColumn(column, config) ? rowData._pivotedFrom : column
8
15
  let formattingParams = {}
9
16
  const { columns } = config
10
17
  if (columns) {
11
18
  Object.keys(columns).forEach(keycol => {
12
19
  const col = columns[keycol]
13
- if (col.name === column) {
14
- inthere = true
20
+ if (col.name === columnName) {
15
21
  formattingParams = {
16
22
  addColPrefix: col.prefix,
17
23
  addColSuffix: col.suffix,
@@ -54,7 +60,7 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
54
60
  if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
55
61
  })
56
62
 
57
- let addColParams = isAdditionalColumn(column, config)
63
+ let addColParams = isAdditionalColumn(column, config, rowObj)
58
64
  if (addColParams) {
59
65
  cellValue = config.dataFormat
60
66
  ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
@@ -57,9 +57,13 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
57
57
  })
58
58
 
59
59
  tmpSeriesColumns.sort((a, b) => {
60
- if (a === 'pivotColumn') return -1
61
- if (b === 'pivotColumn') return 1
60
+ if (a === '_pivotedFrom') return -1
61
+ if (b === '_pivotedFrom') return 1
62
62
  return columnOrderingHash[a] - columnOrderingHash[b]
63
63
  })
64
- return tmpSeriesColumns
64
+
65
+ return tmpSeriesColumns.filter(colName => {
66
+ // remove metadata added by pivotData function
67
+ return colName !== '_pivotedFrom'
68
+ })
65
69
  }
@@ -3,11 +3,70 @@ import CellAnchor from '../components/CellAnchor'
3
3
  import { DataTableProps } from '../DataTable'
4
4
  import { ReactNode } from 'react'
5
5
  import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
6
+ import _ from 'lodash'
6
7
 
7
8
  type MapRowsProps = DataTableProps & {
8
9
  rows: string[]
9
10
  }
10
11
 
12
+ const getGeoLabel = (config, row, formatLegendLocation, displayGeoName) => {
13
+ const { geoType, type } = config.general
14
+ let labelValue
15
+ if (!['single-state', 'us-county'].includes(geoType) || type === 'us-geocode') {
16
+ labelValue = displayGeoName(row)
17
+ labelValue = String(labelValue).startsWith('region') ? _.capitalize(labelValue) : labelValue
18
+ } else {
19
+ labelValue = formatLegendLocation(row)
20
+ }
21
+ return labelValue
22
+ }
23
+
24
+ const getDataValue = (config, rowData, column) => {
25
+ // check for special classes
26
+ let specialValFound = ''
27
+ let columnName = config.columns[column]?.name
28
+ const { specialClasses } = config.legend
29
+ if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
30
+ specialClasses.forEach(specialClass => {
31
+ if (specialClass.key === columnName) {
32
+ if (String(rowData[specialClass.key]) === specialClass.value) {
33
+ specialValFound = specialClass.label
34
+ }
35
+ }
36
+ })
37
+ }
38
+ return specialValFound || rowData[columnName]
39
+ }
40
+
41
+ export const getMapRowData = (
42
+ rows: string[],
43
+ columns: Record<string, { label; name?; dataTable }>,
44
+ config: Record<string, Object>,
45
+ formatLegendLocation: (row: string) => string,
46
+ runtimeData: Record<string, Object>,
47
+ displayGeoName: (row: string) => string,
48
+ filterColumns: string[]
49
+ ) => {
50
+ return rows.map((row: string) => {
51
+ const dataRow = {}
52
+ ;[
53
+ ...filterColumns,
54
+ ...Object.keys(columns).filter(column => columns[column].dataTable === true && columns[column].name)
55
+ ].map(column => {
56
+ const label = columns[column]?.label || columns[column]?.name || column
57
+ if (column === 'geo') {
58
+ dataRow[label] = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
59
+ } else if (filterColumns.includes(column)) {
60
+ dataRow[label] = runtimeData[row][column]
61
+ } else {
62
+ const dataValue = getDataValue(config, runtimeData[row], column)
63
+ dataRow[label] = displayDataAsText(dataValue, column, config)
64
+ }
65
+ })
66
+ return dataRow
67
+ })
68
+ }
69
+
11
70
  const mapCellArray = ({
12
71
  rows,
13
72
  columns,
@@ -23,30 +82,15 @@ const mapCellArray = ({
23
82
  Object.keys(columns)
24
83
  .filter(column => columns[column].dataTable === true && columns[column].name)
25
84
  .map(column => {
26
- let cellValue
27
-
28
85
  if (column === 'geo') {
29
86
  const rowObj = runtimeData[row]
30
87
  const legendColor = applyLegendToRow(rowObj)
31
-
32
- let labelValue
88
+ const labelValue = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
33
89
  const mapZoomHandler =
34
90
  config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world'
35
91
  ? () => setFilteredCountryCode(row)
36
92
  : undefined
37
- if (
38
- (config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') ||
39
- config.general.type === 'us-geocode'
40
- ) {
41
- const capitalize = str => {
42
- return str.charAt(0).toUpperCase() + str.slice(1)
43
- }
44
- labelValue = displayGeoName(row)
45
- labelValue = String(labelValue).startsWith('region') ? capitalize(labelValue) : labelValue
46
- } else {
47
- labelValue = formatLegendLocation(row)
48
- }
49
- cellValue = (
93
+ return (
50
94
  <div className='col-12'>
51
95
  <LegendShape fill={legendColor[0]} />
52
96
  <CellAnchor
@@ -59,23 +103,10 @@ const mapCellArray = ({
59
103
  </div>
60
104
  )
61
105
  } else {
62
- // check for special classes
63
- let specialValFound = ''
64
- let columnName = config.columns[column].name
65
- const { specialClasses } = config.legend
66
- if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
67
- specialClasses.forEach(specialClass => {
68
- if (specialClass.key === columnName) {
69
- if (String(runtimeData[row][specialClass.key]) === specialClass.value) {
70
- specialValFound = specialClass.label
71
- }
72
- }
73
- })
74
- }
75
- cellValue = displayDataAsText(specialValFound || runtimeData[row][columnName], column, config)
106
+ const rowData = runtimeData[row]
107
+ const dataValue = getDataValue(config, rowData, column)
108
+ return displayDataAsText(dataValue, column, config)
76
109
  }
77
-
78
- return cellValue
79
110
  })
80
111
  )
81
112
  }
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getMapRowData } from '../mapCellMatrix'
3
+
4
+ describe('getMapRowData', () => {
5
+ const columns = {
6
+ geo: { dataTable: true, name: 'geo', label: 'Geo' },
7
+ column1: { dataTable: true, name: 'column1', label: 'Column 1' },
8
+ column2: { dataTable: true, name: 'column2', label: 'Column 2' }
9
+ }
10
+ const config = {
11
+ general: { geoType: 'us-state', type: 'map' },
12
+ columns: {
13
+ geo: { name: 'geo' },
14
+ column1: { name: 'column1' },
15
+ column2: { name: 'column2' }
16
+ },
17
+ legend: { specialClasses: [] }
18
+ }
19
+ const formatLegendLocation = row => row
20
+ const rows = ['row2', 'row1']
21
+ const runtimeData = {
22
+ row1: { geo: 'region1', column1: 'data1', column2: 'data2', somethingElse: 'data5' },
23
+ row2: { geo: 'region2', column1: 'data3', column2: 'data4', somethingElse: 'data6' }
24
+ }
25
+ const displayGeoName = row => `displayGeoName -> ${row}`
26
+ const sharedFilterColumns = ['somethingElse']
27
+
28
+ it('should map rows to data rows correctly', () => {
29
+ const result = getMapRowData(
30
+ rows,
31
+ columns,
32
+ config,
33
+ formatLegendLocation,
34
+ runtimeData,
35
+ displayGeoName,
36
+ sharedFilterColumns
37
+ )
38
+
39
+ expect(result).toEqual([
40
+ {
41
+ Geo: 'displayGeoName -> row2',
42
+ 'Column 1': 'data3',
43
+ 'Column 2': 'data4',
44
+ somethingElse: 'data6'
45
+ },
46
+ {
47
+ Geo: 'displayGeoName -> row1',
48
+ 'Column 1': 'data1',
49
+ 'Column 2': 'data2',
50
+ somethingElse: 'data5'
51
+ }
52
+ ])
53
+
54
+ config.general.geoType = 'us-county'
55
+ const result2 = getMapRowData(
56
+ rows,
57
+ columns,
58
+ config,
59
+ formatLegendLocation,
60
+ runtimeData,
61
+ displayGeoName,
62
+ sharedFilterColumns
63
+ )
64
+
65
+ expect(result2).toEqual([
66
+ {
67
+ Geo: 'row2',
68
+ 'Column 1': 'data3',
69
+ 'Column 2': 'data4',
70
+ somethingElse: 'data6'
71
+ },
72
+ {
73
+ Geo: 'row1',
74
+ 'Column 1': 'data1',
75
+ 'Column 2': 'data2',
76
+ somethingElse: 'data5'
77
+ }
78
+ ])
79
+ })
80
+ })
@@ -83,7 +83,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
83
83
  </Tooltip>
84
84
  }
85
85
  />
86
- {config.type !== 'table' ? (
86
+ {config.type !== 'table' && (
87
87
  <CheckBox
88
88
  value={config.table.show}
89
89
  fieldName='show'
@@ -108,15 +108,6 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
108
108
  </Tooltip>
109
109
  }
110
110
  />
111
- ) : (
112
- <CheckBox
113
- value={config.general?.showDownloadButton}
114
- fieldName='showDownloadButton'
115
- label='Show Download CSV link'
116
- section='general'
117
- updateField={updateField}
118
- className='column-heading'
119
- />
120
111
  )}
121
112
 
122
113
  {config.visualizationType !== 'Box Plot' && config.type !== 'table' && (
@@ -234,6 +225,33 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
234
225
  updateField={updateField}
235
226
  />
236
227
  )}
228
+ <CheckBox
229
+ value={config.table.download}
230
+ fieldName='download'
231
+ label='Show Download CSV Link'
232
+ section='table'
233
+ updateField={updateField}
234
+ />
235
+ {config.table.download && (
236
+ <>
237
+ <CheckBox
238
+ value={config.table.showDownloadLinkBelow}
239
+ fieldName='showDownloadLinkBelow'
240
+ className='ms-4'
241
+ label='Show Link Below Table'
242
+ section='table'
243
+ updateField={updateField}
244
+ />
245
+ <CheckBox
246
+ value={config.table.downloadVisibleDataOnly}
247
+ fieldName='downloadVisibleDataOnly'
248
+ className='ms-4'
249
+ label='Download only visible data'
250
+ section='table'
251
+ updateField={updateField}
252
+ />
253
+ </>
254
+ )}
237
255
  {isDashboard && config.type !== 'table' && (
238
256
  <CheckBox
239
257
  value={config.table.showDataTableLink}
@@ -261,14 +279,6 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
261
279
  updateField={updateField}
262
280
  />
263
281
  )}
264
-
265
- <CheckBox
266
- value={config.table.showDownloadLinkBelow}
267
- fieldName='showDownloadLinkBelow'
268
- label='Show Download Link Below Table'
269
- section='table'
270
- updateField={updateField}
271
- />
272
282
  <label>
273
283
  <span className='edit-label column-heading'>Table Cell Min Width</span>
274
284
  <input
@@ -1,5 +1,6 @@
1
1
  import { memo, useEffect, useState } from 'react'
2
2
  import { useDebounce } from 'use-debounce'
3
+ import { DROPDOWN_STYLES } from '../Filters/Filters'
3
4
 
4
5
  export type Input = {
5
6
  label: string
@@ -194,7 +195,7 @@ const Select = memo((props: SelectProps) => {
194
195
  {tooltip}
195
196
  </span>
196
197
  <select
197
- className={`cove-form-select ${required && !value ? 'warning' : ''}`}
198
+ className={`cove-form-select ${required && !value ? 'warning' : ''} ${DROPDOWN_STYLES}`}
198
199
  name={fieldName}
199
200
  value={value}
200
201
  onChange={event => {
@@ -37,6 +37,15 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
37
37
  updateField('filters', filterIndex, prop, value)
38
38
  }
39
39
 
40
+ const updateFilterDefaultValue = (index, value) => {
41
+ const filters = _.cloneDeep(config.filters)
42
+ const currentFilter = { ...filters[index], orderedValues: filters[index].values }
43
+ currentFilter.defaultValue = value
44
+ currentFilter.active = value
45
+ filters[index] = currentFilter
46
+ updateField(null, null, 'filters', filters)
47
+ }
48
+
40
49
  const updateFilterStyle = (index, style: VizFilterStyle) => {
41
50
  const filters = _.cloneDeep(config.filters)
42
51
  const currentFilter = { ...filters[index], orderedValues: filters[index].values }
@@ -160,6 +169,20 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
160
169
  options={filterStyleOptions}
161
170
  />
162
171
 
172
+ <Select
173
+ value={filter.defaultValue}
174
+ options={
175
+ filter.resetLabel
176
+ ? [filter.resetLabel, ...config.filters?.[filterIndex].values]
177
+ : config.filters?.[filterIndex].values
178
+ }
179
+ updateField={(_section, _subSection, _key, value) => {
180
+ updateFilterDefaultValue(filterIndex, value)
181
+ }}
182
+ label='Filter Default Value'
183
+ initial='Select'
184
+ />
185
+
163
186
  {filter.filterStyle !== 'nested-dropdown' ? (
164
187
  <>
165
188
  <Select
@@ -26,6 +26,8 @@ export const VIZ_FILTER_STYLE = {
26
26
  multiSelect: 'multi-select'
27
27
  } as const
28
28
 
29
+ export const DROPDOWN_STYLES = 'py-2 ps-2 w-100 d-block'
30
+
29
31
  export type VizFilterStyle = (typeof VIZ_FILTER_STYLE)[keyof typeof VIZ_FILTER_STYLE]
30
32
 
31
33
  export const filterStyleOptions = Object.values(VIZ_FILTER_STYLE)
@@ -49,7 +51,7 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
49
51
  export const useFilters = props => {
50
52
  const [showApplyButton, setShowApplyButton] = useState(false)
51
53
 
52
- // Desconstructing: notice, adding more descriptive visualizationConfig name over config
54
+ // Deconstructing: notice, adding more descriptive visualizationConfig name over config
53
55
  // visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
54
56
  const {
55
57
  config: visualizationConfig,
@@ -250,8 +252,8 @@ export const useFilters = props => {
250
252
  }
251
253
 
252
254
  const filterConstants = {
253
- buttonText: 'Apply Filters',
254
- resetText: 'Reset All'
255
+ buttonText: 'Apply',
256
+ resetText: 'Clear Filters'
255
257
  }
256
258
 
257
259
  // prettier-ignore
@@ -285,6 +287,16 @@ const Filters = (props: FilterProps) => {
285
287
  const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
286
288
  const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
287
289
  const [wrappingFilters, setWrappingFilters] = useState({})
290
+ const [initialActiveFilters, setInitialActiveFilters] = useState([])
291
+
292
+ useEffect(() => {
293
+ if (!filteredData) return
294
+
295
+ setInitialActiveFilters(filters.map(filter => filter.active))
296
+ }, [])
297
+
298
+ const activeFilters = filters.map(filter => filter.active)
299
+ const initialFiltersActive = initialActiveFilters.every((filter, i) => activeFilters.includes(filter))
288
300
  const id = useId()
289
301
 
290
302
  const wrappingFilterRefs = useRef({})
@@ -378,7 +390,7 @@ const Filters = (props: FilterProps) => {
378
390
  id={`filter-${outerIndex}`}
379
391
  name={label}
380
392
  aria-label={`Filter by ${label}`}
381
- className='cove-form-select'
393
+ className={`cove-form-select ${DROPDOWN_STYLES}`}
382
394
  data-index='0'
383
395
  value={active}
384
396
  onChange={e => {
@@ -547,7 +559,7 @@ const Filters = (props: FilterProps) => {
547
559
  {visualizationConfig.filterIntro && (
548
560
  <p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
549
561
  )}
550
- <div className='d-flex flex-wrap w-100 mb-4 pb-2 filters-section__wrapper align-items-end'>
562
+ <div className='d-flex flex-wrap w-100 filters-section__wrapper align-items-end'>
551
563
  {' '}
552
564
  <>
553
565
  <Style />
@@ -558,13 +570,13 @@ const Filters = (props: FilterProps) => {
558
570
  handleApplyButton(filters)
559
571
  }}
560
572
  disabled={!showApplyButton}
561
- className={[general?.headerColor ? general.headerColor : theme, 'apply'].join(' ')}
573
+ className={[general?.headerColor ? general.headerColor : theme, 'apply', 'me-2'].join(' ')}
562
574
  >
563
575
  {filterConstants.buttonText}
564
576
  </Button>
565
- <a href='#!' role='button' onClick={handleReset}>
577
+ <Button secondary disabled={initialFiltersActive} onClick={handleReset}>
566
578
  {filterConstants.resetText}
567
- </a>
579
+ </Button>
568
580
  </div>
569
581
  ) : (
570
582
  <></>
@@ -1,6 +1,6 @@
1
1
  .cdc-open-viz-module {
2
2
  .cdc-chart-inner-container .cove-component__content {
3
- padding: 0 15px 27px 0 !important;
3
+ padding: 0 0 27px 0 !important;
4
4
  }
5
5
  &.isEditor {
6
6
  overflow: auto;