feat(design): the channel menu's Tags and Priority submenus (C1, E28 S3e3b)
Tags A-Z as in-place checks (the menu stays open for the next); Priority as the fixed steps 10 5 3 2 1 0, an off-scale current value ticked in its place. A pick re-sorts the list at once, moving only that row (placeByPriority, the server's order), and the table / card pager and the focus follow it (DataTable and useCardPager take a reveal seq, listNav.focusNavItem lands on the row's own "…", a virtualized card revealed first). ActionMenu rows take check: many | one. The menu reads the row fresh from the cache and keys its target by id.
This commit is contained in:
@@ -3,13 +3,15 @@ import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||
import { PopoverItem, PopoverSurface } from "./AnchoredPopover";
|
||||
import { closeThenAct, useAnchoredPopover } from "./anchoredPopoverHooks";
|
||||
import { IconButton } from "./IconButton";
|
||||
import { MenuCheck } from "./MenuCheck";
|
||||
import { Pressable } from "./Pressable";
|
||||
import { SURFACE } from "./surfaces";
|
||||
|
||||
// A reusable "⋯ more actions" overflow menu: a compact trigger that opens a list of one-shot actions.
|
||||
// Built on the R8 AnchoredPopover platform (arrows/typeahead/Escape/anchor-flip), same as ViewSwitcher
|
||||
// and LanguageSwitcher. Use it to keep occasional actions out of a crowded toolbar. The items are
|
||||
// fire-and-close (`onSelect`); a `disabled` item (e.g. a mutation already in flight) is inert.
|
||||
// fire-and-close (`onSelect`) — all but a `check: { kind: "many" }` row, which toggles in place; a
|
||||
// `disabled` item (e.g. a mutation already in flight) is inert.
|
||||
//
|
||||
// E10 S2 split the ITEM LIST out of the trigger (`MenuItemList`) so the ⋯ menu and the right-click
|
||||
// context menu (`ContextMenu`) render the same rows from the same item type. That is the whole point
|
||||
@@ -62,6 +64,11 @@ interface ActionItem extends MenuItemBase {
|
||||
* user's gesture (a task later, as `closeThenAct` runs every other row, it is refused). Only for
|
||||
* an action that opens nothing and moves no focus: that is what the deferral exists for. */
|
||||
inGesture?: boolean;
|
||||
/** A checkable row, its state shown in the icon slot. `many`: one of a set that can all be on (a
|
||||
* channel's tags) — a menuitemcheckbox that toggles IN PLACE: the menu stays open, so the next
|
||||
* tag is one click away and the check just set is seen. `one`: one choice of several (a priority
|
||||
* step) — a menuitemradio that picks and closes, like any other row. */
|
||||
check?: { kind: "many" | "one"; checked: boolean };
|
||||
items?: never;
|
||||
}
|
||||
|
||||
@@ -99,19 +106,29 @@ export function MenuItemList({
|
||||
<SubMenu item={it} onClose={onClose} />
|
||||
) : (
|
||||
<PopoverItem
|
||||
role="menuitem"
|
||||
role={
|
||||
it.check
|
||||
? it.check.kind === "many"
|
||||
? "menuitemcheckbox"
|
||||
: "menuitemradio"
|
||||
: "menuitem"
|
||||
}
|
||||
checked={it.check?.checked}
|
||||
disabled={it.disabled}
|
||||
label={it.label}
|
||||
title={it.title}
|
||||
// Any row may open a dialog (a confirm), so every row closes first — see closeThenAct.
|
||||
// A `many` row opens nothing and stays: the focus is still on it, nothing to hand back.
|
||||
onSelect={() => {
|
||||
if (it.check?.kind === "many") return it.onSelect();
|
||||
if (!it.inGesture) return closeThenAct(onClose, it.onSelect);
|
||||
it.onSelect();
|
||||
onClose();
|
||||
}}
|
||||
className={`${ROW}${it.danger ? " text-danger" : ""}`}
|
||||
>
|
||||
{it.icon && (
|
||||
{it.check && <MenuCheck kind={it.check.kind} on={it.check.checked} />}
|
||||
{!it.check && it.icon && (
|
||||
<span
|
||||
className={`shrink-0 ${it.danger ? "text-danger" : "text-muted"}${dim(it.disabled)}`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user