@cdc/dashboard 4.23.11 → 4.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cdcdashboard.js +109007 -98738
  2. package/examples/DEV-6574.json +2224 -0
  3. package/examples/filters/Alabama.json +72 -0
  4. package/examples/filters/Alaska.json +1737 -0
  5. package/examples/filters/Arkansas.json +4713 -0
  6. package/examples/filters/California.json +212 -0
  7. package/examples/filters/Colorado.json +1500 -0
  8. package/examples/filters/Connecticut.json +559 -0
  9. package/examples/filters/Delaware.json +63 -0
  10. package/examples/filters/DistrictofColumbia.json +63 -0
  11. package/examples/filters/Florida.json +4217 -0
  12. package/examples/filters/States.json +146 -0
  13. package/examples/test.json +752 -0
  14. package/examples/zika.json +2274 -0
  15. package/index.html +5 -3
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +124 -963
  18. package/src/CdcDashboardComponent.tsx +903 -0
  19. package/src/_stories/Dashboard.stories.tsx +2 -2
  20. package/src/components/Column.tsx +15 -12
  21. package/src/components/Header/Header.tsx +694 -0
  22. package/src/components/Header/index.tsx +1 -676
  23. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +106 -0
  24. package/src/components/MultiConfigTabs/MultiTabs.tsx +30 -0
  25. package/src/components/MultiConfigTabs/index.tsx +8 -0
  26. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +32 -0
  27. package/src/components/Widget.tsx +25 -9
  28. package/src/helpers/filterData.ts +73 -73
  29. package/src/helpers/generateValuesForFilter.ts +25 -29
  30. package/src/helpers/getUpdateConfig.ts +6 -2
  31. package/src/helpers/processData.ts +13 -0
  32. package/src/helpers/processDataLegacy.ts +14 -0
  33. package/src/{index.jsx → index.tsx} +2 -2
  34. package/src/scss/editor-panel.scss +14 -11
  35. package/src/scss/grid.scss +4 -6
  36. package/src/scss/main.scss +2 -8
  37. package/src/store/dashboard.actions.ts +10 -4
  38. package/src/store/dashboard.reducer.ts +74 -3
  39. package/src/types/ConfigRow.ts +6 -0
  40. package/src/types/Dashboard.ts +11 -0
  41. package/src/types/DashboardConfig.ts +23 -0
  42. package/src/types/InitialState.ts +10 -0
  43. package/src/types/MultiDashboard.ts +11 -0
  44. package/src/types/SharedFilter.ts +31 -20
  45. package/src/types/Config.ts +0 -27
@@ -0,0 +1,106 @@
1
+ import { createRef, useContext, useMemo, useState } from 'react'
2
+ import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
3
+ import Modal from '@cdc/core/components/ui/Modal'
4
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
5
+ import './multiconfigtabs.styles.css'
6
+
7
+ const AreYouSure = deleteCallback => {
8
+ return (
9
+ <Modal>
10
+ <Modal.Content>
11
+ <p>Are you sure you want to delete this dashboard? </p>
12
+ <button className='btn btn-danger' onClick={deleteCallback}>
13
+ DELETE
14
+ </button>
15
+ </Modal.Content>
16
+ </Modal>
17
+ )
18
+ }
19
+
20
+ const Tab = ({ name, handleClick, tabs, index, active }) => {
21
+ const [editing, setEditing] = useState(false)
22
+ const dispatch = useContext(DashboardDispatchContext)
23
+ const { overlay } = useGlobalContext()
24
+ const inputRef = createRef<HTMLInputElement>()
25
+
26
+ const onBlur = () => {
27
+ const newVal = inputRef.current.value
28
+ const sameName = newVal === name
29
+ const blankName = !newVal
30
+ const duplicateName = tabs.includes(newVal)
31
+ if (!sameName && !blankName && !duplicateName) {
32
+ dispatch({ type: 'RENAME_DASHBOARD_TAB', payload: { current: name, new: newVal } })
33
+ }
34
+ setEditing(false)
35
+ }
36
+
37
+ const onClick = e => {
38
+ // ignore click on delete button
39
+ if (e.target.className === 'remove') return
40
+ if (active) {
41
+ setEditing(true)
42
+ } else {
43
+ handleClick()
44
+ }
45
+ }
46
+
47
+ const handleRemove = () => {
48
+ const deleteCallback = () => {
49
+ dispatch({ type: 'REMOVE_MULTIDASHBOARD_AT_INDEX', payload: index })
50
+ overlay?.actions.toggleOverlay(false)
51
+ }
52
+ overlay?.actions.openOverlay(AreYouSure(deleteCallback))
53
+ }
54
+
55
+ const handleReorder = (index: number, moveTo: -1 | 1) => {
56
+ const newIndex = index + moveTo
57
+ const inbounds = newIndex > -1 && newIndex <= tabs.length - 1
58
+ if (inbounds) {
59
+ dispatch({ type: 'REORDER_MULTIDASHBOARDS', payload: { currentIndex: index, newIndex: index + moveTo } })
60
+ }
61
+ }
62
+
63
+ const canMoveLeft = index !== 0
64
+ const canMoveRight = index <= tabs.length - 2
65
+
66
+ return (
67
+ <li className='nav-item'>
68
+ <a className={`edit nav-link${active ? ' active' : ''}`} aria-current={active ? 'page' : null} href='#' onClick={onClick}>
69
+ {canMoveLeft && <button onClick={() => handleReorder(index, -1)}>{'<'}</button>}
70
+ {editing ? <input type='text' defaultValue={name} onBlur={onBlur} ref={inputRef} /> : <>{name}</>}
71
+ {canMoveRight && <button onClick={() => handleReorder(index, 1)}>{'>'}</button>}
72
+ <button className='remove' onClick={handleRemove}>
73
+ X
74
+ </button>
75
+ </a>
76
+ </li>
77
+ )
78
+ }
79
+
80
+ const MultiConfigTabs = () => {
81
+ const { config } = useContext(DashboardContext)
82
+ const dispatch = useContext(DashboardDispatchContext)
83
+ const tabs = useMemo<string[]>(() => (config.multiDashboards || []).map(({ label }) => label), [config.multiDashboards])
84
+ const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
85
+
86
+ const saveAndLoad = (indexToSwitchTo: number) => {
87
+ dispatch({ type: 'SAVE_CURRENT_CHANGES' })
88
+ dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
89
+ }
90
+
91
+ if (!config.multiDashboards) return null
92
+ return (
93
+ <ul className='nav nav-tabs multi-config-tabs'>
94
+ {tabs.map((tab, index) => (
95
+ <Tab key={tab + index} name={tab} tabs={tabs} index={index} handleClick={() => saveAndLoad(index)} active={index === activeTab} />
96
+ ))}
97
+ <li className='nav-item'>
98
+ <a className='nav-link add' href='#' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
99
+ +
100
+ </a>
101
+ </li>
102
+ </ul>
103
+ )
104
+ }
105
+
106
+ export default MultiConfigTabs
@@ -0,0 +1,30 @@
1
+ import { useContext, useMemo } from 'react'
2
+ import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
3
+
4
+ import './multiconfigtabs.styles.css'
5
+
6
+ const MultiTabs = () => {
7
+ const { config } = useContext(DashboardContext)
8
+ const dispatch = useContext(DashboardDispatchContext)
9
+ const tabs = useMemo<string[]>(() => (config.multiDashboards || []).map(({ label }) => label), [config.multiDashboards])
10
+ const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
11
+
12
+ const load = (indexToSwitchTo: number) => {
13
+ dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
14
+ }
15
+
16
+ if (!config.multiDashboards) return null
17
+ return (
18
+ <ul className='nav nav-tabs multi-config-tabs'>
19
+ {tabs.map((tab, index) => (
20
+ <li className='nav-item'>
21
+ <a className={`nav-link${activeTab === index ? ' active' : ''}`} aria-current={activeTab === index ? 'page' : null} href='#' onClick={() => load(index)}>
22
+ {tab}
23
+ </a>
24
+ </li>
25
+ ))}
26
+ </ul>
27
+ )
28
+ }
29
+
30
+ export default MultiTabs
@@ -0,0 +1,8 @@
1
+ import MultiConfigTabs from './MultiConfigTabs'
2
+ import MultiTabs from './MultiTabs'
3
+
4
+ const Tabs: React.FC<{ isEditor: boolean }> = ({ isEditor = false }) => {
5
+ return isEditor ? <MultiConfigTabs /> : <MultiTabs />
6
+ }
7
+
8
+ export default Tabs
@@ -0,0 +1,32 @@
1
+ .multi-config-tabs {
2
+ .nav-link {
3
+ font-weight: 400;
4
+ display: block;
5
+ padding: 0.5rem 1rem;
6
+ :is(button) {
7
+ display: none;
8
+ background: none;
9
+ }
10
+ &:hover {
11
+ :is(button) {
12
+ display: inline-block;
13
+ color: var(--gray);
14
+ &:hover {
15
+ color: var(--black);
16
+ font-weight: bold;
17
+ }
18
+ }
19
+ }
20
+ }
21
+ .remove {
22
+ color: var(--gray);
23
+ &:hover {
24
+ color: var(--black);
25
+ font-weight: bold;
26
+ }
27
+ }
28
+ .add {
29
+ font-weight: 800;
30
+ padding: 0.5rem 1rem;
31
+ }
32
+ }
@@ -10,7 +10,7 @@ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
10
10
  import DataDesigner from '@cdc/core/components/managers/DataDesigner'
11
11
  import Icon from '@cdc/core/components/ui/Icon'
12
12
  import Modal from '@cdc/core/components/ui/Modal'
13
- import { Visualization } from '../types/Config'
13
+ import { Visualization } from '@cdc/core/types/Visualization'
14
14
 
15
15
  const iconHash = {
16
16
  'data-bite': <Icon display='databite' base />,
@@ -87,13 +87,16 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
87
87
  updateConfig({ ...config, rows, visualizations })
88
88
  }
89
89
 
90
- const [{ isDragging, ...collected }, drag] = useDrag({
91
- type: 'vis-widget',
92
- end: handleWidgetMove,
93
- collect: monitor => ({
94
- isDragging: monitor.isDragging()
95
- })
96
- })
90
+ const [{ isDragging, ...collected }, drag] = useDrag(
91
+ {
92
+ type: 'vis-widget',
93
+ end: handleWidgetMove,
94
+ collect: monitor => ({
95
+ isDragging: monitor.isDragging()
96
+ })
97
+ },
98
+ [config.activeDashboard, config.rows]
99
+ )
97
100
 
98
101
  const deleteWidget = () => {
99
102
  if (!data) return
@@ -266,6 +269,19 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
266
269
  }
267
270
  }, [data?.openModal])
268
271
 
272
+ let isConfigurationReady = false;
273
+ if(type === 'markup-include' || type === 'filter-dropdowns'){
274
+ isConfigurationReady = true;
275
+ } else if(data && data.formattedData) {
276
+ isConfigurationReady = true;
277
+ } else if(data && data.dataKey && data.dataDescription && config.datasets[data.dataKey]){
278
+ let formattedDataAttempt = transform.autoStandardize(config.datasets[data.dataKey].data);
279
+ formattedDataAttempt = transform.developerStandardize(formattedDataAttempt, data.dataDescription);
280
+ if(formattedDataAttempt){
281
+ isConfigurationReady = true;
282
+ }
283
+ }
284
+
269
285
  return (
270
286
  <>
271
287
  <div className='widget' ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }} {...collected}>
@@ -273,7 +289,7 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
273
289
  <div className='widget__content'>
274
290
  {data?.rowIdx !== undefined && (
275
291
  <div className='widget-menu'>
276
- {((data.dataKey && data.dataDescription && (data.formattedData || (config.datasets[data.dataKey] && transform.autoStandardize(config.datasets[data.dataKey].data)))) || type === 'markup-include') && type !== 'filter-dropdowns' && (
292
+ {isConfigurationReady && (
277
293
  <button title='Configure Visualization' className='btn btn-configure' onClick={editWidget}>
278
294
  {iconHash['tools']}
279
295
  </button>
@@ -1,73 +1,73 @@
1
- import { SharedFilter } from '../types/SharedFilter'
2
- import { generateValuesForFilter } from './generateValuesForFilter'
3
-
4
- const findFilterTier = (filters: SharedFilter[], sharedFilter: SharedFilter) => {
5
- if (!sharedFilter.parents?.length) {
6
- return 1
7
- } else {
8
- let parent = filters.find(filter => sharedFilter.parents!.includes(filter.key))
9
- if (!parent) return 1
10
- return 1 + findFilterTier(filters, parent)
11
- }
12
- }
13
-
14
- export const filterData = (filters: SharedFilter[], _data: Object[], filterBehavior) => {
15
- if (_data) {
16
- let maxTier = 1
17
- filters.forEach(sharedFilter => {
18
- sharedFilter.tier = findFilterTier(filters, sharedFilter)
19
- })
20
-
21
- filters.forEach(sharedFilter => {
22
- if (sharedFilter.tier && sharedFilter.tier > maxTier) {
23
- maxTier = sharedFilter.tier
24
- }
25
- })
26
-
27
- let filteredData = _data
28
- // TODO triple loop??
29
- for (let i = 0; i < maxTier; i++) {
30
- let filteredDataSubTier: any[] = []
31
-
32
- filteredData.forEach(row => {
33
- let add = true
34
-
35
- filters.forEach(filter => {
36
- // eslint-disable-next-line eqeqeq
37
- if (filter.type !== 'urlfilter' && ((!filter.tier && i === 0) || filter.tier === i + 1) && filter.active && row[filter.columnName!] != filter.active) {
38
- add = false
39
- }
40
- })
41
-
42
- if (add) filteredDataSubTier.push(row)
43
- })
44
-
45
- filters.forEach(sharedFilter => {
46
- if (sharedFilter.tier && sharedFilter.tier === i + 2) {
47
- sharedFilter.values = generateValuesForFilter(sharedFilter.columnName, { data: filteredDataSubTier }, filterBehavior)
48
- if (sharedFilter.values.length > 0 && (!sharedFilter.active || sharedFilter.values.indexOf(sharedFilter.active) === -1)) {
49
- sharedFilter.active = sharedFilter.values[0]
50
- }
51
- }
52
- })
53
-
54
- filteredData = filteredDataSubTier
55
- }
56
-
57
- let filteredDataSubTier: any[] = []
58
- filteredData.forEach(row => {
59
- let add = true
60
-
61
- filters.forEach(filter => {
62
- // eslint-disable-next-line eqeqeq
63
- if (filter.type !== 'urlfilter' && filter.tier && filter.tier === maxTier - 1 && filter.active && row[filter.columnName!] != filter.active) {
64
- add = false
65
- }
66
- })
67
-
68
- if (add) filteredDataSubTier.push(row)
69
- })
70
-
71
- return filteredDataSubTier
72
- }
73
- }
1
+ import { SharedFilter } from '../types/SharedFilter'
2
+ import { generateValuesForFilter } from './generateValuesForFilter'
3
+
4
+ const findFilterTier = (filters: SharedFilter[], sharedFilter: SharedFilter) => {
5
+ if (!sharedFilter.parents?.length) {
6
+ return 1
7
+ } else {
8
+ let parent = filters.find(filter => sharedFilter.parents!.includes(filter.key))
9
+ if (!parent) return 1
10
+ return 1 + findFilterTier(filters, parent)
11
+ }
12
+ }
13
+
14
+ export const filterData = (filters: SharedFilter[], _data: Object[], filterBehavior) => {
15
+ if (_data) {
16
+ let maxTier = 1
17
+ filters.forEach(sharedFilter => {
18
+ sharedFilter.tier = findFilterTier(filters, sharedFilter)
19
+ })
20
+
21
+ filters.forEach(sharedFilter => {
22
+ if (sharedFilter.tier && sharedFilter.tier > maxTier) {
23
+ maxTier = sharedFilter.tier
24
+ }
25
+ })
26
+
27
+ let filteredData = _data
28
+ // TODO triple loop??
29
+ for (let i = 0; i < maxTier; i++) {
30
+ let filteredDataSubTier: any[] = []
31
+
32
+ filteredData.forEach(row => {
33
+ let add = true
34
+
35
+ filters.forEach(filter => {
36
+ // eslint-disable-next-line eqeqeq
37
+ if (filter.type !== 'urlfilter' && ((!filter.tier && i === 0) || filter.tier === i + 1) && (filter.queuedActive || filter.active) && row[filter.columnName!] != (filter.queuedActive || filter.active)) {
38
+ add = false
39
+ }
40
+ })
41
+
42
+ if (add) filteredDataSubTier.push(row)
43
+ })
44
+
45
+ filters.forEach(sharedFilter => {
46
+ if (sharedFilter.tier && sharedFilter.tier === i + 2) {
47
+ sharedFilter.values = generateValuesForFilter(sharedFilter.columnName, { data: filteredDataSubTier }, filterBehavior)
48
+ if (sharedFilter.values.length > 0 && (!sharedFilter.active || sharedFilter.values.indexOf(sharedFilter.active) === -1)) {
49
+ sharedFilter.active = sharedFilter.values[0]
50
+ }
51
+ }
52
+ })
53
+
54
+ filteredData = filteredDataSubTier
55
+ }
56
+
57
+ let filteredDataSubTier: any[] = []
58
+ filteredData.forEach(row => {
59
+ let add = true
60
+
61
+ filters.forEach(filter => {
62
+ // eslint-disable-next-line eqeqeq
63
+ if (filter.type !== 'urlfilter' && filter.tier && filter.tier === maxTier - 1 && (filter.queuedActive || filter.active) && row[filter.columnName!] != (filter.queuedActive || filter.active)) {
64
+ add = false
65
+ }
66
+ })
67
+
68
+ if (add) filteredDataSubTier.push(row)
69
+ })
70
+
71
+ return filteredDataSubTier
72
+ }
73
+ }
@@ -1,29 +1,25 @@
1
- import { FilterBehavior } from '../components/Header'
2
-
3
- // Gets filter values from API response
4
- export const generateValuesForAPIFilter = (columnName, _data): string[] => {
5
- type Row = { [key: string]: any }
6
- return Object.values(_data)
7
- .filter(row => row && !!(row as Row)[columnName])
8
- .map(row => (row as Row)[columnName])
9
- }
10
-
11
- // Gets filter values from dataset
12
- export const generateValuesForFilter = (columnName, _data, filterBehavior) => {
13
- if (filterBehavior === FilterBehavior.Apply) {
14
- return generateValuesForAPIFilter(columnName, _data)
15
- }
16
-
17
- const values: string[] = []
18
-
19
- Object.keys(_data).forEach(key => {
20
- _data[key].forEach(row => {
21
- const value = row[columnName]
22
- if (value && false === values.includes(value)) {
23
- values.push(value)
24
- }
25
- })
26
- })
27
-
28
- return values
29
- }
1
+ import { FilterBehavior } from '../components/Header/Header'
2
+
3
+ // Gets filter values from API response
4
+ export const generateValuesForAPIFilter = (columnName, _data): string[] => {
5
+ type Row = { [key: string]: any }
6
+ return Object.values(_data)
7
+ .filter(row => row && !!(row as Row)[columnName])
8
+ .map(row => (row as Row)[columnName])
9
+ }
10
+
11
+ // Gets filter values from dataset
12
+ export const generateValuesForFilter = (columnName, _data, filterBehavior) => {
13
+ const values: string[] = []
14
+
15
+ Object.keys(_data).forEach(key => {
16
+ _data[key]?.forEach(row => {
17
+ const value = row[columnName]
18
+ if (value && false === values.includes(value)) {
19
+ values.push(value)
20
+ }
21
+ })
22
+ })
23
+
24
+ return values
25
+ }
@@ -1,12 +1,16 @@
1
1
  import { DashboardState } from '../store/dashboard.reducer'
2
- import { Config } from '../types/Config'
2
+ import { DashboardConfig as Config, DashboardConfig } from '../types/DashboardConfig'
3
3
  import { filterData } from './filterData'
4
4
  import { generateValuesForFilter } from './generateValuesForFilter'
5
5
  import { getFormattedData } from './getFormattedData'
6
6
  import { getVizKeys } from './getVizKeys'
7
7
 
8
+ type UpdateState = Omit<DashboardState, 'config'> & {
9
+ config?: DashboardConfig
10
+ }
11
+
8
12
  export const getUpdateConfig =
9
- (state: DashboardState) =>
13
+ (state: UpdateState) =>
10
14
  (newConfig, dataOverride?: Object): [Config, Object] => {
11
15
  let newFilteredData = {}
12
16
  let visualizationKeys = getVizKeys(newConfig)
@@ -0,0 +1,13 @@
1
+ import { FilterBehavior } from '../components/Header/Header'
2
+ import { DataSet } from '../types/DataSet'
3
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
4
+ import { getFormattedData } from './getFormattedData'
5
+
6
+ export const processData = async (dataSet: DataSet, filterBehavior) => {
7
+ if (dataSet.dataUrl && filterBehavior !== FilterBehavior.Apply) {
8
+ const dataset = await fetchRemoteData(`${dataSet.dataUrl}`)
9
+ return getFormattedData(dataset, dataSet.dataDescription)
10
+ }
11
+
12
+ return dataSet.formattedData || dataSet.data
13
+ }
@@ -0,0 +1,14 @@
1
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
2
+ import { getFormattedData } from './getFormattedData'
3
+
4
+ export const processDataLegacy = async (response: any) => {
5
+ let dataset = response.formattedData || response.data
6
+
7
+ if (response.dataUrl) {
8
+ dataset = await fetchRemoteData(`${response.dataUrl}`)
9
+
10
+ dataset = getFormattedData(dataset, response.dataDescription)
11
+ }
12
+
13
+ return dataset
14
+ }
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
 
4
- import CdcDashboard from './CdcDashboard'
4
+ import MultiDashboardWrapper from './CdcDashboard'
5
5
 
6
6
  let isEditor = window.location.href.includes('editor=true')
7
7
  let isDebug = window.location.href.includes('debug=true')
@@ -10,6 +10,6 @@ let domContainer = document.getElementsByClassName('react-container')[0]
10
10
 
11
11
  ReactDOM.createRoot(domContainer).render(
12
12
  <React.StrictMode>
13
- <CdcDashboard configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} isDebug={isDebug} />
13
+ <MultiDashboardWrapper configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} isDebug={isDebug} />
14
14
  </React.StrictMode>
15
15
  )
@@ -3,13 +3,17 @@
3
3
  .editor-panel + .cdc-dashboard-inner-container,
4
4
  .editor-heading + .cdc-open-viz-module {
5
5
  position: relative;
6
- top: 3em;
7
- .editor-panel {
8
- top: 3em;
9
- }
10
- .editor-toggle {
11
- top: 3.5em;
12
- }
6
+ min-height: 80vh;
7
+ }
8
+
9
+ .header-container {
10
+ display: flex;
11
+ position: sticky;
12
+ top: 0;
13
+ flex-direction: column;
14
+ width: calc(350px + 1em);
15
+ max-height: 95vh;
16
+ z-index: 3;
13
17
  }
14
18
 
15
19
  .editor-panel {
@@ -83,12 +87,12 @@
83
87
  background: #fff;
84
88
  width: $editorWidth;
85
89
  overflow-y: overlay;
86
- position: fixed;
90
+ position: absolute;
87
91
  z-index: 8;
88
92
  display: flex;
89
93
  flex-direction: column;
90
94
  left: 0;
91
- top: 48px;
95
+ top: 0;
92
96
  bottom: 0;
93
97
  .accordion__heading {
94
98
  background: $lightestGray;
@@ -587,14 +591,13 @@
587
591
  color: #000;
588
592
  font-size: 1em;
589
593
  border: 0;
590
- position: fixed;
594
+ position: absolute;
591
595
  z-index: 99;
592
596
  transition: 0.1s background;
593
597
  cursor: pointer;
594
598
  width: 25px;
595
599
  height: 25px;
596
600
  left: 307px;
597
- top: calc(3em + 10px);
598
601
  box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
599
602
  &:before {
600
603
  top: 43%;
@@ -3,18 +3,17 @@ $red: #f74242;
3
3
 
4
4
  .layout-container {
5
5
  display: flex;
6
+ margin-top: -80vh;
7
+ margin-left: calc($editorWidth + 1em);
6
8
  }
7
9
 
8
10
  .visualizations-panel {
9
11
  background-color: #fff;
10
12
  padding: 1em;
11
- width: 350px;
13
+ width: $editorWidth;
12
14
  border-right: #c7c7c7 1px solid;
13
- position: fixed;
14
- top: 6em;
15
- bottom: 0;
16
15
  z-index: 1;
17
- overflow-y: auto;
16
+ overflow-y: scroll;
18
17
  }
19
18
 
20
19
  .hidden.editor-panel + .builder-grid {
@@ -29,7 +28,6 @@ $red: #f74242;
29
28
  .builder-grid {
30
29
  flex-grow: 1;
31
30
  padding: 5em 3em 3em;
32
- margin-left: calc(350px + 1em);
33
31
  }
34
32
 
35
33
  .column-container {
@@ -15,11 +15,8 @@
15
15
  border-bottom: #c7c7c7 1px solid;
16
16
  padding: 0 1em;
17
17
  display: flex;
18
- height: 6em;
19
- position: fixed;
20
- width: 100%;
21
- z-index: 19;
22
- top: 0;
18
+ width: 100vw;
19
+ z-index: 5;
23
20
  &.sub-dashboard-viz {
24
21
  height: 3em;
25
22
  }
@@ -285,9 +282,6 @@
285
282
  .editor-panel {
286
283
  top: 40px;
287
284
  }
288
- .editor-toggle {
289
- top: 3em;
290
- }
291
285
  }
292
286
  }
293
287
 
@@ -1,6 +1,5 @@
1
- import type { Config } from '../types/Config'
2
-
3
- type Action<T, P> = { type: T; payload: P }
1
+ import type { DashboardConfig as Config } from '../types/DashboardConfig'
2
+ import { type Action } from '@cdc/core/types/Action'
4
3
 
5
4
  type SET_CONFIG = Action<'SET_CONFIG', Config>
6
5
  type UPDATE_CONFIG = Action<'UPDATE_CONFIG', [Config, Object?]>
@@ -9,6 +8,13 @@ type SET_LOADING = Action<'SET_LOADING', boolean>
9
8
  type SET_PREVIEW = Action<'SET_PREVIEW', boolean>
10
9
  type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', Object>
11
10
  type SET_TAB_SELECTED = Action<'SET_TAB_SELECTED', number>
11
+ type RENAME_DASHBOARD_TAB = Action<'RENAME_DASHBOARD_TAB', { current: string; new: string }>
12
+ type INITIALIZE_MULTIDASHBOARDS = Action<'INITIALIZE_MULTIDASHBOARDS', undefined>
13
+ type REMOVE_MULTIDASHBOARD_AT_INDEX = Action<'REMOVE_MULTIDASHBOARD_AT_INDEX', number>
14
+ type REORDER_MULTIDASHBOARDS = Action<'REORDER_MULTIDASHBOARDS', { currentIndex: number; newIndex: number }>
15
+ type ADD_NEW_DASHBOARD = Action<'ADD_NEW_DASHBOARD', undefined>
16
+ type SAVE_CURRENT_CHANGES = Action<'SAVE_CURRENT_CHANGES', undefined>
17
+ type SWITCH_CONFIG = Action<'SWITCH_CONFIG', number>
12
18
 
13
- type DashboardActions = SET_CONFIG | UPDATE_CONFIG | SET_DATA | SET_LOADING | SET_PREVIEW | SET_FILTERED_DATA | SET_TAB_SELECTED
19
+ type DashboardActions = ADD_NEW_DASHBOARD | SET_CONFIG | UPDATE_CONFIG | REMOVE_MULTIDASHBOARD_AT_INDEX | RENAME_DASHBOARD_TAB | REORDER_MULTIDASHBOARDS | SAVE_CURRENT_CHANGES | SET_DATA | SET_LOADING | SET_PREVIEW | SET_FILTERED_DATA | SET_TAB_SELECTED | SWITCH_CONFIG | INITIALIZE_MULTIDASHBOARDS
14
20
  export default DashboardActions