Tree
Le composant Tree permet d'afficher une arborescence dynamique et hiérarchique à partir d'un tableau d'éléments. Il supporte le chargement synchrone et asynchrone des données, idéal pour :
- Naviguer dans des structures hiérarchiques complexes
- Construire des systèmes de pages imbriquées
- Afficher une organisation de wiki ou de documentation
- Gérer des arbres de fichiers ou de catégories
- Implémenter des menus hiérarchiques interactifs
Le composant offre une expérience utilisateur fluide avec support du chargement à la demande, des actions personnalisées et de la mise à jour dynamique des données.
Utilisation basique
Affiche une arborescence simple avec des données statiques passées via le prop data. Les enfants peuvent être définis directement dans la structure avec la propriété children.
import { Tree } from '@/components/ui/tree';
const mockData = [
{
id: 'foo',
label: 'Foo',
},
{
id: 'bar',
label: 'Bar',
children: [
{
id: 'nested_bar',
label: 'Nested bar',
},
],
},
];
export default function Component() {
return <Tree data={mockData} />
}
Utilisation asynchrone
Charge les enfants à la demande lors de l'expansion d'un élément. Utilisez le prop async avec une fonction onLoad pour récupérer dynamiquement les données depuis une API ou une autre source. Idéal pour les arbres volumineux ou les données en temps réel.
import { Tree } from '@/components/ui/tree';
const mockData = [
{
id: 'foo',
label: 'Foo',
},
{
id: 'bar',
label: 'Bar',
hasChild: true,
},
];
export default function Component() {
/**
* Charge les enfants d'un élément depuis une API (ou une autre source de données)
* @param id - Identifiant de l'élément parent
* @returns Tableau des enfants
*/
async function handleLoad(id) {
try {
const remoteData = await fetch('...');
// Exemple de structure de données à retourner :
// [
// { id: 'nested_foo', label: 'Nested foo' },
// { id: 'nested_bar', label: 'Nested bar', hasChild: true },
// ]
return remoteData;
} catch (error) {
// Si vous souhaitez laisser la possibilité de retenter une chargement
return undefined;
// Si vous ne souhaitez pas laisser la possibilité de retenter une chargement
return [];
}
};
return <Tree data={mockData} async onLoad={handleLoad} />
}
Mise à jour d'un élément racine
Modifiez les éléments au niveau racine en mettant à jour l'état baseData. Ces changements affectent directement le rendu du composant.
import { Tree } from '@/components/ui/tree';
import { useState } from 'react';
const mockData = [
{
id: 'foo',
label: 'Foo',
},
{
id: 'bar',
label: 'Bar',
hasChild: true,
},
];
export default function Component() {
const [baseData, setBaseData] = useState(mockData);
// Pour mettre à jour les éléments racine, il suffit de mettre à jour les données passées au prop data
function handleRootUpdate() {
setBaseData((prevData) => {
return [...]
})
};
return (
<>
<button onClick={handleRootUpdate}>Mise à jour du groupe `nested_bar` (si chargé)</button>
<Tree async data={baseData} onLoad={...} />
</>
)
}
Mise à jour d'un enfant déjà chargé
Modifiez les propriétés d'un élément enfant spécifique en utilisant updateItem. Cette fonction permet une mise à jour directe ou basée sur les données existantes, idéale pour mettre à jour un seul élément sans recharger toute la branche.
import { Tree, useAsyncTreeController } from '@/components/ui/tree';
import { useState } from 'react';
const mockData = [
{
id: 'foo',
label: 'Foo',
},
{
id: 'bar',
label: 'Bar',
hasChild: true,
},
];
export default function Component() {
const [baseData, setBaseData] = useState(mockData);
const { controller, updateItem } = useAsyncTreeController();
/**
* Mise à jour d'un enfant
* - Première approche : mise à jour directe
* - Deuxième approche : mise à jour basée sur la valeur précédente
*/
function handleChildUpdate() {
// Mise à jour directe avec une nouvelle valeur
updateItem('nested_foo', {
label: 'Nested foo (updated)',
hasChild: false,
});
// Mise à jour basée sur l'ancienne valeur
updateItem('nested_foo', (item) => {
return {
...item,
label: 'Nested foo (updated)',
hasChild: false,
};
});
};
return (
<>
<button onClick={handleChildUpdate}>Mise à jour de l'enfant `nested_foo` (si chargé)</button>
<Tree async data={baseData} controller={controller} onLoad={...} />
</>
)
}
Mise à jour d'un groupe d'enfants
Remplacez ou modifiez tous les enfants d'un élément parent en utilisant updateGroup. Cette approche est utile pour ajouter, supprimer ou réorganiser plusieurs enfants en une seule opération.
import { Tree, useAsyncTreeController } from '@/components/ui/tree';
import { useState } from 'react';
const mockData = [
{
id: 'foo',
label: 'Foo',
},
{
id: 'bar',
label: 'Bar',
hasChild: true,
},
];
export default function Component() {
const [baseData, setBaseData] = useState(mockData);
const { controller, updateGroup } = useAsyncTreeController();
/**
* Mise à jour d'un groupe d'enfants
* - Première approche : mise à jour directe
* - Deuxième approche : mise à jour basée sur la valeur précédente
*/
function handleGroupUpdate() {
// Mise à jour directe avec une nouvelle valeur
updateGroup('nested_bar', [
{
id: "double_nested_foo",
label: 'Double nested foo (updated)',
hasChild: false,
},
{
id: "double_nested_bar",
label: 'Double nested bar (updated)',
hasChild: false,
},
]);
// Mise à jour basée sur l'ancienne valeur
updateGroup('nested_bar', (items) => {
return [
...items,
{
id: "double_nested_bar",
label: 'Double nested bar (updated)',
hasChild: false,
},
];
});
};
return (
<>
<button onClick={handleGroupUpdate}>Mise à jour du groupe `nested_bar` (si chargé)</button>
<Tree async data={baseData} controller={controller} onLoad={^...} />
</>
)
}
Référence API
| Prop | Type | Default | Description |
|---|---|---|---|
| data | Array<TreeItem<TData>> | - | Tableau des éléments de l'arborescence. En mode asynchrone, seul le premier niveau est obligatoire |
| gap | number | 4 | Espacement vertical (en pixels) entre les éléments de l'arborescence |
| offset | number | 8 | Indentation (en pixels) appliquée à chaque niveau imbriqué |
| bordered | boolean | false | Affiche des lignes de connexion verticales pour améliorer la lisibilité des niveaux d'imbrication |
| disabled | boolean | false | Désactive l'arborescence et applique un style visuel de composant inactif |
| actions | ((id: string) => ReactNode) | Partial<{ group: (id: string) => ReactNode; item: (id: string) => ReactNode }> | - | Configuration des actions personnalisées affichées à côté de chaque élément |
| async | boolean | false | Active le chargement asynchrone des données. Les enfants d'un groupe sont chargés à la première expansion |
| onLoad | (id: string) => Promise<Array<TreeItem<TData>> | undefined> | - | Fonction de récupération des données du groupe cliqué |
| controller | { setAsyncItems: RefObject<Dispatch<SetStateAction<Record<string, Array<TreeItem<TData>>>>> | null> } | - | Instance du contrôleur pour gérer l'état et les interactions de l'arborescence |
Typescript
TreeProps
interface TreePropsBase<TData = any> {
data: Array<TreeItem<TData>>;
gap?: number;
offset?: number;
bordered?: boolean;
disabled?: boolean;
actions?: ((id: string) => ReactNode) | Partial<{ group: (id: string) => ReactNode; item: (id: string) => ReactNode }>;
async?: never;
onLoad?: never;
controller?: never;
}
interface TreePropsAsync<TData = any> extends Omit<TreePropsBase<TData>, 'async' | 'onLoad' | 'controller'> {
async: true;
onLoad: (id: string) => Promise<Array<TreeItem<TData>> | undefined>;
controller?: AsyncTreeController<TData>['controller'];
}
export type TreeProps<TData = any> = TreePropsBase<TData> | TreePropsAsync<TData>;
TreeItem
export interface TreeItem<TData = any> {
id: string;
label: ReactNode;
icon?: ((props: LucideProps) => ReturnType<typeof Icon>) | ForwardRefExoticComponent<Omit<LucideProps, 'ref'>>;
hasChild?: boolean;
children?: Array<TreeItem<TData>>;
actions?: (id: string) => ReactNode;
disabled?: boolean;
active?: boolean;
data?: TData;
}
useAsyncTreeController
export interface AsyncTreeController<TData = any> {
controller: {
setAsyncItems: RefObject<Dispatch<SetStateAction<Record<string, Array<TreeItem<TData>>>>> | null>;
};
updateItem: (id: string, value: TreeItem<TData> | ((item: TreeItem<TData>) => TreeItem<TData>)) => void;
updateGroup: (id: string, value: Array<TreeItem<TData>> | ((group: Array<TreeItem<TData>>) => Array<TreeItem<TData>>)) => void;
}