From 611eca6f4f283fbc1c1a30d5476fe4d1f3267b23 Mon Sep 17 00:00:00 2001 From: Jaap Haitsma Date: Sun, 5 Jul 2026 21:04:46 -0400 Subject: [PATCH] fix(app): reveal scrollbar in settings dialog panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings dialog panels scrolled but hid their scrollbar entirely, so there was no indication content continued below the fold (e.g. the Appearance / Color scheme setting) and no thumb to drag. Route the General, Keybinds, Providers and Models panels — in both the current and new-layout dialogs — through the shared ScrollView so a thumb reveals on hover. Also make ScrollView defer to a descendant that already handled a key (e.defaultPrevented) so it no longer scrolls the panel when a focused dropdown is opened with the arrow keys. --- packages/app/src/components/settings-general.tsx | 5 +++-- packages/app/src/components/settings-keybinds.tsx | 10 ++++++---- packages/app/src/components/settings-models.tsx | 5 +++-- packages/app/src/components/settings-providers.tsx | 5 +++-- packages/app/src/components/settings-scroll.tsx | 10 ++++++++++ packages/app/src/components/settings-v2/general.tsx | 5 +++-- packages/app/src/components/settings-v2/models.tsx | 5 +++-- packages/app/src/components/settings-v2/providers.tsx | 5 +++-- packages/app/src/components/settings-v2/servers.tsx | 5 +++-- packages/ui/src/components/scroll-view.tsx | 3 +++ 10 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 packages/app/src/components/settings-scroll.tsx diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index cbbf33d2b135..32f2d5e3618f 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -30,6 +30,7 @@ import { decode64 } from "@/utils/base64" import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" import { SettingsList } from "./settings-list" +import { SettingsScroll } from "./settings-scroll" let demoSoundState = { cleanup: undefined as (() => void) | undefined, @@ -720,7 +721,7 @@ export const SettingsGeneral: Component = () => { ) return ( -
+

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

@@ -744,7 +745,7 @@ export const SettingsGeneral: Component = () => {
-
+
) } diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index 98f6c9ffa04c..7976c774da53 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -10,7 +10,9 @@ import fuzzysort from "fuzzysort" import { 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" const ButtonV2 = lazy(() => import("@opencode-ai/ui/v2/button-v2").then((module) => ({ default: module.ButtonV2 }))) const IconV2 = lazy(() => import("@opencode-ai/ui/v2/icon").then((module) => ({ default: module.Icon }))) @@ -486,7 +488,7 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => { +
@@ -517,10 +519,10 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
{groups} -
+
} > - <> +

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

@@ -554,7 +556,7 @@ 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 24e7a60104ae..4cbc5a42bfd5 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -12,6 +12,7 @@ import { DialogConnectProvider } from "./dialog-connect-provider" import { DialogSelectProvider } from "./dialog-select-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" @@ -138,7 +139,7 @@ const SettingsProvidersContent: Component = () => { } return ( -
+

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

@@ -258,6 +259,6 @@ const SettingsProvidersContent: Component = () => {
-
+ ) } 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 0dd48a5523cc..a9a36184a73f 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 { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useLanguage } from "@/context/language" @@ -687,7 +688,7 @@ export const SettingsGeneralV2: Component<{ ) return ( - <> +

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

@@ -709,6 +710,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 a3f058670e4a..6ac544b23024 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 { useLanguage } from "@/context/language" import { useModels } from "@/context/models" @@ -43,7 +44,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 cd24bbd4558c..3984afc62918 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 Component, For, Show } from "solid-js" @@ -132,7 +133,7 @@ export const SettingsProvidersV2: Component = () => { } return ( - <> +

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

@@ -260,6 +261,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/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index 0a567ce1c50f..3a12602bf4c6 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -208,6 +208,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