Skip to content

Trees, lists and declarative panels

Most plugins should not write components at all. A tree is described: rows come from a value source derived from your slice, and the shell handles rendering, selection, + / − folding and the right-click menu. A click emits your event — the handler is still the only place state changes. A list is simply a tree with no children.

const tree = {
items: valueFrom(slice, (state) =>
state.documents.map((document) => ({
id: document.id,
label: document.title, // i18n key or plain text
icon: FileText,
trailing: document.dirty ? "•" : undefined,
payload: { docId: document.id }, // click payload + context-menu context
// children: [...] // → foldable tree
})),
),
selectedId: valueFrom(slice, (state) => state.activeDocId),
event: "notes/document.select",
contextMenu: "notes.document", // a menu you declared as standalone
emptyText: ctx.i18n.key("panel.empty"),
};

An item also accepts description (row tooltip), badge, disabled, children and defaultExpanded.

A panel is then the combination of a toolbar and that tree, with no component of yours:

ctx.contribute({
id: "documents",
surface: "leftSidebar",
title: ctx.i18n.key("panel.documents"),
icon: FileCode,
panel: {
toolbar: true, // renders "panel:<pluginId>:documents" above the tree
tree,
},
});
  • toolbar: true uses the panel’s own toolbar id, so ctx.place({ commandId, toolbar: "panel:notes:documents" }) fills it; pass a string to target another toolbar id. The panel header then stops drawing that toolbar itself.
  • component and panel are exclusive: keep component when you really need custom UI (the close prompt of Notes, for example).
  • The same tree is available anywhere in your own UI as ctx.ui.Tree (or ValueTree from @/kernel in TypeScript). ctx.ui.List / ValueList remain aliases for compatibility.
  • In ui.json, use panel.tree. Existing panel.list files still work as the one-level-tree alias, but a panel cannot declare both tree and list.
  • Both Notes samples declare their Documents panel this way. Notes (TSX) goes one level deeper: every note has two sub-items, Text (the editor tab) and Tasks (the table tab titled <note> : Tasks). Fixed sub-items like these cannot be expressed by a JSON selector, so its ui.json points at a code binding — "items": { "ref": "documentTree" } — registered with ctx.ui.define({ items: { documentTree } }).

Documentation under CC BY 4.0 · © SCFlow contributors ·Back to supplychainflow.org