-
Notifications
You must be signed in to change notification settings - Fork 4
feat: 모바일 톡픽 상세 조회 페이지 댓글 UI 및 기능 구현 #302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
Walkthrough이번 PR은 애플리케이션의 모바일 환경 대응을 강화하기 위해 여러 컴포넌트와 스타일을 수정 및 추가하였습니다. 주요 변경 사항으로는 Changes
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (22)
src/components/atoms/CategoryBarChip/CategoryBarChip.style.ts (1)
34-38: 미디어 쿼리 브레이크포인트 표준화가 필요합니다.
430px와 같은 매직 넘버를 상수로 분리하여 재사용하는 것이 좋습니다.다음과 같이 브레이크포인트를 상수화하는 것을 추천드립니다:
+// src/styles/breakpoints.ts +export const BREAKPOINTS = { + MOBILE: '430px', +} as const; // CategoryBarChip.style.ts export const categoryBarChipStyling = css({ ...typo.Comment.SemiBold, display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: color.MAIN, color: color.WT, - '@media (max-width: 430px)': { + [`@media (max-width: ${BREAKPOINTS.MOBILE})`]: { ...typo.Mobile.Text.SemiBold_10, backgroundColor: color.WT_VIOLET, color: color.MAIN, }, });src/components/atoms/CommentProfile/CommentProfile.style.ts (1)
36-39: 모바일 스타일의 수치 표준화가 필요합니다.
24.5px와 같은 구체적인 픽셀값은 유지보수를 어렵게 만들 수 있습니다.다음과 같이 상수화하거나 상대적인 단위를 사용하는 것을 추천드립니다:
+// src/styles/sizes.ts +export const PROFILE_SIZES = { + DESKTOP: '45px', + MOBILE: '24.5px', +} as const; export const profileWrapper = css({ - width: '45px', - height: '45px', + width: PROFILE_SIZES.DESKTOP, + height: PROFILE_SIZES.DESKTOP, borderRadius: '50%', overflow: 'hidden', backgroundSize: 'cover', backgroundPosition: 'center', cursor: 'pointer', '@media (max-width: 430px)': { - width: '24.5px', - height: '24.5px', + width: PROFILE_SIZES.MOBILE, + height: PROFILE_SIZES.MOBILE, }, });src/components/atoms/ToastModal/ToastModal.style.ts (1)
33-36:Details
❓ Verification inconclusive
모바일 브레이크포인트 일관성 검토 필요
모바일 미디어 쿼리에서 사용된 430px 브레이크포인트가 다른 컴포넌트들과 일관성이 있는지 확인이 필요합니다. 브레이크포인트를 상수로 분리하여 재사용하는 것을 고려해보세요.
다음 스크립트로 프로젝트 내의 브레이크포인트 사용을 확인할 수 있습니다:
🏁 Script executed:
#!/bin/bash # 프로젝트 내의 미디어 쿼리 브레이크포인트 사용 현황 확인 rg -g '*.ts' -g '*.tsx' '@media.*\d+px'Length of output: 7065
모바일 브레이크포인트 일관성 확인 필요
현재 ToastModal 컴포넌트에서는 430px 브레이크포인트가 사용되고 있습니다. 프로젝트 전체를 검색한 결과, 대다수의 컴포넌트에서는 430px를 사용하고 있으나 일부 컴포넌트(예: CategoryButton, CategoryBox)에서는 320px가 사용되고 있음을 확인했습니다.
- 해당 컴포넌트들의 디자인 의도와 breakpoint 값이 명확한지 재검토해 주세요.
- 가능하다면, breakpoint 값을 상수로 분리하여 재사용하도록 리팩토링하는 방안을 고려해 보시기 바랍니다.
src/stories/mobile/atoms/CommentInput.stories.tsx (2)
28-29: 핸들러 구현 개선 필요빈 화살표 함수 대신 Storybook의 action을 활용하여 핸들러의 동작을 모니터링하는 것이 좋습니다.
다음과 같이 수정해보세요:
- onCommentChange={() => {}} - onCommentSubmit={() => {}} + onCommentChange={meta.argTypes.onCommentChange.action} + onCommentSubmit={meta.argTypes.onCommentSubmit.action}Also applies to: 33-34, 38-39
22-44: 테스트 케이스 보완 제안현재 빈 문자열, 긴 문자열, 최대 길이 초과 케이스를 테스트하고 있습니다. 다음 케이스들도 추가하면 좋을 것 같습니다:
- 특수 문자 포함
- 이모지 포함
- 줄바꿈 문자 포함
src/components/atoms/LikeButton/LikeButton.tsx (2)
28-44: switch 문 최적화 제안현재 switch 문이 다소 복잡합니다. 조건부 할당을 사용하여 더 간단하게 구현할 수 있습니다.
다음과 같이 수정해보세요:
- switch (true) { - case likeState && isMobile: - Icon = MobileLikeButtonPR; - break; - case likeState && !isMobile: - Icon = LikeButtonPR; - break; - case !likeState && isMobile: - Icon = MobileLikeButtonDF; - break; - case !likeState && !isMobile: - Icon = LikeButtonDF; - break; - default: - Icon = LikeButtonDF; - break; - } + Icon = isMobile + ? likeState + ? MobileLikeButtonPR + : MobileLikeButtonDF + : likeState + ? LikeButtonPR + : LikeButtonDF;
50-50: 좋아요 텍스트 상수화 권장하드코딩된 '좋아요' 문자열을 상수로 분리하는 것이 좋습니다.
constants폴더에 관련 상수를 추가하고 import하여 사용하세요.src/components/atoms/LikeButton/LikeButton.style.ts (2)
13-16: 미디어 쿼리 중단점 표준화 필요미디어 쿼리 중단점(430px)이 임의적으로 보입니다. 일관성을 위해 공통 상수나 테마 설정에서 중단점을 관리하는 것이 좋습니다.
+ // src/styles/breakpoints.ts 파일 생성 + export const BREAKPOINTS = { + MOBILE: '430px', + TABLET: '768px', + DESKTOP: '1024px', + } as const;
29-63: 중복 스타일 코드 리팩토링 제안
defaultLabel과pressedLabel에 중복된 스타일이 많이 있습니다. 공통 스타일을 분리하여 재사용하면 유지보수가 더 쉬워질 것 같습니다.+ const baseLabel = css({ + ...typo.Number.Medium_18, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + width: '22px', + height: '29px', + padding: 0, + margin: 0, + '@media (max-width: 430px)': { + ...typo.Mobile.Main.Regular_12, + width: '15px', + height: '15px', + }, + }); export const defaultLabel = css({ + ...baseLabel, - ...typo.Number.Medium_18, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - width: '22px', - height: '29px', color: color.GY[1], - padding: 0, - margin: 0, - '@media (max-width: 430px)': { - ...typo.Mobile.Main.Regular_12, - width: '15px', - height: '15px', - }, }); export const pressedLabel = css({ + ...baseLabel, - ...typo.Number.Medium_18, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - width: '22px', - height: '29px', color: color.MAIN, - padding: 0, - margin: 0, - '@media (max-width: 430px)': { - ...typo.Mobile.Main.Regular_12, - width: '15px', - height: '15px', - }, });src/styles/color.ts (2)
25-25: TODO 주석 처리 필요이전 색상 타입 정리에 대한 TODO 주석이 있습니다. 색상 시스템의 일관성을 위해 이 작업을 진행하는 것이 좋을 것 같습니다.
색상 타입 정리 작업을 도와드릴까요? 새로운 이슈를 생성하여 추적할 수 있습니다.
4-12: 색상 네이밍 컨벤션 개선 제안GY.1, GY.2와 같은 숫자 기반의 네이밍은 의미를 파악하기 어렵습니다. 색상의 용도나 의미를 반영하는 네이밍으로 변경하면 좋을 것 같습니다.
const color = { MAIN: '#7782FF', BK: '#181818', GY: { - 1: '#8C8C8C', - 2: '#D9D9D9', - 3: '#F1F1F1', - 4: '#E6E9EF', - 5: '#F6F7F9', - 6: '#97A0A9', - 7: '#555555', + text: '#8C8C8C', + border: '#D9D9D9', + background: '#F1F1F1', + divider: '#E6E9EF', + hover: '#F6F7F9', + icon: '#97A0A9', + label: '#555555', },src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts (1)
5-8: MainContainer 네이밍 개선 제안
MainContainer라는 이름은 너무 일반적입니다. 컴포넌트의 특정 목적을 더 잘 반영하는 이름을 사용하면 좋을 것 같습니다.-export const MainContainer = css({ +export const CommentItemContainer = css({ display: 'flex', flexDirection: 'column', });src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.style.ts (1)
9-9: 주석 처리된 코드 정리 필요주석 처리된
paddingBottom속성이 있습니다. 이 코드가 필요하지 않다면 제거하고, 필요하다면 주석 대신 실제 코드로 변경하는 것이 좋습니다.src/components/mobile/atoms/CommentInput/CommentInput.style.ts (1)
19-41: 반응형 디자인 개선 제안현재 고정된 픽셀 값으로 너비가 지정되어 있어 다양한 모바일 화면 크기에서 문제가 발생할 수 있습니다.
다음과 같이 상대적인 단위를 사용하는 것을 제안드립니다:
export const inputBoxWrapper = (length: number) => css({ backgroundColor: color.GY[5], padding: '3px 4px 3px 18px', - width: '335px', + width: '100%', + maxWidth: '335px', height: 'auto', borderRadius: length < 20 ? '30px' : '17px', display: 'flex', }); export const inputStyling = css(typo.Text.Regular, { flex: 1, border: 'none', outline: 'none', - width: '213px', + width: '100%', resize: 'none', overflowY: 'auto', maxHeight: '66px', height: 'auto', '&::placeholder': { color: color.GY[6], }, });src/components/mobile/atoms/CommentInput/CommentInput.tsx (1)
43-46: 접근성 개선 필요현재 textarea에
aria-label이 누락되어 있습니다.<textarea ref={textareaRef} css={S.inputStyling} placeholder="댓글을 입력해주세요" + aria-label="댓글 입력창" value={comment} onChange={(e) => { onCommentChange(e.target.value); handleInput(); }} maxLength={COMMENT.MAX_LENGTH} rows={1} {...attributes} />src/components/molecules/CommentItem/CommentItem.style.ts (2)
5-9: 반응형 디자인 및 일관성 개선 필요MainContainer의 고정 너비는 모바일 환경에서 문제가 될 수 있습니다.
export const MainContainer = css({ display: 'flex', flexDirection: 'column', - width: '1175px', + width: '100%', + maxWidth: '1175px', });
50-54: 색상 상수 사용의 일관성 개선 필요일부 색상값이 직접 hex 코드로 하드코딩되어 있습니다. 색상 상수를 사용하여 일관성을 유지해야 합니다.
export const createdTime = css({ ...typo.Comment.SemiBold, - color: '#67727E', + color: color.GY[1], marginLeft: '10px', }); export const commentTextWrapper = css({ display: 'flex', width: '100%', paddingRight: '20px', ...typo.Comment.Regular, - color: '#505050', + color: color.GY[2], });Also applies to: 61-67
src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.tsx (2)
1-1: eslint 규칙 비활성화 제거 필요
@typescript-eslint/no-unused-vars규칙을 비활성화하는 대신 사용하지 않는 변수를 제거하는 것이 좋습니다.-/* eslint-disable @typescript-eslint/no-unused-vars */
70-77: 주석 처리된 페이지네이션 코드 처리 필요페이지네이션 기능이 구현되지 않아 주석 처리된 상태입니다. 향후 구현 계획이 있다면 TODO 주석을 추가하고, 그렇지 않다면 코드를 제거하는 것이 좋습니다.
- {/* <div css={S.paginationWrapper}> - <Pagination - pages={pages} - selected={selectedPage} - maxPage={totalPages} - onChangeNavigate={handlePageChange} - /> - </div> */}src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx (2)
1-1: eslint 규칙 비활성화 제거 필요
@typescript-eslint/no-unused-vars규칙을 비활성화하는 대신 사용하지 않는 변수를 제거하는 것이 좋습니다.-/* eslint-disable @typescript-eslint/no-unused-vars */
98-101: 주석 처리된 함수들의 처리 필요여러 기능이 주석 처리되어 있습니다. 각 기능의 구현 계획에 따라 TODO 주석을 추가하거나 코드를 제거하는 것이 좋습니다.
- // const handleDeleteCommentButton = () => { - // onCloseModal(); - // handleDelete(); - // }; - // const handleReportCommentButton = (reason: string) => { - // handleReport(reason); - // onCloseModal(); - // }; - // const handleMoreButton = () => { - // setVisibleReply((reply) => reply + 10); - // };Also applies to: 127-130, 132-134
src/assets/index.ts (1)
160-160:Details
❓ Verification inconclusive
TODO 주석 정리가 필요합니다.
"이전 SVG" 관련 TODO 주석이 있습니다. 이전 SVG 에셋들의 사용 여부를 확인하고, 불필요한 경우 정리가 필요해 보입니다.
다음 스크립트를 실행하여 이전 SVG 에셋들의 사용 여부를 확인해보세요:
🏁 Script executed:
#!/bin/bash # Description: Check usage of old SVG assets # List of old SVG assets to check OLD_ASSETS=("Email" "Profile" "Report" "Like" "Star" "Hearts" "Home" "Key" "LeftButton" "RightButton" "Person" "Rank" "Winner" "Fight" "Vote" "Edit" "Delete" "DisabledHeart" "Closed" "RightArrow") for asset in "${OLD_ASSETS[@]}"; do echo "Checking usage of $asset..." rg -l "\b${asset}\b" --type ts --type tsx doneLength of output: 3590
[검증 필요] 이전 SVG TODO 정리 및 스크립트 수정 요청
현재
src/assets/index.ts의 160번째 줄에 남아있는 "이전 SVG" 관련 TODO 주석이 있습니다. 이 주석은 이전 SVG 에셋들의 사용 여부를 확인한 후 불필요하다면 정리할 필요가 있음을 의미합니다.실행된 스크립트에서 ripgrep이
--type tsx옵션을 인식하지 못해 "unrecognized file type: tsx" 오류가 발생하였습니다. 이에 따라 아래와 같이 조치해 주시기 바랍니다:
검증 조치:
- TS 및 TSX 파일 모두에서 에셋 사용 여부를 확인할 수 있도록 스크립트를 수정하거나, 수동으로 코드베이스 내에서 해당 에셋(예: "Email", "Profile", "Report" 등)의 사용 여부를 재검증해 주세요.
- ripgrep이 TSX 파일을 인식할 수 있도록
--type-add옵션 등의 수정 방안을 적용해 볼 수 있습니다.주석 정리:
- 에셋이 사용 중이지 않다면, 해당 TODO 주석을 제거해 주세요.
- 사용 중인 것이 확인된다면, 관련 코멘트를 최신 상태에 맞게 업데이트하거나 필요에 따라 주석 내용을 보완해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
src/assets/svg/cry-emoji.svgis excluded by!**/*.svgsrc/assets/svg/mobile-arrow-up.svgis excluded by!**/*.svgsrc/assets/svg/mobile-comment.svgis excluded by!**/*.svgsrc/assets/svg/mobile-like-button-df.svgis excluded by!**/*.svgsrc/assets/svg/mobile-like-button-pr.svgis excluded by!**/*.svg
📒 Files selected for processing (22)
src/App.tsx(2 hunks)src/assets/index.ts(1 hunks)src/components/atoms/CategoryBarChip/CategoryBarChip.style.ts(1 hunks)src/components/atoms/CategoryBarChip/CategoryBarChip.tsx(1 hunks)src/components/atoms/CommentProfile/CommentProfile.style.ts(2 hunks)src/components/atoms/LikeButton/LikeButton.style.ts(1 hunks)src/components/atoms/LikeButton/LikeButton.tsx(1 hunks)src/components/atoms/MenuTap/MenuTap.style.ts(2 hunks)src/components/atoms/ToastModal/ToastModal.style.ts(2 hunks)src/components/mobile/atoms/CommentInput/CommentInput.style.ts(1 hunks)src/components/mobile/atoms/CommentInput/CommentInput.tsx(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx(1 hunks)src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.style.ts(1 hunks)src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.tsx(1 hunks)src/components/molecules/CommentItem/CommentItem.style.ts(2 hunks)src/components/organisms/CommentsSection/CommentsSection.style.ts(1 hunks)src/constants/message.ts(1 hunks)src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.style.ts(1 hunks)src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.tsx(1 hunks)src/stories/mobile/atoms/CommentInput.stories.tsx(1 hunks)src/styles/color.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/components/organisms/CommentsSection/CommentsSection.style.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: build
- GitHub Check: storybook
🔇 Additional comments (11)
src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.style.ts (1)
4-8: 스타일 구현이 깔끔합니다!플렉스 컨테이너를 사용하여 콘텐츠를 세로 방향으로 정렬하는 구현이 적절합니다.
src/components/atoms/CategoryBarChip/CategoryBarChip.tsx (1)
5-5: 사이즈 옵션 추가가 잘 구현되었습니다!'extraSmall' 사이즈 옵션을 추가한 것이 모바일 환경에 적합해 보입니다.
src/components/atoms/CategoryBarChip/CategoryBarChip.style.ts (1)
18-21: extraSmall 스타일링이 잘 구현되었습니다!작은 크기의 카테고리 칩에 적합한 스타일링입니다.
src/components/atoms/CommentProfile/CommentProfile.style.ts (1)
4-14: 컨테이너 스타일 구현이 개선되었습니다!객체 문법을 사용한 스타일 정의가 가독성과 유지보수성을 향상시켰습니다.
src/components/atoms/MenuTap/MenuTap.style.ts (1)
45-50:Details
❓ Verification inconclusive
모바일 텍스트 색상 대비 검토 필요
모바일 뷰에서 사용되는
color.GY[1]과 배경색의 대비가 접근성 기준을 충족하는지 확인이 필요합니다.다음 Python 스크립트로 색상 대비를 확인할 수 있습니다:
🏁 Script executed:
#!/bin/bash # 색상 대비 검사 python3 << END from wcag_contrast_ratio import rgb import json # styles/color.ts 파일에서 색상값 추출 with open('src/styles/color.ts', 'r') as f: content = f.read() # GY[1]과 WT 색상의 대비 계산 background = rgb(255, 255, 255) # WT color text = None # GY[1] color will be extracted # Calculate and print contrast ratio if text: ratio = text.contrast_ratio(background) print(f"Contrast ratio: {ratio}") print(f"Meets WCAG AA for large text: {ratio >= 3}") print(f"Meets WCAG AA for small text: {ratio >= 4.5}") ENDLength of output: 141
모바일 텍스트 색상 대비 수동 검증 필요
모바일 뷰에 적용된
color.GY[1]텍스트와 예상 배경색(흰색) 간의 대비가 WCAG 접근성 기준(작은 텍스트: 최소 4.5, 큰 텍스트: 최소 3)을 충족하는지 확인해야 합니다.
현재 실행한 Python 스크립트에서wcag_contrast_ratio모듈이 존재하지 않아 대비 결과를 얻지 못했습니다. 이에 따라 아래 사항을 참고하여 수동 검증 또는 대체 도구를 통한 검토를 부탁드립니다:
- 파일:
src/components/atoms/MenuTap/MenuTap.style.ts(45-50 라인)- 검토 항목: 모바일 뷰에서 사용되는 텍스트 색상(
color.GY[1])과 흰색 배경 간의 색상 대비- 필요 시
wcag_contrast_ratio모듈 설치 또는 외부 도구를 이용해 정확한 색상 대비 값을 확인해주세요.src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.style.ts (1)
40-52: 성능 최적화 고려 필요
backdropFilter: 'blur'는 모바일 기기에서 성능 저하를 일으킬 수 있습니다. 대체 방안을 고려하거나 성능 테스트를 진행하는 것이 좋을 것 같습니다.더 가벼운 대안으로 반투명 배경만 사용하는 것을 고려해보세요:
export const loggedOutBackground = css({ width: '100%', height: '100%', - backgroundColor: 'rgba(255, 255, 255, 0.01)', - backdropFilter: 'blur(11px)', + backgroundColor: 'rgba(255, 255, 255, 0.9)', position: 'absolute', top: 0, left: 0, zIndex: 1, display: 'flex', flexDirection: 'column', gap: '29px', });src/components/mobile/atoms/CommentInput/CommentInput.style.ts (1)
6-17:Details
❓ Verification inconclusive
모바일 키보드 동작 시 입력창 위치 검토 필요
fixed 포지션을 사용한 입력창이 모바일 가상 키보드와 함께 사용될 때 문제가 발생할 수 있습니다. 키보드가 올라올 때 입력창이 가려질 수 있으며, iOS Safari에서 특히 주의가 필요합니다.
🌐 Web query:
How to handle fixed positioning with mobile virtual keyboards?💡 Result:
Handling fixed positioning elements when mobile virtual keyboards appear requires addressing viewport resizing and element repositioning. Here are key approaches based on current best practices:
Modern Solution: VirtualKeyboard API
For browsers supporting the new standard (Chrome 94+):
.fixed-element { position: fixed; bottom: calc(20px + env(keyboard-inset-height, 0px)); }// Enable API behavior if ("virtualKeyboard" in navigator) { navigator.virtualKeyboard.overlaysContent = true; }This API provides real keyboard height through CSS environment variables (
keyboard-inset-*), allowing precise positioning[1].Fallback Techniques
1. Viewport Resize Detection
const vv = window.visualViewport; vv.addEventListener('resize', () => { const keyboardHeight = window.innerHeight - vv.height; document.documentElement.style.setProperty( '--keyboard-height', `${keyboardHeight}px` ); });.fixed-element { position: fixed; bottom: calc(20px + var(--keyboard-height, 0px)); }Uses Visual Viewport API to track keyboard height changes[4].
2. Position Switching (Legacy iOS)
/* Default state */ .fixed-element { position: fixed; bottom: 0; } /* Keyboard active state */ .keyboard-active .fixed-element { position: absolute; bottom: var(--keyboard-height); }document.addEventListener('focusin', () => { document.body.classList.add('keyboard-active'); }); document.addEventListener('focusout', () => { document.body.classList.remove('keyboard-active'); });Switches between fixed/absolute positioning during input interactions[2][6].
Layout Strategies
CSS Grid Approach
body { display: grid; height: 100dvh; grid-template-rows: 1fr auto env(keyboard-inset-height, 0px); }Reserves dedicated space for keyboard using modern viewport units[1].
Technique Pros Cons VirtualKeyboard API Native solution, precise control Limited browser support Visual Viewport Wider support Requires JS calculations Position Switching Works on older iOS Layout shifts For most implementations today, a combination of Visual Viewport detection with CSS custom properties provides the best cross-browser coverage while waiting for VirtualKeyboard API adoption[4][1]. When supporting legacy iOS versions (15+), position switching remains necessary for critical fixed elements like chat inputs[2][6].
Citations:
- 1: https://www.bram.us/2021/09/13/prevent-items-from-being-hidden-underneath-the-virtual-keyboard-by-means-of-the-virtualkeyboard-api/
- 2: https://dansajin.com/2012/12/07/fix-position-fixed/
- 3: https://forum.bubble.io/t/incorrect-positioning-on-mobile-with-keyboard/150272
- 4: https://dev.to/vladimirschneider/how-stick-element-to-bottom-of-viewport-on-mobile-1pg6
- 5: https://forum.jquery.com/portal/en/community/topic/buggy-header-footer-fixed-position-when-virtual-keyboard-is-on-off-in-ios-phonegap-application
- 6: https://gist.github.com/avesus/957889b4941239490c6c441adbe32398
- 7: https://teamtreehouse.com/community/mobile-fixed-header-jumps-down-when-keyboard-opens-on-focus
- 8: w3c/csswg-drafts#7475
모바일 키보드 대응: 입력창 위치 조정 검토 필요
현재
inputContainer가fixed포지션으로 설정되어 있는데, 모바일 기기에서 가상 키보드가 활성화될 경우 입력창이 키보드에 가려질 수 있는 위험이 있습니다. 특히 iOS Safari에서 이러한 문제가 두드러지므로, 아래와 같은 대안 기법들을 검토해보시기 바랍니다.
- VirtualKeyboard API 활용: 최신 브라우저(Chrome 94+ 등)에서는 CSS의
env(keyboard-inset-height)와 함께 VirtualKeyboard API를 사용할 수 있습니다.- Visual Viewport API 활용: JS로 뷰포트 리사이징 이벤트를 감지하여 키보드 높이를 계산하고, CSS 커스텀 프로퍼티를 적용하는 방법.
- 포지션 전환 기법: iOS와 같은 환경에서는 조건에 따라
fixed와absolute포지션을 전환하는 방식.특히 iOS Safari에서의 동작 여부를 충분히 테스트한 후, 필요에 따라 위 대안들을 적용하여 입력창이 항상 올바르게 표시되도록 개선해 주세요.
src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.tsx (1)
13-19: Props 인터페이스가 잘 정의되어 있습니다.필요한 모든 props가 명확하게 타입이 지정되어 있으며, 선택적 props도 적절하게 처리되어 있습니다.
src/constants/message.ts (1)
221-225: 댓글 관련 메시지가 적절하게 정의되어 있습니다.사용자 친화적인 메시지가 잘 구성되어 있으며, 댓글 길이 제한도 적절하게 설정되어 있습니다.
src/App.tsx (1)
96-99: 모바일 대응이 적절하게 구현되어 있습니다.
isMobile상태에 따라 적절한 컴포넌트를 렌더링하도록 구현되어 있습니다. 모바일과 데스크톱 사용자 모두에게 최적화된 경험을 제공할 수 있습니다.src/assets/index.ts (1)
154-158: 모바일 UI 관련 에셋 추가가 적절합니다.모바일 댓글 UI 구현을 위한 새로운 SVG 에셋들이 잘 추가되었습니다. 각 에셋의 이름이 용도를 명확하게 나타내고 있어 좋습니다.
| export const commentsWrapStyle = css({ | ||
| marginTop: '444px', | ||
| borderTop: `10px solid ${color.GY[5]}`, | ||
| width: '100%', | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
고정된 상단 여백 사용에 대한 검토가 필요합니다.
marginTop: '444px'와 같은 고정된 값은 다양한 화면 크기에서 레이아웃 문제를 일으킬 수 있습니다.
다음과 같이 상대적인 값을 사용하는 것을 고려해보세요:
export const commentsWrapStyle = css({
- marginTop: '444px',
+ marginTop: 'clamp(20px, 30vh, 444px)',
borderTop: `10px solid ${color.GY[5]}`,
width: '100%',
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const commentsWrapStyle = css({ | |
| marginTop: '444px', | |
| borderTop: `10px solid ${color.GY[5]}`, | |
| width: '100%', | |
| }); | |
| export const commentsWrapStyle = css({ | |
| marginTop: 'clamp(20px, 30vh, 444px)', | |
| borderTop: `10px solid ${color.GY[5]}`, | |
| width: '100%', | |
| }); |
src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts
Show resolved
Hide resolved
| /* eslint-disable @typescript-eslint/no-unused-vars */ | ||
| import React, { useState } from 'react'; | ||
| import { useLocation, useParams } from 'react-router-dom'; | ||
| import MobileCommentsSection from '@/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection'; | ||
| // import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; | ||
| import { useTalkPickDetailQuery } from '@/hooks/api/talk-pick/useTalkPickDetailQuery'; | ||
| // import { ToggleGroupItem } from '@/types/toggle'; | ||
| import { useCommentsQuery } from '@/hooks/api/comment/useCommentsQuery'; | ||
| import CommentInput from '@/components/mobile/atoms/CommentInput/CommentInput'; | ||
| import { useCreateCommentMutation } from '@/hooks/api/comment/useCreateCommentMutation'; | ||
| import * as S from './TalkPickMobilePage.style'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
미사용 코드 정리 및 에러 처리 개선 필요
- 미사용 코드 주석 제거
- API 호출에 대한 에러 처리 추가 필요
-/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import MobileCommentsSection from '@/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection';
-// import { useMemberQuery } from '@/hooks/api/member/useMemberQuery';
import { useTalkPickDetailQuery } from '@/hooks/api/talk-pick/useTalkPickDetailQuery';
-// import { ToggleGroupItem } from '@/types/toggle';
import { useCommentsQuery } from '@/hooks/api/comment/useCommentsQuery';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /* eslint-disable @typescript-eslint/no-unused-vars */ | |
| import React, { useState } from 'react'; | |
| import { useLocation, useParams } from 'react-router-dom'; | |
| import MobileCommentsSection from '@/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection'; | |
| // import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; | |
| import { useTalkPickDetailQuery } from '@/hooks/api/talk-pick/useTalkPickDetailQuery'; | |
| // import { ToggleGroupItem } from '@/types/toggle'; | |
| import { useCommentsQuery } from '@/hooks/api/comment/useCommentsQuery'; | |
| import CommentInput from '@/components/mobile/atoms/CommentInput/CommentInput'; | |
| import { useCreateCommentMutation } from '@/hooks/api/comment/useCreateCommentMutation'; | |
| import * as S from './TalkPickMobilePage.style'; | |
| import React, { useState } from 'react'; | |
| import { useLocation, useParams } from 'react-router-dom'; | |
| import MobileCommentsSection from '@/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection'; | |
| import { useTalkPickDetailQuery } from '@/hooks/api/talk-pick/useTalkPickDetailQuery'; | |
| import { useCommentsQuery } from '@/hooks/api/comment/useCommentsQuery'; | |
| import CommentInput from '@/components/mobile/atoms/CommentInput/CommentInput'; | |
| import { useCreateCommentMutation } from '@/hooks/api/comment/useCreateCommentMutation'; | |
| import * as S from './TalkPickMobilePage.style'; |
src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (7)
src/constants/message.ts (1)
221-226: 잘 구현된 댓글 관련 상수 정의입니다!상수의 구조와 네이밍이 일관성 있게 잘 정의되어 있으며, 사용자 친화적인 메시지를 포함하고 있습니다.
문서화 제안
MAX_LENGTH와 MAX_SHORTEN_LENGTH의 용도를 주석으로 명확히 설명하면 좋을 것 같습니다.
다음과 같이 주석을 추가하는 것을 제안드립니다:
export const COMMENT = { NO_COMMENTS_YET: '아직 댓글이 없어요', FIRST_COMMENT_SUGGESTION: '첫 댓글을 달아보는건 어때요?', + // 댓글의 최대 길이 제한 MAX_LENGTH: 500, + // 모바일 화면에서 표시되는 댓글 미리보기의 최대 길이 MAX_SHORTEN_LENGTH: 90, };src/components/atoms/MoreButton/MoreButton.style.ts (1)
14-24: 모바일 반응형 스타일링 구현이 적절합니다.미디어 쿼리를 통한 모바일 환경 대응이 잘 구현되어 있습니다. 'arrow' 아이콘과 기본 아이콘에 대해 각각 다른 타이포그래피와 간격을 적용한 것이 좋은 접근입니다.
다만, 다음 사항들을 고려해보시면 좋을 것 같습니다:
- 매직 넘버인 430px을 상수로 분리하여 재사용성을 높이는 것을 고려해보세요.
- 반복되는
color.GY[1]스타일을 상위 스타일로 이동하는 것을 검토해보세요.+const MOBILE_BREAKPOINT = '430px'; + export const moreButtonStyling = (icon: Required<MoreButtonProps>['icon']) => css({ ...typo.Main.Medium, display: 'flex', alignItems: 'center', gap: '7px', color: icon === 'plus' ? color.GY[1] : color.MAIN, cursor: 'pointer', - '@media (max-width: 430px)': + '@media (max-width: ${MOBILE_BREAKPOINT})': { + color: color.GY[1], icon === 'arrow' ? { ...typo.Mobile.Text.Medium_12, - color: color.GY[1], gap: '3px', } : { ...typo.Mobile.Text.SemiBold_14, - color: color.GY[1], }, + } });src/components/atoms/MoreButton/MoreButton.tsx (1)
15-22: 모바일 아이콘 분기 처리가 명확합니다.size와 icon prop에 따른 조건부 렌더링이 잘 구현되어 있습니다. switch 문을 사용한 분기 처리가 가독성이 좋습니다.
다만, 다음과 같은 개선을 고려해보시면 좋을 것 같습니다:
- 아이콘 매핑을 객체로 분리하여 switch 문을 제거하는 것을 고려해보세요.
+const SMALL_ICONS = { + arrow: MobileArrowDown, + plus: MoreSmall, +} as const; + const getIcon = () => { if (size === 'small') { - switch (icon) { - case 'arrow': - return <MobileArrowDown />; - default: - return <MoreSmall />; - } + const Icon = SMALL_ICONS[icon] || MoreSmall; + return <Icon />; } return icon === 'plus' ? <More /> : <MoreReply />; };src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts (4)
10-12: 스타일 네이밍 컨벤션 개선 제안
myCommentColor는 배경색만 정의하고 있으므로, 더 명확한 의미를 전달하기 위해myCommentBackground나highlightedCommentBackground와 같은 이름으로 변경하는 것이 좋을 것 같습니다.
25-41: 간격 값의 일관성 개선 필요현재 gap 값이
8px와3.5px로 일관성이 없습니다. 디자인 시스템의 일관성을 위해 간격 값을 표준화하는 것이 좋습니다. 가능하다면4px의 배수를 사용하는 것을 추천드립니다.export const commentInfoWrapper = css({ display: 'flex', flexDirection: 'column', width: '100%', - gap: '3.5px', + gap: '4px', });
54-62: 크로스 브라우저 호환성 고려 필요텍스트 말줄임(...) 처리를 위해 webkit 전용 속성만 사용하고 있습니다. Firefox나 다른 브라우저에서의 호환성을 위해 대체 스타일을 추가하는 것이 좋습니다.
export const commentTextWrapper = css({ ...typo.Mobile.Text.Regular_14, display: '-webkit-box', + display: 'flex', width: '100%', '-webkit-line-clamp': 3, '-webkit-box-orient': 'vertical', overflow: 'hidden', + textOverflow: 'ellipsis', color: color.BK, });
64-66: 현대적인 레이아웃 방식 사용 제안
float속성 대신 Flexbox나 Grid를 사용하여 더 안정적이고 현대적인 레이아웃을 구현하는 것을 추천드립니다.export const moreButtonWrapper = css({ - float: 'right', + marginLeft: 'auto', + display: 'flex', + alignItems: 'center', });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/svg/mobile-arrow-down.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
src/assets/index.ts(1 hunks)src/components/atoms/MoreButton/MoreButton.style.ts(1 hunks)src/components/atoms/MoreButton/MoreButton.tsx(2 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx(1 hunks)src/constants/message.ts(1 hunks)src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.style.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.style.ts
- src/assets/index.ts
- src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: storybook
- GitHub Check: build
🔇 Additional comments (2)
src/components/atoms/MoreButton/MoreButton.tsx (1)
2-2: 아이콘 import 구문이 깔끔하게 정리되었습니다.모든 아이콘을 한 줄로 import하여 코드가 간결해졌습니다.
src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts (1)
54-62: 데스크톱용 타이포그래피를 모바일용으로 변경 필요이전 리뷰에서도 지적된 사항으로, 모바일 컴포넌트에서 데스크톱용 타이포그래피를 사용하고 있습니다.
export const commentTextWrapper = css({ - ...typo.Text.Regular, + ...typo.Mobile.Text.Regular_14, display: '-webkit-box', width: '100%', '-webkit-line-clamp': 3, '-webkit-box-orient': 'vertical', overflow: 'hidden', color: color.BK, });
| MAX_LENGTH: 500, | ||
| MAX_SHORTEN_LENGTH: 90, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
매직 넘버까지 constants에 표현한 점 좋습니다! 주로 텍스트 부분만 해당 디렉토리에 처리를 해왔었는데, 이 부분은 약간의 규칙성이 필요할 수도 있을 거 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
약간 애매하긴합니다... 둘다 최대 길이이긴 해서 최대한 구분할 수 있도록 생각한건데... 좋은 상수명 있으신가요...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 네이밍에 대한 코멘트가 아니라, 매직 넘버와 상수 데이터 에 대한 코멘트 였어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아...! 문장을 잘못 이해하고 있었네요ㅎ 매직 넘버도 상수처리하는게 좋을거 같긴한데 원준님 말씀대로 한두번쓰이는 숫자도 모두 상수에 포함해야할 지 아니면 비즈니스 로직과 직접적으로 연결된 것, 혹은 반복 사용 숫자만 상수화 하는 등의 규칙이 필요할 거 같긴하네요!
src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/hooks/comment/useCommentActions.ts (2)
9-10: 매개변수 타입 변경에 따른 안전한 속성 접근 구현null 처리를 위해 옵셔널 체이닝과 널 병합 연산자를 사용한 것은 좋습니다. 하지만 타입 안전성을 더욱 강화할 수 있습니다.
-const id = commentData?.id ?? 0; -const talkPickId = commentData?.talkPickId ?? 0; -const content = commentData?.content ?? ''; -const myLike = commentData?.myLike ?? false; +const { id = 0, talkPickId = 0, content = '', myLike = false } = commentData ?? {};Also applies to: 17-20
60-68: 빈 함수 반환 시 타입 안전성 개선 필요빈 함수를 반환할 때 타입 안전성을 보장하기 위해 반환 타입을 명시적으로 정의하는 것이 좋습니다.
+type CommentActions = { + handleEditSubmit: () => void; + handleDelete: () => void; + handleLikeToggle: () => void; + handleReport: () => void; +}; if (!commentData) { - return { + return <CommentActions>{ handleEditSubmit: () => {}, handleDelete: () => {}, handleLikeToggle: () => {}, handleReport: () => {}, }; }src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.tsx (1)
59-69: 성능 최적화 필요댓글 목록을 렌더링할 때 불필요한 리렌더링이 발생할 수 있습니다.
+const MemoizedMobileCommentItem = React.memo(MobileCommentItem); {commentList?.content.map((commentData, idx) => ( - <MobileCommentItem + <MemoizedMobileCommentItem idx={idx} totalElements={commentList.totalElements} key={commentData.id} comment={commentData} selectedPage={selectedPage} talkPickWriter={talkPickWriter} onEditComment={onEditComment} /> ))}src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx (1)
98-120: 메뉴 아이템 상수화 필요메뉴 아이템 배열을 컴포넌트 외부로 분리하여 상수화하는 것이 좋습니다.
+const MY_COMMENT_MENU_ITEMS = [ + { + label: '수정', + action: 'edit', + }, + { + label: '삭제', + action: 'delete', + }, +] as const; + +const REPORT_MENU_ITEMS = [ + { + label: '신고', + action: 'report', + }, +] as const; -const myComment: MenuItem[] = [...] -const reportComment: MenuItem[] = [...] +const myComment = MY_COMMENT_MENU_ITEMS.map(item => ({ + label: item.label, + onClick: () => { + if (item.action === 'edit') { + onEditComment(comment.id, comment.content); + } else if (item.action === 'delete') { + setActiveModal('deleteText'); + } + }, +}));
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/components/mobile/atoms/CommentInput/CommentInput.tsx(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx(1 hunks)src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.style.ts(1 hunks)src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.tsx(1 hunks)src/hooks/comment/useCommentActions.ts(4 hunks)src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/mobile/organisms/MobileCommentsSection/MobileCommentsSection.style.ts
- src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts
- src/pages/mobile/TalkPickMobilePage/TalkPickMobilePage.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: build
- GitHub Check: storybook
🔇 Additional comments (2)
src/components/mobile/atoms/CommentInput/CommentInput.tsx (1)
41-45: 입력값 유효성 검사 누락공백 문자만 입력하는 경우나 최소 길이에 대한 검증이 필요합니다.
onChange={(e) => { + const value = e.target.value.trim(); + if (!value) { + return; + } - onCommentChange(e.target.value); + onCommentChange(value); handleInput(); }}src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx (1)
74-83: 답글 생성 에러 처리 누락답글 생성 시 에러 처리가 되어있지 않습니다.
const { mutate: createReply } = useCreateReplyMutation( comment.talkPickId, comment.id, selectedPage, + { + onError: (error) => { + showToastModal('답글 작성에 실패했습니다.'); + console.error('Reply creation failed:', error); + } + } );
WonJuneKim
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정된 부분까지 확인 완료했습니다! sonarcloud 이슈들만 해결되면 approve 드릴게요!
areumH
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
댓글 관련 구현 수고 많으셨어요!!! api 연동 테스트 진행 후 머지하면 좋을 것 같아요~~
플로우 관련으로 질문 몇 개 남겼습니다!! 😊
| const { comments } = useCommentsQuery( | ||
| id, | ||
| { page: currentPage - 1, size: 7 }, | ||
| 'comments', | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
페이지네이션이 사용되지 않는다고 했는데 사이즈를 7로 넘겨주는 이유가 있나요!??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 파일에 handleCommentPageChange 등 페이지네이션 관련 코드가 많은데 어떻게 사용되고 있는건지 궁금합니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
먼저 늦은 답변 죄송합니다🥲 이 부분은 웹 페이지의 코드를 그대로 가져온거라... size에 대한 수정은 하지 않았습니다..! 그리고 모바일에서는 페이지네이션이 따로 없어서 api 호출 시 pageable 부분인 page와 size를 필수로 넣어서 호출해야하는지 궁금합니다 (스웨거에는 required라고 표시되어 있어서요)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한번에 모든 데이터 배열을 받아올 수 있는지 백엔드에 질문이 필요할 듯 합니다..🥹
아님 다른 페이지처럼 무한 스크롤 처리를 하는 방식도 있겠네요..!!
| const [visibleReply, setVisibleReply] = useState<number>(10); | ||
| const [showReply, setShowReply] = useState(false); | ||
| const [replyValue, setReplyValue] = useState(''); | ||
|
|
||
| const handleReplyToggle = () => { | ||
| setShowReply(!showReply); | ||
| setVisibleReply(10); | ||
| }; | ||
|
|
||
| const { mutate: createReply } = useCreateReplyMutation( | ||
| comment.talkPickId, | ||
| comment.id, | ||
| selectedPage, | ||
| ); | ||
|
|
||
| const handleReplyButton = () => { | ||
| createReply({ content: replyValue }); | ||
| setReplyValue(''); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위 답글 관련 기능은 해당 컴포넌트에서 사용되는 건가요??? 답글 쓰기에 답글이 보여지는 함수가 연결된 것 같아요..!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아뇨 추후에 사용될거 같아서 추가해둔건데 수정하였습니다!
| const handleEditComment = (commentId: number, content: string) => { | ||
| setEditCommentId(commentId); | ||
| setEditCommentText(content); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
댓글 수정도 생성과 같은 input 컴포넌트를 사용하는 것 같은데 한번 댓글 수정을 눌렀을 땐 댓글 수정을 완료하기 전까진 댓글 생성 플로우로 돌아갈 수 없는 건가요?!??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 애초에 모바일에서 하단에 답글 수정 및 생성 관련 input 바가 고정으로 존재하는 거라 수정과 생성 모두 같은 컴포넌트에서 진행됩니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
피그마 확인해보니 댓글 수정 중일 때 텍스트 필드 외의 영역을 클릭하면 수정 모드가 해제된다고 되어있던데 이 부분도 구현된 상태인건지 궁금합니다,!! 안된 상태라면 useOutsideClick 훅을 사용하여 구현하면 편하고 좋을 것 같아요!!!
| label: '삭제', | ||
| onClick: () => { | ||
| setActiveModal('deleteText'); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
댓글 삭제 플로우 누락인걸까요!?? 😶
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
알고보니 MobileReportModal이 디자인이 생성되었더라구요..!! 그래서 추가 구현 완료하였습니다! 3d054b5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx (1)
86-89: 🛠️ Refactor suggestion댓글 삭제 확인 과정 누락
댓글 삭제 시 사용자 확인 과정이 없습니다. 중요한 작업인 삭제는 실수로 클릭했을 때를 대비해 확인 과정이 필요합니다.
{ label: '삭제', - onClick: () => handleDelete(), + onClick: () => { + if (window.confirm('댓글을 삭제하시겠습니까?')) { + handleDelete(); + } + }, },
🧹 Nitpick comments (6)
src/components/mobile/molecules/MobileReportModal/MobileReportModal.tsx (2)
1-77: 신고 모달 기능이 잘 구현되었으나 접근성 개선이 필요합니다.신고 사유 선택 및 제출 기능이 잘 구현되었습니다. 하지만 접근성 측면에서 개선이 필요합니다:
- 버튼에 aria 속성이 없어 스크린 리더 사용자를 위한 접근성이 부족합니다.
- 사유 선택 버튼들이 라디오 버튼 그룹으로 구현되면 더 의미론적으로 올바를 것 같습니다.
다음과 같이 수정하는 것을 권장합니다:
- <div css={S.getButtonContainer(reportReason === '기타')}> - {reportOptions.map((option) => ( - <button - type="button" - key={option.value} - css={S.reasonButton} - value={option.value} - onClick={handleReportReason} - > - {option.label} - </button> - ))} - </div> + <fieldset css={S.getButtonContainer(reportReason === '기타')}> + <legend css={S.srOnly}>신고 사유 선택</legend> + {reportOptions.map((option) => ( + <button + type="button" + key={option.value} + css={[S.reasonButton, reportReason === option.value && S.selectedButton]} + value={option.value} + onClick={handleReportReason} + aria-pressed={reportReason === option.value} + > + {option.label} + </button> + ))} + </fieldset>또한 '기타' 입력 필드에 레이블을 추가하는 것이 좋습니다:
{reportReason === '기타' && ( + <label htmlFor="otherReasonInput" css={S.srOnly}> + 기타 신고 사유 + </label> <input + id="otherReasonInput" css={S.reportInputStyling} placeholder="신고사유를 입력해주세요." onChange={handleOtherReportReason} + aria-required="true" /> )}스타일 파일에 시각적으로만 숨겨진 sr-only 클래스도 추가해야 합니다.
31-34: handleConfirm에 사용자 피드백 추가 필요사용자가 신고를 제출했을 때 피드백이 부족합니다. 신고가 성공적으로 제출되었다는 알림이나 피드백이 있으면 좋을 것 같습니다.
const handleConfirm = () => { if (!finalReportReason.trim()) return; onConfirm?.(finalReportReason); + // 신고가 성공적으로 제출되었다는 알림 표시 + // 예: showToastModal('신고가 성공적으로 제출되었습니다.'); };src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx (2)
74-77: 신고 처리 후 사용자 피드백 부족신고 처리 후 사용자에게 피드백을 주는 로직이 부족합니다. 신고가 성공적으로 처리되었다는 정보를 사용자에게 제공하는 것이 좋습니다.
const handleReportCommentButton = (reason: string) => { handleReport(reason); closeReportModal(); + showToastModal('신고가 접수되었습니다.'); };
52-54: isTalkPickWriter의 초기값 설정 필요isTalkPickWriter 계산 시 talkPickWriter가 undefined일 경우 예기치 않은 동작이 발생할 수 있습니다. 안전한 접근을 위해 초기값 처리가 필요합니다.
const isTalkPickWriter = useMemo(() => { - return comment?.nickname === talkPickWriter; + return !!comment?.nickname && !!talkPickWriter && comment.nickname === talkPickWriter; }, [comment?.nickname, talkPickWriter]);src/components/mobile/molecules/MobileReportModal/MobileReportModal.style.ts (2)
20-20: 불필요한 빈 객체가 있습니다.타이포그래피 스타일을 적용할 때 빈 객체
{}를 추가로 전달하는 것은 불필요합니다.다음과 같이 간소화할 수 있습니다:
-export const titleStyle = css(typo.Main.SemiBold, {}); +export const titleStyle = css(typo.Main.SemiBold);
57-68: 제출 버튼의 고정 패딩 값이 반응형에 제한을 줄 수 있습니다.
padding: '9px 118.5px'와 같은 고정 값은 다양한 화면 크기에서 일관된 모습을 보장하지 못할 수 있습니다.좀 더 반응형 접근 방식을 고려해보세요:
- padding: '9px 118.5px', + padding: '9px 0', + height: '44px', /* 필요한 경우 고정 높이 지정 */너비가 이미 100%로 설정되어 있으므로, 좌우 패딩 대신 텍스트를 중앙에 배치하는 것이 더 유연한 레이아웃에 도움이 됩니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/svg/mobile-report.svgis excluded by!**/*.svg
📒 Files selected for processing (8)
src/assets/index.ts(1 hunks)src/components/mobile/atoms/Modal/Modal.style.ts(1 hunks)src/components/mobile/atoms/Modal/Modal.tsx(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts(1 hunks)src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx(1 hunks)src/components/mobile/molecules/MobileReportModal/MobileReportModal.style.ts(1 hunks)src/components/mobile/molecules/MobileReportModal/MobileReportModal.tsx(1 hunks)src/stories/mobile/molecules/MobileReportModal.stories.tsx(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/stories/mobile/molecules/MobileReportModal.stories.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/assets/index.ts
- src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.style.ts
🔇 Additional comments (11)
src/components/mobile/atoms/Modal/Modal.style.ts (1)
30-32: 새로운 report 모달 스타일 적절하게 추가되었습니다.코드 구조에 맞게 report 액션에 대한 스타일이 잘 추가되었습니다. 패딩 값이 다른 모달 스타일과 일관성 있게 적용되었습니다.
src/components/mobile/atoms/Modal/Modal.tsx (1)
8-8: Modal 컴포넌트 action 속성에 report 옵션 추가 확인.ModalProps 인터페이스에 'report' 옵션이 추가되어 이전 파일에서 추가된 스타일과 일치하는 것을 확인했습니다.
src/components/mobile/molecules/MobileCommentItem/MobileCommentItem.tsx (3)
7-8: 미사용 코드(주석 처리된 임포트) 검토 필요현재 주석 처리된 답글 관련 기능 임포트가 있습니다. 이는 아직 개발되지 않은 기능을 위한 것으로 보입니다. PR이 완료되면 이러한 주석 처리된 코드에 대한 계획을 명확히 하는 것이 좋습니다.
향후 릴리스에서 답글 기능을 구현할 계획이 있는지 확인해주세요. 계획이 없다면 이 주석 처리된 코드를 제거하는 것이 코드 가독성을 높일 수 있습니다.
48-50: 메모이제이션 로직이 적절합니다.사용자 정보를 기반으로 한
isMyComment계산에 useMemo를 사용한 것은 좋은 최적화입니다. 의존성 배열도 적절하게 설정되어 있습니다.
144-157: 미완성 답글 기능 UI 이슈해당 PR 설명에 따르면 답글 기능은 아직 개발되지 않았다고 했는데, 현재 코드에서는 답글 버튼이 활성화되어 있고
handleReplyToggle함수와 연결되어 있습니다. 하지만 상태 변경 후 실제로 보여지는 UI 요소가 없어 사용자에게 혼란을 줄 수 있습니다.- <button - type="button" - css={S.replyButton} - onClick={handleReplyToggle} - > - {comment.replyCount === 0 ? ( - '답글쓰기' - ) : ( - <> - <MobileComment /> - <span>{comment.replyCount}</span> - </> - )} - </button> + {/* TODO: 답글 기능 구현 시 활성화 */} + <div css={S.replyButtonPlaceholder}> + {comment.replyCount > 0 && ( + <> + <MobileComment /> + <span>{comment.replyCount}</span> + </> + )} + </div>또는 아예 댓글 수만 표시하고 클릭 기능을 제거하는 방법도 고려해볼 수 있습니다.
src/components/mobile/molecules/MobileReportModal/MobileReportModal.style.ts (6)
1-4: 필요한 의존성이 적절히 임포트되어 있습니다.임포트된 모듈들이 명확하게 구성되어 있으며, 스타일링에 필요한 필수 요소들이 포함되어 있습니다.
5-10: 모달의 기본 레이아웃이 적절히 설정되었습니다.플렉스 레이아웃을 사용하여 모바일 화면에 적합한 세로 방향 및 중앙 정렬을 구현했습니다.
12-18: 타이틀 컨테이너 스타일이 잘 구성되어 있습니다.플렉스 레이아웃을 사용한 중앙 정렬과 적절한 간격 설정이 되어 있습니다.
22-28: 동적 스타일링이 잘 구현되어 있습니다.입력 필드의 가시성에 따라 마진이 달라지는 조건부 스타일링이 적절히 구현되어 있습니다.
46-55: 입력 필드 스타일이 명확하게 정의되어 있습니다.너비, 패딩, 테두리 등의 스타일과 함께 플레이스홀더 색상도 적절히 설정되어 있습니다.
30-44:Details
❓ Verification inconclusive
버튼 스타일이 상호작용에 따라 적절히 변경됩니다.
호버 시 배경색, 테두리, 텍스트 색상이 변경되는 효과가 잘 구현되어 있습니다.
다만, 색상 대비가 접근성 기준을 충족하는지 확인하는 것이 좋습니다.
🌐 Web query:
웹 접근성 WCAG 2.1 AA 레벨에서 요구하는 텍스트 색상 대비 기준은 무엇인가요?💡 Result:
웹 접근성 지침인 WCAG 2.1 AA 레벨에서 요구하는 텍스트 색상 대비 기준은 다음과 같이 명시되어 있습니다.
일반 텍스트 기준
일반 텍스트(18pt 미만 또는 14pt 미만 굵은 글씨)의 경우 최소 4.5:1 명암비가 요구됩니다. 이는 시력이 약한 사용자나 색각 이상자가 콘텐츠를 명확히 인식할 수 있도록 보장하기 위한 최소 기준입니다[1][2][6].
예를 들어 흰색 배경(#FFFFFF)에 회색 계열 텍스트를 사용할 경우, AA 기준을 충족하려면 Gray-600 이상 색상이 필요합니다[1][5].
큰 텍스트 기준
다음 조건을 충족하는 텍스트는 최소 3:1 명암비를 적용할 수 있습니다:
- 18pt 이상 크기의 일반 텍스트
- 14pt 이상 굵은(Bold) 텍스트[3][4][6]
이 기준은 텍스트가 클수록 가독성이 높아지기 때문에 완화된 수치를 적용한 것으로, Material Design 등 주요 디자인 시스템에서도 동일한 원칙을 사용합니다[1][3].
예외 사항
- 로고 타입: 브랜드 로고의 텍스트는 대비 기준 적용에서 제외됩니다[9].
- 비활성 텍스트(Gray-500 등): 플레이스홀더나 비활성화된 텍스트는 4.5:1 미만 대비 사용이 허용되지만, 기능적 비활성 상태를 명확히 표시해야 합니다[1][5].
검증 도구
명암비 검증을 위해 Coolors Contrast Checker나 AccessibleWeb Color Contrast Checker 같은 도구를 활용할 수 있으며, 이들 도구는 WCAG AA/AAA 기준에 대한 Pass/Fail 결과를 즉시 확인할 수 있게 해줍니다[1][2].
이 기준들은 20/40 시력을 가진 사용자의 인식 능력을 보상하기 위해 수학적 계산(L*=0.2126R + 0.7152G + 0.0722*B)으로 도출되었으며, 전 세계 2억 2천만 명 이상의 시각 장애인이 웹 콘텐츠에 접근할 수 있도록 지원합니다[2][6].
Citations:
- 1: https://brunch.co.kr/@somgs34/18
- 2: https://web.dev/articles/color-and-contrast-accessibility?hl=ko
- 3: https://brunch.co.kr/@chulhochoiucj0/25
- 4: https://yozm.wishket.com/magazine/detail/2901/
- 5: https://yozm.wishket.com/magazine/detail/2921/
- 6: https://www.remain.co.kr/page/designsystem/font-color.php
- 7: https://blog.naver.com/evaham/30188101101?viewType=pc
- 8: https://nuli.navercorp.com/community/article/1132926
- 9: https://a11y.gitbook.io/wcag/1-perceivable/1.4-distinguishable
- 10: https://nuli.navercorp.com/community/article/1132886
버튼 호버 효과 및 접근성 대비 검토 요청
현재 버튼의 호버 상태에서 배경색, 테두리, 텍스트 색상이 의도대로 변경되어 인터랙션 효과가 잘 구현되어 있습니다.
다만, 사용 중인 색상들이 웹 접근성 WCAG 2.1 AA 기준을 충족하는지 확인해 주시기 바랍니다.
구체적으로, 일반 텍스트의 경우 최소 명암비는 4.5:1, 큰 텍스트나 굵은 텍스트(18pt 미만 또는 14pt 미만 굵은)의 경우 3:1 이상의 명암비가 요구됩니다.
해당 기준에 맞게 색상 대비를 한 번 더 점검해 주시면 감사하겠습니다.
| {idx === 0 && totalElements === 1 && ( | ||
| <CategoryBarChip size="extraSmall">첫댓글</CategoryBarChip> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
첫 댓글 확인 로직 개선 필요
현재 첫 댓글 체크 로직은 idx === 0 && totalElements === 1로 되어 있어, 댓글이 딱 하나만 있을 때만 첫 댓글로 표시됩니다. 댓글 수와 상관없이 첫 번째 댓글임을 표시하려면 idx === 0만 확인하는 것이 맞습니다.
- {idx === 0 && totalElements === 1 && (
+ {idx === 0 && (
<CategoryBarChip size="extraSmall">첫댓글</CategoryBarChip>
)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {idx === 0 && totalElements === 1 && ( | |
| <CategoryBarChip size="extraSmall">첫댓글</CategoryBarChip> | |
| )} | |
| {idx === 0 && ( | |
| <CategoryBarChip size="extraSmall">첫댓글</CategoryBarChip> | |
| )} |
| const isLongText = comment?.content.length > COMMENT.MAX_SHORTEN_LENGTH; | ||
| const shortenContent = isExpanded | ||
| ? comment?.content | ||
| : `${comment?.content.slice(0, COMMENT.MAX_SHORTEN_LENGTH)}...`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
텍스트 처리에 안전성 개선 필요
comment.content가 null이거나 undefined일 경우 런타임 오류가 발생할 수 있습니다. 안전한 접근을 위해 옵셔널 체이닝과 기본값 처리가 필요합니다.
- const isLongText = comment?.content.length > COMMENT.MAX_SHORTEN_LENGTH;
- const shortenContent = isExpanded
- ? comment?.content
- : `${comment?.content.slice(0, COMMENT.MAX_SHORTEN_LENGTH)}...`;
+ const contentText = comment?.content || '';
+ const isLongText = contentText.length > COMMENT.MAX_SHORTEN_LENGTH;
+ const shortenContent = isExpanded
+ ? contentText
+ : isLongText ? `${contentText.slice(0, COMMENT.MAX_SHORTEN_LENGTH)}...` : contentText;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const isLongText = comment?.content.length > COMMENT.MAX_SHORTEN_LENGTH; | |
| const shortenContent = isExpanded | |
| ? comment?.content | |
| : `${comment?.content.slice(0, COMMENT.MAX_SHORTEN_LENGTH)}...`; | |
| const contentText = comment?.content || ''; | |
| const isLongText = contentText.length > COMMENT.MAX_SHORTEN_LENGTH; | |
| const shortenContent = isExpanded | |
| ? contentText | |
| : isLongText ? `${contentText.slice(0, COMMENT.MAX_SHORTEN_LENGTH)}...` : contentText; |
| const { handleDelete, handleLikeToggle, handleReport } = useCommentActions( | ||
| comment, | ||
| comment.content, | ||
| selectedPage, | ||
| () => {}, | ||
| showToastModal, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
useCommentActions 콜백 함수 개선 필요
useCommentActions 훅에 빈 콜백 함수(() => {})를 전달하고 있습니다. 이는 좋은 패턴이 아니며, 명확한 목적을 가진 함수를 전달하거나 필요한 경우 인터페이스를 수정하는 것이 좋습니다.
const { handleDelete, handleLikeToggle, handleReport } = useCommentActions(
comment,
comment.content,
selectedPage,
- () => {},
+ // 이 콜백의 목적에 맞는 함수를 구현하거나
+ // 필요 없다면 useCommentActions 훅을 수정하여 옵셔널하게 만드는 것이 좋습니다
showToastModal,
);Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/svg/mobile-report.svgis excluded by!**/*.svg
📒 Files selected for processing (3)
src/App.tsx(2 hunks)src/assets/index.ts(1 hunks)src/constants/message.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/App.tsx
- src/constants/message.ts
🧰 Additional context used
🪛 ESLint
src/assets/index.ts
[error] 160-160: Multiple exports of name 'MobileReport'.
(import/export)
🪛 GitHub Actions: build
src/assets/index.ts
[error] 161-161: Module parse failed: Duplicate export 'MobileReport'.
🔇 Additional comments (1)
src/assets/index.ts (1)
154-159: 모바일 UI 구현을 위한 새로운 아이콘 추가가 잘 되었습니다.PR 목표와 일치하는 모바일 댓글 UI 구현을 위한 필요한 아이콘들이 적절하게 추가되었습니다. 특히
MobileComment,MobileLikeButtonDF,MobileLikeButtonPR등은 모바일 환경에서 사용자 상호작용 개선에 도움이 될 것입니다.
src/assets/index.ts
Outdated
| export { default as MobileReport } from './svg/mobile-report.svg'; | ||
| export { default as MobileReport } from './svg/mobile-report.svg'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중복된 MobileReport 익스포트를 수정해야 합니다.
동일한 이름의 MobileReport 익스포트가 두 번 선언되었습니다. 이로 인해 빌드 파이프라인 오류가 발생하고 있습니다.
다음과 같이 중복된 익스포트를 제거해주세요:
export { default as MobileReport } from './svg/mobile-report.svg';
-export { default as MobileReport } from './svg/mobile-report.svg';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export { default as MobileReport } from './svg/mobile-report.svg'; | |
| export { default as MobileReport } from './svg/mobile-report.svg'; | |
| export { default as MobileReport } from './svg/mobile-report.svg'; |
🧰 Tools
🪛 ESLint
[error] 160-160: Multiple exports of name 'MobileReport'.
(import/export)
[error] 161-161: Multiple exports of name 'MobileReport'.
(import/export)
🪛 GitHub Actions: build
[error] 161-161: Module parse failed: Duplicate export 'MobileReport'.
|




💡 작업 내용
💡 자세한 설명
✅ MobileCommentItem, MobileCommentSection
구현 화면: 댓글 1개일 때 및 답글 없을 때 | 댓글 1개일 때 | 댓글이 없을 때 | 작성자일 때✅ CommentInput
position: fixed로 인해 세가지 상태가 모두 보이지 않아 하단에 사진을 첨부하겠습니다.구현 화면: 스토리북에 구현된 세가지 상태 | 실제 페이지에 적용된 상태📗 참고 자료 (선택)
📢 리뷰 요구 사항 (선택)
🚩 후속 작업 (선택)
✅ 셀프 체크리스트
closes #300
Summary by CodeRabbit
COMMENT가 추가되어 댓글 관련 메시지가 향상되었습니다.