[DevTools] Add Settings button on Suspense Tab (#34624)

The settings dialog appears on all tabs and should be reachable from
Suspense tab too. It's a bit weird because it's not contextual to the
tab and it shows you whatever your last settings tab was opened. Maybe
it should default to opening to the current tab's settings?

There aren't any Suspense specific settings yet but there definitely
will be. We could move the "Show all" into settings but it might be
frequently that you want to check why something isn't suspending a
Suspense boundary or test SSR streaming.

However, the general settings still apply to the Suspense tab. E.g.
switching dark/light mode.

<img width="857" height="233" alt="Screenshot 2025-09-27 at 12 35 05 PM"
src="https://github.com/user-attachments/assets/4a38e94f-2074-4dce-906b-9a1c40bccb9b"
/>
This commit is contained in:
Sebastian Markbåge
2025-09-28 19:09:52 -04:00
committed by GitHub
parent 4616fbe553
commit f7873e2e5c
2 changed files with 85 additions and 66 deletions
@@ -14,6 +14,14 @@
-webkit-font-smoothing: var(--font-smoothing);
}
.VRule {
height: 20px;
width: 1px;
flex: 0 0 1px;
margin: 0 0.5rem;
background-color: var(--color-border);
}
.TreeWrapper {
border-top: 1px solid var(--color-border);
flex: 1 1 65%;
@@ -107,13 +115,13 @@
.SuspenseTreeViewHeader {
flex: 0 0 42px;
padding: 0.5rem;
display: grid;
grid-template-columns: auto 1fr auto auto auto;
display: flex;
align-items: flex-start;
border-bottom: 1px solid var(--color-border);
}
.SuspenseBreadcrumbs {
flex: 1;
/**
* 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.
@@ -33,7 +33,7 @@ import {
SuspenseTreeDispatcherContext,
SuspenseTreeStateContext,
} from './SuspenseTreeContext';
import {StoreContext} from '../context';
import {StoreContext, OptionsContext} from '../context';
import {TreeDispatcherContext} from '../Components/TreeContext';
import Button from '../Button';
import Tooltip from '../Components/reach-ui/tooltip';
@@ -41,6 +41,9 @@ import typeof {
SyntheticEvent,
SyntheticPointerEvent,
} from 'react-dom-bindings/src/events/SyntheticEvent';
import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';
type Orientation = 'horizontal' | 'vertical';
@@ -212,6 +215,7 @@ function ToggleInspectedElement({
}
function SuspenseTab(_: {}) {
const {hideSettings} = useContext(OptionsContext);
const [state, dispatch] = useReducer<LayoutState, null, LayoutAction>(
layoutReducer,
null,
@@ -394,75 +398,82 @@ function SuspenseTab(_: {}) {
};
return (
<div className={styles.SuspenseTab} ref={wrapperTreeRef}>
<div className={styles.TreeWrapper} ref={resizeTreeRef}>
{treeListDisabled ? null : (
<div
className={styles.TreeList}
hidden={treeListHidden}
ref={resizeTreeListRef}>
<SuspenseTreeList />
</div>
)}
{treeListDisabled ? null : (
<div className={styles.ResizeBarWrapper} hidden={treeListHidden}>
<SettingsModalContextController>
<div className={styles.SuspenseTab} ref={wrapperTreeRef}>
<div className={styles.TreeWrapper} ref={resizeTreeRef}>
{treeListDisabled ? null : (
<div
onPointerDown={onResizeStart}
onPointerMove={onResizeTreeList}
onPointerUp={onResizeEnd}
className={styles.ResizeBar}
/>
</div>
)}
<div className={styles.TreeView}>
<header className={styles.SuspenseTreeViewHeader}>
{treeListDisabled ? (
<div />
) : (
<ToggleTreeList dispatch={dispatch} state={state} />
)}
<div className={styles.SuspenseBreadcrumbs}>
<SuspenseBreadcrumbs />
className={styles.TreeList}
hidden={treeListHidden}
ref={resizeTreeListRef}>
<SuspenseTreeList />
</div>
<ToggleUniqueSuspenders />
<SelectRoot />
<ToggleInspectedElement
dispatch={dispatch}
state={state}
orientation="horizontal"
/>
</header>
<div className={styles.Rects}>
<SuspenseRects />
</div>
<footer className={styles.SuspenseTreeViewFooter}>
<div className={styles.SuspenseTimeline}>
<SuspenseTimeline />
)}
{treeListDisabled ? null : (
<div className={styles.ResizeBarWrapper} hidden={treeListHidden}>
<div
onPointerDown={onResizeStart}
onPointerMove={onResizeTreeList}
onPointerUp={onResizeEnd}
className={styles.ResizeBar}
/>
</div>
<ToggleInspectedElement
dispatch={dispatch}
state={state}
orientation="vertical"
/>
</footer>
)}
<div className={styles.TreeView}>
<header className={styles.SuspenseTreeViewHeader}>
{treeListDisabled ? (
<div />
) : (
<ToggleTreeList dispatch={dispatch} state={state} />
)}
<div className={styles.SuspenseBreadcrumbs}>
<SuspenseBreadcrumbs />
</div>
<ToggleUniqueSuspenders />
<SelectRoot />
{!hideSettings && <div className={styles.VRule} />}
{!hideSettings && <SettingsModalContextToggle />}
<ToggleInspectedElement
dispatch={dispatch}
state={state}
orientation="horizontal"
/>
</header>
<div className={styles.Rects}>
<SuspenseRects />
</div>
<footer className={styles.SuspenseTreeViewFooter}>
<div className={styles.SuspenseTimeline}>
<SuspenseTimeline />
</div>
<ToggleInspectedElement
dispatch={dispatch}
state={state}
orientation="vertical"
/>
</footer>
</div>
</div>
</div>
<div className={styles.ResizeBarWrapper} hidden={inspectedElementHidden}>
<div
onPointerDown={onResizeStart}
onPointerMove={onResizeTree}
onPointerUp={onResizeEnd}
className={styles.ResizeBar}
/>
className={styles.ResizeBarWrapper}
hidden={inspectedElementHidden}>
<div
onPointerDown={onResizeStart}
onPointerMove={onResizeTree}
onPointerUp={onResizeEnd}
className={styles.ResizeBar}
/>
</div>
<div
className={styles.InspectedElementWrapper}
hidden={inspectedElementHidden}>
<InspectedElementErrorBoundary>
<InspectedElement />
</InspectedElementErrorBoundary>
</div>
<SettingsModal />
</div>
<div
className={styles.InspectedElementWrapper}
hidden={inspectedElementHidden}>
<InspectedElementErrorBoundary>
<InspectedElement />
</InspectedElementErrorBoundary>
</div>
</div>
</SettingsModalContextController>
);
}