@cdc/data-bite 1.1.4 → 4.22.11

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.
@@ -1,166 +1,185 @@
1
1
  import React, { memo, useContext, useEffect, useState } from 'react'
2
2
 
3
- import {
4
- Accordion,
5
- AccordionItem,
6
- AccordionItemButton,
7
- AccordionItemHeading,
8
- AccordionItemPanel,
9
- } from 'react-accessible-accordion'
3
+ import { Accordion, AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
10
4
 
11
5
  import { useDebounce } from 'use-debounce'
12
6
  import Context from '../context'
13
- import WarningImage from '../images/warning.svg'
7
+ import WarningImage from '@cdc/core/assets/icon-warning-circle.svg'
14
8
  import Tooltip from '@cdc/core/components/ui/Tooltip'
15
9
  import Icon from '@cdc/core/components/ui/Icon'
16
10
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
17
11
  import { BITE_LOCATIONS, DATA_FUNCTIONS, IMAGE_POSITIONS, DATA_OPERATORS } from '../CdcDataBite'
18
12
 
19
- const TextField = memo(({label, section = null, subsection = null, fieldName, updateField, value: stateValue, tooltip, type = "input", i = null, min = null, max = null, ...attributes}) => {
20
- const [ value, setValue ] = useState(stateValue);
13
+ const TextField = memo(({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, tooltip, type = 'input', i = null, min = null, max = null, ...attributes }) => {
14
+ const [value, setValue] = useState(stateValue)
21
15
 
22
- const [ debouncedValue ] = useDebounce(value, 500);
16
+ const [debouncedValue] = useDebounce(value, 500)
23
17
 
24
18
  useEffect(() => {
25
- if('string' === typeof debouncedValue && stateValue !== debouncedValue ) {
19
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
26
20
  updateField(section, subsection, fieldName, debouncedValue, i)
27
21
  }
28
22
  }, [debouncedValue, section, subsection, fieldName, i, stateValue, updateField])
29
23
 
30
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`;
24
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
31
25
 
32
- const onChange = (e) => {
26
+ const onChange = e => {
33
27
  //TODO: This block gives a warning/error in the console, but it still works.
34
- if('number' !== type || min === null){
35
- setValue(e.target.value);
28
+ if ('number' !== type || min === null) {
29
+ setValue(e.target.value)
36
30
  } else {
37
- if(!e.target.value || ( parseFloat(min) <= parseFloat(e.target.value ) & parseFloat(max) >= parseFloat(e.target.value))) {
38
- setValue(e.target.value);
31
+ if (!e.target.value || (parseFloat(min) <= parseFloat(e.target.value)) & (parseFloat(max) >= parseFloat(e.target.value))) {
32
+ setValue(e.target.value)
39
33
  } else {
40
- setValue(min.toString());
34
+ setValue(min.toString())
41
35
  }
42
36
  }
43
- };
37
+ }
44
38
 
45
- let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value} />
39
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
46
40
 
47
- if('textarea' === type) {
48
- formElement = (
49
- <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
50
- )
41
+ if ('textarea' === type) {
42
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
51
43
  }
52
44
 
53
- if('number' === type) {
54
- formElement = <input type="number" name={name} onChange={onChange} {...attributes} value={value} />
45
+ if ('number' === type) {
46
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
55
47
  }
56
48
 
57
49
  return (
58
50
  <>
59
- {label && label.length > 0 &&
51
+ {label && label.length > 0 && (
60
52
  <label>
61
- <span className="edit-label column-heading">{label}{tooltip}</span>
53
+ <span className='edit-label column-heading'>
54
+ {label}
55
+ {tooltip}
56
+ </span>
62
57
  {formElement}
63
58
  </label>
64
- }
59
+ )}
65
60
  {(!label || label.length === 0) && formElement}
66
61
  </>
67
62
  )
68
63
  })
69
64
 
70
- const CheckBox = memo(({label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes}) => (
71
- <label className="checkbox">
72
- <input type="checkbox" name={fieldName} checked={ value } onChange={() => { updateField(section, subsection, fieldName, !value) }} {...attributes}/>
73
- <span className="edit-label column-heading">{label}</span><span className="cove-icon">{tooltip}</span>
65
+ const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
66
+ <label className='checkbox'>
67
+ <input
68
+ type='checkbox'
69
+ name={fieldName}
70
+ checked={value}
71
+ onChange={() => {
72
+ updateField(section, subsection, fieldName, !value)
73
+ }}
74
+ {...attributes}
75
+ />
76
+ <span className='edit-label column-heading'>{label}</span>
77
+ <span className='cove-icon'>{tooltip}</span>
74
78
  </label>
75
79
  ))
76
80
 
77
- const Select = memo(({label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes}) => {
78
- let optionsJsx = '';
79
- if ( Array.isArray(options)) { //Handle basic array
80
- optionsJsx = options.map(optionName => <option value={optionName} key={optionName}>{optionName}</option>)
81
- } else { //Handle object with value/name pairs
82
- optionsJsx = [];
81
+ const Select = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes }) => {
82
+ let optionsJsx = ''
83
+ if (Array.isArray(options)) {
84
+ //Handle basic array
85
+ optionsJsx = options.map(optionName => (
86
+ <option value={optionName} key={optionName}>
87
+ {optionName}
88
+ </option>
89
+ ))
90
+ } else {
91
+ //Handle object with value/name pairs
92
+ optionsJsx = []
83
93
  for (const [optionValue, optionName] of Object.entries(options)) {
84
- optionsJsx.push(<option value={optionValue} key={optionValue}>{optionName}</option>)
94
+ optionsJsx.push(
95
+ <option value={optionValue} key={optionValue}>
96
+ {optionName}
97
+ </option>
98
+ )
85
99
  }
86
100
  }
87
101
 
88
- if(initialValue) {
89
- optionsJsx.unshift(<option value="" key="initial">{initialValue}</option>)
102
+ if (initialValue) {
103
+ optionsJsx.unshift(
104
+ <option value='' key='initial'>
105
+ {initialValue}
106
+ </option>
107
+ )
90
108
  }
91
109
 
92
110
  return (
93
111
  <label>
94
- <span className="edit-label">{label}</span>
95
- <select className={required && !value ? 'warning' : ''} name={fieldName} value={value} onChange={(event) => { updateField(section, subsection, fieldName, event.target.value) }} {...attributes}>
112
+ <span className='edit-label'>{label}</span>
113
+ <select
114
+ className={required && !value ? 'warning' : ''}
115
+ name={fieldName}
116
+ value={value}
117
+ onChange={event => {
118
+ updateField(section, subsection, fieldName, event.target.value)
119
+ }}
120
+ {...attributes}
121
+ >
96
122
  {optionsJsx}
97
123
  </select>
98
124
  </label>
99
125
  )
100
126
  })
101
127
 
102
- const headerColors = ['theme-blue','theme-purple','theme-brown','theme-teal','theme-pink','theme-orange','theme-slate','theme-indigo','theme-cyan','theme-green','theme-amber']
128
+ const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
103
129
 
104
130
  const EditorPanel = memo(() => {
105
- const {
106
- config,
107
- updateConfig,
108
- loading,
109
- data,
110
- setParentConfig,
111
- isDashboard,
112
- } = useContext(Context);
113
-
114
- const [ displayPanel, setDisplayPanel ] = useState(true);
115
- const enforceRestrictions = (updatedConfig) => {
116
- //If there are any dependencies between fields, etc../
117
- };
131
+ const { config, updateConfig, loading, data, setParentConfig, isDashboard } = useContext(Context)
132
+
133
+ const [displayPanel, setDisplayPanel] = useState(true)
134
+ const enforceRestrictions = updatedConfig => {
135
+ //If there are any dependencies between fields, etc../
136
+ }
118
137
 
119
138
  const updateField = (section, subsection, fieldName, newValue) => {
120
139
  // Top level
121
- if( null === section && null === subsection) {
122
- let updatedConfig = {...config, [fieldName]: newValue};
140
+ if (null === section && null === subsection) {
141
+ let updatedConfig = { ...config, [fieldName]: newValue }
123
142
 
124
- if ( 'filterColumn' === fieldName ) {
125
- updatedConfig.filterValue = '';
143
+ if ('filterColumn' === fieldName) {
144
+ updatedConfig.filterValue = ''
126
145
  }
127
146
 
128
- enforceRestrictions(updatedConfig);
147
+ enforceRestrictions(updatedConfig)
129
148
 
130
- updateConfig(updatedConfig);
149
+ updateConfig(updatedConfig)
131
150
  return
132
151
  }
133
152
 
134
- const isArray = Array.isArray(config[section]);
153
+ const isArray = Array.isArray(config[section])
135
154
 
136
- let sectionValue = isArray ? [...config[section], newValue] : {...config[section], [fieldName]: newValue};
155
+ let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
137
156
 
138
- if(null !== subsection) {
139
- if(isArray) {
157
+ if (null !== subsection) {
158
+ if (isArray) {
140
159
  sectionValue = [...config[section]]
141
- sectionValue[subsection] = {...sectionValue[subsection], [fieldName]: newValue}
142
- } else if(typeof newValue === "string") {
160
+ sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
161
+ } else if (typeof newValue === 'string') {
143
162
  sectionValue[subsection] = newValue
144
163
  } else {
145
- sectionValue = {...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue}}
164
+ sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
146
165
  }
147
166
  }
148
167
 
149
- let updatedConfig = {...config, [section]: sectionValue};
168
+ let updatedConfig = { ...config, [section]: sectionValue }
150
169
 
151
- enforceRestrictions(updatedConfig);
170
+ enforceRestrictions(updatedConfig)
152
171
 
153
172
  updateConfig(updatedConfig)
154
173
  }
155
174
 
156
175
  const missingRequiredSections = () => {
157
176
  //Whether to show error message if something is required to show a data-bite and isn't filled in
158
- return false;
159
- };
177
+ return false
178
+ }
160
179
 
161
180
  useEffect(() => {
162
181
  // Pass up to Editor if needed
163
- if(setParentConfig) {
182
+ if (setParentConfig) {
164
183
  const newConfig = convertStateToConfig()
165
184
 
166
185
  setParentConfig(newConfig)
@@ -169,36 +188,46 @@ const EditorPanel = memo(() => {
169
188
  }, [config])
170
189
 
171
190
  const onBackClick = () => {
172
- setDisplayPanel(!displayPanel);
191
+ setDisplayPanel(!displayPanel)
173
192
  }
174
193
 
175
194
  const Error = () => {
176
195
  return (
177
- <section className="waiting">
178
- <section className="waiting-container">
196
+ <section className='waiting'>
197
+ <section className='waiting-container'>
179
198
  <h3>Error With Configuration</h3>
180
199
  <p>{config.runtime.editorErrorMessage}</p>
181
200
  </section>
182
201
  </section>
183
- );
202
+ )
184
203
  }
185
204
 
186
205
  const Confirm = () => {
187
206
  return (
188
- <section className="waiting">
189
- <section className="waiting-container">
207
+ <section className='waiting'>
208
+ <section className='waiting-container'>
190
209
  <h3>Finish Configuring</h3>
191
210
  <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
192
- <button className="btn" style={{margin: '1em auto'}} disabled={missingRequiredSections()} onClick={(e) => {e.preventDefault(); updateConfig({...config, newViz: false})}}>I'm Done</button>
211
+ <button
212
+ className='btn'
213
+ style={{ margin: '1em auto' }}
214
+ disabled={missingRequiredSections()}
215
+ onClick={e => {
216
+ e.preventDefault()
217
+ updateConfig({ ...config, newViz: false })
218
+ }}
219
+ >
220
+ I'm Done
221
+ </button>
193
222
  </section>
194
223
  </section>
195
- );
224
+ )
196
225
  }
197
226
 
198
227
  const convertStateToConfig = () => {
199
228
  let strippedState = JSON.parse(JSON.stringify(config))
200
229
  //if(false === missingRequiredSections()) {
201
- //strippedState.newViz
230
+ //strippedState.newViz
202
231
  //}
203
232
  delete strippedState.runtime
204
233
 
@@ -206,364 +235,452 @@ const EditorPanel = memo(() => {
206
235
  }
207
236
 
208
237
  // Filters -----------------------------------------------
209
- const removeFilter = (index) => {
210
- let filters = [...config.filters];
238
+ const removeFilter = index => {
239
+ let filters = [...config.filters]
211
240
 
212
- filters.splice(index, 1);
241
+ filters.splice(index, 1)
213
242
 
214
- updateConfig({...config, filters})
243
+ updateConfig({ ...config, filters })
215
244
  }
216
245
 
217
246
  const updateFilterProp = (name, index, value) => {
218
- let filters = [...config.filters];
247
+ let filters = [...config.filters]
219
248
 
220
- filters[index][name] = value;
249
+ filters[index][name] = value
221
250
 
222
- updateConfig({...config, filters});
251
+ updateConfig({ ...config, filters })
223
252
  }
224
253
 
225
254
  const addNewFilter = () => {
226
- let filters = config.filters ? [...config.filters] : [];
255
+ let filters = config.filters ? [...config.filters] : []
227
256
 
228
- filters.push({values: []});
257
+ filters.push({ values: [] })
229
258
 
230
- updateConfig({...config, filters});
259
+ updateConfig({ ...config, filters })
231
260
  }
232
261
 
233
262
  const getColumns = (filter = true) => {
234
263
  let columns = {}
235
- if(data.length){
236
- data.map(row => {
237
- return Object.keys(row).forEach(columnName => columns[columnName] = true)
238
- })
239
- }
240
-
264
+ if (data.length) {
265
+ data.map(row => {
266
+ return Object.keys(row).forEach(columnName => (columns[columnName] = true))
267
+ })
268
+ }
241
269
 
242
270
  return Object.keys(columns)
243
271
  }
244
272
 
245
- const getFilterColumnValues = (index) => {
273
+ const getFilterColumnValues = index => {
246
274
  let filterDataOptions = []
247
- const filterColumnName = config.filters[index].columnName;
275
+ const filterColumnName = config.filters[index].columnName
248
276
  if (data && filterColumnName) {
249
- data.forEach(function(row) {
250
- if ( undefined !== row[filterColumnName] && -1 === filterDataOptions.indexOf(row[filterColumnName]) ) {
251
- filterDataOptions.push(row[filterColumnName]);
277
+ data.forEach(function (row) {
278
+ if (undefined !== row[filterColumnName] && -1 === filterDataOptions.indexOf(row[filterColumnName])) {
279
+ filterDataOptions.push(row[filterColumnName])
252
280
  }
253
281
  })
254
- filterDataOptions.sort();
282
+ filterDataOptions.sort()
255
283
  }
256
- return filterDataOptions;
284
+ return filterDataOptions
257
285
  }
258
286
 
259
287
  // Dynamic Images ----------------------------------------
260
288
  const updateDynamicImage = (name, index, subindex = null, value) => {
261
- let imageOptions = [...config.imageData.options];
262
- null === subindex ? imageOptions[index][name] = value : imageOptions[index].arguments[subindex][name] = value
289
+ let imageOptions = [...config.imageData.options]
290
+ null === subindex ? (imageOptions[index][name] = value) : (imageOptions[index].arguments[subindex][name] = value)
263
291
 
264
- let payload = {...config.imageData, options: imageOptions}
265
- updateConfig({...config, imageData: payload});
292
+ let payload = { ...config.imageData, options: imageOptions }
293
+ updateConfig({ ...config, imageData: payload })
266
294
  }
267
295
 
268
296
  const setDynamicArgument = (optionIndex, name, value) => {
269
297
  let imageArguments = [...config.imageData.options[optionIndex].arguments]
270
- imageArguments[1] = {...imageArguments[1], [name]: value }
271
- let argumentsPayload = {...config.imageData.options[optionIndex], arguments: imageArguments}
298
+ imageArguments[1] = { ...imageArguments[1], [name]: value }
299
+ let argumentsPayload = { ...config.imageData.options[optionIndex], arguments: imageArguments }
272
300
  let optionsPayload = [...config.imageData.options]
273
- optionsPayload[optionIndex] = argumentsPayload
274
- let payload = {...config.imageData, options: optionsPayload}
275
- updateConfig({...config, imageData: payload})
301
+ optionsPayload[optionIndex] = argumentsPayload
302
+ let payload = { ...config.imageData, options: optionsPayload }
303
+ updateConfig({ ...config, imageData: payload })
276
304
  }
277
305
 
278
- const removeDynamicArgument = (optionIndex) => {
306
+ const removeDynamicArgument = optionIndex => {
279
307
  if (config.imageData.options[optionIndex].arguments.length > 1) {
280
308
  let imageArguments = [...config.imageData.options[optionIndex].arguments]
281
- imageArguments.pop()
282
- let argumentsPayload = {...config.imageData.options[optionIndex], arguments: imageArguments}
309
+ imageArguments.pop()
310
+ let argumentsPayload = { ...config.imageData.options[optionIndex], arguments: imageArguments }
283
311
  let optionsPayload = [...config.imageData.options]
284
- optionsPayload[optionIndex] = argumentsPayload
285
- let payload = {...config.imageData, options: optionsPayload}
286
- updateConfig({...config, imageData: payload})
312
+ optionsPayload[optionIndex] = argumentsPayload
313
+ let payload = { ...config.imageData, options: optionsPayload }
314
+ updateConfig({ ...config, imageData: payload })
287
315
  }
288
316
  }
289
317
 
290
318
  const addDynamicImage = () => {
291
- let imageOptions = config.imageData.options ? [ ...config.imageData.options ] : []
292
- imageOptions.push({ source: '', arguments: [{ operator: '', threshold: ''}], alt: '', secondArgument: false })
319
+ let imageOptions = config.imageData.options ? [...config.imageData.options] : []
320
+ imageOptions.push({ source: '', arguments: [{ operator: '', threshold: '' }], alt: '', secondArgument: false })
293
321
 
294
- let payload = {...config.imageData, options: imageOptions}
295
- updateConfig({...config, imageData: payload})
322
+ let payload = { ...config.imageData, options: imageOptions }
323
+ updateConfig({ ...config, imageData: payload })
296
324
  }
297
325
 
298
- const removeDynamicImage = (index) => {
299
- let imageOptions = [...config.imageData.options];
300
- imageOptions.splice(index, 1);
326
+ const removeDynamicImage = index => {
327
+ let imageOptions = [...config.imageData.options]
328
+ imageOptions.splice(index, 1)
301
329
 
302
- let payload = {...config.imageData, options: imageOptions}
303
- updateConfig({...config, imageData: payload});
330
+ let payload = { ...config.imageData, options: imageOptions }
331
+ updateConfig({ ...config, imageData: payload })
304
332
  }
305
333
 
306
334
  // General -----------------------------------------------
307
- if(loading) {
335
+ if (loading) {
308
336
  return null
309
337
  }
310
338
 
311
339
  return (
312
- <ErrorBoundary component="EditorPanel">
313
- {!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error /> }
340
+ <ErrorBoundary component='EditorPanel'>
341
+ {!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error />}
314
342
  {(!config.dataColumn || !config.dataFunction) && <Confirm />}
315
343
  <button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick} />
316
344
  <section className={displayPanel ? 'editor-panel cove' : 'hidden editor-panel cove'}>
317
- <div className="heading-2">Configure Data Bite</div>
318
- <section className="form-container">
345
+ <div className='heading-2'>Configure Data Bite</div>
346
+ <section className='form-container'>
319
347
  <form>
320
348
  <Accordion allowZeroExpanded={true}>
321
- <AccordionItem> {/* General */}
349
+ <AccordionItem>
350
+ {' '}
351
+ {/* General */}
322
352
  <AccordionItemHeading>
323
- <AccordionItemButton>
324
- General
325
- </AccordionItemButton>
353
+ <AccordionItemButton>General</AccordionItemButton>
326
354
  </AccordionItemHeading>
327
355
  <AccordionItemPanel>
328
- <Select value={config.biteStyle} fieldName="biteStyle" label="Data Bite Style" updateField={updateField} options={BITE_LOCATIONS} initial="Select" />
329
- <TextField value={config.title} fieldName="title" label="Title" placeholder="Data Bite Title" updateField={updateField} />
330
- <TextField type="textarea" value={config.biteBody} fieldName="biteBody" label="Message" updateField={updateField} tooltip={
331
- <Tooltip style={{textTransform: 'none'}}>
332
- <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
333
- <Tooltip.Content>
334
- <p>Enter the message text for the visualization. The following HTML tags are supported: strong, em, sup, and sub.</p>
335
- </Tooltip.Content>
336
- </Tooltip>
337
- }/>
338
- <TextField value={config.subtext} fieldName="subtext" label="Subtext/Citation" placeholder="Data Bite Subtext or Citation" updateField={updateField} tooltip={
339
- <Tooltip style={{textTransform: 'none'}}>
340
- <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
341
- <Tooltip.Content>
342
- <p>
343
- Enter supporting text to display below the data visualization, if applicable. The following HTML tags are supported: strong, em, sup, and sub.</p>
344
- </Tooltip.Content>
345
- </Tooltip>
346
- }/>
347
- <CheckBox value={config.general.isCompactStyle} section="general" fieldName="isCompactStyle" label="Compact Style" updateField={updateField} tooltip={
348
- <Tooltip style={{ textTransform: 'none' }}>
349
- <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }} /></Tooltip.Target>
350
- <Tooltip.Content>
351
- <p>Simple data bite style that formats certain elements for a more compact view.</p>
352
- </Tooltip.Content>
353
- </Tooltip>
354
- } />
356
+ <Select value={config.biteStyle} fieldName='biteStyle' label='Data Bite Style' updateField={updateField} options={BITE_LOCATIONS} initial='Select' />
357
+ <TextField value={config.title} fieldName='title' label='Title' placeholder='Data Bite Title' updateField={updateField} />
358
+
359
+ <TextField
360
+ type='textarea'
361
+ value={config.biteBody}
362
+ fieldName='biteBody'
363
+ label='Message'
364
+ updateField={updateField}
365
+ tooltip={
366
+ <Tooltip style={{ textTransform: 'none' }}>
367
+ <Tooltip.Target>
368
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
369
+ </Tooltip.Target>
370
+ <Tooltip.Content>
371
+ <p>Enter the message text for the visualization. The following HTML tags are supported: strong, em, sup, and sub.</p>
372
+ </Tooltip.Content>
373
+ </Tooltip>
374
+ }
375
+ />
376
+ <TextField
377
+ value={config.subtext}
378
+ fieldName='subtext'
379
+ label='Subtext/Citation'
380
+ placeholder='Data Bite Subtext or Citation'
381
+ updateField={updateField}
382
+ tooltip={
383
+ <Tooltip style={{ textTransform: 'none' }}>
384
+ <Tooltip.Target>
385
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
386
+ </Tooltip.Target>
387
+ <Tooltip.Content>
388
+ <p>Enter supporting text to display below the data visualization, if applicable. The following HTML tags are supported: strong, em, sup, and sub.</p>
389
+ </Tooltip.Content>
390
+ </Tooltip>
391
+ }
392
+ />
355
393
  </AccordionItemPanel>
356
394
  </AccordionItem>
357
395
 
358
- <AccordionItem> {/*Data*/}
396
+ <AccordionItem>
397
+ {' '}
398
+ {/*Data*/}
359
399
  <AccordionItemHeading>
360
- <AccordionItemButton>
361
- Data {(!config.dataColumn || !config.dataFunction) && <WarningImage width="25" className="warning-icon" />}
362
- </AccordionItemButton>
400
+ <AccordionItemButton>Data {(!config.dataColumn || !config.dataFunction) && <WarningImage width='25' className='warning-icon' />}</AccordionItemButton>
363
401
  </AccordionItemHeading>
364
402
  <AccordionItemPanel>
365
- <ul className="column-edit">
366
- <li className="two-col">
367
- <Select value={config.dataColumn || ""} fieldName="dataColumn" label="Data Column" updateField={updateField} initial="Select" required={true} options={getColumns()} />
368
- <Select value={config.dataFunction || ""} fieldName="dataFunction" label="Data Function" updateField={updateField} initial="Select" required={true} options={DATA_FUNCTIONS} />
403
+ <ul className='column-edit'>
404
+ <li className='two-col'>
405
+ <Select value={config.dataColumn || ''} fieldName='dataColumn' label='Data Column' updateField={updateField} initial='Select' required={true} options={getColumns()} />
406
+ <Select value={config.dataFunction || ''} fieldName='dataFunction' label='Data Function' updateField={updateField} initial='Select' required={true} options={DATA_FUNCTIONS} />
369
407
  </li>
370
408
  </ul>
371
- <span className="divider-heading">Number Formatting</span>
372
- <ul className="column-edit">
373
- <li className="three-col">
374
- <TextField value={config.dataFormat.prefix} section="dataFormat" fieldName="prefix" label="Prefix" updateField={updateField} />
375
- <TextField value={config.dataFormat.suffix} section="dataFormat" fieldName="suffix" label="Suffix" updateField={updateField} />
376
- <TextField type="number" value={config.dataFormat.roundToPlace} section="dataFormat" fieldName="roundToPlace" label="Round" updateField={updateField} min='0' max='99' />
409
+ <span className='divider-heading'>Number Formatting</span>
410
+ <ul className='column-edit'>
411
+ <li className='three-col'>
412
+ <TextField value={config.dataFormat.prefix} section='dataFormat' fieldName='prefix' label='Prefix' updateField={updateField} />
413
+ <TextField value={config.dataFormat.suffix} section='dataFormat' fieldName='suffix' label='Suffix' updateField={updateField} />
414
+ <TextField type='number' value={config.dataFormat.roundToPlace} section='dataFormat' fieldName='roundToPlace' label='Round' updateField={updateField} min='0' max='99' />
377
415
  </li>
378
416
  </ul>
379
- <CheckBox value={config.dataFormat.commas} section="dataFormat" fieldName="commas" label="Add commas" updateField={updateField} />
380
- <CheckBox value={config.dataFormat.ignoreZeros} section="dataFormat" fieldName="ignoreZeros" label="Ignore Zeros" updateField={updateField} />
381
- <hr className="accordion__divider" />
417
+ <CheckBox value={config.dataFormat.commas} section='dataFormat' fieldName='commas' label='Add commas' updateField={updateField} />
418
+ <CheckBox value={config.dataFormat.ignoreZeros} section='dataFormat' fieldName='ignoreZeros' label='Ignore Zeros' updateField={updateField} />
419
+ <hr className='accordion__divider' />
382
420
 
383
- <label style={{marginBottom: '1rem'}}>
384
- <span className="edit-label">
421
+ <label style={{ marginBottom: '1rem' }}>
422
+ <span className='edit-label'>
385
423
  Data Point Filters
386
424
  <Tooltip style={{ textTransform: 'none' }}>
387
- <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
425
+ <Tooltip.Target>
426
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
427
+ </Tooltip.Target>
388
428
  <Tooltip.Content>
389
- <p>To refine the highlighted data point, specify one or more filters (e.g., "Male" and
390
- "Female" for a column called "Sex").</p>
429
+ <p>To refine the highlighted data point, specify one or more filters (e.g., "Male" and "Female" for a column called "Sex").</p>
391
430
  </Tooltip.Content>
392
431
  </Tooltip>
393
432
  </span>
394
433
  </label>
395
- {
396
- config.filters &&
397
- <ul className="filters-list">
434
+ {config.filters && (
435
+ <ul className='filters-list'>
398
436
  {config.filters.map((filter, index) => (
399
- <fieldset className="edit-block" key={index}>
400
- <button type="button" className="remove-column" onClick={() => {removeFilter(index)}}>Remove</button>
437
+ <fieldset className='edit-block' key={index}>
438
+ <button
439
+ type='button'
440
+ className='remove-column'
441
+ onClick={() => {
442
+ removeFilter(index)
443
+ }}
444
+ >
445
+ Remove
446
+ </button>
401
447
  <label>
402
- <span className="edit-label column-heading">Column</span>
403
- <select value={filter.columnName ? filter.columnName : ''} onChange={(e) => {updateFilterProp('columnName', index, e.target.value)}}>
404
- <option value="">- Select Option -</option>
448
+ <span className='edit-label column-heading'>Column</span>
449
+ <select
450
+ value={filter.columnName ? filter.columnName : ''}
451
+ onChange={e => {
452
+ updateFilterProp('columnName', index, e.target.value)
453
+ }}
454
+ >
455
+ <option value=''>- Select Option -</option>
405
456
  {getColumns().map((dataKey, index) => (
406
- <option value={dataKey} key={index}>{dataKey}</option>
457
+ <option value={dataKey} key={index}>
458
+ {dataKey}
459
+ </option>
407
460
  ))}
408
461
  </select>
409
462
  </label>
410
463
  <label>
411
- <span className="edit-label column-heading">Column Value</span>
412
- <select value={filter.columnValue} onChange={(e) => {updateFilterProp('columnValue', index, e.target.value)}}>
413
- <option value="">- Select Option -</option>
464
+ <span className='edit-label column-heading'>Column Value</span>
465
+ <select
466
+ value={filter.columnValue}
467
+ onChange={e => {
468
+ updateFilterProp('columnValue', index, e.target.value)
469
+ }}
470
+ >
471
+ <option value=''>- Select Option -</option>
414
472
  {getFilterColumnValues(index).map((dataKey, index) => (
415
- <option value={dataKey} key={index}>{dataKey}</option>
473
+ <option value={dataKey} key={index}>
474
+ {dataKey}
475
+ </option>
416
476
  ))}
417
477
  </select>
418
478
  </label>
419
479
  </fieldset>
420
480
  ))}
421
481
  </ul>
422
- }
423
- {(!config.filters || config.filters.length === 0) &&
482
+ )}
483
+ {(!config.filters || config.filters.length === 0) && (
424
484
  <div>
425
- <fieldset className="edit-block">
426
- <p style={{textAlign: "center"}}>There are currently no filters.</p>
485
+ <fieldset className='edit-block'>
486
+ <p style={{ textAlign: 'center' }}>There are currently no filters.</p>
427
487
  </fieldset>
428
488
  </div>
429
- }
430
- <button type="button" onClick={addNewFilter} className="btn full-width">Add Filter</button>
489
+ )}
490
+ <button type='button' onClick={addNewFilter} className='btn full-width'>
491
+ Add Filter
492
+ </button>
431
493
  </AccordionItemPanel>
432
494
  </AccordionItem>
433
495
 
434
- <AccordionItem> {/*Visual*/}
496
+ <AccordionItem>
497
+ {' '}
498
+ {/*Visual*/}
435
499
  <AccordionItemHeading>
436
- <AccordionItemButton>
437
- Visual
438
- </AccordionItemButton>
500
+ <AccordionItemButton>Visual</AccordionItemButton>
439
501
  </AccordionItemHeading>
440
502
  <AccordionItemPanel>
441
- <TextField type="number" value={config.biteFontSize} fieldName="biteFontSize" label="Bite Font Size" updateField={updateField} min="16" max="65" />
442
- <Select value={config.fontSize} fieldName="fontSize" label="Overall Font Size" updateField={updateField} options={['small', 'medium', 'large']} />
443
- <CheckBox value={config.shadow} fieldName="shadow" label="Display Shadow" updateField={updateField} />
444
- <label className="header">
445
- <span className="edit-label">Theme</span>
446
- <ul className="color-palette">
447
- {headerColors.map( (palette) => (
448
- <li title={ palette } key={ palette } onClick={ () => { updateConfig({...config, theme: palette})}} className={ config.theme === palette ? "selected " + palette : palette} />
503
+ <TextField type='number' value={config.biteFontSize} fieldName='biteFontSize' label='Bite Font Size' updateField={updateField} min='17' max='65' />
504
+ <Select value={config.fontSize} fieldName='fontSize' label='Overall Font Size' updateField={updateField} options={['small', 'medium', 'large']} />
505
+ <div className='checkbox-group'>
506
+ <CheckBox value={config.visual?.border} section='visual' fieldName='border' label='Display Border' updateField={updateField} />
507
+ <CheckBox value={config.visual?.borderColorTheme} section='visual' fieldName='borderColorTheme' label='Use Border Color Theme' updateField={updateField} />
508
+ <CheckBox value={config.visual?.accent} section='visual' fieldName='accent' label='Use Accent Style' updateField={updateField} />
509
+ <CheckBox value={config.visual?.background} section='visual' fieldName='background' label='Use Theme Background Color' updateField={updateField} />
510
+ <CheckBox value={config.visual?.hideBackgroundColor} section='visual' fieldName='hideBackgroundColor' label='Hide Background Color' updateField={updateField} />
511
+ </div>
512
+ <label>
513
+ <span className='edit-label'>Theme</span>
514
+ <ul className='color-palette'>
515
+ {headerColors.map(palette => (
516
+ <li
517
+ title={palette}
518
+ key={palette}
519
+ onClick={() => {
520
+ updateConfig({ ...config, theme: palette })
521
+ }}
522
+ className={config.theme === palette ? 'selected ' + palette : palette}
523
+ />
449
524
  ))}
450
525
  </ul>
451
526
  </label>
452
- {/* <div className="cove-accordion__panel-section">
453
- <CheckBox value={config.visual.border} section="visual" fieldName="border" label="Display Border" updateField={updateField} />
454
- <CheckBox value={config.visual.borderColorTheme} section="visual" fieldName="borderColorTheme" label="Use Border Color Theme" updateField={updateField} />
455
- <CheckBox value={config.visual.accent} section="visual" fieldName="accent" label="Use Accent Style" updateField={updateField} />
456
- <CheckBox value={config.visual.background} section="visual" fieldName="background" label="Use Theme Background Color" updateField={updateField} />
457
- <CheckBox value={config.visual.hideBackgroundColor} section="visual" fieldName="hideBackgroundColor" label="Hide Background Color" updateField={updateField} />
458
- </div> */}
459
527
  </AccordionItemPanel>
460
528
  </AccordionItem>
461
529
 
462
- {['title', 'body', 'graphic'].includes(config.biteStyle) &&
463
- <AccordionItem> {/*Image & Dynamic Images*/}
530
+ {['title', 'body', 'graphic'].includes(config.biteStyle) && (
531
+ <AccordionItem>
532
+ {' '}
533
+ {/*Image & Dynamic Images*/}
464
534
  <AccordionItemHeading>
465
535
  <AccordionItemButton>
466
- Image{[ 'dynamic' ].includes(config.imageData.display) && 's'}
536
+ Image
537
+ {['dynamic'].includes(config.imageData.display) && 's'}
467
538
  </AccordionItemButton>
468
539
  </AccordionItemHeading>
469
-
470
540
  <AccordionItemPanel>
471
- <Select value={config.imageData.display || ""} section="imageData" fieldName="display" label="Image Display Type" updateField={updateField} options={['none', 'static', 'dynamic']} />
472
- <Select value={config.bitePosition || ""} fieldName="bitePosition" label="Image/Graphic Position" updateField={updateField} initial="Select" options={IMAGE_POSITIONS} />
473
- {['static'].includes(config.imageData.display) &&
541
+ <Select value={config.imageData.display || ''} section='imageData' fieldName='display' label='Image Display Type' updateField={updateField} options={['none', 'static', 'dynamic']} />
542
+ <Select value={config.bitePosition || ''} fieldName='bitePosition' label='Image/Graphic Position' updateField={updateField} initial='Select' options={IMAGE_POSITIONS} />
543
+ {['static'].includes(config.imageData.display) && (
474
544
  <>
475
- <TextField value={config.imageData.url} section="imageData" fieldName="url" label="Image URL" updateField={updateField} />
476
- <TextField value={config.imageData.alt} section="imageData" fieldName="alt" label="Alt Text" updateField={updateField} />
545
+ <TextField value={config.imageData.url} section='imageData' fieldName='url' label='Image URL' updateField={updateField} />
546
+ <TextField value={config.imageData.alt} section='imageData' fieldName='alt' label='Alt Text' updateField={updateField} />
477
547
  </>
478
- }
548
+ )}
479
549
 
480
- {[ 'dynamic' ].includes(config.imageData.display) &&
550
+ {['dynamic'].includes(config.imageData.display) && (
481
551
  <>
482
- <TextField value={config.imageData.url || ""} section="imageData" fieldName="url" label="Image URL (default)" updateField={updateField} />
483
- <TextField value={config.imageData.alt} section="imageData" fieldName="alt" label="Alt Text (default)" updateField={updateField} />
552
+ <TextField value={config.imageData.url || ''} section='imageData' fieldName='url' label='Image URL (default)' updateField={updateField} />
553
+ <TextField value={config.imageData.alt} section='imageData' fieldName='alt' label='Alt Text (default)' updateField={updateField} />
484
554
 
485
- <hr className="accordion__divider" />
555
+ <hr className='accordion__divider' />
486
556
 
487
- {(!config.imageData.options || config.imageData.options.length === 0) && <p style={{textAlign: "center"}}>There are currently no dynamic images.</p>}
488
- {config.imageData.options && config.imageData.options.length > 0 &&
557
+ {(!config.imageData.options || config.imageData.options.length === 0) && <p style={{ textAlign: 'center' }}>There are currently no dynamic images.</p>}
558
+ {config.imageData.options && config.imageData.options.length > 0 && (
489
559
  <>
490
560
  <ul>
491
561
  {config.imageData.options.map((option, index) => (
492
- <fieldset className="edit-block" key={index}>
493
- <button type="button" className="remove-column" onClick={() => {removeDynamicImage(index)}}>Remove</button>
562
+ <fieldset className='edit-block' key={index}>
563
+ <button
564
+ type='button'
565
+ className='remove-column'
566
+ onClick={() => {
567
+ removeDynamicImage(index)
568
+ }}
569
+ >
570
+ Remove
571
+ </button>
494
572
  <label>
495
- <span className="edit-label column-heading"><strong>{'Image #' + (index + 1)}</strong></span>
496
-
497
- <div className="accordion__panel-row align-center">
498
- <div className="accordion__panel-col flex-auto">
499
- If Value
500
- </div>
501
- <div className="accordion__panel-col flex-auto">
502
- <select value={option.arguments[0]?.operator || ""} onChange={(e) => {updateDynamicImage('operator', index, 0, e.target.value)}}>
503
- <option value="" disabled/>
573
+ <span className='edit-label column-heading'>
574
+ <strong>{'Image #' + (index + 1)}</strong>
575
+ </span>
576
+
577
+ <div className='accordion__panel-row align-center'>
578
+ <div className='accordion__panel-col flex-auto'>If Value</div>
579
+ <div className='accordion__panel-col flex-auto'>
580
+ <select
581
+ value={option.arguments[0]?.operator || ''}
582
+ onChange={e => {
583
+ updateDynamicImage('operator', index, 0, e.target.value)
584
+ }}
585
+ >
586
+ <option value='' disabled />
504
587
  {DATA_OPERATORS.map((operator, index) => (
505
- <option value={operator} key={index}>{operator}</option>
588
+ <option value={operator} key={index}>
589
+ {operator}
590
+ </option>
506
591
  ))}
507
592
  </select>
508
593
  </div>
509
- <div className="accordion__panel-col flex-grow flex-shrink">
510
- <input type="number" value={option.arguments[0]?.threshold || ""} onChange={(e) => {updateDynamicImage('threshold', index, 0, e.target.value)}} />
594
+ <div className='accordion__panel-col flex-grow flex-shrink'>
595
+ <input
596
+ type='number'
597
+ value={option.arguments[0]?.threshold || ''}
598
+ onChange={e => {
599
+ updateDynamicImage('threshold', index, 0, e.target.value)
600
+ }}
601
+ />
511
602
  </div>
512
603
  </div>
513
604
 
514
- <div className="accordion__panel-row mb-2 align-center">
515
- <div className="accordion__panel-col flex-grow">
516
- <select className='border-dashed text-center' value={option.secondArgument ? 'and' : 'then'} onChange={(e) => {
517
- if ('then' === e.target.value) {updateDynamicImage('secondArgument', index, null,false); removeDynamicArgument(index)}
518
- if ('and' === e.target.value) {updateDynamicImage('secondArgument', index, null,true)}
519
- }}>
605
+ <div className='accordion__panel-row mb-2 align-center'>
606
+ <div className='accordion__panel-col flex-grow'>
607
+ <select
608
+ className='border-dashed text-center'
609
+ value={option.secondArgument ? 'and' : 'then'}
610
+ onChange={e => {
611
+ if ('then' === e.target.value) {
612
+ updateDynamicImage('secondArgument', index, null, false)
613
+ removeDynamicArgument(index)
614
+ }
615
+ if ('and' === e.target.value) {
616
+ updateDynamicImage('secondArgument', index, null, true)
617
+ }
618
+ }}
619
+ >
520
620
  <option value={'then'}>Then</option>
521
621
  <option value={'and'}>And</option>
522
622
  </select>
523
623
  </div>
524
624
  </div>
525
625
 
526
- {option.secondArgument && true === option.secondArgument &&
626
+ {option.secondArgument && true === option.secondArgument && (
527
627
  <>
528
- <div className="accordion__panel-row align-center">
529
- <div className="accordion__panel-col flex-auto">
530
- If Value
531
- </div>
532
- <div className="accordion__panel-col flex-auto">
533
- <select value={option.arguments[1]?.operator || ""} onChange={(e) => {setDynamicArgument(index, 'operator', e.target.value)}}>
534
- <option value="" disabled/>
628
+ <div className='accordion__panel-row align-center'>
629
+ <div className='accordion__panel-col flex-auto'>If Value</div>
630
+ <div className='accordion__panel-col flex-auto'>
631
+ <select
632
+ value={option.arguments[1]?.operator || ''}
633
+ onChange={e => {
634
+ setDynamicArgument(index, 'operator', e.target.value)
635
+ }}
636
+ >
637
+ <option value='' disabled />
535
638
  {DATA_OPERATORS.map((operator, index) => (
536
- <option value={operator} key={index}>{operator}</option>
639
+ <option value={operator} key={index}>
640
+ {operator}
641
+ </option>
537
642
  ))}
538
643
  </select>
539
644
  </div>
540
- <div className="accordion__panel-col flex-grow flex-shrink">
541
- <input type="number" value={option.arguments[1]?.threshold || ""} onChange={(e) => {setDynamicArgument(index, 'threshold', e.target.value)}} />
645
+ <div className='accordion__panel-col flex-grow flex-shrink'>
646
+ <input
647
+ type='number'
648
+ value={option.arguments[1]?.threshold || ''}
649
+ onChange={e => {
650
+ setDynamicArgument(index, 'threshold', e.target.value)
651
+ }}
652
+ />
542
653
  </div>
543
654
  </div>
544
- <div className="accordion__panel-row mb-2 align-center text-center text-capitalize">
545
- <div className="accordion__panel-col flex-grow">
546
- Then
547
- </div>
655
+ <div className='accordion__panel-row mb-2 align-center text-center text-capitalize'>
656
+ <div className='accordion__panel-col flex-grow'>Then</div>
548
657
  </div>
549
658
  </>
550
- }
551
-
552
- <div className="accordion__panel-row mb-2 align-center">
553
- <div className="accordion__panel-col flex-auto">
554
- Show
555
- </div>
556
- <div className="accordion__panel-col flex-grow">
557
- <input type="text" value={option.source || ""} onChange={(e) => {updateDynamicImage('source', index, null, e.target.value)}} />
659
+ )}
660
+
661
+ <div className='accordion__panel-row mb-2 align-center'>
662
+ <div className='accordion__panel-col flex-auto'>Show</div>
663
+ <div className='accordion__panel-col flex-grow'>
664
+ <input
665
+ type='text'
666
+ value={option.source || ''}
667
+ onChange={e => {
668
+ updateDynamicImage('source', index, null, e.target.value)
669
+ }}
670
+ />
558
671
  </div>
559
672
  </div>
560
673
 
561
- <div className="accordion__panel-row mb-2 align-center">
562
- <div className="accordion__panel-col flex-auto">
563
- Alt Text
564
- </div>
565
- <div className="accordion__panel-col flex-grow">
566
- <input type="text" value={option.alt || ""} onChange={(e) => {updateDynamicImage('alt', index, null, e.target.value)}} />
674
+ <div className='accordion__panel-row mb-2 align-center'>
675
+ <div className='accordion__panel-col flex-auto'>Alt Text</div>
676
+ <div className='accordion__panel-col flex-grow'>
677
+ <input
678
+ type='text'
679
+ value={option.alt || ''}
680
+ onChange={e => {
681
+ updateDynamicImage('alt', index, null, e.target.value)
682
+ }}
683
+ />
567
684
  </div>
568
685
  </div>
569
686
  </label>
@@ -571,14 +688,15 @@ const EditorPanel = memo(() => {
571
688
  ))}
572
689
  </ul>
573
690
  </>
574
- }
575
- <button type="button" onClick={addDynamicImage} className="btn full-width">Add Dynamic Image</button>
691
+ )}
692
+ <button type='button' onClick={addDynamicImage} className='btn full-width'>
693
+ Add Dynamic Image
694
+ </button>
576
695
  </>
577
- }
696
+ )}
578
697
  </AccordionItemPanel>
579
698
  </AccordionItem>
580
- }
581
-
699
+ )}
582
700
  </Accordion>
583
701
  </form>
584
702
  </section>
@@ -587,4 +705,4 @@ const EditorPanel = memo(() => {
587
705
  )
588
706
  })
589
707
 
590
- export default EditorPanel;
708
+ export default EditorPanel