Files
IdP/apps/frontend/components/chat/chat-message-context-menu.tsx
2026-06-25 23:48:57 +03:00

174 lines
5.1 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 { CheckCircle2, Copy, Forward, Pencil, Reply, Trash2 } from 'lucide-react';
import { ChatMessageReactions } from '@/components/chat/chat-message-reactions';
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger
} from '@/components/ui/context-menu';
import type { ChatMessage } from '@/lib/api';
import { QUICK_REACTIONS, canDeleteMessage, canEditMessage, getMessageCopyText } from '@/lib/chat-message-utils';
import { cn } from '@/lib/utils';
interface ChatMessageContextMenuProps {
message: ChatMessage;
userId?: string;
isE2E?: boolean;
visibleText?: string;
children: React.ReactNode;
onEdit?: () => void;
onDelete?: () => void;
onReply?: () => void;
onForward?: () => void;
onSelect?: () => void;
onToggleReaction?: (emoji: string) => void;
onCopy?: () => void;
}
export function ChatMessageContextMenu({
message,
userId,
isE2E,
visibleText,
children,
onEdit,
onDelete,
onReply,
onForward,
onSelect,
onToggleReaction,
onCopy
}: ChatMessageContextMenuProps) {
const editable = canEditMessage(message, userId, isE2E);
const deletable = canDeleteMessage(message, userId);
const copyText = getMessageCopyText(message, visibleText);
const disabled = message.isDeleted;
return (
<ContextMenu>
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
<ContextMenuContent className="min-w-[240px]">
{!disabled ? (
<>
<div className="mb-1 flex items-center gap-1 overflow-x-auto px-1 py-1">
{QUICK_REACTIONS.map((emoji) => (
<button
key={emoji}
type="button"
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-lg transition hover:bg-[#eef1f6]"
onClick={() => onToggleReaction?.(emoji)}
>
{emoji}
</button>
))}
</div>
<ContextMenuSeparator />
</>
) : null}
{onReply && !disabled ? (
<ContextMenuItem onClick={onReply}>
<Reply className="h-4 w-4" />
Ответить
</ContextMenuItem>
) : null}
{editable ? (
<ContextMenuItem onClick={onEdit}>
<Pencil className="h-4 w-4" />
Изменить
</ContextMenuItem>
) : null}
{copyText ? (
<ContextMenuItem
onClick={() => {
void navigator.clipboard.writeText(copyText);
onCopy?.();
}}
>
<Copy className="h-4 w-4" />
Копировать текст
</ContextMenuItem>
) : null}
{onForward && !disabled && !message.isEncrypted ? (
<ContextMenuItem onClick={onForward}>
<Forward className="h-4 w-4" />
Переслать
</ContextMenuItem>
) : null}
{deletable ? (
<ContextMenuItem className="text-red-600 data-[highlighted]:bg-red-50 data-[highlighted]:text-red-700" onClick={onDelete}>
<Trash2 className="h-4 w-4" />
Удалить
</ContextMenuItem>
) : null}
{onSelect && !disabled ? (
<>
<ContextMenuSeparator />
<ContextMenuItem onClick={onSelect}>
<CheckCircle2 className="h-4 w-4" />
Выделить
</ContextMenuItem>
</>
) : null}
{!disabled && message.reactions?.length ? (
<div className="px-2 pb-1 pt-2">
<ChatMessageReactions reactions={message.reactions} onToggle={onToggleReaction} compact />
</div>
) : null}
</ContextMenuContent>
</ContextMenu>
);
}
interface SelectableMessageWrapperProps {
selected?: boolean;
selectionMode?: boolean;
align?: 'left' | 'right';
onClick?: () => void;
className?: string;
children: React.ReactNode;
}
export function SelectableMessageWrapper({
selected,
selectionMode,
align = 'left',
onClick,
className,
children
}: SelectableMessageWrapperProps) {
return (
<div
className={cn(
'relative w-fit max-w-[78%] shrink-0',
selectionMode && 'cursor-pointer',
selectionMode && (align === 'right' ? 'mr-7' : 'ml-7'),
className
)}
onClick={selectionMode ? onClick : undefined}
>
{selectionMode ? (
<span
className={cn(
'absolute top-1/2 z-10 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded-full border text-[10px] shadow-sm transition',
align === 'right' ? 'right-0 translate-x-[calc(100%+8px)]' : 'left-0 -translate-x-[calc(100%+8px)]',
selected ? 'border-[#3390ec] bg-[#3390ec] text-white' : 'border-[#dce3ec] bg-white text-transparent'
)}
>
</span>
) : null}
<div
className={cn(
'rounded-[18px] transition',
selected && 'ring-2 ring-[#3390ec] ring-offset-2 ring-offset-[#eef1f6]'
)}
>
{children}
</div>
</div>
);
}