diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx new file mode 100644 index 0000000..615b8d4 --- /dev/null +++ b/components/Dropdown.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef, useState } from 'react'; + +import Menu from './Menu'; + +interface DropdownProps { + options: string[]; + dropdownSize?: string; + onSelect: (option: string) => void; +} + +/** + * 드롭다운 컴포넌트 + * @param options 메뉴팝업에 들어가는 옵션을 담을 배열 + * @param dropdownSize 팝업의 크기 + * @param onSelect 선택한 옵션을 부모 컴포넌트로 반환 + */ + +export default function Dropdown({ + options, + onSelect, + dropdownSize = 'w-auto', +}: DropdownProps) { + const [selectedOption, setSelectedOption] = useState(options[0]); //현재 선택된 옵션 + const [isOpen, setIsOpen] = useState(false); //드롭다운 열기/닫기 + const dropdownRef = useRef(null); // 드롭다운 DOM 요소 참조 + + //선택한 옵션을 상태로 설정, 부모 컴포넌트로 전달 + const handleOptionClick = (option: string) => { + setSelectedOption(option); + setIsOpen(false); + onSelect(option); + }; + + //외부 클릭시 드롭다운 닫기 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
+ + + {isOpen && ( + + )} +
+ ); +} diff --git a/components/Menu.tsx b/components/Menu.tsx new file mode 100644 index 0000000..d78e85a --- /dev/null +++ b/components/Menu.tsx @@ -0,0 +1,30 @@ +interface MenuProps { + options: string[]; + menuSize?: string; + onSelect: (option: string) => void; +} + +/** + * 메뉴팝업을 띄우는 컴포넌트 + * @param options 메뉴팝업에 들어가는 옵션을 담을 배열 + * @param menuSize 팝업의 크기 + * @param onSelect 선택한 옵션을 반환 + */ + +export default function Menu({ options, onSelect, menuSize }: MenuProps) { + return ( + + ); +} diff --git a/package-lock.json b/package-lock.json index 765a356..23e59c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,8 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^8.57.1", "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", "@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/parser": "^8.18.0", "autoprefixer": "^10.4.20", @@ -29,6 +29,7 @@ "eslint-config-next": "^15.1.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", + "eslint-plugin-next": "^0.0.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.2", "eslint-plugin-simple-import-sort": "^12.1.1", @@ -2678,6 +2679,13 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-next": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-next/-/eslint-plugin-next-0.0.0.tgz", + "integrity": "sha512-IldNDVb6WNduggwRbYzSGZhaskUwVecJ6fhmqwX01+S1aohwAWNzU4me6y47DDzpD/g0fdayNBGxEdt9vKkUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-plugin-prettier": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", diff --git a/package.json b/package.json index 0019e92..83b86a9 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^8.57.1", "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", "@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/parser": "^8.18.0", "autoprefixer": "^10.4.20", @@ -31,6 +31,7 @@ "eslint-config-next": "^15.1.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", + "eslint-plugin-next": "^0.0.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.2", "eslint-plugin-simple-import-sort": "^12.1.1", diff --git a/pages/test/index.tsx b/pages/test/index.tsx index aac1243..3d338fc 100644 --- a/pages/test/index.tsx +++ b/pages/test/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import Button from '@/components/Button'; +import Dropdown from '@/components/Dropdown'; import LinkBar from '@/components/LinkBar'; import SnackBar from '@/components/SnackBar'; @@ -24,6 +25,32 @@ export default function Test() { }; // ----snackBar(end)---- + //-----dropdown(start)----- + const options = ['옵션1', '옵션2', '옵션3']; + + const handleOption1 = () => { + console.log('옵션1'); + }; + + const handleOption2 = () => { + console.log('옵션2'); + }; + + const handleOption3 = () => { + console.log('옵션3'); + }; + + const handleOptionSelect = (option: string) => { + if (option === '옵션1') { + handleOption1(); + } else if (option === '옵션2') { + handleOption2(); + } else if (option === '옵션3') { + handleOption3(); + } + }; + //-----dropdown(end)----- + return (
@@ -67,6 +94,16 @@ export default function Test() { + + + +
Dropdown + +
diff --git a/public/icon/icon-arrowdown.svg b/public/icon/icon-arrowdown.svg new file mode 100644 index 0000000..ce93a2a --- /dev/null +++ b/public/icon/icon-arrowdown.svg @@ -0,0 +1,3 @@ + + +