@cdc/core 4.24.10 → 4.24.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.
- package/components/AdvancedEditor/AdvancedEditor.tsx +17 -13
- package/components/Alert/components/Alert.tsx +34 -8
- package/components/DataTable/DataTable.tsx +12 -2
- package/components/DataTable/data-table.css +4 -22
- package/components/DataTable/helpers/boxplotCellMatrix.tsx +14 -13
- package/components/DataTable/helpers/getChartCellValue.ts +23 -5
- package/components/EditorPanel/ColumnsEditor.tsx +81 -36
- package/components/EditorPanel/DataTableEditor.tsx +33 -33
- package/components/EditorPanel/FieldSetWrapper.tsx +2 -2
- package/components/EditorPanel/Inputs.tsx +26 -16
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +30 -55
- package/components/Filters/Filters.tsx +12 -4
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +0 -4
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/Legend/Legend.Gradient.tsx +49 -34
- package/components/MultiSelect/MultiSelect.tsx +85 -62
- package/components/MultiSelect/multiselect.styles.css +10 -7
- package/components/NestedDropdown/NestedDropdown.tsx +40 -20
- package/components/NestedDropdown/nesteddropdown.styles.css +15 -13
- package/components/Table/Table.tsx +102 -34
- package/components/_stories/DataTable.stories.tsx +14 -0
- package/components/_stories/Filters.stories.tsx +57 -0
- package/components/_stories/_mocks/DataTable/no-data.json +108 -0
- package/components/ui/Icon.tsx +19 -6
- package/dist/cove-main.css +20 -54
- package/dist/cove-main.css.map +1 -1
- package/helpers/DataTransform.ts +2 -1
- package/helpers/cove/{number.js → number.ts} +25 -11
- package/helpers/fetchRemoteData.js +32 -37
- package/helpers/formatConfigBeforeSave.ts +1 -0
- package/helpers/queryStringUtils.ts +6 -0
- package/helpers/useDataVizClasses.ts +42 -20
- package/package.json +2 -2
- package/styles/_button-section.scss +1 -1
- package/styles/_global-variables.scss +3 -3
- package/styles/_global.scss +21 -22
- package/styles/_reset.scss +0 -11
- package/styles/filters.scss +0 -22
- package/styles/v2/base/_reset.scss +0 -7
- package/styles/v2/components/editor.scss +0 -4
- package/styles/v2/components/icon.scss +1 -1
- package/types/Axis.ts +2 -0
- package/types/BoxPlot.ts +5 -3
- package/types/Color.ts +1 -1
- package/types/Legend.ts +1 -2
- package/types/MarkupInclude.ts +1 -0
- package/types/Runtime.ts +3 -1
- package/types/Series.ts +8 -1
- package/types/Table.ts +1 -1
- package/types/Visualization.ts +7 -8
- package/components/ui/Select.jsx +0 -30
- package/helpers/getGradientLegendWidth.ts +0 -15
|
@@ -31,16 +31,6 @@ export type CheckboxProps = {
|
|
|
31
31
|
} & Input &
|
|
32
32
|
Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'>
|
|
33
33
|
|
|
34
|
-
export type SelectProps = {
|
|
35
|
-
value?: string
|
|
36
|
-
options?: string[]
|
|
37
|
-
required?: boolean
|
|
38
|
-
initial?: string
|
|
39
|
-
|
|
40
|
-
// all other props
|
|
41
|
-
[x: string]: any
|
|
42
|
-
} & Input
|
|
43
|
-
|
|
44
34
|
const TextField = memo((props: TextFieldProps) => {
|
|
45
35
|
const {
|
|
46
36
|
display = true,
|
|
@@ -141,6 +131,16 @@ const CheckBox = memo((props: CheckboxProps) => {
|
|
|
141
131
|
)
|
|
142
132
|
})
|
|
143
133
|
|
|
134
|
+
export type SelectProps = {
|
|
135
|
+
value?: string
|
|
136
|
+
options?: string[] | { label: string; value: string }[]
|
|
137
|
+
required?: boolean
|
|
138
|
+
initial?: string
|
|
139
|
+
|
|
140
|
+
// all other props
|
|
141
|
+
[x: string]: any
|
|
142
|
+
} & Input
|
|
143
|
+
|
|
144
144
|
const Select = memo((props: SelectProps) => {
|
|
145
145
|
const {
|
|
146
146
|
display = true,
|
|
@@ -156,11 +156,21 @@ const Select = memo((props: SelectProps) => {
|
|
|
156
156
|
initial: initialValue,
|
|
157
157
|
...attributes
|
|
158
158
|
} = props
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
const optionsJsx = options.map((option, index) => {
|
|
160
|
+
if (typeof option === 'string') {
|
|
161
|
+
return (
|
|
162
|
+
<option value={option} key={index}>
|
|
163
|
+
{option}
|
|
164
|
+
</option>
|
|
165
|
+
)
|
|
166
|
+
} else {
|
|
167
|
+
return (
|
|
168
|
+
<option value={option.value} key={index}>
|
|
169
|
+
{option.label}
|
|
170
|
+
</option>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
164
174
|
|
|
165
175
|
if (initialValue) {
|
|
166
176
|
optionsJsx.unshift(
|
|
@@ -180,7 +190,7 @@ const Select = memo((props: SelectProps) => {
|
|
|
180
190
|
{tooltip}
|
|
181
191
|
</span>
|
|
182
192
|
<select
|
|
183
|
-
className={required && !value ? 'warning' : ''}
|
|
193
|
+
className={`cove-form-select ${required && !value ? 'warning' : ''}`}
|
|
184
194
|
name={fieldName}
|
|
185
195
|
value={value}
|
|
186
196
|
onChange={event => {
|
|
@@ -140,43 +140,24 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
140
140
|
controls={openControls}
|
|
141
141
|
deleteField={() => removeFilter(filterIndex)}
|
|
142
142
|
>
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
152
|
-
{filterStyleOptions.map((item, index) => {
|
|
153
|
-
return (
|
|
154
|
-
<option key={`filter-style-${index}`} value={item}>
|
|
155
|
-
{item}
|
|
156
|
-
</option>
|
|
157
|
-
)
|
|
158
|
-
})}
|
|
159
|
-
</select>
|
|
160
|
-
</label>
|
|
143
|
+
<Select
|
|
144
|
+
value={filter.filterStyle}
|
|
145
|
+
fieldName='filterStyle'
|
|
146
|
+
label='Filter Style'
|
|
147
|
+
updateField={(_section, _subsection, _field, value) => updateFilterStyle(filterIndex, value)}
|
|
148
|
+
options={filterStyleOptions}
|
|
149
|
+
/>
|
|
161
150
|
|
|
162
151
|
{filter.filterStyle !== 'nested-dropdown' ? (
|
|
163
152
|
<>
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<option value=''>- Select Option -</option>
|
|
173
|
-
{dataColumns.map((dataKey, filterIndex) => (
|
|
174
|
-
<option value={dataKey} key={filterIndex}>
|
|
175
|
-
{dataKey}
|
|
176
|
-
</option>
|
|
177
|
-
))}
|
|
178
|
-
</select>
|
|
179
|
-
</label>
|
|
153
|
+
<Select
|
|
154
|
+
value={filter.columnName}
|
|
155
|
+
fieldName='columnName'
|
|
156
|
+
label='Filter'
|
|
157
|
+
updateField={(_section, _subsection, _field, value) => handleNameChange(filterIndex, value)}
|
|
158
|
+
options={dataColumns}
|
|
159
|
+
initial='- Select Option -'
|
|
160
|
+
/>
|
|
180
161
|
|
|
181
162
|
<label>
|
|
182
163
|
<span className='edit-showDropdown column-heading'>Show Filter Input</span>
|
|
@@ -233,27 +214,21 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
233
214
|
/>
|
|
234
215
|
</label>
|
|
235
216
|
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<FilterOrder
|
|
252
|
-
orderedValues={filter.orderedValues || filter.values}
|
|
253
|
-
handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
254
|
-
/>
|
|
255
|
-
)}
|
|
256
|
-
</label>
|
|
217
|
+
<Select
|
|
218
|
+
value={filter.order || 'asc'}
|
|
219
|
+
fieldName='order'
|
|
220
|
+
label='Filter Order'
|
|
221
|
+
updateField={(_section, _subSection, _field, value) =>
|
|
222
|
+
updateFilterProp('order', filterIndex, value)
|
|
223
|
+
}
|
|
224
|
+
options={filterOrderOptions}
|
|
225
|
+
/>
|
|
226
|
+
{filter.order === 'cust' && (
|
|
227
|
+
<FilterOrder
|
|
228
|
+
orderedValues={filter.orderedValues || filter.values}
|
|
229
|
+
handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
257
232
|
</>
|
|
258
233
|
) : (
|
|
259
234
|
<NestedDropdownEditor
|
|
@@ -351,7 +351,7 @@ const Filters = (props: FilterProps) => {
|
|
|
351
351
|
id={`filter-${outerIndex}`}
|
|
352
352
|
name={label}
|
|
353
353
|
aria-label={`Filter by ${label}`}
|
|
354
|
-
className='
|
|
354
|
+
className='cove-form-select'
|
|
355
355
|
data-index='0'
|
|
356
356
|
value={active}
|
|
357
357
|
onChange={e => {
|
|
@@ -439,6 +439,7 @@ const Filters = (props: FilterProps) => {
|
|
|
439
439
|
|
|
440
440
|
const classList = [
|
|
441
441
|
'single-filters',
|
|
442
|
+
'form-group mr-3',
|
|
442
443
|
mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
|
|
443
444
|
]
|
|
444
445
|
const mobileExempt = ['nested-dropdown', 'multi-select'].includes(filterStyle)
|
|
@@ -446,7 +447,11 @@ const Filters = (props: FilterProps) => {
|
|
|
446
447
|
return (
|
|
447
448
|
<div className={classList.join(' ')} key={outerIndex}>
|
|
448
449
|
<>
|
|
449
|
-
{label &&
|
|
450
|
+
{label && (
|
|
451
|
+
<label className='text-capitalize font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
|
|
452
|
+
{label}
|
|
453
|
+
</label>
|
|
454
|
+
)}
|
|
450
455
|
{filterStyle === 'tab' && !mobileFilterStyle && Tabs}
|
|
451
456
|
{filterStyle === 'pill' && !mobileFilterStyle && Pills}
|
|
452
457
|
{filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
|
|
@@ -463,6 +468,7 @@ const Filters = (props: FilterProps) => {
|
|
|
463
468
|
<NestedDropdown
|
|
464
469
|
activeGroup={(singleFilter.active as string) || (singleFilter.queuedActive || [])[0]}
|
|
465
470
|
activeSubGroup={(singleFilter.subGrouping?.active as string) || (singleFilter.queuedActive || [])[1]}
|
|
471
|
+
filterIndex={outerIndex}
|
|
466
472
|
options={getNestedOptions(singleFilter)}
|
|
467
473
|
listLabel={label}
|
|
468
474
|
handleSelectedItems={value => changeFilterActive(outerIndex, value)}
|
|
@@ -500,14 +506,16 @@ const Filters = (props: FilterProps) => {
|
|
|
500
506
|
{filters?.some(filter => filter.active && filter.columnName) ? filterConstants.introText : ''}{' '}
|
|
501
507
|
{visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
|
|
502
508
|
</p>
|
|
503
|
-
<div className='filters-section__wrapper'>
|
|
509
|
+
<div className='d-flex flex-wrap w-100 filters-section__wrapper'>
|
|
504
510
|
{' '}
|
|
505
511
|
<>
|
|
506
512
|
<Style />
|
|
507
513
|
{filterBehavior === 'Apply Button' ? (
|
|
508
514
|
<div className='filters-section__buttons'>
|
|
509
515
|
<Button
|
|
510
|
-
onClick={
|
|
516
|
+
onClick={e => {
|
|
517
|
+
handleApplyButton(filters)
|
|
518
|
+
}}
|
|
511
519
|
disabled={!showApplyButton}
|
|
512
520
|
className={[general?.headerColor ? general.headerColor : theme, 'apply'].join(' ')}
|
|
513
521
|
>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Group } from '@visx/group'
|
|
2
2
|
import { Text } from '@visx/text'
|
|
3
|
-
import { type
|
|
3
|
+
import { type MapConfig } from '@cdc/map/src/types/MapConfig'
|
|
4
4
|
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
5
|
-
import { getGradientLegendWidth } from '@cdc/core/helpers/getGradientLegendWidth'
|
|
6
5
|
import { getTextWidth } from '../../helpers/getTextWidth'
|
|
7
6
|
import { DimensionsType } from '../../types/Dimensions'
|
|
8
7
|
|
|
8
|
+
const MARGIN = 1
|
|
9
|
+
const BORDER_SIZE = 1
|
|
10
|
+
const MOBILE_BREAKPOINT = 576
|
|
11
|
+
|
|
9
12
|
type CombinedConfig = MapConfig | ChartConfig
|
|
10
13
|
|
|
11
14
|
interface GradientProps {
|
|
@@ -13,30 +16,40 @@ interface GradientProps {
|
|
|
13
16
|
colors: string[]
|
|
14
17
|
config: CombinedConfig
|
|
15
18
|
dimensions: DimensionsType
|
|
16
|
-
|
|
19
|
+
parentPaddingToSubtract?: number
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
const LegendGradient = ({
|
|
22
|
+
const LegendGradient = ({
|
|
23
|
+
labels,
|
|
24
|
+
colors,
|
|
25
|
+
config,
|
|
26
|
+
dimensions,
|
|
27
|
+
parentPaddingToSubtract = 0
|
|
28
|
+
}: GradientProps): JSX.Element => {
|
|
29
|
+
const { uid, legend, type } = config
|
|
30
|
+
const { tickRotation, position, style, subStyle, hideBorder } = legend
|
|
31
|
+
|
|
32
|
+
const isLinearBlocks = subStyle === 'linear blocks'
|
|
20
33
|
let [width] = dimensions
|
|
21
34
|
|
|
22
|
-
const
|
|
23
|
-
const
|
|
35
|
+
const smallScreen = width <= MOBILE_BREAKPOINT
|
|
36
|
+
const legendWidth = Number(width) - parentPaddingToSubtract - MARGIN * 2 - BORDER_SIZE * 2
|
|
37
|
+
const uniqueID = `${uid}-${Date.now()}`
|
|
24
38
|
|
|
25
39
|
const numTicks = colors?.length
|
|
26
40
|
|
|
27
41
|
const longestLabel = labels && labels.length > 0 ? labels.reduce((a, b) => (a.length > b.length ? a : b)) : ''
|
|
28
42
|
const boxHeight = 20
|
|
29
43
|
let height = 50
|
|
30
|
-
const margin = 1
|
|
31
44
|
|
|
32
45
|
// configure tick witch and angle
|
|
33
46
|
const textWidth = getTextWidth(longestLabel, `normal 14px sans-serif`)
|
|
34
|
-
const rotationAngle = Number(
|
|
47
|
+
const rotationAngle = Number(tickRotation) || 0
|
|
35
48
|
// Convert the angle from degrees to radians
|
|
36
49
|
const angleInRadians = rotationAngle * (Math.PI / 180)
|
|
37
50
|
const newHeight = height + Number(textWidth) * Math.sin(angleInRadians)
|
|
38
51
|
|
|
39
|
-
//
|
|
52
|
+
// configure gradient colors
|
|
40
53
|
const stops = colors.map((color, index) => {
|
|
41
54
|
const offset = (index / (colors.length - 1)) * 100
|
|
42
55
|
return <stop key={index} offset={`${offset}%`} style={{ stopColor: color, stopOpacity: 1 }} />
|
|
@@ -45,69 +58,71 @@ const LegendGradient = ({ labels, colors, config, dimensions, currentViewport }:
|
|
|
45
58
|
// render ticks and labels
|
|
46
59
|
const ticks = labels.map((key, index) => {
|
|
47
60
|
const segmentWidth = legendWidth / numTicks
|
|
48
|
-
const xPositionX = index * segmentWidth + segmentWidth
|
|
61
|
+
const xPositionX = index * segmentWidth + segmentWidth + MARGIN
|
|
49
62
|
const textAnchor = rotationAngle ? 'end' : 'middle'
|
|
50
63
|
const verticalAnchor = rotationAngle ? 'middle' : 'start'
|
|
64
|
+
const lastTick = index === labels.length - 1
|
|
51
65
|
|
|
52
66
|
return (
|
|
53
|
-
<Group top={
|
|
54
|
-
<line x1={xPositionX} x2={xPositionX} y1={30} y2={boxHeight} stroke='black' />
|
|
67
|
+
<Group top={MARGIN}>
|
|
68
|
+
{!lastTick && !isLinearBlocks && <line x1={xPositionX} x2={xPositionX} y1={30} y2={boxHeight} stroke='black' />}
|
|
55
69
|
<Text
|
|
56
|
-
angle={-
|
|
70
|
+
angle={-tickRotation}
|
|
57
71
|
x={xPositionX}
|
|
58
72
|
y={boxHeight}
|
|
59
73
|
dy={10}
|
|
60
74
|
dx={-segmentWidth / 2}
|
|
61
|
-
fontSize='14'
|
|
75
|
+
fontSize={smallScreen ? '12' : '14'}
|
|
62
76
|
textAnchor={textAnchor}
|
|
63
77
|
verticalAnchor={verticalAnchor}
|
|
78
|
+
width={segmentWidth}
|
|
79
|
+
lineHeight={'14'}
|
|
64
80
|
>
|
|
65
81
|
{key}
|
|
66
82
|
</Text>
|
|
67
83
|
</Group>
|
|
68
84
|
)
|
|
69
85
|
})
|
|
70
|
-
if ((
|
|
86
|
+
if ((type === 'map' && position === 'side') || !position) {
|
|
71
87
|
return
|
|
72
88
|
}
|
|
73
|
-
if (
|
|
74
|
-
config.type === 'chart' &&
|
|
75
|
-
(config.legend.position === 'left' || config.legend.position === 'right' || !config.legend.position)
|
|
76
|
-
) {
|
|
89
|
+
if (type === 'chart' && (position === 'left' || position === 'right' || !position)) {
|
|
77
90
|
return
|
|
78
91
|
}
|
|
79
92
|
|
|
80
|
-
if (
|
|
93
|
+
if (style === 'gradient') {
|
|
81
94
|
return (
|
|
82
|
-
<svg
|
|
95
|
+
<svg
|
|
96
|
+
style={{ overflow: 'visible', width: '100%', marginTop: 10, marginBottom: hideBorder ? 10 : 0 }}
|
|
97
|
+
height={newHeight}
|
|
98
|
+
>
|
|
83
99
|
{/* background border*/}
|
|
84
|
-
<rect
|
|
85
|
-
x={0}
|
|
86
|
-
y={0}
|
|
87
|
-
width={legendWidth + margin * 2}
|
|
88
|
-
height={boxHeight + margin * 2}
|
|
89
|
-
fill='#d3d3d3'
|
|
90
|
-
strokeWidth='0.5'
|
|
91
|
-
/>
|
|
100
|
+
<rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill='#d3d3d3' />
|
|
92
101
|
{/* Define the gradient */}
|
|
93
102
|
<linearGradient id={`gradient-smooth-${uniqueID}`} x1='0%' y1='0%' x2='100%' y2='0%'>
|
|
94
103
|
{stops}
|
|
95
104
|
</linearGradient>
|
|
96
105
|
|
|
97
|
-
{
|
|
98
|
-
<rect
|
|
106
|
+
{subStyle === 'smooth' && (
|
|
107
|
+
<rect
|
|
108
|
+
x={MARGIN}
|
|
109
|
+
y={MARGIN}
|
|
110
|
+
width={legendWidth}
|
|
111
|
+
height={boxHeight}
|
|
112
|
+
fill={`url(#gradient-smooth-${uniqueID})`}
|
|
113
|
+
/>
|
|
99
114
|
)}
|
|
100
115
|
|
|
101
|
-
{
|
|
116
|
+
{subStyle === 'linear blocks' &&
|
|
102
117
|
colors.map((color, index) => {
|
|
103
118
|
const segmentWidth = legendWidth / numTicks
|
|
104
|
-
const xPosition = index * segmentWidth
|
|
119
|
+
const xPosition = index * segmentWidth + MARGIN
|
|
105
120
|
return (
|
|
106
121
|
<Group>
|
|
107
122
|
<rect
|
|
108
123
|
key={index}
|
|
109
124
|
x={xPosition}
|
|
110
|
-
y={
|
|
125
|
+
y={MARGIN}
|
|
111
126
|
width={segmentWidth}
|
|
112
127
|
height={boxHeight}
|
|
113
128
|
fill={color}
|
|
@@ -22,7 +22,17 @@ interface MultiSelectProps {
|
|
|
22
22
|
tooltip?: React.ReactNode
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
25
|
+
const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
26
|
+
section = null,
|
|
27
|
+
subsection = null,
|
|
28
|
+
fieldName,
|
|
29
|
+
label,
|
|
30
|
+
options,
|
|
31
|
+
updateField,
|
|
32
|
+
selected = [],
|
|
33
|
+
limit,
|
|
34
|
+
tooltip
|
|
35
|
+
}) => {
|
|
26
36
|
const preselectedItems = options.filter(opt => selected.includes(opt.value)).slice(0, limit)
|
|
27
37
|
const [selectedItems, setSelectedItems] = useState<Option[]>(preselectedItems)
|
|
28
38
|
const [expanded, setExpanded] = useState(false)
|
|
@@ -67,77 +77,90 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
|
|
|
67
77
|
|
|
68
78
|
const multiID = 'multiSelect_' + label
|
|
69
79
|
return (
|
|
70
|
-
|
|
80
|
+
<>
|
|
71
81
|
{label && (
|
|
72
|
-
<
|
|
82
|
+
<label className='text-capitalize font-weight-bold' id={multiID + label} htmlFor={multiID}>
|
|
73
83
|
{label}
|
|
74
|
-
</
|
|
84
|
+
</label>
|
|
75
85
|
)}
|
|
86
|
+
<div ref={multiSelectRef} className='cove-multiselect'>
|
|
87
|
+
{tooltip && tooltip}
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<button
|
|
85
|
-
aria-label='Remove'
|
|
86
|
-
onClick={e => {
|
|
87
|
-
e.preventDefault()
|
|
88
|
-
handleItemRemove(item)
|
|
89
|
-
}}
|
|
90
|
-
onKeyUp={e => {
|
|
91
|
-
handleItemRemove(item, e)
|
|
92
|
-
}}
|
|
93
|
-
>
|
|
94
|
-
x
|
|
95
|
-
</button>
|
|
96
|
-
</div>
|
|
97
|
-
))}
|
|
98
|
-
<button
|
|
99
|
-
aria-label={expanded ? 'Collapse' : 'Expand'}
|
|
100
|
-
aria-labelledby={label ? multiID : undefined}
|
|
101
|
-
className='expand'
|
|
102
|
-
onClick={e => {
|
|
103
|
-
e.preventDefault()
|
|
104
|
-
setExpanded(!expanded)
|
|
89
|
+
<div className='wrapper'>
|
|
90
|
+
<div
|
|
91
|
+
id={multiID}
|
|
92
|
+
onClick={() => {
|
|
93
|
+
if (!selectedItems.length) {
|
|
94
|
+
setExpanded(true)
|
|
95
|
+
}
|
|
105
96
|
}}
|
|
97
|
+
className='selected'
|
|
106
98
|
>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
className='
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
99
|
+
{selectedItems.length ? (
|
|
100
|
+
selectedItems.map(item => (
|
|
101
|
+
<div key={item.value} aria-labelledby={label ? multiID + label : undefined}>
|
|
102
|
+
{item.label}
|
|
103
|
+
<button
|
|
104
|
+
aria-label='Remove'
|
|
105
|
+
onClick={e => {
|
|
106
|
+
e.preventDefault()
|
|
107
|
+
handleItemRemove(item)
|
|
108
|
+
}}
|
|
109
|
+
onKeyUp={e => {
|
|
110
|
+
handleItemRemove(item, e)
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
x
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
))
|
|
117
|
+
) : (
|
|
118
|
+
<span className='pl-1 pt-1'>- Select -</span>
|
|
119
|
+
)}
|
|
120
|
+
<button
|
|
121
|
+
aria-label={expanded ? 'Collapse' : 'Expand'}
|
|
122
|
+
aria-labelledby={label ? multiID : undefined}
|
|
123
|
+
className='expand'
|
|
130
124
|
onClick={e => {
|
|
131
125
|
e.preventDefault()
|
|
132
|
-
|
|
126
|
+
setExpanded(!expanded)
|
|
133
127
|
}}
|
|
134
|
-
onKeyUp={e => handleItemSelect(option, e)}
|
|
135
128
|
>
|
|
136
|
-
{
|
|
137
|
-
</
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
129
|
+
<Icon display={'caretDown'} style={{ cursor: 'pointer' }} />
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
{!!limit && (
|
|
133
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
134
|
+
<Tooltip.Target>
|
|
135
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
136
|
+
</Tooltip.Target>
|
|
137
|
+
<Tooltip.Content>
|
|
138
|
+
<p>Select up to {limit} items</p>
|
|
139
|
+
</Tooltip.Content>
|
|
140
|
+
</Tooltip>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
<ul className={'dropdown' + (expanded ? '' : ' d-none')}>
|
|
144
|
+
{options
|
|
145
|
+
.filter(option => !selectedItems.find(item => item.value === option.value))
|
|
146
|
+
.map(option => (
|
|
147
|
+
<li
|
|
148
|
+
className='cove-multiselect-li'
|
|
149
|
+
key={option.value}
|
|
150
|
+
role='option'
|
|
151
|
+
tabIndex={0}
|
|
152
|
+
onClick={e => {
|
|
153
|
+
e.preventDefault()
|
|
154
|
+
handleItemSelect(option, e)
|
|
155
|
+
}}
|
|
156
|
+
onKeyUp={e => handleItemSelect(option, e)}
|
|
157
|
+
>
|
|
158
|
+
{option.label}
|
|
159
|
+
</li>
|
|
160
|
+
))}
|
|
161
|
+
</ul>
|
|
162
|
+
</div>
|
|
163
|
+
</>
|
|
141
164
|
)
|
|
142
165
|
}
|
|
143
166
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
.cove-multiselect {
|
|
2
|
-
position: relative;
|
|
3
2
|
.cove-input__label {
|
|
4
3
|
display: block;
|
|
5
4
|
}
|
|
@@ -8,8 +7,7 @@
|
|
|
8
7
|
align-items: center;
|
|
9
8
|
.selected {
|
|
10
9
|
border: 1px solid var(--lightGray);
|
|
11
|
-
padding:
|
|
12
|
-
min-height: 40px;
|
|
10
|
+
padding: 7px;
|
|
13
11
|
min-width: 200px;
|
|
14
12
|
display: inline-block;
|
|
15
13
|
:is(button) {
|
|
@@ -25,10 +23,14 @@
|
|
|
25
23
|
border-radius: 5px;
|
|
26
24
|
}
|
|
27
25
|
.expand {
|
|
28
|
-
padding:
|
|
29
|
-
|
|
30
|
-
background: var(--lightGray);
|
|
26
|
+
padding: 2px 0px;
|
|
27
|
+
margin-right: -6px;
|
|
31
28
|
float: right;
|
|
29
|
+
margin-bottom: -3px;
|
|
30
|
+
color: var(--mediumGray);
|
|
31
|
+
&:focus {
|
|
32
|
+
outline: none;
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
border-radius: 5px;
|
|
34
36
|
}
|
|
@@ -43,7 +45,8 @@
|
|
|
43
45
|
.dropdown {
|
|
44
46
|
background: white;
|
|
45
47
|
position: absolute;
|
|
46
|
-
|
|
48
|
+
top: var(--select-height);
|
|
49
|
+
margin-top: 0px;
|
|
47
50
|
border: 1px solid var(--lightGray);
|
|
48
51
|
padding-left: 0;
|
|
49
52
|
min-height: 40px;
|