Oihana Next UI - 0.48.0

Application logo

Arbres

Deux arbres qui ne font pas le même métier : l’un réordonne une hiérarchie au glisser-déposer, l’autre lit une hiérarchie qui n’existe que dans des chemins pointés.

PathTree — une hiérarchie qui n’existe que dans les chaînes

Aucun des éléments ci-dessous ne dit qui est son parent : la hiérarchie est dans son chemin pointé, et groupByPath la lit. Les deux cartes montrent le même arbre en lecture et en sélection.

En lecture

Le badge compte les feuilles du sous-arbre entier. Le dépliage est un bouton, pas un details : voir le JSDoc du composant.

En sélection

Chaque dossier porte une case tri-état sur tout son sous-arbre. Les deux lignes marquées d’un cadenas sont verrouillées : elles sortent du compte, sinon la case resterait indéterminée pour toujours.

2 sélectionné(s)

Deux décisions visibles dans les données

Le chemin le plus profond dépasse le plafond de 4 niveaux : sa queue est RECOLLÉE en un seul segment plutôt que coupée, pour qu’aucun élément ne se retrouve sans parent.

Le dernier élément n’a pas de chemin du tout : il atterrit dans le dossier « — » au lieu de disparaître de la liste.

Arbre réordonnable (glisser verticalement pour réordonner, horizontalement pour indenter)

Glissez une ligne vers le haut ou le bas pour la réordonner parmi ses voisines, vers la gauche ou la droite pour changer sa profondeur : la position horizontale du pointeur projette le parent visé. Glisser un dossier déplace tout son sous-arbre (replié pendant le glissement), de sorte qu'un nœud ne peut jamais être déposé dans ses propres enfants.

src
components
Button.jsx
Input.jsx
hooks
useValue.js
index.js
public
logo.svg
README.md
<SortableTree
    defaultItems={ [ { id , label , children : [...] } ] }
    renderNode={ node => <SortableTreeItem>{ node.label }</SortableTreeItem> }
/>

Arbre piloté (structure en direct)

src
components
Button.jsx
Input.jsx
hooks
useValue.js
index.js
public
logo.svg
README.md
[
  {
    "src": [
      {
        "components": [
          "Button.jsx",
          "Input.jsx"
        ]
      },
      {
        "hooks": [
          "useValue.js"
        ]
      },
      "index.js"
    ]
  },
  {
    "public": [
      "logo.svg"
    ]
  },
  "README.md"
]
<SortableTree
    items={ tree }
    onChange={ ( next , change ) => setTree( next ) }
/>
// change = { item , fromParent , toParent , fromIndex , toIndex }

Changement asynchrone avec appel d’API (retour en arrière optimiste en cas d’échec)

src
components
Button.jsx
Input.jsx
hooks
useValue.js
index.js
public
logo.svg
README.md
<SortableTree
    defaultItems={ tree }
    onChange={ ( next , change ) => api.save( next ) }
/>
// onChange peut rendre une promesse : un rejet restaure l’arbre précédent

Profondeur maximale (maxDepth = 2) et indicateur de dépôt

L'imbrication est plafonnée à 2 niveaux : glisser une ligne plus profond la ramène à la limite. Le trait bleu est l'indicateur de dépôt — il montre le point d'insertion exact et, par son indentation, la profondeur où le nœud atterrira. Un dossier compte son propre sous-arbre dans la limite.

src
components
Button.jsx
Input.jsx
hooks
useValue.js
index.js
public
logo.svg
README.md
<SortableTree maxDepth={ 2 } defaultItems={ tree } renderNode={ ... } />

Liste plate (maxDepth = 0 — un seul niveau racine, aucune imbrication)

Avec maxDepth à 0, chaque nœud reste à la profondeur 0 : glisser une ligne latéralement ne l'indente jamais, et l'arbre se comporte comme une simple liste réordonnable — seul l'ordre vertical change. collapsible à false va avec, puisqu'il ne reste aucun sous-arbre à replier.

Introduction
Installation
Usage
API Reference
FAQ
<SortableTree
    maxDepth={ 0 }
    collapsible={ false }
    defaultItems={ [ { id , label } , ... ] }
    renderNode={ node => <SortableTreeItem>{ node.label }</SortableTreeItem> }
/>

Règle d’imbrication (canNest — seuls les dossiers acceptent des enfants)

Les fichiers se réordonnent mais ne reçoivent jamais d'enfants : glisser un nœud sous un fichier fait remonter le dépôt au niveau de ce fichier (son dossier parent). L'indicateur de dépôt passe au rouge quand aucun parent valide n'existe à cette position.

src
app
page.jsx
utils.js
README.md
<SortableTree
    canNest={ ( item , parent ) => !parent || parent.type === 'folder' }
    defaultItems={ tree }
    renderNode={ ... }
/>

Tout déplier / tout replier (repli piloté)

L'état replié est piloté par collapsed et onCollapsedChange, donc le parent peut le commander — ici deux boutons le mettent à tous les identifiants de dossier (tout replier) ou à une liste vide (tout déplier).

src
components
Button.jsx
Input.jsx
hooks
useValue.js
index.js
public
logo.svg
README.md
<SortableTree
    collapsed={ collapsedIds }
    onCollapsedChange={ setCollapsedIds }
    defaultItems={ tree } renderNode={ ... }
/>

Arbre gelé (disabled et collapsible à false)

disabled coupe tout glissement et collapsible à false retire les chevrons en gardant chaque nœud déplié — un arbre en lecture seule, entièrement statique.

src
components
Button.jsx
Input.jsx
hooks
useValue.js
index.js
public
logo.svg
README.md
<SortableTree disabled collapsible={ false } defaultItems={ tree } renderNode={ ... } />

Ajouter et retirer des nœuds (insertion dans un sous-dossier)

L'arbre est une valeur imbriquée pilotée, donc ajouter et retirer sont de simples mises à jour d'état. Chaque dossier porte un bouton « + » qui insère un enfant dans SES enfants (visé par son identifiant) — le dossier se déplie tout seul pour le montrer ; chaque nœud porte une « × » pour le supprimer avec son sous-arbre. Le réordonnancement au glisser continue de fonctionner à côté.

src
components
Button.jsx
index.js
README.md
import insertNode from 'oihana-next-ui/helpers/trees/insertNode' ;
import removeNode from 'oihana-next-ui/helpers/trees/removeNode' ;
// insérer dans un sous-dossier (par identifiant) — ou null pour la racine
setTree( t => insertNode( t , folderId , newNode ) ) ;
// retirer un nœud et son sous-arbre
setTree( t => removeNode( t , nodeId ) ) ;

Référence des props

PropTypeDescription
canNestfunction(élément glissé, parent | null) => booléen — null = niveau racine ; un parent refusé fait remonter le dépôt jusqu’au plus proche ancêtre valide (indicateur rouge s’il n’y en a aucun)
collapsedArrayListe pilotée des identifiants de nœuds repliés ; à associer à onCollapsedChange
collapsiblebooleanAutorise le dépliage et le repli (true par défaut) ; à false, les chevrons disparaissent et chaque nœud reste déplié
defaultCollapsedArrayIdentifiants des nœuds repliés au départ (repli non piloté)
defaultItemsArrayArbre initial non piloté (le composant le possède et le met à jour)
disabledbooleanCoupe le glissement sur chaque nœud (false par défaut) ; l’arbre peut toujours être déplié et replié
getItemIdfunction(élément) => chaîne | nombre — accès à l’identifiant unique (item.id par défaut)
handlebooleanAffiche une poignée de glissement sur chaque ligne (true par défaut) ; à false, toute la ligne devient glissable
indentnumberLargeur d’indentation par niveau, en pixels (24 par défaut)
itemsArrayArbre imbriqué piloté ; à associer à onChange
maxDepthnumberProfondeur maximale d’imbrication ; un dossier glissé compte la hauteur de son propre sous-arbre dedans
onChangefunction(arbre, changement) => void | Promesse — une promesse rejetée restaure l’arbre (non piloté)
onCollapsedChangefunction(identifiants repliés) => void — appelé avec les nouveaux identifiants à chaque bascule
renderNodefunction(élément, état) => un élément SortableTreeItem (obligatoire)