@cdc/map 4.22.10 → 4.23.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 (58) hide show
  1. package/dist/495.js +3 -0
  2. package/dist/703.js +1 -0
  3. package/dist/856.js +3 -0
  4. package/dist/cdcmap.js +724 -120
  5. package/examples/bubble-us.json +362 -362
  6. package/examples/bubble-world.json +426 -426
  7. package/examples/city-state.json +434 -0
  8. package/examples/example-city-state.json +202 -25
  9. package/package.json +3 -4
  10. package/src/CdcMap.js +249 -423
  11. package/src/components/BubbleList.js +198 -240
  12. package/src/components/CityList.js +50 -97
  13. package/src/components/CountyMap.js +511 -600
  14. package/src/components/DataTable.js +223 -230
  15. package/src/components/EditorPanel.js +2395 -2551
  16. package/src/components/Filters.js +114 -0
  17. package/src/components/Geo.js +4 -14
  18. package/src/components/Modal.js +13 -23
  19. package/src/components/NavigationMenu.js +43 -39
  20. package/src/components/Sidebar.js +77 -93
  21. package/src/components/SingleStateMap.js +95 -151
  22. package/src/components/UsaMap.js +165 -214
  23. package/src/components/UsaRegionMap.js +122 -160
  24. package/src/components/WorldMap.js +96 -179
  25. package/src/components/ZoomableGroup.js +6 -26
  26. package/src/context.js +5 -0
  27. package/src/data/initial-state.js +20 -15
  28. package/src/data/supported-geos.js +3201 -3175
  29. package/src/hooks/useActiveElement.js +13 -13
  30. package/src/hooks/useColorPalette.ts +66 -74
  31. package/src/hooks/useZoomPan.js +22 -23
  32. package/src/index.html +32 -29
  33. package/src/scss/datatable.scss +1 -2
  34. package/src/scss/filters.scss +42 -0
  35. package/src/scss/main.scss +4 -3
  36. package/src/scss/sidebar.scss +22 -0
  37. package/examples/private/atsdr.json +0 -439
  38. package/examples/private/atsdr_new.json +0 -436
  39. package/examples/private/bubble.json +0 -285
  40. package/examples/private/city-state.json +0 -428
  41. package/examples/private/cty-issue.json +0 -42768
  42. package/examples/private/default-usa.json +0 -460
  43. package/examples/private/default-world-data.json +0 -1444
  44. package/examples/private/default.json +0 -968
  45. package/examples/private/legend-issue.json +0 -1
  46. package/examples/private/map-rounding-error.json +0 -42759
  47. package/examples/private/map.csv +0 -60
  48. package/examples/private/mdx.json +0 -210
  49. package/examples/private/monkeypox.json +0 -376
  50. package/examples/private/regions.json +0 -52
  51. package/examples/private/valid-data-map.csv +0 -59
  52. package/examples/private/wcmsrd-13881-data.json +0 -2858
  53. package/examples/private/wcmsrd-13881.json +0 -5823
  54. package/examples/private/wcmsrd-14492-data.json +0 -292
  55. package/examples/private/wcmsrd-14492.json +0 -114
  56. package/examples/private/wcmsrd-test.json +0 -268
  57. package/examples/private/world.json +0 -1580
  58. package/examples/private/worldmap.json +0 -1490
@@ -1,25 +1,23 @@
1
- import React, {
2
- useEffect, useState, useMemo, memo, useCallback
3
- } from 'react';
4
- import {
5
- useTable, useSortBy, useResizeColumns, useBlockLayout
6
- } from 'react-table';
7
- import Papa from 'papaparse';
8
- import ExternalIcon from '../images/external-link.svg'; // TODO: Move to Icon component
1
+ import React, { useEffect, useState, useMemo, memo, useCallback } from 'react'
2
+ import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
3
+ import Papa from 'papaparse'
4
+ import ExternalIcon from '../images/external-link.svg' // TODO: Move to Icon component
9
5
 
10
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
11
- import LegendCircle from '@cdc/core/components/LegendCircle';
6
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
+ import LegendCircle from '@cdc/core/components/LegendCircle'
8
+ import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
12
9
 
10
+ import Loading from '@cdc/core/components/Loading'
13
11
 
14
- import Loading from '@cdc/core/components/Loading';
15
-
16
- const DataTable = (props) => {
12
+ const DataTable = props => {
17
13
  const {
18
14
  state,
19
15
  tableTitle,
20
16
  indexTitle,
21
17
  mapTitle,
22
18
  rawData,
19
+ showDownloadImgButton,
20
+ showDownloadPdfButton,
23
21
  showDownloadButton,
24
22
  runtimeData,
25
23
  runtimeLegend,
@@ -33,335 +31,330 @@ const DataTable = (props) => {
33
31
  viewport,
34
32
  formatLegendLocation,
35
33
  tabbingId,
36
- setFilteredCountryCode
37
- } = props;
34
+ setFilteredCountryCode,
35
+ innerContainerRef,
36
+ imageRef
37
+ } = props
38
38
 
39
- const [expanded, setExpanded] = useState(expandDataTable);
39
+ const [expanded, setExpanded] = useState(expandDataTable)
40
40
 
41
- const [accessibilityLabel, setAccessibilityLabel] = useState('');
41
+ const [accessibilityLabel, setAccessibilityLabel] = useState('')
42
42
 
43
43
  const [ready, setReady] = useState(false)
44
44
 
45
- const fileName = `${mapTitle || 'data-table'}.csv`;
46
-
45
+ const fileName = `${mapTitle || 'data-table'}.csv`
47
46
 
48
47
  // Catch all sorting method used on load by default but also on user click
49
48
  // Having a custom method means we can add in any business logic we want going forward
50
- const customSort = useCallback((a, b) => {
51
- const digitRegex = /\d+/;
49
+ const customSort = useCallback(
50
+ (a, b) => {
51
+ const digitRegex = /\d+/
52
52
 
53
- const hasNumber = (value) => digitRegex.test(value);
53
+ const hasNumber = value => digitRegex.test(value)
54
54
 
55
- // force null and undefined to the bottom
56
- a = a === null || a === undefined ? '' : a;
57
- b = b === null || b === undefined ? '' : b;
55
+ // force null and undefined to the bottom
56
+ a = a === null || a === undefined ? '' : a
57
+ b = b === null || b === undefined ? '' : b
58
58
 
59
- // convert any strings that are actually numbers to proper data type
60
- const aNum = Number(a);
59
+ // convert any strings that are actually numbers to proper data type
60
+ const aNum = Number(a)
61
61
 
62
- if (!Number.isNaN(aNum)) {
63
- a = aNum;
64
- }
62
+ if (!Number.isNaN(aNum)) {
63
+ a = aNum
64
+ }
65
65
 
66
- const bNum = Number(b);
66
+ const bNum = Number(b)
67
67
 
68
- if (!Number.isNaN(bNum)) {
69
- b = bNum;
70
- }
68
+ if (!Number.isNaN(bNum)) {
69
+ b = bNum
70
+ }
71
71
 
72
- // remove iso code prefixes
73
- if (typeof a === 'string') {
74
- a = a.replace('us-', '');
75
- a = displayGeoName(a);
76
- }
72
+ // remove iso code prefixes
73
+ if (typeof a === 'string') {
74
+ a = a.replace('us-', '')
75
+ a = displayGeoName(a)
76
+ }
77
77
 
78
- if (typeof b === 'string') {
79
- b = b.replace('us-', '');
80
- b = displayGeoName(b);
81
- }
78
+ if (typeof b === 'string') {
79
+ b = b.replace('us-', '')
80
+ b = displayGeoName(b)
81
+ }
82
82
 
83
- // force any string values to lowercase
84
- a = typeof a === 'string' ? a.toLowerCase() : a;
85
- b = typeof b === 'string' ? b.toLowerCase() : b;
83
+ // force any string values to lowercase
84
+ a = typeof a === 'string' ? a.toLowerCase() : a
85
+ b = typeof b === 'string' ? b.toLowerCase() : b
86
86
 
87
- // If the string contains a number, remove the text from the value and only sort by the number. Only uses the first number it finds.
88
- if (typeof a === 'string' && hasNumber(a) === true) {
89
- a = a.match(digitRegex)[0];
87
+ // If the string contains a number, remove the text from the value and only sort by the number. Only uses the first number it finds.
88
+ if (typeof a === 'string' && hasNumber(a) === true) {
89
+ a = a.match(digitRegex)[0]
90
90
 
91
- a = Number(a);
92
- }
91
+ a = Number(a)
92
+ }
93
93
 
94
- if (typeof b === 'string' && hasNumber(b) === true) {
95
- b = b.match(digitRegex)[0];
94
+ if (typeof b === 'string' && hasNumber(b) === true) {
95
+ b = b.match(digitRegex)[0]
96
96
 
97
- b = Number(b);
98
- }
97
+ b = Number(b)
98
+ }
99
99
 
100
- // When comparing a number to a string, always send string to bottom
101
- if (typeof a === 'number' && typeof b === 'string') {
102
- return 1;
103
- }
100
+ // When comparing a number to a string, always send string to bottom
101
+ if (typeof a === 'number' && typeof b === 'string') {
102
+ return 1
103
+ }
104
104
 
105
- if (typeof b === 'number' && typeof a === 'string') {
106
- return -1;
107
- }
105
+ if (typeof b === 'number' && typeof a === 'string') {
106
+ return -1
107
+ }
108
108
 
109
- // Return either 1 or -1 to indicate a sort priority
110
- if (a > b) {
111
- return 1;
112
- }
113
- if (a < b) {
114
- return -1;
115
- }
116
- // returning 0, undefined or any falsey value will use subsequent sorts or
117
- // the index as a tiebreaker
118
- return 0;
119
- }, [displayGeoName]);
109
+ // Return either 1 or -1 to indicate a sort priority
110
+ if (a > b) {
111
+ return 1
112
+ }
113
+ if (a < b) {
114
+ return -1
115
+ }
116
+ // returning 0, undefined or any falsey value will use subsequent sorts or
117
+ // the index as a tiebreaker
118
+ return 0
119
+ },
120
+ [displayGeoName]
121
+ )
120
122
 
121
123
  // Optionally wrap cell with anchor if config defines a navigation url
122
- const getCellAnchor = useCallback((markup, row) => {
123
- if (columns.navigate && row[columns.navigate.name]) {
124
- markup = (
125
- <span
126
- onClick={() => navigationHandler(row[columns.navigate.name])}
127
- className="table-link"
128
- title="Click for more information (Opens in a new window)"
129
- role="link"
130
- tabIndex="0"
131
- onKeyDown={(e) => {
132
- if (e.keyCode === 13) {
133
- navigationHandler(row[columns.navigate.name]);
134
- }
135
- }}
136
- >
137
- {markup}
138
- <ExternalIcon className="inline-icon" />
139
- </span>
140
- );
141
- }
124
+ const getCellAnchor = useCallback(
125
+ (markup, row) => {
126
+ if (columns.navigate && row[columns.navigate.name]) {
127
+ markup = (
128
+ <span
129
+ onClick={() => navigationHandler(row[columns.navigate.name])}
130
+ className='table-link'
131
+ title='Click for more information (Opens in a new window)'
132
+ role='link'
133
+ tabIndex='0'
134
+ onKeyDown={e => {
135
+ if (e.keyCode === 13) {
136
+ navigationHandler(row[columns.navigate.name])
137
+ }
138
+ }}
139
+ >
140
+ {markup}
141
+ <ExternalIcon className='inline-icon' />
142
+ </span>
143
+ )
144
+ }
142
145
 
143
- return markup;
144
- }, [columns.navigate, navigationHandler]);
146
+ return markup
147
+ },
148
+ [columns.navigate, navigationHandler]
149
+ )
145
150
 
146
151
  const DownloadButton = memo(() => {
147
- const csvData = Papa.unparse(rawData);
152
+ const csvData = Papa.unparse(rawData)
148
153
 
149
- const blob = new Blob([csvData], {type: "text/csv;charset=utf-8;"});
154
+ const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
150
155
 
151
156
  const saveBlob = () => {
152
157
  //@ts-ignore
153
158
  if (typeof window.navigator.msSaveBlob === 'function') {
154
159
  //@ts-ignore
155
- navigator.msSaveBlob(blob, fileName);
160
+ navigator.msSaveBlob(blob, fileName)
156
161
  }
157
162
  }
158
163
 
159
164
  return (
160
- <a
161
- download={fileName}
162
- type="button"
163
- onClick={saveBlob}
164
- href={URL.createObjectURL(blob)}
165
- aria-label="Download this data in a CSV file format."
166
- className={`${headerColor} btn btn-download no-border`}
167
- id={`${skipId}`}
168
- data-html2canvas-ignore
169
- role="button"
170
- >
171
- Download Data (CSV)
172
- </a>
165
+ <a download={fileName} type='button' onClick={saveBlob} href={URL.createObjectURL(blob)} aria-label='Download this data in a CSV file format.' className={`${headerColor} no-border`} id={`${skipId}`} data-html2canvas-ignore role='button'>
166
+ Download Data (CSV)
167
+ </a>
173
168
  )
174
- }, [rawData]);
169
+ }, [rawData])
175
170
 
176
171
  // Creates columns structure for the table
177
172
  const tableColumns = useMemo(() => {
178
- const newTableColumns = [];
173
+ const newTableColumns = []
179
174
 
180
- Object.keys(columns).forEach((column) => {
175
+ Object.keys(columns).forEach(column => {
181
176
  if (columns[column].dataTable === true && columns[column].name) {
182
177
  const newCol = {
183
178
  Header: columns[column].label ? columns[column].label : columns[column].name,
184
179
  id: column,
185
- accessor: (row) => {
180
+ accessor: row => {
186
181
  if (runtimeData) {
187
- if(state.legend.specialClasses && state.legend.specialClasses.length && typeof state.legend.specialClasses[0] === 'object'){
188
- for(let i = 0; i < state.legend.specialClasses.length; i++){
189
- if(String(runtimeData[row][state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value){
190
- return state.legend.specialClasses[i].label;
182
+ if (state.legend.specialClasses && state.legend.specialClasses.length && typeof state.legend.specialClasses[0] === 'object') {
183
+ for (let i = 0; i < state.legend.specialClasses.length; i++) {
184
+ if (String(runtimeData[row][state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value) {
185
+ return state.legend.specialClasses[i].label
191
186
  }
192
187
  }
193
188
  }
194
- return runtimeData[row][columns[column].name] ?? null;
189
+ return runtimeData[row][columns[column].name] ?? null
195
190
  }
196
191
 
197
- return null;
192
+ return null
198
193
  },
199
194
  sortType: (a, b) => customSort(a.values[column], b.values[column])
200
- };
195
+ }
201
196
 
202
197
  if (column === 'geo') {
203
- newCol.Header = indexTitle || 'Location';
198
+ newCol.Header = indexTitle || 'Location'
204
199
  newCol.Cell = ({ row, value }) => {
205
- const rowObj = runtimeData[row.original];
200
+ const rowObj = runtimeData[row.original]
206
201
 
207
- const legendColor = applyLegendToRow(rowObj);
202
+ const legendColor = applyLegendToRow(rowObj)
208
203
 
209
- if(state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
210
- var labelValue = displayGeoName(row.original);
204
+ if (state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
205
+ var labelValue = displayGeoName(row.original)
211
206
  } else {
212
207
  var labelValue = formatLegendLocation(row.original)
213
208
  }
214
209
 
215
- labelValue = getCellAnchor(labelValue, rowObj);
210
+ labelValue = getCellAnchor(labelValue, rowObj)
216
211
 
217
212
  const cellMarkup = (
218
213
  <>
219
214
  <LegendCircle fill={legendColor[0]} />
220
215
  {labelValue}
221
216
  </>
222
- );
217
+ )
223
218
 
224
- return cellMarkup;
225
- };
219
+ return cellMarkup
220
+ }
226
221
  } else {
227
222
  newCol.Cell = ({ value }) => {
228
- const cellMarkup = displayDataAsText(value, column);
223
+ const cellMarkup = displayDataAsText(value, column)
229
224
 
230
- return (cellMarkup);
231
- };
225
+ return cellMarkup
226
+ }
232
227
  }
233
228
 
234
- newTableColumns.push(newCol);
229
+ newTableColumns.push(newCol)
235
230
  }
236
- });
231
+ })
237
232
 
238
- return newTableColumns;
239
- }, [indexTitle, columns, runtimeData,getCellAnchor,displayDataAsText,applyLegendToRow,customSort,displayGeoName,state.legend.specialClasses]);
233
+ return newTableColumns
234
+ }, [indexTitle, columns, runtimeData, getCellAnchor, displayDataAsText, applyLegendToRow, customSort, displayGeoName, state.legend.specialClasses])
240
235
 
241
236
  const tableData = useMemo(
242
- () => Object.keys(runtimeData).filter((key) => applyLegendToRow(runtimeData[key])).sort((a, b) => customSort(a, b)),
243
- [ runtimeData, applyLegendToRow, customSort]
244
- );
237
+ () =>
238
+ Object.keys(runtimeData)
239
+ .filter(key => applyLegendToRow(runtimeData[key]))
240
+ .sort((a, b) => customSort(a, b)),
241
+ [runtimeData, applyLegendToRow, customSort]
242
+ )
245
243
 
246
244
  // Change accessibility label depending on expanded status
247
245
  useEffect(() => {
248
- const expandedLabel = 'Accessible data table.';
249
- const collapsedLabel = 'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.';
246
+ const expandedLabel = 'Accessible data table.'
247
+ const collapsedLabel = 'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.'
250
248
 
251
249
  if (expanded === true && accessibilityLabel !== expandedLabel) {
252
- setAccessibilityLabel(expandedLabel);
250
+ setAccessibilityLabel(expandedLabel)
253
251
  }
254
252
 
255
253
  if (expanded === false && accessibilityLabel !== collapsedLabel) {
256
- setAccessibilityLabel(collapsedLabel);
254
+ setAccessibilityLabel(collapsedLabel)
257
255
  }
258
256
  // eslint-disable-next-line react-hooks/exhaustive-deps
259
- }, [expanded, applyLegendToRow, customSort]);
257
+ }, [expanded, applyLegendToRow, customSort])
260
258
 
261
259
  const defaultColumn = useMemo(
262
260
  () => ({
263
261
  minWidth: 150,
264
262
  width: 200,
265
- maxWidth: 400,
263
+ maxWidth: 400
266
264
  }),
267
265
  []
268
- );
266
+ )
269
267
 
270
268
  const mapLookup = {
271
269
  'us-county': 'United States County Map',
272
270
  'single-state': 'State Map',
273
- 'us': 'United States Map',
274
- 'world': 'World Map'
271
+ us: 'United States Map',
272
+ world: 'World Map'
275
273
  }
276
274
 
277
- const {
278
- getTableProps,
279
- getTableBodyProps,
280
- headerGroups,
281
- rows,
282
- prepareRow,
283
- } = useTable({ columns: tableColumns, data: tableData, defaultColumn }, useSortBy, useBlockLayout, useResizeColumns);
284
-
285
- const rand = Math.random().toString(16).substr(2, 8);
275
+ const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns: tableColumns, data: tableData, defaultColumn }, useSortBy, useBlockLayout, useResizeColumns)
276
+
277
+ const rand = Math.random().toString(16).substr(2, 8)
286
278
  const skipId = `btn__${rand}`
287
279
 
288
- if(!state.data) return <Loading />
280
+ if (!state.data) return <Loading />
289
281
  return (
290
- <ErrorBoundary component="DataTable">
282
+ <ErrorBoundary component='DataTable'>
283
+ <CoveMediaControls.Section classes={['download-links']}>
284
+ <CoveMediaControls.Link config={state} />
285
+ {state.general.showDownloadButton && <DownloadButton />}
286
+ </CoveMediaControls.Section>
291
287
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
292
288
  <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
293
289
  Skip Navigation or Skip to Content
294
290
  </a>
295
- <div
296
- className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
297
- onClick={() => { setExpanded(!expanded); }}
298
- tabIndex="0"
299
- onKeyDown={(e) => { if (e.keyCode === 13) { setExpanded(!expanded); } }}
300
- >
301
-
302
- {tableTitle}
303
- </div>
304
- <div
305
- className="table-container"
306
- style={ { maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' } }
307
- >
308
- <table
309
- height={expanded ? null : 0} {...getTableProps()}
310
- aria-live="assertive"
311
- className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
312
- hidden={!expanded}
313
- aria-rowcount={state?.data.length ? state.data.length : '-1' }
291
+ <div
292
+ className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
293
+ onClick={() => {
294
+ setExpanded(!expanded)
295
+ }}
296
+ tabIndex='0'
297
+ onKeyDown={e => {
298
+ if (e.keyCode === 13) {
299
+ setExpanded(!expanded)
300
+ }
301
+ }}
314
302
  >
315
- <caption className='cdcdataviz-sr-only'>{state.dataTable.caption ? state.dataTable.caption : `Datatable showing data for the ${mapLookup[state.general.geoType]} figure.`}</caption>
316
- <thead style={{position: 'sticky', top: 0, zIndex: 999}}>
317
- {headerGroups.map((headerGroup) => (
318
- <tr {...headerGroup.getHeaderGroupProps()}>
319
- {headerGroup.headers.map((column) => (
320
- <th
321
- tabIndex="0"
322
- title={column.Header}
323
- role="columnheader"
324
- scope="col"
325
- {...column.getHeaderProps(column.getSortByToggleProps())}
326
- className={column.isSorted ? column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc' : 'sort'}
327
- onKeyDown={(e) => { if (e.keyCode === 13) { column.toggleSortBy(); } }}
328
- //aria-sort={column.isSorted ? column.isSortedDesc ? 'descending' : 'ascending' : 'none' }
329
- {...(column.isSorted ? column.isSortedDesc ? { 'aria-sort': 'descending' } : { 'aria-sort': 'ascending' } : null)}
330
-
331
- >
332
- {column.render('Header')}
333
- <button>
334
- <span className="cdcdataviz-sr-only">{`Sort by ${(column.render('Header')).toLowerCase() } in ${ column.isSorted ? column.isSortedDesc ? 'descending' : 'ascending' : 'no'} `} order</span>
335
- </button>
336
- <div {...column.getResizerProps()} className="resizer" />
337
- </th>
338
- ))}
339
- </tr>
340
- ))}
341
- </thead>
342
- <tbody {...getTableBodyProps()}>
343
- {rows.map((row) => {
344
- prepareRow(row);
345
- return (
346
- <tr {...row.getRowProps()} role="row">
347
- {row.cells.map((cell) => {
348
- return (
349
- <td tabIndex="0" {...cell.getCellProps()} role="gridcell" onClick={ (e) => (state.general.type === 'bubble' && state.general.allowMapZoom && state.general.geoType === 'world') ? setFilteredCountryCode(cell.row.original) : true }>
350
- {cell.render('Cell')}
351
- </td>
352
- )
353
- }
354
- )}
303
+ {tableTitle}
304
+ </div>
305
+ <div className='table-container' style={{ maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' }}>
306
+ <table height={expanded ? null : 0} {...getTableProps()} aria-live='assertive' className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} hidden={!expanded} aria-rowcount={state?.data.length ? state.data.length : '-1'}>
307
+ <caption className='cdcdataviz-sr-only'>{state.dataTable.caption ? state.dataTable.caption : `Datatable showing data for the ${mapLookup[state.general.geoType]} figure.`}</caption>
308
+ <thead style={{ position: 'sticky', top: 0, zIndex: 999 }}>
309
+ {headerGroups.map(headerGroup => (
310
+ <tr {...headerGroup.getHeaderGroupProps()}>
311
+ {headerGroup.headers.map(column => (
312
+ <th
313
+ tabIndex='0'
314
+ title={column.Header}
315
+ role='columnheader'
316
+ scope='col'
317
+ {...column.getHeaderProps(column.getSortByToggleProps())}
318
+ className={column.isSorted ? (column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc') : 'sort'}
319
+ onKeyDown={e => {
320
+ if (e.keyCode === 13) {
321
+ column.toggleSortBy()
322
+ }
323
+ }}
324
+ //aria-sort={column.isSorted ? column.isSortedDesc ? 'descending' : 'ascending' : 'none' }
325
+ {...(column.isSorted ? (column.isSortedDesc ? { 'aria-sort': 'descending' } : { 'aria-sort': 'ascending' }) : null)}
326
+ >
327
+ {column.render('Header')}
328
+ <button>
329
+ <span className='cdcdataviz-sr-only'>{`Sort by ${column.render('Header').toLowerCase()} in ${column.isSorted ? (column.isSortedDesc ? 'descending' : 'ascending') : 'no'} `} order</span>
330
+ </button>
331
+ <div {...column.getResizerProps()} className='resizer' />
332
+ </th>
333
+ ))}
355
334
  </tr>
356
- );
357
- })}
358
- </tbody>
359
- </table>
360
- </div>
361
- {showDownloadButton === true && <DownloadButton />}
362
- </section>
335
+ ))}
336
+ </thead>
337
+ <tbody {...getTableBodyProps()}>
338
+ {rows.map(row => {
339
+ prepareRow(row)
340
+ return (
341
+ <tr {...row.getRowProps()} role='row'>
342
+ {row.cells.map(cell => {
343
+ return (
344
+ <td tabIndex='0' {...cell.getCellProps()} role='gridcell' onClick={e => (state.general.type === 'bubble' && state.general.allowMapZoom && state.general.geoType === 'world' ? setFilteredCountryCode(cell.row.original) : true)}>
345
+ {cell.render('Cell')}
346
+ </td>
347
+ )
348
+ })}
349
+ </tr>
350
+ )
351
+ })}
352
+ </tbody>
353
+ </table>
354
+ </div>
355
+ </section>
363
356
  </ErrorBoundary>
364
- );
365
- };
357
+ )
358
+ }
366
359
 
367
- export default DataTable;
360
+ export default DataTable