@pareto-engineering/design-system 2.0.0-alpha.45 → 2.0.0-alpha.48
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/cjs/f/FormInput/FormInput.js +8 -1
- package/dist/cjs/f/fields/QueryCombobox/QueryCombobox.js +29 -6
- package/dist/cjs/f/fields/QueryCombobox/common/Combobox/Combobox.js +29 -5
- package/dist/cjs/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.js +101 -26
- package/dist/cjs/f/fields/QueryCombobox/common/index.js +9 -1
- package/dist/cjs/f/fields/QueryCombobox/styles.scss +24 -5
- package/dist/es/f/FormInput/FormInput.js +9 -2
- package/dist/es/f/fields/QueryCombobox/QueryCombobox.js +30 -7
- package/dist/es/f/fields/QueryCombobox/common/Combobox/Combobox.js +30 -6
- package/dist/es/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.js +101 -27
- package/dist/es/f/fields/QueryCombobox/common/index.js +2 -1
- package/dist/es/f/fields/QueryCombobox/styles.scss +24 -5
- package/package.json +2 -2
- package/src/__snapshots__/Storyshots.test.js.snap +379 -14
- package/src/local.scss +3 -3
- package/src/stories/f/FormInput.stories.jsx +115 -0
- package/src/stories/f/QueryCombobox.stories.jsx +55 -8
- package/src/stories/f/__generated__/FormInputAllTeamsQuery.graphql.js +139 -0
- package/src/ui/f/FormInput/FormInput.jsx +11 -0
- package/src/ui/f/fields/QueryCombobox/QueryCombobox.jsx +29 -6
- package/src/ui/f/fields/QueryCombobox/common/Combobox/Combobox.jsx +27 -3
- package/src/ui/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.jsx +318 -0
- package/src/ui/f/fields/QueryCombobox/common/MultipleCombobox/index.js +2 -0
- package/src/ui/f/fields/QueryCombobox/common/index.js +1 -0
- package/src/ui/f/fields/QueryCombobox/styles.scss +24 -5
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* eslint-disable */
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/*::
|
|
10
|
+
import type { ConcreteRequest } from 'relay-runtime';
|
|
11
|
+
export type FormInputAllTeamsQueryVariables = {|
|
|
12
|
+
name_Icontains?: ?string
|
|
13
|
+
|};
|
|
14
|
+
export type FormInputAllTeamsQueryResponse = {|
|
|
15
|
+
+allTeams: ?{|
|
|
16
|
+
+edges: $ReadOnlyArray<?{|
|
|
17
|
+
+node: ?{|
|
|
18
|
+
+id: string,
|
|
19
|
+
+name: string,
|
|
20
|
+
|}
|
|
21
|
+
|}>
|
|
22
|
+
|}
|
|
23
|
+
|};
|
|
24
|
+
export type FormInputAllTeamsQuery = {|
|
|
25
|
+
variables: FormInputAllTeamsQueryVariables,
|
|
26
|
+
response: FormInputAllTeamsQueryResponse,
|
|
27
|
+
|};
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
query FormInputAllTeamsQuery(
|
|
33
|
+
$name_Icontains: String
|
|
34
|
+
) {
|
|
35
|
+
allTeams(name_Icontains: $name_Icontains) {
|
|
36
|
+
edges {
|
|
37
|
+
node {
|
|
38
|
+
id
|
|
39
|
+
name
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const node/*: ConcreteRequest*/ = (function(){
|
|
47
|
+
var v0 = [
|
|
48
|
+
{
|
|
49
|
+
"defaultValue": null,
|
|
50
|
+
"kind": "LocalArgument",
|
|
51
|
+
"name": "name_Icontains"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
v1 = [
|
|
55
|
+
{
|
|
56
|
+
"alias": null,
|
|
57
|
+
"args": [
|
|
58
|
+
{
|
|
59
|
+
"kind": "Variable",
|
|
60
|
+
"name": "name_Icontains",
|
|
61
|
+
"variableName": "name_Icontains"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"concreteType": "TeamNodeConnection",
|
|
65
|
+
"kind": "LinkedField",
|
|
66
|
+
"name": "allTeams",
|
|
67
|
+
"plural": false,
|
|
68
|
+
"selections": [
|
|
69
|
+
{
|
|
70
|
+
"alias": null,
|
|
71
|
+
"args": null,
|
|
72
|
+
"concreteType": "TeamNodeEdge",
|
|
73
|
+
"kind": "LinkedField",
|
|
74
|
+
"name": "edges",
|
|
75
|
+
"plural": true,
|
|
76
|
+
"selections": [
|
|
77
|
+
{
|
|
78
|
+
"alias": null,
|
|
79
|
+
"args": null,
|
|
80
|
+
"concreteType": "TeamNode",
|
|
81
|
+
"kind": "LinkedField",
|
|
82
|
+
"name": "node",
|
|
83
|
+
"plural": false,
|
|
84
|
+
"selections": [
|
|
85
|
+
{
|
|
86
|
+
"alias": null,
|
|
87
|
+
"args": null,
|
|
88
|
+
"kind": "ScalarField",
|
|
89
|
+
"name": "id",
|
|
90
|
+
"storageKey": null
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"alias": null,
|
|
94
|
+
"args": null,
|
|
95
|
+
"kind": "ScalarField",
|
|
96
|
+
"name": "name",
|
|
97
|
+
"storageKey": null
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"storageKey": null
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"storageKey": null
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"storageKey": null
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
return {
|
|
110
|
+
"fragment": {
|
|
111
|
+
"argumentDefinitions": (v0/*: any*/),
|
|
112
|
+
"kind": "Fragment",
|
|
113
|
+
"metadata": null,
|
|
114
|
+
"name": "FormInputAllTeamsQuery",
|
|
115
|
+
"selections": (v1/*: any*/),
|
|
116
|
+
"type": "Query",
|
|
117
|
+
"abstractKey": null
|
|
118
|
+
},
|
|
119
|
+
"kind": "Request",
|
|
120
|
+
"operation": {
|
|
121
|
+
"argumentDefinitions": (v0/*: any*/),
|
|
122
|
+
"kind": "Operation",
|
|
123
|
+
"name": "FormInputAllTeamsQuery",
|
|
124
|
+
"selections": (v1/*: any*/)
|
|
125
|
+
},
|
|
126
|
+
"params": {
|
|
127
|
+
"cacheID": "dc9287c6d087d0f0e1be2e8ef405cb1f",
|
|
128
|
+
"id": null,
|
|
129
|
+
"metadata": {},
|
|
130
|
+
"name": "FormInputAllTeamsQuery",
|
|
131
|
+
"operationKind": "query",
|
|
132
|
+
"text": "query FormInputAllTeamsQuery(\n $name_Icontains: String\n) {\n allTeams(name_Icontains: $name_Icontains) {\n edges {\n node {\n id\n name\n }\n }\n }\n}\n"
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
})();
|
|
136
|
+
// prettier-ignore
|
|
137
|
+
(node/*: any*/).hash = 'c76b6f84436895e8a6d0725bdd41cfe0';
|
|
138
|
+
|
|
139
|
+
module.exports = node;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
TextareaInput,
|
|
11
11
|
ChoicesInput,
|
|
12
12
|
SelectInput,
|
|
13
|
+
QueryCombobox,
|
|
13
14
|
} from '../fields'
|
|
14
15
|
|
|
15
16
|
// Local Definitions
|
|
@@ -64,6 +65,15 @@ const FormInput = ({
|
|
|
64
65
|
/>
|
|
65
66
|
)
|
|
66
67
|
}
|
|
68
|
+
if (type === 'query-combobox') {
|
|
69
|
+
return (
|
|
70
|
+
<QueryCombobox
|
|
71
|
+
className={newClassName}
|
|
72
|
+
disabled={disabled}
|
|
73
|
+
{...otherProps}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
67
77
|
if (extraTypes?.[type]) {
|
|
68
78
|
const Component = extraTypes[type]
|
|
69
79
|
return (
|
|
@@ -106,6 +116,7 @@ FormInput.propTypes = {
|
|
|
106
116
|
'select',
|
|
107
117
|
'choices',
|
|
108
118
|
'textarea',
|
|
119
|
+
'query-combobox',
|
|
109
120
|
// to be removed
|
|
110
121
|
'extendedTypeInput',
|
|
111
122
|
]),
|
|
@@ -11,14 +11,17 @@ import PropTypes from 'prop-types'
|
|
|
11
11
|
|
|
12
12
|
// Local Definitions
|
|
13
13
|
|
|
14
|
-
import { Combobox } from './common'
|
|
14
|
+
import { Combobox, MultipleCombobox } from './common'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* This is the component description.
|
|
18
18
|
*/
|
|
19
19
|
const QueryCombobox = ({
|
|
20
|
+
id,
|
|
21
|
+
style,
|
|
22
|
+
className,
|
|
20
23
|
query,
|
|
21
|
-
|
|
24
|
+
multiple,
|
|
22
25
|
name,
|
|
23
26
|
label,
|
|
24
27
|
color,
|
|
@@ -29,6 +32,8 @@ const QueryCombobox = ({
|
|
|
29
32
|
searchVariable,
|
|
30
33
|
extraVariables,
|
|
31
34
|
optionsKeyMap,
|
|
35
|
+
minLength,
|
|
36
|
+
transformSearch,
|
|
32
37
|
// ...otherProps
|
|
33
38
|
}) => {
|
|
34
39
|
useLayoutEffect(() => {
|
|
@@ -85,6 +90,8 @@ const QueryCombobox = ({
|
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
const comboboxProps = {
|
|
93
|
+
id,
|
|
94
|
+
style,
|
|
88
95
|
options,
|
|
89
96
|
getOptions,
|
|
90
97
|
debounceMs,
|
|
@@ -96,9 +103,13 @@ const QueryCombobox = ({
|
|
|
96
103
|
error,
|
|
97
104
|
value,
|
|
98
105
|
color,
|
|
106
|
+
isFetching,
|
|
107
|
+
className,
|
|
108
|
+
minLength,
|
|
109
|
+
transformSearch,
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
const Input = Combobox
|
|
112
|
+
const Input = multiple ? MultipleCombobox : Combobox
|
|
102
113
|
|
|
103
114
|
return <Input {...comboboxProps} />
|
|
104
115
|
}
|
|
@@ -185,6 +196,16 @@ QueryCombobox.propTypes = {
|
|
|
185
196
|
* The variable to be used to search the data
|
|
186
197
|
*/
|
|
187
198
|
searchVariable:PropTypes.string,
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The minimum length of the search input to start fetching the options
|
|
202
|
+
*/
|
|
203
|
+
minLength:PropTypes.number,
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The function to transform the search input
|
|
207
|
+
*/
|
|
208
|
+
transformSearch:PropTypes.func,
|
|
188
209
|
}
|
|
189
210
|
|
|
190
211
|
QueryCombobox.defaultProps = {
|
|
@@ -192,9 +213,11 @@ QueryCombobox.defaultProps = {
|
|
|
192
213
|
value:'id',
|
|
193
214
|
label:'name',
|
|
194
215
|
},
|
|
195
|
-
multiple
|
|
196
|
-
color
|
|
197
|
-
searchVariable:'search',
|
|
216
|
+
multiple :false,
|
|
217
|
+
color :'background2',
|
|
218
|
+
searchVariable :'search',
|
|
219
|
+
transformSearch:(search) => search,
|
|
220
|
+
minLength :2,
|
|
198
221
|
}
|
|
199
222
|
|
|
200
223
|
export default QueryCombobox
|
|
@@ -11,7 +11,7 @@ import styleNames from '@pareto-engineering/bem'
|
|
|
11
11
|
|
|
12
12
|
import { FormLabel, FormDescription } from 'ui/f'
|
|
13
13
|
|
|
14
|
-
import { Popover } from 'ui/a'
|
|
14
|
+
import { Popover, LoadingCircle } from 'ui/a'
|
|
15
15
|
|
|
16
16
|
// Local Definitions
|
|
17
17
|
|
|
@@ -37,6 +37,9 @@ const Combobox = ({
|
|
|
37
37
|
description,
|
|
38
38
|
value,
|
|
39
39
|
color,
|
|
40
|
+
minLength,
|
|
41
|
+
isFetching,
|
|
42
|
+
transformSearch,
|
|
40
43
|
// ...otherProps
|
|
41
44
|
}) => {
|
|
42
45
|
const {
|
|
@@ -54,7 +57,10 @@ const Combobox = ({
|
|
|
54
57
|
initialSelectedItem:value,
|
|
55
58
|
itemToString :(item) => (item ? item.label : ''),
|
|
56
59
|
onInputValueChange :({ inputValue }) => {
|
|
57
|
-
|
|
60
|
+
const transformedInput = transformSearch(inputValue)
|
|
61
|
+
if (transformedInput.length > minLength) {
|
|
62
|
+
getOptions(transformedInput)
|
|
63
|
+
}
|
|
58
64
|
},
|
|
59
65
|
})
|
|
60
66
|
|
|
@@ -97,6 +103,9 @@ const Combobox = ({
|
|
|
97
103
|
|
|
98
104
|
<div {...getComboboxProps()} className="input-wrapper">
|
|
99
105
|
<input {...getInputProps()} className="input" />
|
|
106
|
+
{isFetching && (
|
|
107
|
+
<LoadingCircle className="x-main2" />
|
|
108
|
+
)}
|
|
100
109
|
</div>
|
|
101
110
|
|
|
102
111
|
<Popover
|
|
@@ -189,10 +198,25 @@ Combobox.propTypes = {
|
|
|
189
198
|
* The base color of the combobox custom select input
|
|
190
199
|
*/
|
|
191
200
|
color:PropTypes.string,
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Whether the query getting the combobox options is inFlight
|
|
204
|
+
*/
|
|
205
|
+
isFetching:PropTypes.bool.isRequired,
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The minimum length of the search input to start fetching the options
|
|
209
|
+
*/
|
|
210
|
+
minLength:PropTypes.number,
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The function to transform the search input
|
|
214
|
+
*/
|
|
215
|
+
transformSearch:PropTypes.func,
|
|
192
216
|
}
|
|
193
217
|
|
|
194
218
|
Combobox.defaultProps = {
|
|
195
|
-
// someProp:false
|
|
219
|
+
// someProp: false
|
|
196
220
|
}
|
|
197
221
|
|
|
198
222
|
export default Combobox
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/* @pareto-engineering/generator-front 1.0.12 */
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
import { useState, useEffect, useRef } from 'react'
|
|
5
|
+
|
|
6
|
+
import PropTypes from 'prop-types'
|
|
7
|
+
|
|
8
|
+
import styleNames from '@pareto-engineering/bem'
|
|
9
|
+
|
|
10
|
+
import { useCombobox, useMultipleSelection } from 'downshift'
|
|
11
|
+
|
|
12
|
+
import { Button } from 'ui/b'
|
|
13
|
+
|
|
14
|
+
import { Popover, LoadingCircle } from 'ui/a'
|
|
15
|
+
|
|
16
|
+
import { FormDescription, FormLabel } from 'ui/f'
|
|
17
|
+
|
|
18
|
+
// Local Definitions
|
|
19
|
+
|
|
20
|
+
import { Menu } from '../Menu'
|
|
21
|
+
|
|
22
|
+
const baseClassName = styleNames.base
|
|
23
|
+
|
|
24
|
+
const componentClassName = 'multiple-combobox'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Array[Object]} first - first array to check if it has an item not in the second array.
|
|
28
|
+
* @param {Array[Object]} second - second array to check against the first array.
|
|
29
|
+
*
|
|
30
|
+
* @returns {Boolean} - true if the first array has an item not in the second array.
|
|
31
|
+
*/
|
|
32
|
+
const testIfArraysAreUnique = (first, second) => first
|
|
33
|
+
.filter((objInFirstArray) => !second
|
|
34
|
+
.some((objInSecondArray) => objInFirstArray.value === objInSecondArray.value))
|
|
35
|
+
.length > 0
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* This is the component description.
|
|
39
|
+
*/
|
|
40
|
+
const MultipleCombobox = ({
|
|
41
|
+
id,
|
|
42
|
+
className:userClassName,
|
|
43
|
+
style,
|
|
44
|
+
label,
|
|
45
|
+
name,
|
|
46
|
+
options:items,
|
|
47
|
+
getOptions,
|
|
48
|
+
setValue,
|
|
49
|
+
error,
|
|
50
|
+
description,
|
|
51
|
+
value,
|
|
52
|
+
color,
|
|
53
|
+
isFetching,
|
|
54
|
+
minLength,
|
|
55
|
+
transformSearch,
|
|
56
|
+
// ...otherProps
|
|
57
|
+
}) => {
|
|
58
|
+
const [searchInputValue, setSearchInputValue] = useState('')
|
|
59
|
+
const {
|
|
60
|
+
getSelectedItemProps,
|
|
61
|
+
getDropdownProps,
|
|
62
|
+
addSelectedItem,
|
|
63
|
+
removeSelectedItem,
|
|
64
|
+
setSelectedItems,
|
|
65
|
+
selectedItems,
|
|
66
|
+
} = useMultipleSelection({
|
|
67
|
+
initialSelectedItems:value || [],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @returns {Boolean} - Unique items from the options array so that the combobox
|
|
72
|
+
* shows only the options that are not yet selected.
|
|
73
|
+
*/
|
|
74
|
+
const getFilteredItems = () => items
|
|
75
|
+
.filter((item) => selectedItems
|
|
76
|
+
.findIndex((e) => e.label === item.label) < 0)
|
|
77
|
+
|
|
78
|
+
const {
|
|
79
|
+
isOpen,
|
|
80
|
+
getLabelProps,
|
|
81
|
+
getMenuProps,
|
|
82
|
+
getInputProps,
|
|
83
|
+
getComboboxProps,
|
|
84
|
+
highlightedIndex,
|
|
85
|
+
getItemProps,
|
|
86
|
+
} = useCombobox({
|
|
87
|
+
searchInputValue,
|
|
88
|
+
defaultHighlightedIndex:0, // after selection, highlight the first item.
|
|
89
|
+
selectedItem :null,
|
|
90
|
+
items :getFilteredItems(),
|
|
91
|
+
circularNavigation :true,
|
|
92
|
+
stateReducer :(state, actionAndChanges) => {
|
|
93
|
+
const { changes, type } = actionAndChanges
|
|
94
|
+
switch (type) {
|
|
95
|
+
case useCombobox.stateChangeTypes.InputKeyDownEnter:
|
|
96
|
+
case useCombobox.stateChangeTypes.ItemClick:
|
|
97
|
+
return {
|
|
98
|
+
...changes,
|
|
99
|
+
isOpen:true, // keep the menu open after selection.
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
return changes
|
|
105
|
+
},
|
|
106
|
+
onStateChange:({ inputValue:newSearchInputValue, type, selectedItem }) => {
|
|
107
|
+
switch (type) {
|
|
108
|
+
case useCombobox.stateChangeTypes.InputChange: {
|
|
109
|
+
const transformedInput = transformSearch(newSearchInputValue)
|
|
110
|
+
if (transformedInput.length > minLength) {
|
|
111
|
+
getOptions(transformedInput)
|
|
112
|
+
}
|
|
113
|
+
setSearchInputValue(newSearchInputValue)
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
case useCombobox.stateChangeTypes.InputKeyDownEnter:
|
|
117
|
+
case useCombobox.stateChangeTypes.ItemClick:
|
|
118
|
+
case useCombobox.stateChangeTypes.InputBlur:
|
|
119
|
+
if (selectedItem) {
|
|
120
|
+
setSearchInputValue('')
|
|
121
|
+
addSelectedItem(selectedItem)
|
|
122
|
+
}
|
|
123
|
+
break
|
|
124
|
+
default:
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (selectedItems?.length > 0) {
|
|
132
|
+
setValue(selectedItems)
|
|
133
|
+
}
|
|
134
|
+
}, [selectedItems])
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (value?.length > 0 && (
|
|
138
|
+
testIfArraysAreUnique(value, selectedItems)
|
|
139
|
+
|| testIfArraysAreUnique(selectedItems, value)
|
|
140
|
+
)) {
|
|
141
|
+
setSelectedItems(value)
|
|
142
|
+
}
|
|
143
|
+
}, [value])
|
|
144
|
+
|
|
145
|
+
const parentRef = useRef(null)
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div
|
|
149
|
+
id={id}
|
|
150
|
+
className={[
|
|
151
|
+
|
|
152
|
+
baseClassName,
|
|
153
|
+
|
|
154
|
+
componentClassName,
|
|
155
|
+
userClassName,
|
|
156
|
+
`y-${color}`,
|
|
157
|
+
]
|
|
158
|
+
.filter((e) => e)
|
|
159
|
+
.join(' ')}
|
|
160
|
+
style={style}
|
|
161
|
+
>
|
|
162
|
+
|
|
163
|
+
<FormLabel {...getLabelProps()} name={name}>
|
|
164
|
+
{label}
|
|
165
|
+
</FormLabel>
|
|
166
|
+
|
|
167
|
+
{selectedItems?.length > 0 && (
|
|
168
|
+
<div className="selected-items">
|
|
169
|
+
{selectedItems.map((selectedItem, index) => (
|
|
170
|
+
<div
|
|
171
|
+
key={selectedItem.label}
|
|
172
|
+
{...getSelectedItemProps({ selectedItem, index })}
|
|
173
|
+
className="item"
|
|
174
|
+
>
|
|
175
|
+
<Button
|
|
176
|
+
onClick={(e) => {
|
|
177
|
+
e.stopPropagation()
|
|
178
|
+
removeSelectedItem(selectedItem)
|
|
179
|
+
}}
|
|
180
|
+
isCompact
|
|
181
|
+
isSimple
|
|
182
|
+
color={color}
|
|
183
|
+
>
|
|
184
|
+
<span className="v25 mr-v">{selectedItem.label}</span>
|
|
185
|
+
<span className="f-icons close">Y</span>
|
|
186
|
+
</Button>
|
|
187
|
+
</div>
|
|
188
|
+
))}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
<div {...getComboboxProps()} className="input-wrapper">
|
|
192
|
+
<input
|
|
193
|
+
{...getInputProps(
|
|
194
|
+
getDropdownProps({ preventKeyAction: isOpen }),
|
|
195
|
+
)}
|
|
196
|
+
className="input"
|
|
197
|
+
/>
|
|
198
|
+
{isFetching && (
|
|
199
|
+
<LoadingCircle className="x-main2" />
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<Popover
|
|
204
|
+
isOpen={isOpen}
|
|
205
|
+
parentRef={parentRef}
|
|
206
|
+
>
|
|
207
|
+
<Menu
|
|
208
|
+
isOpen={isOpen}
|
|
209
|
+
getItemProps={getItemProps}
|
|
210
|
+
highlightedIndex={highlightedIndex}
|
|
211
|
+
items={getFilteredItems()}
|
|
212
|
+
{...getMenuProps()}
|
|
213
|
+
/>
|
|
214
|
+
</Popover>
|
|
215
|
+
|
|
216
|
+
{(description || error) && (
|
|
217
|
+
<FormDescription isError={!!error}>
|
|
218
|
+
{ error || description }
|
|
219
|
+
</FormDescription>
|
|
220
|
+
)}
|
|
221
|
+
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
MultipleCombobox.propTypes = {
|
|
227
|
+
/**
|
|
228
|
+
* The HTML id for this element
|
|
229
|
+
*/
|
|
230
|
+
id:PropTypes.string,
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The HTML class names for this element
|
|
234
|
+
*/
|
|
235
|
+
className:PropTypes.string,
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The React-written, css properties for this element.
|
|
239
|
+
*/
|
|
240
|
+
style:PropTypes.objectOf(PropTypes.string),
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The label of the custom select input
|
|
244
|
+
*/
|
|
245
|
+
label:PropTypes.string,
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The custom select input options from the backend
|
|
249
|
+
*/
|
|
250
|
+
options:PropTypes.arrayOf(
|
|
251
|
+
PropTypes.shape({
|
|
252
|
+
value:PropTypes.string,
|
|
253
|
+
label:PropTypes.string,
|
|
254
|
+
}),
|
|
255
|
+
),
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The name of the custom select input
|
|
259
|
+
*/
|
|
260
|
+
name:PropTypes.string,
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The function to fetch the options from the backend
|
|
264
|
+
*/
|
|
265
|
+
getOptions:PropTypes.func,
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The function to set the value of the custom select input
|
|
269
|
+
*/
|
|
270
|
+
setValue:PropTypes.func.isRequired,
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The custom select input description
|
|
274
|
+
*/
|
|
275
|
+
description:PropTypes.string,
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The error object
|
|
279
|
+
*/
|
|
280
|
+
error:PropTypes.objectOf(PropTypes.string),
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* The value of the custom select input
|
|
284
|
+
*/
|
|
285
|
+
value:PropTypes.arrayOf(
|
|
286
|
+
PropTypes.shape({
|
|
287
|
+
value:PropTypes.string,
|
|
288
|
+
label:PropTypes.string,
|
|
289
|
+
}),
|
|
290
|
+
),
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The base color of the custom select input
|
|
294
|
+
*/
|
|
295
|
+
color:PropTypes.string,
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Whether the query getting the combobox options is inFlight
|
|
299
|
+
*/
|
|
300
|
+
isFetching:PropTypes.bool.isRequired,
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The minimum length of the search input to start fetching the options
|
|
304
|
+
*/
|
|
305
|
+
minLength:PropTypes.number,
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The function to transform the search input
|
|
309
|
+
*/
|
|
310
|
+
transformSearch:PropTypes.func,
|
|
311
|
+
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
MultipleCombobox.defaultProps = {
|
|
315
|
+
// someProp: false
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export default MultipleCombobox
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
$default-input-padding: .75em .75em .55em;
|
|
6
6
|
$default-padding: 1em;
|
|
7
7
|
$default-margin: 1em;
|
|
8
|
+
$default-gap: 1em;
|
|
9
|
+
$default-loading-circle-displacement: 1em;
|
|
8
10
|
|
|
9
11
|
.#{bem.$base}.combobox,
|
|
10
12
|
.#{bem.$base}.multiple-combobox {
|
|
11
13
|
position: relative;
|
|
12
14
|
|
|
15
|
+
.#{bem.$base}.label {
|
|
16
|
+
margin-bottom: $default-margin
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
.#{bem.$base}.popover {
|
|
14
20
|
width: 100%;
|
|
15
21
|
|
|
@@ -30,6 +36,14 @@ $default-margin: 1em;
|
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
>.input-wrapper {
|
|
39
|
+
position: relative;
|
|
40
|
+
|
|
41
|
+
>.#{bem.$base}.loading-circle {
|
|
42
|
+
position: absolute;
|
|
43
|
+
top: $default-loading-circle-displacement;
|
|
44
|
+
right: $default-loading-circle-displacement;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
>.input {
|
|
34
48
|
background: var(--light-y);
|
|
35
49
|
border: var(--theme-border-style) var(--dark-y);
|
|
@@ -60,12 +74,17 @@ $default-margin: 1em;
|
|
|
60
74
|
.#{bem.$base}.multiple-combobox {
|
|
61
75
|
>.selected-items {
|
|
62
76
|
display: flex;
|
|
77
|
+
gap: $default-gap / 2;
|
|
78
|
+
flex-wrap: wrap;
|
|
79
|
+
margin-bottom: $default-margin / 2;
|
|
63
80
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
81
|
+
>.item {
|
|
82
|
+
background-color: var(--main2);
|
|
83
|
+
padding: $default-padding / 4;
|
|
68
84
|
|
|
69
|
-
|
|
85
|
+
.close {
|
|
86
|
+
font-size: calc(var(--s-3) * 1em);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
70
89
|
}
|
|
71
90
|
}
|