React Components

# Tree View

## Overview

---

## Anatomy

---

TreeView

TreeViewNode

TreeViewNodes

---

## TreeView

---

| Property | Type | Required | Default value | Description |
| --- | --- | --- | --- | --- |
| 
defaultExpandedValue

 | `string[]` | - | `undefined` | The initial expanded value(s). Use when you don't need to control the expanded value(s) of the tree view. |
| 

defaultValue

 | `string[]` | - | `undefined` | The initial selected value(s). Use when you don't need to control the selected value(s) of the tree view. |
| 

disabled

 | `boolean` | - | `undefined` | Whether the component is disabled. |
| 

expandedValue

 | `string[]` | - | `undefined` | The controlled expanded value(s). |
| 

items

 | `Array<TreeViewItem>` |  | `undefined` | The list of items |
| 

multiple

 | `boolean` | - | `undefined` | Whether the multiple selection is allowed. |
| 

onExpandedChange

 | `(details: TreeViewExpandedChangeDetail) => void` | - | `undefined` | Callback fired when the expanded value(s) changes. |
| 

onValueChange

 | `(details: TreeViewValueChangeDetail) => void` | - | `undefined` | Callback fired when the value(s) changes. |
| 

value

 | `string[]` | - | `undefined` | The controlled selected value(s). |

## TreeViewNode

---

| Property | Type | Required | Default value | Description |
| --- | --- | --- | --- | --- |
| 
children

 | `ReactNode | ((arg: TreeViewCustomRendererArg) => ReactNode)` | - | `undefined` | Label content or custom render function. |
| 

item

 | `TreeViewItem` |  | `undefined` | The tree node to render. |

## TreeViewNodes

---

This component has no specific properties.

## Interfaces

---

### TreeViewCustomRendererArg<T>

-   `customData?: T`
-   `isBranch: boolean`
-   `isExpanded: boolean`
-   `item: TreeViewItem<T>`

### TreeViewExpandedChangeDetail

-   `expandedValue: string[]`

### TreeViewItem<T>

-   `children?: TreeViewItem[]`
-   `customRendererData?: T`
-   `disabled?: boolean`
-   `expanded?: boolean`
-   `id: string`
-   `name: string`

### TreeViewValueChangeDetail

-   `value: string[]`

## Css Variables

---

| Token | Value | Preview |
| --- | --- | --- |
| --ods-tree-view-node-border-radius | calc(var(--ods-theme-border-radius) / 2) | 
 |
| --ods-tree-view-node-column-gap | var(--ods-theme-column-gap) | 

 |
| --ods-tree-view-node-padding-horizontal | var(--ods-theme-padding-horizontal) | 

 |
| --ods-tree-view-node-padding-vertical | calc(var(--ods-theme-padding-horizontal) / 2) | 

 |
| --ods-tree-view-node-row-gap | var(--ods-theme-row-gap) | 

 |

## Examples

---

### Default

```jsx
{
  globals: {
    imports: `import { TreeView, TreeViewNode, TreeViewNodes } from '@ovhcloud/ods-react';`
  },
  tags: ['!dev'],
  render: ({}) => {
    const items = [{
      id: 'src',
      name: 'src',
      children: [{
        id: 'app.tsx',
        name: 'app.tsx'
      }, {
        id: 'index.ts',
        name: 'index.ts'
      }, {
        id: 'components',
        name: 'components',
        children: [{
          id: 'Button.tsx',
          name: 'Button.tsx'
        }, {
          id: 'Card.tsx',
          name: 'Card.tsx'
        }]
      }]
    }, {
      id: 'package.json',
      name: 'package.json'
    }, {
      id: 'readme.md',
      name: 'README.md'
    }];
    return <TreeView items={items}>
        <TreeViewNodes>
          {items.map(item => <TreeViewNode key={item.id} item={item} />)}
        </TreeViewNodes>
      </TreeView>;
  }
}
```

### Multiple

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  return <TreeView items={items} multiple>
      <TreeViewNodes>
        {items.map(item => <TreeViewNode key={item.id} item={item} />)}
      </TreeViewNodes>
    </TreeView>;
}
```

### Disabled

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  return <TreeView disabled items={items}>
      <TreeViewNodes>
        {items.map(item => <TreeViewNode key={item.id} item={item} />)}
      </TreeViewNodes>
    </TreeView>;
}
```

### Custom render

src

app.tsx

index.ts

components

Button.tsx

Card.tsx

package.json

README.md

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  return <TreeView items={items}>
      <TreeViewNodes>
        {items.map(item => <TreeViewNode key={item.id} item={item}>
            {({
          item,
          isBranch,
          isExpanded        }) => <span style={{
          display: 'inline-flex',
          alignItems: 'center',
          gap: 6
        }}>
                {isBranch ? isExpanded ? <Icon name={ICON_NAME.folderMinus} /> : <Icon name={ICON_NAME.folderPlus} /> : <Icon name={ICON_NAME.file} />}
                <span>{item.name}</span>
              </span>}
          </TreeViewNode>)}
      </TreeViewNodes>
    </TreeView>;
}
```

### Default expanded

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  return <TreeView defaultExpandedValue={["src", "components"]} items={items}>
      <TreeViewNodes>
        {items.map(item => <TreeViewNode key={item.id} item={item} />)}
      </TreeViewNodes>
    </TreeView>;
}
```

### Controlled

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  const [selectedId, setSelectedId] = useState<string | undefined>('package.json');
  return <>
      <TreeView items={items} onValueChange={(d: TreeViewValueChangeDetail) => setSelectedId(d.value[0])} value={selectedId ? [selectedId] : undefined}>
        <TreeViewNodes>
          {items.map(item => <TreeViewNode key={item.id} item={item} />)}
        </TreeViewNodes>
      </TreeView>
      <div style={{
      marginTop: 8
    }}>Selected: {selectedId ?? 'None'}</div>
    </>;
}
```

### Controlled multiple

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  const [selectedIds, setSelectedIds] = useState<string[]>(['package.json', 'index.ts']);
  return <>
      <TreeView items={items} multiple onValueChange={(d: TreeViewValueChangeDetail) => setSelectedIds(Array.isArray(d.value) ? d.value : [d.value].filter(Boolean) as string[])} value={selectedIds}>
        <TreeViewNodes>
          {items.map(item => <TreeViewNode key={item.id} item={item} />)}
        </TreeViewNodes>
      </TreeView>
      <div style={{
      marginTop: 8
    }}>Selected: {selectedIds.length ? selectedIds.join(', ') : 'None'}</div>
    </>;
}
```

### Disabled items

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts',
      disabled: true
    }, {
      id: 'components',
      name: 'components',
      disabled: true,
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx',
        disabled: true
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json',
    disabled: true
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  return <TreeView items={items}>
      <TreeViewNodes>
        {items.map(item => <TreeViewNode key={item.id} item={item} />)}
      </TreeViewNodes>
    </TreeView>;
}
```

### In form field

```jsx
const items = [{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: [{
        id: 'Button.tsx',
        name: 'Button.tsx'
      }, {
        id: 'Card.tsx',
        name: 'Card.tsx'
      }]
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }];
  return <FormField>
      <FormFieldLabel>Choose a file</FormFieldLabel>
      <TreeView items={items}>
        <TreeViewNodes>
          {items.map(item => <TreeViewNode key={item.id} item={item} />)}
        </TreeViewNodes>
      </TreeView>
    </FormField>;
}
```

### Dynamic children

```jsx
type Item = {
    id: string;
    name: string;
    children?: Item[];
  };
  const [items, setItems] = useState<Item[]>([{
    id: 'src',
    name: 'src',
    children: [{
      id: 'app.tsx',
      name: 'app.tsx'
    }, {
      id: 'index.ts',
      name: 'index.ts'
    }, {
      id: 'components',
      name: 'components',
      children: []
    }]
  }, {
    id: 'package.json',
    name: 'package.json'
  }, {
    id: 'readme.md',
    name: 'README.md'
  }]);
  const counter = useRef(1);
  function addChildTo(collection: Item[], parentId: string, newNode: Item): Item[] {
    return collection.map(node => {
      if (node.id === parentId) {
        const nextChildren = Array.isArray(node.children) ? [...node.children, newNode] : [newNode];
        return {
          ...node,
          children: nextChildren        };
      }
      if (node.children?.length) {
        return {
          ...node,
          children: addChildTo(node.children, parentId, newNode)
        };
      }
      return node;
    });
  }
  function removeNodeFrom(collection: Item[], nodeId: string): Item[] {
    return collection.filter(node => node.id !== nodeId).map(node => node.children?.length ? {
      ...node,
      children: removeNodeFrom(node.children, nodeId)
    } : node);
  }
  function handleAddChild(parentId: string): void {
    const id = `new-file-${counter.current++}.txt`;
    const newNode = {
      id,
      name: id    };
    setItems(prev => addChildTo(prev, parentId, newNode));
  }
  function handleDelete(nodeId: string): void {
    setItems(prev => removeNodeFrom(prev, nodeId));
  }
  function handleAddRootFile(): void {
    const id = `new-file-${counter.current++}.txt`;
    const newNode = {
      id,
      name: id    };
    setItems(prev => [...prev, newNode]);
  }
  return <div>
      <div style={{
      marginBottom: 16
    }}>
        <Button aria-label="Add file at root level" onClick={handleAddRootFile} size={BUTTON_SIZE.xs} variant={BUTTON_VARIANT.outline}>
          <Icon name={ICON_NAME.plus} />
          Add file at root level        </Button>
      </div>
      <TreeView items={items} multiple>
        <TreeViewNodes>
          {items.map(item => <TreeViewNode key={item.id} item={item}>
              {({
            item,
            isBranch          }) => <div style={{
            display: 'flex',
            alignItems: 'center',
            width: '100%',
            justifyContent: 'space-between'
          }}>
                  <span style={{
              display: 'inline-flex',
              alignItems: 'center',
              gap: 6
            }}>
                    {isBranch ? <Icon name={ICON_NAME.folder} /> : <Icon name={ICON_NAME.file} />}
                    <span>{item.name}</span>
                  </span>
                  <div style={{
              display: 'inline-flex',
              marginLeft: 'auto',
              alignItems: 'center',
              gap: 8
            }}>
                    {isBranch ? <Button aria-label="Add child" onClick={e => {
                e.stopPropagation();
                handleAddChild(item.id);
              }} size={BUTTON_SIZE.xs} onKeyDown={e => {
                e.stopPropagation();
              }} variant={BUTTON_VARIANT.outline}>
                        <Icon name={ICON_NAME.plus} />
                      </Button> : null}
                    <Button aria-label="Delete" color={BUTTON_COLOR.critical} onClick={e => {
                e.stopPropagation();
                handleDelete(item.id);
              }} onMouseDown={e => {
                e.stopPropagation();
              }} onKeyDown={e => {
                e.stopPropagation();
              }} size={BUTTON_SIZE.xs} variant={BUTTON_VARIANT.outline}>
                      <Icon name={ICON_NAME.trash} />
                    </Button>
                  </div>
                </div>}
            </TreeViewNode>)}
        </TreeViewNodes>
      </TreeView>
    </div>;
}
```

## Recipes

---

Data Grid

| 
 | 

First Name

 | 

Last Name

 | 

Age

 | 

IP Address

 | 

Actions

 |
| --- | --- | --- | --- | --- | --- |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |
| 

 | 

 | 

 | 

 | 

 | 

 |

102550100300

of 0 results