@cdc/core 4.23.11 → 4.24.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 (40) hide show
  1. package/components/DataTable/DataTable.tsx +27 -9
  2. package/components/DataTable/components/ChartHeader.tsx +17 -5
  3. package/components/DataTable/components/ExpandCollapse.tsx +1 -1
  4. package/components/DataTable/helpers/chartCellMatrix.tsx +16 -2
  5. package/components/DataTable/helpers/customColumns.ts +25 -0
  6. package/components/DataTable/helpers/getChartCellValue.ts +1 -0
  7. package/components/DataTable/helpers/getDataSeriesColumns.ts +1 -0
  8. package/components/DataTable/types/TableConfig.ts +5 -10
  9. package/components/EditorPanel/DataTableEditor.tsx +133 -0
  10. package/components/EditorPanel/Inputs.tsx +150 -0
  11. package/components/Filters.jsx +3 -3
  12. package/components/MediaControls.jsx +1 -1
  13. package/components/MultiSelect/MultiSelect.tsx +95 -0
  14. package/components/MultiSelect/index.ts +1 -0
  15. package/components/MultiSelect/multiselect.styles.css +50 -0
  16. package/components/Table/Table.tsx +23 -3
  17. package/components/Table/components/Cell.tsx +3 -3
  18. package/components/Table/components/GroupRow.tsx +6 -2
  19. package/components/Table/components/Row.tsx +9 -2
  20. package/components/Table/types/RowType.ts +5 -0
  21. package/components/_stories/DataTable.stories.tsx +41 -0
  22. package/components/_stories/EditorPanel.stories.tsx +53 -0
  23. package/components/_stories/Inputs.stories.tsx +37 -0
  24. package/components/_stories/MultiSelect.stories.tsx +24 -0
  25. package/components/_stories/_mocks/row_type.json +42 -0
  26. package/components/inputs/{InputSelect.jsx → InputSelect.tsx} +15 -5
  27. package/components/managers/DataDesigner.tsx +8 -8
  28. package/components/ui/{Icon.jsx → Icon.tsx} +3 -3
  29. package/helpers/DataTransform.ts +30 -2
  30. package/helpers/getFileExtension.ts +28 -5
  31. package/package.json +2 -2
  32. package/styles/_data-table.scss +2 -0
  33. package/types/Axis.ts +37 -2
  34. package/types/Column.ts +15 -0
  35. package/types/FilterBehavior.ts +1 -0
  36. package/types/Runtime.ts +21 -1
  37. package/types/Series.ts +1 -1
  38. package/types/Table.ts +18 -0
  39. package/types/UpdateFieldFunc.ts +1 -0
  40. package/types/Visualization.ts +9 -9
@@ -15,12 +15,15 @@ import Table from '../Table'
15
15
  import chartCellMatrix from './helpers/chartCellMatrix'
16
16
  import regionCellMatrix from './helpers/regionCellMatrix'
17
17
  import boxplotCellMatrix from './helpers/boxplotCellMatrix'
18
+ import customColumns from './helpers/customColumns'
18
19
  import { TableConfig } from './types/TableConfig'
19
20
 
20
21
  export type DataTableProps = {
21
22
  applyLegendToRow?: Function
22
23
  colorScale?: Function
23
24
  columns?: { navigate: { name: string } }
25
+ // determines if columns should be wrapped in the table
26
+ wrapColumns?: boolean
24
27
  config: TableConfig
25
28
  dataConfig?: Object
26
29
  displayDataAsText?: Function
@@ -42,7 +45,7 @@ export type DataTableProps = {
42
45
 
43
46
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
44
47
  const DataTable = (props: DataTableProps) => {
45
- const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId } = props
48
+ const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
46
49
 
47
50
  const [expanded, setExpanded] = useState(expandDataTable)
48
51
 
@@ -89,19 +92,21 @@ const DataTable = (props: DataTableProps) => {
89
92
  break
90
93
  }
91
94
 
92
- const rawRows = Object.keys(runtimeData)
95
+ const _runtimeData = config.table.customTableConfig ? customColumns(runtimeData, config.table.excludeColumns) : runtimeData
96
+
97
+ const rawRows = Object.keys(_runtimeData)
93
98
  const rows = isVertical
94
99
  ? rawRows.sort((a, b) => {
95
100
  let dataA
96
101
  let dataB
97
102
  if (config.type === 'map' && config.columns) {
98
103
  const sortByColName = config.columns[sortBy.column].name
99
- dataA = runtimeData[a][sortByColName]
100
- dataB = runtimeData[b][sortByColName]
104
+ dataA = _runtimeData[a][sortByColName]
105
+ dataB = _runtimeData[b][sortByColName]
101
106
  }
102
107
  if (config.type === 'chart' || config.type === 'dashboard') {
103
- dataA = runtimeData[a][sortBy.column]
104
- dataB = runtimeData[b][sortBy.column]
108
+ dataA = _runtimeData[a][sortBy.column]
109
+ dataB = _runtimeData[b][sortBy.column]
105
110
  }
106
111
  return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
107
112
  })
@@ -112,6 +117,8 @@ const DataTable = (props: DataTableProps) => {
112
117
  OverflowY: 'scroll'
113
118
  }
114
119
 
120
+ const hasRowType = !!Object.keys(rawData[0] || {}).find((v: string) => v.match(/row[_-]?type/i))
121
+
115
122
  const caption = useMemo(() => {
116
123
  if (config.type === 'map') {
117
124
  return config.table.caption ? config.table.caption : `Data table showing data for the ${mapLookup[config.general.geoType]} figure.`
@@ -120,6 +127,13 @@ const DataTable = (props: DataTableProps) => {
120
127
  }
121
128
  }, [config.table.caption])
122
129
 
130
+ // Determines if a relative region is being shown to the user.
131
+ // If a relative region is found we don't want to display the data table.
132
+ // Takes backwards compatibility into consideration, ie !region.toType || !region.fromType
133
+ const noRelativeRegions = config?.regions?.every(region => {
134
+ return (region.toType === 'Fixed' && region.fromType === 'Fixed') || (!region.toType && !region.fromType) || (!region.toType && region.fromType === 'Fixed') || (!region.fromType && region.toType === 'Fixed')
135
+ })
136
+
123
137
  // prettier-ignore
124
138
  const tableData = useMemo(() => (
125
139
  config.visualizationType === 'Pie'
@@ -151,17 +165,20 @@ const DataTable = (props: DataTableProps) => {
151
165
  <ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
152
166
  <div className='table-container' style={limitHeight}>
153
167
  <Table
154
- childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, ...props }) : chartCellMatrix({ rows, ...props, isVertical, sortBy })}
168
+ wrapColumns={wrapColumns}
169
+ childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData: _runtimeData }) : chartCellMatrix({ rows, ...props, runtimeData: _runtimeData, isVertical, sortBy, hasRowType })}
155
170
  tableName={config.type}
156
171
  caption={caption}
157
172
  stickyHeader
158
- headContent={config.type === 'map' ? <MapHeader columns={columns} {...props} sortBy={sortBy} setSortBy={setSortBy} /> : <ChartHeader data={runtimeData} {...props} isVertical={isVertical} sortBy={sortBy} setSortBy={setSortBy} />}
173
+ hasRowType={hasRowType}
174
+ headContent={config.type === 'map' ? <MapHeader columns={columns} {...props} sortBy={sortBy} setSortBy={setSortBy} /> : <ChartHeader data={_runtimeData} {...props} hasRowType={hasRowType} isVertical={isVertical} sortBy={sortBy} setSortBy={setSortBy} />}
159
175
  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 }}
160
176
  />
161
177
 
162
178
  {/* REGION Data Table */}
163
- {config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
179
+ {noRelativeRegions && config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
164
180
  <Table
181
+ wrapColumns={wrapColumns}
165
182
  childrenMatrix={regionCellMatrix({ config })}
166
183
  tableName={config.visualizationType}
167
184
  caption='Table of the highlighted regions in the visualization'
@@ -188,6 +205,7 @@ const DataTable = (props: DataTableProps) => {
188
205
  <ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
189
206
  <div className='table-container' style={limitHeight}>
190
207
  <Table
208
+ wrapColumns={wrapColumns}
191
209
  childrenMatrix={boxplotCellMatrix({ rows: tableData, config })}
192
210
  tableName={config.visualizationType}
193
211
  caption={caption}
@@ -3,11 +3,11 @@ import { getSeriesName } from '../helpers/getSeriesName'
3
3
  import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
4
4
  import { DownIcon, UpIcon } from './Icons'
5
5
 
6
- type ChartHeaderProps = { data; isVertical; config; runtimeData; setSortBy; sortBy; groupBy? }
6
+ type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; groupBy?; hasRowType? }
7
7
 
8
- const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy, groupBy }: ChartHeaderProps) => {
8
+ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, hasRowType }: ChartHeaderProps) => {
9
9
  if (!data) return
10
- let dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
10
+ let dataSeriesColumns = getDataSeriesColumns(config, isVertical, data)
11
11
  if (groupBy) {
12
12
  let groupHeaderRemoved = dataSeriesColumns.filter(col => col !== groupBy)
13
13
  if (groupHeaderRemoved.length != dataSeriesColumns.length) {
@@ -17,6 +17,14 @@ const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy,
17
17
  }
18
18
  }
19
19
  if (isVertical) {
20
+ if (hasRowType) {
21
+ // find the row type column and place it at the beginning of the array
22
+ const rowTypeRegex = /row[_-]?type/i
23
+ const rowTypeIndex = dataSeriesColumns.findIndex(column => rowTypeRegex.test(column))
24
+ if (rowTypeIndex > -1) {
25
+ dataSeriesColumns.splice(rowTypeIndex, 1)
26
+ }
27
+ }
20
28
  return (
21
29
  <tr>
22
30
  {dataSeriesColumns.map((column, index) => {
@@ -24,15 +32,18 @@ const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy,
24
32
 
25
33
  return (
26
34
  <th
35
+ style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
27
36
  key={`col-header-${column}__${index}`}
28
37
  tabIndex={0}
29
38
  title={text}
30
39
  role='columnheader'
31
40
  scope='col'
32
41
  onClick={() => {
42
+ if (hasRowType) return
33
43
  setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
34
44
  }}
35
45
  onKeyDown={e => {
46
+ if (hasRowType) return
36
47
  if (e.keyCode === 13) {
37
48
  setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
38
49
  }
@@ -54,11 +65,12 @@ const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy,
54
65
  const sliceVal = config.visualizationType === 'Pie' ? 1 : 0
55
66
  return (
56
67
  <tr>
57
- {['__series__', ...Object.keys(runtimeData)].slice(sliceVal).map((row, index) => {
68
+ {['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
58
69
  let column = config.xAxis?.dataKey
59
- let text = row !== '__series__' ? getChartCellValue(row, column, config, runtimeData) : '__series__'
70
+ let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'
60
71
  return (
61
72
  <th
73
+ style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
62
74
  key={`col-header-${text}__${index}`}
63
75
  tabIndex={0}
64
76
  title={text}
@@ -1,4 +1,4 @@
1
- import Icon from '@cdc/core/components/ui/Icon'
1
+ import Icon from '../../ui/Icon'
2
2
 
3
3
  const ExpandCollapse = ({ expanded, setExpanded, tableTitle }) => (
4
4
  <div
@@ -12,9 +12,10 @@ type ChartRowsProps = DataTableProps & {
12
12
  isVertical: boolean
13
13
  sortBy: { colIndex; column }
14
14
  groupBy?: string
15
+ hasRowType?: boolean
15
16
  }
16
17
 
17
- const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
18
+ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy, hasRowType }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
18
19
  const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
19
20
 
20
21
  const dataSeriesColumnsSorted = () => {
@@ -55,7 +56,20 @@ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorSc
55
56
  return cellMatrix
56
57
  } else {
57
58
  return rows.map(row => {
58
- return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
59
+ if (hasRowType) {
60
+ let rowType
61
+ let rowValues = []
62
+ dataSeriesColumns.forEach((column, j) => {
63
+ if (column.match(/row[_-]?type/i)) {
64
+ rowType = getChartCellValue(row, column, config, runtimeData)
65
+ } else {
66
+ rowValues.push(getChartCellValue(row, column, config, runtimeData))
67
+ }
68
+ })
69
+ return [rowType, ...rowValues]
70
+ } else {
71
+ return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
72
+ }
59
73
  })
60
74
  }
61
75
  } else {
@@ -0,0 +1,25 @@
1
+ // removes null and excluded columns
2
+ const customColumns = (runtimeData: Object[] | Record<string, Object>, excludeColumns: string[] = []): Object[] | Record<string, Object> => {
3
+ if (!Array.isArray(runtimeData)) {
4
+ // currently we don't support Record types
5
+ return runtimeData
6
+ } else {
7
+ const runtimeDataMemo = {}
8
+ runtimeData.forEach(row => {
9
+ Object.keys(row).forEach(key => {
10
+ if (runtimeDataMemo[key] === undefined) runtimeDataMemo[key] = null
11
+ if (row[key] !== null && !excludeColumns.includes(key)) runtimeDataMemo[key] = true
12
+ })
13
+ })
14
+ return runtimeData.map(d => {
15
+ const row = {}
16
+ Object.keys(d).forEach(key => {
17
+ if (key.match(/row[_-]?type/i)) row['row_type'] = d[key]
18
+ if (runtimeDataMemo[key] === true) row[key] = d[key]
19
+ })
20
+ return row
21
+ })
22
+ }
23
+ }
24
+
25
+ export default customColumns
@@ -24,6 +24,7 @@ const isAdditionalColumn = (column, config) => {
24
24
  }
25
25
 
26
26
  export const getChartCellValue = (row, column, config, runtimeData) => {
27
+ if (config.table.customTableConfig) return runtimeData[row][column]
27
28
  const rowObj = runtimeData[row]
28
29
  let cellValue // placeholder for formatting below
29
30
  let labelValue = rowObj[column] // just raw X axis string
@@ -1,4 +1,5 @@
1
1
  export const getDataSeriesColumns = (config, isVertical, runtimeData): string[] => {
2
+ if (config.table.customTableConfig) return runtimeData[0] ? Object.keys(runtimeData[0]) : []
2
3
  let tmpSeriesColumns
3
4
  if (config.visualizationType !== 'Pie') {
4
5
  tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
@@ -1,17 +1,11 @@
1
1
  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
+ import { Table } from '@cdc/core/types/Table'
4
5
 
5
6
  export type TableConfig = {
6
7
  type?: string
7
- table: {
8
- showVertical?: boolean
9
- indexLabel: string
10
- limitHeight: boolean
11
- height: string | number
12
- caption: string
13
- download: boolean
14
- }
8
+ table: Table
15
9
  xAxis?: Axis
16
10
  yAxis?: Axis
17
11
  boxplot?: {
@@ -46,10 +40,11 @@ export type TableConfig = {
46
40
  }
47
41
  }
48
42
  legend?: {
49
- specialClasses: { key: string; label: string; value: string }[]
43
+ specialClasses?: { key: string; label: string; value: string }[]
44
+ hide?: boolean
50
45
  }
51
46
  series?: Series
52
- regions?: { label: string; from: string; to: string }[]
47
+ regions?: { label: string; from: string; to: string; fromType: 'Fixed' | 'Previous Days'; toType: 'Fixed' | 'Last Date' }[]
53
48
  runtimeSeriesLabels?: Object
54
49
  dataFormat?: Object
55
50
  runtime: Runtime
@@ -0,0 +1,133 @@
1
+ import React from 'react'
2
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
3
+ import Icon from '../ui/Icon'
4
+ import { CheckBox, TextField } from './Inputs'
5
+ import type { Table } from '@cdc/core/types/Table'
6
+ import MultiSelect from '../MultiSelect'
7
+ import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
8
+
9
+ interface DataTableProps {
10
+ config: {
11
+ table: Table
12
+ visualizationType: string
13
+ }
14
+ updateField: UpdateFieldFunc<string | boolean | string[] | number>
15
+ isDashboard: boolean
16
+ isLoadedFromUrl: boolean
17
+ columns: string[]
18
+ }
19
+
20
+ const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard, isLoadedFromUrl, columns }) => {
21
+ return (
22
+ <>
23
+ <TextField
24
+ value={config.table.label}
25
+ updateField={updateField}
26
+ section='table'
27
+ fieldName='table-label'
28
+ id='tableLabel'
29
+ label='Data Table Title'
30
+ placeholder='Data Table'
31
+ tooltip={
32
+ <Tooltip style={{ textTransform: 'none' }}>
33
+ <Tooltip.Target>
34
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
35
+ </Tooltip.Target>
36
+ <Tooltip.Content>
37
+ <p>Label is required for Data Table for 508 Compliance</p>
38
+ </Tooltip.Content>
39
+ </Tooltip>
40
+ }
41
+ />
42
+ <CheckBox
43
+ value={config.table.show}
44
+ fieldName='show'
45
+ label='Show Data Table'
46
+ section='table'
47
+ updateField={updateField}
48
+ className='column-heading'
49
+ tooltip={
50
+ <Tooltip style={{ textTransform: 'none' }}>
51
+ <Tooltip.Target>
52
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
53
+ </Tooltip.Target>
54
+ <Tooltip.Content>
55
+ <p>Hiding the data table may affect accessibility. An alternate form of accessing visualization data is a 508 requirement.</p>
56
+ </Tooltip.Content>
57
+ </Tooltip>
58
+ }
59
+ />
60
+ {config.visualizationType !== 'Box Plot' && (
61
+ <CheckBox
62
+ value={config.table.showVertical}
63
+ fieldName='showVertical'
64
+ label='Show Vertical Data'
65
+ section='table'
66
+ updateField={updateField}
67
+ className='column-heading'
68
+ tooltip={
69
+ <Tooltip style={{ textTransform: 'none' }}>
70
+ <Tooltip.Target>
71
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
72
+ </Tooltip.Target>
73
+ <Tooltip.Content>
74
+ <p>This will draw the data table with vertical data instead of horizontal.</p>
75
+ </Tooltip.Content>
76
+ </Tooltip>
77
+ }
78
+ />
79
+ )}
80
+ <TextField value={config.table.indexLabel} section='table' fieldName='indexLabel' label='Index Column Header' updateField={updateField} />
81
+ <TextField
82
+ value={config.table.caption}
83
+ updateField={updateField}
84
+ section='table'
85
+ type='textarea'
86
+ fieldName='caption'
87
+ label='Screen Reader Description'
88
+ placeholder=' Data table'
89
+ tooltip={
90
+ <Tooltip style={{ textTransform: 'none' }}>
91
+ <Tooltip.Target>
92
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
93
+ </Tooltip.Target>
94
+ <Tooltip.Content>
95
+ <p>Enter a description of the data table to be read by screen readers.</p>
96
+ </Tooltip.Content>
97
+ </Tooltip>
98
+ }
99
+ />
100
+ <CheckBox value={config.table.limitHeight} section='table' fieldName='limitHeight' label='Limit Table Height' updateField={updateField} />
101
+ <CheckBox
102
+ value={config.table.customTableConfig}
103
+ fieldName='customTableConfig'
104
+ label='Customize Table Config'
105
+ section='table'
106
+ updateField={updateField}
107
+ tooltip={
108
+ <Tooltip style={{ textTransform: 'none' }}>
109
+ <Tooltip.Target>
110
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
111
+ </Tooltip.Target>
112
+ <Tooltip.Content>
113
+ <p>This will display all available columns in the data set. It will not show any columns where all of the column cells are null.</p>
114
+ </Tooltip.Content>
115
+ </Tooltip>
116
+ }
117
+ />
118
+ {config.table.customTableConfig && <MultiSelect options={columns.map(c => ({ label: c, value: c }))} fieldName='excludeColumns' label='Exclude Columns' section='table' updateField={updateField} />}
119
+ {config.table.limitHeight && <TextField value={config.table.height} fieldName='height' label='Data Table Height' type='number' min={0} max={500} placeholder='Height(px)' updateField={updateField} />}
120
+ <CheckBox value={config.table.expanded} fieldName='expanded' label='Expanded by Default' section='table' updateField={updateField} />
121
+ {isDashboard && <CheckBox value={config.table.showDataTableLink} fieldName='showDataTableLink' label='Show Data Table Name & Link' section='table' updateField={updateField} />}
122
+ {isLoadedFromUrl && <CheckBox value={config.table.showDownloadUrl} fieldName='showDownloadUrl' label='Show URL to Automatically Updated Data' section='table' updateField={updateField} />}
123
+ <CheckBox value={config.table.download} fieldName='download' label='Show Download CSV Link' section='table' updateField={updateField} />
124
+ <CheckBox value={config.table.showDownloadImgButton} fieldName='showDownloadImgButton' label='Display Image Button' section='table' updateField={updateField} />
125
+ <label>
126
+ <span className='edit-label column-heading'>Table Cell Min Width</span>
127
+ <input type='number' value={config.table.cellMinWidth ? config.table.cellMinWidth : 0} onChange={e => updateField('table', null, 'cellMinWidth', e.target.value)} />
128
+ </label>
129
+ </>
130
+ )
131
+ }
132
+
133
+ export default DataTable
@@ -0,0 +1,150 @@
1
+ import { memo, useEffect, useState } from 'react'
2
+ import { useDebounce } from 'use-debounce'
3
+
4
+ export type Input = {
5
+ label: string
6
+ tooltip?: any
7
+ section?: any
8
+ placeholder?: string
9
+ subsection?: any
10
+ updateField?: Function
11
+ fieldName?: string
12
+ }
13
+
14
+ export type TextFieldProps = {
15
+ className?: string
16
+ value: string | number
17
+ type?: 'text' | 'number' | 'textarea' | 'date'
18
+ min?: number
19
+ max?: number
20
+ i?: number
21
+ id?: string
22
+ } & Input
23
+
24
+ export type CheckboxProps = {
25
+ value?: boolean
26
+ min?: number
27
+ i?: number
28
+ className?: string
29
+ } & Input
30
+
31
+ export type SelectProps = {
32
+ value?: string
33
+ options?: string[]
34
+ required?: boolean
35
+ initial?: string
36
+
37
+ // all other props
38
+ [x: string]: any
39
+ } & Input
40
+
41
+ const TextField = memo((props: TextFieldProps) => {
42
+ const { label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'text', i = null, min = null, ...attributes } = props
43
+ const [value, setValue] = useState(stateValue)
44
+ const [debouncedValue] = useDebounce(value, 500)
45
+
46
+ useEffect(() => {
47
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
48
+ updateField(section, subsection, fieldName, debouncedValue, i)
49
+ }
50
+ }, [debouncedValue])
51
+
52
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
53
+
54
+ const onChange = e => {
55
+ if ('number' !== type || min === null) {
56
+ setValue(e.target.value)
57
+ } else {
58
+ if (!e.target.value || min <= parseFloat(e.target.value)) {
59
+ setValue(e.target.value)
60
+ } else {
61
+ setValue(min.toString())
62
+ }
63
+ }
64
+ }
65
+
66
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
67
+
68
+ if ('textarea' === type) {
69
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
70
+ }
71
+
72
+ if ('number' === type) {
73
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
74
+ }
75
+
76
+ if ('date' === type) {
77
+ formElement = <input type='date' name={name} onChange={onChange} {...attributes} value={value} />
78
+ }
79
+
80
+ return (
81
+ <label>
82
+ <span className='edit-label column-heading'>
83
+ {label}
84
+ {tooltip}
85
+ </span>
86
+ {formElement}
87
+ </label>
88
+ )
89
+ })
90
+
91
+ const CheckBox = memo((props: CheckboxProps) => {
92
+ const { label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes } = props
93
+
94
+ return (
95
+ <label className='checkbox column-heading'>
96
+ <input
97
+ type='checkbox'
98
+ name={fieldName}
99
+ checked={value}
100
+ onChange={e => {
101
+ updateField(section, subsection, fieldName, !value)
102
+ }}
103
+ {...attributes}
104
+ />
105
+ <span className='edit-label'>
106
+ {label}
107
+ {tooltip}
108
+ </span>
109
+ </label>
110
+ )
111
+ })
112
+
113
+ const Select = memo((props: SelectProps) => {
114
+ const { label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes } = props
115
+ let optionsJsx = options.map((optionName, index) => (
116
+ <option value={optionName} key={index}>
117
+ {optionName}
118
+ </option>
119
+ ))
120
+
121
+ if (initialValue) {
122
+ optionsJsx.unshift(
123
+ <option value='' key='initial'>
124
+ {initialValue}
125
+ </option>
126
+ )
127
+ }
128
+
129
+ return (
130
+ <label>
131
+ <span className='edit-label'>
132
+ {label}
133
+ {tooltip}
134
+ </span>
135
+ <select
136
+ className={required && !value ? 'warning' : ''}
137
+ name={fieldName}
138
+ value={value}
139
+ onChange={event => {
140
+ updateField(section, subsection, fieldName, event.target.value)
141
+ }}
142
+ {...attributes}
143
+ >
144
+ {optionsJsx}
145
+ </select>
146
+ </label>
147
+ )
148
+ })
149
+
150
+ export { Select, CheckBox, TextField }
@@ -89,7 +89,7 @@ export const useFilters = props => {
89
89
  filters: newFilters
90
90
  })
91
91
  }
92
-
92
+
93
93
  // Used for setting active filter, fromHash breaks the filteredData functionality.
94
94
  if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
95
95
  setFilteredData(newFilters)
@@ -320,7 +320,7 @@ const Filters = props => {
320
320
  const tabValues = []
321
321
  const tabBarValues = []
322
322
 
323
- const { active, label, filterStyle } = singleFilter
323
+ const { active, queuedActive, label, filterStyle } = singleFilter
324
324
 
325
325
  handleSorting(singleFilter)
326
326
 
@@ -387,7 +387,7 @@ const Filters = props => {
387
387
  {filterStyle === 'tab' && !mobileFilterStyle && <Filters.Tabs tabs={tabValues} />}
388
388
  {filterStyle === 'pill' && !mobileFilterStyle && <Filters.Pills pills={pillValues} />}
389
389
  {filterStyle === 'tab bar' && !mobileFilterStyle && <Filters.TabBar filter={singleFilter} index={outerIndex} />}
390
- {(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown filter={singleFilter} index={outerIndex} label={label} active={active} filters={values} />}
390
+ {(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown filter={singleFilter} index={outerIndex} label={label} active={queuedActive || active} filters={values} />}
391
391
  </>
392
392
  </div>
393
393
  )
@@ -59,7 +59,7 @@ const generateMedia = (state, type, elementToCapture) => {
59
59
 
60
60
  switch (type) {
61
61
  case 'image':
62
- html2canvas(baseSvg, {foreignObjectRendering: true}).then(canvas => {
62
+ html2canvas(baseSvg, {foreignObjectRendering: true, x: -1 * (window.pageXOffset + baseSvg.getBoundingClientRect().left), y: -1 * (window.pageYOffset + baseSvg.getBoundingClientRect().top)}).then(canvas => {
63
63
  saveImageAs(canvas.toDataURL(), filename + '.png')
64
64
  })
65
65
  return