Docyrus

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.

iOSAndroid
Preview TreeTable on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-tree-table
Required Packages(3 packages)
pnpm add @shopify/flash-list react-native-reanimated tailwind-variants

Usage

import { useState } from 'react';

import { StatusChip } from '@/components/docyrus-native/status-chip';
import { TreeTable, type TreeTableColumn } from '@/components/docyrus-native/tree-table';
import { type TreeViewItem } from '@/components/docyrus-native/tree-view';

const columns: TreeTableColumn[] = [
  { id: 'status', header: 'Status', width: 130, cell: item => <StatusChip label={String(item.status)} size="sm" /> },
  { id: 'owner', header: 'Owner', accessorKey: 'owner', width: 160 },
  { id: 'progress', header: 'Progress', accessorKey: 'progress', width: 100, align: 'right' }
];

export function ProjectTree({ initial }: { initial: TreeViewItem[] }) {
  const [data, setData] = useState(initial);

  return (
    <TreeTable
      data={data}
      columns={columns}
      nameColumnHeader="Work item"
      draggable
      canDropInside={item => item.type !== 'task'}
      maxHeight={420}
      onDataChange={nextData => setData(nextData)} />
  );
}

Layout and interaction

  • Rows are the flattened visible tree rendered by a FlashList with fixed row heights. The name column plus all data columns sit inside one horizontal ScrollView; with stickyNameColumn (default) the name cells are translated by the horizontal scroll offset (reanimated), so the hierarchy stays visible while the data columns scroll underneath.
  • Tapping the chevron expands / collapses a folder. Tapping a row calls onRowPress; without onRowPress a folder row toggles itself.
  • The options button next to the search opens Expand All / Collapse All (hidden with showExpandAll={false}). Searching filters by name and opens matching branches.
  • draggable replaces web drag-and-drop with a "Move to…" sheet (long-press drag inside nested, horizontally scrolling rows is unreliable on a phone). Each row gets a … button (long-press works too); the sheet lists every valid target (the moved item and its subtree are excluded) plus Top level, and offers Before / Inside / After — Inside only when canDropInside(target) allows. The move goes through moveTreeViewItem and fires onDataChange(nextData, details) exactly like web. Moving is disabled while a search query is active, and items with draggable: false get no … button.

API Reference

TreeTableProps

PropTypeDefaultDescription
dataTreeViewItem[]-Tree rows. The table keeps an internal copy (so moves apply immediately) that is re-seeded whenever data changes
columnsTreeTableColumn[]-Data columns rendered after the name column
variant'default' | 'outline' | 'ghost''default'Container style (treeViewVariants)
size'sm' | 'default' | 'lg''default'Row height (36 / 44 / 52) and text size
nameColumnHeaderReactNodet('ui.treeTable.name', 'Name')Header of the name column
nameColumnWidthnumber200Name column width in dp (native addition)
getIcon(item: TreeViewItem, depth: number) => ReactNode-Icon rendered before the name
showExpandAllbooleantrueShow the options button with Expand All / Collapse All
searchPlaceholderstringt('ui.treeView.searchPlaceholder', 'Search...')Search placeholder
emptyTextstringt('ui.treeView.noItemsFound', 'No items found')Text when no rows match
draggablebooleanfalseAdds the row … button / long-press that opens the "Move to…" sheet
canDropInside(item: TreeViewItem) => booleancanTreeViewItemAcceptChildrenWhether an item accepts children (the Inside position)
onDataChange(nextData: TreeViewItem[], details: TreeViewMoveDetails) => void-Tree changed by a move
maxHeightnumber | string-Max height (dp or 'Npx') before rows scroll vertically; without it every row renders at full height
onRowPress(item: TreeViewItem) => void-Row tapped (native addition). Without it, a folder row tap toggles the folder
stickyNameColumnbooleantrueKeep the name column pinned while the data columns scroll horizontally (native addition)
classNamestring-Additional classes for the root container
testIDstring-Test id for the root container

TreeTableColumn

FieldTypeDefaultDescription
idstring-Column id
headerReactNode-Header content (strings are wrapped in Text)
accessorKeystring-Key read from the item when cell is not set
cell(item: TreeViewItem) => ReactNode-Custom cell renderer
widthnumber120Column width in dp (web also accepts CSS strings)
align'left' | 'center' | 'right''left'Cell alignment
classNamestring-Extra classes for body cells
headerClassNamestring-Extra classes for the header cell

Differences from web

  • ref and div HTML attributes are dropped (className + testID remain).
  • Drag-and-drop (dnd-kit) is replaced by the "Move to…" sheet; onDataChange / TreeViewMoveDetails are identical and position is one of before / after / inside / root.
  • TreeTableColumn.width is a number.
  • nameColumnWidth, onRowPress and stickyNameColumn are native additions.

Translation keys

Copy resolves through useUiTranslation(). Shared with web: ui.treeView.searchPlaceholder, ui.treeView.noItemsFound. Native additions: ui.treeTable.name, ui.treeTable.options, ui.treeView.expandAll, ui.treeView.collapseAll, ui.treeView.expand, ui.treeView.collapse, ui.treeView.moveTo, ui.treeView.moveToRoot, ui.treeView.moveBefore, ui.treeView.moveInside, ui.treeView.moveAfter.

Components

ComponentDescription
TreeTableMain component

Type Exports

TypeDescription
TreeTablePropsProps for TreeTable
TreeTableColumnColumn definition

On this page