diff --git a/src/app/chat/components/browser-hint-overlay.module.css b/src/app/chat/components/browser-hint-overlay.module.css index 9d6fb056..28c0c20b 100644 --- a/src/app/chat/components/browser-hint-overlay.module.css +++ b/src/app/chat/components/browser-hint-overlay.module.css @@ -9,6 +9,7 @@ align-items: center; gap: 8px; z-index: 20; + width: fit-content; max-width: min(74vw, 260px); min-height: 48px; padding: 8px 10px; @@ -25,10 +26,26 @@ pointer-events: auto; backdrop-filter: blur(12px); transition: border-color 0.16s ease, box-shadow 0.16s ease, - opacity 0.16s ease, transform 0.16s ease; + opacity 0.16s ease, padding 0.18s ease, transform 0.16s ease; -webkit-tap-highlight-color: transparent; } +.expanded { + width: fit-content; +} + +.collapsed { + display: inline-flex; + width: 42px; + height: 42px; + min-width: 42px; + min-height: 42px; + align-items: center; + justify-content: center; + padding: 5px; + border-radius: 999px; +} + .iconWrap { display: inline-flex; width: 30px; @@ -39,14 +56,34 @@ background: rgba(255, 255, 255, 0.15); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16); color: #fff; + transition: background 0.18s ease, transform 0.18s ease; +} + +.collapsed .iconWrap { + width: 30px; + height: 30px; } .copy { display: flex; min-width: 0; + max-width: 190px; flex-direction: column; gap: 2px; text-align: left; + opacity: 1; + overflow: hidden; + transform: translateX(0); + transition: max-width 0.2s ease, opacity 0.14s ease, + transform 0.18s ease; + white-space: normal; +} + +.collapsed .copy { + display: none; + opacity: 0; + pointer-events: none; + transform: translateX(6px); } .title { diff --git a/src/app/chat/components/browser-hint-overlay.tsx b/src/app/chat/components/browser-hint-overlay.tsx index 61bcd55b..cca5c9d1 100644 --- a/src/app/chat/components/browser-hint-overlay.tsx +++ b/src/app/chat/components/browser-hint-overlay.tsx @@ -8,9 +8,9 @@ * 行为: * - 开发环境始终显示(测试方便) * - 生产环境仅在应用内浏览器中显示 - * - 点击后主动打开外部浏览器 + * - 首次完整展示,随后收缩为图标;展开后点击主动打开外部浏览器 */ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ExternalLink } from "lucide-react"; import { openChatInExternalBrowser } from "@/lib/chat/chat_external_browser"; @@ -25,40 +25,72 @@ export interface BrowserHintOverlayProps { description?: string; /** 强制显示(开发模式测试用) */ forceShow?: boolean; + /** 自动收缩延迟,设为 0 可禁用自动收缩 */ + autoCollapseDelayMs?: number; } const DEFAULT_TITLE = "Open in external browser"; const DEFAULT_DESCRIPTION = "Find me more easily"; +const DEFAULT_AUTO_COLLAPSE_DELAY_MS = 3500; export function BrowserHintOverlay({ title = DEFAULT_TITLE, description = DEFAULT_DESCRIPTION, forceShow = false, + autoCollapseDelayMs = DEFAULT_AUTO_COLLAPSE_DELAY_MS, }: BrowserHintOverlayProps) { // lazy initializer:首次渲染即计算(避免 useEffect 中调 setState 的 lint 错) const [isInAppBrowser] = useState(BrowserDetector.isInAppBrowser); + const [isExpanded, setIsExpanded] = useState(true); + const [hasAutoCollapsed, setHasAutoCollapsed] = useState(false); // 开发模式或应用内浏览器才显示 const shouldShow = forceShow || isInAppBrowser; + + useEffect(() => { + if (!shouldShow || hasAutoCollapsed || autoCollapseDelayMs <= 0) return; + + const timer = window.setTimeout(() => { + setIsExpanded(false); + setHasAutoCollapsed(true); + }, autoCollapseDelayMs); + + return () => { + window.clearTimeout(timer); + }; + }, [autoCollapseDelayMs, hasAutoCollapsed, shouldShow]); + if (!shouldShow) return null; const handleClick = (): void => { + if (!isExpanded) { + setIsExpanded(true); + return; + } + void openChatInExternalBrowser().catch((error) => { console.warn("[chat] Failed to open external browser", error); }); }; + const ariaLabel = isExpanded + ? `${title}. ${description}` + : "Show external browser hint"; + return (