@cdc/editor 4.26.2 → 4.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@cdc/editor",
3
- "version": "4.26.2",
3
+ "version": "4.26.3",
4
4
  "description": "React component for generating a new component entry",
5
5
  "license": "Apache-2.0",
6
6
  "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
7
7
  "dependencies": {
8
- "@cdc/chart": "^4.26.2",
9
- "@cdc/core": "^4.26.2",
10
- "@cdc/dashboard": "^4.26.2",
11
- "@cdc/data-bite": "^4.26.2",
12
- "@cdc/map": "^4.26.2",
13
- "@cdc/markup-include": "^4.26.2",
14
- "@cdc/waffle-chart": "^4.26.2",
8
+ "@cdc/chart": "^4.26.3",
9
+ "@cdc/core": "^4.26.3",
10
+ "@cdc/dashboard": "^4.26.3",
11
+ "@cdc/data-bite": "^4.26.3",
12
+ "@cdc/map": "^4.26.3",
13
+ "@cdc/markup-include": "^4.26.3",
14
+ "@cdc/waffle-chart": "^4.26.3",
15
15
  "axios": "^1.13.2",
16
16
  "d3": "^7.9.0",
17
17
  "react-dropzone": "^14.3.8",
@@ -23,7 +23,7 @@
23
23
  "vite-plugin-css-injected-by-js": "^2.4.0",
24
24
  "vite-plugin-svgr": "^4.2.0"
25
25
  },
26
- "gitHead": "be3413e8e1149abf94225108f86a7910f56e0616",
26
+ "gitHead": "d50e45a074fbefa56cac904917e707d57f237737",
27
27
  "main": "dist/cdceditor",
28
28
  "moduleName": "CdcEditor",
29
29
  "peerDependencies": {
package/src/CdcEditor.tsx CHANGED
@@ -19,7 +19,6 @@ import { legacyConfigSupport } from './helpers/legacyConfigSupport'
19
19
 
20
20
  import './scss/main.scss'
21
21
  import editorReducer, { EditorState } from '@cdc/core/contexts/editor.reducer'
22
- import _ from 'lodash'
23
22
  import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
24
23
  import { WCMSProps } from '@cdc/core/types/WCMSProps'
25
24
  import { devToolsStore } from '@cdc/core/helpers/withDevTools'
@@ -73,7 +72,7 @@ const CdcEditor: React.FC<WCMSProps> = ({ config: configObj, hostname, container
73
72
  }, [])
74
73
 
75
74
  useEffect(() => {
76
- let strippedConfig = stripConfig(state.config)
75
+ let strippedConfig = stripConfig(state.config, true)
77
76
 
78
77
  const parsedData = JSON.stringify(strippedConfig)
79
78
  // Emit the data in a regular JS event so it can be consumed by anything.
@@ -99,7 +98,7 @@ const CdcEditor: React.FC<WCMSProps> = ({ config: configObj, hostname, container
99
98
  <GlobalContextProvider>
100
99
  <ConfigContext.Provider value={{ ...state, setTempConfig: setTempConfigAndUpdate }}>
101
100
  <EditorDispatchContext.Provider value={dispatch}>
102
- <div className={`cdc-open-viz-module cdc-editor ${state.currentViewport}`} ref={outerContainerRef}>
101
+ <div className={`cove-visualization cdc-editor ${state.currentViewport}`} ref={outerContainerRef}>
103
102
  <Tabs className='top-level'>
104
103
  <TabPane title='1. Choose Visualization Type' className='choose-type'>
105
104
  <ChooseTab />
@@ -1,9 +1,29 @@
1
1
  import { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import CdcEditor from '../CdcEditor'
3
- import { within, userEvent } from 'storybook/test'
3
+ import { within, userEvent, expect } from 'storybook/test'
4
+ import ChartEditorConfig from '../../../chart/src/_stories/_mock/editor-tests/bar-chart-editor-test.json'
5
+ import MapConfig from '../../../map/src/_stories/_mock/default-patterns.json'
6
+ import DashboardConfig from '../../../dashboard/src/_stories/_mock/dashboard_no_filter.json'
7
+ import DataTableConfig from '../../../data-table/examples/data-table-example.json'
4
8
 
5
- const sleep = ms => {
6
- return new Promise(r => setTimeout(r, ms))
9
+ const loadConfigFromTextArea = async (canvasElement, config) => {
10
+ const user = userEvent.setup()
11
+ const textArea = canvasElement.querySelector('#pasteConfig') as HTMLTextAreaElement
12
+ const loadButton = canvasElement.querySelector('#load-data') as HTMLButtonElement
13
+
14
+ expect(textArea).toBeTruthy()
15
+ expect(loadButton).toBeTruthy()
16
+
17
+ await user.click(textArea)
18
+ await user.clear(textArea)
19
+ await user.paste(JSON.stringify(config))
20
+ await user.click(loadButton)
21
+ }
22
+
23
+ const assertImportDataTabAccessible = async canvas => {
24
+ const user = userEvent.setup()
25
+ await user.click(canvas.getByText('2. Import Data'))
26
+ await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
7
27
  }
8
28
 
9
29
  const meta: Meta<typeof CdcEditor> = {
@@ -26,10 +46,95 @@ export const PreviewTableTests: Story = {
26
46
  play: async ({ canvasElement }) => {
27
47
  const canvas = within(canvasElement)
28
48
  const user = userEvent.setup()
29
- await sleep(1000)
30
- const mapButton = canvas.getByText('United States (State- or County-Level)')
49
+
50
+ const mapButton = await canvas.findByRole('button', { name: 'United States (State- or County-Level)' })
31
51
  await user.click(mapButton)
32
- const sampleData = canvas.getByText('United States: County Sample Data')
52
+
53
+ await user.click(canvas.getByText('2. Import Data'))
54
+
55
+ const sampleData = await canvas.findByText('United States: County Sample Data')
33
56
  await user.click(sampleData)
57
+
58
+ await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
59
+ }
60
+ }
61
+
62
+ export const LoadChartJsonConfig: Story = {
63
+ args: {
64
+ config: {}
65
+ },
66
+ play: async ({ canvasElement }) => {
67
+ const canvas = within(canvasElement)
68
+ await loadConfigFromTextArea(canvasElement, ChartEditorConfig)
69
+ await assertImportDataTabAccessible(canvas)
70
+ }
71
+ }
72
+
73
+ export const LoadMapJsonConfig: Story = {
74
+ args: {
75
+ config: {}
76
+ },
77
+ play: async ({ canvasElement }) => {
78
+ const canvas = within(canvasElement)
79
+ await loadConfigFromTextArea(canvasElement, MapConfig)
80
+ await assertImportDataTabAccessible(canvas)
81
+ }
82
+ }
83
+
84
+ export const LoadDashboardJsonConfig: Story = {
85
+ args: {
86
+ config: {}
87
+ },
88
+ play: async ({ canvasElement }) => {
89
+ const canvas = within(canvasElement)
90
+ await loadConfigFromTextArea(canvasElement, DashboardConfig)
91
+ await assertImportDataTabAccessible(canvas)
92
+ }
93
+ }
94
+
95
+ export const LoadDataTableJsonConfig: Story = {
96
+ args: {
97
+ config: {}
98
+ },
99
+ play: async ({ canvasElement }) => {
100
+ const canvas = within(canvasElement)
101
+ await loadConfigFromTextArea(canvasElement, DataTableConfig)
102
+ await assertImportDataTabAccessible(canvas)
103
+ }
104
+ }
105
+
106
+ export const InvalidJsonShowsValidationAlert: Story = {
107
+ args: {
108
+ config: {}
109
+ },
110
+ play: async ({ canvasElement }) => {
111
+ const user = userEvent.setup()
112
+ const textArea = canvasElement.querySelector('#pasteConfig') as HTMLTextAreaElement
113
+ const loadButton = canvasElement.querySelector('#load-data') as HTMLButtonElement
114
+
115
+ expect(textArea).toBeTruthy()
116
+ expect(loadButton).toBeTruthy()
117
+
118
+ const originalAlert = window.alert
119
+ const originalOnError = window.onerror
120
+ let alertText = ''
121
+
122
+ window.alert = message => {
123
+ alertText = String(message)
124
+ }
125
+
126
+ window.onerror = () => true
127
+
128
+ try {
129
+ await user.click(textArea)
130
+ await user.clear(textArea)
131
+ await user.paste('{"broken": true, }')
132
+ await user.click(loadButton)
133
+
134
+ await expect(alertText).toBe('The JSON that was entered is invalid.')
135
+ } finally {
136
+ window.alert = originalAlert
137
+ window.onerror = originalOnError
138
+ }
34
139
  }
35
140
  }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import { render, waitFor } from '@testing-library/react'
3
+
4
+ import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
5
+ import ChooseTab from './ChooseTab'
6
+
7
+ describe('ChooseTab', () => {
8
+ it('dispatches EDITOR_SAVE once when tempConfig is present', async () => {
9
+ const dispatch = vi.fn()
10
+ const tempConfig = { type: 'chart', data: [{ x: 'A', y: 1 }] }
11
+
12
+ render(
13
+ <ConfigContext.Provider
14
+ value={
15
+ {
16
+ config: { type: 'chart' },
17
+ tempConfig,
18
+ errors: [],
19
+ currentViewport: 'lg',
20
+ globalActive: 0,
21
+ setTempConfig: vi.fn()
22
+ } as any
23
+ }
24
+ >
25
+ <EditorDispatchContext.Provider value={dispatch}>
26
+ <ChooseTab />
27
+ </EditorDispatchContext.Provider>
28
+ </ConfigContext.Provider>
29
+ )
30
+
31
+ await waitFor(() => {
32
+ expect(dispatch).toHaveBeenCalledTimes(1)
33
+ expect(dispatch).toHaveBeenCalledWith({ type: 'EDITOR_SAVE', payload: tempConfig })
34
+ })
35
+ })
36
+ })
@@ -1,4 +1,4 @@
1
- import React, { useContext, useState } from 'react'
1
+ import React, { useContext, useEffect, useState } from 'react'
2
2
  import '../scss/choose-vis-tab.scss'
3
3
 
4
4
  import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
@@ -51,6 +51,23 @@ interface ButtonProps {
51
51
  orientation?: string | null
52
52
  }
53
53
 
54
+ interface VizButtonProps extends ButtonProps {
55
+ activeVizButtonID?: number
56
+ onConfigure: (props: Record<string, unknown>) => void
57
+ }
58
+
59
+ const VizButton: React.FC<VizButtonProps> = ({ activeVizButtonID, onConfigure, ...buttonProps }) => {
60
+ const { label, icon, id } = buttonProps
61
+ const isActive = id === activeVizButtonID || 0
62
+
63
+ return (
64
+ <button className={isActive ? 'active' : ''} onClick={() => onConfigure(buttonProps)} aria-label={label}>
65
+ {icon}
66
+ <span className='mt-1'>{label}</span>
67
+ </button>
68
+ )
69
+ }
70
+
54
71
  const ChooseTab: React.FC = (): JSX.Element => {
55
72
  const { config, tempConfig } = useContext(ConfigContext)
56
73
 
@@ -59,6 +76,12 @@ const ChooseTab: React.FC = (): JSX.Element => {
59
76
  const dispatch = useContext(EditorDispatchContext)
60
77
  const rowLabels = ['General', , 'Charts', 'Maps']
61
78
 
79
+ useEffect(() => {
80
+ if (tempConfig) {
81
+ dispatch({ type: 'EDITOR_SAVE', payload: tempConfig })
82
+ }
83
+ }, [dispatch, tempConfig])
84
+
62
85
  const handleUpload = e => {
63
86
  const file = e.target.files[0]
64
87
  const reader = new FileReader()
@@ -75,12 +98,15 @@ const ChooseTab: React.FC = (): JSX.Element => {
75
98
  newConfig = JSON.parse(text)
76
99
  } catch (e) {
77
100
  alert('The JSON that was entered is invalid.')
78
- throw new Error()
101
+ return
79
102
  }
80
103
 
81
104
  const isVega = isVegaConfig(newConfig)
82
105
  if (isVega) {
83
106
  newConfig = importVegaConfig(newConfig)
107
+ if (!newConfig) {
108
+ return
109
+ }
84
110
  }
85
111
 
86
112
  dispatch({ type: 'EDITOR_SET_CONFIG', payload: newConfig })
@@ -112,7 +138,7 @@ const ChooseTab: React.FC = (): JSX.Element => {
112
138
 
113
139
  const errorText = vegaErrors.join('\n\n')
114
140
  alert(errorText)
115
- throw new Error(errorText)
141
+ return null
116
142
  }
117
143
 
118
144
  const generateNewConfig = props => {
@@ -174,25 +200,6 @@ const ChooseTab: React.FC = (): JSX.Element => {
174
200
  dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: 1 })
175
201
  }
176
202
 
177
- const VizButton: React.FC<ButtonProps> = props => {
178
- const { label, icon, id } = props
179
- const isActive = id === config?.activeVizButtonID || 0
180
- const handleClick = () => {
181
- configureTabs(props)
182
- }
183
-
184
- if (tempConfig) {
185
- dispatch({ type: 'EDITOR_SAVE', payload: tempConfig })
186
- }
187
-
188
- return (
189
- <button className={isActive ? 'active' : ''} onClick={handleClick} aria-label={label}>
190
- {icon}
191
- <span className='mt-1'>{label}</span>
192
- </button>
193
- )
194
- }
195
-
196
203
  return (
197
204
  <div className='choose-vis'>
198
205
  <a
@@ -221,7 +228,11 @@ const ChooseTab: React.FC = (): JSX.Element => {
221
228
  <li key={`${label}-button-${buttonIndex}`}>
222
229
  <Tooltip position='right'>
223
230
  <Tooltip.Target>
224
- <VizButton {...button} />
231
+ <VizButton
232
+ {...button}
233
+ activeVizButtonID={config?.activeVizButtonID}
234
+ onConfigure={configureTabs}
235
+ />
225
236
  </Tooltip.Target>
226
237
  <Tooltip.Content>{button.content}</Tooltip.Content>
227
238
  </Tooltip>
@@ -24,7 +24,7 @@ import { type Visualization } from '@cdc/core/types/Visualization'
24
24
  import { type DataSet } from '@cdc/core/types/DataSet'
25
25
 
26
26
  import './data-import.scss'
27
- import '@cdc/core/styles/v2/components/data-designer.scss'
27
+ import '@cdc/core/components/managers/data-designer.scss'
28
28
 
29
29
  import { errorMessages, maxFileSize } from '../../../helpers/errorMessages'
30
30
  import { displaySize } from '../helpers/displaySize'
@@ -39,6 +39,7 @@ import {
39
39
  parseVegaConfig,
40
40
  updateVegaData
41
41
  } from '@cdc/core/helpers/vegaConfig'
42
+ import { extractDataAndMetadata } from '@cdc/core/helpers/extractDataAndMetadata'
42
43
 
43
44
  const DataImport = () => {
44
45
  const { config, errors, tempConfig, sharepath } = useContext(ConfigContext)
@@ -179,12 +180,13 @@ const DataImport = () => {
179
180
  const filereader = new FileReader()
180
181
 
181
182
  filereader.onload = function () {
182
- const handleSetConfig = (newData: Object[], useTempConfig = false) => {
183
+ const handleSetConfig = (newData: Object[], useTempConfig = false, dataMetadata = {}) => {
183
184
  const setDataURL = keepURL && fileSourceType === 'url'
184
185
  if (config.type === 'dashboard') {
185
186
  const dataFileFormat = mimeType.split('/')[1].toUpperCase()
186
187
  const dataset = {
187
188
  data: newData,
189
+ dataMetadata,
188
190
  dataFileSize: fileSize,
189
191
  dataFileName: fileSource, // new file source
190
192
  dataFileSourceType: fileSourceType, // new file source type
@@ -210,6 +212,7 @@ const DataImport = () => {
210
212
  ...config,
211
213
  ...tempConfig,
212
214
  data: newData,
215
+ dataMetadata,
213
216
  dataFileName: fileSource, // new file source
214
217
  dataFileSourceType: fileSourceType, // new file source type
215
218
  formattedData: transform.developerStandardize(newData, config.dataDescription)
@@ -227,14 +230,16 @@ const DataImport = () => {
227
230
  if (config.vegaConfig) {
228
231
  return updateDataFromVegaData(result, fileSource, fileSourceType)
229
232
  }
230
- const text = transform.autoStandardize(result)
233
+ const { data: extractedData, dataMetadata } = extractDataAndMetadata(result)
234
+ const text = transform.autoStandardize(extractedData)
231
235
  if (config.data && config.series) {
232
236
  if (dataExists(text, config.series, config?.xAxis.dataKey)) {
233
- handleSetConfig(text, true)
237
+ handleSetConfig(text, true, dataMetadata)
234
238
  } else {
235
239
  resetEditor(
236
240
  {
237
241
  data: text,
242
+ dataMetadata,
238
243
  dataFileName: fileSource,
239
244
  dataFileSourceType: fileSourceType
240
245
  } as Visualization,
@@ -242,7 +247,7 @@ const DataImport = () => {
242
247
  )
243
248
  }
244
249
  } else {
245
- handleSetConfig(text)
250
+ handleSetConfig(text, false, dataMetadata)
246
251
  }
247
252
 
248
253
  if (editingDataset) {
@@ -1,4 +1,4 @@
1
- import React, { useState, useContext, useMemo, useCallback, useEffect, memo } from 'react'
1
+ import React, { useState, useContext, useMemo, useCallback, useEffect, useRef, memo } from 'react'
2
2
  import {
3
3
  useTable,
4
4
  useBlockLayout,
@@ -20,16 +20,19 @@ import { errorMessages } from '../helpers/errorMessages'
20
20
  import { DataSet } from '@cdc/core/types/DataSet'
21
21
  import Icon from '@cdc/core/components/ui/Icon'
22
22
 
23
- const TableFilter = memo(({ globalFilter, setGlobalFilter, disabled = false }: any) => {
24
- const [filterValue, setFilterValue] = useState(globalFilter)
23
+ const TableFilter = memo(({ globalFilter, setGlobalFilter = () => {}, disabled = false }: any) => {
24
+ const [filterValue, setFilterValue] = useState(globalFilter ?? '')
25
25
 
26
26
  const [debouncedValue] = useDebounce(filterValue, 200)
27
27
 
28
28
  useEffect(() => {
29
- if ('string' === typeof debouncedValue && debouncedValue !== globalFilter) {
30
- setGlobalFilter(debouncedValue ?? '')
29
+ if ('string' === typeof debouncedValue && typeof setGlobalFilter === 'function') {
30
+ const nextFilter = debouncedValue.trim() ? debouncedValue : undefined
31
+ if (nextFilter !== globalFilter) {
32
+ setGlobalFilter(nextFilter)
33
+ }
31
34
  }
32
- }, [debouncedValue])
35
+ }, [debouncedValue, globalFilter, setGlobalFilter])
33
36
 
34
37
  const onChange = e => {
35
38
  setFilterValue(e.target.value)
@@ -86,30 +89,40 @@ const Footer = memo(({ previousPage, nextPage, canPreviousPage, canNextPage, pag
86
89
  ))
87
90
 
88
91
  const PreviewDataTable = () => {
89
- const { config } = useContext(ConfigContext)
92
+ const { config, errors } = useContext(ConfigContext)
90
93
  const previewData = useMemo(() => {
91
94
  if (config.type === 'dashboard') {
92
- return Object.values(config.datasets).find((dataset: DataSet) => {
95
+ const previewDataset = Object.values(config.datasets).find((dataset: DataSet) => {
93
96
  return dataset.preview && Array.isArray(dataset.data)
94
97
  })
98
+ return previewDataset?.data
95
99
  }
96
100
  return config.data
97
101
  }, [config.type, config.data, config.datasets])
98
- const [tableData, _setTableData] = useState(previewData)
102
+ const [tableData, _setTableData] = useState(Array.isArray(previewData) ? previewData : null)
103
+ const lastDataSourceRef = useRef<any[]>(null)
99
104
  const runSideEffects = (td: any[]) => {
105
+ if (!Array.isArray(td) || td.length === 0) return td
106
+
100
107
  const isSankey = Object.keys(td[0]).includes('tableData')
101
- const newData = generateColumns(isSankey ? td[0].tableData : td)
102
- validateFipsCodeLength(newData)
103
- return newData
108
+ const normalizedData = isSankey ? td[0].tableData : td
109
+ validateFipsCodeLength(normalizedData)
110
+ return normalizedData
111
+ }
112
+ const setTableData = td => {
113
+ if (!Array.isArray(td) || td.length === 0) return
114
+ if (lastDataSourceRef.current === td) return
115
+
116
+ lastDataSourceRef.current = td
117
+ _setTableData(runSideEffects(td))
104
118
  }
105
- const setTableData = td => _setTableData(runSideEffects(td))
106
119
 
107
120
  const dispatch = useContext(EditorDispatchContext)
108
121
 
109
122
  const fetchDatasetData = async (datasetKey, datasetConfig) => {
110
123
  if (datasetConfig.preview) {
111
124
  if (datasetConfig.dataUrl) {
112
- const remoteData = await fetchRemoteData(datasetConfig.dataUrl)
125
+ const { data: remoteData } = await fetchRemoteData(datasetConfig.dataUrl)
113
126
  if (Array.isArray(remoteData)) {
114
127
  setTableData(remoteData)
115
128
  }
@@ -132,7 +145,7 @@ const PreviewDataTable = () => {
132
145
  await handleDashboardData(config.datasets)
133
146
  } else {
134
147
  if (config.dataUrl) {
135
- const remoteData = await fetchRemoteData(config.dataUrl)
148
+ const { data: remoteData } = await fetchRemoteData(config.dataUrl)
136
149
  if (Array.isArray(remoteData)) {
137
150
  setTableData(remoteData)
138
151
  }
@@ -144,16 +157,11 @@ const PreviewDataTable = () => {
144
157
  }
145
158
 
146
159
  loadData()
147
- }, [config, config.data, previewData]) // eslint-disable-line
160
+ }, [config.data, config.dataUrl, config.datasets, config.type, previewData]) // eslint-disable-line
148
161
 
149
162
  const tableColumns = useMemo(() => {
150
- if (!tableData) return []
151
- const columns = tableData.columns ?? []
152
- if (columns.length > 0 && columns.includes('')) {
153
- // todo find a way to call the errors. Currently they are in DataImport.js
154
- // maybe these can be moved to a file? but then we need a way to add settings like size...
155
- dispatch({ type: 'EDITOR_SET_ERRORS', payload: [errorMessages.emptyCols] })
156
- }
163
+ if (!Array.isArray(tableData)) return []
164
+ const columns = Object.keys(tableData[0] ?? {})
157
165
 
158
166
  return columns.map(columnName => {
159
167
  const columnConfig = {
@@ -166,27 +174,13 @@ const PreviewDataTable = () => {
166
174
  })
167
175
  }, [tableData])
168
176
 
169
- // This adds a columns property just like the D3 function for JSON parsing.
170
- const generateColumns = data => {
171
- let columns = []
172
-
173
- data.forEach(rowObj => {
174
- Object.keys(rowObj).forEach(columnHeading => {
175
- if (false === columns.includes(columnHeading)) {
176
- columns.push(columnHeading)
177
- }
178
- })
179
- })
180
-
181
- // D3 uses a weird quirk where it attaches a named property to an array. Replicating here.
182
- type D3Data = any[] & { columns }
183
- const newData: D3Data = [...data] as D3Data
184
-
185
- if (Array.isArray(newData)) {
186
- newData.columns = columns
187
- return newData
177
+ useEffect(() => {
178
+ if (!tableData) return
179
+ const columns = Object.keys(tableData[0] ?? {})
180
+ if (columns.length > 0 && columns.includes('') && !errors?.includes(errorMessages.emptyCols)) {
181
+ dispatch({ type: 'EDITOR_SET_ERRORS', payload: [errorMessages.emptyCols] })
188
182
  }
189
- }
183
+ }, [dispatch, errors, tableData])
190
184
 
191
185
  const {
192
186
  getTableProps,
@@ -245,7 +239,8 @@ const PreviewDataTable = () => {
245
239
  )
246
240
  }
247
241
 
248
- if (!tableData) return [<Header key='header' />, <PlaceholderTable key='table' />]
242
+ if (!Array.isArray(tableData) || tableData.length === 0)
243
+ return [<Header key='header' />, <PlaceholderTable key='table' />]
249
244
 
250
245
  const footerProps = {
251
246
  previousPage,
@@ -1,6 +1,6 @@
1
1
  @import 'variables';
2
2
 
3
- .cdc-open-viz-module.cdc-editor {
3
+ .cove-visualization.cdc-editor {
4
4
  min-height: inherit;
5
5
  display: flex;
6
6
  flex-direction: column;