@widergy/energy-ui 3.171.3 → 3.172.0
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/CHANGELOG.md +10 -3
- package/dist/components/UTPaper/index.js +3 -1
- package/dist/components/UTSelect/UTSelectFixedBottomOption.stories.js +171 -0
- package/dist/components/UTSelect/versions/V1/README.md +19 -0
- package/dist/components/UTSelect/versions/V1/components/ListboxComponent/index.js +48 -16
- package/dist/components/UTSelect/versions/V1/components/ListboxComponent/styles.module.scss +11 -0
- package/dist/components/UTSelect/versions/V1/index.js +39 -15
- package/dist/components/UTSelect/versions/V1/types.js +6 -0
- package/dist/esm/components/UTPaper/index.js +3 -1
- package/dist/esm/components/UTSelect/UTSelectFixedBottomOption.stories.js +163 -0
- package/dist/esm/components/UTSelect/versions/V1/README.md +19 -0
- package/dist/esm/components/UTSelect/versions/V1/components/ListboxComponent/index.js +49 -17
- package/dist/esm/components/UTSelect/versions/V1/components/ListboxComponent/styles.module.scss +11 -0
- package/dist/esm/components/UTSelect/versions/V1/index.js +39 -15
- package/dist/esm/components/UTSelect/versions/V1/types.js +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
# [3.172.0](https://github.com/widergy/energy-ui/compare/v3.171.3...v3.172.0) (2026-07-02)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* [AXCH-1900] fixed bottom option in utselectv1 ([#830](https://github.com/widergy/energy-ui/issues/830)) ([c331ebf](https://github.com/widergy/energy-ui/commit/c331ebfb0cc2d2f02617943ae8ef36c566579793))
|
|
7
|
+
|
|
8
|
+
## [3.171.3](https://github.com/widergy/energy-ui/compare/v3.171.2...v3.171.3) (2026-06-29)
|
|
9
|
+
|
|
10
|
+
### Correcciones
|
|
5
11
|
|
|
6
|
-
*
|
|
12
|
+
* Al tocar un marcador en el mapa, este ahora queda visible por encima de otros marcadores cercanos o superpuestos. [#829](https://github.com/widergy/Energy-UI/pull/829) [CX-2712](https://widergy.atlassian.net/browse/CX-2712)
|
|
13
|
+
* Mejoras internas de la plataforma. [`caa1f1a`](https://github.com/widergy/Energy-UI/commit/caa1f1a)
|
|
7
14
|
|
|
8
15
|
## [3.171.2](https://github.com/widergy/energy-ui/compare/v3.171.1...v3.171.2) (2026-06-27)
|
|
9
16
|
|
|
@@ -15,15 +15,17 @@ const UTPaper = _ref => {
|
|
|
15
15
|
let {
|
|
16
16
|
children,
|
|
17
17
|
classes,
|
|
18
|
+
className,
|
|
18
19
|
...otherProps
|
|
19
20
|
} = _ref;
|
|
20
21
|
return /*#__PURE__*/_react.default.createElement(_core.Paper, _extends({}, otherProps, {
|
|
21
|
-
className: classes.paper
|
|
22
|
+
className: "".concat(classes.paper).concat(className ? " ".concat(className) : '')
|
|
22
23
|
}), children);
|
|
23
24
|
};
|
|
24
25
|
UTPaper.propTypes = {
|
|
25
26
|
children: _propTypes.object,
|
|
26
27
|
classes: (0, _propTypes.objectOf)(_propTypes.string),
|
|
28
|
+
className: _propTypes.string,
|
|
27
29
|
otherProps: _propTypes.object
|
|
28
30
|
};
|
|
29
31
|
var _default = exports.default = (0, _WithTheme.default)(_theme.retrieveStyle)(UTPaper);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.SeleccionMultiple = exports.ListaLarga = exports.ListaCorta = exports.ConCategorias = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _propTypes = require("prop-types");
|
|
9
|
+
var _2 = _interopRequireDefault(require("."));
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
12
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
13
|
+
const OPTIONS = [{
|
|
14
|
+
name: 'Electricidad',
|
|
15
|
+
value: 'electricity'
|
|
16
|
+
}, {
|
|
17
|
+
name: 'Gas natural',
|
|
18
|
+
value: 'gas'
|
|
19
|
+
}, {
|
|
20
|
+
name: 'Agua potable',
|
|
21
|
+
value: 'water'
|
|
22
|
+
}, {
|
|
23
|
+
name: 'Energía solar',
|
|
24
|
+
value: 'solar'
|
|
25
|
+
}, {
|
|
26
|
+
name: 'Biomasa',
|
|
27
|
+
value: 'biomass'
|
|
28
|
+
}];
|
|
29
|
+
const OPTIONS_WITH_CATEGORIES = [{
|
|
30
|
+
name: 'Residencial bajo',
|
|
31
|
+
value: 'res-low',
|
|
32
|
+
category: 'Residencial'
|
|
33
|
+
}, {
|
|
34
|
+
name: 'Residencial medio',
|
|
35
|
+
value: 'res-mid',
|
|
36
|
+
category: 'Residencial'
|
|
37
|
+
}, {
|
|
38
|
+
name: 'Residencial alto',
|
|
39
|
+
value: 'res-high',
|
|
40
|
+
category: 'Residencial'
|
|
41
|
+
}, {
|
|
42
|
+
name: 'Comercial pequeño',
|
|
43
|
+
value: 'com-sm',
|
|
44
|
+
category: 'Comercial'
|
|
45
|
+
}, {
|
|
46
|
+
name: 'Comercial grande',
|
|
47
|
+
value: 'com-lg',
|
|
48
|
+
category: 'Comercial'
|
|
49
|
+
}, {
|
|
50
|
+
name: 'Industrial liviano',
|
|
51
|
+
value: 'ind-sm',
|
|
52
|
+
category: 'Industrial'
|
|
53
|
+
}, {
|
|
54
|
+
name: 'Industrial pesado',
|
|
55
|
+
value: 'ind-lg',
|
|
56
|
+
category: 'Industrial'
|
|
57
|
+
}];
|
|
58
|
+
const LONG_OPTIONS = Array.from({
|
|
59
|
+
length: 20
|
|
60
|
+
}, (_, i) => ({
|
|
61
|
+
name: "Opci\xF3n ".concat(i + 1),
|
|
62
|
+
value: "opt-".concat(i + 1)
|
|
63
|
+
}));
|
|
64
|
+
const wrapperStyle = {
|
|
65
|
+
maxWidth: '360px',
|
|
66
|
+
padding: '1rem'
|
|
67
|
+
};
|
|
68
|
+
const SelectWrapper = _ref => {
|
|
69
|
+
let {
|
|
70
|
+
multiple,
|
|
71
|
+
...props
|
|
72
|
+
} = _ref;
|
|
73
|
+
const [value, setValue] = (0, _react.useState)(multiple ? [] : null);
|
|
74
|
+
return /*#__PURE__*/_react.default.createElement("div", {
|
|
75
|
+
style: wrapperStyle
|
|
76
|
+
}, /*#__PURE__*/_react.default.createElement(_2.default, _extends({}, props, {
|
|
77
|
+
multiple: multiple,
|
|
78
|
+
onChange: setValue,
|
|
79
|
+
value: value,
|
|
80
|
+
version: "V1"
|
|
81
|
+
})));
|
|
82
|
+
};
|
|
83
|
+
SelectWrapper.propTypes = {
|
|
84
|
+
multiple: _propTypes.bool,
|
|
85
|
+
title: _propTypes.string
|
|
86
|
+
};
|
|
87
|
+
var _default = exports.default = {
|
|
88
|
+
component: _2.default,
|
|
89
|
+
title: 'Energy-UI/UTSelect/Con opción fija'
|
|
90
|
+
};
|
|
91
|
+
const ListaLarga = exports.ListaLarga = {
|
|
92
|
+
name: 'Lista larga',
|
|
93
|
+
args: {
|
|
94
|
+
fixedBottomOption: {
|
|
95
|
+
name: 'Otro motivo',
|
|
96
|
+
value: 'other'
|
|
97
|
+
},
|
|
98
|
+
options: LONG_OPTIONS,
|
|
99
|
+
placeholder: 'Seleccioná una opción',
|
|
100
|
+
title: 'Con opción fija al final (lista larga)'
|
|
101
|
+
},
|
|
102
|
+
parameters: {
|
|
103
|
+
docs: {
|
|
104
|
+
description: {
|
|
105
|
+
story: 'La prop `fixedBottomOption` agrega una opción fija debajo del scroll, siempre visible independientemente del término de búsqueda. Útil para opciones como "Otro motivo".'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
render: args => /*#__PURE__*/_react.default.createElement(SelectWrapper, args)
|
|
110
|
+
};
|
|
111
|
+
const ListaCorta = exports.ListaCorta = {
|
|
112
|
+
name: 'Lista corta',
|
|
113
|
+
args: {
|
|
114
|
+
fixedBottomOption: {
|
|
115
|
+
name: 'Otro motivo',
|
|
116
|
+
value: 'other'
|
|
117
|
+
},
|
|
118
|
+
options: OPTIONS,
|
|
119
|
+
placeholder: 'Seleccioná una opción',
|
|
120
|
+
title: 'Con opción fija al final (lista corta)'
|
|
121
|
+
},
|
|
122
|
+
parameters: {
|
|
123
|
+
docs: {
|
|
124
|
+
description: {
|
|
125
|
+
story: 'La opción fija también se muestra cuando la lista es corta y no hay scroll. Si el usuario filtra y no hay coincidencias, la opción fija permanece visible.'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
render: args => /*#__PURE__*/_react.default.createElement(SelectWrapper, args)
|
|
130
|
+
};
|
|
131
|
+
const ConCategorias = exports.ConCategorias = {
|
|
132
|
+
name: 'Con categorías',
|
|
133
|
+
args: {
|
|
134
|
+
fixedBottomOption: {
|
|
135
|
+
name: 'Otro motivo',
|
|
136
|
+
value: 'other'
|
|
137
|
+
},
|
|
138
|
+
options: OPTIONS_WITH_CATEGORIES,
|
|
139
|
+
placeholder: 'Seleccioná una opción',
|
|
140
|
+
title: 'Con categorías y opción fija'
|
|
141
|
+
},
|
|
142
|
+
parameters: {
|
|
143
|
+
docs: {
|
|
144
|
+
description: {
|
|
145
|
+
story: 'La opción fija convive con opciones agrupadas por categoría. El divider y la opción fija siempre quedan por debajo de todos los grupos.'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
render: args => /*#__PURE__*/_react.default.createElement(SelectWrapper, args)
|
|
150
|
+
};
|
|
151
|
+
const SeleccionMultiple = exports.SeleccionMultiple = {
|
|
152
|
+
name: 'Selección múltiple',
|
|
153
|
+
args: {
|
|
154
|
+
fixedBottomOption: {
|
|
155
|
+
name: 'Otro motivo',
|
|
156
|
+
value: 'other'
|
|
157
|
+
},
|
|
158
|
+
multiple: true,
|
|
159
|
+
options: OPTIONS,
|
|
160
|
+
placeholder: 'Seleccioná una o más opciones',
|
|
161
|
+
title: 'Selección múltiple con opción fija'
|
|
162
|
+
},
|
|
163
|
+
parameters: {
|
|
164
|
+
docs: {
|
|
165
|
+
description: {
|
|
166
|
+
story: 'Con `multiple={true}` la opción fija se comporta como cualquier otra: se puede seleccionar y deseleccionar de forma independiente. El valor es un array que incluye `fixedBottomOption.value` cuando está marcada.'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
render: args => /*#__PURE__*/_react.default.createElement(SelectWrapper, args)
|
|
171
|
+
};
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
| disableFilterOptions | bool | false | Disables option filtering, showing all options even when typing in the search field. |
|
|
21
21
|
| error | string | | Error message to display below the input field. |
|
|
22
22
|
| errorDataTestId | string | | Test ID for the error validation component. |
|
|
23
|
+
| fixedBottomOption | object | | Option rendered below the scrollable list, always visible regardless of the search term. Shape: `{ name: string, value: string \| number }`. |
|
|
23
24
|
| freeWidth | bool | false | Allows the dropdown to have a free width instead of matching the input width. |
|
|
24
25
|
| helpText | string | | Help text displayed below the input field. |
|
|
25
26
|
| icon | string | | Icon name to display in the input field. |
|
|
@@ -67,6 +68,24 @@ The `action` prop accepts an object with the following properties:
|
|
|
67
68
|
|
|
68
69
|
## Features
|
|
69
70
|
|
|
71
|
+
### `fixedBottomOption`
|
|
72
|
+
|
|
73
|
+
`fixedBottomOption` adds a pinned option at the bottom of the dropdown list, rendered outside the scrollable area so it's always visible regardless of scroll position or active search term. Useful for "catch-all" options like "Other reason".
|
|
74
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
<UTSelect
|
|
77
|
+
version="V1"
|
|
78
|
+
options={categorizedOptions}
|
|
79
|
+
fixedBottomOption={{ name: 'Otro motivo', value: 'other' }}
|
|
80
|
+
onChange={handleSelect}
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- The fixed option is excluded from `filteredOptions` — if it's also present in `options`, it's automatically deduplicated by value.
|
|
85
|
+
- When no options match the search term, `noMatchesText` is shown but the fixed option remains visible.
|
|
86
|
+
- The option is visually separated from the list by a divider.
|
|
87
|
+
- When selected, `onChange` receives the option's value (same behavior as any other option).
|
|
88
|
+
|
|
70
89
|
### `withAutoReset`
|
|
71
90
|
|
|
72
91
|
`withAutoReset` is a prop that controls the behavior of the `UTSelect` component when the selected value is no longer available in the current options. This prop ensures that the selected value resets to `null` if the chosen option is no longer valid. When an auto-reset occurs, the options are re-sorted to ensure the list is up-to-date with the current selections.
|
|
@@ -16,11 +16,13 @@ var _stylesModule = _interopRequireDefault(require("./styles.module.scss"));
|
|
|
16
16
|
var _constants2 = require("./constants");
|
|
17
17
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
18
18
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
19
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
19
20
|
const ListboxComponent = _ref => {
|
|
20
21
|
let {
|
|
21
22
|
CustomRow,
|
|
22
23
|
dataTestId,
|
|
23
24
|
filteredOptions,
|
|
25
|
+
fixedBottomOption,
|
|
24
26
|
handleSelectionChange,
|
|
25
27
|
horizontalSpacing = _constants.SPACING.SMALL,
|
|
26
28
|
itemDataTestId,
|
|
@@ -32,28 +34,54 @@ const ListboxComponent = _ref => {
|
|
|
32
34
|
} = _ref;
|
|
33
35
|
const hasNoOptions = (0, _react.useMemo)(() => (0, _isEmpty.default)(filteredOptions), [filteredOptions]);
|
|
34
36
|
const maxHeightValid = (0, _react.useMemo)(() => (0, _isFinite.default)(maxHeight) ? maxHeight : _constants2.DEFAULT_MAX_HEIGHT, [maxHeight]);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
maxHeight: maxHeightValid
|
|
37
|
+
const fixedSectionRef = (0, _react.useRef)(null);
|
|
38
|
+
const [reservedHeight, setReservedHeight] = (0, _react.useState)(0);
|
|
39
|
+
(0, _react.useLayoutEffect)(() => {
|
|
40
|
+
if (fixedSectionRef.current) {
|
|
41
|
+
setReservedHeight(fixedSectionRef.current.offsetHeight);
|
|
41
42
|
}
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
}, [fixedBottomOption]);
|
|
44
|
+
const scrollMaxHeight = maxHeightValid - reservedHeight;
|
|
45
|
+
const handleFixedOptionChange = newValues => {
|
|
46
|
+
if (!multiple && newValues.length === 0) {
|
|
47
|
+
handleSelectionChange([fixedBottomOption.value]);
|
|
48
|
+
} else {
|
|
49
|
+
handleSelectionChange(newValues);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const checkListProps = {
|
|
53
|
+
CustomRow,
|
|
54
|
+
horizontalSpacing,
|
|
46
55
|
isSimple: !multiple,
|
|
47
|
-
itemDataTestId
|
|
56
|
+
itemDataTestId,
|
|
48
57
|
onChange: handleSelectionChange,
|
|
49
|
-
options: filteredOptions,
|
|
50
58
|
reversed: true,
|
|
51
59
|
showSelectAll: false,
|
|
52
60
|
value: multiple ? value : [value],
|
|
53
|
-
variant:
|
|
54
|
-
version:
|
|
55
|
-
verticalSpacing
|
|
56
|
-
}
|
|
61
|
+
variant: 'button',
|
|
62
|
+
version: 'V1',
|
|
63
|
+
verticalSpacing
|
|
64
|
+
};
|
|
65
|
+
return /*#__PURE__*/_react.default.createElement("div", {
|
|
66
|
+
className: _stylesModule.default.listbox
|
|
67
|
+
}, hasNoOptions ? /*#__PURE__*/_react.default.createElement(_UTLabel.default, {
|
|
68
|
+
colorTheme: "gray",
|
|
69
|
+
className: _stylesModule.default.noOptionsLabel
|
|
70
|
+
}, noMatchesText) : /*#__PURE__*/_react.default.createElement(_reactPerfectScrollbar.default, {
|
|
71
|
+
style: {
|
|
72
|
+
maxHeight: scrollMaxHeight
|
|
73
|
+
}
|
|
74
|
+
}, /*#__PURE__*/_react.default.createElement(_UTCheckList.default, _extends({}, checkListProps, {
|
|
75
|
+
dataTestId: dataTestId,
|
|
76
|
+
options: filteredOptions
|
|
77
|
+
}))), fixedBottomOption && /*#__PURE__*/_react.default.createElement("div", {
|
|
78
|
+
ref: fixedSectionRef
|
|
79
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
80
|
+
className: _stylesModule.default.fixedOptionDivider
|
|
81
|
+
}), /*#__PURE__*/_react.default.createElement(_UTCheckList.default, _extends({}, checkListProps, {
|
|
82
|
+
onChange: handleFixedOptionChange,
|
|
83
|
+
options: [fixedBottomOption]
|
|
84
|
+
}))));
|
|
57
85
|
};
|
|
58
86
|
ListboxComponent.propTypes = {
|
|
59
87
|
CustomRow: _propTypes.elementType,
|
|
@@ -62,6 +90,10 @@ ListboxComponent.propTypes = {
|
|
|
62
90
|
name: _propTypes.string,
|
|
63
91
|
value: (0, _propTypes.oneOfType)([_propTypes.string, _propTypes.number])
|
|
64
92
|
})),
|
|
93
|
+
fixedBottomOption: (0, _propTypes.shape)({
|
|
94
|
+
name: _propTypes.string,
|
|
95
|
+
value: (0, _propTypes.oneOfType)([_propTypes.string, _propTypes.number])
|
|
96
|
+
}),
|
|
65
97
|
handleSelectionChange: _propTypes.func,
|
|
66
98
|
horizontalSpacing: _propTypes.string,
|
|
67
99
|
itemDataTestId: _propTypes.string,
|
|
@@ -37,7 +37,10 @@ const UTSelect = _ref => {
|
|
|
37
37
|
disableFilterOptions = false,
|
|
38
38
|
error,
|
|
39
39
|
errorDataTestId,
|
|
40
|
+
fixedBottomOption,
|
|
40
41
|
freeWidth = false,
|
|
42
|
+
hideSingleCategoryTitle = false,
|
|
43
|
+
paperClassName,
|
|
41
44
|
helpText,
|
|
42
45
|
icon,
|
|
43
46
|
inputSize,
|
|
@@ -72,26 +75,38 @@ const UTSelect = _ref => {
|
|
|
72
75
|
clonedOptions.forEach(option => {
|
|
73
76
|
if (!categoryOrder.has(option.category)) categoryOrder.set(option.category, categoryOrder.size);
|
|
74
77
|
});
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
const sorted = clonedOptions.sort((a, b) => categoryOrder.get(a.category) - categoryOrder.get(b.category));
|
|
79
|
+
if (hideSingleCategoryTitle && categoryOrder.size <= 1) {
|
|
80
|
+
return sorted.map(_ref2 => {
|
|
81
|
+
let {
|
|
82
|
+
category: _category,
|
|
83
|
+
...rest
|
|
84
|
+
} = _ref2;
|
|
85
|
+
return rest;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return sorted;
|
|
89
|
+
}, [options, hideSingleCategoryTitle]);
|
|
90
|
+
const displayOptions = (0, _react.useMemo)(() => fixedBottomOption ? [...optionsSortedByCategory, fixedBottomOption] : optionsSortedByCategory, [optionsSortedByCategory, fixedBottomOption]);
|
|
77
91
|
const [isPopperOpen, setIsPopperOpen] = (0, _react.useState)(false);
|
|
78
|
-
const [searchTerm, setSearchTerm] = (0, _react.useState)((0, _utils2.getDisplayValue)(value,
|
|
92
|
+
const [searchTerm, setSearchTerm] = (0, _react.useState)((0, _utils2.getDisplayValue)(value, displayOptions, multiple));
|
|
79
93
|
const [showClearButton, setShowClearButton] = (0, _react.useState)(!!searchTerm);
|
|
80
94
|
const [sortedOptions, setSortedOptions] = (0, _react.useState)(optionsSortedByCategory);
|
|
81
95
|
const inputRef = (0, _react.useRef)(null);
|
|
82
96
|
(0, _react.useEffect)(() => {
|
|
83
|
-
|
|
97
|
+
const fixedIsSelected = fixedBottomOption && (multiple ? Array.isArray(value) && value.includes(fixedBottomOption.value) : value === (fixedBottomOption === null || fixedBottomOption === void 0 ? void 0 : fixedBottomOption.value));
|
|
98
|
+
if (withAutoReset && !fixedIsSelected && (0, _utils2.shouldReset)(value, optionsSortedByCategory, multiple)) {
|
|
84
99
|
onChange(null);
|
|
85
100
|
setSortedOptions((0, _utils2.sortOptions)(optionsSortedByCategory, null, multiple));
|
|
86
101
|
}
|
|
87
|
-
}, [multiple, onChange, optionsSortedByCategory, value, withAutoReset]);
|
|
102
|
+
}, [multiple, onChange, optionsSortedByCategory, value, withAutoReset, fixedBottomOption]);
|
|
88
103
|
(0, _react.useEffect)(() => {
|
|
89
104
|
if (!isPopperOpen) {
|
|
90
|
-
const displayValue = (0, _utils2.getDisplayValue)(value,
|
|
105
|
+
const displayValue = (0, _utils2.getDisplayValue)(value, displayOptions, multiple);
|
|
91
106
|
setSearchTerm(displayValue);
|
|
92
107
|
setShowClearButton(displayValue);
|
|
93
108
|
}
|
|
94
|
-
}, [value,
|
|
109
|
+
}, [value, displayOptions, multiple, isPopperOpen]);
|
|
95
110
|
(0, _react.useEffect)(() => {
|
|
96
111
|
if (isPopperOpen) {
|
|
97
112
|
setSortedOptions((0, _utils2.sortOptions)(optionsSortedByCategory, value, multiple));
|
|
@@ -105,17 +120,21 @@ const UTSelect = _ref => {
|
|
|
105
120
|
}, [isPopperOpen]);
|
|
106
121
|
const validationData = (0, _react.useMemo)(() => error && (0, _utils.formatErrorToValidation)(error), [error]);
|
|
107
122
|
const isPicker = variant === _constants.VARIANTS.picker;
|
|
108
|
-
const filteredOptions = (0, _react.useMemo)(() =>
|
|
123
|
+
const filteredOptions = (0, _react.useMemo)(() => {
|
|
124
|
+
const base = disableFilterOptions || isPicker ? sortedOptions : sortedOptions.filter(option => "".concat(option.name).toLowerCase().includes(searchTerm.toLowerCase()));
|
|
125
|
+
if (!fixedBottomOption) return base;
|
|
126
|
+
return base.filter(opt => opt.value !== fixedBottomOption.value);
|
|
127
|
+
}, [sortedOptions, searchTerm, disableFilterOptions, isPicker, fixedBottomOption]);
|
|
109
128
|
const handleSearchChange = (0, _react.useCallback)(searchValue => {
|
|
110
129
|
onChangeSearchTerm === null || onChangeSearchTerm === void 0 || onChangeSearchTerm(searchValue);
|
|
111
130
|
setSearchTerm(searchValue);
|
|
112
131
|
}, [onChangeSearchTerm]);
|
|
113
132
|
const clearSearchInputValue = (0, _react.useCallback)(inputValue => {
|
|
114
133
|
setIsPopperOpen(false);
|
|
115
|
-
const displayValue = (0, _utils2.getDisplayValue)(inputValue,
|
|
134
|
+
const displayValue = (0, _utils2.getDisplayValue)(inputValue, displayOptions, multiple);
|
|
116
135
|
handleSearchChange(displayValue);
|
|
117
136
|
setShowClearButton(displayValue !== '');
|
|
118
|
-
}, [_utils2.getDisplayValue,
|
|
137
|
+
}, [_utils2.getDisplayValue, displayOptions, multiple, handleSearchChange]);
|
|
119
138
|
const handleSelectionChange = newValues => {
|
|
120
139
|
const selectedValue = multiple ? newValues : newValues.length > 0 ? newValues[0] : '';
|
|
121
140
|
onChange(selectedValue);
|
|
@@ -161,13 +180,14 @@ const UTSelect = _ref => {
|
|
|
161
180
|
CustomRow,
|
|
162
181
|
dataTestId: listDataTestId,
|
|
163
182
|
filteredOptions,
|
|
183
|
+
fixedBottomOption,
|
|
164
184
|
handleSelectionChange,
|
|
165
185
|
itemDataTestId,
|
|
166
186
|
multiple,
|
|
167
187
|
noMatchesText,
|
|
168
188
|
value,
|
|
169
189
|
...listProps
|
|
170
|
-
}), [CustomRow, filteredOptions, handleSelectionChange, multiple, noMatchesText, value, listProps]);
|
|
190
|
+
}), [CustomRow, filteredOptions, fixedBottomOption, handleSelectionChange, multiple, noMatchesText, value, listProps]);
|
|
171
191
|
const menuWidthRef = (0, _react.useRef)(menuWidth);
|
|
172
192
|
menuWidthRef.current = menuWidth;
|
|
173
193
|
const CustomPopper = (0, _react.useCallback)(props => {
|
|
@@ -191,11 +211,15 @@ const UTSelect = _ref => {
|
|
|
191
211
|
actions: titleActions,
|
|
192
212
|
required: required,
|
|
193
213
|
size: titleVariant
|
|
194
|
-
}, title), /*#__PURE__*/_react.default.createElement(_lab.Autocomplete, _extends({
|
|
214
|
+
}, title), /*#__PURE__*/_react.default.createElement(_lab.Autocomplete, _extends({
|
|
195
215
|
classes: {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
216
|
+
...(freeWidth && !menuWidth ? {
|
|
217
|
+
popper: _stylesModule.default.popper
|
|
218
|
+
} : {}),
|
|
219
|
+
...(paperClassName ? {
|
|
220
|
+
paper: paperClassName
|
|
221
|
+
} : {})
|
|
222
|
+
},
|
|
199
223
|
PopperComponent: CustomPopper,
|
|
200
224
|
disabled: disabled || readOnly,
|
|
201
225
|
getOptionLabel: option => "".concat(option.name),
|
|
@@ -24,7 +24,13 @@ const utselectTypes = exports.utselectTypes = {
|
|
|
24
24
|
disableFilterOptions: _propTypes.bool,
|
|
25
25
|
error: _propTypes.string,
|
|
26
26
|
errorDataTestId: _propTypes.string,
|
|
27
|
+
fixedBottomOption: (0, _propTypes.shape)({
|
|
28
|
+
name: _propTypes.string,
|
|
29
|
+
value: (0, _propTypes.oneOfType)([_propTypes.string, _propTypes.number])
|
|
30
|
+
}),
|
|
27
31
|
freeWidth: _propTypes.bool,
|
|
32
|
+
hideSingleCategoryTitle: _propTypes.bool,
|
|
33
|
+
paperClassName: _propTypes.string,
|
|
28
34
|
helpText: _propTypes.string,
|
|
29
35
|
icon: _propTypes.string,
|
|
30
36
|
inputSize: _propTypes.string,
|
|
@@ -8,15 +8,17 @@ const UTPaper = _ref => {
|
|
|
8
8
|
let {
|
|
9
9
|
children,
|
|
10
10
|
classes,
|
|
11
|
+
className,
|
|
11
12
|
...otherProps
|
|
12
13
|
} = _ref;
|
|
13
14
|
return /*#__PURE__*/React.createElement(Paper, _extends({}, otherProps, {
|
|
14
|
-
className: classes.paper
|
|
15
|
+
className: "".concat(classes.paper).concat(className ? " ".concat(className) : '')
|
|
15
16
|
}), children);
|
|
16
17
|
};
|
|
17
18
|
UTPaper.propTypes = {
|
|
18
19
|
children: object,
|
|
19
20
|
classes: objectOf(string),
|
|
21
|
+
className: string,
|
|
20
22
|
otherProps: object
|
|
21
23
|
};
|
|
22
24
|
export default WithTheme(retrieveStyle)(UTPaper);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { bool, string } from 'prop-types';
|
|
4
|
+
import UTSelect from '.';
|
|
5
|
+
const OPTIONS = [{
|
|
6
|
+
name: 'Electricidad',
|
|
7
|
+
value: 'electricity'
|
|
8
|
+
}, {
|
|
9
|
+
name: 'Gas natural',
|
|
10
|
+
value: 'gas'
|
|
11
|
+
}, {
|
|
12
|
+
name: 'Agua potable',
|
|
13
|
+
value: 'water'
|
|
14
|
+
}, {
|
|
15
|
+
name: 'Energía solar',
|
|
16
|
+
value: 'solar'
|
|
17
|
+
}, {
|
|
18
|
+
name: 'Biomasa',
|
|
19
|
+
value: 'biomass'
|
|
20
|
+
}];
|
|
21
|
+
const OPTIONS_WITH_CATEGORIES = [{
|
|
22
|
+
name: 'Residencial bajo',
|
|
23
|
+
value: 'res-low',
|
|
24
|
+
category: 'Residencial'
|
|
25
|
+
}, {
|
|
26
|
+
name: 'Residencial medio',
|
|
27
|
+
value: 'res-mid',
|
|
28
|
+
category: 'Residencial'
|
|
29
|
+
}, {
|
|
30
|
+
name: 'Residencial alto',
|
|
31
|
+
value: 'res-high',
|
|
32
|
+
category: 'Residencial'
|
|
33
|
+
}, {
|
|
34
|
+
name: 'Comercial pequeño',
|
|
35
|
+
value: 'com-sm',
|
|
36
|
+
category: 'Comercial'
|
|
37
|
+
}, {
|
|
38
|
+
name: 'Comercial grande',
|
|
39
|
+
value: 'com-lg',
|
|
40
|
+
category: 'Comercial'
|
|
41
|
+
}, {
|
|
42
|
+
name: 'Industrial liviano',
|
|
43
|
+
value: 'ind-sm',
|
|
44
|
+
category: 'Industrial'
|
|
45
|
+
}, {
|
|
46
|
+
name: 'Industrial pesado',
|
|
47
|
+
value: 'ind-lg',
|
|
48
|
+
category: 'Industrial'
|
|
49
|
+
}];
|
|
50
|
+
const LONG_OPTIONS = Array.from({
|
|
51
|
+
length: 20
|
|
52
|
+
}, (_, i) => ({
|
|
53
|
+
name: "Opci\xF3n ".concat(i + 1),
|
|
54
|
+
value: "opt-".concat(i + 1)
|
|
55
|
+
}));
|
|
56
|
+
const wrapperStyle = {
|
|
57
|
+
maxWidth: '360px',
|
|
58
|
+
padding: '1rem'
|
|
59
|
+
};
|
|
60
|
+
const SelectWrapper = _ref => {
|
|
61
|
+
let {
|
|
62
|
+
multiple,
|
|
63
|
+
...props
|
|
64
|
+
} = _ref;
|
|
65
|
+
const [value, setValue] = useState(multiple ? [] : null);
|
|
66
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
67
|
+
style: wrapperStyle
|
|
68
|
+
}, /*#__PURE__*/React.createElement(UTSelect, _extends({}, props, {
|
|
69
|
+
multiple: multiple,
|
|
70
|
+
onChange: setValue,
|
|
71
|
+
value: value,
|
|
72
|
+
version: "V1"
|
|
73
|
+
})));
|
|
74
|
+
};
|
|
75
|
+
SelectWrapper.propTypes = {
|
|
76
|
+
multiple: bool,
|
|
77
|
+
title: string
|
|
78
|
+
};
|
|
79
|
+
export default {
|
|
80
|
+
component: UTSelect,
|
|
81
|
+
title: 'Energy-UI/UTSelect/Con opción fija'
|
|
82
|
+
};
|
|
83
|
+
export const ListaLarga = {
|
|
84
|
+
name: 'Lista larga',
|
|
85
|
+
args: {
|
|
86
|
+
fixedBottomOption: {
|
|
87
|
+
name: 'Otro motivo',
|
|
88
|
+
value: 'other'
|
|
89
|
+
},
|
|
90
|
+
options: LONG_OPTIONS,
|
|
91
|
+
placeholder: 'Seleccioná una opción',
|
|
92
|
+
title: 'Con opción fija al final (lista larga)'
|
|
93
|
+
},
|
|
94
|
+
parameters: {
|
|
95
|
+
docs: {
|
|
96
|
+
description: {
|
|
97
|
+
story: 'La prop `fixedBottomOption` agrega una opción fija debajo del scroll, siempre visible independientemente del término de búsqueda. Útil para opciones como "Otro motivo".'
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
render: args => /*#__PURE__*/React.createElement(SelectWrapper, args)
|
|
102
|
+
};
|
|
103
|
+
export const ListaCorta = {
|
|
104
|
+
name: 'Lista corta',
|
|
105
|
+
args: {
|
|
106
|
+
fixedBottomOption: {
|
|
107
|
+
name: 'Otro motivo',
|
|
108
|
+
value: 'other'
|
|
109
|
+
},
|
|
110
|
+
options: OPTIONS,
|
|
111
|
+
placeholder: 'Seleccioná una opción',
|
|
112
|
+
title: 'Con opción fija al final (lista corta)'
|
|
113
|
+
},
|
|
114
|
+
parameters: {
|
|
115
|
+
docs: {
|
|
116
|
+
description: {
|
|
117
|
+
story: 'La opción fija también se muestra cuando la lista es corta y no hay scroll. Si el usuario filtra y no hay coincidencias, la opción fija permanece visible.'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
render: args => /*#__PURE__*/React.createElement(SelectWrapper, args)
|
|
122
|
+
};
|
|
123
|
+
export const ConCategorias = {
|
|
124
|
+
name: 'Con categorías',
|
|
125
|
+
args: {
|
|
126
|
+
fixedBottomOption: {
|
|
127
|
+
name: 'Otro motivo',
|
|
128
|
+
value: 'other'
|
|
129
|
+
},
|
|
130
|
+
options: OPTIONS_WITH_CATEGORIES,
|
|
131
|
+
placeholder: 'Seleccioná una opción',
|
|
132
|
+
title: 'Con categorías y opción fija'
|
|
133
|
+
},
|
|
134
|
+
parameters: {
|
|
135
|
+
docs: {
|
|
136
|
+
description: {
|
|
137
|
+
story: 'La opción fija convive con opciones agrupadas por categoría. El divider y la opción fija siempre quedan por debajo de todos los grupos.'
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
render: args => /*#__PURE__*/React.createElement(SelectWrapper, args)
|
|
142
|
+
};
|
|
143
|
+
export const SeleccionMultiple = {
|
|
144
|
+
name: 'Selección múltiple',
|
|
145
|
+
args: {
|
|
146
|
+
fixedBottomOption: {
|
|
147
|
+
name: 'Otro motivo',
|
|
148
|
+
value: 'other'
|
|
149
|
+
},
|
|
150
|
+
multiple: true,
|
|
151
|
+
options: OPTIONS,
|
|
152
|
+
placeholder: 'Seleccioná una o más opciones',
|
|
153
|
+
title: 'Selección múltiple con opción fija'
|
|
154
|
+
},
|
|
155
|
+
parameters: {
|
|
156
|
+
docs: {
|
|
157
|
+
description: {
|
|
158
|
+
story: 'Con `multiple={true}` la opción fija se comporta como cualquier otra: se puede seleccionar y deseleccionar de forma independiente. El valor es un array que incluye `fixedBottomOption.value` cuando está marcada.'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
render: args => /*#__PURE__*/React.createElement(SelectWrapper, args)
|
|
163
|
+
};
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
| disableFilterOptions | bool | false | Disables option filtering, showing all options even when typing in the search field. |
|
|
21
21
|
| error | string | | Error message to display below the input field. |
|
|
22
22
|
| errorDataTestId | string | | Test ID for the error validation component. |
|
|
23
|
+
| fixedBottomOption | object | | Option rendered below the scrollable list, always visible regardless of the search term. Shape: `{ name: string, value: string \| number }`. |
|
|
23
24
|
| freeWidth | bool | false | Allows the dropdown to have a free width instead of matching the input width. |
|
|
24
25
|
| helpText | string | | Help text displayed below the input field. |
|
|
25
26
|
| icon | string | | Icon name to display in the input field. |
|
|
@@ -67,6 +68,24 @@ The `action` prop accepts an object with the following properties:
|
|
|
67
68
|
|
|
68
69
|
## Features
|
|
69
70
|
|
|
71
|
+
### `fixedBottomOption`
|
|
72
|
+
|
|
73
|
+
`fixedBottomOption` adds a pinned option at the bottom of the dropdown list, rendered outside the scrollable area so it's always visible regardless of scroll position or active search term. Useful for "catch-all" options like "Other reason".
|
|
74
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
<UTSelect
|
|
77
|
+
version="V1"
|
|
78
|
+
options={categorizedOptions}
|
|
79
|
+
fixedBottomOption={{ name: 'Otro motivo', value: 'other' }}
|
|
80
|
+
onChange={handleSelect}
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- The fixed option is excluded from `filteredOptions` — if it's also present in `options`, it's automatically deduplicated by value.
|
|
85
|
+
- When no options match the search term, `noMatchesText` is shown but the fixed option remains visible.
|
|
86
|
+
- The option is visually separated from the list by a divider.
|
|
87
|
+
- When selected, `onChange` receives the option's value (same behavior as any other option).
|
|
88
|
+
|
|
70
89
|
### `withAutoReset`
|
|
71
90
|
|
|
72
91
|
`withAutoReset` is a prop that controls the behavior of the `UTSelect` component when the selected value is no longer available in the current options. This prop ensures that the selected value resets to `null` if the chosen option is no longer valid. When an auto-reset occurs, the options are re-sorted to ensure the list is up-to-date with the current selections.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
1
2
|
import isEmpty from 'lodash/isEmpty';
|
|
2
|
-
import React, { useMemo } from 'react';
|
|
3
|
+
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
4
|
import ScrollBar from 'react-perfect-scrollbar';
|
|
4
5
|
import { arrayOf, bool, elementType, func, oneOfType, shape, string, number } from 'prop-types';
|
|
5
6
|
import isFinite from 'lodash/isFinite';
|
|
@@ -13,6 +14,7 @@ const ListboxComponent = _ref => {
|
|
|
13
14
|
CustomRow,
|
|
14
15
|
dataTestId,
|
|
15
16
|
filteredOptions,
|
|
17
|
+
fixedBottomOption,
|
|
16
18
|
handleSelectionChange,
|
|
17
19
|
horizontalSpacing = SPACING.SMALL,
|
|
18
20
|
itemDataTestId,
|
|
@@ -24,28 +26,54 @@ const ListboxComponent = _ref => {
|
|
|
24
26
|
} = _ref;
|
|
25
27
|
const hasNoOptions = useMemo(() => isEmpty(filteredOptions), [filteredOptions]);
|
|
26
28
|
const maxHeightValid = useMemo(() => isFinite(maxHeight) ? maxHeight : DEFAULT_MAX_HEIGHT, [maxHeight]);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
maxHeight: maxHeightValid
|
|
29
|
+
const fixedSectionRef = useRef(null);
|
|
30
|
+
const [reservedHeight, setReservedHeight] = useState(0);
|
|
31
|
+
useLayoutEffect(() => {
|
|
32
|
+
if (fixedSectionRef.current) {
|
|
33
|
+
setReservedHeight(fixedSectionRef.current.offsetHeight);
|
|
33
34
|
}
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
}, [fixedBottomOption]);
|
|
36
|
+
const scrollMaxHeight = maxHeightValid - reservedHeight;
|
|
37
|
+
const handleFixedOptionChange = newValues => {
|
|
38
|
+
if (!multiple && newValues.length === 0) {
|
|
39
|
+
handleSelectionChange([fixedBottomOption.value]);
|
|
40
|
+
} else {
|
|
41
|
+
handleSelectionChange(newValues);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const checkListProps = {
|
|
45
|
+
CustomRow,
|
|
46
|
+
horizontalSpacing,
|
|
38
47
|
isSimple: !multiple,
|
|
39
|
-
itemDataTestId
|
|
48
|
+
itemDataTestId,
|
|
40
49
|
onChange: handleSelectionChange,
|
|
41
|
-
options: filteredOptions,
|
|
42
50
|
reversed: true,
|
|
43
51
|
showSelectAll: false,
|
|
44
52
|
value: multiple ? value : [value],
|
|
45
|
-
variant:
|
|
46
|
-
version:
|
|
47
|
-
verticalSpacing
|
|
48
|
-
}
|
|
53
|
+
variant: 'button',
|
|
54
|
+
version: 'V1',
|
|
55
|
+
verticalSpacing
|
|
56
|
+
};
|
|
57
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
58
|
+
className: styles.listbox
|
|
59
|
+
}, hasNoOptions ? /*#__PURE__*/React.createElement(UTLabel, {
|
|
60
|
+
colorTheme: "gray",
|
|
61
|
+
className: styles.noOptionsLabel
|
|
62
|
+
}, noMatchesText) : /*#__PURE__*/React.createElement(ScrollBar, {
|
|
63
|
+
style: {
|
|
64
|
+
maxHeight: scrollMaxHeight
|
|
65
|
+
}
|
|
66
|
+
}, /*#__PURE__*/React.createElement(UTCheckList, _extends({}, checkListProps, {
|
|
67
|
+
dataTestId: dataTestId,
|
|
68
|
+
options: filteredOptions
|
|
69
|
+
}))), fixedBottomOption && /*#__PURE__*/React.createElement("div", {
|
|
70
|
+
ref: fixedSectionRef
|
|
71
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
72
|
+
className: styles.fixedOptionDivider
|
|
73
|
+
}), /*#__PURE__*/React.createElement(UTCheckList, _extends({}, checkListProps, {
|
|
74
|
+
onChange: handleFixedOptionChange,
|
|
75
|
+
options: [fixedBottomOption]
|
|
76
|
+
}))));
|
|
49
77
|
};
|
|
50
78
|
ListboxComponent.propTypes = {
|
|
51
79
|
CustomRow: elementType,
|
|
@@ -54,6 +82,10 @@ ListboxComponent.propTypes = {
|
|
|
54
82
|
name: string,
|
|
55
83
|
value: oneOfType([string, number])
|
|
56
84
|
})),
|
|
85
|
+
fixedBottomOption: shape({
|
|
86
|
+
name: string,
|
|
87
|
+
value: oneOfType([string, number])
|
|
88
|
+
}),
|
|
57
89
|
handleSelectionChange: func,
|
|
58
90
|
horizontalSpacing: string,
|
|
59
91
|
itemDataTestId: string,
|
|
@@ -29,7 +29,10 @@ const UTSelect = _ref => {
|
|
|
29
29
|
disableFilterOptions = false,
|
|
30
30
|
error,
|
|
31
31
|
errorDataTestId,
|
|
32
|
+
fixedBottomOption,
|
|
32
33
|
freeWidth = false,
|
|
34
|
+
hideSingleCategoryTitle = false,
|
|
35
|
+
paperClassName,
|
|
33
36
|
helpText,
|
|
34
37
|
icon,
|
|
35
38
|
inputSize,
|
|
@@ -64,26 +67,38 @@ const UTSelect = _ref => {
|
|
|
64
67
|
clonedOptions.forEach(option => {
|
|
65
68
|
if (!categoryOrder.has(option.category)) categoryOrder.set(option.category, categoryOrder.size);
|
|
66
69
|
});
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
const sorted = clonedOptions.sort((a, b) => categoryOrder.get(a.category) - categoryOrder.get(b.category));
|
|
71
|
+
if (hideSingleCategoryTitle && categoryOrder.size <= 1) {
|
|
72
|
+
return sorted.map(_ref2 => {
|
|
73
|
+
let {
|
|
74
|
+
category: _category,
|
|
75
|
+
...rest
|
|
76
|
+
} = _ref2;
|
|
77
|
+
return rest;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return sorted;
|
|
81
|
+
}, [options, hideSingleCategoryTitle]);
|
|
82
|
+
const displayOptions = useMemo(() => fixedBottomOption ? [...optionsSortedByCategory, fixedBottomOption] : optionsSortedByCategory, [optionsSortedByCategory, fixedBottomOption]);
|
|
69
83
|
const [isPopperOpen, setIsPopperOpen] = useState(false);
|
|
70
|
-
const [searchTerm, setSearchTerm] = useState(getDisplayValue(value,
|
|
84
|
+
const [searchTerm, setSearchTerm] = useState(getDisplayValue(value, displayOptions, multiple));
|
|
71
85
|
const [showClearButton, setShowClearButton] = useState(!!searchTerm);
|
|
72
86
|
const [sortedOptions, setSortedOptions] = useState(optionsSortedByCategory);
|
|
73
87
|
const inputRef = useRef(null);
|
|
74
88
|
useEffect(() => {
|
|
75
|
-
|
|
89
|
+
const fixedIsSelected = fixedBottomOption && (multiple ? Array.isArray(value) && value.includes(fixedBottomOption.value) : value === (fixedBottomOption === null || fixedBottomOption === void 0 ? void 0 : fixedBottomOption.value));
|
|
90
|
+
if (withAutoReset && !fixedIsSelected && shouldReset(value, optionsSortedByCategory, multiple)) {
|
|
76
91
|
onChange(null);
|
|
77
92
|
setSortedOptions(sortOptions(optionsSortedByCategory, null, multiple));
|
|
78
93
|
}
|
|
79
|
-
}, [multiple, onChange, optionsSortedByCategory, value, withAutoReset]);
|
|
94
|
+
}, [multiple, onChange, optionsSortedByCategory, value, withAutoReset, fixedBottomOption]);
|
|
80
95
|
useEffect(() => {
|
|
81
96
|
if (!isPopperOpen) {
|
|
82
|
-
const displayValue = getDisplayValue(value,
|
|
97
|
+
const displayValue = getDisplayValue(value, displayOptions, multiple);
|
|
83
98
|
setSearchTerm(displayValue);
|
|
84
99
|
setShowClearButton(displayValue);
|
|
85
100
|
}
|
|
86
|
-
}, [value,
|
|
101
|
+
}, [value, displayOptions, multiple, isPopperOpen]);
|
|
87
102
|
useEffect(() => {
|
|
88
103
|
if (isPopperOpen) {
|
|
89
104
|
setSortedOptions(sortOptions(optionsSortedByCategory, value, multiple));
|
|
@@ -97,17 +112,21 @@ const UTSelect = _ref => {
|
|
|
97
112
|
}, [isPopperOpen]);
|
|
98
113
|
const validationData = useMemo(() => error && formatErrorToValidation(error), [error]);
|
|
99
114
|
const isPicker = variant === VARIANTS.picker;
|
|
100
|
-
const filteredOptions = useMemo(() =>
|
|
115
|
+
const filteredOptions = useMemo(() => {
|
|
116
|
+
const base = disableFilterOptions || isPicker ? sortedOptions : sortedOptions.filter(option => "".concat(option.name).toLowerCase().includes(searchTerm.toLowerCase()));
|
|
117
|
+
if (!fixedBottomOption) return base;
|
|
118
|
+
return base.filter(opt => opt.value !== fixedBottomOption.value);
|
|
119
|
+
}, [sortedOptions, searchTerm, disableFilterOptions, isPicker, fixedBottomOption]);
|
|
101
120
|
const handleSearchChange = useCallback(searchValue => {
|
|
102
121
|
onChangeSearchTerm === null || onChangeSearchTerm === void 0 || onChangeSearchTerm(searchValue);
|
|
103
122
|
setSearchTerm(searchValue);
|
|
104
123
|
}, [onChangeSearchTerm]);
|
|
105
124
|
const clearSearchInputValue = useCallback(inputValue => {
|
|
106
125
|
setIsPopperOpen(false);
|
|
107
|
-
const displayValue = getDisplayValue(inputValue,
|
|
126
|
+
const displayValue = getDisplayValue(inputValue, displayOptions, multiple);
|
|
108
127
|
handleSearchChange(displayValue);
|
|
109
128
|
setShowClearButton(displayValue !== '');
|
|
110
|
-
}, [getDisplayValue,
|
|
129
|
+
}, [getDisplayValue, displayOptions, multiple, handleSearchChange]);
|
|
111
130
|
const handleSelectionChange = newValues => {
|
|
112
131
|
const selectedValue = multiple ? newValues : newValues.length > 0 ? newValues[0] : '';
|
|
113
132
|
onChange(selectedValue);
|
|
@@ -153,13 +172,14 @@ const UTSelect = _ref => {
|
|
|
153
172
|
CustomRow,
|
|
154
173
|
dataTestId: listDataTestId,
|
|
155
174
|
filteredOptions,
|
|
175
|
+
fixedBottomOption,
|
|
156
176
|
handleSelectionChange,
|
|
157
177
|
itemDataTestId,
|
|
158
178
|
multiple,
|
|
159
179
|
noMatchesText,
|
|
160
180
|
value,
|
|
161
181
|
...listProps
|
|
162
|
-
}), [CustomRow, filteredOptions, handleSelectionChange, multiple, noMatchesText, value, listProps]);
|
|
182
|
+
}), [CustomRow, filteredOptions, fixedBottomOption, handleSelectionChange, multiple, noMatchesText, value, listProps]);
|
|
163
183
|
const menuWidthRef = useRef(menuWidth);
|
|
164
184
|
menuWidthRef.current = menuWidth;
|
|
165
185
|
const CustomPopper = useCallback(props => {
|
|
@@ -183,11 +203,15 @@ const UTSelect = _ref => {
|
|
|
183
203
|
actions: titleActions,
|
|
184
204
|
required: required,
|
|
185
205
|
size: titleVariant
|
|
186
|
-
}, title), /*#__PURE__*/React.createElement(Autocomplete, _extends({
|
|
206
|
+
}, title), /*#__PURE__*/React.createElement(Autocomplete, _extends({
|
|
187
207
|
classes: {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
208
|
+
...(freeWidth && !menuWidth ? {
|
|
209
|
+
popper: styles.popper
|
|
210
|
+
} : {}),
|
|
211
|
+
...(paperClassName ? {
|
|
212
|
+
paper: paperClassName
|
|
213
|
+
} : {})
|
|
214
|
+
},
|
|
191
215
|
PopperComponent: CustomPopper,
|
|
192
216
|
disabled: disabled || readOnly,
|
|
193
217
|
getOptionLabel: option => "".concat(option.name),
|
|
@@ -18,7 +18,13 @@ export const utselectTypes = {
|
|
|
18
18
|
disableFilterOptions: bool,
|
|
19
19
|
error: string,
|
|
20
20
|
errorDataTestId: string,
|
|
21
|
+
fixedBottomOption: shape({
|
|
22
|
+
name: string,
|
|
23
|
+
value: oneOfType([string, number])
|
|
24
|
+
}),
|
|
21
25
|
freeWidth: bool,
|
|
26
|
+
hideSingleCategoryTitle: bool,
|
|
27
|
+
paperClassName: string,
|
|
22
28
|
helpText: string,
|
|
23
29
|
icon: string,
|
|
24
30
|
inputSize: string,
|