Загрузка данных
import { Button } from 'exchange-elements/v2';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { Portal } from '@src/components/Portal';
import styles from './index.module.scss';
import { Coords, DropdownProps } from './types';
export function Dropdown({
children,
selectedValue,
position = 'bottom',
className = '',
buttonClassName = '',
menuClassName = '',
anchorRef,
renderTrigger,
}: DropdownProps) {
const [isOpenMenu, setIsOpenMenu] = useState(false);
const [coords, setCoords] = useState<Coords | null>(null);
const rootRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLDivElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const handleToggle = () => setIsOpenMenu((prev) => !prev);
const handleClose = () => setIsOpenMenu(false);
useLayoutEffect(() => {
if (!isOpenMenu) {
setCoords(null);
return;
}
const calcMenuPosition = () => {
const triggerEl = triggerRef.current;
const menuEl = menuRef.current;
if (!triggerEl || !menuEl) return;
const triggerRect = triggerEl.getBoundingClientRect();
const anchorRect = anchorRef?.current?.getBoundingClientRect() ?? triggerRect;
let topMenu = 0;
let leftMenu = 0;
if (position === 'bottom') {
topMenu = triggerRect.bottom;
leftMenu = triggerRect.left;
}
if (position === 'right') {
topMenu = anchorRect.top;
leftMenu = triggerRect.right;
}
setCoords({ top: topMenu, left: leftMenu });
};
calcMenuPosition();
window.addEventListener('resize', calcMenuPosition);
window.addEventListener('scroll', calcMenuPosition, true);
return () => {
window.removeEventListener('resize', calcMenuPosition);
window.removeEventListener('scroll', calcMenuPosition, true);
};
}, [isOpenMenu, position, anchorRef]);
useEffect(() => {
const onPointerDown = (e: PointerEvent) => {
const t = e.target as Node;
const insideRoot = rootRef.current?.contains(t);
const insideMenu = menuRef.current?.contains(t);
if (!insideRoot && !insideMenu) handleClose();
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') handleClose();
};
document.addEventListener('pointerdown', onPointerDown, true);
document.addEventListener('keydown', onKeyDown);
return () => {
document.removeEventListener('pointerdown', onPointerDown, true);
document.removeEventListener('keydown', onKeyDown);
};
}, []);
return (
<div
ref={rootRef}
className={`${styles.dropdown} ${className}`}
>
<div ref={triggerRef}>
{renderTrigger ? (
renderTrigger({ isOpen: isOpenMenu, onToggle: handleToggle, onClose: handleClose })
) : (
<Button
size="sm"
onClick={handleToggle}
className={`${styles.button} ${isOpenMenu ? styles.pressed : ''} ${buttonClassName}`}
label={selectedValue}
/>
)}
</div>
{isOpenMenu && (
<Portal>
<div
ref={menuRef}
className={`${styles.menu} ${menuClassName}`}
style={{
top: coords?.top,
left: coords?.left,
visibility: coords ? 'visible' : 'hidden',
}}
>
{children}
</div>
</Portal>
)}
</div>
);
}
@use '../../theme/mixins' as m;
$menu-width: 200px;
.dropdown {
position: relative;
.button {
@include m.buttonBase;
}
}
.menu {
font-family: var(--font-family);
width: $menu-width;
position: fixed;
background-color: var(--neutral-2);
border-radius: var(--space-0250);
padding: var(--space-0250) 0;
z-index: 999999; // todo: временное решение
}