# Tree List

## Usage

`TreeList` is a data-driven tree. Each item has an `id` and a `label`. `children` nests the next level. `isExpanded` is the starting state. A click on a branch toggles it. A leaf with `href` is a link. A leaf with `onClick` is a button.

`variant="lineGuides"` draws the connectors. `noGuides` keeps the indent and drops the lines. `density` changes the row padding: compact 4px, balanced 8px, spacious 12px.

`icon` and `endIcon` are either an `Icon` glyph name or a CSS icon class that starts with `i-`. `badge` is a short trailing count. The `start` and `end` slots can replace those strings when the content is richer than an icon.

Keyboard behavior follows the tree pattern: up and down move, left and right collapse and expand (they swap in right-to-left layouts), Home and End jump, Enter and Space activate, and typing focuses a matching label.

```vue
<script setup lang="ts">
import { TreeList } from '@astryx-vue/core/TreeList'

const items = [
  {
    id: 'src',
    label: 'src',
    isExpanded: true,
    children: [{ id: 'app', label: 'App.tsx', onClick: () => {} }],
  },
]
</script>

<template>
  <TreeList :items="items" />
</template>
```

### Anatomy

- **Tree list**: Container that presents the hierarchical tree.
- **Header**: Caller-provided content that visibly names the tree.
- **Item**: Painted row for one node in the hierarchy.
- **Chevron**: Expand and collapse control rendered for an Item with children.
- **Chevron glyph**: Directional symbol rendered by Icon inside a Chevron.
- **Item label**: Primary content that identifies an Item.
- **Item description**: Secondary text rendered below an Item label.
- **Start content**: Caller-provided content rendered before an Item label.
- **End content**: Caller-provided content rendered after an Item label.
- **Guide**: Connector line that shows parent-child relationships.

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Give every item a stable `id`. Focus and expansion are tracked by it. |
| Do | Use `lineGuides` when the nesting is deep enough that indent alone is hard to follow. |
| Do | Put counts in `badge` and a trailing affordance in `endIcon`, instead of appending them to the label. |
| Don't | Mix links and buttons in one row. A row is one action. |
| Don't | Expand every branch by default in a large tree. Start from the path the reader is on. |

## Examples

### TreeList — File Tree With Icons

File browser tree with folder and document icons distinguishing directories from files.

```vue
<script setup lang="ts">
import type { TreeListItemData } from '@astryx-vue/core/TreeList'
import { TreeList } from '@astryx-vue/core/TreeList'

function noop() {}

const items: TreeListItemData[] = [
  {
    id: 'src',
    label: 'src',
    isExpanded: true,
    icon: 'i-heroicons-folder',
    children: [
      { id: 'app', label: 'App.tsx', onClick: noop, icon: 'i-heroicons-document' },
      { id: 'index', label: 'index.tsx', onClick: noop, icon: 'i-heroicons-document' },
    ],
  },
  { id: 'pkg', label: 'package.json', onClick: noop, icon: 'i-heroicons-document' },
]
</script>

<template>
  <TreeList :items="items" />
</template>
```

### TreeList — Interactive Settings

Settings tree with clickable items and a documentation link.

```vue
<script setup lang="ts">
import type { TreeListItemData } from '@astryx-vue/core/TreeList'
import { TreeList } from '@astryx-vue/core/TreeList'

function noop() {}

const items: TreeListItemData[] = [
  {
    id: 'settings',
    label: 'Settings',
    isExpanded: true,
    icon: 'i-heroicons-cog-6-tooth',
    children: [
      { id: 'general', label: 'General', onClick: noop },
      { id: 'advanced', label: 'Advanced', onClick: noop },
    ],
  },
  {
    id: 'docs',
    label: 'Documentation',
    href: '#',
    endIcon: 'i-heroicons-chevron-right',
  },
]
</script>

<template>
  <TreeList :items="items" />
</template>
```

### TreeList — Mailbox Tree

Email folder tree with unread badge counts.

```vue
<script setup lang="ts">
import type { TreeListItemData } from '@astryx-vue/core/TreeList'
import { TreeList } from '@astryx-vue/core/TreeList'

function noop() {}

const items: TreeListItemData[] = [
  {
    id: 'inbox',
    label: 'Inbox',
    isExpanded: true,
    badge: '3',
    children: [
      { id: 'unread', label: 'Unread', onClick: noop, badge: '3' },
      { id: 'starred', label: 'Starred', onClick: noop },
    ],
  },
  { id: 'sent', label: 'Sent', onClick: noop },
  { id: 'drafts', label: 'Drafts', onClick: noop, badge: '1' },
]
</script>

<template>
  <TreeList :items="items" />
</template>
```

### TreeList — Navigation Tree

Navigation tree with a selected item for the current page.

```vue
<script setup lang="ts">
import type { TreeListItemData } from '@astryx-vue/core/TreeList'
import { TreeList } from '@astryx-vue/core/TreeList'

function noop() {}

const items: TreeListItemData[] = [
  {
    id: 'nav',
    label: 'Navigation',
    isExpanded: true,
    children: [
      { id: 'home', label: 'Home', onClick: noop },
      { id: 'about', label: 'About', onClick: noop, isSelected: true },
      { id: 'contact', label: 'Contact', onClick: noop },
    ],
  },
]
</script>

<template>
  <TreeList :items="items" />
</template>
```

### Tree List — Variants

lineGuides draws connectors between a parent and its children. noGuides keeps the indent and drops the lines. Density is separate and only changes the row padding.

```vue
<script setup lang="ts">
import type { TreeListItemData } from '@astryx-vue/core/TreeList'
import { Stack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { TreeList } from '@astryx-vue/core/TreeList'

function noop() {}

const items: TreeListItemData[] = [
  {
    id: 'src',
    label: 'src',
    isExpanded: true,
    children: [
      {
        id: 'components',
        label: 'components',
        isExpanded: true,
        children: [
          { id: 'button', label: 'Button.tsx', onClick: noop },
          { id: 'card', label: 'Card.tsx', onClick: noop },
        ],
      },
      { id: 'app', label: 'App.tsx', onClick: noop },
    ],
  },
  { id: 'readme', label: 'README.md', onClick: noop },
]
</script>

<template>
  <Stack align="start" direction="horizontal" :gap="6" h-align="start" wrap="wrap">
    <Stack direction="vertical" :gap="2">
      <Text type="supporting" color="secondary" weight="bold">
        lineGuides (default)
      </Text>
      <TreeList :items="items" variant="lineGuides" />
    </Stack>
    <Stack direction="vertical" :gap="2">
      <Text type="supporting" color="secondary" weight="bold">
        noGuides
      </Text>
      <TreeList :items="items" variant="noGuides" />
    </Stack>
  </Stack>
</template>
```

## Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `TreeListItemData[]` | - | The tree. See the item fields below. |
| `density` | `'compact' \| 'balanced' \| 'spacious'` | `'balanced'` | Block padding of a row: 4px, 8px, or 12px. |
| `variant` | `'lineGuides' \| 'noGuides'` | `'lineGuides'` | Whether parent and child rows are joined by a guide line. |
| `header` | `string` | - | Plain title above the tree. Use the `header` slot for rich content. |
| `label` | `string` | - | Accessible name of the tree when there is no visible header. |

### TreeListItemData

| Field | Type | Description |
| --- | --- | --- |
| <code>id</code> | <code>string</code> | Stable identity for focus and expansion. |
| <code>label</code> | <code>string</code> | Visible name. |
| <code>description</code> | <code>string</code> | Secondary line. |
| <code>icon</code> | <code>string</code> | Leading glyph name, or a CSS icon class starting with <code>i-</code>. |
| <code>endIcon</code> | <code>string</code> | Trailing glyph or CSS icon class. |
| <code>badge</code> | <code>string</code> | Trailing count. |
| <code>children</code> | <code>TreeListItemData[]</code> | Nested items. Present means the row can expand. |
| <code>href</code> / <code>target</code> | <code>string</code> | Renders the row as a link. <code>target="_blank"</code> adds <code>rel="noopener noreferrer"</code>. |
| <code>onClick</code> | <code>(event: MouseEvent) =&gt; void</code> | Renders a leaf as a button. |
| <code>isDisabled</code> | <code>boolean</code> | The row does not take focus or activation. |
| <code>isSelected</code> | <code>boolean</code> | Callers mark the current item. The tree has no root selection value and does not force a single selected row. |
| <code>isExpanded</code> | <code>boolean</code> | Starting expansion. Later toggles are owned by the tree. |
| <code>className</code> | <code>string</code> | Extra class on that row. |
| <code>style</code> | <code>CSSProperties</code> | Inline style on that row. |

The <code>start</code> and <code>end</code> slots receive <code>{ item }</code> and replace <code>icon</code> and <code>endIcon</code>.

A row's accessible name is its label. The description is exposed with <code>aria-describedby</code>, so the chevron and icons stay out of the name. Typeahead matches the label only. A visible <code>header</code> names the tree and wins over a passed <code>aria-label</code>. A keydown listener on the root that calls <code>preventDefault</code> keeps arrow keys from moving focus inside the tree.

## Theming

Indent is <code>--astryx-spacing-4</code>. Row content uses <code>--astryx-spacing-2</code> of inline padding and the same gap. The guide is a 1px emphasized border. Focus uses the shared focus outline tokens. A selected row uses the accent muted fill.
