@cdc/core 4.24.12 → 4.25.2-25

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 (77) hide show
  1. package/components/DataTable/DataTable.tsx +58 -45
  2. package/components/DataTable/DataTableStandAlone.tsx +3 -3
  3. package/components/DataTable/components/ChartHeader.tsx +26 -6
  4. package/components/DataTable/components/ExpandCollapse.tsx +1 -4
  5. package/components/DataTable/components/MapHeader.tsx +5 -1
  6. package/components/DataTable/data-table.css +3 -8
  7. package/components/DataTable/helpers/chartCellMatrix.tsx +14 -5
  8. package/components/DataTable/helpers/customSort.ts +2 -2
  9. package/components/DataTable/helpers/mapCellMatrix.tsx +83 -60
  10. package/components/DataTable/types/TableConfig.ts +0 -1
  11. package/components/EditorPanel/FootnotesEditor.tsx +49 -7
  12. package/components/EditorPanel/Inputs.tsx +4 -0
  13. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -3
  14. package/components/Filters/Filters.tsx +95 -51
  15. package/components/Filters/helpers/filterWrapping.ts +43 -0
  16. package/components/Filters/helpers/handleSorting.ts +6 -0
  17. package/components/Filters/helpers/tests/handleSorting.test.ts +26 -0
  18. package/components/Footnotes/Footnotes.tsx +1 -1
  19. package/components/Layout/components/Visualization/index.tsx +18 -4
  20. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  21. package/components/Legend/Legend.Gradient.tsx +1 -4
  22. package/components/Legend/index.tsx +1 -1
  23. package/components/LegendShape.tsx +2 -3
  24. package/components/MediaControls.jsx +32 -8
  25. package/components/NestedDropdown/NestedDropdown.tsx +25 -17
  26. package/components/NestedDropdown/nesteddropdown.styles.css +13 -7
  27. package/components/Table/Table.tsx +11 -11
  28. package/components/Table/components/Row.tsx +14 -5
  29. package/components/_stories/DataTable.stories.tsx +1 -2
  30. package/components/elements/Button.jsx +38 -19
  31. package/components/elements/Confirm.tsx +45 -0
  32. package/components/elements/Error.tsx +24 -0
  33. package/components/managers/DataDesigner.tsx +198 -143
  34. package/components/ui/Title/Title.scss +12 -5
  35. package/components/ui/Title/index.tsx +1 -1
  36. package/dist/cove-main.css +260 -723
  37. package/dist/cove-main.css.map +1 -1
  38. package/helpers/DataTransform.ts +55 -61
  39. package/helpers/addValuesToFilters.ts +45 -16
  40. package/helpers/cove/accessibility.ts +24 -0
  41. package/helpers/cove/fontSettings.ts +1 -1
  42. package/helpers/cove/number.ts +1 -7
  43. package/helpers/coveUpdateWorker.ts +5 -1
  44. package/helpers/displayDataAsText.ts +64 -0
  45. package/helpers/filterVizData.ts +2 -2
  46. package/helpers/formatConfigBeforeSave.ts +17 -2
  47. package/helpers/isOlderVersion.ts +20 -0
  48. package/helpers/isRightAlignedTableValue.js +14 -0
  49. package/helpers/missingRequiredSections.ts +20 -0
  50. package/helpers/queryStringUtils.ts +7 -0
  51. package/helpers/tests/addValuesToFilters.test.ts +19 -1
  52. package/helpers/useDataVizClasses.ts +8 -4
  53. package/helpers/ver/4.24.10.ts +12 -0
  54. package/helpers/ver/4.24.11.ts +18 -0
  55. package/helpers/ver/4.24.7.ts +19 -1
  56. package/helpers/ver/4.25.1.ts +18 -0
  57. package/package.json +2 -2
  58. package/styles/_button-section.scss +2 -5
  59. package/styles/_global-variables.scss +17 -7
  60. package/styles/_global.scss +8 -12
  61. package/styles/_reset.scss +4 -5
  62. package/styles/_typography.scss +0 -20
  63. package/styles/_variables.scss +0 -3
  64. package/styles/base.scss +44 -5
  65. package/styles/cove-main.scss +1 -1
  66. package/styles/filters.scss +65 -6
  67. package/styles/v2/base/_general.scss +3 -2
  68. package/styles/v2/components/button.scss +0 -1
  69. package/styles/v2/main.scss +3 -4
  70. package/styles/v2/themes/_color-definitions.scss +4 -4
  71. package/styles/v2/utils/index.scss +0 -1
  72. package/types/BoxPlot.ts +1 -0
  73. package/types/Runtime.ts +1 -0
  74. package/types/Table.ts +0 -1
  75. package/types/Version.ts +1 -1
  76. package/types/VizFilter.ts +3 -1
  77. package/styles/v2/utils/_spacers.scss +0 -31
@@ -39,13 +39,40 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }
39
39
  <>
40
40
  <em>Dynamic Footnotes</em>
41
41
  <div className='row border p-2'>
42
- <InputSelect label='Select a Footnote Dataset' value={config.dataKey} options={dataSetOptions} fieldName='dataKey' updateField={updateField} />
42
+ <InputSelect
43
+ label='Select a Footnote Dataset'
44
+ value={config.dataKey}
45
+ options={dataSetOptions}
46
+ fieldName='dataKey'
47
+ updateField={updateField}
48
+ />
43
49
 
44
50
  {config.dataKey && (
45
51
  <div className='p-3'>
46
- <InputSelect label='Footnote Symbol Column' value={config.dynamicFootnotes?.symbolColumn} options={dataColumns} section='dynamicFootnotes' fieldName='symbolColumn' updateField={updateField} />
47
- <InputSelect label='Footnote Text Column' value={config.dynamicFootnotes?.textColumn} options={dataColumns} section='dynamicFootnotes' fieldName='textColumn' updateField={updateField} />
48
- <InputSelect label='Footnote Order Column' value={config.dynamicFootnotes?.orderColumn} options={dataColumns} section='dynamicFootnotes' fieldName='orderColumn' updateField={updateField} />
52
+ <InputSelect
53
+ label='Footnote Symbol Column'
54
+ value={config.dynamicFootnotes?.symbolColumn}
55
+ options={dataColumns}
56
+ section='dynamicFootnotes'
57
+ fieldName='symbolColumn'
58
+ updateField={updateField}
59
+ />
60
+ <InputSelect
61
+ label='Footnote Text Column'
62
+ value={config.dynamicFootnotes?.textColumn}
63
+ options={dataColumns}
64
+ section='dynamicFootnotes'
65
+ fieldName='textColumn'
66
+ updateField={updateField}
67
+ />
68
+ <InputSelect
69
+ label='Footnote Order Column'
70
+ value={config.dynamicFootnotes?.orderColumn}
71
+ options={dataColumns}
72
+ section='dynamicFootnotes'
73
+ fieldName='orderColumn'
74
+ updateField={updateField}
75
+ />
49
76
  </div>
50
77
  )}
51
78
  </div>
@@ -57,10 +84,25 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }
57
84
  {config.staticFootnotes?.map((note, index) => (
58
85
  <div key={index} className='row border p-2'>
59
86
  <div className='col-8'>
60
- <InputSelect label='Symbol' value={note.symbol} options={[['', '--Select--'], ...footnotesSymbols]} fieldName='symbol' updateField={(section, subsection, fieldName, value) => updateStaticFootnote(index, { ...note, symbol: value })} />{' '}
61
- <TextField label='Text' value={note.text} fieldName='text' updateField={(section, subsection, fieldName, value) => updateStaticFootnote(index, { ...note, text: value })} />
87
+ <InputSelect
88
+ label='Symbol'
89
+ value={note.symbol}
90
+ options={[['', '--Select--'], ...footnotesSymbols]}
91
+ fieldName='symbol'
92
+ updateField={(section, subsection, fieldName, value) =>
93
+ updateStaticFootnote(index, { ...note, symbol: value })
94
+ }
95
+ />{' '}
96
+ <TextField
97
+ label='Text'
98
+ value={note.text}
99
+ fieldName='text'
100
+ updateField={(section, subsection, fieldName, value) =>
101
+ updateStaticFootnote(index, { ...note, text: value })
102
+ }
103
+ />
62
104
  </div>
63
- <div className='col-2 ml-4'>
105
+ <div className='col-2 ms-4'>
64
106
  <button className='btn btn-danger p-1' onClick={() => deleteStaticFootnote(index)}>
65
107
  Delete
66
108
  </button>
@@ -55,6 +55,10 @@ const TextField = memo((props: TextFieldProps) => {
55
55
  }
56
56
  }, [debouncedValue])
57
57
 
58
+ useEffect(() => {
59
+ setValue(stateValue) // Update local state when props change
60
+ }, [stateValue])
61
+
58
62
  let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
59
63
 
60
64
  const onChange = e => {
@@ -91,7 +91,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
91
91
  // Overwrite filterItem.values since thats what we map through in the editor panel
92
92
  filterItem.values = updatedValues
93
93
  filterItem.orderedValues = updatedValues
94
- filterItem.active = updatedValues[0]
94
+ filterItem.active = filterItem.defaultValue ? filterItem.defaultValue : updatedValues[0]
95
95
 
96
96
  filterItem.order = 'cust'
97
97
 
@@ -230,9 +230,10 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
230
230
  value={filter.order || 'asc'}
231
231
  fieldName='order'
232
232
  label='Filter Order'
233
- updateField={(_section, _subSection, _field, value) =>
233
+ updateField={(_section, _subSection, _field, value) => {
234
234
  updateFilterProp('order', filterIndex, value)
235
- }
235
+ if (filter.orderColumn && value !== 'column') updateFilterProp('orderColumn', filterIndex, '')
236
+ }}
236
237
  options={filterOrderOptions}
237
238
  />
238
239
  {filter.order === 'cust' && (
@@ -241,6 +242,17 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
241
242
  handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
242
243
  />
243
244
  )}
245
+ {filter.order === 'column' && (
246
+ <Select
247
+ value={filter.orderColumn}
248
+ fieldName='orderColumn'
249
+ label='Order Column'
250
+ updateField={(_section, _subSection, _field, value) =>
251
+ updateFilterProp('orderColumn', filterIndex, value)
252
+ }
253
+ options={dataColumns}
254
+ />
255
+ )}
244
256
  </>
245
257
  ) : (
246
258
  <NestedDropdownEditor
@@ -274,6 +286,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
274
286
  updateFilterProp('parents', filterIndex, value)
275
287
  }}
276
288
  options={getParentFilterOptions(filterIndex)}
289
+ selected={config.filters[filterIndex].parents}
277
290
  />
278
291
  </label>
279
292
  </FieldSetWrapper>
@@ -1,5 +1,5 @@
1
- import { useState, useEffect, useMemo } from 'react'
2
- import { useId } from 'react'
1
+ import { useState, useEffect, useMemo, useRef, useId } from 'react'
2
+ import _ from 'lodash'
3
3
 
4
4
  // CDC
5
5
  import Button from '../elements/Button'
@@ -11,16 +11,17 @@ import { filterVizData } from '../../helpers/filterVizData'
11
11
  import { addValuesToFilters } from '../../helpers/addValuesToFilters'
12
12
  import { DimensionsType } from '../../types/Dimensions'
13
13
  import NestedDropdown from '../NestedDropdown'
14
- import _ from 'lodash'
15
14
  import { getNestedOptions } from './helpers/getNestedOptions'
16
15
  import { applyQueuedActive } from './helpers/applyQueuedActive'
17
16
  import { handleSorting } from './helpers/handleSorting'
17
+ import { getWrappingStatuses } from './helpers/filterWrapping'
18
18
 
19
19
  export const VIZ_FILTER_STYLE = {
20
20
  dropdown: 'dropdown',
21
21
  nestedDropdown: 'nested-dropdown',
22
22
  pill: 'pill',
23
23
  tab: 'tab',
24
+ tabSimple: 'tab-simple',
24
25
  tabBar: 'tab bar',
25
26
  multiSelect: 'multi-select'
26
27
  } as const
@@ -41,18 +42,25 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
41
42
  {
42
43
  label: 'Custom',
43
44
  value: 'cust'
44
- }
45
+ },
46
+ { label: 'Order By Data Column', value: 'column' }
45
47
  ]
46
48
 
47
- const hasStandardFilterBehavior = ['chart', 'table']
48
-
49
49
  export const useFilters = props => {
50
50
  const [showApplyButton, setShowApplyButton] = useState(false)
51
51
 
52
52
  // Desconstructing: notice, adding more descriptive visualizationConfig name over config
53
53
  // 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
54
+ const {
55
+ config: visualizationConfig,
56
+ setConfig,
57
+ filteredData,
58
+ setFilteredData,
59
+ excludedData,
60
+ getUniqueValues,
61
+ standaloneMap
62
+ } = props
63
+ const { data } = visualizationConfig
56
64
 
57
65
  /**
58
66
  * Re-orders a filter based on two indices and updates the runtime filters array and filters state
@@ -72,21 +80,19 @@ export const useFilters = props => {
72
80
  const [movedItem] = updatedValues.splice(idx1, 1)
73
81
  updatedValues.splice(idx2, 0, movedItem)
74
82
 
75
- const filtersCopy = hasStandardFilterBehavior.includes(visualizationConfig.type)
76
- ? [...visualizationConfig.filters]
77
- : [...filteredData]
83
+ const filtersCopy = !standaloneMap ? [...visualizationConfig.filters] : [...filteredData]
78
84
  const filterItem = { ...filtersCopy[filterIndex] }
79
85
 
80
86
  // Overwrite filterItem.values since thats what we map through in the editor panel
81
87
  filterItem.values = updatedValues
82
88
  filterItem.orderedValues = updatedValues
83
- filterItem.active = updatedValues[0]
89
+ if (!filterItem.active) filterItem.active = filterItem.defaultValue ? filterItem.defaultValue : updatedValues[0]
84
90
  filterItem.order = 'cust'
85
91
 
86
92
  // Update the filters
87
93
  filtersCopy[filterIndex] = filterItem
88
94
 
89
- if (visualizationConfig.type === 'map') {
95
+ if (standaloneMap) {
90
96
  setFilteredData(filtersCopy)
91
97
  }
92
98
 
@@ -94,13 +100,13 @@ export const useFilters = props => {
94
100
  }
95
101
 
96
102
  const changeFilterActive = (index, value) => {
97
- let newFilters = visualizationConfig.type === 'map' ? [...filteredData] : [...visualizationConfig.filters]
103
+ let newFilters = standaloneMap ? [...filteredData] : [...visualizationConfig.filters]
98
104
 
105
+ const newFilter = newFilters[index]
99
106
  if (visualizationConfig.filterBehavior === 'Apply Button') {
100
- newFilters[index].queuedActive = value
107
+ newFilter.queuedActive = value
101
108
  setShowApplyButton(true)
102
109
  } else {
103
- const newFilter = newFilters[index]
104
110
  if (newFilter.filterStyle !== 'nested-dropdown') {
105
111
  newFilter.active = value
106
112
  } else {
@@ -120,7 +126,7 @@ export const useFilters = props => {
120
126
  queryParams[newFilter?.subGrouping?.setByQueryParameter] = newFilter.subGrouping.active
121
127
  updateQueryString(queryParams)
122
128
  }
123
- setFilteredData(newFilters[index])
129
+ setFilteredData(newFilters)
124
130
  }
125
131
 
126
132
  if (!visualizationConfig.dynamicSeries) {
@@ -132,15 +138,12 @@ export const useFilters = props => {
132
138
  }
133
139
 
134
140
  // Used for setting active filter, fromHash breaks the filteredData functionality.
135
- if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
141
+ if (standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
136
142
  setFilteredData(newFilters)
137
143
  }
138
144
 
139
145
  // 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
- ) {
146
+ if (!standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
144
147
  const newFilteredData = filterVizData(newFilters, excludedData)
145
148
  setFilteredData(newFilteredData)
146
149
 
@@ -204,11 +207,9 @@ export const useFilters = props => {
204
207
 
205
208
  setConfig({ ...visualizationConfig, filters: newFilters })
206
209
 
207
- if (type === 'map') {
210
+ if (standaloneMap) {
208
211
  setFilteredData(newFilters, excludedData)
209
- }
210
-
211
- if (hasStandardFilterBehavior.includes(visualizationConfig.type)) {
212
+ } else {
212
213
  setFilteredData(filterVizData(newFilters, excludedData))
213
214
  }
214
215
 
@@ -226,6 +227,7 @@ export const useFilters = props => {
226
227
  if (!filter.values || filter.values.length === 0) {
227
228
  filter.values = getUniqueValues(data, filter.columnName)
228
229
  }
230
+
229
231
  newFilters[i].active = handleSorting(filter).values[0]
230
232
 
231
233
  if (filter.setByQueryParameter && queryParams[filter.setByQueryParameter] !== filter.active) {
@@ -240,7 +242,7 @@ export const useFilters = props => {
240
242
 
241
243
  setConfig({ ...visualizationConfig, filters: newFilters })
242
244
 
243
- if (type === 'map') {
245
+ if (standaloneMap) {
244
246
  setFilteredData(newFilters, excludedData)
245
247
  } else {
246
248
  setFilteredData(filterVizData(newFilters, excludedData))
@@ -274,17 +276,25 @@ type FilterProps = {
274
276
  setFilteredData: Function
275
277
  // updating function for setting fitlerBehavior
276
278
  setConfig: Function
277
- // exclusions
278
- exclusions: any[]
279
+ standaloneMap?: boolean
279
280
  }
280
281
 
281
282
  const Filters = (props: FilterProps) => {
282
- const { config: visualizationConfig, filteredData, dimensions } = props
283
- const { filters, type, general, theme, filterBehavior } = visualizationConfig
283
+ const { config: visualizationConfig, filteredData, dimensions, standaloneMap } = props
284
+ const { filters, general, theme, filterBehavior } = visualizationConfig
284
285
  const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
285
286
  const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
287
+ const [wrappingFilters, setWrappingFilters] = useState({})
286
288
  const id = useId()
287
289
 
290
+ const wrappingFilterRefs = useRef({})
291
+ const filterWrappingStatusesToUpdate = getWrappingStatuses(wrappingFilterRefs, wrappingFilters, filters)
292
+
293
+ if (filterWrappingStatusesToUpdate.length) {
294
+ const validStatuses = filterWrappingStatusesToUpdate.filter(Boolean) as [string, any][]
295
+ setWrappingFilters({ ...wrappingFilters, ...Object.fromEntries(validStatuses) })
296
+ }
297
+
288
298
  // useFilters hook provides data and logic for handling various filter functions
289
299
  // prettier-ignore
290
300
  const {
@@ -298,13 +308,32 @@ const Filters = (props: FilterProps) => {
298
308
 
299
309
  useEffect(() => {
300
310
  if (!dimensions) return
301
- if (Number(dimensions[0]) < 768 && filters?.length > 0) {
302
- setMobileFilterStyle(true)
303
- } else {
304
- setMobileFilterStyle(false)
305
- }
311
+ const [width] = dimensions
312
+
313
+ const isMobile = Number(width) < 768
314
+ const isTabSimple = filters?.some(filter => filter.filterStyle === VIZ_FILTER_STYLE.tabSimple)
315
+
316
+ const defaultToMobile = isMobile && filters?.length && !isTabSimple
317
+
318
+ setMobileFilterStyle(defaultToMobile)
306
319
  }, [dimensions])
307
320
 
321
+ useEffect(() => {
322
+ const noLongerTabSimple = Object.keys(wrappingFilters).filter(columnName => {
323
+ const filter = filters.find(filter => filter.columnName === columnName)
324
+ if (!filter) return false
325
+ return filter.filterStyle !== VIZ_FILTER_STYLE.tabSimple
326
+ })
327
+
328
+ if (!noLongerTabSimple.length) return
329
+
330
+ setWrappingFilters(
331
+ Object.fromEntries(
332
+ Object.entries(wrappingFilters).filter(([columnName]) => !noLongerTabSimple.includes(columnName))
333
+ )
334
+ )
335
+ }, [filters])
336
+
308
337
  useEffect(() => {
309
338
  if (selectedFilter) {
310
339
  const el = document.getElementById(selectedFilter.id)
@@ -363,7 +392,7 @@ const Filters = (props: FilterProps) => {
363
392
 
364
393
  const vizFiltersWithValues = useMemo(() => {
365
394
  // Here charts is using config.filters where maps is using a runtime value
366
- let vizfilters = type === 'map' ? filteredData : filters
395
+ let vizfilters = standaloneMap ? filteredData : filters
367
396
  if (!vizfilters) return []
368
397
  if (vizfilters.fromHash) delete vizfilters.fromHash // support for Maps config
369
398
  return addValuesToFilters(vizfilters as VizFilter[], visualizationConfig.data)
@@ -377,13 +406,17 @@ const Filters = (props: FilterProps) => {
377
406
  const DropdownOptions = []
378
407
  const Pills = []
379
408
  const Tabs = []
409
+ const isTabSimple = singleFilter.filterStyle === 'tab-simple'
380
410
 
381
- const { active, queuedActive, label, filterStyle } = singleFilter as VizFilter
411
+ const { active, queuedActive, label, filterStyle, columnName } = singleFilter as VizFilter
412
+ const { isDropdown } = wrappingFilters[columnName] || {}
382
413
 
383
414
  handleSorting(singleFilter)
384
415
  singleFilter.values?.forEach((filterOption, index) => {
385
- const pillClassList = ['pill', active === filterOption ? 'pill--active' : null, theme && theme]
386
- const tabClassList = ['tab', active === filterOption && 'tab--active', theme && theme]
416
+ const isActive = active === filterOption
417
+
418
+ const pillClassList = ['pill', isActive ? 'pill--active' : null, theme && theme]
419
+ const tabClassList = ['tab', isActive && 'tab--active', theme && theme, isTabSimple && 'tab--simple']
387
420
 
388
421
  Pills.push(
389
422
  <div className='pill__wrapper' key={`pill-${index}`}>
@@ -437,20 +470,31 @@ const Filters = (props: FilterProps) => {
437
470
 
438
471
  const classList = [
439
472
  'single-filters',
440
- 'form-group mr-3',
473
+ 'form-group',
441
474
  mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
442
475
  ]
443
- const mobileExempt = ['nested-dropdown', 'multi-select'].includes(filterStyle)
444
- const showDefaultDropdown = (filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt
476
+ const mobileExempt = ['nested-dropdown', 'multi-select', VIZ_FILTER_STYLE.tabSimple].includes(filterStyle)
477
+ const showDefaultDropdown = ((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
478
+ const [nestedActiveGroup, nestedActiveSubGroup] = useMemo<string[]>(() => {
479
+ if (filterStyle !== 'nested-dropdown') return []
480
+ return (singleFilter.queuedActive || [singleFilter.active, singleFilter.subGrouping?.active]) as [
481
+ string,
482
+ string
483
+ ]
484
+ }, [singleFilter])
485
+ const hideLabelMargin = isTabSimple && !showDefaultDropdown
445
486
  return (
446
- <div className={classList.join(' ')} key={outerIndex}>
487
+ <div className={classList.join(' ')} key={outerIndex} ref={el => (wrappingFilterRefs.current[columnName] = el)}>
447
488
  <>
448
489
  {label && (
449
- <label className='font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
490
+ <label className={`font-weight-bold mb-${hideLabelMargin ? '0' : '2'}`} htmlFor={`filter-${outerIndex}`}>
450
491
  {label}
451
492
  </label>
452
493
  )}
453
494
  {filterStyle === 'tab' && !mobileFilterStyle && Tabs}
495
+ {filterStyle === 'tab-simple' && !showDefaultDropdown && (
496
+ <div className='tab-simple-container d-flex w-100'>{Tabs}</div>
497
+ )}
454
498
  {filterStyle === 'pill' && !mobileFilterStyle && Pills}
455
499
  {filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
456
500
  {filterStyle === 'multi-select' && (
@@ -464,8 +508,8 @@ const Filters = (props: FilterProps) => {
464
508
  )}
465
509
  {filterStyle === 'nested-dropdown' && (
466
510
  <NestedDropdown
467
- activeGroup={(singleFilter.active as string) || (singleFilter.queuedActive || [])[0]}
468
- activeSubGroup={(singleFilter.subGrouping?.active as string) || (singleFilter.queuedActive || [])[1]}
511
+ activeGroup={nestedActiveGroup}
512
+ activeSubGroup={nestedActiveSubGroup}
469
513
  filterIndex={outerIndex}
470
514
  options={getNestedOptions(singleFilter)}
471
515
  listLabel={label}
@@ -492,18 +536,18 @@ const Filters = (props: FilterProps) => {
492
536
  const getClasses = () => {
493
537
  const { visualizationType, legend } = visualizationConfig || {}
494
538
  const baseClass = 'filters-section'
495
- const conditionalClass = type === 'map' ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
539
+ const conditionalClass = standaloneMap ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
496
540
  const legendClass = legend && !legend.hide && legend.position === 'top' ? 'mb-0' : null
497
541
 
498
- return [baseClass, conditionalClass, legendClass].filter(Boolean)
542
+ return [baseClass, conditionalClass, legendClass, 'w-100'].filter(Boolean)
499
543
  }
500
544
 
501
545
  return (
502
546
  <section className={getClasses().join(' ')}>
503
547
  {visualizationConfig.filterIntro && (
504
- <p className='filters-section__intro-text'>{visualizationConfig.filterIntro}</p>
548
+ <p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
505
549
  )}
506
- <div className='d-flex flex-wrap w-100 filters-section__wrapper'>
550
+ <div className='d-flex flex-wrap w-100 mb-4 pb-2 filters-section__wrapper align-items-end'>
507
551
  {' '}
508
552
  <>
509
553
  <Style />
@@ -0,0 +1,43 @@
1
+ import { VIZ_FILTER_STYLE } from '../Filters'
2
+
3
+ const WRAPPING_HEIGHT_THRESHOLD_NO_LABEL = 60
4
+ const WRAPPING_HEIGHT_THRESHOLD_WITH_LABEL = 100
5
+ export const getWrappingStatuses = (wrappingFilterRefs, wrappingFilters, allFilters) =>
6
+ Object.entries(wrappingFilterRefs.current)
7
+ .map(([columnValue, ref]) => {
8
+ if (!ref) return false
9
+
10
+ const filter = allFilters.find(filter => filter.columnName === columnValue)
11
+ const { filterStyle, label } = filter || {}
12
+
13
+ if (!filterStyle || filterStyle !== VIZ_FILTER_STYLE.tabSimple) return false
14
+
15
+ const filterStyleClass = (ref as HTMLElement).className
16
+ .split(' ')
17
+ .find(className => className.includes(filterStyle))
18
+ ?.split('single-filters--')[1]
19
+
20
+ const classMatchesStyle = filterStyleClass && filterStyleClass === filterStyle
21
+
22
+ if (!classMatchesStyle) return false
23
+
24
+ const wrappingState = wrappingFilters[columnValue]
25
+ const { height, width } = (ref as HTMLElement).getBoundingClientRect()
26
+ const wrappingThreshold = label ? WRAPPING_HEIGHT_THRESHOLD_WITH_LABEL : WRAPPING_HEIGHT_THRESHOLD_NO_LABEL
27
+ const isWrapped = height > wrappingThreshold
28
+
29
+ if (!wrappingState) return [columnValue, { highestWrappedWidth: isWrapped ? width : 0, isDropdown: isWrapped }]
30
+
31
+ const { highestWrappedWidth, isDropdown: wasDropdown } = wrappingState
32
+ const isDropdown = width <= highestWrappedWidth
33
+ const widthIsLarger = width > highestWrappedWidth
34
+ const largestWidth = Math.max(highestWrappedWidth, width)
35
+
36
+ if ((isDropdown || isWrapped) && !wasDropdown) {
37
+ return [columnValue, { highestWrappedWidth: largestWidth, isDropdown: true }]
38
+ }
39
+ if (wasDropdown && widthIsLarger) {
40
+ return [columnValue, { highestWrappedWidth, isDropdown: false }]
41
+ }
42
+ })
43
+ .filter(Boolean)
@@ -7,12 +7,18 @@ 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 })
13
18
  }
14
19
 
15
20
  singleFilter.values = singleFilterValues.sort(sort)
21
+ singleFilter.orderedValues = singleFilterValues.sort(sort)
16
22
 
17
23
  return singleFilter
18
24
  }
@@ -16,6 +16,32 @@ describe('handleSorting', () => {
16
16
  expect(result.values).toEqual(['value1', 'value2', 'value3'])
17
17
  })
18
18
 
19
+ it('should sort orderedValues in asc order if order is asc"', () => {
20
+ const singleFilter = {
21
+ values: ['value3', 'value1', 'value2'],
22
+ orderedValues: ['value2', 'value1', 'value3'],
23
+ order: 'asc',
24
+ filterStyle: 'nested-dropdown'
25
+ }
26
+
27
+ const result = handleSorting(singleFilter)
28
+
29
+ expect(result.orderedValues).toEqual(['value1', 'value2', 'value3'])
30
+ })
31
+
32
+ it('should sort orderedValues in desc order if order is desc"', () => {
33
+ const singleFilter = {
34
+ values: ['value3', 'value1', 'value2'],
35
+ orderedValues: ['value1', 'value2', 'value1'],
36
+ order: 'desc',
37
+ filterStyle: 'nested-dropdown'
38
+ }
39
+
40
+ const result = handleSorting(singleFilter)
41
+
42
+ expect(result.orderedValues).toEqual(['value3', 'value2', 'value1'])
43
+ })
44
+
19
45
  it('should sort values in ascending order by default', () => {
20
46
  const singleFilter = {
21
47
  values: ['value3', 'value1', 'value2'],
@@ -12,7 +12,7 @@ const Footnotes: React.FC<FootnotesProps> = ({ footnotes }) => {
12
12
  {footnotes.map((note, i) => {
13
13
  return (
14
14
  <li key={note.symbol + i} className='mb-1'>
15
- {note.symbol && <span className='mr-1'>{note.symbol}</span>}
15
+ {note.symbol && <span className='me-1'>{note.symbol}</span>}
16
16
  {note.text}
17
17
  </li>
18
18
  )
@@ -17,10 +17,17 @@ type VisualizationWrapper = {
17
17
  }
18
18
 
19
19
  const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) => {
20
- const { config = {}, isEditor = false, currentViewport = 'lg', imageId = '', showEditorPanel = true, className } = props
20
+ const {
21
+ config = {},
22
+ isEditor = false,
23
+ currentViewport = 'lg',
24
+ imageId = '',
25
+ showEditorPanel = true,
26
+ className
27
+ } = props
21
28
 
22
29
  const getWrappingClasses = () => {
23
- let classes = ['cdc-open-viz-module', `${currentViewport}`, `font-${config?.fontSize}`, `${config?.theme}`]
30
+ let classes = ['cdc-open-viz-module', `${currentViewport}`, `${config?.theme}`]
24
31
 
25
32
  if (className) {
26
33
  classes.push(className)
@@ -40,7 +47,7 @@ const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) =>
40
47
  }
41
48
 
42
49
  if (config.type === 'filtered-text') {
43
- classes.push('type-filtered-text')
50
+ classes.push('type-filtered-text', `font-${config.fontSize}`)
44
51
  classes = classes.filter(item => item !== 'cove-component__content')
45
52
  return classes
46
53
  }
@@ -67,7 +74,14 @@ const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) =>
67
74
  }
68
75
 
69
76
  if (config.type === 'waffle-chart') {
70
- classes.push('cove', 'cdc-open-viz-module', 'type-waffle-chart', currentViewport, config.theme, 'font-' + config.overallFontSize)
77
+ classes.push(
78
+ 'cove',
79
+ 'cdc-open-viz-module',
80
+ 'type-waffle-chart',
81
+ currentViewport,
82
+ config.theme,
83
+ 'font-' + config.overallFontSize
84
+ )
71
85
 
72
86
  if (isEditor) {
73
87
  classes.push('is-editor')
@@ -1,6 +1,6 @@
1
1
  .cdc-open-viz-module {
2
2
  .cdc-chart-inner-container .cove-component__content {
3
- padding: 25px 15px 25px 0 !important;
3
+ padding: 0 15px 27px 0 !important;
4
4
  }
5
5
  &.isEditor {
6
6
  overflow: auto;
@@ -92,10 +92,7 @@ const LegendGradient = ({
92
92
 
93
93
  if (style === 'gradient') {
94
94
  return (
95
- <svg
96
- style={{ overflow: 'visible', width: '100%', marginTop: 10, marginBottom: hideBorder ? 10 : 0 }}
97
- height={newHeight}
98
- >
95
+ <svg className={'w-100 overflow-visible'} height={newHeight}>
99
96
  {/* background border*/}
100
97
  <rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill='#d3d3d3' />
101
98
  {/* Define the gradient */}
@@ -28,7 +28,7 @@ const ResetButton = props => {
28
28
  if (config.runtime.disabledAmt === 0) return <></>
29
29
 
30
30
  return (
31
- <button onClick={handleReset} className={legendClasses.resetButton.join(' ') || ''}>
31
+ <button onClick={handleReset} className={legendClasses.showAllButton.join(' ') || ''}>
32
32
  Reset
33
33
  </button>
34
34
  )
@@ -10,9 +10,8 @@ interface LegendShapeProps {
10
10
  const LegendShape: React.FC<LegendShapeProps> = props => {
11
11
  const { fill, borderColor, display = 'inline-block', shape = 'circle' } = props
12
12
  const dimensions = { width: '1em', height: '1em' }
13
- const marginRight = ['circle', 'square'].includes(shape) ? '5px' : '0'
13
+ const isCircleOrSquare = ['circle', 'square'].includes(shape)
14
14
  const styles = {
15
- marginRight: marginRight,
16
15
  borderRadius: shape === 'circle' ? '50%' : '0px',
17
16
  verticalAlign: 'middle',
18
17
  display: display,
@@ -22,7 +21,7 @@ const LegendShape: React.FC<LegendShapeProps> = props => {
22
21
  backgroundColor: fill
23
22
  }
24
23
 
25
- return <span className='legend-item' style={styles} />
24
+ return <span className={`legend-item ${isCircleOrSquare ? 'me-2' : ''}`} style={styles} />
26
25
  }
27
26
 
28
27
  export default LegendShape