fix(ui): a disabled menu row says why it is off, under its label

The reason for a disabled menu row lived in a `title` on a natively disabled
button: no pointer events, so no tooltip, and roving focus skips the row —
four translated sentences reached nobody (the Watch later rename/delete
lock, "no other list to move to", the reorder gate). An aria-disabled row was
tried and reverted in E10's review. ActionMenu items now take a `note`,
rendered as a muted line under a disabled row's label: seen, and readable in
a screen reader's browse mode. Only the icon and label dim — row-level
opacity would dim the explanation too. The reorder group states its reason
once, under its first row.
This commit is contained in:
2026-09-19 07:57:20 +02:00
parent df7c3db085
commit bc138e3b37
4 changed files with 55 additions and 26 deletions
+5 -9
View File
@@ -772,17 +772,14 @@ export default function Playlists() {
})),
}
: // Kept visible and disabled rather than dropped: an action that vanishes teaches
// nobody why. KNOWN GAP: the `title` that carries the why currently reaches nobody
// either — see ActionMenu's `title` prop, where the two failed attempts at fixing
// that are recorded. The row is honest about being off; it is not yet honest about
// the reason.
// nobody why — and its `note` says the why, under the label.
{
id: "move",
label: t("playlists.moveTo"),
icon: <FolderInput className="w-4 h-4" />,
separatorBefore: !recentMoveTarget,
disabled: true,
title: t("playlists.moveNoTargets"),
note: t("playlists.moveNoTargets"),
onSelect: () => {},
},
...(
@@ -797,11 +794,10 @@ export default function Playlists() {
label,
icon,
separatorBefore: i === 0,
// Same gate as the drag handle. NOTE the drag handle explains itself and this row
// does not: the handle is `aria-disabled` (focusable, tooltip-capable) while a menu
// row cannot be — see ActionMenu's `title` prop for why.
// 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,
title: storedOrderEditable ? undefined : dragHint,
note: i === 0 ? dragHint : undefined,
onSelect: () => moveInOrder(ctxRow, to),
})),
{