# Component/Tree

> Props: component-tree.props.txt

## Examples


### Async

```tsx
{
  render: () => {
    const [items, setItems] = useState<TreeEntry[]>([{
      id: 'item-1',
      label: 'Node 1',
      hasChildren: true
    }, {
      id: 'item-2',
      label: 'Node 2',
      hasChildren: true
    }, {
      id: 'item-3',
      label: 'Node 3'
    }]);
    function update(tree: TreeEntry[], id: string, value: TreeEntry): TreeEntry[] {
      return tree.map(item => {
        if (item.id === id) {
          return value;
        }
        return {
          ...item,
          children: update(item.children ?? [], id, value)
        };
      });
    }
    const [selected, setSelected] = useState<string | null>(null);
    return <Tree items={items} selectedIds={selected ? [selected] : []} onSelect={item => {
      setSelected(item.id);
    }} onExpand={item => {
      if (item.hasChildren && !item.loading) {
        setItems(update(items, item.id, {
          ...item,
          loading: true
        }));
        setTimeout(() => {
          setItems(update(items, item.id, {
            ...item,
            loading: false,
            hasChildren: undefined,
            children: [{
              id: `${item.id}-1`,
              label: 'Node 1',
              hasChildren: true
            }, {
              id: `${item.id}-2`,
              label: 'Node 2',
              hasChildren: true
            }, {
              id: `${item.id}-3`,
              label: 'Node 3'
            }]
          }));
        }, 300);
      }
    }} />;
  }
}
```

### Checkable

```tsx
{
  args: {
    checkable: true,
    items: [{
      id: 'fruit',
      label: 'Fruit',
      children: [{
        id: 'apple',
        label: 'Apple'
      }, {
        id: 'banana',
        label: 'Banana'
      }, {
        id: 'citrus',
        label: 'Citrus',
        children: [{
          id: 'orange',
          label: 'Orange'
        }, {
          id: 'lemon',
          label: 'Lemon'
        }]
      }]
    }, {
      id: 'vegetables',
      label: 'Vegetables',
      children: [{
        id: 'leafy',
        label: 'Leafy Greens',
        children: [{
          id: 'spinach',
          label: 'Spinach',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }, {
          id: 'kale',
          label: 'Kale'
        }]
      }, {
        id: 'root',
        label: 'Root Vegetables',
        children: [{
          id: 'carrot',
          label: 'Carrot'
        }, {
          id: 'beet',
          label: 'Beetroot'
        }]
      }]
    }, {
      id: 'desserts',
      label: 'Desserts',
      children: [{
        id: 'cake',
        label: 'Cake'
      }, {
        id: 'icecream',
        label: 'Ice Cream',
        children: [{
          id: 'vanilla',
          label: 'Vanilla'
        }, {
          id: 'chocolate',
          label: 'Chocolate'
        }, {
          id: 'veryverylong',
          label: 'Strawberry Marshmallow SuperMegaUltraSupremeSwirlDeliciousConfetti Explosion XL'
        }, {
          id: 'veryverylong2',
          label: 'ChocolateFudgeSuperMegaUltraHyperDeliciousRainbowSprinkleGalaxyExplosionDelight XXL',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }]
      }]
    }]
  },
  render: args => {
    const [checkedIds, setCheckedIds] = useState<string[]>([]);
    return <Stack direction='column' gap={12}>
        <div>Checked IDs: {checkedIds.length > 0 && checkedIds.join(', ')}</div>
        <Tree {...args} checkedIds={checkedIds} onChecked={(_items: TreeEntry[], checked_ids: string[]) => {
        setCheckedIds(checked_ids);
      }} />
      </Stack>;
  }
}
```

### Checkable With Default Expanded Ids

```tsx
{
  args: {
    checkable: true,
    items: [{
      id: 'fruit',
      label: 'Fruit',
      children: [{
        id: 'apple',
        label: 'Apple'
      }, {
        id: 'banana',
        label: 'Banana'
      }, {
        id: 'citrus',
        label: 'Citrus',
        children: [{
          id: 'orange',
          label: 'Orange'
        }, {
          id: 'lemon',
          label: 'Lemon'
        }]
      }]
    }, {
      id: 'vegetables',
      label: 'Vegetables',
      children: [{
        id: 'leafy',
        label: 'Leafy Greens',
        children: [{
          id: 'spinach',
          label: 'Spinach',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }, {
          id: 'kale',
          label: 'Kale'
        }]
      }, {
        id: 'root',
        label: 'Root Vegetables',
        children: [{
          id: 'carrot',
          label: 'Carrot'
        }, {
          id: 'beet',
          label: 'Beetroot'
        }]
      }]
    }, {
      id: 'desserts',
      label: 'Desserts',
      children: [{
        id: 'cake',
        label: 'Cake'
      }, {
        id: 'icecream',
        label: 'Ice Cream',
        children: [{
          id: 'vanilla',
          label: 'Vanilla'
        }, {
          id: 'chocolate',
          label: 'Chocolate'
        }, {
          id: 'veryverylong',
          label: 'Strawberry Marshmallow SuperMegaUltraSupremeSwirlDeliciousConfetti Explosion XL'
        }, {
          id: 'veryverylong2',
          label: 'ChocolateFudgeSuperMegaUltraHyperDeliciousRainbowSprinkleGalaxyExplosionDelight XXL',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }]
      }]
    }]
  },
  render: args => {
    const [checkedIds, setCheckedIds] = useState<string[]>([]);
    return <Stack direction='column' gap={12}>
        <div>Checked IDs: {checkedIds.length > 0 && checkedIds.join(', ')}</div>
        <Tree {...args} checkedIds={checkedIds} onChecked={(_items: TreeEntry[], checked_ids: string[]) => {
        setCheckedIds(checked_ids);
      }} />
      </Stack>;
  },
  render: args => {
    const [checkedIds, setCheckedIds] = useState<string[]>(['orange']);
    const defaultExpandedIds = useMemo(() => ['orange'], []);
    return <Stack direction='column' gap={12}>
        <div>Checked IDs: {checkedIds.length > 0 && checkedIds.join(', ')}</div>
        <Tree {...args} checkedIds={checkedIds} defaultExpandedIds={defaultExpandedIds} onChecked={(items: TreeEntry[]) => {
        setCheckedIds(items.map(item => item.id));
      }} />
      </Stack>;
  }
}
```

### Default Expand

```tsx
{
  args: {
    items: [{
      id: 'fruit',
      label: 'Fruit',
      children: [{
        id: 'apple',
        label: 'Apple'
      }, {
        id: 'banana',
        label: 'Banana'
      }, {
        id: 'citrus',
        label: 'Citrus',
        children: [{
          id: 'orange',
          label: 'Orange'
        }, {
          id: 'lemon',
          label: 'Lemon'
        }]
      }]
    }, {
      id: 'vegetables',
      label: 'Vegetables',
      children: [{
        id: 'leafy',
        label: 'Leafy Greens',
        children: [{
          id: 'spinach',
          label: 'Spinach',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }, {
          id: 'kale',
          label: 'Kale',
          loading: true
        }]
      }, {
        id: 'root',
        label: 'Root Vegetables',
        children: [{
          id: 'carrot',
          label: 'Carrot'
        }, {
          id: 'beet',
          label: 'Beetroot'
        }]
      }]
    }, {
      id: 'desserts',
      label: 'Desserts',
      children: [{
        id: 'cake',
        label: 'Cake'
      }, {
        id: 'icecream',
        label: 'Ice Cream',
        children: [{
          id: 'vanilla',
          label: 'Vanilla'
        }, {
          id: 'chocolate',
          label: 'Chocolate'
        }, {
          id: 'veryverylong',
          label: 'Strawberry Marshmallow SuperMegaUltraSupremeSwirlDeliciousConfetti Explosion XL'
        }, {
          id: 'veryverylong2',
          label: 'ChocolateFudgeSuperMegaUltraHyperDeliciousRainbowSprinkleGalaxyExplosionDelight XXL',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }]
      }]
    }, {
      id: 'loading',
      label: 'Loading',
      loading: true
    }, {
      id: 'action',
      label: 'Action',
      actionIcon: <IconPlus size={12} />,
      action: 'Action'
    }]
  },
  render: args => {
    const [selected, setSelected] = useState<string | null>(null);
    const defaultExpandedIds = useMemo(() => ['veryverylong', 'kale'], []);
    return <Tree {...args} selectedIds={selected ? [selected] : []} defaultExpandedIds={defaultExpandedIds} onSelect={item => {
      if (item.children == null || item.children.length === 0) {
        setSelected(item.id);
      }
    }} />;
  }
}
```

### Primary

```tsx
{
  args: {
    items: [{
      id: 'fruit',
      label: 'Fruit',
      children: [{
        id: 'apple',
        label: 'Apple'
      }, {
        id: 'banana',
        label: 'Banana'
      }, {
        id: 'citrus',
        label: 'Citrus',
        children: [{
          id: 'orange',
          label: 'Orange'
        }, {
          id: 'lemon',
          label: 'Lemon'
        }]
      }]
    }, {
      id: 'vegetables',
      label: 'Vegetables',
      children: [{
        id: 'leafy',
        label: 'Leafy Greens',
        children: [{
          id: 'spinach',
          label: 'Spinach',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }, {
          id: 'kale',
          label: 'Kale',
          loading: true
        }]
      }, {
        id: 'root',
        label: 'Root Vegetables',
        children: [{
          id: 'carrot',
          label: 'Carrot'
        }, {
          id: 'beet',
          label: 'Beetroot'
        }]
      }]
    }, {
      id: 'desserts',
      label: 'Desserts',
      children: [{
        id: 'cake',
        label: 'Cake'
      }, {
        id: 'icecream',
        label: 'Ice Cream',
        children: [{
          id: 'vanilla',
          label: 'Vanilla'
        }, {
          id: 'chocolate',
          label: 'Chocolate'
        }, {
          id: 'veryverylong',
          label: 'Strawberry Marshmallow SuperMegaUltraSupremeSwirlDeliciousConfetti Explosion XL'
        }, {
          id: 'veryverylong2',
          label: 'ChocolateFudgeSuperMegaUltraHyperDeliciousRainbowSprinkleGalaxyExplosionDelight XXL',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }]
      }]
    }, {
      id: 'loading',
      label: 'Loading',
      loading: true
    }, {
      id: 'action',
      label: 'Action',
      actionIcon: <IconPlus size={12} />,
      action: 'Action'
    }]
  }
}
```

### Search

```tsx
{
  args: {
    items: [{
      id: 'fruit',
      label: 'Fruit',
      children: [{
        id: 'apple',
        label: 'Apple'
      }, {
        id: 'banana',
        label: 'Banana'
      }, {
        id: 'citrus',
        label: 'Citrus',
        children: [{
          id: 'orange',
          label: 'Orange'
        }, {
          id: 'lemon',
          label: 'Lemon'
        }]
      }]
    }, {
      id: 'vegetables',
      label: 'Vegetables',
      children: [{
        id: 'leafy',
        label: 'Leafy Greens',
        children: [{
          id: 'spinach',
          label: 'Spinach',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }, {
          id: 'kale',
          label: 'Kale',
          loading: true
        }]
      }, {
        id: 'root',
        label: 'Root Vegetables',
        children: [{
          id: 'carrot',
          label: 'Carrot'
        }, {
          id: 'beet',
          label: 'Beetroot'
        }]
      }]
    }, {
      id: 'desserts',
      label: 'Desserts',
      children: [{
        id: 'cake',
        label: 'Cake'
      }, {
        id: 'icecream',
        label: 'Ice Cream',
        children: [{
          id: 'vanilla',
          label: 'Vanilla'
        }, {
          id: 'chocolate',
          label: 'Chocolate'
        }, {
          id: 'veryverylong',
          label: 'Strawberry Marshmallow SuperMegaUltraSupremeSwirlDeliciousConfetti Explosion XL'
        }, {
          id: 'veryverylong2',
          label: 'ChocolateFudgeSuperMegaUltraHyperDeliciousRainbowSprinkleGalaxyExplosionDelight XXL',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }]
      }]
    }, {
      id: 'loading',
      label: 'Loading',
      loading: true
    }, {
      id: 'action',
      label: 'Action',
      actionIcon: <IconPlus size={12} />,
      action: 'Action'
    }]
  },
  render: args => {
    const [keyword, setKeyword] = useState('');
    const [selected, setSelected] = useState<string | null>(null);
    return <Stack direction='column' width={300} gap={12}>
        <Input value={keyword} onChange={e => setKeyword(e.currentTarget.value)} placeholder='검색어 입력' startElement={<IconSearch color={semantic_colors.content.secondary} />} />
        <Tree {...args} searchKeyword={keyword} selectedIds={selected ? [selected] : []} onSelect={item => {
        if (item.children == null || item.children.length === 0) {
          setSelected(item.id);
        }
      }} />
      </Stack>;
  }
}
```

### Select

```tsx
{
  args: {
    items: [{
      id: 'fruit',
      label: 'Fruit',
      children: [{
        id: 'apple',
        label: 'Apple'
      }, {
        id: 'banana',
        label: 'Banana'
      }, {
        id: 'citrus',
        label: 'Citrus',
        children: [{
          id: 'orange',
          label: 'Orange'
        }, {
          id: 'lemon',
          label: 'Lemon'
        }]
      }]
    }, {
      id: 'vegetables',
      label: 'Vegetables',
      children: [{
        id: 'leafy',
        label: 'Leafy Greens',
        children: [{
          id: 'spinach',
          label: 'Spinach',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }, {
          id: 'kale',
          label: 'Kale',
          loading: true
        }]
      }, {
        id: 'root',
        label: 'Root Vegetables',
        children: [{
          id: 'carrot',
          label: 'Carrot'
        }, {
          id: 'beet',
          label: 'Beetroot'
        }]
      }]
    }, {
      id: 'desserts',
      label: 'Desserts',
      children: [{
        id: 'cake',
        label: 'Cake'
      }, {
        id: 'icecream',
        label: 'Ice Cream',
        children: [{
          id: 'vanilla',
          label: 'Vanilla'
        }, {
          id: 'chocolate',
          label: 'Chocolate'
        }, {
          id: 'veryverylong',
          label: 'Strawberry Marshmallow SuperMegaUltraSupremeSwirlDeliciousConfetti Explosion XL'
        }, {
          id: 'veryverylong2',
          label: 'ChocolateFudgeSuperMegaUltraHyperDeliciousRainbowSprinkleGalaxyExplosionDelight XXL',
          actionIcon: <IconPlus size={12} />,
          action: 'Action'
        }]
      }]
    }, {
      id: 'loading',
      label: 'Loading',
      loading: true
    }, {
      id: 'action',
      label: 'Action',
      actionIcon: <IconPlus size={12} />,
      action: 'Action'
    }]
  },
  render: args => {
    const [selected, setSelected] = useState<string | null>(null);
    return <Stack direction='column'>
        <Button onClick={() => setSelected(selected === 'orange' ? 'vanilla' : 'orange')}>선택된 항목 변경</Button>
        <Tree {...args} selectedIds={selected ? [selected] : []} onSelect={item => {
        if (item.children == null || item.children.length === 0) {
          setSelected(item.id);
        }
      }} />
      </Stack>;
  }
}
```