Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RouterProvider } from "react-router-dom";

import { router } from "./Router";

import ToastContainer from "@/components/Toast/ToastContainer";

function App() {
Expand Down
80 changes: 80 additions & 0 deletions src/components/Dropdown.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현해주신 DropdownSelect와 크게 다르지 않은 것 같아요. 🥲
Dropdown을 활용해 공고 리스트의 상세 필터나 헤더의 알림 컴포넌트도 구현할 수 있게
"공통 컴포넌트"로 구현이 되면 좋을 것 같아요.

  • 공고 리스트의 상세 필터
image
  • 헤더의 알림
image

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useState, useRef, useEffect, useCallback } from "react";

import DropdownIcon from "@/assets/icon/dropdown-down.svg?react";
import DropUpIcon from "@/assets/icon/dropdown-up.svg?react";

interface DropdownProps {
placeholder?: string;
options: string[];
selected: string;
onSelect: (value: string) => void;
}

export default function Dropdown({
placeholder = "선택",
options,
selected,
onSelect,
}: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const handleClickOutside = useCallback((e: MouseEvent) => {
if (
dropdownRef.current &&
e.target instanceof Node &&
!dropdownRef.current.contains(e.target)
) {
setIsOpen(false);
}
}, []);

useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [handleClickOutside]);

return (
<div className="w-full relative" ref={dropdownRef}>
<button
type="button"
onClick={() => setIsOpen((prev) => !prev)}
className="w-[21.875rem] md:w-[20.625rem] lg:w-[19.25rem]
h-[3.625rem] border border-gray-30 rounded-[0.375rem]
px-[1.25rem] py-[1rem] flex justify-between items-center"
>
<span className={selected ? "text-black" : "text-gray-40"}>
{selected || placeholder}
</span>

<span className="ml-2">
{isOpen ? (
<DropUpIcon className="w-4 h-4" />
) : (
<DropdownIcon className="w-4 h-4" />
)}
</span>
</button>

{isOpen && (
<ul
className="absolute left-0 top-full mt-1 z-10
w-[21.875rem] md:w-[20.625rem] lg:w-[19.25rem]
max-h-48 overflow-y-auto rounded-md border border-gray-200 bg-white"
>
{options.map((option) => (
<li
key={option}
onClick={() => {
onSelect(option);
setIsOpen(false);
}}
className="px-4 py-3 text-center hover:bg-gray-100 cursor-pointer text-sm font-normal border-b border-gray-20"
>
{option}
</li>
))}
</ul>
)}
</div>
);
}