@cdc/data-bite 4.26.2 → 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.
- package/LICENSE +201 -0
- package/dist/cdcdatabite.js +7008 -6727
- package/package.json +3 -3
- package/src/CdcDataBite.tsx +186 -127
- package/src/_stories/DataBite.Editor.stories.tsx +8 -8
- package/src/_stories/DataBite.stories.tsx +13 -1
- package/src/components/EditorPanel/EditorPanel.tsx +459 -418
- package/src/components/GradientBite.jsx +11 -9
- package/src/data/initial-state.js +3 -2
- package/src/scss/bite.scss +93 -49
- package/src/scss/kpi.scss +17 -2
- package/src/scss/main.scss +1 -1
- package/src/test/CdcDataBite.test.jsx +23 -2
- package/src/types/Config.ts +4 -0
- package/tests/fixtures/data-bite-config-with-metadata.json +35 -0
- package/tests/fixtures/data-with-metadata.json +30 -0
- package/src/images/callout-flag.svg +0 -7
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/data-bite",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.3",
|
|
4
4
|
"description": "React component for displaying a single piece of data in a card module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "cooms13 <dmo7@cdc.gov>",
|
|
7
7
|
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@cdc/core": "^4.26.
|
|
9
|
+
"@cdc/core": "^4.26.3",
|
|
10
10
|
"chroma-js": "^3.1.2",
|
|
11
11
|
"html-react-parser": "^5.2.3",
|
|
12
12
|
"lodash": "^4.17.23",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
21
21
|
"vite-plugin-svgr": "^4.2.0"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "d50e45a074fbefa56cac904917e707d57f237737",
|
|
24
24
|
"homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
|
|
25
25
|
"main": "dist/cdcdatabite",
|
|
26
26
|
"moduleName": "CdcDataBite",
|
package/src/CdcDataBite.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import Loading from '@cdc/core/components/Loading'
|
|
|
11
11
|
import Title from '@cdc/core/components/ui/Title'
|
|
12
12
|
import CircleCallout from './components/CircleCallout'
|
|
13
13
|
import GradientBite from './components/GradientBite'
|
|
14
|
-
import
|
|
14
|
+
import { VisualizationContainer, VisualizationContent } from '@cdc/core/components/Layout'
|
|
15
15
|
|
|
16
16
|
// external
|
|
17
17
|
import ResizeObserver from 'resize-observer-polyfill'
|
|
@@ -29,12 +29,13 @@ import { publish } from '@cdc/core/helpers/events'
|
|
|
29
29
|
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
30
30
|
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
31
31
|
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
32
|
+
import { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
|
|
32
33
|
import { Config } from './types/Config'
|
|
33
34
|
import dataBiteReducer from './store/db.reducer'
|
|
34
35
|
import { IMAGE_POSITION_LEFT, IMAGE_POSITION_RIGHT, IMAGE_POSITION_TOP, IMAGE_POSITION_BOTTOM } from './constants'
|
|
35
36
|
|
|
36
37
|
// images
|
|
37
|
-
import CalloutFlag from '
|
|
38
|
+
import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
|
|
38
39
|
|
|
39
40
|
import {
|
|
40
41
|
DATA_FUNCTION_COUNT,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
DATA_FUNCTION_MEDIAN,
|
|
44
45
|
DATA_FUNCTION_MODE,
|
|
45
46
|
DATA_FUNCTION_MIN,
|
|
47
|
+
DATA_FUNCTION_PASSTHROUGH,
|
|
46
48
|
DATA_FUNCTION_RANGE,
|
|
47
49
|
DATA_FUNCTION_SUM
|
|
48
50
|
} from '@cdc/core/helpers/constants'
|
|
@@ -71,8 +73,37 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
71
73
|
interactionLabel = ''
|
|
72
74
|
} = props
|
|
73
75
|
|
|
76
|
+
// Ensure imageData and dataFormat sub-fields are always defined before the reducer initializes.
|
|
77
|
+
// Defaults must match initial-state.js — updateConfig() will enforce them again once loading completes.
|
|
78
|
+
const safeConfigObj = {
|
|
79
|
+
...defaults,
|
|
80
|
+
...configObj,
|
|
81
|
+
imageData: {
|
|
82
|
+
...defaults.imageData,
|
|
83
|
+
...(configObj?.imageData || {}),
|
|
84
|
+
display: configObj?.imageData?.display ?? 'none',
|
|
85
|
+
prefix: configObj?.imageData?.prefix ?? ''
|
|
86
|
+
},
|
|
87
|
+
dataFormat: {
|
|
88
|
+
...defaults.dataFormat,
|
|
89
|
+
...(configObj?.dataFormat || {}),
|
|
90
|
+
prefix: configObj?.dataFormat?.prefix ?? '',
|
|
91
|
+
suffix: configObj?.dataFormat?.suffix ?? '%',
|
|
92
|
+
roundToPlace: configObj?.dataFormat?.roundToPlace ?? 0,
|
|
93
|
+
commas: configObj?.dataFormat?.commas ?? true
|
|
94
|
+
},
|
|
95
|
+
visual: {
|
|
96
|
+
...defaults.visual,
|
|
97
|
+
...(configObj?.visual || {})
|
|
98
|
+
},
|
|
99
|
+
general: {
|
|
100
|
+
...defaults.general,
|
|
101
|
+
...(configObj?.general || {})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
74
105
|
const initialState = {
|
|
75
|
-
config:
|
|
106
|
+
config: safeConfigObj ?? defaults,
|
|
76
107
|
loading: true,
|
|
77
108
|
currentViewport: 'lg',
|
|
78
109
|
coveLoadedHasRan: false,
|
|
@@ -111,12 +142,7 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
111
142
|
})
|
|
112
143
|
|
|
113
144
|
const updateConfig = newConfig => {
|
|
114
|
-
|
|
115
|
-
Object.keys(defaults).forEach(key => {
|
|
116
|
-
if (newConfig[key] && 'object' === typeof newConfig[key] && !Array.isArray(newConfig[key])) {
|
|
117
|
-
newConfig[key] = { ...defaults[key], ...newConfig[key] }
|
|
118
|
-
}
|
|
119
|
-
})
|
|
145
|
+
backfillDefaults(newConfig, defaults)
|
|
120
146
|
|
|
121
147
|
//Enforce default values that need to be calculated at runtime
|
|
122
148
|
newConfig.runtime = {}
|
|
@@ -154,7 +180,8 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
154
180
|
|
|
155
181
|
if (response.dataUrl) {
|
|
156
182
|
response.dataUrl = `${response.dataUrl}?${cacheBustingString()}`
|
|
157
|
-
let newData = await fetchRemoteData(response.dataUrl)
|
|
183
|
+
let { data: newData, dataMetadata } = await fetchRemoteData(response.dataUrl)
|
|
184
|
+
response.dataMetadata = dataMetadata
|
|
158
185
|
|
|
159
186
|
if (newData && response.dataDescription) {
|
|
160
187
|
newData = transform.autoStandardize(newData)
|
|
@@ -170,6 +197,12 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
170
197
|
|
|
171
198
|
const processedConfig = { ...coveUpdateWorker(response) }
|
|
172
199
|
|
|
200
|
+
// Migrate: borders always showed in previous versions regardless of config,
|
|
201
|
+
// so treat any existing config without an explicit border setting as having borders on.
|
|
202
|
+
if (processedConfig.visual && processedConfig.visual.border === false) {
|
|
203
|
+
processedConfig.visual.border = true
|
|
204
|
+
}
|
|
205
|
+
|
|
173
206
|
updateConfig({ ...defaults, ...processedConfig })
|
|
174
207
|
dispatch({ type: 'SET_LOADING', payload: false })
|
|
175
208
|
}
|
|
@@ -184,7 +217,9 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
184
217
|
isEditor,
|
|
185
218
|
showNoDataMessage: false,
|
|
186
219
|
allowHideSection: false,
|
|
187
|
-
filters: config.filters || []
|
|
220
|
+
filters: config.filters || [],
|
|
221
|
+
locale: config.locale,
|
|
222
|
+
dataMetadata: config.dataMetadata
|
|
188
223
|
})
|
|
189
224
|
|
|
190
225
|
return result.processedContent
|
|
@@ -313,8 +348,7 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
313
348
|
if (Number.isNaN(value) || typeof value === 'number') {
|
|
314
349
|
value = String(value)
|
|
315
350
|
}
|
|
316
|
-
|
|
317
|
-
let formattedValue = parseFloat(value).toLocaleString(language, {
|
|
351
|
+
let formattedValue = parseFloat(value).toLocaleString(config.locale, {
|
|
318
352
|
useGrouping: true,
|
|
319
353
|
maximumFractionDigits: 6
|
|
320
354
|
})
|
|
@@ -342,6 +376,23 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
342
376
|
}
|
|
343
377
|
})
|
|
344
378
|
|
|
379
|
+
if (dataFunction === DATA_FUNCTION_PASSTHROUGH) {
|
|
380
|
+
const sourceData = filteredData.length ? filteredData : config.data
|
|
381
|
+
if (sourceData && sourceData.length > 0) {
|
|
382
|
+
const rawValue = sourceData[0][dataColumn]
|
|
383
|
+
dataBite = rawValue !== undefined && rawValue !== null ? String(rawValue) : ''
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (typeof numberFromString(dataBite) === 'number') {
|
|
387
|
+
dataBite = applyPrecision(dataBite)
|
|
388
|
+
if (config.dataFormat.commas) {
|
|
389
|
+
dataBite = applyLocaleString(dataBite)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return includePrefixSuffix ? dataFormat.prefix + dataBite + dataFormat.suffix : dataBite
|
|
394
|
+
}
|
|
395
|
+
|
|
345
396
|
let numericalData = []
|
|
346
397
|
// Get the column's data
|
|
347
398
|
if (filteredData.length) {
|
|
@@ -443,6 +494,9 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
443
494
|
}
|
|
444
495
|
|
|
445
496
|
let body = <Loading />
|
|
497
|
+
const isCompactStyle = config.general?.isCompactStyle ?? false
|
|
498
|
+
const bodySubtext =
|
|
499
|
+
subtext && !isCompactStyle ? <p className='bite-subtext mt-0'>{parse(processContentWithMarkup(subtext))}</p> : null
|
|
446
500
|
|
|
447
501
|
const DataImage = useCallback(() => {
|
|
448
502
|
let operators = {
|
|
@@ -594,120 +648,118 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
594
648
|
}
|
|
595
649
|
|
|
596
650
|
const showBite = undefined !== dataColumn && undefined !== dataFunction
|
|
651
|
+
const isTp5 = showBite && biteStyle === 'tp5'
|
|
652
|
+
const bodyClasses = [
|
|
653
|
+
...innerContainerClasses,
|
|
654
|
+
...contentClasses,
|
|
655
|
+
isTp5 ? 'bite__style--tp5' : '',
|
|
656
|
+
isTp5 && config.visual?.whiteBackground ? 'white-background-style' : '',
|
|
657
|
+
isTp5 && config.visual?.whiteBackground && config.visual?.border ? 'display-border' : '',
|
|
658
|
+
isTp5 && config.visual?.useWrap ? 'use-wrap' : '',
|
|
659
|
+
!config.visual?.border ? 'no-borders' : ''
|
|
660
|
+
]
|
|
661
|
+
.filter(Boolean)
|
|
662
|
+
.join(' ')
|
|
597
663
|
body = (
|
|
598
664
|
<>
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
<
|
|
629
|
-
{
|
|
630
|
-
|
|
665
|
+
<VisualizationContent
|
|
666
|
+
bodyClassName={bodyClasses}
|
|
667
|
+
header={
|
|
668
|
+
!isTp5 ? (
|
|
669
|
+
<Title
|
|
670
|
+
showTitle={config.visual?.showTitle}
|
|
671
|
+
titleStyle='legacy'
|
|
672
|
+
config={config}
|
|
673
|
+
title={processContentWithMarkup(title)}
|
|
674
|
+
isDashboard={isDashboard}
|
|
675
|
+
classes={['bite-header', `${config.theme}`]}
|
|
676
|
+
/>
|
|
677
|
+
) : null
|
|
678
|
+
}
|
|
679
|
+
footer={link && link}
|
|
680
|
+
>
|
|
681
|
+
{!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error />}
|
|
682
|
+
{(!config.dataColumn || !config.dataFunction) && <Confirm />}
|
|
683
|
+
{showBite && biteStyle === 'tp5' ? (
|
|
684
|
+
<div
|
|
685
|
+
className={`bite-content cdc-callout d-flex flex-column h-100 ${
|
|
686
|
+
!config.visual?.whiteBackground ? 'dfe-block cdc-callout--data' : ''
|
|
687
|
+
}`}
|
|
688
|
+
>
|
|
689
|
+
{!config.visual?.whiteBackground && (
|
|
690
|
+
<img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />
|
|
691
|
+
)}
|
|
692
|
+
|
|
693
|
+
{config.visual?.showTitle && title && title.trim() && (
|
|
694
|
+
<h3 className='cdc-callout__heading fw-bold flex-shrink-0 d-flex align-items-start'>
|
|
695
|
+
<span>{parse(processContentWithMarkup(title))}</span>
|
|
696
|
+
</h3>
|
|
697
|
+
)}
|
|
698
|
+
<div className='cdc-callout__body d-flex flex-row align-content-start flex-grow-1'>
|
|
699
|
+
{showBite && <div className='cdc-callout__databite flex-shrink-0 me-3'>{calculateDataBite(true)}</div>}
|
|
700
|
+
<div className='cdc-callout__content flex-grow-1 d-flex flex-column min-w-0'>
|
|
701
|
+
<p className='mb-0'>{parse(processContentWithMarkup(biteBody))}</p>
|
|
702
|
+
{subtext && !isCompactStyle && (
|
|
703
|
+
<p className='bite-subtext fst-italic flex-shrink-0'>{parse(processContentWithMarkup(subtext))}</p>
|
|
631
704
|
)}
|
|
632
|
-
<div className='cdc-callout__content flex-grow-1 d-flex flex-column min-w-0'>
|
|
633
|
-
<p className='mb-0'>{parse(processContentWithMarkup(biteBody))}</p>
|
|
634
|
-
{subtext && !config.general.isCompactStyle && (
|
|
635
|
-
<p className='bite-subtext fst-italic flex-shrink-0 mt-3'>
|
|
636
|
-
{parse(processContentWithMarkup(subtext))}
|
|
637
|
-
</p>
|
|
638
|
-
)}
|
|
639
|
-
</div>
|
|
640
705
|
</div>
|
|
641
706
|
</div>
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
{
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
707
|
+
</div>
|
|
708
|
+
) : (
|
|
709
|
+
<div className={`bite ${biteClasses.join(' ')}`}>
|
|
710
|
+
<div className='bite-content-container'>
|
|
711
|
+
{showBite && 'graphic' === biteStyle && isTop && (
|
|
712
|
+
<CircleCallout
|
|
713
|
+
theme={config.theme}
|
|
714
|
+
text={calculateDataBite()}
|
|
715
|
+
biteFontSize={biteFontSize}
|
|
716
|
+
dataFormat={dataFormat}
|
|
717
|
+
/>
|
|
718
|
+
)}
|
|
719
|
+
{isTop && <DataImage />}
|
|
720
|
+
<div className={`bite-content`}>
|
|
721
|
+
{showBite && 'title' === biteStyle && (
|
|
722
|
+
<div className='bite-value' style={{ fontSize: biteFontSize + 'px' }}>
|
|
723
|
+
{calculateDataBite()}
|
|
724
|
+
</div>
|
|
725
|
+
)}
|
|
726
|
+
{showBite && 'split' === biteStyle && (
|
|
727
|
+
<div className='bite-value' style={{ fontSize: biteFontSize + 'px' }}>
|
|
728
|
+
{calculateDataBite()}
|
|
729
|
+
</div>
|
|
730
|
+
)}
|
|
731
|
+
<Fragment>
|
|
732
|
+
<div className='bite-content__text-wrap'>
|
|
733
|
+
<p className='bite-text'>
|
|
734
|
+
{showBite && 'body' === biteStyle && (
|
|
735
|
+
<span className='bite-value data-bite-body' style={{ fontSize: biteFontSize + 'px' }}>
|
|
736
|
+
{calculateDataBite()}
|
|
737
|
+
</span>
|
|
738
|
+
)}
|
|
739
|
+
{parse(processContentWithMarkup(biteBody))}
|
|
740
|
+
</p>
|
|
741
|
+
{showBite && 'end' === biteStyle && (
|
|
742
|
+
<span className='bite-value data-bite-body' style={{ fontSize: biteFontSize + 'px' }}>
|
|
671
743
|
{calculateDataBite()}
|
|
672
|
-
</
|
|
744
|
+
</span>
|
|
673
745
|
)}
|
|
674
|
-
|
|
675
|
-
<div className='bite-content__text-wrap'>
|
|
676
|
-
<p className='bite-text'>
|
|
677
|
-
{showBite && 'body' === biteStyle && (
|
|
678
|
-
<span className='bite-value data-bite-body' style={{ fontSize: biteFontSize + 'px' }}>
|
|
679
|
-
{calculateDataBite()}
|
|
680
|
-
</span>
|
|
681
|
-
)}
|
|
682
|
-
{parse(processContentWithMarkup(biteBody))}
|
|
683
|
-
</p>
|
|
684
|
-
{showBite && 'end' === biteStyle && (
|
|
685
|
-
<span className='bite-value data-bite-body' style={{ fontSize: biteFontSize + 'px' }}>
|
|
686
|
-
{calculateDataBite()}
|
|
687
|
-
</span>
|
|
688
|
-
)}
|
|
689
|
-
{subtext && !config.general.isCompactStyle && (
|
|
690
|
-
<p className='bite-subtext'>{parse(processContentWithMarkup(subtext))}</p>
|
|
691
|
-
)}
|
|
692
|
-
</div>
|
|
693
|
-
</Fragment>
|
|
746
|
+
{bodySubtext}
|
|
694
747
|
</div>
|
|
695
|
-
|
|
696
|
-
{showBite && 'graphic' === biteStyle && !isTop && (
|
|
697
|
-
<CircleCallout
|
|
698
|
-
theme={config.theme}
|
|
699
|
-
text={calculateDataBite()}
|
|
700
|
-
biteFontSize={biteFontSize}
|
|
701
|
-
dataFormat={dataFormat}
|
|
702
|
-
/>
|
|
703
|
-
)}
|
|
704
|
-
</div>
|
|
748
|
+
</Fragment>
|
|
705
749
|
</div>
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
750
|
+
{isBottom && <DataImage />}
|
|
751
|
+
{showBite && 'graphic' === biteStyle && !isTop && (
|
|
752
|
+
<CircleCallout
|
|
753
|
+
theme={config.theme}
|
|
754
|
+
text={calculateDataBite()}
|
|
755
|
+
biteFontSize={biteFontSize}
|
|
756
|
+
dataFormat={dataFormat}
|
|
757
|
+
/>
|
|
758
|
+
)}
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
)}
|
|
762
|
+
</VisualizationContent>
|
|
711
763
|
</>
|
|
712
764
|
)
|
|
713
765
|
}
|
|
@@ -717,29 +769,36 @@ const CdcDataBite = (props: CdcDataBiteProps) => {
|
|
|
717
769
|
value={{ config, updateConfig, loading, data: config.data, setParentConfig, isDashboard, isEditor }}
|
|
718
770
|
>
|
|
719
771
|
{biteStyle !== 'gradient' && (
|
|
720
|
-
<
|
|
772
|
+
<VisualizationContainer
|
|
721
773
|
ref={outerContainerRef}
|
|
722
774
|
config={config}
|
|
723
775
|
isEditor={isEditor}
|
|
724
|
-
|
|
776
|
+
currentViewport={currentViewport}
|
|
777
|
+
editorPanel={<EditorPanel />}
|
|
725
778
|
>
|
|
726
779
|
{body}
|
|
727
|
-
</
|
|
780
|
+
</VisualizationContainer>
|
|
728
781
|
)}
|
|
729
782
|
{'gradient' === biteStyle && (
|
|
730
|
-
<
|
|
783
|
+
<VisualizationContainer
|
|
731
784
|
ref={outerContainerRef}
|
|
732
785
|
config={config}
|
|
733
786
|
isEditor={isEditor}
|
|
734
|
-
|
|
787
|
+
currentViewport={currentViewport}
|
|
788
|
+
editorPanel={<EditorPanel />}
|
|
735
789
|
>
|
|
736
|
-
|
|
737
|
-
|
|
790
|
+
<VisualizationContent
|
|
791
|
+
bodyClassName={[...innerContainerClasses, ...contentClasses, 'bite__style--gradient']
|
|
792
|
+
.filter(Boolean)
|
|
793
|
+
.join(' ')}
|
|
794
|
+
footer={link && link}
|
|
795
|
+
subtext={bodySubtext}
|
|
796
|
+
>
|
|
738
797
|
{!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error />}
|
|
739
798
|
{(!config.dataColumn || !config.dataFunction) && <Confirm />}
|
|
740
799
|
<GradientBite label={config.title} value={calculateDataBite()} />
|
|
741
|
-
</
|
|
742
|
-
</
|
|
800
|
+
</VisualizationContent>
|
|
801
|
+
</VisualizationContainer>
|
|
743
802
|
)}
|
|
744
803
|
</Context.Provider>
|
|
745
804
|
)
|
|
@@ -100,7 +100,7 @@ export const GeneralSectionTests: Story = {
|
|
|
100
100
|
return {
|
|
101
101
|
hasSvg: !!svg,
|
|
102
102
|
textCount: textElements.length,
|
|
103
|
-
containerClasses: canvasElement.querySelector('.
|
|
103
|
+
containerClasses: canvasElement.querySelector('.cove-visualization')?.className || ''
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -145,7 +145,7 @@ export const GeneralSectionTests: Story = {
|
|
|
145
145
|
|
|
146
146
|
await performAndAssert(
|
|
147
147
|
'Title Update',
|
|
148
|
-
() => canvasElement.querySelector('.cove-
|
|
148
|
+
() => canvasElement.querySelector('.cove-visualization__header')?.textContent?.trim() || '',
|
|
149
149
|
async () => {}, // action already performed above
|
|
150
150
|
(before, after) => after === 'Updated Data Bite Title'
|
|
151
151
|
)
|
|
@@ -158,7 +158,7 @@ export const GeneralSectionTests: Story = {
|
|
|
158
158
|
expect(showTitleCheckbox).toBeTruthy()
|
|
159
159
|
|
|
160
160
|
const getTitleVisibility = () => {
|
|
161
|
-
const titleElement = canvasElement.querySelector('.cove-
|
|
161
|
+
const titleElement = canvasElement.querySelector('.cove-visualization__header') as HTMLElement
|
|
162
162
|
return titleElement && titleElement.offsetParent !== null
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -702,7 +702,7 @@ export const VisualSectionTests: Story = {
|
|
|
702
702
|
// TEST 3: Display Border Toggle
|
|
703
703
|
// Expectation: Border styling changes when toggled (classes or computed styles)
|
|
704
704
|
// ============================================================================
|
|
705
|
-
const contentContainer = () => canvasElement.querySelector('.cove-
|
|
705
|
+
const contentContainer = () => canvasElement.querySelector('.cove-visualization__body') as HTMLElement
|
|
706
706
|
expect(contentContainer()).toBeTruthy()
|
|
707
707
|
|
|
708
708
|
// Note: Border checkbox uses name="border", other checkboxes use similar simple names
|
|
@@ -736,7 +736,7 @@ export const VisualSectionTests: Story = {
|
|
|
736
736
|
|
|
737
737
|
// Test border checkbox with comprehensive boolean testing AND visual validation
|
|
738
738
|
const getBorderVisualState = () => {
|
|
739
|
-
const element = canvasElement.querySelector('.cove-
|
|
739
|
+
const element = canvasElement.querySelector('.cove-visualization__body')
|
|
740
740
|
return {
|
|
741
741
|
classes: Array.from(element!.classList).sort().join(' '),
|
|
742
742
|
hasNoBordersClass: element!.classList.contains('no-borders'),
|
|
@@ -792,14 +792,14 @@ export const VisualSectionTests: Story = {
|
|
|
792
792
|
|
|
793
793
|
// Test remaining checkboxes with comprehensive boolean testing AND visual validation
|
|
794
794
|
const getGeneralVisualState = () => {
|
|
795
|
-
const element = canvasElement.querySelector('.cove-
|
|
795
|
+
const element = canvasElement.querySelector('.cove-visualization__body')
|
|
796
796
|
return {
|
|
797
797
|
classes: Array.from(element!.classList).sort().join(' '),
|
|
798
798
|
// Check for specific component classes that these controls add
|
|
799
799
|
hasAccentClass: element!.classList.contains('component--has-accent'),
|
|
800
800
|
hasBackgroundClass: element!.classList.contains('component--has-background'),
|
|
801
|
-
hasBorderColorThemeClass: element!.classList.contains('component--has-
|
|
802
|
-
hideBackgroundColorClass: element!.classList.contains('component--
|
|
801
|
+
hasBorderColorThemeClass: element!.classList.contains('component--has-border-color-theme'),
|
|
802
|
+
hideBackgroundColorClass: element!.classList.contains('component--hide-background-color'),
|
|
803
803
|
themeClass: Array.from(element!.classList).find(cls => cls.includes('theme-')) || 'no-theme',
|
|
804
804
|
backgroundStyle: getComputedStyle(element!).backgroundColor
|
|
805
805
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import DataBite from '../CdcDataBite'
|
|
3
|
-
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
3
|
+
import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
|
|
4
|
+
import { expect } from 'storybook/test'
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof DataBite> = {
|
|
6
7
|
title: 'Components/Templates/Data Bite',
|
|
@@ -89,6 +90,17 @@ export const Data_Bite_TP5_White_Background: Story = {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
export const Data_Bite_With_Metadata: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
configUrl: '/packages/data-bite/tests/fixtures/data-bite-config-with-metadata.json'
|
|
96
|
+
},
|
|
97
|
+
play: async ({ canvasElement }) => {
|
|
98
|
+
await assertVisualizationRendered(canvasElement)
|
|
99
|
+
const subtext = await waitForPresence('.bite-subtext', canvasElement)
|
|
100
|
+
expect(subtext?.textContent).toContain('January 15, 2026')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
// Simple editor mode story for basic rendering
|
|
93
105
|
export const Editor_Mode_Basic: Story = {
|
|
94
106
|
args: {
|