// AlignUI Avatar v0.0.0 import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cn } from '@/utils/cn'; import { recursiveCloneChildren } from '@/utils/recursive-clone-children'; import { tv, type VariantProps } from '@/utils/tv'; import { IconEmptyCompany, IconEmptyUser, } from '@/components/ui/avatar-empty-icons'; export const AVATAR_ROOT_NAME = 'AvatarRoot'; const AVATAR_IMAGE_NAME = 'AvatarImage'; const AVATAR_INDICATOR_NAME = 'AvatarIndicator'; const AVATAR_STATUS_NAME = 'AvatarStatus'; const AVATAR_BRAND_LOGO_NAME = 'AvatarBrandLogo'; const AVATAR_NOTIFICATION_NAME = 'AvatarNotification'; export const avatarVariants = tv({ slots: { root: [ 'relative flex shrink-0 items-center justify-center rounded-full', 'select-none text-center uppercase', ], image: 'size-full rounded-full object-cover', indicator: 'absolute flex size-8 items-center justify-center drop-shadow-[0_2px_4px_#1b1c1d0a]', }, variants: { size: { '80': { root: 'size-20 text-title-h5', }, '72': { root: 'size-[72px] text-title-h5', }, '64': { root: 'size-16 text-title-h5', }, '56': { root: 'size-14 text-label-lg', }, '48': { root: 'size-12 text-label-lg', }, '40': { root: 'size-10 text-label-md', }, '32': { root: 'size-8 text-label-sm', }, '24': { root: 'size-6 text-label-xs', }, '20': { root: 'size-5 text-label-xs', }, }, color: { gray: { root: 'bg-bg-soft-200 text-static-black', }, yellow: { root: 'bg-yellow-200 text-yellow-950', }, blue: { root: 'bg-blue-200 text-blue-950', }, sky: { root: 'bg-sky-200 text-sky-950', }, purple: { root: 'bg-purple-200 text-purple-950', }, red: { root: 'bg-red-200 text-red-950', }, }, }, compoundVariants: [ { size: ['80', '72'], class: { indicator: '-right-2', }, }, { size: '64', class: { indicator: '-right-2 scale-[.875]', }, }, { size: '56', class: { indicator: '-right-1.5 scale-75', }, }, { size: '48', class: { indicator: '-right-1.5 scale-[.625]', }, }, { size: '40', class: { indicator: '-right-1.5 scale-[.5625]', }, }, { size: '32', class: { indicator: '-right-1.5 scale-50', }, }, { size: '24', class: { indicator: '-right-1 scale-[.375]', }, }, { size: '20', class: { indicator: '-right-1 scale-[.3125]', }, }, ], defaultVariants: { size: '80', color: 'gray', }, }); type AvatarSharedProps = VariantProps; export type AvatarRootProps = VariantProps & React.HTMLAttributes & { asChild?: boolean; placeholderType?: 'user' | 'company'; }; const AvatarRoot = React.forwardRef( ( { asChild, children, size, color, className, placeholderType = 'user', ...rest }, forwardedRef, ) => { const uniqueId = React.useId(); const Component = asChild ? Slot : 'div'; const { root } = avatarVariants({ size, color }); const sharedProps: AvatarSharedProps = { size, color, }; // use placeholder icon if no children provided if (!children) { return (
{placeholderType === 'company' ? ( ) : ( )}
); } const extendedChildren = recursiveCloneChildren( children as React.ReactElement[], sharedProps, [AVATAR_IMAGE_NAME, AVATAR_INDICATOR_NAME], uniqueId, asChild, ); return ( {extendedChildren} ); }, ); AvatarRoot.displayName = AVATAR_ROOT_NAME; type AvatarImageProps = AvatarSharedProps & Omit, 'color'> & { asChild?: boolean; }; const AvatarImage = React.forwardRef( ({ asChild, className, size, color, ...rest }, forwardedRef) => { const Component = asChild ? Slot : 'img'; const { image } = avatarVariants({ size, color }); return ( ); }, ); AvatarImage.displayName = AVATAR_IMAGE_NAME; function AvatarIndicator({ size, color, className, position = 'bottom', ...rest }: AvatarSharedProps & React.HTMLAttributes & { position?: 'top' | 'bottom'; }) { const { indicator } = avatarVariants({ size, color }); return (
); } AvatarIndicator.displayName = AVATAR_INDICATOR_NAME; export const avatarStatusVariants = tv({ base: 'box-content size-3 rounded-full border-4 border-bg-white-0', variants: { status: { online: 'bg-success-base', offline: 'bg-faded-base', busy: 'bg-error-base', away: 'bg-away-base', }, }, defaultVariants: { status: 'online', }, }); function AvatarStatus({ status, className, ...rest }: VariantProps & React.HTMLAttributes) { return (
); } AvatarStatus.displayName = AVATAR_STATUS_NAME; type AvatarBrandLogoProps = React.ImgHTMLAttributes & { asChild?: boolean; }; const AvatarBrandLogo = React.forwardRef< HTMLImageElement, AvatarBrandLogoProps >(({ asChild, className, ...rest }, forwardedRef) => { const Component = asChild ? Slot : 'img'; return ( ); }); AvatarBrandLogo.displayName = AVATAR_BRAND_LOGO_NAME; function AvatarNotification({ className, ...rest }: React.HTMLAttributes) { return (
); } AvatarNotification.displayName = AVATAR_NOTIFICATION_NAME; export { AvatarRoot as Root, AvatarImage as Image, AvatarIndicator as Indicator, AvatarStatus as Status, AvatarBrandLogo as BrandLogo, AvatarNotification as Notification, };