fix(webapp): align tree mouse and keyboard interactions (#4701)

## Summary

Move tree selection onto semantic tree items and use native expansion
buttons.

Dashboard and story tree rows now share mouse and keyboard selection
through `getNodeProps`. Expand and collapse affordances are named
buttons instead of clickable layout elements.

Base: [#4700](https://github.com/triggerdotdev/trigger.dev/pull/4700)
This commit is contained in:
Chris Arderne
2026-08-19 16:35:47 +01:00
committed by GitHub
parent 5ae24710e4
commit 5e50d2f80d
3 changed files with 47 additions and 18 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: fix
---
Run trace rows now respond consistently to mouse and keyboard selection, including Alt-click expansion controls.
@@ -968,6 +968,11 @@ function TasksTreeView({
},
});
const getInteractiveNodeProps = (id: string) => ({
...getNodeProps(id),
onClick: () => selectNode(id),
});
return (
<div className="grid h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden">
<div className="flex items-center justify-between gap-2 border-b border-grid-dimmed px-1.5">
@@ -1047,7 +1052,7 @@ function TasksTreeView({
autoFocus
tree={events}
nodes={nodes}
getNodeProps={getNodeProps}
getNodeProps={getInteractiveNodeProps}
getTreeProps={getTreeProps}
parentClassName="pl-3"
renderNode={({ node, state, index }) => (
@@ -1058,9 +1063,6 @@ function TasksTreeView({
? "bg-grid-dimmed hover:bg-grid-bright"
: "bg-transparent hover:bg-grid-dimmed"
)}
onClick={() => {
selectNode(node.id);
}}
>
<div className="flex h-8 items-center">
{Array.from({ length: node.level }).map((_, index) => (
@@ -1070,9 +1072,18 @@ function TasksTreeView({
isSelected={state.selected}
/>
))}
<div
<button
type="button"
tabIndex={-1}
aria-label={
node.hasChildren
? state.expanded
? "Collapse task"
: "Expand task"
: "Select task"
}
className={cn(
"flex h-8 w-4 items-center",
"flex h-8 w-4 items-center focus-custom",
node.hasChildren && "hover:bg-surface-control"
)}
onClick={(e) => {
@@ -1083,10 +1094,13 @@ function TasksTreeView({
} else {
expandAllBelowDepth(node.level);
}
} else {
} else if (node.hasChildren) {
toggleExpandNode(node.id);
} else {
selectNode(node.id, false);
}
scrollToNode(node.id);
parentRef.current?.focus({ preventScroll: true });
}}
>
{node.hasChildren ? (
@@ -1098,7 +1112,7 @@ function TasksTreeView({
) : (
<div className="h-8 w-4" />
)}
</div>
</button>
</div>
<div className="flex w-full items-center justify-between gap-2 pl-1">
@@ -177,6 +177,11 @@ function TreeViewParent({
},
});
const getInteractiveNodeProps = (id: string) => ({
...getNodeProps(id),
onClick: () => toggleNodeSelection(id),
});
return (
<div className="flex w-72 flex-col items-start gap-y-4 p-4">
<div className="flex items-center gap-2">
@@ -203,7 +208,7 @@ function TreeViewParent({
autoFocus
tree={tree}
nodes={nodes}
getNodeProps={getNodeProps}
getNodeProps={getInteractiveNodeProps}
getTreeProps={getTreeProps}
parentClassName="h-96 bg-background-deep"
renderNode={({ node, state, index, virtualizer, virtualItem }) => (
@@ -215,19 +220,23 @@ function TreeViewParent({
"flex cursor-pointer items-center gap-2 py-1 hover:bg-blue-500/10",
state.selected && "bg-blue-500/20 hover:bg-blue-500/30"
)}
onClick={() => {
toggleNodeSelection(node.id);
}}
>
<div
className="h-4 w-4"
<button
type="button"
tabIndex={-1}
aria-label={
node.hasChildren
? state.expanded
? "Collapse node"
: "Expand node"
: "Select node"
}
className="h-4 w-4 focus-custom"
onClick={(e) => {
e.stopPropagation();
toggleExpandNode(node.id);
selectNode(node.id, true);
}}
onKeyDown={(e) => {
console.log(e.key);
parentRef.current?.focus();
}}
>
{node.hasChildren ? (
@@ -239,7 +248,7 @@ function TreeViewParent({
) : (
<DocumentIcon className="h-4 w-4" />
)}
</div>
</button>
<div>{node.data.title}</div>
</div>
)}