Преглед изворни кода

feat(app): visual improvements (#36073)

Aarav Sareen пре 1 месец
родитељ
комит
c769380776

+ 7 - 8
packages/app/src/components/prompt-input.tsx

@@ -36,6 +36,8 @@ import { Icon } from "@opencode-ai/ui/icon"
 import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
 import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
 import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
+import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
+import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
 import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
 import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
 import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
@@ -1704,22 +1706,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   >
                     <MenuV2 gutter={6} modal={false} placement="top-start">
                       <MenuV2.Trigger
-                        as={IconButton}
+                        as={IconButtonV2}
                         data-action="prompt-attach"
                         type="button"
-                        icon="plus"
-                        variant="ghost"
-                        class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted"
+                        icon={<IconV2 name="plus" />}
+                        variant="ghost-muted"
+                        size="large"
                         style={buttons()}
                         disabled={store.mode !== "normal"}
                         tabIndex={store.mode === "normal" ? undefined : -1}
                         aria-label={language.t("prompt.menu.addImagesAndFiles")}
                       />
                       <MenuV2.Portal>
-                        <MenuV2.Content
-                          class="[&_[data-slot=menu-v2-item-shortcut]]:w-5 [&_[data-slot=menu-v2-item-shortcut]]:justify-center"
-                          style={{ "min-width": "180px" }}
-                        >
+                        <MenuV2.Content style={{ "min-width": "180px" }}>
                           <MenuV2.Item onSelect={pick} shortcut={command.keybind("file.attach")}>
                             {language.t("prompt.menu.imagesAndFiles")}
                           </MenuV2.Item>

+ 11 - 1
packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx

@@ -211,7 +211,17 @@ export function SortableTerminalTabV2(props: {
           <MenuV2.Context.Trigger class="relative" as="div">
             <Tabs.Trigger
               value={props.terminal.id}
-              onClick={focus}
+              onMouseDown={(e) => {
+                // Switch on mousedown to shave the press-release delay off tab switches.
+                if (e.button !== 0) return
+                if (store.editing) return
+                focus()
+              }}
+              onClick={(e) => {
+                // Mouse navigation already happened on mousedown; detail 0 means keyboard activation.
+                if (e.detail > 0) return
+                focus()
+              }}
               closeButton={
                 <IconButton
                   icon="close-small"

+ 6 - 0
packages/app/src/components/settings-v2/settings-v2.css

@@ -20,6 +20,11 @@
   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 {
@@ -181,6 +186,7 @@
   flex-direction: column;
   gap: 8px;
   padding: 4px 0 4px 4px;
+  user-select: none;
 }
 
 .settings-v2-nav-footer > span {

+ 19 - 2
packages/app/src/components/terminal.tsx

@@ -1,7 +1,8 @@
 import { withAlpha } from "@opencode-ai/ui/theme/color"
 import { useTheme } from "@opencode-ai/ui/theme/context"
 import { resolveThemeVariant } from "@opencode-ai/ui/theme/resolve"
-import type { HexColor } from "@opencode-ai/ui/theme/types"
+import { resolveThemeVariantV2 } from "@opencode-ai/ui/theme/v2/resolve"
+import type { HexColor, ResolvedV2Theme } from "@opencode-ai/ui/theme/types"
 import { showToast } from "@/utils/toast"
 import type { FitAddon, Ghostty, Terminal as Term } from "ghostty-web"
 import { type ComponentProps, createEffect, createMemo, onCleanup, onMount, splitProps } from "solid-js"
@@ -68,6 +69,19 @@ const debugTerminal = (...values: unknown[]) => {
   console.debug("[terminal]", ...values)
 }
 
+const resolveV2Token = (tokens: ResolvedV2Theme, key: string) => {
+  let current = tokens[key]
+  for (let i = 0; i < 8 && current; i++) {
+    const match = /^var\(--([^)]+)\)$/.exec(current.trim())
+    if (!match) {
+      const hex = current.trim()
+      if (/^#[0-9a-fA-F]{8}$/.test(hex)) return hex.slice(0, 7)
+      return hex
+    }
+    current = tokens[match[1]]
+  }
+}
+
 const useTerminalUiBindings = (input: {
   container: HTMLDivElement
   term: Term
@@ -238,7 +252,10 @@ export const Terminal = (props: TerminalProps) => {
     if (!variant?.seeds && !variant?.palette) return fallback
     const resolved = resolveThemeVariant(variant, mode === "dark")
     const text = resolved["text-stronger"] ?? fallback.foreground
-    const background = resolved["background-stronger"] ?? fallback.background
+    const background = settings.general.newLayoutDesigns()
+      ? (resolveV2Token(resolveThemeVariantV2(variant, mode === "dark"), "v2-background-bg-base") ??
+        fallback.background)
+      : (resolved["background-stronger"] ?? fallback.background)
     const alpha = mode === "dark" ? 0.25 : 0.2
     const base = text.startsWith("#") ? (text as HexColor) : (fallback.foreground as HexColor)
     const selectionBackground = withAlpha(base, alpha)

+ 1 - 1
packages/app/src/pages/session.tsx

@@ -1304,7 +1304,7 @@ export default function Page() {
   const reviewPanelV2Rendered = createMemo<boolean>((prev) => prev || !store.deferRender, false)
 
   const reviewPanelV2 = () => (
-    <div class="flex flex-col h-full overflow-hidden bg-background-stronger contain-strict">
+    <div class="flex flex-col h-full overflow-hidden bg-v2-background-bg-base contain-strict">
       <Show when={reviewPanelV2Rendered()}>
         <ReviewPanelV2 {...reviewPanelV2Props()} />
       </Show>

+ 24 - 4
packages/app/src/pages/session/session-side-panel.tsx

@@ -255,8 +255,10 @@ export function SessionSidePanel(props: {
         aria-label={language.t("session.panel.reviewAndFiles")}
         aria-hidden={!open()}
         inert={!open()}
-        class="relative min-w-0 flex overflow-hidden bg-background-base"
+        class="relative min-w-0 flex overflow-hidden"
         classList={{
+          "bg-v2-background-bg-base": settings.general.newLayoutDesigns(),
+          "bg-background-base": !settings.general.newLayoutDesigns(),
           "h-full shrink-0": !props.stacked,
           "h-full min-h-0": props.stacked,
           "pointer-events-none": !open(),
@@ -275,8 +277,20 @@ export function SessionSidePanel(props: {
             }}
           >
             <Show when={reviewOpen()}>
-              <div class="relative min-w-0 h-full flex-1 overflow-hidden bg-background-base">
-                <div class="size-full min-w-0 h-full bg-background-base">
+              <div
+                class="relative min-w-0 h-full flex-1 overflow-hidden"
+                classList={{
+                  "bg-v2-background-bg-base": settings.general.newLayoutDesigns(),
+                  "bg-background-base": !settings.general.newLayoutDesigns(),
+                }}
+              >
+                <div
+                  class="size-full min-w-0 h-full"
+                  classList={{
+                    "bg-v2-background-bg-base": settings.general.newLayoutDesigns(),
+                    "bg-background-base": !settings.general.newLayoutDesigns(),
+                  }}
+                >
                   <DragDropProvider
                     onDragStart={handleDragStart}
                     onDragEnd={handleDragEnd}
@@ -379,7 +393,13 @@ export function SessionSidePanel(props: {
                               )}
                             </For>
                           </SortableProvider>
-                          <div class="bg-background-stronger h-full shrink-0 sticky right-0 z-10 flex items-center justify-center pr-3">
+                          <div
+                            class="h-full shrink-0 sticky right-0 z-10 flex items-center justify-center pr-3"
+                            classList={{
+                              "bg-v2-background-bg-base": settings.general.newLayoutDesigns(),
+                              "bg-background-stronger": !settings.general.newLayoutDesigns(),
+                            }}
+                          >
                             <TooltipKeybind
                               title={language.t("command.file.open")}
                               keybind={command.keybind("file.open")}

+ 2 - 2
packages/app/src/pages/session/terminal-panel-v2.tsx

@@ -195,7 +195,7 @@ export function TerminalPanelV2(props: { stacked?: boolean } = {}) {
       aria-label={language.t("terminal.title")}
       aria-hidden={!opened()}
       inert={!opened()}
-      class="relative shrink-0 overflow-hidden bg-background-stronger"
+      class="relative shrink-0 overflow-hidden bg-v2-background-bg-base"
       classList={{
         "w-full": !isDesktop() || stacked(),
         "min-w-0 h-full flex-1": isDesktop() && opened() && !stacked(),
@@ -237,7 +237,7 @@ export function TerminalPanelV2(props: { stacked?: boolean } = {}) {
           when={terminal.ready()}
           fallback={
             <div class="flex flex-col h-full pointer-events-none">
-              <div class="h-10 flex items-center gap-2 px-2 border-b border-border-weaker-base bg-background-stronger overflow-hidden">
+              <div class="h-10 flex items-center gap-2 px-2 border-b border-border-weaker-base bg-v2-background-bg-base overflow-hidden">
                 <For each={handoff()}>
                   {(title) => (
                     <div class="px-2 py-1 rounded-md bg-surface-base text-14-regular text-text-weak truncate max-w-40">

+ 1 - 1
packages/app/src/pages/session/timeline/message-timeline.tsx

@@ -1386,7 +1386,7 @@ export function MessageTimeline(props: {
               "w-full": true,
               "pb-4": true,
               "pr-3": true,
-              "pl-2": settings.general.newLayoutDesigns(),
+              "pl-2.5": settings.general.newLayoutDesigns(),
               "pl-2 md:pl-4": !settings.general.newLayoutDesigns(),
               "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered && !settings.general.newLayoutDesigns(),
             }}

+ 2 - 2
packages/session-ui/src/v2/components/session-review-v2.css

@@ -4,7 +4,7 @@
   height: 100%;
   min-height: 0;
   overflow: hidden;
-  background: var(--background-stronger, var(--v2-background-bg-base));
+  background: var(--v2-background-bg-base);
 }
 
 [data-component="session-review-v2"] [data-slot="session-review-v2-body"] {
@@ -31,7 +31,7 @@
   min-height: 0;
   overflow: hidden;
   border-right: 1px solid var(--border-weaker-base, var(--v2-border-border-weak));
-  background: var(--background-stronger, var(--v2-background-bg-base));
+  background: var(--v2-background-bg-base);
 }
 
 [data-component="session-review-v2-sidebar-root"] [data-slot="session-review-v2-sidebar"][aria-hidden="true"] {

+ 19 - 0
packages/ui/src/components/tabs.css

@@ -679,3 +679,22 @@
 [data-component="tabs-drag-preview"] > * {
   position: relative;
 }
+
+body[data-new-layout] #review-panel [data-component="tabs"],
+body[data-new-layout] #terminal-panel [data-component="tabs"],
+body[data-new-layout] #review-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"],
+body[data-new-layout] #terminal-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"] {
+  background-color: var(--v2-background-bg-base);
+
+  [data-slot="tabs-list"] {
+    background-color: var(--v2-background-bg-base);
+
+    > .sticky {
+      background-color: var(--v2-background-bg-base);
+
+      &::before {
+        background: linear-gradient(90deg, transparent, var(--v2-background-bg-base));
+      }
+    }
+  }
+}

+ 8 - 4
packages/ui/src/v2/components/icon-button-v2.css

@@ -112,8 +112,8 @@
   color: var(--v2-text-text-base);
 }
 
-[data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"], [data-expanded]):not(
-    :disabled
+[data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not(
+    [data-expanded]
   ) {
   background-color: var(--v2-overlay-simple-overlay-hover);
 }
@@ -122,6 +122,10 @@
   background-color: var(--v2-overlay-simple-overlay-pressed);
 }
 
+[data-component="icon-button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) {
+  background-color: var(--v2-overlay-simple-overlay-pressed);
+}
+
 [data-component="icon-button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) {
   opacity: 0.5;
   cursor: not-allowed;
@@ -133,8 +137,8 @@
   color: var(--v2-icon-icon-muted);
 }
 
-[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"], [data-expanded]):not(
-    :disabled
+[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not(
+    [data-expanded]
   ) {
   background-color: var(--v2-overlay-simple-overlay-hover);
 }

+ 1 - 0
packages/ui/src/v2/components/line-comment-v2.css

@@ -6,6 +6,7 @@
 
 [data-component="line-comment-v2"] {
   box-sizing: border-box;
+  font-family: var(--v2-font-family-sans);
   font-variant-numeric: tabular-nums;
   min-width: 0;
   width: 100%;

+ 1 - 0
packages/ui/src/v2/components/select-v2.css

@@ -65,6 +65,7 @@
   box-shadow: var(--v2-elevation-button-neutral);
   flex: none;
   align-self: stretch;
+  user-select: none;
   transition:
     background 85ms ease-out,
     outline-color 85ms ease-out,

+ 1 - 0
packages/ui/src/v2/components/tabs-v2.css

@@ -192,6 +192,7 @@
   color: var(--v2-text-text-muted);
   font-size: 12px;
   font-weight: 500;
+  user-select: none;
 }
 
 [data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-trigger-wrapper"] {