Skip to content

Commit 14c50e3

Browse files
authored
[DevTools] Use Visually Lighter Skeletons (#34185)
The skeletons right now are too jarring because they're visually heavier than the content that comes in later. This makes them draw attention to themselves as flashing things. A good skeleton and loading indicator should ideally start as invisible as possible and then gradually become more visible the longer time passes so that if it loads quickly then it was never much visible at all. Even at its max it should never be heavier weight than the final content so that it visually reverts into lesser. Another rule of thumb is that it should be as close as possible to the final content in size but if it's unknown it should always be smaller than the final content so that the content grows into its slot rather than the slot contracting. This makes the skeleton fade from invisible into the dimmest color just as a subtle hint that something is still loading. I also added a missing skeleton since the stack traces in rendered by can now suspend while source mapping. The other tweak I did is use disabled buttons in all the cases where we load the ability to enable a button. This is more subtle and if you hover over you can see why it's still disabled. Rather than flashing the button each time you change element.
1 parent f1222f7 commit 14c50e3

File tree

7 files changed

+84
-46
lines changed

7 files changed

+84
-46
lines changed

packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import FetchFileWithCachingContext from './FetchFileWithCachingContext';
2424
import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
2525
import OpenInEditorButton from './OpenInEditorButton';
2626
import InspectedElementViewSourceButton from './InspectedElementViewSourceButton';
27-
import Skeleton from './Skeleton';
2827
import useEditorURL from '../useEditorURL';
2928

3029
import styles from './InspectedElement.css';
@@ -203,7 +202,9 @@ export default function InspectedElementWrapper(_: Props): React.Node {
203202
}
204203

205204
return (
206-
<div className={styles.InspectedElement}>
205+
<div
206+
className={styles.InspectedElement}
207+
key={inspectedElementID /* Force reset when selected Element changes */}>
207208
<div className={styles.TitleRow} data-testname="InspectedElement-Title">
208209
{strictModeBadge}
209210

@@ -232,13 +233,11 @@ export default function InspectedElementWrapper(_: Props): React.Node {
232233
!!editorURL &&
233234
source != null &&
234235
symbolicatedSourcePromise != null && (
235-
<React.Suspense fallback={<Skeleton height={16} width={24} />}>
236-
<OpenInEditorButton
237-
editorURL={editorURL}
238-
source={source}
239-
symbolicatedSourcePromise={symbolicatedSourcePromise}
240-
/>
241-
</React.Suspense>
236+
<OpenInEditorButton
237+
editorURL={editorURL}
238+
source={source}
239+
symbolicatedSourcePromise={symbolicatedSourcePromise}
240+
/>
242241
)}
243242

244243
{canToggleError && (
@@ -294,9 +293,6 @@ export default function InspectedElementWrapper(_: Props): React.Node {
294293

295294
{inspectedElement !== null && symbolicatedSourcePromise != null && (
296295
<InspectedElementView
297-
key={
298-
inspectedElementID /* Force reset when selected Element changes */
299-
}
300296
element={element}
301297
hookNames={hookNames}
302298
inspectedElement={inspectedElement}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ function InspectedElementSourcePanel({
3636
<div className={styles.SourceHeaderRow}>
3737
<div className={styles.SourceHeader}>source</div>
3838

39-
<React.Suspense fallback={<Skeleton height={16} width={16} />}>
39+
<React.Suspense
40+
fallback={
41+
<Button disabled={true} title="Loading source maps...">
42+
<ButtonIcon type="copy" />
43+
</Button>
44+
}>
4045
<CopySourceButton
4146
source={source}
4247
symbolicatedSourcePromise={symbolicatedSourcePromise}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@
2424
overflow: hidden;
2525
text-overflow: ellipsis;
2626
}
27+
28+
.RenderedBySkeleton {
29+
padding-left: 1.25rem;
30+
}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {enableStyleXFeatures} from 'react-devtools-feature-flags';
2424
import InspectedElementSourcePanel from './InspectedElementSourcePanel';
2525
import StackTraceView from './StackTraceView';
2626
import OwnerView from './OwnerView';
27+
import Skeleton from './Skeleton';
2728

2829
import styles from './InspectedElementView.css';
2930

@@ -170,34 +171,40 @@ export default function InspectedElementView({
170171
className={styles.InspectedElementSection}
171172
data-testname="InspectedElementView-Owners">
172173
<div className={styles.OwnersHeader}>rendered by</div>
173-
174-
{showStack ? <StackTraceView stack={stack} /> : null}
175-
{showOwnersList &&
176-
owners?.map(owner => (
177-
<Fragment key={owner.id}>
178-
<OwnerView
179-
displayName={owner.displayName || 'Anonymous'}
180-
hocDisplayNames={owner.hocDisplayNames}
181-
environmentName={
182-
inspectedElement.env === owner.env ? null : owner.env
183-
}
184-
compiledWithForget={owner.compiledWithForget}
185-
id={owner.id}
186-
isInStore={store.containsElement(owner.id)}
187-
type={owner.type}
188-
/>
189-
{owner.stack != null && owner.stack.length > 0 ? (
190-
<StackTraceView stack={owner.stack} />
191-
) : null}
192-
</Fragment>
193-
))}
194-
195-
{rootType !== null && (
196-
<div className={styles.OwnersMetaField}>{rootType}</div>
197-
)}
198-
{rendererLabel !== null && (
199-
<div className={styles.OwnersMetaField}>{rendererLabel}</div>
200-
)}
174+
<React.Suspense
175+
fallback={
176+
<div className={styles.RenderedBySkeleton}>
177+
<Skeleton height={16} width="40%" />
178+
</div>
179+
}>
180+
{showStack ? <StackTraceView stack={stack} /> : null}
181+
{showOwnersList &&
182+
owners?.map(owner => (
183+
<Fragment key={owner.id}>
184+
<OwnerView
185+
displayName={owner.displayName || 'Anonymous'}
186+
hocDisplayNames={owner.hocDisplayNames}
187+
environmentName={
188+
inspectedElement.env === owner.env ? null : owner.env
189+
}
190+
compiledWithForget={owner.compiledWithForget}
191+
id={owner.id}
192+
isInStore={store.containsElement(owner.id)}
193+
type={owner.type}
194+
/>
195+
{owner.stack != null && owner.stack.length > 0 ? (
196+
<StackTraceView stack={owner.stack} />
197+
) : null}
198+
</Fragment>
199+
))}
200+
201+
{rootType !== null && (
202+
<div className={styles.OwnersMetaField}>{rootType}</div>
203+
)}
204+
{rendererLabel !== null && (
205+
<div className={styles.OwnersMetaField}>{rendererLabel}</div>
206+
)}
207+
</React.Suspense>
201208
</div>
202209
)}
203210

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import * as React from 'react';
1111

1212
import ButtonIcon from '../ButtonIcon';
1313
import Button from '../Button';
14-
import Skeleton from './Skeleton';
1514

1615
import type {ReactFunctionLocation} from 'shared/ReactTypes';
1716

@@ -27,7 +26,12 @@ function InspectedElementViewSourceButton({
2726
symbolicatedSourcePromise,
2827
}: Props): React.Node {
2928
return (
30-
<React.Suspense fallback={<Skeleton height={16} width={24} />}>
29+
<React.Suspense
30+
fallback={
31+
<Button disabled={true} title="Loading source maps...">
32+
<ButtonIcon type="view-source" />
33+
</Button>
34+
}>
3135
<ActualSourceButton
3236
source={source}
3337
symbolicatedSourcePromise={symbolicatedSourcePromise}

packages/react-devtools-shared/src/devtools/views/Components/Skeleton.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
@keyframes pulse {
77
0%, 100% {
8-
background-color: var(--color-dim);
8+
background-color: none;
99
}
1010
50% {
11-
background-color: var(--color-dimmest)
11+
background-color: var(--color-dimmest);
1212
}
1313
}

packages/react-devtools-shared/src/devtools/views/Editor/OpenInEditorButton.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ type Props = {
2424
className?: string,
2525
};
2626

27-
function OpenInEditorButton({editorURL, source, className}: Props): React.Node {
27+
function ActualOpenInEditorButton({
28+
editorURL,
29+
source,
30+
className,
31+
}: Props): React.Node {
2832
let disable;
2933
if (source == null) {
3034
disable = true;
@@ -68,4 +72,22 @@ function OpenInEditorButton({editorURL, source, className}: Props): React.Node {
6872
);
6973
}
7074

75+
function OpenInEditorButton({editorURL, source, className}: Props): React.Node {
76+
return (
77+
<React.Suspense
78+
fallback={
79+
<Button disabled={true} className={className}>
80+
<ButtonIcon type="editor" />
81+
<ButtonLabel>Loading source maps...</ButtonLabel>
82+
</Button>
83+
}>
84+
<ActualOpenInEditorButton
85+
editorURL={editorURL}
86+
source={source}
87+
className={className}
88+
/>
89+
</React.Suspense>
90+
);
91+
}
92+
7193
export default OpenInEditorButton;

0 commit comments

Comments
 (0)