@cdc/core 4.24.2 → 4.24.4

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 (84) hide show
  1. package/assets/icon-command.svg +3 -0
  2. package/assets/icon-rotate-left.svg +3 -0
  3. package/assets/icon-sankey.svg +1 -0
  4. package/assets/icon-table.svg +1 -0
  5. package/components/AdvancedEditor.jsx +9 -0
  6. package/components/DataTable/DataTable.tsx +37 -13
  7. package/components/DataTable/DataTableStandAlone.tsx +15 -0
  8. package/components/DataTable/components/CellAnchor.tsx +3 -1
  9. package/components/DataTable/components/ChartHeader.tsx +48 -12
  10. package/components/DataTable/components/DataTableEditorPanel.tsx +42 -0
  11. package/components/DataTable/components/ExpandCollapse.tsx +22 -16
  12. package/components/DataTable/components/MapHeader.tsx +10 -5
  13. package/components/DataTable/helpers/chartCellMatrix.tsx +2 -2
  14. package/components/DataTable/helpers/customColumns.ts +4 -2
  15. package/components/DataTable/helpers/getChartCellValue.ts +4 -2
  16. package/components/DataTable/helpers/getDataSeriesColumns.ts +9 -1
  17. package/components/DataTable/helpers/mapCellMatrix.tsx +2 -2
  18. package/components/DataTable/types/TableConfig.ts +7 -7
  19. package/components/EditorPanel/ColumnsEditor.tsx +312 -0
  20. package/components/EditorPanel/DataTableEditor.tsx +42 -27
  21. package/components/Filters.jsx +35 -17
  22. package/components/Layout/components/Responsive.tsx +184 -0
  23. package/components/Layout/components/Sidebar/components/Sidebar.tsx +47 -0
  24. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +902 -0
  25. package/components/Layout/components/Sidebar/index.tsx +3 -0
  26. package/components/Layout/components/Visualization/index.tsx +79 -0
  27. package/components/Layout/components/Visualization/visualizations.scss +33 -0
  28. package/components/Layout/index.tsx +11 -0
  29. package/components/Layout/styles/editor-grid-view.scss +156 -0
  30. package/components/Layout/styles/editor-utils.scss +197 -0
  31. package/components/Layout/styles/editor.scss +144 -0
  32. package/components/LegendCircle.jsx +4 -3
  33. package/components/MediaControls.jsx +1 -1
  34. package/components/MultiSelect/MultiSelect.tsx +39 -20
  35. package/components/MultiSelect/multiselect.styles.css +44 -27
  36. package/components/NestedDropdown/NestedDropdown.tsx +257 -0
  37. package/components/NestedDropdown/index.ts +1 -0
  38. package/components/NestedDropdown/nesteddropdown.styles.css +70 -0
  39. package/components/Table/Table.tsx +8 -6
  40. package/components/Table/components/Row.tsx +6 -2
  41. package/components/Table/types/RowType.ts +3 -0
  42. package/components/Waiting.jsx +11 -1
  43. package/components/_stories/MultiSelect.stories.tsx +10 -1
  44. package/components/_stories/NestedDropdown.stories.tsx +58 -0
  45. package/components/_stories/styles.scss +1 -0
  46. package/components/createBarElement.jsx +120 -0
  47. package/components/elements/ScreenReaderText.tsx +8 -0
  48. package/components/elements/SkipTo.tsx +46 -0
  49. package/components/managers/DataDesigner.tsx +18 -18
  50. package/components/ui/Icon.tsx +9 -1
  51. package/components/ui/Title/Title.scss +7 -1
  52. package/components/ui/Title/index.tsx +3 -3
  53. package/components/ui/Tooltip.jsx +1 -1
  54. package/data/colorPalettes.js +1 -6
  55. package/helpers/cove/accessibility.ts +23 -0
  56. package/helpers/cove/date.ts +19 -0
  57. package/helpers/{coveUpdateWorker.js → coveUpdateWorker.ts} +9 -5
  58. package/helpers/isDomainExternal.js +14 -0
  59. package/helpers/queryStringUtils.js +26 -0
  60. package/helpers/tests/updateFieldFactory.test.ts +89 -0
  61. package/helpers/updateFieldFactory.ts +38 -0
  62. package/helpers/useDataVizClasses.js +7 -7
  63. package/helpers/ver/4.24.3.ts +56 -0
  64. package/package.json +4 -3
  65. package/styles/_data-table.scss +8 -13
  66. package/styles/_global.scss +7 -4
  67. package/styles/_variables.scss +3 -0
  68. package/styles/base.scss +4 -14
  69. package/styles/v2/base/index.scss +1 -1
  70. package/styles/v2/components/ui/tooltip.scss +0 -21
  71. package/types/Axis.ts +3 -0
  72. package/types/BaseVisualizationType.ts +1 -0
  73. package/types/ConfidenceInterval.ts +1 -0
  74. package/types/ConfigureData.ts +8 -0
  75. package/types/DataDescription.ts +9 -0
  76. package/types/Legend.ts +18 -0
  77. package/types/Region.ts +10 -0
  78. package/types/Runtime.ts +2 -0
  79. package/types/Table.ts +2 -1
  80. package/types/UpdateFieldFunc.ts +1 -1
  81. package/types/Visualization.ts +19 -10
  82. package/components/DataTable/components/SkipNav.tsx +0 -7
  83. package/helpers/cove/date.js +0 -9
  84. package/helpers/ver/4.23.js +0 -10
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden="true" focusable="false" role="img" fill="currentColor">
2
+ <path d="M404.83356,297.97872H354.67715V214.177h50.15641a107.08854,107.08854,0,1,0,0-214.177C345.64284,0,297.51145,47.97571,297.82294,107.01074v50.46778H214.33287V107.01074A107.09452,107.09452,0,0,0,107.32225,0C47.97572,0,0,47.97571,0,107.01074c0,59.3464,48.28721,107.6336,107.32225,107.16629h50.15628v83.80169H107.32225C47.97572,297.97872,0,345.79862,0,404.83352a107.16644,107.16644,0,1,0,214.33287,0V354.83291h83.49007v50.00061a107.08879,107.08879,0,1,0,107.01062-106.8548ZM354.67715,106.85493a50.2348,50.2348,0,1,1,50.0006,50.46791h-50.0006ZM157.47853,404.83352a50.2348,50.2348,0,1,1-50.0006-50.00061h50.0006Zm0-247.51068h-50.0006a50.2348,50.2348,0,1,1,50.0006-50.46791ZM297.97862,297.97872H214.33287V214.33285h83.64575Zm106.69913,157.3227a50.15888,50.15888,0,0,1-50.0006-50.4679V354.83291h50.0006a50.2348,50.2348,0,1,1,0,100.46851Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden="true" focusable="false" role="img" fill="currentColor">
2
+ <path d="M480 256c0 123.4-100.5 223.9-223.9 223.9c-48.84 0-95.17-15.58-134.2-44.86c-14.12-10.59-16.97-30.66-6.375-44.81c10.59-14.12 30.62-16.94 44.81-6.375c27.84 20.91 61 31.94 95.88 31.94C344.3 415.8 416 344.1 416 256s-71.69-159.8-159.8-159.8c-37.46 0-73.09 13.49-101.3 36.64l45.12 45.14c17.01 17.02 4.955 46.1-19.1 46.1H35.17C24.58 224.1 16 215.5 16 204.9V59.04c0-24.04 29.07-36.08 46.07-19.07l47.6 47.63C149.9 52.71 201.5 32.11 256.1 32.11C379.5 32.11 480 132.6 480 256z"/>
3
+ </svg>
@@ -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"><defs><style>.c{stroke-linecap:round;stroke-width:3px;}.c,.d,.e{stroke:#000;}.e{fill:none;}</style></defs><g id="a"/><g id="b"><path class="c" d="M6.88,39.98V5.57"/><path class="c" d="M89.42,22.98V5.57"/><path class="c" d="M89.42,52.79V27.38"/><path class="c" d="M89.42,76v-18.41"/><path class="c" d="M6.88,76v-28.43"/><rect class="e" x="12.3" y="5.98" width="71.62" height="9.69"/><path class="d" d="M83.92,16.67s-11.46,1.07-27.63,11.61-27.1,16.32-35.66,17.97c-4.59,.88-8.34,.72-8.34,.72v5.31s3.75,.17,8.34-.72c8.56-1.65,19.49-7.42,35.66-17.97s27.63-11.61,27.63-11.61v-5.31Z"/><path class="e" d="M12.3,16.67s41.36,10.71,71.62,10.71v8.71s-31.58,1.88-71.62-10.71v-8.71Z"/><path class="d" d="M83.92,53.29s-13.18,1.49-35.77,9.06c0,0-18.05,7.37-35.85,7.37v-15.89c17.8,0,35.85-7.37,35.85-7.37,22.59-7.57,35.77-9.06,35.77-9.06v15.89Z"/><rect class="d" x="12.3" y="71" width="71.62" height="5.44"/><path class="e" d="M83.92,57.58l-8.82-2.38c-6.78-1.93-13.27-4.73-19.33-8.34l-14.69-8.76c-5.94-3.54-12.3-6.31-18.93-8.25l-9.85-2.88v12.14l9.85,2.88c6.64,1.94,13,4.71,18.93,8.25l14.69,8.76c6.05,3.61,12.55,6.41,19.33,8.34l8.82,2.38v-12.14Z"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 256V160H224v96H64zm0 64H224v96H64V320zm224 96V320H448v96H288zM448 256H288V160H448v96zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"/></svg>
@@ -51,6 +51,15 @@ export const AdvancedEditor = ({ loadConfig, state, convertStateToConfig }) => {
51
51
  <p className='pb-2'>
52
52
  This tool displays the actual <acronym title='JavaScript Object Notation'>JSON</acronym> configuration that is generated by this editor and allows you to edit properties directly and apply them.
53
53
  </p>
54
+ <button
55
+ className='btn'
56
+ onClick={() => {
57
+ navigator.clipboard.writeText(configTextboxValue)
58
+ alert('Copied!')
59
+ }}
60
+ >
61
+ Copy to Clipboard
62
+ </button>
54
63
  <textarea value={configTextboxValue} onChange={event => setConfigTextbox(event.target.value)} />
55
64
  <button className='btn full-width' onClick={() => loadConfig(JSON.parse(configTextboxValue))}>
56
65
  Apply
@@ -9,7 +9,7 @@ import { customSort } from './helpers/customSort'
9
9
  import ChartHeader from './components/ChartHeader'
10
10
  import BoxplotHeader from './components/BoxplotHeader'
11
11
  import MapHeader from './components/MapHeader'
12
- import SkipNav from './components/SkipNav'
12
+ import SkipTo from '../elements/SkipTo'
13
13
  import ExpandCollapse from './components/ExpandCollapse'
14
14
  import mapCellMatrix from './helpers/mapCellMatrix'
15
15
  import Table from '../Table'
@@ -39,12 +39,12 @@ export type DataTableProps = {
39
39
  navigationHandler?: Function
40
40
  outerContainerRef?: Function
41
41
  rawData: Object[]
42
- runtimeData: Object[] | Record<string, Object> // UNSAFE
42
+ runtimeData: Object[] & Record<string, Object>
43
43
  setFilteredCountryCode?: Function // used for Maps only
44
44
  showDownloadButton?: boolean
45
45
  tabbingId: string
46
46
  tableTitle: string
47
- viewport: string
47
+ viewport: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
48
48
  vizTitle?: string
49
49
  // determines if columns should be wrapped in the table
50
50
  wrapColumns?: boolean
@@ -53,7 +53,6 @@ export type DataTableProps = {
53
53
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
54
54
  const DataTable = (props: DataTableProps) => {
55
55
  const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
56
-
57
56
  const [expanded, setExpanded] = useState(expandDataTable)
58
57
 
59
58
  const [sortBy, setSortBy] = useState<any>({ column: config.type === 'map' ? 'geo' : 'date', asc: false, colIndex: null })
@@ -91,7 +90,7 @@ const DataTable = (props: DataTableProps) => {
91
90
  case 'Box Plot':
92
91
  if (!config.boxplot) return <Loading />
93
92
  break
94
- case 'Line' || 'Bar' || 'Combo' || 'Pie' || 'Deviation Bar' || 'Paired Bar':
93
+ case 'Line' || 'Bar' || 'Combo' || 'Pie' || 'Deviation Bar' || 'Paired Bar' || 'Sankey' || 'Table':
95
94
  if (!runtimeData) return <Loading />
96
95
  break
97
96
  default:
@@ -115,7 +114,7 @@ const DataTable = (props: DataTableProps) => {
115
114
  dataA = _runtimeData[a][sortBy.column]
116
115
  dataB = _runtimeData[b][sortBy.column]
117
116
  }
118
- if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date' && config.xAxis.sortDates) {
117
+ if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
119
118
  dataA = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[a][config.xAxis.dataKey])
120
119
  dataB = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[b][config.xAxis.dataKey])
121
120
  }
@@ -142,12 +141,25 @@ const DataTable = (props: DataTableProps) => {
142
141
  // If a relative region is found we don't want to display the data table.
143
142
  // Takes backwards compatibility into consideration, ie !region.toType || !region.fromType
144
143
  const noRelativeRegions = config?.regions?.every(region => {
145
- return (region.toType === 'Fixed' && region.fromType === 'Fixed') || (!region.toType && !region.fromType) || (!region.toType && region.fromType === 'Fixed') || (!region.fromType && region.toType === 'Fixed')
144
+ const toTypeFixed = region.toType === 'Fixed'
145
+ const fromTypeFixed = region.fromType === 'Fixed'
146
+ const toIsNotSet = !region.toType
147
+ const fromIsNotSet = !region.fromType
148
+ const BothFixed = toTypeFixed && fromTypeFixed
149
+ const NeitherFixed = toIsNotSet && fromIsNotSet
150
+ const ToFixedFromNotSet = toTypeFixed && fromIsNotSet
151
+ const FromFixedToNotSet = fromTypeFixed && toIsNotSet
152
+
153
+ return BothFixed || NeitherFixed || ToFixedFromNotSet || FromFixedToNotSet
146
154
  })
147
155
 
148
156
  // prettier-ignore
149
157
  const tableData = useMemo(() => (
150
- config.visualizationType === 'Pie'
158
+ config.data?.[0]?.tableData
159
+ ? config.data?.[0]?.tableData
160
+ : config.visualizationType === 'Sankey'
161
+ ? config.data?.[0]?.tableData
162
+ : config.visualizationType === 'Pie'
151
163
  ? [config.yAxis.dataKey]
152
164
  : config.visualizationType === 'Box Plot'
153
165
  ? Object.entries(config.boxplot.tableData[0])
@@ -169,26 +181,29 @@ const DataTable = (props: DataTableProps) => {
169
181
  <ErrorBoundary component='DataTable'>
170
182
  <MediaControls.Section classes={['download-links']}>
171
183
  <MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
172
- {(config.table.download || config.general?.showDownloadButton) && <DownloadButton rawData={getDownloadData()} fileName={`${vizTitle || 'data-table'}.csv`} headerColor={headerColor} skipId={skipId} />}
184
+ {(config.table.download || config.general?.showDownloadButton) && <DownloadButton rawData={getDownloadData()} fileName={`${vizTitle || 'data-table'}.csv`} headerColor={headerColor} />}
173
185
  </MediaControls.Section>
174
186
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
175
- <SkipNav skipId={skipId} />
176
- <ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
187
+ <SkipTo skipId={skipId} skipMessage='Skip Data Table' />
188
+ <ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} fontSize={config.fontSize} viewport={viewport} />
177
189
  <div className='table-container' style={limitHeight}>
178
190
  <Table
191
+ viewport={viewport}
179
192
  wrapColumns={wrapColumns}
180
- childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData: _runtimeData }) : chartCellMatrix({ rows, ...props, runtimeData: _runtimeData, isVertical, sortBy, hasRowType })}
193
+ childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData: _runtimeData, viewport }) : chartCellMatrix({ rows, ...props, runtimeData: _runtimeData, isVertical, sortBy, hasRowType, viewport })}
181
194
  tableName={config.type}
182
195
  caption={caption}
183
196
  stickyHeader
184
197
  hasRowType={hasRowType}
185
198
  headContent={config.type === 'map' ? <MapHeader columns={columns} {...props} sortBy={sortBy} setSortBy={setSortBy} /> : <ChartHeader data={_runtimeData} {...props} hasRowType={hasRowType} isVertical={isVertical} sortBy={sortBy} setSortBy={setSortBy} />}
186
199
  tableOptions={{ className: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${isVertical ? '' : ' horizontal'}`, 'aria-live': 'assertive', 'aria-rowcount': config?.data?.length ? config.data.length : -1, hidden: !expanded }}
200
+ fontSize={config.fontSize}
187
201
  />
188
202
 
189
203
  {/* REGION Data Table */}
190
204
  {noRelativeRegions && config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
191
205
  <Table
206
+ viewport={viewport}
192
207
  wrapColumns={wrapColumns}
193
208
  childrenMatrix={regionCellMatrix({ config })}
194
209
  tableName={config.visualizationType}
@@ -201,10 +216,14 @@ const DataTable = (props: DataTableProps) => {
201
216
  </tr>
202
217
  }
203
218
  tableOptions={{ className: 'region-table data-table' }}
219
+ fontSize={config.fontSize}
204
220
  />
205
221
  )}
206
222
  </div>
207
223
  </section>
224
+ <div id={skipId} className='cdcdataviz-sr-only'>
225
+ Skipped data table.
226
+ </div>
208
227
  </ErrorBoundary>
209
228
  )
210
229
  } else {
@@ -212,10 +231,11 @@ const DataTable = (props: DataTableProps) => {
212
231
  return (
213
232
  <ErrorBoundary component='DataTable'>
214
233
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
215
- <SkipNav skipId={skipId} />
234
+ <SkipTo skipId={skipId} skipMessage='Skip Data Table' />
216
235
  <ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
217
236
  <div className='table-container' style={limitHeight}>
218
237
  <Table
238
+ viewport={viewport}
219
239
  wrapColumns={wrapColumns}
220
240
  childrenMatrix={boxplotCellMatrix({ rows: tableData, config })}
221
241
  tableName={config.visualizationType}
@@ -223,9 +243,13 @@ const DataTable = (props: DataTableProps) => {
223
243
  stickyHeader
224
244
  headContent={<BoxplotHeader categories={config.boxplot.categories} />}
225
245
  tableOptions={{ className: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}`, 'aria-live': 'assertive', 'aria-rowcount': 11, hidden: !expanded }}
246
+ fontSize={config.fontSize}
226
247
  />
227
248
  </div>
228
249
  </section>
250
+ <div id={skipId} className='cdcdataviz-sr-only'>
251
+ Skipped data table.
252
+ </div>
229
253
  </ErrorBoundary>
230
254
  )
231
255
  }
@@ -0,0 +1,15 @@
1
+ import { ViewPort } from '../../types/ViewPort'
2
+ import { Visualization } from '../../types/Visualization'
3
+ import DataTable from './DataTable'
4
+
5
+ type StandAloneProps = {
6
+ visualizationKey: string
7
+ config: Visualization
8
+ viewport?: ViewPort
9
+ }
10
+
11
+ const DataTableStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, config, viewport }) => {
12
+ return <DataTable expandDataTable={true} config={config} rawData={config.data} runtimeData={config.formattedData} tabbingId={visualizationKey} tableTitle={config.table.label} viewport={viewport || 'lg'} />
13
+ }
14
+
15
+ export default DataTableStandAlone
@@ -1,4 +1,6 @@
1
1
  import ExternalIcon from '@cdc/core/assets/external-link.svg'
2
+ import isDomainExternal from '@cdc/core/helpers/isDomainExternal'
3
+
2
4
  // Optionally wrap cell with anchor if config defines a navigation url
3
5
  const CellAnchor = ({ markup, row, columns, navigationHandler, mapZoomHandler }) => {
4
6
  if (columns.navigate && row[columns.navigate.name]) {
@@ -16,7 +18,7 @@ const CellAnchor = ({ markup, row, columns, navigationHandler, mapZoomHandler })
16
18
  }}
17
19
  >
18
20
  {markup}
19
- <ExternalIcon className='inline-icon' />
21
+ {isDomainExternal(row[columns.navigate.name]) && <ExternalIcon className='inline-icon' />}
20
22
  </span>
21
23
  )
22
24
  } else if (mapZoomHandler) {
@@ -2,6 +2,7 @@ import { getChartCellValue } from '../helpers/getChartCellValue'
2
2
  import { getSeriesName } from '../helpers/getSeriesName'
3
3
  import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
4
4
  import { DownIcon, UpIcon } from './Icons'
5
+ import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
5
6
 
6
7
  type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; groupBy?; hasRowType? }
7
8
 
@@ -16,6 +17,45 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, has
16
17
  dataSeriesColumns = groupHeaderRemoved
17
18
  }
18
19
  }
20
+
21
+ const handleHeaderClasses = (sortBy, text) => {
22
+ let classes = ['sort']
23
+ if (sortBy.column === text && sortBy.asc) {
24
+ classes.push('sort-asc')
25
+ }
26
+ if (sortBy.column === text && sortBy.desc) {
27
+ classes.push('sort-desc')
28
+ }
29
+ return classes.join(' ')
30
+ }
31
+
32
+ const ScreenReaderSortByText = ({ text, config, sortBy }) => {
33
+ const notApplicableText = 'Not Applicable'
34
+ let columnHeaderText = `${text} `
35
+ if (text !== '__series__' || text !== '') {
36
+ columnHeaderText = `${text} `
37
+ }
38
+
39
+ if ((text === '__series__' || text === '') && !config.table.indexLabel) {
40
+ columnHeaderText = notApplicableText
41
+ }
42
+
43
+ if ((text === '__series__' || text === '') && config.table.indexLabel) {
44
+ columnHeaderText = config.table.indexLabel
45
+ }
46
+
47
+ if (columnHeaderText === notApplicableText) return
48
+
49
+ 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>
50
+ }
51
+
52
+ const ColumnHeadingText = ({ column, text, config }) => {
53
+ let notApplicableText = 'Not Applicable'
54
+ if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
55
+ if (text === '__series__' && !config.table.indexLabel) return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
56
+ return text
57
+ }
58
+
19
59
  if (isVertical) {
20
60
  if (hasRowType) {
21
61
  // find the row type column and place it at the beginning of the array
@@ -25,6 +65,7 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, has
25
65
  dataSeriesColumns.splice(rowTypeIndex, 1)
26
66
  }
27
67
  }
68
+
28
69
  return (
29
70
  <tr>
30
71
  {dataSeriesColumns.map((column, index) => {
@@ -35,7 +76,6 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, has
35
76
  style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
36
77
  key={`col-header-${column}__${index}`}
37
78
  tabIndex={0}
38
- title={text}
39
79
  role='columnheader'
40
80
  scope='col'
41
81
  onClick={() => {
@@ -48,14 +88,12 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, has
48
88
  setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
49
89
  }
50
90
  }}
51
- className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
91
+ className={handleHeaderClasses(sortBy, text)}
52
92
  {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
53
93
  >
54
- {text}
94
+ <ColumnHeadingText text={text} column={column} config={config} />
55
95
  {column === sortBy.column && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
56
- <button>
57
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
58
- </button>
96
+ <ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
59
97
  </th>
60
98
  )
61
99
  })}
@@ -68,12 +106,12 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, has
68
106
  {['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
69
107
  let column = config.xAxis?.dataKey
70
108
  let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'
109
+
71
110
  return (
72
111
  <th
73
112
  style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
74
113
  key={`col-header-${text}__${index}`}
75
114
  tabIndex={0}
76
- title={text}
77
115
  role='columnheader'
78
116
  scope='col'
79
117
  onClick={() => {
@@ -84,14 +122,12 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, has
84
122
  setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
85
123
  }
86
124
  }}
87
- className={sortBy.column === text ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
125
+ className={handleHeaderClasses(sortBy, text)}
88
126
  {...(sortBy.column === text ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
89
127
  >
90
- {text === '__series__' ? '' : text}
128
+ <ColumnHeadingText text={text} column={column} config={config} />
91
129
  {index === sortBy.colIndex && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
92
- <button>
93
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === text ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
94
- </button>
130
+ <ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
95
131
  </th>
96
132
  )
97
133
  })}
@@ -0,0 +1,42 @@
1
+ import { AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
2
+ import DataTableEditor from '../../EditorPanel/DataTableEditor'
3
+ import { Visualization } from '@cdc/core/types/Visualization'
4
+ import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
5
+ import { useMemo } from 'react'
6
+ import ColumnsEditor from '../../EditorPanel/ColumnsEditor'
7
+
8
+ type DataTableEditorProps = {
9
+ config: Visualization
10
+ updateConfig: Function
11
+ }
12
+
13
+ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateConfig }) => {
14
+ const updateField = useMemo(() => updateFieldFactory(config, updateConfig), [JSON.stringify(config)])
15
+ const deleteColumn = columnName => {
16
+ const newColumns = config.columns
17
+
18
+ delete newColumns[columnName]
19
+
20
+ updateConfig({
21
+ ...config,
22
+ columns: newColumns
23
+ })
24
+ }
25
+
26
+ const columns = Object.keys(config.columns || {})
27
+ return (
28
+ <>
29
+ <ColumnsEditor config={config} updateField={updateField} deleteColumn={deleteColumn} />
30
+ <AccordionItem>
31
+ <AccordionItemHeading>
32
+ <AccordionItemButton>Data Table</AccordionItemButton>
33
+ </AccordionItemHeading>
34
+ <AccordionItemPanel>
35
+ <DataTableEditor config={config} columns={columns} updateField={updateField} isDashboard={true} />
36
+ </AccordionItemPanel>
37
+ </AccordionItem>
38
+ </>
39
+ )
40
+ }
41
+
42
+ export default DataTableEditorPanel
@@ -1,21 +1,27 @@
1
1
  import Icon from '../../ui/Icon'
2
2
 
3
- const ExpandCollapse = ({ expanded, setExpanded, tableTitle }) => (
4
- <div
5
- className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
6
- onClick={() => {
7
- setExpanded(!expanded)
8
- }}
9
- tabIndex={0}
10
- onKeyDown={e => {
11
- if (e.keyCode === 13) {
3
+ const ExpandCollapse = ({ expanded, setExpanded, tableTitle, fontSize, viewport }) => {
4
+ const fontSizes = { small: 16, medium: 18, large: 20 }
5
+ const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[fontSize]}px`
6
+ return (
7
+ <div
8
+ style={{ fontSize: titleFontSize }}
9
+ role='button'
10
+ className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
11
+ onClick={() => {
12
12
  setExpanded(!expanded)
13
- }
14
- }}
15
- >
16
- <Icon display={expanded ? 'minus' : 'plus'} base />
17
- {tableTitle}
18
- </div>
19
- )
13
+ }}
14
+ tabIndex={0}
15
+ onKeyDown={e => {
16
+ if (e.keyCode === 13) {
17
+ setExpanded(!expanded)
18
+ }
19
+ }}
20
+ >
21
+ <Icon display={expanded ? 'minus' : 'plus'} base />
22
+ {tableTitle}
23
+ </div>
24
+ )
25
+ }
20
26
 
21
27
  export default ExpandCollapse
@@ -1,11 +1,19 @@
1
1
  import { DataTableProps } from '../DataTable'
2
2
  import { DownIcon, UpIcon } from './Icons'
3
+ import ScreenReaderText from '../../elements/ScreenReaderText'
3
4
 
4
5
  type MapHeaderProps = DataTableProps & {
5
6
  sortBy: { column; asc }
6
7
  setSortBy: Function
7
8
  }
8
9
 
10
+ const ColumnHeadingText = ({ column, text, config }) => {
11
+ let notApplicableText = 'Not Applicable'
12
+ if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
13
+ if (text === '__series__' && !config.table.indexLabel) return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
14
+ return text
15
+ }
16
+
9
17
  const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeaderProps) => {
10
18
  return (
11
19
  <tr>
@@ -27,7 +35,6 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
27
35
  key={`col-header-${column}__${index}`}
28
36
  id={column}
29
37
  tabIndex={0}
30
- title={text}
31
38
  role='columnheader'
32
39
  scope='col'
33
40
  onClick={() => {
@@ -41,11 +48,9 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
41
48
  className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
42
49
  {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
43
50
  >
44
- {text}
51
+ <ColumnHeadingText text={text} config={config} column={column} />
45
52
  {sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
46
- <button>
47
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
48
- </button>
53
+ <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} order`}</span>
49
54
  </th>
50
55
  )
51
56
  })}
@@ -15,7 +15,7 @@ type ChartRowsProps = DataTableProps & {
15
15
  hasRowType?: boolean
16
16
  }
17
17
 
18
- const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy, hasRowType }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
18
+ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy, hasRowType, viewport }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
19
19
  const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
20
20
 
21
21
  const dataSeriesColumnsSorted = () => {
@@ -79,7 +79,7 @@ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorSc
79
79
  config.visualizationType !== 'Pie'
80
80
  ? [
81
81
  <>
82
- {colorScale && colorScale(seriesName) && <LegendCircle fill={colorScale(seriesName)} />}
82
+ {colorScale && colorScale(seriesName) && <LegendCircle viewport={viewport} fill={colorScale(seriesName)} />}
83
83
  {seriesName}
84
84
  </>
85
85
  ]
@@ -1,5 +1,7 @@
1
+ type RuntimeData = Object[] & Record<string, Object>
2
+
1
3
  // removes null and excluded columns
2
- const customColumns = (runtimeData: Object[] | Record<string, Object>, excludeColumns: string[] = []): Object[] | Record<string, Object> => {
4
+ const customColumns = (runtimeData: Object[] | RuntimeData, excludeColumns: string[] = []): RuntimeData => {
3
5
  if (!Array.isArray(runtimeData)) {
4
6
  // currently we don't support Record types
5
7
  return runtimeData
@@ -18,7 +20,7 @@ const customColumns = (runtimeData: Object[] | Record<string, Object>, excludeCo
18
20
  if (runtimeDataMemo[key] === true) row[key] = d[key]
19
21
  })
20
22
  return row
21
- })
23
+ }) as RuntimeData
22
24
  }
23
25
  }
24
26
 
@@ -1,5 +1,6 @@
1
1
  import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
2
2
  import { formatNumber } from '@cdc/core/helpers/cove/number'
3
+ import { TableConfig } from '../types/TableConfig'
3
4
 
4
5
  // if its additional column, return formatting params
5
6
  const isAdditionalColumn = (column, config) => {
@@ -23,8 +24,9 @@ const isAdditionalColumn = (column, config) => {
23
24
  return formattingParams
24
25
  }
25
26
 
26
- export const getChartCellValue = (row, column, config, runtimeData) => {
27
- if (config.table.customTableConfig) return runtimeData[row][column]
27
+ export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[]) => {
28
+ if (config.table.customTableConfig || config.visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) return runtimeData[row][column]
29
+
28
30
  const rowObj = runtimeData[row]
29
31
  let cellValue // placeholder for formatting below
30
32
  let labelValue = rowObj[column] // just raw X axis string
@@ -1,5 +1,13 @@
1
- export const getDataSeriesColumns = (config, isVertical, runtimeData): string[] => {
1
+ import { TableConfig } from '../types/TableConfig'
2
+
3
+ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, runtimeData: Object[]): string[] => {
2
4
  if (config.table.customTableConfig) return runtimeData[0] ? Object.keys(runtimeData[0]) : []
5
+ if (config.type === 'table') {
6
+ const excludeColumns = Object.values(config.columns)
7
+ .filter(column => column.dataTable === false)
8
+ .map(column => column.name)
9
+ return Object.keys(runtimeData[0]).filter(columnName => !excludeColumns.includes(columnName))
10
+ }
3
11
  let tmpSeriesColumns
4
12
  if (config.visualizationType !== 'Pie') {
5
13
  tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
@@ -7,7 +7,7 @@ type MapRowsProps = DataTableProps & {
7
7
  rows: string[]
8
8
  }
9
9
 
10
- const mapCellArray = ({ rows, columns, runtimeData, config, applyLegendToRow, displayGeoName, formatLegendLocation, displayDataAsText, navigationHandler, setFilteredCountryCode }: MapRowsProps): ReactNode[][] => {
10
+ const mapCellArray = ({ rows, columns, runtimeData, config, applyLegendToRow, displayGeoName, formatLegendLocation, displayDataAsText, navigationHandler, setFilteredCountryCode, viewport }: MapRowsProps): ReactNode[][] => {
11
11
  return rows.map(row =>
12
12
  Object.keys(columns)
13
13
  .filter(column => columns[column].dataTable === true && columns[column].name)
@@ -31,7 +31,7 @@ const mapCellArray = ({ rows, columns, runtimeData, config, applyLegendToRow, di
31
31
  }
32
32
  cellValue = (
33
33
  <div className='col-12'>
34
- <LegendCircle fill={legendColor[0]} />
34
+ <LegendCircle viewport={viewport} fill={legendColor[0]} />
35
35
  <CellAnchor markup={labelValue} row={rowObj} columns={columns} navigationHandler={navigationHandler} mapZoomHandler={mapZoomHandler} />
36
36
  </div>
37
37
  )
@@ -2,9 +2,11 @@ import { Axis } from '@cdc/core/types/Axis'
2
2
  import { Series } from '@cdc/core/types/Series'
3
3
  import { Runtime } from '@cdc/core/types/Runtime'
4
4
  import { Table } from '@cdc/core/types/Table'
5
+ import { Region } from '@cdc/core/types/Region'
5
6
  import { BoxPlot } from '../../../types/BoxPlot'
6
7
  import { General } from '../../../types/General'
7
8
  import { Column } from '../../../types/Column'
9
+ import { Legend } from '@cdc/core/types/Legend'
8
10
 
9
11
  export type TableConfig = {
10
12
  type?: string
@@ -12,17 +14,15 @@ export type TableConfig = {
12
14
  xAxis?: Axis
13
15
  yAxis?: Axis
14
16
  boxplot?: BoxPlot
15
- visualizationType?: string
17
+ visualizationType: string
16
18
  general?: General
17
19
  columns?: Record<string, Column>
18
- legend?: {
19
- specialClasses?: { key: string; label: string; value: string }[]
20
- hide?: boolean
21
- }
20
+ legend?: Legend
22
21
  series?: Series
23
- regions?: { label: string; from: string; to: string; fromType: 'Fixed' | 'Previous Days'; toType: 'Fixed' | 'Last Date' }[]
22
+ regions?: Region[]
24
23
  runtimeSeriesLabels?: Object
25
24
  dataFormat?: Object
26
- runtime: Runtime
25
+ runtime?: Runtime
27
26
  data: Object[]
27
+ fontSize: 'small' | 'medium' | 'large'
28
28
  }