TreeView
Hierarchical tree with search, selection, checkboxes, long-press actions and drag-and-drop reordering — API-aligned with the web TreeView.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-tree-viewpnpm add react-native-gesture-handler react-native-reanimated tailwind-variantsUsage
import { useState } from 'react';
import {
TreeView,
type TreeViewItem,
type TreeViewMenuItem
} from '@/components/docyrus-native/tree-view';
const menuItems: TreeViewMenuItem[] = [
{ id: 'open', label: 'Open', icon: 'fal eye', action: items => console.log(items) }
];
export function FilesTree({ initial }: { initial: TreeViewItem[] }) {
const [data, setData] = useState(initial);
return (
<TreeView
data={data}
iconMap={{ folder: 'fal folder', file: 'fal file-lines' }}
defaultExpandedIds={['root']}
showCheckboxes
draggable
maxHeight={420}
menuItems={menuItems}
onSelectionChange={items => console.log(items.map(i => i.id))}
onCheckChange={(item, checked) => setData(prev => setChecked(prev, item.id, checked))}
onDataChange={nextData => setData(nextData)} />
);
}Interaction model
| Gesture | Result |
|---|---|
| Tap row | Fires onItemClick; replaces the selection (toggles it with multiSelect); a folder expands when it is already selected, when expandFolderOnRowClick is set, or when selectOnRowClick is false |
| Tap chevron | Expands / collapses the folder without selecting it |
| Tap checkbox | Fires onCheckChange(item, checked) — the tree reads item.checked from data, so the host applies the change (same contract as web) |
| Long-press (non-draggable) | Opens the row action sheet: menuItems, Select / Deselect (adds to the selection — the mobile stand-in for ⌘/Ctrl-click), Move to… (when draggable), Details (when showInfo) |
Long-press + drag (draggable) | Lifts the row; dragging over rows shows a before / after line or an inside highlight (inside only when canDropInside allows, never into the item's own subtree). Hovering inside a collapsed folder opens it; the tree's own scroller (with maxHeight) auto-scrolls at its edges. Releasing below the last row moves the item to the root. Drop calls moveTreeViewItem and fires onDataChange |
Long-press + release without moving (draggable) | Opens the row action sheet |
Move to… is the accessible fallback for drag and drop: it lists every valid target (the moved item and its descendants are excluded) plus Top level, and offers Before / Inside / After for the picked target. It fires the same onDataChange(nextData, details) contract. Drag is disabled while a search query is active (web parity).
onSelectionChange is emitted only from user interaction (tap, action sheet, clear) — never because a callback identity or data changed — so inline handlers cannot cause update loops.
API Reference
TreeViewProps
| Prop | Type | Default | Description |
|---|---|---|---|
data | TreeViewItem[] | - | Tree data. The tree keeps an internal copy (so drag moves apply immediately) that is re-seeded whenever data changes |
variant | 'default' | 'outline' | 'ghost' | 'default' | Container style |
size | 'sm' | 'default' | 'lg' | 'default' | Row height and text size |
showExpandAll | boolean | true | Show the expand / collapse all toggle next to the search input |
showCheckboxes | boolean | false | Show checkboxes next to items (leaf state from item.checked, folder state derived from children incl. indeterminate) |
checkboxPosition | 'left' | 'right' | 'left' | Checkbox position |
searchPlaceholder | string | t('ui.treeView.searchPlaceholder', 'Search...') | Search input placeholder |
selectionText | string | t('ui.treeView.selected', 'selected') | Text after the selection count ("3 selected") |
checkboxLabels | { check: string; uncheck: string } | { check: 'Check', uncheck: 'Uncheck' } (translated) | Labels of the bulk check / uncheck buttons shown in the selection toolbar when showCheckboxes |
getIcon | (item: TreeViewItem, depth: number) => ReactNode | - | Custom icon renderer (wins over iconMap) |
iconMap | TreeViewIconMap | {} | item.type → icon. A string renders as a DocyrusIcon identifier ('fal folder'), any other node renders as-is |
onSelectionChange | (selectedItems: TreeViewItem[]) => void | - | Selection changed (items in tree order) |
onCheckChange | (item: TreeViewItem, checked: boolean) => void | - | A checkbox or bulk check / uncheck changed an item (bulk calls it for every selected item and its descendants) |
menuItems | TreeViewMenuItem[] | - | Long-press action sheet items |
onAction | (action: string, items: TreeViewItem[]) => void | - | Fired after a menu item's action with the menu item id |
draggable | boolean | false | Enable long-press drag reorder / re-parenting and the "Move to…" action |
canDropInside | (item: TreeViewItem) => boolean | canTreeViewItemAcceptChildren | Whether an item can accept children (default: it has a children array) |
onDataChange | (nextData: TreeViewItem[], details: TreeViewMoveDetails) => void | - | Tree changed by drag or "Move to…" |
showInfo | boolean | true | Offer a Details entry (type, id, location path, direct item count) in the long-press sheet |
maxHeight | number | string | - | Max height before the tree scrolls in its own scroller — 420, '420px' or '60%' |
defaultExpandedIds | ReadonlyArray<string> | - | Ids expanded on mount (uncontrolled — later changes do not re-seed) |
expandFolderOnRowClick | boolean | false | Tapping a folder row toggles it on the first tap, not only when it is already selected |
onItemClick | (item: TreeViewItem) => void | - | Row tapped — always fired, independent of selectOnRowClick |
selectOnRowClick | boolean | true | When false, a row tap never changes the selection (picker mode) — it only expands folders and fires onItemClick; the Select entry is hidden |
multiSelect | boolean | false | Native adaptation of web's ⌘/Ctrl-click: a row tap toggles the row in the selection instead of replacing it |
longPressDelay | number | 400 | Long-press duration (ms) before a row lifts for dragging / the action sheet opens |
className | string | - | Additional classes for the root container |
testID | string | - | Test id for the root container |
TreeViewItem
| Field | Type | Default | Description |
|---|---|---|---|
id | string | - | Unique identifier |
name | string | - | Display name (also searched) |
type | string | - | Item type — keys iconMap, shown in Details |
children | TreeViewItem[] | - | Child items. An array (even empty) makes the item a folder |
checked | boolean | - | Checked state for leaves (folders derive theirs) |
draggable | boolean | - | false excludes the item from drag / "Move to…" |
[key: string] | unknown | - | Any extra payload |
TreeViewMenuItem
| Field | Type | Description |
|---|---|---|
id | string | Action id passed to onAction |
label | string | Action label |
icon | ReactNode | A DocyrusIcon string ('fal eye') is shown in the action sheet; other nodes are ignored on native |
action | (items: TreeViewItem[]) => void | Receives the whole selection when the long-pressed row is selected, otherwise just that row |
TreeViewMoveDetails
| Field | Type | Description |
|---|---|---|
activeItem | TreeViewItem | The moved item |
overItem | TreeViewItem | null | The target item (null for root) |
parentItem | TreeViewItem | null | The new parent (null at root level) |
position | TreeViewDropPosition | 'before' | 'inside' | 'after' | 'root' |
Helpers
Pure helpers synced byte-for-byte with web @docyrus/ui — use them to apply moves or validate targets outside the component.
| Export | Signature | Description |
|---|---|---|
moveTreeViewItem | (items, { activeId, overId, position, canDropInside? }) => { nextData, details } | null | Immutable move; returns null for invalid moves (onto itself, into its own subtree, inside a non-container) |
canTreeViewItemAcceptChildren | (item) => boolean | Default canDropInside — true when children is an array |
treeItemContainsId | (item, targetId) => boolean | true when targetId is the item or one of its descendants |
treeViewVariants | tv() → string | Root container classes (variant, size) |
Translation keys
Copy resolves through useUiTranslation() (explicit prop → <UiTranslationProvider> → English fallback). Shared with web: ui.treeView.searchPlaceholder, ui.treeView.selected, ui.treeView.check, ui.treeView.uncheck, ui.treeView.clearSelection, ui.treeView.noItemsFound. Native additions: ui.treeView.expandAll, ui.treeView.collapseAll, ui.treeView.clearSearch, ui.treeView.select, ui.treeView.deselect, ui.treeView.moveTo, ui.treeView.moveToRoot, ui.treeView.moveBefore, ui.treeView.moveInside, ui.treeView.moveAfter, ui.treeView.details, ui.treeView.close, ui.treeView.infoType, ui.treeView.infoId, ui.treeView.infoLocation, ui.treeView.infoItems, ui.treeView.directItems.
Components
| Component | Description |
|---|---|
TreeView | Main component |
Type Exports
| Type | Description |
|---|---|
TreeViewProps | Props for TreeView |
TreeViewItem | Tree node |
TreeViewIconMap | item.type → icon map |
TreeViewMenuItem | Long-press action |
TreeViewMoveDetails | Payload of onDataChange |
TreeViewDropPosition | 'before' | 'inside' | 'after' | 'root' |
TreeViewCheckboxPosition | 'left' | 'right' |
TreeViewVariant | 'default' | 'outline' | 'ghost' |
TreeViewSize | 'sm' | 'default' | 'lg' |
TreeTable
Hierarchical table with a pinned name column, horizontally scrolling data columns, search, expand / collapse all and a "Move to…" sheet — API-aligned with the web TreeTable.
Value Renderers
Read-only display components for Docyrus field values — tenant-aware dates / numbers, status companions, approval history, relation expand and more.