TreeSelect
목록이 아니라 트리에서 값을 고릅니다. 카테고리, 폴더, 지역, 조직도 노드처럼 평평한 목록으로는 계층이 드러나지 않는 값에 씁니다.
import { TreeSelect } from 'neba';
<TreeSelect label="카테고리" items={categories} value={value} onValueChange={setValue} />;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 표면의 무게. TextField·Select와 같은 셸입니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 높이와 타입 스케일 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| items | TreeSelectItem[] | — | 중첩된 item으로 표현한 트리 |
| value | TreeViewValue | TreeViewValue[] | null | — | 고른 값, multiple이면 값들. onValueChange와 함께 제어 컴포넌트로 씁니다 |
| defaultValue | TreeViewValue | TreeViewValue[] | null | — | 초기 값 |
| onValueChange | (value: TreeViewValue[]) => void | — | 선택이 바뀔 때. single에서도 배열로 옵니다 |
| multiple | boolean | false | 둘 이상을 담을 수 있는지 |
| selectableBranches | boolean | false | 자식이 있는 노드도 고를 수 있는지. 기본값은 꺼짐이며, 가지는 분류에 쓰고 잎만 고르게 됩니다 |
| searchable | boolean | false | 트리를 거르는 필드를 위에 붙입니다. 일치한 노드는 조상과 함께 남고 남은 가지는 펼쳐집니다. 조상 없는 일치는 목록이고, 접힌 일치는 보여 주지 않은 일치입니다 |
| clearLabel | string | — | 지우기 버튼의 accessible name. 기본값은 locale의 표현 |
| searchPlaceholder | string | — | 그 필드의 placeholder |
| expanded | TreeViewValue[] | — | 펼쳐진 가지들. onExpandedChange와 함께 제어합니다 |
| defaultExpanded | TreeViewValue[] | — | 처음에 펼쳐진 가지들 |
| onExpandedChange | (expanded: TreeViewValue[]) => void | — | 펼침이 바뀔 때 |
| format | (chosen: TreeSelectItem[]) => ReactNode | — | trigger가 담긴 것을 쓰는 방식. 기본값은 라벨을 쉼표로 이은 것 |
| closeOnSelect | boolean | !multiple | 고르는 즉시 팝업을 닫습니다 |
| name | string | — | 폼 제출 시의 필드 이름. 값 하나당 hidden input 하나로 나갑니다 |
| open | boolean | — | 팝업이 열려 있는지. `onOpenChange`와 함께 제어 컴포넌트로 씁니다 |
| defaultOpen | boolean | — | 팝업이 열린 채로 시작할지 |
| onOpenChange | (open: boolean) => void | — | 팝업이 열리거나 닫힐 때 |
| placeholder | ReactNode | — | 아무것도 고르지 않았을 때 트리거에 보이는 내용 |
| clearable | boolean | false | 값을 비우는 ×를 트리거에 답니다 |
| locale | string | — | 이 컴포넌트가 스스로 쓰는 문자열의 BCP 47 태그 |
| classNames | NebaSlots<'popup' | 'tree' | 'item' | 'empty'> | — | 루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다 |
| label | ReactNode | — | 컨트롤과 연결되는 라벨. Base UI Field가 묶어 줍니다 |
| description | ReactNode | — | 보조 설명 |
| error | ReactNode | — | 오류 메시지. 값이 있으면 invalid 상태도 함께 켜지고 색 계열이 danger로 넘어갑니다 |
| invalid | boolean | !!error | 메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때 |
| readOnly | boolean | false | 값은 보이지만 바꿀 수 없음. 색과 가장자리는 유지한 채 채도만 빠집니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
items
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value | string | number | — | 고를 때 저장되는 것. 형제들 사이가 아니라 트리 전체에서 유일해야 합니다 |
| label | ReactNode | — | 행에 그려지는 것 |
| searchLabel | string | — | 검색이 대조하는 문자열. label이 문자열이면 그것으로 대신합니다 |
| startIcon | ReactNode | — | 라벨 앞의 글리프 |
| selectable | boolean | — | 이 노드 자체를 고를 수 있는지. 잎은 true, 자식이 있으면 selectableBranches를 따릅니다 |
| disabled | boolean | — | 고를 수 없음 |
| children | TreeSelectItem[] | — | 이 노드 아래의 노드들 |
value는 형제들 사이에서만이 아니라 트리 전체에서 유일해야 합니다. 컴포넌트가 노드를 찾는 열쇠이기 때문입니다.
어느 쪽을 쓸까
| 선택지가 | 쓸 것 |
|---|---|
| 평평한 목록 | Select |
| 타이핑해서 좁히는 평평한 목록 | Combobox |
| 계층 구조이고, 거기서 고른다 | TreeSelect |
| 계층 구조를 고르는 게 아니라 보여 준다 | TreeView |
예시
selectableBranches
기본값은 꺼짐이고, 이 기본값에는 무게가 있습니다. 이런 트리의 대부분에서 가지는 분류 체계이고 잎이 답입니다. "France" 옆에서 함께 고를 수 있는 "Europe"은 보통 아무도 의도하지 않은 데이터 모델입니다.
고를 수 없는 가지도 펼치고 접히는 것은 그대로입니다. item 자신의 selectable이 어느 방향으로든 이 설정을 덮으므로, 제목들의 트리에서 하나만 고를 수 있게 하거나 반대로 하는 것도 됩니다.
multiple
몇 개든 담고, format이 달리 말하지 않으면 trigger가 쉼표로 이어 씁니다. closeOnSelect가 이것을 따라갑니다. 값이 하나인 TreeSelect는 첫 선택에서 닫히고, multiple은 열린 채로 있습니다.
searchable
트리 위에 그것을 거르는 필드를 붙입니다.
일치한 노드는 조상을 함께 남기고, 필터가 남긴 모든 가지를 펼칩니다. 둘 다 중요합니다. 일치한 것만 남긴 트리는 목록이고, 잎만 모은 목록이야말로 트리를 고른 이유였습니다. 위에 아무것도 없는 "Seoul"은 어느 분류에서 왔는지 말하지 않고, 닫힌 부모 안에 접혀 있는 일치는 독자에게 보여 주지 않은 일치입니다.
label이 문자열이 아니라 노드일 때 무엇과 대조할지는 searchLabel이 정합니다.
format
trigger가 담긴 것을 쓰는 방식입니다.
format={(chosen) => (chosen.length === 1 ? chosen[0].label : `${chosen.length}개 카테고리`)}name
폼 제출 시 값 하나당 hidden input 하나로 나갑니다. multiple이 <select multiple>처럼 반복 필드로 도착합니다.
접근성
- 팝업은
role="tree"와role="treeitem"행들을 담고, 방향키 이동과 단일 tab 정지를 TreeView에서 가져옵니다. - 가지의 accessible name에는 그 아래 subtree가 포함됩니다. 행의 element가 자식을 담고 있기 때문입니다. 질의와 테스트는 행 자신의 텍스트로 하세요.
- 고를 수 없는 노드는
aria-disabled를 달고 자리를 지키므로 방향키 경로에서 빠지지 않습니다. - 팝업은
<body>끝으로 portal되며 positioner에neba-portal클래스가 붙습니다.