Tree

폴더 안의 파일처럼 항목이 자기 목록을 품는 목록입니다. Tree.Root는 role="tree"이며, 어떤 항목이 열려 있고 어떤 항목이 선택됐는지를 가집니다. 각 Tree.Item은 role="treeitem"이고, 눈에 보이는 행인 Tree.Label이 이름을 붙입니다. 안에 Tree.Group이 있는 항목은 가지(branch)입니다. 열고 닫을 수 있고, 그룹이 자식 항목을 담습니다. Tree.Icon은 텍스트 앞의 아이콘을 담습니다.

이 페이지의 내용
import { Folder } from 'lucide-react';

<Tree.Root aria-label="Files" defaultExpanded={['src']}>
  <Tree.Item value="src">
    <Tree.Label>
      <Tree.Icon>
        <Folder />
      </Tree.Icon>
      src
    </Tree.Label>
    <Tree.Group>
      <Tree.Item value="src/index.ts">
        <Tree.Label>index.ts</Tree.Label>
      </Tree.Item>
    </Tree.Group>
  </Tree.Item>
  <Tree.Item value="README.md">
    <Tree.Label>README.md</Tree.Label>
  </Tree.Item>
</Tree.Root>
  • src
    • components
      • Button.tsx
      • Button.module.css
      • Tree.tsx
    • index.ts
  • package.json
  • README.md

Selected: src/components/Tree.tsx

트리는 Tab 정지점을 하나 가집니다. 선택된 항목에 있고, 선택된 항목이 없거나 닫힌 항목 안에 있으면 첫 항목에 있습니다. 키는 useRovingFocus의 tree 모드를 거쳐 트리 뷰 패턴을 따릅니다. 위쪽과 아래쪽 화살표는 보이는 항목 사이를 움직이며 끝에서 돌아가지 않습니다. 오른쪽 화살표는 닫힌 항목을 열고, 열린 항목에서는 그 안의 첫 항목으로 움직입니다. 왼쪽 화살표는 열린 항목을 닫고, 그 밖에는 이 항목을 품은 항목으로 움직입니다. Home과 End는 보이는 첫 항목과 마지막 항목으로 가고, 글자를 입력하면 레이블이 그 글자로 시작하는 다음 항목으로 움직입니다. 오른쪽에서 왼쪽으로 읽는 페이지에서는 왼쪽과 오른쪽이 바뀝니다.

레이블 클릭, Enter, Space는 항목을 선택하고 가지를 열거나 닫습니다. 루트는 선택된 항목으로 value와 onValueChange, 또는 defaultValue를, 열린 항목으로 expanded와 onExpandedChange, 또는 defaultExpanded를 받습니다. value, defaultValue, onValueChange를 하나도 받지 않은 트리에는 선택이 없습니다. 항목은 열고 닫기만 하고, 어느 항목도 aria-selected를 받지 않습니다. disabled 항목은 선택할 수 없지만 포커스는 받으므로, 스크린 리더가 사용할 수 없다고 알립니다.

닫힌 항목의 그룹은 마운트된 채 hidden을 받습니다. 항목이 열릴 때 자식을 불러오려면 빈 Tree.Group을 렌더링해 항목을 가지로 만들고, onExpandedChange에서 채우세요.

chevron은 글리프입니다. GlyphsProvider에서 chevron을 설정하면 아이콘 라이브러리로 그립니다. 아래쪽을 가리키게 그리세요. 닫힌 항목은 이를 텍스트 쪽으로 돌립니다. 그룹이 없는 항목도 chevron 자리를 남기므로, 같은 깊이의 레이블이 나란히 정렬됩니다. 각 단계는 tree.indent만큼 들여 씁니다.

네이티브 트리 요소는 없습니다. 중첩된 <details>가 비슷해 보이지만, 요약마다 따로 Tab 정지점이 되고 선택도 트리 키도 없습니다.

접근성을 지키는 조합

Todae가 맡는 것: Tree.Root는 role="tree", 각 Tree.Item은 aria-labelledby가 Tree.Label을 가리키는 role="treeitem", 각 Tree.Group은 role="group"입니다. 가지는 aria-expanded를 받고, 선택이 있는 트리에서는 모든 항목이 aria-selected를 받습니다. 비활성 항목은 aria-disabled를 받습니다. Todae는 Tab 정지점을 하나로 유지하고, 위의 키로 포커스를 옮기며, 포커스가 안에 있는 동안 닫힌 항목에 포커스를 둡니다. chevron과 Tree.Icon은 aria-hidden입니다.

직접 제공할 것: 트리의 이름: aria-label, 또는 보이는 제목을 가리키는 aria-labelledby. 타입이 이를 요구하지는 않습니다. 각 항목에 이름이 되는 텍스트를 담은 Tree.Label 하나와, 트리 안에서 고유한 value.

주의할 점: Tree.Label이 없는 항목: aria-labelledby가 없는 id를 가리킵니다. 레이블 안의 버튼이나 링크: 스크린 리더가 하나의 항목으로 읽는 트리 항목 안에 들어가며, 키는 항목 사이를 움직일 뿐 그 안으로 들어가지 않습니다. 스타일을 바꾼 뒤 색으로만 표시되는 선택된 항목.

언제 쓰나요

언제: 파일과 폴더, 개요, 중첩된 분류처럼 단계를 열고 닫으며 계층을 살펴볼 때.

쓰지 않을 때: 고를 항목의 평평한 목록 → OptionList. 단계가 몇 개뿐인 사이트 내비게이션 → Nav. 동작 목록 → Menu. 열이 있는 행 → Table.

Props

Tree.Root props
이름타입설명
valuestring | null

제어 선택 항목. null이면 아무것도 선택하지 않습니다. onValueChange와 함께 전달합니다.

defaultValuestring | null

비제어일 때의 초기 선택 항목.

onValueChange(value: string) => void

클릭, Enter, Space가 선택한 항목과 함께 호출됩니다. value, defaultValue, onValueChange를 하나도 받지 않은 트리에는 선택이 없습니다.

expandedstring[]

제어 열린 항목. onExpandedChange와 함께 전달합니다.

defaultExpandedstring[]

비제어일 때의 초기 열린 항목.

onExpandedChange(expanded: string[]) => void

항목이 열리거나 닫힌 뒤 열린 항목과 함께 호출됩니다.

childrenReact.ReactNode

Tree.Item 자식.

classNamestring

트리에 붙는 선택적 클래스.

Tree.Item props
이름타입설명
value 필수string

선택과 expanded에서 항목을 가리키는 이름. 트리 안에서 고유해야 합니다.

disabledboolean

선택할 수 없습니다. 포커스는 받으므로 스크린 리더가 사용할 수 없다고 알리고, 열고 닫을 수도 있습니다.

childrenReact.ReactNode

Tree.Label, 그리고 가지라면 그 뒤의 Tree.Group.

classNamestring

항목에 붙는 선택적 클래스.

Tree.Label props
이름타입설명
childrenReact.ReactNode

항목 텍스트. 앞에 Tree.Icon을 둘 수 있습니다.

classNamestring

행에 붙는 선택적 클래스.

Tree.Group props
이름타입설명
childrenReact.ReactNode

Tree.Item 자식. 열릴 때 자식을 불러오는 가지라면 비워 두세요.

classNamestring

그룹에 붙는 선택적 클래스.

Tree.Icon props
이름타입설명
childrenReact.ReactNode

아이콘 라이브러리의 파일이나 폴더 같은 아이콘.

classNamestring

슬롯에 붙는 선택적 클래스.