@cdc/core 4.25.8 → 4.25.11

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 (163) hide show
  1. package/_stories/StoryRenderingTests.stories.tsx +164 -0
  2. package/components/AdvancedEditor/AdvancedEditor.tsx +32 -9
  3. package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
  4. package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
  5. package/components/CustomColorsEditor/index.ts +1 -0
  6. package/components/DataTable/DataTable.tsx +56 -38
  7. package/components/DataTable/DataTableStandAlone.tsx +8 -3
  8. package/components/DataTable/components/ChartHeader.tsx +44 -14
  9. package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
  10. package/components/DataTable/components/ExpandCollapse.tsx +10 -1
  11. package/components/DataTable/components/MapHeader.tsx +24 -13
  12. package/components/DataTable/data-table.css +12 -0
  13. package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
  14. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -4
  15. package/components/DataTable/helpers/standardizeState.js +2 -2
  16. package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
  17. package/components/DownloadButton.tsx +40 -14
  18. package/components/EditorPanel/DataTableEditor.tsx +3 -3
  19. package/components/EditorPanel/EditorPanel.styles.css +423 -0
  20. package/components/EditorPanel/FootnotesEditor.tsx +44 -37
  21. package/components/EditorPanel/Inputs.tsx +12 -2
  22. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
  23. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -2
  24. package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
  25. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +450 -0
  26. package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
  27. package/components/ErrorBoundary.jsx +3 -1
  28. package/components/Filters/Filters.tsx +52 -24
  29. package/components/Filters/components/Dropdown.tsx +6 -1
  30. package/components/Filters/components/Tabs.tsx +1 -0
  31. package/components/Footnotes/Footnotes.tsx +35 -25
  32. package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
  33. package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
  34. package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
  35. package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
  36. package/components/HeaderThemeSelector/index.ts +2 -0
  37. package/components/Layout/styles/editor.scss +2 -1
  38. package/components/Legend/Legend.Gradient.tsx +3 -6
  39. package/components/LegendShape.tsx +121 -3
  40. package/components/Loader/Loader.tsx +1 -1
  41. package/components/MediaControls.tsx +72 -21
  42. package/components/PaletteConversionModal.tsx +90 -0
  43. package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
  44. package/components/PaletteSelector/PaletteSelector.css +94 -0
  45. package/components/PaletteSelector/PaletteSelector.tsx +112 -0
  46. package/components/PaletteSelector/index.ts +2 -0
  47. package/components/RichTooltip/RichTooltip.tsx +1 -0
  48. package/components/Table/Table.tsx +3 -1
  49. package/components/Table/components/Cell.tsx +23 -2
  50. package/components/Table/components/Row.tsx +5 -3
  51. package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
  52. package/components/_stories/DataTable.stories.tsx +1 -1
  53. package/components/_stories/Filters.stories.tsx +21 -2
  54. package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
  55. package/components/_stories/Footnotes.stories.tsx +769 -4
  56. package/components/_stories/Inputs.stories.tsx +3 -3
  57. package/components/_stories/MultiSelect.stories.tsx +3 -3
  58. package/components/_stories/NestedDropdown.stories.tsx +1 -1
  59. package/components/_stories/Table.stories.tsx +1 -1
  60. package/components/_stories/styles.scss +0 -1
  61. package/components/elements/_stories/Button.stories.tsx +1 -1
  62. package/components/elements/_stories/Card.stories.tsx +1 -1
  63. package/components/inputs/InputToggle.tsx +2 -0
  64. package/components/managers/DataDesigner.tsx +10 -9
  65. package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
  66. package/components/ui/Accordion.jsx +1 -1
  67. package/components/ui/Tooltip.tsx +2 -1
  68. package/components/ui/_stories/Accordion.stories.tsx +1 -1
  69. package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
  70. package/components/ui/_stories/Colors.stories.tsx +330 -0
  71. package/components/ui/_stories/IconGallery.stories.tsx +316 -0
  72. package/components/ui/_stories/Title.stories.tsx +1 -1
  73. package/components/ui/accordion.styles.css +57 -0
  74. package/contexts/EditorContext.ts +18 -0
  75. package/contexts/editor.actions.ts +28 -0
  76. package/contexts/editor.reducer.ts +94 -0
  77. package/data/chartColorPalettes.ts +118 -0
  78. package/data/colorPalettes.ts +9 -0
  79. package/data/mapColorPalettes.ts +45 -0
  80. package/data/sharedPalettes.ts +50 -0
  81. package/dist/cove-main.css +63 -14
  82. package/dist/cove-main.css.map +1 -1
  83. package/generateViteConfig.js +80 -0
  84. package/helpers/addValuesToFilters.ts +7 -3
  85. package/helpers/cloneConfig.ts +31 -0
  86. package/helpers/configDataHelpers.ts +128 -0
  87. package/helpers/configHelpers.ts +27 -0
  88. package/helpers/constants.ts +42 -2
  89. package/helpers/cove/number.ts +33 -12
  90. package/helpers/coveUpdateWorker.ts +15 -3
  91. package/helpers/fetchRemoteData.ts +3 -15
  92. package/helpers/filterColorPalettes.ts +152 -0
  93. package/helpers/generateColorsArray.ts +13 -0
  94. package/helpers/getColorPaletteVersion.ts +33 -0
  95. package/helpers/getPaletteAccessor.ts +18 -0
  96. package/helpers/markupProcessor.ts +220 -0
  97. package/helpers/mergeCustomOrderValues.ts +37 -0
  98. package/helpers/metrics/helpers.ts +42 -19
  99. package/helpers/metrics/types.ts +48 -9
  100. package/helpers/metrics/utils.ts +34 -0
  101. package/helpers/palettes/colorDistributions.ts +56 -0
  102. package/helpers/palettes/migratePaletteName.ts +150 -0
  103. package/helpers/palettes/standardizePaletteNames.ts +77 -0
  104. package/helpers/palettes/utils.ts +267 -0
  105. package/helpers/parseCsvWithQuotes.ts +65 -0
  106. package/helpers/queryStringUtils.ts +13 -0
  107. package/helpers/testing.ts +358 -0
  108. package/helpers/tests/addValuesToFilters.test.ts +1 -2
  109. package/helpers/tests/generateColorsArray.test.ts +24 -0
  110. package/helpers/tests/markupProcessor.test.ts +538 -0
  111. package/helpers/tests/testStandaloneBuild.ts +44 -0
  112. package/helpers/useMarkupVariables.ts +31 -0
  113. package/helpers/vegaConfig.ts +0 -1
  114. package/helpers/ver/4.24.10.ts +2 -1
  115. package/helpers/ver/4.24.11.ts +2 -1
  116. package/helpers/ver/4.24.3.ts +2 -1
  117. package/helpers/ver/4.24.4.ts +2 -1
  118. package/helpers/ver/4.24.5.ts +2 -1
  119. package/helpers/ver/4.24.7.ts +2 -1
  120. package/helpers/ver/4.24.9.ts +2 -1
  121. package/helpers/ver/4.25.1.ts +2 -1
  122. package/helpers/ver/4.25.10.ts +36 -0
  123. package/helpers/ver/4.25.11.ts +13 -0
  124. package/helpers/ver/4.25.3.ts +2 -1
  125. package/helpers/ver/4.25.4.ts +2 -1
  126. package/helpers/ver/4.25.6.ts +2 -1
  127. package/helpers/ver/4.25.7.ts +2 -1
  128. package/helpers/ver/4.25.8.ts +2 -1
  129. package/helpers/ver/4.25.9.ts +293 -0
  130. package/helpers/ver/tests/4.25.10.test.ts +204 -0
  131. package/helpers/ver/tests/4.25.8.test.ts +1 -1
  132. package/helpers/ver/tests/4.25.9.test.ts +51 -0
  133. package/helpers/viewports.ts +2 -0
  134. package/hooks/useColorPalette.ts +79 -0
  135. package/package.json +13 -4
  136. package/styles/_common-components.css +73 -0
  137. package/styles/_global.scss +32 -10
  138. package/styles/base.scss +8 -55
  139. package/styles/cove-main.scss +3 -1
  140. package/styles/filters.scss +10 -3
  141. package/styles/v2/base/index.scss +0 -1
  142. package/styles/v2/components/button.scss +4 -3
  143. package/styles/v2/components/editor.scss +16 -7
  144. package/styles/v2/layout/_data-table.scss +3 -2
  145. package/styles/v2/themes/_color-definitions.scss +18 -17
  146. package/styles/v2/utils/_breakpoints.scss +1 -1
  147. package/styles/v2/utils/index.scss +0 -1
  148. package/styles/waiting.scss +1 -1
  149. package/testing-setup.js +32 -0
  150. package/types/MarkupInclude.ts +8 -2
  151. package/types/MarkupVariable.ts +19 -0
  152. package/types/VizFilter.ts +2 -0
  153. package/vitest.config.ts +16 -0
  154. package/components/ui/_stories/Colors.stories.mdx +0 -220
  155. package/components/ui/_stories/IconGallery.stories.mdx +0 -14
  156. package/data/colorPalettes.js +0 -171
  157. package/helpers/formatConfigBeforeSave.ts +0 -135
  158. package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
  159. package/styles/_mixins.scss +0 -13
  160. package/styles/v2/base/_typography.scss +0 -0
  161. package/styles/v2/components/guidance-block.scss +0 -74
  162. package/styles/v2/utils/_functions.scss +0 -0
  163. /package/{styles/_typography.scss → testBuild.js} +0 -0
@@ -0,0 +1,209 @@
1
+ import React, { useState } from 'react'
2
+ import { TextField } from '../EditorPanel/Inputs'
3
+ import './CustomColorsEditor.css'
4
+
5
+ interface CustomColorsEditorProps {
6
+ colors: string[]
7
+ onChange: (colors: string[]) => void
8
+ label?: string
9
+ minColors?: number
10
+ maxColors?: number
11
+ }
12
+
13
+ const CustomColorsEditor: React.FC<CustomColorsEditorProps> = ({
14
+ colors = [],
15
+ onChange,
16
+ label = 'Custom Colors',
17
+ minColors = 1,
18
+ maxColors = 20
19
+ }) => {
20
+ const [draggedIndex, setDraggedIndex] = useState<number | null>(null)
21
+
22
+ const handleColorChange = (index: number, newColor: string) => {
23
+ const newColors = [...colors]
24
+ newColors[index] = newColor
25
+ onChange(newColors)
26
+ }
27
+
28
+ const handleAddColor = () => {
29
+ if (colors.length < maxColors) {
30
+ // Add a new color (default to the last color or a neutral color)
31
+ const defaultColor = colors.length > 0 ? colors[colors.length - 1] : '#3366cc'
32
+ onChange([...colors, defaultColor])
33
+ }
34
+ }
35
+
36
+ const handleRemoveColor = (index: number) => {
37
+ if (colors.length > minColors) {
38
+ const newColors = colors.filter((_, i) => i !== index)
39
+ onChange(newColors)
40
+ }
41
+ }
42
+
43
+ const handleMoveUp = (index: number) => {
44
+ if (index > 0) {
45
+ const newColors = [...colors]
46
+ ;[newColors[index - 1], newColors[index]] = [newColors[index], newColors[index - 1]]
47
+ onChange(newColors)
48
+ }
49
+ }
50
+
51
+ const handleMoveDown = (index: number) => {
52
+ if (index < colors.length - 1) {
53
+ const newColors = [...colors]
54
+ ;[newColors[index], newColors[index + 1]] = [newColors[index + 1], newColors[index]]
55
+ onChange(newColors)
56
+ }
57
+ }
58
+
59
+ const handleDragStart = (index: number) => {
60
+ setDraggedIndex(index)
61
+ }
62
+
63
+ const handleDragOver = (e: React.DragEvent, index: number) => {
64
+ e.preventDefault()
65
+ }
66
+
67
+ const handleDrop = (e: React.DragEvent, dropIndex: number) => {
68
+ e.preventDefault()
69
+
70
+ if (draggedIndex === null || draggedIndex === dropIndex) {
71
+ setDraggedIndex(null)
72
+ return
73
+ }
74
+
75
+ const newColors = [...colors]
76
+ const draggedColor = newColors[draggedIndex]
77
+ newColors.splice(draggedIndex, 1)
78
+ newColors.splice(dropIndex, 0, draggedColor)
79
+
80
+ onChange(newColors)
81
+ setDraggedIndex(null)
82
+ }
83
+
84
+ return (
85
+ <div className="custom-colors-editor">
86
+ <label className="custom-colors-label">{label}</label>
87
+
88
+ <div className="custom-colors-notice">
89
+ <svg className="notice-icon" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
90
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
91
+ <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
92
+ </svg>
93
+ <span>
94
+ <strong>Accessibility Notice:</strong> When using custom colors, conduct a{' '}
95
+ Section 508 review to ensure adequate color contrast and accessibility compliance.{' '}
96
+ </span>
97
+ </div>
98
+
99
+ <div className="custom-colors-preview">
100
+ {colors.map((color, index) => (
101
+ <div
102
+ key={index}
103
+ className="preview-swatch"
104
+ style={{ backgroundColor: color }}
105
+ title={`Color ${index + 1}: ${color}`}
106
+ />
107
+ ))}
108
+ </div>
109
+
110
+ <div className="custom-colors-list">
111
+ {colors.map((color, index) => (
112
+ <div
113
+ key={index}
114
+ className={`custom-color-item ${draggedIndex === index ? 'dragging' : ''}`}
115
+ draggable
116
+ onDragStart={() => handleDragStart(index)}
117
+ onDragOver={(e) => handleDragOver(e, index)}
118
+ onDrop={(e) => handleDrop(e, index)}
119
+ >
120
+ <div className="color-item-controls">
121
+ <span className="color-item-drag-handle" title="Drag to reorder">
122
+ ⋮⋮
123
+ </span>
124
+ {/* <span className="color-item-number">{index + 1}.</span> */}
125
+
126
+ {/* <input
127
+ type="color"
128
+ value={color}
129
+ onChange={(e) => handleColorChange(index, e.target.value)}
130
+ className="color-picker"
131
+ title="Click to change color"
132
+ aria-label={`Color picker for color ${index + 1}`}
133
+ /> */}
134
+
135
+ <div className="color-input-wrapper">
136
+ <TextField
137
+ type="text"
138
+ value={color}
139
+ label=""
140
+ section="colors"
141
+ subsection={null}
142
+ fieldName={`color-${index}`}
143
+ updateField={(section, subsection, fieldName, value) => {
144
+ handleColorChange(index, value)
145
+ }}
146
+ placeholder="#000000"
147
+ maxLength={15}
148
+ className="color-text-input"
149
+ />
150
+ </div>
151
+
152
+ <div className="color-item-buttons">
153
+ <button
154
+ type="button"
155
+ onClick={() => handleMoveUp(index)}
156
+ disabled={index === 0}
157
+ className="btn-move"
158
+ title="Move up"
159
+ aria-label="Move color up"
160
+ >
161
+
162
+ </button>
163
+
164
+ <button
165
+ type="button"
166
+ onClick={() => handleMoveDown(index)}
167
+ disabled={index === colors.length - 1}
168
+ className="btn-move"
169
+ title="Move down"
170
+ aria-label="Move color down"
171
+ >
172
+
173
+ </button>
174
+
175
+ <button
176
+ type="button"
177
+ onClick={() => handleRemoveColor(index)}
178
+ disabled={colors.length <= minColors}
179
+ className="btn-remove"
180
+ title="Remove color"
181
+ aria-label="Remove color"
182
+ >
183
+ ×
184
+ </button>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ ))}
189
+ </div>
190
+
191
+ <button
192
+ type="button"
193
+ onClick={handleAddColor}
194
+ disabled={colors.length >= maxColors}
195
+ className="btn-add-color"
196
+ >
197
+ + Add Color
198
+ </button>
199
+
200
+ <div className="custom-colors-info">
201
+ {colors.length} color{colors.length !== 1 ? 's' : ''}
202
+ {colors.length < minColors && ` (minimum ${minColors} required)`}
203
+ {colors.length >= maxColors && ` (maximum reached)`}
204
+ </div>
205
+ </div>
206
+ )
207
+ }
208
+
209
+ export default CustomColorsEditor
@@ -0,0 +1 @@
1
+ export { default as CustomColorsEditor } from './CustomColorsEditor'
@@ -1,4 +1,4 @@
1
- import { useEffect, useState, useMemo } from 'react'
1
+ import { useEffect, useState, useMemo, useRef } from 'react'
2
2
  import { timeParse } from 'd3-time-format'
3
3
 
4
4
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
@@ -53,6 +53,10 @@ export type DataTableProps = {
53
53
  // determines if columns should be wrapped in the table
54
54
  wrapColumns?: boolean
55
55
  interactionLabel?: string
56
+ // Map-specific props (optional)
57
+ legendMemo?: React.MutableRefObject<Map<any, any>>
58
+ legendSpecialClassLastMemo?: React.MutableRefObject<Map<any, any>>
59
+ runtimeLegend?: any
56
60
  }
57
61
 
58
62
  const DataTable = (props: DataTableProps) => {
@@ -95,6 +99,11 @@ const DataTable = (props: DataTableProps) => {
95
99
 
96
100
  const [accessibilityLabel, setAccessibilityLabel] = useState('')
97
101
 
102
+ // Create default refs for map-specific props when not provided
103
+ const defaultLegendMemo = useRef(new Map())
104
+ const defaultLegendSpecialClassLastMemo = useRef(new Map())
105
+ const defaultRuntimeLegend = null
106
+
98
107
  const isVertical = !(config.type === 'chart' && !config.table?.showVertical)
99
108
 
100
109
  const rand = Math.random().toString(16).substr(2, 8)
@@ -136,23 +145,23 @@ const DataTable = (props: DataTableProps) => {
136
145
  const rows =
137
146
  isVertical && sortBy.column
138
147
  ? rawRows.sort((a, b) => {
139
- let dataA
140
- let dataB
141
- if (config.type === 'map' && config.columns) {
142
- const sortByColName = config.columns[sortBy.column].name
143
- dataA = runtimeData[a][sortByColName]
144
- dataB = runtimeData[b][sortByColName]
145
- }
146
- if (['chart', 'dashboard', 'table'].includes(config.type)) {
147
- dataA = runtimeData[a][sortBy.column]
148
- dataB = runtimeData[b][sortBy.column]
149
- }
150
- if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
151
- dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
152
- dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
153
- }
154
- return dataA || dataB ? customSort(dataA, dataB, sortBy, config) : 0
155
- })
148
+ let dataA
149
+ let dataB
150
+ if (config.type === 'map' && config.columns) {
151
+ const sortByColName = config.columns[sortBy.column].name
152
+ dataA = runtimeData[a][sortByColName]
153
+ dataB = runtimeData[b][sortByColName]
154
+ }
155
+ if (['chart', 'dashboard', 'table'].includes(config.type)) {
156
+ dataA = runtimeData[a][sortBy.column]
157
+ dataB = runtimeData[b][sortBy.column]
158
+ }
159
+ if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
160
+ dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
161
+ dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
162
+ }
163
+ return dataA || dataB ? customSort(dataA, dataB, sortBy, config) : 0
164
+ })
156
165
  : rawRows
157
166
 
158
167
  const limitHeight = {
@@ -231,17 +240,17 @@ const DataTable = (props: DataTableProps) => {
231
240
  const visibleData =
232
241
  config.type === 'map'
233
242
  ? getMapRowData(
234
- rows,
235
- columns,
236
- config,
237
- formatLegendLocation,
238
- runtimeData as Record<string, Object>,
239
- displayGeoName,
240
- filterColumns
241
- )
243
+ rows,
244
+ columns,
245
+ config,
246
+ formatLegendLocation,
247
+ runtimeData as Record<string, Object>,
248
+ displayGeoName,
249
+ filterColumns
250
+ )
242
251
  : runtimeData.map(d => {
243
- return _.pick(d, [...filterColumns, ...dataSeriesColumns])
244
- })
252
+ return _.pick(d, [...filterColumns, ...dataSeriesColumns])
253
+ })
245
254
  const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
246
255
 
247
256
  // only use fullGeoName on County maps and no other
@@ -277,7 +286,16 @@ const DataTable = (props: DataTableProps) => {
277
286
 
278
287
  const childrenMatrix =
279
288
  config.type === 'map'
280
- ? mapCellMatrix({ ...props, rows, wrapColumns, runtimeData, viewport })
289
+ ? mapCellMatrix({
290
+ ...props,
291
+ rows,
292
+ wrapColumns,
293
+ runtimeData,
294
+ viewport,
295
+ legendMemo: props.legendMemo || defaultLegendMemo,
296
+ legendSpecialClassLastMemo: props.legendSpecialClassLastMemo || defaultLegendSpecialClassLastMemo,
297
+ runtimeLegend: props.runtimeLegend || defaultRuntimeLegend
298
+ })
281
299
  : chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
282
300
 
283
301
  const useBottomExpandCollapse = config.table.showBottomCollapse && expanded && Array.isArray(childrenMatrix)
@@ -285,12 +303,12 @@ const DataTable = (props: DataTableProps) => {
285
303
  // If every value in a column is a number, record the column index so the header and cells can be right-aligned
286
304
  const rightAlignedCols = childrenMatrix.length
287
305
  ? Object.fromEntries(
288
- Object.keys(childrenMatrix[0])
289
- .filter(
290
- i => childrenMatrix.filter(row => isRightAlignedTableValue(row[i])).length === childrenMatrix.length
291
- )
292
- .map(x => [x, true])
293
- )
306
+ Object.keys(childrenMatrix[0])
307
+ .filter(
308
+ i => childrenMatrix.filter(row => isRightAlignedTableValue(row[i])).length === childrenMatrix.length
309
+ )
310
+ .map(x => [x, true])
311
+ )
294
312
  : {}
295
313
 
296
314
  const showCollapseButton = config.table.collapsible !== false && useBottomExpandCollapse
@@ -305,6 +323,7 @@ const DataTable = (props: DataTableProps) => {
305
323
  fileName={`${vizTitle || 'data-table'}.csv`}
306
324
  headerColor={headerColor}
307
325
  interactionLabel={interactionLabel}
326
+ config={config}
308
327
  />
309
328
  )}
310
329
  </MediaControls.Section>
@@ -365,9 +384,8 @@ const DataTable = (props: DataTableProps) => {
365
384
  )
366
385
  }
367
386
  tableOptions={{
368
- className: `table table-striped table-width-unset ${
369
- expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'
370
- }${isVertical ? '' : ' horizontal'}`,
387
+ className: `table table-striped table-width-unset ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'
388
+ }${isVertical ? '' : ' horizontal'}`,
371
389
  'aria-live': 'assertive',
372
390
  'aria-rowcount': config?.data?.length ? config.data.length : -1,
373
391
  hidden: !expanded,
@@ -1,7 +1,6 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import { ViewPort } from '../../types/ViewPort'
3
- import Footnotes from '../../types/Footnotes'
4
- import EditorWrapper from '../EditorWrapper/EditorWrapper'
3
+ import EditorWrapper from '../EditorWrapper'
5
4
  import DataTable from './DataTable'
6
5
  import DataTableEditorPanel from './components/DataTableEditorPanel'
7
6
  import Filters from '../Filters'
@@ -73,7 +72,13 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
73
72
  viewport={viewport || 'lg'}
74
73
  interactionLabel={interactionLabel}
75
74
  />
76
- <FootnotesStandAlone config={config.footnotes} filters={config.filters?.filter(f => f.filterFootnotes)} />
75
+ <FootnotesStandAlone
76
+ config={config.footnotes}
77
+ filters={config.filters?.filter(f => f.filterFootnotes)}
78
+ markupVariables={config['markupVariables']}
79
+ enableMarkupVariables={config['enableMarkupVariables']}
80
+ data={config.data}
81
+ />
77
82
  </>
78
83
  )
79
84
  }
@@ -7,6 +7,7 @@ import { getNewSortBy } from '../helpers/getNewSortBy'
7
7
  import parse from 'html-react-parser'
8
8
  import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
9
9
  import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
10
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
10
11
 
11
12
  type ChartHeaderProps = {
12
13
  data
@@ -61,9 +62,8 @@ const ChartHeader = ({
61
62
  if (columnHeaderText === notApplicableText) return
62
63
 
63
64
  return (
64
- <span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
65
- sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
66
- } order`}</span>
65
+ <span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
66
+ } order`}</span>
67
67
  )
68
68
  }
69
69
 
@@ -116,18 +116,29 @@ const ChartHeader = ({
116
116
  scope='col'
117
117
  onClick={() => {
118
118
  if (hasRowType) return
119
- publishAnalyticsEvent(
120
- `data_table_sort_by|${newSortBy.column}|${
121
- newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'undefined'
122
- }`,
123
- 'click',
124
- interactionLabel
125
- )
119
+ publishAnalyticsEvent({
120
+ vizType: config.type,
121
+ vizSubType: getVizSubType(config),
122
+ eventType: `data_table_sort`,
123
+ eventAction: 'click',
124
+ eventLabel: interactionLabel,
125
+ vizTitle: getVizTitle(config),
126
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
127
+ })
126
128
  setSortBy(newSortBy)
127
129
  }}
128
130
  onKeyDown={e => {
129
131
  if (hasRowType) return
130
- if (e.keyCode === 13) {
132
+ if (e.key === 'Enter') {
133
+ publishAnalyticsEvent({
134
+ vizType: config.type,
135
+ vizSubType: getVizSubType(config),
136
+ eventType: `data_table_sort`,
137
+ eventAction: 'keyboard',
138
+ eventLabel: interactionLabel,
139
+ vizTitle: getVizTitle(config),
140
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
141
+ })
131
142
  setSortBy(newSortBy)
132
143
  }
133
144
  }}
@@ -137,7 +148,7 @@ const ChartHeader = ({
137
148
  : { 'aria-sort': 'descending' }
138
149
  : null)}
139
150
  >
140
- <ColumnHeadingText text={text} column={column} config={config} />
151
+ <ColumnHeadingText text={text} config={config} />
141
152
  {isSortedCol && <SortIcon ascending={sortByAsc} />}
142
153
  <ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
143
154
  </th>
@@ -171,10 +182,29 @@ const ChartHeader = ({
171
182
  role='columnheader'
172
183
  scope='col'
173
184
  onClick={() => {
185
+ if (hasRowType) return
186
+ publishAnalyticsEvent({
187
+ vizType: config.type,
188
+ vizSubType: getVizSubType(config),
189
+ eventType: `data_table_sort`,
190
+ eventAction: 'click',
191
+ eventLabel: interactionLabel,
192
+ vizTitle: getVizTitle(config),
193
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
194
+ })
174
195
  setSortBy(newSortBy)
175
196
  }}
176
197
  onKeyDown={e => {
177
- if (e.keyCode === 13) {
198
+ if (e.key === 'Enter') {
199
+ publishAnalyticsEvent({
200
+ vizType: config.type,
201
+ vizSubType: getVizSubType(config),
202
+ eventType: `data_table_sort`,
203
+ eventAction: 'keyboard',
204
+ eventLabel: interactionLabel,
205
+ vizTitle: getVizTitle(config),
206
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
207
+ })
178
208
  setSortBy(newSortBy)
179
209
  }
180
210
  }}
@@ -184,7 +214,7 @@ const ChartHeader = ({
184
214
  : { 'aria-sort': 'descending' }
185
215
  : null)}
186
216
  >
187
- <ColumnHeadingText text={text} column={column} config={config} />
217
+ <ColumnHeadingText text={text} config={config} />
188
218
  {isSortedCol && <SortIcon ascending={sortByAsc} />}
189
219
 
190
220
  <ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
@@ -34,7 +34,17 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
34
34
  })
35
35
  }
36
36
 
37
- const columns = Object.keys(config.originalFormattedData?.[0] || {})
37
+ const columns = Object.keys(
38
+ config.originalFormattedData?.[0] || config.formattedData?.[0] || config.data?.[0] || {}
39
+ )
40
+ // If no data is available, fallback to column names from config.columns
41
+ const columnsFromConfig = config.columns
42
+ ? Object.values(config.columns)
43
+ .map(col => col.name)
44
+ .filter(Boolean)
45
+ : []
46
+ const finalColumns = columns.length > 0 ? columns : columnsFromConfig
47
+
38
48
  return (
39
49
  <Accordion allowZeroExpanded={true}>
40
50
  <AccordionItem>
@@ -63,7 +73,7 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
63
73
  <AccordionItemButton>Data Table</AccordionItemButton>
64
74
  </AccordionItemHeading>
65
75
  <AccordionItemPanel>
66
- <DataTableEditor config={config} columns={columns} updateField={updateField} isDashboard={true} />
76
+ <DataTableEditor config={config} columns={finalColumns} updateField={updateField} isDashboard={true} />
67
77
  </AccordionItemPanel>
68
78
  </AccordionItem>
69
79
  <AccordionItem>
@@ -1,3 +1,4 @@
1
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
1
2
  import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
2
3
  import { Visualization } from '../../../types/Visualization'
3
4
  import Icon from '../../ui/Icon'
@@ -17,7 +18,15 @@ const ExpandCollapse = ({ expanded, setExpanded, tableTitle, config, interaction
17
18
  role='button'
18
19
  className={expanded ? 'data-table-heading p-3' : 'collapsed data-table-heading p-3'}
19
20
  onClick={() => {
20
- publishAnalyticsEvent('data_table_toggled', 'click', interactionLabel, config.type || 'unknown')
21
+ publishAnalyticsEvent({
22
+ vizType: config?.type,
23
+ vizSubType: getVizSubType(config),
24
+ eventType: 'expand_collapse_toggled',
25
+ eventAction: 'click',
26
+ eventLabel: interactionLabel,
27
+ vizTitle: getVizTitle(config),
28
+ specifics: expanded ? 'collapsed' : 'expanded'
29
+ })
21
30
  setExpanded(!expanded)
22
31
  }}
23
32
  tabIndex={0}
@@ -3,6 +3,7 @@ import ScreenReaderText from '../../elements/ScreenReaderText'
3
3
  import { SortIcon } from './SortIcon'
4
4
  import { getNewSortBy } from '../helpers/getNewSortBy'
5
5
  import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
6
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
6
7
 
7
8
  type MapHeaderProps = DataTableProps & {
8
9
  sortBy: { column; asc }
@@ -56,17 +57,28 @@ const MapHeader = ({
56
57
  role='columnheader'
57
58
  scope='col'
58
59
  onClick={() => {
59
- publishAnalyticsEvent(
60
- `data_table_sort_by|${newSortBy.column}|${
61
- newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'undefined'
62
- }`,
63
- 'click',
64
- interactionLabel
65
- )
60
+ publishAnalyticsEvent({
61
+ vizType: config.type,
62
+ vizSubType: getVizSubType(config),
63
+ eventType: `data_table_sort`,
64
+ eventAction: 'click',
65
+ eventLabel: interactionLabel,
66
+ vizTitle: getVizTitle(config),
67
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
68
+ })
66
69
  setSortBy(newSortBy)
67
70
  }}
68
71
  onKeyDown={e => {
69
- if (e.keyCode === 13) {
72
+ if (e.key === 'Enter') {
73
+ publishAnalyticsEvent({
74
+ vizType: config.type,
75
+ vizSubType: getVizSubType(config),
76
+ eventType: `data_table_sort`,
77
+ eventAction: 'keyboard',
78
+ eventLabel: interactionLabel,
79
+ vizTitle: getVizTitle(config),
80
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
81
+ })
70
82
  setSortBy(newSortBy)
71
83
  }
72
84
  }}
@@ -77,15 +89,14 @@ const MapHeader = ({
77
89
  : { 'aria-sort': 'descending' }
78
90
  : null)}
79
91
  >
80
- <ColumnHeadingText text={text} config={config} column={column} />
92
+ <ColumnHeadingText text={text} config={config} />
81
93
  <SortIcon ascending={sortByAsc} />
82
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
83
- sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
84
- } order`}</span>
94
+ <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
95
+ } order`}</span>
85
96
  </th>
86
97
  )
87
98
  })}
88
- </tr>
99
+ </tr >
89
100
  )
90
101
  }
91
102
 
@@ -145,7 +145,13 @@ table.data-table {
145
145
 
146
146
  svg {
147
147
  margin-left: 1rem;
148
+
149
+ &.legend-shape-svg {
150
+ display: flex;
151
+ margin-left: 0 !important;
152
+ }
148
153
  }
154
+
149
155
  }
150
156
 
151
157
  td a {
@@ -160,6 +166,12 @@ table.data-table {
160
166
  text-decoration: none;
161
167
  }
162
168
 
169
+ td div a {
170
+ position: relative;
171
+ padding: 0;
172
+ display: inline;
173
+ }
174
+
163
175
  td span.table-link {
164
176
  text-decoration: underline;
165
177
  cursor: pointer;