@cdc/core 4.25.6 → 4.25.8
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/components/DataTable/DataTable.tsx +52 -19
- package/components/DataTable/DataTableStandAlone.tsx +4 -1
- package/components/DataTable/components/ChartHeader.tsx +44 -13
- package/components/DataTable/components/ExpandCollapse.tsx +14 -2
- package/components/DataTable/components/MapHeader.tsx +18 -1
- package/components/DataTable/components/SortIcon/sort-icon.css +21 -27
- package/components/DataTable/data-table.css +33 -4
- package/components/DataTable/helpers/customSort.ts +4 -2
- package/components/DownloadButton.tsx +4 -1
- package/components/EditorPanel/DataTableEditor.tsx +10 -1
- package/components/Filters/Filters.tsx +26 -9
- package/components/Footnotes/FootnotesStandAlone.tsx +2 -1
- package/components/{MediaControls.jsx → MediaControls.tsx} +32 -16
- package/components/Table/components/Row.tsx +15 -12
- package/dist/cove-main.css +0 -10
- package/dist/cove-main.css.map +1 -1
- package/helpers/cove/number.ts +6 -2
- package/helpers/coveUpdateWorker.ts +5 -1
- package/helpers/events.ts +32 -0
- package/helpers/isRightAlignedTableValue.js +1 -1
- package/helpers/metrics/helpers.ts +52 -0
- package/helpers/metrics/types.ts +43 -0
- package/helpers/vegaConfig.ts +647 -0
- package/helpers/ver/4.25.7.ts +26 -0
- package/helpers/ver/4.25.8.ts +61 -0
- package/helpers/ver/tests/4.25.8.test.ts +86 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +6 -4
- package/styles/_accessibility.scss +8 -0
- package/styles/_button-section.scss +0 -2
- package/styles/_global.scss +0 -4
- package/styles/filters.scss +0 -4
- package/types/ForecastingSeriesKey.ts +15 -0
- package/types/Runtime.ts +1 -7
- package/types/Series.ts +4 -0
- package/types/Table.ts +1 -0
- package/helpers/events.js +0 -14
|
@@ -52,9 +52,9 @@ export type DataTableProps = {
|
|
|
52
52
|
vizTitle?: string
|
|
53
53
|
// determines if columns should be wrapped in the table
|
|
54
54
|
wrapColumns?: boolean
|
|
55
|
+
interactionLabel?: string
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
58
58
|
const DataTable = (props: DataTableProps) => {
|
|
59
59
|
const {
|
|
60
60
|
columns,
|
|
@@ -71,7 +71,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
71
71
|
tableTitle,
|
|
72
72
|
viewport,
|
|
73
73
|
vizTitle,
|
|
74
|
-
wrapColumns
|
|
74
|
+
wrapColumns,
|
|
75
|
+
interactionLabel = ''
|
|
75
76
|
} = props
|
|
76
77
|
const runtimeData = useMemo(() => {
|
|
77
78
|
const data = removeNullColumns(parentRuntimeData)
|
|
@@ -150,7 +151,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
150
151
|
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
|
|
151
152
|
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
|
|
152
153
|
}
|
|
153
|
-
return dataA
|
|
154
|
+
return dataA || dataB ? customSort(dataA, dataB, sortBy, config) : 0
|
|
154
155
|
})
|
|
155
156
|
: rawRows
|
|
156
157
|
|
|
@@ -189,15 +190,15 @@ const DataTable = (props: DataTableProps) => {
|
|
|
189
190
|
|
|
190
191
|
// prettier-ignore
|
|
191
192
|
const tableData = useMemo(() => (
|
|
192
|
-
|
|
193
|
-
? config.data?.[0]?.tableData
|
|
194
|
-
: config.visualizationType === 'Sankey'
|
|
193
|
+
config.data?.[0]?.tableData
|
|
195
194
|
? config.data?.[0]?.tableData
|
|
196
|
-
: config.visualizationType === '
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
: config.visualizationType === 'Sankey'
|
|
196
|
+
? config.data?.[0]?.tableData
|
|
197
|
+
: config.visualizationType === 'Pie'
|
|
198
|
+
? [config.yAxis.dataKey]
|
|
199
|
+
: config.visualizationType === 'Box Plot'
|
|
200
|
+
? config?.boxplot?.plots?.[0] ? Object.entries(config.boxplot.plots[0]) : []
|
|
201
|
+
: config.runtime?.seriesKeys),
|
|
201
202
|
[config.runtime?.seriesKeys]) // eslint-disable-line
|
|
202
203
|
|
|
203
204
|
const hasNoData = runtimeData.length === 0
|
|
@@ -279,6 +280,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
279
280
|
? mapCellMatrix({ ...props, rows, wrapColumns, runtimeData, viewport })
|
|
280
281
|
: chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
|
|
281
282
|
|
|
283
|
+
const useBottomExpandCollapse = config.table.showBottomCollapse && expanded && Array.isArray(childrenMatrix)
|
|
284
|
+
|
|
282
285
|
// If every value in a column is a number, record the column index so the header and cells can be right-aligned
|
|
283
286
|
const rightAlignedCols = childrenMatrix.length
|
|
284
287
|
? Object.fromEntries(
|
|
@@ -290,16 +293,18 @@ const DataTable = (props: DataTableProps) => {
|
|
|
290
293
|
)
|
|
291
294
|
: {}
|
|
292
295
|
|
|
296
|
+
const showCollapseButton = config.table.collapsible !== false && useBottomExpandCollapse
|
|
293
297
|
const TableMediaControls = ({ belowTable }) => {
|
|
294
298
|
const hasDownloadLink = config.table.download
|
|
295
299
|
return (
|
|
296
300
|
<MediaControls.Section classes={getMediaControlsClasses(belowTable, hasDownloadLink)}>
|
|
297
|
-
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
301
|
+
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} interactionLabel={interactionLabel} />
|
|
298
302
|
{hasDownloadLink && (
|
|
299
303
|
<DownloadButton
|
|
300
304
|
rawData={getDownloadData()}
|
|
301
305
|
fileName={`${vizTitle || 'data-table'}.csv`}
|
|
302
306
|
headerColor={headerColor}
|
|
307
|
+
interactionLabel={interactionLabel}
|
|
303
308
|
/>
|
|
304
309
|
)}
|
|
305
310
|
</MediaControls.Section>
|
|
@@ -308,11 +313,21 @@ const DataTable = (props: DataTableProps) => {
|
|
|
308
313
|
|
|
309
314
|
return (
|
|
310
315
|
<ErrorBoundary component='DataTable'>
|
|
311
|
-
{!config.table.showDownloadLinkBelow &&
|
|
316
|
+
{!config.table.showDownloadLinkBelow && (
|
|
317
|
+
<div className='w-100 d-flex justify-content-end'>
|
|
318
|
+
<TableMediaControls />
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
312
321
|
<section id={tabbingId.replace('#', '')} className={getClassNames()} aria-label={accessibilityLabel}>
|
|
313
322
|
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
314
323
|
{config.table.collapsible !== false && (
|
|
315
|
-
<ExpandCollapse
|
|
324
|
+
<ExpandCollapse
|
|
325
|
+
expanded={expanded}
|
|
326
|
+
setExpanded={setExpanded}
|
|
327
|
+
tableTitle={tableTitle}
|
|
328
|
+
config={config}
|
|
329
|
+
interactionLabel={interactionLabel}
|
|
330
|
+
/>
|
|
316
331
|
)}
|
|
317
332
|
<div className='table-container' style={limitHeight}>
|
|
318
333
|
<Table
|
|
@@ -333,6 +348,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
333
348
|
sortBy={sortBy}
|
|
334
349
|
setSortBy={setSortBy}
|
|
335
350
|
rightAlignedCols={rightAlignedCols}
|
|
351
|
+
interactionLabel={interactionLabel}
|
|
336
352
|
/>
|
|
337
353
|
) : (
|
|
338
354
|
<ChartHeader
|
|
@@ -344,13 +360,14 @@ const DataTable = (props: DataTableProps) => {
|
|
|
344
360
|
setSortBy={setSortBy}
|
|
345
361
|
viewport={viewport}
|
|
346
362
|
rightAlignedCols={rightAlignedCols}
|
|
363
|
+
interactionLabel={interactionLabel}
|
|
347
364
|
/>
|
|
348
365
|
)
|
|
349
366
|
}
|
|
350
367
|
tableOptions={{
|
|
351
|
-
className: `table table-striped
|
|
352
|
-
|
|
353
|
-
}`,
|
|
368
|
+
className: `table table-striped table-width-unset ${
|
|
369
|
+
expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'
|
|
370
|
+
}${isVertical ? '' : ' horizontal'}`,
|
|
354
371
|
'aria-live': 'assertive',
|
|
355
372
|
'aria-rowcount': config?.data?.length ? config.data.length : -1,
|
|
356
373
|
hidden: !expanded,
|
|
@@ -383,7 +400,18 @@ const DataTable = (props: DataTableProps) => {
|
|
|
383
400
|
)}
|
|
384
401
|
</div>
|
|
385
402
|
</section>
|
|
386
|
-
|
|
403
|
+
<div className={`w-100 d-flex ${showCollapseButton ? 'justify-content-between' : 'justify-content-end'}`}>
|
|
404
|
+
{showCollapseButton && (
|
|
405
|
+
<button
|
|
406
|
+
className='border-0 bg-transparent text-decoration-underline mt-2'
|
|
407
|
+
style={{ color: 'var(--colors-link-blue)', fontSize: '0.772rem', textUnderlineOffset: '6px' }}
|
|
408
|
+
onClick={() => setExpanded(false)}
|
|
409
|
+
>
|
|
410
|
+
- Collapse table
|
|
411
|
+
</button>
|
|
412
|
+
)}
|
|
413
|
+
{config.table.showDownloadLinkBelow && <TableMediaControls belowTable={true} />}
|
|
414
|
+
</div>
|
|
387
415
|
<div id={skipId} className='cdcdataviz-sr-only'>
|
|
388
416
|
Skipped data table.
|
|
389
417
|
</div>
|
|
@@ -395,7 +423,12 @@ const DataTable = (props: DataTableProps) => {
|
|
|
395
423
|
<ErrorBoundary component='DataTable'>
|
|
396
424
|
<section id={tabbingId.replace('#', '')} className={getClassNames()} aria-label={accessibilityLabel}>
|
|
397
425
|
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
398
|
-
<ExpandCollapse
|
|
426
|
+
<ExpandCollapse
|
|
427
|
+
expanded={expanded}
|
|
428
|
+
setExpanded={setExpanded}
|
|
429
|
+
tableTitle={tableTitle}
|
|
430
|
+
interactionLabel={interactionLabel}
|
|
431
|
+
/>
|
|
399
432
|
<div className='table-container' style={limitHeight}>
|
|
400
433
|
<Table
|
|
401
434
|
viewport={viewport}
|
|
@@ -18,6 +18,7 @@ type StandAloneProps = {
|
|
|
18
18
|
isEditor?: boolean
|
|
19
19
|
updateConfig?: (Visualization) => void
|
|
20
20
|
datasets?: Datasets
|
|
21
|
+
interactionLabel?: string
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
@@ -26,7 +27,8 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
26
27
|
updateConfig,
|
|
27
28
|
viewport,
|
|
28
29
|
isEditor,
|
|
29
|
-
datasets
|
|
30
|
+
datasets,
|
|
31
|
+
interactionLabel = ''
|
|
30
32
|
}) => {
|
|
31
33
|
const [filteredData, setFilteredData] = useState<Record<string, any>[]>(
|
|
32
34
|
filterVizData(config.filters, config.formattedData || config.data)
|
|
@@ -69,6 +71,7 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
69
71
|
tabbingId={visualizationKey}
|
|
70
72
|
tableTitle={config.table.label}
|
|
71
73
|
viewport={viewport || 'lg'}
|
|
74
|
+
interactionLabel={interactionLabel}
|
|
72
75
|
/>
|
|
73
76
|
<FootnotesStandAlone config={config.footnotes} filters={config.filters?.filter(f => f.filterFootnotes)} />
|
|
74
77
|
</>
|
|
@@ -5,8 +5,20 @@ import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
|
|
|
5
5
|
import { SortIcon } from './SortIcon'
|
|
6
6
|
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
7
7
|
import parse from 'html-react-parser'
|
|
8
|
+
import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
9
|
+
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
8
10
|
|
|
9
|
-
type ChartHeaderProps = {
|
|
11
|
+
type ChartHeaderProps = {
|
|
12
|
+
data
|
|
13
|
+
isVertical
|
|
14
|
+
config
|
|
15
|
+
setSortBy
|
|
16
|
+
sortBy
|
|
17
|
+
hasRowType?
|
|
18
|
+
viewport
|
|
19
|
+
rightAlignedCols
|
|
20
|
+
interactionLabel: string
|
|
21
|
+
}
|
|
10
22
|
|
|
11
23
|
const ChartHeader = ({
|
|
12
24
|
data,
|
|
@@ -16,7 +28,8 @@ const ChartHeader = ({
|
|
|
16
28
|
sortBy,
|
|
17
29
|
hasRowType,
|
|
18
30
|
viewport,
|
|
19
|
-
rightAlignedCols
|
|
31
|
+
rightAlignedCols,
|
|
32
|
+
interactionLabel
|
|
20
33
|
}: ChartHeaderProps) => {
|
|
21
34
|
const groupBy = config.table?.groupBy
|
|
22
35
|
if (!data) return
|
|
@@ -48,20 +61,30 @@ const ChartHeader = ({
|
|
|
48
61
|
if (columnHeaderText === notApplicableText) return
|
|
49
62
|
|
|
50
63
|
return (
|
|
51
|
-
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
|
|
52
|
-
|
|
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>
|
|
53
67
|
)
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
const ColumnHeadingText = ({
|
|
70
|
+
const ColumnHeadingText = ({ text, config }: { text: string; config: ChartConfig }) => {
|
|
71
|
+
const notApplicableText = 'Not Applicable'
|
|
57
72
|
if (text === '_pivotedFrom') return ''
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
if (text === '__series__') {
|
|
74
|
+
if (config.table.indexLabel) {
|
|
75
|
+
return parse(String(config.table.indexLabel))
|
|
76
|
+
} else {
|
|
77
|
+
return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
|
|
78
|
+
}
|
|
79
|
+
}
|
|
64
80
|
|
|
81
|
+
// handle any unexpected values
|
|
82
|
+
if (typeof text !== 'string') {
|
|
83
|
+
return parse('')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return parse(text)
|
|
87
|
+
}
|
|
65
88
|
if (isVertical) {
|
|
66
89
|
if (hasRowType) {
|
|
67
90
|
// find the row type column and place it at the beginning of the array
|
|
@@ -75,7 +98,7 @@ const ChartHeader = ({
|
|
|
75
98
|
return (
|
|
76
99
|
<tr>
|
|
77
100
|
{dataSeriesColumns.map((column, index) => {
|
|
78
|
-
const text =
|
|
101
|
+
const text = getSeriesName(column, config)
|
|
79
102
|
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
80
103
|
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
|
|
81
104
|
const isSortedCol = column === sortBy.column && !hasRowType
|
|
@@ -93,6 +116,13 @@ const ChartHeader = ({
|
|
|
93
116
|
scope='col'
|
|
94
117
|
onClick={() => {
|
|
95
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
|
+
)
|
|
96
126
|
setSortBy(newSortBy)
|
|
97
127
|
}}
|
|
98
128
|
onKeyDown={e => {
|
|
@@ -124,7 +154,8 @@ const ChartHeader = ({
|
|
|
124
154
|
const rightAxisItemsMap = new Map(rightAxisItems.map(item => [item.dataKey, item]))
|
|
125
155
|
|
|
126
156
|
let column = config.xAxis?.dataKey
|
|
127
|
-
let text =
|
|
157
|
+
let text =
|
|
158
|
+
row !== '__series__' ? getChartCellValue(row, column, config, data, rightAxisItemsMap) : '__series__'
|
|
128
159
|
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
129
160
|
const sortByAsc = sortBy.colIndex === index ? sortBy.asc : undefined
|
|
130
161
|
const isSortedCol = index === sortBy.colIndex && !hasRowType
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
2
|
+
import { Visualization } from '../../../types/Visualization'
|
|
1
3
|
import Icon from '../../ui/Icon'
|
|
4
|
+
import parse from 'html-react-parser'
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
interface ExpandCollapseProps {
|
|
7
|
+
expanded: boolean
|
|
8
|
+
setExpanded: (expanded: boolean) => void
|
|
9
|
+
tableTitle: string
|
|
10
|
+
config?: Visualization
|
|
11
|
+
interactionLabel?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ExpandCollapse = ({ expanded, setExpanded, tableTitle, config, interactionLabel = '' }: ExpandCollapseProps) => {
|
|
4
15
|
return (
|
|
5
16
|
<div
|
|
6
17
|
role='button'
|
|
7
18
|
className={expanded ? 'data-table-heading p-3' : 'collapsed data-table-heading p-3'}
|
|
8
19
|
onClick={() => {
|
|
20
|
+
publishAnalyticsEvent('data_table_toggled', 'click', interactionLabel, config.type || 'unknown')
|
|
9
21
|
setExpanded(!expanded)
|
|
10
22
|
}}
|
|
11
23
|
tabIndex={0}
|
|
@@ -16,7 +28,7 @@ const ExpandCollapse = ({ expanded, setExpanded, tableTitle, fontSize, viewport
|
|
|
16
28
|
}}
|
|
17
29
|
>
|
|
18
30
|
<Icon display={expanded ? 'minus' : 'plus'} base />
|
|
19
|
-
{tableTitle}
|
|
31
|
+
{parse(tableTitle)}
|
|
20
32
|
</div>
|
|
21
33
|
)
|
|
22
34
|
}
|
|
@@ -2,10 +2,12 @@ import { DataTableProps } from '../DataTable'
|
|
|
2
2
|
import ScreenReaderText from '../../elements/ScreenReaderText'
|
|
3
3
|
import { SortIcon } from './SortIcon'
|
|
4
4
|
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
5
|
+
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
5
6
|
|
|
6
7
|
type MapHeaderProps = DataTableProps & {
|
|
7
8
|
sortBy: { column; asc }
|
|
8
9
|
setSortBy: Function
|
|
10
|
+
interactionLabel: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
const ColumnHeadingText = ({ text, config }) => {
|
|
@@ -16,7 +18,15 @@ const ColumnHeadingText = ({ text, config }) => {
|
|
|
16
18
|
return text
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const MapHeader = ({
|
|
21
|
+
const MapHeader = ({
|
|
22
|
+
columns,
|
|
23
|
+
config,
|
|
24
|
+
indexTitle,
|
|
25
|
+
sortBy,
|
|
26
|
+
setSortBy,
|
|
27
|
+
rightAlignedCols,
|
|
28
|
+
interactionLabel = ''
|
|
29
|
+
}: MapHeaderProps) => {
|
|
20
30
|
return (
|
|
21
31
|
<tr>
|
|
22
32
|
{Object.keys(columns)
|
|
@@ -46,6 +56,13 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy, rightAligne
|
|
|
46
56
|
role='columnheader'
|
|
47
57
|
scope='col'
|
|
48
58
|
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
|
+
)
|
|
49
66
|
setSortBy(newSortBy)
|
|
50
67
|
}}
|
|
51
68
|
onKeyDown={e => {
|
|
@@ -1,36 +1,30 @@
|
|
|
1
|
-
/* format the white triangle sort icon in data table headers */
|
|
2
1
|
.sort-icon {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
:
|
|
7
|
-
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
|
|
7
|
+
position: absolute;
|
|
8
|
+
right: 4px;
|
|
9
|
+
top: 50%;
|
|
10
|
+
transform: translateY(-50%);
|
|
11
|
+
z-index: 1;
|
|
12
|
+
|
|
13
|
+
svg {
|
|
14
|
+
width: 0.75rem;
|
|
15
|
+
height: 0.75rem;
|
|
8
16
|
fill: rgba(255, 255, 255, 0.5);
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
line-height: 1;
|
|
20
|
+
|
|
9
21
|
&.active {
|
|
10
22
|
fill: white;
|
|
11
23
|
}
|
|
12
|
-
width: 1rem;
|
|
13
|
-
height: 1rem;
|
|
14
|
-
}
|
|
15
|
-
.up {
|
|
16
|
-
bottom: 0.5rem;
|
|
17
|
-
}
|
|
18
|
-
.down {
|
|
19
|
-
top: 0.5rem;
|
|
20
24
|
}
|
|
21
|
-
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
:
|
|
26
|
-
width: 0.9rem;
|
|
27
|
-
height: 0.9rem;
|
|
28
|
-
}
|
|
29
|
-
.up {
|
|
30
|
-
bottom: 0.3rem;
|
|
31
|
-
}
|
|
32
|
-
.down {
|
|
33
|
-
top: 0.3rem;
|
|
34
|
-
}
|
|
26
|
+
svg.up,
|
|
27
|
+
svg.down {
|
|
28
|
+
margin: 0 !important;
|
|
35
29
|
}
|
|
36
30
|
}
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
min-width: 100%;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
.
|
|
6
|
+
.bs4 .table.table-width-unset {
|
|
7
|
+
width: unset;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.collapsed+.table-container {
|
|
7
11
|
border-bottom: none;
|
|
8
12
|
}
|
|
13
|
+
|
|
9
14
|
.table-container {
|
|
10
15
|
overflow-x: auto;
|
|
11
16
|
border-right: 1px solid var(--lightGray);
|
|
@@ -33,6 +38,7 @@
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
table.horizontal {
|
|
41
|
+
|
|
36
42
|
th,
|
|
37
43
|
td {
|
|
38
44
|
min-width: 200px;
|
|
@@ -47,6 +53,7 @@ table.data-table {
|
|
|
47
53
|
border-collapse: collapse;
|
|
48
54
|
appearance: none;
|
|
49
55
|
table-layout: fixed;
|
|
56
|
+
|
|
50
57
|
* {
|
|
51
58
|
box-sizing: border-box;
|
|
52
59
|
}
|
|
@@ -67,8 +74,10 @@ table.data-table {
|
|
|
67
74
|
background: none;
|
|
68
75
|
}
|
|
69
76
|
}
|
|
77
|
+
|
|
70
78
|
thead {
|
|
71
79
|
color: #fff;
|
|
80
|
+
|
|
72
81
|
.resizer {
|
|
73
82
|
cursor: e-resize;
|
|
74
83
|
width: 10px;
|
|
@@ -78,9 +87,11 @@ table.data-table {
|
|
|
78
87
|
right: 0;
|
|
79
88
|
touch-action: none;
|
|
80
89
|
}
|
|
90
|
+
|
|
81
91
|
tr {
|
|
82
92
|
text-align: left;
|
|
83
93
|
}
|
|
94
|
+
|
|
84
95
|
th,
|
|
85
96
|
td {
|
|
86
97
|
padding: 0.5em 0.7em;
|
|
@@ -108,8 +119,11 @@ table.data-table {
|
|
|
108
119
|
background-color: var(--tertiary) !important;
|
|
109
120
|
font-weight: bold;
|
|
110
121
|
}
|
|
122
|
+
|
|
111
123
|
border-bottom: solid 1px #e5e5e5;
|
|
112
|
-
min-width: 100%;
|
|
124
|
+
min-width: 100%;
|
|
125
|
+
|
|
126
|
+
/* Needed to fill content up*/
|
|
113
127
|
&:last-child {
|
|
114
128
|
border-bottom: 0;
|
|
115
129
|
}
|
|
@@ -120,6 +134,7 @@ table.data-table {
|
|
|
120
134
|
padding: 0.3em 0.7em;
|
|
121
135
|
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
|
122
136
|
white-space: nowrap;
|
|
137
|
+
|
|
123
138
|
&:last-child {
|
|
124
139
|
border-right: 0 !important;
|
|
125
140
|
}
|
|
@@ -127,6 +142,7 @@ table.data-table {
|
|
|
127
142
|
|
|
128
143
|
td {
|
|
129
144
|
position: relative;
|
|
145
|
+
|
|
130
146
|
svg {
|
|
131
147
|
margin-left: 1rem;
|
|
132
148
|
}
|
|
@@ -148,6 +164,7 @@ table.data-table {
|
|
|
148
164
|
text-decoration: underline;
|
|
149
165
|
cursor: pointer;
|
|
150
166
|
color: #075290;
|
|
167
|
+
|
|
151
168
|
svg {
|
|
152
169
|
max-width: 13px;
|
|
153
170
|
vertical-align: baseline;
|
|
@@ -163,6 +180,7 @@ table.data-table {
|
|
|
163
180
|
|
|
164
181
|
.no-data {
|
|
165
182
|
position: relative;
|
|
183
|
+
|
|
166
184
|
.no-data-message {
|
|
167
185
|
background: rgba(255, 255, 255, 0.5);
|
|
168
186
|
top: 0;
|
|
@@ -175,18 +193,22 @@ table.data-table {
|
|
|
175
193
|
align-items: center;
|
|
176
194
|
justify-content: center;
|
|
177
195
|
z-index: 7;
|
|
196
|
+
|
|
178
197
|
:is(h3) {
|
|
179
198
|
font-size: 1.3rem;
|
|
180
199
|
font-weight: 600;
|
|
181
200
|
margin-bottom: 0.3rem;
|
|
182
201
|
}
|
|
183
202
|
}
|
|
203
|
+
|
|
184
204
|
tr:hover {
|
|
185
205
|
background: #fff;
|
|
186
206
|
}
|
|
207
|
+
|
|
187
208
|
th,
|
|
188
209
|
td {
|
|
189
210
|
width: 50%;
|
|
211
|
+
|
|
190
212
|
&::before {
|
|
191
213
|
content: '\00a0';
|
|
192
214
|
}
|
|
@@ -197,15 +219,19 @@ table.data-table {
|
|
|
197
219
|
margin: 1rem 0;
|
|
198
220
|
display: flex;
|
|
199
221
|
align-items: center;
|
|
222
|
+
|
|
200
223
|
ul {
|
|
201
224
|
list-style: none;
|
|
202
225
|
margin: 0 1rem 0 0;
|
|
203
226
|
display: flex;
|
|
204
|
-
|
|
227
|
+
|
|
228
|
+
li+li {
|
|
205
229
|
margin-left: 0.3rem;
|
|
206
230
|
}
|
|
231
|
+
|
|
207
232
|
button {
|
|
208
233
|
background: var(--mediumGray);
|
|
234
|
+
|
|
209
235
|
&:hover {
|
|
210
236
|
background: lighten(var(--mediumGray), 5%);
|
|
211
237
|
}
|
|
@@ -219,6 +245,7 @@ table.data-table {
|
|
|
219
245
|
background: var(--mediumGray);
|
|
220
246
|
opacity: 0.3;
|
|
221
247
|
cursor: default;
|
|
248
|
+
|
|
222
249
|
&:hover {
|
|
223
250
|
background: var(--mediumGray);
|
|
224
251
|
}
|
|
@@ -232,14 +259,16 @@ table.data-table {
|
|
|
232
259
|
text-decoration: none;
|
|
233
260
|
transition: 0.3s all;
|
|
234
261
|
margin: 1em 0;
|
|
262
|
+
|
|
235
263
|
&:hover {
|
|
236
264
|
transition: 0.3s all;
|
|
237
265
|
}
|
|
238
266
|
}
|
|
267
|
+
|
|
239
268
|
.cove,
|
|
240
269
|
.cdc-open-viz-module {
|
|
241
270
|
.download-links a:not(:last-child) {
|
|
242
271
|
margin-right: 10px;
|
|
243
272
|
display: inline-block;
|
|
244
273
|
}
|
|
245
|
-
}
|
|
274
|
+
}
|
|
@@ -37,8 +37,10 @@ export const customSort = (a, b, sortBy, config) => {
|
|
|
37
37
|
const isNumB = !isNaN(Number(valueB)) && valueB !== undefined && valueB !== null && trimmedB !== ''
|
|
38
38
|
|
|
39
39
|
// Handle empty strings or spaces
|
|
40
|
-
|
|
41
|
-
if (trimmedA
|
|
40
|
+
// empty string should always come last no matter the asc
|
|
41
|
+
if (trimmedA === '' && trimmedB === '') return 0
|
|
42
|
+
if (trimmedA === '' && trimmedB !== '') return 1
|
|
43
|
+
if (trimmedA !== '' && trimmedB === '') return -1
|
|
42
44
|
|
|
43
45
|
// Both are numbers: Compare numerically
|
|
44
46
|
if (isNumA && isNumB) {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import Papa from 'papaparse'
|
|
2
|
+
import { publishAnalyticsEvent } from '../helpers/metrics/helpers'
|
|
2
3
|
|
|
3
4
|
type DownloadButtonProps = {
|
|
4
5
|
rawData: Object
|
|
5
6
|
fileName: string
|
|
6
7
|
headerColor: string
|
|
7
8
|
skipId: string | number
|
|
9
|
+
configUrl?: string
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButtonProps) => {
|
|
12
|
+
const DownloadButton = ({ rawData, fileName, headerColor, skipId, configUrl }: DownloadButtonProps) => {
|
|
11
13
|
const csvData = Papa.unparse(rawData)
|
|
12
14
|
// Prepend a Byte Order Mark (BOM) to the CSV data.
|
|
13
15
|
// The BOM is a special marker that helps applications like Excel recognize the file as UTF-8 encoded.
|
|
@@ -22,6 +24,7 @@ const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButt
|
|
|
22
24
|
//@ts-ignore
|
|
23
25
|
navigator.msSaveBlob(blob, fileName)
|
|
24
26
|
}
|
|
27
|
+
publishAnalyticsEvent('data_downloaded', 'click', configUrl)
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
return (
|
|
@@ -216,7 +216,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
216
216
|
section='table'
|
|
217
217
|
updateField={updateField}
|
|
218
218
|
/>
|
|
219
|
-
{config.table.collapsible
|
|
219
|
+
{config.table.collapsible && (
|
|
220
220
|
<CheckBox
|
|
221
221
|
value={config.table.expanded}
|
|
222
222
|
fieldName='expanded'
|
|
@@ -225,6 +225,15 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
225
225
|
updateField={updateField}
|
|
226
226
|
/>
|
|
227
227
|
)}
|
|
228
|
+
{config.table.collapsible && (
|
|
229
|
+
<CheckBox
|
|
230
|
+
value={config.table.showBottomCollapse}
|
|
231
|
+
fieldName='showBottomCollapse'
|
|
232
|
+
label='Show collapse below table'
|
|
233
|
+
section='table'
|
|
234
|
+
updateField={updateField}
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
228
237
|
<CheckBox
|
|
229
238
|
value={config.table.download}
|
|
230
239
|
fieldName='download'
|