el.xwx.moe/components/Dropdown.tsx

106 lines
2.6 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 =
| {
2024-07-27 16:17:38 -05:00
name: string;
onClick: MouseEventHandler;
href?: string;
}
2023-05-27 22:21:35 -05:00
| {
2024-07-27 16:17:38 -05:00
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
2024-07-27 16:17:38 -05:00
return !points || pos && (
2023-11-07 07:03:35 -06:00
<ClickAwayHandler
onMount={(e) => {
setDropdownHeight(e.height);
setDropdownWidth(e.width);
}}
style={
points
? {
2024-07-27 16:17:38 -05:00
position: "fixed",
top: `${pos?.y}px`,
left: `${pos?.x}px`,
}
2023-11-07 07:03:35 -06:00
: undefined
}
onClickOutside={onClickOutside}
2024-07-27 16:17:38 -05:00
className={`${className || ""
} py-1 shadow-md border border-neutral-content bg-base-200 rounded-md flex flex-col z-20`}
2023-11-07 07:03:35 -06:00
>
{items.map((e, i) => {
const inner = e && (
<div className="cursor-pointer rounded-md">
2023-11-26 04:17:08 -06:00
<div className="flex items-center gap-2 py-1 px-2 hover:bg-base-100 duration-100">
2023-11-24 07:39:55 -06:00
<p className="select-none">{e.name}</p>
2023-03-23 10:25:17 -05:00
</div>
2023-11-07 07:03:35 -06:00
</div>
);
2023-10-28 11:50:11 -05:00
2023-11-07 07:03:35 -06:00
return e && e.href ? (
<Link key={i} href={e.href}>
{inner}
</Link>
) : (
e && (
<div key={i} onClick={e.onClick}>
{inner}
2023-11-07 07:03:35 -06:00
</div>
)
);
})}
</ClickAwayHandler>
2024-07-27 16:17:38 -05:00
);
2023-03-22 18:11:54 -05:00
}