From 5df0086cf87c87e3931d6ca4f1ea329551caa768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Sun, 15 Dec 2024 20:27:46 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20=EB=A9=94=EB=89=B4=20=ED=8C=9D?= =?UTF-8?q?=EC=97=85=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Menu.tsx | 27 +++++++++++++++++++++++++++ package-lock.json | 12 ++++++++++-- package.json | 5 +++-- pages/_app.tsx | 2 -- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 components/Menu.tsx diff --git a/components/Menu.tsx b/components/Menu.tsx new file mode 100644 index 0000000..d27013e --- /dev/null +++ b/components/Menu.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +interface MenuProps { + options: string[]; + menuSize?: string; + onSelect: (option: string) => void; +} + +const Menu: React.FC = ({ options, onSelect, menuSize }) => { + return ( + + ); +}; + +export default Menu; 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/_app.tsx b/pages/_app.tsx index 6fa9382..a850de5 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,7 +1,5 @@ import type { AppProps } from 'next/app'; - import DarkModeToggle from '@/components/DarkmodeToggle'; - import '@/styles/globals.css'; export default function App({ Component, pageProps }: AppProps) { From b114ccedd122204b7ef0826809909f8ed5ea69f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Sun, 15 Dec 2024 20:32:46 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9D=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Menu.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/Menu.tsx b/components/Menu.tsx index d27013e..ecc9a14 100644 --- a/components/Menu.tsx +++ b/components/Menu.tsx @@ -6,6 +6,13 @@ interface MenuProps { onSelect: (option: string) => void; } +/** + * 메뉴팝업을 띄우는 컴포넌트 + * @param options 메뉴팝업에 들어가는 옵션을 담을 배열 + * @param onClick 선택한 옵션을 반환 + * @param menuSize 팝업의 크기 + */ + const Menu: React.FC = ({ options, onSelect, menuSize }) => { return (
    Date: Sun, 15 Dec 2024 20:39:15 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9D=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Menu.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/Menu.tsx b/components/Menu.tsx index ecc9a14..995a90f 100644 --- a/components/Menu.tsx +++ b/components/Menu.tsx @@ -13,7 +13,7 @@ interface MenuProps { * @param menuSize 팝업의 크기 */ -const Menu: React.FC = ({ options, onSelect, menuSize }) => { +export default function Menu({ options, onSelect, menuSize }: MenuProps) { return (
      = ({ options, onSelect, menuSize }) => { ))}
    ); -}; - -export default Menu; +} From 0ea1436bd0ccb8118e6e7aa3ed4b867dac4eea70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Sun, 15 Dec 2024 22:30:50 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9C=A8=20=EB=93=9C=EB=A1=AD=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Dropdown.tsx | 63 ++++++++++++++++++++++++++++++++++ components/Menu.tsx | 4 +-- pages/test/index.tsx | 37 +++++++++++++++++++- public/icon/icon-arrowdown.svg | 3 ++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 components/Dropdown.tsx create mode 100644 public/icon/icon-arrowdown.svg diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx new file mode 100644 index 0000000..ac7edee --- /dev/null +++ b/components/Dropdown.tsx @@ -0,0 +1,63 @@ +import { useState, useEffect, useRef } from 'react'; +import Menu from './Menu'; + +interface DropdownProps { + options: string[]; + dropdownSize?: string; + onSelect: (option: string) => void; +} + +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); + + 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 index 995a90f..219287d 100644 --- a/components/Menu.tsx +++ b/components/Menu.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - interface MenuProps { options: string[]; menuSize?: string; @@ -9,7 +7,7 @@ interface MenuProps { /** * 메뉴팝업을 띄우는 컴포넌트 * @param options 메뉴팝업에 들어가는 옵션을 담을 배열 - * @param onClick 선택한 옵션을 반환 + * @param onSelect 선택한 옵션을 반환 * @param menuSize 팝업의 크기 */ diff --git a/pages/test/index.tsx b/pages/test/index.tsx index 79585f4..7e27495 100644 --- a/pages/test/index.tsx +++ b/pages/test/index.tsx @@ -1,10 +1,35 @@ import Button from '@/components/Button'; +import Dropdown from '@/components/Dropdown'; import LinkBar from '@/components/LinkBar'; export default function Test() { const commonCellClass = 'border-r border-gray-300'; const commonRowClass = 'flex flex-wrap items-end gap-2'; + 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(); + } + }; + return (
    @@ -33,12 +58,22 @@ export default function Test() { - + + + + +
    LinkBar
    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 @@ + + + From 49fab53c34c045aae2b84320bf055bcb0df714fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Sun, 15 Dec 2024 22:35:38 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=93=9D=20=EB=93=9C=EB=A1=AD=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Dropdown.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx index ac7edee..1b0ab28 100644 --- a/components/Dropdown.tsx +++ b/components/Dropdown.tsx @@ -7,21 +7,30 @@ interface DropdownProps { 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); + 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 ( From e24c2b7e6e651fbfcc7fb9dcfff785d2e098344d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Sun, 15 Dec 2024 23:09:44 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=90=9B=20ESLint=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Dropdown.tsx | 3 ++- pages/_app.tsx | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx index 1b0ab28..d9cbae2 100644 --- a/components/Dropdown.tsx +++ b/components/Dropdown.tsx @@ -1,4 +1,5 @@ -import { useState, useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; + import Menu from './Menu'; interface DropdownProps { diff --git a/pages/_app.tsx b/pages/_app.tsx index a850de5..6fa9382 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,5 +1,7 @@ import type { AppProps } from 'next/app'; + import DarkModeToggle from '@/components/DarkmodeToggle'; + import '@/styles/globals.css'; export default function App({ Component, pageProps }: AppProps) { From 936d8e603c3744e2ccccbd924d781e28ceed6db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Mon, 16 Dec 2024 14:13:04 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=92=84=20=EB=94=94=EC=9E=90=EC=9D=B8?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Dropdown.tsx | 2 +- components/Menu.tsx | 6 +++--- pages/test/index.tsx | 39 ++++++++++++++++++--------------------- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx index d9cbae2..615b8d4 100644 --- a/components/Dropdown.tsx +++ b/components/Dropdown.tsx @@ -52,7 +52,7 @@ export default function Dropdown({