110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
/* eslint-disable react-hooks/exhaustive-deps */
|
|
import { motion } from "motion/react";
|
|
import { AnimatePresence } from "motion/react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useClickAway } from "@uidotdev/usehooks";
|
|
import clsx from "clsx";
|
|
import ChevronDownIcon from "../icons/ChevronDownIcon";
|
|
import CheckIcon from "../icons/CheckIcon";
|
|
|
|
function Select({
|
|
options,
|
|
onSelect,
|
|
className = "",
|
|
label = "",
|
|
defaultOption = "",
|
|
}: {
|
|
options: string[];
|
|
onSelect: (option: string) => void;
|
|
defaultOption: string;
|
|
className?: string;
|
|
label?: string;
|
|
}) {
|
|
const [isShow, setIsShow] = useState(false);
|
|
const [selectedOption, setSelectedOption] = useState(defaultOption);
|
|
|
|
const ref = useClickAway<HTMLDivElement>(() => setIsShow(false));
|
|
|
|
const dropDownRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => setSelectedOption(defaultOption), [defaultOption]);
|
|
|
|
useEffect(() => onSelect(selectedOption), [selectedOption]);
|
|
|
|
function handleScroll() {
|
|
if (dropDownRef.current)
|
|
dropDownRef.current.style.maxHeight = `calc(100vh - ${
|
|
dropDownRef.current?.getBoundingClientRect().y
|
|
}px - 0.278vw)`;
|
|
}
|
|
|
|
useEffect(() => {
|
|
handleScroll();
|
|
document.addEventListener("scroll", handleScroll);
|
|
|
|
return () => document.removeEventListener("scroll", handleScroll);
|
|
}, [isShow]);
|
|
|
|
return (
|
|
<div ref={ref} className={clsx("relative", className)}>
|
|
{label && (
|
|
<p className="text-s text-[#0D1922]/70 2xl:mb-[0.556vw] mb-2">
|
|
{label}
|
|
</p>
|
|
)}
|
|
<button
|
|
className={clsx(
|
|
"2xl:px-[1.111vw] px-4 2xl:py-[0.833vw] py-3 2xl:rounded-[0.833vw] rounded-xl 2xl:ring-[0.069vw] ring-1 transition-[box-shadow] w-full text-left flex items-center justify-between group bg-white",
|
|
isShow ? " ring-[#00BED7]" : "ring-[#E2E2DC]"
|
|
)}
|
|
onClick={() => setIsShow(!isShow)}
|
|
>
|
|
<p className="text-s">{selectedOption}</p>
|
|
<ChevronDownIcon
|
|
className={clsx(
|
|
"2xl:w-[1.667vw] 2xl:h-[1.667vw] w-6 h-6 flex-shrink-0 group-hover:text-[#00BED7] transition-[color,rotate]",
|
|
isShow && "rotate-180"
|
|
)}
|
|
/>
|
|
</button>
|
|
<AnimatePresence>
|
|
{isShow && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.15 }}
|
|
ref={dropDownRef}
|
|
className="absolute 2xl:mt-[0.278vw] 2xl:pt-[0.278vw] mt-1 p-1 2xl:space-y-[0.139vw] space-y-0.5 shadow-[0px_2px_8px_rgba(0,0,0,0.15)] overflow-auto rounded-xl bg-white w-full z-10"
|
|
>
|
|
{options.map((option, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => {
|
|
setSelectedOption(option);
|
|
setIsShow(false);
|
|
}}
|
|
className="px-4 py-[14px] group hover:bg-[#F3F3F2] transition-colors rounded-xl w-full text-left flex items-center justify-between"
|
|
>
|
|
<p
|
|
className={clsx(
|
|
"text-s group-hover:text-[#0D1922] transition-colors",
|
|
selectedOption !== option && "text-[#0D1922]/70"
|
|
)}
|
|
>
|
|
{option}
|
|
</p>
|
|
{selectedOption === option && (
|
|
<CheckIcon className="2xl:w-[1.667vw] 2xl:h-[1.667vw] w-6 h-6 flex-shrink-0 text-[#00BED7]" />
|
|
)}
|
|
</button>
|
|
))}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Select;
|