Files
official-chat/src/pages/chat/QuickReply.tsx
2026-01-01 16:36:12 +08:00

118 lines
3.3 KiB
TypeScript

import { Chip, ScrollShadow } from '@heroui/react';
import type { QuickReply as QuickReplyType } from '@mujian/js-sdk/types';
import { useEffect, useRef } from 'react';
import { mjChatCls } from '@/utils/cls';
export interface QuickReplyProps {
quickReplies: Array<QuickReplyType>;
onSend: (query: string) => void;
}
export const QuickReply = (props: QuickReplyProps) => {
const { quickReplies, onSend } = props;
const scrollShadowRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleWheel = (e: WheelEvent) => {
if (Math.abs(e.deltaY) > 0 && scrollShadowRef.current) {
e.preventDefault();
scrollShadowRef.current.scrollLeft += e.deltaY;
}
};
scrollShadowRef?.current?.addEventListener('wheel', handleWheel, {
passive: false,
});
return () =>
scrollShadowRef?.current?.removeEventListener('wheel', handleWheel);
}, []);
const lastDragTime = useRef(0);
const onmousedown = (e: React.MouseEvent<HTMLDivElement>) => {
// 防止文字选择
e.preventDefault();
// 禁用页面文字选择
document.body.style.userSelect = 'none';
document.body.style.webkitUserSelect = 'none';
lastDragTime.current = 0;
let dragged = false;
// biome-ignore lint/suspicious/noExplicitAny: 抄来的代码
const mouseMove: EventListener = (e: any) => {
dragged = true;
if (scrollShadowRef.current) {
scrollShadowRef.current.scrollLeft += e.movementX * -2;
}
};
const mouseUp: EventListener = () => {
// 恢复文字选择
document.body.style.userSelect = '';
document.body.style.webkitUserSelect = '';
document.removeEventListener('mousemove', mouseMove);
document.removeEventListener('mouseup', mouseUp);
if (dragged) {
lastDragTime.current = Date.now();
}
};
document.addEventListener('mousemove', mouseMove);
document.addEventListener('mouseup', mouseUp);
};
return (
<ScrollShadow
ref={scrollShadowRef}
hideScrollBar
className={mjChatCls(
['input-quickreply-container'],
'flex w-full overflow-auto gap-2',
)}
orientation="horizontal"
style={{
userSelect: 'none',
WebkitUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
}}
onMouseDown={onmousedown}
>
{quickReplies
.filter((qr) => !qr.isHidden)
.map((qr, index) => (
<Chip
key={index}
className={mjChatCls(
['input-quickreply-chip', `input-quickreply-chip-${index + 1}`],
'bg-white/15 text-default shrink-0 cursor-pointer',
)}
classNames={{
base: mjChatCls([
'input-quickreply-chip-base',
`input-quickreply-chip-${index + 1}-base`,
]),
content: mjChatCls([
'input-quickreply-chip-content',
`input-quickreply-chip-${index + 1}-content`,
]),
}}
radius="full"
variant="light"
onClick={() => {
const isClick = Date.now() - lastDragTime.current > 50;
if (isClick && qr.message) {
onSend(qr.message);
}
}}
>
{qr.label}
</Chip>
))}
</ScrollShadow>
);
};