Skip to content

Commit

Permalink
[DevOverlay] Add Runtime Error CodeFrame (#74682)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
devjiwonchoi and huozhi authored Jan 10, 2025
1 parent 121fb9f commit 6c52257
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 122 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Meta, StoryObj } from '@storybook/react'
import { CodeFrame } from './CodeFrame'
import { withShadowPortal } from '../../storybook/with-shadow-portal'

const meta: Meta<typeof CodeFrame> = {
title: 'CodeFrame',
component: CodeFrame,
parameters: {
layout: 'fullscreen',
},
decorators: [withShadowPortal],
}

export default meta
type Story = StoryObj<typeof CodeFrame>

const baseStackFrame = {
file: './app/page.tsx',
methodName: 'Home',
arguments: [],
lineNumber: 10,
column: 5,
}

export const SimpleCodeFrame: Story = {
args: {
stackFrame: baseStackFrame,
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
\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
\u001b[0m \u001b[90m \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m
\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
\u001b[0m \u001b[90m 4 \u001b[39m }\u001b[0m
\u001b[0m \u001b[90m 5 \u001b[39m\u001b[0m`,
},
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import Anser from 'next/dist/compiled/anser'
import * as React from 'react'
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'

import Anser from 'next/dist/compiled/anser'
import stripAnsi from 'next/dist/compiled/strip-ansi'

import { useMemo } from 'react'
import { HotlinkedText } from '../hot-linked-text'
import { getFrameSource } from '../../helpers/stack-frame'
import { useOpenInEditor } from '../../helpers/use-open-in-editor'
import { HotlinkedText } from '../hot-linked-text'
import { noop as css } from '../../helpers/noop-template'
import { ExternalIcon } from '../../icons/external'

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

export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
stackFrame,
codeFrame,
}) {
export function CodeFrame({ stackFrame, codeFrame }: CodeFrameProps) {
// Strip leading spaces out of the code frame:
const formattedFrame = React.useMemo<string>(() => {
const formattedFrame = useMemo<string>(() => {
const lines = codeFrame.split(/\r?\n/g)

// Find the minimum length of leading spaces after `|` in the code frame
Expand All @@ -34,15 +35,17 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
.map((line, a) =>
~(a = line.indexOf('|'))
? line.substring(0, a) +
line.substring(a).replace(`^\\ {${miniLeadingSpacesLength}}`, '')
line
.substring(a + 1)
.replace(`^\\ {${miniLeadingSpacesLength}}`, '')
: line
)
.join('\n')
}
return lines.join('\n')
}, [codeFrame])

const decoded = React.useMemo(() => {
const decoded = useMemo(() => {
return Anser.ansiToJson(formattedFrame, {
json: true,
use_classes: true,
Expand All @@ -59,30 +62,19 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
// TODO: make the caret absolute
return (
<div data-nextjs-codeframe>
<div>
<div className="code-frame-header">
<p
role="link"
onClick={open}
tabIndex={1}
title="Click to open in your editor"
>
<span>
<FileIcon />
{getFrameSource(stackFrame)} @{' '}
<HotlinkedText text={stackFrame.methodName} />
</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
<ExternalIcon width={16} height={16} />
</p>
</div>
<pre>
Expand All @@ -105,3 +97,76 @@ export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({
</div>
)
}

export const CODE_FRAME_STYLES = css`
[data-nextjs-codeframe] {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex: 1 0 0;
background-color: var(--color-background-200);
overflow: hidden;
color: var(--color-gray-1000);
text-overflow: ellipsis;
font-family: var(--font-stack-monospace);
font-size: 12px;
line-height: 16px;
}
.code-frame-header {
border-top: 1px solid var(--color-gray-400);
border-bottom: 1px solid var(--color-gray-400);
}
[data-nextjs-codeframe]::selection,
[data-nextjs-codeframe] *::selection {
background-color: var(--color-ansi-selection);
}
[data-nextjs-codeframe] * {
color: inherit;
background-color: transparent;
font-family: var(--font-stack-monospace);
}
[data-nextjs-codeframe] > * {
margin: 0;
padding: calc(var(--size-gap) + var(--size-gap-half))
calc(var(--size-gap-double) + var(--size-gap-half));
}
[data-nextjs-codeframe] > div > p {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin: 0;
}
[data-nextjs-codeframe] > div > p:hover {
text-decoration: underline dotted;
}
[data-nextjs-codeframe] div > pre {
overflow: hidden;
display: inline-block;
}
[data-nextjs-codeframe] svg {
color: var(--color-gray-900);
margin-right: 6px;
}
`

// TODO: Add more Icons (react, next, etc.)
function FileIcon() {
return (
<svg width="16" height="17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
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"
fill="currentColor"
/>
</svg>
)
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import type { OriginalStackFrame } from '../../helpers/stack-frame'

import { HotlinkedText } from '../hot-linked-text'
import { ExternalIcon } from '../../icons/external'
import { getFrameSource } from '../../helpers/stack-frame'
import { useOpenInEditor } from '../../helpers/use-open-in-editor'
import { noop as css } from '../../helpers/noop-template'
Expand Down Expand Up @@ -50,7 +51,7 @@ export const CallStackFrame: React.FC<{
className="call-stack-frame-method-name"
>
<HotlinkedText text={formattedMethod} />
{hasSource && <External />}
{hasSource && <ExternalIcon width={16} height={16} />}
</span>
<span
className="call-stack-frame-file-source"
Expand All @@ -62,25 +63,6 @@ export const CallStackFrame: React.FC<{
)
}

function External() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
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"
fill="currentColor"
/>
</svg>
)
}

export const CALL_STACK_FRAME_STYLES = css`
[data-nextjs-call-stack-frame-ignored] {
padding: var(--size-1_5) var(--size-2);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReadyRuntimeError } from '../../helpers/get-error-by-type'

import { useMemo } from 'react'
import { CodeFrame } from '../../components/CodeFrame'
import { CodeFrame } from '../../components/CodeFrame/CodeFrame'
import { CallStack } from '../../components/Errors/call-stack/call-stack'
import { noop as css } from '../../helpers/noop-template'
import { PSEUDO_HTML_DIFF_STYLES } from './component-stack-pseudo-html'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function ExternalIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
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"
/>
</svg>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,6 @@ export function Base() {
--font-stack-sans: 'Geist', -apple-system, 'Source Sans Pro',
sans-serif;
--color-ansi-selection: rgba(95, 126, 151, 0.48);
--color-ansi-bg: #111111;
--color-ansi-fg: #cccccc;
--color-ansi-white: #777777;
--color-ansi-black: #141414;
--color-ansi-blue: #00aaff;
--color-ansi-cyan: #88ddff;
--color-ansi-green: #98ec65;
--color-ansi-magenta: #aa88ff;
--color-ansi-red: #ff5555;
--color-ansi-yellow: #ffcc33;
--color-ansi-bright-white: #ffffff;
--color-ansi-bright-black: #777777;
--color-ansi-bright-blue: #33bbff;
--color-ansi-bright-cyan: #bbecff;
--color-ansi-bright-green: #b6f292;
--color-ansi-bright-magenta: #cebbff;
--color-ansi-bright-red: #ff8888;
--color-ansi-bright-yellow: #ffd966;
font-family: var(--font-stack-sans);
/* TODO: Remove replaced ones. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { styles as codeFrame } from '../components/CodeFrame/styles'
import { CODE_FRAME_STYLES } from '../components/CodeFrame/CodeFrame'
import { styles as dialog } from '../components/Dialog'
import { styles as errorLayout } from '../components/Errors/error-overlay-layout/error-overlay-layout'
import { styles as bottomStacks } from '../components/Errors/error-overlay-bottom-stacks/error-overlay-bottom-stacks'
Expand Down Expand Up @@ -29,7 +29,7 @@ export function ComponentStyles() {
${footer}
${bottomStacks}
${pagination}
${codeFrame}
${CODE_FRAME_STYLES}
${terminal}
${buildErrorStyles}
${containerErrorStyles}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ export function Colors() {
<style>
{css`
:host {
/* Ansi - Temporary */
--color-ansi-selection: var(--color-gray-alpha-300);
--color-ansi-bg: var(--color-background-200);
--color-ansi-fg: var(--color-gray-1000);
--color-ansi-white: var(--color-gray-700);
--color-ansi-black: var(--color-gray-200);
--color-ansi-blue: var(--color-blue-700);
--color-ansi-cyan: var(--color-blue-700);
--color-ansi-green: var(--color-green-700);
--color-ansi-magenta: var(--color-blue-700);
--color-ansi-red: var(--color-red-700);
--color-ansi-yellow: var(--color-amber-800);
--color-ansi-bright-white: var(--color-gray-1000);
--color-ansi-bright-black: var(--color-gray-700);
--color-ansi-bright-blue: var(--color-blue-800);
--color-ansi-bright-cyan: var(--color-blue-800);
--color-ansi-bright-green: var(--color-green-800);
--color-ansi-bright-magenta: var(--color-blue-800);
--color-ansi-bright-red: var(--color-red-800);
--color-ansi-bright-yellow: var(--color-amber-900);
/* Background Light */
--color-background-100: #ffffff;
--color-background-200: #fafafa;
Expand Down

0 comments on commit 6c52257

Please sign in to comment.