diff --git a/.changeset/nasty-bees-sniff.md b/.changeset/nasty-bees-sniff.md
new file mode 100644
index 0000000000..3f129edff2
--- /dev/null
+++ b/.changeset/nasty-bees-sniff.md
@@ -0,0 +1,7 @@
+---
+"@nextui-org/listbox": patch
+"@nextui-org/menu": patch
+"@nextui-org/pagination": patch
+---
+
+Fixes missing `
` wrapper when `href` prop is passed in `ListboxItem`, `MenuItem`, and `PaginationItem` (#4147)
\ No newline at end of file
diff --git a/packages/components/listbox/__tests__/listbox.test.tsx b/packages/components/listbox/__tests__/listbox.test.tsx
index 60614536bf..e77196aed1 100644
--- a/packages/components/listbox/__tests__/listbox.test.tsx
+++ b/packages/components/listbox/__tests__/listbox.test.tsx
@@ -124,6 +124,40 @@ describe("Listbox", () => {
expect(() => wrapper.unmount()).not.toThrow();
});
+ it("should not have anchor tag when href prop is not passed", () => {
+ render(
+
+ New file
+ Copy link
+ Edit file
+ ,
+ );
+
+ let anchorTag = document.getElementsByTagName("a")[0];
+
+ expect(anchorTag).toBeFalsy();
+ });
+
+ it("should have anchor tag when href prop is passed", () => {
+ const href = "http://www.nextui.org/";
+
+ render(
+
+
+ New file
+
+ Copy link
+ Edit file
+ ,
+ );
+
+ let anchorTag = document.getElementsByTagName("a")[0];
+
+ expect(anchorTag).toBeTruthy();
+
+ expect(anchorTag).toHaveProperty("href", href);
+ });
+
it("should work with single selection (controlled)", async () => {
let onSelectionChange = jest.fn();
diff --git a/packages/components/listbox/src/listbox-item.tsx b/packages/components/listbox/src/listbox-item.tsx
index f2e8010f74..bf624a9e3b 100644
--- a/packages/components/listbox/src/listbox-item.tsx
+++ b/packages/components/listbox/src/listbox-item.tsx
@@ -12,6 +12,7 @@ export interface ListboxItemProps extends UseListboxI
const ListboxItem = forwardRef<"li", ListboxItemProps>((props, _) => {
const {
Component,
+ FragmentWrapper,
rendered,
description,
isSelectable,
@@ -22,6 +23,7 @@ const ListboxItem = forwardRef<"li", ListboxItemProps>((props, _) => {
endContent,
hideSelectedIcon,
disableAnimation,
+ fragmentWrapperProps,
getItemProps,
getLabelProps,
getWrapperProps,
@@ -45,19 +47,21 @@ const ListboxItem = forwardRef<"li", ListboxItemProps>((props, _) => {
return (
- {startContent}
- {description ? (
-
+
+ {startContent}
+ {description ? (
+
+ {rendered}
+ {description}
+
+ ) : (
{rendered}
- {description}
-
- ) : (
- {rendered}
- )}
- {isSelectable && !hideSelectedIcon && (
- {selectedContent}
- )}
- {endContent}
+ )}
+ {isSelectable && !hideSelectedIcon && (
+ {selectedContent}
+ )}
+ {endContent}
+
);
});
diff --git a/packages/components/listbox/src/use-listbox-item.ts b/packages/components/listbox/src/use-listbox-item.ts
index 37103f2107..43be909f3c 100644
--- a/packages/components/listbox/src/use-listbox-item.ts
+++ b/packages/components/listbox/src/use-listbox-item.ts
@@ -1,6 +1,6 @@
import type {ListboxItemBaseProps} from "./base/listbox-item-base";
-import {useMemo, useRef, useCallback} from "react";
+import {useMemo, useRef, useCallback, Fragment} from "react";
import {listboxItem} from "@nextui-org/theme";
import {
HTMLNextUIProps,
@@ -48,6 +48,7 @@ export function useListboxItem(originalProps: UseListboxItemPr
shouldHighlightOnFocus,
hideSelectedIcon = false,
isReadOnly = false,
+ href,
...otherProps
} = props;
@@ -56,9 +57,12 @@ export function useListboxItem(originalProps: UseListboxItemPr
const domRef = useRef(null);
- const Component = as || (originalProps.href ? "a" : "li");
+ const Component = as || "li";
const shouldFilterDOMProps = typeof Component === "string";
+ const FragmentWrapper = href ? "a" : Fragment;
+ const fragmentWrapperProps = href ? {href} : {};
+
const {rendered, key} = item;
const isDisabled = state.disabledKeys.has(key) || originalProps.isDisabled;
@@ -167,6 +171,7 @@ export function useListboxItem(originalProps: UseListboxItemPr
return {
Component,
+ FragmentWrapper,
domRef,
slots,
classNames,
@@ -180,6 +185,7 @@ export function useListboxItem(originalProps: UseListboxItemPr
selectedIcon,
hideSelectedIcon,
disableAnimation,
+ fragmentWrapperProps,
getItemProps,
getLabelProps,
getWrapperProps,
diff --git a/packages/components/menu/__tests__/menu.test.tsx b/packages/components/menu/__tests__/menu.test.tsx
index eeaa3eb068..0b4ea75e87 100644
--- a/packages/components/menu/__tests__/menu.test.tsx
+++ b/packages/components/menu/__tests__/menu.test.tsx
@@ -125,6 +125,46 @@ describe("Menu", () => {
expect(() => wrapper.unmount()).not.toThrow();
});
+ it("should not have anchor tag when href prop is not passed", () => {
+ render(
+ ,
+ );
+
+ let anchorTag = document.getElementsByTagName("a")[0];
+
+ expect(anchorTag).toBeFalsy();
+ });
+
+ it("should have anchor tag when href prop is passed", () => {
+ const href = "http://www.nextui.org/";
+
+ render(
+ ,
+ );
+
+ let anchorTag = document.getElementsByTagName("a")[0];
+
+ expect(anchorTag).toBeTruthy();
+
+ expect(anchorTag).toHaveProperty("href", href);
+ });
+
it("should work with single selection (controlled)", async () => {
let onSelectionChange = jest.fn();
diff --git a/packages/components/menu/src/menu-item.tsx b/packages/components/menu/src/menu-item.tsx
index 226d28fd85..ac05b98aa9 100644
--- a/packages/components/menu/src/menu-item.tsx
+++ b/packages/components/menu/src/menu-item.tsx
@@ -1,5 +1,6 @@
import {useMemo, ReactNode} from "react";
import {forwardRef} from "@nextui-org/system";
+import * as React from "react";
import {UseMenuItemProps, useMenuItem} from "./use-menu-item";
import {MenuSelectedIcon} from "./menu-selected-icon";
@@ -12,6 +13,7 @@ export interface MenuItemProps extends UseMenuItemPro
const MenuItem = forwardRef<"li", MenuItemProps>((props, _) => {
const {
Component,
+ FragmentWrapper,
slots,
classNames,
rendered,
@@ -25,6 +27,7 @@ const MenuItem = forwardRef<"li", MenuItemProps>((props, _) => {
endContent,
disableAnimation,
hideSelectedIcon,
+ fragmentWrapperProps,
getItemProps,
getLabelProps,
getDescriptionProps,
@@ -48,20 +51,22 @@ const MenuItem = forwardRef<"li", MenuItemProps>((props, _) => {
return (
- {startContent}
- {description ? (
-
+
+ {startContent}
+ {description ? (
+
+ {rendered}
+ {description}
+
+ ) : (
{rendered}
- {description}
-
- ) : (
- {rendered}
- )}
- {shortcut && {shortcut}}
- {isSelectable && !hideSelectedIcon && (
- {selectedContent}
- )}
- {endContent}
+ )}
+ {shortcut && {shortcut}}
+ {isSelectable && !hideSelectedIcon && (
+ {selectedContent}
+ )}
+ {endContent}
+
);
});
diff --git a/packages/components/menu/src/use-menu-item.ts b/packages/components/menu/src/use-menu-item.ts
index 4d40050d00..9ed4e31253 100644
--- a/packages/components/menu/src/use-menu-item.ts
+++ b/packages/components/menu/src/use-menu-item.ts
@@ -1,7 +1,7 @@
import type {MenuItemBaseProps} from "./base/menu-item-base";
import type {Node} from "@react-types/shared";
-import {useMemo, useRef, useCallback} from "react";
+import {useMemo, useRef, useCallback, Fragment} from "react";
import {menuItem} from "@nextui-org/theme";
import {
HTMLNextUIProps,
@@ -57,6 +57,7 @@ export function useMenuItem(originalProps: UseMenuItemProps
isReadOnly = false,
closeOnSelect,
onClose,
+ href,
...otherProps
} = props;
@@ -65,9 +66,12 @@ export function useMenuItem(originalProps: UseMenuItemProps
const domRef = useRef(null);
- const Component = as || (otherProps?.href ? "a" : "li");
+ const Component = as || "li";
const shouldFilterDOMProps = typeof Component === "string";
+ const FragmentWrapper = href ? "a" : Fragment;
+ const fragmentWrapperProps = href ? {href} : {};
+
const {rendered, key} = item;
const isDisabledProp = state.disabledKeys.has(key) || originalProps.isDisabled;
@@ -192,6 +196,7 @@ export function useMenuItem(originalProps: UseMenuItemProps
return {
Component,
+ FragmentWrapper,
domRef,
slots,
classNames,
@@ -205,6 +210,7 @@ export function useMenuItem(originalProps: UseMenuItemProps
endContent,
selectedIcon,
disableAnimation,
+ fragmentWrapperProps,
getItemProps,
getLabelProps,
hideSelectedIcon,
diff --git a/packages/components/pagination/__tests__/pagination.test.tsx b/packages/components/pagination/__tests__/pagination.test.tsx
index 0a0ff16376..71bffe0e43 100644
--- a/packages/components/pagination/__tests__/pagination.test.tsx
+++ b/packages/components/pagination/__tests__/pagination.test.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import {render} from "@testing-library/react";
-import {Pagination} from "../src";
+import {Pagination, PaginationItem} from "../src";
describe("Pagination", () => {
it("should render correctly", () => {
@@ -37,6 +37,25 @@ describe("Pagination", () => {
expect(prevButton).toBeNull();
});
+ it("should not have anchor tag when href prop is not passed", () => {
+ render();
+ let anchorTag = document.getElementsByTagName("a")[0];
+
+ expect(anchorTag).toBeFalsy();
+ });
+
+ it("should have anchor tag when href prop is passed", () => {
+ const href = "http://www.nextui.org/";
+
+ render();
+
+ let anchorTag = document.getElementsByTagName("a")[0];
+
+ expect(anchorTag).toBeTruthy();
+
+ expect(anchorTag).toHaveProperty("href", href);
+ });
+
it("should show dots when total is greater than 10", () => {
const wrapper = render();
diff --git a/packages/components/pagination/src/pagination-item.tsx b/packages/components/pagination/src/pagination-item.tsx
index 988e554b23..03ef05c91c 100644
--- a/packages/components/pagination/src/pagination-item.tsx
+++ b/packages/components/pagination/src/pagination-item.tsx
@@ -5,9 +5,14 @@ import {usePaginationItem, UsePaginationItemProps} from "./use-pagination-item";
export interface PaginationItemProps extends UsePaginationItemProps {}
const PaginationItem = forwardRef<"li", PaginationItemProps>((props, ref) => {
- const {Component, children, getItemProps} = usePaginationItem({...props, ref});
+ const {Component, FragmentWrapper, fragmentWrapperProps, children, getItemProps} =
+ usePaginationItem({...props, ref});
- return {children};
+ return (
+
+ {children}
+
+ );
});
PaginationItem.displayName = "NextUI.PaginationItem";
diff --git a/packages/components/pagination/src/use-pagination-item.ts b/packages/components/pagination/src/use-pagination-item.ts
index 1bef299b98..e507d3aaaf 100644
--- a/packages/components/pagination/src/use-pagination-item.ts
+++ b/packages/components/pagination/src/use-pagination-item.ts
@@ -2,7 +2,7 @@ import type {Ref} from "react";
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
import type {LinkDOMProps, PressEvent} from "@react-types/shared";
-import {useMemo} from "react";
+import {Fragment, useMemo} from "react";
import {PaginationItemValue} from "@nextui-org/use-pagination";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
import {chain, mergeProps, shouldClientNavigate, useRouter} from "@react-aria/utils";
@@ -64,10 +64,13 @@ export function usePaginationItem(props: UsePaginationItemProps) {
} = props;
const isLink = !!props?.href;
- const Component = as || isLink ? "a" : "li";
+ const Component = as || "li";
const shouldFilterDOMProps = typeof Component === "string";
- const domRef = useDOMRef(ref);
+ const FragmentWrapper = isLink ? "a" : Fragment;
+ const fragmentWrapperProps = isLink ? {href: props.href} : {};
+
+ const domRef = useDOMRef(ref);
const router = useRouter();
const ariaLabel = useMemo(
@@ -129,6 +132,8 @@ export function usePaginationItem(props: UsePaginationItemProps) {
return {
Component,
+ FragmentWrapper,
+ fragmentWrapperProps,
children,
ariaLabel,
isFocused,