diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index af920c0d615..966bbef7324 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -29,6 +29,7 @@ import { type KeyboardEvent, type MouseEvent, type ReactNode, + type CSSProperties, } from "react"; import { LegendList, type LegendListRef } from "@legendapp/list/react"; import { FileDiff } from "@pierre/diffs/react"; @@ -450,6 +451,12 @@ export const MessagesTimeline = memo(function MessagesTimeline({ ); return config ? { ...config, onReady: handleAnchorReady } : undefined; }, [anchorMessageId, handleAnchorReady, rows]); + const timelineViewportStyle = + contentInsetEndAdjustment === 0 + ? undefined + : ({ + "--timeline-composer-overlay-height": `min(${contentInsetEndAdjustment}px, 60%)`, + } as CSSProperties); const handleScroll = useCallback(() => { const state = listRef.current?.getState?.(); @@ -581,7 +588,11 @@ export const MessagesTimeline = memo(function MessagesTimeline({ return ( -
+
ref={listRef} data={rows} @@ -601,7 +612,10 @@ export const MessagesTimeline = memo(function MessagesTimeline({ onScroll={handleScroll} className={cn( "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", - topFadeEnabled && "topbar-scroll-fade", + contentInsetEndAdjustment > 0 && "virtualized-scroll-fade", + contentInsetEndAdjustment > 0 && + (topFadeEnabled ? "timeline-scroll-combined-fade" : "timeline-scroll-bottom-fade"), + contentInsetEndAdjustment === 0 && topFadeEnabled && "topbar-scroll-fade", )} ListHeaderComponent={ loadEarlier !== null ? ( diff --git a/apps/web/src/index.css b/apps/web/src/index.css index f69adb9cf08..1573e7abdcc 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -415,6 +415,48 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil var(--app-scrollbar-width) 100%; } +@utility timeline-scroll-bottom-fade { + --virtualized-scroll-fade-mask: linear-gradient( + to bottom, + black 0, + black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), + transparent calc(100% - var(--timeline-composer-overlay-height)), + transparent 100% + ); +} + +@utility timeline-scroll-combined-fade { + --virtualized-scroll-fade-mask: linear-gradient( + to bottom, + transparent 0, + rgb(0 0 0 / 10%) 0.25rem, + rgb(0 0 0 / 30%) 0.6rem, + rgb(0 0 0 / 58%) 1.05rem, + rgb(0 0 0 / 82%) 1.55rem, + rgb(0 0 0 / 96%) 2.05rem, + black 2.5rem, + black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), + transparent calc(100% - var(--timeline-composer-overlay-height)), + transparent 100% + ); + + @variant sm { + --virtualized-scroll-fade-mask: linear-gradient( + to bottom, + transparent 0, + rgb(0 0 0 / 10%) 0.3rem, + rgb(0 0 0 / 30%) 0.72rem, + rgb(0 0 0 / 58%) 1.26rem, + rgb(0 0 0 / 82%) 1.86rem, + rgb(0 0 0 / 96%) 2.46rem, + black 3rem, + black calc(100% - var(--timeline-composer-overlay-height) - 1.5rem), + transparent calc(100% - var(--timeline-composer-overlay-height)), + transparent 100% + ); + } +} + /* Stage-channel art needs a mask and pseudo-element gradient, so keep the behavior composable without tying it to the global components layer. */ @utility sidebar-stage-backdrop {