Skip to content

Commit

Permalink
[DevOverlay] Hydration Error Code Frame (#74822)
Browse files Browse the repository at this point in the history
  • Loading branch information
devjiwonchoi authored Jan 14, 2025
1 parent 23dee53 commit b0c14f6
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,6 @@ export const styles = css`
margin-bottom: var(--size-gap);
font-size: var(--size-font-big);
}
.nextjs__container_errors__component-stack {
margin: 0;
padding: 12px 32px;
color: var(--color-ansi-fg);
background: var(--color-ansi-bg);
}
.nextjs-toast-errors-parent {
cursor: pointer;
transition: transform 0.2s ease;
Expand Down Expand Up @@ -384,6 +378,6 @@ export const styles = css`
margin-bottom: var(--size-3);
}
.error-overlay-notes-container {
padding: 0 var (--size-4);
padding: 0 var(--size-4);
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Meta, StoryObj } from '@storybook/react'
import { PseudoHtmlDiff } from './component-stack-pseudo-html'
import { withShadowPortal } from '../../storybook/with-shadow-portal'

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

export default meta
type Story = StoryObj<typeof PseudoHtmlDiff>

const sampleComponentStack = [
{
component: 'div',
canOpenInEditor: false,
},
{
component: 'article',
canOpenInEditor: false,
},
{
component: 'main',
canOpenInEditor: false,
},
{
component: 'Home',
canOpenInEditor: false,
},
]

export const TextMismatch: Story = {
args: {
componentStackFrames: sampleComponentStack,
firstContent: 'Server rendered content',
secondContent: 'Client rendered content',
hydrationMismatchType: 'text',
reactOutputComponentDiff: undefined,
},
}

export const TextInTagMismatch: Story = {
args: {
componentStackFrames: sampleComponentStack,
firstContent: 'Mismatched content',
secondContent: 'p',
hydrationMismatchType: 'text-in-tag',
reactOutputComponentDiff: undefined,
},
}

export const ReactUnifiedMismatch: Story = {
args: {
componentStackFrames: sampleComponentStack,
hydrationMismatchType: 'tag',
reactOutputComponentDiff: `<Page>
<Layout>
<div>
- <p>Server content</p>
+ <p>Client content</p>`,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,19 @@ export function PseudoHtmlDiff({
sign === '+' ? 'add' : 'remove'
}
>
{sign}
{spaces}
{trimmedLine}
{'\n'}
{sign === '+' ? <PlusIcon /> : <MinusIcon />}
<span
className={
isHtmlCollapsed
? 'error-overlay-hydration-error-collapsed'
: ''
}
>
{/* Slice 2 spaces for the icon */}
{spaces.slice(2)}
{trimmedLine}
{'\n'}
</span>
</span>
)
} else if (currentComponentIndex >= 0) {
Expand Down Expand Up @@ -295,41 +304,85 @@ export function PseudoHtmlDiff({

return (
<div data-nextjs-container-errors-pseudo-html>
<button
tabIndex={10} // match CallStackFrame
data-nextjs-container-errors-pseudo-html-collapse
onClick={() => toggleCollapseHtml(!isHtmlCollapsed)}
>
<CollapseIcon collapsed={isHtmlCollapsed} />
</button>
<span>
<button
tabIndex={10} // match CallStackFrame
data-nextjs-container-errors-pseudo-html-collapse
onClick={() => toggleCollapseHtml(!isHtmlCollapsed)}
>
<CollapseIcon collapsed={isHtmlCollapsed} />
</button>
</span>
<pre {...props}>
<code>{htmlComponents}</code>
</pre>
</div>
)
}

function PlusIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="error-overlay-hydration-error-diff-plus-icon"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.25 8.75V12H8.75V8.75H12V7.25H8.75V4H7.25V7.25H4V8.75H7.25Z"
fill="currentColor"
/>
</svg>
)
}

function MinusIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="error-overlay-hydration-error-diff-minus-icon"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 8.75H4V7.25H12V8.75Z"
fill="currentColor"
/>
</svg>
)
}

export const PSEUDO_HTML_DIFF_STYLES = css`
[data-nextjs-container-errors-pseudo-html] {
position: relative;
border-top: 1px solid var(--color-gray-400);
background: var(--color-background-200);
color: var(--color-syntax-constant);
font-family: var(--font-stack-monospace);
font-size: var(--size-font-smaller);
line-height: var(--size-4);
}
[data-nextjs-container-errors-pseudo-html-collapse] {
position: absolute;
left: 10px;
top: 10px;
color: inherit;
background: none;
border: none;
padding: 0;
all: unset;
&:focus {
outline: none;
}
}
[data-nextjs-container-errors-pseudo-html--diff='add'] {
color: var(--color-ansi-green);
background: var(--color-green-300);
}
[data-nextjs-container-errors-pseudo-html--diff='remove'] {
color: var(--color-ansi-red);
background: var(--color-red-300);
}
[data-nextjs-container-errors-pseudo-html--tag-error] {
color: var(--color-ansi-red);
background: var(--color-red-300);
font-weight: bold;
}
/* hide but text are still accessible in DOM */
Expand All @@ -340,4 +393,26 @@ export const PSEUDO_HTML_DIFF_STYLES = css`
[data-nextjs-container-errors-pseudo-html--tag-adjacent='false'] {
color: var(--color-accents-1);
}
[data-nextjs-container-errors-pseudo-html] > span {
display: block;
height: var(--size-5);
}
[data-nextjs-container-errors-pseudo-html] > pre > code > span {
display: block;
height: var(--size-5);
}
.nextjs__container_errors__component-stack {
margin: 0;
}
.error-overlay-hydration-error-collapsed {
padding-left: var(--size-4);
}
.error-overlay-hydration-error-diff-plus-icon {
color: var(--color-green-900);
}
.error-overlay-hydration-error-diff-minus-icon {
color: var(--color-red-900);
}
`

0 comments on commit b0c14f6

Please sign in to comment.