Ver Fonte

fix(ui): standardize tooltip delay

Luke Parker há 3 semanas atrás
pai
commit
f77f5a343e

+ 1 - 1
packages/app/src/components/dialog-select-model-unpaid-v2.tsx

@@ -88,7 +88,7 @@ export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (pro
                   class="w-full"
                   placement="right-start"
                   gutter={6}
-                  openDelay={0}
+                  delay="intent"
                   contentStyle={{ "font-family": "var(--v2-font-family-sans)" }}
                   value={
                     <ModelTooltip

+ 1 - 1
packages/app/src/components/dialog-select-model.tsx

@@ -452,7 +452,7 @@ export function ModelSelectorPopoverV2(props: {
                               class="w-full"
                               placement="right-start"
                               gutter={6}
-                              openDelay={0}
+                              delay="intent"
                               value={
                                 <ModelTooltip
                                   model={item}

+ 1 - 1
packages/app/src/components/prompt-input/context-items.tsx

@@ -47,7 +47,7 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
                   </span>
                 }
                 placement="top"
-                openDelay={800}
+                {...(!props.newLayoutDesigns ? { openDelay: 800 } : {})}
               >
                 <div
                   classList={{

+ 1 - 6
packages/app/src/components/prompt-input/image-attachments.tsx

@@ -52,12 +52,7 @@ export const PromptImageAttachments: Component<PromptImageAttachmentsProps> = (p
             <For each={props.comments ?? []}>
               {(item) => (
                 <div class="relative group shrink-0">
-                  <TooltipV2
-                    value={item.comment}
-                    placement="top"
-                    openDelay={800}
-                    contentClass="max-w-[300px] break-words"
-                  >
+                  <TooltipV2 value={item.comment} placement="top" contentClass="max-w-[300px] break-words">
                     <CommentCardV2
                       comment={item.comment ?? ""}
                       path={item.path}

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

@@ -263,7 +263,6 @@ function ProviderTip(props: { ready: () => boolean; connected: () => boolean; op
           <TooltipV2
             class="hover-reveal absolute left-full top-0 flex h-6 w-7 items-center justify-end delay-0 duration-0 group-hover/provider-tip:delay-[250ms] group-hover/provider-tip:duration-150 group-hover/provider-tip:opacity-100 focus-within:delay-0 focus-within:duration-0 focus-within:opacity-100"
             placement="top"
-            openDelay={1000}
             value={language.t("common.dismiss")}
           >
             <button

+ 0 - 1
packages/session-ui/src/v2/components/comment-card-v2.tsx

@@ -32,7 +32,6 @@ export function CommentCardV2(props: {
   return (
     <TooltipV2
       placement="top"
-      openDelay={1000}
       value={props.title ?? props.comment}
       disabled={!props.tooltip || !truncated()}
       class={props.wide ? "w-full" : undefined}

+ 1 - 6
packages/session-ui/src/v2/components/prompt-input/index.tsx

@@ -385,12 +385,7 @@ export function PromptInputV2Attachments(props: {
           <For each={props.comments ?? []}>
             {(comment) => (
               <div class="relative group shrink-0">
-                <TooltipV2
-                  value={comment.comment}
-                  placement="top"
-                  openDelay={800}
-                  contentClass="max-w-[300px] break-words"
-                >
+                <TooltipV2 value={comment.comment} placement="top" contentClass="max-w-[300px] break-words">
                   <CommentCardV2
                     comment={comment.comment ?? ""}
                     path={comment.path}

+ 4 - 6
packages/session-ui/src/v2/components/session-review-v2.tsx

@@ -214,7 +214,6 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
       </Show>
       <div class="flex items-center">
         <TooltipV2
-          openDelay={2000}
           inactive={!prev()}
           value={
             <>
@@ -234,7 +233,6 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
           />
         </TooltipV2>
         <TooltipV2
-          openDelay={2000}
           inactive={!next()}
           value={
             <>
@@ -268,12 +266,12 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
         class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
         aria-label={i18n.t("ui.sessionReviewV2.expandMode")}
       >
-        <TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.showAllLines")}>
+        <TooltipV2 value={i18n.t("ui.sessionReviewV2.showAllLines")}>
           <SegmentedControlItemV2 value="expand" aria-label={i18n.t("ui.sessionReviewV2.showAllLines")}>
             <Icon name="expand" />
           </SegmentedControlItemV2>
         </TooltipV2>
-        <TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
+        <TooltipV2 value={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
           <SegmentedControlItemV2 value="collapse" aria-label={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
             <Icon name="collapse" />
           </SegmentedControlItemV2>
@@ -289,12 +287,12 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
           class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
           aria-label={i18n.t("ui.sessionReviewV2.diffView")}
         >
-          <TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
+          <TooltipV2 value={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
             <SegmentedControlItemV2 value="unified" aria-label={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
               <Icon name="unified" />
             </SegmentedControlItemV2>
           </TooltipV2>
-          <TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.splitDiff")}>
+          <TooltipV2 value={i18n.t("ui.sessionReviewV2.splitDiff")}>
             <SegmentedControlItemV2 value="split" aria-label={i18n.t("ui.sessionReviewV2.splitDiff")}>
               <Icon name="split" />
             </SegmentedControlItemV2>

+ 35 - 0
packages/ui/src/v2/components/tooltip-intent.ts

@@ -0,0 +1,35 @@
+const OPEN_DELAY = 1_000
+
+// Kobalte warms every tooltip globally. Keep intent previews isolated so opening
+// a model picker never inherits warm state from an unrelated tooltip.
+let warm = false
+let reset: ReturnType<typeof setTimeout> | undefined
+
+export function openTooltipIntent(open: () => void) {
+  clearTimeout(reset)
+  reset = undefined
+  if (warm) {
+    open()
+    return
+  }
+  const timer = setTimeout(() => {
+    warm = true
+    open()
+  }, OPEN_DELAY)
+  return () => clearTimeout(timer)
+}
+
+export function closeTooltipIntent() {
+  clearTimeout(reset)
+  // Adjacent triggers enter before the next task; leaving the group does not.
+  reset = setTimeout(() => {
+    warm = false
+    reset = undefined
+  })
+}
+
+export function resetTooltipIntent() {
+  clearTimeout(reset)
+  reset = undefined
+  warm = false
+}

+ 36 - 6
packages/ui/src/v2/components/tooltip-v2.tsx

@@ -2,19 +2,22 @@ import { Tooltip as KobalteTooltip } from "@kobalte/core/tooltip"
 import { createEffect, Match, onCleanup, splitProps, Switch, type JSX } from "solid-js"
 import type { ComponentProps } from "solid-js"
 import { createStore } from "solid-js/store"
+import { closeTooltipIntent, openTooltipIntent, resetTooltipIntent } from "./tooltip-intent"
 import "./tooltip-v2.css"
 
-export interface TooltipV2Props extends ComponentProps<typeof KobalteTooltip> {
+export interface TooltipV2Props extends Omit<ComponentProps<typeof KobalteTooltip>, "openDelay"> {
   value: JSX.Element
   class?: string
   contentClass?: string
   contentStyle?: JSX.CSSProperties
   inactive?: boolean
+  delay?: "standard" | "intent"
   forceOpen?: boolean
 }
 
 export function TooltipV2(props: TooltipV2Props) {
   let ref: HTMLDivElement | undefined
+  let cancelIntent: (() => void) | undefined
   const [state, setState] = createStore({
     open: false,
     block: false,
@@ -26,19 +29,37 @@ export function TooltipV2(props: TooltipV2Props) {
     "contentClass",
     "contentStyle",
     "inactive",
+    "delay",
     "forceOpen",
     "ignoreSafeArea",
     "value",
   ])
 
-  const close = () => setState("open", false)
-
   const inside = () => {
     const active = document.activeElement
     if (!ref || !active) return false
     return ref.contains(active)
   }
 
+  const close = () => {
+    cancelIntent?.()
+    cancelIntent = undefined
+    if (local.delay === "intent") closeTooltipIntent()
+    setState("open", false)
+  }
+
+  const show = () => {
+    if (local.delay !== "intent" || inside()) {
+      setState("open", true)
+      return
+    }
+    if (cancelIntent) return
+    cancelIntent = openTooltipIntent(() => {
+      cancelIntent = undefined
+      setState("open", true)
+    })
+  }
+
   const drop = (expand = state.expand) => {
     if (expand) return
     if (ref?.matches(":hover")) return
@@ -80,6 +101,11 @@ export function TooltipV2(props: TooltipV2Props) {
     onCleanup(() => obs.disconnect())
   })
 
+  onCleanup(() => {
+    cancelIntent?.()
+    if (local.delay === "intent") resetTooltipIntent()
+  })
+
   let justClickedTrigger = false
 
   return (
@@ -88,8 +114,8 @@ export function TooltipV2(props: TooltipV2Props) {
       <Match when={true}>
         <KobalteTooltip
           gutter={4}
-          openDelay={400}
-          skipDelayDuration={300}
+          openDelay={local.delay === "intent" ? 0 : 400}
+          skipDelayDuration={local.delay === "intent" ? 0 : 300}
           {...others}
           closeDelay={0}
           ignoreSafeArea={local.ignoreSafeArea ?? true}
@@ -101,7 +127,11 @@ export function TooltipV2(props: TooltipV2Props) {
               justClickedTrigger = false
               return
             }
-            setState("open", open)
+            if (open) {
+              show()
+              return
+            }
+            close()
           }}
         >
           <KobalteTooltip.Trigger