Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,8 +13,10 @@ 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 { SettingsListV2 } from "./settings-v2/parts/list"
import { SettingsScroll } from "./settings-scroll"

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