@cdc/core 4.26.1 → 4.26.3

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 (249) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/LICENSE +201 -0
  4. package/_stories/Gallery.Charts.stories.tsx +35 -42
  5. package/_stories/Gallery.DataBite.stories.tsx +15 -8
  6. package/_stories/Gallery.Maps.stories.tsx +37 -28
  7. package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
  8. package/_stories/PageART.stories.tsx +5 -4
  9. package/_stories/PageBRFSS.stories.tsx +21 -16
  10. package/_stories/PageCancerRegistries.stories.tsx +15 -15
  11. package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
  12. package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
  13. package/_stories/PageMaternalMortality.stories.tsx +5 -4
  14. package/_stories/PageOralHealth.stories.tsx +15 -10
  15. package/_stories/PageRespiratory.stories.tsx +4 -4
  16. package/_stories/PageSmokingTobacco.stories.tsx +15 -10
  17. package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
  18. package/_stories/PageWastewater.stories.tsx +44 -30
  19. package/_stories/VegaImport.stories.tsx +401 -0
  20. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  21. package/_stories/vega-fixtures/bars.json +58 -0
  22. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  23. package/_stories/vega-fixtures/combo.json +68 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  25. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  26. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  27. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  28. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  29. package/_stories/vega-fixtures/lines.json +227 -0
  30. package/_stories/vega-fixtures/measles-bars.json +348 -0
  31. package/_stories/vega-fixtures/measles-map.json +11101 -0
  32. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  33. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  34. package/_stories/vega-fixtures/no-data.json +14 -0
  35. package/_stories/vega-fixtures/pie-chart.json +94 -0
  36. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  37. package/_stories/vega-fixtures/stacked-area.json +222 -0
  38. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  39. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  40. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  41. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  42. package/_stories/vega-fixtures/warning-combo.json +59 -0
  43. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  44. package/assets/callout-flag.svg +7 -0
  45. package/assets/icon-chart-area.svg +1 -0
  46. package/assets/icon-chart-radar.svg +23 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
  49. package/components/Alert/components/Alert.styles.css +2 -2
  50. package/components/ComboBox/combobox.styles.css +48 -48
  51. package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
  52. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  53. package/components/DataTable/DataTable.tsx +46 -18
  54. package/components/DataTable/DataTableStandAlone.tsx +1 -0
  55. package/components/DataTable/components/ChartHeader.tsx +21 -12
  56. package/components/DataTable/components/MapHeader.tsx +34 -28
  57. package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
  58. package/components/DataTable/data-table.css +50 -52
  59. package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
  60. package/components/DataTable/helpers/getChartCellValue.ts +10 -7
  61. package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
  62. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  63. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
  64. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
  65. package/components/DownloadButton.tsx +14 -6
  66. package/components/EditorPanel/ColumnsEditor.tsx +38 -31
  67. package/components/EditorPanel/CustomSortOrder.tsx +94 -0
  68. package/components/EditorPanel/DataTableEditor.tsx +139 -23
  69. package/components/EditorPanel/EditorPanel.styles.css +71 -71
  70. package/components/EditorPanel/EditorPanel.tsx +3 -8
  71. package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
  72. package/components/EditorPanel/FootnotesEditor.tsx +2 -2
  73. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
  74. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
  75. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  76. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
  77. package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
  78. package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
  79. package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
  80. package/components/EditorPanel/sections/VisualSection.tsx +11 -0
  81. package/components/EditorWrapper/editor-wrapper.style.css +1 -1
  82. package/components/Filters/Filters.tsx +3 -5
  83. package/components/Filters/components/Tabs.tsx +19 -7
  84. package/{styles → components/Filters}/filters.scss +3 -3
  85. package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
  86. package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
  87. package/components/Layout/components/Responsive.tsx +14 -6
  88. package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
  89. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
  90. package/components/Layout/components/Visualization/index.tsx +50 -38
  91. package/components/Layout/components/Visualization/visualizations.scss +232 -15
  92. package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
  93. package/components/Layout/components/VisualizationContainer.tsx +37 -0
  94. package/components/Layout/components/VisualizationContent.test.tsx +182 -0
  95. package/components/Layout/components/VisualizationContent.tsx +75 -0
  96. package/components/Layout/index.tsx +5 -5
  97. package/components/Layout/styles/editor-utils.scss +3 -3
  98. package/components/Layout/styles/editor.scss +4 -4
  99. package/components/Legend/Legend.Gradient.tsx +7 -1
  100. package/components/Loader/loader.styles.css +2 -2
  101. package/components/Loading.jsx +1 -1
  102. package/components/MediaControls.tsx +10 -3
  103. package/components/MultiSelect/multiselect.styles.css +19 -19
  104. package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
  105. package/components/PaletteSelector/PaletteSelector.css +15 -15
  106. package/components/RichTooltip/richTooltip.css +6 -6
  107. package/components/Table/table.styles.css +2 -2
  108. package/components/Waiting.tsx +1 -1
  109. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  110. package/components/_stories/DataTable.stories.tsx +1 -0
  111. package/components/_stories/Filters.stories.tsx +1 -1
  112. package/components/_stories/styles.scss +0 -1
  113. package/components/elements/Button.jsx +1 -1
  114. package/components/elements/Card.jsx +1 -1
  115. package/{styles/v2/components → components/elements}/button.scss +9 -8
  116. package/components/inputs/InputCheckbox.jsx +1 -1
  117. package/components/inputs/InputSelect.tsx +1 -1
  118. package/components/inputs/InputText.jsx +1 -1
  119. package/components/inputs/InputToggle.tsx +1 -1
  120. package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
  121. package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
  122. package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
  123. package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
  124. package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
  125. package/{styles → components}/loading.scss +1 -1
  126. package/components/managers/DataDesigner.tsx +1 -1
  127. package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
  128. package/components/ui/Accordion.jsx +1 -1
  129. package/components/ui/Icon.tsx +1 -1
  130. package/components/ui/LoadSpin.jsx +1 -1
  131. package/components/ui/Modal.jsx +1 -1
  132. package/components/ui/Overlay.jsx +1 -1
  133. package/components/ui/Title/index.test.tsx +34 -0
  134. package/components/ui/Title/index.tsx +24 -7
  135. package/components/ui/Title/title.styles.css +119 -25
  136. package/components/ui/Tooltip.tsx +1 -1
  137. package/components/ui/_stories/Title.stories.tsx +1 -1
  138. package/{styles/v2/components → components/ui}/accordion.scss +3 -3
  139. package/components/ui/accordion.styles.css +11 -11
  140. package/{styles/v2/components → components/ui}/modal.scss +2 -2
  141. package/{styles/v2/components → components/ui}/overlay.scss +6 -6
  142. package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
  143. package/{styles → components}/waiting.scss +9 -3
  144. package/data/colorPalettes.ts +18 -5
  145. package/data/mapColorPalettes.ts +10 -0
  146. package/devTemplate/dev.js +285 -0
  147. package/devTemplate/index.html +30 -0
  148. package/devTemplate/preview.html +1503 -0
  149. package/devTemplate/sidebar.css +151 -0
  150. package/dist/cove-main.css +2530 -3901
  151. package/dist/cove-main.css.map +1 -1
  152. package/generateViteConfig.js +111 -2
  153. package/helpers/DataTransform.ts +1 -5
  154. package/helpers/backfillDefaults.ts +35 -0
  155. package/helpers/constants.ts +12 -0
  156. package/helpers/cove/date.ts +64 -3
  157. package/helpers/cove/number.ts +29 -15
  158. package/helpers/cove/string.ts +29 -0
  159. package/helpers/coveUpdateWorker.ts +14 -8
  160. package/helpers/displayDataAsText.ts +1 -1
  161. package/helpers/embed/embedCodeGenerator.ts +80 -0
  162. package/helpers/embed/embedHelper.js +169 -0
  163. package/helpers/embed/filterUtils.ts +121 -0
  164. package/helpers/embed/index.ts +17 -0
  165. package/helpers/embed/urlValidation.ts +119 -0
  166. package/helpers/extractDataAndMetadata.ts +20 -0
  167. package/helpers/fetchRemoteData.ts +14 -8
  168. package/helpers/filterVizData.ts +6 -1
  169. package/helpers/getFileExtension.ts +0 -6
  170. package/helpers/labelHash.ts +9 -0
  171. package/helpers/markupProcessor.ts +56 -38
  172. package/helpers/metrics/types.ts +3 -0
  173. package/helpers/palettes/colorDistributions.ts +1 -1
  174. package/helpers/palettes/utils.ts +12 -12
  175. package/helpers/parseCsvWithQuotes.ts +15 -14
  176. package/helpers/prepareScreenshot.ts +33 -10
  177. package/helpers/testing.ts +44 -0
  178. package/helpers/tests/DataTransform.test.ts +125 -0
  179. package/helpers/tests/abbreviateNumber.test.ts +59 -0
  180. package/helpers/tests/backfillDefaults.test.ts +253 -0
  181. package/helpers/tests/date.test.ts +110 -0
  182. package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
  183. package/helpers/tests/markupProcessor.test.ts +315 -124
  184. package/helpers/tests/number.test.ts +42 -0
  185. package/helpers/tests/prepareScreenshot.test.ts +28 -28
  186. package/helpers/tests/testStandaloneBuild.ts +36 -26
  187. package/helpers/tests/useDataVizClasses.test.ts +66 -0
  188. package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
  189. package/helpers/useDataVizClasses.ts +13 -7
  190. package/helpers/vegaConfig.ts +1 -1
  191. package/helpers/vegaConfigImport.ts +160 -0
  192. package/helpers/ver/4.24.4.ts +24 -0
  193. package/helpers/ver/4.26.1.ts +1 -1
  194. package/helpers/ver/4.26.2.ts +84 -0
  195. package/helpers/ver/4.26.3.ts +44 -0
  196. package/helpers/ver/4.26.4.ts +31 -0
  197. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  198. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  199. package/helpers/ver/tests/4.26.3.test.ts +168 -0
  200. package/helpers/ver/tests/4.26.4.test.ts +88 -0
  201. package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
  202. package/helpers/viewports.ts +2 -0
  203. package/package.json +27 -32
  204. package/styles/_global.scss +7 -7
  205. package/styles/_reset.scss +2 -2
  206. package/styles/{v2/base → base}/_file-selector.scss +4 -4
  207. package/styles/{v2/base → base}/_general.scss +2 -4
  208. package/styles/{v2/base → base}/index.scss +1 -1
  209. package/styles/base.scss +107 -165
  210. package/styles/cove-main.scss +3 -6
  211. package/styles/layout/_component.scss +110 -0
  212. package/styles/{v2/layout → layout}/_data-table.scss +7 -7
  213. package/styles/layout/_wrapper-padding.scss +27 -0
  214. package/styles/{v2/main.scss → main.scss} +3 -1
  215. package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
  216. package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
  217. package/styles/{v2/utils → utils}/_grid.scss +8 -3
  218. package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
  219. package/styles/{v2/utils → utils}/index.scss +2 -1
  220. package/types/Annotation.ts +10 -11
  221. package/types/Axis.ts +2 -0
  222. package/types/ComponentStyles.ts +1 -0
  223. package/types/ConfigureData.ts +1 -0
  224. package/types/General.ts +2 -0
  225. package/types/MarkupInclude.ts +1 -0
  226. package/types/MarkupVariable.ts +2 -1
  227. package/types/Palette.ts +22 -0
  228. package/types/Table.ts +9 -0
  229. package/types/Visualization.ts +7 -0
  230. package/_stories/StoryRenderingTests.stories.tsx +0 -164
  231. package/helpers/embedCodeGenerator.ts +0 -109
  232. package/styles/_common-components.css +0 -73
  233. package/styles/_variables.scss +0 -63
  234. package/styles/v2/layout/_component.scss +0 -21
  235. package/styles/v2/utils/_variables.scss +0 -9
  236. package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
  237. /package/{styles/v2/components → components/ui}/icon.scss +0 -0
  238. /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
  239. /package/styles/{v2/base → base}/_heading.scss +0 -0
  240. /package/styles/{v2/base → base}/_reset.scss +0 -0
  241. /package/styles/{v2/layout → layout}/_alert.scss +0 -0
  242. /package/styles/{v2/layout → layout}/_progression.scss +0 -0
  243. /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
  244. /package/styles/{v2/layout → layout}/index.scss +0 -0
  245. /package/styles/{v2/themes → themes}/index.scss +0 -0
  246. /package/styles/{v2/utils → utils}/_align.scss +0 -0
  247. /package/styles/{v2/utils → utils}/_animations.scss +0 -0
  248. /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
  249. /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
@@ -1,38 +1,38 @@
1
1
  .custom-colors-editor {
2
- padding: 0.75rem;
2
+ background-color: #f8f9fa;
3
3
  border: 1px solid #dee2e6;
4
4
  border-radius: 0.25rem;
5
- background-color: #f8f9fa;
5
+ padding: 0.75rem;
6
6
  }
7
7
 
8
8
  .custom-colors-editor .custom-colors-label {
9
+ color: #495057;
9
10
  display: block;
11
+ font-size: 0.875rem;
10
12
  font-weight: 600;
11
13
  margin-bottom: 0.5rem;
12
- font-size: 0.875rem;
13
- color: #495057;
14
14
  }
15
15
 
16
16
  .custom-colors-editor .custom-colors-notice {
17
- display: flex;
18
17
  align-items: flex-start;
19
- gap: 0.5rem;
20
- padding: 0.75rem;
21
- margin-bottom: 0.75rem;
22
18
  background-color: #d1ecf1;
23
19
  border: 1px solid #bee5eb;
24
20
  border-radius: 0.25rem;
25
21
  color: #0c5460;
22
+ display: flex;
26
23
  font-size: 0.8125rem;
24
+ gap: 0.5rem;
27
25
  line-height: 1.5;
26
+ margin-bottom: 0.75rem;
27
+ padding: 0.75rem;
28
28
  }
29
29
 
30
30
  .custom-colors-editor .custom-colors-notice .notice-icon {
31
- width: 16px;
32
- height: 16px;
31
+ color: #17a2b8;
33
32
  flex-shrink: 0;
33
+ height: 16px;
34
34
  margin-top: 0.125rem;
35
- color: #17a2b8;
35
+ width: 16px;
36
36
  }
37
37
 
38
38
  .custom-colors-editor .custom-colors-notice strong {
@@ -41,8 +41,8 @@
41
41
 
42
42
  .custom-colors-editor .custom-colors-notice a {
43
43
  color: #0c5460;
44
- text-decoration: underline;
45
44
  font-weight: 500;
45
+ text-decoration: underline;
46
46
  }
47
47
 
48
48
  .custom-colors-editor .custom-colors-notice a:hover {
@@ -51,21 +51,21 @@
51
51
  }
52
52
 
53
53
  .custom-colors-editor .custom-colors-preview {
54
- display: flex;
55
- gap: 2px;
56
- margin-bottom: 0.75rem;
57
- padding: 0.5rem;
58
54
  background-color: #fff;
59
55
  border: 1px solid #dee2e6;
60
56
  border-radius: 0.25rem;
57
+ display: flex;
58
+ gap: 2px;
59
+ margin-bottom: 0.75rem;
61
60
  min-height: 36px;
62
61
  overflow: hidden;
62
+ padding: 0.5rem;
63
63
  }
64
64
 
65
65
  .custom-colors-editor .custom-colors-preview .preview-swatch {
66
+ border-radius: 2px;
66
67
  flex: 1;
67
68
  min-width: 8px;
68
- border-radius: 2px;
69
69
  transition: transform 0.15s ease;
70
70
  }
71
71
 
@@ -110,21 +110,21 @@
110
110
  }
111
111
 
112
112
  .custom-colors-editor .custom-color-item.dragging {
113
- opacity: 0.5;
114
113
  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
115
114
  cursor: grabbing;
115
+ opacity: 0.5;
116
116
  }
117
117
 
118
118
  .custom-colors-editor .custom-color-item:hover:not(.dragging) {
119
- box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.1);
120
119
  border-color: #adb5bd;
120
+ box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.1);
121
121
  }
122
122
 
123
123
  .custom-colors-editor .color-item-controls {
124
- display: flex;
125
124
  align-items: center;
126
- gap: 0.5rem;
125
+ display: flex;
127
126
  flex-wrap: wrap;
127
+ gap: 0.5rem;
128
128
  }
129
129
 
130
130
  @media (max-width: 576px) {
@@ -135,12 +135,12 @@
135
135
 
136
136
  .custom-colors-editor .color-item-drag-handle {
137
137
  color: #adb5bd;
138
- font-size: 0.875rem;
139
138
  cursor: grab;
140
- user-select: none;
141
- line-height: 1;
142
139
  flex-shrink: 0;
140
+ font-size: 0.875rem;
141
+ line-height: 1;
143
142
  padding: 0 0.125rem;
143
+ user-select: none;
144
144
  }
145
145
 
146
146
  .custom-colors-editor .color-item-drag-handle:active {
@@ -152,23 +152,23 @@
152
152
  }
153
153
 
154
154
  .custom-colors-editor .color-item-number {
155
+ color: #6c757d;
156
+ flex-shrink: 0;
157
+ font-size: 0.75rem;
155
158
  font-weight: 600;
156
159
  min-width: 24px;
157
160
  text-align: right;
158
- color: #6c757d;
159
- font-size: 0.75rem;
160
- flex-shrink: 0;
161
161
  }
162
162
 
163
163
  .custom-colors-editor .color-picker {
164
- width: 48px;
165
- height: 34px;
166
164
  border: 1px solid #ced4da;
167
165
  border-radius: 0.25rem;
168
166
  cursor: pointer;
169
- padding: 2px;
170
167
  flex-shrink: 0;
168
+ height: 34px;
169
+ padding: 2px;
171
170
  transition: border-color 0.15s ease;
171
+ width: 48px;
172
172
  }
173
173
 
174
174
  .custom-colors-editor .color-picker:hover {
@@ -176,15 +176,15 @@
176
176
  }
177
177
 
178
178
  .custom-colors-editor .color-picker:focus {
179
- outline: none;
180
179
  border-color: #80bdff;
181
180
  box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
181
+ outline: none;
182
182
  }
183
183
 
184
184
  .custom-colors-editor .color-input-wrapper {
185
185
  flex: 1;
186
- min-width: 0;
187
186
  margin-bottom: 0;
187
+ min-width: 0;
188
188
  }
189
189
 
190
190
  /* Override TextField label wrapper styles */
@@ -210,26 +210,26 @@
210
210
 
211
211
  .custom-colors-editor .color-item-buttons {
212
212
  display: flex;
213
+ flex-shrink: 0;
213
214
  gap: 0.25rem;
214
215
  margin-left: auto;
215
- flex-shrink: 0;
216
216
  }
217
217
 
218
218
  .custom-colors-editor .btn-move,
219
219
  .custom-colors-editor .btn-remove {
220
- padding: 0.25rem 0.5rem;
220
+ align-items: center;
221
+ background-color: #fff;
221
222
  border: 1px solid #ced4da;
222
223
  border-radius: 0.25rem;
223
- background-color: #fff;
224
224
  cursor: pointer;
225
- font-size: 0.875rem;
226
- line-height: 1.5;
227
- transition: all 0.15s ease;
228
225
  display: inline-flex;
229
- align-items: center;
226
+ font-size: 0.875rem;
227
+ height: 28px;
230
228
  justify-content: center;
229
+ line-height: 1.5;
231
230
  min-width: 28px;
232
- height: 28px;
231
+ padding: 0.25rem 0.5rem;
232
+ transition: all 0.15s ease;
233
233
  }
234
234
 
235
235
  .custom-colors-editor .btn-move:hover:not(:disabled),
@@ -246,39 +246,39 @@
246
246
 
247
247
  .custom-colors-editor .btn-move:disabled,
248
248
  .custom-colors-editor .btn-remove:disabled {
249
- opacity: 0.35;
250
249
  cursor: not-allowed;
250
+ opacity: 0.35;
251
251
  }
252
252
 
253
253
  .custom-colors-editor .btn-remove {
254
254
  color: #dc3545;
255
- font-weight: 700;
256
255
  font-size: 1.125rem;
256
+ font-weight: 700;
257
257
  }
258
258
 
259
259
  .custom-colors-editor .btn-remove:hover:not(:disabled) {
260
260
  background-color: #dc3545;
261
- color: #fff;
262
261
  border-color: #dc3545;
262
+ color: #fff;
263
263
  }
264
264
 
265
265
  .custom-colors-editor .btn-add-color {
266
- width: 100%;
267
- padding: 0.5rem;
266
+ background-color: #fff;
268
267
  border: 2px dashed #ced4da;
269
268
  border-radius: 0.25rem;
270
- background-color: #fff;
271
269
  color: #6c757d;
272
- font-weight: 600;
273
- font-size: 0.875rem;
274
270
  cursor: pointer;
271
+ font-size: 0.875rem;
272
+ font-weight: 600;
273
+ padding: 0.5rem;
275
274
  transition: all 0.15s ease;
275
+ width: 100%;
276
276
  }
277
277
 
278
278
  .custom-colors-editor .btn-add-color:hover:not(:disabled) {
279
+ background-color: #e7f1ff;
279
280
  border-color: #007bff;
280
281
  color: #007bff;
281
- background-color: #e7f1ff;
282
282
  }
283
283
 
284
284
  .custom-colors-editor .btn-add-color:active:not(:disabled) {
@@ -286,14 +286,14 @@
286
286
  }
287
287
 
288
288
  .custom-colors-editor .btn-add-color:disabled {
289
- opacity: 0.5;
290
289
  cursor: not-allowed;
290
+ opacity: 0.5;
291
291
  }
292
292
 
293
293
  .custom-colors-editor .custom-colors-info {
294
- text-align: center;
295
- font-size: 0.75rem;
296
294
  color: #6c757d;
297
- margin-top: 0.5rem;
295
+ font-size: 0.75rem;
298
296
  font-style: italic;
297
+ margin-top: 0.5rem;
298
+ text-align: center;
299
299
  }
@@ -7,15 +7,13 @@ interface CustomColorsEditorProps {
7
7
  onChange: (colors: string[]) => void
8
8
  label?: string
9
9
  minColors?: number
10
- maxColors?: number
11
10
  }
12
11
 
13
12
  const CustomColorsEditor: React.FC<CustomColorsEditorProps> = ({
14
13
  colors = [],
15
14
  onChange,
16
15
  label = 'Custom Colors',
17
- minColors = 1,
18
- maxColors = 20
16
+ minColors = 1
19
17
  }) => {
20
18
  const [draggedIndex, setDraggedIndex] = useState<number | null>(null)
21
19
 
@@ -26,11 +24,8 @@ const CustomColorsEditor: React.FC<CustomColorsEditorProps> = ({
26
24
  }
27
25
 
28
26
  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
- }
27
+ const defaultColor = colors.length > 0 ? colors[colors.length - 1] : '#3366cc'
28
+ onChange([...colors, defaultColor])
34
29
  }
35
30
 
36
31
  const handleRemoveColor = (index: number) => {
@@ -191,7 +186,6 @@ const CustomColorsEditor: React.FC<CustomColorsEditorProps> = ({
191
186
  <button
192
187
  type="button"
193
188
  onClick={handleAddColor}
194
- disabled={colors.length >= maxColors}
195
189
  className="btn-add-color"
196
190
  >
197
191
  + Add Color
@@ -200,7 +194,6 @@ const CustomColorsEditor: React.FC<CustomColorsEditorProps> = ({
200
194
  <div className="custom-colors-info">
201
195
  {colors.length} color{colors.length !== 1 ? 's' : ''}
202
196
  {colors.length < minColors && ` (minimum ${minColors} required)`}
203
- {colors.length >= maxColors && ` (maximum reached)`}
204
197
  </div>
205
198
  </div>
206
199
  )
@@ -6,6 +6,7 @@ import MediaControls from '@cdc/core/components/MediaControls'
6
6
  import Loading from '@cdc/core/components/Loading'
7
7
  import DownloadButton from '../DownloadButton'
8
8
  import { customSort } from './helpers/customSort'
9
+ import { applyCustomOrder } from './helpers/applyCustomOrder'
9
10
  import ChartHeader from './components/ChartHeader'
10
11
  import BoxplotHeader from './components/BoxplotHeader'
11
12
  import MapHeader from './components/MapHeader'
@@ -25,18 +26,17 @@ import isRightAlignedTableValue from '@cdc/core/helpers/isRightAlignedTableValue
25
26
  import './data-table.css'
26
27
  import _ from 'lodash'
27
28
  import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
29
+ import { getMapDataTableColumnKeys } from './helpers/getMapDataTableColumnKeys'
28
30
 
29
31
  export type DataTableProps = {
30
32
  colorScale?: Function
31
33
  columns?: Record<string, Column>
32
34
  config: TableConfig
33
35
  dataConfig?: Object
34
- defaultSortBy?: string
35
36
  displayGeoName?: (row: string) => string
36
37
  expandDataTable: boolean
37
38
  formatLegendLocation?: (row: string, runtimeLookup: string) => string
38
39
  groupBy?: string
39
- headerColor?: string
40
40
  imageRef?: string
41
41
  indexTitle?: string
42
42
  isDebug?: boolean
@@ -64,6 +64,7 @@ export type DataTableProps = {
64
64
  showDownloadImgButton?: boolean
65
65
  showDownloadPdfButton?: boolean
66
66
  includeContextInDownload?: boolean
67
+ hasSubtextAbove?: boolean
67
68
  // Map-specific props (optional)
68
69
  legendMemo?: React.MutableRefObject<Map<any, any>>
69
70
  legendSpecialClassLastMemo?: React.MutableRefObject<Map<any, any>>
@@ -75,11 +76,9 @@ const DataTable = (props: DataTableProps) => {
75
76
  columns,
76
77
  config,
77
78
  dataConfig,
78
- defaultSortBy,
79
79
  displayGeoName,
80
80
  expandDataTable,
81
81
  formatLegendLocation,
82
- headerColor,
83
82
  rawData,
84
83
  runtimeData: parentRuntimeData,
85
84
  tabbingId,
@@ -91,6 +90,7 @@ const DataTable = (props: DataTableProps) => {
91
90
  showDownloadImgButton,
92
91
  showDownloadPdfButton,
93
92
  includeContextInDownload = false,
93
+ hasSubtextAbove = false,
94
94
  imageRef
95
95
  } = props
96
96
  const runtimeData = useMemo(() => {
@@ -106,12 +106,33 @@ const DataTable = (props: DataTableProps) => {
106
106
  }, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumns])
107
107
 
108
108
  const [expanded, setExpanded] = useState(expandDataTable)
109
- const [sortBy, setSortBy] = useState<any>({
110
- column: defaultSortBy || '',
111
- asc: false,
112
- colIndex: null
109
+
110
+ // Initialize sort state from config.table.defaultSort
111
+ const defaultSort = config.table?.defaultSort
112
+ const [sortBy, setSortBy] = useState<any>(() => {
113
+ if (defaultSort?.column) {
114
+ return {
115
+ column: defaultSort.column,
116
+ asc: defaultSort.sortDirection === 'asc' ? true : defaultSort.sortDirection === 'custom' ? null : false,
117
+ colIndex: null
118
+ }
119
+ }
120
+ return { column: '', asc: false, colIndex: null }
113
121
  })
114
122
 
123
+ // Re-sync sort state when defaultSort changes in the editor
124
+ useEffect(() => {
125
+ if (defaultSort?.column) {
126
+ setSortBy({
127
+ column: defaultSort.column,
128
+ asc: defaultSort.sortDirection === 'asc' ? true : defaultSort.sortDirection === 'custom' ? null : false,
129
+ colIndex: null
130
+ })
131
+ } else {
132
+ setSortBy({ column: '', asc: false, colIndex: null })
133
+ }
134
+ }, [defaultSort?.column, defaultSort?.sortDirection, defaultSort?.customOrder])
135
+
115
136
  const [accessibilityLabel, setAccessibilityLabel] = useState('')
116
137
 
117
138
  // Create default refs for map-specific props when not provided
@@ -157,6 +178,14 @@ const DataTable = (props: DataTableProps) => {
157
178
  }
158
179
 
159
180
  const rawRows = Object.keys(runtimeData).filter(column => column != 'columns')
181
+
182
+ // Determine if custom order sort is active (user hasn't overridden by clicking a column header)
183
+ const isCustomOrderActive =
184
+ sortBy.asc === null &&
185
+ defaultSort?.sortDirection === 'custom' &&
186
+ defaultSort?.customOrder?.length > 0 &&
187
+ sortBy.column === defaultSort.column
188
+
160
189
  const rows =
161
190
  isVertical && sortBy.column
162
191
  ? rawRows.sort((a, b) => {
@@ -175,6 +204,10 @@ const DataTable = (props: DataTableProps) => {
175
204
  dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
176
205
  dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
177
206
  }
207
+ // Use custom order when active
208
+ if (isCustomOrderActive && dataA !== undefined && dataB !== undefined) {
209
+ return applyCustomOrder(dataA, dataB, defaultSort.customOrder)
210
+ }
178
211
  return dataA || dataB ? customSort(dataA, dataB, sortBy, config) : 0
179
212
  })
180
213
  : rawRows
@@ -229,14 +262,6 @@ const DataTable = (props: DataTableProps) => {
229
262
  const getClassNames = (): string => {
230
263
  const classes = ['data-table-container']
231
264
 
232
- const hasDownloadLinkAbove =
233
- (config.table.download || showDownloadImgButton || showDownloadPdfButton) && !config.table.showDownloadLinkBelow
234
- const isStandaloneTable = config.type === 'table'
235
-
236
- if (!hasDownloadLinkAbove && !isStandaloneTable) {
237
- classes.push('mt-4')
238
- }
239
-
240
265
  classes.push(viewport)
241
266
 
242
267
  return classes.join(' ')
@@ -251,6 +276,10 @@ const DataTable = (props: DataTableProps) => {
251
276
  const getVisibleColumns = () => {
252
277
  if (!config.columns) return []
253
278
 
279
+ if (config.type === 'map') {
280
+ return getMapDataTableColumnKeys(config.columns).map(columnKey => config.columns[columnKey].name)
281
+ }
282
+
254
283
  return Object.values(config.columns)
255
284
  .filter(col => col.dataTable !== false)
256
285
  .map(col => col.name)
@@ -325,7 +354,7 @@ const DataTable = (props: DataTableProps) => {
325
354
  const classes = ['download-links']
326
355
  if (!belowTable) {
327
356
  if (hasDownloadLink) {
328
- classes.push('mt-4', 'mb-2')
357
+ classes.push('mb-2')
329
358
  }
330
359
  } else {
331
360
  if (hasDownloadLink) {
@@ -394,7 +423,6 @@ const DataTable = (props: DataTableProps) => {
394
423
  <DownloadButton
395
424
  rawData={getDownloadData()}
396
425
  fileName={`${vizTitle || 'data-table'}.csv`}
397
- headerColor={headerColor}
398
426
  interactionLabel={interactionLabel}
399
427
  config={config}
400
428
  />
@@ -78,6 +78,7 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
78
78
  markupVariables={config['markupVariables']}
79
79
  enableMarkupVariables={config['enableMarkupVariables']}
80
80
  data={config.data}
81
+ dataMetadata={config['dataMetadata']}
81
82
  />
82
83
  </>
83
84
  )
@@ -62,8 +62,9 @@ const ChartHeader = ({
62
62
  if (columnHeaderText === notApplicableText) return
63
63
 
64
64
  return (
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>
65
+ <span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
66
+ sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
67
+ } order`}</span>
67
68
  )
68
69
  }
69
70
 
@@ -101,14 +102,13 @@ const ChartHeader = ({
101
102
  const text = getSeriesName(column, config)
102
103
  const newSortBy = getNewSortBy(sortBy, column, index)
103
104
  const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
104
- const isSortedCol = column === sortBy.column && !hasRowType
105
105
 
106
106
  return (
107
107
  <th
108
108
  style={{
109
109
  minWidth: (config.table.cellMinWidth || 0) + 'px',
110
110
  textAlign: rightAlignedCols && rightAlignedCols[index] ? 'right' : '',
111
- paddingRight: isSortedCol ? '1.3em' : ''
111
+ paddingRight: '1.8em'
112
112
  }}
113
113
  key={`col-header-${column}__${index}`}
114
114
  tabIndex={0}
@@ -123,7 +123,9 @@ const ChartHeader = ({
123
123
  eventAction: 'click',
124
124
  eventLabel: interactionLabel,
125
125
  vizTitle: getVizTitle(config),
126
- specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
126
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${
127
+ newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'
128
+ }`
127
129
  })
128
130
  setSortBy(newSortBy)
129
131
  }}
@@ -137,11 +139,14 @@ const ChartHeader = ({
137
139
  eventAction: 'keyboard',
138
140
  eventLabel: interactionLabel,
139
141
  vizTitle: getVizTitle(config),
140
- specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
142
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${
143
+ newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'
144
+ }`
141
145
  })
142
146
  setSortBy(newSortBy)
143
147
  }
144
148
  }}
149
+ className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
145
150
  {...(sortBy.column === column
146
151
  ? sortBy.asc
147
152
  ? { 'aria-sort': 'ascending' }
@@ -149,7 +154,7 @@ const ChartHeader = ({
149
154
  : null)}
150
155
  >
151
156
  <ColumnHeadingText text={text} config={config} />
152
- {isSortedCol && <SortIcon ascending={sortByAsc} />}
157
+ <SortIcon ascending={sortByAsc} />
153
158
  <ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
154
159
  </th>
155
160
  )
@@ -169,13 +174,12 @@ const ChartHeader = ({
169
174
  row !== '__series__' ? getChartCellValue(row, column, config, data, rightAxisItemsMap) : '__series__'
170
175
  const newSortBy = getNewSortBy(sortBy, column, index)
171
176
  const sortByAsc = sortBy.colIndex === index ? sortBy.asc : undefined
172
- const isSortedCol = index === sortBy.colIndex && !hasRowType
173
177
  return (
174
178
  <th
175
179
  style={{
176
180
  minWidth: (config.table.cellMinWidth || 0) + 'px',
177
181
  textAlign: rightAlignedCols && rightAlignedCols[index] ? 'right' : '',
178
- paddingRight: isSortedCol ? '1.3em' : ''
182
+ paddingRight: '1.8em'
179
183
  }}
180
184
  key={`col-header-${text}__${index}`}
181
185
  tabIndex={0}
@@ -190,7 +194,9 @@ const ChartHeader = ({
190
194
  eventAction: 'click',
191
195
  eventLabel: interactionLabel,
192
196
  vizTitle: getVizTitle(config),
193
- specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
197
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${
198
+ newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'
199
+ }`
194
200
  })
195
201
  setSortBy(newSortBy)
196
202
  }}
@@ -203,11 +209,14 @@ const ChartHeader = ({
203
209
  eventAction: 'keyboard',
204
210
  eventLabel: interactionLabel,
205
211
  vizTitle: getVizTitle(config),
206
- specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
212
+ specifics: `column: ${newSortBy.column || 'none'}, order: ${
213
+ newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'
214
+ }`
207
215
  })
208
216
  setSortBy(newSortBy)
209
217
  }
210
218
  }}
219
+ className={sortBy.colIndex === index ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
211
220
  {...(sortBy.column === text
212
221
  ? sortBy.asc
213
222
  ? { 'aria-sort': 'ascending' }
@@ -215,7 +224,7 @@ const ChartHeader = ({
215
224
  : null)}
216
225
  >
217
226
  <ColumnHeadingText text={text} config={config} />
218
- {isSortedCol && <SortIcon ascending={sortByAsc} />}
227
+ <SortIcon ascending={sortByAsc} />
219
228
 
220
229
  <ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
221
230
  </th>