@cdc/core 4.25.11 → 4.26.1
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/_stories/Gallery.Charts.stories.tsx +307 -0
- package/_stories/Gallery.DataBite.stories.tsx +72 -0
- package/_stories/Gallery.Maps.stories.tsx +230 -0
- package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
- package/_stories/PageART.stories.tsx +192 -0
- package/_stories/PageBRFSS.stories.tsx +289 -0
- package/_stories/PageCancerRegistries.stories.tsx +199 -0
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
- package/_stories/PageMaternalMortality.stories.tsx +192 -0
- package/_stories/PageOralHealth.stories.tsx +196 -0
- package/_stories/PageRespiratory.stories.tsx +332 -0
- package/_stories/PageSmokingTobacco.stories.tsx +195 -0
- package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
- package/_stories/PageWastewater.stories.tsx +463 -0
- package/assets/icon-magnifying-glass.svg +5 -0
- package/assets/icon-warming-stripes.svg +13 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +281 -0
- package/components/ComboBox/ComboBox.tsx +345 -0
- package/components/ComboBox/combobox.styles.css +185 -0
- package/components/ComboBox/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +132 -58
- package/components/DataTable/data-table.css +216 -215
- package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +37 -19
- package/components/EditorPanel/DataTableEditor.tsx +51 -25
- package/components/EditorPanel/EditorPanel.styles.css +16 -0
- package/components/EditorPanel/EditorPanel.tsx +144 -0
- package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
- package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
- package/components/EditorPanel/Inputs.tsx +33 -7
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +236 -175
- package/components/EditorPanel/sections/VisualSection.tsx +169 -0
- package/components/Filters/Filters.tsx +31 -5
- package/components/Filters/helpers/getNestedOptions.ts +2 -1
- package/components/Filters/helpers/handleSorting.ts +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
- package/components/Layout/components/Visualization/index.tsx +16 -1
- package/components/Layout/components/Visualization/visualizations.scss +7 -0
- package/components/Legend/Legend.Gradient.tsx +1 -1
- package/components/MediaControls.tsx +53 -27
- package/components/ui/Icon.tsx +3 -1
- package/components/ui/Title/index.tsx +30 -2
- package/components/ui/Title/title.styles.css +42 -0
- package/dist/cove-main.css +26 -3
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +8 -1
- package/helpers/addValuesToFilters.ts +6 -1
- package/helpers/coveUpdateWorker.ts +19 -12
- package/helpers/embedCodeGenerator.ts +109 -0
- package/helpers/getUniqueValues.ts +19 -0
- package/helpers/hashObj.ts +25 -0
- package/helpers/isRightAlignedTableValue.js +5 -0
- package/helpers/metrics/helpers.ts +1 -0
- package/helpers/pivotData.ts +2 -2
- package/helpers/prepareScreenshot.ts +268 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/tests/prepareScreenshot.test.ts +414 -0
- package/helpers/tests/queryStringUtils.test.ts +381 -0
- package/helpers/tests/testStandaloneBuild.ts +23 -5
- package/helpers/useDataVizClasses.ts +0 -1
- package/helpers/ver/4.26.1.ts +80 -0
- package/hooks/useDataColumns.ts +63 -0
- package/hooks/useFilterManagement.ts +94 -0
- package/hooks/useLegendSeparators.ts +26 -0
- package/hooks/useListManagement.ts +192 -0
- package/package.json +4 -3
- package/styles/_button-section.scss +0 -3
- package/types/Axis.ts +1 -0
- package/types/ForecastingSeriesKey.ts +1 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/Series.ts +3 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +1 -0
- package/types/VizFilter.ts +1 -0
- package/LICENSE +0 -201
|
@@ -5,6 +5,7 @@ import parse from 'html-react-parser'
|
|
|
5
5
|
// CDC
|
|
6
6
|
import Button from '../elements/Button'
|
|
7
7
|
import MultiSelect from '../MultiSelect'
|
|
8
|
+
import ComboBox from '../ComboBox'
|
|
8
9
|
import { Visualization } from '../../types/Visualization'
|
|
9
10
|
import { MultiSelectFilter, VizFilter } from '../../types/VizFilter'
|
|
10
11
|
import { addValuesToFilters } from '../../helpers/addValuesToFilters'
|
|
@@ -14,7 +15,7 @@ import { getNestedOptions } from './helpers/getNestedOptions'
|
|
|
14
15
|
import { getWrappingStatuses } from './helpers/filterWrapping'
|
|
15
16
|
import { handleSorting } from './helpers/handleSorting'
|
|
16
17
|
import { getChangedFilters } from './helpers/getChangedFilters'
|
|
17
|
-
import { getUniqueValues } from '
|
|
18
|
+
import { getUniqueValues } from '../../helpers/getUniqueValues'
|
|
18
19
|
import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
|
|
19
20
|
import { applyQueuedActive } from './helpers/applyQueuedActive'
|
|
20
21
|
import Tabs from './components/Tabs'
|
|
@@ -23,13 +24,14 @@ import { publishAnalyticsEvent } from '../../helpers/metrics/helpers'
|
|
|
23
24
|
import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
|
|
24
25
|
|
|
25
26
|
export const VIZ_FILTER_STYLE = {
|
|
27
|
+
combobox: 'combobox',
|
|
26
28
|
dropdown: 'dropdown',
|
|
29
|
+
multiSelect: 'multi-select',
|
|
27
30
|
nestedDropdown: 'nested-dropdown',
|
|
28
31
|
pill: 'pill',
|
|
29
32
|
tab: 'tab',
|
|
30
33
|
tabSimple: 'tab-simple',
|
|
31
|
-
tabBar: 'tab bar'
|
|
32
|
-
multiSelect: 'multi-select'
|
|
34
|
+
tabBar: 'tab bar'
|
|
33
35
|
} as const
|
|
34
36
|
|
|
35
37
|
export type VizFilterStyle = (typeof VIZ_FILTER_STYLE)[keyof typeof VIZ_FILTER_STYLE]
|
|
@@ -95,7 +97,9 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
95
97
|
eventAction: 'change',
|
|
96
98
|
eventLabel: interactionLabel,
|
|
97
99
|
vizTitle: getVizTitle(visualizationConfig),
|
|
98
|
-
specifics: `key: ${String(newFilters?.[index]?.columnName).toLowerCase()}, value: ${String(
|
|
100
|
+
specifics: `key: ${String(newFilters?.[index]?.columnName).toLowerCase()}, value: ${String(
|
|
101
|
+
newFilters?.[index]?.active
|
|
102
|
+
).toLowerCase()}`
|
|
99
103
|
})
|
|
100
104
|
}
|
|
101
105
|
|
|
@@ -202,6 +206,9 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
202
206
|
|
|
203
207
|
if (visualizationConfig?.filters?.length === 0) return <></>
|
|
204
208
|
|
|
209
|
+
const hasVisibleFilters = filters?.some(filter => filter.showDropdown !== false)
|
|
210
|
+
if (!hasVisibleFilters) return <></>
|
|
211
|
+
|
|
205
212
|
const getClasses = () => {
|
|
206
213
|
const { visualizationType, legend } = visualizationConfig || {}
|
|
207
214
|
const baseClass = 'filters-section'
|
|
@@ -216,6 +223,12 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
216
223
|
return (singleFilter.queuedActive || [singleFilter.active, singleFilter.subGrouping?.active]) as [string, string]
|
|
217
224
|
}
|
|
218
225
|
|
|
226
|
+
// Don't render filter section if all filters are hidden
|
|
227
|
+
const allFiltersHidden = vizFiltersWithValues.every(filter => filter.showDropdown === false)
|
|
228
|
+
if (allFiltersHidden) {
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
|
|
219
232
|
return (
|
|
220
233
|
<section className={getClasses().join(' ')}>
|
|
221
234
|
{visualizationConfig.filterIntro && (
|
|
@@ -235,7 +248,9 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
235
248
|
'form-group',
|
|
236
249
|
mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
|
|
237
250
|
]
|
|
238
|
-
const mobileExempt = ['nested-dropdown', 'multi-select', VIZ_FILTER_STYLE.tabSimple].includes(
|
|
251
|
+
const mobileExempt = ['nested-dropdown', 'multi-select', 'combobox', VIZ_FILTER_STYLE.tabSimple].includes(
|
|
252
|
+
filterStyle
|
|
253
|
+
)
|
|
239
254
|
const { isDropdown } = wrappingFilters[columnName] || {}
|
|
240
255
|
const showDefaultDropdown =
|
|
241
256
|
((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
|
|
@@ -302,6 +317,17 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
302
317
|
handleSelectedItems={value => changeFilterActive(outerIndex, value)}
|
|
303
318
|
/>
|
|
304
319
|
)}
|
|
320
|
+
{filterStyle === 'combobox' && (
|
|
321
|
+
<ComboBox
|
|
322
|
+
options={singleFilter.values.map(v => ({ value: v, label: v }))}
|
|
323
|
+
fieldName={outerIndex}
|
|
324
|
+
updateField={(_section, _subSection, fieldName, value) => {
|
|
325
|
+
changeFilterActive(fieldName, value)
|
|
326
|
+
}}
|
|
327
|
+
selected={(singleFilter.queuedActive || singleFilter.active) as string}
|
|
328
|
+
label={label}
|
|
329
|
+
/>
|
|
330
|
+
)}
|
|
305
331
|
</div>
|
|
306
332
|
)
|
|
307
333
|
})}
|
|
@@ -8,9 +8,10 @@ type GetOptionsMemoParams = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const getNestedOptions = ({ orderedValues, values, subGrouping }: GetOptionsMemoParams): NestedOptions => {
|
|
11
|
+
if (!values?.length && !orderedValues?.length) return []
|
|
11
12
|
// keep custom ordered value order
|
|
12
13
|
const filteredValues = orderedValues?.length
|
|
13
|
-
? orderedValues.filter(orderedValue => values
|
|
14
|
+
? orderedValues.filter(orderedValue => values?.includes(orderedValue))
|
|
14
15
|
: values
|
|
15
16
|
const options: NestedOptions = filteredValues.map<[ValueTextPair, ValueTextPair[]]>(value => {
|
|
16
17
|
if (!subGrouping) return [[value], []]
|
|
@@ -2,7 +2,7 @@ import _ from 'lodash'
|
|
|
2
2
|
|
|
3
3
|
export const handleSorting = singleFilter => {
|
|
4
4
|
const singleFilterValues = _.cloneDeep(singleFilter.values)
|
|
5
|
-
if (singleFilter.order === 'cust'
|
|
5
|
+
if (singleFilter.order === 'cust') {
|
|
6
6
|
singleFilter.values = singleFilter.orderedValues?.length ? singleFilter.orderedValues : singleFilterValues
|
|
7
7
|
return singleFilter
|
|
8
8
|
}
|
|
@@ -186,6 +186,88 @@
|
|
|
186
186
|
overflow: hidden;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
.editor-field-item {
|
|
190
|
+
position: relative;
|
|
191
|
+
padding: 5px;
|
|
192
|
+
background-color: #fff;
|
|
193
|
+
border: 1px solid #ccc;
|
|
194
|
+
margin-bottom: 10px;
|
|
195
|
+
|
|
196
|
+
&:last-child {
|
|
197
|
+
padding-bottom: 5px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
&__header {
|
|
201
|
+
width: 100%;
|
|
202
|
+
background-color: #f5f5f5;
|
|
203
|
+
border: 1px solid #ccc;
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
padding: 5px;
|
|
207
|
+
padding-left: 5px !important;
|
|
208
|
+
|
|
209
|
+
.cove-icon {
|
|
210
|
+
flex-shrink: 0;
|
|
211
|
+
padding-right: 5px;
|
|
212
|
+
margin-right: 10px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.btn {
|
|
216
|
+
flex-shrink: 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
&__name {
|
|
221
|
+
margin-left: 0.5rem;
|
|
222
|
+
user-select: none;
|
|
223
|
+
flex: 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
&__content {
|
|
227
|
+
padding: 10px;
|
|
228
|
+
background-color: #fff;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
&__remove-wrapper {
|
|
232
|
+
display: flex;
|
|
233
|
+
justify-content: flex-end;
|
|
234
|
+
margin-bottom: 10px;
|
|
235
|
+
|
|
236
|
+
.btn {
|
|
237
|
+
border: 1px solid red;
|
|
238
|
+
border-radius: 10px;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.draggable-field-list {
|
|
244
|
+
list-style: none;
|
|
245
|
+
padding: 0;
|
|
246
|
+
margin: 0;
|
|
247
|
+
|
|
248
|
+
.currently-dragging {
|
|
249
|
+
opacity: 0.8;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.editor-field-item {
|
|
253
|
+
cursor: grab;
|
|
254
|
+
|
|
255
|
+
&:active {
|
|
256
|
+
cursor: grabbing;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
&__header .cove-icon {
|
|
260
|
+
cursor: grab;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.filters-list {
|
|
266
|
+
list-style: none;
|
|
267
|
+
padding: 0;
|
|
268
|
+
margin: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
189
271
|
.accordion__heading {
|
|
190
272
|
background: var(--lightestGray);
|
|
191
273
|
}
|
|
@@ -11,7 +11,14 @@ import { MapConfig } from '@cdc/map/src/types/MapConfig'
|
|
|
11
11
|
|
|
12
12
|
type VisualizationWrapper = {
|
|
13
13
|
children: React.ReactNode
|
|
14
|
-
config:
|
|
14
|
+
config:
|
|
15
|
+
| ChartConfig
|
|
16
|
+
| DataBiteConfig
|
|
17
|
+
| WaffleChartConfig
|
|
18
|
+
| MarkupIncludeConfig
|
|
19
|
+
| DashboardFilters
|
|
20
|
+
| MapConfig
|
|
21
|
+
| DataTableConfig
|
|
15
22
|
currentViewport?: string
|
|
16
23
|
imageId?: string
|
|
17
24
|
isEditor: boolean
|
|
@@ -89,6 +96,14 @@ const Visualization = forwardRef<HTMLDivElement, VisualizationWrapper>((props, r
|
|
|
89
96
|
classes.push('is-editor')
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
// Add TP5 style classes
|
|
100
|
+
if (config.visualizationType === 'TP5 Waffle') {
|
|
101
|
+
classes.push('waffle__style--tp5')
|
|
102
|
+
if (config.visual?.whiteBackground) {
|
|
103
|
+
classes.push('white-background-style')
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
classes.push('cove-component', 'waffle-chart')
|
|
93
108
|
}
|
|
94
109
|
return classes
|
|
@@ -34,6 +34,13 @@
|
|
|
34
34
|
left: 0;
|
|
35
35
|
width: 100% !important;
|
|
36
36
|
grid-area: content;
|
|
37
|
+
padding: 1rem;
|
|
38
|
+
|
|
39
|
+
// Prevent double padding on nested .cove-component__content divs
|
|
40
|
+
// (e.g., in markup-include, waffle-chart, filtered-text)
|
|
41
|
+
.cove-component__content {
|
|
42
|
+
padding: 0;
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
46
|
}
|
|
@@ -4,7 +4,7 @@ import { type MapConfig } from '@cdc/map/src/types/MapConfig'
|
|
|
4
4
|
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
5
5
|
import { getTextWidth } from '../../helpers/getTextWidth'
|
|
6
6
|
import { DimensionsType } from '../../types/Dimensions'
|
|
7
|
-
import useLegendSeparators from '
|
|
7
|
+
import useLegendSeparators from '../../hooks/useLegendSeparators'
|
|
8
8
|
|
|
9
9
|
const MARGIN = 1
|
|
10
10
|
const BORDER_SIZE = 1
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
// import html2pdf from 'html2pdf.js'
|
|
3
3
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
4
4
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
5
|
+
import { prepareScreenshotContainer } from '@cdc/core/helpers/prepareScreenshot'
|
|
5
6
|
|
|
6
7
|
const buttonText = {
|
|
7
8
|
pdf: 'Download PDF',
|
|
@@ -35,7 +36,7 @@ const saveImageAs = (uri, filename) => {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
39
|
+
const generateMedia = (state, type, elementToCapture, interactionLabel, includeContextInDownload = false) => {
|
|
39
40
|
// Identify Selector
|
|
40
41
|
const baseSvg = document.querySelector(`[data-download-id=${elementToCapture}]`)
|
|
41
42
|
|
|
@@ -58,22 +59,13 @@ const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
|
58
59
|
// Apparently some packages use state.title where others use state.general.title
|
|
59
60
|
const handleFileName = state => {
|
|
60
61
|
// dashboard titles
|
|
61
|
-
if (state?.dashboard?.title)
|
|
62
|
-
return (
|
|
63
|
-
`${state.dashboard.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
64
|
-
)
|
|
62
|
+
if (state?.dashboard?.title) return `${state.dashboard.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
65
63
|
|
|
66
64
|
// map titles
|
|
67
|
-
if (state?.general?.title)
|
|
68
|
-
return (
|
|
69
|
-
`${state.general.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
70
|
-
)
|
|
65
|
+
if (state?.general?.title) return `${state.general.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
71
66
|
|
|
72
67
|
// chart titles
|
|
73
|
-
if (state?.title)
|
|
74
|
-
return (
|
|
75
|
-
`${state.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
76
|
-
)
|
|
68
|
+
if (state?.title) return `${state.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
77
69
|
|
|
78
70
|
return 'no-title'
|
|
79
71
|
}
|
|
@@ -82,18 +74,13 @@ const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
|
82
74
|
|
|
83
75
|
switch (type) {
|
|
84
76
|
case 'image':
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Simple configurable padding (main fix for spacing issues)
|
|
88
|
-
const downloadPadding = state.downloadImagePadding !== undefined ? state.downloadImagePadding : (!state.showTitle ? 35 : 0)
|
|
89
|
-
if (downloadPadding > 0) {
|
|
90
|
-
container.style.padding = `${downloadPadding}px`
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
container.appendChild(baseSvg.cloneNode(true));
|
|
77
|
+
// Prepare screenshot container with all cloning, styling, and transformations
|
|
78
|
+
const container = prepareScreenshotContainer(baseSvg, includeContextInDownload, elementToCapture)
|
|
94
79
|
|
|
95
80
|
const downloadImage = async () => {
|
|
96
|
-
|
|
81
|
+
// Append to main element if exists, otherwise body
|
|
82
|
+
const targetElement = document.querySelector('main') || document.body
|
|
83
|
+
targetElement.appendChild(container)
|
|
97
84
|
|
|
98
85
|
// Fix select elements to show their current selected values before screenshot
|
|
99
86
|
const selectElements = container.querySelectorAll('select')
|
|
@@ -119,10 +106,10 @@ const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
|
119
106
|
el.className.search(/download-buttons|download-links|data-table-container/) !== -1,
|
|
120
107
|
useCORS: true,
|
|
121
108
|
scale: 2, // Better quality
|
|
122
|
-
allowTaint: true
|
|
109
|
+
allowTaint: true
|
|
123
110
|
})
|
|
124
111
|
.then(canvas => {
|
|
125
|
-
|
|
112
|
+
targetElement.removeChild(container) // Clean up container from wherever we appended it
|
|
126
113
|
saveImageAs(canvas.toDataURL(), filename + '.png')
|
|
127
114
|
publishAnalyticsEvent({
|
|
128
115
|
vizType: state.type,
|
|
@@ -163,13 +150,23 @@ const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
|
163
150
|
}
|
|
164
151
|
}
|
|
165
152
|
|
|
166
|
-
|
|
153
|
+
// Button component for Dashboard downloads (renders as actual button)
|
|
154
|
+
const Button = ({
|
|
155
|
+
state,
|
|
156
|
+
text,
|
|
157
|
+
type,
|
|
158
|
+
title,
|
|
159
|
+
elementToCapture,
|
|
160
|
+
interactionLabel = '',
|
|
161
|
+
includeContextInDownload = false
|
|
162
|
+
}) => {
|
|
167
163
|
const buttonClasses = ['btn', 'btn-primary']
|
|
164
|
+
|
|
168
165
|
return (
|
|
169
166
|
<button
|
|
170
167
|
className={buttonClasses.join(' ')}
|
|
171
168
|
title={title}
|
|
172
|
-
onClick={() => generateMedia(state, type, elementToCapture, interactionLabel)}
|
|
169
|
+
onClick={() => generateMedia(state, type, elementToCapture, interactionLabel, includeContextInDownload)}
|
|
173
170
|
style={{ lineHeight: '1.4em' }}
|
|
174
171
|
>
|
|
175
172
|
{buttonText[type]}
|
|
@@ -177,6 +174,34 @@ const Button = ({ state, text, type, title, elementToCapture, interactionLabel =
|
|
|
177
174
|
)
|
|
178
175
|
}
|
|
179
176
|
|
|
177
|
+
// DownloadLink component for Chart/Map downloads (renders as text link)
|
|
178
|
+
const DownloadLink = ({
|
|
179
|
+
state,
|
|
180
|
+
type,
|
|
181
|
+
title,
|
|
182
|
+
elementToCapture,
|
|
183
|
+
interactionLabel = '',
|
|
184
|
+
includeContextInDownload = false
|
|
185
|
+
}) => {
|
|
186
|
+
const vizType = state?.type === 'map' ? 'Map' : 'Chart'
|
|
187
|
+
const format = type === 'pdf' ? 'PDF' : 'PNG'
|
|
188
|
+
const linkText = `Download ${vizType} (${format})`
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<a
|
|
192
|
+
role='button'
|
|
193
|
+
onClick={() => generateMedia(state, type, elementToCapture, interactionLabel, includeContextInDownload)}
|
|
194
|
+
aria-label={title}
|
|
195
|
+
title={title}
|
|
196
|
+
className={`no-border`}
|
|
197
|
+
style={{ cursor: 'pointer' }}
|
|
198
|
+
data-html2canvas-ignore
|
|
199
|
+
>
|
|
200
|
+
{linkText}
|
|
201
|
+
</a>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
180
205
|
// Link to CSV/JSON data
|
|
181
206
|
const Link = ({ config, dashboardDataConfig, interactionLabel }) => {
|
|
182
207
|
let dataConfig = dashboardDataConfig || config
|
|
@@ -238,6 +263,7 @@ const MediaControls = () => null
|
|
|
238
263
|
MediaControls.Section = Section
|
|
239
264
|
MediaControls.Link = Link
|
|
240
265
|
MediaControls.Button = Button
|
|
266
|
+
MediaControls.DownloadLink = DownloadLink
|
|
241
267
|
MediaControls.generateMedia = generateMedia
|
|
242
268
|
|
|
243
269
|
export default MediaControls
|
package/components/ui/Icon.tsx
CHANGED
|
@@ -36,6 +36,7 @@ import iconTable from '../../assets/icon-table.svg'
|
|
|
36
36
|
import iconSankey from '../../assets/icon-sankey.svg'
|
|
37
37
|
import iconRotateLeft from '../../assets/icon-rotate-left.svg'
|
|
38
38
|
import iconCommand from '../../assets/icon-command.svg'
|
|
39
|
+
import iconMagnifyingGlass from '../../assets/icon-magnifying-glass.svg'
|
|
39
40
|
|
|
40
41
|
import '../../styles/v2/components/icon.scss'
|
|
41
42
|
|
|
@@ -75,7 +76,8 @@ const iconHash = {
|
|
|
75
76
|
table: iconTable,
|
|
76
77
|
sankey: iconSankey,
|
|
77
78
|
rotateLeft: iconRotateLeft,
|
|
78
|
-
command: iconCommand
|
|
79
|
+
command: iconCommand,
|
|
80
|
+
magnifyingGlass: iconMagnifyingGlass
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
export type IconType = keyof typeof iconHash
|
|
@@ -13,12 +13,40 @@ type HeaderProps = {
|
|
|
13
13
|
ariaLevel?: number
|
|
14
14
|
config: Visualization
|
|
15
15
|
theme?: string
|
|
16
|
+
titleStyle: 'legacy' | 'large' | 'small'
|
|
17
|
+
noContent?: boolean
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const Title = (props: HeaderProps) => {
|
|
19
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
isDashboard,
|
|
23
|
+
title,
|
|
24
|
+
superTitle,
|
|
25
|
+
classes = [],
|
|
26
|
+
showTitle = true,
|
|
27
|
+
ariaLevel = 2,
|
|
28
|
+
titleStyle,
|
|
29
|
+
noContent = false
|
|
30
|
+
} = props
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
if (titleStyle === 'large' || titleStyle === 'small') {
|
|
33
|
+
const TitleElement = titleStyle === 'large' ? 'h2' : 'h3'
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
title &&
|
|
37
|
+
showTitle && (
|
|
38
|
+
<div
|
|
39
|
+
className={`cove-title cove-title--${titleStyle}${noContent ? ' cove-title--no-content' : ''}`}
|
|
40
|
+
style={props.style}
|
|
41
|
+
>
|
|
42
|
+
{superTitle && <sup>{parse(superTitle)}</sup>}
|
|
43
|
+
<TitleElement>{parse(title)}</TitleElement>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// LEGACY BLOCKY HEADER (Original design with colored backgrounds)
|
|
22
50
|
const updatedClasses = ['cove-component__header', 'component__header', 'mb-3', ...classes]
|
|
23
51
|
|
|
24
52
|
return (
|
|
@@ -96,3 +96,45 @@
|
|
|
96
96
|
.type-sparkline.type-chart .cove-component__header {
|
|
97
97
|
margin: 0px !important;
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
/* ============================================================================
|
|
101
|
+
MODERN TITLE
|
|
102
|
+
============================================================================ */
|
|
103
|
+
|
|
104
|
+
.cdc-open-viz-module .cove-title--small {
|
|
105
|
+
margin-bottom: 0.75rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.cdc-open-viz-module .cove-title--large {
|
|
109
|
+
margin-bottom: 1.33rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Used in dashboards to more closely associate a title-only component with the content below it */
|
|
113
|
+
.cdc-open-viz-module .cove-title--no-content {
|
|
114
|
+
margin-top: 1rem;
|
|
115
|
+
margin-bottom: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.cdc-open-viz-module .cove-title h2 {
|
|
119
|
+
color: #0b4778;
|
|
120
|
+
font-size: 1.95rem;
|
|
121
|
+
font-family: var(--app-font-secondary);
|
|
122
|
+
border-bottom: 1px solid var(--cool-gray-10);
|
|
123
|
+
font-weight: 300;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.cdc-open-viz-module .cove-title h3 {
|
|
127
|
+
font-size: 1.5rem;
|
|
128
|
+
font-family: var(--app-font-secondary);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.cdc-open-viz-module .cove-title sup {
|
|
132
|
+
font-family: var(--app-font-secondary);
|
|
133
|
+
font-size: 0.75rem;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
text-transform: uppercase;
|
|
136
|
+
color: #666;
|
|
137
|
+
sup {
|
|
138
|
+
font-size: 75%;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/dist/cove-main.css
CHANGED
|
@@ -552,9 +552,6 @@ body.post-type-cdc_visualization .cdc-editor .configure .editor-panel {
|
|
|
552
552
|
justify-content: flex-end;
|
|
553
553
|
line-height: 1;
|
|
554
554
|
}
|
|
555
|
-
.cdc-open-viz-module .download-links.brush-active {
|
|
556
|
-
margin-top: 2em;
|
|
557
|
-
}
|
|
558
555
|
.cdc-open-viz-module .download-links a:not(:last-child) {
|
|
559
556
|
margin-right: 10px;
|
|
560
557
|
}
|
|
@@ -1903,6 +1900,32 @@ body.post-type-cdc_visualization .cdc-editor .configure .editor-panel {
|
|
|
1903
1900
|
margin-bottom: 24px;
|
|
1904
1901
|
}
|
|
1905
1902
|
|
|
1903
|
+
.cove-accordion__panel.panel-visual .checkbox-group label.checkbox,
|
|
1904
|
+
.cove-accordion__panel.panel-visual .reverse-labels label.checkbox,
|
|
1905
|
+
.panel-visual .checkbox-group label.checkbox,
|
|
1906
|
+
.panel-visual .reverse-labels label.checkbox {
|
|
1907
|
+
display: flex !important;
|
|
1908
|
+
align-items: center !important;
|
|
1909
|
+
width: 100% !important;
|
|
1910
|
+
margin-bottom: 8px !important;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
.cove-accordion__panel.panel-visual .checkbox-group label.checkbox input[type=checkbox],
|
|
1914
|
+
.cove-accordion__panel.panel-visual .reverse-labels label.checkbox input[type=checkbox],
|
|
1915
|
+
.panel-visual .checkbox-group label.checkbox input[type=checkbox],
|
|
1916
|
+
.panel-visual .reverse-labels label.checkbox input[type=checkbox] {
|
|
1917
|
+
flex-shrink: 0 !important;
|
|
1918
|
+
margin-right: 8px !important;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
.cove-accordion__panel.panel-visual .checkbox-group label.checkbox span,
|
|
1922
|
+
.cove-accordion__panel.panel-visual .reverse-labels label.checkbox span,
|
|
1923
|
+
.panel-visual .checkbox-group label.checkbox span,
|
|
1924
|
+
.panel-visual .reverse-labels label.checkbox span {
|
|
1925
|
+
flex: 1 !important;
|
|
1926
|
+
display: inline-block !important;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1906
1929
|
:root {
|
|
1907
1930
|
--cove-tooltip-bg: #fff;
|
|
1908
1931
|
--cove-tooltip-color: black;
|