From 749bd49f78f0d040b8ae1056f6bf73d1eaa18efd Mon Sep 17 00:00:00 2001 From: kurtddanielbigtas Date: Thu, 9 Apr 2026 22:50:41 +0800 Subject: [PATCH] Add sticky header to SidebarSelectedFiberInfo Previously, WhatChanged and InspectedElementBadges were rendered inside the same scrollable Content div as the commit list. When a component had many render timestamps, scrolling down to pick one caused the "what changed" context to scroll out of view, requiring the user to scroll back up to see it. While the profiler already exposes render details via hover tooltips on fibers, those are transient and pointer-dependent. This change introduces a sticky StickyHeader section at the top of the Content div that holds the current-commit summary, badges, and WhatChanged. The section is collapsible (click or Enter/Space) so users can trade vertical space for the commit list when needed. The commit list now sits below the sticky header as a sibling, so it scrolls independently while the summary remains always visible. --- .../Components/InspectedElementBadges.css | 11 +-- .../Profiler/SidebarSelectedFiberInfo.css | 63 ++++++++++++++++- .../Profiler/SidebarSelectedFiberInfo.js | 69 ++++++++++++++++--- 3 files changed, 127 insertions(+), 16 deletions(-) diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css index 5c8c977fb810..1414405153e2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css @@ -1,8 +1,9 @@ .Root { user-select: none; - display: inline-flex; -} - -.Root *:not(:first-child) { - margin-left: 0.25rem; + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-wrap: wrap; + justify-content: start; + gap: 0.25rem; } diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css index 2992247eb8f2..ef0b90694405 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css @@ -7,10 +7,69 @@ border-bottom: 1px solid var(--color-border); } +.StickyHeader { + position: sticky; + top: 0; + z-index: 1; + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem 0; + background: var(--color-background); +} + +.StickyHeader::after { + content: ""; + position: absolute; + inset-inline: -0.5rem; + bottom: 0; + height: 1px; + background: var(--color-border); +} + +.HeaderRow { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + cursor: pointer; + user-select: none; + border-radius: 4px; + padding: 0.25rem 0; +} + +.HeaderRow:hover .RenderSummary { + text-decoration: underline; +} + +.HeaderRow:focus-visible { + background: var(--color-button-background-focus); + outline: none; +} + +.HeaderRowControls { + flex: 0 0 auto; +} + +.RenderSummary { + flex: 1; + min-width: 0; + color: var(--color-text-secondary); +} + +.CurrentRenderInfo strong { + color: var(--color-text-primary); +} + +.CollapsibleContent { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + .Content { - padding: 0.5rem; + padding: 0 0.5rem 0.5rem; user-select: none; - overflow-y: auto; display: flex; flex-direction: column; gap: 0.5rem; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js index d785cfebdacd..7b07dbef4159 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js @@ -8,7 +8,7 @@ */ import * as React from 'react'; -import {Fragment, useContext, useEffect, useRef} from 'react'; +import {Fragment, useContext, useEffect, useRef, useState} from 'react'; import WhatChanged from './WhatChanged'; import {ProfilerContext} from './ProfilerContext'; @@ -32,6 +32,7 @@ export default function SidebarSelectedFiberInfo(): React.Node { } = useContext(ProfilerContext); const {profilingCache} = profilerStore; const selectedListItemRef = useRef(null); + const [collapsed, setCollapsed] = useState(false); useEffect(() => { const selectedElement = selectedListItemRef.current; @@ -114,6 +115,29 @@ export default function SidebarSelectedFiberInfo(): React.Node { ); } + let selectedCommitTime = 0; + let selectedCommitDuration = 0; + + if (selectedCommitIndex !== null && rootID !== null) { + const commitData = profilerStore.getCommitData( + ((rootID: any): number), + selectedCommitIndex, + ); + selectedCommitTime = commitData.timestamp; + selectedCommitDuration = commitData.duration; + } + + // $FlowFixMe[missing-local-annot] + const handleHeaderKeyDown = event => { + if (event.key === 'Enter' || event.key === ' ') { + // Prevent the browser from scrolling down when Space is pressed + if (event.key === ' ') { + event.preventDefault(); + } + setCollapsed(prevCollapsedVal => !prevCollapsedVal); + } + }; + return (
@@ -127,14 +151,41 @@ export default function SidebarSelectedFiberInfo(): React.Node {
-
- {node != null && ( - - )} - +
+
+
setCollapsed(prevCollapsedVal => !prevCollapsedVal)} + tabIndex={0} + role="button" + aria-expanded={!collapsed} + onKeyDown={handleHeaderKeyDown}> +
+ + + {formatTime(selectedCommitTime)}s for{' '} + {formatDuration(selectedCommitDuration)}ms + + +
+ +
+ +
+
+ + {!collapsed && ( +
+ {node != null && ( + + )} + +
+ )} +
{listItems.length > 0 && (