@rovula/ui 0.1.23 → 0.1.25

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.
@@ -14,6 +14,8 @@ export type MenuOption = {
14
14
  danger?: boolean;
15
15
  checked?: boolean;
16
16
  onClick?: () => void;
17
+ /** data-testid attached to the item element */
18
+ testId?: string;
17
19
  };
18
20
  export type MenuItemType = {
19
21
  type: "item";
package/dist/index.d.ts CHANGED
@@ -1379,6 +1379,8 @@ type MenuOption = {
1379
1379
  danger?: boolean;
1380
1380
  checked?: boolean;
1381
1381
  onClick?: () => void;
1382
+ /** data-testid attached to the item element */
1383
+ testId?: string;
1382
1384
  };
1383
1385
  type MenuItemType = {
1384
1386
  type: "item";
@@ -23,7 +23,7 @@ function renderMenuItems(items, selectedValues, onSelect) {
23
23
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(val, opt);
24
24
  (_a = opt.onClick) === null || _a === void 0 ? void 0 : _a.call(opt);
25
25
  }
26
- }, children: buffer.map((opt) => (_jsx(DropdownMenuRadioItem, { value: opt.value, disabled: opt.disabled, icon: opt.icon, children: opt.label }, opt.value))) }, key));
26
+ }, children: buffer.map((opt) => (_jsx(DropdownMenuRadioItem, { value: opt.value, disabled: opt.disabled, icon: opt.icon, "data-testid": opt.testId, children: opt.label }, opt.value))) }, key));
27
27
  };
28
28
  items.forEach((item, index) => {
29
29
  var _a;
@@ -54,7 +54,7 @@ function renderMenuItems(items, selectedValues, onSelect) {
54
54
  const opt = item.item;
55
55
  const isSelected = (_a = opt.checked) !== null && _a !== void 0 ? _a : selectedValues.includes(opt.value);
56
56
  if (opt.type === "checkbox") {
57
- result.push(_jsx(DropdownMenuCheckboxItem, { checked: isSelected, disabled: opt.disabled, onCheckedChange: () => {
57
+ result.push(_jsx(DropdownMenuCheckboxItem, { checked: isSelected, disabled: opt.disabled, "data-testid": opt.testId, onCheckedChange: () => {
58
58
  var _a;
59
59
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(opt.value, opt);
60
60
  (_a = opt.onClick) === null || _a === void 0 ? void 0 : _a.call(opt);
@@ -62,7 +62,7 @@ function renderMenuItems(items, selectedValues, onSelect) {
62
62
  return;
63
63
  }
64
64
  // default item
65
- result.push(_jsx(DropdownMenuItem, { selected: isSelected, icon: opt.icon, disabled: opt.disabled, className: cn(opt.danger && "text-red-500"), onSelect: () => {
65
+ result.push(_jsx(DropdownMenuItem, { selected: isSelected, icon: opt.icon, disabled: opt.disabled, className: cn(opt.danger && "text-red-500"), "data-testid": opt.testId, onSelect: () => {
66
66
  var _a;
67
67
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(opt.value, opt);
68
68
  (_a = opt.onClick) === null || _a === void 0 ? void 0 : _a.call(opt);
@@ -86,7 +86,12 @@ function renderMenuItems(items, selectedValues, onSelect) {
86
86
  // - Full a11y / WAI-ARIA
87
87
  // - Sub-menu support via DropdownMenuSub
88
88
  // ---------------------------------------------------------------------------
89
- export const Menu = ({ trigger, items, selectedValues = [], onSelect, header, open, onOpenChange, align = "start", side = "bottom", sideOffset = 4, contentClassName, testId, }) => (_jsxs(DropdownMenuRoot, { open: open, onOpenChange: onOpenChange, children: [trigger && (_jsx(DropdownMenuTrigger, { asChild: true, children: trigger })), _jsxs(DropdownMenuContent, { align: align, side: side, sideOffset: sideOffset, className: contentClassName, "data-testid": testId, children: [header && (_jsx("div", { className: "sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]", children: header })), renderMenuItems(items, selectedValues, onSelect)] })] }));
89
+ export const Menu = ({ trigger, items, selectedValues = [], onSelect, header, open, onOpenChange, align = "start", side = "bottom", sideOffset = 4, contentClassName, testId, }) => (
90
+ // Stop click events from bubbling through React's portal event system.
91
+ // DropdownMenuContent renders in a DOM portal (document.body) but React
92
+ // synthetic events still bubble through the component tree, so clicks on
93
+ // menu items reach ancestor onClick handlers (e.g. a clickable card).
94
+ _jsx("div", { onClick: (e) => e.stopPropagation(), children: _jsxs(DropdownMenuRoot, { open: open, onOpenChange: onOpenChange, children: [trigger && (_jsx(DropdownMenuTrigger, { asChild: true, children: trigger })), _jsxs(DropdownMenuContent, { align: align, side: side, sideOffset: sideOffset, className: contentClassName, "data-testid": testId, children: [header && (_jsx("div", { className: "sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]", children: header })), renderMenuItems(items, selectedValues, onSelect)] })] }) }));
90
95
  Menu.displayName = "Menu";
91
96
  // ---------------------------------------------------------------------------
92
97
  // Re-exports — backward compat for consumers using Menu's sub-components
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,6 +36,8 @@ export type MenuOption = {
36
36
  danger?: boolean;
37
37
  checked?: boolean;
38
38
  onClick?: () => void;
39
+ /** data-testid attached to the item element */
40
+ testId?: string;
39
41
  };
40
42
 
41
43
  export type MenuItemType =
@@ -121,6 +123,7 @@ function renderMenuItems(
121
123
  value={opt.value}
122
124
  disabled={opt.disabled}
123
125
  icon={opt.icon as React.ReactNode}
126
+ data-testid={opt.testId}
124
127
  >
125
128
  {opt.label}
126
129
  </DropdownMenuRadioItem>
@@ -187,6 +190,7 @@ function renderMenuItems(
187
190
  key={opt.value}
188
191
  checked={isSelected}
189
192
  disabled={opt.disabled}
193
+ data-testid={opt.testId}
190
194
  onCheckedChange={() => {
191
195
  onSelect?.(opt.value, opt);
192
196
  opt.onClick?.();
@@ -206,6 +210,7 @@ function renderMenuItems(
206
210
  icon={opt.icon as React.ReactNode}
207
211
  disabled={opt.disabled}
208
212
  className={cn(opt.danger && "text-red-500")}
213
+ data-testid={opt.testId}
209
214
  onSelect={() => {
210
215
  onSelect?.(opt.value, opt);
211
216
  opt.onClick?.();
@@ -250,25 +255,31 @@ export const Menu = ({
250
255
  contentClassName,
251
256
  testId,
252
257
  }: MenuProps) => (
253
- <DropdownMenuRoot open={open} onOpenChange={onOpenChange}>
254
- {trigger && (
255
- <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
256
- )}
257
- <DropdownMenuContent
258
- align={align}
259
- side={side}
260
- sideOffset={sideOffset}
261
- className={contentClassName}
262
- data-testid={testId}
263
- >
264
- {header && (
265
- <div className="sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]">
266
- {header}
267
- </div>
258
+ // Stop click events from bubbling through React's portal event system.
259
+ // DropdownMenuContent renders in a DOM portal (document.body) but React
260
+ // synthetic events still bubble through the component tree, so clicks on
261
+ // menu items reach ancestor onClick handlers (e.g. a clickable card).
262
+ <div onClick={(e) => e.stopPropagation()}>
263
+ <DropdownMenuRoot open={open} onOpenChange={onOpenChange}>
264
+ {trigger && (
265
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
268
266
  )}
269
- {renderMenuItems(items, selectedValues, onSelect)}
270
- </DropdownMenuContent>
271
- </DropdownMenuRoot>
267
+ <DropdownMenuContent
268
+ align={align}
269
+ side={side}
270
+ sideOffset={sideOffset}
271
+ className={contentClassName}
272
+ data-testid={testId}
273
+ >
274
+ {header && (
275
+ <div className="sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]">
276
+ {header}
277
+ </div>
278
+ )}
279
+ {renderMenuItems(items, selectedValues, onSelect)}
280
+ </DropdownMenuContent>
281
+ </DropdownMenuRoot>
282
+ </div>
272
283
  );
273
284
 
274
285
  Menu.displayName = "Menu";