fix: CopiedToast not visible due to CSS transform context

Use createPortal to render toast at document.body level,
escaping framer-motion's transform on the tab content wrapper.
Remove unused useRef import and shareToastTapCopy i18n key.
This commit is contained in:
Fringg
2026-03-10 07:39:04 +03:00
parent 0322974ebd
commit 39bdf8b5c3
3 changed files with 22 additions and 37 deletions

View File

@@ -4249,7 +4249,6 @@
"shareModalActivateVia": "Activate via bot:", "shareModalActivateVia": "Activate via bot:",
"shareModalActivateViaCabinet": "Or via website:", "shareModalActivateViaCabinet": "Or via website:",
"copyMessage": "Copy message", "copyMessage": "Copy message",
"shareToastCopied": "Message copied", "shareToastCopied": "Message copied"
"shareToastTapCopy": "tap to copy again"
} }
} }

View File

@@ -4816,7 +4816,6 @@
"shareModalActivateVia": "Активировать через бота:", "shareModalActivateVia": "Активировать через бота:",
"shareModalActivateViaCabinet": "Или через сайт:", "shareModalActivateViaCabinet": "Или через сайт:",
"copyMessage": "Скопировать сообщение", "copyMessage": "Скопировать сообщение",
"shareToastCopied": "Сообщение скопировано", "shareToastCopied": "Сообщение скопировано"
"shareToastTapCopy": "нажмите чтобы скопировать ещё раз"
} }
} }

View File

@@ -1,4 +1,5 @@
import { useState, useMemo, useEffect, useCallback, useRef } from 'react'; import { useState, useMemo, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useNavigate, useSearchParams, Link } from 'react-router'; import { useNavigate, useSearchParams, Link } from 'react-router';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -953,43 +954,26 @@ function ActivateTabContent({ initialCode }: { initialCode?: string | null }) {
// Sub-components: My Gifts Tab // Sub-components: My Gifts Tab
// ============================================================ // ============================================================
function ShareToast({ message, onDismiss }: { message: string; onDismiss: () => void }) { function CopiedToast({ onDismiss }: { onDismiss: () => void }) {
const { t } = useTranslation(); const { t } = useTranslation();
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
useEffect(() => { useEffect(() => {
timerRef.current = setTimeout(onDismiss, 5000); const timer = setTimeout(onDismiss, 2000);
return () => { return () => clearTimeout(timer);
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [onDismiss]); }, [onDismiss]);
const handleClick = useCallback(async () => {
await copyToClipboard(message);
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(onDismiss, 2000);
}, [message, onDismiss]);
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 60 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 60 }} exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.25, ease: 'easeOut' }} transition={{ duration: 0.2, ease: 'easeOut' }}
className="fixed inset-x-0 bottom-6 z-50 mx-auto w-[calc(100%-2rem)] max-w-md px-1" className="fixed inset-x-0 bottom-6 z-50 flex justify-center"
> >
<button <div className="flex items-center gap-2 rounded-full border border-dark-700/50 bg-dark-900/95 px-5 py-2.5 shadow-2xl shadow-black/40 backdrop-blur-md">
type="button" <CheckIcon className="h-4 w-4 text-success-400" />
onClick={handleClick} <span className="text-sm font-medium text-success-400">{t('gift.shareToastCopied')}</span>
className="w-full rounded-2xl border border-dark-700/50 bg-dark-900/95 p-4 text-left shadow-2xl shadow-black/40 backdrop-blur-md transition-colors active:bg-dark-800/95" </div>
>
<div className="mb-2 flex items-center gap-2">
<CheckIcon className="h-4 w-4 text-success-400" />
<span className="text-xs font-bold text-success-400">{t('gift.shareToastCopied')}</span>
<span className="ml-auto text-xs text-dark-500">{t('gift.shareToastTapCopy')}</span>
</div>
<p className="line-clamp-4 whitespace-pre-line text-xs text-dark-300">{message}</p>
</button>
</motion.div> </motion.div>
); );
} }
@@ -1095,10 +1079,13 @@ function SentGiftCard({ gift }: { gift: SentGift }) {
</p> </p>
)} )}
{/* Share toast */} {/* Copied toast — portal to escape motion.div transform context */}
<AnimatePresence> {createPortal(
{showToast && <ShareToast message={buildShareMessage()} onDismiss={handleDismissToast} />} <AnimatePresence>
</AnimatePresence> {showToast && <CopiedToast onDismiss={handleDismissToast} />}
</AnimatePresence>,
document.body,
)}
</div> </div>
); );
} }