From e3cdaac265107c9b025c52b2aebd3e2ee2477401 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 26 Sep 2025 15:41:42 -0400 Subject: [PATCH] Move Timeline to footer instead of header --- .../views/SuspenseTab/SuspenseTab.css | 37 +++--- .../devtools/views/SuspenseTab/SuspenseTab.js | 125 ++++++++++++++++-- .../views/SuspenseTab/SuspenseTimeline.js | 72 +--------- 3 files changed, 134 insertions(+), 100 deletions(-) diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css index 60a7328589274..3e042402a4c3a 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css @@ -49,10 +49,6 @@ cursor: ew-resize; } -.TreeView footer { - display: none; -} - @container devtools (width < 600px) { .SuspenseTab { flex-direction: column; @@ -76,13 +72,13 @@ cursor: ns-resize; } - .TreeView footer { - display: flex; - justify-content: end; - border-top: 1px solid var(--color-border); + .ToggleInspectedElement[data-orientation="horizontal"] { + display: none; } +} - .ToggleInspectedElement[data-orientation="horizontal"] { +@container devtools (width >= 600px) { + .ToggleInspectedElement[data-orientation="vertical"] { display: none; } } @@ -103,22 +99,18 @@ } .Rects { - border-top: 1px solid var(--color-border); padding: 0.25rem; flex-grow: 1; overflow: auto; } .SuspenseTreeViewHeader { - padding: 0.25rem; + flex: 0 0 42px; + padding: 0.5rem; display: grid; - grid-template-columns: auto 1fr auto; + grid-template-columns: auto 1fr auto auto auto; align-items: flex-start; -} - -.SuspenseTreeViewHeaderMain { - display: grid; - grid-template-rows: auto auto; + border-bottom: 1px solid var(--color-border); } .SuspenseBreadcrumbs { @@ -128,3 +120,14 @@ */ overflow-x: auto; } + +.SuspenseTreeViewFooter { + flex: 0 0 42px; + display: flex; + justify-content: end; + border-top: 1px solid var(--color-border); + padding: 0.5rem; + display: grid; + grid-template-columns: 1fr auto; + align-items: flex-start; +} diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js index 8c93cf968a2ac..c7b53605b70a1 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js @@ -8,7 +8,13 @@ */ import * as React from 'react'; -import {useEffect, useLayoutEffect, useReducer, useRef} from 'react'; +import { + useContext, + useEffect, + useLayoutEffect, + useReducer, + useRef, +} from 'react'; import { localStorageGetItem, @@ -23,8 +29,18 @@ import SuspenseBreadcrumbs from './SuspenseBreadcrumbs'; import SuspenseRects from './SuspenseRects'; import SuspenseTimeline from './SuspenseTimeline'; import SuspenseTreeList from './SuspenseTreeList'; +import { + SuspenseTreeDispatcherContext, + SuspenseTreeStateContext, +} from './SuspenseTreeContext'; +import {StoreContext} from '../context'; +import {TreeDispatcherContext} from '../Components/TreeContext'; import Button from '../Button'; -import typeof {SyntheticPointerEvent} from 'react-dom-bindings/src/events/SyntheticEvent'; +import Tooltip from '../Components/reach-ui/tooltip'; +import typeof { + SyntheticEvent, + SyntheticPointerEvent, +} from 'react-dom-bindings/src/events/SyntheticEvent'; type Orientation = 'horizontal' | 'vertical'; @@ -48,6 +64,91 @@ type LayoutState = { }; type LayoutDispatch = (action: LayoutAction) => void; +function ToggleUniqueSuspenders() { + const store = useContext(StoreContext); + const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); + + const {selectedRootID: rootID, uniqueSuspendersOnly} = useContext( + SuspenseTreeStateContext, + ); + + function handleToggleUniqueSuspenders(event: SyntheticEvent) { + const nextUniqueSuspendersOnly = (event.currentTarget as HTMLInputElement) + .checked; + const nextTimeline = + rootID === null + ? [] + : // TODO: Handle different timeline modes (e.g. random order) + store.getSuspendableDocumentOrderSuspense( + rootID, + nextUniqueSuspendersOnly, + ); + suspenseTreeDispatch({ + type: 'SET_SUSPENSE_TIMELINE', + payload: [nextTimeline, null, nextUniqueSuspendersOnly], + }); + } + + return ( + + + + ); +} + +function SelectRoot() { + const store = useContext(StoreContext); + const {roots, selectedRootID, uniqueSuspendersOnly} = useContext( + SuspenseTreeStateContext, + ); + const treeDispatch = useContext(TreeDispatcherContext); + const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); + + function handleChange(event: SyntheticEvent) { + const newRootID = +event.currentTarget.value; + // TODO: scrollIntoView both suspense rects and host instance. + const nextTimeline = store.getSuspendableDocumentOrderSuspense( + newRootID, + uniqueSuspendersOnly, + ); + suspenseTreeDispatch({ + type: 'SET_SUSPENSE_TIMELINE', + payload: [nextTimeline, newRootID, uniqueSuspendersOnly], + }); + if (nextTimeline.length > 0) { + const milestone = nextTimeline[nextTimeline.length - 1]; + treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone}); + } + } + return ( + roots.length > 0 && ( + + ) + ); +} + function ToggleTreeList({ dispatch, state, @@ -314,30 +415,30 @@ function SuspenseTab(_: {}) { )}
-
+
{treeListDisabled ? (
) : ( )} -
-
- -
-
- -
+
+
+ + -
+
-
+
+
+ +
(null); const inputBBox = useRef(null); useLayoutEffect(() => { @@ -190,66 +171,15 @@ function SuspenseTimelineInput() { ref={inputRef} />
- - - ); } export default function SuspenseTimeline(): React$Node { - const store = useContext(StoreContext); - const {roots, selectedRootID, uniqueSuspendersOnly} = useContext( - SuspenseTreeStateContext, - ); - const treeDispatch = useContext(TreeDispatcherContext); - const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); - - function handleChange(event: SyntheticEvent) { - const newRootID = +event.currentTarget.value; - // TODO: scrollIntoView both suspense rects and host instance. - const nextTimeline = store.getSuspendableDocumentOrderSuspense( - newRootID, - uniqueSuspendersOnly, - ); - suspenseTreeDispatch({ - type: 'SET_SUSPENSE_TIMELINE', - payload: [nextTimeline, newRootID, uniqueSuspendersOnly], - }); - if (nextTimeline.length > 0) { - const milestone = nextTimeline[nextTimeline.length - 1]; - treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone}); - } - } - + const {selectedRootID} = useContext(SuspenseTreeStateContext); return (
- {roots.length > 0 && ( - - )}
); }