선택 컨트롤
Checkbox · RadioGroup · Switch · SegmentedControl 입니다. 모두 작은 키캡이고 두께는 (1,1), 누르면 1px 대각으로 가라앉습니다. radix-ui 위에 만들어 ./form 에서 가져옵니다.
- import
import { Checkbox, … } from "@uyoolabs/ui/form";- 쓰는 경우
- 제출해야 반영되는 켬/끔 · 동의 · 여럿 고르기 · 표 행 선택은 Checkbox, 누르는 즉시 저장되는 켬/끔 하나는 Switch, 설명이 필요한 하나 고르기(2~6개)는 RadioGroup, 툴바 · 카드 머리의 보기 · 기간 · 거르기(2~5칸)는 SegmentedControl 을 씁니다.
- 쓰지 않는 경우
- 저장 버튼이 있는 폼 안에 Switch 를 두지 않습니다(눌렀는데 아직 반영되지 않았습니다). 스위치 라벨에 동작 · 상태(“알림 켜기” · “켜짐”)를 쓰지 않습니다. 세그먼트로 아래 패널을 갈아 끼우지 않습니다(Tabs). 7개 이상은 Select 입니다. 선택을 색만으로 말하지 않고, 못 고르는 항목은 설명 줄에 이유를 적습니다.
- 상태
- 선택은 체크 · 점 · 손잡이 위치 · 굵은 글자로도 말합니다. hover 는 작은 컨트롤의 모양을 바꾸지 않습니다. 비활성은 투명도 대신 파인 면과 흐린 글자로, 오류는 경계 색과 묶음 아래 문장 한 번으로 보입니다.
무엇을 쓸까
| 상황 | 컨트롤 | 예 |
|---|---|---|
| 켬/끔 하나, 누르는 즉시 저장 · 적용 | Switch | 멘션 알림 · 초대 링크 사용 |
| 켬/끔 하나, 제출해야 반영 · 동의 | Checkbox | 약관 동의 · “다시 묻지 않기” |
| 여럿 고르기 · 표 행 선택 | Checkbox 묶음 | 요약 받을 채널 · 작업 표 |
| 하나 고르기 2~6개, 설명이 필요하거나 폼 안 | RadioGroup | 기본 역할 · 보관 기간 |
| 하나 고르기 2~5개, 짧은 이름, 툴바 · 카드 머리 | SegmentedControl | 보기 · 기간 · 거르기 |
| 하나 고르기 7개 이상, 또는 자리가 좁음 | Select | 기본 언어 · 시간대 |
Checkbox
두 상태 · 세 상태(일부). 라벨 글자까지 누르는 자리입니다.
RadioGroup
묶음 이름은 FieldGroup 의 legend 를 render-prop 으로 받습니다. 화살표가 옮기며 바로 고릅니다.
Switch
누르는 즉시 저장되는 켬/끔 하나에만 씁니다. 손잡이를 바로 옮기고 저장 중 · 저장됨 · 실패를 줄의 SaveStatus 로 보입니다.
SegmentedControl
radiogroup 입니다(결정 #12). 고른 면이 220ms 로 미끄러지고, 줄인 모션에서는 바로 옮깁니다. 6칸 이상이면 개발 모드에서 경고합니다.
네이티브 클래스
radix 를 설치하지 않은 소비자(서버 폼)는 checkboxClass · radioClass 를 네이티브 input 에 붙입니다. 모양과 상태가 컴포넌트와 같습니다(결정 #10).
키보드
Checkbox
| 키 | 동작 |
|---|---|
| Space | 켬 ↔ 끔. 일부에서 누르면 켬 |
| Enter | 토글하지 않습니다. 폼 안이면 폼에 맡깁니다 |
RadioGroup · SegmentedControl
| 키 | 동작 |
|---|---|
| Tab | 묶음에 한 번 들어옵니다. 고른 항목(없으면 첫 항목)에 멈추고, 다음 Tab 은 묶음 밖 |
| ↓ · → | 다음 항목으로 옮기며 바로 고릅니다. 끝에서 처음으로 돌고, 비활성 항목은 건너뜁니다 |
| ↑ · ← | 이전 항목으로 옮기며 고릅니다 |
| Home · End | 처음 · 끝 항목으로 갑니다 |
| Space | 포커스한 항목을 고릅니다 |
Switch
| 키 | 동작 |
|---|---|
| Space · Enter | 켬 ↔ 끔. 저장 중(pending)에는 무시합니다 |
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
Checkbox
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| label | ReactNode | — | 보이는 라벨. 주면 <label data-slot="choice"> 로 감싸 라벨까지 누르는 자리가 된다. 주지 않으면 aria-label 또는 aria-labelledby 가 반드시 있어야 한다(표 행: "PAY-142 선택") |
| description | ReactNode | — | 라벨 아래 설명 한 줄(13px · muted). aria-describedby 로 잇는다. 비활성 이유도 여기에 쓴다 |
| invalid | boolean | — | 오류 경계. aria-invalid 를 단다. 오류 문장은 Field · FieldGroup 의 error 가 맡는다 |
이 밖에 Radix Checkbox.Root props(children · asChild 제외)를 그대로 받습니다.
RadioGroup
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| invalid | boolean | — | 묶음 오류. 루트(role="radiogroup")에 aria-invalid, 고르지 않은 항목의 경계를 오류 색으로 |
이 밖에 Radix RadioGroup.Root props(asChild 제외)를 그대로 받습니다. 기본값을 바꾼 것은 orientation="vertical" 입니다.
Radio
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| label필수 | ReactNode | — | 보이는 라벨. <label data-slot="choice"> 로 감싸 라벨 글자까지 누르는 자리가 된다 |
| description | ReactNode | — | 라벨 아래 설명. aria-describedby 로 잇는다. 비활성 항목은 이유를 여기에(“소유자만 고를 수 있습니다.”) |
이 밖에 Radix RadioGroup.Item props(children · asChild 제외)를 그대로 받습니다.
Switch
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| label | ReactNode | — | 켜지는 것의 이름(“멘션 알림”). 동작 · 상태(“알림 켜기” · “켜짐”)를 쓰지 않는다. 없으면 aria-label 이 필요하다 |
| description | ReactNode | — | 라벨 아래 설명. aria-describedby 로 잇는다 |
| layout | "inline" | "row" | "inline" | inline: 스위치 → 라벨. row: 라벨 · 설명 왼쪽, 스위치 오른쪽 끝(설정 줄, 줄 사이 헤어라인) |
| size | "sm" | "md" | "md" | md 36×20 · sm 28×16. sm 은 손잡이 안 체크가 없어 옆에 상태 글자를 둔다 |
| pending | boolean | false | 저장 중. 손잡이에 스피너, aria-busy, 클릭 · 키 입력을 무시한다(포커스는 둔다) |
| status | ReactNode | — | row 레이아웃에서 스위치 왼쪽 자리(SaveStatus · 다시 시도). inline 에서는 그리지 않는다 |
| error | ReactNode | — | 저장에 실패한 이유와 할 일(되돌린 뒤 줄 아래에 보인다). aria-describedby 로 잇는다 |
| lang | "ko" | "en" | "ja" | — | 내장 문구의 언어 |
| labels | Partial<SwitchLabels> | — | 내장 문구 중 바꿀 것만 |
이 밖에 Radix Switch.Root props(children · asChild 제외)를 그대로 받습니다.
SegmentedControl
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| label필수 | string | — | 축 이름. 화면에 그리지 않고 aria-label 로만 쓴다(칸 이름이 이미 축을 말한다) |
| options필수 | ReadonlyArray<SegmentedOption<T>> | — | 2~5개. 6개 이상이면 개발 모드에서 경고한다(Select 를 쓴다) |
| value | T | — | 고른 칸(제어) |
| defaultValue | T | — | 처음 고른 칸(비제어). 없으면 첫 칸 |
| onValueChange | (value: T) => void | — | 다른 칸을 골랐을 때만 부른다. 고른 칸을 다시 눌러도 부르지 않는다(빈 값이 없다) |
| fill | boolean | — | 균등 폭. 좁은 패널(시트 · 카드 머리)에서만 |
| disabled | boolean | — | 묶음 전체 비활성 |
| name | string | — | form 안이면 숨은 radio 로 FormData 에 들어간다 |
| className | string | — |
import
./form 은 radix-ui 가 peer 로 필요합니다. 내장 문구는 Switch 의 저장 중 설명 하나(lang · labels)뿐이고, 나머지 글자는 모두 소비자가 줍니다.