@@ -7,6 +7,11 @@ Its stable separation boundary is the public root exports, no monorepo runtime
77imports, and component CSS using only semantic ` --ui-* ` tokens. A future package
88build can emit those same entry points without API changes.
99
10+ Consumers compile these components with the React Compiler, so they follow the
11+ rules of React and lean on it for memoization. A component that breaks the
12+ rules is skipped silently rather than reported, which for a list or a tree
13+ costs a re-render per row, so check with the compiler and not only the linter.
14+
1015## CSS
1116
1217Import the semantic token mapping and codicon assets once in each real webview
@@ -38,12 +43,89 @@ Every component forwards `className` and `style` to its root element, and
3843default rules use single-class specificity, so a consumer class imported
3944after the library overrides any default (width, height, spacing).
4045
41- Where VS Code's stable rendering and its Modern UI preview
42- (` workbench.experimental.modernUI ` ) diverge, components follow Modern UI,
43- and new components should too. Webviews get no signal for the setting, so
44- the default cannot follow the host. Until the design settles,
45- ` data-ui-style="stable" ` on the document root restores the stable-parity
46- menu motion; Storybook's "UI style" toolbar switch toggles it live.
46+ VS Code currently uses its stable UI by default; Modern UI remains behind the
47+ experimental ` workbench.experimental.modernUI ` setting. ` @repo/ui `
48+ intentionally uses Modern UI as its package default because webviews receive no
49+ host signal for that setting. The divergence is isolated: set
50+ ` data-ui-style="stable" ` on the document root to restore stable row geometry,
51+ focus behavior, and menu motion. Storybook's "UI style" toolbar switch toggles
52+ that override live.
53+
54+ ## Tree
55+
56+ ` Tree ` is controlled: ` nodes ` describe the hierarchy, ` expandedIds ` controls
57+ branches, and ` selectedItemId ` controls selection. Each
58+ visible node renders as a flat ` treeitem ` , while normal keyboard navigation
59+ keeps DOM focus on the ` tree ` container and identifies the active row with
60+ ` aria-activedescendant ` . Focus and selection are independent.
61+
62+ ``` tsx
63+ const [selectedItemId, setSelectedItemId] = useState (" src" );
64+ const [expandedIds, setExpandedIds] = useState <readonly string []>([" src" ]);
65+
66+ <Tree
67+ aria-label = " Explorer"
68+ variant = " explorer"
69+ nodes = { [
70+ {
71+ id: " src" ,
72+ label: " src" ,
73+ children: [{ id: " tree" , label: " Tree.tsx" , icon: " symbol-class" }],
74+ },
75+ { id: " readme" , label: " README.md" , icon: " markdown" },
76+ ]}
77+ expandedIds = { expandedIds }
78+ onExpandedIdsChange = { setExpandedIds }
79+ selectedItemId = { selectedItemId }
80+ onSelectedItemChange = { setSelectedItemId }
81+ />;
82+ ```
83+
84+ Ids must be unique across the whole tree, and a duplicate throws. A string
85+ ` label ` is also the accessible name; a rich label must provide ` textValue ` . ` children ` marks a branch, including an empty array for a branch
86+ whose children are still loading. ` icon ` , ` action ` , and ` className ` customize
87+ the row. Actions stay live on plain hover, as in the native list, and are
88+ isolated from row selection and expansion.
89+
90+ Arrow Up/Down, Home, and End move the active row through visible rows. Arrow Right
91+ expands a branch or enters it; Arrow Left collapses it or moves to its parent.
92+
93+ ` expandMode="singleClick" ` is the default: clicking a branch selects
94+ and toggles it, and Enter does the same. With ` expandMode="doubleClick" ` , a
95+ single click or Enter only selects and a double click toggles expansion. Space
96+ toggles a branch without selecting it, or selects a leaf. A normal-row twistie
97+ toggles without changing selection. Alt-click recursively toggles descendant
98+ branches.
99+
100+ Escape clears selection, then the active focus mark. Once neither remains,
101+ Escape is left to the host. The root ` onKeyDown ` runs first, so a host
102+ can intercept shortcuts with ` preventDefault() ` .
103+
104+ ``` mermaid
105+ flowchart LR
106+ accTitle: Tree architecture
107+ accDescr: Data and input flow through the pure Tree modules into the React and DOM adapter.
108+
109+ Props[Nodes and controlled props] --> Model[treeModel.ts]
110+ Events[Pointer and keyboard events] --> Policy[treePolicy.ts]
111+ Policy --> Commands[Tree commands]
112+ Model --> Transition[treeTransition.ts]
113+ Commands --> Transition
114+ Transition --> Adapter[useTreeAdapter.ts]
115+ Adapter --> Rows[Tree.tsx and TreeRow.tsx]
116+ ```
117+
118+ The model, policy, and transitions stay pure. The adapter owns React and DOM
119+ integration. The flat visible model supports future windowing, but the Tree is
120+ not currently virtualized.
121+
122+ Rows are 22px tall and keep the VS Code twistie gutter. For Explorer-style file
123+ trees whose branches have no icons, ` variant="explorer" ` aligns leaf icons with
124+ branch twisties; do not combine it with branch icons. Indent guides appear on
125+ hover, selected ancestor paths stay active, and the focused path is active only
126+ while the tree has focus. The package default uses inset Modern UI rows;
127+ ` data-ui-style="stable" ` restores edge-to-edge square rows and stable focus
128+ styling.
47129
48130## Overlays
49131
@@ -79,7 +161,6 @@ until the exit animation ends. High contrast, `forced-colors`, and
79161- Keybinding hints show the contributed defaults the consumer passes, not
80162 user remaps: VS Code exposes no API for extensions to resolve a command's
81163 effective keybinding.
82- - List/selection-row tokens are deferred to the Tree suite (#1037 ).
83164
84165## Codicons
85166
@@ -97,4 +178,6 @@ declared CSS exports.
97178
98179Shared internals are reached through ` package.json ` subpath imports (` #cx ` ,
99180` #codicons ` , ` #storybook ` ). These resolve only inside this package and ship
100- with it, so they survive a standalone NPM split.
181+ with it, so they survive a standalone NPM split. Component families keep
182+ their own internals (contexts, stores) inside their folder and import them
183+ relatively, so a family can lift out wholesale.
0 commit comments