diff --git a/src/pages/Chat/ChatHeader.tsx b/src/pages/Chat/ChatHeader.tsx new file mode 100644 index 0000000..1293b7f --- /dev/null +++ b/src/pages/Chat/ChatHeader.tsx @@ -0,0 +1,234 @@ +import React from 'react' +import { + Aperture, + BarChart3, + Calendar, + Download, + Image as ImageIcon, + Info, + Loader2, + Mic, + RefreshCw, + Search, + Users +} from 'lucide-react' +import { Avatar } from '../../components/Avatar' +import type { ChatSession } from '../../types/models' +import type { BatchVoiceTaskType } from '../../stores/batchTranscribeStore' + +export interface ChatHeaderProps { + session: ChatSession + isGroupChat: boolean + standaloneSessionWindow: boolean + showGroupMembersPanel: boolean + showJumpPopover: boolean + showInSessionSearch: boolean + showDetailPanel: boolean + shouldHideStandaloneDetailButton: boolean + isPrivateSnsSupported: boolean + isExportActionBusy: boolean + isCurrentSessionExporting: boolean + isPreparingExportDialog: boolean + isBatchTranscribing: boolean + runningBatchVoiceTaskType?: BatchVoiceTaskType + isBatchDecrypting: boolean + isRefreshingMessages: boolean + isLoadingMessages: boolean + currentSessionId?: string | null + jumpCalendarWrapRef: React.RefObject + onGroupAnalytics: () => void + onToggleGroupMembersPanel: () => void + onExportCurrentSession: () => void + onOpenSnsTimeline: () => void + onBatchTranscribe: () => void + onBatchDecrypt: () => void + onToggleJumpPopover: () => void + onToggleInSessionSearch: () => void + onRefreshMessages: () => void + onToggleDetailPanel: () => void +} + +function ChatHeader({ + session, + isGroupChat, + standaloneSessionWindow, + showGroupMembersPanel, + showJumpPopover, + showInSessionSearch, + showDetailPanel, + shouldHideStandaloneDetailButton, + isPrivateSnsSupported, + isExportActionBusy, + isCurrentSessionExporting, + isPreparingExportDialog, + isBatchTranscribing, + runningBatchVoiceTaskType, + isBatchDecrypting, + isRefreshingMessages, + isLoadingMessages, + currentSessionId, + jumpCalendarWrapRef, + onGroupAnalytics, + onToggleGroupMembersPanel, + onExportCurrentSession, + onOpenSnsTimeline, + onBatchTranscribe, + onBatchDecrypt, + onToggleJumpPopover, + onToggleInSessionSearch, + onRefreshMessages, + onToggleDetailPanel +}: ChatHeaderProps) { + const sessionName = session.displayName || session.username + const exportTitle = isCurrentSessionExporting + ? '导出中' + : isPreparingExportDialog + ? '正在准备导出模块' + : '导出当前会话' + const batchVoiceTitle = isBatchTranscribing + ? `${runningBatchVoiceTaskType === 'decrypt' ? '批量语音解密' : '批量转写'}中,可在导出页任务中心查看进度` + : '批量语音处理' + + return ( +
+ +
+

{sessionName}

+ {isGroupChat &&
群聊
} +
+
+ {!standaloneSessionWindow && isGroupChat && ( + + )} + {isGroupChat && ( + + )} + {!standaloneSessionWindow && ( + + )} + {!standaloneSessionWindow && isPrivateSnsSupported && ( + + )} + {!standaloneSessionWindow && ( + + )} + {!standaloneSessionWindow && ( + + )} +
+ +
+ + + {!shouldHideStandaloneDetailButton && ( + + )} +
+
+ ) +} + +function areEqual(prev: ChatHeaderProps, next: ChatHeaderProps) { + return ( + prev.session.username === next.session.username && + prev.session.displayName === next.session.displayName && + prev.session.avatarUrl === next.session.avatarUrl && + prev.isGroupChat === next.isGroupChat && + prev.standaloneSessionWindow === next.standaloneSessionWindow && + prev.showGroupMembersPanel === next.showGroupMembersPanel && + prev.showJumpPopover === next.showJumpPopover && + prev.showInSessionSearch === next.showInSessionSearch && + prev.showDetailPanel === next.showDetailPanel && + prev.shouldHideStandaloneDetailButton === next.shouldHideStandaloneDetailButton && + prev.isPrivateSnsSupported === next.isPrivateSnsSupported && + prev.isExportActionBusy === next.isExportActionBusy && + prev.isCurrentSessionExporting === next.isCurrentSessionExporting && + prev.isPreparingExportDialog === next.isPreparingExportDialog && + prev.isBatchTranscribing === next.isBatchTranscribing && + prev.runningBatchVoiceTaskType === next.runningBatchVoiceTaskType && + prev.isBatchDecrypting === next.isBatchDecrypting && + prev.isRefreshingMessages === next.isRefreshingMessages && + prev.isLoadingMessages === next.isLoadingMessages && + prev.currentSessionId === next.currentSessionId && + prev.jumpCalendarWrapRef === next.jumpCalendarWrapRef && + prev.onGroupAnalytics === next.onGroupAnalytics && + prev.onToggleGroupMembersPanel === next.onToggleGroupMembersPanel && + prev.onExportCurrentSession === next.onExportCurrentSession && + prev.onOpenSnsTimeline === next.onOpenSnsTimeline && + prev.onBatchTranscribe === next.onBatchTranscribe && + prev.onBatchDecrypt === next.onBatchDecrypt && + prev.onToggleJumpPopover === next.onToggleJumpPopover && + prev.onToggleInSessionSearch === next.onToggleInSessionSearch && + prev.onRefreshMessages === next.onRefreshMessages && + prev.onToggleDetailPanel === next.onToggleDetailPanel + ) +} + +export default React.memo(ChatHeader, areEqual) diff --git a/src/pages/Chat/ChatInputArea.tsx b/src/pages/Chat/ChatInputArea.tsx new file mode 100644 index 0000000..c8303d5 --- /dev/null +++ b/src/pages/Chat/ChatInputArea.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import { Lock, Search } from 'lucide-react' + +export interface ChatInputAreaProps { + disabled?: boolean + placeholder?: string + onFocusSearch?: () => void +} + +function ChatInputArea({ + disabled = true, + placeholder = '聊天记录', + onFocusSearch +}: ChatInputAreaProps) { + return ( +
+ +
+ + {placeholder} +
+
+ ) +} + +function areEqual(prev: ChatInputAreaProps, next: ChatInputAreaProps) { + return ( + prev.disabled === next.disabled && + prev.placeholder === next.placeholder && + prev.onFocusSearch === next.onFocusSearch + ) +} + +export default React.memo(ChatInputArea, areEqual) diff --git a/src/pages/Chat/ChatMessageBubble.tsx b/src/pages/Chat/ChatMessageBubble.tsx new file mode 100644 index 0000000..d88c6fd --- /dev/null +++ b/src/pages/Chat/ChatMessageBubble.tsx @@ -0,0 +1,136 @@ +import React from 'react' +import { Check } from 'lucide-react' +import { Avatar } from '../../components/Avatar' +import type { ChatSession, Message } from '../../types/models' + +export interface ChatMessageBubbleProps { + message: Message + messageKey: string + session: ChatSession + showTime?: boolean + timeText?: string + isSent: boolean + isSystem: boolean + isEmoji?: boolean + isImage?: boolean + isVoice?: boolean + emojiHasAsset?: boolean + emojiError?: boolean + avatarUrl?: string + isGroupChat?: boolean + resolvedSenderName?: string + isSelectionMode?: boolean + isSelected?: boolean + onContextMenu?: (event: React.MouseEvent, message: Message) => void + onToggleSelection?: (messageKey: string, isShiftKey?: boolean) => void + children: React.ReactNode + portal?: React.ReactNode +} + +function SelectionCheckbox({ checked, side }: { checked?: boolean; side: 'left' | 'right' }) { + return ( +
+ {checked && } +
+ ) +} + +function ChatMessageBubble({ + message, + messageKey, + session, + showTime, + timeText, + isSent, + isSystem, + isEmoji, + isImage, + isVoice, + emojiHasAsset, + emojiError, + avatarUrl, + isGroupChat, + resolvedSenderName, + isSelectionMode, + isSelected, + onContextMenu, + onToggleSelection, + children, + portal +}: ChatMessageBubbleProps) { + const bubbleClass = isSystem ? 'system' : (isSent ? 'sent' : 'received') + const avatarName = !isSent + ? (isGroupChat ? (resolvedSenderName || '?') : (session.displayName || session.username)) + : '我' + + return ( + <> + {showTime && timeText && ( +
+ {timeText} +
+ )} +
{ + if (!isSelectionMode) return + event.stopPropagation() + onToggleSelection?.(messageKey, event.shiftKey) + }} + > + {isSelectionMode && !isSent && } + +
onContextMenu?.(event, message)} + > +
+ +
+
+ {isGroupChat && !isSent && ( +
+ {resolvedSenderName || '群成员'} +
+ )} + {children} +
+
+ + {isSelectionMode && isSent && } + {portal} +
+ + ) +} + +function areEqual(prev: ChatMessageBubbleProps, next: ChatMessageBubbleProps) { + return ( + prev.message === next.message && + prev.messageKey === next.messageKey && + prev.session.username === next.session.username && + prev.session.displayName === next.session.displayName && + prev.session.avatarUrl === next.session.avatarUrl && + prev.showTime === next.showTime && + prev.timeText === next.timeText && + prev.isSent === next.isSent && + prev.isSystem === next.isSystem && + prev.isEmoji === next.isEmoji && + prev.isImage === next.isImage && + prev.isVoice === next.isVoice && + prev.emojiHasAsset === next.emojiHasAsset && + prev.emojiError === next.emojiError && + prev.avatarUrl === next.avatarUrl && + prev.isGroupChat === next.isGroupChat && + prev.resolvedSenderName === next.resolvedSenderName && + prev.isSelectionMode === next.isSelectionMode && + prev.isSelected === next.isSelected && + prev.onContextMenu === next.onContextMenu && + prev.onToggleSelection === next.onToggleSelection && + prev.children === next.children && + prev.portal === next.portal + ) +} + +export default React.memo(ChatMessageBubble, areEqual) diff --git a/src/pages/ChatPage.scss b/src/pages/ChatPage.scss index e998744..39ad989 100644 --- a/src/pages/ChatPage.scss +++ b/src/pages/ChatPage.scss @@ -5301,3 +5301,581 @@ .in-session-search-btn.active { color: var(--accent-color, #07c160); } + +// Modern chat surface overrides for the refactored Chat components. +.chat-page { + gap: 0; + background: var(--bg-sidebar); + + &:not(.standalone) { + .message-area { + margin-left: 0; + } + } +} + +.session-sidebar { + background: var(--bg-sidebar); + border-right: 0; + border-radius: 0; +} + +.resize-handle { + background: transparent; + + &:hover { + background: color-mix(in srgb, var(--text-tertiary) 18%, transparent); + } +} + +.message-area { + background: var(--bg-primary); + border-radius: 0; + box-shadow: none; +} + +.chat-page.standalone { + background: var(--bg-primary); + + .session-sidebar { + background: var(--bg-sidebar); + border-right: 0; + backdrop-filter: none; + } + + .message-area { + background: var(--bg-primary); + + .message-header { + min-height: 64px; + padding: 12px 22px; + border-bottom: 0; + background: color-mix(in srgb, var(--bg-primary) 88%, transparent); + backdrop-filter: blur(14px); + box-shadow: 0 1px 0 color-mix(in srgb, var(--border-color) 70%, transparent); + + .session-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + } + + .header-actions .icon-btn { + width: 34px; + height: 34px; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--text-secondary); + box-shadow: none; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + box-shadow: none; + } + + &.active { + background: var(--primary-light); + color: var(--primary); + } + } + } + + .message-list { + background: var(--bg-primary); + padding: 18px clamp(16px, 3vw, 48px) 104px; + padding-bottom: calc(104px + env(safe-area-inset-bottom)); + } + } +} + +.message-header { + min-height: 64px; + padding: 12px 22px; + border-bottom: 0; + background: color-mix(in srgb, var(--bg-primary) 88%, transparent); + backdrop-filter: blur(14px); + box-shadow: 0 1px 0 color-mix(in srgb, var(--border-color) 70%, transparent); + + .session-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + } + + .header-info { + min-width: 0; + + h3 { + font-size: 15px; + font-weight: 600; + letter-spacing: 0; + line-height: 1.25; + } + } + + .header-actions { + gap: 2px; + } + + .icon-btn { + width: 34px; + height: 34px; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--text-secondary); + box-shadow: none; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + box-shadow: none; + } + + &.active { + background: var(--primary-light); + color: var(--primary); + } + } +} + +.message-content-wrapper { + background: var(--bg-primary); +} + +.message-list { + background: var(--bg-primary); + padding: 18px clamp(16px, 3vw, 48px) 104px; + padding-bottom: calc(104px + env(safe-area-inset-bottom)); + gap: 0; +} + +.message-wrapper { + padding-bottom: 14px; + + &.sent { + align-items: flex-end; + } + + &.received { + align-items: flex-start; + } +} + +.message-wrapper-with-selection { + display: flex; + align-items: flex-start; + width: 100%; + cursor: default; + + &[data-sent="true"] { + justify-content: flex-end; + } + + &[data-sent="false"] { + justify-content: flex-start; + } + + &.selectable { + cursor: pointer; + } +} + +.chat-selection-checkbox { + width: 20px; + height: 20px; + border-radius: 6px; + border: 1.5px solid color-mix(in srgb, var(--text-tertiary) 55%, transparent); + background: transparent; + color: var(--on-primary); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-top: 9px; + + &.left { + margin-right: 12px; + } + + &.right { + margin-left: 12px; + } + + &.checked { + background: var(--primary); + border-color: var(--primary); + } +} + +.message-bubble { + gap: 10px; + max-width: min(76%, 760px); + + .bubble-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--bg-tertiary); + } + + .bubble-body { + min-width: 0; + width: fit-content; + } + + .bubble-content { + border-radius: 18px; + padding: 10px 14px; + line-height: 1.56; + box-shadow: none; + border: 0; + } + + &.sent { + .bubble-content { + background: #3b82f6; + color: #fff; + border-radius: 18px; + } + + .bubble-body { + align-items: flex-end; + } + } + + &.received { + .bubble-content { + background: var(--bg-tertiary); + color: var(--text-primary); + border-radius: 18px; + backdrop-filter: none; + } + + .bubble-body { + align-items: flex-start; + } + } + + &.system { + max-width: min(86%, 680px); + + .bubble-avatar { + display: none; + } + + .bubble-content { + background: color-mix(in srgb, var(--bg-tertiary) 70%, transparent); + color: var(--text-tertiary); + border-radius: 999px; + padding: 6px 12px; + text-align: center; + } + } + + &.emoji, + &.image { + max-width: min(82%, 760px); + + .bubble-content { + background: transparent; + padding: 0; + } + } + + &.voice.sent .bubble-content { + background: var(--bg-tertiary); + color: var(--text-primary); + } +} + +.message-bubble .bubble-content:has(> .link-message), +.message-bubble .bubble-content:has(> .card-message), +.message-bubble .bubble-content:has(> .chat-record-message), +.message-bubble .bubble-content:has(> .solitaire-message), +.message-bubble .bubble-content:has(> .official-message), +.message-bubble .bubble-content:has(> .channel-video-card), +.message-bubble .bubble-content:has(> .location-message), +.message-bubble .bubble-content:has(> .hongbao-message), +.message-bubble .bubble-content:has(> .transfer-message), +.message-bubble .bubble-content:has(> .gift-message), +.message-bubble .bubble-content:has(> .miniapp-message), +.message-bubble .bubble-content:has(> .file-message) { + background: transparent !important; + padding: 0 !important; + border: 0 !important; + box-shadow: none !important; +} + +.sender-name { + color: var(--text-tertiary); + font-size: 12px; + margin-bottom: 5px; +} + +.quoted-message { + background: transparent; + border-left: 2px solid color-mix(in srgb, var(--primary) 62%, var(--text-tertiary)); + border-radius: 0; + padding: 2px 0 2px 10px; + color: var(--text-secondary); +} + +.message-bubble.sent .quoted-message { + background: color-mix(in srgb, #fff 12%, transparent); + border-left-color: color-mix(in srgb, #fff 62%, #3b82f6); + + .quoted-sender, + .quoted-text { + color: color-mix(in srgb, #fff 82%, #3b82f6); + } +} + +.link-message, +.card-message, +.chat-record-message, +.solitaire-message, +.official-message, +.channel-video-card, +.location-message, +.miniapp-message, +.file-message { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: var(--shadow-sm); +} + +.link-message, +.appmsg-rich-card { + width: min(320px, calc(100vw - 112px)); + overflow: hidden; + + &:hover { + background: var(--bg-hover); + border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color)); + } + + .link-thumb, + .link-thumb-placeholder { + border-radius: 8px; + } +} + +.message-bubble.sent .link-message, +.message-bubble.sent .card-message, +.message-bubble.sent .miniapp-message, +.message-bubble.sent .appmsg-rich-card { + background: var(--card-bg); + border-color: var(--border-color); + + .card-name, + .miniapp-title, + .link-title { + color: var(--text-primary); + } + + .card-label, + .miniapp-label, + .link-desc, + .appmsg-url-line { + color: var(--text-secondary); + } +} + +.hongbao-message, +.transfer-message, +.gift-message { + width: min(280px, calc(100vw - 112px)); + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 14px; + box-shadow: var(--shadow-sm); + color: var(--text-primary); +} + +.hongbao-message, +.transfer-message { + padding: 14px; + + .hongbao-icon, + .transfer-icon { + width: 38px; + height: 38px; + border-radius: 12px; + display: grid; + place-items: center; + flex-shrink: 0; + } + + .hongbao-icon { + background: #ef4444; + } + + .transfer-icon { + background: #f59e0b; + } + + .hongbao-info, + .transfer-info { + color: var(--text-primary); + } + + .hongbao-greeting, + .transfer-amount { + color: var(--text-primary); + text-shadow: none; + } + + .hongbao-label, + .transfer-desc, + .transfer-memo, + .transfer-label { + color: var(--text-secondary); + opacity: 1; + text-shadow: none; + } +} + +.transfer-message.received .transfer-icon { + background: #64748b; +} + +.gift-message { + padding: 12px; + + .gift-info { + color: var(--text-primary); + } + + .gift-wish, + .gift-price { + color: var(--text-primary); + } + + .gift-label { + color: var(--text-secondary); + opacity: 1; + } +} + +[data-mode="dark"] { + .hongbao-message, + .transfer-message, + .gift-message { + background: #2b2b2b; + border-color: rgba(255, 255, 255, 0.08); + } + + .message-bubble.received .bubble-content, + .message-bubble.voice.sent .bubble-content { + background: #2f2f2f; + } +} + +.chat-input-area { + position: absolute; + left: max(18px, env(safe-area-inset-left)); + right: max(18px, env(safe-area-inset-right)); + bottom: calc(14px + env(safe-area-inset-bottom)); + z-index: 5; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + pointer-events: none; + + &::before { + content: ''; + position: absolute; + left: -18px; + right: -18px; + bottom: -14px; + height: 86px; + background: linear-gradient(to top, var(--bg-primary) 62%, transparent); + pointer-events: none; + z-index: -1; + } +} + +.chat-input-search-btn, +.chat-input-shell { + height: 44px; + border: 1px solid var(--border-color); + background: color-mix(in srgb, var(--bg-primary) 92%, transparent); + color: var(--text-secondary); + box-shadow: var(--shadow-sm); + backdrop-filter: blur(14px); +} + +.chat-input-search-btn { + width: 44px; + border-radius: 12px; + display: grid; + place-items: center; + cursor: pointer; + pointer-events: auto; + + &:hover:not(:disabled) { + background: var(--bg-hover); + color: var(--text-primary); + } + + &:disabled { + cursor: default; + opacity: 0.72; + } +} + +.chat-input-shell { + width: min(680px, 100%); + border-radius: 14px; + padding: 0 14px; + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; +} + +.scroll-to-bottom { + bottom: 74px; + background: color-mix(in srgb, var(--bg-primary) 92%, transparent); + color: var(--text-primary); + border: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + } +} + +@media (max-width: 720px) { + .message-header { + padding-inline: 14px; + + .header-actions { + gap: 0; + } + + .icon-btn { + width: 32px; + height: 32px; + } + } + + .message-list { + padding-inline: 14px; + } + + .message-bubble { + max-width: 88%; + } + + .chat-input-area { + left: 12px; + right: 12px; + } +} diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 6194a5f..e8b320f 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -29,6 +29,9 @@ import { onSingleExportDialogStatus, requestExportSessionStatus } from '../services/exportBridge' +import ChatHeader from './Chat/ChatHeader' +import ChatInputArea from './Chat/ChatInputArea' +import ChatMessageBubble from './Chat/ChatMessageBubble' import '../styles/batchTranscribe.scss' import './ChatPage.scss' @@ -6988,155 +6991,62 @@ function ChatPage(props: ChatPageProps) { ) : currentSession ? ( <> -
- -
-

{currentSession.displayName || currentSession.username}

- {isCurrentSessionGroup && ( -
群聊
- )} -
-
- {!standaloneSessionWindow && isCurrentSessionGroup && ( - - )} - {isCurrentSessionGroup && ( - - )} - {!standaloneSessionWindow && ( - - )} - {!standaloneSessionWindow && isCurrentSessionPrivateSnsSupported && ( - - )} - {!standaloneSessionWindow && ( - - )} - {!standaloneSessionWindow && ( - - )} -
- -
- {showJumpPopover && createPortal( -
- setShowJumpPopover(false)} - onSelect={handleJumpDateSelect} - messageDates={messageDates} - hasLoadedMessageDates={hasLoadedMessageDates} - messageDateCounts={messageDateCounts} - loadingDates={loadingDates} - loadingDateCounts={loadingDateCounts} - style={{ position: 'static', top: 'auto', right: 'auto' }} - /> -
, - document.body - )} - - - {!shouldHideStandaloneDetailButton && ( - - )} -
-
+ setShowJumpPopover(false)} + onSelect={handleJumpDateSelect} + messageDates={messageDates} + hasLoadedMessageDates={hasLoadedMessageDates} + messageDateCounts={messageDateCounts} + loadingDates={loadingDates} + loadingDateCounts={loadingDateCounts} + style={{ position: 'static', top: 'auto', right: 'auto' }} + /> + , + document.body + )} {isPreparingExportDialog && exportPrepareHint && (
@@ -7292,6 +7202,7 @@ function ChatPage(props: ChatPageProps) { 回到底部
+ {/* 群成员面板 */} {showGroupMembersPanel && isCurrentSessionGroup && ( @@ -10696,115 +10607,57 @@ function MessageBubble({ return
{renderTextWithEmoji(cleanedParsedContent)}
} - return ( - <> - {showTime && ( -
- {formatTime(message.createTime)} + const systemAlertPortal = systemAlert ? createPortal( +
setSystemAlert(null)} style={{ zIndex: 99999 }}> +
e.stopPropagation()} style={{ maxWidth: '400px' }}> +
+
- )} -
{ - if (isSelectionMode) { - e.stopPropagation() - onToggleSelection?.(messageKey, e.shiftKey) - } - }} - > - {isSelectionMode && !isSent && ( -
- {isSelected && } -
- )} - -
onContextMenu?.(e, message)} - > -
- -
-
- {/* 群聊中显示发送者名称 */} - {isGroupChat && !isSent && ( -
- {resolvedSenderName || '群成员'} -
- )} - {renderContent()} -
+
+

{systemAlert.title}

+

+ {systemAlert.message} +

+
+
+
- - {isSelectionMode && isSent && ( -
- {isSelected && } -
- )} - {systemAlert && createPortal( -
setSystemAlert(null)} style={{ zIndex: 99999 }}> -
e.stopPropagation()} style={{ maxWidth: '400px' }}> -
- -
-
-

{systemAlert.title}

-

- {systemAlert.message} -

-
-
- -
-
-
, - document.body - )}
- +
, + document.body + ) : null + + return ( + + {renderContent()} + ) }