From 2f1cc8f4ed83d87f5db98ca2fab7971710b9868a Mon Sep 17 00:00:00 2001
From: Kim DoKyun <51306225+jasper200207@users.noreply.github.com>
Date: Mon, 15 Jul 2024 15:10:12 +0900
Subject: [PATCH] Feature/#284 participant menu (#285)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat : ParticipantMenuProps & ParticipantItemProps
* feat : ParticipantItem
* feat : ParticipantMenu
* refactor : 리뷰반영
- includeMembers, excludeMembers optional
- excludeMember가 존재할때만 Divider
- #284
---
.../ParticipantMenu/ParticipantItem/index.tsx | 79 +++++++++++++
src/components/ParticipantMenu/index.tsx | 111 ++++++++++++++++++
src/components/ParticipantMenu/types.ts | 24 ++++
3 files changed, 214 insertions(+)
create mode 100644 src/components/ParticipantMenu/ParticipantItem/index.tsx
create mode 100644 src/components/ParticipantMenu/index.tsx
create mode 100644 src/components/ParticipantMenu/types.ts
diff --git a/src/components/ParticipantMenu/ParticipantItem/index.tsx b/src/components/ParticipantMenu/ParticipantItem/index.tsx
new file mode 100644
index 0000000..afb51c3
--- /dev/null
+++ b/src/components/ParticipantMenu/ParticipantItem/index.tsx
@@ -0,0 +1,79 @@
+import { Avatar, Flex, IconButton, Text } from '@chakra-ui/react';
+import { RiAddLine, RiCloseFill, RiVipCrownLine } from 'react-icons/ri';
+
+import { ParticipantItemProps } from '@/components/ParticipantMenu/types';
+import colors from '@/theme/foundations/colors';
+
+const ParticipantItem = ({ member, type, onAdd, onMandateLeader, onRemove }: ParticipantItemProps) => {
+ const handleDeleteMember = () => {
+ onRemove(member);
+ };
+
+ const handleMandateLeader = () => {
+ onMandateLeader(member);
+ };
+
+ const handleAddMember = () => {
+ onAdd(member);
+ };
+
+ return (
+
+
+
+ {member.name}
+
+ {type === 'INCLUDE' && (
+
+ }
+ onClick={handleDeleteMember}
+ size="icon_sm"
+ />
+ }
+ onClick={handleMandateLeader}
+ size="icon_sm"
+ />
+
+ )}
+ {type === 'EXCLUDE' && (
+
+ }
+ onClick={handleAddMember}
+ size="icon_sm"
+ />
+
+ )}
+
+ );
+};
+
+export default ParticipantItem;
diff --git a/src/components/ParticipantMenu/index.tsx b/src/components/ParticipantMenu/index.tsx
new file mode 100644
index 0000000..e57a5f0
--- /dev/null
+++ b/src/components/ParticipantMenu/index.tsx
@@ -0,0 +1,111 @@
+/* eslint-disable react/jsx-props-no-spreading */
+import { Divider, Flex, Input } from '@chakra-ui/react';
+import { useEffect, useRef, useState } from 'react';
+import { BiSearch } from 'react-icons/bi';
+
+import ParticipantItem from '@/components/ParticipantMenu/ParticipantItem';
+import { ParticipantMenuProps } from '@/components/ParticipantMenu/types';
+import { Member } from '@/types';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const defaultFunction = (member: Member) => {};
+
+const ParticipantMenu = ({
+ leader,
+ includeMembers = [],
+ excludeMembers = [],
+ children,
+ isOpen,
+ setIsOpen,
+ onRemove = defaultFunction,
+ onAdd = defaultFunction,
+ onMandateLeader = defaultFunction,
+ ...flexProps
+}: ParticipantMenuProps) => {
+ const [search, setSearch] = useState('');
+ const menuRef = useRef(null);
+
+ const searchedLeader = leader?.name.includes(search) ? leader : null;
+ const searchedIncludeMember = includeMembers.filter((member) => member.name.includes(search));
+ const searchedExcludeMember = excludeMembers.filter((member) => member.name.includes(search));
+
+ useEffect(() => {
+ const handleOutsideClose = (e: MouseEvent) => {
+ if (isOpen && !menuRef.current?.contains(e.target as Node)) setIsOpen(false);
+ };
+ document.addEventListener('click', handleOutsideClose);
+
+ return () => document.removeEventListener('click', handleOutsideClose);
+ }, [isOpen, setIsOpen]);
+
+ return (
+
+ {
+ setIsOpen((prev) => !prev);
+ }}
+ >
+ {children}
+
+
+
+
+ setSearch(e.target.value)}
+ value={search}
+ />
+
+
+
+ {searchedLeader && (
+
+ )}
+ {searchedIncludeMember.map((member: Member) => (
+
+ ))}
+ {searchedExcludeMember && searchedExcludeMember.length > 0 && }
+ {searchedExcludeMember.map((member: Member) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ParticipantMenu;
diff --git a/src/components/ParticipantMenu/types.ts b/src/components/ParticipantMenu/types.ts
new file mode 100644
index 0000000..9bdd990
--- /dev/null
+++ b/src/components/ParticipantMenu/types.ts
@@ -0,0 +1,24 @@
+import { FlexProps } from '@chakra-ui/react';
+import { ReactNode } from 'react';
+
+import { Member } from '@/types';
+
+export interface ParticipantMenuProps extends FlexProps {
+ leader?: Member;
+ includeMembers?: Member[];
+ excludeMembers?: Member[];
+ children: ReactNode;
+ isOpen: boolean;
+ setIsOpen: React.Dispatch>;
+ onRemove?: (member: Member) => void;
+ onAdd?: (member: Member) => void;
+ onMandateLeader?: (member: Member) => void;
+}
+
+export interface ParticipantItemProps {
+ member: Member;
+ type: 'LEADER' | 'INCLUDE' | 'EXCLUDE';
+ onRemove: (member: Member) => void;
+ onAdd: (member: Member) => void;
+ onMandateLeader: (member: Member) => void;
+}