el.xwx.moe/components/Dropdown.tsx

114 lines
2.9 KiB
TypeScript
Raw Normal View History

2023-03-22 18:11:54 -05:00
import Link from "next/link";
2023-10-28 11:50:11 -05:00
import React, { MouseEventHandler, useEffect, useState } from "react";
2023-03-22 18:11:54 -05:00
import ClickAwayHandler from "./ClickAwayHandler";
2023-05-27 22:21:35 -05:00
type MenuItem =
| {
name: string;
onClick: MouseEventHandler;
href?: string;
}
| {
name: string;
onClick?: MouseEventHandler;
href: string;
}
| undefined;
2023-03-22 18:11:54 -05:00
type Props = {
onClickOutside: Function;
className?: string;
items: MenuItem[];
2023-10-28 11:50:11 -05:00
points: { x: number; y: number };
2023-10-28 06:20:35 -05:00
style?: React.CSSProperties;
2023-10-28 11:50:11 -05:00
width: number; // in rem
2023-03-22 18:11:54 -05:00
};
2023-10-28 06:20:35 -05:00
export default function Dropdown({
2023-10-28 11:50:11 -05:00
points,
2023-10-28 06:20:35 -05:00
onClickOutside,
className,
items,
2023-10-28 11:50:11 -05:00
width,
2023-10-28 06:20:35 -05:00
}: Props) {
2023-10-28 11:50:11 -05:00
const [pos, setPos] = useState<{ x: number; y: number }>();
const [dropdownHeight, setDropdownHeight] = useState<number>();
function convertRemToPixels(rem: number) {
return (
rem * parseFloat(getComputedStyle(document.documentElement).fontSize)
);
}
useEffect(() => {
const dropdownWidth = convertRemToPixels(width);
let finalX = points.x;
let finalY = points.y;
// Check for x-axis overflow (left side)
if (points.x + dropdownWidth > window.innerWidth) {
finalX = points.x - dropdownWidth;
}
// Check for y-axis overflow (bottom side)
if (dropdownHeight && points.y + dropdownHeight > window.innerHeight) {
finalY =
window.innerHeight - (dropdownHeight + (window.innerHeight - points.y));
}
setPos({ x: finalX, y: finalY });
}, [points, width, dropdownHeight]);
useEffect(() => {
const dropdownWidth = convertRemToPixels(width);
if (points.x + dropdownWidth > window.innerWidth) {
setPos({ x: points.x - dropdownWidth, y: points.y });
} else setPos(points);
}, [points, width]);
2023-03-22 18:11:54 -05:00
return (
2023-10-28 11:50:11 -05:00
pos && (
<ClickAwayHandler
onMount={(e) => {
setDropdownHeight(e.height);
}}
style={{
position: "fixed",
top: `${pos?.y}px`,
left: `${pos?.x}px`,
}}
onClickOutside={onClickOutside}
className={`${
className || ""
} py-1 shadow-md border border-sky-100 dark:border-neutral-700 bg-gray-50 dark:bg-neutral-800 rounded-md flex flex-col z-20 w-[${width}rem]`}
>
{items.map((e, i) => {
const inner = e && (
<div className="cursor-pointer rounded-md">
<div className="flex items-center gap-2 py-1 px-2 hover:bg-slate-200 dark:hover:bg-neutral-700 duration-100">
<p className="text-black dark:text-white select-none">
{e.name}
</p>
</div>
2023-03-23 10:25:17 -05:00
</div>
2023-10-28 11:50:11 -05:00
);
return e && e.href ? (
<Link key={i} href={e.href}>
{inner}
2023-10-28 11:50:11 -05:00
</Link>
) : (
e && (
<div key={i} onClick={e.onClick}>
{inner}
</div>
)
);
})}
</ClickAwayHandler>
)
2023-03-22 18:11:54 -05:00
);
}