feat(组件): 新增 Tooltip 组件并优化输入区交互体验

- 新增通用 Tooltip 组件,支持上下左右四个方向显示
- ChatInput 组件使用 Tooltip 替代原生 title 属性
- ChatInput 添加附件图标改为 Paperclip,更直观
- ToolsDropdown 工具按钮添加 Tooltip 提示
- 优化按钮 cursor 样式,提升交互体验
This commit is contained in:
gaoziman 2025-12-21 14:32:48 +08:00
parent 3b0683faf9
commit f50766b742
3 changed files with 98 additions and 38 deletions

View File

@ -1,10 +1,11 @@
'use client';
import { useState, useRef } from 'react';
import { Plus, ArrowUp, Upload } from 'lucide-react';
import { Paperclip, ArrowUp, Upload } from 'lucide-react';
import { ModelSelector } from './ModelSelector';
import { ToolsDropdown } from './ToolsDropdown';
import { FilePreviewList } from './FilePreviewList';
import { Tooltip } from '@/components/ui/Tooltip';
import { useFileUpload } from '@/hooks/useFileUpload';
import { cn } from '@/lib/utils';
import type { Model, Tool } from '@/types';
@ -136,17 +137,18 @@ export function ChatInput({
{/* 左侧按钮 */}
<div className="flex items-center gap-1">
{/* 添加附件 */}
<button
onClick={openFileDialog}
className={cn(
'w-8 h-8 flex items-center justify-center rounded-lg transition-colors',
'text-[var(--color-text-tertiary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-secondary)]',
files.length > 0 && 'text-[var(--color-primary)]'
)}
title="添加附件"
>
<Plus size={20} />
</button>
<Tooltip content="添加附件">
<button
onClick={openFileDialog}
className={cn(
'w-8 h-8 flex items-center justify-center rounded-lg transition-colors cursor-pointer',
'text-[var(--color-text-tertiary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-secondary)]',
files.length > 0 && 'text-[var(--color-primary)]'
)}
>
<Paperclip size={20} />
</button>
</Tooltip>
{/* 隐藏的文件输入 */}
<input
@ -176,19 +178,20 @@ export function ChatInput({
/>
{/* 发送按钮 */}
<button
onClick={handleSend}
disabled={!message.trim() && files.length === 0}
className={cn(
'w-[38px] h-[38px] flex items-center justify-center bg-[var(--color-primary)] text-white rounded-xl transition-all duration-150',
message.trim() || files.length > 0
? 'hover:bg-[var(--color-primary-hover)] hover:-translate-y-0.5'
: 'opacity-50 cursor-not-allowed'
)}
title="Send message"
>
<ArrowUp size={18} />
</button>
<Tooltip content="发送消息">
<button
onClick={handleSend}
disabled={!message.trim() && files.length === 0}
className={cn(
'w-[38px] h-[38px] flex items-center justify-center bg-[var(--color-primary)] text-white rounded-xl transition-all duration-150 cursor-pointer',
message.trim() || files.length > 0
? 'hover:bg-[var(--color-primary-hover)] hover:-translate-y-0.5'
: 'opacity-50 cursor-not-allowed'
)}
>
<ArrowUp size={18} />
</button>
</Tooltip>
</div>
</div>
</div>

View File

@ -3,6 +3,7 @@
import { useState, useRef, useEffect } from 'react';
import { Wrench, Search, Terminal, Globe, Check } from 'lucide-react';
import { Toggle } from '@/components/ui/Toggle';
import { Tooltip } from '@/components/ui/Tooltip';
import { cn } from '@/lib/utils';
import type { Tool } from '@/types';
@ -41,20 +42,21 @@ export function ToolsDropdown({ tools, onToolToggle, onEnableAllToggle }: ToolsD
<div className="relative" ref={dropdownRef}>
{/* 触发按钮 */}
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className={cn(
'w-8 h-8 flex items-center justify-center rounded-lg transition-colors',
enabledCount > 0
? 'text-[var(--color-primary)] bg-[var(--color-primary-light)]'
: 'text-[var(--color-text-tertiary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-secondary)]'
)}
title="Tools"
>
<Wrench size={20} />
</button>
<Tooltip content="工具" position="top">
<button
onClick={() => setIsOpen(!isOpen)}
className={cn(
'w-8 h-8 flex items-center justify-center rounded-lg transition-colors cursor-pointer',
enabledCount > 0
? 'text-[var(--color-primary)] bg-[var(--color-primary-light)]'
: 'text-[var(--color-text-tertiary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-secondary)]'
)}
>
<Wrench size={20} />
</button>
</Tooltip>
{enabledCount > 0 && (
<span className="absolute -top-0.5 -right-0.5 min-w-4 h-4 bg-[var(--color-primary)] text-white text-[10px] font-semibold rounded-full flex items-center justify-center px-1">
<span className="absolute -top-0.5 -right-0.5 min-w-4 h-4 bg-[var(--color-primary)] text-white text-[10px] font-semibold rounded-full flex items-center justify-center px-1 pointer-events-none">
{enabledCount}
</span>
)}

View File

@ -0,0 +1,55 @@
'use client';
import { ReactNode } from 'react';
import { cn } from '@/lib/utils';
interface TooltipProps {
children: ReactNode;
content: string;
position?: 'top' | 'bottom' | 'left' | 'right';
className?: string;
}
/**
* Tooltip
*
*/
export function Tooltip({ children, content, position = 'top', className }: TooltipProps) {
const positionStyles = {
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
};
const arrowStyles = {
top: 'top-full left-1/2 -translate-x-1/2 border-t-gray-800 border-x-transparent border-b-transparent',
bottom: 'bottom-full left-1/2 -translate-x-1/2 border-b-gray-800 border-x-transparent border-t-transparent',
left: 'left-full top-1/2 -translate-y-1/2 border-l-gray-800 border-y-transparent border-r-transparent',
right: 'right-full top-1/2 -translate-y-1/2 border-r-gray-800 border-y-transparent border-l-transparent',
};
return (
<div className={cn('relative group/tooltip inline-flex', className)}>
{children}
<div
className={cn(
'absolute z-50 px-2.5 py-1.5 text-xs font-medium text-white bg-gray-800 rounded-md',
'opacity-0 invisible group-hover/tooltip:opacity-100 group-hover/tooltip:visible',
'transition-all duration-200 whitespace-nowrap pointer-events-none',
'shadow-lg',
positionStyles[position]
)}
>
{content}
{/* 小三角箭头 */}
<div
className={cn(
'absolute w-0 h-0 border-4',
arrowStyles[position]
)}
/>
</div>
</div>
);
}