Skip to content

Commit

Permalink
[feat] 토론 피드 상세 모달 읽기 전용 UI 구현 (#27)
Browse files Browse the repository at this point in the history
* [feat] added FeedWrapper Component
- FeedWrapper depends on whether there is pathname value "p" or not
- If pathname "p" value of browser navigation is existed, FeedWrapper render children props

* [fix] fixed commit error
- The folder name included "@" caused error when committing
- The folder name to start "@" means slot router

* [feat] added compactTimeFormatter util for feed created time

* [feat] added FeedWrapper, ReadOnlyCommonFeed, FeedTextContent Component
- FeedWrapper depends on whether or not browser navigation has value of key "f"
- ReadOnlyCommonFeed is only rendering from props information about common feed
- FeedTextContent is rendering feed information (profile, content, username etc.)

* [style] added "flex-wrap: wrap;" style at .hashtag_wrapper in FeedTextContent Component

* [feat] added FeedComment, FeedCommentList
- not included server request logic

* [feat] added FeedLikeBox Component and svg heart icon

* [feat] added LikeButton Component

* [feat] added FeedCommentWriteInput Component and useAutoResizeTextArea hook

* [feat] added Slide Component
- added height, maxHeight props at Modal Component

* [feat] added FeedReplyWriteInput Component
- update useAutoResizeTextArea hook (not use row count, use scrollHeight)

* [feat] added ReadOnlyDebateFeed Component

* [feat] added DebateContent Component

* [feat] completed ReadOnlyDebateFeed Component and Refactoring
  • Loading branch information
seongjin2427 authored Nov 5, 2024
1 parent 5b8eb6a commit 2f3076e
Show file tree
Hide file tree
Showing 23 changed files with 545 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@use '../../../../styles/helpers/index' as *;

.blue {
background-color: rgba(43, 89, 195, 0.3);
}

.red {
background-color: rgba(211, 101, 130, 0.3);
}

.container {
flex: 1;
border-right: 1px solid $COLOR_GRAY_2;

&:last-of-type {
border-right: none;
}

.debate_container {
position: relative;
height: 200px;
border-bottom: 1px solid $COLOR_GRAY_2;

@include flex-center;

h2 {
font-size: $FONT_SIZE_32;
font-weight: bold;
color: $COLOR_GRAY_2;
}

.vote_count {
position: absolute;
top: 20px;
left: 20px;
gap: 5px;

@include flex-center;
}

.vote {
position: absolute;
width: 100%;
height: 200px;
top: 0;
left: 0;
opacity: 0.5;
border-right: 1px solid $COLOR_GRAY_2;
}
}

.comment_container {
height: 539px;
overflow: scroll;
}
}
84 changes: 84 additions & 0 deletions src/app/(MainLayout)/components/DebateContent/DebateContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import clsx from 'clsx';

import { digitNumberFormatter } from '@/utils/formatter';

import BlueFlag from '@/assets/icons/blue_flag.svg';
import BlueFillFlag from '@/assets/icons/blue_flag_fill.svg';
import RedFlag from '@/assets/icons/red_flag.svg';
import RedFillFlag from '@/assets/icons/red_flag_fill.svg';
import FeedCommentList from '../FeedCommentList';

import styles from './DebateContent.module.scss';
import FeedCommentWriteInput from '../FeedCommentWriteInput';
import { DebateOptionType } from '../../types/feed';

interface DebateContentProps {
postId: number;
index: number;
option: DebateOptionType;
isSelected?: boolean;
ratio: number;
disabled?: boolean;
}

const FLAG_SET = {
BLUE: {
NORMAL: BlueFlag,
FILL: BlueFillFlag,
},
RED: {
NORMAL: RedFlag,
FILL: RedFillFlag,
},
};

const DebateContent = ({
postId,
index,
option,
ratio,
isSelected,
disabled,
}: DebateContentProps) => {
const { optionContent, voteCount } = option;

const currentFlagType = index === 1 ? 'BLUE' : ('RED' as const);
const FLAG = FLAG_SET[currentFlagType];

const currentColor = currentFlagType === 'BLUE' ? styles.blue : styles.red;

return (
<div className={styles.container}>
<div className={clsx(styles.debate_container, currentColor)}>
<h2>{optionContent}</h2>
<div className={styles.vote_count}>
{isSelected ? <FLAG.FILL /> : <FLAG.NORMAL />}
{digitNumberFormatter(voteCount)}
</div>
<div
className={clsx(styles.vote, currentColor)}
style={{ width: `${ratio}%` }}
/>
</div>
<div className={styles.comment_container}>
<FeedCommentList
feedId={postId.toString()}
feedCommentIds={['1', '2', '3']}
/>
</div>
<div>
<FeedCommentWriteInput
postId={1}
placeholder={
disabled
? '댓글 달기...'
: '다른 의견에 동의하셔서 입력하실 수 없습니다.'
}
disabled={!disabled}
/>
</div>
</div>
);
};

export default DebateContent;
1 change: 1 addition & 0 deletions src/app/(MainLayout)/components/DebateContent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './DebateContent';
4 changes: 3 additions & 1 deletion src/app/(MainLayout)/components/FeedComment/FeedComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type FeedCommentType = {
createdAt: string;
updatedAt: string;
likeCount: number;
isLike: boolean;
childFeedComments: string[];
};

Expand All @@ -31,6 +32,7 @@ const FeedComment = ({ commentData }: FeedCommentProps) => {
createdAt,
updatedAt,
likeCount,
isLike,
childFeedComments,
} = commentData;

Expand All @@ -45,7 +47,7 @@ const FeedComment = ({ commentData }: FeedCommentProps) => {
</div>
<p>{nickname}</p>
</div>
<LikeButton postId={1} />
<LikeButton commentId={+commentId} isLike={isLike} />
</div>
<p className={styles.feed_content}>{feedContent}</p>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const makeMockData = (id: number) => ({
createdAt: '2024-10-25 20:08:22',
updatedAt: '2024-10-28 21:29:22',
likeCount: 1357345 + id,
isLike: false,
childFeedComments: +id < 4 ? [4, 5, 6] : [],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@
padding: 18px 0 18px 18px;
border-top: 1px solid $COLOR_GRAY_2;

&:has(.comment_textarea:disabled) {
& * {
color: $COLOR_GRAY_2;
}

.submit_button {
cursor: default;
}
}

.comment_textarea {
flex: 1;
color: $COLOR_GRAY_2;
height: 20px;
line-height: 1.25rem;
color: $COLOR_GRAY_2;
white-space: pre-wrap;
word-wrap: break-word;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ interface FeedCommentWriteInputProps {
postId: number;
parentId?: number;
placeholder?: string;
disabled?: boolean;
}

const FeedCommentWriteInput = ({
postId,
parentId,
placeholder = '댓글 달기...',
disabled,
}: FeedCommentWriteInputProps) => {
const { textareaContent, setTextareaContent, textareaRef } =
useAutoResizeTextArea({ maxLine: 3, lineHeight: 20 });
Expand All @@ -44,8 +46,13 @@ const FeedCommentWriteInput = ({
onChange={changeHandler}
value={textareaContent}
ref={textareaRef}
disabled={disabled}
/>
<button
className={styles.submit_button}
onClick={submitHandler}
disabled={disabled}
/>
<button className={styles.submit_button} onClick={submitHandler}>
게시
</button>
</div>
Expand Down
10 changes: 8 additions & 2 deletions src/app/(MainLayout)/components/FeedLikeBox/FeedLikeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ interface FeedLikeBoxProps {
postId: number;
likeCount: number;
commentCount: number;
isLike: boolean;
}

const FeedLikeBox = ({ postId, likeCount, commentCount }: FeedLikeBoxProps) => {
const FeedLikeBox = ({
postId,
likeCount,
commentCount,
isLike,
}: FeedLikeBoxProps) => {
return (
<div className={styles.container}>
<div className={styles.item}>
<span>
<LikeButton postId={postId} />
<LikeButton postId={postId} isLike={isLike} />
</span>
<span>{digitNumberFormatter(likeCount)}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
.container {
max-height: 300px;
padding: 20px;
border-bottom: 1px solid $COLOR_GRAY_2;


.profile_wrapper {
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -36,12 +35,24 @@
}

.feed_content {
padding-bottom: 30px;
max-height: 140px;
margin-bottom: 20px;
font-size: $FONT_SIZE_14;
color: $COLOR_GRAY_2;
line-height: 1.25rem;
white-space: pre-line;
word-wrap: break-word;
overflow: auto;

&::-webkit-scrollbar {
scrollbar-width: thin;
background: $COLOR_WHITE;
}

&::-webkit-scrollbar-thumb {
background: $COLOR_PRIMARY_5;
border-radius: 5px;
}
}

.hashtag_wrapper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import styles from './FeedTextContent.module.scss';

interface FeedTextContentProps {
feedData: {
username: string;
userName: string;
profileUrl?: string;
createdAt: string;
updatedAt?: string;
isFollow: boolean;
content: string;
hashtags: string[];
hashTagNames: string[];
};
}

Expand All @@ -22,13 +21,12 @@ const DEFAULT_PROFILE =

const FeedTextContent = ({ feedData }: FeedTextContentProps) => {
const {
username,
userName,
profileUrl = DEFAULT_PROFILE,
isFollow,
createdAt,
updatedAt,
content,
hashtags,
hashTagNames,
} = feedData;
return (
<div className={styles.container}>
Expand All @@ -37,18 +35,16 @@ const FeedTextContent = ({ feedData }: FeedTextContentProps) => {
<div className={styles.profile_image}>
<Image width="32" height="32" src={profileUrl} alt="user_profile" />
</div>
<p className={styles.username}>{username}</p>
<p className={styles.feed_time}>
{compactTimeFormatter(updatedAt ?? createdAt)}
</p>
<p className={styles.username}>{userName}</p>
<p className={styles.feed_time}>{compactTimeFormatter(createdAt)}</p>
</div>
<div className={styles.profile_follow_btn}>
<ProfileFollowButton size="small" isFollow={isFollow} />
</div>
</div>
<p className={styles.feed_content}>{content}</p>
<div className={styles.hashtag_wrapper}>
{hashtags.map((hashtag) => (
{hashTagNames.map((hashtag) => (
<p key={hashtag}>#{hashtag}</p>
))}
</div>
Expand Down
31 changes: 14 additions & 17 deletions src/app/(MainLayout)/components/LikeButton/LikeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import ToggleWrapper from '@/components/common/ToggleWrapper';
'use client';

import { useState } from 'react';

import HeartFillIcon from '@/assets/icons/heart-fill.svg';
import HeartIcon from '@/assets/icons/heart.svg';
import styles from './LikeButton.module.scss';

interface LikeButtonProps {
postId: number;
postId?: number;
commentId?: number;
isLike: boolean;
}

const LikeButton = ({ postId }: LikeButtonProps) => {
const LikeButton = ({ postId, commentId, isLike }: LikeButtonProps) => {
const [isToggle, setIsToggle] = useState<boolean>(isLike);

const clickHandler = () => {
console.log(postId);
console.log(postId, commentId);
setIsToggle(!isToggle);
};

return (
<ToggleWrapper>
{({ isToggle, toggleHandler }) => (
<button
className={styles.heart_icon}
onClick={() => {
toggleHandler();
clickHandler();
}}
>
{isToggle ? <HeartFillIcon /> : <HeartIcon />}
</button>
)}
</ToggleWrapper>
<button className={styles.heart_icon} onClick={clickHandler}>
{isToggle ? <HeartFillIcon /> : <HeartIcon />}
</button>
);
};

Expand Down
Loading

0 comments on commit 2f3076e

Please sign in to comment.