@cdc/data-bite 1.1.1 → 1.1.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/LICENSE +201 -0
- package/dist/cdcdatabite.js +64 -0
- package/examples/example-config.json +26 -0
- package/examples/example-data.json +833 -0
- package/examples/private/WCMSRD-12345.json +1027 -0
- package/package.json +11 -13
- package/src/CdcDataBite.tsx +600 -0
- package/src/components/CircleCallout.js +26 -0
- package/src/components/EditorPanel.js +590 -0
- package/src/context.tsx +5 -0
- package/src/data/initial-state.js +39 -0
- package/src/images/active-checkmark.svg +1 -0
- package/src/images/asc.svg +1 -0
- package/src/images/desc.svg +1 -0
- package/src/images/inactive-checkmark.svg +1 -0
- package/src/images/warning.svg +1 -0
- package/src/index.html +19 -0
- package/src/index.tsx +16 -0
- package/src/scss/bite.scss +374 -0
- package/src/scss/editor-panel.scss +564 -0
- package/src/scss/main.scss +72 -0
- package/src/scss/variables.scss +1 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import React, { memo, useContext, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Accordion,
|
|
5
|
+
AccordionItem,
|
|
6
|
+
AccordionItemButton,
|
|
7
|
+
AccordionItemHeading,
|
|
8
|
+
AccordionItemPanel,
|
|
9
|
+
} from 'react-accessible-accordion'
|
|
10
|
+
|
|
11
|
+
import { useDebounce } from 'use-debounce'
|
|
12
|
+
import Context from '../context'
|
|
13
|
+
import WarningImage from '../images/warning.svg'
|
|
14
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
15
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
16
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
17
|
+
import { BITE_LOCATIONS, DATA_FUNCTIONS, IMAGE_POSITIONS, DATA_OPERATORS } from '../CdcDataBite'
|
|
18
|
+
|
|
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);
|
|
21
|
+
|
|
22
|
+
const [ debouncedValue ] = useDebounce(value, 500);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if('string' === typeof debouncedValue && stateValue !== debouncedValue ) {
|
|
26
|
+
updateField(section, subsection, fieldName, debouncedValue, i)
|
|
27
|
+
}
|
|
28
|
+
}, [debouncedValue, section, subsection, fieldName, i, stateValue, updateField])
|
|
29
|
+
|
|
30
|
+
let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`;
|
|
31
|
+
|
|
32
|
+
const onChange = (e) => {
|
|
33
|
+
//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);
|
|
36
|
+
} else {
|
|
37
|
+
if(!e.target.value || ( parseFloat(min) <= parseFloat(e.target.value ) & parseFloat(max) >= parseFloat(e.target.value))) {
|
|
38
|
+
setValue(e.target.value);
|
|
39
|
+
} else {
|
|
40
|
+
setValue(min.toString());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value} />
|
|
46
|
+
|
|
47
|
+
if('textarea' === type) {
|
|
48
|
+
formElement = (
|
|
49
|
+
<textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if('number' === type) {
|
|
54
|
+
formElement = <input type="number" name={name} onChange={onChange} {...attributes} value={value} />
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
{label && label.length > 0 &&
|
|
60
|
+
<label>
|
|
61
|
+
<span className="edit-label column-heading">{label}{tooltip}</span>
|
|
62
|
+
{formElement}
|
|
63
|
+
</label>
|
|
64
|
+
}
|
|
65
|
+
{(!label || label.length === 0) && formElement}
|
|
66
|
+
</>
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
|
|
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>
|
|
74
|
+
</label>
|
|
75
|
+
))
|
|
76
|
+
|
|
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 = [];
|
|
83
|
+
for (const [optionValue, optionName] of Object.entries(options)) {
|
|
84
|
+
optionsJsx.push(<option value={optionValue} key={optionValue}>{optionName}</option>)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if(initialValue) {
|
|
89
|
+
optionsJsx.unshift(<option value="" key="initial">{initialValue}</option>)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<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}>
|
|
96
|
+
{optionsJsx}
|
|
97
|
+
</select>
|
|
98
|
+
</label>
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
|
|
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']
|
|
103
|
+
|
|
104
|
+
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
|
+
};
|
|
118
|
+
|
|
119
|
+
const updateField = (section, subsection, fieldName, newValue) => {
|
|
120
|
+
// Top level
|
|
121
|
+
if( null === section && null === subsection) {
|
|
122
|
+
let updatedConfig = {...config, [fieldName]: newValue};
|
|
123
|
+
|
|
124
|
+
if ( 'filterColumn' === fieldName ) {
|
|
125
|
+
updatedConfig.filterValue = '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
enforceRestrictions(updatedConfig);
|
|
129
|
+
|
|
130
|
+
updateConfig(updatedConfig);
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const isArray = Array.isArray(config[section]);
|
|
135
|
+
|
|
136
|
+
let sectionValue = isArray ? [...config[section], newValue] : {...config[section], [fieldName]: newValue};
|
|
137
|
+
|
|
138
|
+
if(null !== subsection) {
|
|
139
|
+
if(isArray) {
|
|
140
|
+
sectionValue = [...config[section]]
|
|
141
|
+
sectionValue[subsection] = {...sectionValue[subsection], [fieldName]: newValue}
|
|
142
|
+
} else if(typeof newValue === "string") {
|
|
143
|
+
sectionValue[subsection] = newValue
|
|
144
|
+
} else {
|
|
145
|
+
sectionValue = {...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue}}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let updatedConfig = {...config, [section]: sectionValue};
|
|
150
|
+
|
|
151
|
+
enforceRestrictions(updatedConfig);
|
|
152
|
+
|
|
153
|
+
updateConfig(updatedConfig)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const missingRequiredSections = () => {
|
|
157
|
+
//Whether to show error message if something is required to show a data-bite and isn't filled in
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
// Pass up to Editor if needed
|
|
163
|
+
if(setParentConfig) {
|
|
164
|
+
const newConfig = convertStateToConfig()
|
|
165
|
+
|
|
166
|
+
setParentConfig(newConfig)
|
|
167
|
+
}
|
|
168
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
169
|
+
}, [config])
|
|
170
|
+
|
|
171
|
+
const onBackClick = () => {
|
|
172
|
+
setDisplayPanel(!displayPanel);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const Error = () => {
|
|
176
|
+
return (
|
|
177
|
+
<section className="waiting">
|
|
178
|
+
<section className="waiting-container">
|
|
179
|
+
<h3>Error With Configuration</h3>
|
|
180
|
+
<p>{config.runtime.editorErrorMessage}</p>
|
|
181
|
+
</section>
|
|
182
|
+
</section>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const Confirm = () => {
|
|
187
|
+
return (
|
|
188
|
+
<section className="waiting">
|
|
189
|
+
<section className="waiting-container">
|
|
190
|
+
<h3>Finish Configuring</h3>
|
|
191
|
+
<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>
|
|
193
|
+
</section>
|
|
194
|
+
</section>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const convertStateToConfig = () => {
|
|
199
|
+
let strippedState = JSON.parse(JSON.stringify(config))
|
|
200
|
+
//if(false === missingRequiredSections()) {
|
|
201
|
+
//strippedState.newViz
|
|
202
|
+
//}
|
|
203
|
+
delete strippedState.runtime
|
|
204
|
+
|
|
205
|
+
return strippedState
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Filters -----------------------------------------------
|
|
209
|
+
const removeFilter = (index) => {
|
|
210
|
+
let filters = [...config.filters];
|
|
211
|
+
|
|
212
|
+
filters.splice(index, 1);
|
|
213
|
+
|
|
214
|
+
updateConfig({...config, filters})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const updateFilterProp = (name, index, value) => {
|
|
218
|
+
let filters = [...config.filters];
|
|
219
|
+
|
|
220
|
+
filters[index][name] = value;
|
|
221
|
+
|
|
222
|
+
updateConfig({...config, filters});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const addNewFilter = () => {
|
|
226
|
+
let filters = config.filters ? [...config.filters] : [];
|
|
227
|
+
|
|
228
|
+
filters.push({values: []});
|
|
229
|
+
|
|
230
|
+
updateConfig({...config, filters});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const getColumns = (filter = true) => {
|
|
234
|
+
let columns = {}
|
|
235
|
+
if(data.length){
|
|
236
|
+
data.map(row => {
|
|
237
|
+
return Object.keys(row).forEach(columnName => columns[columnName] = true)
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
return Object.keys(columns)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const getFilterColumnValues = (index) => {
|
|
246
|
+
let filterDataOptions = []
|
|
247
|
+
const filterColumnName = config.filters[index].columnName;
|
|
248
|
+
if (data && filterColumnName) {
|
|
249
|
+
data.forEach(function(row) {
|
|
250
|
+
if ( undefined !== row[filterColumnName] && -1 === filterDataOptions.indexOf(row[filterColumnName]) ) {
|
|
251
|
+
filterDataOptions.push(row[filterColumnName]);
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
filterDataOptions.sort();
|
|
255
|
+
}
|
|
256
|
+
return filterDataOptions;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Dynamic Images ----------------------------------------
|
|
260
|
+
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
|
|
263
|
+
|
|
264
|
+
let payload = {...config.imageData, options: imageOptions}
|
|
265
|
+
updateConfig({...config, imageData: payload});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const setDynamicArgument = (optionIndex, name, value) => {
|
|
269
|
+
let imageArguments = [...config.imageData.options[optionIndex].arguments]
|
|
270
|
+
imageArguments[1] = {...imageArguments[1], [name]: value }
|
|
271
|
+
let argumentsPayload = {...config.imageData.options[optionIndex], arguments: imageArguments}
|
|
272
|
+
let optionsPayload = [...config.imageData.options]
|
|
273
|
+
optionsPayload[optionIndex] = argumentsPayload
|
|
274
|
+
let payload = {...config.imageData, options: optionsPayload}
|
|
275
|
+
updateConfig({...config, imageData: payload})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const removeDynamicArgument = (optionIndex) => {
|
|
279
|
+
if (config.imageData.options[optionIndex].arguments.length > 1) {
|
|
280
|
+
let imageArguments = [...config.imageData.options[optionIndex].arguments]
|
|
281
|
+
imageArguments.pop()
|
|
282
|
+
let argumentsPayload = {...config.imageData.options[optionIndex], arguments: imageArguments}
|
|
283
|
+
let optionsPayload = [...config.imageData.options]
|
|
284
|
+
optionsPayload[optionIndex] = argumentsPayload
|
|
285
|
+
let payload = {...config.imageData, options: optionsPayload}
|
|
286
|
+
updateConfig({...config, imageData: payload})
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const addDynamicImage = () => {
|
|
291
|
+
let imageOptions = config.imageData.options ? [ ...config.imageData.options ] : []
|
|
292
|
+
imageOptions.push({ source: '', arguments: [{ operator: '', threshold: ''}], alt: '', secondArgument: false })
|
|
293
|
+
|
|
294
|
+
let payload = {...config.imageData, options: imageOptions}
|
|
295
|
+
updateConfig({...config, imageData: payload})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const removeDynamicImage = (index) => {
|
|
299
|
+
let imageOptions = [...config.imageData.options];
|
|
300
|
+
imageOptions.splice(index, 1);
|
|
301
|
+
|
|
302
|
+
let payload = {...config.imageData, options: imageOptions}
|
|
303
|
+
updateConfig({...config, imageData: payload});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// General -----------------------------------------------
|
|
307
|
+
if(loading) {
|
|
308
|
+
return null
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<ErrorBoundary component="EditorPanel">
|
|
313
|
+
{!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error /> }
|
|
314
|
+
{(!config.dataColumn || !config.dataFunction) && <Confirm />}
|
|
315
|
+
<button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick} />
|
|
316
|
+
<section className={displayPanel ? 'editor-panel cove' : 'hidden editor-panel cove'}>
|
|
317
|
+
<div className="heading-2">Configure Data Bite</div>
|
|
318
|
+
<section className="form-container">
|
|
319
|
+
<form>
|
|
320
|
+
<Accordion allowZeroExpanded={true}>
|
|
321
|
+
<AccordionItem> {/* General */}
|
|
322
|
+
<AccordionItemHeading>
|
|
323
|
+
<AccordionItemButton>
|
|
324
|
+
General
|
|
325
|
+
</AccordionItemButton>
|
|
326
|
+
</AccordionItemHeading>
|
|
327
|
+
<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
|
+
} />
|
|
355
|
+
</AccordionItemPanel>
|
|
356
|
+
</AccordionItem>
|
|
357
|
+
|
|
358
|
+
<AccordionItem> {/*Data*/}
|
|
359
|
+
<AccordionItemHeading>
|
|
360
|
+
<AccordionItemButton>
|
|
361
|
+
Data {(!config.dataColumn || !config.dataFunction) && <WarningImage width="25" className="warning-icon" />}
|
|
362
|
+
</AccordionItemButton>
|
|
363
|
+
</AccordionItemHeading>
|
|
364
|
+
<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} />
|
|
369
|
+
</li>
|
|
370
|
+
</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' />
|
|
377
|
+
</li>
|
|
378
|
+
</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" />
|
|
382
|
+
|
|
383
|
+
<label style={{marginBottom: '1rem'}}>
|
|
384
|
+
<span className="edit-label">
|
|
385
|
+
Data Point Filters
|
|
386
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
387
|
+
<Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
|
|
388
|
+
<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>
|
|
391
|
+
</Tooltip.Content>
|
|
392
|
+
</Tooltip>
|
|
393
|
+
</span>
|
|
394
|
+
</label>
|
|
395
|
+
{
|
|
396
|
+
config.filters &&
|
|
397
|
+
<ul className="filters-list">
|
|
398
|
+
{config.filters.map((filter, index) => (
|
|
399
|
+
<fieldset className="edit-block" key={index}>
|
|
400
|
+
<button type="button" className="remove-column" onClick={() => {removeFilter(index)}}>Remove</button>
|
|
401
|
+
<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>
|
|
405
|
+
{getColumns().map((dataKey, index) => (
|
|
406
|
+
<option value={dataKey} key={index}>{dataKey}</option>
|
|
407
|
+
))}
|
|
408
|
+
</select>
|
|
409
|
+
</label>
|
|
410
|
+
<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>
|
|
414
|
+
{getFilterColumnValues(index).map((dataKey, index) => (
|
|
415
|
+
<option value={dataKey} key={index}>{dataKey}</option>
|
|
416
|
+
))}
|
|
417
|
+
</select>
|
|
418
|
+
</label>
|
|
419
|
+
</fieldset>
|
|
420
|
+
))}
|
|
421
|
+
</ul>
|
|
422
|
+
}
|
|
423
|
+
{(!config.filters || config.filters.length === 0) &&
|
|
424
|
+
<div>
|
|
425
|
+
<fieldset className="edit-block">
|
|
426
|
+
<p style={{textAlign: "center"}}>There are currently no filters.</p>
|
|
427
|
+
</fieldset>
|
|
428
|
+
</div>
|
|
429
|
+
}
|
|
430
|
+
<button type="button" onClick={addNewFilter} className="btn full-width">Add Filter</button>
|
|
431
|
+
</AccordionItemPanel>
|
|
432
|
+
</AccordionItem>
|
|
433
|
+
|
|
434
|
+
<AccordionItem> {/*Visual*/}
|
|
435
|
+
<AccordionItemHeading>
|
|
436
|
+
<AccordionItemButton>
|
|
437
|
+
Visual
|
|
438
|
+
</AccordionItemButton>
|
|
439
|
+
</AccordionItemHeading>
|
|
440
|
+
<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} />
|
|
449
|
+
))}
|
|
450
|
+
</ul>
|
|
451
|
+
</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
|
+
</AccordionItemPanel>
|
|
460
|
+
</AccordionItem>
|
|
461
|
+
|
|
462
|
+
{['title', 'body', 'graphic'].includes(config.biteStyle) &&
|
|
463
|
+
<AccordionItem> {/*Image & Dynamic Images*/}
|
|
464
|
+
<AccordionItemHeading>
|
|
465
|
+
<AccordionItemButton>
|
|
466
|
+
Image{[ 'dynamic' ].includes(config.imageData.display) && 's'}
|
|
467
|
+
</AccordionItemButton>
|
|
468
|
+
</AccordionItemHeading>
|
|
469
|
+
|
|
470
|
+
<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) &&
|
|
474
|
+
<>
|
|
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} />
|
|
477
|
+
</>
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
{[ 'dynamic' ].includes(config.imageData.display) &&
|
|
481
|
+
<>
|
|
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} />
|
|
484
|
+
|
|
485
|
+
<hr className="accordion__divider" />
|
|
486
|
+
|
|
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 &&
|
|
489
|
+
<>
|
|
490
|
+
<ul>
|
|
491
|
+
{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>
|
|
494
|
+
<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/>
|
|
504
|
+
{DATA_OPERATORS.map((operator, index) => (
|
|
505
|
+
<option value={operator} key={index}>{operator}</option>
|
|
506
|
+
))}
|
|
507
|
+
</select>
|
|
508
|
+
</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)}} />
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
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
|
+
}}>
|
|
520
|
+
<option value={'then'}>Then</option>
|
|
521
|
+
<option value={'and'}>And</option>
|
|
522
|
+
</select>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
|
|
526
|
+
{option.secondArgument && true === option.secondArgument &&
|
|
527
|
+
<>
|
|
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/>
|
|
535
|
+
{DATA_OPERATORS.map((operator, index) => (
|
|
536
|
+
<option value={operator} key={index}>{operator}</option>
|
|
537
|
+
))}
|
|
538
|
+
</select>
|
|
539
|
+
</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)}} />
|
|
542
|
+
</div>
|
|
543
|
+
</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>
|
|
548
|
+
</div>
|
|
549
|
+
</>
|
|
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)}} />
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
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)}} />
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
</label>
|
|
570
|
+
</fieldset>
|
|
571
|
+
))}
|
|
572
|
+
</ul>
|
|
573
|
+
</>
|
|
574
|
+
}
|
|
575
|
+
<button type="button" onClick={addDynamicImage} className="btn full-width">Add Dynamic Image</button>
|
|
576
|
+
</>
|
|
577
|
+
}
|
|
578
|
+
</AccordionItemPanel>
|
|
579
|
+
</AccordionItem>
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
</Accordion>
|
|
583
|
+
</form>
|
|
584
|
+
</section>
|
|
585
|
+
</section>
|
|
586
|
+
</ErrorBoundary>
|
|
587
|
+
)
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
export default EditorPanel;
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
type: "data-bite",
|
|
3
|
+
data: [],
|
|
4
|
+
dataBite: "",
|
|
5
|
+
dataFunction: "",
|
|
6
|
+
dataColumn: "",
|
|
7
|
+
bitePosition: "Left",
|
|
8
|
+
biteFontSize: 24,
|
|
9
|
+
fontSize: "medium",
|
|
10
|
+
biteBody: "",
|
|
11
|
+
imageData: {
|
|
12
|
+
display: "none",
|
|
13
|
+
url: "",
|
|
14
|
+
alt: "",
|
|
15
|
+
options: []
|
|
16
|
+
},
|
|
17
|
+
dataFormat: {
|
|
18
|
+
roundToPlace: 0,
|
|
19
|
+
commas: true,
|
|
20
|
+
prefix: "",
|
|
21
|
+
suffix: "%"
|
|
22
|
+
},
|
|
23
|
+
biteStyle: "graphic",
|
|
24
|
+
filters: [],
|
|
25
|
+
subtext: "",
|
|
26
|
+
title: "",
|
|
27
|
+
theme: "theme-blue",
|
|
28
|
+
shadow: false,
|
|
29
|
+
visual: {
|
|
30
|
+
border: false,
|
|
31
|
+
accent: false,
|
|
32
|
+
background: false,
|
|
33
|
+
hideBackgroundColor: false,
|
|
34
|
+
borderColorTheme: false
|
|
35
|
+
},
|
|
36
|
+
general: {
|
|
37
|
+
isCompactStyle: false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"/><circle fill="#FFF" cx="12" cy="12" r="8"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#4C4C4C" fill-rule="nonzero"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 10 5" xmlns="http://www.w3.org/2000/svg"><path d="M0 5l5-5 5 5z" fill="#FFF" fill-rule="nonzero"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 10 5" xmlns="http://www.w3.org/2000/svg"><path d="M0 0l5 5 5-5z" fill="#FFF" fill-rule="nonzero"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M-2-2h24v24H-2z"/><path d="M10 1.5A8.504 8.504 0 001.5 10c0 4.692 3.808 8.5 8.5 8.5s8.5-3.808 8.5-8.5-3.808-8.5-8.5-8.5z" stroke="#4C4C4C" stroke-width="3"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/></svg>
|