crush-level-web/src/app/(main)/chat/[id]/Input.tsx

131 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { IconButton } from '@/components/ui/button';
import { useAsyncFn } from '@/hooks/tools';
import { cn } from '@/lib/utils';
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
import { useState, useRef, useEffect } from 'react';
const AuthHeightTextarea = (
props: React.ComponentProps<'textarea'> & {
maxHeight?: number;
onSend?: (text: string) => void;
}
) => {
const { maxHeight = 200, className, value, onChange, onSend, ...restProps } = props;
const textareaRef = useRef<HTMLTextAreaElement>(null);
// 调整高度的函数
const adjustHeight = () => {
const textarea = textareaRef.current;
if (!textarea) return;
// 先重置高度为 0这样才能获取真实的 scrollHeight
textarea.style.height = '0px';
// 获取内容实际需要的高度
const scrollHeight = textarea.scrollHeight;
// 计算新高度:取 scrollHeight 和 maxHeight 的较小值
const newHeight = Math.min(scrollHeight, maxHeight);
textarea.style.height = `${newHeight}px`;
// 如果内容超过最大高度,显示滚动条
textarea.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden';
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Enter 发送Shift+Enter 换行
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); // 阻止默认的换行行为
onSend?.(value as string);
}
};
// 监听内容变化,自动调整高度
useEffect(() => {
adjustHeight();
}, [value, maxHeight]);
return (
<textarea
ref={textareaRef}
value={value}
onChange={onChange}
className={cn(
'w-full resize-none',
// 移除所有默认样式
'border-none outline-none focus:outline-none',
'bg-transparent',
// 自定义滚动条样式(可选)
'scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent',
className
)}
style={{
minHeight: '24px', // 最小高度,一行文字的高度
height: '24px', // 初始高度
overflow: 'hidden', // 初始隐藏滚动条
}}
onKeyDown={handleKeyDown}
{...restProps}
/>
);
};
export default function Input() {
const [isRecording, setIsRecording] = useState(false);
const [inputValue, setInputValue] = useState('');
const sendMessage = useStreamChatStore((state) => state.sendMessage);
const { run: sendMessageAsync, loading } = useAsyncFn(sendMessage);
const handleSend = () => {
if (inputValue.trim()) {
sendMessageAsync(inputValue);
setInputValue('');
}
};
return (
<div className="flex flex-col mb-6 items-end gap-4">
<div></div>
<div className="flex w-full items-end gap-4">
{/* 打电话按钮 */}
<IconButton onClick={() => {}} iconfont="icon-gift-border" />
<div className="flex-1 flex items-end gap-2 min-h-12 py-2 px-2 bg-white/15 rounded-3xl">
{/* 语音录制按钮 */}
<IconButton
variant="ghost"
size="small"
iconfont="icon-voice_msg"
className="flex-shrink-0"
/>
<AuthHeightTextarea
placeholder="Chat"
maxHeight={70}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onSend={handleSend}
className="py-1"
/>
{/* 提示词提示按钮 */}
<IconButton
variant="ghost"
size="small"
onClick={() => null}
className={cn('bg-surface-element-hover flex-shrink-0')}
iconfont="icon-prompt"
/>
</div>
<IconButton
size="large"
loading={loading}
iconfont="icon-icon-send"
onClick={handleSend}
disabled={!inputValue.trim()}
className="flex-shrink-0"
/>
</div>
</div>
);
}