@rsuci/shared-form-components 1.0.60 → 1.0.61
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../../src/components/inputs/SearchableSelect.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../../src/components/inputs/SearchableSelect.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,UAAU,qBAAqB;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,MAAM,CAAC;CACtD;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA2O5D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -3,12 +3,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
* Composant de sélection avec recherche intégrée
|
|
4
4
|
* Inspiré du design de production avec champ de filtre
|
|
5
5
|
*/
|
|
6
|
-
import { useState, useRef, useEffect } from 'react';
|
|
6
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
7
|
+
import { createPortal } from 'react-dom';
|
|
7
8
|
export const SearchableSelect = ({ options, value, onChange, placeholder = 'Sélectionner...', searchPlaceholder = 'Rechercher...', disabled = false, required = false, loading = false, error, className = '', noOptionsMessage = 'Aucune option disponible', formatOptionLabel }) => {
|
|
8
9
|
const [isOpen, setIsOpen] = useState(false);
|
|
9
10
|
const [searchTerm, setSearchTerm] = useState('');
|
|
11
|
+
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 });
|
|
10
12
|
const dropdownRef = useRef(null);
|
|
13
|
+
const buttonRef = useRef(null);
|
|
11
14
|
const searchInputRef = useRef(null);
|
|
15
|
+
// Calculer la position du dropdown par rapport au bouton
|
|
16
|
+
const updateDropdownPosition = useCallback(() => {
|
|
17
|
+
if (buttonRef.current) {
|
|
18
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
19
|
+
setDropdownPosition({
|
|
20
|
+
top: rect.bottom + window.scrollY,
|
|
21
|
+
left: rect.left + window.scrollX,
|
|
22
|
+
width: rect.width
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
12
26
|
// Fermer le dropdown quand on clique à l'extérieur
|
|
13
27
|
useEffect(() => {
|
|
14
28
|
const handleClickOutside = (event) => {
|
|
@@ -31,6 +45,18 @@ export const SearchableSelect = ({ options, value, onChange, placeholder = 'Sél
|
|
|
31
45
|
searchInputRef.current.focus();
|
|
32
46
|
}
|
|
33
47
|
}, [isOpen]);
|
|
48
|
+
// Mettre à jour la position du dropdown à l'ouverture et lors du scroll/resize
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (isOpen) {
|
|
51
|
+
updateDropdownPosition();
|
|
52
|
+
window.addEventListener('scroll', updateDropdownPosition, true);
|
|
53
|
+
window.addEventListener('resize', updateDropdownPosition);
|
|
54
|
+
}
|
|
55
|
+
return () => {
|
|
56
|
+
window.removeEventListener('scroll', updateDropdownPosition, true);
|
|
57
|
+
window.removeEventListener('resize', updateDropdownPosition);
|
|
58
|
+
};
|
|
59
|
+
}, [isOpen, updateDropdownPosition]);
|
|
34
60
|
// Filtrer les options selon le terme de recherche
|
|
35
61
|
const filteredOptions = options.filter(option => {
|
|
36
62
|
const label = formatOptionLabel ? formatOptionLabel(option) : option.designation;
|
|
@@ -68,11 +94,15 @@ export const SearchableSelect = ({ options, value, onChange, placeholder = 'Sél
|
|
|
68
94
|
return placeholder;
|
|
69
95
|
return getOptionLabel(value);
|
|
70
96
|
};
|
|
71
|
-
return (_jsxs("div", { className: `relative w-full ${className}`, ref: dropdownRef, children: [_jsx("button", { type: "button", onClick: toggleDropdown, disabled: disabled || loading, className: `w-full px-3 py-2 text-left border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors ${error ? 'border-red-300 bg-red-50' : 'border-gray-300'} ${disabled || loading
|
|
97
|
+
return (_jsxs("div", { className: `relative w-full ${className}`, ref: dropdownRef, children: [_jsx("button", { ref: buttonRef, type: "button", onClick: toggleDropdown, disabled: disabled || loading, className: `w-full px-3 py-2 text-left border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors ${error ? 'border-red-300 bg-red-50' : 'border-gray-300'} ${disabled || loading
|
|
72
98
|
? 'bg-gray-100 cursor-not-allowed text-gray-500'
|
|
73
|
-
: 'bg-white text-gray-900 hover:border-gray-400'} ${isOpen ? 'ring-2 ring-blue-500 border-transparent' : ''}`, children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: value ? 'text-gray-900' : 'text-gray-500', children: loading ? 'Chargement...' : getSelectedLabel() }), _jsx("svg", { className: `w-5 h-5 text-gray-400 transition-transform ${isOpen ? 'transform rotate-180' : ''}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && !disabled && !loading && (_jsxs("div", { className: "
|
|
99
|
+
: 'bg-white text-gray-900 hover:border-gray-400'} ${isOpen ? 'ring-2 ring-blue-500 border-transparent' : ''}`, children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: value ? 'text-gray-900' : 'text-gray-500', children: loading ? 'Chargement...' : getSelectedLabel() }), _jsx("svg", { className: `w-5 h-5 text-gray-400 transition-transform ${isOpen ? 'transform rotate-180' : ''}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && !disabled && !loading && typeof document !== 'undefined' && createPortal(_jsxs("div", { className: "fixed z-[9999] bg-white border border-gray-300 rounded-lg shadow-lg", style: {
|
|
100
|
+
top: dropdownPosition.top,
|
|
101
|
+
left: dropdownPosition.left,
|
|
102
|
+
width: dropdownPosition.width
|
|
103
|
+
}, children: [_jsx("div", { className: "p-2 border-b border-gray-200 bg-gray-50", children: _jsxs("div", { className: "relative", children: [_jsx("svg", { className: "absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }), _jsx("input", { ref: searchInputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: searchPlaceholder, className: "w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" })] }) }), _jsx("div", { className: "overflow-y-auto", style: { maxHeight: '240px' }, children: filteredOptions.length === 0 ? (_jsx("div", { className: "px-4 py-3 text-sm text-gray-500 text-center", children: searchTerm ? `Aucun résultat pour "${searchTerm}"` : noOptionsMessage })) : (_jsx("ul", { className: "py-1", children: filteredOptions.map((option) => {
|
|
74
104
|
const isSelected = value?.id === option.id;
|
|
75
105
|
return (_jsx("li", { children: _jsx("button", { type: "button", onMouseDown: (e) => handleSelect(option, e), className: `w-full px-4 py-2 text-left text-sm hover:bg-blue-50 transition-colors ${isSelected ? 'bg-blue-100 text-blue-900 font-medium' : 'text-gray-900'}`, children: getOptionLabel(option) }) }, option.id));
|
|
76
|
-
}) })) })] })), error && (_jsxs("p", { className: "mt-1 text-sm text-red-600 flex items-center", children: [_jsx("svg", { className: "w-4 h-4 mr-1", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }), error] })), required && !value && (_jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Ce champ est obligatoire" }))] }));
|
|
106
|
+
}) })) })] }), document.body), error && (_jsxs("p", { className: "mt-1 text-sm text-red-600 flex items-center", children: [_jsx("svg", { className: "w-4 h-4 mr-1", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }), error] })), required && !value && (_jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Ce champ est obligatoire" }))] }));
|
|
77
107
|
};
|
|
78
108
|
export default SearchableSelect;
|
package/package.json
CHANGED