@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.
Files changed (74) hide show
  1. package/assets/icon-combo-chart.svg +1 -0
  2. package/assets/icon-epi-chart.svg +27 -0
  3. package/components/BlurStrokeText.tsx +44 -0
  4. package/components/DataTable/DataTable.tsx +51 -35
  5. package/components/DataTable/DataTableStandAlone.tsx +37 -6
  6. package/components/DataTable/components/ChartHeader.tsx +31 -26
  7. package/components/DataTable/components/MapHeader.tsx +19 -10
  8. package/components/DataTable/components/SortIcon/index.tsx +25 -0
  9. package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
  10. package/{styles/_data-table.scss → components/DataTable/data-table.css} +268 -298
  11. package/components/DataTable/helpers/customSort.ts +11 -15
  12. package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
  13. package/components/DataTable/helpers/getNewSortBy.ts +35 -0
  14. package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
  15. package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
  16. package/components/EditorPanel/DataTableEditor.tsx +132 -26
  17. package/components/EditorPanel/Inputs.tsx +42 -4
  18. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
  19. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +1 -1
  20. package/components/{Filters.tsx → Filters/Filters.tsx} +48 -39
  21. package/components/Filters/helpers/applyQueuedActive.ts +12 -0
  22. package/components/Filters/helpers/getNestedOptions.ts +29 -0
  23. package/components/Filters/helpers/handleSorting.ts +18 -0
  24. package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
  25. package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
  26. package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
  27. package/components/Filters/index.ts +5 -0
  28. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -3
  29. package/components/Legend/Legend.Gradient.tsx +2 -9
  30. package/components/Loader/Loader.tsx +33 -0
  31. package/components/Loader/index.ts +1 -0
  32. package/components/Loader/loader.styles.css +13 -0
  33. package/components/NestedDropdown/NestedDropdown.tsx +90 -48
  34. package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
  35. package/components/NestedDropdown/nesteddropdown.styles.css +7 -0
  36. package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
  37. package/components/Table/components/GroupRow.tsx +1 -1
  38. package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
  39. package/components/_stories/NestedDropdown.stories.tsx +22 -46
  40. package/components/_stories/_mocks/nested-dropdown.json +30 -0
  41. package/components/_stories/styles.scss +0 -1
  42. package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
  43. package/data/colorPalettes.js +107 -10
  44. package/dist/cove-main.css +6114 -0
  45. package/dist/cove-main.css.map +1 -0
  46. package/helpers/addValuesToFilters.ts +8 -3
  47. package/helpers/cove/number.js +46 -25
  48. package/helpers/coveUpdateWorker.ts +6 -7
  49. package/helpers/pivotData.ts +52 -11
  50. package/helpers/tests/gatherQueryParams.test.ts +13 -1
  51. package/helpers/tests/pivotData.test.ts +50 -0
  52. package/helpers/ver/4.24.10.ts +47 -0
  53. package/helpers/ver/4.24.9.ts +0 -3
  54. package/helpers/ver/tests/4.24.10.test.ts +45 -0
  55. package/helpers/viewports.ts +9 -0
  56. package/package.json +7 -3
  57. package/styles/_button-section.scss +4 -0
  58. package/styles/_global-variables.scss +19 -1
  59. package/styles/_global.scss +1 -8
  60. package/styles/_reset.scss +2 -15
  61. package/styles/base.scss +0 -1
  62. package/styles/cove-main.scss +6 -0
  63. package/styles/filters.scss +6 -4
  64. package/styles/v2/components/ui/tooltip.scss +42 -40
  65. package/styles/v2/layout/_component.scss +0 -6
  66. package/styles/v2/layout/index.scss +0 -1
  67. package/types/Axis.ts +2 -0
  68. package/types/General.ts +1 -0
  69. package/types/Table.ts +2 -1
  70. package/types/Visualization.ts +13 -1
  71. package/types/VizFilter.ts +2 -1
  72. package/components/DataTable/components/Icons.tsx +0 -10
  73. package/components/_stories/EditorPanel.stories.tsx +0 -54
  74. 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, valueColumn } = config.table.pivot
75
- if (columnName && valueColumn) {
76
- return pivotData(data, columnName, valueColumn)
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 = isVertical
134
- ? rawRows.sort((a, b) => {
135
- let dataA
136
- let dataB
137
- if (config.type === 'map' && config.columns) {
138
- const sortByColName = config.columns[sortBy.column].name
139
- dataA = runtimeData[a][sortByColName]
140
- dataB = runtimeData[b][sortByColName]
141
- }
142
- if (config.type === 'chart' || config.type === 'dashboard') {
143
- dataA = runtimeData[a][sortBy.column]
144
- dataB = runtimeData[b][sortBy.column]
145
- }
146
- if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
147
- dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
148
- dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
149
- }
150
- return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
151
- })
152
- : rawRows
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
- const isLegendOnBottom = config?.legend?.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(viewport)
214
- if (config.brush?.active && !isLegendOnBottom) classes.push('brush-active')
215
- if (config.brush?.active && config.legend.hide) classes.push('brush-active')
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
- return (
220
- <ErrorBoundary component='DataTable'>
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} w-100`}
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: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${
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} w-100`}
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: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}`,
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> = ({ visualizationKey, config, updateConfig, viewport, isEditor }) => {
19
- const [filteredData, setFilteredData] = useState<Record<string, any>[]>(filterVizData(config.filters, config.formattedData))
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(filterVizData(config.filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
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 component={DataTableStandAlone} visualizationKey={visualizationKey} visualizationConfig={config} updateConfig={updateConfig} type={'Table'} viewport={viewport}>
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 config={config} setConfig={updateConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={config.formattedData} />
36
- <DataTable expandDataTable={true} config={config} rawData={config.data} runtimeData={filteredData} tabbingId={visualizationKey} tableTitle={config.table.label} viewport={viewport || 'lg'} />
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 <span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'} order`}</span>
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) return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
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({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
81
+ setSortBy(newSortBy)
85
82
  }}
86
83
  onKeyDown={e => {
87
84
  if (hasRowType) return
88
85
  if (e.keyCode === 13) {
89
- setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
86
+ setSortBy(newSortBy)
90
87
  }
91
88
  }}
92
- className={handleHeaderClasses(sortBy, text)}
93
- {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
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 && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
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({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
120
+ setSortBy(newSortBy)
120
121
  }}
121
122
  onKeyDown={e => {
122
123
  if (e.keyCode === 13) {
123
- setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
124
+ setSortBy(newSortBy)
124
125
  }
125
126
  }}
126
- className={handleHeaderClasses(sortBy, text)}
127
- {...(sortBy.column === text ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
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 && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
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 = ({ column, text, config }) => {
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) return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
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({ column, asc: sortBy.column === column ? !sortBy.asc : false })
44
+ setSortBy(newSortBy)
42
45
  }}
43
46
  onKeyDown={e => {
44
47
  if (e.keyCode === 13) {
45
- setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
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 ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
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
- {sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
53
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} order`}</span>
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
+ }