[DevTools] Give a distinct color to the root (#34690)

Stacked on #34654.

The root is special since it represents "Initial Paint" (or a
"Transition" when an Activity is selected). This gives it a different
color in the timeline as well as gives it an outline that's clickable.
Hovering the timeline now shows "Initial Paint" or "Suspense".

Also made the cursor a pointer to invite you to try to click things and
some rounded corners.

<img width="1219" height="420" alt="Screenshot 2025-10-02 at 1 26 38 PM"
src="https://github.com/user-attachments/assets/12451f93-8917-4f3b-8f01-930129e5fc13"
/>

<img width="1217" height="419" alt="Screenshot 2025-10-02 at 1 26 54 PM"
src="https://github.com/user-attachments/assets/02b5e94c-3fbe-488d-b0f2-225b73578608"
/>

<img width="1215" height="419" alt="Screenshot 2025-10-02 at 1 27 24 PM"
src="https://github.com/user-attachments/assets/c24e8861-e74a-4ccc-8643-ee9d04bef43c"
/>

<img width="1216" height="419" alt="Screenshot 2025-10-02 at 1 27 10 PM"
src="https://github.com/user-attachments/assets/d5cc2b62-fa64-41bf-b485-116b1cd67467"
/>
This commit is contained in:
Sebastian Markbåge 2025-10-02 14:37:03 -04:00 committed by GitHub
parent ced705d756
commit 2e68dc76a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 20 deletions

View File

@ -1,5 +1,12 @@
.SuspenseRectsContainer { .SuspenseRectsContainer {
padding: .25rem; padding: .25rem;
cursor: pointer;
outline: 1px solid var(--color-component-name);
border-radius: 0.25rem;
}
.SuspenseRectsContainer[data-highlighted='true'] {
background: var(--color-dimmest);
} }
.SuspenseRectsViewBox { .SuspenseRectsViewBox {
@ -28,6 +35,8 @@
pointer-events: all; pointer-events: all;
outline-style: solid; outline-style: solid;
outline-width: 1px; outline-width: 1px;
border-radius: 0.125rem;
cursor: pointer;
} }
.SuspenseRectsScaledRect { .SuspenseRectsScaledRect {

View File

@ -295,6 +295,7 @@ const ViewBox = createContext<Rect>((null: any));
function SuspenseRectsContainer(): React$Node { function SuspenseRectsContainer(): React$Node {
const store = useContext(StoreContext); const store = useContext(StoreContext);
const {inspectedElementID} = useContext(TreeStateContext);
const treeDispatch = useContext(TreeDispatcherContext); const treeDispatch = useContext(TreeDispatcherContext);
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
// TODO: This relies on a full re-render of all children when the Suspense tree changes. // TODO: This relies on a full re-render of all children when the Suspense tree changes.
@ -329,8 +330,13 @@ function SuspenseRectsContainer(): React$Node {
}); });
} }
const isRootSelected = roots.includes(inspectedElementID);
return ( return (
<div className={styles.SuspenseRectsContainer} onClick={handleClick}> <div
className={styles.SuspenseRectsContainer}
onClick={handleClick}
data-highlighted={isRootSelected}>
<ViewBox.Provider value={boundingBox}> <ViewBox.Provider value={boundingBox}>
<div <div
className={styles.SuspenseRectsViewBox} className={styles.SuspenseRectsViewBox}

View File

@ -37,7 +37,7 @@
padding-right: 0; padding-right: 0;
} }
.SuspenseScrubberBead, .SuspenseScrubberBeadSelected { .SuspenseScrubberBead {
flex: 1; flex: 1;
height: 0.5rem; height: 0.5rem;
background: var(--color-background-selected); background: var(--color-background-selected);
@ -51,9 +51,11 @@
background: var(--color-background-selected); background: var(--color-background-selected);
} }
.SuspenseScrubberBeadTransition {
background: var(--color-component-name);
}
.SuspenseScrubberStepHighlight > .SuspenseScrubberBead, .SuspenseScrubberStepHighlight > .SuspenseScrubberBead,
.SuspenseScrubberStepHighlight > .SuspenseScrubberBeadSelected, .SuspenseScrubberStep:hover > .SuspenseScrubberBead {
.SuspenseScrubberStep:hover > .SuspenseScrubberBead,
.SuspenseScrubberStep:hover > .SuspenseScrubberBeadSelected {
height: 0.75rem; height: 0.75rem;
} }

View File

@ -14,6 +14,8 @@ import {useRef} from 'react';
import styles from './SuspenseScrubber.css'; import styles from './SuspenseScrubber.css';
import Tooltip from '../Components/reach-ui/tooltip';
export default function SuspenseScrubber({ export default function SuspenseScrubber({
min, min,
max, max,
@ -53,8 +55,17 @@ export default function SuspenseScrubber({
const steps = []; const steps = [];
for (let index = min; index <= max; index++) { for (let index = min; index <= max; index++) {
steps.push( steps.push(
<div <Tooltip
key={index} key={index}
label={
index === min
? // The first step in the timeline is always a Transition (Initial Paint).
// TODO: Support multiple environments.
'Initial Paint'
: // TODO: Consider adding the name of this specific boundary if this step has only one.
'Suspense'
}>
<div
className={ className={
styles.SuspenseScrubberStep + styles.SuspenseScrubberStep +
(highlight === index (highlight === index
@ -65,12 +76,17 @@ export default function SuspenseScrubber({
onMouseEnter={onHoverSegment.bind(null, index)}> onMouseEnter={onHoverSegment.bind(null, index)}>
<div <div
className={ className={
index <= value styles.SuspenseScrubberBead +
? styles.SuspenseScrubberBeadSelected (index === min
: styles.SuspenseScrubberBead ? // The first step in the timeline is always a Transition (Initial Paint).
// TODO: Support multiple environments.
' ' + styles.SuspenseScrubberBeadTransition
: '') +
(index <= value ? ' ' + styles.SuspenseScrubberBeadSelected : '')
} }
/> />
</div>, </div>
</Tooltip>,
); );
} }