feat(design): reorder without a drag everywhere a grip drags (WCAG 2.5.7, E28 S3e4a)
One rule, two forms: useOrderItems (Top / Up / Down / Bottom rows of a row menu) and OrderButtons (four buttons with the place, or a compact Up / Down pair). A move that would go nowhere is shown off. - YouTube playlist rows: the four moves now come from useOrderItems. - Plex playlist rows: a block among the blocks, an episode within its show, off while a write is in flight; drag and menu share moveBlock/moveEpisode. - Saved views editor: the selected view's four buttons (owner: A), and the grip's keyboard pick-up (its instructions promised one it did not have). - Side panel groups in edit mode: an Up / Down pair (owner: A); a group the stored order does not hold yet moves too; the name wraps there. - The grip's touch-action: none is now test-held.
This commit is contained in:
@@ -18,19 +18,7 @@ import {
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowDownToLine,
|
||||
ArrowUp,
|
||||
ArrowUpToLine,
|
||||
Check,
|
||||
FolderInput,
|
||||
History,
|
||||
ListPlus,
|
||||
Play,
|
||||
X,
|
||||
Youtube,
|
||||
} from "lucide-react";
|
||||
import { Check, FolderInput, History, ListPlus, Play, X, Youtube } from "lucide-react";
|
||||
import { api, type Playlist, type PlaylistDetail, type Video } from "../lib/api";
|
||||
import { notify } from "../lib/notifications";
|
||||
import { notifyActionError } from "../lib/actionErrors";
|
||||
@@ -60,6 +48,7 @@ import { filterRows, isActive, type Column, type FilterMap } from "../lib/tableC
|
||||
import { GRIP_KEY, playlistColumns } from "./playlistColumns";
|
||||
import { useSortableA11y, useSortableItem } from "./ui/sortableA11y";
|
||||
import { DragHandle } from "./ui/DragHandle";
|
||||
import { useOrderItems } from "./ui/useOrderItems";
|
||||
import { Input } from "./ui/Input";
|
||||
import { CheckRow } from "./ui/NativeControls";
|
||||
import { useReturnFocus } from "./ui/useReturnFocus";
|
||||
@@ -561,13 +550,11 @@ export default function Playlists() {
|
||||
// `order.set` (undoable + persisted), not a direct API call. Gated by the same rule as the drag
|
||||
// handle: a view (sort/group/filter) is not the stored order, so moving "up" inside one would
|
||||
// write something the user never saw.
|
||||
function moveInOrder(video: Video, to: "top" | "up" | "down" | "bottom") {
|
||||
const orderItems = useOrderItems();
|
||||
function moveInOrder(video: Video, to: number) {
|
||||
const from = items.findIndex((v) => v.id === video.id);
|
||||
if (from < 0) return;
|
||||
const target =
|
||||
to === "top" ? 0 : to === "bottom" ? items.length - 1 : to === "up" ? from - 1 : from + 1;
|
||||
if (target === from || target < 0 || target >= items.length) return;
|
||||
order.set(arrayMove(items, from, target)); // onApply persists
|
||||
if (from < 0 || to === from) return;
|
||||
order.set(arrayMove(items, from, to)); // onApply persists
|
||||
}
|
||||
|
||||
async function saveRename() {
|
||||
@@ -687,24 +674,13 @@ export default function Playlists() {
|
||||
note: t("playlists.moveNoTargets"),
|
||||
onSelect: () => {},
|
||||
},
|
||||
...(
|
||||
[
|
||||
["top", t("playlists.orderTop"), <ArrowUpToLine className="w-4 h-4" />],
|
||||
["up", t("playlists.orderUp"), <ArrowUp className="w-4 h-4" />],
|
||||
["down", t("playlists.orderDown"), <ArrowDown className="w-4 h-4" />],
|
||||
["bottom", t("playlists.orderBottom"), <ArrowDownToLine className="w-4 h-4" />],
|
||||
] as const
|
||||
).map(([to, label, icon], i) => ({
|
||||
id: `order-${to}`,
|
||||
label,
|
||||
icon,
|
||||
separatorBefore: i === 0,
|
||||
// Same gate as the drag handle. The reason goes under the FIRST of the four rows
|
||||
// only — one explanation for the group, not the same line four times.
|
||||
disabled: !storedOrderEditable,
|
||||
note: i === 0 ? dragHint : undefined,
|
||||
onSelect: () => moveInOrder(ctxRow, to),
|
||||
})),
|
||||
// Same gate as the drag handle; the reason goes under the first of the four rows.
|
||||
...orderItems(
|
||||
items.findIndex((v) => v.id === ctxRow.id),
|
||||
items.length,
|
||||
(to) => moveInOrder(ctxRow, to),
|
||||
storedOrderEditable ? undefined : { reason: dragHint },
|
||||
),
|
||||
{
|
||||
id: "remove",
|
||||
label: t("playlists.removeItem"),
|
||||
|
||||
Reference in New Issue
Block a user