mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[DevTools] Add breadcrumbs to Suspense tab (#34312)
This commit is contained in:
parent
8d7b5e4903
commit
89a803fcec
|
|
@ -0,0 +1,33 @@
|
||||||
|
.SuspenseBreadcrumbsList {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SuspenseBreadcrumbsListItem {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SuspenseBreadcrumbsListItem[aria-current="true"] .SuspenseBreadcrumbsButton {
|
||||||
|
color: var(--color-button-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SuspenseBreadcrumbsButton {
|
||||||
|
background: var(--color-button-background);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0.25rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SuspenseBreadcrumbsButton:hover {
|
||||||
|
background-color: var(--color-button-background-hover);
|
||||||
|
color: var(--color-button-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SuspenseBreadcrumbsButton:focus-visible {
|
||||||
|
background: var(--color-button-background-focus);
|
||||||
|
}
|
||||||
79
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
vendored
Normal file
79
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {SuspenseNode} from 'react-devtools-shared/src/frontend/types';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
import {
|
||||||
|
TreeDispatcherContext,
|
||||||
|
TreeStateContext,
|
||||||
|
} from '../Components/TreeContext';
|
||||||
|
import {StoreContext} from '../context';
|
||||||
|
import {useHighlightHostInstance} from '../hooks';
|
||||||
|
import styles from './SuspenseBreadcrumbs.css';
|
||||||
|
import typeof {SyntheticMouseEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
|
||||||
|
|
||||||
|
export default function SuspenseBreadcrumbs(): React$Node {
|
||||||
|
const store = useContext(StoreContext);
|
||||||
|
const dispatch = useContext(TreeDispatcherContext);
|
||||||
|
const {inspectedElementID} = useContext(TreeStateContext);
|
||||||
|
|
||||||
|
const {highlightHostInstance, clearHighlightHostInstance} =
|
||||||
|
useHighlightHostInstance();
|
||||||
|
|
||||||
|
// TODO: Use the nearest Suspense boundary
|
||||||
|
const inspectedSuspenseID = inspectedElementID;
|
||||||
|
if (inspectedSuspenseID === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suspense = store.getSuspenseByID(inspectedSuspenseID);
|
||||||
|
if (suspense === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineage: SuspenseNode[] = [];
|
||||||
|
let next: null | SuspenseNode = suspense;
|
||||||
|
while (next !== null) {
|
||||||
|
if (next.parentID === 0) {
|
||||||
|
next = null;
|
||||||
|
} else {
|
||||||
|
lineage.unshift(next);
|
||||||
|
next = store.getSuspenseByID(next.parentID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(node: SuspenseNode, event: SyntheticMouseEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: node.id});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ol className={styles.SuspenseBreadcrumbsList}>
|
||||||
|
{lineage.map((node, index) => {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={node.id}
|
||||||
|
className={styles.SuspenseBreadcrumbsListItem}
|
||||||
|
aria-current={index === lineage.length - 1}
|
||||||
|
onPointerEnter={highlightHostInstance.bind(null, node.id)}
|
||||||
|
onPointerLeave={clearHighlightHostInstance}>
|
||||||
|
<button
|
||||||
|
className={styles.SuspenseBreadcrumbsButton}
|
||||||
|
onClick={handleClick.bind(null, node)}
|
||||||
|
type="button">
|
||||||
|
{node.name}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -108,14 +108,22 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TimelineWrapper {
|
.SuspenseTreeViewHeader {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-template-columns: auto 1fr auto;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Timeline {
|
.SuspenseTreeViewHeaderMain {
|
||||||
flex-grow: 1;
|
display: grid;
|
||||||
align-self: anchor-center;
|
grid-template-rows: auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SuspenseBreadcrumbs {
|
||||||
|
/**
|
||||||
|
* TODO: Switch to single item view on overflow like OwnerStack does.
|
||||||
|
* OwnerStack has more constraints that make it easier so it won't be a 1:1 port.
|
||||||
|
*/
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import InspectedElementErrorBoundary from '../Components/InspectedElementErrorBo
|
||||||
import InspectedElement from '../Components/InspectedElement';
|
import InspectedElement from '../Components/InspectedElement';
|
||||||
import portaledContent from '../portaledContent';
|
import portaledContent from '../portaledContent';
|
||||||
import styles from './SuspenseTab.css';
|
import styles from './SuspenseTab.css';
|
||||||
|
import SuspenseBreadcrumbs from './SuspenseBreadcrumbs';
|
||||||
import SuspenseRects from './SuspenseRects';
|
import SuspenseRects from './SuspenseRects';
|
||||||
import SuspenseTimeline from './SuspenseTimeline';
|
import SuspenseTimeline from './SuspenseTimeline';
|
||||||
import SuspenseTreeList from './SuspenseTreeList';
|
import SuspenseTreeList from './SuspenseTreeList';
|
||||||
|
|
@ -304,11 +305,16 @@ function SuspenseTab(_: {}) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.TreeView}>
|
<div className={styles.TreeView}>
|
||||||
<div className={styles.TimelineWrapper}>
|
<div className={styles.SuspenseTreeViewHeader}>
|
||||||
<ToggleTreeList dispatch={dispatch} state={state} />
|
<ToggleTreeList dispatch={dispatch} state={state} />
|
||||||
<div className={styles.Timeline}>
|
<div className={styles.SuspenseTreeViewHeaderMain}>
|
||||||
|
<div className={styles.SuspenseTimeline}>
|
||||||
<SuspenseTimeline />
|
<SuspenseTimeline />
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.SuspenseBreadcrumbs}>
|
||||||
|
<SuspenseBreadcrumbs />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ToggleInspectedElement
|
<ToggleInspectedElement
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
state={state}
|
state={state}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
padding: 0 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SuspenseTimelineInput {
|
.SuspenseTimelineInput {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user