@cdc/core 4.24.9 → 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/LICENSE +201 -0
- package/assets/icon-combo-chart.svg +1 -0
- package/assets/icon-epi-chart.svg +27 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +17 -13
- package/components/Alert/components/Alert.tsx +34 -8
- package/components/BlurStrokeText.tsx +44 -0
- package/components/DataTable/DataTable.tsx +62 -36
- package/components/DataTable/DataTableStandAlone.tsx +37 -6
- package/components/DataTable/components/ChartHeader.tsx +31 -26
- package/components/DataTable/components/MapHeader.tsx +19 -10
- package/components/DataTable/components/SortIcon/index.tsx +25 -0
- package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
- package/{styles/_data-table.scss → components/DataTable/data-table.css} +250 -298
- package/components/DataTable/helpers/boxplotCellMatrix.tsx +14 -13
- package/components/DataTable/helpers/customSort.ts +11 -15
- package/components/DataTable/helpers/getChartCellValue.ts +23 -5
- package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
- package/components/DataTable/helpers/getNewSortBy.ts +35 -0
- package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
- package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
- package/components/EditorPanel/ColumnsEditor.tsx +81 -36
- package/components/EditorPanel/DataTableEditor.tsx +149 -43
- package/components/EditorPanel/FieldSetWrapper.tsx +2 -2
- package/components/EditorPanel/Inputs.tsx +68 -20
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +30 -55
- package/components/{Filters.tsx → Filters/Filters.tsx} +60 -43
- package/components/Filters/helpers/applyQueuedActive.ts +12 -0
- package/components/Filters/helpers/getNestedOptions.ts +29 -0
- package/components/Filters/helpers/handleSorting.ts +18 -0
- package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
- package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
- package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
- package/components/Filters/index.ts +5 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -7
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/Legend/Legend.Gradient.tsx +44 -36
- package/components/Loader/Loader.tsx +33 -0
- package/components/Loader/index.ts +1 -0
- package/components/Loader/loader.styles.css +13 -0
- package/components/MultiSelect/MultiSelect.tsx +85 -62
- package/components/MultiSelect/multiselect.styles.css +10 -7
- package/components/NestedDropdown/NestedDropdown.tsx +118 -56
- package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
- package/components/NestedDropdown/nesteddropdown.styles.css +22 -13
- package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
- package/components/Table/Table.tsx +102 -34
- package/components/Table/components/GroupRow.tsx +1 -1
- package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
- package/components/_stories/DataTable.stories.tsx +14 -0
- package/components/_stories/Filters.stories.tsx +57 -0
- package/components/_stories/NestedDropdown.stories.tsx +22 -46
- package/components/_stories/_mocks/DataTable/no-data.json +108 -0
- package/components/_stories/_mocks/nested-dropdown.json +30 -0
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Icon.tsx +19 -6
- package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
- package/data/colorPalettes.js +107 -10
- package/dist/cove-main.css +6080 -0
- package/dist/cove-main.css.map +1 -0
- package/helpers/DataTransform.ts +2 -1
- package/helpers/addValuesToFilters.ts +8 -3
- package/helpers/cove/{number.js → number.ts} +62 -27
- package/helpers/coveUpdateWorker.ts +6 -7
- package/helpers/fetchRemoteData.js +32 -37
- package/helpers/formatConfigBeforeSave.ts +17 -1
- package/helpers/gatherQueryParams.ts +12 -2
- package/helpers/pivotData.ts +52 -11
- package/helpers/queryStringUtils.ts +6 -0
- package/helpers/tests/gatherQueryParams.test.ts +34 -0
- package/helpers/tests/pivotData.test.ts +50 -0
- package/helpers/useDataVizClasses.ts +42 -20
- package/helpers/ver/4.24.10.ts +47 -0
- package/helpers/ver/4.24.9.ts +0 -3
- package/helpers/ver/tests/4.24.10.test.ts +45 -0
- package/helpers/viewports.ts +9 -0
- package/package.json +7 -3
- package/styles/_button-section.scss +5 -1
- package/styles/_global-variables.scss +20 -2
- package/styles/_global.scss +22 -30
- package/styles/_reset.scss +2 -26
- package/styles/base.scss +0 -1
- package/styles/cove-main.scss +6 -0
- package/styles/filters.scss +6 -26
- 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/styles/v2/components/ui/tooltip.scss +42 -40
- package/styles/v2/layout/_component.scss +0 -6
- package/styles/v2/layout/index.scss +0 -1
- package/types/Axis.ts +4 -0
- package/types/BoxPlot.ts +5 -3
- package/types/Color.ts +1 -1
- package/types/General.ts +1 -0
- 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 +3 -2
- package/types/Visualization.ts +19 -8
- package/types/VizFilter.ts +2 -1
- package/components/DataTable/components/Icons.tsx +0 -10
- package/components/_stories/EditorPanel.stories.tsx +0 -54
- package/components/_stories/Layout.Debug.stories.tsx +0 -91
- package/components/ui/Select.jsx +0 -30
- package/helpers/getGradientLegendWidth.ts +0 -15
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getNestedOptions } from '../getNestedOptions'
|
|
3
|
+
import { SubGrouping } from '../../../../types/VizFilter'
|
|
4
|
+
import { NestedOptions } from '../../../NestedDropdown/nestedDropdownHelpers'
|
|
5
|
+
|
|
6
|
+
describe('getNestedOptions', () => {
|
|
7
|
+
it('should return nested options when orderedValues is not provided', () => {
|
|
8
|
+
const params = {
|
|
9
|
+
values: ['value1', 'value2'],
|
|
10
|
+
subGrouping: null
|
|
11
|
+
}
|
|
12
|
+
const expectedOutput: NestedOptions = [
|
|
13
|
+
[['value1'], []],
|
|
14
|
+
[['value2'], []]
|
|
15
|
+
]
|
|
16
|
+
expect(getNestedOptions(params)).toEqual(expectedOutput)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should return nested options when orderedValues is provided', () => {
|
|
20
|
+
const params = {
|
|
21
|
+
orderedValues: ['value2', 'value1'],
|
|
22
|
+
values: ['value1', 'value2'],
|
|
23
|
+
subGrouping: null
|
|
24
|
+
}
|
|
25
|
+
const expectedOutput: NestedOptions = [
|
|
26
|
+
[['value2'], []],
|
|
27
|
+
[['value1'], []]
|
|
28
|
+
]
|
|
29
|
+
expect(getNestedOptions(params)).toEqual(expectedOutput)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should return nested options when subGrouping is not provided', () => {
|
|
33
|
+
const params = {
|
|
34
|
+
values: ['value1', 'value2'],
|
|
35
|
+
subGrouping: null
|
|
36
|
+
}
|
|
37
|
+
const expectedOutput: NestedOptions = [
|
|
38
|
+
[['value1'], []],
|
|
39
|
+
[['value2'], []]
|
|
40
|
+
]
|
|
41
|
+
expect(getNestedOptions(params)).toEqual(expectedOutput)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should return nested options when subGrouping is provided with nested values', () => {
|
|
45
|
+
const subGrouping: SubGrouping = {
|
|
46
|
+
valuesLookup: {
|
|
47
|
+
value1: {
|
|
48
|
+
orderedValues: ['subValue2', 'subValue1'],
|
|
49
|
+
values: ['subValue1', 'subValue2']
|
|
50
|
+
},
|
|
51
|
+
value2: {
|
|
52
|
+
orderedValues: null,
|
|
53
|
+
values: ['subValue3']
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const params = {
|
|
58
|
+
orderedValues: ['value1', 'value2'],
|
|
59
|
+
values: ['value1', 'value2'],
|
|
60
|
+
subGrouping
|
|
61
|
+
}
|
|
62
|
+
const expectedOutput: NestedOptions = [
|
|
63
|
+
[['value1'], [['subValue2'], ['subValue1']]],
|
|
64
|
+
[['value2'], [['subValue3']]]
|
|
65
|
+
]
|
|
66
|
+
expect(getNestedOptions(params)).toEqual(expectedOutput)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should return nested options when subGrouping is provided without orderedValues', () => {
|
|
70
|
+
const subGrouping: SubGrouping = {
|
|
71
|
+
valuesLookup: {
|
|
72
|
+
value1: {
|
|
73
|
+
orderedValues: null,
|
|
74
|
+
values: ['subValue1', 'subValue2']
|
|
75
|
+
},
|
|
76
|
+
value2: {
|
|
77
|
+
orderedValues: null,
|
|
78
|
+
values: ['subValue3']
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const params = {
|
|
83
|
+
orderedValues: ['value1', 'value2'],
|
|
84
|
+
values: ['value1', 'value2'],
|
|
85
|
+
subGrouping
|
|
86
|
+
}
|
|
87
|
+
const expectedOutput: NestedOptions = [
|
|
88
|
+
[['value1'], [['subValue1'], ['subValue2']]],
|
|
89
|
+
[['value2'], [['subValue3']]]
|
|
90
|
+
]
|
|
91
|
+
expect(getNestedOptions(params)).toEqual(expectedOutput)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { handleSorting } from '../handleSorting'
|
|
3
|
+
import _ from 'lodash'
|
|
4
|
+
|
|
5
|
+
describe('handleSorting', () => {
|
|
6
|
+
it('should use orderedValues when order is "cust" and filterStyle is not "nested-dropdown"', () => {
|
|
7
|
+
const singleFilter = {
|
|
8
|
+
values: ['value3', 'value1', 'value2'],
|
|
9
|
+
orderedValues: ['value1', 'value2', 'value3'],
|
|
10
|
+
order: 'cust',
|
|
11
|
+
filterStyle: 'someOtherStyle'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const result = handleSorting(singleFilter)
|
|
15
|
+
|
|
16
|
+
expect(result.values).toEqual(['value1', 'value2', 'value3'])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should sort values in ascending order by default', () => {
|
|
20
|
+
const singleFilter = {
|
|
21
|
+
values: ['value3', 'value1', 'value2'],
|
|
22
|
+
order: 'asc',
|
|
23
|
+
filterStyle: 'someOtherStyle'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = handleSorting(singleFilter)
|
|
27
|
+
|
|
28
|
+
expect(result.values).toEqual(['value1', 'value2', 'value3'])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should sort values in descending order when order is "desc"', () => {
|
|
32
|
+
const singleFilter = {
|
|
33
|
+
values: ['value3', 'value1', 'value2'],
|
|
34
|
+
order: 'desc',
|
|
35
|
+
filterStyle: 'someOtherStyle'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const result = handleSorting(singleFilter)
|
|
39
|
+
|
|
40
|
+
expect(result.values).toEqual(['value3', 'value2', 'value1'])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should not use orderedValues when filterStyle is "nested-dropdown"', () => {
|
|
44
|
+
const singleFilter = {
|
|
45
|
+
values: ['value3', 'value1', 'value2'],
|
|
46
|
+
orderedValues: ['value1', 'value2', 'value3'],
|
|
47
|
+
order: 'cust',
|
|
48
|
+
filterStyle: 'nested-dropdown'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = handleSorting(singleFilter)
|
|
52
|
+
|
|
53
|
+
expect(result.values).toEqual(['value1', 'value2', 'value3'])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should handle empty orderedValues when order is "cust"', () => {
|
|
57
|
+
const singleFilter = {
|
|
58
|
+
values: ['value3', 'value1', 'value2'],
|
|
59
|
+
orderedValues: [],
|
|
60
|
+
order: 'cust',
|
|
61
|
+
filterStyle: 'someOtherStyle'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = handleSorting(singleFilter)
|
|
65
|
+
|
|
66
|
+
expect(result.values).toEqual(['value3', 'value1', 'value2'])
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
@import '@cdc/core/styles/v2/themes/_color-definitions.scss';
|
|
2
|
-
|
|
3
1
|
.cdc-editor .configure .type-dashboard .sidebar {
|
|
4
2
|
top: 0;
|
|
5
3
|
}
|
|
@@ -210,7 +208,7 @@
|
|
|
210
208
|
|
|
211
209
|
svg {
|
|
212
210
|
width: 60px;
|
|
213
|
-
color:
|
|
211
|
+
color: var(--blue);
|
|
214
212
|
margin-right: 1rem;
|
|
215
213
|
height: 60px; // IE11
|
|
216
214
|
path {
|
|
@@ -544,10 +542,6 @@
|
|
|
544
542
|
font-weight: normal;
|
|
545
543
|
}
|
|
546
544
|
|
|
547
|
-
.btn {
|
|
548
|
-
margin-top: 1em;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
545
|
.sort-list {
|
|
552
546
|
list-style: none;
|
|
553
547
|
|
|
@@ -1,10 +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 {
|
|
5
|
+
import { getTextWidth } from '../../helpers/getTextWidth'
|
|
6
6
|
import { DimensionsType } from '../../types/Dimensions'
|
|
7
7
|
|
|
8
|
+
const MARGIN = 1
|
|
9
|
+
const BORDER_SIZE = 1
|
|
10
|
+
const MOBILE_BREAKPOINT = 576
|
|
11
|
+
|
|
8
12
|
type CombinedConfig = MapConfig | ChartConfig
|
|
9
13
|
|
|
10
14
|
interface GradientProps {
|
|
@@ -12,8 +16,7 @@ interface GradientProps {
|
|
|
12
16
|
colors: string[]
|
|
13
17
|
config: CombinedConfig
|
|
14
18
|
dimensions: DimensionsType
|
|
15
|
-
|
|
16
|
-
getTextWidth: (text: string, font: string) => string
|
|
19
|
+
parentPaddingToSubtract?: number
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const LegendGradient = ({
|
|
@@ -21,29 +24,32 @@ const LegendGradient = ({
|
|
|
21
24
|
colors,
|
|
22
25
|
config,
|
|
23
26
|
dimensions,
|
|
24
|
-
|
|
25
|
-
getTextWidth
|
|
27
|
+
parentPaddingToSubtract = 0
|
|
26
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'
|
|
27
33
|
let [width] = dimensions
|
|
28
34
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
35
|
+
const smallScreen = width <= MOBILE_BREAKPOINT
|
|
36
|
+
const legendWidth = Number(width) - parentPaddingToSubtract - MARGIN * 2 - BORDER_SIZE * 2
|
|
37
|
+
const uniqueID = `${uid}-${Date.now()}`
|
|
31
38
|
|
|
32
39
|
const numTicks = colors?.length
|
|
33
40
|
|
|
34
41
|
const longestLabel = labels && labels.length > 0 ? labels.reduce((a, b) => (a.length > b.length ? a : b)) : ''
|
|
35
42
|
const boxHeight = 20
|
|
36
43
|
let height = 50
|
|
37
|
-
const margin = 1
|
|
38
44
|
|
|
39
45
|
// configure tick witch and angle
|
|
40
46
|
const textWidth = getTextWidth(longestLabel, `normal 14px sans-serif`)
|
|
41
|
-
const rotationAngle = Number(
|
|
47
|
+
const rotationAngle = Number(tickRotation) || 0
|
|
42
48
|
// Convert the angle from degrees to radians
|
|
43
49
|
const angleInRadians = rotationAngle * (Math.PI / 180)
|
|
44
50
|
const newHeight = height + Number(textWidth) * Math.sin(angleInRadians)
|
|
45
51
|
|
|
46
|
-
//
|
|
52
|
+
// configure gradient colors
|
|
47
53
|
const stops = colors.map((color, index) => {
|
|
48
54
|
const offset = (index / (colors.length - 1)) * 100
|
|
49
55
|
return <stop key={index} offset={`${offset}%`} style={{ stopColor: color, stopOpacity: 1 }} />
|
|
@@ -52,69 +58,71 @@ const LegendGradient = ({
|
|
|
52
58
|
// render ticks and labels
|
|
53
59
|
const ticks = labels.map((key, index) => {
|
|
54
60
|
const segmentWidth = legendWidth / numTicks
|
|
55
|
-
const xPositionX = index * segmentWidth + segmentWidth
|
|
61
|
+
const xPositionX = index * segmentWidth + segmentWidth + MARGIN
|
|
56
62
|
const textAnchor = rotationAngle ? 'end' : 'middle'
|
|
57
63
|
const verticalAnchor = rotationAngle ? 'middle' : 'start'
|
|
64
|
+
const lastTick = index === labels.length - 1
|
|
58
65
|
|
|
59
66
|
return (
|
|
60
|
-
<Group top={
|
|
61
|
-
<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' />}
|
|
62
69
|
<Text
|
|
63
|
-
angle={-
|
|
70
|
+
angle={-tickRotation}
|
|
64
71
|
x={xPositionX}
|
|
65
72
|
y={boxHeight}
|
|
66
73
|
dy={10}
|
|
67
74
|
dx={-segmentWidth / 2}
|
|
68
|
-
fontSize='14'
|
|
75
|
+
fontSize={smallScreen ? '12' : '14'}
|
|
69
76
|
textAnchor={textAnchor}
|
|
70
77
|
verticalAnchor={verticalAnchor}
|
|
78
|
+
width={segmentWidth}
|
|
79
|
+
lineHeight={'14'}
|
|
71
80
|
>
|
|
72
81
|
{key}
|
|
73
82
|
</Text>
|
|
74
83
|
</Group>
|
|
75
84
|
)
|
|
76
85
|
})
|
|
77
|
-
if ((
|
|
86
|
+
if ((type === 'map' && position === 'side') || !position) {
|
|
78
87
|
return
|
|
79
88
|
}
|
|
80
|
-
if (
|
|
81
|
-
config.type === 'chart' &&
|
|
82
|
-
(config.legend.position === 'left' || config.legend.position === 'right' || !config.legend.position)
|
|
83
|
-
) {
|
|
89
|
+
if (type === 'chart' && (position === 'left' || position === 'right' || !position)) {
|
|
84
90
|
return
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
if (
|
|
93
|
+
if (style === 'gradient') {
|
|
88
94
|
return (
|
|
89
|
-
<svg
|
|
95
|
+
<svg
|
|
96
|
+
style={{ overflow: 'visible', width: '100%', marginTop: 10, marginBottom: hideBorder ? 10 : 0 }}
|
|
97
|
+
height={newHeight}
|
|
98
|
+
>
|
|
90
99
|
{/* background border*/}
|
|
91
|
-
<rect
|
|
92
|
-
x={0}
|
|
93
|
-
y={0}
|
|
94
|
-
width={legendWidth + margin * 2}
|
|
95
|
-
height={boxHeight + margin * 2}
|
|
96
|
-
fill='#d3d3d3'
|
|
97
|
-
strokeWidth='0.5'
|
|
98
|
-
/>
|
|
100
|
+
<rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill='#d3d3d3' />
|
|
99
101
|
{/* Define the gradient */}
|
|
100
102
|
<linearGradient id={`gradient-smooth-${uniqueID}`} x1='0%' y1='0%' x2='100%' y2='0%'>
|
|
101
103
|
{stops}
|
|
102
104
|
</linearGradient>
|
|
103
105
|
|
|
104
|
-
{
|
|
105
|
-
<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
|
+
/>
|
|
106
114
|
)}
|
|
107
115
|
|
|
108
|
-
{
|
|
116
|
+
{subStyle === 'linear blocks' &&
|
|
109
117
|
colors.map((color, index) => {
|
|
110
118
|
const segmentWidth = legendWidth / numTicks
|
|
111
|
-
const xPosition = index * segmentWidth
|
|
119
|
+
const xPosition = index * segmentWidth + MARGIN
|
|
112
120
|
return (
|
|
113
121
|
<Group>
|
|
114
122
|
<rect
|
|
115
123
|
key={index}
|
|
116
124
|
x={xPosition}
|
|
117
|
-
y={
|
|
125
|
+
y={MARGIN}
|
|
118
126
|
width={segmentWidth}
|
|
119
127
|
height={boxHeight}
|
|
120
128
|
fill={color}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import './loader.styles.css'
|
|
3
|
+
|
|
4
|
+
type LoaderProps = {
|
|
5
|
+
fullScreen?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Spinner = () => (
|
|
9
|
+
<div className='spinner-border text-primary' role='status'>
|
|
10
|
+
<span className='sr-only'>Loading...</span>
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
const Loader: React.FC<LoaderProps> = ({ fullScreen = false }) => {
|
|
15
|
+
const backgroundRef = useRef(null)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (backgroundRef?.current) {
|
|
19
|
+
const backgroundHeight = backgroundRef.current.parentElement.clientHeight
|
|
20
|
+
backgroundRef.current.style.height = `${backgroundHeight}px`
|
|
21
|
+
}
|
|
22
|
+
}, [])
|
|
23
|
+
|
|
24
|
+
return fullScreen ? (
|
|
25
|
+
<div ref={backgroundRef} className='cove-loader fullscreen'>
|
|
26
|
+
<Spinner />
|
|
27
|
+
</div>
|
|
28
|
+
) : (
|
|
29
|
+
<Spinner />
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default Loader
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Loader'
|
|
@@ -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;
|