feat(playlists): act on the selected rows from the toolbar, and undo either
While rows are selected the toolbar's second row is the band: the count, the last target as one click, Move to, Remove, Clear selection. Both write in one request and undo back to the places; a video the target held before stays there. Delete takes the selection when there is one. A failure puts the rows back ticked. A row's own move goes through the same write, so it undoes too, and the single-row move route and service function are gone. A playlist row drops its grip below a 24rem table: the row menu keeps the moves.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import type { ReactNode, Ref } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ChevronDown, FolderInput, History, 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. */
|
||||
export function SelectionBand({
|
||||
ref,
|
||||
count,
|
||||
hidden,
|
||||
recentName,
|
||||
onMoveRecent,
|
||||
moveMenuProps,
|
||||
moveMenuOpen,
|
||||
onRemove,
|
||||
onClear,
|
||||
onClearView,
|
||||
trailing,
|
||||
}: {
|
||||
ref: Ref<HTMLDivElement>;
|
||||
count: number;
|
||||
hidden: number;
|
||||
/** The list the last move went to: one click away, absent when there is none to repeat. */
|
||||
recentName: string | null;
|
||||
onMoveRecent: () => void;
|
||||
moveMenuProps: Record<string, unknown>;
|
||||
moveMenuOpen: boolean;
|
||||
onRemove: () => void;
|
||||
onClear: () => void;
|
||||
/** The view's reset, which the row this one replaces offers. */
|
||||
onClearView?: () => void;
|
||||
trailing: ReactNode;
|
||||
}) {
|
||||
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">
|
||||
<span className="font-medium">{t("playlists.selectedCount", { count })}</span>
|
||||
{hidden > 0 && (
|
||||
<span className="text-muted">
|
||||
{" · "}
|
||||
<span>{t("playlists.selectedHidden", { count: hidden })}</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{recentName != null && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={none}
|
||||
onClick={onMoveRecent}
|
||||
icon={<History className="w-3.5 h-3.5" />}
|
||||
>
|
||||
<span className="block max-w-48 truncate">
|
||||
{t("playlists.moveToRecent", { name: recentName })}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={none}
|
||||
aria-expanded={moveMenuOpen}
|
||||
icon={<FolderInput className="w-3.5 h-3.5" />}
|
||||
{...moveMenuProps}
|
||||
>
|
||||
{t("playlists.moveSelected")}
|
||||
<ChevronDown className="w-3.5 h-3.5" aria-hidden="true" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline-danger"
|
||||
size="xs"
|
||||
disabled={none}
|
||||
onClick={onRemove}
|
||||
icon={<X className="w-3.5 h-3.5" />}
|
||||
>
|
||||
{t("playlists.removeSelected")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="link-muted"
|
||||
onClick={onClear}
|
||||
className="text-xs underline underline-offset-2"
|
||||
>
|
||||
{t("playlists.clearSelection")}
|
||||
</Button>
|
||||
{onClearView && (
|
||||
<Button
|
||||
variant="link-muted"
|
||||
onClick={onClearView}
|
||||
className="text-xs underline underline-offset-2"
|
||||
>
|
||||
{t("playlists.clearView")}
|
||||
</Button>
|
||||
)}
|
||||
<div className="ml-auto">{trailing}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user