From 9c70b8a11fbd781fa376f12732efbb582131c41e Mon Sep 17 00:00:00 2001 From: Senko-san Date: Sat, 13 Jun 2026 17:37:17 +0300 Subject: [PATCH] feat(queue): add per-track overflow menu in queue panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the bare "remove" cross on each queue row with a ghost three-dot menu offering Play now, Move next (reposition right after the current track), Track info, and Remove — consolidating the previously separate info button into the same menu. --- src/components/player/QueuePanel.tsx | 59 +++++++++++++++++++--------- src/i18n/locales/en.ts | 7 ++++ src/i18n/locales/ru.ts | 7 ++++ 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/components/player/QueuePanel.tsx b/src/components/player/QueuePanel.tsx index 4a85153..344efdc 100644 --- a/src/components/player/QueuePanel.tsx +++ b/src/components/player/QueuePanel.tsx @@ -1,4 +1,12 @@ -import { Slider, Badge } from '@olly/modern-sk'; +import { + Slider, + Badge, + Menu, + MenuTrigger, + MenuContent, + MenuItem, + IconButton, +} from '@olly/modern-sk'; import { useTranslation } from 'react-i18next'; import { Icon } from '../common/Icon'; import { ArtTile } from '../common/ArtTile'; @@ -7,6 +15,7 @@ import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch'; import { goToIndex, removeFromQueue, + moveInQueue, clearQueue, type QueueEntry, } from '../../store/slices/queue'; @@ -112,6 +121,11 @@ export function QueuePanel() { isCurrent={index === queue.currentIndex} isPlaying={isPlaying} onPlay={() => dispatch(goToIndex(index))} + onMoveNext={() => + dispatch( + moveInQueue({ from: index, to: queue.currentIndex + 1 }), + ) + } onRemove={() => dispatch(removeFromQueue(index))} /> ))} @@ -137,12 +151,14 @@ function QueueRow({ isCurrent, isPlaying, onPlay, + onMoveNext, onRemove, }: { entry: QueueEntry; isCurrent: boolean; isPlaying: boolean; onPlay: () => void; + onMoveNext: () => void; onRemove: () => void; }) { const { t } = useTranslation(); @@ -174,24 +190,29 @@ function QueueRow({
{resolved?.title ?? entry.title}
{resolved?.artistName ?? entry.artistName}
- {isCurrent && ( - - )} - + + + + ⋯ + + + + {t('queue.menu.playNow')} + {!isCurrent && ( + + {t('queue.menu.moveNext')} + + )} + dispatch(openTrackInfo(entry.trackId))}> + {t('queue.menu.info')} + + {t('queue.menu.remove')} + + ); } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 0af765d..32bfc59 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -147,6 +147,13 @@ const en = { loadingMore: 'Loading more from radio…', doubleClickPlay: 'Double-click to play', removeFromQueue: 'Remove from queue', + menu: { + options: 'Track options', + playNow: 'Play now', + moveNext: 'Move next', + info: 'Track info', + remove: 'Remove from queue', + }, }, track: { menu: { diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 6731b5a..94be15f 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -149,6 +149,13 @@ const ru: Translations = { loadingMore: 'Загрузка радио…', doubleClickPlay: 'Двойной клик для воспроизведения', removeFromQueue: 'Убрать из очереди', + menu: { + options: 'Параметры трека', + playNow: 'Воспроизвести сейчас', + moveNext: 'Сделать следующим', + info: 'Информация о треке', + remove: 'Убрать из очереди', + }, }, track: { menu: {