@cdc/data-bite 4.22.10 → 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,12 +1,6 @@
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'
@@ -16,151 +10,176 @@ 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,195 +235,157 @@ 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 &&
314
- config.runtime &&
315
- config.runtime.editorErrorMessage && <Error />}
340
+ <ErrorBoundary component='EditorPanel'>
341
+ {!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error />}
316
342
  {(!config.dataColumn || !config.dataFunction) && <Confirm />}
317
- <button
318
- className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`}
319
- title={displayPanel ? `Collapse Editor` : `Expand Editor`}
320
- onClick={onBackClick}
321
- />
322
- <section
323
- className={
324
- displayPanel ? "editor-panel cove" : "hidden editor-panel cove"
325
- }
326
- >
327
- <div className="heading-2">Configure Data Bite</div>
328
- <section className="form-container">
343
+ <button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick} />
344
+ <section className={displayPanel ? 'editor-panel cove' : 'hidden editor-panel cove'}>
345
+ <div className='heading-2'>Configure Data Bite</div>
346
+ <section className='form-container'>
329
347
  <form>
330
348
  <Accordion allowZeroExpanded={true}>
331
349
  <AccordionItem>
332
- {" "}
350
+ {' '}
333
351
  {/* General */}
334
352
  <AccordionItemHeading>
335
353
  <AccordionItemButton>General</AccordionItemButton>
336
354
  </AccordionItemHeading>
337
355
  <AccordionItemPanel>
338
- <Select
339
- value={config.biteStyle}
340
- fieldName="biteStyle"
341
- label="Data Bite Style"
342
- updateField={updateField}
343
- options={BITE_LOCATIONS}
344
- initial="Select"
345
- />
346
- <TextField
347
- value={config.title}
348
- fieldName="title"
349
- label="Title"
350
- placeholder="Data Bite Title"
351
- updateField={updateField}
352
- />
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} />
353
358
 
354
359
  <TextField
355
- type="textarea"
360
+ type='textarea'
356
361
  value={config.biteBody}
357
- fieldName="biteBody"
358
- label="Message"
362
+ fieldName='biteBody'
363
+ label='Message'
359
364
  updateField={updateField}
360
365
  tooltip={
361
- <Tooltip style={{ textTransform: "none" }}>
366
+ <Tooltip style={{ textTransform: 'none' }}>
362
367
  <Tooltip.Target>
363
- <Icon
364
- display="question"
365
- style={{ marginLeft: "0.5rem" }}
366
- />
368
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
367
369
  </Tooltip.Target>
368
370
  <Tooltip.Content>
369
- <p>
370
- Enter the message text for the visualization. The
371
- following HTML tags are supported: strong, em, sup,
372
- and sub.
373
- </p>
371
+ <p>Enter the message text for the visualization. The following HTML tags are supported: strong, em, sup, and sub.</p>
374
372
  </Tooltip.Content>
375
373
  </Tooltip>
376
374
  }
377
375
  />
378
376
  <TextField
379
377
  value={config.subtext}
380
- fieldName="subtext"
381
- label="Subtext/Citation"
382
- placeholder="Data Bite Subtext or Citation"
378
+ fieldName='subtext'
379
+ label='Subtext/Citation'
380
+ placeholder='Data Bite Subtext or Citation'
383
381
  updateField={updateField}
384
382
  tooltip={
385
- <Tooltip style={{ textTransform: "none" }}>
383
+ <Tooltip style={{ textTransform: 'none' }}>
386
384
  <Tooltip.Target>
387
- <Icon
388
- display="question"
389
- style={{ marginLeft: "0.5rem" }}
390
- />
385
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
391
386
  </Tooltip.Target>
392
387
  <Tooltip.Content>
393
- <p>
394
- Enter supporting text to display below the data
395
- visualization, if applicable. The following HTML
396
- tags are supported: strong, em, sup, and sub.
397
- </p>
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>
398
389
  </Tooltip.Content>
399
390
  </Tooltip>
400
391
  }
@@ -403,120 +394,65 @@ const EditorPanel = memo(() => {
403
394
  </AccordionItem>
404
395
 
405
396
  <AccordionItem>
406
- {" "}
397
+ {' '}
407
398
  {/*Data*/}
408
399
  <AccordionItemHeading>
409
- <AccordionItemButton>
410
- Data{" "}
411
- {(!config.dataColumn || !config.dataFunction) && (
412
- <WarningImage width="25" className="warning-icon" />
413
- )}
414
- </AccordionItemButton>
400
+ <AccordionItemButton>Data {(!config.dataColumn || !config.dataFunction) && <WarningImage width='25' className='warning-icon' />}</AccordionItemButton>
415
401
  </AccordionItemHeading>
416
402
  <AccordionItemPanel>
417
- <ul className="column-edit">
418
- <li className="two-col">
419
- <Select
420
- value={config.dataColumn || ""}
421
- fieldName="dataColumn"
422
- label="Data Column"
423
- updateField={updateField}
424
- initial="Select"
425
- required={true}
426
- options={getColumns()}
427
- />
428
- <Select
429
- value={config.dataFunction || ""}
430
- fieldName="dataFunction"
431
- label="Data Function"
432
- updateField={updateField}
433
- initial="Select"
434
- required={true}
435
- options={DATA_FUNCTIONS}
436
- />
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} />
437
407
  </li>
438
408
  </ul>
439
- <span className="divider-heading">Number Formatting</span>
440
- <ul className="column-edit">
441
- <li className="three-col">
442
- <TextField
443
- value={config.dataFormat.prefix}
444
- section="dataFormat"
445
- fieldName="prefix"
446
- label="Prefix"
447
- updateField={updateField}
448
- />
449
- <TextField
450
- value={config.dataFormat.suffix}
451
- section="dataFormat"
452
- fieldName="suffix"
453
- label="Suffix"
454
- updateField={updateField}
455
- />
456
- <TextField
457
- type="number"
458
- value={config.dataFormat.roundToPlace}
459
- section="dataFormat"
460
- fieldName="roundToPlace"
461
- label="Round"
462
- updateField={updateField}
463
- min="0"
464
- max="99"
465
- />
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' />
466
415
  </li>
467
416
  </ul>
468
- <CheckBox value={config.dataFormat.commas} section="dataFormat" fieldName="commas" label="Add commas" updateField={updateField} />
469
- <CheckBox value={config.dataFormat.ignoreZeros} section="dataFormat" fieldName="ignoreZeros" label="Ignore Zeros" updateField={updateField} />
470
- <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' />
471
420
 
472
- <label style={{ marginBottom: "1rem" }}>
473
- <span className="edit-label">
421
+ <label style={{ marginBottom: '1rem' }}>
422
+ <span className='edit-label'>
474
423
  Data Point Filters
475
- <Tooltip style={{ textTransform: "none" }}>
424
+ <Tooltip style={{ textTransform: 'none' }}>
476
425
  <Tooltip.Target>
477
- <Icon
478
- display="question"
479
- style={{ marginLeft: "0.5rem" }}
480
- />
426
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
481
427
  </Tooltip.Target>
482
428
  <Tooltip.Content>
483
- <p>
484
- To refine the highlighted data point, specify one or
485
- more filters (e.g., "Male" and "Female" for a column
486
- called "Sex").
487
- </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>
488
430
  </Tooltip.Content>
489
431
  </Tooltip>
490
432
  </span>
491
433
  </label>
492
434
  {config.filters && (
493
- <ul className="filters-list">
435
+ <ul className='filters-list'>
494
436
  {config.filters.map((filter, index) => (
495
- <fieldset className="edit-block" key={index}>
437
+ <fieldset className='edit-block' key={index}>
496
438
  <button
497
- type="button"
498
- className="remove-column"
439
+ type='button'
440
+ className='remove-column'
499
441
  onClick={() => {
500
- removeFilter(index);
442
+ removeFilter(index)
501
443
  }}
502
444
  >
503
445
  Remove
504
446
  </button>
505
447
  <label>
506
- <span className="edit-label column-heading">
507
- Column
508
- </span>
448
+ <span className='edit-label column-heading'>Column</span>
509
449
  <select
510
- value={filter.columnName ? filter.columnName : ""}
511
- onChange={(e) => {
512
- updateFilterProp(
513
- "columnName",
514
- index,
515
- e.target.value
516
- );
450
+ value={filter.columnName ? filter.columnName : ''}
451
+ onChange={e => {
452
+ updateFilterProp('columnName', index, e.target.value)
517
453
  }}
518
454
  >
519
- <option value="">- Select Option -</option>
455
+ <option value=''>- Select Option -</option>
520
456
  {getColumns().map((dataKey, index) => (
521
457
  <option value={dataKey} key={index}>
522
458
  {dataKey}
@@ -525,27 +461,19 @@ const EditorPanel = memo(() => {
525
461
  </select>
526
462
  </label>
527
463
  <label>
528
- <span className="edit-label column-heading">
529
- Column Value
530
- </span>
464
+ <span className='edit-label column-heading'>Column Value</span>
531
465
  <select
532
466
  value={filter.columnValue}
533
- onChange={(e) => {
534
- updateFilterProp(
535
- "columnValue",
536
- index,
537
- e.target.value
538
- );
467
+ onChange={e => {
468
+ updateFilterProp('columnValue', index, e.target.value)
539
469
  }}
540
470
  >
541
- <option value="">- Select Option -</option>
542
- {getFilterColumnValues(index).map(
543
- (dataKey, index) => (
544
- <option value={dataKey} key={index}>
545
- {dataKey}
546
- </option>
547
- )
548
- )}
471
+ <option value=''>- Select Option -</option>
472
+ {getFilterColumnValues(index).map((dataKey, index) => (
473
+ <option value={dataKey} key={index}>
474
+ {dataKey}
475
+ </option>
476
+ ))}
549
477
  </select>
550
478
  </label>
551
479
  </fieldset>
@@ -554,98 +482,44 @@ const EditorPanel = memo(() => {
554
482
  )}
555
483
  {(!config.filters || config.filters.length === 0) && (
556
484
  <div>
557
- <fieldset className="edit-block">
558
- <p style={{ textAlign: "center" }}>
559
- There are currently no filters.
560
- </p>
485
+ <fieldset className='edit-block'>
486
+ <p style={{ textAlign: 'center' }}>There are currently no filters.</p>
561
487
  </fieldset>
562
488
  </div>
563
489
  )}
564
- <button
565
- type="button"
566
- onClick={addNewFilter}
567
- className="btn full-width"
568
- >
490
+ <button type='button' onClick={addNewFilter} className='btn full-width'>
569
491
  Add Filter
570
492
  </button>
571
493
  </AccordionItemPanel>
572
494
  </AccordionItem>
573
495
 
574
496
  <AccordionItem>
575
- {" "}
497
+ {' '}
576
498
  {/*Visual*/}
577
499
  <AccordionItemHeading>
578
500
  <AccordionItemButton>Visual</AccordionItemButton>
579
501
  </AccordionItemHeading>
580
502
  <AccordionItemPanel>
581
- <TextField
582
- type="number"
583
- value={config.biteFontSize}
584
- fieldName="biteFontSize"
585
- label="Bite Font Size"
586
- updateField={updateField}
587
- min="17"
588
- max="65"
589
- />
590
- <Select
591
- value={config.fontSize}
592
- fieldName="fontSize"
593
- label="Overall Font Size"
594
- updateField={updateField}
595
- options={["small", "medium", "large"]}
596
- />
597
- <div className="checkbox-group">
598
- <CheckBox
599
- value={config.visual?.border}
600
- section="visual"
601
- fieldName="border"
602
- label="Display Border"
603
- updateField={updateField}
604
- />
605
- <CheckBox
606
- value={config.visual?.borderColorTheme}
607
- section="visual"
608
- fieldName="borderColorTheme"
609
- label="Use Border Color Theme"
610
- updateField={updateField}
611
- />
612
- <CheckBox
613
- value={config.visual?.accent}
614
- section="visual"
615
- fieldName="accent"
616
- label="Use Accent Style"
617
- updateField={updateField}
618
- />
619
- <CheckBox
620
- value={config.visual?.background}
621
- section="visual"
622
- fieldName="background"
623
- label="Use Theme Background Color"
624
- updateField={updateField}
625
- />
626
- <CheckBox
627
- value={config.visual?.hideBackgroundColor}
628
- section="visual"
629
- fieldName="hideBackgroundColor"
630
- label="Hide Background Color"
631
- updateField={updateField}
632
- />
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} />
633
511
  </div>
634
512
  <label>
635
- <span className="edit-label">Theme</span>
636
- <ul className="color-palette">
637
- {headerColors.map((palette) => (
513
+ <span className='edit-label'>Theme</span>
514
+ <ul className='color-palette'>
515
+ {headerColors.map(palette => (
638
516
  <li
639
517
  title={palette}
640
518
  key={palette}
641
519
  onClick={() => {
642
- updateConfig({ ...config, theme: palette });
520
+ updateConfig({ ...config, theme: palette })
643
521
  }}
644
- className={
645
- config.theme === palette
646
- ? "selected " + palette
647
- : palette
648
- }
522
+ className={config.theme === palette ? 'selected ' + palette : palette}
649
523
  />
650
524
  ))}
651
525
  </ul>
@@ -655,299 +529,167 @@ const EditorPanel = memo(() => {
655
529
 
656
530
  {['title', 'body', 'graphic'].includes(config.biteStyle) && (
657
531
  <AccordionItem>
658
- {" "}
532
+ {' '}
659
533
  {/*Image & Dynamic Images*/}
660
534
  <AccordionItemHeading>
661
535
  <AccordionItemButton>
662
536
  Image
663
- {["dynamic"].includes(config.imageData.display) && "s"}
537
+ {['dynamic'].includes(config.imageData.display) && 's'}
664
538
  </AccordionItemButton>
665
539
  </AccordionItemHeading>
666
540
  <AccordionItemPanel>
667
- <Select
668
- value={config.imageData.display || ""}
669
- section="imageData"
670
- fieldName="display"
671
- label="Image Display Type"
672
- updateField={updateField}
673
- options={["none", "static", "dynamic"]}
674
- />
675
- <Select
676
- value={config.bitePosition || ""}
677
- fieldName="bitePosition"
678
- label="Image/Graphic Position"
679
- updateField={updateField}
680
- initial="Select"
681
- options={IMAGE_POSITIONS}
682
- />
683
- {["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) && (
684
544
  <>
685
- <TextField
686
- value={config.imageData.url}
687
- section="imageData"
688
- fieldName="url"
689
- label="Image URL"
690
- updateField={updateField}
691
- />
692
- <TextField
693
- value={config.imageData.alt}
694
- section="imageData"
695
- fieldName="alt"
696
- label="Alt Text"
697
- updateField={updateField}
698
- />
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} />
699
547
  </>
700
548
  )}
701
549
 
702
- {["dynamic"].includes(config.imageData.display) && (
550
+ {['dynamic'].includes(config.imageData.display) && (
703
551
  <>
704
- <TextField
705
- value={config.imageData.url || ""}
706
- section="imageData"
707
- fieldName="url"
708
- label="Image URL (default)"
709
- updateField={updateField}
710
- />
711
- <TextField
712
- value={config.imageData.alt}
713
- section="imageData"
714
- fieldName="alt"
715
- label="Alt Text (default)"
716
- updateField={updateField}
717
- />
718
-
719
- <hr className="accordion__divider" />
720
-
721
- {(!config.imageData.options ||
722
- config.imageData.options.length === 0) && (
723
- <p style={{ textAlign: "center" }}>
724
- There are currently no dynamic images.
725
- </p>
726
- )}
727
- {config.imageData.options &&
728
- config.imageData.options.length > 0 && (
729
- <>
730
- <ul>
731
- {config.imageData.options.map(
732
- (option, index) => (
733
- <fieldset
734
- className="edit-block"
735
- key={index}
736
- >
737
- <button
738
- type="button"
739
- className="remove-column"
740
- onClick={() => {
741
- removeDynamicImage(index);
742
- }}
743
- >
744
- Remove
745
- </button>
746
- <label>
747
- <span className="edit-label column-heading">
748
- <strong>
749
- {"Image #" + (index + 1)}
750
- </strong>
751
- </span>
752
-
753
- <div className="accordion__panel-row align-center">
754
- <div className="accordion__panel-col flex-auto">
755
- If Value
756
- </div>
757
- <div className="accordion__panel-col flex-auto">
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} />
554
+
555
+ <hr className='accordion__divider' />
556
+
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 && (
559
+ <>
560
+ <ul>
561
+ {config.imageData.options.map((option, index) => (
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>
572
+ <label>
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 />
587
+ {DATA_OPERATORS.map((operator, index) => (
588
+ <option value={operator} key={index}>
589
+ {operator}
590
+ </option>
591
+ ))}
592
+ </select>
593
+ </div>
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
+ />
602
+ </div>
603
+ </div>
604
+
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
+ >
620
+ <option value={'then'}>Then</option>
621
+ <option value={'and'}>And</option>
622
+ </select>
623
+ </div>
624
+ </div>
625
+
626
+ {option.secondArgument && true === option.secondArgument && (
627
+ <>
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'>
758
631
  <select
759
- value={
760
- option.arguments[0]?.operator ||
761
- ""
762
- }
763
- onChange={(e) => {
764
- updateDynamicImage(
765
- "operator",
766
- index,
767
- 0,
768
- e.target.value
769
- );
632
+ value={option.arguments[1]?.operator || ''}
633
+ onChange={e => {
634
+ setDynamicArgument(index, 'operator', e.target.value)
770
635
  }}
771
636
  >
772
- <option value="" disabled />
773
- {DATA_OPERATORS.map(
774
- (operator, index) => (
775
- <option
776
- value={operator}
777
- key={index}
778
- >
779
- {operator}
780
- </option>
781
- )
782
- )}
637
+ <option value='' disabled />
638
+ {DATA_OPERATORS.map((operator, index) => (
639
+ <option value={operator} key={index}>
640
+ {operator}
641
+ </option>
642
+ ))}
783
643
  </select>
784
644
  </div>
785
- <div className="accordion__panel-col flex-grow flex-shrink">
645
+ <div className='accordion__panel-col flex-grow flex-shrink'>
786
646
  <input
787
- type="number"
788
- value={
789
- option.arguments[0]
790
- ?.threshold || ""
791
- }
792
- onChange={(e) => {
793
- updateDynamicImage(
794
- "threshold",
795
- index,
796
- 0,
797
- e.target.value
798
- );
647
+ type='number'
648
+ value={option.arguments[1]?.threshold || ''}
649
+ onChange={e => {
650
+ setDynamicArgument(index, 'threshold', e.target.value)
799
651
  }}
800
652
  />
801
653
  </div>
802
654
  </div>
803
-
804
- <div className="accordion__panel-row mb-2 align-center">
805
- <div className="accordion__panel-col flex-grow">
806
- <select
807
- className="border-dashed text-center"
808
- value={
809
- option.secondArgument
810
- ? "and"
811
- : "then"
812
- }
813
- onChange={(e) => {
814
- if ("then" === e.target.value) {
815
- updateDynamicImage(
816
- "secondArgument",
817
- index,
818
- null,
819
- false
820
- );
821
- removeDynamicArgument(index);
822
- }
823
- if ("and" === e.target.value) {
824
- updateDynamicImage(
825
- "secondArgument",
826
- index,
827
- null,
828
- true
829
- );
830
- }
831
- }}
832
- >
833
- <option value={"then"}>
834
- Then
835
- </option>
836
- <option value={"and"}>And</option>
837
- </select>
838
- </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>
839
657
  </div>
840
-
841
- {option.secondArgument &&
842
- true === option.secondArgument && (
843
- <>
844
- <div className="accordion__panel-row align-center">
845
- <div className="accordion__panel-col flex-auto">
846
- If Value
847
- </div>
848
- <div className="accordion__panel-col flex-auto">
849
- <select
850
- value={
851
- option.arguments[1]
852
- ?.operator || ""
853
- }
854
- onChange={(e) => {
855
- setDynamicArgument(
856
- index,
857
- "operator",
858
- e.target.value
859
- );
860
- }}
861
- >
862
- <option value="" disabled />
863
- {DATA_OPERATORS.map(
864
- (operator, index) => (
865
- <option
866
- value={operator}
867
- key={index}
868
- >
869
- {operator}
870
- </option>
871
- )
872
- )}
873
- </select>
874
- </div>
875
- <div className="accordion__panel-col flex-grow flex-shrink">
876
- <input
877
- type="number"
878
- value={
879
- option.arguments[1]
880
- ?.threshold || ""
881
- }
882
- onChange={(e) => {
883
- setDynamicArgument(
884
- index,
885
- "threshold",
886
- e.target.value
887
- );
888
- }}
889
- />
890
- </div>
891
- </div>
892
- <div className="accordion__panel-row mb-2 align-center text-center text-capitalize">
893
- <div className="accordion__panel-col flex-grow">
894
- Then
895
- </div>
896
- </div>
897
- </>
898
- )}
899
-
900
- <div className="accordion__panel-row mb-2 align-center">
901
- <div className="accordion__panel-col flex-auto">
902
- Show
903
- </div>
904
- <div className="accordion__panel-col flex-grow">
905
- <input
906
- type="text"
907
- value={option.source || ""}
908
- onChange={(e) => {
909
- updateDynamicImage(
910
- "source",
911
- index,
912
- null,
913
- e.target.value
914
- );
915
- }}
916
- />
917
- </div>
918
- </div>
919
-
920
- <div className="accordion__panel-row mb-2 align-center">
921
- <div className="accordion__panel-col flex-auto">
922
- Alt Text
923
- </div>
924
- <div className="accordion__panel-col flex-grow">
925
- <input
926
- type="text"
927
- value={option.alt || ""}
928
- onChange={(e) => {
929
- updateDynamicImage(
930
- "alt",
931
- index,
932
- null,
933
- e.target.value
934
- );
935
- }}
936
- />
937
- </div>
938
- </div>
939
- </label>
940
- </fieldset>
941
- )
942
- )}
943
- </ul>
944
- </>
945
- )}
946
- <button
947
- type="button"
948
- onClick={addDynamicImage}
949
- className="btn full-width"
950
- >
658
+ </>
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
+ />
671
+ </div>
672
+ </div>
673
+
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
+ />
684
+ </div>
685
+ </div>
686
+ </label>
687
+ </fieldset>
688
+ ))}
689
+ </ul>
690
+ </>
691
+ )}
692
+ <button type='button' onClick={addDynamicImage} className='btn full-width'>
951
693
  Add Dynamic Image
952
694
  </button>
953
695
  </>
@@ -960,7 +702,7 @@ const EditorPanel = memo(() => {
960
702
  </section>
961
703
  </section>
962
704
  </ErrorBoundary>
963
- );
705
+ )
964
706
  })
965
707
 
966
- export default EditorPanel;
708
+ export default EditorPanel