@cdc/dashboard 4.24.2 → 4.24.4

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 (52) hide show
  1. package/dist/cdcdashboard.js +128512 -99417
  2. package/examples/chart-data.json +5409 -0
  3. package/examples/full-dash-test.json +14643 -0
  4. package/examples/full-dashboard.json +10036 -0
  5. package/examples/sankey.json +5218 -0
  6. package/index.html +4 -3
  7. package/package.json +11 -10
  8. package/src/CdcDashboard.tsx +129 -124
  9. package/src/CdcDashboardComponent.tsx +316 -441
  10. package/src/DashboardContext.tsx +4 -1
  11. package/src/_stories/Dashboard.stories.tsx +79 -36
  12. package/src/_stories/_mock/api-filter-chart.json +11 -35
  13. package/src/_stories/_mock/api-filter-map.json +17 -31
  14. package/src/_stories/_mock/dashboard-gallery.json +523 -534
  15. package/src/_stories/_mock/multi-viz.json +378 -0
  16. package/src/_stories/_mock/pivot-filter.json +161 -0
  17. package/src/_stories/_mock/standalone-table.json +122 -0
  18. package/src/_stories/_mock/toggle-example.json +4035 -0
  19. package/src/components/DataDesignerModal.tsx +145 -0
  20. package/src/components/EditorWrapper/EditorWrapper.tsx +52 -0
  21. package/src/components/EditorWrapper/editor-wrapper.style.css +13 -0
  22. package/src/components/Filters.tsx +88 -0
  23. package/src/components/Grid.tsx +3 -1
  24. package/src/components/Header/FilterModal.tsx +506 -0
  25. package/src/components/Header/Header.tsx +25 -465
  26. package/src/components/Row.tsx +65 -29
  27. package/src/components/Toggle/Toggle.tsx +36 -0
  28. package/src/components/Toggle/index.tsx +1 -0
  29. package/src/components/Toggle/toggle-style.css +34 -0
  30. package/src/components/VisualizationRow.tsx +174 -0
  31. package/src/components/VisualizationsPanel.tsx +13 -3
  32. package/src/components/Widget.tsx +28 -126
  33. package/src/helpers/filterData.ts +75 -50
  34. package/src/helpers/generateValuesForFilter.ts +2 -12
  35. package/src/helpers/getApiFilterKey.ts +5 -0
  36. package/src/helpers/getFilteredData.ts +39 -0
  37. package/src/helpers/getUpdateConfig.ts +39 -22
  38. package/src/helpers/getVizConfig.ts +31 -0
  39. package/src/helpers/getVizRowColumnLocator.ts +9 -0
  40. package/src/helpers/iconHash.tsx +34 -0
  41. package/src/helpers/tests/filterData.test.ts +149 -0
  42. package/src/images/icon-toggle.svg +1 -0
  43. package/src/scss/grid.scss +10 -3
  44. package/src/scss/main.scss +11 -0
  45. package/src/store/dashboard.actions.ts +35 -3
  46. package/src/store/dashboard.reducer.ts +33 -2
  47. package/src/types/APIFilter.ts +4 -5
  48. package/src/types/ConfigRow.ts +13 -2
  49. package/src/types/DataSet.ts +11 -8
  50. package/src/types/InitialState.ts +2 -1
  51. package/src/types/SharedFilter.ts +6 -3
  52. package/src/types/Tab.ts +1 -0
@@ -0,0 +1,145 @@
1
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
2
+ import DataDesigner from '@cdc/core/components/managers/DataDesigner'
3
+ import { useContext, useMemo, useState } from 'react'
4
+ import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
5
+ import Modal from '@cdc/core/components/ui/Modal'
6
+ import { CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
7
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
8
+ import _ from 'lodash'
9
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
10
+ import DataTransform from '@cdc/core/helpers/DataTransform'
11
+ import { ConfigureData } from '@cdc/core/types/ConfigureData'
12
+ import Icon from '@cdc/core/components/ui/Icon'
13
+ import InputSelect from '@cdc/core/components/inputs/InputSelect'
14
+
15
+ type DataDesignerModalProps = {
16
+ rowIndex: number
17
+ vizKey?: string
18
+ }
19
+
20
+ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, rowIndex }) => {
21
+ const { config } = useContext(DashboardContext)
22
+ const { overlay } = useGlobalContext()
23
+ const transform = new DataTransform()
24
+ const dispatch = useContext(DashboardDispatchContext)
25
+ const [canContinue, setCanContinue] = useState(false)
26
+ const [useRow, setUseRow] = useState(!vizKey)
27
+ const [multiViz, setMultiViz] = useState(!!config.rows[rowIndex].multiVizColumn)
28
+
29
+ const configureData = useMemo(() => {
30
+ if (vizKey && !useRow) {
31
+ return config.visualizations[vizKey]
32
+ }
33
+ return config.rows[rowIndex]
34
+ }, [config.visualizations, config.rows, vizKey, rowIndex, useRow])
35
+
36
+ const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
37
+ if (vizKey && !useRow) {
38
+ dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
39
+ } else {
40
+ dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
41
+ }
42
+ }
43
+
44
+ const changeDataset = ({ target: { value } }) => {
45
+ const newConfigureData = { dataDescription: {}, formattedData: undefined, dataKey: value }
46
+
47
+ updateConfigureData(newConfigureData)
48
+ }
49
+
50
+ const updateDescriptionProp = async (key, value) => {
51
+ const datasetKey = configureData.dataKey
52
+ const { data, dataUrl } = config.datasets[datasetKey]
53
+ let newData = data
54
+ if (!data && dataUrl) {
55
+ newData = await fetchRemoteData(dataUrl)
56
+ newData = transform.autoStandardize(newData)
57
+ }
58
+
59
+ const dataDescription = { ...configureData.dataDescription, [key]: value }
60
+
61
+ const newConfigureData = { data: newData, dataDescription, formattedData: transform.developerStandardize(newData, dataDescription) }
62
+
63
+ updateConfigureData(newConfigureData)
64
+ setCanContinue(true)
65
+ }
66
+
67
+ const setMultiVizColumn = (column: string) => {
68
+ if (column !== '') {
69
+ dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: { multiVizColumn: column } } })
70
+ setCanContinue(true)
71
+ }
72
+ }
73
+
74
+ return (
75
+ <Modal>
76
+ <Modal.Content>
77
+ <div className='dataset-selector-container'>
78
+ Select a dataset:&nbsp;
79
+ <select className='dataset-selector' value={configureData.dataKey || ''} onChange={changeDataset}>
80
+ <option value=''>Select a dataset</option>
81
+ {config.datasets && Object.keys(config.datasets).map(datasetKey => <option key={datasetKey}>{datasetKey}</option>)}
82
+ </select>
83
+ {vizKey && (
84
+ <CheckBox
85
+ label='Apply To Row'
86
+ value={useRow}
87
+ updateField={(section, subsection, fieldName, value) => {
88
+ setUseRow(value)
89
+ changeDataset({ target: { value: '' } })
90
+ }}
91
+ />
92
+ )}
93
+ </div>
94
+ {configureData.dataKey && (
95
+ <DataDesigner
96
+ {...{
97
+ configureData,
98
+ visualizationKey: vizKey,
99
+ updateDescriptionProp
100
+ }}
101
+ />
102
+ )}
103
+ {useRow && !!configureData.dataKey ? (
104
+ !multiViz ? (
105
+ <CheckBox
106
+ label='Configure Multiple Visualizations'
107
+ value={multiViz}
108
+ tooltip={
109
+ <Tooltip style={{ textTransform: 'none' }}>
110
+ <Tooltip.Target>
111
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
112
+ </Tooltip.Target>
113
+ <Tooltip.Content>
114
+ <p>You can select a column where for each unique value in the column the configuration for the row will be repeated in to the data preview.</p>
115
+ </Tooltip.Content>
116
+ </Tooltip>
117
+ }
118
+ updateField={(section, subsection, fieldName, value) => {
119
+ if (canContinue && value === true) setCanContinue(false)
120
+ setMultiViz(value)
121
+ }}
122
+ />
123
+ ) : (
124
+ <InputSelect
125
+ options={Object.keys(config.datasets[configureData.dataKey]?.data[0] || {})}
126
+ value={config.rows[rowIndex].multiVizColumn}
127
+ label='Multi-Visualization Column'
128
+ initial='--Select--'
129
+ fieldName=''
130
+ updateField={(section, subsection, fieldName, value) => setMultiVizColumn(value)}
131
+ required
132
+ />
133
+ )
134
+ ) : (
135
+ <></>
136
+ )}
137
+ {canContinue && (
138
+ <button style={{ margin: '1em', display: 'block' }} className='cove-button' onClick={() => overlay?.actions.toggleOverlay()}>
139
+ Continue
140
+ </button>
141
+ )}
142
+ </Modal.Content>
143
+ </Modal>
144
+ )
145
+ }
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import { Accordion } from 'react-accessible-accordion'
3
+ import Header from '../Header'
4
+ import { Visualization } from '@cdc/core/types/Visualization'
5
+ import { ViewPort } from '@cdc/core/types/ViewPort'
6
+ import './editor-wrapper.style.css'
7
+
8
+ type StandAloneComponentProps = {
9
+ visualizationKey: string
10
+ config: Visualization
11
+ isEditor: boolean
12
+ setConfig: Function
13
+ isDashboard: boolean
14
+ configUrl: string
15
+ setEditing: Function
16
+ hostname: string
17
+ viewport?: ViewPort
18
+ }
19
+
20
+ type EditorProps = {
21
+ component: React.JSXElementConstructor<StandAloneComponentProps>
22
+ type: string
23
+ visualizationKey: string
24
+ visualizationConfig: Visualization
25
+ updateConfig: Function
26
+ viewport?: ViewPort
27
+ }
28
+
29
+ const EditorWrapper: React.FC<React.PropsWithChildren<EditorProps>> = ({ children, visualizationKey, visualizationConfig, type, component: Component, updateConfig, viewport }) => {
30
+ const [displayPanel, setDisplayPanel] = React.useState(true)
31
+ return (
32
+ <>
33
+ <Header visualizationKey={visualizationKey} subEditor={type} />
34
+ <div className='editor-wrapper'>
35
+ <button className={`editor-toggle ${displayPanel ? '' : 'collapsed'}`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={() => setDisplayPanel(!displayPanel)} />
36
+ <section className={`${displayPanel ? '' : 'hidden'} editor-panel cove`}>
37
+ <div aria-level={2} role='heading' className='heading-2'>
38
+ Configure {type}
39
+ </div>
40
+ <form>
41
+ <Accordion allowZeroExpanded={true}>{children}</Accordion>
42
+ </form>
43
+ </section>
44
+ <div className='preview-wrapper'>
45
+ <Component visualizationKey={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true} configUrl={undefined} setEditing={undefined} hostname={undefined} viewport={viewport} />
46
+ </div>
47
+ </div>
48
+ </>
49
+ )
50
+ }
51
+
52
+ export default EditorWrapper
@@ -0,0 +1,13 @@
1
+ .editor-wrapper {
2
+ --editorPanelWidth: 350px;
3
+ position: relative;
4
+ .editor-panel {
5
+ :is(form) {
6
+ border-right: var(--lightGray) 1px solid;
7
+ flex-grow: 1;
8
+ }
9
+ }
10
+ .preview-wrapper {
11
+ padding-left: var(--editorPanelWidth);
12
+ }
13
+ }
@@ -0,0 +1,88 @@
1
+ import MultiSelect from '@cdc/core/components/MultiSelect'
2
+ import { getApiFilterKey } from '../helpers/getApiFilterKey'
3
+ import { SharedFilter } from '../types/SharedFilter'
4
+
5
+ export type DropdownOptions = Record<'value' | 'text', string>[]
6
+
7
+ export type APIFilterDropdowns = {
8
+ // null means still loading
9
+ [filtername: string]: null | DropdownOptions
10
+ }
11
+
12
+ type FilterProps = {
13
+ hide?: number[]
14
+ filters: SharedFilter[]
15
+ apiFilterDropdowns: APIFilterDropdowns
16
+ handleOnChange: Function
17
+ }
18
+
19
+ const Filters: React.FC<FilterProps> = ({ hide, filters, apiFilterDropdowns, handleOnChange }) => {
20
+ const updateField = (_section, _subsection, fieldName, value) => {
21
+ handleOnChange(fieldName, value)
22
+ }
23
+ return (
24
+ <>
25
+ {filters.map((singleFilter, filterIndex) => {
26
+ if ((singleFilter.type !== 'urlfilter' && !singleFilter.showDropdown) || (hide && hide.indexOf(filterIndex) !== -1)) return <></>
27
+ const values: JSX.Element[] = []
28
+ const multiValues = []
29
+ if (singleFilter.resetLabel) {
30
+ values.push(
31
+ <option key={`${singleFilter.resetLabel}-option`} value={singleFilter.resetLabel}>
32
+ {singleFilter.resetLabel}
33
+ </option>
34
+ )
35
+ }
36
+ const _key = singleFilter.apiFilter ? getApiFilterKey(singleFilter.apiFilter) : undefined
37
+ if (_key && apiFilterDropdowns[_key]) {
38
+ // URL Filter
39
+ apiFilterDropdowns[_key].forEach(({ text, value }, index) => {
40
+ values.push(
41
+ <option key={`${value}-option-${index}`} value={value}>
42
+ {text}
43
+ </option>
44
+ )
45
+ })
46
+ } else {
47
+ // Data Filter
48
+ singleFilter.values?.forEach((filterOption, index) => {
49
+ const labeledOpt = singleFilter.labels && singleFilter.labels[filterOption]
50
+ values.push(
51
+ <option key={`${singleFilter.key}-option-${index}`} value={filterOption}>
52
+ {labeledOpt || filterOption}
53
+ </option>
54
+ )
55
+ multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
56
+ })
57
+ }
58
+
59
+ return (
60
+ <div className='cove-dashboard-filters' key={`${singleFilter.key}-filtersection-${filterIndex}`}>
61
+ <section className='dashboard-filters-section'>
62
+ {!singleFilter.pivot ? (
63
+ <>
64
+ <label htmlFor={`filter-${filterIndex}`}>{singleFilter.key}</label>
65
+ <select
66
+ id={`filter-${filterIndex}`}
67
+ className='filter-select'
68
+ data-index='0'
69
+ value={singleFilter.queuedActive || singleFilter.active}
70
+ onChange={val => {
71
+ handleOnChange(filterIndex, val.target.value)
72
+ }}
73
+ >
74
+ {values}
75
+ </select>
76
+ </>
77
+ ) : (
78
+ <MultiSelect label={singleFilter.key} options={multiValues} fieldName={filterIndex} updateField={updateField} selected={singleFilter.active as string[]} limit={singleFilter.selectLimit || 5} />
79
+ )}
80
+ </section>
81
+ </div>
82
+ )
83
+ })}
84
+ </>
85
+ )
86
+ }
87
+
88
+ export default Filters
@@ -2,6 +2,7 @@ import React, { useContext } from 'react'
2
2
  import Row from './Row'
3
3
 
4
4
  import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
5
+ import { ConfigRow } from '../types/ConfigRow'
5
6
 
6
7
  const Grid = () => {
7
8
  const { config } = useContext(DashboardContext)
@@ -10,9 +11,10 @@ const Grid = () => {
10
11
  const dispatch = useContext(DashboardDispatchContext)
11
12
  const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
12
13
  const addRow = () => {
14
+ const blankRow: Partial<ConfigRow> = { columns: [{ width: 12 }] }
13
15
  updateConfig({
14
16
  ...config,
15
- rows: [...rows, [{ width: 12 }, { equalHeight: false }, {}, {}]],
17
+ rows: [...rows, blankRow],
16
18
  uuid: Date.now()
17
19
  })
18
20
  }