-
Notifications
You must be signed in to change notification settings - Fork 0
question, questionList 컴포넌트 수정 #256
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
Conversation
Walkthrough문제 목록 UI를 새로운 Question 컴포넌트와 listbox 접근성 패턴으로 재구성하고, GameCreate 컴포넌트를 shared 디자인 라이브러리에서 로컬 엔티티로 이동했습니다. 테스트 셀렉터를 role 기반으로 업데이트하고 keyboard 네비게이션 훅을 추가했습니다. Changes
Sequence DiagramsequenceDiagram
participant User
participant QuestionList
participant Question
participant useListboxNavigation
participant Store as useCreateGameStore
User->>QuestionList: 포커스 (onFocus)
activate QuestionList
QuestionList->>useListboxNavigation: onListboxFocus 호출
activate useListboxNavigation
useListboxNavigation->>Question: 선택된 옵션 또는 첫 옵션으로 포커스
deactivate useListboxNavigation
deactivate QuestionList
User->>Question: ArrowDown/Up 키 입력
activate Question
Question->>useListboxNavigation: onOptionKeyDown 호출
activate useListboxNavigation
useListboxNavigation->>Question: 다음/이전 옵션으로 포커스 이동
deactivate useListboxNavigation
deactivate Question
User->>Question: Enter/Space 또는 클릭
activate Question
Question->>Store: 질문 선택 상태 업데이트
activate Store
Store-->>Question: 상태 반영
deactivate Store
deactivate Question
User->>Question: 삭제/이동 액션 버튼 클릭
activate Question
Question->>Store: deleteQuestion / moveQuestion 호출
activate Store
Store->>Store: 경계 체크 수행
Store-->>QuestionList: 업데이트된 질문 목록
deactivate Store
deactivate Question
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (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. Comment |
|
Deploy preview for re-creation ready! ✅ Preview Built with commit 257a641. |
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: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
service/app/src/entities/game/ui/gameLibraryGrid.tsx (1)
66-68: 중첩된 Link 컴포넌트 제거 필요
GameCreate는 이미/create로 이동하는Link컴포넌트입니다. 이를 다시Link로 감싸면 중첩된<a>태그가 생성되어 HTML 유효성 검증 실패 및 접근성 문제가 발생합니다.🔎 수정 제안
- <Link href="/create" prefetch={true}> - <GameCreate /> - </Link> + <GameCreate />또는
GameCreate가 단순 div로 변경되어야 한다면, 외부 Link만 유지:<Link href="/create" prefetch={true}> - <GameCreate /> + {/* GameCreate를 링크가 아닌 콘텐츠 컴포넌트로 변경 필요 */} </Link>service/app/src/app/dashboard/components/dashboardGameSection.tsx (1)
47-49: 중복된 클릭 핸들러 제거 필수
GameCreate컴포넌트는 이미/create로 이동하는Link요소이고,handleCreateGame도router.push("/create")만 수행합니다. 외부div의onClick={handleCreateGame}은 동일한 네비게이션을 중복으로 처리하고 있어 불필요합니다.
div와Link요소를 중첩하면 접근성 위반이 발생합니다:
- 스크린 리더가 두 개의 인터랙티브 요소로 인식
- 의미론적으로 올바르지 않은 HTML 구조
- 사용자 혼란 유발 가능
해결: 래퍼
div를 제거하고GameCreate컴포넌트만 사용하거나, 추가 로직이 필요하면GameCreate내부에 통합하세요.
🧹 Nitpick comments (4)
service/app/src/entities/game/ui/gameCreate.tsx (2)
11-11:cn유틸리티 사용 권장코드베이스의 다른 컴포넌트들(예:
gameCard.tsx)은 클래스명 결합에cn유틸리티를 사용하고 있습니다. 일관성을 위해 템플릿 리터럴 대신cn을 사용하는 것을 권장합니다.🔎 제안된 리팩터링
+import { cn } from "@ject-5-fe/design/utils/cn" import Image from "next/image" import Link from "next/link" interface GameCreateProps { className?: string } export const GameCreate = ({ className = "" }: GameCreateProps) => { return ( <Link - className={`flex w-full flex-col items-center gap-12 ${className}`} + className={cn("flex w-full flex-col items-center gap-12", className)} href="/create" aria-label="게임 만들기" >
15-21: Image 크기 속성 일관성 확인 필요
width={178}와height={178}속성을 지정하면서 동시에className="size-full"을 사용하고 있습니다.size-full은width: 100%; height: 100%를 의미하므로 고정 크기와 충돌할 수 있습니다. Next.js Image의fill속성 사용 또는 고정 크기만 사용하는 것이 명확합니다.🔎 제안된 수정 방안
방안 1: 고정 크기 유지 (현재 의도가 178px 고정인 경우)
<Image src="/create-game-icon.svg" alt="게임 만들기 아이콘" width={178} height={178} - className="size-full object-contain" + className="object-contain" />방안 2: 부모 컨테이너 크기에 맞추기 (반응형이 필요한 경우)
<Image src="/create-game-icon.svg" alt="게임 만들기 아이콘" - width={178} - height={178} - className="size-full object-contain" + fill + className="object-contain" />service/app/src/shared/lib/useListBoxNavigation.ts (1)
21-50: 키보드 네비게이션 구현 우수ARIA listbox 패턴을 잘 따르는 키보드 핸들러입니다:
- ArrowDown/Up으로 옵션 간 이동
- Enter/Space로 선택
- Escape로 포커스 복원
- 자식 요소 이벤트 필터링
단,
onSelect매개변수가UseListboxNavigationParams인터페이스에 포함되지 않아 문서화가 부족합니다. 옵션별 콜백 지원이 의도된 것이라면 JSDoc 주석 추가를 고려하세요.🔎 문서화 개선 제안
+/** + * Keyboard navigation handler for listbox options. + * @param e - Keyboard event + * @param onSelect - Optional callback invoked when Enter or Space is pressed + */ const onOptionKeyDown = ( e: React.KeyboardEvent<HTMLElement>, onSelect?: () => void, ) => {service/app/src/app/create/components/question.tsx (1)
59-66: 제목 렌더링 및 에러 표시 우수에러 시 전체 텍스트 표시와 ❗ 이모지 추가, 정상 시
line-clamp-1적용이 잘 구현되었습니다.break-keep은 한국어 텍스트에 적합합니다.단, Line 62의 클래스명에 약간의 중복이 있습니다:
🔎 클래스명 정리 제안
line-clamp-1은 Tailwind에서 이미overflow-hidden과text-ellipsis를 포함하므로 중복 제거 가능:className={cn( "typography-heading-sm-medium w-full min-w-0 break-keep text-text-primary", - shouldClampTitle && "line-clamp-1 overflow-hidden text-ellipsis", + shouldClampTitle && "line-clamp-1", )}
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
service/app/src/app/create/__tests__/createPOM.tsservice/app/src/app/create/components/question.tsxservice/app/src/app/create/components/questionList.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/shared/SSRSafeSuspense.tsxservice/app/src/shared/lib/useListBoxNavigation.tsshared/design/src/components/gameCreate/index.tsxshared/design/src/stories/gameCreate.stories.tsx
💤 Files with no reviewable changes (3)
- shared/design/src/stories/gameCreate.stories.tsx
- service/app/src/shared/SSRSafeSuspense.tsx
- shared/design/src/components/gameCreate/index.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
All file names must be camelCase (not PascalCase or kebab-case)
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/shared/lib/useListBoxNavigation.tsservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/__tests__/createPOM.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Component names must use PascalCase in the code (while file names use camelCase)
Use Zustand for client-side state management
Use TanStack Query for server state and API caching
Use async/await for promises instead of .then() chains
Use custom fetch client with interceptors from shared/lib/fetchClient.ts for API requests
**/*.{ts,tsx}: Use TypeScript with ES modules; keep files lower-case kebab or camel per existing folder conventions (e.g.,gameLibraryGrid.tsx)
Favor explicit domain names (e.g.,GameCardOptions,useInfiniteMyGames) and colocate UI, API, and hooks within eachentities/<domain>module
Run ESLint using lint scripts; linting is enforced via lint-staged
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/shared/lib/useListBoxNavigation.tsservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/__tests__/createPOM.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use shared ESLint configuration across all workspaces
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/shared/lib/useListBoxNavigation.tsservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/__tests__/createPOM.ts
**/*.{js,ts,jsx,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Prettier with Tailwind CSS plugin for formatting
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/shared/lib/useListBoxNavigation.tsservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/__tests__/createPOM.ts
**/*.{ts,tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation throughout the codebase
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/shared/lib/useListBoxNavigation.tsservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/__tests__/createPOM.ts
**/*.{ts,tsx,css,json,md}
📄 CodeRabbit inference engine (AGENTS.md)
Apply Prettier formatting using
yarn workspace <pkg> format; this is enforced via lint-staged
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/shared/lib/useListBoxNavigation.tsservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/__tests__/createPOM.ts
**/src/entities/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
In the Next.js app workspace, place feature logic under
src/entitiesand colocate Vitest specs in feature folders
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Tailwind CSS utilities should be ordered via the Prettier Tailwind plugin; avoid inline style objects unless necessary
Files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/app/create/components/questionList.tsx
**/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
In the Next.js app workspace, UI routes live in
src/app
Files:
service/app/src/app/dashboard/components/dashboardGameSection.tsxservice/app/src/app/create/components/question.tsxservice/app/src/app/create/components/questionList.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-27T13:46:12.950Z
Learnt from: CR
Repo: JECT-Study/JECT-5-FE PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T13:46:12.950Z
Learning: Applies to **/*.{ts,tsx} : Favor explicit domain names (e.g., `GameCardOptions`, `useInfiniteMyGames`) and colocate UI, API, and hooks within each `entities/<domain>` module
Applied to files:
service/app/src/entities/game/ui/gameCreate.tsxservice/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsx
📚 Learning: 2025-11-27T13:46:12.950Z
Learnt from: CR
Repo: JECT-Study/JECT-5-FE PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T13:46:12.950Z
Learning: Applies to **/src/entities/**/*.tsx : In the Next.js app workspace, place feature logic under `src/entities` and colocate Vitest specs in feature folders
Applied to files:
service/app/src/entities/game/ui/gameCreate.tsx
📚 Learning: 2025-11-27T13:46:12.950Z
Learnt from: CR
Repo: JECT-Study/JECT-5-FE PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T13:46:12.950Z
Learning: Applies to **/src/components/**/*.{ts,tsx} : In `shared/design` package, define reusable UI components in `src/components` and use them in the app workspace instead of redefining widgets
Applied to files:
service/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsx
📚 Learning: 2025-11-27T13:46:12.950Z
Learnt from: CR
Repo: JECT-Study/JECT-5-FE PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T13:46:12.950Z
Learning: Applies to **/src/icons/**/*.{ts,tsx} : In `shared/design` package, define reusable icons in `src/icons` and import from this shared package instead of duplicating icon definitions
Applied to files:
service/app/src/entities/game/ui/gameLibraryGrid.tsxservice/app/src/app/dashboard/components/dashboardGameSection.tsx
🧬 Code graph analysis (2)
service/app/src/entities/game/ui/gameCreate.tsx (1)
service/app/src/entities/game/ui/GameCard/gameCard.tsx (1)
Image(45-74)
service/app/src/app/create/components/questionList.tsx (2)
service/app/src/shared/lib/useListBoxNavigation.ts (1)
useListboxNavigation(7-56)service/app/src/app/create/components/question.tsx (1)
Question(26-116)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci
🔇 Additional comments (15)
service/app/src/app/dashboard/components/dashboardGameSection.tsx (1)
9-9: 컴포넌트 임포트 경로 변경 확인 완료
GameCreate컴포넌트를 외부 디자인 라이브러리에서 내부 엔티티 경로로 이동한 것이 PR 목표와 일치합니다.service/app/src/entities/game/ui/gameLibraryGrid.tsx (1)
10-10: 컴포넌트 임포트 경로 변경 확인 완료내부 엔티티 경로로의 임포트 변경이 올바르게 적용되었습니다.
service/app/src/app/create/__tests__/createPOM.ts (2)
52-52: 접근성 기반 셀렉터로 개선
data-testid에서role="listbox"기반 셀렉터로 변경하여 실제 사용자 경험과 접근성을 반영하는 테스트가 되었습니다.
75-77: ARIA 역할 기반 셀렉터 개선
role="group"에서role="option"으로 변경하여 ARIA listbox 패턴을 올바르게 반영합니다. 새로운Question컴포넌트의 구조와 일치합니다.service/app/src/app/create/components/questionList.tsx (4)
5-17: listbox 네비게이션 훅 통합 우수
useListboxNavigation훅을 올바르게 통합하여 키보드 네비게이션과 포커스 관리를 구현했습니다. 접근성 개선을 위한 좋은 아키텍처 선택입니다.
37-45: ARIA listbox 구현 우수
role="listbox",aria-label,tabIndex={0},onFocus핸들러를 사용하여 완전한 키보드 접근 가능한 listbox를 구현했습니다. ARIA 모범 사례를 잘 따르고 있습니다.
50-50: 이미지 소스 폴백 체인 우수
imageUrl || previewImageUrl || null폴백 체인은 다양한 이미지 상태를 안전하게 처리합니다.
53-68: Question 컴포넌트 props 구조 우수각 Question에 필요한 모든 속성(index, isSelected, hasError, 이미지, 액션 등)을 명확하게 전달하고 있습니다. 잘 구조화된 컴포넌트 인터페이스입니다.
service/app/src/shared/lib/useListBoxNavigation.ts (2)
10-19: 포커스 관리 로직 우수선택된 옵션(aria-selected="true")이나 첫 번째 옵션으로 포커스를 이동하는 로직이 올바르게 구현되었습니다.
currentTarget체크로 이벤트 버블링을 적절히 처리합니다.
29-37: DOM 탐색 방식 검증 - 현재 구조에서 안전함
nextElementSibling과previousElementSibling을 사용한 옵션 간 이동은 현재QuestionList구조에서 정상 작동합니다.questions.map()으로 렌더링된 각Question컴포넌트가 listbox div의 직접 자식 요소로 형제 관계를 이루고 있기 때문입니다. 다만 향후 옵션 요소들을 추가 컨테이너로 감싸는 구조로 리팩터링할 경우, 이 방식은 실패할 수 있으니 주의가 필요합니다.service/app/src/app/create/components/question.tsx (5)
9-24: Props 인터페이스 설계 우수선택적 props와 액션 객체를 사용한 유연한 인터페이스 설계가 잘 되어 있습니다. 타입 안정성과 사용성을 모두 고려한 구조입니다.
37-38: 파생 값 로직 명확함
canDelete기본값과shouldClampTitle로직이 명확합니다. 에러가 있을 때 전체 텍스트를 표시하는 것은 좋은 UX 결정입니다.
41-57: ARIA 옵션 구현 우수
role="option",aria-label,aria-selected,tabIndex={-1}을 사용한 완벽한 ARIA listbox option 구현입니다. 조건부 스타일링(에러/선택 상태)도 올바르게 우선순위가 지정되어 있습니다.
68-78: 삭제 버튼 구현 우수
stopPropagation으로 이벤트 전파를 막고,disabled상태와 접근성 레이블을 올바르게 구현했습니다.
82-90: 선택적 이미지 렌더링 우수
imageSrc존재 여부에 따른 조건부 렌더링과 일관된 스타일링이 잘 구현되어 있습니다.
| const questionError = | ||
| question.text.length > 50 || question.text.length < 1 |
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.
🧩 Analysis chain
🏁 Script executed:
# Search for question text length constraints and validation patterns
rg -nP --type=ts --type=tsx -C3 '(question.*text|text.*length).*(50|1|length|validation)' -g '!node_modules' | head -100Repository: JECT-Study/JECT-5-FE
Length of output: 91
🏁 Script executed:
# Also search for any constant definitions or type constraints
rg -nP --type=ts --type=tsx 'const.*50|MAX.*QUESTION|MIN.*QUESTION|length.*50' -g '!node_modules'Repository: JECT-Study/JECT-5-FE
Length of output: 91
🏁 Script executed:
# Search for server-side validation in API routes or validation files
rg -nP --type=ts 'question.*text.*50|length.*50|text.*validate' service/api | head -50Repository: JECT-Study/JECT-5-FE
Length of output: 115
🏁 Script executed:
# Look for the specific file and examine its context
fd -e tsx -e ts 'questionList' service/app/src/app/create/components/Repository: JECT-Study/JECT-5-FE
Length of output: 118
🏁 Script executed:
# Search for any constants or validation schemas
rg -nP 'QUESTION.*TEXT|QUESTION.*LENGTH|validation.*question' --type=ts --type=tsx -g '!node_modules' | head -50Repository: JECT-Study/JECT-5-FE
Length of output: 91
🏁 Script executed:
# Explore the repository structure
git ls-files | head -50Repository: JECT-Study/JECT-5-FE
Length of output: 1721
🏁 Script executed:
# Read the questionList.tsx file
cat -n service/app/src/app/create/components/questionList.tsxRepository: JECT-Study/JECT-5-FE
Length of output: 3373
🏁 Script executed:
# Search for validation patterns without file type restriction
rg 'length.*50|50.*length' -C3 | head -100Repository: JECT-Study/JECT-5-FE
Length of output: 2457
🏁 Script executed:
# Search for question validation in the entire codebase
rg 'question.*text.*50|text.*50' -C2 | head -100Repository: JECT-Study/JECT-5-FE
Length of output: 4303
🏁 Script executed:
# Look for server-side validation files
fd 'validator\|validation\|schema' service/Repository: JECT-Study/JECT-5-FE
Length of output: 46
클라이언트 검증의 중앙화 및 서버 검증 필요
질문 텍스트 길이 제약(1-50자)이 questionList.tsx, createGameNavigation.tsx, questionInputForm.tsx에 걸쳐 하드코딩되어 있습니다. 또한 일부는 trim() 사용 여부가 다르며, 답변 필드도 동일 제약을 공유합니다. 이 비즈니스 규칙을 다음과 같이 개선하세요:
- 길이 제약을 공유 상수로 정의 (예:
shared/lib/constants.ts) - 모든 클라이언트 검증에서 이 상수 사용
- 서버 측 검증 추가 필요 (현재 서버 측 유효성 검사 미발견)
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
♻️ Duplicate comments (1)
service/app/src/app/create/components/questionList.tsx (1)
48-49: 검증 상수 중앙화 필요 (이전 리뷰 지적사항)질문 텍스트 길이 제약(1-50자)이 여전히 하드코딩되어 있습니다. 이전 리뷰에서 이미 지적된 사항으로, 공유 상수로 정의하고 서버 측 검증도 추가해야 합니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
service/app/src/app/create/components/question.tsxservice/app/src/app/create/components/questionList.tsxservice/app/src/app/create/store/useCreateGameStore.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- service/app/src/app/create/components/question.tsx
🧰 Additional context used
📓 Path-based instructions (8)
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
All file names must be camelCase (not PascalCase or kebab-case)
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Component names must use PascalCase in the code (while file names use camelCase)
Use Zustand for client-side state management
Use TanStack Query for server state and API caching
Use async/await for promises instead of .then() chains
Use custom fetch client with interceptors from shared/lib/fetchClient.ts for API requests
**/*.{ts,tsx}: Use TypeScript with ES modules; keep files lower-case kebab or camel per existing folder conventions (e.g.,gameLibraryGrid.tsx)
Favor explicit domain names (e.g.,GameCardOptions,useInfiniteMyGames) and colocate UI, API, and hooks within eachentities/<domain>module
Run ESLint using lint scripts; linting is enforced via lint-staged
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use shared ESLint configuration across all workspaces
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/*.{js,ts,jsx,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Prettier with Tailwind CSS plugin for formatting
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/*.{ts,tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation throughout the codebase
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/*.{ts,tsx,css,json,md}
📄 CodeRabbit inference engine (AGENTS.md)
Apply Prettier formatting using
yarn workspace <pkg> format; this is enforced via lint-staged
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
In the Next.js app workspace, UI routes live in
src/app
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Tailwind CSS utilities should be ordered via the Prettier Tailwind plugin; avoid inline style objects unless necessary
Files:
service/app/src/app/create/store/useCreateGameStore.tsxservice/app/src/app/create/components/questionList.tsx
🧬 Code graph analysis (1)
service/app/src/app/create/components/questionList.tsx (2)
service/app/src/shared/lib/useListBoxNavigation.ts (1)
useListboxNavigation(7-56)service/app/src/app/create/components/question.tsx (1)
Question(28-122)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci
🔇 Additional comments (4)
service/app/src/app/create/store/useCreateGameStore.tsx (1)
78-85: 경계 검사 로직이 올바르게 구현되었습니다!
moveQuestion함수에 추가된 방어 코드가 적절합니다:
- 유효하지 않은 인덱스에 대한 조기 반환
- 배열 경계를 벗어나는 이동 방지
이는 UI에서 첫 번째/마지막 질문의 버튼을 비활성화하는 변경사항과 잘 연계됩니다.
service/app/src/app/create/components/questionList.tsx (3)
5-17: 접근성 개선을 위한 리팩토링이 잘 적용되었습니다.
useListboxNavigation훅과 새로운Question컴포넌트를 사용하여 키보드 탐색이 가능한 리스트박스 패턴을 적절히 도입했습니다.
37-45: 리스트박스 ARIA 속성이 올바르게 구현되었습니다.
role="listbox",aria-label,tabIndex={0}및 포커스 핸들러가 적절히 설정되어 스크린 리더 및 키보드 사용자에게 접근 가능한 인터페이스를 제공합니다.
63-70: 액션 버튼 비활성화 로직이 정확합니다.첫 번째/마지막 질문에서 이동 버튼을 비활성화하는 로직(
canMoveUp,canMoveDown)이useCreateGameStore의 경계 검사와 올바르게 연동됩니다. PR 목표와 일치합니다.
| onMoveUp: () => moveQuestion(question.id, "up"), | ||
| onMoveDown: () => moveQuestion(question.id, "down"), | ||
| }} | ||
| onKeyDown={onOptionKeyDown} |
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.
키보드로 질문 선택이 작동하지 않습니다.
onOptionKeyDown에 onSelect 콜백이 전달되지 않아, 키보드 사용자가 Enter 또는 Space 키로 질문을 선택할 수 없습니다. useListboxNavigation 훅의 구현을 보면 Enter/Space 입력 시 onSelect?.() 를 호출하는데, 현재는 undefined입니다.
🔎 제안하는 수정 방법
onKeyDown 핸들러를 래핑하여 onSelect 콜백을 전달하세요:
onKeyDown={onOptionKeyDown}
+ onKeyDown={(e) =>
+ onOptionKeyDown(e, () => setSelectedQuestionId(question.id))
+ }또는 더 명확하게:
+ onKeyDown={(e) => {
+ onOptionKeyDown(e, () => {
+ setSelectedQuestionId(question.id)
+ })
+ }}
- onKeyDown={onOptionKeyDown}🤖 Prompt for AI Agents
In service/app/src/app/create/components/questionList.tsx around line 71, the
onKeyDown handler is set to onOptionKeyDown without passing the onSelect
callback so keyboard Enter/Space cannot trigger selection; fix by wrapping the
handler to call onOptionKeyDown with the onSelect prop (or modify
onOptionKeyDown to accept and forward onSelect) — e.g. provide an inline
function that calls onOptionKeyDown(event, { onSelect }) or bind the onSelect
into the hook call so that when Enter/Space is detected it invokes the provided
onSelect callback.
| className={cn( | ||
| "flex min-h-[134px] w-full min-w-0 justify-between gap-16 rounded-8 bg-background-primary px-20 py-24", | ||
| onClick ? "cursor-pointer" : "cursor-default", | ||
| hasError && "ring-2 ring-inset ring-border-interactive-input-error", | ||
| !hasError && | ||
| isSelected && | ||
| "ring-2 ring-inset ring-border-interactive-primary", | ||
| className, | ||
| )} |
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.
cva 이용해서 정의해도 좋을것 같습니다
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.
지금 service/app에 cva가 따로 설치가 안되어 있어서 우선 cn만 썼는데 참고하겠습니다
| @@ -0,0 +1,56 @@ | |||
| import { RefObject } from "react" | |||
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.
지금 저 리스트를 키보드로 focus 이동하기위한 hook인가요? 그렇다면 roving-tabindex, roving-focus 같은 키워드로 한번 찾아보시면 좋을것 같습니다. 훨씬 깔끔하게 처리할 수 있을것같아요
https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/src/tabs.tsx
| <div | ||
| ref={listboxRef} | ||
| className="flex flex-col gap-24 overflow-y-auto" | ||
| role="listbox" | ||
| aria-label="문제 목록" | ||
| tabIndex={0} | ||
| onFocus={onListboxFocus} | ||
| > |
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.
목록이라면 div가 아니라 ui/li 태그로 처리해도 되지 않나요?
https://techblog.woowahan.com/24605/ 한번 참고해서 보이스오버로 의도한대로 읽히는지도 테스트한번 해보면 좋을것 같습니다
- ul 태그를 쓰더라도 css reset 처리를 해놨으면
role=list는 유지해야 한다고 하네요
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.
저는 ul/li는 좀 더 단순 텍스트 위주 요소에 붙는 게 맞다고 봅니다
지금은 카드형 ui + 그 자체로 인터랙션 컨테이너 역할을 해서 div가 적합하다고 생각해요
보이스오버 테스트는 곧 따로 진행할 예정입니다
📝 설명
question,questionList컴포넌트 관련 수정 요청사항을 처리하면서 일부 리팩토링, 접근성 개선을 진행했습니다.🛠️ 주요 변경 사항
수정 요청사항 반영
error일 때는ellipsis가 적용되지 않도록 수정statevariant를 없앴습니다기타 수정
question을 공통 컴포넌트에서 삭제questionList,question을 listbox - option 기반으로 처리리뷰 시 고려해야 할 사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
리팩토링
✏️ Tip: You can customize this high-level summary in your review settings.