Skip to content

Commit 52c2248

Browse files
committed
[devtools] fork call stack
1 parent aeec55f commit 52c2248

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { CallStack } from './call-stack'
3+
import { withShadowPortal } from '../../storybook/with-shadow-portal'
4+
5+
const meta: Meta<typeof CallStack> = {
6+
component: CallStack,
7+
parameters: {
8+
layout: 'fullscreen',
9+
backgrounds: {
10+
default: 'background-100-dark',
11+
},
12+
a11y: {
13+
config: {
14+
rules: [
15+
{
16+
id: 'color-contrast',
17+
// Manual testing shows no violation.
18+
// TODO: We might have setup more explicit backgrounds depending on theme.
19+
enabled: false,
20+
},
21+
],
22+
},
23+
},
24+
},
25+
decorators: [withShadowPortal],
26+
}
27+
28+
export default meta
29+
type Story = StoryObj<typeof CallStack>
30+
31+
const frame = {
32+
originalStackFrame: {
33+
file: './app/page.tsx',
34+
methodName: 'MyComponent',
35+
arguments: [],
36+
lineNumber: 10,
37+
column: 5,
38+
ignored: false,
39+
},
40+
sourceStackFrame: {
41+
file: './app/page.tsx',
42+
methodName: 'MyComponent',
43+
arguments: [],
44+
lineNumber: 10,
45+
column: 5,
46+
},
47+
originalCodeFrame: 'export default function MyComponent() {',
48+
error: false as const,
49+
reason: null,
50+
external: false,
51+
ignored: false,
52+
}
53+
54+
const ignoredFrame = {
55+
...frame,
56+
ignored: true,
57+
}
58+
59+
export const SingleFrame: Story = {
60+
args: {
61+
frames: [frame],
62+
},
63+
}
64+
65+
export const MultipleFrames: Story = {
66+
args: {
67+
frames: [
68+
frame,
69+
{
70+
...frame,
71+
originalStackFrame: {
72+
...frame.originalStackFrame,
73+
methodName: 'ParentComponent',
74+
lineNumber: 5,
75+
},
76+
},
77+
...Array(5).fill(ignoredFrame),
78+
{
79+
...frame,
80+
originalStackFrame: {
81+
...frame.originalStackFrame,
82+
methodName: 'GrandparentComponent',
83+
lineNumber: 1,
84+
},
85+
},
86+
...Array(5).fill(ignoredFrame),
87+
],
88+
},
89+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { OriginalStackFrame } from '../../../shared/stack-frame'
2+
3+
import { useMemo, useState } from 'react'
4+
import { CallStackFrame } from '../call-stack-frame/call-stack-frame'
5+
import { ChevronUpDownIcon } from '../../icons/chevron-up-down'
6+
import { css } from '../../utils/css'
7+
8+
export function CallStack({ frames }: { frames: OriginalStackFrame[] }) {
9+
const [isIgnoreListOpen, setIsIgnoreListOpen] = useState(false)
10+
11+
const ignoredFramesTally = useMemo(() => {
12+
return frames.reduce((tally, frame) => tally + (frame.ignored ? 1 : 0), 0)
13+
}, [frames])
14+
15+
return (
16+
<div data-nextjs-call-stack-container>
17+
<div data-nextjs-call-stack-header>
18+
<p data-nextjs-call-stack-title>
19+
Call Stack <span data-nextjs-call-stack-count>{frames.length}</span>
20+
</p>
21+
{ignoredFramesTally > 0 && (
22+
<button
23+
data-nextjs-call-stack-ignored-list-toggle-button
24+
onClick={() => setIsIgnoreListOpen((prev) => !prev)}
25+
>
26+
{`${isIgnoreListOpen ? 'Hide' : 'Show'} ${ignoredFramesTally} ignore-listed frame(s)`}
27+
<ChevronUpDownIcon />
28+
</button>
29+
)}
30+
</div>
31+
{frames.map((frame, frameIndex) => {
32+
return !frame.ignored || isIgnoreListOpen ? (
33+
<CallStackFrame key={frameIndex} frame={frame} />
34+
) : null
35+
})}
36+
</div>
37+
)
38+
}
39+
40+
export const CALL_STACK_STYLES = css`
41+
[data-nextjs-call-stack-container] {
42+
position: relative;
43+
margin-top: 8px;
44+
}
45+
46+
[data-nextjs-call-stack-header] {
47+
display: flex;
48+
justify-content: space-between;
49+
align-items: center;
50+
min-height: var(--size-28);
51+
padding: 8px 8px 12px 4px;
52+
width: 100%;
53+
}
54+
55+
[data-nextjs-call-stack-title] {
56+
display: flex;
57+
justify-content: space-between;
58+
align-items: center;
59+
gap: 8px;
60+
61+
margin: 0;
62+
63+
color: var(--color-gray-1000);
64+
font-size: var(--size-16);
65+
font-weight: 500;
66+
}
67+
68+
[data-nextjs-call-stack-count] {
69+
display: flex;
70+
justify-content: center;
71+
align-items: center;
72+
73+
width: var(--size-20);
74+
height: var(--size-20);
75+
gap: 4px;
76+
77+
color: var(--color-gray-1000);
78+
text-align: center;
79+
font-size: var(--size-11);
80+
font-weight: 500;
81+
line-height: var(--size-16);
82+
83+
border-radius: var(--rounded-full);
84+
background: var(--color-gray-300);
85+
}
86+
87+
[data-nextjs-call-stack-ignored-list-toggle-button] {
88+
all: unset;
89+
display: flex;
90+
align-items: center;
91+
gap: 6px;
92+
color: var(--color-gray-900);
93+
font-size: var(--size-14);
94+
line-height: var(--size-20);
95+
border-radius: 6px;
96+
padding: 4px 6px;
97+
margin-right: -6px;
98+
transition: background 150ms ease;
99+
100+
&:hover {
101+
background: var(--color-gray-100);
102+
}
103+
104+
&:focus {
105+
outline: var(--focus-ring);
106+
}
107+
108+
svg {
109+
width: var(--size-16);
110+
height: var(--size-16);
111+
}
112+
}
113+
`
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export function ChevronUpDownIcon() {
2+
return (
3+
<svg
4+
width="16"
5+
height="16"
6+
viewBox="0 0 16 16"
7+
fill="none"
8+
xmlns="http://www.w3.org/2000/svg"
9+
>
10+
<path
11+
fillRule="evenodd"
12+
clipRule="evenodd"
13+
d="M8.70722 2.39641C8.3167 2.00588 7.68353 2.00588 7.29301 2.39641L4.46978 5.21963L3.93945 5.74996L5.00011 6.81062L5.53044 6.28029L8.00011 3.81062L10.4698 6.28029L11.0001 6.81062L12.0608 5.74996L11.5304 5.21963L8.70722 2.39641ZM5.53044 9.71963L5.00011 9.1893L3.93945 10.25L4.46978 10.7803L7.29301 13.6035C7.68353 13.994 8.3167 13.994 8.70722 13.6035L11.5304 10.7803L12.0608 10.25L11.0001 9.1893L10.4698 9.71963L8.00011 12.1893L5.53044 9.71963Z"
14+
fill="currentColor"
15+
/>
16+
</svg>
17+
)
18+
}

packages/next/src/next-devtools/dev-overlay/styles/component-styles.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ import { DEVTOOLS_PANEL_STYLES } from '../components/devtools-panel/devtools-pan
2828
import { DEVTOOLS_PANEL_FOOTER_STYLES } from '../components/devtools-panel/devtools-panel-footer'
2929
import { DEVTOOLS_PANEL_VERSION_INFO_STYLES } from '../components/devtools-panel/devtools-panel-version-info'
3030
import { DEVTOOLS_PANEL_TAB_SETTINGS_STYLES } from '../components/devtools-panel/devtools-panel-tab/settings-tab'
31+
import { CALL_STACK_STYLES } from '../components/call-stack/call-stack'
3132

3233
export function ComponentStyles() {
3334
return (
3435
<style>
3536
{css`
3637
${COPY_BUTTON_STYLES}
3738
${CALL_STACK_FRAME_STYLES}
39+
${CALL_STACK_STYLES}
3840
${ENVIRONMENT_NAME_LABEL_STYLES}
3941
${overlay}
4042
${toast}

0 commit comments

Comments
 (0)