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';
|
'use client';
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
const hexToRgb = (hex) => {
|
const hexToRgb = (hex) => {
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(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 [inputValue, setInputValue] = useState('');
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [dropdownStyle, setDropdownStyle] = useState({});
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
@@ -72,6 +74,23 @@ const TagInput = ({
|
|||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
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) => {
|
const selectOption = (option) => {
|
||||||
onChange([...value, option.value]);
|
onChange([...value, option.value]);
|
||||||
setInputValue('');
|
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"
|
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 && (
|
{isOpen && filtered.length > 0 && createPortal(
|
||||||
<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">
|
<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 => (
|
{filtered.map(option => (
|
||||||
<button
|
<button
|
||||||
key={option.value}
|
key={option.value}
|
||||||
@@ -145,13 +164,15 @@ const TagInput = ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>,
|
||||||
|
document.body
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isOpen && filtered.length === 0 && inputValue && (
|
{isOpen && filtered.length === 0 && inputValue && createPortal(
|
||||||
<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">
|
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user