From e845eedbc325b05a19679bc439a57cc0fbf23aa3 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:28:34 -0600 Subject: wip(desktop): progress --- packages/ui/src/components/list.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'packages/ui/src/components/list.css') diff --git a/packages/ui/src/components/list.css b/packages/ui/src/components/list.css index 38dcb773b..783b0ef4a 100644 --- a/packages/ui/src/components/list.css +++ b/packages/ui/src/components/list.css @@ -98,17 +98,13 @@ display: block; } [data-slot="list-item-extra-icon"] { + display: block !important; color: var(--icon-strong-base) !important; } } &:active { background: var(--surface-raised-base-active); } - &:hover { - [data-slot="list-item-extra-icon"] { - color: var(--icon-strong-base) !important; - } - } } } } -- cgit v1.2.3 From 16b7370d8cac304eb2af800b9c6a584784a0c600 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:02:57 -0600 Subject: wip(desktop): progress --- packages/desktop/src/components/prompt-input.tsx | 10 +- packages/desktop/src/pages/layout.tsx | 108 +++++++++++++------- packages/ui/src/components/dialog.css | 11 ++ packages/ui/src/components/icon.tsx | 2 + packages/ui/src/components/input.css | 99 ------------------ packages/ui/src/components/input.tsx | 75 -------------- packages/ui/src/components/list.css | 3 + packages/ui/src/components/select-dialog.tsx | 4 +- packages/ui/src/components/text-field.css | 125 +++++++++++++++++++++++ packages/ui/src/components/text-field.tsx | 103 +++++++++++++++++++ packages/ui/src/styles/index.css | 2 +- 11 files changed, 328 insertions(+), 214 deletions(-) delete mode 100644 packages/ui/src/components/input.css delete mode 100644 packages/ui/src/components/input.tsx create mode 100644 packages/ui/src/components/text-field.css create mode 100644 packages/ui/src/components/text-field.tsx (limited to 'packages/ui/src/components/list.css') diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index 7f8568291..2c153ecc3 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -33,7 +33,6 @@ import { popularProviders, useProviders } from "@/hooks/use-providers" import { Dialog } from "@opencode-ai/ui/dialog" import { List, ListRef } from "@opencode-ai/ui/list" import { iife } from "@opencode-ai/util/iife" -import { Input } from "@opencode-ai/ui/input" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { IconName } from "@opencode-ai/ui/icons/provider" @@ -557,6 +556,14 @@ export const PromptInput: Component = (props) => { if (e.key === "Escape") return listRef?.onKeyDown(e) } + + onMount(() => { + document.addEventListener("keydown", handleKey) + onCleanup(() => { + document.removeEventListener("keydown", handleKey) + }) + }) + return ( = (props) => { -
Free models provided by OpenCode
{ if (methods().length === 1) { selectMethod(0) } + + document.addEventListener("keydown", handleKey) + onCleanup(() => { + document.removeEventListener("keydown", handleKey) + }) }) - let listRef: ListRef | undefined - function handleKey(e: KeyboardEvent) { - if (e.key === "Escape") return - listRef?.onKeyDown(e) + async function complete() { + await globalSDK.client.global.dispose() + setTimeout(() => { + showToast({ + variant: "success", + icon: "circle-check", + title: `${provider().name} connected`, + description: `${provider().name} models are now available to use.`, + }) + layout.connect.complete() + }, 500) } return ( @@ -753,7 +774,6 @@ export default function Layout(props: ParentProps) {
Select login method for {provider().name}.
- (listRef = ref)} items={methods} @@ -820,16 +840,7 @@ export default function Layout(props: ParentProps) { key: apiKey, }, }) - await globalSDK.client.global.dispose() - setTimeout(() => { - showToast({ - variant: "success", - icon: "circle-check", - title: `${provider().name} connected`, - description: `${provider().name} models are now available to use.`, - }) - layout.connect.complete() - }, 500) + await complete() } return ( @@ -862,7 +873,7 @@ export default function Layout(props: ParentProps) {
- { - showToast({ - variant: "success", - icon: "circle-check", - title: `${provider().name} connected`, - description: `${provider().name} models are now available to use.`, - }) - layout.connect.complete() - }, 500) + await complete() return } setFormStore("error", "Invalid authorization code") @@ -938,7 +940,7 @@ export default function Layout(props: ParentProps) { OpenCode.
- -
-
- Visit this link and enter the code below - to connect your account and use {provider().name} models in OpenCode. -
-
+ {iife(() => { + const code = createMemo(() => { + const instructions = store.authorization?.instructions + if (instructions?.includes(":")) { + return instructions?.split(":")[1]?.trim() + } + return instructions + }) + + onMount(async () => { + const result = await globalSDK.client.provider.oauth.callback({ + providerID: providerID(), + method: methodIndex(), + }) + if (result.error) { + // TODO: show error + layout.dialog.close("connect") + return + } + await complete() + }) + + return ( +
+
+ Visit this link and enter the code + below to connect your account and use {provider().name} models in OpenCode. +
+ +
+ + Waiting for authorization... +
+
+ ) + })}
diff --git a/packages/ui/src/components/dialog.css b/packages/ui/src/components/dialog.css index 1c7cd4f41..979906e26 100644 --- a/packages/ui/src/components/dialog.css +++ b/packages/ui/src/components/dialog.css @@ -88,8 +88,19 @@ flex-direction: column; flex: 1; overflow-y: auto; + + &:focus-visible { + outline: none; + } + } + &:focus-visible { + outline: none; } } + + &:focus-visible { + outline: none; + } } } diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 56cef2d8c..ce4bf7556 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -47,6 +47,8 @@ const icons = { "layout-bottom-full": ``, "dot-grid": ``, "circle-check": ``, + copy: ``, + check: ``, } export interface IconProps extends ComponentProps<"svg"> { diff --git a/packages/ui/src/components/input.css b/packages/ui/src/components/input.css deleted file mode 100644 index 276e8069b..000000000 --- a/packages/ui/src/components/input.css +++ /dev/null @@ -1,99 +0,0 @@ -[data-component="input"] { - width: 100%; - - [data-slot="input-input"] { - width: 100%; - color: var(--text-strong); - - /* text-14-regular */ - font-family: var(--font-family-sans); - font-size: 14px; - font-style: normal; - font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 142.857% */ - letter-spacing: var(--letter-spacing-normal); - - &:focus { - outline: none; - } - - &::placeholder { - color: var(--text-weak); - } - } - - &[data-variant="normal"] { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - - [data-slot="input-label"] { - color: var(--text-weak); - - /* text-12-medium */ - font-family: var(--font-family-sans); - font-size: var(--font-size-small); - font-style: normal; - font-weight: var(--font-weight-medium); - line-height: 18px; /* 150% */ - letter-spacing: var(--letter-spacing-normal); - } - - [data-slot="input-input"] { - color: var(--text-strong); - - display: flex; - height: 32px; - padding: 2px 12px; - align-items: center; - gap: 8px; - align-self: stretch; - - border-radius: var(--radius-md); - border: 1px solid var(--border-weak-base); - background: var(--input-base); - - /* text-14-regular */ - font-family: var(--font-family-sans); - font-size: 14px; - font-style: normal; - font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 142.857% */ - letter-spacing: var(--letter-spacing-normal); - - &:focus { - outline: none; - - /* border/shadow-xs/select */ - box-shadow: - 0 0 0 3px var(--border-weak-selected), - 0 0 0 1px var(--border-selected), - 0 1px 2px -1px rgba(19, 16, 16, 0.25), - 0 1px 2px 0 rgba(19, 16, 16, 0.08), - 0 1px 3px 0 rgba(19, 16, 16, 0.12); - } - - &[data-invalid] { - background: var(--surface-critical-weak); - border: 1px solid var(--border-critical-selected); - } - - &::placeholder { - color: var(--text-weak); - } - } - - [data-slot="input-error"] { - color: var(--text-on-critical-base); - - /* text-12-medium */ - font-family: var(--font-family-sans); - font-size: var(--font-size-small); - font-style: normal; - font-weight: var(--font-weight-medium); - line-height: 18px; /* 150% */ - letter-spacing: var(--letter-spacing-normal); - } - } -} diff --git a/packages/ui/src/components/input.tsx b/packages/ui/src/components/input.tsx deleted file mode 100644 index 8e2a115c6..000000000 --- a/packages/ui/src/components/input.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { TextField as Kobalte } from "@kobalte/core/text-field" -import { Show, splitProps } from "solid-js" -import type { ComponentProps } from "solid-js" - -export interface InputProps - extends ComponentProps, - Partial< - Pick< - ComponentProps, - | "name" - | "defaultValue" - | "value" - | "onChange" - | "onKeyDown" - | "validationState" - | "required" - | "disabled" - | "readOnly" - > - > { - label?: string - hideLabel?: boolean - hidden?: boolean - description?: string - error?: string - variant?: "normal" | "ghost" -} - -export function Input(props: InputProps) { - const [local, others] = splitProps(props, [ - "name", - "defaultValue", - "value", - "onChange", - "onKeyDown", - "validationState", - "required", - "disabled", - "readOnly", - "class", - "label", - "hidden", - "hideLabel", - "description", - "error", - "variant", - ]) - return ( - - - - {local.label} - - - - - {local.description} - - {local.error} - - ) -} diff --git a/packages/ui/src/components/list.css b/packages/ui/src/components/list.css index 783b0ef4a..132824164 100644 --- a/packages/ui/src/components/list.css +++ b/packages/ui/src/components/list.css @@ -105,6 +105,9 @@ &:active { background: var(--surface-raised-base-active); } + &:focus-visible { + outline: none; + } } } } diff --git a/packages/ui/src/components/select-dialog.tsx b/packages/ui/src/components/select-dialog.tsx index efa6c405b..06953168c 100644 --- a/packages/ui/src/components/select-dialog.tsx +++ b/packages/ui/src/components/select-dialog.tsx @@ -3,7 +3,7 @@ import { Dialog, DialogProps } from "./dialog" import { Icon } from "./icon" import { IconButton } from "./icon-button" import { List, ListRef, ListProps } from "./list" -import { Input } from "./input" +import { TextField } from "./text-field" interface SelectDialogProps extends Omit, "filter">, @@ -55,7 +55,7 @@ export function SelectDialog(props: SelectDialogProps) {
- , + Partial< + Pick< + ComponentProps, + | "name" + | "defaultValue" + | "value" + | "onChange" + | "onKeyDown" + | "validationState" + | "required" + | "disabled" + | "readOnly" + > + > { + label?: string + hideLabel?: boolean + description?: string + error?: string + variant?: "normal" | "ghost" + copyable?: boolean +} + +export function TextField(props: TextFieldProps) { + const [local, others] = splitProps(props, [ + "name", + "defaultValue", + "value", + "onChange", + "onKeyDown", + "validationState", + "required", + "disabled", + "readOnly", + "class", + "label", + "hideLabel", + "description", + "error", + "variant", + "copyable", + ]) + const [copied, setCopied] = createSignal(false) + + async function handleCopy() { + const value = local.value ?? local.defaultValue ?? "" + await navigator.clipboard.writeText(value) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( + + + + {local.label} + + +
+ + + + + + +
+ + {local.description} + + {local.error} +
+ ) +} + +/** @deprecated Use TextField instead */ +export const Input = TextField +/** @deprecated Use TextFieldProps instead */ +export type InputProps = TextFieldProps diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 918ba9e44..d60082d93 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -21,7 +21,7 @@ @import "../components/provider-icon.css" layer(components); @import "../components/icon.css" layer(components); @import "../components/icon-button.css" layer(components); -@import "../components/input.css" layer(components); +@import "../components/text-field.css" layer(components); @import "../components/list.css" layer(components); @import "../components/logo.css" layer(components); @import "../components/markdown.css" layer(components); -- cgit v1.2.3