feat(web): polish catalog motion, empty states, and mobile now-playing
release / desktop (Linux и Windows в релиз) (push) Successful in 16m22s
release / prune (старые файлы и прогоны) (push) Successful in 6s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m23s
release / release (версия и заметки) (push) Successful in 6s
ci / ts / ts (packages + web) (push) Failing after 5m25s
release / android (APK в релиз) (push) Successful in 4m18s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mrleo1nidandCursor committed 2026-10-22 09:53:37 +03:00
1 parent 39aaaed969
commit 72567acd28
31 files changed
+939 -131

No files matched your search

+19 -7
View File
@@ -1,7 +1,7 @@
import { queueIcon } from '../shared/ui/icons';
import { unwrap } from '@pipu/core';
import { useQuery } from '@tanstack/react-query';
import { Link, Outlet, useNavigate } from '@tanstack/react-router';
import { Link, Outlet, useNavigate, useRouterState } from '@tanstack/react-router';
import { Suspense, useCallback, useState, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
@@ -30,7 +30,7 @@ import {
chevronRightIcon,
storageFillIcon,
storageIcon, inboxFillIcon, inboxIcon, myUploadsFillIcon, myUploadsIcon, uploadFillIcon, uploadIcon, tuneFillIcon, tuneIcon} from '../shared/ui/icons';
import { Avatar, cx, Dialog, Icon, Logo, Menu } from '../shared/ui/ui';
import { Avatar, cx, Dialog, Icon, Logo, Menu, PageSkeleton } from '../shared/ui/ui';
import { LanguageSwitch } from './LanguageSwitch';
import { PlayerProvider, usePlayer, usePlayerState } from '../player/context';
import { Ambient } from './Ambient';
@@ -226,6 +226,9 @@ function Shell() {
return (
<div className={cx(s.shell, island && s.island)}>
{user.ui_prefs.ambient !== false && <ShellAmbient />}
<a href="#content" className={s.skip}>
{t('nav.skip')}
</a>
<header className={s.top}>
<div className={s.topLeft}>
<Link to="/" className={s.logoLink}>
@@ -284,17 +287,26 @@ function Shell() {
</Dialog>
)}
<main className={s.content}>
<Suspense fallback={null}>
<Outlet />
</Suspense>
</main>
<ShellMain />
<PlayerBar variant={island ? 'island' : 'bar'} />
</div>
);
}
function ShellMain() {
const key = useRouterState({ select: (st) => st.location.pathname });
return (
<main id="content" className={s.content} tabIndex={-1}>
<div key={key} className={s.routeIn}>
<Suspense fallback={<PageSkeleton />}>
<Outlet />
</Suspense>
</div>
</main>
);
}
/** Третье пятно фона — в цвете обложки текущего трека. */
function ShellAmbient() {
const player = usePlayer();