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

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.
Files changed (44) hide show
  1. package/dist/cjs/f/FormInput/FormInput.js +7 -0
  2. package/dist/cjs/f/fields/QueryCombobox/QueryCombobox.js +33 -11
  3. package/dist/cjs/f/fields/QueryCombobox/common/Combobox/Combobox.js +20 -9
  4. package/dist/cjs/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.js +40 -17
  5. package/dist/cjs/f/fields/QueryCombobox/common/index.js +9 -1
  6. package/dist/cjs/f/fields/QueryCombobox/styles.scss +11 -5
  7. package/dist/cjs/f/fields/QuerySelect/QuerySelect.js +201 -0
  8. package/dist/cjs/f/fields/QuerySelect/index.js +15 -0
  9. package/dist/cjs/f/fields/QuerySelect/styles.scss +21 -0
  10. package/dist/cjs/f/fields/SelectInput/SelectInput.js +15 -3
  11. package/dist/cjs/f/fields/SelectInput/styles.scss +27 -14
  12. package/dist/cjs/f/fields/index.js +9 -1
  13. package/dist/es/f/FormInput/FormInput.js +8 -1
  14. package/dist/es/f/fields/QueryCombobox/QueryCombobox.js +34 -12
  15. package/dist/es/f/fields/QueryCombobox/common/Combobox/Combobox.js +20 -9
  16. package/dist/es/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.js +40 -17
  17. package/dist/es/f/fields/QueryCombobox/common/index.js +2 -1
  18. package/dist/es/f/fields/QueryCombobox/styles.scss +11 -5
  19. package/dist/es/f/fields/QuerySelect/QuerySelect.js +179 -0
  20. package/dist/es/f/fields/QuerySelect/index.js +2 -0
  21. package/dist/es/f/fields/QuerySelect/styles.scss +21 -0
  22. package/dist/es/f/fields/SelectInput/SelectInput.js +14 -3
  23. package/dist/es/f/fields/SelectInput/styles.scss +27 -14
  24. package/dist/es/f/fields/index.js +2 -1
  25. package/package.json +2 -2
  26. package/src/__snapshots__/Storyshots.test.js.snap +783 -237
  27. package/src/local.scss +3 -3
  28. package/src/stories/f/FormInput.stories.jsx +122 -4
  29. package/src/stories/f/QueryCombobox.stories.jsx +59 -10
  30. package/src/stories/f/QuerySelect.stories.jsx +134 -0
  31. package/src/stories/f/__generated__/FormInputAllTaskStatusesQuery.graphql.js +122 -0
  32. package/src/stories/f/__generated__/QuerySelectAllTaskStatusesQuery.graphql.js +122 -0
  33. package/src/ui/f/FormInput/FormInput.jsx +10 -0
  34. package/src/ui/f/fields/QueryCombobox/QueryCombobox.jsx +34 -14
  35. package/src/ui/f/fields/QueryCombobox/common/Combobox/Combobox.jsx +15 -7
  36. package/src/ui/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.jsx +318 -0
  37. package/src/ui/f/fields/QueryCombobox/common/MultipleCombobox/index.js +2 -0
  38. package/src/ui/f/fields/QueryCombobox/common/index.js +1 -0
  39. package/src/ui/f/fields/QueryCombobox/styles.scss +11 -5
  40. package/src/ui/f/fields/QuerySelect/QuerySelect.jsx +200 -0
  41. package/src/ui/f/fields/QuerySelect/index.js +2 -0
  42. package/src/ui/f/fields/SelectInput/SelectInput.jsx +16 -3
  43. package/src/ui/f/fields/SelectInput/styles.scss +27 -14
  44. package/src/ui/f/fields/index.js +1 -0
@@ -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,17 +21,18 @@ const QueryCombobox = ({
21
21
  style,
22
22
  className,
23
23
  query,
24
- // multiple,
24
+ multiple,
25
25
  name,
26
26
  label,
27
27
  color,
28
28
  description,
29
29
  disabled,
30
30
  debounceMs,
31
- graphQlNode,
32
31
  searchVariable,
33
32
  extraVariables,
34
33
  optionsKeyMap,
34
+ minLength,
35
+ transformSearch,
35
36
  // ...otherProps
36
37
  }) => {
37
38
  useLayoutEffect(() => {
@@ -50,6 +51,8 @@ const QueryCombobox = ({
50
51
 
51
52
  const [options, setOptions] = useState([])
52
53
 
54
+ const { graphql, accessor } = query
55
+
53
56
  const getOptions = (inputValue) => {
54
57
  if (isFetching) return
55
58
 
@@ -64,7 +67,7 @@ const QueryCombobox = ({
64
67
 
65
68
  fetchQuery(
66
69
  environment,
67
- query,
70
+ graphql,
68
71
  variables,
69
72
  )
70
73
  .subscribe({
@@ -79,7 +82,7 @@ const QueryCombobox = ({
79
82
  if (setError)setError(fetchError.message)
80
83
  },
81
84
  next:(data) => {
82
- setOptions(data[graphQlNode].edges.map(({ node }) => ({
85
+ setOptions(data[accessor].edges.map(({ node }) => ({
83
86
  value:node[optionsKeyMap.value],
84
87
  label:node[optionsKeyMap.label],
85
88
  })))
@@ -103,9 +106,11 @@ const QueryCombobox = ({
103
106
  color,
104
107
  isFetching,
105
108
  className,
109
+ minLength,
110
+ transformSearch,
106
111
  }
107
112
 
108
- const Input = Combobox
113
+ const Input = multiple ? MultipleCombobox : Combobox
109
114
 
110
115
  return <Input {...comboboxProps} />
111
116
  }
@@ -157,12 +162,15 @@ QueryCombobox.propTypes = {
157
162
  debounceMs:PropTypes.number,
158
163
 
159
164
  /**
160
- * The query to fetch the options
165
+ * The graphql query to fetch the options and the accessor to destructure the results from
161
166
  */
162
- query:PropTypes.oneOfType([
163
- PropTypes.string,
164
- PropTypes.object,
165
- ]).isRequired,
167
+ query:PropTypes.shape({
168
+ accessor:PropTypes.string,
169
+ graphql :PropTypes.oneOfType([
170
+ PropTypes.string,
171
+ PropTypes.object,
172
+ ]).isRequired,
173
+ }),
166
174
 
167
175
  /**
168
176
  * The extra variables required to be used in the query.
@@ -192,6 +200,16 @@ QueryCombobox.propTypes = {
192
200
  * The variable to be used to search the data
193
201
  */
194
202
  searchVariable:PropTypes.string,
203
+
204
+ /**
205
+ * The minimum length of the search input to start fetching the options
206
+ */
207
+ minLength:PropTypes.number,
208
+
209
+ /**
210
+ * The function to transform the search input
211
+ */
212
+ transformSearch:PropTypes.func,
195
213
  }
196
214
 
197
215
  QueryCombobox.defaultProps = {
@@ -199,9 +217,11 @@ QueryCombobox.defaultProps = {
199
217
  value:'id',
200
218
  label:'name',
201
219
  },
202
- multiple :false,
203
- color :'background2',
204
- searchVariable:'search',
220
+ multiple :false,
221
+ color :'background2',
222
+ searchVariable :'search',
223
+ transformSearch:(search) => search,
224
+ minLength :2,
205
225
  }
206
226
 
207
227
  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,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
@@ -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'
@@ -5,6 +5,7 @@
5
5
  $default-input-padding: .75em .75em .55em;
6
6
  $default-padding: 1em;
7
7
  $default-margin: 1em;
8
+ $default-gap: 1em;
8
9
  $default-loading-circle-displacement: 1em;
9
10
 
10
11
  .#{bem.$base}.combobox,
@@ -73,12 +74,17 @@ $default-loading-circle-displacement: 1em;
73
74
  .#{bem.$base}.multiple-combobox {
74
75
  >.selected-items {
75
76
  display: flex;
77
+ gap: $default-gap / 2;
78
+ flex-wrap: wrap;
79
+ margin-bottom: $default-margin / 2;
76
80
 
77
- /* stylelint-disable selector-max-universal -- Allow */
78
- >*:not(:first-child) {
79
- margin-left: $default-margin;
80
- }
81
+ >.item {
82
+ background-color: var(--main2);
83
+ padding: $default-padding / 4;
81
84
 
82
- /* stylelint-enable selector-max-universal */
85
+ .close {
86
+ font-size: calc(var(--s-3) * 1em);
87
+ }
88
+ }
83
89
  }
84
90
  }