fix(playlists): the selection band keeps to one row on a tablet, and a tick moves no row

Below xl the band's buttons are glyphs named in their tooltips, and the row it
replaces stays in the same grid cell, invisible, so the first tick leaves the
list where the second tap expects it. The test's row menu asked for the pins
unmocked, and the offline toast could land before the check: mocked.
This commit is contained in:
2026-11-01 22:44:32 +01:00
parent efbbb23525
commit bf54a3ce5f
6 changed files with 144 additions and 44 deletions
@@ -1,11 +1,13 @@
import type { ReactNode, Ref } from "react";
import { useTranslation } from "react-i18next";
import { ChevronDown, FolderInput, History, X } from "lucide-react";
import { ChevronDown, FolderInput, History, ListRestart, Square, X } from "lucide-react";
import { Button } from "../../components/ui/Button";
/** The playlist's second toolbar row while rows are selected: how many, and what can be done to them.
* `count` is the rows shown, what an action covers; `hidden` the selected ones a filter hides, which
* it leaves alone. With only hidden ones the actions stay in place, off, and the way out stays on. */
* it leaves alone. With only hidden ones the actions stay in place, off, and the way out stays on.
* Below `xl` the buttons are glyphs named in their tooltips: in words the row wraps there, and the
* first tick would push the list from under the second tap. */
export function SelectionBand({
ref,
count,
@@ -36,13 +38,24 @@ export function SelectionBand({
const { t } = useTranslation();
const none = count === 0;
return (
<div ref={ref} className="flex items-center gap-2 mt-1 flex-wrap">
<span role="status" className="text-xs">
<div ref={ref} className="flex items-center gap-1 mt-1 flex-wrap xl:gap-2">
<span role="status" className="text-xs whitespace-nowrap">
<span className="font-medium">{t("playlists.selectedCount", { count })}</span>
{hidden > 0 && (
<span className="text-muted">
{" · "}
<span>{t("playlists.selectedHidden", { count: hidden })}</span>
<span aria-hidden="true" className="hidden xl:inline">
{" · "}
</span>
<span className="sr-only xl:not-sr-only">
{t("playlists.selectedHidden", { count: hidden })}
</span>
<span
aria-hidden="true"
title={t("playlists.selectedHidden", { count: hidden })}
className="ml-1 xl:hidden"
>
{t("playlists.selectedHiddenShort", { n: hidden })}
</span>
</span>
)}
</span>
@@ -52,9 +65,10 @@ export function SelectionBand({
size="xs"
disabled={none}
onClick={onMoveRecent}
label={t("playlists.moveToRecent", { name: recentName })}
icon={<History className="w-3.5 h-3.5" />}
>
<span className="block max-w-48 truncate">
<span className="hidden max-w-48 truncate xl:inline">
{t("playlists.moveToRecent", { name: recentName })}
</span>
</Button>
@@ -64,10 +78,11 @@ export function SelectionBand({
size="xs"
disabled={none}
aria-expanded={moveMenuOpen}
label={t("playlists.moveSelected")}
icon={<FolderInput className="w-3.5 h-3.5" />}
{...moveMenuProps}
>
{t("playlists.moveSelected")}
<span className="hidden xl:inline">{t("playlists.moveSelected")}</span>
<ChevronDown className="w-3.5 h-3.5" aria-hidden="true" />
</Button>
<Button
@@ -75,24 +90,31 @@ export function SelectionBand({
size="xs"
disabled={none}
onClick={onRemove}
label={t("playlists.removeSelected")}
icon={<X className="w-3.5 h-3.5" />}
>
{t("playlists.removeSelected")}
<span className="hidden xl:inline">{t("playlists.removeSelected")}</span>
</Button>
<Button
variant="link-muted"
variant="ghost"
size="xs"
onClick={onClear}
className="text-xs underline underline-offset-2"
label={t("playlists.clearSelection")}
icon={<Square className="w-3.5 h-3.5 xl:hidden" />}
className="xl:underline xl:underline-offset-2"
>
{t("playlists.clearSelection")}
<span className="hidden xl:inline">{t("playlists.clearSelection")}</span>
</Button>
{onClearView && (
<Button
variant="link-muted"
variant="ghost"
size="xs"
onClick={onClearView}
className="text-xs underline underline-offset-2"
label={t("playlists.clearView")}
icon={<ListRestart className="w-3.5 h-3.5 xl:hidden" />}
className="xl:underline xl:underline-offset-2"
>
{t("playlists.clearView")}
<span className="hidden xl:inline">{t("playlists.clearView")}</span>
</Button>
)}
<div className="ml-auto">{trailing}</div>