diff --git a/.changeset/serious-panthers-beg.md b/.changeset/serious-panthers-beg.md new file mode 100644 index 0000000000..34b1ee0a71 --- /dev/null +++ b/.changeset/serious-panthers-beg.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/dropdown": patch +"@nextui-org/use-aria-menu": patch +--- + +fixed `_a2.find` is not a function (#3761) diff --git a/packages/components/dropdown/__tests__/dropdown.test.tsx b/packages/components/dropdown/__tests__/dropdown.test.tsx index b719db3c0c..359e337116 100644 --- a/packages/components/dropdown/__tests__/dropdown.test.tsx +++ b/packages/components/dropdown/__tests__/dropdown.test.tsx @@ -796,7 +796,7 @@ describe("Keyboard interactions", () => { logSpy.mockRestore(); }); - it("should respect closeOnSelect setting of DropdownItem", async () => { + it("should respect closeOnSelect setting of DropdownItem (static)", async () => { const onOpenChange = jest.fn(); const wrapper = render( @@ -831,4 +831,51 @@ describe("Keyboard interactions", () => { expect(onOpenChange).toBeCalledTimes(2); }); }); + + it("should respect closeOnSelect setting of DropdownItem (dynamic)", async () => { + const onOpenChange = jest.fn(); + const items = [ + { + key: "new", + label: "New file", + }, + { + key: "copy", + label: "Copy link", + }, + ]; + const wrapper = render( + + + + + + {(item) => ( + + {item.label} + + )} + + , + ); + + let triggerButton = wrapper.getByTestId("trigger-test"); + + act(() => { + triggerButton.click(); + }); + expect(onOpenChange).toBeCalledTimes(1); + + let menuItems = wrapper.getAllByRole("menuitem"); + + await act(async () => { + await userEvent.click(menuItems[0]); + expect(onOpenChange).toBeCalledTimes(1); + }); + + await act(async () => { + await userEvent.click(menuItems[1]); + expect(onOpenChange).toBeCalledTimes(2); + }); + }); }); diff --git a/packages/components/dropdown/src/use-dropdown.ts b/packages/components/dropdown/src/use-dropdown.ts index 74294a4032..cff1cc1b9e 100644 --- a/packages/components/dropdown/src/use-dropdown.ts +++ b/packages/components/dropdown/src/use-dropdown.ts @@ -13,6 +13,7 @@ import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useMemo, useRef} from "react"; import {mergeProps} from "@react-aria/utils"; import {MenuProps} from "@nextui-org/menu"; +import {CollectionElement} from "@react-types/shared"; interface Props extends HTMLNextUIProps<"div"> { /** @@ -42,6 +43,40 @@ interface Props extends HTMLNextUIProps<"div"> { export type UseDropdownProps = Props & Omit; +const getMenuItem = (props: Partial> | undefined, key: string) => { + if (props) { + const mergedChildren = Array.isArray(props.children) + ? props.children + : [...(props?.items || [])]; + + if (mergedChildren && mergedChildren.length) { + const item = ((mergedChildren as CollectionElement[]).find((item) => { + if (item.key === key) { + return item; + } + }) || {}) as {props: MenuProps}; + + return item; + } + } + + return null; +}; + +const getCloseOnSelect = ( + props: Partial> | undefined, + key: string, + item?: any, +) => { + const mergedItem = item || getMenuItem(props, key); + + if (mergedItem && mergedItem.props && "closeOnSelect" in mergedItem.props) { + return mergedItem.props.closeOnSelect; + } + + return props?.closeOnSelect; +}; + export function useDropdown(props: UseDropdownProps) { const globalContext = useProviderContext(); @@ -152,16 +187,10 @@ export function useDropdown(props: UseDropdownProps) { menuProps, closeOnSelect, ...mergeProps(props, { - onAction: (key: any) => { - // @ts-ignore - const item = props?.children?.find((item) => item.key === key); - - if (item?.props?.closeOnSelect === false) { - onMenuAction(false); + onAction: (key: any, item?: any) => { + const closeOnSelect = getCloseOnSelect(props, key, item); - return; - } - onMenuAction(props?.closeOnSelect); + onMenuAction(closeOnSelect); }, onClose: state.close, }), diff --git a/packages/hooks/use-aria-menu/src/use-menu-item.ts b/packages/hooks/use-aria-menu/src/use-menu-item.ts index 4696f8ea6a..ccc346c767 100644 --- a/packages/hooks/use-aria-menu/src/use-menu-item.ts +++ b/packages/hooks/use-aria-menu/src/use-menu-item.ts @@ -94,7 +94,7 @@ export interface AriaMenuItemProps * Handler that is called when the user activates the item. * @deprecated - pass to the menu instead. */ - onAction?: (key: Key) => void; + onAction?: (key: Key, item: any) => void; /** * The native button click event handler @@ -167,11 +167,11 @@ export function useMenuItem( if (props.onAction) { // @ts-ignore - props.onAction(key); + props.onAction(key, item); // @ts-ignore } else if (data.onAction) { // @ts-ignore - data.onAction(key); + data.onAction(key, item); } if (e.target instanceof HTMLAnchorElement) { diff --git a/packages/hooks/use-aria-menu/src/use-menu.ts b/packages/hooks/use-aria-menu/src/use-menu.ts index ff551c1341..220b4fae05 100644 --- a/packages/hooks/use-aria-menu/src/use-menu.ts +++ b/packages/hooks/use-aria-menu/src/use-menu.ts @@ -24,7 +24,7 @@ export interface AriaMenuOptions extends Omit, "children">, interface MenuData { onClose?: () => void; - onAction?: (key: Key) => void; + onAction?: (key: Key, item: any) => void; } export const menuData = new WeakMap, MenuData>();