NockerlTree takes the recursive nodes forest + controlled expand/select state. Auto-expand
root folders on first load to match the canonical behaviour. A NockerlTreeNode is
{ id, name, kind: 'folder' | 'file', fileType?, children?, count?, loading?, disabled? }.
Prop
Type
Default
Description
nodes *
NockerlTreeNode[]
n/a
The recursive forest. Auto-expand root folders on first load to match the canonical behaviour.
selectable
NockerlTreeSelectable
'single'
single: one selected node (a cyan ring + cyan ink, no fill). multi: tri-state checkboxes that propagate parent↔child.
ariaLabel *
string
n/a
REQUIRED accessible name for the role="tree" container.
expandedIds *
Set<string>
n/a
Controlled set of expanded folder ids.
onToggleExpand
(next: Set<string>) => void
n/a
Fired with the next expanded set when a chevron (or a folder row) toggles.
selectedId
string | null
n/a
Selected node id (single-select).
onSelect
(id: string) => void
n/a
Fired when a leaf is activated (single-select).
checkedIds
Set<string>
n/a
Checked leaf ids (multi-select). A folder reads on / off / mixed from its descendants.
onCheckedChange
(next: Set<string>) => void
n/a
Fired when a node toggles its whole leaf-subtree (multi-select).
Row tap: toggle the folder (FileTreeViewModel.toggleFolder) or open the file (openFile).
modifier
Modifier
Modifier
External modifier applied to the row.
horizontalScrollState
ScrollState?
null
When set, the row sizes to its content and pans with one shared state (the Files sheet 2D scroll) so deep nesting / long names overflow instead of truncating.
minRowWidth
Dp
0.dp
Minimum content width in scrollable mode, sized to the viewport so short rows stay fully tappable.
NockerlTree rides the native hierarchical List(children:) over a recursive
NockerlTreeNode model and brands the rows. Single-select via selected +
onSelect (multi-select tri-state is a follow-up). Disclosure chevrons, indentation,
and hairline guides come from the platform list.
Parameter
Type
Default
Description
nodes *
[NockerlTreeNode]
n/a
The root nodes. Each is { id, name, kind: .folder | .file, children? }; kind defaults from children.
selected
String?
nil
The selected node id. Draws a thin cyan border + cyan ink on that row (law §6: state by outline, never a wash).
label *
String
n/a
The REQUIRED accessible name for the whole tree ("Project files"). Empty is a runtime assert (law §13).
onSelect *
(NockerlTreeNode) -> Void
n/a
Invoked with the tapped node (folders AND files). The caller decides whether a folder tap navigates or only discloses.
NockerlTreeNode.children is nil for a leaf (no chevron) and [] for an empty,
expandable folder.
Cross-platform drift (tracked for reconciliation)
The recursive tree ships on all three platforms. Web publishesNockerlTree
(@dizyx/nockerl-react) with both single-select and multi-select tri-state checkboxes.
Swift publishesNockerlTree (NockerlDesign), riding the native List(children:) over
NockerlTreeNode. Compose ships its own real hierarchy (FileTreeRow + FileTreeViewModel
flattening FileTreeNode → FlatFileNode, plus the EpicListRow/ChildListRow task
subtree). The native Swift + Android trees are single-select today. Multi-select
tri-state checkboxes (plus git-status badges and async-loading folders) are on web now and a
follow-up for Swift/Android. Voice’s older flat VocabRow / HistoryRow (expandedID) rows
are the accordion idiom, not this hierarchy.
Tree vs list vs accordion. A list is flat sectioned rows; an accordion is a
few stacked collapsible sections; a tree is a recursive, multi-level hierarchy with
per-node expand/collapse, depth indentation + guide lines, and the full WAI-ARIA tree
keyboard pattern. Reach for tree only when nesting is genuinely recursive (a file tree, a
task subtree).
Indent step: Compose uses 20dp per level for files (depth * 20) and 28dp for
the two-tier task subtree; the shipped web NockerlTree mirrors the 20px file-tree step. One
indent token should win across platforms.