fix(ui): контент, пришедший из API после mount, оставался невидимым (stagger)

Оркестрация staggerContainer/staggerItem у framer-motion раздаёт детям
команду «показаться» один раз — при анимации родителя на mount. Дети,
смонтированные позже (когда resolve'ится запрос react-query), в
оркестрацию не попадают и навсегда остаются в initial (opacity: 0, y: 8).

Симптом: после жёсткой перезагрузки (F5) на странице виден только
заголовок — карточки есть в DOM, но прозрачны. При SPA-навигации кэш
react-query тёплый, дети монтируются вместе с родителем — поэтому баг
проявлялся только при холодном заходе и маскировался на проде (нет
StrictMode, same-origin API, тёплый кэш почти всегда выигрывают гонку
с ~300-мс окном анимации).

Два способа фикса в зависимости от формы страницы:

1. key={isLoading ? 'loading' : 'ready'} на контейнере — перемонтирование
   после загрузки, stagger проигрывается заново уже со всеми детьми.
   Для страниц, где весь контент асинхронный (каскад сохраняется):
   - ConnectedAccounts — карточки провайдеров
   - SavedCards — список сохранённых карт

2. Явные initial="initial" animate="animate" на условном staggerItem —
   элемент анимирует себя сам, не дожидаясь оркестрации родителя. Для
   страниц, где асинхронны только 1-2 опциональные карточки (не
   перезапускает анимацию всей страницы на каждый resolve):
   - Balance — «Способы пополнения» и ссылка «Сохранённые карты»
   - Support — карточка «Написать нам» (support_type='both')
   - Profile — реферальный виджет

Попутно: в Wheel у контейнера истории спинов стояли несуществующие
варианты initial="hidden"/animate="show" (у staggerContainer/staggerItem
ключи — initial/animate/exit), из-за чего stagger там молча не работал
вовсе; имена исправлены.

Проверено и не требует правок: TopUpAmount (early-return до загрузки
методов), TopUpMethodSelect и MergeAccounts (динамический контент —
обычный HTML внутри всегда смонтированных staggerItem), вложенный
контейнер транзакций в Balance (самоанимирующийся).
This commit is contained in:
kewldan
2026-07-12 19:08:09 +03:00
parent 20565b8f59
commit adaaa23da3
6 changed files with 28 additions and 10 deletions

View File

@@ -324,9 +324,11 @@ export default function Profile() {
</Card>
</motion.div>
{/* Referral Link Widget */}
{/* Referral Link Widget — self-animated: mounts after the referral queries
resolve, when the parent stagger orchestration has already finished and
would leave it stuck at opacity 0 */}
{referralTerms?.is_enabled && referralLink && (
<motion.div variants={staggerItem}>
<motion.div variants={staggerItem} initial="initial" animate="animate">
<Card>
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-dark-100">{t('referral.yourLink')}</h2>