# Wheel Picker URL: /lynx/components/wheel-picker Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/wheel-picker.mdx 항목을 세로로 스크롤하여 하나 이상의 값을 선택하는 컴포넌트입니다. 사용 가능 버전: @seed-design/lynx-react@1.0.0, @seed-design/lynx-css@1.0.0 ## Preview ```tsx import "./styles"; import * as React from "@lynx-js/react"; import { WheelPicker } from "@/components/ui/wheel-picker"; const buildingOptions = Array.from({ length: 10 }, (_, index) => { const value = String(101 + index); return { value, label: `${value}동` }; }); const unitOptions = Array.from({ length: 15 }, (_, floorIndex) => Array.from({ length: 4 }, (_, lineIndex) => { const value = String((floorIndex + 1) * 100 + lineIndex + 1); return { value, label: `${value}호` }; }), ).flat(); export default function WheelPickerPreview() { const [{ building, unit }, setAddress] = React.useState({ building: "103", unit: "1202", }); const columns = [ { id: "building", "accessibility-label": "동", options: buildingOptions, value: building, onValueChange: (nextBuilding: string) => { "background only"; setAddress((current) => ({ ...current, building: nextBuilding })); }, }, { id: "unit", "accessibility-label": "호수", options: unitOptions, value: unit, onValueChange: (nextUnit: string) => { "background only"; setAddress((current) => ({ ...current, unit: nextUnit })); }, }, ]; return ( {building}동 {unit}호 ); } ``` ## Installation - npm: npx @seed-design/cli@latest add ui:wheel-picker - pnpm: pnpm dlx @seed-design/cli@latest add ui:wheel-picker - yarn: yarn dlx @seed-design/cli@latest add ui:wheel-picker - bun: bun x @seed-design/cli@latest add ui:wheel-picker ## Props ### `WheelPicker` ### `WheelPickerColumn` ## Examples ### Customizing Columns ```tsx import "./styles"; import * as React from "@lynx-js/react"; import { WheelPickerItemLabel } from "@seed-design/lynx-react"; import { WheelPicker } from "@/components/ui/wheel-picker"; const options = ["작게", "보통", "크게"].map((label) => ({ value: label, label })); export default function WheelPickerPackageApi() { const [value, setValue] = React.useState("보통"); return ( { "background only"; setValue(nextValue); }, renderLabel: (option) => ( {option.label} ), }, ]} /> 선택값: {value} ); } ``` Registry snippet의 `WheelPicker`에 `columns` 배열을 전달해 컬럼을 구성합니다. `WheelPicker`의 `className`과 각 컬럼의 `className`으로 스타일을 조정하고, 컬럼의 `renderLabel`로 항목 label을 바꿀 수 있습니다. 이 예제의 `WheelPickerItemLabel`은 snippet에서 제공하지 않는 label 부품이므로 `@seed-design/lynx-react`에서 가져옵니다. `size="small"`의 기본 항목 높이는 36px, `size="medium"`은 44px입니다. 기본 `visibleItemCount`는 5이므로 `WheelPicker` 높이는 각각 180px과 220px입니다. `itemSize`와 `visibleItemCount`로 이를 조정할 수 있습니다. 컬럼의 `loop`는 기본 `false`이며 양 끝을 넘겨 끌면 저항을 주다가 놓으면 돌아옵니다. ### Value Change `onIndexChange`는 사용자가 스크롤하거나 항목을 탭해 중앙 항목이 바뀔 때마다 호출됩니다. 정착 전에도 통과한 각 항목의 인덱스와 값을 순서대로 전달하므로 항목별 햅틱에 사용할 수 있습니다. 최종 선택값은 스크롤이 정착한 뒤 `onValueChange`로 처리하세요. 두 번째 인자인 `{ stepDelta }`는 직전에 정착한 항목에서 이동한 칸 수입니다. 외부 `value` 변경 시 `valueChangeBehavior="smooth"`로 부드럽게 이동할 수 있습니다. 기본값인 `"auto"`와 `"instant"`는 즉시 이동합니다. 외부 값 변경에 따른 이동은 사용자 조작 callback을 호출하지 않습니다. 상태를 변경하는 callback에는 `"background only"` 지시문을 사용하세요. ### React Element Label ```tsx import "./styles"; import * as React from "@lynx-js/react"; import { WheelPicker } from "@/components/ui/wheel-picker"; function ColorDot({ color }: { color: string }) { return ( ); } const colorOptions = [ { value: "carrot", name: "당근색", color: "var(--seed-color-palette-carrot-600)" }, { value: "blue", name: "파란색", color: "var(--seed-color-palette-blue-700)" }, { value: "green", name: "초록색", color: "var(--seed-color-palette-green-700)" }, ].map(({ value, name, color }) => ({ value, ariaLabel: name, label: ( {name} ), })); export default function WheelPickerReactElementLabel() { const [value, setValue] = React.useState("carrot"); return ( { "background only"; setValue(nextValue); }, }, ]} /> 선택값: {value} ); } ``` 문자열 label은 접근성 값으로 사용됩니다. React 요소를 label로 사용한다면 각 option에 `ariaLabel`을 제공하세요. 접근성 텍스트를 별도로 계산해야 할 때는 `getAriaValueText`를 사용할 수 있습니다. 접근성 값은 `getAriaValueText`, `ariaLabel`, 문자열 label, `value` 순으로 선택합니다. 버튼, 입력 요소와 여러 줄 콘텐츠는 넣지 않습니다. 커스텀 label의 문자열은 `` 안에 넣으세요. `renderLabel` 안의 ``는 항목의 선택·비활성 색을 따릅니다. 이 색을 유지하려면 커스텀 label에 색을 직접 지정하지 마세요. 타이포그래피 상속에는 [Feature Flags](/lynx/getting-started/feature-flags)의 `enableCSSInheritance: true` 설정을 사용합니다. Lynx에서는 CSS `inherit` 값을 지원하지 않습니다. ### Disabled ```tsx import "./styles"; import { WheelPicker } from "@/components/ui/wheel-picker"; const options = ["낮음", "보통", "높음"].map((label) => ({ value: label, label })); export default function WheelPickerStates() { return ( Disabled ); } ``` `disabled`는 조작과 값 변경을 막고 항목을 비활성 색으로 표시합니다. `readOnly`는 사용자 조작으로 값을 변경하지 못하게 하되 활성 색을 유지합니다. Lynx에는 웹의 포커스 유지 동작이 없습니다. ### Data Contract - 각 컬럼의 `options`에는 하나 이상의 항목을 제공하세요. - option의 `value`는 컬럼 안에서 고유해야 합니다. - 제어 중인 `value`는 현재 `options`에 있어야 합니다. - 의존 컬럼이 바뀌면 새 `options`와 유효한 `value`를 같은 렌더에서 전달하세요. - 사용자가 조작 중이거나 정착을 기다리는 동안 외부 `value`가 바뀌면 스크롤 위치 동기화를 미루고 조작이 끝난 뒤 반영합니다. - 로딩, 오류, 빈 상태는 Wheel Picker 바깥에서 처리하세요. 유효하지 않은 값이면 첫 항목을 표시하지만 전달받은 값을 보정하거나 보정 callback을 호출하지 않습니다. 날짜 계산, 컬럼 간 의존성과 확인 UI도 사용하는 쪽에서 구성합니다. 웹의 hidden input과 form 제출 기능은 제공하지 않습니다. ### Accessibility `columns`의 각 항목에 `accessibility-label`을 지정하세요. 컬럼은 이름과 현재 선택값을 알리며, `disabled` 상태도 전달합니다. raw 접근성 속성과 `flatten`을 직접 지정하면 기본값보다 우선합니다. ## 웹 버전과의 차이 - `WheelPicker`를 접근성 요소로 만들면 iOS에서 자식 컬럼이 가려질 수 있습니다. 이름과 값은 `columns`의 각 항목에 지정하세요. - 위치 계산과 애니메이션은 Main Thread에서 실행하고 값 callback은 Background Thread에서 실행합니다. - 활성 컬럼의 세로 drag는 바깥 scroll view 대신 Wheel Picker가 처리합니다. `disabled` 또는 `readOnly`일 때는 바깥 scroll view가 처리합니다. - Bottom Sheet 안에서 drag로 시트를 움직이려면 Wheel Picker 바깥에서 시작하세요. ## Lynx 미지원 기능 웹의 키보드 `ArrowUp`, `ArrowDown`, `Home`, `End` 조작과 `Tab` 컬럼 이동, 포커스 API는 지원하지 않습니다. 터치 드래그나 보이는 항목 탭으로 선택하세요. `readOnly`는 사용자 값 변경만 막으며 웹처럼 포커스를 유지하는 기능은 없습니다. 스크린 리더는 각 컬럼의 이름과 현재 값을 읽지만, 스와이프로 값을 올리고 내리는 조작(`accessibility-actions` 등)은 제공하지 않습니다.