@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.
Files changed (77) hide show
  1. package/_stories/Gallery.Charts.stories.tsx +307 -0
  2. package/_stories/Gallery.DataBite.stories.tsx +72 -0
  3. package/_stories/Gallery.Maps.stories.tsx +230 -0
  4. package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
  5. package/_stories/PageART.stories.tsx +192 -0
  6. package/_stories/PageBRFSS.stories.tsx +289 -0
  7. package/_stories/PageCancerRegistries.stories.tsx +199 -0
  8. package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
  9. package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
  10. package/_stories/PageMaternalMortality.stories.tsx +192 -0
  11. package/_stories/PageOralHealth.stories.tsx +196 -0
  12. package/_stories/PageRespiratory.stories.tsx +332 -0
  13. package/_stories/PageSmokingTobacco.stories.tsx +195 -0
  14. package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
  15. package/_stories/PageWastewater.stories.tsx +463 -0
  16. package/assets/icon-magnifying-glass.svg +5 -0
  17. package/assets/icon-warming-stripes.svg +13 -0
  18. package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
  19. package/components/AdvancedEditor/EmbedEditor.tsx +281 -0
  20. package/components/ComboBox/ComboBox.tsx +345 -0
  21. package/components/ComboBox/combobox.styles.css +185 -0
  22. package/components/ComboBox/index.ts +1 -0
  23. package/components/DataTable/DataTable.tsx +132 -58
  24. package/components/DataTable/data-table.css +216 -215
  25. package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
  26. package/components/EditorPanel/ColumnsEditor.tsx +37 -19
  27. package/components/EditorPanel/DataTableEditor.tsx +51 -25
  28. package/components/EditorPanel/EditorPanel.styles.css +16 -0
  29. package/components/EditorPanel/EditorPanel.tsx +144 -0
  30. package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
  31. package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
  32. package/components/EditorPanel/Inputs.tsx +33 -7
  33. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +236 -175
  34. package/components/EditorPanel/sections/VisualSection.tsx +169 -0
  35. package/components/Filters/Filters.tsx +31 -5
  36. package/components/Filters/helpers/getNestedOptions.ts +2 -1
  37. package/components/Filters/helpers/handleSorting.ts +1 -1
  38. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
  39. package/components/Layout/components/Visualization/index.tsx +16 -1
  40. package/components/Layout/components/Visualization/visualizations.scss +7 -0
  41. package/components/Legend/Legend.Gradient.tsx +1 -1
  42. package/components/MediaControls.tsx +53 -27
  43. package/components/ui/Icon.tsx +3 -1
  44. package/components/ui/Title/index.tsx +30 -2
  45. package/components/ui/Title/title.styles.css +42 -0
  46. package/dist/cove-main.css +26 -3
  47. package/dist/cove-main.css.map +1 -1
  48. package/generateViteConfig.js +8 -1
  49. package/helpers/addValuesToFilters.ts +6 -1
  50. package/helpers/coveUpdateWorker.ts +19 -12
  51. package/helpers/embedCodeGenerator.ts +109 -0
  52. package/helpers/getUniqueValues.ts +19 -0
  53. package/helpers/hashObj.ts +25 -0
  54. package/helpers/isRightAlignedTableValue.js +5 -0
  55. package/helpers/metrics/helpers.ts +1 -0
  56. package/helpers/pivotData.ts +2 -2
  57. package/helpers/prepareScreenshot.ts +268 -0
  58. package/helpers/queryStringUtils.ts +29 -0
  59. package/helpers/tests/prepareScreenshot.test.ts +414 -0
  60. package/helpers/tests/queryStringUtils.test.ts +381 -0
  61. package/helpers/tests/testStandaloneBuild.ts +23 -5
  62. package/helpers/useDataVizClasses.ts +0 -1
  63. package/helpers/ver/4.26.1.ts +80 -0
  64. package/hooks/useDataColumns.ts +63 -0
  65. package/hooks/useFilterManagement.ts +94 -0
  66. package/hooks/useLegendSeparators.ts +26 -0
  67. package/hooks/useListManagement.ts +192 -0
  68. package/package.json +4 -3
  69. package/styles/_button-section.scss +0 -3
  70. package/types/Axis.ts +1 -0
  71. package/types/ForecastingSeriesKey.ts +1 -0
  72. package/types/MarkupInclude.ts +1 -0
  73. package/types/Series.ts +3 -0
  74. package/types/Table.ts +1 -0
  75. package/types/Visualization.ts +1 -0
  76. package/types/VizFilter.ts +1 -0
  77. 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 '@cdc/map/src/helpers'
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(newFilters?.[index]?.active).toLowerCase()}`
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(filterStyle)
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.includes(orderedValue))
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' && singleFilter.filterStyle !== 'nested-dropdown') {
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: ChartConfig | DataBiteConfig | WaffleChartConfig | MarkupIncludeConfig | DashboardFilters | MapConfig | DataTableConfig
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 '@cdc/map/src/hooks/useLegendSeparators'
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
- const container = document.createElement('div')
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
- document.body.appendChild(container) // Append container to the DOM
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
- document.body.removeChild(container) // Clean up container
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
- const Button = ({ state, text, type, title, elementToCapture, interactionLabel = '' }) => {
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
@@ -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 { isDashboard, title, superTitle, classes = [], showTitle = true, ariaLevel = 2 } = props
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
- // standard classes every vis should have
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
+ }
@@ -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;