diff options
| author | Adam <[email protected]> | 2026-02-20 10:43:18 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-02-20 10:54:17 -0600 |
| commit | fe89bedfcc6d97fdd4b8066c2c3d8eac92b531ea (patch) | |
| tree | 99eccf57e17ceb90e9f8b28829a8a5f9ab2ecbcc /packages/app/src | |
| parent | 1e48d7fe8228d94ded379e36975b2cce12f4a510 (diff) | |
| download | opencode-fe89bedfcc6d97fdd4b8066c2c3d8eac92b531ea.tar.gz opencode-fe89bedfcc6d97fdd4b8066c2c3d8eac92b531ea.zip | |
wip(app): custom scroll view
Diffstat (limited to 'packages/app/src')
5 files changed, 59 insertions, 55 deletions
diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index 162e016c6..1ea97c395 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -11,6 +11,7 @@ import { Accordion } from "@opencode-ai/ui/accordion" import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header" import { Code } from "@opencode-ai/ui/code" import { Markdown } from "@opencode-ai/ui/markdown" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client" import { useLanguage } from "@/context/language" import { getSessionContextMetrics } from "./session-context-metrics" @@ -268,9 +269,9 @@ export function SessionContextTab() { }) return ( - <div - class="@container h-full overflow-y-auto no-scrollbar pb-10" - ref={(el) => { + <ScrollView + class="@container h-full pb-10" + viewportRef={(el) => { scroll = el restoreScroll() }} @@ -336,6 +337,6 @@ export function SessionContextTab() { </Accordion> </div> </div> - </div> + </ScrollView> ) } diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx index 1ccac937c..fd2ced3dc 100644 --- a/packages/app/src/pages/session/composer/session-question-dock.tsx +++ b/packages/app/src/pages/session/composer/session-question-dock.tsx @@ -62,7 +62,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit const measure = () => { if (!root) return - const scroller = document.querySelector(".session-scroller") + const scroller = document.querySelector(".scroll-view__viewport") const head = scroller instanceof HTMLElement ? scroller.firstElementChild : undefined const top = head instanceof HTMLElement && head.classList.contains("sticky") ? head.getBoundingClientRect().bottom : 0 @@ -95,7 +95,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit window.addEventListener("resize", update) const dock = root?.closest('[data-component="session-prompt-dock"]') - const scroller = document.querySelector(".session-scroller") + const scroller = document.querySelector(".scroll-view__viewport") const observer = new ResizeObserver(update) if (dock instanceof HTMLElement) observer.observe(dock) if (scroller instanceof HTMLElement) observer.observe(scroller) diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index ebc1f5922..032756cab 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -9,6 +9,7 @@ import { showToast } from "@opencode-ai/ui/toast" import { LineComment as LineCommentView, LineCommentEditor } from "@opencode-ai/ui/line-comment" import { Mark } from "@opencode-ai/ui/logo" import { Tabs } from "@opencode-ai/ui/tabs" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import { useLayout } from "@/context/layout" import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file" import { useComments } from "@/context/comments" @@ -509,51 +510,52 @@ export function FileTabContent(props: { tab: string }) { ) return ( - <Tabs.Content - value={props.tab} - class="mt-3 relative" - ref={(el: HTMLDivElement) => { - scroll = el - restoreScroll() - }} - onScroll={handleScroll} - > - <Switch> - <Match when={state()?.loaded && isImage()}> - <div class="px-6 py-4 pb-40"> - <img - src={imageDataUrl()} - alt={path()} - class="max-w-full" - onLoad={() => requestAnimationFrame(restoreScroll)} - /> - </div> - </Match> - <Match when={state()?.loaded && isSvg()}> - <div class="flex flex-col gap-4 px-6 py-4"> - {renderCode(svgContent() ?? "", "")} - <Show when={svgPreviewUrl()}> - <div class="flex justify-center pb-40"> - <img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" /> + <Tabs.Content value={props.tab} class="mt-3 relative h-full"> + <ScrollView + class="h-full" + viewportRef={(el: HTMLDivElement) => { + scroll = el + restoreScroll() + }} + onScroll={handleScroll as any} + > + <Switch> + <Match when={state()?.loaded && isImage()}> + <div class="px-6 py-4 pb-40"> + <img + src={imageDataUrl()} + alt={path()} + class="max-w-full" + onLoad={() => requestAnimationFrame(restoreScroll)} + /> + </div> + </Match> + <Match when={state()?.loaded && isSvg()}> + <div class="flex flex-col gap-4 px-6 py-4"> + {renderCode(svgContent() ?? "", "")} + <Show when={svgPreviewUrl()}> + <div class="flex justify-center pb-40"> + <img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" /> + </div> + </Show> + </div> + </Match> + <Match when={state()?.loaded && isBinary()}> + <div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6"> + <Mark class="w-14 opacity-10" /> + <div class="flex flex-col gap-2 max-w-md"> + <div class="text-14-semibold text-text-strong truncate">{path()?.split("/").pop()}</div> + <div class="text-14-regular text-text-weak">{language.t("session.files.binaryContent")}</div> </div> - </Show> - </div> - </Match> - <Match when={state()?.loaded && isBinary()}> - <div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6"> - <Mark class="w-14 opacity-10" /> - <div class="flex flex-col gap-2 max-w-md"> - <div class="text-14-semibold text-text-strong truncate">{path()?.split("/").pop()}</div> - <div class="text-14-regular text-text-weak">{language.t("session.files.binaryContent")}</div> </div> - </div> - </Match> - <Match when={state()?.loaded}>{renderCode(contents(), "pb-40")}</Match> - <Match when={state()?.loading}> - <div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div> - </Match> - <Match when={state()?.error}>{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}</Match> - </Switch> + </Match> + <Match when={state()?.loaded}>{renderCode(contents(), "pb-40")}</Match> + <Match when={state()?.loading}> + <div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div> + </Match> + <Match when={state()?.error}>{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}</Match> + </Switch> + </ScrollView> </Tabs.Content> ) } diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 6ac89a3a7..b13ccb474 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -8,6 +8,7 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Dialog } from "@opencode-ai/ui/dialog" import { InlineInput } from "@opencode-ai/ui/inline-input" import { SessionTurn } from "@opencode-ai/ui/session-turn" +import { ScrollView } from "@opencode-ai/ui/scroll-view" import type { UserMessage } from "@opencode-ai/sdk/v2" import { showToast } from "@opencode-ai/ui/toast" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" @@ -322,8 +323,8 @@ export function MessageTimeline(props: { <Icon name="arrow-down-to-line" /> </button> </div> - <div - ref={props.setScrollRef} + <ScrollView + viewportRef={props.setScrollRef} onWheel={(e) => { const root = e.currentTarget const delta = normalizeWheelDelta({ @@ -367,7 +368,7 @@ export function MessageTimeline(props: { if (props.isDesktop) props.onScrollSpyScroll() }} onClick={props.onAutoScrollInteraction} - class="relative min-w-0 w-full h-full overflow-y-auto session-scroller" + class="relative min-w-0 w-full h-full" style={{ "--session-title-height": showHeader() ? "40px" : "0px", "--sticky-accordion-top": showHeader() ? "48px" : "0px", @@ -548,7 +549,7 @@ export function MessageTimeline(props: { )} </For> </div> - </div> + </ScrollView> </div> </Show> ) diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx index 3a9f63949..9349e9937 100644 --- a/packages/app/src/pages/session/review-tab.tsx +++ b/packages/app/src/pages/session/review-tab.tsx @@ -143,9 +143,9 @@ export function SessionReviewTab(props: SessionReviewTabProps) { open={props.view().review.open()} onOpenChange={props.view().review.setOpen} classes={{ - root: props.classes?.root ?? "pb-6", + root: props.classes?.root ?? "pb-6 pr-3", header: props.classes?.header ?? "px-3", - container: props.classes?.container ?? "px-3", + container: props.classes?.container ?? "pl-3", }} diffs={props.diffs()} diffStyle={props.diffStyle} |
