Skip to content

[dev-overlay] Blur fader for scrollable container #77196

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

Merged
merged 5 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type DialogProps = {
className?: string
onClose?: () => void
dialogResizerRef?: React.RefObject<HTMLDivElement | null>
}
} & React.HTMLAttributes<HTMLDivElement>

const CSS_SELECTORS_TO_EXCLUDE_ON_CLICK_OUTSIDE = [
'[data-next-mark]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ const styles = `
--next-dialog-footer-height: var(--size-48);
--next-dialog-max-width: 960px;
--next-dialog-row-padding: 16px;
--next-dialog-container-padding: 12px;
--next-dialog-padding-x: 12px;
--next-dialog-notch-height: 42px;
--next-dialog-border-width: 1px;

display: flex;
flex-direction: column-reverse;
Expand All @@ -23,24 +25,35 @@ const styles = `
opacity: 1;
scale: 1;
}

[data-nextjs-scroll-fader][data-side="top"] {
left: 1px;
top: calc(var(--next-dialog-notch-height) + var(--next-dialog-border-width));
width: calc(100% - var(--next-dialog-padding-x));
opacity: 0;
}
}

[data-nextjs-dialog] {
outline: none;
overflow: hidden;
}

[data-nextjs-dialog]::-webkit-scrollbar {
width: 6px;
border-radius: 0 0 1rem 1rem;
margin-bottom: 1rem;
}

[data-nextjs-dialog]::-webkit-scrollbar-button {
display: none;
}

[data-nextjs-dialog]::-webkit-scrollbar-track {
border-radius: 0 0 1rem 1rem;
background-color: var(--color-background-100);
}

[data-nextjs-dialog]::-webkit-scrollbar-thumb {
border-radius: 1rem;
background-color: var(--color-gray-500);
Expand Down Expand Up @@ -71,7 +84,7 @@ const styles = `
display: flex;
flex-direction: column;
position: relative;
padding: 16px 12px;
padding: 16px var(--next-dialog-padding-x);
}

/* Account for the footer height, when present */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type ErrorOverlayDialogProps = {
children?: React.ReactNode
onClose?: () => void
dialogResizerRef?: React.RefObject<HTMLDivElement | null>
}
} & React.HTMLAttributes<HTMLDivElement>

export function ErrorOverlayDialog({
children,
Expand All @@ -31,7 +31,7 @@ export const DIALOG_STYLES = `
-webkit-font-smoothing: antialiased;
background: var(--color-background-100);
background-clip: padding-box;
border: 1px solid var(--color-gray-400);
border: var(--next-dialog-border-width) solid var(--color-gray-400);
border-radius: var(--rounded-xl);
box-shadow: var(--shadow-menu);
position: relative;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type { ErrorBaseProps } from '../error-overlay/error-overlay'
import type { ReadyRuntimeError } from '../../../../utils/get-error-by-type'
import { EnvironmentNameLabel } from '../environment-name-label/environment-name-label'
import { useFocusTrap } from '../dev-tools-indicator/utils'
import { Fader } from '../../fader'

interface ErrorOverlayLayoutProps extends ErrorBaseProps {
errorMessage: ErrorMessageType
Expand Down Expand Up @@ -82,17 +83,26 @@ export function ErrorOverlayLayout({
} as React.CSSProperties,
}

const faderRef = React.useRef<HTMLDivElement | null>(null)
const hasFooter = Boolean(footerMessage || errorCode)
const dialogRef = React.useRef<HTMLDivElement | null>(null)
useFocusTrap(dialogRef, null, rendered)

function onScroll(e: React.UIEvent<HTMLDivElement>) {
if (faderRef.current) {
const opacity = clamp(e.currentTarget.scrollTop / 17, [0, 1])
faderRef.current.style.opacity = String(opacity)
}
}

return (
<ErrorOverlayOverlay fixed={isBuildError} {...animationProps}>
<div data-nextjs-dialog-root ref={dialogRef} {...animationProps}>
<ErrorOverlayDialog
onClose={onClose}
dialogResizerRef={dialogResizerRef}
data-has-footer={hasFooter}
onScroll={onScroll}
>
<DialogContent>
<ErrorOverlayDialogHeader>
Expand Down Expand Up @@ -136,11 +146,16 @@ export function ErrorOverlayLayout({
versionInfo={versionInfo}
isTurbopack={isTurbopack}
/>
<Fader ref={faderRef} side="top" stop="50%" blur="4px" height={48} />
</div>
</ErrorOverlayOverlay>
)
}

function clamp(value: number, [min, max]: [number, number]) {
return Math.min(Math.max(value, min), max)
}

export const styles = `
${OVERLAY_STYLES}
${DIALOG_STYLES}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export function ErrorOverlayNav({

export const styles = `
[data-nextjs-error-overlay-nav] {
--notch-height: 42px;
display: flex;
justify-content: space-between;
align-items: center;
Expand All @@ -60,7 +59,7 @@ export const styles = `

translate: -1px 0;
width: auto;
height: var(--notch-height);
height: var(--next-dialog-notch-height);
padding: 12px;
background: var(--background-color);
border: 1px solid var(--stroke-color);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { CSSProperties } from 'react'

export function Fader({
stop,
blur,
side,
style,
height,
ref,
}: {
stop?: string
blur?: string
height?: number
side: 'top' | 'bottom' | 'left' | 'right'
className?: string
style?: CSSProperties
ref?: React.Ref<HTMLDivElement>
}) {
return (
<div
ref={ref}
aria-hidden
data-nextjs-scroll-fader
className="nextjs-scroll-fader"
data-side={side}
style={
{
'--stop': stop,
'--blur': blur,
'--height': `${height}px`,
...style,
} as React.CSSProperties
}
/>
)
}

export const FADER_STYLES = `
.nextjs-scroll-fader {
--blur: 1px;
--stop: 25%;
--height: 150px;
--color-bg: var(--color-background-100);
position: absolute;
pointer-events: none;
user-select: none;
width: 100%;
height: var(--height);
left: 0;
backdrop-filter: blur(var(--blur));

&[data-side="top"] {
top: 0;
background: linear-gradient(to top, transparent, var(--color-bg));
mask-image: linear-gradient(to bottom, var(--color-bg) var(--stop), transparent);
}
}

`
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { DEV_TOOLS_INFO_STYLES } from '../components/errors/dev-tools-indicator/
import { DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/turbopack-info'
import { DEV_TOOLS_INFO_ROUTE_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/route-info'
import { DEV_TOOLS_INFO_USER_PREFERENCES_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/user-preferences'
import { FADER_STYLES } from '../components/fader'

export function ComponentStyles() {
return (
Expand Down Expand Up @@ -48,6 +49,7 @@ export function ComponentStyles() {
${DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES}
${DEV_TOOLS_INFO_ROUTE_INFO_STYLES}
${DEV_TOOLS_INFO_USER_PREFERENCES_STYLES}
${FADER_STYLES}
`}
</style>
)
Expand Down
Loading