@cdc/core 4.24.1 → 4.24.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/assets/icon-sankey.svg +1 -0
- package/assets/icon-table.svg +1 -0
- package/components/DataTable/DataTable.tsx +44 -15
- package/components/DataTable/DataTableStandAlone.tsx +15 -0
- package/components/DataTable/components/CellAnchor.tsx +3 -1
- package/components/DataTable/components/ChartHeader.tsx +48 -12
- package/components/DataTable/components/DataTableEditorPanel.tsx +42 -0
- package/components/DataTable/components/MapHeader.tsx +10 -5
- package/components/DataTable/helpers/customColumns.ts +4 -2
- package/components/DataTable/helpers/customSort.ts +9 -0
- package/components/DataTable/helpers/getChartCellValue.ts +5 -3
- package/components/DataTable/helpers/getDataSeriesColumns.ts +10 -2
- package/components/DataTable/helpers/getSeriesName.ts +15 -20
- package/components/DataTable/helpers/mapCellMatrix.tsx +4 -0
- package/components/DataTable/types/TableConfig.ts +12 -37
- package/components/EditorPanel/ColumnsEditor.tsx +311 -0
- package/components/EditorPanel/DataTableEditor.tsx +27 -28
- package/components/Filters.jsx +35 -16
- package/components/MultiSelect/MultiSelect.tsx +39 -20
- package/components/MultiSelect/multiselect.styles.css +44 -27
- package/components/NestedDropdown/NestedDropdown.tsx +257 -0
- package/components/NestedDropdown/index.ts +1 -0
- package/components/NestedDropdown/nesteddropdown.styles.css +70 -0
- package/components/Table/Table.tsx +1 -1
- package/components/_stories/MultiSelect.stories.tsx +10 -1
- package/components/_stories/NestedDropdown.stories.tsx +58 -0
- package/components/createBarElement.jsx +117 -0
- package/components/elements/ScreenReaderText.tsx +8 -0
- package/components/elements/SkipTo.tsx +14 -0
- package/components/ui/Icon.tsx +5 -1
- package/components/ui/Title/Title.scss +7 -1
- package/components/ui/Title/index.tsx +3 -3
- package/components/ui/Tooltip.jsx +1 -1
- package/components/ui/_stories/Colors.stories.tsx +92 -0
- package/components/ui/_stories/Icon.stories.tsx +17 -10
- package/data/colorPalettes.js +1 -6
- package/helpers/cove/accessibility.ts +23 -0
- package/helpers/cove/date.ts +19 -0
- package/helpers/coveUpdateWorker.js +4 -0
- package/helpers/fetchRemoteData.js +5 -5
- package/helpers/getViewport.ts +23 -0
- package/helpers/isDomainExternal.js +14 -0
- package/helpers/isSolr.js +13 -0
- package/helpers/queryStringUtils.js +26 -0
- package/helpers/tests/updateFieldFactory.test.ts +89 -0
- package/helpers/updateFieldFactory.ts +38 -0
- package/helpers/useDataVizClasses.js +2 -2
- package/helpers/ver/4.24.3.js +25 -0
- package/helpers/withDevTools.ts +50 -0
- package/package.json +4 -3
- package/styles/_data-table.scss +2 -20
- package/styles/_global-variables.scss +75 -0
- package/styles/base.scss +97 -69
- package/types/Action.ts +1 -0
- package/types/Axis.ts +3 -0
- package/types/BaseVisualizationType.ts +1 -0
- package/types/BoxPlot.ts +21 -0
- package/types/Column.ts +1 -0
- package/types/ConfidenceInterval.ts +1 -0
- package/types/General.ts +9 -0
- package/types/Legend.ts +18 -0
- package/types/Region.ts +10 -0
- package/types/Runtime.ts +3 -1
- package/types/Table.ts +5 -2
- package/types/UpdateFieldFunc.ts +1 -1
- package/types/ViewPort.ts +2 -0
- package/types/Visualization.ts +23 -5
- package/types/WCMSProps.ts +11 -0
- package/components/DataTable/components/SkipNav.tsx +0 -7
- package/helpers/cove/date.js +0 -9
- package/helpers/getViewport.js +0 -21
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
|
|
2
|
+
import Tooltip from '../ui/Tooltip'
|
|
3
|
+
import Icon from '../ui/Icon'
|
|
4
|
+
import { TextField } from './Inputs'
|
|
5
|
+
import { Visualization } from '../../types/Visualization'
|
|
6
|
+
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
7
|
+
import { Column } from '../../types/Column'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
|
|
10
|
+
interface ColumnsEditorProps {
|
|
11
|
+
config: Visualization
|
|
12
|
+
updateField: UpdateFieldFunc<string | boolean | string[] | number | Column>
|
|
13
|
+
deleteColumn: (colName: string) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, deleteColumn }) => {
|
|
17
|
+
const additionalColumns = Object.keys(config.columns).filter(value => {
|
|
18
|
+
const dataKey = config.xAxis?.dataKey
|
|
19
|
+
const defaultCols = dataKey ? [dataKey] : []
|
|
20
|
+
|
|
21
|
+
if (true === defaultCols.includes(value)) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
return true
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const editColumn = (addCol, columnName, setval) => {
|
|
28
|
+
updateField('columns', addCol, columnName, setval)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// just adds a new column but not set to any data yet
|
|
32
|
+
const addAdditionalColumn = number => {
|
|
33
|
+
const columnKey = `additionalColumn${number}`
|
|
34
|
+
const newColumn: Column = {
|
|
35
|
+
label: 'New Column',
|
|
36
|
+
dataTable: false,
|
|
37
|
+
tooltips: false,
|
|
38
|
+
prefix: '',
|
|
39
|
+
suffix: '',
|
|
40
|
+
forestPlot: false,
|
|
41
|
+
startingPoint: '0',
|
|
42
|
+
forestPlotAlignRight: false,
|
|
43
|
+
roundToPlace: 0,
|
|
44
|
+
commas: false,
|
|
45
|
+
showInViz: false,
|
|
46
|
+
forestPlotStartingPoint: 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
updateField('columns', null, columnKey, newColumn)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getColumns = () => {
|
|
53
|
+
const columns: string[] = config.data.flatMap(row => {
|
|
54
|
+
return Object.keys(row).map(columnName => columnName)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const { lower, upper } = config.confidenceKeys || {}
|
|
58
|
+
return _.uniq(columns).filter(key => {
|
|
59
|
+
const keyIsPresentInSeries = config.series?.filter(series => series.dataKey === key).length > 0
|
|
60
|
+
if (keyIsPresentInSeries || (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key) && (lower || upper) && key !== lower && key !== upper)) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<AccordionItem>
|
|
69
|
+
<AccordionItemHeading>
|
|
70
|
+
<AccordionItemButton>Columns</AccordionItemButton>
|
|
71
|
+
</AccordionItemHeading>
|
|
72
|
+
<AccordionItemPanel>
|
|
73
|
+
{'navigation' !== config.type && (
|
|
74
|
+
<fieldset className='primary-fieldset edit-block'>
|
|
75
|
+
<label>
|
|
76
|
+
<span className='edit-label'>
|
|
77
|
+
Configurations
|
|
78
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
79
|
+
<Tooltip.Target>
|
|
80
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
81
|
+
</Tooltip.Target>
|
|
82
|
+
<Tooltip.Content>
|
|
83
|
+
<p>You can specify additional columns to display in tooltips and / or the supporting data table.</p>
|
|
84
|
+
</Tooltip.Content>
|
|
85
|
+
</Tooltip>
|
|
86
|
+
</span>
|
|
87
|
+
</label>
|
|
88
|
+
{additionalColumns.map(val => (
|
|
89
|
+
<fieldset className='edit-block' key={val}>
|
|
90
|
+
<button
|
|
91
|
+
className='remove-column'
|
|
92
|
+
onClick={event => {
|
|
93
|
+
event.preventDefault()
|
|
94
|
+
deleteColumn(val)
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
Remove
|
|
98
|
+
</button>
|
|
99
|
+
<label>
|
|
100
|
+
<span className='edit-label column-heading'>Column</span>
|
|
101
|
+
<select
|
|
102
|
+
value={config.columns[val] ? config.columns[val].name : undefined}
|
|
103
|
+
onChange={event => {
|
|
104
|
+
editColumn(val, 'name', event.target.value)
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{['-Select-', ...getColumns()].map(option => (
|
|
108
|
+
<option>{option}</option>
|
|
109
|
+
))}
|
|
110
|
+
</select>
|
|
111
|
+
</label>
|
|
112
|
+
{config.type !== 'table' && (
|
|
113
|
+
<label>
|
|
114
|
+
<span className='edit-label column-heading'>Associate to Series</span>
|
|
115
|
+
<select
|
|
116
|
+
value={config.columns[val] ? config.columns[val].series : ''}
|
|
117
|
+
onChange={event => {
|
|
118
|
+
editColumn(val, 'series', event.target.value)
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<option value=''>Select series</option>
|
|
122
|
+
{(config.series || []).map(series => (
|
|
123
|
+
<option>{series.dataKey}</option>
|
|
124
|
+
))}
|
|
125
|
+
</select>
|
|
126
|
+
</label>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
<TextField value={config.columns[val].label} section='columns' subsection={val} fieldName='label' label='Label' updateField={updateField} />
|
|
130
|
+
<ul className='column-edit'>
|
|
131
|
+
<li className='three-col'>
|
|
132
|
+
<TextField value={config.columns[val].prefix} section='columns' subsection={val} fieldName='prefix' label='Prefix' updateField={updateField} />
|
|
133
|
+
<TextField value={config.columns[val].suffix} section='columns' subsection={val} fieldName='suffix' label='Suffix' updateField={updateField} />
|
|
134
|
+
<TextField type='number' value={config.columns[val].roundToPlace} section='columns' subsection={val} fieldName='roundToPlace' label='Round' updateField={updateField} />
|
|
135
|
+
</li>
|
|
136
|
+
<li>
|
|
137
|
+
<label className='checkbox'>
|
|
138
|
+
<input
|
|
139
|
+
type='checkbox'
|
|
140
|
+
checked={config.columns[val].commas}
|
|
141
|
+
onChange={event => {
|
|
142
|
+
editColumn(val, 'commas', event.target.checked)
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
<span className='edit-label'>Add Commas to Numbers</span>
|
|
146
|
+
</label>
|
|
147
|
+
</li>
|
|
148
|
+
{config.type !== 'table' && (
|
|
149
|
+
<li>
|
|
150
|
+
{config.table.showVertical && (
|
|
151
|
+
<label className='checkbox'>
|
|
152
|
+
<input
|
|
153
|
+
type='checkbox'
|
|
154
|
+
checked={config.columns[val].dataTable}
|
|
155
|
+
onChange={event => {
|
|
156
|
+
editColumn(val, 'dataTable', event.target.checked)
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
<span className='edit-label'>Show in Data Table</span>
|
|
160
|
+
</label>
|
|
161
|
+
)}
|
|
162
|
+
</li>
|
|
163
|
+
)}
|
|
164
|
+
{config.visualizationType === 'Pie' && (
|
|
165
|
+
<li>
|
|
166
|
+
<label className='checkbox'>
|
|
167
|
+
<input
|
|
168
|
+
type='checkbox'
|
|
169
|
+
checked={config.columns[val].showInViz}
|
|
170
|
+
onChange={event => {
|
|
171
|
+
editColumn(val, 'showInViz', event.target.checked)
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
<span className='edit-label'>Show in Visualization</span>
|
|
175
|
+
</label>
|
|
176
|
+
</li>
|
|
177
|
+
)}
|
|
178
|
+
{config.type !== 'table' && (
|
|
179
|
+
<li>
|
|
180
|
+
<label className='checkbox'>
|
|
181
|
+
<input
|
|
182
|
+
type='checkbox'
|
|
183
|
+
checked={config.columns[val].tooltips || false}
|
|
184
|
+
onChange={event => {
|
|
185
|
+
updateField('columns', val, 'tooltips', event.target.checked)
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
<span className='edit-label'>Show in tooltip</span>
|
|
189
|
+
</label>
|
|
190
|
+
</li>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
{config.visualizationType === 'Forest Plot' && (
|
|
194
|
+
<>
|
|
195
|
+
<li>
|
|
196
|
+
<label className='checkbox'>
|
|
197
|
+
<input
|
|
198
|
+
type='checkbox'
|
|
199
|
+
checked={config.columns[val].forestPlot || false}
|
|
200
|
+
onChange={event => {
|
|
201
|
+
editColumn(val, 'forestPlot', event.target.checked)
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
<span className='edit-label'>Show in Forest Plot</span>
|
|
205
|
+
</label>
|
|
206
|
+
</li>
|
|
207
|
+
<li>
|
|
208
|
+
<label className='checkbox'>
|
|
209
|
+
<input
|
|
210
|
+
type='checkbox'
|
|
211
|
+
checked={config.columns[val].forestPlotAlignRight || false}
|
|
212
|
+
onChange={event => {
|
|
213
|
+
editColumn(val, 'forestPlotAlignRight', event.target.checked)
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
<span className='edit-label'>Align Right</span>
|
|
217
|
+
</label>
|
|
218
|
+
</li>
|
|
219
|
+
|
|
220
|
+
{!config.columns[val].forestPlotAlignRight && (
|
|
221
|
+
<li>
|
|
222
|
+
<label className='text'>
|
|
223
|
+
<span className='edit-label'>Forest Plot Starting Point</span>
|
|
224
|
+
<input
|
|
225
|
+
type='number'
|
|
226
|
+
value={config.columns[val].forestPlotStartingPoint || 0}
|
|
227
|
+
onChange={event => {
|
|
228
|
+
editColumn(val, 'forestPlotStartingPoint', event.target.value)
|
|
229
|
+
}}
|
|
230
|
+
/>
|
|
231
|
+
</label>
|
|
232
|
+
</li>
|
|
233
|
+
)}
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
</ul>
|
|
237
|
+
</fieldset>
|
|
238
|
+
))}
|
|
239
|
+
<button
|
|
240
|
+
className={'btn full-width'}
|
|
241
|
+
onClick={event => {
|
|
242
|
+
event.preventDefault()
|
|
243
|
+
addAdditionalColumn(additionalColumns.length + 1)
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
Add Column Configuration
|
|
247
|
+
</button>
|
|
248
|
+
</fieldset>
|
|
249
|
+
)}
|
|
250
|
+
{'category' === config.legend?.type && (
|
|
251
|
+
<fieldset className='primary-fieldset edit-block'>
|
|
252
|
+
<label>
|
|
253
|
+
<span className='edit-label'>
|
|
254
|
+
Additional Category
|
|
255
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
256
|
+
<Tooltip.Target>
|
|
257
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
258
|
+
</Tooltip.Target>
|
|
259
|
+
<Tooltip.Content>
|
|
260
|
+
<p>You can provide additional categories to ensure they appear in the legend</p>
|
|
261
|
+
</Tooltip.Content>
|
|
262
|
+
</Tooltip>
|
|
263
|
+
</span>
|
|
264
|
+
</label>
|
|
265
|
+
{config.legend.additionalCategories &&
|
|
266
|
+
config.legend.additionalCategories.map((val, i) => (
|
|
267
|
+
<fieldset className='edit-block' key={val}>
|
|
268
|
+
<button
|
|
269
|
+
className='remove-column'
|
|
270
|
+
onClick={event => {
|
|
271
|
+
event.preventDefault()
|
|
272
|
+
const updatedAdditionaCategories = [...config.legend.additionalCategories]
|
|
273
|
+
updatedAdditionaCategories.splice(i, 1)
|
|
274
|
+
updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
Remove
|
|
278
|
+
</button>
|
|
279
|
+
<TextField
|
|
280
|
+
value={val}
|
|
281
|
+
label='Category'
|
|
282
|
+
section='legend'
|
|
283
|
+
subsection={null}
|
|
284
|
+
fieldName='additionalCategories'
|
|
285
|
+
updateField={(section, subsection, fieldName, value) => {
|
|
286
|
+
const updatedAdditionaCategories = [...config.legend.additionalCategories]
|
|
287
|
+
updatedAdditionaCategories[i] = value
|
|
288
|
+
updateField(section, subsection, fieldName, updatedAdditionaCategories)
|
|
289
|
+
}}
|
|
290
|
+
/>
|
|
291
|
+
</fieldset>
|
|
292
|
+
))}
|
|
293
|
+
<button
|
|
294
|
+
className={'btn full-width'}
|
|
295
|
+
onClick={event => {
|
|
296
|
+
event.preventDefault()
|
|
297
|
+
const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
|
|
298
|
+
updatedAdditionaCategories.push('')
|
|
299
|
+
updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
Add Category
|
|
303
|
+
</button>
|
|
304
|
+
</fieldset>
|
|
305
|
+
)}
|
|
306
|
+
</AccordionItemPanel>
|
|
307
|
+
</AccordionItem>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export default ColumnsEditor
|
|
@@ -5,26 +5,24 @@ import { CheckBox, TextField } from './Inputs'
|
|
|
5
5
|
import type { Table } from '@cdc/core/types/Table'
|
|
6
6
|
import MultiSelect from '../MultiSelect'
|
|
7
7
|
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
8
|
+
import { Visualization } from '../../types/Visualization'
|
|
8
9
|
|
|
9
10
|
interface DataTableProps {
|
|
10
|
-
config:
|
|
11
|
-
table: Table
|
|
12
|
-
visualizationType: string
|
|
13
|
-
}
|
|
11
|
+
config: Visualization
|
|
14
12
|
updateField: UpdateFieldFunc<string | boolean | string[] | number>
|
|
15
13
|
isDashboard: boolean
|
|
16
|
-
isLoadedFromUrl: boolean
|
|
17
14
|
columns: string[]
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard,
|
|
17
|
+
const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard, columns }) => {
|
|
18
|
+
const isLoadedFromUrl = config.dataKey?.includes('http://') || config?.dataKey?.includes('https://')
|
|
21
19
|
return (
|
|
22
20
|
<>
|
|
23
21
|
<TextField
|
|
24
22
|
value={config.table.label}
|
|
25
23
|
updateField={updateField}
|
|
26
24
|
section='table'
|
|
27
|
-
fieldName='
|
|
25
|
+
fieldName='label'
|
|
28
26
|
id='tableLabel'
|
|
29
27
|
label='Data Table Title'
|
|
30
28
|
placeholder='Data Table'
|
|
@@ -98,30 +96,31 @@ const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard,
|
|
|
98
96
|
}
|
|
99
97
|
/>
|
|
100
98
|
<CheckBox value={config.table.limitHeight} section='table' fieldName='limitHeight' label='Limit Table Height' updateField={updateField} />
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<Tooltip
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
99
|
+
{config.type !== 'table' && (
|
|
100
|
+
<CheckBox
|
|
101
|
+
value={config.table.customTableConfig}
|
|
102
|
+
fieldName='customTableConfig'
|
|
103
|
+
label='Customize Table Config'
|
|
104
|
+
section='table'
|
|
105
|
+
updateField={updateField}
|
|
106
|
+
tooltip={
|
|
107
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
108
|
+
<Tooltip.Target>
|
|
109
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
110
|
+
</Tooltip.Target>
|
|
111
|
+
<Tooltip.Content>
|
|
112
|
+
<p>This will display all available columns in the data set. It will not show any columns where all of the column cells are null.</p>
|
|
113
|
+
</Tooltip.Content>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
118
118
|
{config.table.customTableConfig && <MultiSelect options={columns.map(c => ({ label: c, value: c }))} fieldName='excludeColumns' label='Exclude Columns' section='table' updateField={updateField} />}
|
|
119
|
-
{config.table.limitHeight && <TextField value={config.table.height} fieldName='height' label='Data Table Height' type='number' min={0} max={500} placeholder='Height(px)' updateField={updateField} />}
|
|
119
|
+
{config.table.limitHeight && <TextField value={config.table.height} section='table' fieldName='height' label='Data Table Height' type='number' min={0} max={500} placeholder='Height(px)' updateField={updateField} />}
|
|
120
120
|
<CheckBox value={config.table.expanded} fieldName='expanded' label='Expanded by Default' section='table' updateField={updateField} />
|
|
121
|
-
{isDashboard && <CheckBox value={config.table.showDataTableLink} fieldName='showDataTableLink' label='Show Data Table Name & Link' section='table' updateField={updateField} />}
|
|
121
|
+
{isDashboard && config.type !== 'table' && <CheckBox value={config.table.showDataTableLink} fieldName='showDataTableLink' label='Show Data Table Name & Link' section='table' updateField={updateField} />}
|
|
122
122
|
{isLoadedFromUrl && <CheckBox value={config.table.showDownloadUrl} fieldName='showDownloadUrl' label='Show URL to Automatically Updated Data' section='table' updateField={updateField} />}
|
|
123
|
-
<CheckBox value={config.table.
|
|
124
|
-
<CheckBox value={config.table.showDownloadImgButton} fieldName='showDownloadImgButton' label='Display Image Button' section='table' updateField={updateField} />
|
|
123
|
+
{config.type !== 'table' && <CheckBox value={config.table.showDownloadImgButton} fieldName='showDownloadImgButton' label='Display Image Button' section='table' updateField={updateField} />}
|
|
125
124
|
<label>
|
|
126
125
|
<span className='edit-label column-heading'>Table Cell Min Width</span>
|
|
127
126
|
<input type='number' value={config.table.cellMinWidth ? config.table.cellMinWidth : 0} onChange={e => updateField('table', null, 'cellMinWidth', e.target.value)} />
|
package/components/Filters.jsx
CHANGED
|
@@ -3,6 +3,7 @@ import { useId } from 'react'
|
|
|
3
3
|
|
|
4
4
|
// CDC
|
|
5
5
|
import Button from '@cdc/core/components/elements/Button'
|
|
6
|
+
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
6
7
|
|
|
7
8
|
// Third Party
|
|
8
9
|
import PropTypes from 'prop-types'
|
|
@@ -72,24 +73,26 @@ export const useFilters = props => {
|
|
|
72
73
|
const announceChange = text => {}
|
|
73
74
|
|
|
74
75
|
const changeFilterActive = (index, value) => {
|
|
75
|
-
|
|
76
|
+
const newFilters = visualizationConfig.type === 'map' ? [...filteredData] : [...visualizationConfig.filters]
|
|
76
77
|
|
|
77
|
-
newFilters[index].active = value
|
|
78
|
-
setConfig({ ...visualizationConfig })
|
|
79
|
-
|
|
80
|
-
// If this is a button filter type show the button.
|
|
81
78
|
if (visualizationConfig.filterBehavior === 'Apply Button') {
|
|
79
|
+
newFilters[index].queuedActive = value
|
|
82
80
|
setShowApplyButton(true)
|
|
81
|
+
} else {
|
|
82
|
+
const newFilter = newFilters[index]
|
|
83
|
+
newFilter.active = value
|
|
84
|
+
|
|
85
|
+
const queryParams = getQueryParams()
|
|
86
|
+
if (newFilter.setByQueryParameter && queryParams[newFilter.setByQueryParameter] !== newFilter.active) {
|
|
87
|
+
queryParams[newFilter.setByQueryParameter] = newFilter.active
|
|
88
|
+
updateQueryString(queryParams)
|
|
89
|
+
}
|
|
83
90
|
}
|
|
91
|
+
setConfig({
|
|
92
|
+
...visualizationConfig,
|
|
93
|
+
filters: newFilters
|
|
94
|
+
})
|
|
84
95
|
|
|
85
|
-
// If we're not using the apply button we can set the filters right away.
|
|
86
|
-
if (visualizationConfig.filterBehavior !== 'Apply Button') {
|
|
87
|
-
setConfig({
|
|
88
|
-
...visualizationConfig,
|
|
89
|
-
filters: newFilters
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
96
|
// Used for setting active filter, fromHash breaks the filteredData functionality.
|
|
94
97
|
if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
95
98
|
setFilteredData(newFilters)
|
|
@@ -102,6 +105,22 @@ export const useFilters = props => {
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
const handleApplyButton = newFilters => {
|
|
108
|
+
let needsQueryUpdate = false
|
|
109
|
+
const queryParams = getQueryParams()
|
|
110
|
+
newFilters.forEach(newFilter => {
|
|
111
|
+
if (newFilter.queuedActive) {
|
|
112
|
+
newFilter.active = newFilter.queuedActive
|
|
113
|
+
delete newFilter.queuedActive
|
|
114
|
+
if (newFilter.setByQueryParameter && queryParams[newFilter.setByQueryParameter] !== newFilter.active) {
|
|
115
|
+
queryParams[newFilter.setByQueryParameter] = newFilter.active
|
|
116
|
+
needsQueryUpdate = true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
if (needsQueryUpdate) {
|
|
121
|
+
updateQueryString(queryParams)
|
|
122
|
+
}
|
|
123
|
+
|
|
105
124
|
setConfig({ ...visualizationConfig, filters: newFilters })
|
|
106
125
|
|
|
107
126
|
if (type === 'map') {
|
|
@@ -219,7 +238,7 @@ const Filters = props => {
|
|
|
219
238
|
|
|
220
239
|
const Filters = props => props.children
|
|
221
240
|
|
|
222
|
-
const filterSectionClassList = ['filters-section', type === 'map' ? general.headerColor : theme]
|
|
241
|
+
const filterSectionClassList = ['filters-section', type === 'map' ? general.headerColor : visualizationConfig?.visualizationType === 'Spark Line' ? null : theme]
|
|
223
242
|
|
|
224
243
|
// Exterior Section Wrapper
|
|
225
244
|
Filters.Section = props => {
|
|
@@ -289,7 +308,7 @@ const Filters = props => {
|
|
|
289
308
|
<select
|
|
290
309
|
id={`filter-${outerIndex}`}
|
|
291
310
|
name={label}
|
|
292
|
-
aria-label={label}
|
|
311
|
+
aria-label={`Filter by ${label}`}
|
|
293
312
|
className='filter-select'
|
|
294
313
|
data-index='0'
|
|
295
314
|
value={active}
|
|
@@ -351,7 +370,7 @@ const Filters = props => {
|
|
|
351
370
|
)
|
|
352
371
|
|
|
353
372
|
values.push(
|
|
354
|
-
<option key={index} value={filterOption}>
|
|
373
|
+
<option key={index} value={filterOption} aria-label={filterOption}>
|
|
355
374
|
{singleFilter.labels && singleFilter.labels[filterOption] ? singleFilter.labels[filterOption] : filterOption}
|
|
356
375
|
</option>
|
|
357
376
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react'
|
|
2
|
+
import Tooltip from '../ui/Tooltip'
|
|
2
3
|
import Icon from '../ui/Icon'
|
|
3
4
|
|
|
4
5
|
import './multiselect.styles.css'
|
|
@@ -12,14 +13,17 @@ interface Option {
|
|
|
12
13
|
interface MultiSelectProps {
|
|
13
14
|
section?: string
|
|
14
15
|
subsection?: string
|
|
15
|
-
fieldName: string
|
|
16
|
+
fieldName: string | number
|
|
16
17
|
options: Option[]
|
|
17
18
|
updateField: UpdateFieldFunc<string[]>
|
|
18
19
|
label?: string
|
|
20
|
+
selected?: string[]
|
|
21
|
+
limit?: number
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection = null, fieldName, label, options, updateField }) => {
|
|
22
|
-
const
|
|
24
|
+
const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection = null, fieldName, label, options, updateField, selected, limit }) => {
|
|
25
|
+
const preselectedItems = options.filter(opt => selected?.includes(opt.value)).slice(0, limit)
|
|
26
|
+
const [selectedItems, setSelectedItems] = useState<Option[]>(preselectedItems)
|
|
23
27
|
const [expanded, setExpanded] = useState(false)
|
|
24
28
|
const multiSelectRef = useRef(null)
|
|
25
29
|
|
|
@@ -45,13 +49,16 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
|
|
|
45
49
|
newItems.map(item => item.value)
|
|
46
50
|
)
|
|
47
51
|
|
|
48
|
-
const handleItemSelect = (option: Option) => {
|
|
52
|
+
const handleItemSelect = (option: Option, e = null) => {
|
|
53
|
+
if (e && e.type === 'keyup' && e.key !== 'Enter') return
|
|
54
|
+
if (limit && selectedItems.length >= limit) return
|
|
49
55
|
const newItems = [...selectedItems, option]
|
|
50
56
|
setSelectedItems(newItems)
|
|
51
57
|
update(newItems)
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
const handleItemRemove = (option: Option,
|
|
60
|
+
const handleItemRemove = (option: Option, e = null) => {
|
|
61
|
+
if (e && e.type === 'keyup' && e.key !== 'Enter') return
|
|
55
62
|
const newItems = selectedItems.filter(item => item.value !== option.value)
|
|
56
63
|
setSelectedItems(newItems)
|
|
57
64
|
update(newItems)
|
|
@@ -61,29 +68,41 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
|
|
|
61
68
|
return (
|
|
62
69
|
<div ref={multiSelectRef} className='cove-multiselect'>
|
|
63
70
|
{label && (
|
|
64
|
-
<
|
|
71
|
+
<label id={multiID} className='cove-input__label'>
|
|
65
72
|
{label}
|
|
66
|
-
</
|
|
73
|
+
</label>
|
|
67
74
|
)}
|
|
68
75
|
|
|
69
|
-
<div
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{item.label}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
76
|
+
<div className='wrapper'>
|
|
77
|
+
<div className='selected'>
|
|
78
|
+
{selectedItems.map(item => (
|
|
79
|
+
<div key={item.value} aria-labelledby={label ? multiID : undefined} role='button' onClick={() => handleItemRemove(item)} onKeyUp={e => handleItemRemove(item, e)}>
|
|
80
|
+
{item.label}
|
|
81
|
+
<button aria-label='Remove' onClick={() => handleItemRemove(item)}>
|
|
82
|
+
x
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
))}
|
|
86
|
+
<button aria-label={expanded ? 'Collapse' : 'Expand'} aria-labelledby={label ? multiID : undefined} className='expand' onClick={() => setExpanded(!expanded)}>
|
|
87
|
+
<Icon display={expanded ? 'caretDown' : 'caretUp'} style={{ cursor: 'pointer' }} />
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
{!!limit && (
|
|
91
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
92
|
+
<Tooltip.Target>
|
|
93
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
94
|
+
</Tooltip.Target>
|
|
95
|
+
<Tooltip.Content>
|
|
96
|
+
<p>Select up to {limit} items</p>
|
|
97
|
+
</Tooltip.Content>
|
|
98
|
+
</Tooltip>
|
|
99
|
+
)}
|
|
81
100
|
</div>
|
|
82
101
|
<ul className={'dropdown' + (expanded ? '' : ' hide')}>
|
|
83
102
|
{options
|
|
84
103
|
.filter(option => !selectedItems.find(item => item.value === option.value))
|
|
85
104
|
.map(option => (
|
|
86
|
-
<li className='cove-multiselect-li' key={option.value} role='option' tabIndex={0} onClick={() => handleItemSelect(option)} onKeyUp={
|
|
105
|
+
<li className='cove-multiselect-li' key={option.value} role='option' tabIndex={0} onClick={() => handleItemSelect(option)} onKeyUp={e => handleItemSelect(option, e)}>
|
|
87
106
|
{option.label}
|
|
88
107
|
</li>
|
|
89
108
|
))}
|