@pareto-engineering/design-system 2.0.0-alpha.46 → 2.0.0-alpha.47

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.
@@ -36,7 +36,7 @@ var QueryCombobox = _ref => {
36
36
  style,
37
37
  className,
38
38
  query,
39
- // multiple,
39
+ multiple,
40
40
  name,
41
41
  label,
42
42
  color,
@@ -46,7 +46,9 @@ var QueryCombobox = _ref => {
46
46
  graphQlNode,
47
47
  searchVariable,
48
48
  extraVariables,
49
- optionsKeyMap // ...otherProps
49
+ optionsKeyMap,
50
+ minLength,
51
+ transformSearch // ...otherProps
50
52
 
51
53
  } = _ref;
52
54
  (0, React.useLayoutEffect)(() => {
@@ -115,9 +117,11 @@ var QueryCombobox = _ref => {
115
117
  value,
116
118
  color,
117
119
  isFetching,
118
- className
120
+ className,
121
+ minLength,
122
+ transformSearch
119
123
  };
120
- var Input = _common.Combobox;
124
+ var Input = multiple ? _common.MultipleCombobox : _common.Combobox;
121
125
  return /*#__PURE__*/React.createElement(Input, comboboxProps);
122
126
  };
123
127
 
@@ -199,7 +203,17 @@ QueryCombobox.propTypes = {
199
203
  /**
200
204
  * The variable to be used to search the data
201
205
  */
202
- searchVariable: _propTypes.default.string
206
+ searchVariable: _propTypes.default.string,
207
+
208
+ /**
209
+ * The minimum length of the search input to start fetching the options
210
+ */
211
+ minLength: _propTypes.default.number,
212
+
213
+ /**
214
+ * The function to transform the search input
215
+ */
216
+ transformSearch: _propTypes.default.func
203
217
  };
204
218
  QueryCombobox.defaultProps = {
205
219
  optionsKeyMap: {
@@ -208,7 +222,9 @@ QueryCombobox.defaultProps = {
208
222
  },
209
223
  multiple: false,
210
224
  color: 'background2',
211
- searchVariable: 'search'
225
+ searchVariable: 'search',
226
+ transformSearch: search => search,
227
+ minLength: 2
212
228
  };
213
229
  var _default = QueryCombobox;
214
230
  exports.default = _default;
@@ -47,8 +47,9 @@ var Combobox = _ref => {
47
47
  description,
48
48
  value,
49
49
  color,
50
- loadingCircleColor,
51
- isFetching // ...otherProps
50
+ minLength,
51
+ isFetching,
52
+ transformSearch // ...otherProps
52
53
 
53
54
  } = _ref;
54
55
  var {
@@ -69,7 +70,11 @@ var Combobox = _ref => {
69
70
  var {
70
71
  inputValue
71
72
  } = _ref2;
72
- getOptions(inputValue);
73
+ var transformedInput = transformSearch(inputValue);
74
+
75
+ if (transformedInput.length > minLength) {
76
+ getOptions(transformedInput);
77
+ }
73
78
  }
74
79
  }); // If the user has selected an item, we'll set the value of the field
75
80
  // or if the combobox state has a selected item, we'll set the value to the formik state
@@ -88,7 +93,7 @@ var Combobox = _ref => {
88
93
  var parentRef = (0, React.useRef)(null);
89
94
  return /*#__PURE__*/React.createElement("div", {
90
95
  id: id,
91
- className: [baseClassName, componentClassName, userClassName, "y-".concat(color), "x-".concat(loadingCircleColor)].filter(e => e).join(' '),
96
+ className: [baseClassName, componentClassName, userClassName, "y-".concat(color)].filter(e => e).join(' '),
92
97
  style: style,
93
98
  ref: parentRef
94
99
  }, /*#__PURE__*/React.createElement(_.FormLabel, _extends({}, getLabelProps(), {
@@ -97,7 +102,9 @@ var Combobox = _ref => {
97
102
  className: "input-wrapper"
98
103
  }), /*#__PURE__*/React.createElement("input", _extends({}, getInputProps(), {
99
104
  className: "input"
100
- })), isFetching && /*#__PURE__*/React.createElement(_a.LoadingCircle, null)), /*#__PURE__*/React.createElement(_a.Popover, {
105
+ })), isFetching && /*#__PURE__*/React.createElement(_a.LoadingCircle, {
106
+ className: "x-main2"
107
+ })), /*#__PURE__*/React.createElement(_a.Popover, {
101
108
  isOpen: isOpen,
102
109
  parentRef: parentRef
103
110
  }, /*#__PURE__*/React.createElement(_Menu.Menu, _extends({
@@ -183,12 +190,16 @@ Combobox.propTypes = {
183
190
  isFetching: _propTypes.default.bool.isRequired,
184
191
 
185
192
  /**
186
- * The loading circle color
193
+ * The minimum length of the search input to start fetching the options
194
+ */
195
+ minLength: _propTypes.default.number,
196
+
197
+ /**
198
+ * The function to transform the search input
187
199
  */
188
- loadingCircleColor: _propTypes.default.string
200
+ transformSearch: _propTypes.default.func
189
201
  };
190
- Combobox.defaultProps = {
191
- loadingCircleColor: 'main2'
202
+ Combobox.defaultProps = {// someProp: false
192
203
  };
193
204
  var _default = Combobox;
194
205
  exports.default = _default;
@@ -65,7 +65,8 @@ var MultipleCombobox = _ref => {
65
65
  value,
66
66
  color,
67
67
  isFetching,
68
- loadingCircleColor // ...otherProps
68
+ minLength,
69
+ transformSearch // ...otherProps
69
70
 
70
71
  } = _ref;
71
72
  var [searchInputValue, setSearchInputValue] = (0, React.useState)('');
@@ -79,8 +80,12 @@ var MultipleCombobox = _ref => {
79
80
  } = (0, _downshift.useMultipleSelection)({
80
81
  initialSelectedItems: value || []
81
82
  });
83
+ /**
84
+ * @returns {Boolean} - Unique items from the options array so that the combobox
85
+ * shows only the options that are not yet selected.
86
+ */
82
87
 
83
- var getFilteredItems = () => items.filter(item => selectedItems.findIndex(e => e.label === item.label) < 0 && item.label.toLowerCase().startsWith(searchInputValue.toLowerCase()));
88
+ var getFilteredItems = () => items.filter(item => selectedItems.findIndex(e => e.label === item.label) < 0);
84
89
 
85
90
  var {
86
91
  isOpen,
@@ -126,9 +131,16 @@ var MultipleCombobox = _ref => {
126
131
 
127
132
  switch (type) {
128
133
  case _downshift.useCombobox.stateChangeTypes.InputChange:
129
- getOptions(newSearchInputValue);
130
- setSearchInputValue(newSearchInputValue);
131
- break;
134
+ {
135
+ var transformedInput = transformSearch(newSearchInputValue);
136
+
137
+ if (transformedInput.length > minLength) {
138
+ getOptions(transformedInput);
139
+ }
140
+
141
+ setSearchInputValue(newSearchInputValue);
142
+ break;
143
+ }
132
144
 
133
145
  case _downshift.useCombobox.stateChangeTypes.InputKeyDownEnter:
134
146
  case _downshift.useCombobox.stateChangeTypes.ItemClick:
@@ -158,7 +170,7 @@ var MultipleCombobox = _ref => {
158
170
  var parentRef = (0, React.useRef)(null);
159
171
  return /*#__PURE__*/React.createElement("div", {
160
172
  id: id,
161
- className: [baseClassName, componentClassName, userClassName, "y-".concat(color), "x-".concat(loadingCircleColor)].filter(e => e).join(' '),
173
+ className: [baseClassName, componentClassName, userClassName, "y-".concat(color)].filter(e => e).join(' '),
162
174
  style: style
163
175
  }, /*#__PURE__*/React.createElement(_.FormLabel, _extends({}, getLabelProps(), {
164
176
  name: name
@@ -184,7 +196,9 @@ var MultipleCombobox = _ref => {
184
196
  preventKeyAction: isOpen
185
197
  })), {
186
198
  className: "input"
187
- })), isFetching && /*#__PURE__*/React.createElement(_a.LoadingCircle, null)), /*#__PURE__*/React.createElement(_a.Popover, {
199
+ })), isFetching && /*#__PURE__*/React.createElement(_a.LoadingCircle, {
200
+ className: "x-main2"
201
+ })), /*#__PURE__*/React.createElement(_a.Popover, {
188
202
  isOpen: isOpen,
189
203
  parentRef: parentRef
190
204
  }, /*#__PURE__*/React.createElement(_Menu.Menu, _extends({
@@ -270,12 +284,16 @@ MultipleCombobox.propTypes = {
270
284
  isFetching: _propTypes.default.bool.isRequired,
271
285
 
272
286
  /**
273
- * The loading circle color
287
+ * The minimum length of the search input to start fetching the options
288
+ */
289
+ minLength: _propTypes.default.number,
290
+
291
+ /**
292
+ * The function to transform the search input
274
293
  */
275
- loadingCircleColor: _propTypes.default.string
294
+ transformSearch: _propTypes.default.func
276
295
  };
277
- MultipleCombobox.defaultProps = {
278
- loadingCircleColor: 'main2'
296
+ MultipleCombobox.defaultProps = {// someProp: false
279
297
  };
280
298
  var _default = MultipleCombobox;
281
299
  exports.default = _default;
@@ -15,7 +15,15 @@ Object.defineProperty(exports, "Combobox", {
15
15
  return _Combobox.Combobox;
16
16
  }
17
17
  });
18
+ Object.defineProperty(exports, "MultipleCombobox", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _MultipleCombobox.MultipleCombobox;
22
+ }
23
+ });
18
24
 
19
25
  var _Menu = require("./Menu");
20
26
 
21
- var _Combobox = require("./Combobox");
27
+ var _Combobox = require("./Combobox");
28
+
29
+ var _MultipleCombobox = require("./MultipleCombobox");
@@ -73,12 +73,6 @@ $default-loading-circle-displacement: 1em;
73
73
  .#{bem.$base}.multiple-combobox {
74
74
  >.selected-items {
75
75
  display: flex;
76
-
77
- /* stylelint-disable selector-max-universal -- Allow */
78
- >*:not(:first-child) {
79
- margin-left: $default-margin;
80
- }
81
-
82
- /* stylelint-enable selector-max-universal */
76
+ gap: var(--default-gap);
83
77
  }
84
78
  }
@@ -5,7 +5,7 @@ import { useField } from 'formik';
5
5
  import { useRelayEnvironment, fetchQuery } from 'react-relay';
6
6
  import PropTypes from 'prop-types'; // Local Definitions
7
7
 
8
- import { Combobox } from "./common";
8
+ import { Combobox, MultipleCombobox } from "./common";
9
9
  /**
10
10
  * This is the component description.
11
11
  */
@@ -15,7 +15,7 @@ const QueryCombobox = ({
15
15
  style,
16
16
  className,
17
17
  query,
18
- // multiple,
18
+ multiple,
19
19
  name,
20
20
  label,
21
21
  color,
@@ -25,7 +25,9 @@ const QueryCombobox = ({
25
25
  graphQlNode,
26
26
  searchVariable,
27
27
  extraVariables,
28
- optionsKeyMap // ...otherProps
28
+ optionsKeyMap,
29
+ minLength,
30
+ transformSearch // ...otherProps
29
31
 
30
32
  }) => {
31
33
  useLayoutEffect(() => {
@@ -93,9 +95,11 @@ const QueryCombobox = ({
93
95
  value,
94
96
  color,
95
97
  isFetching,
96
- className
98
+ className,
99
+ minLength,
100
+ transformSearch
97
101
  };
98
- const Input = Combobox;
102
+ const Input = multiple ? MultipleCombobox : Combobox;
99
103
  return /*#__PURE__*/React.createElement(Input, comboboxProps);
100
104
  };
101
105
 
@@ -177,7 +181,17 @@ QueryCombobox.propTypes = {
177
181
  /**
178
182
  * The variable to be used to search the data
179
183
  */
180
- searchVariable: PropTypes.string
184
+ searchVariable: PropTypes.string,
185
+
186
+ /**
187
+ * The minimum length of the search input to start fetching the options
188
+ */
189
+ minLength: PropTypes.number,
190
+
191
+ /**
192
+ * The function to transform the search input
193
+ */
194
+ transformSearch: PropTypes.func
181
195
  };
182
196
  QueryCombobox.defaultProps = {
183
197
  optionsKeyMap: {
@@ -186,6 +200,8 @@ QueryCombobox.defaultProps = {
186
200
  },
187
201
  multiple: false,
188
202
  color: 'background2',
189
- searchVariable: 'search'
203
+ searchVariable: 'search',
204
+ transformSearch: search => search,
205
+ minLength: 2
190
206
  };
191
207
  export default QueryCombobox;
@@ -29,8 +29,9 @@ const Combobox = ({
29
29
  description,
30
30
  value,
31
31
  color,
32
- loadingCircleColor,
33
- isFetching // ...otherProps
32
+ minLength,
33
+ isFetching,
34
+ transformSearch // ...otherProps
34
35
 
35
36
  }) => {
36
37
  const {
@@ -50,7 +51,11 @@ const Combobox = ({
50
51
  onInputValueChange: ({
51
52
  inputValue
52
53
  }) => {
53
- getOptions(inputValue);
54
+ const transformedInput = transformSearch(inputValue);
55
+
56
+ if (transformedInput.length > minLength) {
57
+ getOptions(transformedInput);
58
+ }
54
59
  }
55
60
  }); // If the user has selected an item, we'll set the value of the field
56
61
  // or if the combobox state has a selected item, we'll set the value to the formik state
@@ -69,7 +74,7 @@ const Combobox = ({
69
74
  const parentRef = useRef(null);
70
75
  return /*#__PURE__*/React.createElement("div", {
71
76
  id: id,
72
- className: [baseClassName, componentClassName, userClassName, `y-${color}`, `x-${loadingCircleColor}`].filter(e => e).join(' '),
77
+ className: [baseClassName, componentClassName, userClassName, `y-${color}`].filter(e => e).join(' '),
73
78
  style: style,
74
79
  ref: parentRef
75
80
  }, /*#__PURE__*/React.createElement(FormLabel, _extends({}, getLabelProps(), {
@@ -78,7 +83,9 @@ const Combobox = ({
78
83
  className: "input-wrapper"
79
84
  }), /*#__PURE__*/React.createElement("input", _extends({}, getInputProps(), {
80
85
  className: "input"
81
- })), isFetching && /*#__PURE__*/React.createElement(LoadingCircle, null)), /*#__PURE__*/React.createElement(Popover, {
86
+ })), isFetching && /*#__PURE__*/React.createElement(LoadingCircle, {
87
+ className: "x-main2"
88
+ })), /*#__PURE__*/React.createElement(Popover, {
82
89
  isOpen: isOpen,
83
90
  parentRef: parentRef
84
91
  }, /*#__PURE__*/React.createElement(Menu, _extends({
@@ -164,11 +171,15 @@ Combobox.propTypes = {
164
171
  isFetching: PropTypes.bool.isRequired,
165
172
 
166
173
  /**
167
- * The loading circle color
174
+ * The minimum length of the search input to start fetching the options
175
+ */
176
+ minLength: PropTypes.number,
177
+
178
+ /**
179
+ * The function to transform the search input
168
180
  */
169
- loadingCircleColor: PropTypes.string
181
+ transformSearch: PropTypes.func
170
182
  };
171
- Combobox.defaultProps = {
172
- loadingCircleColor: 'main2'
183
+ Combobox.defaultProps = {// someProp: false
173
184
  };
174
185
  export default Combobox;
@@ -40,7 +40,8 @@ const MultipleCombobox = ({
40
40
  value,
41
41
  color,
42
42
  isFetching,
43
- loadingCircleColor // ...otherProps
43
+ minLength,
44
+ transformSearch // ...otherProps
44
45
 
45
46
  }) => {
46
47
  const [searchInputValue, setSearchInputValue] = useState('');
@@ -54,8 +55,12 @@ const MultipleCombobox = ({
54
55
  } = useMultipleSelection({
55
56
  initialSelectedItems: value || []
56
57
  });
58
+ /**
59
+ * @returns {Boolean} - Unique items from the options array so that the combobox
60
+ * shows only the options that are not yet selected.
61
+ */
57
62
 
58
- const getFilteredItems = () => items.filter(item => selectedItems.findIndex(e => e.label === item.label) < 0 && item.label.toLowerCase().startsWith(searchInputValue.toLowerCase()));
63
+ const getFilteredItems = () => items.filter(item => selectedItems.findIndex(e => e.label === item.label) < 0);
59
64
 
60
65
  const {
61
66
  isOpen,
@@ -99,9 +104,16 @@ const MultipleCombobox = ({
99
104
  }) => {
100
105
  switch (type) {
101
106
  case useCombobox.stateChangeTypes.InputChange:
102
- getOptions(newSearchInputValue);
103
- setSearchInputValue(newSearchInputValue);
104
- break;
107
+ {
108
+ const transformedInput = transformSearch(newSearchInputValue);
109
+
110
+ if (transformedInput.length > minLength) {
111
+ getOptions(transformedInput);
112
+ }
113
+
114
+ setSearchInputValue(newSearchInputValue);
115
+ break;
116
+ }
105
117
 
106
118
  case useCombobox.stateChangeTypes.InputKeyDownEnter:
107
119
  case useCombobox.stateChangeTypes.ItemClick:
@@ -131,7 +143,7 @@ const MultipleCombobox = ({
131
143
  const parentRef = useRef(null);
132
144
  return /*#__PURE__*/React.createElement("div", {
133
145
  id: id,
134
- className: [baseClassName, componentClassName, userClassName, `y-${color}`, `x-${loadingCircleColor}`].filter(e => e).join(' '),
146
+ className: [baseClassName, componentClassName, userClassName, `y-${color}`].filter(e => e).join(' '),
135
147
  style: style
136
148
  }, /*#__PURE__*/React.createElement(FormLabel, _extends({}, getLabelProps(), {
137
149
  name: name
@@ -157,7 +169,9 @@ const MultipleCombobox = ({
157
169
  preventKeyAction: isOpen
158
170
  })), {
159
171
  className: "input"
160
- })), isFetching && /*#__PURE__*/React.createElement(LoadingCircle, null)), /*#__PURE__*/React.createElement(Popover, {
172
+ })), isFetching && /*#__PURE__*/React.createElement(LoadingCircle, {
173
+ className: "x-main2"
174
+ })), /*#__PURE__*/React.createElement(Popover, {
161
175
  isOpen: isOpen,
162
176
  parentRef: parentRef
163
177
  }, /*#__PURE__*/React.createElement(Menu, _extends({
@@ -243,11 +257,15 @@ MultipleCombobox.propTypes = {
243
257
  isFetching: PropTypes.bool.isRequired,
244
258
 
245
259
  /**
246
- * The loading circle color
260
+ * The minimum length of the search input to start fetching the options
261
+ */
262
+ minLength: PropTypes.number,
263
+
264
+ /**
265
+ * The function to transform the search input
247
266
  */
248
- loadingCircleColor: PropTypes.string
267
+ transformSearch: PropTypes.func
249
268
  };
250
- MultipleCombobox.defaultProps = {
251
- loadingCircleColor: 'main2'
269
+ MultipleCombobox.defaultProps = {// someProp: false
252
270
  };
253
271
  export default MultipleCombobox;
@@ -1,2 +1,3 @@
1
1
  export { Menu } from "./Menu";
2
- export { Combobox } from "./Combobox";
2
+ export { Combobox } from "./Combobox";
3
+ export { MultipleCombobox } from "./MultipleCombobox";
@@ -73,12 +73,6 @@ $default-loading-circle-displacement: 1em;
73
73
  .#{bem.$base}.multiple-combobox {
74
74
  >.selected-items {
75
75
  display: flex;
76
-
77
- /* stylelint-disable selector-max-universal -- Allow */
78
- >*:not(:first-child) {
79
- margin-left: $default-margin;
80
- }
81
-
82
- /* stylelint-enable selector-max-universal */
76
+ gap: var(--default-gap);
83
77
  }
84
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pareto-engineering/design-system",
3
- "version": "2.0.0-alpha.46",
3
+ "version": "2.0.0-alpha.47",
4
4
  "description": "",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/es/index.js",
@@ -10737,7 +10737,7 @@ exports[`Storyshots f/FormInput With Query Combobox 1`] = `
10737
10737
  </div>
10738
10738
  </div>
10739
10739
  <div
10740
- className="base combobox form-input y-background2 x-main2"
10740
+ className="base combobox form-input y-background2"
10741
10741
  >
10742
10742
  <label
10743
10743
  className="base label x-main2"
@@ -11353,6 +11353,201 @@ exports[`Storyshots f/fields/ChoicesInput Multiple With Grid 1`] = `
11353
11353
  </form>
11354
11354
  `;
11355
11355
 
11356
+ exports[`Storyshots f/fields/QueryCombobox Multiple Select 1`] = `
11357
+ <form
11358
+ action="#"
11359
+ onReset={[Function]}
11360
+ onSubmit={[Function]}
11361
+ >
11362
+ <div
11363
+ className="base multiple-combobox y-background2"
11364
+ >
11365
+ <label
11366
+ className="base label x-main2"
11367
+ htmlFor="teams"
11368
+ id="downshift-3-label"
11369
+ >
11370
+ Search for a team
11371
+ </label>
11372
+ <div
11373
+ className="selected-items"
11374
+ />
11375
+ <div
11376
+ aria-expanded={false}
11377
+ aria-haspopup="listbox"
11378
+ aria-owns="downshift-3-menu"
11379
+ className="input-wrapper"
11380
+ role="combobox"
11381
+ >
11382
+ <input
11383
+ aria-autocomplete="list"
11384
+ aria-controls="downshift-3-menu"
11385
+ aria-labelledby="downshift-3-label"
11386
+ autoComplete="off"
11387
+ className="input"
11388
+ id="downshift-3-input"
11389
+ onBlur={[Function]}
11390
+ onChange={[Function]}
11391
+ onClick={[Function]}
11392
+ onKeyDown={[Function]}
11393
+ value=""
11394
+ />
11395
+ </div>
11396
+ <div
11397
+ className="base popover x-background1 bottom left"
11398
+ >
11399
+ <ul
11400
+ aria-labelledby="downshift-3-label"
11401
+ className="base menu"
11402
+ id="downshift-3-menu"
11403
+ onMouseLeave={[Function]}
11404
+ role="listbox"
11405
+ />
11406
+ </div>
11407
+ </div>
11408
+ <div
11409
+ style={
11410
+ Object {
11411
+ "alignItems": "flex-end",
11412
+ "display": "flex",
11413
+ "flexDirection": "column",
11414
+ }
11415
+ }
11416
+ >
11417
+ <div
11418
+ className="debugger"
11419
+ >
11420
+ <button
11421
+ className="base button x-main2"
11422
+ onClick={[Function]}
11423
+ type="button"
11424
+ >
11425
+ Open FormDebugger
11426
+ </button>
11427
+ </div>
11428
+ <button
11429
+ className="base button x-main1"
11430
+ onClick={[Function]}
11431
+ type="button"
11432
+ >
11433
+ Add formik values
11434
+ </button>
11435
+ </div>
11436
+ </form>
11437
+ `;
11438
+
11439
+ exports[`Storyshots f/fields/QueryCombobox Multiple Select With Default Formik State 1`] = `
11440
+ <form
11441
+ action="#"
11442
+ onReset={[Function]}
11443
+ onSubmit={[Function]}
11444
+ >
11445
+ <div
11446
+ className="base multiple-combobox y-background2"
11447
+ >
11448
+ <label
11449
+ className="base label x-main2"
11450
+ htmlFor="teams"
11451
+ id="downshift-4-label"
11452
+ >
11453
+ Search for a team
11454
+ </label>
11455
+ <div
11456
+ className="selected-items"
11457
+ >
11458
+ <div
11459
+ onClick={[Function]}
11460
+ onKeyDown={[Function]}
11461
+ tabIndex={-1}
11462
+ >
11463
+ Apple
11464
+ <button
11465
+ className="base button f-icons x-main2 modifierCompact modifierSimple"
11466
+ onClick={[Function]}
11467
+ type="button"
11468
+ >
11469
+ X
11470
+ </button>
11471
+ </div>
11472
+ <div
11473
+ onClick={[Function]}
11474
+ onKeyDown={[Function]}
11475
+ tabIndex={-1}
11476
+ >
11477
+ Pear
11478
+ <button
11479
+ className="base button f-icons x-main2 modifierCompact modifierSimple"
11480
+ onClick={[Function]}
11481
+ type="button"
11482
+ >
11483
+ X
11484
+ </button>
11485
+ </div>
11486
+ </div>
11487
+ <div
11488
+ aria-expanded={false}
11489
+ aria-haspopup="listbox"
11490
+ aria-owns="downshift-4-menu"
11491
+ className="input-wrapper"
11492
+ role="combobox"
11493
+ >
11494
+ <input
11495
+ aria-autocomplete="list"
11496
+ aria-controls="downshift-4-menu"
11497
+ aria-labelledby="downshift-4-label"
11498
+ autoComplete="off"
11499
+ className="input"
11500
+ id="downshift-4-input"
11501
+ onBlur={[Function]}
11502
+ onChange={[Function]}
11503
+ onClick={[Function]}
11504
+ onKeyDown={[Function]}
11505
+ value=""
11506
+ />
11507
+ </div>
11508
+ <div
11509
+ className="base popover x-background1 bottom left"
11510
+ >
11511
+ <ul
11512
+ aria-labelledby="downshift-4-label"
11513
+ className="base menu"
11514
+ id="downshift-4-menu"
11515
+ onMouseLeave={[Function]}
11516
+ role="listbox"
11517
+ />
11518
+ </div>
11519
+ </div>
11520
+ <div
11521
+ style={
11522
+ Object {
11523
+ "alignItems": "flex-end",
11524
+ "display": "flex",
11525
+ "flexDirection": "column",
11526
+ }
11527
+ }
11528
+ >
11529
+ <div
11530
+ className="debugger"
11531
+ >
11532
+ <button
11533
+ className="base button x-main2"
11534
+ onClick={[Function]}
11535
+ type="button"
11536
+ >
11537
+ Open FormDebugger
11538
+ </button>
11539
+ </div>
11540
+ <button
11541
+ className="base button x-main1"
11542
+ onClick={[Function]}
11543
+ type="button"
11544
+ >
11545
+ Add formik values
11546
+ </button>
11547
+ </div>
11548
+ </form>
11549
+ `;
11550
+
11356
11551
  exports[`Storyshots f/fields/QueryCombobox Single Select 1`] = `
11357
11552
  <form
11358
11553
  action="#"
@@ -11360,7 +11555,7 @@ exports[`Storyshots f/fields/QueryCombobox Single Select 1`] = `
11360
11555
  onSubmit={[Function]}
11361
11556
  >
11362
11557
  <div
11363
- className="base combobox y-background2 x-main2"
11558
+ className="base combobox y-background2"
11364
11559
  >
11365
11560
  <label
11366
11561
  className="base label x-main2"
@@ -11426,7 +11621,7 @@ exports[`Storyshots f/fields/QueryCombobox Single Select 1`] = `
11426
11621
  onClick={[Function]}
11427
11622
  type="button"
11428
11623
  >
11429
- Replace initial value
11624
+ Replace formik value
11430
11625
  </button>
11431
11626
  </div>
11432
11627
  </form>
@@ -11439,7 +11634,7 @@ exports[`Storyshots f/fields/QueryCombobox Single Select With Default Formik Sta
11439
11634
  onSubmit={[Function]}
11440
11635
  >
11441
11636
  <div
11442
- className="base combobox y-background2 x-main2"
11637
+ className="base combobox y-background2"
11443
11638
  >
11444
11639
  <label
11445
11640
  className="base label x-main2"
@@ -11505,7 +11700,7 @@ exports[`Storyshots f/fields/QueryCombobox Single Select With Default Formik Sta
11505
11700
  onClick={[Function]}
11506
11701
  type="button"
11507
11702
  >
11508
- Replace initial value
11703
+ Replace formik value
11509
11704
  </button>
11510
11705
  </div>
11511
11706
  </form>
package/src/local.scss CHANGED
@@ -5,12 +5,12 @@
5
5
  --theme-border-style: 1px solid;
6
6
  --theme-border-color: var(--paragraph);
7
7
  --theme-border: var(--theme-border-style) var(--theme-border-color);
8
+ --default-gap: 1em;
8
9
  }
9
10
 
10
11
  html {
11
12
  font-family: var(--font-default);
12
13
  font-size: 18px;
13
- font-feature-settings:'liga' on;
14
+ font-feature-settings: 'liga' on;
14
15
  scroll-behavior: smooth;
15
- }
16
-
16
+ }
@@ -80,7 +80,7 @@ const FETCH_TEAMS_QUERY = graphql`
80
80
  `
81
81
 
82
82
  // eslint-disable-next-line react/prop-types
83
- const ResolvedTemplate = ({ multiple, defaultFormikState = {} }) => {
83
+ const ResolvedTemplate = ({ multiple, defaultFormikState }) => {
84
84
  mockRelayOperation(allTeamsMockData)
85
85
  mockRelayOperation(allTeamsMockData)
86
86
  mockRelayOperation(allTeamsMockData)
@@ -88,15 +88,31 @@ const ResolvedTemplate = ({ multiple, defaultFormikState = {} }) => {
88
88
  const Content = () => {
89
89
  const name = multiple ? 'teams' : 'team'
90
90
 
91
- const [, , helpers] = useField(name)
91
+ const [, meta, helpers] = useField(name)
92
+
93
+ const { value } = meta
92
94
 
93
95
  const { setValue } = helpers
94
96
 
95
97
  const updateFormikState = () => {
96
- setValue({
97
- value:'VGVhbU5vZGU6MDAxZTIyOGEtYzA5My00MGI0LWE1MTUtYTNkMTM1NTE1MDNk',
98
- label:'Kafagoho',
99
- })
98
+ if (multiple) {
99
+ setValue([
100
+ ...(value || []),
101
+ {
102
+ value:'VGVhbU5vZGU6MDAxZTIyOGEtYzA5My00MGI0LWE1MTUtYTNkMTM1NTE1MDNk',
103
+ label:'Matomoko',
104
+ },
105
+ {
106
+ value:'VGVhbU5vZGU6MDAxZTIyOGEtYzA5My00MGI0LWE1MTUtYTNkMTM1NTE1MDNp',
107
+ label:'Chungwa',
108
+ },
109
+ ])
110
+ } else {
111
+ setValue({
112
+ value:'VGVhbU5vZGU6MDAxZTIyOGEtYzA5My00MGI0LWE1MTUtYTNkMTM1NTE1MDNk',
113
+ label:'Kafagoho',
114
+ })
115
+ }
100
116
  }
101
117
 
102
118
  return (
@@ -124,16 +140,24 @@ const ResolvedTemplate = ({ multiple, defaultFormikState = {} }) => {
124
140
  <Button
125
141
  onClick={updateFormikState}
126
142
  >
127
- {multiple ? 'Add more initial values' : 'Replace initial value'}
143
+ {multiple ? 'Add formik values' : 'Replace formik value'}
128
144
  </Button>
129
145
  </div>
130
146
  </>
131
147
  )
132
148
  }
133
149
 
150
+ let initialValues = defaultFormikState
151
+
152
+ if (!defaultFormikState && multiple) {
153
+ initialValues = []
154
+ } else if (!defaultFormikState && !multiple) {
155
+ initialValues = {}
156
+ }
157
+
134
158
  return (
135
159
  <Formik
136
- initialValues={defaultFormikState}
160
+ initialValues={initialValues}
137
161
  >
138
162
  <Form>
139
163
  <Content />
@@ -158,6 +182,29 @@ SingleSelectWithDefaultFormikState.args = {
158
182
  },
159
183
  }
160
184
 
185
+ export const MultipleSelect = ResolvedTemplate.bind({})
186
+ MultipleSelect.args = {
187
+ multiple :true,
188
+ defaultFormikState:{ teams: [] },
189
+ }
190
+
191
+ export const MultipleSelectWithDefaultFormikState = ResolvedTemplate.bind({})
192
+ MultipleSelectWithDefaultFormikState.args = {
193
+ multiple :true,
194
+ defaultFormikState:{
195
+ teams:[
196
+ {
197
+ value:'VGVhbU5vZGU6MDAxZTIyOGEtYzA5My00MGI0LWE1MTUtYTNkMTM1NTE1MDNl',
198
+ label:'Apple',
199
+ },
200
+ {
201
+ value:'VGVhbU5vZGU6MDA0N2U4MzktODY0Zi00N2U5LTg3ZjgtZGUwMmM2Yzg1YWJm',
202
+ label:'Pear',
203
+ },
204
+ ],
205
+ },
206
+ }
207
+
161
208
  // eslint-disable-next-line react/prop-types
162
209
  // const RejectedTemplate = ({ multiple }) => {
163
210
  // const Content = () => {
@@ -11,7 +11,7 @@ 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.
@@ -21,7 +21,7 @@ const QueryCombobox = ({
21
21
  style,
22
22
  className,
23
23
  query,
24
- // multiple,
24
+ multiple,
25
25
  name,
26
26
  label,
27
27
  color,
@@ -32,6 +32,8 @@ const QueryCombobox = ({
32
32
  searchVariable,
33
33
  extraVariables,
34
34
  optionsKeyMap,
35
+ minLength,
36
+ transformSearch,
35
37
  // ...otherProps
36
38
  }) => {
37
39
  useLayoutEffect(() => {
@@ -103,9 +105,11 @@ const QueryCombobox = ({
103
105
  color,
104
106
  isFetching,
105
107
  className,
108
+ minLength,
109
+ transformSearch,
106
110
  }
107
111
 
108
- const Input = Combobox
112
+ const Input = multiple ? MultipleCombobox : Combobox
109
113
 
110
114
  return <Input {...comboboxProps} />
111
115
  }
@@ -192,6 +196,16 @@ QueryCombobox.propTypes = {
192
196
  * The variable to be used to search the data
193
197
  */
194
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,
195
209
  }
196
210
 
197
211
  QueryCombobox.defaultProps = {
@@ -199,9 +213,11 @@ QueryCombobox.defaultProps = {
199
213
  value:'id',
200
214
  label:'name',
201
215
  },
202
- multiple :false,
203
- color :'background2',
204
- searchVariable:'search',
216
+ multiple :false,
217
+ color :'background2',
218
+ searchVariable :'search',
219
+ transformSearch:(search) => search,
220
+ minLength :2,
205
221
  }
206
222
 
207
223
  export default QueryCombobox
@@ -37,8 +37,9 @@ const Combobox = ({
37
37
  description,
38
38
  value,
39
39
  color,
40
- loadingCircleColor,
40
+ minLength,
41
41
  isFetching,
42
+ transformSearch,
42
43
  // ...otherProps
43
44
  }) => {
44
45
  const {
@@ -56,7 +57,10 @@ const Combobox = ({
56
57
  initialSelectedItem:value,
57
58
  itemToString :(item) => (item ? item.label : ''),
58
59
  onInputValueChange :({ inputValue }) => {
59
- getOptions(inputValue)
60
+ const transformedInput = transformSearch(inputValue)
61
+ if (transformedInput.length > minLength) {
62
+ getOptions(transformedInput)
63
+ }
60
64
  },
61
65
  })
62
66
 
@@ -87,7 +91,6 @@ const Combobox = ({
87
91
  componentClassName,
88
92
  userClassName,
89
93
  `y-${color}`,
90
- `x-${loadingCircleColor}`,
91
94
  ]
92
95
  .filter((e) => e)
93
96
  .join(' ')}
@@ -101,7 +104,7 @@ const Combobox = ({
101
104
  <div {...getComboboxProps()} className="input-wrapper">
102
105
  <input {...getInputProps()} className="input" />
103
106
  {isFetching && (
104
- <LoadingCircle />
107
+ <LoadingCircle className="x-main2" />
105
108
  )}
106
109
  </div>
107
110
 
@@ -202,13 +205,18 @@ Combobox.propTypes = {
202
205
  isFetching:PropTypes.bool.isRequired,
203
206
 
204
207
  /**
205
- * The loading circle color
208
+ * The minimum length of the search input to start fetching the options
206
209
  */
207
- loadingCircleColor:PropTypes.string,
210
+ minLength:PropTypes.number,
211
+
212
+ /**
213
+ * The function to transform the search input
214
+ */
215
+ transformSearch:PropTypes.func,
208
216
  }
209
217
 
210
218
  Combobox.defaultProps = {
211
- loadingCircleColor:'main2',
219
+ // someProp: false
212
220
  }
213
221
 
214
222
  export default Combobox
@@ -0,0 +1,317 @@
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
+ <div className="selected-items">
168
+ {selectedItems && selectedItems.map((selectedItem, index) => (
169
+ <div
170
+ key={selectedItem.label}
171
+ {...getSelectedItemProps({ selectedItem, index })}
172
+ >
173
+ {selectedItem.label}
174
+ <Button
175
+ className="f-icons"
176
+ onClick={(e) => {
177
+ e.stopPropagation()
178
+ removeSelectedItem(selectedItem)
179
+ }}
180
+ isCompact
181
+ isSimple
182
+ color="main2"
183
+ >
184
+ X
185
+ </Button>
186
+ </div>
187
+ ))}
188
+ </div>
189
+
190
+ <div {...getComboboxProps()} className="input-wrapper">
191
+ <input
192
+ {...getInputProps(
193
+ getDropdownProps({ preventKeyAction: isOpen }),
194
+ )}
195
+ className="input"
196
+ />
197
+ {isFetching && (
198
+ <LoadingCircle className="x-main2" />
199
+ )}
200
+ </div>
201
+
202
+ <Popover
203
+ isOpen={isOpen}
204
+ parentRef={parentRef}
205
+ >
206
+ <Menu
207
+ isOpen={isOpen}
208
+ getItemProps={getItemProps}
209
+ highlightedIndex={highlightedIndex}
210
+ items={getFilteredItems()}
211
+ {...getMenuProps()}
212
+ />
213
+ </Popover>
214
+
215
+ {(description || error) && (
216
+ <FormDescription isError={!!error}>
217
+ { error || description }
218
+ </FormDescription>
219
+ )}
220
+
221
+ </div>
222
+ )
223
+ }
224
+
225
+ MultipleCombobox.propTypes = {
226
+ /**
227
+ * The HTML id for this element
228
+ */
229
+ id:PropTypes.string,
230
+
231
+ /**
232
+ * The HTML class names for this element
233
+ */
234
+ className:PropTypes.string,
235
+
236
+ /**
237
+ * The React-written, css properties for this element.
238
+ */
239
+ style:PropTypes.objectOf(PropTypes.string),
240
+
241
+ /**
242
+ * The label of the custom select input
243
+ */
244
+ label:PropTypes.string,
245
+
246
+ /**
247
+ * The custom select input options from the backend
248
+ */
249
+ options:PropTypes.arrayOf(
250
+ PropTypes.shape({
251
+ value:PropTypes.string,
252
+ label:PropTypes.string,
253
+ }),
254
+ ),
255
+
256
+ /**
257
+ * The name of the custom select input
258
+ */
259
+ name:PropTypes.string,
260
+
261
+ /**
262
+ * The function to fetch the options from the backend
263
+ */
264
+ getOptions:PropTypes.func,
265
+
266
+ /**
267
+ * The function to set the value of the custom select input
268
+ */
269
+ setValue:PropTypes.func.isRequired,
270
+
271
+ /**
272
+ * The custom select input description
273
+ */
274
+ description:PropTypes.string,
275
+
276
+ /**
277
+ * The error object
278
+ */
279
+ error:PropTypes.objectOf(PropTypes.string),
280
+
281
+ /**
282
+ * The value of the custom select input
283
+ */
284
+ value:PropTypes.arrayOf(
285
+ PropTypes.shape({
286
+ value:PropTypes.string,
287
+ label:PropTypes.string,
288
+ }),
289
+ ),
290
+
291
+ /**
292
+ * The base color of the custom select input
293
+ */
294
+ color:PropTypes.string,
295
+
296
+ /**
297
+ * Whether the query getting the combobox options is inFlight
298
+ */
299
+ isFetching:PropTypes.bool.isRequired,
300
+
301
+ /**
302
+ * The minimum length of the search input to start fetching the options
303
+ */
304
+ minLength:PropTypes.number,
305
+
306
+ /**
307
+ * The function to transform the search input
308
+ */
309
+ transformSearch:PropTypes.func,
310
+
311
+ }
312
+
313
+ MultipleCombobox.defaultProps = {
314
+ // someProp: false
315
+ }
316
+
317
+ export default MultipleCombobox
@@ -0,0 +1,2 @@
1
+ /* @pareto-engineering/generator-front 1.0.12 */
2
+ export { default as MultipleCombobox } from './MultipleCombobox'
@@ -1,2 +1,3 @@
1
1
  export { Menu } from './Menu'
2
2
  export { Combobox } from './Combobox'
3
+ export { MultipleCombobox } from './MultipleCombobox'
@@ -73,12 +73,6 @@ $default-loading-circle-displacement: 1em;
73
73
  .#{bem.$base}.multiple-combobox {
74
74
  >.selected-items {
75
75
  display: flex;
76
-
77
- /* stylelint-disable selector-max-universal -- Allow */
78
- >*:not(:first-child) {
79
- margin-left: $default-margin;
80
- }
81
-
82
- /* stylelint-enable selector-max-universal */
76
+ gap: var(--default-gap);
83
77
  }
84
78
  }