@cdc/filtered-text 4.23.1 → 4.23.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.
@@ -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
+ )
@@ -0,0 +1,11 @@
1
+ @import '@cdc/core/styles/v2/main';
2
+
3
+ .cove-accordion__panel-section {
4
+ overflow: auto;
5
+
6
+ .cove-input__checkbox--small {
7
+ float: left;
8
+ display: block;
9
+ margin-right: 5px;
10
+ }
11
+ }
@@ -0,0 +1,6 @@
1
+ // Placeholder test until we add them in.
2
+ describe('Filtered Text', () => {
3
+ it('has a test.', async () => {
4
+ return true
5
+ })
6
+ })
package/vite.config.js ADDED
@@ -0,0 +1,4 @@
1
+ import GenerateViteConfig from '../../generateViteConfig.js'
2
+ import { moduleName } from './package.json'
3
+
4
+ export default GenerateViteConfig(moduleName)