@cdc/core 4.24.4 → 4.24.7

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 (80) hide show
  1. package/components/AdvancedEditor/AdvancedEditor.tsx +93 -0
  2. package/components/AdvancedEditor/advanced-editor-styles.css +3 -0
  3. package/components/AdvancedEditor/index.ts +1 -0
  4. package/components/DataTable/DataTable.tsx +33 -15
  5. package/components/DataTable/DataTableStandAlone.tsx +30 -4
  6. package/components/DataTable/components/ChartHeader.tsx +3 -2
  7. package/components/DataTable/components/DataTableEditorPanel.tsx +23 -6
  8. package/components/DataTable/components/ExpandCollapse.tsx +1 -1
  9. package/components/DataTable/helpers/chartCellMatrix.tsx +5 -10
  10. package/components/DataTable/helpers/getChartCellValue.ts +26 -2
  11. package/components/DataTable/helpers/getDataSeriesColumns.ts +40 -16
  12. package/components/DataTable/helpers/getRowType.ts +6 -0
  13. package/components/DataTable/helpers/getSeriesName.ts +2 -1
  14. package/components/DataTable/helpers/{customColumns.ts → removeNullColumns.ts} +3 -3
  15. package/components/DataTable/types/TableConfig.ts +12 -22
  16. package/components/EditorPanel/ColumnsEditor.tsx +278 -262
  17. package/components/EditorPanel/DataTableEditor.tsx +159 -60
  18. package/components/EditorPanel/FieldSetWrapper.tsx +51 -0
  19. package/components/EditorPanel/FootnotesEditor.tsx +77 -0
  20. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +227 -0
  21. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +54 -0
  22. package/components/EditorPanel/VizFilterEditor/index.ts +1 -0
  23. package/components/EditorWrapper/EditorWrapper.tsx +47 -0
  24. package/components/EditorWrapper/editor-wrapper.style.css +14 -0
  25. package/components/EditorWrapper/index.ts +1 -0
  26. package/components/{Filters.jsx → Filters.tsx} +102 -70
  27. package/components/Footnotes/Footnotes.tsx +25 -0
  28. package/components/Footnotes/FootnotesStandAlone.tsx +45 -0
  29. package/components/Footnotes/footnotes.css +5 -0
  30. package/components/Footnotes/index.ts +1 -0
  31. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +8 -4
  32. package/components/Layout/components/Visualization/index.tsx +14 -5
  33. package/components/MediaControls.jsx +1 -1
  34. package/components/MultiSelect/MultiSelect.tsx +36 -9
  35. package/components/MultiSelect/multiselect.styles.css +0 -3
  36. package/components/_stories/DataTable.stories.tsx +1 -2
  37. package/components/_stories/EditorPanel.stories.tsx +1 -0
  38. package/components/_stories/Footnotes.stories.tsx +17 -0
  39. package/components/inputs/InputSelect.tsx +17 -6
  40. package/components/ui/Icon.tsx +1 -2
  41. package/helpers/DataTransform.ts +9 -32
  42. package/helpers/addValuesToFilters.ts +56 -0
  43. package/helpers/cove/accessibility.ts +1 -0
  44. package/helpers/cove/fontSettings.ts +2 -0
  45. package/helpers/coveUpdateWorker.ts +11 -2
  46. package/helpers/filterVizData.ts +30 -0
  47. package/helpers/footnoteSymbols.ts +11 -0
  48. package/helpers/formatConfigBeforeSave.ts +90 -0
  49. package/helpers/gatherQueryParams.ts +14 -7
  50. package/helpers/lineChartHelpers.js +2 -1
  51. package/helpers/pivotData.ts +18 -0
  52. package/helpers/queryStringUtils.ts +29 -0
  53. package/helpers/tests/updateFieldFactory.test.ts +1 -0
  54. package/helpers/updateFieldFactory.ts +1 -1
  55. package/helpers/useDataVizClasses.js +0 -4
  56. package/helpers/ver/4.23.4.ts +27 -0
  57. package/helpers/ver/4.24.5.ts +32 -0
  58. package/helpers/ver/4.24.7.ts +92 -0
  59. package/package.json +6 -4
  60. package/styles/_button-section.scss +6 -1
  61. package/styles/_data-table.scss +0 -1
  62. package/styles/_reset.scss +7 -6
  63. package/styles/base.scss +4 -0
  64. package/styles/v2/themes/_color-definitions.scss +1 -0
  65. package/types/Annotation.ts +46 -0
  66. package/types/Column.ts +1 -0
  67. package/types/ConfigureData.ts +1 -1
  68. package/types/Footnotes.ts +17 -0
  69. package/types/General.ts +5 -0
  70. package/types/Legend.ts +1 -0
  71. package/types/MarkupInclude.ts +26 -0
  72. package/types/Runtime.ts +3 -7
  73. package/types/Series.ts +1 -1
  74. package/types/Table.ts +21 -14
  75. package/types/Visualization.ts +40 -11
  76. package/types/VizFilter.ts +24 -0
  77. package/LICENSE +0 -201
  78. package/components/AdvancedEditor.jsx +0 -74
  79. package/helpers/queryStringUtils.js +0 -26
  80. package/types/BaseVisualizationType.ts +0 -1
@@ -0,0 +1,54 @@
1
+ import { VizFilter } from '../../../../types/VizFilter'
2
+ import { filterOrderOptions } from '../../../Filters'
3
+ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
4
+
5
+ type FilterOrderProps = {
6
+ filterIndex: number
7
+ filter: VizFilter
8
+ updateFilterProp: (prop: string, index: number, value: string) => void
9
+ handleFilterOrder: (sourceIndex: number, destinationIndex: number, filterIndex: number, filter: VizFilter) => void
10
+ }
11
+
12
+ const FilterOrder: React.FC<FilterOrderProps> = ({ filterIndex, filter, updateFilterProp, handleFilterOrder }) => {
13
+ return (
14
+ <label>
15
+ <span className='edit-filterOrder column-heading'>Filter Order</span>
16
+ <select value={filter.order ? filter.order : 'asc'} onChange={e => updateFilterProp('order', filterIndex, e.target.value)}>
17
+ {filterOrderOptions.map((option, index) => {
18
+ return (
19
+ <option value={option.value} key={`filter-${index}`}>
20
+ {option.label}
21
+ </option>
22
+ )
23
+ })}
24
+ </select>
25
+
26
+ {filter.order === 'cust' && (
27
+ <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, filterIndex, filter)}>
28
+ <Droppable droppableId='filter_order'>
29
+ {provided => (
30
+ <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
31
+ {filter?.values.map((value, index) => {
32
+ return (
33
+ <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
34
+ {(provided, snapshot) => (
35
+ <li>
36
+ <div className={snapshot.isDragging ? 'currently-dragging' : ''} style={provided.draggableProps.style} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
37
+ {value}
38
+ </div>
39
+ </li>
40
+ )}
41
+ </Draggable>
42
+ )
43
+ })}
44
+ {provided.placeholder}
45
+ </ul>
46
+ )}
47
+ </Droppable>
48
+ </DragDropContext>
49
+ )}
50
+ </label>
51
+ )
52
+ }
53
+
54
+ export default FilterOrder
@@ -0,0 +1 @@
1
+ export { default } from './VizFilterEditor'
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { Visualization } from '../../types/Visualization'
3
+ import { ViewPort } from '../../types/ViewPort'
4
+ import './editor-wrapper.style.css'
5
+
6
+ type StandAloneComponentProps = {
7
+ visualizationKey: string
8
+ config
9
+ updateConfig: (Visualization) => void
10
+ configUrl: string
11
+ setEditing: Function
12
+ hostname: string
13
+ viewport?: ViewPort
14
+
15
+ [key: string]: any
16
+ }
17
+
18
+ type EditorProps = {
19
+ component: React.JSXElementConstructor<StandAloneComponentProps>
20
+ type: string
21
+ visualizationKey: string
22
+ visualizationConfig: Visualization
23
+ updateConfig: (Visualization) => void
24
+ viewport?: ViewPort
25
+ }
26
+
27
+ const EditorWrapper: React.FC<React.PropsWithChildren<EditorProps>> = ({ children, visualizationKey, visualizationConfig, type, component: Component, updateConfig, viewport }) => {
28
+ const [displayPanel, setDisplayPanel] = React.useState(true)
29
+ return (
30
+ <>
31
+ <div className='editor-wrapper'>
32
+ <button className={`editor-toggle ${displayPanel ? '' : 'collapsed'}`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={() => setDisplayPanel(!displayPanel)} />
33
+ <section className={`${displayPanel ? '' : 'hidden'} editor-panel cove`}>
34
+ <div aria-level={2} role='heading' className='heading-2'>
35
+ Configure {type}
36
+ </div>
37
+ <section>{children}</section>
38
+ </section>
39
+ <div className='preview-wrapper'>
40
+ <Component visualizationKey={visualizationKey} config={visualizationConfig} updateConfig={updateConfig} configUrl={undefined} setEditing={undefined} hostname={undefined} viewport={viewport} />
41
+ </div>
42
+ </div>
43
+ </>
44
+ )
45
+ }
46
+
47
+ export default EditorWrapper
@@ -0,0 +1,14 @@
1
+ .editor-wrapper {
2
+ --editorPanelWidth: 350px;
3
+ position: relative;
4
+ min-height: 80vh;
5
+ .editor-panel {
6
+ :is(form) {
7
+ border-right: var(--lightGray) 1px solid;
8
+ flex-grow: 1;
9
+ }
10
+ }
11
+ .preview-wrapper {
12
+ padding-left: var(--editorPanelWidth);
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ export { default } from './EditorWrapper'
@@ -1,37 +1,69 @@
1
- import React, { useState, useEffect, useRef } from 'react'
1
+ import { useState, useEffect } from 'react'
2
2
  import { useId } from 'react'
3
3
 
4
4
  // CDC
5
- import Button from '@cdc/core/components/elements/Button'
6
- import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
5
+ import Button from './elements/Button'
6
+ import { getQueryParams, updateQueryString } from '../helpers/queryStringUtils'
7
7
 
8
8
  // Third Party
9
9
  import PropTypes from 'prop-types'
10
+ import MultiSelect from './MultiSelect'
11
+ import { Visualization } from '../types/Visualization'
12
+ import { MultiSelectFilter, VizFilter } from '../types/VizFilter'
13
+ import { filterVizData } from '../helpers/filterVizData'
14
+ import { addValuesToFilters } from '../helpers/addValuesToFilters'
15
+
16
+ export const filterStyleOptions = ['dropdown', 'pill', 'tab', 'tab bar', 'multi-select']
17
+
18
+ export const filterOrderOptions = [
19
+ {
20
+ label: 'Ascending Alphanumeric',
21
+ value: 'asc'
22
+ },
23
+ {
24
+ label: 'Descending Alphanumeric',
25
+ value: 'desc'
26
+ },
27
+ {
28
+ label: 'Custom',
29
+ value: 'cust'
30
+ }
31
+ ]
32
+
33
+ export const handleSorting = singleFilter => {
34
+ const { order } = singleFilter
35
+
36
+ const sortAsc = (a, b) => {
37
+ return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
38
+ }
39
+
40
+ const sortDesc = (a, b) => {
41
+ return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
42
+ }
43
+
44
+ if (!order || order === '') {
45
+ singleFilter.order = 'asc'
46
+ }
47
+
48
+ if (order === 'desc') {
49
+ singleFilter.values = singleFilter.values.sort(sortDesc)
50
+ }
51
+
52
+ if (order === 'asc') {
53
+ singleFilter.values = singleFilter.values.sort(sortAsc)
54
+ }
55
+ return singleFilter
56
+ }
57
+
58
+ const hasStandardFilterBehavior = ['chart', 'table']
10
59
 
11
60
  export const useFilters = props => {
12
61
  const [showApplyButton, setShowApplyButton] = useState(false)
13
62
 
14
63
  // Desconstructing: notice, adding more descriptive visualizationConfig name over config
15
64
  // visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
16
- const { config: visualizationConfig, setConfig, filteredData, setFilteredData, excludedData, filterData } = props
17
- const { type, filterBehavior, filters } = visualizationConfig
18
-
19
- const filterStyleOptions = ['dropdown', 'pill', 'tab', 'tab bar']
20
-
21
- const filterOrderOptions = [
22
- {
23
- label: 'Ascending Alphanumeric',
24
- value: 'asc'
25
- },
26
- {
27
- label: 'Descending Alphanumeric',
28
- value: 'desc'
29
- },
30
- {
31
- label: 'Custom',
32
- value: 'cust'
33
- }
34
- ]
65
+ const { config: visualizationConfig, setConfig, filteredData, setFilteredData, excludedData, getUniqueValues } = props
66
+ const { type, data } = visualizationConfig
35
67
 
36
68
  /**
37
69
  * Re-orders a filter based on two indices and updates the runtime filters array and filters state
@@ -51,7 +83,7 @@ export const useFilters = props => {
51
83
  const [movedItem] = updatedValues.splice(idx1, 1)
52
84
  updatedValues.splice(idx2, 0, movedItem)
53
85
 
54
- const filtersCopy = visualizationConfig.type === 'chart' ? [...visualizationConfig.filters] : [...filteredData]
86
+ const filtersCopy = hasStandardFilterBehavior.includes(visualizationConfig.type) ? [...visualizationConfig.filters] : [...filteredData]
55
87
  const filterItem = { ...filtersCopy[filterIndex] }
56
88
 
57
89
  // Overwrite filterItem.values since thats what we map through in the editor panel
@@ -99,8 +131,8 @@ export const useFilters = props => {
99
131
  }
100
132
 
101
133
  // If we're on a chart and not using the apply button
102
- if (visualizationConfig.type === 'chart' && visualizationConfig.filterBehavior === 'Filter Change') {
103
- setFilteredData(filterData(newFilters, excludedData))
134
+ if (hasStandardFilterBehavior.includes(visualizationConfig.type) && visualizationConfig.filterBehavior === 'Filter Change') {
135
+ setFilteredData(filterVizData(newFilters, excludedData))
104
136
  }
105
137
  }
106
138
 
@@ -127,8 +159,8 @@ export const useFilters = props => {
127
159
  setFilteredData(newFilters, excludedData)
128
160
  }
129
161
 
130
- if (type === 'chart') {
131
- setFilteredData(filterData(newFilters, excludedData))
162
+ if (hasStandardFilterBehavior.includes(visualizationConfig.type)) {
163
+ setFilteredData(filterVizData(newFilters, excludedData))
132
164
  }
133
165
 
134
166
  setShowApplyButton(false)
@@ -139,19 +171,31 @@ export const useFilters = props => {
139
171
  e.preventDefault()
140
172
 
141
173
  // reset to first item in values array.
142
- newFilters.map(filter => {
143
- filter = handleSorting(filter)
144
- filter.active = filter.values[0]
145
- return filter
174
+ let needsQueryUpdate = false
175
+ const queryParams = getQueryParams()
176
+ newFilters.forEach((filter, i) => {
177
+ if (!filter.values || filter.values.length === 0) {
178
+ filter.values = getUniqueValues(data, filter.columnName)
179
+ }
180
+ newFilters[i].active = handleSorting(filter).values[0]
181
+
182
+ if (filter.setByQueryParameter && queryParams[filter.setByQueryParameter] !== filter.active) {
183
+ queryParams[filter.setByQueryParameter] = filter.active
184
+ needsQueryUpdate = true
185
+ }
146
186
  })
147
187
 
188
+ if (needsQueryUpdate) {
189
+ updateQueryString(queryParams)
190
+ }
191
+
192
+ setConfig({ ...visualizationConfig, filters: newFilters })
193
+
148
194
  if (type === 'map') {
149
195
  setFilteredData(newFilters, excludedData)
150
196
  } else {
151
- setFilteredData(filterData(newFilters, excludedData))
197
+ setFilteredData(filterVizData(newFilters, excludedData))
152
198
  }
153
-
154
- setConfig({ ...visualizationConfig, filters: newFilters })
155
199
  }
156
200
 
157
201
  const filterConstants = {
@@ -161,31 +205,6 @@ export const useFilters = props => {
161
205
  applyText: 'Select the apply button to update the visualization information.'
162
206
  }
163
207
 
164
- const handleSorting = singleFilter => {
165
- const { order } = singleFilter
166
-
167
- const sortAsc = (a, b) => {
168
- return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
169
- }
170
-
171
- const sortDesc = (a, b) => {
172
- return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
173
- }
174
-
175
- if (!order || order === '') {
176
- singleFilter.order = 'asc'
177
- }
178
-
179
- if (order === 'desc') {
180
- singleFilter.values = singleFilter.values.sort(sortDesc)
181
- }
182
-
183
- if (order === 'asc') {
184
- singleFilter.values = singleFilter.values.sort(sortAsc)
185
- }
186
- return singleFilter
187
- }
188
-
189
208
  // prettier-ignore
190
209
  return {
191
210
  handleApplyButton,
@@ -201,11 +220,17 @@ export const useFilters = props => {
201
220
  }
202
221
  }
203
222
 
204
- const Filters = props => {
223
+ type FilterProps = {
224
+ filteredData
225
+ dimensions
226
+ config: Visualization
227
+ }
228
+
229
+ const Filters = (props: FilterProps) => {
205
230
  const { config: visualizationConfig, filteredData, dimensions } = props
206
231
  const { filters, type, general, theme, filterBehavior } = visualizationConfig
207
232
  const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
208
- const [selectedFilter, setSelectedFilter] = useState('')
233
+ const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
209
234
  const id = useId()
210
235
 
211
236
  // useFilters hook provides data and logic for handling various filter functions
@@ -231,7 +256,7 @@ const Filters = props => {
231
256
 
232
257
  useEffect(() => {
233
258
  if (selectedFilter) {
234
- let el = document.getElementById(selectedFilter.id)
259
+ const el = document.getElementById(selectedFilter.id)
235
260
  if (el) el.focus()
236
261
  }
237
262
  }, [changeFilterActive, selectedFilter])
@@ -240,21 +265,21 @@ const Filters = props => {
240
265
 
241
266
  const filterSectionClassList = ['filters-section', type === 'map' ? general.headerColor : visualizationConfig?.visualizationType === 'Spark Line' ? null : theme]
242
267
  // Exterior Section Wrapper
243
- Filters.Section = props => {
268
+ Filters.Section = ({ children }) => {
244
269
  return (
245
270
  visualizationConfig?.filters && (
246
271
  <section className={filterSectionClassList.join(' ')}>
247
272
  <p className='filters-section__intro-text'>
248
273
  {filters?.some(f => f.active) ? filterConstants.introText : ''} {visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
249
274
  </p>
250
- <div className='filters-section__wrapper'>{props.children}</div>
275
+ <div className='filters-section__wrapper'>{children}</div>
251
276
  </section>
252
277
  )
253
278
  )
254
279
  }
255
280
 
256
281
  // Apply/Reset Buttons
257
- Filters.ApplyBehavior = props => {
282
+ Filters.ApplyBehavior = () => {
258
283
  if (filterBehavior !== 'Apply Button') return
259
284
  const applyButtonClasses = [general?.headerColor ? general.headerColor : theme, 'apply']
260
285
  return (
@@ -332,7 +357,7 @@ const Filters = props => {
332
357
  // Remove fromHash if it exists on filters to loop so we can loop nicely
333
358
  delete filtersToLoop.fromHash
334
359
 
335
- return filtersToLoop.map((singleFilter, outerIndex) => {
360
+ return addValuesToFilters<VizFilter>(filtersToLoop, visualizationConfig.data).map((singleFilter: VizFilter, outerIndex) => {
336
361
  if (singleFilter.showDropdown === false) return
337
362
 
338
363
  const values = []
@@ -340,11 +365,11 @@ const Filters = props => {
340
365
  const tabValues = []
341
366
  const tabBarValues = []
342
367
 
343
- const { active, queuedActive, label, filterStyle } = singleFilter
368
+ const { active, queuedActive, label, filterStyle } = singleFilter as VizFilter
344
369
 
345
370
  handleSorting(singleFilter)
346
371
 
347
- singleFilter.values.forEach((filterOption, index) => {
372
+ singleFilter.values?.forEach((filterOption, index) => {
348
373
  const pillClassList = ['pill', active === filterOption ? 'pill--active' : null, theme && theme]
349
374
  const tabClassList = ['tab', active === filterOption && 'tab--active', theme && theme]
350
375
 
@@ -408,6 +433,15 @@ const Filters = props => {
408
433
  {filterStyle === 'pill' && !mobileFilterStyle && <Filters.Pills pills={pillValues} />}
409
434
  {filterStyle === 'tab bar' && !mobileFilterStyle && <Filters.TabBar filter={singleFilter} index={outerIndex} />}
410
435
  {(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown filter={singleFilter} index={outerIndex} label={label} active={queuedActive || active} filters={values} />}
436
+ {filterStyle === 'multi-select' && (
437
+ <MultiSelect
438
+ options={singleFilter.values.map(v => ({ value: v, label: v }))}
439
+ fieldName={outerIndex}
440
+ updateField={(_section, _subSection, fieldName, value) => changeFilterActive(fieldName, value)}
441
+ selected={singleFilter.active as string[]}
442
+ limit={(singleFilter as MultiSelectFilter).selectLimit || 5}
443
+ />
444
+ )}
411
445
  </>
412
446
  </div>
413
447
  )
@@ -437,8 +471,6 @@ Filters.propTypes = {
437
471
  setConfig: PropTypes.func,
438
472
  // exclusions
439
473
  excludedData: PropTypes.array,
440
- // function for filtering the data
441
- filterData: PropTypes.func,
442
474
  dimensions: PropTypes.array
443
475
  }
444
476
 
@@ -0,0 +1,25 @@
1
+ import { Footnote } from '../../types/Footnotes'
2
+ import './footnotes.css'
3
+
4
+ type FootnotesProps = {
5
+ footnotes: Footnote[]
6
+ }
7
+
8
+ const Footnotes: React.FC<FootnotesProps> = ({ footnotes }) => {
9
+ return (
10
+ <footer className='col-12 m-3 mt-1 mb-0'>
11
+ <ul className='cove-footnotes'>
12
+ {footnotes.map((note, i) => {
13
+ return (
14
+ <li key={note.symbol + i} className='mb-1'>
15
+ {note.symbol && <span className='mr-1'>{note.symbol}</span>}
16
+ {note.text}
17
+ </li>
18
+ )
19
+ })}
20
+ </ul>
21
+ </footer>
22
+ )
23
+ }
24
+
25
+ export default Footnotes
@@ -0,0 +1,45 @@
1
+ import EditorWrapper from '../EditorWrapper'
2
+ import Footnotes from './Footnotes'
3
+ import FootnotesEditor from '../EditorPanel/FootnotesEditor'
4
+ import { ViewPort } from '../../types/ViewPort'
5
+ import FootnotesConfig, { Footnote } from '../../types/Footnotes'
6
+ import _ from 'lodash'
7
+ import { useMemo } from 'react'
8
+ import { updateFieldFactory } from '../../helpers/updateFieldFactory'
9
+
10
+ type StandAloneProps = {
11
+ isEditor?: boolean
12
+ visualizationKey: string
13
+ config: FootnotesConfig
14
+ updateConfig?: (config: FootnotesConfig) => void
15
+ viewport?: ViewPort
16
+ }
17
+
18
+ const FootnotesStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, config, viewport, isEditor, updateConfig }) => {
19
+ const updateField = updateFieldFactory<Footnote[]>(config, updateConfig)
20
+ if (isEditor)
21
+ return (
22
+ <EditorWrapper component={FootnotesStandAlone} visualizationKey={visualizationKey} visualizationConfig={config} updateConfig={updateConfig} type={'Footnotes'} viewport={viewport}>
23
+ <FootnotesEditor key={visualizationKey} config={config} updateField={updateField} />
24
+ </EditorWrapper>
25
+ )
26
+
27
+ // get the api footnotes from the config
28
+ const apiFootnotes = useMemo(() => {
29
+ if (config.dataKey && config.dynamicFootnotes) {
30
+ const { symbolColumn, textColumn, orderColumn } = config.dynamicFootnotes
31
+ const configData = config.formattedData || config.data
32
+ const _data = configData.map(row => _.pick(row, [symbolColumn, textColumn, orderColumn]))
33
+ _data.sort((a, b) => a[orderColumn] - b[orderColumn])
34
+ return _data.map(row => ({ symbol: row[symbolColumn], text: row[textColumn] }))
35
+ }
36
+ return []
37
+ }, [config.dynamicFootnotes, config.formattedData, config.data])
38
+
39
+ // get static footnotes from the config.footnotes
40
+ const staticFootnotes = config.staticFootnotes || []
41
+
42
+ return <Footnotes footnotes={[...apiFootnotes, ...staticFootnotes]} />
43
+ }
44
+
45
+ export default FootnotesStandAlone
@@ -0,0 +1,5 @@
1
+ :is(ul).cove-footnotes {
2
+ :is(li) {
3
+ list-style: none;
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ export { default } from './Footnotes'
@@ -601,7 +601,7 @@
601
601
  /* clicking anywhere will focus the input */
602
602
  cursor: text;
603
603
 
604
- span {
604
+ span:not(.cove-tooltip, .cove-icon) {
605
605
  display: inline;
606
606
  }
607
607
  }
@@ -723,6 +723,10 @@
723
723
  margin-right: 5px;
724
724
  }
725
725
 
726
+ .cove-tooltip {
727
+ position: relative;
728
+ }
729
+
726
730
  // tooltips
727
731
  .cove-label + .cove-tooltip {
728
732
  top: 1px;
@@ -731,9 +735,9 @@
731
735
  }
732
736
 
733
737
  .cove-accordion__button .cove-tooltip {
734
- display: inline-flex;
735
- right: 1.5rem;
736
- line-height: inherit;
738
+ // display: inline-flex;
739
+ // right: 1.5rem;
740
+ // line-height: inherit;
737
741
  }
738
742
 
739
743
  .cove-list-group__item .cove-tooltip {
@@ -1,16 +1,19 @@
1
1
  // main visualization wrapper
2
2
  import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
3
- import React, { forwardRef, useRef } from 'react'
3
+ import React, { forwardRef } from 'react'
4
4
  import { Config as DataBiteConfig } from '@cdc/data-bite/src/types/Config'
5
5
  import './visualizations.scss'
6
6
  import { Config as WaffleChartConfig } from '@cdc/waffle-chart/src/types/Config'
7
+ import { MarkupIncludeConfig } from '@cdc/core/types/MarkupInclude'
8
+ import { DashboardFilters } from '@cdc/dashboard/src/types/DashboardFilters'
7
9
 
8
10
  type VisualizationWrapper = {
9
- config: ChartConfig | DataBiteConfig | WaffleChartConfig
10
- isEditor: boolean
11
- currentViewport: string
12
- imageId: string
13
11
  children: React.ReactNode
12
+ config: ChartConfig | DataBiteConfig | WaffleChartConfig | MarkupIncludeConfig | DashboardFilters
13
+ currentViewport?: string
14
+ imageId?: string
15
+ isEditor: boolean
16
+ showEditorPanel?: boolean
14
17
  }
15
18
 
16
19
  const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) => {
@@ -31,6 +34,12 @@ const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) =>
31
34
  classes.push('editor-panel--hidden')
32
35
  }
33
36
 
37
+ if (config.type === 'filtered-text') {
38
+ classes.push('type-filtered-text')
39
+ classes = classes.filter(item => item !== 'cove-component__content')
40
+ return classes
41
+ }
42
+
34
43
  if (config.type === 'chart') {
35
44
  classes.push('type-chart')
36
45
  config?.visualizationType === 'Spark Line' && classes.push(`type-sparkline`)
@@ -59,7 +59,7 @@ const generateMedia = (state, type, elementToCapture) => {
59
59
 
60
60
  switch (type) {
61
61
  case 'image':
62
- html2canvas(baseSvg).then(canvas => {
62
+ html2canvas(baseSvg, {ignoreElements: el => el.className?.indexOf && el.className.search(/download-buttons|download-links|data-table-container/) !== -1}).then(canvas => {
63
63
  saveImageAs(canvas.toDataURL(), filename + '.png')
64
64
  })
65
65
  return
@@ -21,8 +21,8 @@ interface MultiSelectProps {
21
21
  limit?: number
22
22
  }
23
23
 
24
- const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection = null, fieldName, label, options, updateField, selected, limit }) => {
25
- const preselectedItems = options.filter(opt => selected?.includes(opt.value)).slice(0, limit)
24
+ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection = null, fieldName, label, options, updateField, selected = [], limit }) => {
25
+ const preselectedItems = options.filter(opt => selected.includes(opt.value)).slice(0, limit)
26
26
  const [selectedItems, setSelectedItems] = useState<Option[]>(preselectedItems)
27
27
  const [expanded, setExpanded] = useState(false)
28
28
  const multiSelectRef = useRef(null)
@@ -68,22 +68,39 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
68
68
  return (
69
69
  <div ref={multiSelectRef} className='cove-multiselect'>
70
70
  {label && (
71
- <label id={multiID} className='cove-input__label'>
71
+ <span id={multiID} className='edit-label column-heading'>
72
72
  {label}
73
- </label>
73
+ </span>
74
74
  )}
75
75
 
76
76
  <div className='wrapper'>
77
77
  <div className='selected'>
78
78
  {selectedItems.map(item => (
79
- <div key={item.value} aria-labelledby={label ? multiID : undefined} role='button' onClick={() => handleItemRemove(item)} onKeyUp={e => handleItemRemove(item, e)}>
79
+ <div key={item.value} aria-labelledby={label ? multiID : undefined}>
80
80
  {item.label}
81
- <button aria-label='Remove' onClick={() => handleItemRemove(item)}>
81
+ <button
82
+ aria-label='Remove'
83
+ onClick={e => {
84
+ e.preventDefault()
85
+ handleItemRemove(item)
86
+ }}
87
+ onKeyUp={e => {
88
+ handleItemRemove(item, e)
89
+ }}
90
+ >
82
91
  x
83
92
  </button>
84
93
  </div>
85
94
  ))}
86
- <button aria-label={expanded ? 'Collapse' : 'Expand'} aria-labelledby={label ? multiID : undefined} className='expand' onClick={() => setExpanded(!expanded)}>
95
+ <button
96
+ aria-label={expanded ? 'Collapse' : 'Expand'}
97
+ aria-labelledby={label ? multiID : undefined}
98
+ className='expand'
99
+ onClick={e => {
100
+ e.preventDefault()
101
+ setExpanded(!expanded)
102
+ }}
103
+ >
87
104
  <Icon display={expanded ? 'caretDown' : 'caretUp'} style={{ cursor: 'pointer' }} />
88
105
  </button>
89
106
  </div>
@@ -98,11 +115,21 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
98
115
  </Tooltip>
99
116
  )}
100
117
  </div>
101
- <ul className={'dropdown' + (expanded ? '' : ' hide')}>
118
+ <ul className={'dropdown' + (expanded ? '' : ' d-none')}>
102
119
  {options
103
120
  .filter(option => !selectedItems.find(item => item.value === option.value))
104
121
  .map(option => (
105
- <li className='cove-multiselect-li' key={option.value} role='option' tabIndex={0} onClick={() => handleItemSelect(option)} onKeyUp={e => handleItemSelect(option, e)}>
122
+ <li
123
+ className='cove-multiselect-li'
124
+ key={option.value}
125
+ role='option'
126
+ tabIndex={0}
127
+ onClick={e => {
128
+ e.preventDefault()
129
+ handleItemSelect(option, e)
130
+ }}
131
+ onKeyUp={e => handleItemSelect(option, e)}
132
+ >
106
133
  {option.label}
107
134
  </li>
108
135
  ))}
@@ -51,9 +51,6 @@
51
51
  overflow-y: scroll;
52
52
  min-width: 200px;
53
53
  z-index: 4;
54
- &.hide {
55
- display: none;
56
- }
57
54
 
58
55
  :is(li) {
59
56
  cursor: pointer;