From 3e997b630ad67017d075c7cd305b7638038d86a4 Mon Sep 17 00:00:00 2001 From: rauno Date: Mon, 17 Mar 2025 18:25:21 +0200 Subject: [PATCH 1/4] [dev-overlay] Blur fader for scrollable container --- .../ui/components/dialog/dialog.tsx | 2 +- .../ui/components/dialog/styles.ts | 17 ++++- .../ui/components/errors/dialog/dialog.tsx | 2 +- .../error-overlay-layout.tsx | 15 +++++ .../error-overlay-nav/error-overlay-nav.tsx | 3 +- .../ui/components/fader/index.tsx | 59 +++++++++++++++++ .../react-dev-overlay/ui/container/errors.tsx | 64 ++++++++++--------- .../ui/styles/component-styles.tsx | 2 + 8 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 packages/next/src/client/components/react-dev-overlay/ui/components/fader/index.tsx diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/dialog.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/dialog.tsx index a33ddc46e1047..fa329e7965003 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/dialog.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/dialog.tsx @@ -10,7 +10,7 @@ export type DialogProps = { className?: string onClose?: () => void dialogResizerRef?: React.RefObject -} +} & React.HTMLAttributes const CSS_SELECTORS_TO_EXCLUDE_ON_CLICK_OUTSIDE = [ '[data-next-mark]', diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/styles.ts b/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/styles.ts index 3d0e90a2de208..252a08f8fb59a 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/styles.ts +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/dialog/styles.ts @@ -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; @@ -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); @@ -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 */ diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx index 89274a9a66b0d..fdab667de630d 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx @@ -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; diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx index 8210848acf2e8..6b5b8404eb65b 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx @@ -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 @@ -82,10 +83,18 @@ export function ErrorOverlayLayout({ } as React.CSSProperties, } + const faderRef = React.useRef(null) const hasFooter = Boolean(footerMessage || errorCode) const dialogRef = React.useRef(null) useFocusTrap(dialogRef, null, rendered) + function onScroll(e: React.UIEvent) { + if (faderRef.current) { + const opacity = clamp(e.currentTarget.scrollTop / 17, [0, 1]) + faderRef.current.style.opacity = String(opacity) + } + } + return (
@@ -93,6 +102,7 @@ export function ErrorOverlayLayout({ onClose={onClose} dialogResizerRef={dialogResizerRef} data-has-footer={hasFooter} + onScroll={onScroll} > @@ -136,11 +146,16 @@ export function ErrorOverlayLayout({ versionInfo={versionInfo} isTurbopack={isTurbopack} /> +
) } +function clamp(value: number, [min, max]: [number, number]) { + return Math.min(Math.max(value, min), max) +} + export const styles = ` ${OVERLAY_STYLES} ${DIALOG_STYLES} diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-nav/error-overlay-nav.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-nav/error-overlay-nav.tsx index 26e5ec4bc13b2..d340ea788d782 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-nav/error-overlay-nav.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-nav/error-overlay-nav.tsx @@ -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; @@ -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); diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/fader/index.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/fader/index.tsx new file mode 100644 index 0000000000000..16c1a308efc02 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/fader/index.tsx @@ -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 +}) { + return ( +
+ ) +} + +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); + } + } + +` diff --git a/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx b/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx index 199a92b971c5d..533e334712c27 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx @@ -165,41 +165,43 @@ export function Errors({ dialogResizerRef={dialogResizerRef} {...props} > -
- {notes ? ( - <> + +
+ {notes ? ( + <> +

+ {notes} +

+ + ) : null} + {hydrationWarning ? (

- {notes} +

- - ) : null} - {hydrationWarning ? ( - - ) : null} -
+ ) : null} +
- {hydrationWarning && - (activeError.componentStackFrames?.length || - !!errorDetails.reactOutputComponentDiff) ? ( - - ) : null} - }> + {hydrationWarning && + (activeError.componentStackFrames?.length || + !!errorDetails.reactOutputComponentDiff) ? ( + + ) : null} ) From ad6965e55acf1a5ae9a12894215f50941ff60d91 Mon Sep 17 00:00:00 2001 From: rauno Date: Mon, 17 Mar 2025 18:29:30 +0200 Subject: [PATCH 2/4] Update dialog.tsx --- .../react-dev-overlay/ui/components/errors/dialog/dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx index fdab667de630d..8f5841a385963 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/dialog/dialog.tsx @@ -4,7 +4,7 @@ type ErrorOverlayDialogProps = { children?: React.ReactNode onClose?: () => void dialogResizerRef?: React.RefObject -} +} & React.HTMLAttributes export function ErrorOverlayDialog({ children, From bc6def05fd2feacde901a52aa66d05c46df80733 Mon Sep 17 00:00:00 2001 From: rauno Date: Mon, 17 Mar 2025 18:31:07 +0200 Subject: [PATCH 3/4] Update error-overlay-layout.tsx --- .../errors/error-overlay-layout/error-overlay-layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx index 6b5b8404eb65b..cea7bea87bb42 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/components/errors/error-overlay-layout/error-overlay-layout.tsx @@ -146,7 +146,7 @@ export function ErrorOverlayLayout({ versionInfo={versionInfo} isTurbopack={isTurbopack} /> - +
) From 40a9474cb7531edcc0058d8afca4cf25d786a97c Mon Sep 17 00:00:00 2001 From: rauno Date: Mon, 17 Mar 2025 18:47:40 +0200 Subject: [PATCH 4/4] Update errors.tsx --- .../react-dev-overlay/ui/container/errors.tsx | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx b/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx index 533e334712c27..199a92b971c5d 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx @@ -165,43 +165,41 @@ export function Errors({ dialogResizerRef={dialogResizerRef} {...props} > - -
- {notes ? ( - <> -

- {notes} -

- - ) : null} - {hydrationWarning ? ( +
+ {notes ? ( + <> - ) : null} -
- - {hydrationWarning && - (activeError.componentStackFrames?.length || - !!errorDetails.reactOutputComponentDiff) ? ( - + + ) : null} + {hydrationWarning ? ( + ) : null} +
+ + {hydrationWarning && + (activeError.componentStackFrames?.length || + !!errorDetails.reactOutputComponentDiff) ? ( + + ) : null} + }>