Jelajahi Sumber

feat(app): add truncation tooltip to server items in status popover

David Hill 1 bulan lalu
induk
melakukan
a98add29d1
1 mengubah file dengan 71 tambahan dan 36 penghapusan
  1. 71 36
      packages/app/src/components/status-popover.tsx

+ 71 - 36
packages/app/src/components/status-popover.tsx

@@ -1,4 +1,4 @@
-import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
+import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
 import { createStore, reconcile } from "solid-js/store"
 import { useNavigate } from "@solidjs/router"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
@@ -7,6 +7,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
 import { Button } from "@opencode-ai/ui/button"
 import { Switch } from "@opencode-ai/ui/switch"
 import { Icon } from "@opencode-ai/ui/icon"
+import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { useSync } from "@/context/sync"
 import { useSDK } from "@/context/sdk"
 import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
@@ -210,44 +211,78 @@ export function StatusPopover() {
                     const isDefault = () => url === defaultServerUrl()
                     const status = () => store.status[url]
                     const isBlocked = () => status()?.healthy === false
+                    const [truncated, setTruncated] = createSignal(false)
+                    let nameRef: HTMLSpanElement | undefined
+                    let versionRef: HTMLSpanElement | undefined
+
+                    onMount(() => {
+                      const check = () => {
+                        const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
+                        const versionTruncated = versionRef ? versionRef.scrollWidth > versionRef.clientWidth : false
+                        setTruncated(nameTruncated || versionTruncated)
+                      }
+                      check()
+                      window.addEventListener("resize", check)
+                      onCleanup(() => window.removeEventListener("resize", check))
+                    })
+
+                    const tooltipValue = () => {
+                      const name = serverDisplayName(url)
+                      const version = status()?.version
+                      return (
+                        <span class="flex items-center gap-2">
+                          <span>{name}</span>
+                          <Show when={version}>
+                            <span class="text-text-invert-base">{version}</span>
+                          </Show>
+                        </span>
+                      )
+                    }
+
                     return (
-                      <button
-                        type="button"
-                        class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
-                        classList={{
-                          "opacity-50": isBlocked(),
-                          "hover:bg-surface-raised-base-hover": !isBlocked(),
-                          "cursor-not-allowed": isBlocked(),
-                        }}
-                        aria-disabled={isBlocked()}
-                        onClick={() => {
-                          if (isBlocked()) return
-                          server.setActive(url)
-                          navigate("/")
-                        }}
-                      >
-                        <div
+                      <Tooltip value={tooltipValue()} placement="top" inactive={!truncated()}>
+                        <button
+                          type="button"
+                          class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
                           classList={{
-                            "size-1.5 rounded-full shrink-0": true,
-                            "bg-icon-success-base": status()?.healthy === true,
-                            "bg-icon-critical-base": status()?.healthy === false,
-                            "bg-border-weak-base": status() === undefined,
+                            "opacity-50": isBlocked(),
+                            "hover:bg-surface-raised-base-hover": !isBlocked(),
+                            "cursor-not-allowed": isBlocked(),
                           }}
-                        />
-                        <span class="text-14-regular text-text-base truncate">{serverDisplayName(url)}</span>
-                        <Show when={status()?.version}>
-                          <span class="text-12-regular text-text-weak truncate">{status()?.version}</span>
-                        </Show>
-                        <Show when={isDefault()}>
-                          <span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
-                            Default
+                          aria-disabled={isBlocked()}
+                          onClick={() => {
+                            if (isBlocked()) return
+                            server.setActive(url)
+                            navigate("/")
+                          }}
+                        >
+                          <div
+                            classList={{
+                              "size-1.5 rounded-full shrink-0": true,
+                              "bg-icon-success-base": status()?.healthy === true,
+                              "bg-icon-critical-base": status()?.healthy === false,
+                              "bg-border-weak-base": status() === undefined,
+                            }}
+                          />
+                          <span ref={nameRef} class="text-14-regular text-text-base truncate">
+                            {serverDisplayName(url)}
                           </span>
-                        </Show>
-                        <div class="flex-1" />
-                        <Show when={isActive()}>
-                          <Icon name="check" size="small" class="text-icon-weak shrink-0" />
-                        </Show>
-                      </button>
+                          <Show when={status()?.version}>
+                            <span ref={versionRef} class="text-12-regular text-text-weak truncate">
+                              {status()?.version}
+                            </span>
+                          </Show>
+                          <Show when={isDefault()}>
+                            <span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
+                              Default
+                            </span>
+                          </Show>
+                          <div class="flex-1" />
+                          <Show when={isActive()}>
+                            <Icon name="check" size="small" class="text-icon-weak shrink-0" />
+                          </Show>
+                        </button>
+                      </Tooltip>
                     )
                   }}
                 </For>
@@ -276,7 +311,7 @@ export function StatusPopover() {
                       return (
                         <button
                           type="button"
-                          class="flex items-center gap-2 w-full h-8 px-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
+                          class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
                           onClick={() => toggleMcp(item.name)}
                           disabled={loading() === item.name}
                         >