@cdc/core 4.24.9-1 → 4.24.10
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/assets/icon-combo-chart.svg +1 -0
- package/assets/icon-epi-chart.svg +27 -0
- package/components/BlurStrokeText.tsx +44 -0
- package/components/DataTable/DataTable.tsx +51 -35
- package/components/DataTable/DataTableStandAlone.tsx +37 -6
- package/components/DataTable/components/ChartHeader.tsx +31 -26
- package/components/DataTable/components/MapHeader.tsx +19 -10
- package/components/DataTable/components/SortIcon/index.tsx +25 -0
- package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
- package/{styles/_data-table.scss → components/DataTable/data-table.css} +268 -298
- package/components/DataTable/helpers/customSort.ts +11 -15
- package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
- package/components/DataTable/helpers/getNewSortBy.ts +35 -0
- package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
- package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
- package/components/EditorPanel/DataTableEditor.tsx +132 -26
- package/components/EditorPanel/Inputs.tsx +42 -4
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +1 -1
- package/components/{Filters.tsx → Filters/Filters.tsx} +48 -39
- package/components/Filters/helpers/applyQueuedActive.ts +12 -0
- package/components/Filters/helpers/getNestedOptions.ts +29 -0
- package/components/Filters/helpers/handleSorting.ts +18 -0
- package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
- package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
- package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
- package/components/Filters/index.ts +5 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -3
- package/components/Legend/Legend.Gradient.tsx +2 -9
- package/components/Loader/Loader.tsx +33 -0
- package/components/Loader/index.ts +1 -0
- package/components/Loader/loader.styles.css +13 -0
- package/components/NestedDropdown/NestedDropdown.tsx +90 -48
- package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
- package/components/NestedDropdown/nesteddropdown.styles.css +7 -0
- package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
- package/components/Table/components/GroupRow.tsx +1 -1
- package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
- package/components/_stories/NestedDropdown.stories.tsx +22 -46
- package/components/_stories/_mocks/nested-dropdown.json +30 -0
- package/components/_stories/styles.scss +0 -1
- package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
- package/data/colorPalettes.js +107 -10
- package/dist/cove-main.css +6114 -0
- package/dist/cove-main.css.map +1 -0
- package/helpers/addValuesToFilters.ts +8 -3
- package/helpers/cove/number.js +46 -25
- package/helpers/coveUpdateWorker.ts +6 -7
- package/helpers/pivotData.ts +52 -11
- package/helpers/tests/gatherQueryParams.test.ts +13 -1
- package/helpers/tests/pivotData.test.ts +50 -0
- package/helpers/ver/4.24.10.ts +47 -0
- package/helpers/ver/4.24.9.ts +0 -3
- package/helpers/ver/tests/4.24.10.test.ts +45 -0
- package/helpers/viewports.ts +9 -0
- package/package.json +7 -3
- package/styles/_button-section.scss +4 -0
- package/styles/_global-variables.scss +19 -1
- package/styles/_global.scss +1 -8
- package/styles/_reset.scss +2 -15
- package/styles/base.scss +0 -1
- package/styles/cove-main.scss +6 -0
- package/styles/filters.scss +6 -4
- package/styles/v2/components/ui/tooltip.scss +42 -40
- package/styles/v2/layout/_component.scss +0 -6
- package/styles/v2/layout/index.scss +0 -1
- package/types/Axis.ts +2 -0
- package/types/General.ts +1 -0
- package/types/Table.ts +2 -1
- package/types/Visualization.ts +13 -1
- package/types/VizFilter.ts +2 -1
- package/components/DataTable/components/Icons.tsx +0 -10
- package/components/_stories/EditorPanel.stories.tsx +0 -54
- package/components/_stories/Layout.Debug.stories.tsx +0 -91
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96.31 82.55"><g id="a"><path d="M88.56,66.3H15.36V9.68c0-1.1-.9-2-2-2H7.89c-1.1,0-2,.9-2,2V73.77c0,1.1,.9,2,2,2H88.56c1.1,0,2-.9,2-2v-5.48c0-1.1-.9-2-2-2Z"/><path d="M18.36,54.67v6.94c0,1.1,.9,2,2,2h5.48c1.1,0,2-.9,2-2l-.13-6.94c0-1.1-.9-2-2-2h-5.34c-1.1,0-2,.9-2,2Z"/><path d="M30.43,45.3v16.31c0,1.1,.9,2,2,2h5.48c1.1,0,2-.9,2-2l-.13-16.31c0-1.1-.9-2-2-2h-5.34c-1.1,0-2,.9-2,2Z"/><path d="M42.51,32.3v29.31c0,1.1,.9,2,2,2h5.48c1.1,0,2-.9,2-2l-.13-29.31c0-1.1-.9-2-2-2h-5.34c-1.1,0-2,.9-2,2Z"/><path d="M54.58,32.3v29.31c0,1.1,.9,2,2,2h5.48c1.1,0,2-.9,2-2l-.13-29.31c0-1.1-.9-2-2-2h-5.34c-1.1,0-2,.9-2,2Z"/><path d="M66.66,42.22v19.33c0,1.1,.9,2,2,2h5.48c1.1,0,2-.9,2-2l-.13-19.33c0-1.1-.9-2-2-2h-5.34c-1.1,0-2,.9-2,2Z"/><path d="M78.73,58.89v2.72c0,1.1,.9,2,2,2h5.48c1.1,0,2-.9,2-2l-.13-2.72c0-1.1-.9-2-2-2h-5.34c-1.1,0-2,.9-2,2Z"/><g><path d="M20.25,38.69c-.65,0-1.3-.32-1.68-.91-.6-.93-.34-2.16,.59-2.77,.01,0,1.17-.76,2.86-1.92,.91-.62,2.16-.39,2.78,.52,.62,.91,.39,2.16-.52,2.78-1.75,1.2-2.95,1.98-2.95,1.98-.34,.22-.71,.32-1.09,.32Z"/><path d="M28.5,32.92c-.61,0-1.21-.28-1.6-.8-.66-.88-.48-2.14,.4-2.8,1.91-1.43,3.51-2.72,4.76-3.83l.21-.19c.82-.74,2.09-.67,2.82,.16,.74,.82,.66,2.09-.16,2.82l-.22,.2c-1.34,1.19-3.03,2.55-5.02,4.04-.36,.27-.78,.4-1.2,.4Zm50.28-1.66c-.34,0-.68-.09-1-.27-1.64-.95-3.38-2.08-5.66-3.67-.91-.63-1.13-1.88-.49-2.79,.63-.9,1.88-1.13,2.79-.49,2.17,1.52,3.83,2.6,5.37,3.48,.96,.55,1.28,1.78,.73,2.73-.37,.64-1.04,1-1.73,1Zm-40.24-6.8c-.58,0-1.15-.25-1.55-.73-.7-.85-.58-2.11,.28-2.81,1.98-1.63,3.82-2.93,5.61-3.98,.95-.56,2.18-.24,2.74,.72s.24,2.18-.72,2.74c-1.61,.94-3.27,2.12-5.09,3.62-.37,.31-.82,.45-1.27,.45Zm29.37-.59c-.4,0-.8-.12-1.15-.37-1.61-1.13-3.5-2.44-5.34-3.52-.95-.56-1.27-1.78-.72-2.74,.56-.95,1.78-1.27,2.74-.72,1.98,1.16,3.95,2.52,5.63,3.7,.9,.64,1.12,1.88,.48,2.79-.39,.55-1.01,.85-1.64,.85Zm-18-5.77c-.89,0-1.7-.6-1.93-1.49-.28-1.07,.36-2.16,1.43-2.44,1.72-.45,3.46-.68,5.17-.68,.71,0,1.46,.08,2.24,.24,1.08,.23,1.77,1.29,1.55,2.37s-1.29,1.77-2.37,1.55c-.5-.11-.98-.16-1.42-.16-1.37,0-2.77,.18-4.16,.55-.17,.04-.34,.07-.51,.07Z"/><path d="M88.21,34.46c-1.26,0-2.6-.2-4-.6-1.06-.3-1.68-1.41-1.37-2.47,.3-1.06,1.41-1.68,2.47-1.37,1.04,.3,2.02,.45,2.9,.45,1.1,0,2,.9,2,2s-.9,2-2,2Z"/></g></g><g id="b"/></svg>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
4
|
+
viewBox="0 0 96.3 82.6" style="enable-background:new 0 0 96.3 82.6;" xml:space="preserve">
|
|
5
|
+
<path d="M88.6,66.5H15.4V7.6c0-1.1-0.9-2-2-2H7.9c-1.1,0-2,0.9-2,2V74c0,1.1,0.9,2,2,2h80.7c1.1,0,2-0.9,2-2v-5.5
|
|
6
|
+
C90.6,67.4,89.7,66.5,88.6,66.5L88.6,66.5z"/>
|
|
7
|
+
<path d="M31.7,51.1V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-11.9c0-1.1-0.9-2-2-2h-0.9C32.6,49.1,31.7,50,31.7,51.1L31.7,51.1z
|
|
8
|
+
"/>
|
|
9
|
+
<path d="M38.7,41.7V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-21.3c0-1.1-0.9-2-2-2h-0.9C39.6,39.7,38.7,40.6,38.7,41.7
|
|
10
|
+
L38.7,41.7z"/>
|
|
11
|
+
<path d="M45.7,28.7V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-34.3c0-1.1-0.9-2-2-2h-0.9C46.6,26.7,45.7,27.6,45.7,28.7
|
|
12
|
+
L45.7,28.7z"/>
|
|
13
|
+
<path d="M59.7,28.7V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-34.3c0-1.1-0.9-2-2-2h-0.9C60.6,26.7,59.7,27.6,59.7,28.7
|
|
14
|
+
L59.7,28.7z"/>
|
|
15
|
+
<path d="M52.7,23.6V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-39.4c0-1.1-0.9-2-2-2h-0.9C53.6,21.6,52.7,22.5,52.7,23.6
|
|
16
|
+
L52.7,23.6z"/>
|
|
17
|
+
<path d="M66.8,38.6v24.3c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-24.3c0-1.1-0.9-2-2-2h-0.9C67.7,36.6,66.8,37.5,66.8,38.6
|
|
18
|
+
L66.8,38.6z"/>
|
|
19
|
+
<path d="M73.8,55.3V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-7.7c0-1.1-0.9-2-2-2h-0.9C74.7,53.3,73.8,54.2,73.8,55.3L73.8,55.3
|
|
20
|
+
z"/>
|
|
21
|
+
<path d="M24.7,55.3V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2l-0.1-7.7c0-1.1-0.9-2-2-2h-0.9C25.6,53.3,24.7,54.2,24.7,55.3L24.7,55.3
|
|
22
|
+
z"/>
|
|
23
|
+
<path d="M17.6,61.5V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2v-1.6c0-1.1-1-1.9-2.1-1.9h-0.9C18.5,59.5,17.6,60.4,17.6,61.5L17.6,61.5
|
|
24
|
+
z"/>
|
|
25
|
+
<path d="M80.8,61.5V63c0,1.1,0.9,2,2,2h1.1c1.1,0,2-0.9,2-2v-1.6c0-1.1-1-1.9-2.1-1.9h-0.9C81.7,59.5,80.8,60.4,80.8,61.5L80.8,61.5
|
|
26
|
+
z"/>
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text } from '@visx/text'
|
|
3
|
+
|
|
4
|
+
// Extension for visx Text component that creates a second text element and a def with a blur effect to create a feathered stroke effect using a filter with Gaussian blur. No actual stroke is used
|
|
5
|
+
|
|
6
|
+
export interface BlurStrokeTextProps extends React.ComponentProps<typeof Text> {
|
|
7
|
+
blurRadius?: number
|
|
8
|
+
stroke?: string
|
|
9
|
+
strokeWidth?: number
|
|
10
|
+
strokeMiterLimit?: number
|
|
11
|
+
disableStroke?: boolean
|
|
12
|
+
disableBlur?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const BlurStrokeText: React.FC<BlurStrokeTextProps> = ({
|
|
16
|
+
blurRadius = 1,
|
|
17
|
+
stroke = 'white',
|
|
18
|
+
strokeWidth = 8,
|
|
19
|
+
strokeMiterLimit = 2,
|
|
20
|
+
disableStroke = false,
|
|
21
|
+
disableBlur = false,
|
|
22
|
+
...props
|
|
23
|
+
}) => {
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<defs>
|
|
27
|
+
<filter id='blur' x='-50%' y='-50%' width='200%' height='200%'>
|
|
28
|
+
<feGaussianBlur in='SourceGraphic' stdDeviation={blurRadius} />
|
|
29
|
+
</filter>
|
|
30
|
+
</defs>
|
|
31
|
+
{!disableStroke && (
|
|
32
|
+
<Text
|
|
33
|
+
{...props}
|
|
34
|
+
filter={disableBlur ? undefined : 'url(#blur)'}
|
|
35
|
+
fill={stroke}
|
|
36
|
+
stroke={stroke}
|
|
37
|
+
strokeWidth={strokeWidth}
|
|
38
|
+
strokeMiterlimit={strokeMiterLimit}
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
<Text {...props} />
|
|
42
|
+
</>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -20,6 +20,8 @@ import removeNullColumns from './helpers/removeNullColumns'
|
|
|
20
20
|
import { TableConfig } from './types/TableConfig'
|
|
21
21
|
import { Column } from '../../types/Column'
|
|
22
22
|
import { pivotData } from '../../helpers/pivotData'
|
|
23
|
+
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
24
|
+
import './data-table.css'
|
|
23
25
|
|
|
24
26
|
export type DataTableProps = {
|
|
25
27
|
applyLegendToRow?: Function
|
|
@@ -71,9 +73,9 @@ const DataTable = (props: DataTableProps) => {
|
|
|
71
73
|
const runtimeData = useMemo(() => {
|
|
72
74
|
const data = removeNullColumns(parentRuntimeData)
|
|
73
75
|
if (config.table.pivot) {
|
|
74
|
-
const { columnName,
|
|
75
|
-
if (columnName &&
|
|
76
|
-
return pivotData(data, columnName,
|
|
76
|
+
const { columnName, valueColumns } = config.table.pivot
|
|
77
|
+
if (columnName && valueColumns) {
|
|
78
|
+
return pivotData(data, columnName, valueColumns)
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
return data
|
|
@@ -130,26 +132,27 @@ const DataTable = (props: DataTableProps) => {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
const rawRows = Object.keys(runtimeData).filter(column => column != 'columns')
|
|
133
|
-
const rows =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
dataA
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
const rows =
|
|
136
|
+
isVertical && sortBy.column
|
|
137
|
+
? rawRows.sort((a, b) => {
|
|
138
|
+
let dataA
|
|
139
|
+
let dataB
|
|
140
|
+
if (config.type === 'map' && config.columns) {
|
|
141
|
+
const sortByColName = config.columns[sortBy.column].name
|
|
142
|
+
dataA = runtimeData[a][sortByColName]
|
|
143
|
+
dataB = runtimeData[b][sortByColName]
|
|
144
|
+
}
|
|
145
|
+
if (['chart', 'dashboard', 'table'].includes(config.type)) {
|
|
146
|
+
dataA = runtimeData[a][sortBy.column]
|
|
147
|
+
dataB = runtimeData[b][sortBy.column]
|
|
148
|
+
}
|
|
149
|
+
if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
|
|
150
|
+
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
|
|
151
|
+
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
|
|
152
|
+
}
|
|
153
|
+
return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
|
|
154
|
+
})
|
|
155
|
+
: rawRows
|
|
153
156
|
|
|
154
157
|
const limitHeight = {
|
|
155
158
|
maxHeight: config.table.limitHeight && `${config.table.height}px`,
|
|
@@ -208,17 +211,21 @@ const DataTable = (props: DataTableProps) => {
|
|
|
208
211
|
}
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
const getMediaControlsClasses =
|
|
214
|
+
const getMediaControlsClasses = belowTable => {
|
|
212
215
|
const classes = ['download-links']
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
if (!belowTable) {
|
|
217
|
+
const isLegendOnBottom = config?.legend?.position === 'bottom' || isLegendWrapViewport(viewport)
|
|
218
|
+
if (config.brush?.active && !isLegendOnBottom) classes.push('brush-active')
|
|
219
|
+
if (config.brush?.active && config.legend.hide) classes.push('brush-active')
|
|
220
|
+
} else {
|
|
221
|
+
classes.push('below-table')
|
|
222
|
+
}
|
|
216
223
|
return classes
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<MediaControls.Section classes={getMediaControlsClasses()}>
|
|
226
|
+
const TableMediaControls = ({ belowTable }) => {
|
|
227
|
+
return (
|
|
228
|
+
<MediaControls.Section classes={getMediaControlsClasses(belowTable)}>
|
|
222
229
|
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
223
230
|
{(config.table.download || config.general?.showDownloadButton) && (
|
|
224
231
|
<DownloadButton
|
|
@@ -228,9 +235,17 @@ const DataTable = (props: DataTableProps) => {
|
|
|
228
235
|
/>
|
|
229
236
|
)}
|
|
230
237
|
</MediaControls.Section>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<ErrorBoundary component='DataTable'>
|
|
243
|
+
{!config.table.showDownloadLinkBelow && <TableMediaControls />}
|
|
231
244
|
<section
|
|
232
245
|
id={tabbingId.replace('#', '')}
|
|
233
|
-
className={`data-table-container ${viewport}
|
|
246
|
+
className={`data-table-container ${viewport} ${
|
|
247
|
+
!config.table.showDownloadLinkBelow ? 'download-link-above' : ''
|
|
248
|
+
}`}
|
|
234
249
|
aria-label={accessibilityLabel}
|
|
235
250
|
>
|
|
236
251
|
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
@@ -272,7 +287,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
272
287
|
)
|
|
273
288
|
}
|
|
274
289
|
tableOptions={{
|
|
275
|
-
className:
|
|
290
|
+
className: `table table-striped ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${
|
|
276
291
|
isVertical ? '' : ' horizontal'
|
|
277
292
|
}`,
|
|
278
293
|
'aria-live': 'assertive',
|
|
@@ -300,12 +315,13 @@ const DataTable = (props: DataTableProps) => {
|
|
|
300
315
|
<th>End Date</th>
|
|
301
316
|
</tr>
|
|
302
317
|
}
|
|
303
|
-
tableOptions={{ className: 'region-table data-table' }}
|
|
318
|
+
tableOptions={{ className: 'table table-striped region-table data-table' }}
|
|
304
319
|
fontSize={config.fontSize}
|
|
305
320
|
/>
|
|
306
321
|
)}
|
|
307
322
|
</div>
|
|
308
323
|
</section>
|
|
324
|
+
{config.table.showDownloadLinkBelow && <TableMediaControls belowTable={true} />}
|
|
309
325
|
<div id={skipId} className='cdcdataviz-sr-only'>
|
|
310
326
|
Skipped data table.
|
|
311
327
|
</div>
|
|
@@ -317,7 +333,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
317
333
|
<ErrorBoundary component='DataTable'>
|
|
318
334
|
<section
|
|
319
335
|
id={tabbingId.replace('#', '')}
|
|
320
|
-
className={`data-table-container ${viewport}
|
|
336
|
+
className={`data-table-container ${viewport}`}
|
|
321
337
|
aria-label={accessibilityLabel}
|
|
322
338
|
>
|
|
323
339
|
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
@@ -332,7 +348,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
332
348
|
stickyHeader
|
|
333
349
|
headContent={<BoxplotHeader categories={config.boxplot.categories} />}
|
|
334
350
|
tableOptions={{
|
|
335
|
-
className:
|
|
351
|
+
className: `table table-striped ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}`,
|
|
336
352
|
'aria-live': 'assertive',
|
|
337
353
|
'aria-rowcount': 11,
|
|
338
354
|
hidden: !expanded
|
|
@@ -15,25 +15,56 @@ type StandAloneProps = {
|
|
|
15
15
|
updateConfig?: (Visualization) => void
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
19
|
-
|
|
18
|
+
const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
19
|
+
visualizationKey,
|
|
20
|
+
config,
|
|
21
|
+
updateConfig,
|
|
22
|
+
viewport,
|
|
23
|
+
isEditor
|
|
24
|
+
}) => {
|
|
25
|
+
const [filteredData, setFilteredData] = useState<Record<string, any>[]>(
|
|
26
|
+
filterVizData(config.filters, config.formattedData || config.data)
|
|
27
|
+
)
|
|
20
28
|
|
|
21
29
|
useEffect(() => {
|
|
22
30
|
// when using editor changes to filter should update the data
|
|
23
|
-
setFilteredData(
|
|
31
|
+
setFilteredData(
|
|
32
|
+
filterVizData(config.filters, config?.formattedData?.length > 0 ? config.formattedData : config.data)
|
|
33
|
+
)
|
|
24
34
|
}, [config.filters])
|
|
25
35
|
|
|
26
36
|
if (isEditor)
|
|
27
37
|
return (
|
|
28
|
-
<EditorWrapper
|
|
38
|
+
<EditorWrapper
|
|
39
|
+
component={DataTableStandAlone}
|
|
40
|
+
visualizationKey={visualizationKey}
|
|
41
|
+
visualizationConfig={config}
|
|
42
|
+
updateConfig={updateConfig}
|
|
43
|
+
type={'Table'}
|
|
44
|
+
viewport={viewport}
|
|
45
|
+
>
|
|
29
46
|
<DataTableEditorPanel key={visualizationKey} config={config} updateConfig={updateConfig} />
|
|
30
47
|
</EditorWrapper>
|
|
31
48
|
)
|
|
32
49
|
|
|
33
50
|
return (
|
|
34
51
|
<>
|
|
35
|
-
<Filters
|
|
36
|
-
|
|
52
|
+
<Filters
|
|
53
|
+
config={config}
|
|
54
|
+
setConfig={updateConfig}
|
|
55
|
+
setFilteredData={setFilteredData}
|
|
56
|
+
filteredData={filteredData}
|
|
57
|
+
excludedData={config.formattedData}
|
|
58
|
+
/>
|
|
59
|
+
<DataTable
|
|
60
|
+
expandDataTable={config.table.expanded}
|
|
61
|
+
config={config}
|
|
62
|
+
rawData={config.data}
|
|
63
|
+
runtimeData={filteredData}
|
|
64
|
+
tabbingId={visualizationKey}
|
|
65
|
+
tableTitle={config.table.label}
|
|
66
|
+
viewport={viewport || 'lg'}
|
|
67
|
+
/>
|
|
37
68
|
</>
|
|
38
69
|
)
|
|
39
70
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getChartCellValue } from '../helpers/getChartCellValue'
|
|
2
2
|
import { getSeriesName } from '../helpers/getSeriesName'
|
|
3
3
|
import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
|
|
4
|
-
import { DownIcon, UpIcon } from './Icons'
|
|
5
4
|
import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
|
|
5
|
+
import { SortIcon } from './SortIcon'
|
|
6
|
+
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
6
7
|
|
|
7
8
|
type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; hasRowType? }
|
|
8
9
|
|
|
@@ -19,17 +20,6 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const handleHeaderClasses = (sortBy, text) => {
|
|
23
|
-
let classes = ['sort']
|
|
24
|
-
if (sortBy.column === text && sortBy.asc) {
|
|
25
|
-
classes.push('sort-asc')
|
|
26
|
-
}
|
|
27
|
-
if (sortBy.column === text && sortBy.desc) {
|
|
28
|
-
classes.push('sort-desc')
|
|
29
|
-
}
|
|
30
|
-
return classes.join(' ')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
23
|
const ScreenReaderSortByText = ({ text, config, sortBy }) => {
|
|
34
24
|
const notApplicableText = 'Not Applicable'
|
|
35
25
|
let columnHeaderText = `${text} `
|
|
@@ -47,13 +37,19 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
|
|
|
47
37
|
|
|
48
38
|
if (columnHeaderText === notApplicableText) return
|
|
49
39
|
|
|
50
|
-
return
|
|
40
|
+
return (
|
|
41
|
+
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
|
|
42
|
+
sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
|
|
43
|
+
} order`}</span>
|
|
44
|
+
)
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
const ColumnHeadingText = ({ column, text, config }) => {
|
|
48
|
+
if (text === 'pivotColumn') return ''
|
|
54
49
|
let notApplicableText = 'Not Applicable'
|
|
55
50
|
if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
|
|
56
|
-
if (text === '__series__' && !config.table.indexLabel)
|
|
51
|
+
if (text === '__series__' && !config.table.indexLabel)
|
|
52
|
+
return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
|
|
57
53
|
return text
|
|
58
54
|
}
|
|
59
55
|
|
|
@@ -71,7 +67,8 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
|
|
|
71
67
|
<tr>
|
|
72
68
|
{dataSeriesColumns.map((column, index) => {
|
|
73
69
|
const text = getSeriesName(column, config)
|
|
74
|
-
|
|
70
|
+
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
71
|
+
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
|
|
75
72
|
return (
|
|
76
73
|
<th
|
|
77
74
|
style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
|
|
@@ -81,19 +78,22 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
|
|
|
81
78
|
scope='col'
|
|
82
79
|
onClick={() => {
|
|
83
80
|
if (hasRowType) return
|
|
84
|
-
setSortBy(
|
|
81
|
+
setSortBy(newSortBy)
|
|
85
82
|
}}
|
|
86
83
|
onKeyDown={e => {
|
|
87
84
|
if (hasRowType) return
|
|
88
85
|
if (e.keyCode === 13) {
|
|
89
|
-
setSortBy(
|
|
86
|
+
setSortBy(newSortBy)
|
|
90
87
|
}
|
|
91
88
|
}}
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
{...(sortBy.column === column
|
|
90
|
+
? sortBy.asc
|
|
91
|
+
? { 'aria-sort': 'ascending' }
|
|
92
|
+
: { 'aria-sort': 'descending' }
|
|
93
|
+
: null)}
|
|
94
94
|
>
|
|
95
95
|
<ColumnHeadingText text={text} column={column} config={config} />
|
|
96
|
-
{column === sortBy.column && <
|
|
96
|
+
{column === sortBy.column && !hasRowType && <SortIcon ascending={sortByAsc} />}
|
|
97
97
|
<ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
|
|
98
98
|
</th>
|
|
99
99
|
)
|
|
@@ -107,7 +107,8 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
|
|
|
107
107
|
{['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
|
|
108
108
|
let column = config.xAxis?.dataKey
|
|
109
109
|
let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'
|
|
110
|
-
|
|
110
|
+
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
111
|
+
const sortByAsc = sortBy.colIndex === index ? sortBy.asc : undefined
|
|
111
112
|
return (
|
|
112
113
|
<th
|
|
113
114
|
style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
|
|
@@ -116,18 +117,22 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
|
|
|
116
117
|
role='columnheader'
|
|
117
118
|
scope='col'
|
|
118
119
|
onClick={() => {
|
|
119
|
-
setSortBy(
|
|
120
|
+
setSortBy(newSortBy)
|
|
120
121
|
}}
|
|
121
122
|
onKeyDown={e => {
|
|
122
123
|
if (e.keyCode === 13) {
|
|
123
|
-
setSortBy(
|
|
124
|
+
setSortBy(newSortBy)
|
|
124
125
|
}
|
|
125
126
|
}}
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
{...(sortBy.column === text
|
|
128
|
+
? sortBy.asc
|
|
129
|
+
? { 'aria-sort': 'ascending' }
|
|
130
|
+
: { 'aria-sort': 'descending' }
|
|
131
|
+
: null)}
|
|
128
132
|
>
|
|
129
133
|
<ColumnHeadingText text={text} column={column} config={config} />
|
|
130
|
-
{index === sortBy.colIndex && <
|
|
134
|
+
{index === sortBy.colIndex && !hasRowType && <SortIcon ascending={sortByAsc} />}
|
|
135
|
+
|
|
131
136
|
<ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
|
|
132
137
|
</th>
|
|
133
138
|
)
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { DataTableProps } from '../DataTable'
|
|
2
|
-
import { DownIcon, UpIcon } from './Icons'
|
|
3
2
|
import ScreenReaderText from '../../elements/ScreenReaderText'
|
|
3
|
+
import { SortIcon } from './SortIcon'
|
|
4
|
+
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
4
5
|
|
|
5
6
|
type MapHeaderProps = DataTableProps & {
|
|
6
7
|
sortBy: { column; asc }
|
|
7
8
|
setSortBy: Function
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
const ColumnHeadingText = ({
|
|
11
|
+
const ColumnHeadingText = ({ text, config }) => {
|
|
11
12
|
let notApplicableText = 'Not Applicable'
|
|
12
13
|
if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
|
|
13
|
-
if (text === '__series__' && !config.table.indexLabel)
|
|
14
|
+
if (text === '__series__' && !config.table.indexLabel)
|
|
15
|
+
return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
|
|
14
16
|
return text
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -21,7 +23,7 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
|
|
|
21
23
|
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
22
24
|
.map((column, index) => {
|
|
23
25
|
let text
|
|
24
|
-
if (column !== 'geo') {
|
|
26
|
+
if (column && column !== 'geo') {
|
|
25
27
|
text = columns[column].label ? columns[column].label : columns[column].name
|
|
26
28
|
} else {
|
|
27
29
|
text = config.type === 'map' ? indexTitle : config.xAxis?.dataKey
|
|
@@ -29,7 +31,8 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
|
|
|
29
31
|
if (config.type === 'map' && (text === undefined || text === '')) {
|
|
30
32
|
text = 'Location'
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
35
|
+
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
|
|
33
36
|
return (
|
|
34
37
|
<th
|
|
35
38
|
key={`col-header-${column}__${index}`}
|
|
@@ -38,19 +41,25 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
|
|
|
38
41
|
role='columnheader'
|
|
39
42
|
scope='col'
|
|
40
43
|
onClick={() => {
|
|
41
|
-
setSortBy(
|
|
44
|
+
setSortBy(newSortBy)
|
|
42
45
|
}}
|
|
43
46
|
onKeyDown={e => {
|
|
44
47
|
if (e.keyCode === 13) {
|
|
45
|
-
setSortBy(
|
|
48
|
+
setSortBy(newSortBy)
|
|
46
49
|
}
|
|
47
50
|
}}
|
|
48
51
|
className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
|
|
49
|
-
{...(sortBy.column === column
|
|
52
|
+
{...(sortBy.column === column
|
|
53
|
+
? sortBy.asc
|
|
54
|
+
? { 'aria-sort': 'ascending' }
|
|
55
|
+
: { 'aria-sort': 'descending' }
|
|
56
|
+
: null)}
|
|
50
57
|
>
|
|
51
58
|
<ColumnHeadingText text={text} config={config} column={column} />
|
|
52
|
-
|
|
53
|
-
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
|
|
59
|
+
<SortIcon ascending={sortByAsc} />
|
|
60
|
+
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
|
|
61
|
+
sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
|
|
62
|
+
} order`}</span>
|
|
54
63
|
</th>
|
|
55
64
|
)
|
|
56
65
|
})}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import './sort-icon.css'
|
|
2
|
+
|
|
3
|
+
const UpIcon = ({ active }) => (
|
|
4
|
+
<svg className={'up' + (active ? ' active' : '')} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
|
|
5
|
+
<path d='M0 5l5-5 5 5z' />
|
|
6
|
+
</svg>
|
|
7
|
+
)
|
|
8
|
+
const DownIcon = ({ active }) => (
|
|
9
|
+
<svg className={'down' + (active ? ' active' : '')} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
|
|
10
|
+
<path d='M0 0l5 5 5-5z' />
|
|
11
|
+
</svg>
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
type SortIconProps = {
|
|
15
|
+
ascending?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SortIcon: React.FC<SortIconProps> = ({ ascending }) => {
|
|
19
|
+
return (
|
|
20
|
+
<span role='button' className={'sort-icon'}>
|
|
21
|
+
<UpIcon active={ascending === true} />
|
|
22
|
+
<DownIcon active={ascending === false} />
|
|
23
|
+
</span>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* format the white triangle sort icon in data table headers */
|
|
2
|
+
.sort-icon {
|
|
3
|
+
fill: white;
|
|
4
|
+
position: relative;
|
|
5
|
+
margin: 0 0.5rem !important;
|
|
6
|
+
:is(svg) {
|
|
7
|
+
position: absolute;
|
|
8
|
+
fill: rgba(255, 255, 255, 0.5);
|
|
9
|
+
&.active {
|
|
10
|
+
fill: white;
|
|
11
|
+
}
|
|
12
|
+
width: 1rem;
|
|
13
|
+
height: 1rem;
|
|
14
|
+
}
|
|
15
|
+
.up {
|
|
16
|
+
bottom: 0.5rem;
|
|
17
|
+
}
|
|
18
|
+
.down {
|
|
19
|
+
top: 0.5rem;
|
|
20
|
+
}
|
|
21
|
+
}
|