diff options
| author | Jay V <[email protected]> | 2025-06-27 19:16:46 -0400 |
|---|---|---|
| committer | Jay V <[email protected]> | 2025-06-27 19:16:46 -0400 |
| commit | 688f3fd12f56e1fde152435a7464ffaf18473c67 (patch) | |
| tree | c7c78c87b0ea6f86a47b977461cbfffe25e2372d | |
| parent | c0773dc7c53cf15e9c8d63b4e49aa7527c9a1328 (diff) | |
| parent | 145df084440470bb53655f62b5f5588e2615f1ba (diff) | |
| download | opencode-688f3fd12f56e1fde152435a7464ffaf18473c67.tar.gz opencode-688f3fd12f56e1fde152435a7464ffaf18473c67.zip | |
Merge branch 'jeremyosih-feat/scroll-to-bottom-button' into dev
| -rw-r--r-- | packages/web/src/components/Share.tsx | 85 | ||||
| -rw-r--r-- | packages/web/src/components/share.module.css | 28 |
2 files changed, 113 insertions, 0 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index d11276153..008f863c9 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -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> ) } diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css index ae45d3768..dafbdd8a2 100644 --- a/packages/web/src/components/share.module.css +++ b/packages/web/src/components/share.module.css @@ -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; + } +} |
