fix(ui): render TagInput dropdown via portal to avoid overflow clipping
- import createPortal from react-dom - add dropdownStyle state to track fixed position coordinates - calculate and update dropdown position on open, scroll, and resize - render dropdown and empty-state divs into document.body using createPortal
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
const hexToRgb = (hex) => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
@@ -53,6 +54,7 @@ const TagInput = ({
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [dropdownStyle, setDropdownStyle] = useState({});
|
||||
const containerRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
@@ -72,6 +74,23 @@ const TagInput = ({
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || !containerRef.current) return;
|
||||
const updatePosition = () => {
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (rect) {
|
||||
setDropdownStyle({ top: rect.bottom + 6, left: rect.left, width: rect.width });
|
||||
}
|
||||
};
|
||||
updatePosition();
|
||||
window.addEventListener('scroll', updatePosition, true);
|
||||
window.addEventListener('resize', updatePosition);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', updatePosition, true);
|
||||
window.removeEventListener('resize', updatePosition);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const selectOption = (option) => {
|
||||
onChange([...value, option.value]);
|
||||
setInputValue('');
|
||||
@@ -124,8 +143,8 @@ const TagInput = ({
|
||||
className="flex-1 min-w-[80px] bg-transparent outline-none text-neutral-900 dark:text-white placeholder-neutral-400 dark:placeholder-neutral-500 text-sm"
|
||||
/>
|
||||
|
||||
{isOpen && filtered.length > 0 && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1.5 z-50 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-lg overflow-hidden max-h-56 overflow-y-auto">
|
||||
{isOpen && filtered.length > 0 && createPortal(
|
||||
<div className="z-[9999] bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-lg overflow-hidden max-h-56 overflow-y-auto" style={{ position: 'fixed', ...dropdownStyle }}>
|
||||
{filtered.map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
@@ -145,13 +164,15 @@ const TagInput = ({
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{isOpen && filtered.length === 0 && inputValue && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1.5 z-50 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-lg overflow-hidden">
|
||||
{isOpen && filtered.length === 0 && inputValue && createPortal(
|
||||
<div className="z-[9999] bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-lg overflow-hidden" style={{ position: 'fixed', ...dropdownStyle }}>
|
||||
<p className="px-3 py-2.5 text-sm text-neutral-400 dark:text-neutral-500">Aucun résultat</p>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user