Conversation
- 터치/마우스 드래그 지원 (Pointer Events API) - 관성 스크롤 및 snap 애니메이션 (RAF + Lerp) - loop 모드 (ghost 아이템 기반 무한 루프) - Controlled/Uncontrolled 모드 지원 - 키보드 접근성 (ArrowUp/Down, Home, End) - 드래그 중 React 리렌더링 없이 DOM 직접 조작
There was a problem hiding this comment.
Code Review
This pull request introduces a new WheelPicker component to the design system, featuring custom scroll and flick animations implemented via requestAnimationFrame for high performance. The implementation includes comprehensive state management for both controlled and uncontrolled modes, keyboard navigation support, and a looping feature. Review feedback highlights several areas for improvement to align with project standards, specifically regarding the use of absolute imports, constant type assertions (as const), and avoiding index-based keys in lists. Additionally, there are recommendations to enhance accessibility by correctly managing ARIA attributes such as aria-selected and aria-activedescendant within both the component and the performance-optimized DOM manipulation logic.
| <div | ||
| ref={containerRef} | ||
| className={clsx(container, disabled && containerDisabled)} | ||
| style={{ height: `${ITEM_HEIGHT * visibleCount}px` }} | ||
| role="listbox" | ||
| aria-orientation="vertical" | ||
| tabIndex={disabled ? -1 : 0} | ||
| {...handlers} | ||
| > |
There was a problem hiding this comment.
🔧 개선 제안
- 접근성 향상을 위해
aria-label과aria-activedescendant를 추가하는 것이 좋습니다. 현재 선택된 아이템의 ID를 추적하여 스크린 리더가 인식할 수 있게 합니다.
<div
ref={containerRef}
className={clsx(container, disabled && containerDisabled)}
style={{ height: `${ITEM_HEIGHT * visibleCount}px` }}
role="listbox"
aria-label="Wheel Picker"
aria-orientation="vertical"
aria-activedescendant={disabled ? undefined : `wheel-picker-item-${selectedIndex}`}
tabIndex={disabled ? -1 : 0}
{...handlers}
>
References
- 접근성 준수 (aria, semantic tag) (link)
| ))} | ||
| {renderedItems.map(({ item: pickerItem, realIndex, isGhost }, i) => ( | ||
| <div | ||
| key={`${isGhost ? 'ghost' : 'real'}-${i}`} |
There was a problem hiding this comment.
❌ 위반 사항
- [index key 금지] 리스트 렌더링 시 index를 key로 사용하는 것은 금지되어 있습니다.
renderedItems모델에서 고유한 key를 생성하여 전달받는 방식을 권장합니다.
References
- index key 금지 (link)
| @@ -0,0 +1,190 @@ | |||
| import { useRef, useCallback, useEffect } from 'react'; | |||
|
|
|||
| export const ITEM_HEIGHT = 44; | |||
There was a problem hiding this comment.
❌ 위반 사항
- [as const 사용] 상수는
as const를 사용하여 타입을 고정해야 합니다.
| export const ITEM_HEIGHT = 44; | |
| export const ITEM_HEIGHT = 44 as const; |
References
- as const 사용 (link)
🛠️ 변경 사항
세부 변경 내용
WheelPicker 디자인 시스템 컴포넌트를 추가했습니다. 스토리북에서 테스트 가능합니다.
파일 구조
애니메이션
scrollTo: Lerp(선형 보간)로 목표 위치까지 ease-out 감속 이동startFlick: 빠른 드래그 시 관성 스크롤 후 가장 가까운 아이템을 중앙에 배치 (매 프레임 velocity * 0.92 감쇠)터치/드래그
setPointerCapture로 영역 밖 드래그 유지성능 최적화
classList,style.transform)으로 React 리렌더링 없이 60fps 유지useRef로 관리loop 모드
🔍 관련 이슈
📸 스크린샷 / GIF (선택)
2026-04-07.12.35.45.mov
🔄 연관 작업