@charcuterie/ui 3.25.0 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import type { IntentName } from "@charcuterie/tokens";
2
2
  import type { ReactNode } from "react";
3
3
  import type { CardAccentEdge } from "../Card/cardAccentEdge.ts";
4
+ import type { MenuProps } from "../Menu/Menu.tsx";
4
5
  type BoardItemFields = {
5
6
  /** What the bar means, in words. "Priority 0", "Movies and Shows". */
6
7
  accentLabel?: string;
@@ -39,6 +40,12 @@ type BoardItemFields = {
39
40
  title: string;
40
41
  /** The trailing cluster: an assignee avatar, an elapsed-time chip. */
41
42
  trailing?: ReactNode;
43
+ /**
44
+ * Extra actions in the card's existing menu, after the lane moves.
45
+ * A separator is added when both sets have entries, so a consumer
46
+ * can add a card action without creating a second trigger.
47
+ */
48
+ menuItems?: MenuProps["items"];
42
49
  };
43
50
  /**
44
51
  * The leading colour bar, in one of two shapes — and the second one
@@ -1 +1 @@
1
- {"version":3,"file":"BoardCard.d.ts","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAc/D,KAAK,eAAe,GAAG;IACrB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,KAAK,eAAe,GAChB;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,UAAU,CAAA;CAAE,GACjD;IAAE,UAAU,EAAE,cAAc,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,KAAK,kBAAkB,GACnB;IACE,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,YAAY,CAAC,EAAE,KAAK,CAAA;CACrB,GACD;IACE,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;;;;OAQG;IACH,YAAY,EAAE,SAAS,CAAA;CACxB,CAAA;AAEL,MAAM,MAAM,SAAS,GAAG,eAAe,GACrC,eAAe,GACf,kBAAkB,CAAA;AAEpB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,0DAA0D;IAC1D,WAAW,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACtD,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,aAAa,EAAE,WAAW,GAAG,OAAO,CAAA;QACpC,SAAS,EAAE,MAAM,CAAA;KAClB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,SAAS,GAAI,wEAOvB,cAAc,KAAG,SAqOnB,CAAA"}
1
+ {"version":3,"file":"BoardCard.d.ts","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAS/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAMjD,KAAK,eAAe,GAAG;IACrB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,KAAK,eAAe,GAChB;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,UAAU,CAAA;CAAE,GACjD;IAAE,UAAU,EAAE,cAAc,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,KAAK,kBAAkB,GACnB;IACE,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,YAAY,CAAC,EAAE,KAAK,CAAA;CACrB,GACD;IACE,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;;;;OAQG;IACH,YAAY,EAAE,SAAS,CAAA;CACxB,CAAA;AAEL,MAAM,MAAM,SAAS,GAAG,eAAe,GACrC,eAAe,GACf,kBAAkB,CAAA;AAEpB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,0DAA0D;IAC1D,WAAW,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACtD,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,aAAa,EAAE,WAAW,GAAG,OAAO,CAAA;QACpC,SAAS,EAAE,MAAM,CAAA;KAClB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,SAAS,GAAI,wEAOvB,cAAc,KAAG,SAiQnB,CAAA"}
@@ -79,6 +79,30 @@ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
79
79
  */
80
80
  export const BoardCard = ({ item, laneLabel, moveIcon, moveTargets, onMoveToLane, onStartDrag, }) => {
81
81
  const [isMenuVisible, setIsMenuVisible] = useState(false);
82
+ const isMovable = onMoveToLane != null && moveTargets.length > 0;
83
+ const hasMenuItems = (item.menuItems?.length ?? 0) > 0;
84
+ const hasMenu = onMoveToLane != null && (isMovable || hasMenuItems);
85
+ const menuItems = [
86
+ ...(isMovable
87
+ ? moveTargets.map((target) => ({
88
+ key: target.key,
89
+ label: target.label,
90
+ onSelect: () => {
91
+ onMoveToLane?.(target.key);
92
+ },
93
+ }))
94
+ : []),
95
+ ...(hasMenuItems && isMovable
96
+ ? [
97
+ {
98
+ key: "board-extra-actions",
99
+ type: "separator",
100
+ },
101
+ ]
102
+ : []),
103
+ ...(item.menuItems ?? []),
104
+ ];
105
+ const menuLabel = isMovable ? "Move" : "More";
82
106
  const titleBlock = (_jsx("span", { className: "line-clamp-2 cq-sm:line-clamp-none cq-sm:block cq-sm:truncate", children: item.titleContent ?? item.title }));
83
107
  return (_jsxs("li", { className: toClassName("group relative flex gap-2 border-border-subtle border-b py-2 pe-1 last:border-b-0",
84
108
  // At `cq-lg` the list stops being a list of rows: the
@@ -100,13 +124,7 @@ export const BoardCard = ({ item, laneLabel, moveIcon, moveTargets, onMoveToLane
100
124
  item.accentEdge != null &&
101
125
  toClassName("ps-2", getAccentEdgeClassName(item.accentEdge)), item.isDimmed && "opacity-60"), "data-board-card": item.key, ...(item.accentEdge == null
102
126
  ? {}
103
- : { style: getAccentEdgeStyle(item.accentEdge) }), children: [item.accentEdge != null && item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null, item.accentIntent ? (_jsx("span", { className: toClassName("w-1 shrink-0 self-stretch rounded-full", INTENT_SOLID_FILL_CLASS[item.accentIntent]), children: item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null })) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1 cq-sm:flex-row cq-sm:items-center cq-sm:gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-1.5 text-sm leading-snug", children: [item.marks ? (_jsx("span", { className: "flex shrink-0 items-center gap-1 pt-1", children: item.marks })) : null, item.href ? (_jsx(TextLink, { appearance: "standalone", className: "min-w-0 max-w-full font-normal", href: item.href, intent: "neutral", children: titleBlock })) : item.onSelect ? (_jsx("button", { className: toClassName("min-w-0 max-w-full cursor-pointer rounded-xs text-start text-intent-neutral-content hover:underline", FOCUS_RING_CLASS), onClick: item.onSelect, type: "button", children: titleBlock })) : (titleBlock)] }), item.meta ? (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1 cq-sm:shrink-0 cq-sm:flex-nowrap cq-sm:justify-end", children: item.meta })) : null] }), item.trailing || onMoveToLane ? (_jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [item.trailing, onMoveToLane && moveTargets.length > 0 ? (_jsx(Menu, { isVisible: isMenuVisible, items: moveTargets.map((target) => ({
104
- key: target.key,
105
- label: target.label,
106
- onSelect: () => {
107
- onMoveToLane(target.key);
108
- },
109
- })), onDismiss: () => {
127
+ : { style: getAccentEdgeStyle(item.accentEdge) }), children: [item.accentEdge != null && item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null, item.accentIntent ? (_jsx("span", { className: toClassName("w-1 shrink-0 self-stretch rounded-full", INTENT_SOLID_FILL_CLASS[item.accentIntent]), children: item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null })) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1 cq-sm:flex-row cq-sm:items-center cq-sm:gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-1.5 text-sm leading-snug", children: [item.marks ? (_jsx("span", { className: "flex shrink-0 items-center gap-1 pt-1", children: item.marks })) : null, item.href ? (_jsx(TextLink, { appearance: "standalone", className: "min-w-0 max-w-full font-normal", href: item.href, intent: "neutral", children: titleBlock })) : item.onSelect ? (_jsx("button", { className: toClassName("min-w-0 max-w-full cursor-pointer rounded-xs text-start text-intent-neutral-content hover:underline", FOCUS_RING_CLASS), onClick: item.onSelect, type: "button", children: titleBlock })) : (titleBlock)] }), item.meta ? (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1 cq-sm:shrink-0 cq-sm:flex-nowrap cq-sm:justify-end", children: item.meta })) : null] }), item.trailing || hasMenu ? (_jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [item.trailing, hasMenu ? (_jsx(Menu, { isVisible: isMenuVisible, items: menuItems, onDismiss: () => {
110
128
  setIsMenuVisible(false);
111
129
  }, placement: "bottom-end", trigger: _jsxs(Button, { appearance: "ghost", intent: "neutral", className: toClassName(
112
130
  // `touch-none` so a touch drag is a drag
@@ -131,9 +149,13 @@ export const BoardCard = ({ item, laneLabel, moveIcon, moveTargets, onMoveToLane
131
149
  moveIcon != null &&
132
150
  "@min-[48rem]/board:w-(--control-height-sm) @min-[48rem]/board:px-0"), onClick: () => {
133
151
  setIsMenuVisible((isVisible) => !isVisible);
134
- }, onPointerDown: (pointerEvent) => {
135
- onStartDrag?.(pointerEvent);
136
- }, size: "sm", sizing: "control", children: [moveIcon == null ? null : (_jsx("span", { "aria-hidden": "true", className: "hidden @min-[48rem]/board:contents", children: moveIcon })), _jsx("span", { "aria-hidden": "true", className: toClassName(moveIcon != null &&
137
- "@min-[48rem]/board:hidden"), children: "Move" }), _jsx(VisuallyHidden, { children: `Move ${item.title}, currently in ${laneLabel}` })] }) })) : null] })) : null] }), item.footer ? (_jsx("div", { className: "min-w-0 text-content-secondary text-xs", children: item.footer })) : null] })] }));
152
+ }, onPointerDown: isMovable
153
+ ? (pointerEvent) => {
154
+ onStartDrag?.(pointerEvent);
155
+ }
156
+ : undefined, size: "sm", sizing: "control", children: [moveIcon == null ? null : (_jsx("span", { "aria-hidden": "true", className: "hidden @min-[48rem]/board:contents", children: moveIcon })), _jsx("span", { "aria-hidden": "true", className: toClassName(moveIcon != null &&
157
+ "@min-[48rem]/board:hidden"), children: menuLabel }), _jsx(VisuallyHidden, { children: isMovable
158
+ ? `Move ${item.title}, currently in ${laneLabel}`
159
+ : `More actions for ${item.title}, currently in ${laneLabel}` })] }) })) : null] })) : null] }), item.footer ? (_jsx("div", { className: "min-w-0 text-content-secondary text-xs", children: item.footer })) : null] })] }));
138
160
  };
139
161
  //# sourceMappingURL=BoardCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BoardCard.js","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BoardCard.js","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "3.25.0",
3
+ "version": "3.26.0",
4
4
  "description": "The Charcuterie components. Tokens for look, @charcuterie/logic for state, ARIA that agents can actually drive.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,6 +11,8 @@ Lanes of cards, sized by their **container**, moved by keyboard or by pointer
11
11
 
12
12
  <Canvas of={BoardStories.Default} />
13
13
 
14
+ <Canvas of={BoardStories.CardMenuItems} />
15
+
14
16
  ## Props
15
17
 
16
18
  <Controls />
@@ -84,6 +86,9 @@ The move is one control with two drivers, and they commit through the same `onMo
84
86
  code path, with `setPointerCapture` so the drag survives the pointer leaving the card.
85
87
  Escape abandons it.
86
88
 
89
+ An item can append actions to that existing menu with `menuItems`. The Board inserts a
90
+ separator after the lane destinations, so a card action does not need a second trigger.
91
+
87
92
  <Canvas of={BoardStories.Interactive} />
88
93
 
89
94
  Every move is announced in a `role="status"` region that names the **destination and the
@@ -750,6 +750,46 @@ export const Interactive: Story = {
750
750
  render: () => <MovableBoard />,
751
751
  }
752
752
 
753
+ /**
754
+ * A consumer can add a card action to the same menu as the lane
755
+ * moves. The separator is supplied by `BoardCard`, so the action
756
+ * does not need a second trigger beside the existing one.
757
+ */
758
+ export const CardMenuItems: Story = {
759
+ render: () => (
760
+ <Frame inlineSize="72rem">
761
+ <Board
762
+ label="Today, with card actions"
763
+ lanes={[
764
+ {
765
+ items: [
766
+ {
767
+ ...TODO_ITEMS[0],
768
+ menuItems: [
769
+ {
770
+ key: "send-to-backlog",
771
+ label: "Send to Backlog",
772
+ onSelect: () => undefined,
773
+ },
774
+ ],
775
+ },
776
+ ],
777
+ key: "todo",
778
+ label: "Todo",
779
+ },
780
+ {
781
+ items: IN_PROGRESS_ITEMS.slice(0, 1),
782
+ key: "in-progress",
783
+ label: "In Progress",
784
+ },
785
+ ]}
786
+ moveIcon={<MoreIcon />}
787
+ onMove={() => undefined}
788
+ />
789
+ </Frame>
790
+ ),
791
+ }
792
+
753
793
  /**
754
794
  * Where it actually goes, and what the consumer still owns.
755
795
  *
@@ -15,6 +15,7 @@ import * as stories from "./Board.stories.tsx"
15
15
  const {
16
16
  AccentEdge,
17
17
  AllStates,
18
+ CardMenuItems,
18
19
  Default,
19
20
  InBoardScreen,
20
21
  Interactive,
@@ -408,6 +409,46 @@ test("the board adds no link of its own around a rich title", async () => {
408
409
  )
409
410
  })
410
411
 
412
+ /**
413
+ * Extra card actions share the existing move menu. A second
414
+ * trigger would make the card look busy and would split related
415
+ * actions across two controls, so the component owns the separator
416
+ * between the lane destinations and the consumer's items.
417
+ */
418
+ test("a card can append an action to its existing menu", async () => {
419
+ const { body, canvas } = await mountStory(CardMenuItems)
420
+
421
+ const todo = expectAgentDrivable(canvas, {
422
+ name: "Todo",
423
+ role: "group",
424
+ })
425
+
426
+ await userEvent.click(
427
+ within(todo).getByRole("button", {
428
+ name: /^Move Retire the second scheduler/,
429
+ }),
430
+ )
431
+
432
+ const menu = await waitFor(() =>
433
+ expectAgentDrivable(body, {
434
+ name: /Move Retire the second scheduler/,
435
+ role: "menu",
436
+ }),
437
+ )
438
+
439
+ expect(
440
+ within(menu).getByRole("menuitem", {
441
+ name: "In Progress",
442
+ }),
443
+ ).toBeInTheDocument()
444
+ expect(
445
+ within(menu).getByRole("menuitem", {
446
+ name: "Send to Backlog",
447
+ }),
448
+ ).toBeInTheDocument()
449
+ expect(menu.querySelector("hr")).not.toBeNull()
450
+ })
451
+
411
452
  /**
412
453
  * The edge is drawn on the card's OWN box, which is the whole
413
454
  * difference between it and the `accentIntent` pill.
@@ -12,6 +12,7 @@ import {
12
12
  FOCUS_RING_CLASS,
13
13
  INTENT_SOLID_FILL_CLASS,
14
14
  } from "../intentStyles.ts"
15
+ import type { MenuProps } from "../Menu/Menu.tsx"
15
16
  import { Menu } from "../Menu/Menu.tsx"
16
17
  import { TextLink } from "../TextLink/TextLink.tsx"
17
18
  import { toClassName } from "../toClassName.ts"
@@ -55,6 +56,12 @@ type BoardItemFields = {
55
56
  title: string
56
57
  /** The trailing cluster: an assignee avatar, an elapsed-time chip. */
57
58
  trailing?: ReactNode
59
+ /**
60
+ * Extra actions in the card's existing menu, after the lane moves.
61
+ * A separator is added when both sets have entries, so a consumer
62
+ * can add a card action without creating a second trigger.
63
+ */
64
+ menuItems?: MenuProps["items"]
58
65
  }
59
66
 
60
67
  /**
@@ -251,6 +258,34 @@ export const BoardCard = ({
251
258
  }: BoardCardProps): ReactNode => {
252
259
  const [isMenuVisible, setIsMenuVisible] = useState(false)
253
260
 
261
+ const isMovable =
262
+ onMoveToLane != null && moveTargets.length > 0
263
+ const hasMenuItems = (item.menuItems?.length ?? 0) > 0
264
+ const hasMenu =
265
+ onMoveToLane != null && (isMovable || hasMenuItems)
266
+ const menuItems: MenuProps["items"] = [
267
+ ...(isMovable
268
+ ? moveTargets.map((target) => ({
269
+ key: target.key,
270
+ label: target.label,
271
+ onSelect: () => {
272
+ onMoveToLane?.(target.key)
273
+ },
274
+ }))
275
+ : []),
276
+ ...(hasMenuItems && isMovable
277
+ ? [
278
+ {
279
+ key: "board-extra-actions",
280
+ type: "separator" as const,
281
+ },
282
+ ]
283
+ : []),
284
+ ...(item.menuItems ?? []),
285
+ ]
286
+
287
+ const menuLabel = isMovable ? "Move" : "More"
288
+
254
289
  const titleBlock = (
255
290
  // `line-clamp-none` before `truncate`, because `line-clamp-2`
256
291
  // sets `display: -webkit-box` and `text-overflow` has nothing to
@@ -359,20 +394,14 @@ export const BoardCard = ({
359
394
  ) : null}
360
395
  </div>
361
396
 
362
- {item.trailing || onMoveToLane ? (
397
+ {item.trailing || hasMenu ? (
363
398
  <div className="flex shrink-0 items-center gap-1">
364
399
  {item.trailing}
365
400
 
366
- {onMoveToLane && moveTargets.length > 0 ? (
401
+ {hasMenu ? (
367
402
  <Menu
368
403
  isVisible={isMenuVisible}
369
- items={moveTargets.map((target) => ({
370
- key: target.key,
371
- label: target.label,
372
- onSelect: () => {
373
- onMoveToLane(target.key)
374
- },
375
- }))}
404
+ items={menuItems}
376
405
  onDismiss={() => {
377
406
  setIsMenuVisible(false)
378
407
  }}
@@ -409,9 +438,13 @@ export const BoardCard = ({
409
438
  (isVisible) => !isVisible,
410
439
  )
411
440
  }}
412
- onPointerDown={(pointerEvent) => {
413
- onStartDrag?.(pointerEvent)
414
- }}
441
+ onPointerDown={
442
+ isMovable
443
+ ? (pointerEvent) => {
444
+ onStartDrag?.(pointerEvent)
445
+ }
446
+ : undefined
447
+ }
415
448
  size="sm"
416
449
  sizing="control"
417
450
  >
@@ -441,7 +474,7 @@ export const BoardCard = ({
441
474
  "@min-[48rem]/board:hidden",
442
475
  )}
443
476
  >
444
- Move
477
+ {menuLabel}
445
478
  </span>
446
479
 
447
480
  {/*
@@ -459,7 +492,9 @@ export const BoardCard = ({
459
492
  * X" narrow is one control with two names.
460
493
  */}
461
494
  <VisuallyHidden>
462
- {`Move ${item.title}, currently in ${laneLabel}`}
495
+ {isMovable
496
+ ? `Move ${item.title}, currently in ${laneLabel}`
497
+ : `More actions for ${item.title}, currently in ${laneLabel}`}
463
498
  </VisuallyHidden>
464
499
  </Button>
465
500
  }