mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[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.
This commit is contained in:
committed by
GitHub
parent
4c18aab076
commit
651df45dd9
+8
-12
@@ -24,7 +24,6 @@ import FetchFileWithCachingContext from './FetchFileWithCachingContext';
|
||||
import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
|
||||
import OpenInEditorButton from './OpenInEditorButton';
|
||||
import InspectedElementViewSourceButton from './InspectedElementViewSourceButton';
|
||||
import Skeleton from './Skeleton';
|
||||
import useEditorURL from '../useEditorURL';
|
||||
|
||||
import styles from './InspectedElement.css';
|
||||
@@ -203,7 +202,9 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.InspectedElement}>
|
||||
<div
|
||||
className={styles.InspectedElement}
|
||||
key={inspectedElementID /* Force reset when selected Element changes */}>
|
||||
<div className={styles.TitleRow} data-testname="InspectedElement-Title">
|
||||
{strictModeBadge}
|
||||
|
||||
@@ -232,13 +233,11 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
!!editorURL &&
|
||||
source != null &&
|
||||
symbolicatedSourcePromise != null && (
|
||||
<React.Suspense fallback={<Skeleton height={16} width={24} />}>
|
||||
<OpenInEditorButton
|
||||
editorURL={editorURL}
|
||||
source={source}
|
||||
symbolicatedSourcePromise={symbolicatedSourcePromise}
|
||||
/>
|
||||
</React.Suspense>
|
||||
<OpenInEditorButton
|
||||
editorURL={editorURL}
|
||||
source={source}
|
||||
symbolicatedSourcePromise={symbolicatedSourcePromise}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canToggleError && (
|
||||
@@ -294,9 +293,6 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
|
||||
{inspectedElement !== null && symbolicatedSourcePromise != null && (
|
||||
<InspectedElementView
|
||||
key={
|
||||
inspectedElementID /* Force reset when selected Element changes */
|
||||
}
|
||||
element={element}
|
||||
hookNames={hookNames}
|
||||
inspectedElement={inspectedElement}
|
||||
|
||||
Vendored
+6
-1
@@ -36,7 +36,12 @@ function InspectedElementSourcePanel({
|
||||
<div className={styles.SourceHeaderRow}>
|
||||
<div className={styles.SourceHeader}>source</div>
|
||||
|
||||
<React.Suspense fallback={<Skeleton height={16} width={16} />}>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<Button disabled={true} title="Loading source maps...">
|
||||
<ButtonIcon type="copy" />
|
||||
</Button>
|
||||
}>
|
||||
<CopySourceButton
|
||||
source={source}
|
||||
symbolicatedSourcePromise={symbolicatedSourcePromise}
|
||||
|
||||
@@ -24,3 +24,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.RenderedBySkeleton {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
+34
-27
@@ -24,6 +24,7 @@ import {enableStyleXFeatures} from 'react-devtools-feature-flags';
|
||||
import InspectedElementSourcePanel from './InspectedElementSourcePanel';
|
||||
import StackTraceView from './StackTraceView';
|
||||
import OwnerView from './OwnerView';
|
||||
import Skeleton from './Skeleton';
|
||||
|
||||
import styles from './InspectedElementView.css';
|
||||
|
||||
@@ -170,34 +171,40 @@ export default function InspectedElementView({
|
||||
className={styles.InspectedElementSection}
|
||||
data-testname="InspectedElementView-Owners">
|
||||
<div className={styles.OwnersHeader}>rendered by</div>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<div className={styles.RenderedBySkeleton}>
|
||||
<Skeleton height={16} width="40%" />
|
||||
</div>
|
||||
}>
|
||||
{showStack ? <StackTraceView stack={stack} /> : null}
|
||||
{showOwnersList &&
|
||||
owners?.map(owner => (
|
||||
<Fragment key={owner.id}>
|
||||
<OwnerView
|
||||
displayName={owner.displayName || 'Anonymous'}
|
||||
hocDisplayNames={owner.hocDisplayNames}
|
||||
environmentName={
|
||||
inspectedElement.env === owner.env ? null : owner.env
|
||||
}
|
||||
compiledWithForget={owner.compiledWithForget}
|
||||
id={owner.id}
|
||||
isInStore={store.containsElement(owner.id)}
|
||||
type={owner.type}
|
||||
/>
|
||||
{owner.stack != null && owner.stack.length > 0 ? (
|
||||
<StackTraceView stack={owner.stack} />
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{showStack ? <StackTraceView stack={stack} /> : null}
|
||||
{showOwnersList &&
|
||||
owners?.map(owner => (
|
||||
<Fragment key={owner.id}>
|
||||
<OwnerView
|
||||
displayName={owner.displayName || 'Anonymous'}
|
||||
hocDisplayNames={owner.hocDisplayNames}
|
||||
environmentName={
|
||||
inspectedElement.env === owner.env ? null : owner.env
|
||||
}
|
||||
compiledWithForget={owner.compiledWithForget}
|
||||
id={owner.id}
|
||||
isInStore={store.containsElement(owner.id)}
|
||||
type={owner.type}
|
||||
/>
|
||||
{owner.stack != null && owner.stack.length > 0 ? (
|
||||
<StackTraceView stack={owner.stack} />
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{rootType !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rootType}</div>
|
||||
)}
|
||||
{rendererLabel !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rendererLabel}</div>
|
||||
)}
|
||||
{rootType !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rootType}</div>
|
||||
)}
|
||||
{rendererLabel !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rendererLabel}</div>
|
||||
)}
|
||||
</React.Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Vendored
+6
-2
@@ -11,7 +11,6 @@ import * as React from 'react';
|
||||
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Button from '../Button';
|
||||
import Skeleton from './Skeleton';
|
||||
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
@@ -27,7 +26,12 @@ function InspectedElementViewSourceButton({
|
||||
symbolicatedSourcePromise,
|
||||
}: Props): React.Node {
|
||||
return (
|
||||
<React.Suspense fallback={<Skeleton height={16} width={24} />}>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<Button disabled={true} title="Loading source maps...">
|
||||
<ButtonIcon type="view-source" />
|
||||
</Button>
|
||||
}>
|
||||
<ActualSourceButton
|
||||
source={source}
|
||||
symbolicatedSourcePromise={symbolicatedSourcePromise}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
background-color: var(--color-dim);
|
||||
background-color: none;
|
||||
}
|
||||
50% {
|
||||
background-color: var(--color-dimmest)
|
||||
background-color: var(--color-dimmest);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -24,7 +24,11 @@ type Props = {
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function OpenInEditorButton({editorURL, source, className}: Props): React.Node {
|
||||
function ActualOpenInEditorButton({
|
||||
editorURL,
|
||||
source,
|
||||
className,
|
||||
}: Props): React.Node {
|
||||
let disable;
|
||||
if (source == null) {
|
||||
disable = true;
|
||||
@@ -68,4 +72,22 @@ function OpenInEditorButton({editorURL, source, className}: Props): React.Node {
|
||||
);
|
||||
}
|
||||
|
||||
function OpenInEditorButton({editorURL, source, className}: Props): React.Node {
|
||||
return (
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<Button disabled={true} className={className}>
|
||||
<ButtonIcon type="editor" />
|
||||
<ButtonLabel>Loading source maps...</ButtonLabel>
|
||||
</Button>
|
||||
}>
|
||||
<ActualOpenInEditorButton
|
||||
editorURL={editorURL}
|
||||
source={source}
|
||||
className={className}
|
||||
/>
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default OpenInEditorButton;
|
||||
|
||||
Reference in New Issue
Block a user