Skip to content

Commit

Permalink
feat: 이벤트 홈 화면 새로운 플로우로 교체 (참여자 별 정산, 전체 지출 내역), Dropdown 컴포넌트 구현 (#582)
Browse files Browse the repository at this point in the history
* feat: z index theme에 추가

* feat: 탭 컴포넌트 스타일 바뀐대로 설정

* design: gap 만큼 더 움직이도록 설정

* feat: ExpenseList안에 검색input 추가

* feat: Flex 컴포넌트에 다른 style 넣을 수 있도록 확장

* feat: inputType search일 때 검색 아이콘 보이도록 설정

* feat: 바뀐 이벤트 홈 디자인 반영

* remove: 사용하지 않는 Search 컴포넌트 삭제

* fix: Flex컴포넌트 다른 스타일 확장으로 인한 ts 오류 수정

* feat: ExpenseList isDeposited 내용 추가

* style: 사용하지 않는 import제거

* design: 버튼 색상 변경

* feat: 지출내역이 없을 때 fallback 추가

* feat: 지출 내역이 없을 때 fallback 적용

* feat: Dropdown 컴포넌트 구현

* fix: createPortal 제거 -> position absolute를 이용해 대체

* feat: AdminPage에 dropdown 컴포넌트 적용

* design: 모바일에서 클릭 시 파란색 강조되는 색 제거

* feat: 계좌번호 입력 드랍다운 눌렀을 때 페이지 이동 (머지되면 붙일 예정)

* design: 탭 컴포넌트 애니메이션 cubic-bezier를 이용해서 변경

* design: 드랍다운 버튼 hover 강조

* fix: ResizeObserver를 이용해서 tab width의 값을 정확히 가져오도록 설정

* design: tab width가 0일 때 indicator가 랜더링 되지 않도록 설정

* chore: 충돌 병합
  • Loading branch information
jinhokim98 authored Sep 24, 2024
1 parent e419eb2 commit 396ff78
Show file tree
Hide file tree
Showing 35 changed files with 438 additions and 250 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/** @jsxImportSource @emotion/react */
import type {Meta, StoryObj} from '@storybook/react';

import Dropdown from '@HDcomponents/Dropdown/Dropdown';

import DropdownButton from './DropdownButton';

const meta = {
title: 'Components/Dropdown',
component: Dropdown,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
decorators: [
Story => (
<div style={{height: '420px'}}>
<Story />
</div>
),
],
args: {
children: [
<DropdownButton text="전체 참여자 관리" onClick={() => alert('전체 참여자 관리 클릭')} />,
<DropdownButton text="계좌번호 입력하기" onClick={() => alert('계좌번호 입력하기 클릭')} />,
],
},
} satisfies Meta<typeof Dropdown>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Playground: Story = {};
33 changes: 33 additions & 0 deletions client/src/components/Design/components/Dropdown/Dropdown.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {css} from '@emotion/react';

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',

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

export const dropdownButtonStyle = (theme: Theme) =>
css({
height: '2rem',
padding: '0.25rem 0.5rem',

':hover': {
borderRadius: '0.625rem',
backgroundColor: theme.colors.grayContainer,
},
});
36 changes: 36 additions & 0 deletions client/src/components/Design/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @jsxImportSource @emotion/react */
import Icon from '../Icon/Icon';
import IconButton from '../IconButton/IconButton';
import Flex from '../Flex/Flex';

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

const Dropdown = ({children}: DropdownProps) => {
const {isOpen, openDropdown, meetBallsRef, dropdownRef} = useDropdown();
const isDropdownOpen = isOpen && meetBallsRef.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>
);
};

export default Dropdown;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type DropdownButtonProps = React.HTMLAttributes<HTMLButtonElement> & {
text: string;
};

export type DropdownProps = {
children: React.ReactElement<DropdownButtonProps>[];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** @jsxImportSource @emotion/react */

import {useTheme} from '@components/Design/theme/HDesignProvider';

import Text from '../Text/Text';

import {dropdownButtonStyle} from './Dropdown.style';
import {DropdownButtonProps} from './Dropdown.type';

const DropdownButton = ({text, ...buttonProps}: DropdownButtonProps) => {
const {theme} = useTheme();
return (
<button css={dropdownButtonStyle(theme)} {...buttonProps}>
<Text size="body" color="black">
{text}
</Text>
</button>
);
};

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

const useDropdown = () => {
const [isOpen, setIsOpen] = useState(false);
const meetBallsRef = useRef<HTMLButtonElement>(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,
dropdownRef,
};
};

export default useDropdown;
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,43 @@ const meta = {
},
},
args: {
name: '소하',
onSearch: () => console.log('쿠키'),
placeholder: '안녕',

expenseList: [
{name: '소하', price: 2000, clipboardText: '토스은행 2000원', onBankButtonClick: () => console.log('소하')},
{name: '토다리', price: 2000, clipboardText: '토스은행 2000원', onBankButtonClick: () => console.log('토다리')},
{name: '웨디', price: 1080, clipboardText: '토스은행 1080원', onBankButtonClick: () => console.log('웨디')},
{name: '쿠키', price: 3020, clipboardText: '토스은행 3020원', onBankButtonClick: () => console.log('쿠키')},
{
memberId: 1,
name: '소하',
price: 2000,
isDeposited: true,
clipboardText: '토스은행 2000원',
onBankButtonClick: () => console.log('소하'),
},
{
memberId: 2,
name: '토다리',
price: 2000,
isDeposited: false,
clipboardText: '토스은행 2000원',
onBankButtonClick: () => console.log('토다리'),
},
{
memberId: 3,
name: '웨디',
price: 1080,
isDeposited: true,
clipboardText: '토스은행 1080원',
onBankButtonClick: () => console.log('웨디'),
},
{
memberId: 4,
name: '쿠키',
price: 3020,
isDeposited: false,
clipboardText: '토스은행 3020원',
onBankButtonClick: () => console.log('쿠키'),
},
],
},
} satisfies Meta<typeof ExpenseList>;
Expand Down

This file was deleted.

49 changes: 33 additions & 16 deletions client/src/components/Design/components/ExpenseList/ExpenseList.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
/** @jsxImportSource @emotion/react */

import Text from '@HDcomponents/Text/Text';
import {useTheme} from '@theme/HDesignProvider';

import isMobileDevice from '@utils/isMobileDevice';

import BankSendButton from '../BankSendButton/BankSendButton';
import Icon from '../Icon/Icon';
import IconButton from '../IconButton/IconButton';
import Flex from '../Flex/Flex';
import Input from '../Input/Input';
import Amount from '../Amount/Amount';
import DepositCheck from '../DepositCheck/DepositCheck';

import {ExpenseItemProps, ExpenseListProps} from './ExpenseList.type';
import {expenseListStyle} from './ExpenseList.style';

// TODO: (@soha) 따로 파일 분리할까 고민중.. 여기서만 사용할 것 같긴 한데.. 흠
// TODO: (@todari) : 추후 클릭 시 상호작용이 생기면 iconButton으로 변경할 수 있음
function ExpenseItem({name, price, onBankButtonClick, clipboardText, ...divProps}: ExpenseItemProps) {
function ExpenseItem({name, price, isDeposited, onBankButtonClick, clipboardText, ...divProps}: ExpenseItemProps) {
return (
<Flex justifyContent="spaceBetween" alignItems="center" height="2.5rem" padding="0.5rem 1rem" {...divProps}>
<Text size="bodyBold">{name}</Text>
<Flex
justifyContent="spaceBetween"
alignItems="center"
height="2.5rem"
padding="0.5rem 1rem"
paddingInline="0.5rem"
{...divProps}
>
<Flex gap="0.5rem" alignItems="center">
<DepositCheck isDeposited={isDeposited} />
<Text size="bodyBold" color="onTertiary">
{name}
</Text>
</Flex>
<Flex alignItems="center" gap="0.5rem">
<Text>{price.toLocaleString('ko-kr')}</Text>
<Amount amount={price} />
{isMobileDevice() ? (
<BankSendButton
clipboardText={clipboardText}
Expand All @@ -37,14 +47,21 @@ function ExpenseItem({name, price, onBankButtonClick, clipboardText, ...divProps
);
}

function ExpenseList({expenseList = []}: ExpenseListProps) {
const {theme} = useTheme();
function ExpenseList({name, onSearch, placeholder, expenseList = []}: ExpenseListProps) {
return (
<div css={expenseListStyle(theme)}>
{expenseList.map((expense, index: number) => (
<ExpenseItem key={expense.name + index} {...expense} />
))}
</div>
<Flex
flexDirection="column"
width="100%"
backgroundColor="white"
padding="0.5rem 1rem"
paddingInline="0.5rem"
gap="0.5rem"
height="100%"
otherStyle={{borderRadius: '1rem'}}
>
<Input inputType="search" value={name} onChange={onSearch} placeholder={placeholder} />
{expenseList.length !== 0 && expenseList.map(expense => <ExpenseItem key={expense.memberId} {...expense} />)}
</Flex>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
export interface ExpenseItemCustomProps {
name: string;
price: number;
import {Report} from 'types/serviceType';

export type ExpenseItemCustomProps = Report & {
onBankButtonClick: () => void;
clipboardText: string;
}
};

export type ExpenseItemProps = React.ComponentProps<'div'> & ExpenseItemCustomProps;

export type ExpenseListProps = {
name: string;
onSearch: ({target}: React.ChangeEvent<HTMLInputElement>) => void;
placeholder: string;
expenseList: ExpenseItemProps[];
};
2 changes: 0 additions & 2 deletions client/src/components/Design/components/Flex/Flex.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const flexStyle = ({
minHeight,
backgroundColor,
theme,
...rest
}: FlexProps) =>
css({
display: 'flex',
Expand All @@ -32,7 +31,6 @@ export const flexStyle = ({
width,
height,
minHeight,
...rest,

backgroundColor: (() => {
switch (backgroundColor) {
Expand Down
10 changes: 6 additions & 4 deletions client/src/components/Design/components/Flex/Flex.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/** @jsxImportSource @emotion/react */
import {css} from '@emotion/react';

import {StrictPropsWithChildren} from '@type/strictPropsWithChildren';

import {useTheme} from '../../index';
Expand All @@ -9,9 +7,13 @@ import {FlexProps} from './Flex.type';
import {flexStyle} from './Flex.style';

// TODO: (@weadie) 지정된 프롭 말고 다른 프롭도 가져올 수 있게 하자.
function Flex({children, ...props}: StrictPropsWithChildren<FlexProps>) {
function Flex({children, otherStyle, ...props}: StrictPropsWithChildren<FlexProps>) {
const {theme} = useTheme();
return <div css={flexStyle({theme, ...props})}>{children}</div>;
return (
<div css={flexStyle({theme, ...props})} style={otherStyle}>
{children}
</div>
);
}

export default Flex;
2 changes: 2 additions & 0 deletions client/src/components/Design/components/Flex/Flex.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ export interface FlexProps {
backgroundColor?: FlexBackgroundColor;
theme?: Theme;
minHeight?: string;

otherStyle?: React.CSSProperties;
}
Loading

0 comments on commit 396ff78

Please sign in to comment.