Skip to content

Commit 0a8d687

Browse files
[dev-overlay] Blur fader for scrollable container (#77196)
The navigation items overlapping with content looks very bad on scroll: <img width="289" alt="IMG_9601" src="https://github.com/user-attachments/assets/85197dc6-eb7e-4919-be59-bc8b2b6a2e5f" /> This PR creates a masked fade area that will be visible only when scrolling: https://github.com/user-attachments/assets/99757916-8374-4d79-ac2d-762786608f91 --- Closes NDX-982
1 parent 1d936c4 commit 0a8d687

File tree

7 files changed

+95
-7
lines changed

7 files changed

+95
-7
lines changed

Diff for: packages/next/src/client/components/react-dev-overlay/ui/components/dialog/dialog.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export type DialogProps = {
1010
className?: string
1111
onClose?: () => void
1212
dialogResizerRef?: React.RefObject<HTMLDivElement | null>
13-
}
13+
} & React.HTMLAttributes<HTMLDivElement>
1414

1515
const CSS_SELECTORS_TO_EXCLUDE_ON_CLICK_OUTSIDE = [
1616
'[data-next-mark]',

Diff for: packages/next/src/client/components/react-dev-overlay/ui/components/dialog/styles.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ const styles = `
44
--next-dialog-footer-height: var(--size-48);
55
--next-dialog-max-width: 960px;
66
--next-dialog-row-padding: 16px;
7-
--next-dialog-container-padding: 12px;
7+
--next-dialog-padding-x: 12px;
8+
--next-dialog-notch-height: 42px;
9+
--next-dialog-border-width: 1px;
810
911
display: flex;
1012
flex-direction: column-reverse;
@@ -23,24 +25,35 @@ const styles = `
2325
opacity: 1;
2426
scale: 1;
2527
}
28+
29+
[data-nextjs-scroll-fader][data-side="top"] {
30+
left: 1px;
31+
top: calc(var(--next-dialog-notch-height) + var(--next-dialog-border-width));
32+
width: calc(100% - var(--next-dialog-padding-x));
33+
opacity: 0;
34+
}
2635
}
2736
2837
[data-nextjs-dialog] {
2938
outline: none;
3039
overflow: hidden;
3140
}
41+
3242
[data-nextjs-dialog]::-webkit-scrollbar {
3343
width: 6px;
3444
border-radius: 0 0 1rem 1rem;
3545
margin-bottom: 1rem;
3646
}
47+
3748
[data-nextjs-dialog]::-webkit-scrollbar-button {
3849
display: none;
3950
}
51+
4052
[data-nextjs-dialog]::-webkit-scrollbar-track {
4153
border-radius: 0 0 1rem 1rem;
4254
background-color: var(--color-background-100);
4355
}
56+
4457
[data-nextjs-dialog]::-webkit-scrollbar-thumb {
4558
border-radius: 1rem;
4659
background-color: var(--color-gray-500);
@@ -71,7 +84,7 @@ const styles = `
7184
display: flex;
7285
flex-direction: column;
7386
position: relative;
74-
padding: 16px 12px;
87+
padding: 16px var(--next-dialog-padding-x);
7588
}
7689
7790
/* Account for the footer height, when present */

Diff for: packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type ErrorOverlayDialogProps = {
44
children?: React.ReactNode
55
onClose?: () => void
66
dialogResizerRef?: React.RefObject<HTMLDivElement | null>
7-
}
7+
} & React.HTMLAttributes<HTMLDivElement>
88

99
export function ErrorOverlayDialog({
1010
children,
@@ -31,7 +31,7 @@ export const DIALOG_STYLES = `
3131
-webkit-font-smoothing: antialiased;
3232
background: var(--color-background-100);
3333
background-clip: padding-box;
34-
border: 1px solid var(--color-gray-400);
34+
border: var(--next-dialog-border-width) solid var(--color-gray-400);
3535
border-radius: var(--rounded-xl);
3636
box-shadow: var(--shadow-menu);
3737
position: relative;

Diff for: packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import type { ErrorBaseProps } from '../error-overlay/error-overlay'
3535
import type { ReadyRuntimeError } from '../../../../utils/get-error-by-type'
3636
import { EnvironmentNameLabel } from '../environment-name-label/environment-name-label'
3737
import { useFocusTrap } from '../dev-tools-indicator/utils'
38+
import { Fader } from '../../fader'
3839

3940
interface ErrorOverlayLayoutProps extends ErrorBaseProps {
4041
errorMessage: ErrorMessageType
@@ -82,17 +83,26 @@ export function ErrorOverlayLayout({
8283
} as React.CSSProperties,
8384
}
8485

86+
const faderRef = React.useRef<HTMLDivElement | null>(null)
8587
const hasFooter = Boolean(footerMessage || errorCode)
8688
const dialogRef = React.useRef<HTMLDivElement | null>(null)
8789
useFocusTrap(dialogRef, null, rendered)
8890

91+
function onScroll(e: React.UIEvent<HTMLDivElement>) {
92+
if (faderRef.current) {
93+
const opacity = clamp(e.currentTarget.scrollTop / 17, [0, 1])
94+
faderRef.current.style.opacity = String(opacity)
95+
}
96+
}
97+
8998
return (
9099
<ErrorOverlayOverlay fixed={isBuildError} {...animationProps}>
91100
<div data-nextjs-dialog-root ref={dialogRef} {...animationProps}>
92101
<ErrorOverlayDialog
93102
onClose={onClose}
94103
dialogResizerRef={dialogResizerRef}
95104
data-has-footer={hasFooter}
105+
onScroll={onScroll}
96106
>
97107
<DialogContent>
98108
<ErrorOverlayDialogHeader>
@@ -136,11 +146,16 @@ export function ErrorOverlayLayout({
136146
versionInfo={versionInfo}
137147
isTurbopack={isTurbopack}
138148
/>
149+
<Fader ref={faderRef} side="top" stop="50%" blur="4px" height={48} />
139150
</div>
140151
</ErrorOverlayOverlay>
141152
)
142153
}
143154

155+
function clamp(value: number, [min, max]: [number, number]) {
156+
return Math.min(Math.max(value, min), max)
157+
}
158+
144159
export const styles = `
145160
${OVERLAY_STYLES}
146161
${DIALOG_STYLES}

Diff for: packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-nav/error-overlay-nav.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export function ErrorOverlayNav({
4343

4444
export const styles = `
4545
[data-nextjs-error-overlay-nav] {
46-
--notch-height: 42px;
4746
display: flex;
4847
justify-content: space-between;
4948
align-items: center;
@@ -60,7 +59,7 @@ export const styles = `
6059
6160
translate: -1px 0;
6261
width: auto;
63-
height: var(--notch-height);
62+
height: var(--next-dialog-notch-height);
6463
padding: 12px;
6564
background: var(--background-color);
6665
border: 1px solid var(--stroke-color);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { CSSProperties } from 'react'
2+
3+
export function Fader({
4+
stop,
5+
blur,
6+
side,
7+
style,
8+
height,
9+
ref,
10+
}: {
11+
stop?: string
12+
blur?: string
13+
height?: number
14+
side: 'top' | 'bottom' | 'left' | 'right'
15+
className?: string
16+
style?: CSSProperties
17+
ref?: React.Ref<HTMLDivElement>
18+
}) {
19+
return (
20+
<div
21+
ref={ref}
22+
aria-hidden
23+
data-nextjs-scroll-fader
24+
className="nextjs-scroll-fader"
25+
data-side={side}
26+
style={
27+
{
28+
'--stop': stop,
29+
'--blur': blur,
30+
'--height': `${height}px`,
31+
...style,
32+
} as React.CSSProperties
33+
}
34+
/>
35+
)
36+
}
37+
38+
export const FADER_STYLES = `
39+
.nextjs-scroll-fader {
40+
--blur: 1px;
41+
--stop: 25%;
42+
--height: 150px;
43+
--color-bg: var(--color-background-100);
44+
position: absolute;
45+
pointer-events: none;
46+
user-select: none;
47+
width: 100%;
48+
height: var(--height);
49+
left: 0;
50+
backdrop-filter: blur(var(--blur));
51+
52+
&[data-side="top"] {
53+
top: 0;
54+
background: linear-gradient(to top, transparent, var(--color-bg));
55+
mask-image: linear-gradient(to bottom, var(--color-bg) var(--stop), transparent);
56+
}
57+
}
58+
59+
`

Diff for: packages/next/src/client/components/react-dev-overlay/ui/styles/component-styles.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { DEV_TOOLS_INFO_STYLES } from '../components/errors/dev-tools-indicator/
2121
import { DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/turbopack-info'
2222
import { DEV_TOOLS_INFO_ROUTE_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/route-info'
2323
import { DEV_TOOLS_INFO_USER_PREFERENCES_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/user-preferences'
24+
import { FADER_STYLES } from '../components/fader'
2425

2526
export function ComponentStyles() {
2627
return (
@@ -48,6 +49,7 @@ export function ComponentStyles() {
4849
${DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES}
4950
${DEV_TOOLS_INFO_ROUTE_INFO_STYLES}
5051
${DEV_TOOLS_INFO_USER_PREFERENCES_STYLES}
52+
${FADER_STYLES}
5153
`}
5254
</style>
5355
)

0 commit comments

Comments
 (0)