-
Notifications
You must be signed in to change notification settings - Fork 28.7k
[devtools] panel ui issues tab sidebar #80728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
74bdc7c
fa93127
89c329b
302983a
5d0023f
e8e1ae5
779037f
e1ea32b
e98f6bb
c38ee9c
5fe07f7
4a73507
6e2ded9
da8ac07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { css } from '../../../../utils/css' | ||
|
||
export function IssuesTabSidebarFrameSkeleton() { | ||
return ( | ||
<> | ||
<div data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar="1" /> | ||
<div data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar="2" /> | ||
</> | ||
) | ||
} | ||
|
||
export const DEVTOOLS_PANEL_TAB_ISSUES_SIDEBAR_FRAME_SKELETON_STYLES = css` | ||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar] { | ||
height: var(--size-12); | ||
border-radius: 100px; | ||
background: linear-gradient( | ||
90deg, | ||
var(--color-gray-200) 25%, | ||
var(--color-gray-100) 50%, | ||
var(--color-gray-200) 75% | ||
); | ||
background-size: 200% 100%; | ||
animation: skeleton-shimmer 1.5s ease-in-out infinite; | ||
} | ||
|
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar='1'] { | ||
width: 75%; | ||
margin-bottom: 8px; | ||
} | ||
|
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar='2'] { | ||
width: 36.5%; | ||
} | ||
|
||
@keyframes skeleton-shimmer { | ||
0% { | ||
background-position: -200% 0; | ||
} | ||
100% { | ||
background-position: 200% 0; | ||
} | ||
} | ||
|
||
/* Respect user's motion preferences */ | ||
@media (prefers-reduced-motion: reduce) { | ||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar='1'], | ||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-skeleton-bar='2'] { | ||
animation: none; | ||
background: var(--color-gray-200); | ||
} | ||
} | ||
` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { useState } from 'react' | ||
import { IssuesTabSidebar } from './issues-tab-sidebar' | ||
import { withShadowPortal } from '../../../../storybook/with-shadow-portal' | ||
import { runtimeErrors } from '../../../../storybook/errors' | ||
|
||
const meta: Meta<typeof IssuesTabSidebar> = { | ||
component: IssuesTabSidebar, | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
decorators: [withShadowPortal], | ||
} | ||
|
||
export default meta | ||
type Story = StoryObj<typeof IssuesTabSidebar> | ||
|
||
const SidebarWrapper = ({ | ||
runtimeErrors: errors, | ||
errorType, | ||
}: { | ||
runtimeErrors: any[] | ||
errorType: string | null | ||
}) => { | ||
const [activeIdx, setActiveIdx] = useState(0) | ||
|
||
return ( | ||
<IssuesTabSidebar | ||
runtimeErrors={errors} | ||
errorType={errorType} | ||
activeIdx={activeIdx} | ||
setActiveIndex={setActiveIdx} | ||
/> | ||
) | ||
} | ||
|
||
export const Default: Story = { | ||
render: () => ( | ||
<SidebarWrapper runtimeErrors={runtimeErrors} errorType="runtime" /> | ||
), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import type { ReadyRuntimeError } from '../../../../utils/get-error-by-type' | ||
|
||
import { Suspense, useMemo, memo } from 'react' | ||
|
||
import { css } from '../../../../utils/css' | ||
import { getFrameSource } from '../../../../../shared/stack-frame' | ||
import { useFrames } from '../../../../utils/get-error-by-type' | ||
import { getErrorTypeLabel } from '../../../../container/errors' | ||
import { IssuesTabSidebarFrameSkeleton } from './issues-tab-sidebar-frame-skeleton' | ||
|
||
export function IssuesTabSidebar({ | ||
runtimeErrors, | ||
activeIdx, | ||
setActiveIndex, | ||
}: { | ||
runtimeErrors: ReadyRuntimeError[] | ||
errorType: string | null | ||
activeIdx: number | ||
setActiveIndex: (idx: number) => void | ||
}) { | ||
return ( | ||
<aside data-nextjs-devtools-panel-tab-issues-sidebar> | ||
{runtimeErrors.map((runtimeError, idx) => { | ||
const isActive = idx === activeIdx | ||
return ( | ||
<IssuesTabSidebarFrame | ||
key={idx} | ||
runtimeError={runtimeError} | ||
idx={idx} | ||
isActive={isActive} | ||
setActiveIndex={setActiveIndex} | ||
/> | ||
) | ||
})} | ||
</aside> | ||
) | ||
} | ||
|
||
const IssuesTabSidebarFrameItem = memo(function IssuesTabSidebarFrameItem({ | ||
runtimeError, | ||
}: { | ||
runtimeError: ReadyRuntimeError | ||
}) { | ||
const frames = useFrames(runtimeError) | ||
|
||
const firstFrame = useMemo(() => { | ||
const firstFirstPartyFrameIndex = frames.findIndex( | ||
(entry) => | ||
!entry.ignored && | ||
Boolean(entry.originalCodeFrame) && | ||
Boolean(entry.originalStackFrame) | ||
) | ||
|
||
return frames[firstFirstPartyFrameIndex] ?? null | ||
}, [frames]) | ||
|
||
if (!firstFrame?.originalStackFrame) { | ||
return null | ||
} | ||
|
||
const errorType = getErrorTypeLabel(runtimeError.error, runtimeError.type) | ||
const frameSource = getFrameSource(firstFrame.originalStackFrame) | ||
|
||
return ( | ||
<> | ||
<span data-nextjs-devtools-panel-tab-issues-sidebar-frame-error-type> | ||
{errorType} | ||
</span> | ||
<span data-nextjs-devtools-panel-tab-issues-sidebar-frame-source> | ||
{frameSource} | ||
</span> | ||
</> | ||
) | ||
}) | ||
|
||
const IssuesTabSidebarFrame = memo(function IssuesTabSidebarFrame({ | ||
runtimeError, | ||
idx, | ||
isActive, | ||
setActiveIndex, | ||
}: { | ||
runtimeError: ReadyRuntimeError | ||
idx: number | ||
isActive: boolean | ||
setActiveIndex: (idx: number) => void | ||
}) { | ||
return ( | ||
<button | ||
data-nextjs-devtools-panel-tab-issues-sidebar-frame | ||
data-nextjs-devtools-panel-tab-issues-sidebar-frame-active={isActive} | ||
onClick={() => setActiveIndex(idx)} | ||
> | ||
<Suspense fallback={<IssuesTabSidebarFrameSkeleton />}> | ||
<IssuesTabSidebarFrameItem runtimeError={runtimeError} /> | ||
</Suspense> | ||
Comment on lines
+93
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved Suspense down to the layer right above where it's needed. |
||
</button> | ||
) | ||
}) | ||
|
||
export const DEVTOOLS_PANEL_TAB_ISSUES_SIDEBAR_STYLES = css` | ||
[data-nextjs-devtools-panel-tab-issues-sidebar] { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 4px; | ||
padding: 8px; | ||
border-right: 1px solid var(--color-gray-400); | ||
overflow-y: auto; | ||
min-height: 0; | ||
|
||
devjiwonchoi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@media (max-width: 575px) { | ||
max-width: 112px; | ||
} | ||
|
||
@media (min-width: 576px) { | ||
max-width: 138px; | ||
width: 100%; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you clarify? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
@media (min-width: 768px) { | ||
max-width: 172.5px; | ||
width: 100%; | ||
} | ||
|
||
@media (min-width: 992px) { | ||
max-width: 230px; | ||
width: 100%; | ||
} | ||
} | ||
|
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame] { | ||
display: flex; | ||
flex-direction: column; | ||
padding: 10px 8px; | ||
border-radius: var(--rounded-lg); | ||
transition: background-color 0.2s ease-in-out; | ||
|
||
&:hover { | ||
background-color: var(--color-gray-200); | ||
} | ||
|
||
&:active { | ||
background-color: var(--color-gray-300); | ||
} | ||
} | ||
|
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-active='true'] { | ||
background-color: var(--color-gray-100); | ||
} | ||
|
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-error-type] { | ||
display: inline-block; | ||
align-self: flex-start; | ||
color: var(--color-gray-1000); | ||
font-size: var(--size-14); | ||
font-weight: 500; | ||
line-height: var(--size-20); | ||
} | ||
|
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-source] { | ||
display: inline-block; | ||
align-self: flex-start; | ||
color: var(--color-gray-900); | ||
font-size: var(--size-13); | ||
line-height: var(--size-18); | ||
} | ||
|
||
/* Ellipsis for long stack frame source or small devices. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-error-type], | ||
[data-nextjs-devtools-panel-tab-issues-sidebar-frame-source] { | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
white-space: nowrap; | ||
max-width: 100%; | ||
} | ||
` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memoized so the list doesn't update unless a new error is added.
CleanShot.2025-06-23.at.16.32.10.mp4