Avatar

An image that represents a person on your platform.

Installation

Copy/paste the following code to the specified file path:

~/components/nativewindui/Avatar.tsx
import * as React from 'react';
import {
  ImageErrorEventData,
  ImageLoadEventData,
  NativeSyntheticEvent,
  Image as RNImage,
  View,
} from 'react-native';

import { cn } from '~/lib/cn';

interface AvatarRootProps {
  alt: string;
}

interface AvatarImageProps {
  children?: React.ReactNode;
  onLoadingStatusChange?: (status: 'error' | 'loaded') => void;
}

type AvatarState = 'loading' | 'error' | 'loaded';

interface IRootContext extends AvatarRootProps {
  status: AvatarState;
  setStatus: (status: AvatarState) => void;
}

const RootContext = React.createContext<IRootContext | null>(null);

const Avatar = React.forwardRef<
  React.ElementRef<typeof View>,
  React.ComponentPropsWithoutRef<typeof View> & AvatarRootProps
  >(({ alt, className, ...viewProps }, ref) => {
  const [status, setStatus] = React.useState<AvatarState>('loading');

  return (
    <RootContext.Provider value={{ alt, status, setStatus }}>
      <View
        ref={ref}
        className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
        {...viewProps}
      />
    </RootContext.Provider>
  );
});

Avatar.displayName = 'Avatar';

function useRootContext() {
  const context = React.useContext(RootContext);
  if (!context) {
    throw new Error('Avatar compound components cannot be rendered outside the Avatar component');
  }
  return context;
}

const AvatarImage = React.forwardRef<
React.ElementRef<typeof RNImage>,
Omit<React.ComponentPropsWithoutRef<typeof RNImage>, 'alt'> & AvatarImageProps
>(
(
  { onLoad: onLoadProps, onError: onErrorProps, onLoadingStatusChange, className, ...props },
  ref
) => {
  const { alt, setStatus, status } = useRootContext();

  const onLoad = React.useCallback(
    (e: NativeSyntheticEvent<ImageLoadEventData>) => {
      setStatus('loaded');
      onLoadingStatusChange?.('loaded');
      onLoadProps?.(e);
    },
    [onLoadProps]
  );

  const onError = React.useCallback(
    (e: NativeSyntheticEvent<ImageErrorEventData>) => {
      setStatus('error');
      onLoadingStatusChange?.('error');
      onErrorProps?.(e);
    },
    [onErrorProps]
  );

  if (status === 'error') {
    return null;
  }

  return (
    <RNImage
      ref={ref}
      alt={alt}
      onLoad={onLoad}
      onError={onError}
      className={cn('aspect-square h-full w-full', className)}
      {...props}
    />
  );
});

AvatarImage.displayName = 'AvatarImage';

const AvatarFallback = React.forwardRef<
  React.ElementRef<typeof View>,
  React.ComponentPropsWithoutRef<typeof View>
  >(({ className, ...props }, ref) => {
  const { alt, status } = useRootContext();

  if (status !== 'error') {
    return null;
  }

  return (
    <View
      ref={ref}
      role="img"
      aria-label={alt}
      className={cn(
        'flex h-full w-full items-center justify-center rounded-full bg-muted',
        className
      )}
      {...props}
    />
  );
});

AvatarFallback.displayName = 'AvatarFallback';

export { Avatar, AvatarFallback, AvatarImage };

Usage

index.tsx
import { View } from 'react-native';

import { Avatar, AvatarFallback, AvatarImage } from '~/components/nativewindui/Avatar';
import { Text } from '~/components/nativewindui/Text';

function AvatarExample() {
  const TWITTER_AVATAR_URI =
  'https://pbs.twimg.com/profile_images/1782428433898708992/1voyv4_A_400x400.jpg';

  return (
    <View className="items-center">
      <Avatar alt="NativeWindUI Avatar">
      <AvatarImage source={{ uri: TWITTER_AVATAR_URI }} />
      <AvatarFallback>
        <Text>NUI</Text>
      </AvatarFallback>
      </Avatar>
    </View>
  );
}
© Ronin Technologies LLC 2024