@pagamio/frontend-commons-lib 0.8.324 → 0.8.325
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.
|
@@ -3,6 +3,12 @@ import { ChevronDownIcon, Cross2Icon } from '@radix-ui/react-icons';
|
|
|
3
3
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { cn } from '../../helpers';
|
|
5
5
|
import Button from './Button';
|
|
6
|
+
const DROPDOWN_MARGIN = 4;
|
|
7
|
+
const VIEWPORT_PADDING = 8;
|
|
8
|
+
// Target height when there's plenty of room. The actual height is capped to
|
|
9
|
+
// the available space, so in tight viewports the dropdown shrinks rather
|
|
10
|
+
// than overshooting.
|
|
11
|
+
const PREFERRED_DROPDOWN_HEIGHT = 224;
|
|
6
12
|
const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, onChange, placeholder, className, field, tagPosition = 'inside' }, ref) => {
|
|
7
13
|
const [selectedValues, setSelectedValues] = useState(defaultValue);
|
|
8
14
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -15,15 +21,29 @@ const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, o
|
|
|
15
21
|
setSelectedValues(value);
|
|
16
22
|
}
|
|
17
23
|
}, [value]);
|
|
18
|
-
// Measure trigger position
|
|
24
|
+
// Measure trigger position and decide whether to render the dropdown
|
|
25
|
+
// below or above the trigger based on available viewport space. Cap the
|
|
26
|
+
// height to whatever room is left so the list never extends past the
|
|
27
|
+
// visible area — that's what produced the "I can't see more shifts" bug
|
|
28
|
+
// when the trigger sat near the bottom of a drawer.
|
|
19
29
|
const updateDropdownPosition = useCallback(() => {
|
|
20
30
|
if (!triggerRef.current)
|
|
21
31
|
return;
|
|
22
32
|
const rect = triggerRef.current.getBoundingClientRect();
|
|
33
|
+
const viewportH = window.innerHeight;
|
|
34
|
+
const spaceBelow = Math.max(0, viewportH - rect.bottom - DROPDOWN_MARGIN - VIEWPORT_PADDING);
|
|
35
|
+
const spaceAbove = Math.max(0, rect.top - DROPDOWN_MARGIN - VIEWPORT_PADDING);
|
|
36
|
+
const placeBelow = spaceBelow >= PREFERRED_DROPDOWN_HEIGHT || spaceBelow >= spaceAbove;
|
|
37
|
+
const available = placeBelow ? spaceBelow : spaceAbove;
|
|
38
|
+
// Never exceed the actual room — overshooting causes the list to
|
|
39
|
+
// disappear under the trigger or off the bottom of the viewport.
|
|
40
|
+
const maxHeight = Math.min(PREFERRED_DROPDOWN_HEIGHT, available);
|
|
23
41
|
setDropdownPos({
|
|
24
|
-
top: rect.bottom +
|
|
25
|
-
left: rect.left
|
|
42
|
+
top: placeBelow ? rect.bottom + DROPDOWN_MARGIN : rect.top - DROPDOWN_MARGIN,
|
|
43
|
+
left: rect.left,
|
|
26
44
|
width: rect.width,
|
|
45
|
+
maxHeight,
|
|
46
|
+
placement: placeBelow ? 'bottom' : 'top',
|
|
27
47
|
});
|
|
28
48
|
}, []);
|
|
29
49
|
// Reposition on scroll / resize while open
|
|
@@ -90,10 +110,12 @@ const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, o
|
|
|
90
110
|
? displayPlaceholder
|
|
91
111
|
: `${selectedOptions.length} selected` })) }), _jsx(ChevronDownIcon, { className: cn('ml-2 flex-shrink-0 text-muted-foreground transition-transform duration-150', isOpen && 'rotate-180') })] }), tagPosition === 'bottom' && selectedOptions.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1 mt-2", children: tags })), isOpen && dropdownPos && (_jsx("div", { ref: ref, id: "ms-dropdown-portal", style: {
|
|
92
112
|
position: 'fixed',
|
|
93
|
-
top: dropdownPos.top,
|
|
113
|
+
top: dropdownPos.placement === 'bottom' ? dropdownPos.top : undefined,
|
|
114
|
+
bottom: dropdownPos.placement === 'top' ? window.innerHeight - dropdownPos.top : undefined,
|
|
94
115
|
left: dropdownPos.left,
|
|
95
116
|
width: dropdownPos.width,
|
|
96
|
-
|
|
117
|
+
maxHeight: dropdownPos.maxHeight,
|
|
118
|
+
}, className: cn('z-[9999] flex flex-col', 'bg-popover border border-border rounded-md shadow-lg', 'animate-in fade-in-0 zoom-in-95 duration-100'), children: _jsx("ul", { className: "overflow-y-auto p-1.5 space-y-0.5", style: { maxHeight: dropdownPos.maxHeight }, children: options.length > 0 ? (options.map((option) => {
|
|
97
119
|
const checked = internalValues.includes(option.value);
|
|
98
120
|
const id = `ms-opt-${field?.name ?? 'ms'}-${option.value}`;
|
|
99
121
|
return (_jsx("li", { children: _jsxs("label", { htmlFor: id, className: cn('flex items-center gap-2.5 px-2 py-2 rounded-md cursor-pointer text-sm', 'hover:bg-accent hover:text-accent-foreground transition-colors', checked && 'bg-primary/5'), children: [_jsx("input", { id: id, type: "checkbox", checked: checked, onChange: () => handleToggle(option.value), className: cn('w-4 h-4 rounded border border-input bg-background flex-shrink-0', 'accent-[hsl(var(--primary))] cursor-pointer', 'focus:ring-2 focus:ring-ring focus:ring-offset-0') }), _jsx("span", { className: "flex-1 font-medium text-foreground", children: option.label })] }) }, option.value));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagamio/frontend-commons-lib",
|
|
3
3
|
"description": "Pagamio library for Frontend reusable components like the form engine and table container",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.325",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|