From 8cb65f96364305972157a8c32b928a1247a3a2eb Mon Sep 17 00:00:00 2001 From: Jay <53023+jayair@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:02:05 +0000 Subject: [PATCH] fix(app): preserve provider dialog backdrop --- .../components/dialog-connect-provider.tsx | 143 ++++---- .../src/components/dialog-custom-provider.tsx | 315 +++++++++--------- .../src/components/dialog-select-provider.tsx | 163 +++++---- 3 files changed, 349 insertions(+), 272 deletions(-) diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 3a57769b5a68..886aa7ffa3ce 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -17,7 +17,13 @@ import { useServerSync } from "@/context/server-sync" import { useLanguage } from "@/context/language" import { useProviders } from "@/hooks/use-providers" -export function DialogConnectProvider(props: { provider: string; directory?: Accessor }) { +export function DialogConnectProvider(props: { + provider: string + directory?: Accessor + onBack?: () => void + content?: boolean + bind?: (value: { back: () => void }) => void +}) { const dialog = useDialog() const serverSync = useServerSync() const serverSDK = useServerSDK() @@ -25,6 +31,10 @@ export function DialogConnectProvider(props: { provider: string; directory?: Acc const providers = useProviders(props.directory) const all = () => { + if (props.onBack) { + props.onBack() + return + } void import("./dialog-select-provider").then((x) => { dialog.show(() => ) }) @@ -598,6 +608,74 @@ export function DialogConnectProvider(props: { provider: string; directory?: Acc ) } + const content = ( +
+
+ +
+ + + {language.t("provider.connect.title.anthropicProMax")} + + {language.t("provider.connect.title", { provider: provider().name })} + +
+
+
+
+ + +
+
+ + {language.t("provider.connect.status.inProgress")} +
+
+
+ + + + +
+
+ + {language.t("provider.connect.status.inProgress")} +
+
+
+ + + + +
+
+ + {language.t("provider.connect.status.failed", { error: store.error ?? "" })} +
+
+
+ + + + + + + + + + + + + +
+
+
+
+ ) + + props.bind?.({ back: goBack }) + if (props.content) return content + return ( } > -
-
- -
- - - {language.t("provider.connect.title.anthropicProMax")} - - {language.t("provider.connect.title", { provider: provider().name })} - -
-
-
-
- - -
-
- - {language.t("provider.connect.status.inProgress")} -
-
-
- - - - -
-
- - {language.t("provider.connect.status.inProgress")} -
-
-
- - - - -
-
- - {language.t("provider.connect.status.failed", { error: store.error ?? "" })} -
-
-
- - - - - - - - - - - - - -
-
-
-
+ {content}
) } diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index 647e5002a297..2afec1460a6c 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -16,8 +16,10 @@ import { type FormState, headerRow, modelRow, validateCustomProvider } from "./d import { DialogSelectProvider } from "./dialog-select-provider" type Props = { - back?: "providers" | "close" + back?: "providers" | "close" | (() => void) directory?: Accessor + content?: boolean + bind?: (value: { back: () => void }) => void } export function DialogCustomProvider(props: Props) { @@ -37,6 +39,10 @@ export function DialogCustomProvider(props: Props) { }) const goBack = () => { + if (typeof props.back === "function") { + props.back() + return + } if (props.back === "close") { dialog.close() return @@ -162,6 +168,162 @@ export function DialogCustomProvider(props: Props) { saveMutation.mutate(result) } + const content = ( +
+
+ +
{language.t("provider.custom.title")}
+
+ +
+

+ {language.t("provider.custom.description.prefix")} + + {language.t("provider.custom.description.link")} + + {language.t("provider.custom.description.suffix")} +

+ +
+ setField("providerID", v)} + validationState={form.err.providerID ? "invalid" : undefined} + error={form.err.providerID} + /> + setField("name", v)} + validationState={form.err.name ? "invalid" : undefined} + error={form.err.name} + /> + setField("baseURL", v)} + validationState={form.err.baseURL ? "invalid" : undefined} + error={form.err.baseURL} + /> + setField("apiKey", v)} + /> +
+ +
+ + + {(m, i) => ( +
+
+ setModel(i(), "id", v)} + validationState={m.err.id ? "invalid" : undefined} + error={m.err.id} + /> +
+
+ setModel(i(), "name", v)} + validationState={m.err.name ? "invalid" : undefined} + error={m.err.name} + /> +
+ removeModel(i())} + disabled={form.models.length <= 1} + aria-label={language.t("provider.custom.models.remove")} + /> +
+ )} +
+ +
+ +
+ + + {(h, i) => ( +
+
+ setHeader(i(), "key", v)} + validationState={h.err.key ? "invalid" : undefined} + error={h.err.key} + /> +
+
+ setHeader(i(), "value", v)} + validationState={h.err.value ? "invalid" : undefined} + error={h.err.value} + /> +
+ removeHeader(i())} + disabled={form.headers.length <= 1} + aria-label={language.t("provider.custom.headers.remove")} + /> +
+ )} +
+ +
+ + +
+
+ ) + + props.bind?.({ back: goBack }) + if (props.content) return content + return ( -
-
- -
{language.t("provider.custom.title")}
-
- -
-

- {language.t("provider.custom.description.prefix")} - - {language.t("provider.custom.description.link")} - - {language.t("provider.custom.description.suffix")} -

- -
- setField("providerID", v)} - validationState={form.err.providerID ? "invalid" : undefined} - error={form.err.providerID} - /> - setField("name", v)} - validationState={form.err.name ? "invalid" : undefined} - error={form.err.name} - /> - setField("baseURL", v)} - validationState={form.err.baseURL ? "invalid" : undefined} - error={form.err.baseURL} - /> - setField("apiKey", v)} - /> -
- -
- - - {(m, i) => ( -
-
- setModel(i(), "id", v)} - validationState={m.err.id ? "invalid" : undefined} - error={m.err.id} - /> -
-
- setModel(i(), "name", v)} - validationState={m.err.name ? "invalid" : undefined} - error={m.err.name} - /> -
- removeModel(i())} - disabled={form.models.length <= 1} - aria-label={language.t("provider.custom.models.remove")} - /> -
- )} -
- -
- -
- - - {(h, i) => ( -
-
- setHeader(i(), "key", v)} - validationState={h.err.key ? "invalid" : undefined} - error={h.err.key} - /> -
-
- setHeader(i(), "value", v)} - validationState={h.err.value ? "invalid" : undefined} - error={h.err.value} - /> -
- removeHeader(i())} - disabled={form.headers.length <= 1} - aria-label={language.t("provider.custom.headers.remove")} - /> -
- )} -
- -
- - -
-
+ {content}
) } diff --git a/packages/app/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx index ff0ab5d2bf0c..022827854818 100644 --- a/packages/app/src/components/dialog-select-provider.tsx +++ b/packages/app/src/components/dialog-select-provider.tsx @@ -1,7 +1,8 @@ -import { type Accessor, Component, Show } from "solid-js" -import { useDialog } from "@opencode-ai/ui/context/dialog" +import { type Accessor, Component, Match, Show, Switch } from "solid-js" +import { createStore } from "solid-js/store" import { popularProviders, useProviders } from "@/hooks/use-providers" import { Dialog } from "@opencode-ai/ui/dialog" +import { IconButton } from "@opencode-ai/ui/icon-button" import { List } from "@opencode-ai/ui/list" import { Tag } from "@opencode-ai/ui/tag" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" @@ -12,9 +13,15 @@ import { DialogCustomProvider } from "./dialog-custom-provider" const CUSTOM_ID = "_custom" export const DialogSelectProvider: Component<{ directory?: Accessor }> = (props) => { - const dialog = useDialog() const providers = useProviders(props.directory) const language = useLanguage() + const [store, setStore] = createStore({ selected: undefined as string | undefined }) + const navigation = { back: showPicker } + + function showPicker() { + navigation.back = showPicker + setStore("selected", undefined) + } const popularGroup = () => language.t("dialog.provider.group.popular") const otherGroup = () => language.t("dialog.provider.group.other") @@ -27,61 +34,101 @@ export const DialogSelectProvider: Component<{ directory?: Accessor - x?.id} - items={() => { - language.locale() - return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()] - }} - filterKeys={["id", "name"]} - groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())} - sortBy={(a, b) => { - if (a.id === CUSTOM_ID) return -1 - if (b.id === CUSTOM_ID) return 1 - if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) - return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) - return a.name.localeCompare(b.name) - }} - sortGroupsBy={(a, b) => { - const popular = popularGroup() - if (a.category === popular && b.category !== popular) return -1 - if (b.category === popular && a.category !== popular) return 1 - return 0 - }} - onSelect={(x) => { - if (!x) return - if (x.id === CUSTOM_ID) { - dialog.show(() => ) - return - } - dialog.show(() => ) - }} - > - {(i) => ( -
- - {i.name} - -
{language.t("dialog.provider.opencode.tagline")}
-
- - {language.t("settings.providers.tag.custom")} - - - {language.t("dialog.provider.tag.recommended")} - - {(value) =>
{value()}
}
- - {language.t("dialog.provider.tag.recommended")} - -
- )} -
+ + + navigation.back()} + aria-label={language.t("common.goBack")} + /> + + {language.t("command.provider.connect")} + + } + > + + + { + navigation.back = value.back + }} + /> + + + {(provider) => ( + { + navigation.back = value.back + }} + /> + )} + + + x?.id} + items={() => { + language.locale() + return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()] + }} + filterKeys={["id", "name"]} + groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())} + sortBy={(a, b) => { + if (a.id === CUSTOM_ID) return -1 + if (b.id === CUSTOM_ID) return 1 + if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) + return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) + return a.name.localeCompare(b.name) + }} + sortGroupsBy={(a, b) => { + const popular = popularGroup() + if (a.category === popular && b.category !== popular) return -1 + if (b.category === popular && a.category !== popular) return 1 + return 0 + }} + onSelect={(x) => { + if (!x) return + setStore("selected", x.id) + }} + > + {(i) => ( +
+ + {i.name} + +
{language.t("dialog.provider.opencode.tagline")}
+
+ + {language.t("settings.providers.tag.custom")} + + + {language.t("dialog.provider.tag.recommended")} + + {(value) =>
{value()}
}
+ + {language.t("dialog.provider.tag.recommended")} + +
+ )} +
+
+
) }