Skip to content

Commit 6c52257

Browse files
devjiwonchoihuozhi
andauthored
[DevOverlay] Add Runtime Error CodeFrame (vercel#74682)
This PR added style for Runtime Error Code Frame. The file icon will follow up and will be replaced with correct icons (e.g. js, ts, etc.) > [!NOTE] > Does not fully align with Figma due to restrictions on how `@babel/code-frame` handles colors and spacing. ### Light ![CleanShot 2025-01-10 at 23 15 52](https://github.com/user-attachments/assets/fec89610-117a-416e-8554-9f83e2b68003) ### Dark ![CleanShot 2025-01-10 at 23 16 22](https://github.com/user-attachments/assets/58b39260-a1b5-494d-b334-295e8bb0faa8) Closes NDX-650 --------- Co-authored-by: Jiachi Liu <inbox@huozhi.im>
1 parent 121fb9f commit 6c52257

File tree

10 files changed

+163
-122
lines changed

10 files changed

+163
-122
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { CodeFrame } from './CodeFrame'
3+
import { withShadowPortal } from '../../storybook/with-shadow-portal'
4+
5+
const meta: Meta<typeof CodeFrame> = {
6+
title: 'CodeFrame',
7+
component: CodeFrame,
8+
parameters: {
9+
layout: 'fullscreen',
10+
},
11+
decorators: [withShadowPortal],
12+
}
13+
14+
export default meta
15+
type Story = StoryObj<typeof CodeFrame>
16+
17+
const baseStackFrame = {
18+
file: './app/page.tsx',
19+
methodName: 'Home',
20+
arguments: [],
21+
lineNumber: 10,
22+
column: 5,
23+
}
24+
25+
export const SimpleCodeFrame: Story = {
26+
args: {
27+
stackFrame: baseStackFrame,
28+
codeFrame: `\u001b[0m \u001b[90m 1 \u001b[39m \u001b[36mexport\u001b[39m \u001b[36mdefault\u001b[39m \u001b[36mfunction\u001b[39m \u001b[33mHome\u001b[39m() {\u001b[0m
29+
\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 2 \u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'boom'\u001b[39m)\u001b[0m
30+
\u001b[0m \u001b[90m \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m
31+
\u001b[0m \u001b[90m 3 \u001b[39m \u001b[36mreturn\u001b[39m \u001b[33m<\u001b[39m\u001b[33mdiv\u001b[39m\u001b[33m>\u001b[39m\u001b[33mHello\u001b[39m \u001b[33mWorld\u001b[39m\u001b[33m<\u001b[39m\u001b[33m/\u001b[39m\u001b[33mdiv\u001b[39m\u001b[33m>\u001b[39m\u001b[0m
32+
\u001b[0m \u001b[90m 4 \u001b[39m }\u001b[0m
33+
\u001b[0m \u001b[90m 5 \u001b[39m\u001b[0m`,
34+
},
35+
}

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

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
import Anser from 'next/dist/compiled/anser'
2-
import * as React from 'react'
31
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
2+
3+
import Anser from 'next/dist/compiled/anser'
44
import stripAnsi from 'next/dist/compiled/strip-ansi'
5+
6+
import { useMemo } from 'react'
7+
import { HotlinkedText } from '../hot-linked-text'
58
import { getFrameSource } from '../../helpers/stack-frame'
69
import { useOpenInEditor } from '../../helpers/use-open-in-editor'
7-
import { HotlinkedText } from '../hot-linked-text'
10+
import { noop as css } from '../../helpers/noop-template'
11+
import { ExternalIcon } from '../../icons/external'
812

913
export type CodeFrameProps = { stackFrame: StackFrame; codeFrame: string }
1014

11-
export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
12-
stackFrame,
13-
codeFrame,
14-
}) {
15+
export function CodeFrame({ stackFrame, codeFrame }: CodeFrameProps) {
1516
// Strip leading spaces out of the code frame:
16-
const formattedFrame = React.useMemo<string>(() => {
17+
const formattedFrame = useMemo<string>(() => {
1718
const lines = codeFrame.split(/\r?\n/g)
1819

1920
// Find the minimum length of leading spaces after `|` in the code frame
@@ -34,15 +35,17 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
3435
.map((line, a) =>
3536
~(a = line.indexOf('|'))
3637
? line.substring(0, a) +
37-
line.substring(a).replace(`^\\ {${miniLeadingSpacesLength}}`, '')
38+
line
39+
.substring(a + 1)
40+
.replace(`^\\ {${miniLeadingSpacesLength}}`, '')
3841
: line
3942
)
4043
.join('\n')
4144
}
4245
return lines.join('\n')
4346
}, [codeFrame])
4447

45-
const decoded = React.useMemo(() => {
48+
const decoded = useMemo(() => {
4649
return Anser.ansiToJson(formattedFrame, {
4750
json: true,
4851
use_classes: true,
@@ -59,30 +62,19 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
5962
// TODO: make the caret absolute
6063
return (
6164
<div data-nextjs-codeframe>
62-
<div>
65+
<div className="code-frame-header">
6366
<p
6467
role="link"
6568
onClick={open}
6669
tabIndex={1}
6770
title="Click to open in your editor"
6871
>
6972
<span>
73+
<FileIcon />
7074
{getFrameSource(stackFrame)} @{' '}
7175
<HotlinkedText text={stackFrame.methodName} />
7276
</span>
73-
<svg
74-
xmlns="http://www.w3.org/2000/svg"
75-
viewBox="0 0 24 24"
76-
fill="none"
77-
stroke="currentColor"
78-
strokeWidth="2"
79-
strokeLinecap="round"
80-
strokeLinejoin="round"
81-
>
82-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
83-
<polyline points="15 3 21 3 21 9"></polyline>
84-
<line x1="10" y1="14" x2="21" y2="3"></line>
85-
</svg>
77+
<ExternalIcon width={16} height={16} />
8678
</p>
8779
</div>
8880
<pre>
@@ -105,3 +97,76 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
10597
</div>
10698
)
10799
}
100+
101+
export const CODE_FRAME_STYLES = css`
102+
[data-nextjs-codeframe] {
103+
display: -webkit-box;
104+
-webkit-box-orient: vertical;
105+
-webkit-line-clamp: 1;
106+
flex: 1 0 0;
107+
108+
background-color: var(--color-background-200);
109+
overflow: hidden;
110+
color: var(--color-gray-1000);
111+
text-overflow: ellipsis;
112+
font-family: var(--font-stack-monospace);
113+
font-size: 12px;
114+
line-height: 16px;
115+
}
116+
117+
.code-frame-header {
118+
border-top: 1px solid var(--color-gray-400);
119+
border-bottom: 1px solid var(--color-gray-400);
120+
}
121+
122+
[data-nextjs-codeframe]::selection,
123+
[data-nextjs-codeframe] *::selection {
124+
background-color: var(--color-ansi-selection);
125+
}
126+
127+
[data-nextjs-codeframe] * {
128+
color: inherit;
129+
background-color: transparent;
130+
font-family: var(--font-stack-monospace);
131+
}
132+
133+
[data-nextjs-codeframe] > * {
134+
margin: 0;
135+
padding: calc(var(--size-gap) + var(--size-gap-half))
136+
calc(var(--size-gap-double) + var(--size-gap-half));
137+
}
138+
139+
[data-nextjs-codeframe] > div > p {
140+
display: flex;
141+
align-items: center;
142+
justify-content: space-between;
143+
cursor: pointer;
144+
margin: 0;
145+
}
146+
[data-nextjs-codeframe] > div > p:hover {
147+
text-decoration: underline dotted;
148+
}
149+
[data-nextjs-codeframe] div > pre {
150+
overflow: hidden;
151+
display: inline-block;
152+
}
153+
154+
[data-nextjs-codeframe] svg {
155+
color: var(--color-gray-900);
156+
margin-right: 6px;
157+
}
158+
`
159+
160+
// TODO: Add more Icons (react, next, etc.)
161+
function FileIcon() {
162+
return (
163+
<svg width="16" height="17" fill="none" xmlns="http://www.w3.org/2000/svg">
164+
<path
165+
fillRule="evenodd"
166+
clipRule="evenodd"
167+
d="M14.5 7v7a2.5 2.5 0 0 1-2.5 2.5H4A2.5 2.5 0 0 1 1.5 14V.5h7.586a1 1 0 0 1 .707.293l4.414 4.414a1 1 0 0 1 .293.707V7zM13 7v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2h5v5h5zM9.5 2.621V5.5h2.879L9.5 2.621z"
168+
fill="currentColor"
169+
/>
170+
</svg>
171+
)
172+
}

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

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/CodeFrame/styles.tsx

Lines changed: 0 additions & 53 deletions
This file was deleted.

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

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
22
import type { OriginalStackFrame } from '../../helpers/stack-frame'
33

44
import { HotlinkedText } from '../hot-linked-text'
5+
import { ExternalIcon } from '../../icons/external'
56
import { getFrameSource } from '../../helpers/stack-frame'
67
import { useOpenInEditor } from '../../helpers/use-open-in-editor'
78
import { noop as css } from '../../helpers/noop-template'
@@ -50,7 +51,7 @@ export const CallStackFrame: React.FC<{
5051
className="call-stack-frame-method-name"
5152
>
5253
<HotlinkedText text={formattedMethod} />
53-
{hasSource && <External />}
54+
{hasSource && <ExternalIcon width={16} height={16} />}
5455
</span>
5556
<span
5657
className="call-stack-frame-file-source"
@@ -62,25 +63,6 @@ export const CallStackFrame: React.FC<{
6263
)
6364
}
6465

65-
function External() {
66-
return (
67-
<svg
68-
width="16"
69-
height="16"
70-
viewBox="0 0 16 16"
71-
fill="none"
72-
xmlns="http://www.w3.org/2000/svg"
73-
>
74-
<path
75-
fillRule="evenodd"
76-
clipRule="evenodd"
77-
d="M11.5 9.75V11.25C11.5 11.3881 11.3881 11.5 11.25 11.5H4.75C4.61193 11.5 4.5 11.3881 4.5 11.25L4.5 4.75C4.5 4.61193 4.61193 4.5 4.75 4.5H6.25H7V3H6.25H4.75C3.7835 3 3 3.7835 3 4.75V11.25C3 12.2165 3.7835 13 4.75 13H11.25C12.2165 13 13 12.2165 13 11.25V9.75V9H11.5V9.75ZM8.5 3H9.25H12.2495C12.6637 3 12.9995 3.33579 12.9995 3.75V6.75V7.5H11.4995V6.75V5.56066L8.53033 8.52978L8 9.06011L6.93934 7.99945L7.46967 7.46912L10.4388 4.5H9.25H8.5V3Z"
78-
fill="currentColor"
79-
/>
80-
</svg>
81-
)
82-
}
83-
8466
export const CALL_STACK_FRAME_STYLES = css`
8567
[data-nextjs-call-stack-frame-ignored] {
8668
padding: var(--size-1_5) var(--size-2);

packages/next/src/client/components/react-dev-overlay/_experimental/internal/container/RuntimeError/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ReadyRuntimeError } from '../../helpers/get-error-by-type'
22

33
import { useMemo } from 'react'
4-
import { CodeFrame } from '../../components/CodeFrame'
4+
import { CodeFrame } from '../../components/CodeFrame/CodeFrame'
55
import { CallStack } from '../../components/Errors/call-stack/call-stack'
66
import { noop as css } from '../../helpers/noop-template'
77
import { PSEUDO_HTML_DIFF_STYLES } from './component-stack-pseudo-html'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function ExternalIcon(props: React.SVGProps<SVGSVGElement>) {
2+
return (
3+
<svg xmlns="http://www.w3.org/2000/svg" {...props}>
4+
<path
5+
fillRule="evenodd"
6+
clipRule="evenodd"
7+
fill="currentColor"
8+
d="M11.5 9.75V11.25C11.5 11.3881 11.3881 11.5 11.25 11.5H4.75C4.61193 11.5 4.5 11.3881 4.5 11.25L4.5 4.75C4.5 4.61193 4.61193 4.5 4.75 4.5H6.25H7V3H6.25H4.75C3.7835 3 3 3.7835 3 4.75V11.25C3 12.2165 3.7835 13 4.75 13H11.25C12.2165 13 13 12.2165 13 11.25V9.75V9H11.5V9.75ZM8.5 3H9.25H12.2495C12.6637 3 12.9995 3.33579 12.9995 3.75V6.75V7.5H11.4995V6.75V5.56066L8.53033 8.52978L8 9.06011L6.93934 7.99945L7.46967 7.46912L10.4388 4.5H9.25H8.5V3Z"
9+
/>
10+
</svg>
11+
)
12+
}

packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/Base.tsx

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,6 @@ export function Base() {
4444
--font-stack-sans: 'Geist', -apple-system, 'Source Sans Pro',
4545
sans-serif;
4646
47-
--color-ansi-selection: rgba(95, 126, 151, 0.48);
48-
--color-ansi-bg: #111111;
49-
--color-ansi-fg: #cccccc;
50-
51-
--color-ansi-white: #777777;
52-
--color-ansi-black: #141414;
53-
--color-ansi-blue: #00aaff;
54-
--color-ansi-cyan: #88ddff;
55-
--color-ansi-green: #98ec65;
56-
--color-ansi-magenta: #aa88ff;
57-
--color-ansi-red: #ff5555;
58-
--color-ansi-yellow: #ffcc33;
59-
--color-ansi-bright-white: #ffffff;
60-
--color-ansi-bright-black: #777777;
61-
--color-ansi-bright-blue: #33bbff;
62-
--color-ansi-bright-cyan: #bbecff;
63-
--color-ansi-bright-green: #b6f292;
64-
--color-ansi-bright-magenta: #cebbff;
65-
--color-ansi-bright-red: #ff8888;
66-
--color-ansi-bright-yellow: #ffd966;
67-
6847
font-family: var(--font-stack-sans);
6948
7049
/* TODO: Remove replaced ones. */

packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/ComponentStyles.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { styles as codeFrame } from '../components/CodeFrame/styles'
1+
import { CODE_FRAME_STYLES } from '../components/CodeFrame/CodeFrame'
22
import { styles as dialog } from '../components/Dialog'
33
import { styles as errorLayout } from '../components/Errors/error-overlay-layout/error-overlay-layout'
44
import { styles as bottomStacks } from '../components/Errors/error-overlay-bottom-stacks/error-overlay-bottom-stacks'
@@ -29,7 +29,7 @@ export function ComponentStyles() {
2929
${footer}
3030
${bottomStacks}
3131
${pagination}
32-
${codeFrame}
32+
${CODE_FRAME_STYLES}
3333
${terminal}
3434
${buildErrorStyles}
3535
${containerErrorStyles}

packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/colors.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ export function Colors() {
66
<style>
77
{css`
88
:host {
9+
/* Ansi - Temporary */
10+
--color-ansi-selection: var(--color-gray-alpha-300);
11+
--color-ansi-bg: var(--color-background-200);
12+
--color-ansi-fg: var(--color-gray-1000);
13+
14+
--color-ansi-white: var(--color-gray-700);
15+
--color-ansi-black: var(--color-gray-200);
16+
--color-ansi-blue: var(--color-blue-700);
17+
--color-ansi-cyan: var(--color-blue-700);
18+
--color-ansi-green: var(--color-green-700);
19+
--color-ansi-magenta: var(--color-blue-700);
20+
--color-ansi-red: var(--color-red-700);
21+
--color-ansi-yellow: var(--color-amber-800);
22+
--color-ansi-bright-white: var(--color-gray-1000);
23+
--color-ansi-bright-black: var(--color-gray-700);
24+
--color-ansi-bright-blue: var(--color-blue-800);
25+
--color-ansi-bright-cyan: var(--color-blue-800);
26+
--color-ansi-bright-green: var(--color-green-800);
27+
--color-ansi-bright-magenta: var(--color-blue-800);
28+
--color-ansi-bright-red: var(--color-red-800);
29+
--color-ansi-bright-yellow: var(--color-amber-900);
30+
931
/* Background Light */
1032
--color-background-100: #ffffff;
1133
--color-background-200: #fafafa;

0 commit comments

Comments
 (0)