@cdc/dashboard 4.25.10 → 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 (86) hide show
  1. package/Dynamic_Data.md +66 -0
  2. package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
  3. package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
  4. package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
  5. package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
  6. package/dist/cdcdashboard.js +84214 -79641
  7. package/examples/api-dashboard-data.json +272 -0
  8. package/examples/api-dashboard-years.json +11 -0
  9. package/examples/api-geographies-data.json +11 -0
  10. package/examples/api-test/categories.json +18 -0
  11. package/examples/api-test/chart-data.json +602 -0
  12. package/examples/api-test/topics.json +47 -0
  13. package/examples/api-test/years.json +22 -0
  14. package/examples/markup-axis-label.json +4167 -0
  15. package/examples/private/big-dashboard.json +39095 -39077
  16. package/examples/private/cat-y.json +1235 -0
  17. package/examples/private/chronic-dash.json +1584 -0
  18. package/examples/private/clade-2.json +430 -0
  19. package/examples/private/diabetes.json +546 -196
  20. package/examples/private/map-issue.json +2260 -0
  21. package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
  22. package/examples/private/mpinc-state-reports.json +2260 -0
  23. package/examples/private/mpox.json +38128 -0
  24. package/examples/private/nwss/rsv.json +1240 -0
  25. package/examples/private/reset.json +32920 -0
  26. package/examples/private/simple-dash.json +490 -0
  27. package/examples/private/test-dash.json +0 -0
  28. package/examples/private/test123.json +491 -0
  29. package/examples/test-api-filter-reset.json +132 -0
  30. package/examples/test-dashboard-simple.json +503 -0
  31. package/index.html +25 -26
  32. package/package.json +11 -11
  33. package/src/CdcDashboardComponent.tsx +35 -10
  34. package/src/DashboardContext.tsx +3 -1
  35. package/src/_stories/Dashboard.DataSetup.stories.tsx +203 -0
  36. package/src/_stories/Dashboard.stories.tsx +402 -1
  37. package/src/_stories/_mock/custom-order-new-values.json +116 -0
  38. package/src/_stories/_mock/filter-cascade.json +3350 -0
  39. package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
  40. package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
  41. package/src/_stories/_mock/parent-child-filters.json +233 -0
  42. package/src/components/DashboardFilters/DashboardFilters.tsx +54 -31
  43. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +118 -50
  44. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +96 -108
  45. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +196 -59
  46. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +129 -29
  47. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
  48. package/src/components/DataDesignerModal.tsx +18 -6
  49. package/src/components/Header/Header.tsx +53 -21
  50. package/src/components/Toggle/Toggle.tsx +48 -48
  51. package/src/components/VisualizationRow.tsx +73 -6
  52. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -3
  53. package/src/components/Widget/Widget.tsx +1 -1
  54. package/src/data/initial-state.js +1 -0
  55. package/src/helpers/addValuesToDashboardFilters.ts +24 -6
  56. package/src/helpers/apiFilterHelpers.ts +26 -2
  57. package/src/helpers/changeFilterActive.ts +67 -65
  58. package/src/helpers/filterData.ts +52 -7
  59. package/src/helpers/filterResetHelpers.ts +102 -0
  60. package/src/helpers/formatConfigBeforeSave.ts +6 -5
  61. package/src/helpers/getUpdateConfig.ts +91 -91
  62. package/src/helpers/getVizConfig.ts +2 -2
  63. package/src/helpers/loadAPIFilters.ts +109 -99
  64. package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
  65. package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
  66. package/src/helpers/updateChildFilters.ts +50 -27
  67. package/src/index.tsx +1 -0
  68. package/src/scss/editor-panel.scss +3 -431
  69. package/src/scss/main.scss +142 -25
  70. package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
  71. package/src/test/CdcDashboard.test.jsx +9 -4
  72. package/src/types/Dashboard.ts +1 -0
  73. package/src/types/DashboardFilters.ts +9 -8
  74. package/src/types/FilterStyles.ts +8 -7
  75. package/src/types/SharedFilter.ts +13 -0
  76. package/LICENSE +0 -201
  77. package/examples/private/DEV-11072.json +0 -7591
  78. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
  79. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
  80. package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
  81. package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
  82. package/examples/private/pedro.json +0 -1
  83. package/src/helpers/getAutoLoadVisualization.ts +0 -11
  84. package/src/scss/mixins.scss +0 -47
  85. package/src/scss/variables.scss +0 -5
  86. /package/dist/{cdcdashboard-548642e6.es.js → cdcdashboard-Ct2SB0vL.es.js} +0 -0
@@ -1,12 +1,14 @@
1
1
  import { SharedFilter } from '../types/SharedFilter'
2
2
  import _ from 'lodash'
3
+ import { mergeCustomOrderValues } from '@cdc/core/helpers/mergeCustomOrderValues'
4
+ import { handleSorting } from '@cdc/core/components/Filters'
3
5
 
4
6
  export const updateChildFilters = (newSharedFilters: SharedFilter[], data: Record<string, any>): SharedFilter[] => {
5
7
  const dataSet = Object.values(data).flat()
6
8
 
7
9
  // Find indexes of all child filters
8
10
  const childFilterIndexes: number[] = newSharedFilters
9
- .map((filter, index) => (filter.type === 'datafilter' && filter.parents ? index : -1))
11
+ .map((filter, index) => (filter.type === 'datafilter' && filter.parents?.length ? index : -1))
10
12
  .filter(index => index !== -1)
11
13
  if (childFilterIndexes.length === 0) return newSharedFilters
12
14
 
@@ -16,33 +18,54 @@ export const updateChildFilters = (newSharedFilters: SharedFilter[], data: Recor
16
18
  // Update each child filter
17
19
  childFilterIndexes.forEach(childIndex => {
18
20
  const childFilter: SharedFilter = newSharedFilters[childIndex]
19
- const parentFilter: SharedFilter = newSharedFilters.find(
20
- filter => String(childFilter.parents) === String(filter.key)
21
- )
22
-
23
- if (parentFilter) {
24
- // Filter dataset based on parent's active value
25
- const parentsActiveValues: string[] = dataSet.filter((d: Record<string, any>) => {
26
- return parentFilter.active?.includes(d[parentFilter.columnName])
21
+
22
+ // Get all parent filters for this child
23
+ const parentFilters: SharedFilter[] = newSharedFilters.filter(filter => childFilter.parents?.includes(filter.key))
24
+
25
+ if (parentFilters.length > 0) {
26
+ const filteredDataSet = dataSet.filter((d: Record<string, any>) => {
27
+ return parentFilters.every(parentFilter => {
28
+ const parentActive = parentFilter.active
29
+ if (Array.isArray(parentActive)) {
30
+ return parentActive.includes(d[parentFilter.columnName])
31
+ }
32
+ return parentActive == d[parentFilter.columnName]
33
+ })
27
34
  })
28
- // Get unique active values for the child filter
29
- const childFilterValues = _.uniq(parentsActiveValues.map(d => d[childFilter.columnName]).filter(Boolean))
30
-
31
- // Update the child filter if unique values exist
32
- if (childFilterValues.length > 0) {
33
- const isChildMultiSelect = childFilter.filterStyle === 'multi-select'
34
- const activeValue = isChildMultiSelect
35
- ? childFilterValues
36
- : childFilter.active
37
- ? childFilter.active
38
- : childFilter.defaultValue
39
- ? childFilter.defaultValue
40
- : childFilterValues[0]
41
- updatedFilters[childIndex] = {
42
- ...childFilter,
43
- values: childFilterValues,
44
- active: activeValue
45
- }
35
+
36
+ // Get unique active values for the child filter from the filtered dataset
37
+ const childFilterValues = _.uniq(filteredDataSet.map(d => d[childFilter.columnName]).filter(Boolean))
38
+
39
+ // Prepare filter with values and orderedValues for sorting
40
+ const filterToSort = {
41
+ ...childFilter,
42
+ values: childFilterValues,
43
+ orderedValues: mergeCustomOrderValues(childFilterValues, childFilter.orderedValues, childFilter.order)
44
+ }
45
+
46
+ // Use handleSorting to apply proper sorting logic (handles asc/desc/cust/column)
47
+ const sortedFilter = handleSorting(filterToSort)
48
+
49
+ // Update the child filter with new values (even if empty)
50
+ const isChildMultiSelect = childFilter.filterStyle === 'multi-select'
51
+ const activeValue =
52
+ sortedFilter.values.length > 0
53
+ ? isChildMultiSelect
54
+ ? sortedFilter.values
55
+ : childFilter.active && sortedFilter.values.includes(childFilter.active)
56
+ ? childFilter.active
57
+ : childFilter.defaultValue && sortedFilter.values.includes(childFilter.defaultValue)
58
+ ? childFilter.defaultValue
59
+ : childFilter.resetLabel || sortedFilter.values[0]
60
+ : isChildMultiSelect
61
+ ? []
62
+ : childFilter.active || ''
63
+
64
+ updatedFilters[childIndex] = {
65
+ ...childFilter,
66
+ values: sortedFilter.values,
67
+ orderedValues: sortedFilter.orderedValues,
68
+ active: activeValue
46
69
  }
47
70
  }
48
71
  })
package/src/index.tsx CHANGED
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
 
4
4
  import '@cdc/core/styles/cove-main.scss'
5
+ import '@cdc/core/components/EditorPanel/EditorPanel.styles.css'
5
6
 
6
7
  import MultiDashboardWrapper from './CdcDashboard'
7
8
 
@@ -1,3 +1,6 @@
1
+ // Dashboard-specific Editor Panel Styles
2
+ // Common editor panel styles are imported from @cdc/core/styles/editor-panel-base via cove-main.scss
3
+
1
4
  // Shift down for top level Dashboard bar when in edit mode
2
5
  .layout-container,
3
6
  .editor-panel + .cdc-dashboard-inner-container,
@@ -101,437 +104,6 @@
101
104
  width: 75px;
102
105
  }
103
106
  }
104
- .form-container {
105
- border-right: var(--lightGray) 1px solid;
106
- flex-grow: 1;
107
- }
108
- .guidance-link {
109
- margin: 2em 1em 0;
110
- padding: 0.75em 1em;
111
- svg {
112
- width: 60px;
113
- color: var(--blue);
114
- margin-right: 1rem;
115
- height: 60px; // IE11
116
- path {
117
- fill: currentColor;
118
- }
119
- }
120
- }
121
- .warning {
122
- color: #d8000c;
123
- background-color: #ffd2d2;
124
- padding: 0.75em 1em;
125
- margin: 1em 0;
126
- font-size: 0.8em;
127
- border: #d8000c 1px solid;
128
- border-radius: 0.4em;
129
- strong {
130
- font-weight: 600;
131
- display: block;
132
- }
133
- }
134
- .accordion__button {
135
- cursor: pointer;
136
- font-size: 1em;
137
- padding: 0.3em 1em;
138
- width: 100%;
139
- text-align: left;
140
- position: relative;
141
- border-bottom: 1px solid rgba(0, 0, 0, 0.2);
142
- }
143
- .accordion__panel:first-child {
144
- margin-top: 0;
145
- }
146
- .accordion__button:before {
147
- display: inline-block;
148
- content: '';
149
- height: 7px;
150
- width: 7px;
151
- margin-right: 1em;
152
- border-bottom: 2px solid currentColor;
153
- border-right: 2px solid currentColor;
154
- transform: rotate(-45deg);
155
- right: 0;
156
- position: absolute;
157
- top: 50%;
158
- transform: rotate(-45deg) translateY(-50%);
159
- transition: 0.1s all;
160
- }
161
-
162
- .accordion__button[aria-expanded='true']::before,
163
- .accordion__button[aria-selected='true']::before {
164
- transform: rotate(45deg) translateY(-50%);
165
- margin-right: 1.3em;
166
- transition: 0.1s all;
167
- }
168
-
169
- .accordion__panel {
170
- border-bottom: 1px solid rgba(0, 0, 0, 0.2);
171
- padding: 1em 1.25em 2em;
172
- animation: fadein 0.2s ease-in;
173
- h5 {
174
- font-size: 0.8em;
175
- }
176
- }
177
-
178
- .advanced {
179
- padding: 0 1em 1em;
180
- text-align: left;
181
- p {
182
- font-size: 0.8rem;
183
- }
184
- .advanced-toggle-link {
185
- padding-top: 1em;
186
- display: block;
187
- text-align: left;
188
- cursor: pointer;
189
- text-decoration: underline;
190
- span {
191
- text-decoration: none;
192
- display: inline-block;
193
- font-family: monospace;
194
- padding-right: 5px;
195
- }
196
- &:hover {
197
- color: rgba(0, 0, 0, 0.7);
198
- }
199
- }
200
- textarea {
201
- height: 400px;
202
- width: 100%;
203
- font-size: 0.9em;
204
- padding: 0.5em;
205
- font-family: monospace;
206
- box-sizing: border-box;
207
- }
208
- }
209
-
210
- @keyframes fadein {
211
- 0% {
212
- opacity: 0;
213
- }
214
-
215
- 100% {
216
- opacity: 1;
217
- }
218
- }
219
-
220
- .heading-2 {
221
- background: #565656;
222
- color: #fff;
223
- font-size: 1.1em;
224
- padding: 0.6em 1em;
225
- position: relative;
226
- border-bottom: #565656 3px solid;
227
- z-index: 3;
228
- }
229
-
230
- label {
231
- text-transform: uppercase;
232
- display: block;
233
- font-size: 0.8em;
234
- font-weight: 600;
235
- &:not(:first-child) {
236
- margin-top: 1em;
237
- }
238
- span.edit-label {
239
- margin-bottom: 0.3em;
240
- display: block;
241
- }
242
- span.column-heading {
243
- font-size: 1em;
244
- }
245
- &.checkbox {
246
- display: flex;
247
- span:not(.cove-icon) {
248
- display: inline;
249
- }
250
- input {
251
- margin-left: 0;
252
- width: inherit;
253
- display: inline;
254
- }
255
- }
256
- }
257
- input[type='text'],
258
- input[role='combobox'],
259
- input[type='number'],
260
- textarea {
261
- min-width: 100%;
262
- max-width: 100%; // Doing this prevents width of textarea from being changed
263
- }
264
- .header .color-palette li {
265
- width: 21px;
266
- height: 21px;
267
- border-radius: 40px;
268
- margin-right: 2.8px;
269
- }
270
- .color-palette {
271
- display: flex;
272
- max-width: 100%;
273
- padding: 0;
274
- margin: 0.5em 0 0 0;
275
- list-style: none;
276
- flex-wrap: wrap;
277
- li {
278
- width: 45px;
279
- height: 23px;
280
- margin-right: 4px;
281
- margin-bottom: 10px;
282
- display: flex;
283
- border-radius: 5px;
284
- overflow: hidden;
285
- cursor: pointer;
286
- position: relative;
287
- .click-target {
288
- position: absolute;
289
- top: 0;
290
- left: 0;
291
- right: 0;
292
- bottom: 0;
293
- }
294
- &.selected {
295
- border: rgba(0, 0, 0, 0.8) 2px solid;
296
- }
297
- span {
298
- width: 33.3%;
299
- }
300
- }
301
- }
302
-
303
- fieldset {
304
- display: block;
305
- }
306
-
307
- .primary-fieldset {
308
- padding: 0;
309
- margin: 0;
310
- border: 0;
311
- }
312
-
313
- ul.column-edit {
314
- list-style: none;
315
- li {
316
- margin-top: 1em;
317
- }
318
- .three-col {
319
- display: flex;
320
- justify-content: space-between;
321
- > label {
322
- margin-top: 0;
323
- width: 30%;
324
- display: inline-block;
325
- input[type='text'],
326
- input[type='number'] {
327
- width: 50px;
328
- }
329
- }
330
- }
331
- }
332
-
333
- &.hidden {
334
- display: none;
335
- }
336
-
337
- .emove-column {
338
- float: right;
339
- text-transform: uppercase;
340
- color: #c32b2b;
341
- font-size: 0.7em;
342
- line-height: 1.6em;
343
- border-radius: 5px;
344
- margin: 0 auto;
345
- transition: 0.1s all;
346
- border: 0;
347
- text-decoration: underline;
348
- outline: 0;
349
- cursor: pointer;
350
- font-weight: bold;
351
- }
352
-
353
- .edit-block {
354
- padding-left: 1em;
355
- border-left: rgba(0, 0, 0, 0.2) 4px solid;
356
- transition: 0.2s all;
357
- &:not(:first-child) {
358
- margin-top: 2em;
359
- }
360
- input[type='text'],
361
- input[type='number'] {
362
- font-size: 1em;
363
- }
364
- label {
365
- margin-top: 0;
366
- }
367
- label + label {
368
- margin-top: 1em;
369
- }
370
- &:hover {
371
- border-left: rgba(0, 0, 0, 0.7) 4px solid;
372
- transition: 0.2s all;
373
- }
374
- }
375
-
376
- span.subtext {
377
- display: block;
378
- color: rgba(0, 0, 0, 0.5);
379
- text-transform: none;
380
- font-weight: normal;
381
- }
382
-
383
- .sort-list {
384
- list-style: none;
385
- > li {
386
- margin-right: 0.3em;
387
- margin-bottom: 0.3em;
388
- }
389
- }
390
- .sort-list li > div {
391
- display: block;
392
- box-sizing: border-box;
393
- border: 1px solid #d1d1d1;
394
- border-radius: 2px;
395
- background: #f1f1f1;
396
- padding: 0.4em 0.6em;
397
- font-size: 0.8em;
398
- margin-bottom: 0.3em;
399
- cursor: move;
400
- }
401
-
402
- .info {
403
- font-size: 0.8em;
404
- line-height: 1.4em;
405
- font-style: italic;
406
- padding-top: 10px;
407
- }
408
-
409
- .react-tags__search {
410
- width: 100%;
411
- }
412
-
413
- .react-tags {
414
- position: relative;
415
- input.react-tags__search-input {
416
- font-size: 0.8rem;
417
- }
418
- /* clicking anywhere will focus the input */
419
- cursor: text;
420
- span {
421
- display: inline;
422
- }
423
- }
424
-
425
- .react-tags.is-focused {
426
- border-color: rgba(0, 0, 0, 0.7);
427
- }
428
-
429
- .react-tags__selected {
430
- display: inline;
431
- }
432
-
433
- .react-tags__selected-tag {
434
- display: inline-block;
435
- box-sizing: border-box;
436
- border: 1px solid #d1d1d1;
437
- border-radius: 2px;
438
- background: #f1f1f1;
439
- padding: 0.4em 0.6em;
440
- font-size: 0.8em;
441
- margin-right: 0.3em;
442
- margin-bottom: 0.3em;
443
- }
444
-
445
- .react-tags__selected-tag:after {
446
- content: '\2715';
447
- color: #aaa;
448
- margin-left: 8px;
449
- }
450
-
451
- .react-tags__selected-tag:hover,
452
- .react-tags__selected-tag:focus {
453
- border-color: #b1b1b1;
454
- }
455
-
456
- .react-tags__search {
457
- display: inline-block;
458
-
459
- /* prevent autoresize overflowing the container */
460
- max-width: 100%;
461
- }
462
-
463
- @media screen and (min-width: 30em) {
464
- .react-tags__search {
465
- /* this will become the offsetParent for suggestions */
466
- position: relative;
467
- }
468
- }
469
-
470
- .react-tags__search input {
471
- /* prevent autoresize overflowing the container */
472
- max-width: 100%;
473
-
474
- /* emove styles and layout from this element */
475
- margin: 0;
476
- outline: none;
477
- padding: 0.5em 0.3em;
478
-
479
- /* match the font styles */
480
- font-size: inherit;
481
- line-height: inherit;
482
- }
483
-
484
- .react-tags__search input::-ms-clear {
485
- display: none;
486
- }
487
-
488
- .react-tags__suggestions {
489
- position: absolute;
490
- top: 100%;
491
- left: 0;
492
- width: 100%;
493
- }
494
-
495
- @media screen and (min-width: 30em) {
496
- .react-tags__suggestions {
497
- width: 240px;
498
- }
499
- }
500
-
501
- .react-tags__suggestions ul {
502
- margin: 4px -1px;
503
- padding: 0;
504
- list-style: none;
505
- background: white;
506
- border: 1px solid #d1d1d1;
507
- border-radius: 2px;
508
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
509
- }
510
-
511
- .react-tags__suggestions li {
512
- border-bottom: 1px solid #ddd;
513
- padding: 6px 8px;
514
- }
515
-
516
- .react-tags__suggestions li mark {
517
- text-decoration: underline;
518
- background: none;
519
- font-weight: 600;
520
- }
521
-
522
- .react-tags__suggestions li:hover {
523
- cursor: pointer;
524
- background: #eee;
525
- }
526
-
527
- .react-tags__suggestions li.is-active {
528
- background: #b7cfe0;
529
- }
530
-
531
- .react-tags__suggestions li.is-disabled {
532
- opacity: 0.5;
533
- cursor: auto;
534
- }
535
107
 
536
108
  .current-viz-list {
537
109
  margin: 0.5em 0;