Skip to content

Commit d70cac3

Browse files
raunofreibergdevjiwonchoiztanner
authored
[Error Overlay] Comprehensive refinement (#75471)
This PR adds a lot of polish to the experimental Error Overlay: https://github.com/user-attachments/assets/2540b966-c61d-43eb-b816-5ef25f3ecb11 ### Motion - Stacked layers transition smoothly when switching between errors - Height transition for the dialog panel to complement the stacked layers transition - Dialog contents and the backdrop fade in as per Geist Motion Guidelines (`scale`, `opacity`) ### Design - New language icon based on file extension à la (`app.tsx`) - Shadows are clipped with translucent borders to visually be more crisp - Use `backdrop-filter: blur()` for backdrop to blend with application content - Use `font-variant-numeric: tabular-nums` for ←→ switcher to avoid layout shift - Misc alignment improvements ### Interactions - Focus is now trapped in the dialog, <kbd>Escape</kbd> will un-trap and re-focus the last `root.activeElement` - Focus states for all interactive elements - Fluid reveal & collapse of ignored Call Stack items ↓ https://github.com/user-attachments/assets/59a81cea-24ec-4fe4-be00-b911ab484757 Closes NDX-711 Closes NDX-744 --------- Co-authored-by: devjiwonchoi <[email protected]> Co-authored-by: Zack Tanner <[email protected]>
1 parent 0783eb8 commit d70cac3

File tree

47 files changed

+1107
-696
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1107
-696
lines changed

packages/next/.storybook/preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const preview: Preview = {
1313
},
1414
backgrounds: {
1515
values: [
16-
{ name: 'backdrop', value: 'rgb(44, 44, 46);' },
16+
{ name: 'backdrop', value: 'rgba(250, 250, 250, 0.80)' },
1717
{ name: 'background-100-light', value: '#ffffff' },
1818
{ name: 'background-200-light', value: '#fafafa' },
1919
{ name: 'background-100-dark', value: '#0a0a0a' },
Loading

packages/next/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.stories.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type { OverlayState } from '../../shared'
44
import ReactDevOverlay from './react-dev-overlay'
55
import { ACTION_UNHANDLED_ERROR } from '../../shared'
66

7+
// @ts-expect-error
8+
import imgApp from './app.png'
9+
710
const meta: Meta<typeof ReactDevOverlay> = {
811
component: ReactDevOverlay,
912
parameters: {
@@ -80,6 +83,17 @@ const state: OverlayState = {
8083
export const Default: Story = {
8184
args: {
8285
state,
83-
children: <div>Application Content</div>,
86+
children: (
87+
<div>
88+
<img
89+
src={imgApp}
90+
style={{
91+
width: '100%',
92+
height: '100%',
93+
objectFit: 'contain',
94+
}}
95+
/>
96+
</div>
97+
),
8498
},
8599
}

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/call-stack-frame/call-stack-frame.tsx

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { noop as css } from '../../helpers/noop-template'
99

1010
export const CallStackFrame: React.FC<{
1111
frame: OriginalStackFrame
12-
}> = function CallStackFrame({ frame }) {
12+
index: number
13+
}> = function CallStackFrame({ frame, index }) {
1314
// TODO: ability to expand resolved frames
1415
// TODO: render error or external indicator
1516

@@ -32,19 +33,32 @@ export const CallStackFrame: React.FC<{
3233
// Formatted file source could be empty. e.g. <anonymous> will be formatted to empty string,
3334
// we'll skip rendering the frame in this case.
3435
const fileSource = getFrameSource(f)
36+
3537
if (!fileSource) {
3638
return null
3739
}
3840

41+
const props = {
42+
...(hasSource && {
43+
role: 'button',
44+
tabIndex: 0,
45+
'aria-label': 'Click to open in your editor',
46+
title: 'Click to open in your editor',
47+
onClick: open,
48+
}),
49+
}
50+
3951
return (
4052
<div
4153
data-nextjs-call-stack-frame
4254
data-nextjs-call-stack-frame-ignored={!hasSource}
43-
onClick={hasSource ? open : undefined}
44-
role="button"
45-
tabIndex={0}
46-
aria-label={hasSource ? 'Click to open in your editor' : undefined}
47-
title={hasSource ? 'Click to open in your editor' : undefined}
55+
data-animate={frame.ignored}
56+
{...props}
57+
style={
58+
{
59+
'--index': index,
60+
} as React.CSSProperties
61+
}
4862
>
4963
<span
5064
data-nextjs-frame-expanded={!frame.ignored}
@@ -86,18 +100,25 @@ export const CALL_STACK_FRAME_STYLES = css`
86100
margin-bottom: var(--size-1);
87101
88102
border-radius: var(--rounded-lg);
103+
transition: background 100ms ease-out;
104+
105+
&[data-animate='true'] {
106+
filter: blur(4px);
107+
animation: fadeIn 250ms var(--timing-swift) forwards
108+
calc(var(--index) * 25ms);
109+
}
89110
90-
&:not(:disabled):hover {
111+
&:not(:disabled)[role='button']:hover {
91112
background: var(--color-gray-alpha-100);
92113
cursor: pointer;
93114
}
94115
95-
&:not(:disabled):active {
116+
&:not(:disabled)[role='button']:active {
96117
background: var(--color-gray-alpha-200);
97118
}
98119
99-
&:focus {
100-
outline: none;
120+
&:focus-visible {
121+
outline: var(--focus-ring);
101122
}
102123
}
103124
@@ -107,6 +128,7 @@ export const CALL_STACK_FRAME_STYLES = css`
107128
gap: var(--size-1);
108129
109130
margin-bottom: var(--size-1);
131+
font-family: var(--font-stack-monospace);
110132
111133
color: var(--color-gray-1000);
112134
font-size: var(--size-font-small);
@@ -119,4 +141,10 @@ export const CALL_STACK_FRAME_STYLES = css`
119141
font-size: var(--size-font-small);
120142
line-height: var(--size-5);
121143
}
144+
145+
@keyframes fadeIn {
146+
to {
147+
filter: blur(0px);
148+
}
149+
}
122150
`

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/code-frame/code-frame.tsx

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,26 @@ export function CodeFrame({ stackFrame, codeFrame }: CodeFrameProps) {
5858
column: stackFrame.column,
5959
})
6060

61+
const fileExtension = stackFrame?.file?.split('.').pop()
62+
6163
// TODO: make the caret absolute
6264
return (
6365
<div data-nextjs-codeframe>
64-
<div className="code-frame-header">
65-
<p
66-
role="link"
67-
onClick={open}
68-
tabIndex={1}
69-
title="Click to open in your editor"
70-
>
71-
<span>
72-
<FileIcon />
66+
<button
67+
aria-label="Open error location in editor"
68+
className="code-frame-header"
69+
onClick={open}
70+
>
71+
<p className="code-frame-link">
72+
<span className="code-frame-icon">
73+
<FileIcon lang={fileExtension} />
7374
{getFrameSource(stackFrame)} @{' '}
7475
<HotlinkedText text={stackFrame.methodName} />
7576
</span>
7677
<ExternalIcon width={16} height={16} />
7778
</p>
78-
</div>
79-
<pre>
79+
</button>
80+
<pre className="code-frame-pre">
8081
{decoded.map((entry, index) => (
8182
<span
8283
key={`frame-${index}`}
@@ -99,11 +100,6 @@ export function CodeFrame({ stackFrame, codeFrame }: CodeFrameProps) {
99100

100101
export const CODE_FRAME_STYLES = css`
101102
[data-nextjs-codeframe] {
102-
display: -webkit-box;
103-
-webkit-box-orient: vertical;
104-
-webkit-line-clamp: 1;
105-
flex: 1 0 0;
106-
107103
background-color: var(--color-background-200);
108104
overflow: hidden;
109105
color: var(--color-gray-1000);
@@ -116,12 +112,35 @@ export const CODE_FRAME_STYLES = css`
116112
border-radius: var(--size-2);
117113
}
118114
119-
[data-nextjs-codeframe] pre {
115+
.code-frame-link,
116+
.code-frame-pre {
117+
padding: 12px;
118+
}
119+
120+
.code-frame-pre {
120121
white-space: pre-wrap;
121122
}
122123
123124
.code-frame-header {
125+
width: 100%;
126+
cursor: pointer;
124127
border-bottom: 1px solid var(--color-gray-400);
128+
transition: background 100ms ease-out;
129+
130+
&:focus-visible {
131+
outline: var(--focus-ring);
132+
outline-offset: -2px;
133+
}
134+
135+
&:hover {
136+
background: var(--color-gray-100);
137+
}
138+
}
139+
140+
.code-frame-icon {
141+
display: flex;
142+
align-items: center;
143+
gap: 6px;
125144
}
126145
127146
[data-nextjs-codeframe]::selection,
@@ -137,27 +156,22 @@ export const CODE_FRAME_STYLES = css`
137156
138157
[data-nextjs-codeframe] > * {
139158
margin: 0;
140-
padding: calc(var(--size-gap) + var(--size-gap-half))
141-
calc(var(--size-gap-double) + var(--size-gap-half));
142159
}
143160
144-
[data-nextjs-codeframe] > div > p {
161+
.code-frame-link {
145162
display: flex;
146163
align-items: center;
147164
justify-content: space-between;
148-
cursor: pointer;
149165
margin: 0;
166+
outline: 0;
150167
}
151-
[data-nextjs-codeframe] > div > p:hover {
152-
text-decoration: underline dotted;
153-
}
168+
154169
[data-nextjs-codeframe] div > pre {
155170
overflow: hidden;
156171
display: inline-block;
157172
}
158173
159174
[data-nextjs-codeframe] svg {
160175
color: var(--color-gray-900);
161-
margin-right: 6px;
162176
}
163177
`

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/copy-button/index.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,6 @@ export const COPY_BUTTON_STYLES = css`
252252
.nextjs-data-runtime-error-copy-button--initial:hover {
253253
cursor: pointer;
254254
}
255-
.nextjs-data-runtime-error-copy-button[aria-disabled='true'] {
256-
opacity: 0.3;
257-
cursor: not-allowed;
258-
}
259255
.nextjs-data-runtime-error-copy-button--error,
260256
.nextjs-data-runtime-error-copy-button--error:hover {
261257
color: var(--color-ansi-red);

0 commit comments

Comments
 (0)