diff --git a/src/components/BottomSheet/OptionsBottomSheet/ReportBottomSheetMenu/index.tsx b/src/components/BottomSheet/OptionsBottomSheet/ReportBottomSheetMenu/index.tsx index 6f5f05ec..71e59462 100644 --- a/src/components/BottomSheet/OptionsBottomSheet/ReportBottomSheetMenu/index.tsx +++ b/src/components/BottomSheet/OptionsBottomSheet/ReportBottomSheetMenu/index.tsx @@ -1,29 +1,15 @@ import React, { useState, useRef, useCallback, useEffect } from 'react'; -import BottomButton from '../../../BottomButton/index.tsx'; -import BottomSheetMenu from '../../../BottomSheetMenu/index.tsx'; -import { SheetItemDto } from '../../../BottomSheetMenu/dto.ts'; -import { ReportBottomSheetMenuProps } from './dto.ts'; -import { InputLayout, ReportBottomSheetMenuWrappar } from './styles.tsx'; +import BottomButton from '@components/BottomButton'; +import BottomSheetMenu from '@components/BottomSheetMenu'; +import { SheetItemDto } from '@components/BottomSheetMenu/dto'; +import type { ReportBottomSheetMenuProps } from './dto'; +import { InputLayout, ReportBottomSheetMenuWrappar } from './styles'; const ReportBottomSheetMenu: React.FC = React.memo( ({ onCloseReportSheet, onOpenStatusModal, sendReport, isUserReport }) => { + const [isVisibleTextarea, setIsTextareaVisible] = useState(false); const [inputValue, setInputValue] = useState(''); const textareaRef = useRef(null); - const [isVisibleTextarea, setIsTextareaVisible] = useState(false); - - useEffect(() => { - if (textareaRef.current) { - textareaRef.current.focus(); // 마운트 또는 업데이트 시 textarea에 포커스 유지 - } - }, []); - - const handleInputChange = useCallback((e: React.ChangeEvent) => { - setInputValue(e.target.value); - }, []); - - const handleSubmit = useCallback(() => { - sendReport(inputValue); - }, [onCloseReportSheet, onOpenStatusModal]); // 유저 신고 사유 목록 const userReportItems: SheetItemDto[] = [ @@ -111,6 +97,20 @@ const ReportBottomSheetMenu: React.FC = React.memo( }, ]; + const handleInputChange = useCallback((e: React.ChangeEvent) => { + setInputValue(e.target.value); + }, []); + + const handleSubmit = useCallback(() => { + sendReport(inputValue); + }, [onCloseReportSheet, onOpenStatusModal]); + + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.focus(); // 마운트 또는 업데이트 시 textarea에 포커스 유지 + } + }, []); + return ( diff --git a/src/components/BottomSheet/OptionsBottomSheet/dto.ts b/src/components/BottomSheet/OptionsBottomSheet/dto.ts index 047a8e3f..67fe62fd 100644 --- a/src/components/BottomSheet/OptionsBottomSheet/dto.ts +++ b/src/components/BottomSheet/OptionsBottomSheet/dto.ts @@ -9,10 +9,3 @@ export interface OptionsBottomSheetProps { isBottomSheetOpen: boolean; onClose: () => void; } - -export interface BlockInfoDto { - userId: number; - friendId: number; - friendName: string; - action: 'toggle'; -} diff --git a/src/components/BottomSheet/OptionsBottomSheet/index.tsx b/src/components/BottomSheet/OptionsBottomSheet/index.tsx index 5241b99a..454ff160 100644 --- a/src/components/BottomSheet/OptionsBottomSheet/index.tsx +++ b/src/components/BottomSheet/OptionsBottomSheet/index.tsx @@ -1,23 +1,24 @@ import { useState } from 'react'; -import BottomSheet from '..'; -import BottomSheetMenu from '../../BottomSheetMenu'; +import BottomSheet from '../index'; +import BottomSheetMenu from '@components/BottomSheetMenu'; import ReportBottomSheetMenu from './ReportBottomSheetMenu'; -import Modal from '../../Modal'; +import Modal from '@components/Modal'; -import { OptionsBottomSheetProps } from './dto'; -import { BottomSheetProps } from '../dto'; -import { BottomSheetMenuProps } from '../../BottomSheetMenu/dto'; -import { ReportBottomSheetMenuProps } from './ReportBottomSheetMenu/dto'; -import { ModalProps } from '../../Modal/dto'; +import type { OptionsBottomSheetProps } from './dto'; +import type { BottomSheetProps } from '../dto'; +import type { BottomSheetMenuProps } from '@components/BottomSheetMenu/dto'; +import type { ReportBottomSheetMenuProps } from './ReportBottomSheetMenu/dto'; +import type { ModalProps } from '@components/Modal/dto'; -import { SendPostReportRequest } from '../../../apis/post-report/dto'; -import { PostUserReportRequest } from '../../../apis/user-report/dto'; -import { PostUserBlockRequest } from '../../../apis/user-block/dto'; +import type { SendPostReportRequest } from '@apis/post-report/dto'; +import type { PostUserReportRequest } from '@apis/user-report/dto'; +import type { PostUserBlockRequest } from '@apis/user-block/dto'; -import { StyledText } from '../../Text/StyledText'; -import { handleError } from '../../../apis/util/handleError'; -import blockIcon from '../../../assets/default/block.svg'; -import reportIcon from '../../../assets/default/report.svg'; +import { StyledText } from '@components/Text/StyledText'; +import { handleError } from '@apis/util/handleError'; +import blockIcon from '@assets/default/block.svg'; +import reportIcon from '@assets/default/report.svg'; +import closeIcon from '@assets/default/modal-close-white.svg'; import { ReportBottomSheetLayout, @@ -25,13 +26,14 @@ import { ReportModalWrapper, ReportModalContainer, ReportModalHeader, - XButton, + CloseButton, ReportModalBox, } from './styles'; -import theme from '../../../styles/theme'; -import { postUserBlockApi } from '../../../apis/user-block'; -import { postUserReportApi } from '../../../apis/user-report'; -import { sendPostReportApi } from '../../../apis/post-report'; +import theme from '@styles/theme'; +import { postUserBlockApi } from '@apis/user-block'; +import { postUserReportApi } from '@apis/user-report'; +import { sendPostReportApi } from '@apis/post-report'; +import { getCurrentUserId } from '@utils/getCurrentUserId'; const OptionsBottomSheet: React.FC = ({ domain, @@ -44,18 +46,25 @@ const OptionsBottomSheet: React.FC = ({ const [isReportBottomSheetOpen, setIsReportBottomSheetOpen] = useState(false); const [isStatusModalOpen, setIsStatusModalOpen] = useState(false); const [modalContent, setModalContent] = useState('알 수 없는 오류입니다.\n관리자에게 문의해 주세요.'); - const storageValue = localStorage.getItem('my_id'); - const userId = Number(storageValue); + const currentUserId = getCurrentUserId(); - const sendBlock = async () => { + const handleBackgroundClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (e.target === e.currentTarget) { + setIsReportBottomSheetOpen(false); + } + }; + + // 유저 차단 api + const postUserBlock = async () => { try { - const blockRequest: PostUserBlockRequest = { - fromUserId: userId, + const request: PostUserBlockRequest = { + fromUserId: currentUserId, toUserId: targetId.userId || -1, action: 'block', }; - const response = await postUserBlockApi(blockRequest); - + const response = await postUserBlockApi(request); + if (response.isSuccess) { setModalContent('정상적으로 처리되었습니다.'); } @@ -68,19 +77,20 @@ const OptionsBottomSheet: React.FC = ({ } }; + // 유저 또는 게시글 신고 api const sendReport = async (reason: string) => { try { let reportData: PostUserReportRequest | SendPostReportRequest; if (domain === 'user') { reportData = { - fromUserId: userId, + fromUserId: currentUserId, toUserId: targetId.userId || -1, reason: reason, }; } else { reportData = { - requesterId: userId, + requesterId: currentUserId, postId: targetId.postId || -1, reason: reason, }; @@ -91,7 +101,6 @@ const OptionsBottomSheet: React.FC = ({ ? await postUserReportApi(reportData as PostUserReportRequest) : await sendPostReportApi(reportData as SendPostReportRequest); - // response.isSuccess if (response.isSuccess) { setModalContent('정상적으로 처리되었습니다.'); } @@ -126,7 +135,7 @@ const OptionsBottomSheet: React.FC = ({ ], }; - // 더보기(kebab) 메뉴 바텀시트 + // 더보기 바텀시트 const optionsBottomSheetProps: BottomSheetProps = { isOpenBottomSheet: isBottomSheetOpen, Component: BottomSheetMenu, @@ -143,7 +152,7 @@ const OptionsBottomSheet: React.FC = ({ content: `${targetNickname || '알수없음'} 님을\n정말로 차단하시겠어요?`, button: { content: '차단하기', - onClick: sendBlock, + onClick: postUserBlock, }, }; @@ -177,13 +186,6 @@ const OptionsBottomSheet: React.FC = ({ }, }; - const handleBackgroundClick = (e: React.MouseEvent) => { - e.stopPropagation(); - if (e.target === e.currentTarget) { - setIsReportBottomSheetOpen(false); - } - }; - return ( <> @@ -201,11 +203,13 @@ const OptionsBottomSheet: React.FC = ({ 신고 사유 선택 - { setIsReportBottomSheetOpen(false); }} - /> + > + 닫기 + diff --git a/src/components/BottomSheet/OptionsBottomSheet/styles.tsx b/src/components/BottomSheet/OptionsBottomSheet/styles.tsx index 2b6226d0..ade74d3b 100644 --- a/src/components/BottomSheet/OptionsBottomSheet/styles.tsx +++ b/src/components/BottomSheet/OptionsBottomSheet/styles.tsx @@ -1,5 +1,4 @@ import styled from 'styled-components'; -import CloseIcon from '../../../assets/default/modal-close-white.svg'; export const ReportBottomSheetLayout = styled.div` ${({ theme }) => theme.visibleOnMobileTablet}; @@ -54,13 +53,11 @@ export const ReportModalBox = styled.section` width: 100%; `; -export const XButton = styled.button` +export const CloseButton = styled.button` width: 1.875rem; height: 1.875rem; - margin: auto 0 auto auto; - background-image: url(${CloseIcon}); - background-repeat: no-repeat; - background-size: 1.875rem; - background-position: center; + display: flex; + justify-content: center; + align-items: center; opacity: 0.5; `; diff --git a/src/components/BottomSheet/dto.ts b/src/components/BottomSheet/dto.ts index 69c83235..59aea91a 100644 --- a/src/components/BottomSheet/dto.ts +++ b/src/components/BottomSheet/dto.ts @@ -4,5 +4,4 @@ export interface BottomSheetProps { Component: React.ComponentType; // BottomSheet 내부에 전달할 컴포넌트 componentProps?: T; // props가 있는 경우 객체 형태로 전달 onCloseBottomSheet: () => void; // BottomSheet을 닫는 함수 - initialTab?: 'likes' | 'comments'; // 추가: initialTab 속성 } diff --git a/src/components/BottomSheet/index.tsx b/src/components/BottomSheet/index.tsx index e0f813cf..066bd221 100644 --- a/src/components/BottomSheet/index.tsx +++ b/src/components/BottomSheet/index.tsx @@ -1,14 +1,15 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { BottomSheetProps } from './dto'; +import type { BottomSheetProps } from './dto'; import { BottomSheetWrapper, BottomSheetLayout, Handler, SideBarLayout, - XButton, + CloseButton, SideBarTopBar, ComponentBox, } from './styles'; +import closeIcon from '@assets/default/x.svg'; const BottomSheet: React.FC = ({ isOpenBottomSheet, @@ -17,43 +18,44 @@ const BottomSheet: React.FC = ({ componentProps, onCloseBottomSheet, }) => { - const startY = useRef(null); - const [isSideBarOpen, setIsSideBarOpen] = useState(false); const [isInitialRender, setisInitialRender] = useState(true); const [isRendered, setIsRendered] = useState(false); + const [isDragging, setIsDragging] = useState(false); + const [isSideBarOpen, setIsSideBarOpen] = useState(false); const [currentTranslateY, setCurrentTranslateY] = useState(0); - const [isDragging, setIsDragging] = useState(false); + const startY = useRef(null); - useEffect(() => { - if (isOpenBottomSheet) { - setIsSideBarOpen(true); - setisInitialRender(false); - setIsRendered(true); - setCurrentTranslateY(0); - } else { - setIsSideBarOpen(false); - setIsRendered(false); + // BottomSheet 외부를 클릭할 경우 BottomSheet 닫음 + const handleBackgroundClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (!isDragging && e.target === e.currentTarget) { + onCloseBottomSheet(); } - }, [isOpenBottomSheet]); + }; + + const handleCloseButtonClick = () => { + onCloseBottomSheet(); + }; - // 드래그 시작 시점의 y값 - const onPointerDown = useCallback((event: React.PointerEvent | React.TouchEvent) => { - if ('touches' in event) { - startY.current = event.touches[0].clientY; + // 드래그 시작 + const handlePointerDown = useCallback((e: React.PointerEvent | React.TouchEvent) => { + if ('touches' in e) { + startY.current = e.touches[0].clientY; } else { - startY.current = event.clientY; + startY.current = e.clientY; } setIsDragging(true); }, []); - const onPointerMove = useCallback( - (event: PointerEvent | TouchEvent) => { + // 드래그 중 + const handlePointerMove = useCallback( + (e: PointerEvent | TouchEvent) => { if (startY.current !== null) { let currentY; - if ('touches' in event) { - currentY = event.touches[0].clientY; + if ('touches' in e) { + currentY = e.touches[0].clientY; } else { - currentY = event.clientY; + currentY = e.clientY; } const deltaY = currentY - startY.current; if (deltaY > 0) { @@ -64,15 +66,15 @@ const BottomSheet: React.FC = ({ [startY], ); - // 드래그 종료 시점의 y값 - const onPointerUp = useCallback( - (event: PointerEvent | TouchEvent) => { + // 드래그 종료 + const handlePonterUp = useCallback( + (e: PointerEvent | TouchEvent) => { if (startY.current !== null) { let endY; - if ('changedTouches' in event) { - endY = event.changedTouches[0].clientY; + if ('changedTouches' in e) { + endY = e.changedTouches[0].clientY; } else { - endY = event.clientY; + endY = e.clientY; } // 두 값의 변화량이 50px보다 크면 아래로 드래그한 것으로 간주하여 바텀시트 닫음 @@ -85,7 +87,7 @@ const BottomSheet: React.FC = ({ } startY.current = null; // 초기화 - // PointerUp 직후 onClick 동작 방지 + // pointerUp 직후 onClick 동작 방지 setTimeout(() => { setIsDragging(false); }, 100); @@ -94,48 +96,52 @@ const BottomSheet: React.FC = ({ [startY, onCloseBottomSheet], ); + // 초기 렌더링 시 window에 이벤트 리스너 등록 + // 이벤트 리스너는 on[event] 형식으로 작성했습니다 useEffect(() => { // 데스크탑 - const handlePointerUp = (event: PointerEvent) => onPointerUp(event); - const handlePointerMove = (event: PointerEvent) => onPointerMove(event); + const onPointerUp = (e: PointerEvent) => handlePonterUp(e); + const onPointerMove = (e: PointerEvent) => handlePointerMove(e); // 모바일 & 태블릿 - const handleTouchEnd = (event: TouchEvent) => onPointerUp(event); - const handleTouchMove = (event: TouchEvent) => onPointerMove(event); + const onTouchEnd = (e: TouchEvent) => handlePonterUp(e); + const onTouchMove = (e: TouchEvent) => handlePointerMove(e); - window.addEventListener('pointermove', handlePointerMove); - window.addEventListener('touchmove', handleTouchMove); - window.addEventListener('pointerup', handlePointerUp); - window.addEventListener('touchend', handleTouchEnd); + window.addEventListener('pointermove', onPointerMove); + window.addEventListener('pointerup', onPointerUp); + window.addEventListener('touchmove', onTouchMove); + window.addEventListener('touchend', onTouchEnd); + // 언마운트 시 이벤트리스너 제거 return () => { - window.removeEventListener('pointermove', handlePointerMove); - window.removeEventListener('touchmove', handleTouchMove); - window.removeEventListener('pointerup', handlePointerUp); - window.removeEventListener('touchend', handleTouchEnd); + window.removeEventListener('pointermove', onPointerMove); + window.removeEventListener('pointerup', onPointerUp); + window.removeEventListener('touchmove', onTouchMove); + window.removeEventListener('touchend', onTouchEnd); }; - }, [onPointerMove, onPointerUp]); - - // 초기 렌더링 시 바텀시트 안 보이게 설정 - if (isInitialRender && !isOpenBottomSheet) return null; + }, [handlePointerMove, handlePonterUp]); - // BottomSheet 외부를 클릭할 경우 BottomSheet 닫음 - const handleBackgroundClick = (e: React.MouseEvent) => { - e.stopPropagation(); - if (!isDragging && e.target === e.currentTarget) { - onCloseBottomSheet(); + // 바텀시트 초기 렌더링 버그를 해결하기 위한 상태값 업데이트 + useEffect(() => { + if (isOpenBottomSheet) { + setisInitialRender(false); + setIsRendered(true); + setIsSideBarOpen(true); + setCurrentTranslateY(0); + } else { + setIsRendered(false); + setIsSideBarOpen(false); } - }; + }, [isOpenBottomSheet]); - const handleButtonClick = () => { - onCloseBottomSheet(); - }; + // 부모 요소 초기 렌더링 시 바텀시트 안 보이게 설정 + if (isInitialRender && !isOpenBottomSheet) return null; return ( {/* 모바일 & 태블릿 UI */} @@ -145,7 +151,9 @@ const BottomSheet: React.FC = ({ {/* 데스크탑 UI */} - + + 닫기 + diff --git a/src/components/BottomSheet/styles.tsx b/src/components/BottomSheet/styles.tsx index 3e37a96a..0a161cbd 100644 --- a/src/components/BottomSheet/styles.tsx +++ b/src/components/BottomSheet/styles.tsx @@ -1,6 +1,5 @@ import React from 'react'; import styled from 'styled-components'; -import XIcon from '../../assets/default/x.svg'; export const BottomSheetWrapper = styled.div<{ $isBottomSheetOpen: boolean }>` position: fixed; @@ -65,18 +64,17 @@ export const SideBarLayout = styled.div<{ $isSideBarOpen: boolean }>` export const SideBarTopBar = styled.header` display: flex; width: 100%; - padding: 0.5rem 1rem; + padding: 1rem 1.25rem 1rem 1rem; margin-top: 0; + justify-content: flex-end; `; -export const XButton = styled.button` - width: 2.25rem; - height: 2.25rem; - margin: auto 0 auto auto; - background-image: url(${XIcon}); - background-repeat: no-repeat; - background-size: 17px; - background-position: center; +export const CloseButton = styled.button` + width: 1.875rem; + height: 1.875rem; + display: flex; + justify-content: center; + align-items: center; `; export const ComponentBox = styled.section` diff --git a/src/components/BottomSheetMenu/dto.ts b/src/components/BottomSheetMenu/dto.ts index 4d2a3dd7..ddaa71f5 100644 --- a/src/components/BottomSheetMenu/dto.ts +++ b/src/components/BottomSheetMenu/dto.ts @@ -1,12 +1,11 @@ -// SheetItemWithDivider에서 사용되는 Items 배열 요소 -export interface SheetItemDto { - text: string; - action: () => void; - icon?: string; // svg를 import하여 값으로 사용 -} - //TODO: marginBottom prop 제거 export interface BottomSheetMenuProps { items: SheetItemDto[]; marginBottom?: string; } + +export interface SheetItemDto { + text: string; + action: () => void; + icon?: string; // svg를 import하여 값으로 사용 +} diff --git a/src/components/BottomSheetMenu/index.tsx b/src/components/BottomSheetMenu/index.tsx index 7d47ea39..c5689730 100644 --- a/src/components/BottomSheetMenu/index.tsx +++ b/src/components/BottomSheetMenu/index.tsx @@ -1,6 +1,6 @@ -import { StyledText } from '../Text/StyledText'; -import { BottomSheetMenuLayout, SheetItem, Icon } from './styles'; -import { BottomSheetMenuProps, SheetItemDto } from './dto'; +import { StyledText } from '@components/Text/StyledText'; +import { BottomSheetMenuLayout, SheetItem, IconButton } from './styles'; +import type { BottomSheetMenuProps, SheetItemDto } from './dto'; import React from 'react'; const BottomSheetMenu: React.FC = React.memo(({ items }) => { @@ -12,7 +12,11 @@ const BottomSheetMenu: React.FC = React.memo(({ items }) = {item.text} - {item.icon && } + {item.icon && ( + + {`${item.text} + + )} ))} diff --git a/src/components/BottomSheetMenu/styles.tsx b/src/components/BottomSheetMenu/styles.tsx index 6045d462..a9ed9dca 100644 --- a/src/components/BottomSheetMenu/styles.tsx +++ b/src/components/BottomSheetMenu/styles.tsx @@ -16,7 +16,10 @@ export const SheetItem = styled.li` border-bottom: 1px solid rgb(0, 0, 0, 0.2); `; -export const Icon = styled.img` +export const IconButton = styled.button` width: 1.5rem; height: 1.5rem; + display: flex; + align-items: center; + justify-content: center; `; diff --git a/src/components/Comment/dto.ts b/src/components/CommentBottomSheet/Comment/dto.ts similarity index 100% rename from src/components/Comment/dto.ts rename to src/components/CommentBottomSheet/Comment/dto.ts diff --git a/src/components/Comment/index.tsx b/src/components/CommentBottomSheet/Comment/index.tsx similarity index 63% rename from src/components/Comment/index.tsx rename to src/components/CommentBottomSheet/Comment/index.tsx index 54344f1e..49a6db72 100644 --- a/src/components/Comment/index.tsx +++ b/src/components/CommentBottomSheet/Comment/index.tsx @@ -1,14 +1,14 @@ -import { StyledText } from '../Text/StyledText'; -import { CommentLayout, SendContainer, CommentTextarea, SendImg } from './styles'; -import Send from '../../assets/default/send-comment.svg'; +import { StyledText } from '@components/Text/StyledText'; +import { CommentLayout, SendContainer, CommentTextarea, SendButton } from './styles'; +import send from '@assets/default/send-comment.svg'; import React, { useEffect, useRef, useState } from 'react'; import { CommentProps } from './dto'; -const Comment: React.FC = ({ content, sendComment, isModal }) => { +const Comment: React.FC = ({ content, sendComment, isModal = false }) => { const [comment, setComment] = useState(''); const textareaRef = useRef(null); - // textarea 높이 조정 함수 + // textarea 높이 조정 const adjustTextareaHeight = () => { if (textareaRef.current) { textareaRef.current.style.height = '1.2rem'; // 초기 높이 설정 @@ -16,17 +16,13 @@ const Comment: React.FC = ({ content, sendComment, isModal }) => { } }; - useEffect(() => { - adjustTextareaHeight(); - }, [comment]); // comment가 변경될 때만 높이 재조정 - - const handleChangeComment = (e: React.ChangeEvent) => { + const handleCommentChange = (e: React.ChangeEvent) => { if (e.target.value.length <= 100) { setComment(e.target.value); } }; - const handleKeyDown = (e: React.KeyboardEvent) => { + const handleEnterKeyDown = (e: React.KeyboardEvent) => { if (comment === '') { e.preventDefault(); return; @@ -48,18 +44,25 @@ const Comment: React.FC = ({ content, sendComment, isModal }) => { } }; + // comment가 변경될 때만 높이 재조정 + useEffect(() => { + adjustTextareaHeight(); + }, [comment]); + return ( - {content} + {content} - + + 메시지 전송 아이콘 + ); diff --git a/src/components/Comment/styles.tsx b/src/components/CommentBottomSheet/Comment/styles.tsx similarity index 81% rename from src/components/Comment/styles.tsx rename to src/components/CommentBottomSheet/Comment/styles.tsx index 98fadefe..f0c602bc 100644 --- a/src/components/Comment/styles.tsx +++ b/src/components/CommentBottomSheet/Comment/styles.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; -export const CommentLayout = styled.div<{ $isModal: boolean | undefined }>` +export const CommentLayout = styled.div<{ $isModal: boolean }>` margin: 1.38rem auto 1.25rem auto; width: 100%; display: flex; @@ -34,9 +34,11 @@ export const CommentTextarea = styled.textarea` line-height: 1.2rem; `; -export const SendImg = styled.img` +export const SendButton = styled.button` width: 2.5rem; height: 2.5rem; - cursor: pointer; + display: flex; + justify-content: center; + align-items: center; margin-right: 0.62rem; `; diff --git a/src/components/CommentBottomSheet/dto.ts b/src/components/CommentBottomSheet/dto.ts index 54b25fda..b3f0b1c0 100644 --- a/src/components/CommentBottomSheet/dto.ts +++ b/src/components/CommentBottomSheet/dto.ts @@ -1,4 +1,4 @@ -import { CommentProps } from '../Comment/dto'; +import { CommentProps } from './Comment/dto'; export interface CommentBottomSheetProps { isBottomSheetOpen: boolean; diff --git a/src/components/CommentBottomSheet/index.tsx b/src/components/CommentBottomSheet/index.tsx index 06a870fe..103b9bbf 100644 --- a/src/components/CommentBottomSheet/index.tsx +++ b/src/components/CommentBottomSheet/index.tsx @@ -1,10 +1,11 @@ -import { StyledText } from '../Text/StyledText'; -import theme from '../../styles/theme'; +import { StyledText } from '@components/Text/StyledText'; +import theme from '@styles/theme'; -import BottomSheet from '../BottomSheet'; -import Comment from '../Comment'; -import { BottomSheetProps } from '../BottomSheet/dto'; -import { CommentBottomSheetProps } from './dto'; +import BottomSheet from '@components/BottomSheet'; +import Comment from './Comment/index'; +import type { BottomSheetProps } from '@components/BottomSheet/dto'; +import type { CommentBottomSheetProps } from './dto'; +import closeIcon from '@assets/default/modal-close-white.svg'; import { CommentBottomSheetLayout, @@ -13,7 +14,7 @@ import { CommentModalHeader, CommentModalLayout, CommentModalWrapper, - XButton, + CloseButton, } from './styles'; const CommentBottomSheet: React.FC = ({ @@ -21,13 +22,6 @@ const CommentBottomSheet: React.FC = ({ commentProps, handleCloseBottomSheet, }) => { - const bottomSheetProps: BottomSheetProps = { - isOpenBottomSheet: isBottomSheetOpen, - Component: Comment, - componentProps: commentProps, - onCloseBottomSheet: handleCloseBottomSheet, - }; - const handleBackgroundClick = (e: React.MouseEvent) => { e.stopPropagation(); if (e.target === e.currentTarget) { @@ -35,10 +29,17 @@ const CommentBottomSheet: React.FC = ({ } }; - const handleButtonClick = () => { + const handleCloseButtonClick = () => { handleCloseBottomSheet(); }; + const bottomSheetProps: BottomSheetProps = { + isOpenBottomSheet: isBottomSheetOpen, + Component: Comment, + componentProps: commentProps, + onCloseBottomSheet: handleCloseBottomSheet, + }; + return ( <> {/* 모바일 & 태블릿 UI */} @@ -54,7 +55,9 @@ const CommentBottomSheet: React.FC = ({ 메시지 보내기 - + + 닫기 + diff --git a/src/components/CommentBottomSheet/styles.tsx b/src/components/CommentBottomSheet/styles.tsx index d0cc03fb..05c74196 100644 --- a/src/components/CommentBottomSheet/styles.tsx +++ b/src/components/CommentBottomSheet/styles.tsx @@ -1,5 +1,4 @@ import styled from 'styled-components'; -import CloseIcon from '../../assets/default/modal-close-white.svg'; export const CommentBottomSheetLayout = styled.div` ${({ theme }) => theme.visibleOnMobileTablet}; @@ -54,13 +53,11 @@ export const CommentModalBox = styled.section` width: 100%; `; -export const XButton = styled.button` +export const CloseButton = styled.button` width: 1.875rem; height: 1.875rem; - margin: auto 0 auto auto; - background-image: url(${CloseIcon}); - background-repeat: no-repeat; - background-size: 1.875rem; - background-position: center; + display: flex; + justify-content: center; + align-items: center; opacity: 0.5; `; diff --git a/src/components/Frame/Frame.tsx b/src/components/Frame/Frame.tsx index 3e9d398c..4ca4387a 100644 --- a/src/components/Frame/Frame.tsx +++ b/src/components/Frame/Frame.tsx @@ -1,7 +1,6 @@ import styled from 'styled-components'; -import theme from '../../styles/theme'; +import theme from '@styles/theme'; -// 공통 레이아웃 -> 모두 적용해주세요 export const OODDFrame = styled.div` ${theme.breakPoints}; background-color: ${({ theme }) => theme.colors.white}; diff --git a/src/components/Icons/Alarm.tsx b/src/components/Icons/Alarm.tsx index 5b8d5d3e..52e98d62 100644 --- a/src/components/Icons/Alarm.tsx +++ b/src/components/Icons/Alarm.tsx @@ -1,9 +1,9 @@ import React from 'react'; import type { IconsProps } from './dto'; -const Alarm: React.FC = ({ isFilled = false }) => { +const Alarm: React.FC = ({ isFilled = false, width = '14', height = '18' }) => { return ( - + = ({ isFilled = false }) => { +const Heart: React.FC = ({ isFilled = false, width = '56', height = '56' }) => { return ( - + = ({ color = '', isFilled = false }) => { +const Home: React.FC = ({ color = '', isFilled = false, width = '14', height = '14' }) => { return ( <> {isFilled ? ( // isFilled가 true일 때 원하는 색 사용 (desktopNavBar의 home-fill는 color을 black으로, default는 color을 white로) - + ) : ( // isFilled가 false일 때 원하는 색 사용 (desktopNavBar의 home은은 color을 black으로, default는 color을 white로) - + = ({ isFilled = false, color = '' }) => { +const Like: React.FC = ({ isFilled = false, color = '', width = '16', height = '14' }) => { return ( <> {isFilled ? ( // like-fill.svg (isFilled = true) - + = ({ isFilled = false, color = '' }) => { ) : ( // like.svg - + = ({ color = '', isFilled = false }) => { +const Message: React.FC = ({ color = '', isFilled = false, width = '14', height = '12' }) => { return ( <> {isFilled ? ( // isFilled가 true일 때 원하는 색 사용 (desktopNavBar의 message-fill는 color을 black으로, default는 color을 white로) - + = ({ color = '', isFilled = false }) => { ) : ( // isFilled가 false일 때 원하는 색 사용 (desktopNavBar의 message는 color을 black으로, default는 color을 #8E8E8E로, default의 message-white는 color을 white로) - + = ({ color = '', isFilled = false }) => { +const MyPage: React.FC = ({ color = '', isFilled = false, width = '16', height = '16' }) => { return ( <> {isFilled ? ( // isFilled가 true일 때 원하는 색 사용 (desktopNavBar의 my-page-fill은 color을 black으로, default는 color을 white로) - + = ({ color = '', isFilled = false }) => { ) : ( // isFilled가 false일 때 원하는 색 사용 (desktopNavBar의 my-page는 color을 black으로, default는 color을 #8E8E8E로, default의 my-page-white는 color을 white로) - + ` // 각 점에 대해 딜레이를 적용하여 순차적으로 애니메이션을 시작 animation: ${bounceGroup} 2s ease-in-out infinite; - animation-delay: ${({ $index }) => `${($index % 3) * 0.2}s`}}; + animation-delay: ${({ $index }) => `${($index % 3) * 0.2}s`}; `; diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index b1ecabfd..d62ae8e7 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { ModalWrapper, ModalContainer, CloseButton, ConfirmButton } from './styles'; -import { StyledText } from '../Text/StyledText'; -import { ModalProps } from './dto'; -import XIcon from '../../assets/default/x.svg'; +import { StyledText } from '@components/Text/StyledText'; +import type { ModalProps } from './dto'; +import closeIcon from '@assets/default/x.svg'; const Modal: React.FC = ({ isCloseButtonVisible, onClose, content, button }) => { const handleBackgroundClick = (e: React.MouseEvent) => { @@ -26,7 +26,7 @@ const Modal: React.FC = ({ isCloseButtonVisible, onClose, content, b {isCloseButtonVisible && ( - + )} {content} diff --git a/src/components/NavBar/index.tsx b/src/components/NavBar/index.tsx index 4be360f5..96dde99b 100644 --- a/src/components/NavBar/index.tsx +++ b/src/components/NavBar/index.tsx @@ -3,7 +3,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { NavBarContainer, NavBarWrapper, - IconImg, IconWrapper, SideNavBarContainer, SideNavBarList, @@ -11,46 +10,49 @@ import { SideNavBarButton, SideNavBarHeader, SideNavBarFooter, + Icon, } from './styles'; -import Chat_s from './../../assets/default/message-white.svg'; -import Chat_f from './../../assets/default/message-fill.svg'; -import Home_s from './../../assets/default/home.svg'; -import Home_f from './../../assets/default/home-fill.svg'; -import Profile_s from './../../assets/default/my-page-white.svg'; -import Profile_f from './../../assets/default/my-page-fill.svg'; -import logo from './../../assets/default/oodd.svg'; -import alarm from './../../assets/default/alarm.svg'; -// import clickedAlarm from './../../assets/default/alarm-on.svg'; -import chatDesktopIcon from './../../assets/default/desktopNavBar/message.svg'; -import chatFillDesktopIcon from './../../assets/default/desktopNavBar/message-fill.svg'; -import homeDesktopIcon from './../../assets/default/desktopNavBar/home.svg'; -import homeFillDesktopIcon from './../../assets/default/desktopNavBar/home-fill.svg'; -import profileDesktopIcon from './../../assets/default/desktopNavBar/my-page.svg'; -import profileFillDesktopIcon from './../../assets/default/desktopNavBar/my-page-fill.svg'; -import logout from './../../assets/default/leave.svg'; -import { StyledText } from '../Text/StyledText'; -import Modal from '../Modal'; -import { ModalProps } from '../Modal/dto'; - -const userId = localStorage.getItem('my_id'); - -const tabs = [ - { name: 'Chats', iconSelected: Chat_f, iconUnselected: Chat_s, route: '/chats' }, - { name: 'Home', iconSelected: Home_f, iconUnselected: Home_s, route: '/' }, - { name: 'Profile', iconSelected: Profile_f, iconUnselected: Profile_s, route: `/profile/${userId}` }, -]; - -const desktopTabs = [ - { name: 'Home', iconSelected: homeFillDesktopIcon, iconUnselected: homeDesktopIcon, route: '/' }, - { name: 'Chats', iconSelected: chatFillDesktopIcon, iconUnselected: chatDesktopIcon, route: '/chats' }, - { name: 'Profile', iconSelected: profileFillDesktopIcon, iconUnselected: profileDesktopIcon, route: `/profile/${userId}` }, -]; +import Message from '@components/Icons/Message'; +import Home from '@components/Icons/Home'; +import MyPage from '@components/Icons/MyPage'; +import Alarm from '@components/Icons/Alarm'; +import logo from '@assets/default/oodd.svg'; +import logout from '@assets/default/leave.svg'; +import { StyledText } from '@components/Text/StyledText'; +import Modal from '@components/Modal'; +import type { ModalProps } from '@components/Modal/dto'; +import { getCurrentUserId } from '@utils/getCurrentUserId'; const NavBar: React.FC = () => { - const [selectedTab, setSelectedTab] = useState(''); const [isLogoutModalOpen, setIsLogoutModalOpen] = useState(false); + const [selectedTab, setSelectedTab] = useState(''); const navigate = useNavigate(); const location = useLocation(); + const currentUserId = getCurrentUserId(); + + const tabs = [ + { + name: 'Chats', + Icon: (isSelected: boolean, isDesktop: boolean) => ( + + ), + route: '/chats', + }, + { + name: 'Home', + Icon: (isSelected: boolean, isDesktop: boolean) => ( + + ), + route: '/', + }, + { + name: 'Profile', + Icon: (isSelected: boolean, isDesktop: boolean) => ( + + ), + route: `/profile/${currentUserId}`, + }, + ]; useEffect(() => { const currentTab = tabs.find((tab) => tab.route === location.pathname); @@ -68,7 +70,7 @@ const NavBar: React.FC = () => { } }; - const handleConfirmLogout = () => { + const handleLogoutConfirmButtonClick = () => { localStorage.clear(); setIsLogoutModalOpen(false); @@ -87,18 +89,17 @@ const NavBar: React.FC = () => { content: '이 기기에서 정말 로그아웃 할까요?', button: { content: '로그아웃', - onClick: handleConfirmLogout, + onClick: handleLogoutConfirmButtonClick, }, }; return ( <> - {isLogoutModalOpen && } {tabs.map((tab) => ( handleTabClick(tab)}> - + {tab.Icon(selectedTab === tab.name, false)}

{tab.name}

))} @@ -108,16 +109,14 @@ const NavBar: React.FC = () => { oodd - {desktopTabs.map((tab) => ( + {tabs.map((tab) => ( handleTabClick(tab)}> - + { + {isLogoutModalOpen && } ); }; diff --git a/src/components/NavBar/styles.tsx b/src/components/NavBar/styles.tsx index 62eb1fd9..4f60c0fc 100644 --- a/src/components/NavBar/styles.tsx +++ b/src/components/NavBar/styles.tsx @@ -35,7 +35,6 @@ export const IconWrapper = styled.div` cursor: pointer; gap: 10px; - p { margin: 0; bottom: 0; @@ -49,7 +48,7 @@ export const IconWrapper = styled.div` } `; -export const IconImg = styled.img` +export const Icon = styled.div` width: 1.13rem; height: 1.13rem; object-fit: cover; diff --git a/src/components/Text/StyledText.tsx b/src/components/Text/StyledText.tsx index 331135a2..247af562 100644 --- a/src/components/Text/StyledText.tsx +++ b/src/components/Text/StyledText.tsx @@ -1,29 +1,11 @@ import styled from 'styled-components'; -import theme from '../../styles/theme'; +import theme from '@styles/theme'; import { useMediaQuery } from 'react-responsive'; - -export type FontStyleKey = keyof typeof theme.fontStyles; - -// 플랫폼 별 폰트가 다른 경우 -interface FontStylesByPlatform { - mobile: FontStyleKey; - tablet: FontStyleKey; - desktop: FontStyleKey; -} - -export interface StyledTextProps { - $textTheme: { - style: FontStyleKey | FontStylesByPlatform; - lineHeight?: number; - }; - color?: string; - children: any; -} +import type { StyledTextProps } from './dto'; export const StyledText = styled.div` color: ${(props) => props.color || theme.colors.black}; white-space: pre-line; - line-height: ${(props) => props.$textTheme.lineHeight || 1.5}; ${(props) => { const isMobile = useMediaQuery({ maxWidth: '767px' }); const isTabletPortrait = useMediaQuery({ minWidth: '768px', maxWidth: '991px' }); @@ -31,9 +13,12 @@ export const StyledText = styled.div` const isDesktop = useMediaQuery({ minWidth: '1220px' }); let fontStyle; + if (typeof props.$textTheme.style === 'string') { + // style이 문자열이면 일괄적으로 사용 fontStyle = theme.fontStyles[props.$textTheme.style]; } else if (typeof props.$textTheme.style === 'object') { + // style이 객체면 기기에 따른 분기 if (isMobile) { fontStyle = theme.fontStyles[props.$textTheme.style.mobile]; } else if (isTabletPortrait || isTabletLandscape) { diff --git a/src/components/Text/dto.ts b/src/components/Text/dto.ts new file mode 100644 index 00000000..9d8a8e95 --- /dev/null +++ b/src/components/Text/dto.ts @@ -0,0 +1,19 @@ +import theme from '@styles/theme'; + +// 플랫폼 별 폰트가 다른 경우 +interface FontStylesByPlatform { + mobile: FontStyleKey; + tablet: FontStyleKey; + desktop: FontStyleKey; +} + +export interface StyledTextProps { + $textTheme: { + style: FontStyleKey | FontStylesByPlatform; + lineHeight?: number; + }; + color?: string; + children: any; +} + +export type FontStyleKey = keyof typeof theme.fontStyles; diff --git a/src/pages/Chats/ChatRoom/index.tsx b/src/pages/Chats/ChatRoom/index.tsx index f7125cff..d77f567b 100644 --- a/src/pages/Chats/ChatRoom/index.tsx +++ b/src/pages/Chats/ChatRoom/index.tsx @@ -184,7 +184,6 @@ const ChatRoom: React.FC = () => { icon: Block, }, ], - marginBottom: '4.38rem', }; const leaveModalProps: ModalProps = {