Merge branch 'jeremyosih-feat/scroll-to-bottom-button' into dev

This commit is contained in:
Jay V 2025-06-27 19:16:46 -04:00
commit 688f3fd12f
2 changed files with 113 additions and 0 deletions

View file

@ -39,6 +39,7 @@ import {
IconMagnifyingGlass,
IconWrenchScrewdriver,
IconDocumentMagnifyingGlass,
IconArrowDown,
} from "./icons"
import DiffView from "./DiffView"
import CodeBlock from "./CodeBlock"
@ -594,12 +595,17 @@ export default function Share(props: {
info: Session.Info
messages: Record<string, Message.Info>
}) {
let lastScrollY = 0
let hasScrolled = false
let scrollTimeout: number | undefined
const id = props.id
const params = new URLSearchParams(window.location.search)
const debug = params.get("debug") === "true"
const [showScrollButton, setShowScrollButton] = createSignal(false)
const [isButtonHovered, setIsButtonHovered] = createSignal(false)
const anchorId = createMemo<string | null>(() => {
const raw = window.location.hash.slice(1)
const [id] = raw.split("-")
@ -715,6 +721,54 @@ export default function Share(props: {
})
})
function checkScrollNeed() {
const currentScrollY = window.scrollY
const isScrollingDown = currentScrollY > lastScrollY
const scrolled = currentScrollY > 200 // Show after scrolling 200px
const isNearBottom = window.innerHeight + currentScrollY >= document.body.scrollHeight - 100
// Only show when scrolling down, scrolled enough, and not near bottom
const shouldShow = isScrollingDown && scrolled && !isNearBottom
// Update last scroll position
lastScrollY = currentScrollY
if (shouldShow) {
setShowScrollButton(true)
// Clear existing timeout
if (scrollTimeout) {
clearTimeout(scrollTimeout)
}
// Hide button after 3 seconds of no scrolling (unless hovered)
scrollTimeout = window.setTimeout(() => {
if (!isButtonHovered()) {
setShowScrollButton(false)
}
}, 3000)
} else if (!isButtonHovered()) {
// Only hide if not hovered (to prevent disappearing while user is about to click)
setShowScrollButton(false)
if (scrollTimeout) {
clearTimeout(scrollTimeout)
}
}
}
onMount(() => {
lastScrollY = window.scrollY // Initialize scroll position
checkScrollNeed()
window.addEventListener("scroll", checkScrollNeed)
window.addEventListener("resize", checkScrollNeed)
})
onCleanup(() => {
window.removeEventListener("scroll", checkScrollNeed)
window.removeEventListener("resize", checkScrollNeed)
if (scrollTimeout) {
clearTimeout(scrollTimeout)
}
})
const data = createMemo(() => {
const result = {
rootDir: undefined as string | undefined,
@ -825,6 +879,7 @@ export default function Share(props: {
</span>
)}
</div>
</div>
</div>
@ -1902,6 +1957,36 @@ export default function Share(props: {
</div>
</div>
</Show>
<Show when={showScrollButton()}>
<button
type="button"
class={styles["scroll-button"]}
onClick={() =>
document.body.scrollIntoView({ behavior: "smooth", block: "end" })
}
onMouseEnter={() => {
setIsButtonHovered(true)
if (scrollTimeout) {
clearTimeout(scrollTimeout)
}
}}
onMouseLeave={() => {
setIsButtonHovered(false)
if (showScrollButton()) {
scrollTimeout = window.setTimeout(() => {
if (!isButtonHovered()) {
setShowScrollButton(false)
}
}, 3000)
}
}}
title="Scroll to bottom"
aria-label="Scroll to bottom"
>
<IconArrowDown width={20} height={20} />
</button>
</Show>
</main>
)
}

View file

@ -784,3 +784,31 @@
}
}
}
.scroll-button {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.25rem;
border: 1px solid var(--sl-color-divider);
background-color: var(--sl-color-bg-surface);
color: var(--sl-color-text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease, opacity 0.5s ease;
z-index: 100;
appearance: none;
opacity: 1;
&:active {
transform: translateY(1px);
}
svg {
display: block;
}
}