Skip to content

Commit

Permalink
feat: 초대하기를 링크 공유와 카카오톡을 선택할 수 있도록 변경 (#719)
Browse files Browse the repository at this point in the history
* feat: 공유버튼 모바일과 데탑 분리

* feat: 송금버튼 새로운 반영사항 적용

* feat: 계좌번호 없을 때 금액 복사 기능 추가

* test: prop 변경으로 인한 스토리북 변경

* feat: 계좌번호 없을 때 금액 복사되는 onCopy 추가

* feat: 다나까 -> 요 체로 변경

* feat: 송금기능 페이지 이동으로 인해 memberId 추가

* feat: 송금 페이지 라우트

* feat: 송금하기 버튼을 눌렀을 때 navigate state로 정보 전달

* feat: 송금 방법(복사, 카카오페이, 토스) 제공

* feat: 아래 => 토스열기 명시적으로 변경

* feat: Banner 컴포넌트 생성

* feat: 세션스토리지 util 추가

* feat: 계좌번호 입력 유도 기능 구현

* feat: Flex div prop도 받도록 설정

* feat: 돌아가는 Chevron 컴포넌트 생성

* feat: 바깥 클릭 시 실행되는 컴포넌트 생성

* feat: Select 컴포넌트 제작

* feat: 요구사항 변경으로 인한 송금 플로우 변경

* feat: Select 컴포넌트 제작

* design: 텍스트 색깔 변경

* style: export 정리

* refactor: ClickOutsideDetector 사용

* feat: dropdown base 미트볼, 버튼 두 개 지원

* style: dropdown list z-index 추가

* feat: 드랍다운 버튼을 클릭할 때 드랍다운 리스트가 닫히는 기능 구현

* feat: 모바일에서 링크 초대와 카카오톡 초대 분리

* feat: 공유 메시지 변경
  • Loading branch information
jinhokim98 authored and Todari committed Oct 10, 2024
1 parent 38da5a1 commit 158bd62
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 149 deletions.
38 changes: 38 additions & 0 deletions client/src/components/Design/components/Dropdown/ButtonBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/** @jsxImportSource @emotion/react */
import {useTheme} from '@components/Design/theme/HDesignProvider';

import Button from '../Button/Button';
import Flex from '../Flex/Flex';

import {dropdownButtonBaseStyle} from './Dropdown.style';
import {DropdownProps} from './Dropdown.type';
import DropdownButton from './DropdownButton';

type ButtonBaseProps = DropdownProps & {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
dropdownRef: React.RefObject<HTMLElement>;
};

const ButtonBase = ({isOpen, setIsOpen, dropdownRef, baseButtonText, children}: ButtonBaseProps) => {
const {theme} = useTheme();

return (
<>
<Button variants="tertiary" size="small" onClick={() => setIsOpen(true)}>
{baseButtonText}
</Button>
{isOpen && (
<section ref={dropdownRef}>
<Flex {...dropdownButtonBaseStyle(theme)}>
{children.map((button, index) => (
<DropdownButton key={index} setIsOpen={setIsOpen} {...button.props} />
))}
</Flex>
</section>
)}
</>
);
};

export default ButtonBase;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ const meta = {
</div>
),
],
argTypes: {
base: {
description: '',
control: {type: 'select'},
options: ['meatballs', 'button'],
},
},
args: {
baseButtonText: '정산 초대하기',
children: [
<DropdownButton text="전체 참여자 관리" onClick={() => alert('전체 참여자 관리 클릭')} />,
<DropdownButton text="계좌번호 입력하기" onClick={() => alert('계좌번호 입력하기 클릭')} />,
Expand Down
59 changes: 44 additions & 15 deletions client/src/components/Design/components/Dropdown/Dropdown.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,50 @@ import {Theme} from '@components/Design/theme/theme.type';

import {FlexProps} from '../Flex/Flex.type';

export const dropdownStyle: FlexProps = {
flexDirection: 'column',
width: '12.5rem',
padding: '0.5rem',
paddingInline: '0.5rem',
gap: '0.25rem',
backgroundColor: 'white',

cssProp: {
position: 'absolute',
top: '2rem',
right: '-1rem',
borderRadius: '0.75rem',
boxShadow: '2px 4px 16px 0 rgba(0, 0, 0, 0.08)',
},
export const dropdownBaseStyle = css({
position: 'relative',

WebkitTapHighlightColor: 'transparent',
});

export const dropdownStyle = (theme: Theme): FlexProps => {
return {
flexDirection: 'column',
width: '12.5rem',
padding: '0.5rem',
paddingInline: '0.5rem',
gap: '0.25rem',
backgroundColor: 'white',

cssProp: {
position: 'absolute',
top: '2rem',
right: '-1rem',
borderRadius: '0.75rem',
boxShadow: '2px 4px 16px 0 rgba(0, 0, 0, 0.08)',
zIndex: theme.zIndex.dropdownList,
},
};
};

export const dropdownButtonBaseStyle = (theme: Theme): FlexProps => {
return {
flexDirection: 'column',
width: '12.5rem',
padding: '0.5rem',
paddingInline: '0.5rem',
gap: '0.25rem',
backgroundColor: 'white',

cssProp: {
position: 'absolute',
top: '2.5rem',
right: '-0.5rem',
borderRadius: '0.75rem',
boxShadow: '2px 4px 16px 0 rgba(0, 0, 0, 0.08)',
zIndex: theme.zIndex.dropdownList,
},
};
};

export const dropdownButtonStyle = (theme: Theme) =>
Expand Down
48 changes: 23 additions & 25 deletions client/src/components/Design/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
/** @jsxImportSource @emotion/react */
import Icon from '../Icon/Icon';
import IconButton from '../IconButton/IconButton';
import Flex from '../Flex/Flex';
import ClickOutsideDetector from '../ClickOutsideDetector';

import useDropdown from './useDropdown';
import {DropdownProps} from './Dropdown.type';
import DropdownButton from './DropdownButton';
import {dropdownStyle} from './Dropdown.style';
import MeatballBase from './MeatballBase';
import ButtonBase from './ButtonBase';
import {dropdownBaseStyle} from './Dropdown.style';

const Dropdown = ({children}: DropdownProps) => {
const {isOpen, openDropdown, meetBallsRef, dropdownRef} = useDropdown();
const isDropdownOpen = isOpen && meetBallsRef.current;
const Dropdown = ({base = 'meatballs', baseButtonText, children}: DropdownProps) => {
const {isOpen, setIsOpen, baseRef, dropdownRef} = useDropdown();
const isDropdownOpen = isOpen && !!baseRef.current;

return (
<IconButton
ref={meetBallsRef}
variants="none"
onClick={openDropdown}
style={{position: 'relative', WebkitTapHighlightColor: 'transparent'}}
>
<Icon iconType="meatballs" />
{isDropdownOpen && (
<section ref={dropdownRef}>
<Flex {...dropdownStyle}>
{children.map(button => (
<DropdownButton {...button.props} />
))}
</Flex>
</section>
)}
</IconButton>
<ClickOutsideDetector targetRef={baseRef} onClickOutside={() => setIsOpen(false)}>
<div ref={baseRef} css={dropdownBaseStyle}>
{base === 'meatballs' && (
<MeatballBase isOpen={isDropdownOpen} setIsOpen={setIsOpen} dropdownRef={dropdownRef} children={children} />
)}
{base === 'button' && (
<ButtonBase
isOpen={isDropdownOpen}
setIsOpen={setIsOpen}
dropdownRef={dropdownRef}
children={children}
baseButtonText={baseButtonText}
/>
)}
</div>
</ClickOutsideDetector>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
export type DropdownBase = 'meatballs' | 'button';

export type DropdownButtonProps = React.HTMLAttributes<HTMLButtonElement> & {
text: string;
setIsOpen?: React.Dispatch<React.SetStateAction<boolean>>; // 내부에서 사용하기 위한 props 외부에서 넣어주지 말 것
};

export type DropdownProps = {
base?: DropdownBase;
baseButtonText?: string;
children: React.ReactElement<DropdownButtonProps>[];
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ import Text from '../Text/Text';
import {dropdownButtonStyle} from './Dropdown.style';
import {DropdownButtonProps} from './Dropdown.type';

const DropdownButton = ({text, ...buttonProps}: DropdownButtonProps) => {
const DropdownButton = ({text, onClick, setIsOpen, ...buttonProps}: DropdownButtonProps) => {
const {theme} = useTheme();

return (
<button css={dropdownButtonStyle(theme)} {...buttonProps}>
<button
css={dropdownButtonStyle(theme)}
onClick={event => {
event.stopPropagation();
if (onClick) onClick(event);
if (setIsOpen) setIsOpen(false);
}}
{...buttonProps}
>
<Text size="body" color="black">
{text}
</Text>
Expand Down
39 changes: 39 additions & 0 deletions client/src/components/Design/components/Dropdown/MeatballBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** @jsxImportSource @emotion/react */
import {useTheme} from '@components/Design/theme/HDesignProvider';

import Flex from '../Flex/Flex';
import Icon from '../Icon/Icon';
import IconButton from '../IconButton/IconButton';

import {dropdownStyle} from './Dropdown.style';
import {DropdownProps} from './Dropdown.type';
import DropdownButton from './DropdownButton';

type MeatballBaseProps = DropdownProps & {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
dropdownRef: React.RefObject<HTMLElement>;
};

const MeatballBase = ({isOpen, setIsOpen, dropdownRef, children}: MeatballBaseProps) => {
const {theme} = useTheme();

return (
<>
<IconButton variants="none" onClick={() => setIsOpen(true)}>
<Icon iconType="meatballs" />
</IconButton>
{isOpen && (
<section ref={dropdownRef}>
<Flex {...dropdownStyle(theme)}>
{children.map((button, index) => (
<DropdownButton key={index} setIsOpen={setIsOpen} {...button.props} />
))}
</Flex>
</section>
)}
</>
);
};

export default MeatballBase;
31 changes: 4 additions & 27 deletions client/src/components/Design/components/Dropdown/useDropdown.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
import {useEffect, useRef, useState} from 'react';
import {useRef, useState} from 'react';

const useDropdown = () => {
const [isOpen, setIsOpen] = useState(false);
const meetBallsRef = useRef<HTMLButtonElement>(null);
const baseRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLElement>(null);

const openDropdown = () => {
setIsOpen(true);
};

useEffect(() => {
const clickOutSide = (event: MouseEvent) => {
const targetNode = event.target as Node;

if (
(dropdownRef.current && dropdownRef.current.contains(targetNode)) ||
(meetBallsRef.current && meetBallsRef.current.contains(targetNode))
) {
return;
}

setIsOpen(false);
};
document.addEventListener('mousedown', clickOutSide);
return () => {
document.removeEventListener('mousedown', clickOutSide);
};
}, [dropdownRef]);

return {
isOpen,
openDropdown,
meetBallsRef,
setIsOpen,
baseRef,
dropdownRef,
};
};
Expand Down
5 changes: 4 additions & 1 deletion client/src/components/Design/token/zIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const BOTTOM_SHEET_DIMMED_LAYER = NUMBER_KEYBOARD_BOTTOM_SHEET + ABOVE;
const BOTTOM_SHEET_CONTAINER = BOTTOM_SHEET_DIMMED_LAYER + ABOVE;
const TOAST = BOTTOM_SHEET_CONTAINER + ABOVE;
const SELECT_OPTION = ABOVE;
const DROPDOWN_LIST = BASE + ABOVE;

export const ZINDEX = {
bottomSheetDimmedLayer: BOTTOM_SHEET_DIMMED_LAYER,
Expand All @@ -25,6 +26,7 @@ export const ZINDEX = {
tabText: TAB_TEXT,
toast: TOAST,
selectOption: SELECT_OPTION,
dropdownList: DROPDOWN_LIST,
} as const;

type ZIndexKeys =
Expand All @@ -37,6 +39,7 @@ type ZIndexKeys =
| 'tabText'
| 'tabIndicator'
| 'toast'
| 'selectOption';
| 'selectOption'
| 'dropdownList';

export type ZIndexTokens = Record<ZIndexKeys, number>;
33 changes: 14 additions & 19 deletions client/src/components/ShareEventButton/DesktopShareEventButton.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
import CopyToClipboard from 'react-copy-to-clipboard';

import toast from '@hooks/useToast/toast';

import {Button} from '@components/Design';

type DesktopShareEventButtonProps = React.HTMLAttributes<HTMLButtonElement> & {
shareText: string;
text: string;
type DesktopShareEventButtonProps = React.PropsWithChildren<React.HTMLAttributes<HTMLButtonElement>> & {
onCopy: () => Promise<void>;
};

const DesktopShareEventButton = ({shareText, text, onClick}: DesktopShareEventButtonProps) => {
const DesktopShareEventButton = ({onCopy, children, ...buttonProps}: DesktopShareEventButtonProps) => {
const copyAndToast = async () => {
await onCopy();
toast.confirm('링크가 복사되었어요 :) \n참여자들에게 링크를 공유해 주세요!', {
showingTime: 3000,
position: 'bottom',
});
};

return (
<CopyToClipboard
text={shareText}
onCopy={() =>
toast.confirm('링크가 복사되었어요 :) \n참여자들에게 링크를 공유해 주세요!', {
showingTime: 3000,
position: 'bottom',
})
}
>
<Button size="small" variants="tertiary" onClick={onClick} style={{marginRight: '1rem'}}>
{text}
</Button>
</CopyToClipboard>
<Button size="small" variants="tertiary" onClick={copyAndToast} style={{marginRight: '1rem'}} {...buttonProps}>
{children}
</Button>
);
};

Expand Down
Loading

0 comments on commit 158bd62

Please sign in to comment.