Skip to content
2 changes: 1 addition & 1 deletion packages/app/src/components/dialog-manage-models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export const DialogManageModelsV2: Component = () => {
</div>
</div>
<div data-slot="manage-models-scroll" class="relative min-h-0 flex-1">
<div class="settings-v2-panel settings-v2-models h-full px-4 pt-4 pb-4">
<div class="settings-v2-panel settings-v2-panel--scroll settings-v2-models h-full px-4 pt-4 pb-4">
<Show
when={!list.grouped.loading}
fallback={
Expand Down
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { decode64 } from "@/utils/base64"
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
import { ExternalLink } from "./external-link"
import { SettingsList } from "./settings-list"
import { SettingsScroll } from "./settings-scroll"

let demoSoundState = {
cleanup: undefined as (() => void) | undefined,
Expand Down Expand Up @@ -740,7 +741,7 @@ export const SettingsGeneral: Component = () => {
)

return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<SettingsScroll>
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-1 pt-6 pb-8">
<h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
Expand Down Expand Up @@ -772,7 +773,7 @@ export const SettingsGeneral: Component = () => {
<AdvancedSection />
</Show>
</div>
</div>
</SettingsScroll>
)
}

Expand Down
10 changes: 6 additions & 4 deletions packages/app/src/components/settings-keybinds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import fuzzysort from "fuzzysort"
import { DEFAULT_PALETTE_KEYBIND, formatKeybind, parseKeybind, useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { SettingsList } from "./settings-list"
import { SettingsScroll } from "./settings-scroll"
import { SettingsListV2 } from "./settings-v2/parts/list"

const IconV2 = lazy(() => import("@opencode-ai/ui/v2/icon").then((module) => ({ default: module.Icon })))
Expand Down Expand Up @@ -448,7 +450,7 @@ function SettingsKeybindsV2View(props: {
const hasResults = createMemo(() => props.groups.some((group) => (filtered().get(group)?.length ?? 0) > 0))

return (
<>
<ScrollView class="flex-1 min-h-0">
<div class="settings-v2-tab-header settings-v2-tab-header--stacked">
<div class="settings-v2-tab-header-row">
<h2 class="settings-v2-tab-title">{language.t("settings.shortcuts.title")}</h2>
Expand Down Expand Up @@ -525,7 +527,7 @@ function SettingsKeybindsV2View(props: {
</Show>
</div>
</div>
</>
</ScrollView>
)
}

Expand Down Expand Up @@ -745,7 +747,7 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
)

return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<SettingsScroll>
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
<div class="flex items-center justify-between gap-4">
Expand Down Expand Up @@ -776,6 +778,6 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
</div>
</div>
{groups}
</div>
</SettingsScroll>
)
}
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useLanguage } from "@/context/language"
import { useModels } from "@/context/models"
import { popularProviders } from "@/hooks/use-providers"
import { SettingsList } from "./settings-list"
import { SettingsScroll } from "./settings-scroll"
import { SettingsServerPicker, SettingsServerScope } from "./settings-server-picker"

type ModelItem = ReturnType<ReturnType<typeof useModels>["list"]>[number]
Expand Down Expand Up @@ -67,7 +68,7 @@ const SettingsModelsContent: Component = () => {
})

return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<SettingsScroll>
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
<div class="flex items-center justify-between gap-4">
Expand Down Expand Up @@ -144,6 +145,6 @@ const SettingsModelsContent: Component = () => {
</Show>
</Show>
</div>
</div>
</SettingsScroll>
)
}
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useServerSync } from "@/context/server-sync"
import { DialogConnectProvider, useProviderConnectController } from "./dialog-connect-provider"
import { DialogCustomProvider } from "./dialog-custom-provider"
import { SettingsList } from "./settings-list"
import { SettingsScroll } from "./settings-scroll"
import { SettingsServerPicker, SettingsServerScope } from "./settings-server-picker"

type ProviderSource = "env" | "api" | "config" | "custom"
Expand Down Expand Up @@ -146,7 +147,7 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) =>
}

return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<SettingsScroll>
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex items-center justify-between gap-4 pt-6 pb-8 max-w-[720px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
Expand Down Expand Up @@ -259,6 +260,6 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) =>
</Button>
</div>
</div>
</div>
</SettingsScroll>
)
}
10 changes: 10 additions & 0 deletions packages/app/src/components/settings-scroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type Component, type JSX } from "solid-js"
import { ScrollView } from "@opencode-ai/ui/scroll-view"

export const SettingsScroll: Component<{ children: JSX.Element }> = (props) => {
return (
<ScrollView class="h-full">
<div class="flex flex-col px-4 pb-10 sm:px-10 sm:pb-10">{props.children}</div>
</ScrollView>
)
}
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-v2/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
Expand Down Expand Up @@ -536,7 +537,7 @@ export const SettingsGeneralV2: Component<{
)

return (
<>
<ScrollView class="flex-1 min-h-0">
<div class="settings-v2-tab-header">
<h2 class="settings-v2-tab-title">{language.t("settings.tab.general")}</h2>
</div>
Expand Down Expand Up @@ -566,6 +567,6 @@ export const SettingsGeneralV2: Component<{

<AdvancedSection />
</div>
</>
</ScrollView>
)
}
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-v2/models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Switch } from "@opencode-ai/ui/v2/switch-v2"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { type Component, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { useLanguage } from "@/context/language"
Expand Down Expand Up @@ -51,7 +52,7 @@ export const SettingsModelsV2: Component = () => {
})

return (
<>
<ScrollView class="flex-1 min-h-0">
<div class="settings-v2-tab-header settings-v2-tab-header--stacked">
<h2 class="settings-v2-tab-title">{language.t("settings.models.title")}</h2>
<div class="settings-v2-tab-search">
Expand Down Expand Up @@ -182,6 +183,6 @@ export const SettingsModelsV2: Component = () => {
</Show>
</Show>
</div>
</>
</ScrollView>
)
}
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-v2/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { showToast } from "@/utils/toast"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { createMemo, type Accessor, type Component, For, Show } from "solid-js"
Expand Down Expand Up @@ -143,7 +144,7 @@ export const SettingsProvidersV2: Component<{
}

return (
<>
<ScrollView class="flex-1 min-h-0">
<div class="settings-v2-tab-header">
<h2 class="settings-v2-tab-title">{language.t("settings.providers.title")}</h2>
</div>
Expand Down Expand Up @@ -262,6 +263,6 @@ export const SettingsProvidersV2: Component<{
</button>
</div>
</div>
</>
</ScrollView>
)
}
5 changes: 3 additions & 2 deletions packages/app/src/components/settings-v2/servers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Tag } from "@opencode-ai/ui/v2/badge-v2"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import fuzzysort from "fuzzysort"
import { type Component, For, Show, createMemo } from "solid-js"
Expand Down Expand Up @@ -47,7 +48,7 @@ export const SettingsServersV2: Component = () => {
}

return (
<>
<ScrollView class="flex-1 min-h-0">
<div
class="settings-v2-tab-header settings-v2-servers-header"
classList={{ "settings-v2-tab-header--stacked": showSearch() }}
Expand Down Expand Up @@ -134,6 +135,6 @@ export const SettingsServersV2: Component = () => {
</SettingsListV2>
</Show>
</div>
</>
</ScrollView>
)
}
15 changes: 12 additions & 3 deletions packages/app/src/components/settings-v2/settings-v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,25 @@
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
scrollbar-width: none;
user-select: none;
}

.settings-v2-panel :is(input, textarea, [contenteditable="true"]) {
user-select: text;
}

.settings-v2-panel::-webkit-scrollbar {
/*
* Panels that scroll themselves rather than delegating to a ScrollView child.
* The settings dialog tabs wrap their content in ScrollView so a thumb reveals
* on hover, and must not also scroll here — otherwise the thumb tracks an inner
* viewport while the panel scrolls behind it.
*/
.settings-v2-panel--scroll {
overflow-y: auto;
scrollbar-width: none;
}

.settings-v2-panel--scroll::-webkit-scrollbar {
display: none;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/components/scroll-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ export function ScrollView(props: ScrollViewProps) {
// We can also explicitly catch PageUp/Down if we want smooth scroll or specific behavior,
// but native usually handles this perfectly. Let's explicitly ensure it behaves well.
const onKeyDown = (e: KeyboardEvent) => {
// Defer to a descendant that already handled the key (e.g. an opening/open
// dropdown, listbox, or menu) — don't additionally scroll the viewport.
if (e.defaultPrevented) return
// If user is focused on an input inside the scroll view, don't hijack keys
if (document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) {
return
Expand Down
Loading