Repository: ui
Issue: [feat]: Using `next/image` for Avatar as radix doest use next/image
Labels: area: request

### Feature description

Want next/image optimisation on AvatarImage

### Affected component/components

AvatarImage

### Additional Context

I have remade the radix Avatar with next/image so would like to use it instead of radix Components for Avatar, a drop in replacement, but small changes are required in `ui/avatar`, there might be few improvements that can be done please do let me know,

> Changes in `ui/avatar`
> Add className `object-cover` to AvatarPrimitive.Image
> Add `fill={props.fill || !props.width || !props.height}` to AvatarPrimitive.Image
> Create the other file of the radix replacement and use the new import instead of the `radix`
> Check & update if you have missed to add `alt` values anywhere while using it, as `next/image` `alt` is a required prop

```tsx
"use client";

import * as React from "react";
import * as AvatarPrimitive from "./extension/new-avatar";

import { cn } from "@/utils/cn";

const Avatar = React.forwardRef<
  React.ElementRef<typeof AvatarPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
  <AvatarPrimitive.Root
    ref={ref}
    className={cn(
      "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
      className,
    )}
    {...props}
  />
));
Avatar.displayName = AvatarPrimitive.Root.displayName;

const AvatarImage = React.forwardRef<
  React.ElementRef<typeof AvatarPrimitive.Image>,
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
  <AvatarPrimitive.Image
    ref={ref}
    className={cn("aspect-square h-full w-full object-cover", className)}          // Add object-cover
    fill={props.fill || !props.width || !props.height}                                                         // And a fill like this
    {...props}
  />
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;

const AvatarFallback = React.forwardRef<
  React.ElementRef<typeof AvatarPrimitive.Fallback>,
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
  <AvatarPrimitive.Fallback
    ref={ref}
    className={cn(
      "flex h-full w-full items-center justify-center rounded-full bg-muted",
      className,
    )}
    {...props}
  />
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;

export { Avatar, AvatarFallback, AvatarImage };
```

> Make a new file `new-avatar.tsx' and use this
```tsx
"use client";

import * as X from "next/image";
import * as React from "react";

const NextImage = X.default;

/* -------------------------------------------------------------------------------------------------
 * Avatar
 * -----------------------------------------------------------------------------------------------*/

const AVATAR_NAME = "Avatar";

// Create a context for the Avatar
const AvatarContext = React.createContext<AvatarContextValue | undefined>(
  undefined,
);

type ImageLoadingStatus = "idle" | "loading" | "loaded" | "error";

type AvatarContextValue = {
  imageLoadingStatus: ImageLoadingStatus;
  onImageLoadingStatusChange: (status: ImageLoadingStatus) => void;
};

type AvatarProps = React.ComponentPropsWithoutRef<"span">;

const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
  (props, forwardedRef) => {
    const [imageLoadingStatus, setImageLoadingStatus] =
      React.useState<ImageLoadingStatus>("idle");

    const value = {
      imageLoadingStatus,
      onImageLoadingStatusChange: setImageLoadingStatus,
    };

    return (
      <AvatarContext.Provider value={value}>
        <span {...props} ref={forwardedRef} />
      </AvatarContext.Provider>
    );
  },
);

Avatar.displayName = AVATAR_NAME;

/* -------------------------------------------------------------------------------------------------
 * AvatarImage
 * -----------------------------------------------------------------------------------------------*/

const IMAGE_NAME = "AvatarImage";

type AvatarImageProps = React.ComponentPropsWithoutRef<typeof NextImage> & {
  onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
};

const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImageProps>(
  (props, forwardedRef) => {
    const { src, onLoadingStatusChange = () => {}, ...imageProps } = props;
    const context = React.useContext(AvatarContext);

    const imageLoadingStatus = useImageLoadingStatus(
      src,
      imageProps.referrerPolicy,
    );

    React.useEffect(() => {
      if (context) {
        if (imageLoadingStatus !== "idle") {
          onLoadingStatusChange(imageLoadingStatus);
          context.onImageLoadingStatusChange(imageLoadingStatus);
        }
      }
    }, [imageLoadingStatus, context, onLoadingStatusChange]);

    return imageLoadingStatus === "loaded" ? (
      <NextImage {...imageProps} ref={forwardedRef} src={src} alt="" />
    ) : null;
  },
);

AvatarImage.displayName = IMAGE_NAME;

/* -------------------------------------------------------------------------------------------------
 * AvatarFallback
 * -----------------------------------------------------------------------------------------------*/

const FALLBACK_NAME = "AvatarFallback";

type AvatarFallbackProps = React.ComponentPropsWithoutRef<"span"> & {
  delayMs?: number;
};

const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallbackProps>(
  (props, forwardedRef) => {
    const { delayMs, ...fallbackProps } = props;
    const context = React.useContext(AvatarContext);
    const [canRender, setCanRender] = React.useState(delayMs === undefined);

    React.useEffect(() => {
      if (delayMs !== undefined) {
        const timerId = window.setTimeout(() => setCanRender(true), delayMs);
        return () => window.clearTimeout(timerId);
      }
    }, [delayMs]);

    return canRender && context?.imageLoadingStatus !== "loaded" ? (
      <span {...fallbackProps} ref={forwardedRef} />
    ) : null;
  },
);

AvatarFallback.displayName = FALLBACK_NAME;

/* -----------------------------------------------------------------------------------------------*/

function useImageLoadingStatus(
  src?: AvatarImageProps["src"],
  referrerPolicy?: React.HTMLAttributeReferrerPolicy,
) {
  const [loadingStatus, setLoadingStatus] =
    React.useState<ImageLoadingStatus>("idle");

  React.useEffect(() => {
    if (!src) {
      setLoadingStatus("error");
      return;
    }

    let isMounted = true;
    const image = new window.Image();

    const updateStatus = (status: ImageLoadingStatus) => () => {
      if (!isMounted) return;
      setLoadingStatus(status);
    };

    setLoadingStatus("loading");
    image.onload = updateStatus("loaded");
    image.onerror = updateStatus("error");
    image.src = src as string;
    if (referrerPolicy) {
      image.referrerPolicy = referrerPolicy;
    }

    return () => {
      isMounted = false;
    };
  }, [src, referrerPolicy]);

  return loadingStatus;
}

const Root = Avatar;
const Image = AvatarImage;
const Fallback = AvatarFallback;

export { Avatar, AvatarFallback, AvatarImage, Fallback, Image, Root };
export type { AvatarFallbackProps, AvatarImageProps, AvatarProps };
```

### Before submitting

- [X] I've made research efforts and searched the documentation
- [X] I've searched for existing issues and PRs