@pareto-engineering/design-system 2.0.0-alpha.44 → 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.
Files changed (35) hide show
  1. package/dist/cjs/f/FormInput/FormInput.js +8 -1
  2. package/dist/cjs/f/fields/QueryCombobox/QueryCombobox.js +49 -41
  3. package/dist/cjs/f/fields/QueryCombobox/common/Combobox/Combobox.js +72 -15
  4. package/dist/cjs/f/fields/QueryCombobox/common/Menu/Menu.js +1 -1
  5. package/dist/cjs/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.js +91 -21
  6. package/dist/cjs/f/fields/QueryCombobox/styles.scss +52 -39
  7. package/dist/cjs/f/fields/index.js +9 -1
  8. package/dist/es/f/FormInput/FormInput.js +9 -2
  9. package/dist/es/f/fields/QueryCombobox/QueryCombobox.js +53 -43
  10. package/dist/es/f/fields/QueryCombobox/common/Combobox/Combobox.js +73 -17
  11. package/dist/es/f/fields/QueryCombobox/common/Menu/Menu.js +1 -1
  12. package/dist/es/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.js +91 -22
  13. package/dist/es/f/fields/QueryCombobox/styles.scss +52 -39
  14. package/dist/es/f/fields/index.js +2 -1
  15. package/package.json +3 -2
  16. package/src/__snapshots__/Storyshots.test.js.snap +508 -0
  17. package/src/local.scss +3 -3
  18. package/src/stories/f/FormInput.stories.jsx +115 -0
  19. package/src/stories/f/QueryCombobox.stories.jsx +267 -0
  20. package/src/stories/f/__generated__/FormInputAllTeamsQuery.graphql.js +139 -0
  21. package/src/stories/f/__generated__/QueryComboboxAllTeamsQuery.graphql.js +139 -0
  22. package/src/stories/utils/generateNodeId.js +12 -0
  23. package/src/stories/utils/testData.js +63 -0
  24. package/src/ui/f/FormInput/FormInput.jsx +11 -0
  25. package/src/ui/f/fields/QueryCombobox/QueryCombobox.jsx +223 -0
  26. package/src/ui/f/fields/QueryCombobox/common/Combobox/Combobox.jsx +222 -0
  27. package/src/ui/f/fields/QueryCombobox/common/Combobox/index.js +2 -0
  28. package/src/ui/f/fields/QueryCombobox/common/Menu/Menu.jsx +103 -0
  29. package/src/ui/f/fields/QueryCombobox/common/Menu/index.js +2 -0
  30. package/src/ui/f/fields/QueryCombobox/common/MultipleCombobox/MultipleCombobox.jsx +317 -0
  31. package/src/ui/f/fields/QueryCombobox/common/MultipleCombobox/index.js +2 -0
  32. package/src/ui/f/fields/QueryCombobox/common/index.js +3 -0
  33. package/src/ui/f/fields/QueryCombobox/index.js +2 -0
  34. package/src/ui/f/fields/QueryCombobox/styles.scss +78 -0
  35. package/src/ui/f/fields/index.js +1 -0
@@ -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'
@@ -0,0 +1,3 @@
1
+ export { Menu } from './Menu'
2
+ export { Combobox } from './Combobox'
3
+ export { MultipleCombobox } from './MultipleCombobox'
@@ -0,0 +1,2 @@
1
+ /* @pareto-engineering/generator-front 1.0.12 */
2
+ export { default as QueryCombobox } from './QueryCombobox'
@@ -0,0 +1,78 @@
1
+ /* @pareto-engineering/generator-front 1.0.12 */
2
+
3
+ @use "@pareto-engineering/bem";
4
+
5
+ $default-input-padding: .75em .75em .55em;
6
+ $default-padding: 1em;
7
+ $default-margin: 1em;
8
+ $default-loading-circle-displacement: 1em;
9
+
10
+ .#{bem.$base}.combobox,
11
+ .#{bem.$base}.multiple-combobox {
12
+ position: relative;
13
+
14
+ .#{bem.$base}.label {
15
+ margin-bottom: $default-margin
16
+ }
17
+
18
+ .#{bem.$base}.popover {
19
+ width: 100%;
20
+
21
+ >.menu {
22
+ list-style: none;
23
+ margin: 0;
24
+ outline: 0;
25
+ padding: 0;
26
+
27
+ >.item {
28
+ padding: $default-padding / 2;
29
+
30
+ &.#{bem.$modifier-active} {
31
+ background-color: var(--background2);
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ >.input-wrapper {
38
+ position: relative;
39
+
40
+ >.#{bem.$base}.loading-circle {
41
+ position: absolute;
42
+ top: $default-loading-circle-displacement;
43
+ right: $default-loading-circle-displacement;
44
+ }
45
+
46
+ >.input {
47
+ background: var(--light-y);
48
+ border: var(--theme-border-style) var(--dark-y);
49
+ color: var(--on-y);
50
+ padding: $default-input-padding;
51
+ width: 100%;
52
+
53
+ &::placeholder {
54
+ color: var(--metadata);
55
+ }
56
+
57
+ &:not(:disabled):hover {
58
+ border: var(--theme-border-style) var(--light-background4);
59
+ }
60
+
61
+ &:disabled {
62
+ background-color: var(--dark-y);
63
+ }
64
+
65
+ &:focus {
66
+ background: var(--light-background4);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+
73
+ .#{bem.$base}.multiple-combobox {
74
+ >.selected-items {
75
+ display: flex;
76
+ gap: var(--default-gap);
77
+ }
78
+ }
@@ -3,3 +3,4 @@ export { SelectInput } from './SelectInput'
3
3
  export { ChoicesInput } from './ChoicesInput'
4
4
  export { TextareaInput } from './TextareaInput'
5
5
  export { RatingsInput } from './RatingsInput'
6
+ export { QueryCombobox } from './QueryCombobox'