@cdc/filtered-text 4.22.11 → 4.23.2
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/dist/cdcfilteredtext.js +5326 -0
- package/examples/example-config.json +7 -0
- package/examples/sex-ageGroup-no-values.json +86 -0
- package/examples/sex-ageGroup-with-values.json +86 -0
- package/examples/sex-no-values-markup-test.json +14 -0
- package/index.html +29 -0
- package/package.json +20 -27
- package/src/CdcFilteredText.jsx +153 -0
- package/src/ConfigContext.jsx +5 -0
- package/src/components/EditorPanel.jsx +299 -0
- package/src/data/initial-state.js +16 -0
- package/src/index.jsx +15 -0
- package/src/scss/main.scss +11 -0
- package/vite.config.js +4 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import React, { useState, useEffect, memo, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
4
|
+
|
|
5
|
+
import ConfigContext from '../ConfigContext'
|
|
6
|
+
|
|
7
|
+
import Accordion from '@cdc/core/components/ui/Accordion'
|
|
8
|
+
import InputText from '@cdc/core/components/inputs/InputText'
|
|
9
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
10
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
11
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
12
|
+
import InputSelect from '@cdc/core/components/inputs/InputSelect'
|
|
13
|
+
import InputCheckbox from '@cdc/core/components/inputs/InputCheckbox'
|
|
14
|
+
|
|
15
|
+
import '@cdc/core/styles/v2/components/editor.scss'
|
|
16
|
+
|
|
17
|
+
const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
|
|
18
|
+
|
|
19
|
+
const EditorPanel = memo(props => {
|
|
20
|
+
const { config, updateConfig, loading, stateData: data, setParentConfig, isDashboard } = useContext(ConfigContext)
|
|
21
|
+
|
|
22
|
+
const [displayPanel, setDisplayPanel] = useState(true)
|
|
23
|
+
const [showConfigConfirm, setShowConfigConfirm] = useState(false)
|
|
24
|
+
|
|
25
|
+
const updateField = (section, subsection, fieldName, newValue) => {
|
|
26
|
+
// Top level
|
|
27
|
+
if (null === section && null === subsection) {
|
|
28
|
+
let updatedConfig = { ...config, [fieldName]: newValue }
|
|
29
|
+
|
|
30
|
+
if ('filterColumn' === fieldName) {
|
|
31
|
+
updatedConfig.filterValue = ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
updateConfig(updatedConfig)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const isArray = Array.isArray(config[section])
|
|
38
|
+
|
|
39
|
+
let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
|
|
40
|
+
|
|
41
|
+
if (null !== subsection) {
|
|
42
|
+
if (isArray) {
|
|
43
|
+
sectionValue = [...config[section]]
|
|
44
|
+
sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
|
|
45
|
+
} else if (typeof newValue === 'string') {
|
|
46
|
+
sectionValue[subsection] = newValue
|
|
47
|
+
} else {
|
|
48
|
+
sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let updatedConfig = { ...config, [section]: sectionValue }
|
|
53
|
+
|
|
54
|
+
updateConfig(updatedConfig)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const missingRequiredSections = () => {
|
|
58
|
+
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Filters -------------------------
|
|
63
|
+
const removeFilter = index => {
|
|
64
|
+
let filters = [...config.filters]
|
|
65
|
+
|
|
66
|
+
filters.splice(index, 1)
|
|
67
|
+
|
|
68
|
+
updateConfig({ ...config, filters })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const updateFilterProp = (name, index, value) => {
|
|
72
|
+
let filters = [...config.filters]
|
|
73
|
+
|
|
74
|
+
filters[index][name] = value
|
|
75
|
+
|
|
76
|
+
updateConfig({ ...config, filters })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const addNewFilter = () => {
|
|
80
|
+
let filters = config.filters ? [...config.filters] : []
|
|
81
|
+
|
|
82
|
+
filters.push({ values: [] })
|
|
83
|
+
|
|
84
|
+
updateConfig({ ...config, filters })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const getColumns = (filter = true) => {
|
|
88
|
+
let columns = {}
|
|
89
|
+
if (data.length) {
|
|
90
|
+
data.map(row => {
|
|
91
|
+
return Object.keys(row).forEach(columnName => (columns[columnName] = true))
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Object.keys(columns)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const getFilterColumnValues = index => {
|
|
99
|
+
let filterDataOptions = []
|
|
100
|
+
const filterColumnName = config.filters[index].columnName
|
|
101
|
+
if (data && filterColumnName) {
|
|
102
|
+
data.forEach(function (row) {
|
|
103
|
+
if (undefined !== row[filterColumnName] && -1 === filterDataOptions.indexOf(row[filterColumnName])) {
|
|
104
|
+
filterDataOptions.push(row[filterColumnName])
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
filterDataOptions.sort()
|
|
108
|
+
}
|
|
109
|
+
return filterDataOptions
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
// Pass up to Editor if needed
|
|
114
|
+
if (setParentConfig) {
|
|
115
|
+
const newConfig = convertStateToConfig()
|
|
116
|
+
setParentConfig(newConfig)
|
|
117
|
+
}
|
|
118
|
+
}, [config])
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!showConfigConfirm) {
|
|
122
|
+
let newConfig = { ...config }
|
|
123
|
+
delete newConfig.newViz
|
|
124
|
+
updateConfig(newConfig)
|
|
125
|
+
}
|
|
126
|
+
}, [])
|
|
127
|
+
|
|
128
|
+
const onBackClick = () => {
|
|
129
|
+
setDisplayPanel(!displayPanel)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const Error = () => {
|
|
133
|
+
return (
|
|
134
|
+
<section className='waiting'>
|
|
135
|
+
<section className='waiting-container'>
|
|
136
|
+
<h3>Error With Configuration</h3>
|
|
137
|
+
<p>{config.runtime.editorErrorMessage}</p>
|
|
138
|
+
</section>
|
|
139
|
+
</section>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const Confirm = () => {
|
|
144
|
+
const confirmDone = e => {
|
|
145
|
+
e.preventDefault()
|
|
146
|
+
let newConfig = { ...config }
|
|
147
|
+
delete newConfig.newViz
|
|
148
|
+
updateConfig(newConfig)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<section className='waiting'>
|
|
153
|
+
<section className='waiting-container'>
|
|
154
|
+
<h3>Finish Configuring</h3>
|
|
155
|
+
<p>Set all required options to the left and confirm below to display a preview of the markup.</p>
|
|
156
|
+
<button className='btn' style={{ margin: '1em auto' }} onClick={confirmDone}>
|
|
157
|
+
I'm Done
|
|
158
|
+
</button>
|
|
159
|
+
</section>
|
|
160
|
+
</section>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
const convertStateToConfig = () => {
|
|
164
|
+
let strippedState = JSON.parse(JSON.stringify(config))
|
|
165
|
+
delete strippedState.newViz
|
|
166
|
+
delete strippedState.runtime
|
|
167
|
+
|
|
168
|
+
return strippedState
|
|
169
|
+
}
|
|
170
|
+
const editorContent = (
|
|
171
|
+
<Accordion>
|
|
172
|
+
<Accordion.Section title='General'>
|
|
173
|
+
<InputText value={config.title} fieldName='title' label='Title' placeholder='Filterable Text Title' updateField={updateField} />
|
|
174
|
+
</Accordion.Section>
|
|
175
|
+
<Accordion.Section title='Data'>
|
|
176
|
+
<div className='cove-accordion__panel-section'>
|
|
177
|
+
<div className='cove-input-group'>
|
|
178
|
+
<InputSelect value={config.textColumn || ''} fieldName='textColumn' label='Text Column' updateField={updateField} initial='Select' options={getColumns()} />
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<hr className='cove-accordion__divider' />
|
|
182
|
+
|
|
183
|
+
<label style={{ marginBottom: '1rem' }}>
|
|
184
|
+
<span className='edit-label'>Data Point Filters</span>
|
|
185
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
186
|
+
<Tooltip.Target>
|
|
187
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
188
|
+
</Tooltip.Target>
|
|
189
|
+
<Tooltip.Content>
|
|
190
|
+
<p>To refine the highlighted data point, specify one or more filters (e.g., "Male" and "Female" for a column called "Sex").</p>
|
|
191
|
+
</Tooltip.Content>
|
|
192
|
+
</Tooltip>
|
|
193
|
+
</label>
|
|
194
|
+
{config.filters && (
|
|
195
|
+
<ul className='filters-list' style={{ paddingLeft: 0, marginBottom: '1rem' }}>
|
|
196
|
+
{config.filters.map((filter, index) => (
|
|
197
|
+
<fieldset className='edit-block' key={index}>
|
|
198
|
+
<button
|
|
199
|
+
type='button'
|
|
200
|
+
className='remove-column'
|
|
201
|
+
onClick={() => {
|
|
202
|
+
removeFilter(index)
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
Remove
|
|
206
|
+
</button>
|
|
207
|
+
<label>
|
|
208
|
+
<span className='edit-label column-heading'>Column</span>
|
|
209
|
+
<select
|
|
210
|
+
value={filter.columnName}
|
|
211
|
+
onChange={e => {
|
|
212
|
+
updateFilterProp('columnName', index, e.target.value)
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<option value=''>- Select Option -</option>
|
|
216
|
+
{getColumns().map((dataKey, index) => (
|
|
217
|
+
<option value={dataKey} key={index}>
|
|
218
|
+
{dataKey}
|
|
219
|
+
</option>
|
|
220
|
+
))}
|
|
221
|
+
</select>
|
|
222
|
+
</label>
|
|
223
|
+
<label>
|
|
224
|
+
<span className='edit-label column-heading'>Column Value</span>
|
|
225
|
+
<select
|
|
226
|
+
value={filter.columnValue}
|
|
227
|
+
onChange={e => {
|
|
228
|
+
updateFilterProp('columnValue', index, e.target.value)
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
<option value=''>- Select Option -</option>
|
|
232
|
+
{getFilterColumnValues(index).map((dataKey, index) => (
|
|
233
|
+
<option value={dataKey} key={index}>
|
|
234
|
+
{dataKey}
|
|
235
|
+
</option>
|
|
236
|
+
))}
|
|
237
|
+
</select>
|
|
238
|
+
</label>
|
|
239
|
+
</fieldset>
|
|
240
|
+
))}
|
|
241
|
+
</ul>
|
|
242
|
+
)}
|
|
243
|
+
<Button onClick={addNewFilter} fluid>
|
|
244
|
+
Add Filter
|
|
245
|
+
</Button>
|
|
246
|
+
</Accordion.Section>
|
|
247
|
+
<Accordion.Section title='Visual'>
|
|
248
|
+
<InputSelect value={config.fontSize} fieldName='fontSize' label='Font Size' updateField={updateField} options={['small', 'medium', 'large']} />
|
|
249
|
+
<br />
|
|
250
|
+
<label>
|
|
251
|
+
<span className='edit-label'>Theme</span>
|
|
252
|
+
<ul className='color-palette'>
|
|
253
|
+
{headerColors.map(palette => (
|
|
254
|
+
<li
|
|
255
|
+
title={palette}
|
|
256
|
+
key={palette}
|
|
257
|
+
onClick={() => {
|
|
258
|
+
updateConfig({ ...config, theme: palette })
|
|
259
|
+
}}
|
|
260
|
+
className={config.theme === palette ? 'selected ' + palette : palette}
|
|
261
|
+
></li>
|
|
262
|
+
))}
|
|
263
|
+
</ul>
|
|
264
|
+
</label>
|
|
265
|
+
|
|
266
|
+
<div className='cove-accordion__panel-section checkbox-group'>
|
|
267
|
+
<InputCheckbox inline size='small' value={config.visual.border} section='visual' fieldName='border' label='Display Border' updateField={updateField} />
|
|
268
|
+
<InputCheckbox inline size='small' value={config.visual.borderColorTheme} section='visual' fieldName='borderColorTheme' label='Use theme border color' updateField={updateField} />
|
|
269
|
+
<InputCheckbox size='small' value={config.visual.accent} section='visual' fieldName='accent' label='Use Accent Style' updateField={updateField} />
|
|
270
|
+
<InputCheckbox size='small' value={config.visual.background} section='visual' fieldName='background' label='Use Theme Background Color' updateField={updateField} />
|
|
271
|
+
<InputCheckbox size='small' value={config.visual.hideBackgroundColor} section='visual' fieldName='hideBackgroundColor' label='Hide Background Color' updateField={updateField} />
|
|
272
|
+
</div>
|
|
273
|
+
</Accordion.Section>
|
|
274
|
+
</Accordion>
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if (loading) return null
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<ErrorBoundary component='EditorPanel'>
|
|
281
|
+
<div className='cove-editor'>
|
|
282
|
+
{!config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error />}
|
|
283
|
+
{config.newViz && showConfigConfirm && <Confirm />}
|
|
284
|
+
<button className={`cove-editor--toggle` + (!displayPanel ? ` collapsed` : ``)} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick} />
|
|
285
|
+
<section className={`cove-editor__panel` + (displayPanel ? `` : ' hidden')}>
|
|
286
|
+
<div className='cove-editor__panel-container'>
|
|
287
|
+
<h2 className='cove-editor__heading'>Configure Filtered Text</h2>
|
|
288
|
+
<section className='cove-editor__content'>{editorContent}</section>
|
|
289
|
+
</div>
|
|
290
|
+
</section>
|
|
291
|
+
<div className='cove-editor__content'>
|
|
292
|
+
<div className='cove-editor__content-wrap'>{props.children}</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</ErrorBoundary>
|
|
296
|
+
)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
export default EditorPanel
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
title: 'Filtered Text',
|
|
3
|
+
type: 'filtered-text',
|
|
4
|
+
theme: 'theme-blue',
|
|
5
|
+
fontSize: 'small',
|
|
6
|
+
shadow: false,
|
|
7
|
+
filters: [],
|
|
8
|
+
visual: {
|
|
9
|
+
hideBackgroundColor: false,
|
|
10
|
+
background: false,
|
|
11
|
+
roundedBorders: false,
|
|
12
|
+
accent: false,
|
|
13
|
+
border: false,
|
|
14
|
+
borderColorTheme: false
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/index.jsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
|
|
4
|
+
import CdcFilteredText from './CdcFilteredText'
|
|
5
|
+
|
|
6
|
+
//@ts-ignore
|
|
7
|
+
let isEditor = window.location.href.includes('editor=true')
|
|
8
|
+
|
|
9
|
+
let domContainer = document.getElementsByClassName('react-container')[0]
|
|
10
|
+
|
|
11
|
+
ReactDOM.createRoot(domContainer).render(
|
|
12
|
+
<React.StrictMode>
|
|
13
|
+
<CdcFilteredText configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} />
|
|
14
|
+
</React.StrictMode>,
|
|
15
|
+
)
|
package/vite.config.js
ADDED