Skip to content

Commit 16c7c92

Browse files
authored
Merge pull request #86 from DevKor-github/feat/#84/chat
[#84] feat: chat, chatroom 뷰 만들기
2 parents e3da2df + ad60e6f commit 16c7c92

30 files changed

Lines changed: 801 additions & 17 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"dependencies": {
1616
"@tanstack/react-query": "^5.79.0",
1717
"axios": "^1.9.0",
18+
"date-fns": "^4.1.0",
1819
"mingcute_icon": "^2.9.6",
1920
"react": "^19.1.0",
2021
"react-dom": "^19.1.0",

panda.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const PANDA_CSS_CONSTANTS = {
55
HOME_HEADER_HEIGHT: { value: '52px' },
66
DETAIL_PAGE_NAVIGATOR_HEIGHT: { value: '6.75rem' },
77
MAX_WIDTH: { value: '700px' },
8+
CHAT_ROOM_FOOTER_HEIGHT: { value: '102px' },
9+
CHAT_MAX_WIDTH: { value: '270px' },
810
};
911

1012
export default defineConfig({
@@ -35,6 +37,7 @@ export default defineConfig({
3537
'100': { value: 'rgba(255, 255, 255, 1)' },
3638
'80': { value: 'rgba(255, 255, 255, 0.80)' },
3739
'54': { value: 'rgba(255, 255, 255, 0.54)' },
40+
'20': { value: 'rgba(255, 255, 255, 0.20)' },
3841
},
3942
lineHeights: {
4043
normal: { value: '1.2' },

src/common/components/Button/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import type { HTMLAttributes, PropsWithChildren } from 'react';
22
import * as s from './style.css';
33

44
interface Props extends PropsWithChildren {
5-
color?: 'gray' | 'main' | 'softgray' | 'deemedgray';
5+
mode?: 'main' | 'default' | 'back' | 'disabled';
66
onClick?: () => void;
77
style?: HTMLAttributes<HTMLDivElement>['style'];
88
}
9-
const Btn = ({ children, color = 'softgray', onClick, style }: Props) => {
9+
const Btn = ({ children, mode = 'default', onClick, style }: Props) => {
1010
return (
11-
<div className={s.Button({ color })} style={style} onClick={onClick}>
11+
<div className={s.Button({ mode })} style={style} onClick={onClick}>
1212
{children}
1313
</div>
1414
);

src/common/components/Button/style.css.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,34 @@ export const Button = cva({
99
alignItems: 'center',
1010
padding: '0.625rem',
1111
borderRadius: '0.375rem',
12-
// width: '100%',
1312
height: '3.125rem',
1413
fontFamily: 'Pretendard',
1514
fontSize: '1.25rem',
1615
fontStyle: 'normal',
1716
fontWeight: '500',
1817
lineHeight: 'normal',
1918
letterSpacing: '-0.05rem',
20-
transition: 'background 0.3s ease-in-out',
19+
transition: 'background 0.3s ease-in-out, color 0.3s ease-in-out',
2120
cursor: 'pointer',
2221
},
2322
variants: {
24-
// TODO: 언제 쓰이는 건지 이름 바꾸기
25-
color: {
23+
mode: {
2624
main: {
2725
backgroundColor: 'main',
2826
color: '100',
2927
},
30-
gray: {
31-
backgroundColor: 'systemGray5',
32-
color: '100',
33-
},
34-
softgray: {
28+
default: {
3529
backgroundColor: 'systemGray5',
3630
color: '80',
3731
},
38-
deemedgray: {
32+
back: {
3933
backgroundColor: 'systemGray5',
4034
color: '54',
4135
},
36+
disabled: {
37+
backgroundColor: 'systemGray5',
38+
color: '20',
39+
},
4240
},
4341
},
4442
});

src/common/utils/parseDate.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ko } from 'date-fns/locale/ko';
2+
import { format } from 'date-fns';
3+
4+
export const parseDate = (raw: string) => {
5+
const date = new Date(raw);
6+
return format(date, 'yyyy년 MM월 dd일 eeee', { locale: ko });
7+
};
8+
9+
export const parseTime = (raw: string) => {
10+
const date = new Date(raw);
11+
return format(date, 'a h:mm', { locale: ko });
12+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import VerifyIcon from '@/libs/assets/VerifyIcon';
2+
import * as s from './style.css';
3+
import { Link } from 'react-router';
4+
5+
// TODO: 나중에 수정하기~~~
6+
interface Props {
7+
nickName: string;
8+
message: string;
9+
count: number;
10+
chatRoomId: number;
11+
isVerified: boolean;
12+
}
13+
14+
const ChatList = ({ nickName, message, count, chatRoomId, isVerified }: Props) => {
15+
return (
16+
<Link className={s.List} to={`/chatroom/${chatRoomId}`}>
17+
<div className={s.ProfileCircle} />
18+
<div className={s.Contents}>
19+
<div className={s.UserInfo}>
20+
<h1>{nickName}</h1>
21+
{isVerified && (
22+
<span className={s.VerifiedTag}>
23+
<p>학교인증</p>
24+
<VerifyIcon />
25+
</span>
26+
)}
27+
</div>
28+
<div className={s.Message}>
29+
<p>{message}</p>
30+
<div className={s.Count}>{count}</div>
31+
</div>
32+
</div>
33+
</Link>
34+
);
35+
};
36+
37+
export default ChatList;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { css } from '@styled-system/css';
2+
3+
export const List = css({
4+
display: 'flex',
5+
justifyContent: 'space-between',
6+
gap: '0.87rem',
7+
});
8+
9+
export const ProfileCircle = css({
10+
width: '2.625rem',
11+
height: '2.625rem',
12+
borderRadius: 'full',
13+
backgroundColor: 'systemGray',
14+
});
15+
16+
export const Contents = css({
17+
display: 'flex',
18+
flex: 1,
19+
flexDir: 'column',
20+
gap: '0.25rem',
21+
justifyContent: 'center',
22+
alignItems: 'flex-start',
23+
fontFamily: 'Pretendard',
24+
fontStyle: 'normal',
25+
fontSize: '1rem',
26+
lineHeight: 'normal',
27+
letterSpacing: '-0.04rem',
28+
'& h1': {
29+
color: '100',
30+
fontWeight: '500',
31+
},
32+
'& p': {
33+
color: '80',
34+
fontWeight: '400',
35+
},
36+
});
37+
38+
export const VerifiedTag = css({
39+
padding: '0.1rem 0.275rem',
40+
bgColor: 'main-26',
41+
rounded: '3.32px',
42+
fontSize: '0.625rem',
43+
fontWeight: 400,
44+
lineHeight: 1.4,
45+
letterSpacing: '-0.025rem',
46+
color: 'main',
47+
display: 'flex',
48+
alignItems: 'center',
49+
gap: '0.22rem',
50+
'& > p': {
51+
color: '100',
52+
},
53+
});
54+
55+
export const UserInfo = css({
56+
display: 'flex',
57+
alignItems: 'center',
58+
gap: '0.375rem',
59+
whiteSpace: 'nowrap',
60+
61+
'& h1': {
62+
whiteSpace: 'nowrap',
63+
overflow: 'hidden',
64+
textOverflow: 'ellipsis',
65+
maxWidth: '6rem',
66+
},
67+
});
68+
69+
export const Message = css({
70+
display: 'flex',
71+
w: '100%',
72+
justifyContent: 'space-between',
73+
alignItems: 'center',
74+
75+
'& p': {
76+
whiteSpace: 'nowrap',
77+
overflow: 'hidden',
78+
textOverflow: 'ellipsis',
79+
maxWidth: '15rem',
80+
},
81+
});
82+
83+
export const Count = css({
84+
display: 'flex',
85+
w: '1.125rem',
86+
h: '1.125rem',
87+
padding: '0.0625rem 0.375rem',
88+
justifyContent: 'center',
89+
alignItems: 'center',
90+
gap: '0.625rem',
91+
color: 'main',
92+
backgroundColor: 'main-26',
93+
borderRadius: 'full',
94+
textAlign: 'center',
95+
fontFamily: 'Pretendard',
96+
fontSize: '0.75rem',
97+
fontStyle: 'normal',
98+
fontWeight: '400',
99+
lineHeight: '1.4',
100+
letterSpacing: '-0.03rem',
101+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { parseDate, parseTime } from '@/common/utils/parseDate';
2+
import MyChat from '../../MyChat';
3+
import OtherChat from '../../OtherChat';
4+
import * as s from './style.css';
5+
import PickChat from '../../PickChat';
6+
import { useEffect, useRef } from 'react';
7+
import React from 'react';
8+
9+
// [ TODO ]
10+
// 스크롤 좀 올렸을 때 맨 아래로 가는 버튼도 만들어야 하려나
11+
// 키보드 올리면 화면 밀려 올라가게
12+
// api 붙이기~~~ camera, send 아이콘 동작 붙이기~~~
13+
14+
interface Chat {
15+
isMine?: boolean;
16+
message?: string;
17+
isPick?: boolean;
18+
date: string;
19+
}
20+
21+
const dummyData: Chat[] = [
22+
{ isMine: true, message: '응 어쩔', date: '2025-07-09T15:56:31.591185' },
23+
{ isMine: true, message: '배고파', date: '2025-07-09T15:56:31.591185' },
24+
{ isMine: true, message: '응', date: '2025-07-09T15:57:31.591185' },
25+
{ isMine: false, message: '으아아아아ㅏ 노페인 노개인 음악업슨 ㄴ세상', date: '2025-07-09T15:57:31.591185' },
26+
{ isMine: false, message: '요즘 매미가 비가 와도 안 울어', date: '2025-07-09T15:58:31.591185' },
27+
{ isMine: false, message: '어떤 데이터가 올까...?', date: '2025-07-09T15:58:31.591185' },
28+
{ isMine: true, message: 'isMine도 보내주나? 아님 내가 체크해야 하나?', date: '2025-07-09T15:59:31.591185' },
29+
{ isMine: true, message: '스크롤 체크를 해보아요', date: '2025-07-09T15:59:31.591185' },
30+
{ isMine: true, message: '그룹핑이 바로 전 거랑 내가 직접 비교하는 수밖에 없나', date: '2025-07-09T15:59:31.591185' },
31+
{ isMine: false, message: '나 내일 권예진이랑 초밥 먹는당', date: '2025-07-09T15:59:31.591185' },
32+
{ isMine: false, message: '부럽지 ㅋㅋ', date: '2025-07-09T16:00:31.591185' },
33+
{ isMine: true, message: '너무 부럽다.', date: '2025-07-09T16:00:31.591185' },
34+
{ isMine: true, message: '집 가야지 하하하 예재무 공부해야지', date: '2025-07-09T16:01:31.591185' },
35+
{ isPick: true, isMine: true, date: '2025-07-09T16:07:31.591185' },
36+
{ isPick: true, isMine: false, date: '2025-07-09T16:07:31.591185' },
37+
{ isMine: true, message: '공부는 안 하고 지금 왜 개발만', date: '2025-07-18T16:01:31.591185' },
38+
{ isMine: true, message: 'ㅠㅠㅠ', date: '2025-07-18T16:02:31.591185' },
39+
{ isMine: false, message: 'ㅠㅠㅠ', date: '2025-07-18T16:02:31.591185' },
40+
];
41+
42+
export const ChatRoomContent = () => {
43+
const ref = useRef<HTMLDivElement | null>(null);
44+
45+
useEffect(() => {
46+
ref.current?.scrollIntoView({ behavior: 'auto' });
47+
}, []);
48+
49+
return (
50+
<div>
51+
<div className={s.Wrapper}>
52+
{dummyData.map((chat, index) => {
53+
const time = parseTime(chat.date);
54+
const date = parseDate(chat.date);
55+
56+
const prevDate = index > 0 ? parseDate(dummyData[index - 1].date) : null;
57+
const prevIsMine = index > 0 ? dummyData[index - 1].isMine : null;
58+
59+
const isNewDate = date !== prevDate;
60+
const isNewIsMine = chat.isMine !== prevIsMine;
61+
const isFirst = index === 0;
62+
63+
const marginTop = isNewIsMine ? '2.25rem' : '0.75rem';
64+
65+
return (
66+
// TODO: <React.Fragment key={`${chat.date}-${chat.message || 'no-message'}`}>로 변경 (key를 index 말고 고유할 수 있는 값으로,,,)
67+
<React.Fragment key={index}>
68+
{isNewDate && <div className={s.Date({ isFirst: isFirst })}>{date}</div>}
69+
70+
{chat.isPick ? (
71+
<PickChat marginTop={marginTop} isMine={chat.isMine} />
72+
) : chat.isMine ? (
73+
<MyChat marginTop={marginTop} time={time}>
74+
{chat.message}
75+
</MyChat>
76+
) : (
77+
<OtherChat marginTop={marginTop} time={time}>
78+
{chat.message}
79+
</OtherChat>
80+
)}
81+
</React.Fragment>
82+
);
83+
})}
84+
</div>
85+
<div ref={ref} />
86+
</div>
87+
);
88+
};
89+
90+
export default ChatRoomContent;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { css, cva } from '@styled-system/css';
2+
3+
export const Wrapper = css({
4+
display: 'flex',
5+
flexDirection: 'column',
6+
});
7+
8+
export const Date = cva({
9+
base: {
10+
color: '54',
11+
textAlign: 'center',
12+
fontSize: '0.625rem',
13+
fontFamily: 'Pretendard',
14+
fontStyle: 'normal',
15+
fontWeight: '400',
16+
lineHeight: 'normal',
17+
letterSpacing: '-0.025rem',
18+
},
19+
variants: {
20+
isFirst: {
21+
true: {
22+
marginTop: '0',
23+
},
24+
false: {
25+
marginTop: '2.25rem',
26+
},
27+
},
28+
},
29+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import InputField from '../../InputField';
2+
import * as s from './style.css';
3+
4+
export const ChatRoomFooter = () => {
5+
return (
6+
<footer className={s.Wrapper}>
7+
<div className={s.Container}>
8+
<div className={`${'mgc_camera_2_fill'} ${s.Icon}`} />
9+
<InputField />
10+
</div>
11+
</footer>
12+
);
13+
};
14+
15+
export default ChatRoomFooter;

0 commit comments

Comments
 (0)