diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx index d6c5e9918670..06b37d054e08 100644 --- a/packages/app/src/components/dialog-manage-models.tsx +++ b/packages/app/src/components/dialog-manage-models.tsx @@ -195,7 +195,7 @@ export const DialogManageModelsV2: Component = () => {
-
+
void) | undefined, @@ -740,7 +741,7 @@ export const SettingsGeneral: Component = () => { ) return ( -
+

{language.t("settings.tab.general")}

@@ -772,7 +773,7 @@ export const SettingsGeneral: Component = () => {
-
+
) } diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index ce74be45864f..d0147f2674d9 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -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 }))) @@ -448,7 +450,7 @@ function SettingsKeybindsV2View(props: { const hasResults = createMemo(() => props.groups.some((group) => (filtered().get(group)?.length ?? 0) > 0)) return ( - <> +

{language.t("settings.shortcuts.title")}

@@ -525,7 +527,7 @@ function SettingsKeybindsV2View(props: {
- +
) } @@ -745,7 +747,7 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => { ) return ( -
+
@@ -776,6 +778,6 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
{groups} -
+
) } diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index f3d9e1522ff4..41f2a9ca7d0e 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -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["list"]>[number] @@ -67,7 +68,7 @@ const SettingsModelsContent: Component = () => { }) return ( -
+
@@ -144,6 +145,6 @@ const SettingsModelsContent: Component = () => {
-
+ ) } diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index 080e6c517bfe..c3867a744fd0 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -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" @@ -146,7 +147,7 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) => } return ( -
+

{language.t("settings.providers.title")}

@@ -259,6 +260,6 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) =>
-
+ ) } diff --git a/packages/app/src/components/settings-scroll.tsx b/packages/app/src/components/settings-scroll.tsx new file mode 100644 index 000000000000..17a9a243a4ed --- /dev/null +++ b/packages/app/src/components/settings-scroll.tsx @@ -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 ( + +
{props.children}
+
+ ) +} diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index b41da107ed6e..110f11fea900 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -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" @@ -536,7 +537,7 @@ export const SettingsGeneralV2: Component<{ ) return ( - <> +

{language.t("settings.tab.general")}

@@ -566,6 +567,6 @@ export const SettingsGeneralV2: Component<{
- + ) } diff --git a/packages/app/src/components/settings-v2/models.tsx b/packages/app/src/components/settings-v2/models.tsx index 482e8cbffbd4..8bdf70add036 100644 --- a/packages/app/src/components/settings-v2/models.tsx +++ b/packages/app/src/components/settings-v2/models.tsx @@ -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" @@ -51,7 +52,7 @@ export const SettingsModelsV2: Component = () => { }) return ( - <> +

{language.t("settings.models.title")}

- + ) } diff --git a/packages/app/src/components/settings-v2/providers.tsx b/packages/app/src/components/settings-v2/providers.tsx index acd73ddbe544..a02645f6f640 100644 --- a/packages/app/src/components/settings-v2/providers.tsx +++ b/packages/app/src/components/settings-v2/providers.tsx @@ -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" @@ -143,7 +144,7 @@ export const SettingsProvidersV2: Component<{ } return ( - <> +

{language.t("settings.providers.title")}

@@ -262,6 +263,6 @@ export const SettingsProvidersV2: Component<{
- + ) } diff --git a/packages/app/src/components/settings-v2/servers.tsx b/packages/app/src/components/settings-v2/servers.tsx index b93f700d24cf..98fded32630b 100644 --- a/packages/app/src/components/settings-v2/servers.tsx +++ b/packages/app/src/components/settings-v2/servers.tsx @@ -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" @@ -47,7 +48,7 @@ export const SettingsServersV2: Component = () => { } return ( - <> +
{
- +
) } diff --git a/packages/app/src/components/settings-v2/settings-v2.css b/packages/app/src/components/settings-v2/settings-v2.css index c4d47344a26c..e20fa9f10070 100644 --- a/packages/app/src/components/settings-v2/settings-v2.css +++ b/packages/app/src/components/settings-v2/settings-v2.css @@ -18,8 +18,6 @@ display: flex; flex-direction: column; height: 100%; - overflow-y: auto; - scrollbar-width: none; user-select: none; } @@ -27,7 +25,18 @@ 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; } diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index 5116d94f9ead..c815824f133b 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -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