el.xwx.moe/components/Dropdown.tsx

111 lines
2.8 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 23:57:24 -05:00
points?: { x: number; y: number };
2023-10-28 06:20:35 -05:00
style?: React.CSSProperties;
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,
}: Props) {
2023-10-28 11:50:11 -05:00
const [pos, setPos] = useState<{ x: number; y: number }>();
const [dropdownHeight, setDropdownHeight] = useState<number>();
2023-10-28 23:57:24 -05:00
const [dropdownWidth, setDropdownWidth] = useState<number>();
2023-10-28 11:50:11 -05:00
function convertRemToPixels(rem: number) {
return (
rem * parseFloat(getComputedStyle(document.documentElement).fontSize)
);
}
useEffect(() => {
2023-10-28 23:57:24 -05:00
if (points) {
let finalX = points.x;
let finalY = points.y;
2023-10-28 11:50:11 -05:00
2023-10-28 23:57:24 -05:00
// Check for x-axis overflow (left side)
if (dropdownWidth && points.x + dropdownWidth > window.innerWidth) {
finalX = points.x - dropdownWidth;
}
2023-10-28 11:50:11 -05:00
2023-10-28 23:57:24 -05:00
// Check for y-axis overflow (bottom side)
if (dropdownHeight && points.y + dropdownHeight > window.innerHeight) {
finalY =
window.innerHeight -
(dropdownHeight + (window.innerHeight - points.y));
}
2023-10-28 11:50:11 -05:00
2023-10-28 23:57:24 -05:00
setPos({ x: finalX, y: finalY });
2023-10-28 11:50:11 -05:00
}
2023-10-31 14:44:58 -05:00
}, [points, dropdownHeight]);
2023-10-28 11:50:11 -05:00
2023-03-22 18:11:54 -05:00
return (
2023-10-28 23:57:24 -05:00
(!points || pos) && (
2023-10-28 11:50:11 -05:00
<ClickAwayHandler
onMount={(e) => {
setDropdownHeight(e.height);
2023-10-28 23:57:24 -05:00
setDropdownWidth(e.width);
2023-10-28 11:50:11 -05:00
}}
2023-10-28 23:57:24 -05:00
style={
points
? {
position: "fixed",
top: `${pos?.y}px`,
left: `${pos?.x}px`,
}
: undefined
}
2023-10-28 11:50:11 -05:00
onClickOutside={onClickOutside}
className={`${
className || ""
2023-10-28 23:57:24 -05:00
} 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`}
2023-10-28 11:50:11 -05:00
>
{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
);
}