diff --git a/.changeset/lovely-snakes-approve.md b/.changeset/lovely-snakes-approve.md
new file mode 100644
index 0000000000..d780585257
--- /dev/null
+++ b/.changeset/lovely-snakes-approve.md
@@ -0,0 +1,7 @@
+---
+"@nextui-org/popover": patch
+"@nextui-org/system-rsc": patch
+"@nextui-org/theme": patch
+---
+
+Fixed unexpected props on a DOM element (#2474)
diff --git a/packages/components/dropdown/__tests__/dropdown.test.tsx b/packages/components/dropdown/__tests__/dropdown.test.tsx
index 37b0eb82e6..71aab8f9ab 100644
--- a/packages/components/dropdown/__tests__/dropdown.test.tsx
+++ b/packages/components/dropdown/__tests__/dropdown.test.tsx
@@ -2,6 +2,7 @@ import * as React from "react";
import {act, render} from "@testing-library/react";
import {Button} from "@nextui-org/button";
import userEvent from "@testing-library/user-event";
+import {User} from "@nextui-org/user";
import {Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, DropdownSection} from "../src";
@@ -416,4 +417,48 @@ describe("Dropdown", () => {
expect(onSelectionChange).toBeCalledTimes(0);
});
+
+ it("should render without error (custom trigger + isDisabled)", async () => {
+ const spy = jest.spyOn(console, "error").mockImplementation(() => {});
+
+ render(
+
+
+ Trigger
+
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+
+ ,
+ );
+
+ expect(spy).toBeCalledTimes(0);
+
+ spy.mockRestore();
+
+ render(
+
+
+
+
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+
+ ,
+ );
+
+ expect(spy).toBeCalledTimes(0);
+
+ spy.mockRestore();
+ });
});
diff --git a/packages/components/popover/src/popover-trigger.tsx b/packages/components/popover/src/popover-trigger.tsx
index dded9c1e70..a32a584c02 100644
--- a/packages/components/popover/src/popover-trigger.tsx
+++ b/packages/components/popover/src/popover-trigger.tsx
@@ -1,6 +1,6 @@
import React, {Children, cloneElement, useMemo} from "react";
-import {forwardRef} from "@nextui-org/system";
-import {pickChildren} from "@nextui-org/react-utils";
+import {forwardRef, isNextUIEl} from "@nextui-org/system";
+import {pickChildren, filterDOMProps} from "@nextui-org/react-utils";
import {useAriaButton} from "@nextui-org/use-aria-button";
import {Button} from "@nextui-org/button";
import {mergeProps} from "@react-aria/utils";
@@ -29,7 +29,7 @@ const PopoverTrigger = forwardRef<"button", PopoverTriggerProps>((props, _) => {
};
}, [children]);
- const {onPress, ...rest} = useMemo(() => {
+ const {onPress, ...restProps} = useMemo(() => {
return getTriggerProps(mergeProps(otherProps, child.props), child.ref);
}, [getTriggerProps, child.props, otherProps, child.ref]);
@@ -42,7 +42,22 @@ const PopoverTrigger = forwardRef<"button", PopoverTriggerProps>((props, _) => {
return triggerChildren?.[0] !== undefined;
}, [triggerChildren]);
- return cloneElement(child, mergeProps(rest, hasNextUIButton ? {onPress} : buttonProps));
+ const isDisabled = !!restProps?.isDisabled;
+
+ const isNextUIElement = isNextUIEl(child);
+
+ return cloneElement(
+ child,
+ mergeProps(
+ // if we add `isDisabled` prop to DOM elements,
+ // react will fail to recognize it on a DOM element,
+ // hence, apply filterDOMProps for such case
+ filterDOMProps(restProps, {
+ enabled: isDisabled && !isNextUIElement,
+ }),
+ hasNextUIButton ? {onPress} : buttonProps,
+ ),
+ );
});
PopoverTrigger.displayName = "NextUI.PopoverTrigger";
diff --git a/packages/components/popover/src/use-popover.ts b/packages/components/popover/src/use-popover.ts
index d7593b0371..df0e72dd34 100644
--- a/packages/components/popover/src/use-popover.ts
+++ b/packages/components/popover/src/use-popover.ts
@@ -244,7 +244,12 @@ export function usePopover(originalProps: UsePopoverProps) {
"aria-haspopup": "dialog",
...mergeProps(triggerProps, props),
onPress,
- className: slots.trigger({class: clsx(classNames?.trigger, props.className)}),
+ className: slots.trigger({
+ class: clsx(classNames?.trigger, props.className),
+ // apply isDisabled class names to make the trigger child disabled
+ // e.g. for elements like div or NextUI elements that don't have `isDisabled` prop
+ isDropdownDisabled: !!props?.isDisabled,
+ }),
ref: mergeRefs(_ref, triggerRef),
};
},
diff --git a/packages/core/system-rsc/src/utils.ts b/packages/core/system-rsc/src/utils.ts
index 884e8341e5..151c1536ab 100644
--- a/packages/core/system-rsc/src/utils.ts
+++ b/packages/core/system-rsc/src/utils.ts
@@ -100,3 +100,12 @@ export const mapPropsVariantsWithCommon = <
* Classnames utility
*/
export const cn = clsx;
+
+/**
+ * Checks if a component is a NextUI component.
+ * @param component - The component to check.
+ * @returns `true` if the component is a NextUI component, `false` otherwise.
+ */
+export const isNextUIEl = (component: React.ReactComponentElement) => {
+ return !!component.type?.render?.displayName?.includes("NextUI");
+};
diff --git a/packages/core/theme/src/components/popover.ts b/packages/core/theme/src/components/popover.ts
index a10af68039..e845146c90 100644
--- a/packages/core/theme/src/components/popover.ts
+++ b/packages/core/theme/src/components/popover.ts
@@ -162,6 +162,12 @@ const popover = tv({
base: "animate-none",
},
},
+ isDropdownDisabled: {
+ true: {
+ trigger: "opacity-disabled pointer-events-none",
+ },
+ false: {},
+ },
},
defaultVariants: {
color: "default",