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

+14 -7
View File
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useSession } from '../app/session';
import { Cover } from '../entities/catalog';
import { usePlayer } from '../player/context';
import { formatDuration } from '../shared/lib/format';
import { searchIcon } from '../shared/ui/icons';
import { cx, Icon } from '../shared/ui/ui';
@@ -36,6 +37,7 @@ export function SearchBox({
}) {
const { t } = useTranslation();
const session = useSession();
const player = usePlayer();
const navigate = useNavigate();
const listId = useId();
const inputRef = useRef<HTMLInputElement>(null);
@@ -118,8 +120,7 @@ export function SearchBox({
title: tr.title,
subtitle: `${tr.artists.map((a) => a.name).join(', ')} · ${formatDuration(tr.duration_ms)}`,
cover: tr.album,
// Воспроизведение — M3; пока трек открывает свой альбом.
go: () => void navigate({ to: '/album/$id', params: { id: tr.album.id } }),
go: () => player.playList([tr], 0, { type: 'search', id: debounced, label: debounced }),
});
}
@@ -149,7 +150,8 @@ export function SearchBox({
}
};
const showList = open && debounced !== '' && data !== undefined;
const showPopover = open && debounced !== '';
const showList = showPopover && data !== undefined;
const groups: Item['group'][] = ['artists', 'albums', 'tracks'];
const optionId = (i: number) => `${listId}-${i}`;
@@ -188,12 +190,17 @@ export function SearchBox({
<kbd className={s.kbd} aria-hidden>
/
</kbd>
{showList && (
{showPopover && (
<div className={s.popover}>
{data.corrected_query && (
{showPopover && !showList && (
<div className={s.nothing} role="status">
{t('search.searching')}
</div>
)}
{data?.corrected_query && (
<div className={s.corrected}>{t('search.corrected', { query: data.corrected_query })}</div>
)}
<div id={listId} role="listbox" aria-label={t('search.label')}>
{showList && <div id={listId} role="listbox" aria-label={t('search.label')}>
{items.length === 0 && <div className={s.nothing}>{t('search.nothing')}</div>}
{groups.map((g) => {
const groupItems = items.map((it, i) => ({ it, i })).filter(({ it }) => it.group === g);
@@ -221,7 +228,7 @@ export function SearchBox({
</div>
);
})}
</div>
</div>}
<button
type="button"
className={s.all}