Просмотр исходного кода

feat(app): align slash popover to v2 tokens (#34286)

Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Aarav Sareen 2 месяцев назад
Родитель
Сommit
7fac84319d

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

@@ -791,8 +791,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     }
   }
 
-  // Auto-scroll active command into view when navigating with keyboard
-  createEffect(() => {
+  const scrollSlashActiveIntoView = () => {
     const activeId = slashActive()
     if (!activeId || !slashPopoverRef) return
 
@@ -800,7 +799,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       const element = slashPopoverRef.querySelector(`[data-slash-id="${activeId}"]`)
       element?.scrollIntoView({ block: "nearest", behavior: "smooth" })
     })
-  })
+  }
   const selectPopoverActive = () => {
     if (store.popover === "at") {
       const items = atFlat()
@@ -1287,6 +1286,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         }
         if (store.popover === "slash") {
           slashOnKeyDown(event)
+          if (event.key === "ArrowUp" || event.key === "ArrowDown" || ctrlNav) {
+            scrollSlashActiveIntoView()
+          }
         }
         event.preventDefault()
         return
@@ -1406,6 +1408,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         setSlashActive={setSlashActive}
         onSlashSelect={handleSlashSelect}
         commandKeybind={command.keybind}
+        commandKeybindParts={command.keybindParts}
+        newLayoutDesigns={props.controls.newLayoutDesigns}
         t={(key) => language.t(key as Parameters<typeof language.t>[0])}
       />
       <Switch>

+ 167 - 45
packages/app/src/components/prompt-input/slash-popover.tsx

@@ -1,6 +1,8 @@
 import { Component, For, Match, Show, Switch } from "solid-js"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { Icon } from "@opencode-ai/ui/icon"
+import { Tag } from "@opencode-ai/ui/v2/badge-v2"
+import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
 import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
 
 export type AtOption =
@@ -30,6 +32,8 @@ type PromptPopoverProps = {
   setSlashActive: (id: string) => void
   onSlashSelect: (item: SlashCommand) => void
   commandKeybind: (id: string) => string | undefined
+  commandKeybindParts: (id: string) => string[]
+  newLayoutDesigns: boolean
   t: (key: string) => string
 }
 
@@ -41,15 +45,30 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
           if (props.popover === "slash") props.setSlashPopoverRef(el)
         }}
         class="absolute inset-x-0 -top-2 -translate-y-full origin-bottom-left max-h-80 min-h-10
-                 overflow-auto no-scrollbar flex flex-col p-2 rounded-[12px]
-                 bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]"
+                 overflow-auto no-scrollbar flex flex-col p-2"
+        classList={{
+          "z-[70] rounded-[10px] bg-v2-background-bg-base shadow-[var(--v2-elevation-raised)]":
+            props.newLayoutDesigns,
+          "rounded-[12px] bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]":
+            !props.newLayoutDesigns,
+        }}
         onMouseDown={(e) => e.preventDefault()}
       >
         <Switch>
           <Match when={props.popover === "at"}>
             <Show
               when={props.atFlat.length > 0}
-              fallback={<div class="text-text-weak px-2 py-1">{props.t("prompt.popover.emptyResults")}</div>}
+              fallback={
+                <div
+                  class="px-2 py-1"
+                  classList={{
+                    "text-v2-text-text-muted": props.newLayoutDesigns,
+                    "text-text-weak": !props.newLayoutDesigns,
+                  }}
+                >
+                  {props.t("prompt.popover.emptyResults")}
+                </div>
+              }
             >
               <For each={props.atFlat.slice(0, 10)}>
                 {(item) => {
@@ -58,13 +77,29 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
                   if (item.type === "agent") {
                     return (
                       <button
-                        class="w-full flex items-center gap-x-2 rounded-md px-2 py-0.5"
-                        classList={{ "bg-surface-raised-base-hover": props.atActive === key }}
+                        class="w-full flex items-center gap-x-2 px-2 py-0.5"
+                        classList={{
+                          "rounded-[4px]": props.newLayoutDesigns,
+                          "rounded-md": !props.newLayoutDesigns,
+                          "bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
+                          "bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
+                        }}
                         onClick={() => props.onAtSelect(item)}
-                        onMouseEnter={() => props.setAtActive(key)}
+                        onPointerMove={() => props.setAtActive(key)}
                       >
                         <Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
-                        <span class="text-14-regular text-text-strong whitespace-nowrap">@{item.name}</span>
+                        <span
+                          class="whitespace-nowrap"
+                          classList={{
+                            "text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
+                              props.newLayoutDesigns,
+                            "text-v2-text-text-base": props.newLayoutDesigns,
+                            "text-14-regular": !props.newLayoutDesigns,
+                            "text-text-strong": !props.newLayoutDesigns,
+                          }}
+                        >
+                          @{item.name}
+                        </span>
                       </button>
                     )
                   }
@@ -75,16 +110,44 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
 
                   return (
                     <button
-                      class="w-full flex items-center gap-x-2 rounded-md px-2 py-0.5"
-                      classList={{ "bg-surface-raised-base-hover": props.atActive === key }}
+                      class="w-full flex items-center gap-x-2 px-2 py-0.5"
+                      classList={{
+                        "rounded-[4px]": props.newLayoutDesigns,
+                        "rounded-md": !props.newLayoutDesigns,
+                        "bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
+                        "bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
+                      }}
                       onClick={() => props.onAtSelect(item)}
-                      onMouseEnter={() => props.setAtActive(key)}
+                      onPointerMove={() => props.setAtActive(key)}
                     >
                       <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
-                      <div class="flex items-center text-14-regular min-w-0">
-                        <span class="text-text-weak whitespace-nowrap truncate min-w-0">{directory}</span>
+                      <div
+                        class="flex items-center min-w-0"
+                        classList={{
+                          "text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
+                            props.newLayoutDesigns,
+                          "text-14-regular": !props.newLayoutDesigns,
+                        }}
+                      >
+                        <span
+                          class="whitespace-nowrap truncate min-w-0"
+                          classList={{
+                            "text-v2-text-text-muted": props.newLayoutDesigns,
+                            "text-text-weak": !props.newLayoutDesigns,
+                          }}
+                        >
+                          {directory}
+                        </span>
                         <Show when={!isDirectory}>
-                          <span class="text-text-strong whitespace-nowrap">{filename}</span>
+                          <span
+                            class="whitespace-nowrap"
+                            classList={{
+                              "text-v2-text-text-base": props.newLayoutDesigns,
+                              "text-text-strong": !props.newLayoutDesigns,
+                            }}
+                          >
+                            {filename}
+                          </span>
                         </Show>
                       </div>
                     </button>
@@ -96,41 +159,100 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
           <Match when={props.popover === "slash"}>
             <Show
               when={props.slashFlat.length > 0}
-              fallback={<div class="text-text-weak px-2 py-1">{props.t("prompt.popover.emptyCommands")}</div>}
+              fallback={
+                <div
+                  class="px-2 py-1"
+                  classList={{
+                    "text-v2-text-text-muted": props.newLayoutDesigns,
+                    "text-text-weak": !props.newLayoutDesigns,
+                  }}
+                >
+                  {props.t("prompt.popover.emptyCommands")}
+                </div>
+              }
             >
               <For each={props.slashFlat}>
-                {(cmd) => (
-                  <button
-                    data-slash-id={cmd.id}
-                    classList={{
-                      "w-full flex items-center justify-between gap-4 rounded-md px-2 py-1": true,
-                      "bg-surface-raised-base-hover": props.slashActive === cmd.id,
-                    }}
-                    onClick={() => props.onSlashSelect(cmd)}
-                    onMouseEnter={() => props.setSlashActive(cmd.id)}
-                  >
-                    <div class="flex items-center gap-2 min-w-0">
-                      <span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
-                      <Show when={cmd.description}>
-                        <span class="text-14-regular text-text-weak truncate">{cmd.description}</span>
-                      </Show>
-                    </div>
-                    <div class="flex items-center gap-2 shrink-0">
-                      <Show when={cmd.type === "custom" && cmd.source !== "command"}>
-                        <span class="text-11-regular text-text-subtle px-1.5 py-0.5 bg-surface-base rounded">
-                          {cmd.source === "skill"
-                            ? props.t("prompt.slash.badge.skill")
-                            : cmd.source === "mcp"
-                              ? props.t("prompt.slash.badge.mcp")
-                              : props.t("prompt.slash.badge.custom")}
+                {(cmd) => {
+                  const keybind = () => props.commandKeybind(cmd.id)
+                  const keybindParts = () => props.commandKeybindParts(cmd.id)
+                  return (
+                    <button
+                      data-slash-id={cmd.id}
+                      classList={{
+                        "w-full flex items-center justify-between gap-4 px-2 py-1": true,
+                        "rounded-[4px] scroll-my-2": props.newLayoutDesigns,
+                        "rounded-md": !props.newLayoutDesigns,
+                        "bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.slashActive === cmd.id,
+                        "bg-surface-raised-base-hover": !props.newLayoutDesigns && props.slashActive === cmd.id,
+                      }}
+                      onClick={() => props.onSlashSelect(cmd)}
+                      onPointerMove={() => props.setSlashActive(cmd.id)}
+                    >
+                      <div class="flex items-center gap-2 min-w-0">
+                        <span
+                          class="whitespace-nowrap"
+                          classList={{
+                            "text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
+                              props.newLayoutDesigns,
+                            "text-v2-text-text-base": props.newLayoutDesigns,
+                            "text-14-regular": !props.newLayoutDesigns,
+                            "text-text-strong": !props.newLayoutDesigns,
+                          }}
+                        >
+                          /{cmd.trigger}
                         </span>
-                      </Show>
-                      <Show when={props.commandKeybind(cmd.id)}>
-                        <span class="text-12-regular text-text-subtle">{props.commandKeybind(cmd.id)}</span>
-                      </Show>
-                    </div>
-                  </button>
-                )}
+                        <Show when={cmd.description}>
+                          <span
+                            class="truncate"
+                            classList={{
+                              "text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
+                                props.newLayoutDesigns,
+                              "text-v2-text-text-muted": props.newLayoutDesigns,
+                              "text-14-regular": !props.newLayoutDesigns,
+                              "text-text-weak": !props.newLayoutDesigns,
+                            }}
+                          >
+                            {cmd.description}
+                          </span>
+                        </Show>
+                      </div>
+                      <div class="flex items-center gap-2 shrink-0">
+                        <Show when={cmd.type === "custom" && cmd.source !== "command"}>
+                          <Show
+                            when={props.newLayoutDesigns}
+                            fallback={
+                              <span class="text-11-regular px-1.5 py-0.5 rounded bg-surface-base text-text-subtle">
+                                {cmd.source === "skill"
+                                  ? props.t("prompt.slash.badge.skill")
+                                  : cmd.source === "mcp"
+                                    ? props.t("prompt.slash.badge.mcp")
+                                    : props.t("prompt.slash.badge.custom")}
+                              </span>
+                            }
+                          >
+                            <Tag>
+                              {cmd.source === "skill"
+                                ? props.t("prompt.slash.badge.skill")
+                                : cmd.source === "mcp"
+                                  ? props.t("prompt.slash.badge.mcp")
+                                  : props.t("prompt.slash.badge.custom")}
+                            </Tag>
+                          </Show>
+                        </Show>
+                        <Show
+                          when={props.newLayoutDesigns ? keybindParts().length > 0 : keybind()}
+                        >
+                          <Show
+                            when={props.newLayoutDesigns}
+                            fallback={<span class="text-12-regular text-text-subtle">{keybind()}</span>}
+                          >
+                            <KeybindV2 keys={keybindParts()} variant="neutral" />
+                          </Show>
+                        </Show>
+                      </div>
+                    </button>
+                  )
+                }}
               </For>
             </Show>
           </Match>

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

@@ -125,7 +125,7 @@ export function SessionComposerRegion(props: {
             </Show>
             <div
               classList={{
-                "relative z-30": true,
+                "relative z-[70]": true,
               }}
               style={{
                 "margin-top": `${-controller.lift()}px`,