fix(avatar): add the safeInitials function to get the initials name (#5677)

* fix(Avatar): fix getInitials

* chore: update test

* chore(user): revise example

---------

Co-authored-by: WK Wong <wingkwong.code@gmail.com>
This commit is contained in:
doki- 2025-10-04 01:14:00 +08:00 committed by GitHub
parent 7b7190e222
commit 0d95d7faa0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 27 additions and 9 deletions

View File

@ -0,0 +1,6 @@
---
"@heroui/shared-utils": patch
"@heroui/avatar": patch
---
fix getInitials (#5671)

View File

@ -31,7 +31,7 @@ describe("Avatar", () => {
it("should render initials", () => {
const {container} = render(<Avatar name="Junior" />);
expect(container.querySelector("span")).toHaveTextContent("Jun");
expect(container.querySelector("span")).toHaveTextContent("J");
});
it('should work with custom "getInitials" function', () => {
@ -96,7 +96,7 @@ describe("Avatar - fallback + loading strategy", () => {
test("should render a name avatar if no src", () => {
const {container} = render(<Avatar name="Junior" />);
expect(container.querySelector("span")).toHaveTextContent("Jun");
expect(container.querySelector("span")).toHaveTextContent("J");
});
test("should render a default avatar if no name or src", () => {
@ -118,7 +118,7 @@ describe("Avatar - fallback + loading strategy", () => {
jest.runAllTimers();
});
expect(container.querySelector("span")).toHaveTextContent("Jun");
expect(container.querySelector("span")).toHaveTextContent("JG");
});
test("should render default avatar fallback when image fails to load with no name and showFallback is true", async () => {
@ -149,7 +149,7 @@ describe("Avatar - fallback + loading strategy", () => {
jest.runAllTimers();
});
expect(container.querySelector("span")).not.toHaveTextContent("Jun");
expect(container.querySelector("span")).not.toHaveTextContent("JG");
expect(container.querySelector("svg")).not.toBeInTheDocument();
});
@ -166,7 +166,7 @@ describe("Avatar - fallback + loading strategy", () => {
jest.runAllTimers();
});
expect(container.querySelector("span")).not.toHaveTextContent("Jun");
expect(container.querySelector("span")).not.toHaveTextContent("JG");
expect(container.querySelector("svg")).not.toBeInTheDocument();
});
});

View File

@ -5,7 +5,7 @@ import type {ReactRef} from "@heroui/react-utils";
import {avatar} from "@heroui/theme";
import {useProviderContext} from "@heroui/system";
import {useDOMRef, filterDOMProps} from "@heroui/react-utils";
import {clsx, safeText, dataAttr, mergeProps} from "@heroui/shared-utils";
import {clsx, dataAttr, mergeProps, safeInitials} from "@heroui/shared-utils";
import {useFocusRing} from "@react-aria/focus";
import {useMemo, useCallback} from "react";
import {useImage} from "@heroui/use-image";
@ -117,7 +117,7 @@ export function useAvatar(originalProps: UseAvatarProps = {}) {
isBordered = groupContext?.isBordered ?? false,
isDisabled = groupContext?.isDisabled ?? false,
isFocusable = false,
getInitials = safeText,
getInitials = safeInitials,
ignoreFallback = false,
showFallback: showFallbackProp = false,
ImgComponent = "img",

View File

@ -94,12 +94,12 @@ describe("User", () => {
<User
avatarProps={{
icon: <AvatarIcon />,
name: "WK",
name: "Marcus Wong",
}}
name="test"
/>,
);
expect(getByRole("img")).toHaveTextContent("WK");
expect(getByRole("img")).toHaveTextContent("MW");
});
});

View File

@ -4,6 +4,18 @@ export const safeText = (text: string): string => {
return text?.slice(0, 3);
};
export const safeInitials = (text: string): string => {
const initials =
text
?.trim()
.split(/[\s\-_.]+/)
.filter(Boolean)
.map((word) => word.charAt(0).toUpperCase())
.join("") || "";
return safeText(initials);
};
export const safeAriaLabel = (...texts: any[]): string => {
let ariaLabel = " ";