Reputation: 1
I'm building a dropdown menu in React, and I'm facing an issue where the menu doesn't stay fixed relative to the button when the user scrolls the page. Instead, the menu scrolls with the page, which is not the desired behavior.
Here is the relevant part of my code:
import { createContext, useContext, useState, useEffect } from "react";
import { createPortal } from "react-dom";
import { HiEllipsisVertical } from "react-icons/hi2";
import styled from "styled-components";
const Menu = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
`;
const StyledToggle = styled.button`
background: none;
border: none;
padding: 0.4rem;
border-radius: var(--border-radius-sm);
transform: translateX(0.8rem);
transition: all 0.2s;
&:hover {
background-color: var(--color-grey-100);
}
& svg {
width: 2.4rem;
height: 2.4rem;
color: var(--color-grey-700);
}
`;
const StyledList = styled.ul`
position: absolute; /* Should be relative to the button */
background-color: var(--color-grey-0);
box-shadow: var(--shadow-md);
border-radius: var(--border-radius-md);
left: ${(props) => props.$position.x}px;
top: ${(props) => props.$position.y}px;
`;
const StyledButton = styled.button`
width: 100%;
text-align: left;
background: none;
border: none;
padding: 1.2rem 2.4rem;
font-size: 1.4rem;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 1.6rem;
&:hover {
background-color: var(--color-grey-50);
}
& svg {
width: 1.6rem;
height: 1.6rem;
color: var(--color-grey-400);
transition: all 0.3s;
}
`;
const MenusContext = createContext();
function Menus({ children }) {
const [openId, setOpenId] = useState("");
const [position, setPosition] = useState(null);
const close = () => setOpenId("");
const open = (id) => setOpenId(id);
return (
<MenusContext.Provider value={{ openId, close, open, position, setPosition }}>
{children}
</MenusContext.Provider>
);
}
function Toggle({ id }) {
const { openId, close, open, setPosition } = useContext(MenusContext);
function handleClick(e) {
const rect = e.target.closest("button").getBoundingClientRect();
setPosition({
x: rect.x, // Position relative to the button's left corner
y: rect.y + rect.height + 8, // Below the button
});
openId === "" || openId !== id ? open(id) : close();
}
return (
<StyledToggle onClick={handleClick}>
<HiEllipsisVertical />
</StyledToggle>
);
}
function List({ id, children }) {
const { openId, position, setPosition } = useContext(MenusContext);
useEffect(() => {
function updatePosition() {
const toggleButton = document.querySelector(`button[data-id="${id}"]`);
if (toggleButton) {
const rect = toggleButton.getBoundingClientRect();
setPosition({
x: rect.x,
y: rect.y + rect.height + 8,
});
}
}
if (openId === id) {
window.addEventListener("scroll", updatePosition);
window.addEventListener("resize", updatePosition);
updatePosition(); // Initial position update
}
return () => {
window.removeEventListener("scroll", updatePosition);
window.removeEventListener("resize", updatePosition);
};
}, [openId, id, setPosition]);
if (openId !== id) return null;
return createPortal(
<StyledList $position={position}>{children}</StyledList>,
document.body
);
}
function Button({ children }) {
return (
<li>
<StyledButton>{children}</StyledButton>
</li>
);
}
Menus.Menu = Menu;
Menus.Toggle = Toggle;
Menus.List = List;
Menus.Button = Button;
export default Menus;
Issue:
When I open the dropdown menu by clicking the toggle button, the menu correctly appears. However, when I scroll the page, the menu scrolls along with the content, rather than staying fixed relative to the button that triggered it.
Expected Behavior:
I want the dropdown menu to stay fixed relative to the button, even when the page is scrolled. It should remain aligned with the button's position and not move with the page content.
What I’ve Tried:
I’ve tried changing position
to absolute
but it still scrolls with the page.
I’ve added event listeners for scroll
and resize
to update the menu’s position dynamically.
Questions:
Additional Context:
The dropdown menu uses position: absolute
to align it with the button.
Event listeners for scroll
and resize
are in place to update the menu’s position, but it still does not work as intended.
Upvotes: 0
Views: 40