diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.css new file mode 100644 index 0000000000000..324a95c5a5837 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.css @@ -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); +} diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js new file mode 100644 index 0000000000000..92a8d2d3e694b --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js @@ -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 ( +