Skip to content

Commit

Permalink
Feature/#284 participant menu (#285)
Browse files Browse the repository at this point in the history
* feat : ParticipantMenuProps & ParticipantItemProps

* feat : ParticipantItem

* feat : ParticipantMenu

* refactor : 리뷰반영

- includeMembers, excludeMembers optional
- excludeMember가 존재할때만 Divider
- #284
  • Loading branch information
jasper200207 authored Jul 15, 2024
1 parent b9b3827 commit 2f1cc8f
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 0 deletions.
79 changes: 79 additions & 0 deletions src/components/ParticipantMenu/ParticipantItem/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Flex key={member.id} align="center" gap="4" role="group">
<Avatar size="sm" />
<Text textStyle="bold_sm" ml="2" textColor={type === 'LEADER' ? colors.orange_dark : 'black'}>
{member.name}
</Text>
{type === 'INCLUDE' && (
<Flex
gap="2"
ml="auto"
_groupHover={{
visibility: 'visible',
}}
visibility="hidden"
>
<IconButton
color="orange_dark"
_hover={{ bgColor: 'transparent' }}
aria-label=""
bgColor="transparent"
icon={<RiCloseFill />}
onClick={handleDeleteMember}
size="icon_sm"
/>
<IconButton
color="orange_dark"
_hover={{ bgColor: 'transparent' }}
aria-label=""
bgColor="transparent"
icon={<RiVipCrownLine />}
onClick={handleMandateLeader}
size="icon_sm"
/>
</Flex>
)}
{type === 'EXCLUDE' && (
<Flex
gap="2"
ml="auto"
_groupHover={{
visibility: 'visible',
}}
visibility="hidden"
>
<IconButton
color="orange_dark"
_hover={{ bgColor: 'transparent' }}
aria-label=""
bgColor="transparent"
icon={<RiAddLine />}
onClick={handleAddMember}
size="icon_sm"
/>
</Flex>
)}
</Flex>
);
};

export default ParticipantItem;
111 changes: 111 additions & 0 deletions src/components/ParticipantMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(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 (
<Flex pos="relative">
<Flex
{...flexProps}
pos="relative"
cursor="pointer"
onClick={() => {
setIsOpen((prev) => !prev);
}}
>
{children}
</Flex>
<Flex pos="absolute" zIndex="10" top="9" right="0" hidden={!isOpen}>
<Flex ref={menuRef} direction="column" gap="2" maxH="30vh" p="4" bg="white" borderRadius="16" shadow="md">
<Flex
alignContent="center"
justify="center"
w="full"
borderWidth="1px"
borderColor="#6c6c6c"
borderRadius="full"
>
<Input
color="black"
fontSize="16px"
bg="transparent"
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
<Flex as={BiSearch} my="auto" mr="1" color="#6c6c6c" size="26px" />
</Flex>
<Flex className="scroll" direction="column" gap="2" overflowY="scroll" h="full">
{searchedLeader && (
<ParticipantItem
key={searchedLeader.id}
member={searchedLeader}
type="LEADER"
onRemove={onRemove}
onAdd={onAdd}
onMandateLeader={onMandateLeader}
/>
)}
{searchedIncludeMember.map((member: Member) => (
<ParticipantItem
key={member.id}
member={member}
type="INCLUDE"
onRemove={onRemove}
onAdd={onAdd}
onMandateLeader={onMandateLeader}
/>
))}
{searchedExcludeMember && searchedExcludeMember.length > 0 && <Divider />}
{searchedExcludeMember.map((member: Member) => (
<ParticipantItem
key={member.id}
member={member}
type="EXCLUDE"
onRemove={onRemove}
onAdd={onAdd}
onMandateLeader={onMandateLeader}
/>
))}
</Flex>
</Flex>
</Flex>
</Flex>
);
};

export default ParticipantMenu;
24 changes: 24 additions & 0 deletions src/components/ParticipantMenu/types.ts
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<boolean>>;
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;
}

0 comments on commit 2f1cc8f

Please sign in to comment.