@cdc/dashboard 4.26.3 → 4.26.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.
- package/CONFIG.md +172 -0
- package/README.md +60 -20
- package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
- package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
- package/dist/cdcdashboard.js +51744 -49003
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data.json +6 -0
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/package.json +9 -9
- package/src/CdcDashboardComponent.tsx +1 -1
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +43 -2
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- package/src/components/DashboardEditors.tsx +15 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +129 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +10 -7
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +5 -4
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +127 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +28 -4
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
- package/src/components/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +4 -3
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/Row.tsx +11 -10
- package/src/components/VisualizationRow.tsx +71 -20
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -1
- package/src/components/Widget/Widget.tsx +5 -4
- package/src/components/Widget/widget.styles.css +41 -10
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addVisualization.ts +3 -1
- package/src/helpers/tests/addVisualization.test.ts +1 -1
- package/src/scss/grid.scss +38 -8
- package/src/scss/main.scss +110 -38
- package/src/store/dashboard.reducer.ts +1 -0
- package/src/test/CdcDashboard.test.jsx +24 -0
- package/src/types/SharedFilter.ts +1 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
- package/LICENSE +0 -201
- package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
- package/examples/DEV-6574.json +0 -2224
- package/examples/api-dashboard-data.json +0 -272
- package/examples/api-dashboard-years.json +0 -11
- package/examples/api-geographies-data.json +0 -11
- package/examples/chart-data.json +0 -5409
- package/examples/custom/css/respiratory.css +0 -236
- package/examples/custom/js/respiratory.js +0 -242
- package/examples/default-data.json +0 -368
- package/examples/default-filter-control.json +0 -209
- package/examples/default-multi-dataset-shared-filter.json +0 -1729
- package/examples/default-multi-dataset.json +0 -506
- package/examples/ed-visits-county-file.json +0 -402
- package/examples/filters/Alabama.json +0 -72
- package/examples/filters/Alaska.json +0 -1737
- package/examples/filters/Arkansas.json +0 -4713
- package/examples/filters/California.json +0 -212
- package/examples/filters/Colorado.json +0 -1500
- package/examples/filters/Connecticut.json +0 -559
- package/examples/filters/Delaware.json +0 -63
- package/examples/filters/DistrictofColumbia.json +0 -63
- package/examples/filters/Florida.json +0 -4217
- package/examples/filters/States.json +0 -146
- package/examples/state-level.json +0 -90136
- package/examples/state-points.json +0 -10474
- package/examples/temp-example-data.json +0 -130
- package/examples/test-dashboard-simple.json +0 -503
- package/examples/test-example.json +0 -752
- package/examples/test-file.json +0 -147
- package/examples/test.json +0 -752
- package/examples/testing.json +0 -94456
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
- /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
- /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
- /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
- /package/examples/api-test/{years.json → __data__/years.json} +0 -0
- /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
|
@@ -1,140 +1,141 @@
|
|
|
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 '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
dispatch({ type: '
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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 Button from '@cdc/core/components/elements/Button'
|
|
6
|
+
import './multiconfigtabs.styles.css'
|
|
7
|
+
|
|
8
|
+
const AreYouSure = deleteCallback => {
|
|
9
|
+
return (
|
|
10
|
+
<Modal>
|
|
11
|
+
<Modal.Content>
|
|
12
|
+
<p>Are you sure you want to delete this dashboard? </p>
|
|
13
|
+
<Button variant='danger' onClick={deleteCallback}>
|
|
14
|
+
DELETE
|
|
15
|
+
</Button>
|
|
16
|
+
</Modal.Content>
|
|
17
|
+
</Modal>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
22
|
+
const [editing, setEditing] = useState(false)
|
|
23
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
24
|
+
const { overlay } = useGlobalContext()
|
|
25
|
+
const inputRef = createRef<HTMLInputElement>()
|
|
26
|
+
|
|
27
|
+
const saveName = e => {
|
|
28
|
+
e.stopPropagation()
|
|
29
|
+
const newVal = inputRef.current.value
|
|
30
|
+
const sameName = newVal === name
|
|
31
|
+
const blankName = !newVal
|
|
32
|
+
const duplicateName = tabs.includes(newVal)
|
|
33
|
+
if (!sameName && !blankName && !duplicateName) {
|
|
34
|
+
dispatch({ type: 'RENAME_DASHBOARD_TAB', payload: { current: name, new: newVal } })
|
|
35
|
+
}
|
|
36
|
+
setEditing(false)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const onClick = e => {
|
|
40
|
+
// ignore click on delete button
|
|
41
|
+
if (e.target.className === 'remove') return
|
|
42
|
+
if (active) {
|
|
43
|
+
setEditing(true)
|
|
44
|
+
} else {
|
|
45
|
+
handleClick()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleRemove = () => {
|
|
50
|
+
const deleteCallback = () => {
|
|
51
|
+
dispatch({ type: 'REMOVE_MULTIDASHBOARD_AT_INDEX', payload: index })
|
|
52
|
+
overlay?.actions.toggleOverlay(false)
|
|
53
|
+
}
|
|
54
|
+
overlay?.actions.openOverlay(AreYouSure(deleteCallback))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleReorder = (index: number, moveTo: -1 | 1) => {
|
|
58
|
+
const newIndex = index + moveTo
|
|
59
|
+
const inbounds = newIndex > -1 && newIndex <= tabs.length - 1
|
|
60
|
+
if (inbounds) {
|
|
61
|
+
dispatch({ type: 'REORDER_MULTIDASHBOARDS', payload: { currentIndex: index, newIndex: index + moveTo } })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const canMoveLeft = index !== 0
|
|
66
|
+
const canMoveRight = index <= tabs.length - 2
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<li className='nav-item d-flex mt-0'>
|
|
70
|
+
{canMoveLeft && editing && (
|
|
71
|
+
<button className='border-0' onClick={() => handleReorder(index, -1)}>
|
|
72
|
+
{'<'}
|
|
73
|
+
</button>
|
|
74
|
+
)}
|
|
75
|
+
<div
|
|
76
|
+
className={`edit nav-link${active ? ' active' : ''}`}
|
|
77
|
+
aria-current={active ? 'page' : null}
|
|
78
|
+
onClick={onClick}
|
|
79
|
+
>
|
|
80
|
+
{editing ? (
|
|
81
|
+
<div className='d-flex'>
|
|
82
|
+
<input type='text' defaultValue={name} onBlur={saveName} ref={inputRef} />
|
|
83
|
+
<Button variant='link' className='save' onClick={saveName}>
|
|
84
|
+
save
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
) : (
|
|
88
|
+
<>
|
|
89
|
+
{name}
|
|
90
|
+
<Button variant='danger' className='border-0 ms-1' onClick={handleRemove}>
|
|
91
|
+
X
|
|
92
|
+
</Button>
|
|
93
|
+
</>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
{canMoveRight && editing && (
|
|
97
|
+
<button className='border-0' onClick={() => handleReorder(index, 1)}>
|
|
98
|
+
{'>'}
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</li>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const MultiConfigTabs = () => {
|
|
106
|
+
const { config } = useContext(DashboardContext)
|
|
107
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
108
|
+
const tabs = useMemo<string[]>(
|
|
109
|
+
() => (config.multiDashboards || []).map(({ label }) => label),
|
|
110
|
+
[config.multiDashboards]
|
|
111
|
+
)
|
|
112
|
+
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
113
|
+
|
|
114
|
+
const saveAndLoad = (indexToSwitchTo: number) => {
|
|
115
|
+
dispatch({ type: 'SAVE_CURRENT_CHANGES' })
|
|
116
|
+
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!config.multiDashboards) return null
|
|
120
|
+
return (
|
|
121
|
+
<ul className='nav nav-tabs multi-config-tabs mb-4'>
|
|
122
|
+
{tabs.map((tab, index) => (
|
|
123
|
+
<Tab
|
|
124
|
+
key={tab + index}
|
|
125
|
+
name={tab}
|
|
126
|
+
tabs={tabs}
|
|
127
|
+
index={index}
|
|
128
|
+
handleClick={() => saveAndLoad(index)}
|
|
129
|
+
active={index === activeTab}
|
|
130
|
+
/>
|
|
131
|
+
))}
|
|
132
|
+
<li className='nav-item'>
|
|
133
|
+
<button className='nav-link add' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
|
|
134
|
+
+
|
|
135
|
+
</button>
|
|
136
|
+
</li>
|
|
137
|
+
</ul>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default MultiConfigTabs
|
package/src/components/Row.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import ToggleIcon from '../images/icon-toggle.svg'
|
|
|
15
15
|
import { ConfigRow } from '../types/ConfigRow'
|
|
16
16
|
import { DataDesignerModal } from './DataDesignerModal'
|
|
17
17
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
18
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
18
19
|
import { iconHash } from '../helpers/iconHash'
|
|
19
20
|
import _ from 'lodash'
|
|
20
21
|
import { Visualization } from '@cdc/core/types/Visualization'
|
|
@@ -191,7 +192,7 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
191
192
|
<nav className='row-menu'>
|
|
192
193
|
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
193
194
|
{isMultiColumn && (
|
|
194
|
-
<
|
|
195
|
+
<Button
|
|
195
196
|
className={`btn row-menu__btn border-0${row.equalHeight ? ' btn-primary' : ''}`}
|
|
196
197
|
title={row.equalHeight ? 'Disable Equal Height Rows' : 'Enable Equal Height Rows'}
|
|
197
198
|
onClick={toggleEqualHeight}
|
|
@@ -201,33 +202,33 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
201
202
|
<rect x='14' y='2' width='9' height='14' rx='1' />
|
|
202
203
|
<line x1='0' y1='19' x2='24' y2='19' stroke='#fff' strokeWidth='2' strokeDasharray='3 2' />
|
|
203
204
|
</svg>
|
|
204
|
-
</
|
|
205
|
+
</Button>
|
|
205
206
|
)}
|
|
206
207
|
<div className='spacer'></div>
|
|
207
|
-
<
|
|
208
|
+
<Button
|
|
208
209
|
className={`btn btn-primary row-menu__btn border-0`}
|
|
209
210
|
title='Move Row Up'
|
|
210
211
|
onClick={() => moveRow('up')}
|
|
211
212
|
disabled={rowIdx === 0}
|
|
212
213
|
>
|
|
213
214
|
<Icon display='caretUp' color='#fff' size={25} />
|
|
214
|
-
</
|
|
215
|
-
<
|
|
215
|
+
</Button>
|
|
216
|
+
<Button
|
|
216
217
|
className={'btn btn-primary row-menu__btn border-0'}
|
|
217
218
|
title='Move Row Down'
|
|
218
219
|
onClick={() => moveRow('down')}
|
|
219
220
|
disabled={rowIdx + 1 === rows.length}
|
|
220
221
|
>
|
|
221
222
|
<Icon display='caretDown' color='#fff' size={25} />
|
|
222
|
-
</
|
|
223
|
-
<
|
|
223
|
+
</Button>
|
|
224
|
+
<Button
|
|
224
225
|
className={'btn btn-danger row-menu__btn row-menu__btn--remove border-0'}
|
|
225
226
|
title='Delete Row'
|
|
226
227
|
onClick={deleteRow}
|
|
227
228
|
disabled={rowIdx === 0 && rows.length === 1}
|
|
228
229
|
>
|
|
229
230
|
<Icon display='close' color='#fff' size={25} />
|
|
230
|
-
</
|
|
231
|
+
</Button>
|
|
231
232
|
</nav>
|
|
232
233
|
)
|
|
233
234
|
}
|
|
@@ -241,7 +242,7 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
|
241
242
|
<div className='builder-row' data-row-id={rowIdx}>
|
|
242
243
|
<RowMenu rowIdx={rowIdx} />
|
|
243
244
|
<span className='ms-2 mt-n3'>Row - {rowIdx + 1}</span>
|
|
244
|
-
<
|
|
245
|
+
<Button
|
|
245
246
|
title='Configure Data'
|
|
246
247
|
className='btn btn-configure-row'
|
|
247
248
|
onClick={() => {
|
|
@@ -249,7 +250,7 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
|
249
250
|
}}
|
|
250
251
|
>
|
|
251
252
|
{iconHash['gearMulti']}
|
|
252
|
-
</
|
|
253
|
+
</Button>
|
|
253
254
|
<div className='column-container'>
|
|
254
255
|
{row.columns
|
|
255
256
|
.filter(column => column.width)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
2
|
-
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import Toggle from './Toggle'
|
|
4
4
|
import cloneDeep from 'lodash/cloneDeep'
|
|
5
5
|
import { ConfigRow } from '../types/ConfigRow'
|
|
@@ -88,6 +88,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
88
88
|
}) => {
|
|
89
89
|
const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
|
|
90
90
|
const [toggledRow, setToggled] = React.useState<number>(0)
|
|
91
|
+
const rowRef = useRef<HTMLDivElement>(null)
|
|
91
92
|
|
|
92
93
|
useEffect(() => {
|
|
93
94
|
if (row.toggle) setToggled(0)
|
|
@@ -103,15 +104,18 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
103
104
|
}
|
|
104
105
|
}, [toggledRow, row.toggle])
|
|
105
106
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const setupMinHeightEqualizer = (rowElement: HTMLElement, selectors: string[]) => {
|
|
108
|
+
let items: HTMLElement[] = []
|
|
109
|
+
const selector = selectors.join(', ')
|
|
110
|
+
const resizeObserver = new ResizeObserver(() => equalizeHeights())
|
|
109
111
|
|
|
110
112
|
const equalizeHeights = () => {
|
|
111
113
|
items.forEach(item => {
|
|
112
114
|
item.style.minHeight = ''
|
|
113
115
|
})
|
|
114
116
|
|
|
117
|
+
if (items.length <= 1) return
|
|
118
|
+
|
|
115
119
|
let maxHeight = 0
|
|
116
120
|
items.forEach(item => {
|
|
117
121
|
const height = item.offsetHeight
|
|
@@ -125,42 +129,86 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
const refreshItems = () => {
|
|
133
|
+
const nextItems = Array.from(rowElement.querySelectorAll(selector)) as HTMLElement[]
|
|
134
|
+
const nextSet = new Set(nextItems)
|
|
135
|
+
|
|
136
|
+
items.forEach(item => {
|
|
137
|
+
if (!nextSet.has(item)) {
|
|
138
|
+
resizeObserver.unobserve(item)
|
|
139
|
+
item.style.minHeight = ''
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
nextItems.forEach(item => {
|
|
144
|
+
if (!items.includes(item)) {
|
|
145
|
+
resizeObserver.observe(item)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
items = nextItems
|
|
131
150
|
equalizeHeights()
|
|
132
|
-
}
|
|
151
|
+
}
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
const mutationObserver = new MutationObserver(() => {
|
|
154
|
+
refreshItems()
|
|
136
155
|
})
|
|
137
156
|
|
|
138
|
-
|
|
157
|
+
mutationObserver.observe(rowElement, { childList: true, subtree: true })
|
|
158
|
+
refreshItems()
|
|
159
|
+
|
|
160
|
+
return () => {
|
|
161
|
+
mutationObserver.disconnect()
|
|
162
|
+
resizeObserver.disconnect()
|
|
163
|
+
items.forEach(item => {
|
|
164
|
+
item.style.minHeight = ''
|
|
165
|
+
})
|
|
166
|
+
}
|
|
139
167
|
}
|
|
140
168
|
|
|
141
|
-
|
|
169
|
+
const tp5CountInRow = row.columns.reduce((count, col) => {
|
|
170
|
+
if (!col.widget) return count
|
|
171
|
+
const viz = config.visualizations[col.widget]
|
|
172
|
+
if (!viz) return count
|
|
173
|
+
|
|
174
|
+
const isTp5DataBite = viz.type === 'data-bite' && (viz as any).biteStyle === 'tp5'
|
|
175
|
+
const isTp5WaffleOrGauge =
|
|
176
|
+
viz.type === 'waffle-chart' && (viz.visualizationType === 'TP5 Waffle' || viz.visualizationType === 'TP5 Gauge')
|
|
177
|
+
const isTp5MarkupInclude = viz.type === 'markup-include' && (viz as any).contentEditor?.style === 'tp5'
|
|
178
|
+
|
|
179
|
+
return isTp5DataBite || isTp5WaffleOrGauge || isTp5MarkupInclude ? count + 1 : count
|
|
180
|
+
}, 0)
|
|
181
|
+
const needsTP5AutoEqualization = tp5CountInRow > 1
|
|
182
|
+
const shouldEqualizeRow = !!row.equalHeight || needsTP5AutoEqualization
|
|
183
|
+
|
|
184
|
+
// Layer TP5 equalization for row-level title consistency and same-type internals.
|
|
142
185
|
useEffect(() => {
|
|
143
|
-
if (!
|
|
186
|
+
if (!shouldEqualizeRow) return
|
|
144
187
|
|
|
145
|
-
const rowElement =
|
|
188
|
+
const rowElement = rowRef.current
|
|
146
189
|
if (!rowElement) return
|
|
147
190
|
|
|
148
191
|
const cleanups = [
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
192
|
+
// Cross-type TP5 title alignment in the row.
|
|
193
|
+
setupMinHeightEqualizer(rowElement, [
|
|
194
|
+
'.bite__style--tp5 .cdc-callout__heading',
|
|
195
|
+
'.waffle__style--tp5 .cdc-callout__heading',
|
|
196
|
+
'.gauge__style--tp5 .cdc-callout__heading',
|
|
197
|
+
'.markup-include__style--tp5 .cdc-callout__heading'
|
|
198
|
+
]),
|
|
199
|
+
// Same-type gauge internals alignment so gauge meters line up despite content variance.
|
|
200
|
+
setupMinHeightEqualizer(rowElement, ['.gauge__style--tp5 .cove-gauge-chart__content'])
|
|
201
|
+
]
|
|
154
202
|
|
|
155
203
|
return () => {
|
|
156
204
|
cleanups.forEach(cleanup => cleanup())
|
|
157
205
|
}
|
|
158
|
-
}, [
|
|
206
|
+
}, [shouldEqualizeRow, row.columns, config.activeDashboard, filteredDataOverride, dashboardFilteredData[index]])
|
|
159
207
|
|
|
160
208
|
const isFilterRow = row.columns.some(
|
|
161
209
|
col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
|
|
162
210
|
)
|
|
163
|
-
const needsEqualHeight =
|
|
211
|
+
const needsEqualHeight = shouldEqualizeRow && !isFilterRow
|
|
164
212
|
|
|
165
213
|
const show = useMemo(() => {
|
|
166
214
|
if (row.toggle) {
|
|
@@ -236,6 +284,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
236
284
|
|
|
237
285
|
return (
|
|
238
286
|
<div
|
|
287
|
+
ref={rowRef}
|
|
239
288
|
className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
|
|
240
289
|
key={`row__${index}`}
|
|
241
290
|
data-row-index={index}
|
|
@@ -355,6 +404,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
355
404
|
<CdcDataBite
|
|
356
405
|
key={col.widget}
|
|
357
406
|
config={visualizationConfig}
|
|
407
|
+
rawData={rawData?.[visualizationConfig.dataKey] || []}
|
|
358
408
|
setConfig={newConfig => {
|
|
359
409
|
updateChildConfig(col.widget, newConfig)
|
|
360
410
|
}}
|
|
@@ -367,6 +417,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
367
417
|
<CdcWaffleChart
|
|
368
418
|
key={col.widget}
|
|
369
419
|
config={visualizationConfig}
|
|
420
|
+
rawData={rawData?.[visualizationConfig.dataKey] || []}
|
|
370
421
|
setConfig={newConfig => {
|
|
371
422
|
updateChildConfig(col.widget, newConfig)
|
|
372
423
|
}}
|
|
@@ -43,7 +43,7 @@ const VisualizationsPanel = () => {
|
|
|
43
43
|
<span className='subheading-3'>Misc.</span>
|
|
44
44
|
<div className='drag-grid'>
|
|
45
45
|
<Widget addVisualization={() => addVisualization('data-bite', '')} type='data-bite' />
|
|
46
|
-
<Widget addVisualization={() => addVisualization('waffle-chart', '')} type='waffle-chart' />
|
|
46
|
+
<Widget addVisualization={() => addVisualization('waffle-chart', 'Waffle')} type='waffle-chart' />
|
|
47
47
|
<Widget addVisualization={() => addVisualization('markup-include', '')} type='markup-include' />
|
|
48
48
|
<Widget addVisualization={() => addVisualization('filtered-text', '')} type='filtered-text' />
|
|
49
49
|
<Widget addVisualization={() => addVisualization('dashboardFilters', '')} type='dashboardFilters' />
|
|
@@ -5,6 +5,7 @@ import { DashboardContext, DashboardDispatchContext } from '../../DashboardConte
|
|
|
5
5
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
6
6
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
7
7
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
8
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
8
9
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
9
10
|
import { iconHash } from '../../helpers/iconHash'
|
|
10
11
|
import _ from 'lodash'
|
|
@@ -167,12 +168,12 @@ const Widget = ({
|
|
|
167
168
|
{widgetConfig?.rowIdx !== undefined && (
|
|
168
169
|
<div className='widget-menu'>
|
|
169
170
|
{isConfigurationReady && (
|
|
170
|
-
<
|
|
171
|
+
<Button title='Configure Visualization' className='btn btn-configure' onClick={editWidget}>
|
|
171
172
|
{iconHash['tools']}
|
|
172
|
-
</
|
|
173
|
+
</Button>
|
|
173
174
|
)}
|
|
174
175
|
{needsDataConfiguration && (
|
|
175
|
-
<
|
|
176
|
+
<Button
|
|
176
177
|
title='Configure Data'
|
|
177
178
|
className='btn btn-configure'
|
|
178
179
|
onClick={() => {
|
|
@@ -182,7 +183,7 @@ const Widget = ({
|
|
|
182
183
|
}}
|
|
183
184
|
>
|
|
184
185
|
{iconHash['gear']}
|
|
185
|
-
</
|
|
186
|
+
</Button>
|
|
186
187
|
)}
|
|
187
188
|
<div className='widget-menu-item' onClick={deleteWidget}>
|
|
188
189
|
<Icon display='close' base />
|
|
@@ -33,25 +33,56 @@
|
|
|
33
33
|
.widget-menu {
|
|
34
34
|
align-items: center;
|
|
35
35
|
display: flex;
|
|
36
|
-
|
|
36
|
+
gap: 6px;
|
|
37
|
+
justify-content: flex-end;
|
|
38
|
+
padding: 2px;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
.btn-configure {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
.widget-menu .cove-button.btn-configure {
|
|
42
|
+
align-items: center;
|
|
43
|
+
background: transparent !important;
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: 0;
|
|
46
|
+
box-shadow: none;
|
|
47
|
+
color: var(--mediumGray);
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
height: 28px;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
margin: 0;
|
|
52
|
+
min-height: 28px;
|
|
53
|
+
padding: 4px;
|
|
54
|
+
transform: none;
|
|
55
|
+
width: 28px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.widget-menu .cove-button.btn-configure:hover:not(:disabled),
|
|
59
|
+
.widget-menu .cove-button.btn-configure:active:not(:disabled) {
|
|
60
|
+
background: transparent !important;
|
|
61
|
+
box-shadow: none;
|
|
62
|
+
transform: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.widget-menu .cove-button.btn-configure svg {
|
|
66
|
+
margin-bottom: 0;
|
|
67
|
+
top: 0;
|
|
68
|
+
vertical-align: middle;
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
.widget-menu-item {
|
|
72
|
+
align-items: center;
|
|
48
73
|
cursor: pointer;
|
|
49
|
-
display:
|
|
50
|
-
height:
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
height: 28px;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
line-height: 1;
|
|
78
|
+
padding: 4px;
|
|
51
79
|
user-select: none;
|
|
52
|
-
width:
|
|
80
|
+
width: 28px;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
.widget-menu-item svg {
|
|
56
84
|
fill: var(--mediumGray);
|
|
85
|
+
margin-bottom: 0;
|
|
86
|
+
top: 0;
|
|
87
|
+
vertical-align: middle;
|
|
57
88
|
}
|
|
@@ -35,10 +35,12 @@ export const addVisualization = (type, subType) => {
|
|
|
35
35
|
}
|
|
36
36
|
break
|
|
37
37
|
case 'data-bite':
|
|
38
|
-
case 'waffle-chart':
|
|
39
38
|
case 'filtered-text':
|
|
40
39
|
newVisualizationConfig.visualizationType = type
|
|
41
40
|
break
|
|
41
|
+
case 'waffle-chart':
|
|
42
|
+
newVisualizationConfig.visualizationType = subType
|
|
43
|
+
break
|
|
42
44
|
case 'table': {
|
|
43
45
|
const tableConfig: Table = {
|
|
44
46
|
label: 'Data Table',
|