import { type ActionMenuItem } from "./ActionMenu"; import { menuGroups } from "./menuGroups"; import { pickSort, type SortOption, type SortState } from "../../lib/columnSort"; // The rows a page's background menu (./pageMenu) mirrors its toolbar with. Each takes the SAME // vocabulary and setter as the control it mirrors (a SortControl's options, a FilterSelect's options), // so the menu can never offer a choice the toolbar does not, or set it another way. /** "Sort ▸": the keys as a pick-one set, then the direction when the active key has one. Picking a * key keeps its direction, a new one starts at its default — `pickSort`, as the SortControl does. * `defaultLabel` leads with the "no sort" choice, for a control that offers one (a table's). */ export function sortSubmenu({ id, label, options, sort, onChange, ascLabel, descLabel, defaultLabel, }: { id: string; label: string; options: readonly SortOption[]; sort: SortState; onChange: (next: SortState) => void; ascLabel: string; descLabel: string; defaultLabel?: string; }): ActionMenuItem { const active = sort ? options.find((o) => o.key === sort.key) : undefined; const none: ActionMenuItem[] = defaultLabel ? [ { id: `${id}.none`, label: defaultLabel, check: { kind: "one", checked: !active }, onSelect: () => onChange(null), }, ] : []; const keys: ActionMenuItem[] = options.map((o) => ({ id: `${id}.${o.key}`, label: o.label, check: { kind: "one", checked: active?.key === o.key }, onSelect: () => onChange(pickSort([...options], sort, o.key)), })); const dirs: ActionMenuItem[] = active && sort && !active.directionless ? (["asc", "desc"] as const).map((dir) => ({ id: `${id}.dir.${dir}`, label: dir === "asc" ? ascLabel : descLabel, check: { kind: "one", checked: sort.dir === dir }, onSelect: () => onChange({ key: sort.key, dir }), })) : []; return { id, label, items: menuGroups([...none, ...keys], dirs) }; } /** "Label ▸" over one choice of several (a FilterSelect without `multiple`, a view switcher). */ export function choiceSubmenu({ id, label, options, selected, onPick, }: { id: string; label: string; options: readonly { value: V; label: string }[]; selected: V; onPick: (value: V) => void; }): ActionMenuItem { return { id, label, items: options.map((o) => ({ id: `${id}.${o.value}`, label: o.label, check: { kind: "one", checked: o.value === selected }, onSelect: () => onPick(o.value), })), }; } /** "Label ▸" over a set that can all be on (a `multiple` FilterSelect): each toggles in place. */ export function multiSubmenu({ id, label, options, selected, onChange, }: { id: string; label: string; options: readonly { value: V; label: string }[]; selected: readonly V[]; onChange: (next: V[]) => void; }): ActionMenuItem { return { id, label, items: options.map((o) => { const on = selected.includes(o.value); return { id: `${id}.${o.value}`, label: o.label, check: { kind: "many", checked: on }, onSelect: () => onChange(on ? selected.filter((v) => v !== o.value) : [...selected, o.value]), }; }), }; }