Skip to content

Commit

Permalink
Feature/adding avatar component (#2)
Browse files Browse the repository at this point in the history
* test: inconsistency in stories with `asChild` props

* fix: returns null dynamically if fails to fetch image from FrameworkImage's Component

* test: reverted accidental removal of `waitFor`
  • Loading branch information
Udhayarajan committed Sep 14, 2024
1 parent c546fa9 commit fed51e6
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 18 deletions.
21 changes: 18 additions & 3 deletions packages/react/avatar/src/Avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ export const Styled = () => (

<h1>With image framework component & with fallback (but broken src)</h1>
<Avatar.Root className={rootClass()}>
<Avatar.Image className={imageClass()} alt="John Smith" onLoadingStatusChange={console.log}>
<Avatar.Image
className={imageClass()}
alt="John Smith"
onLoadingStatusChange={console.log}
asChild
>
<FakeFrameworkImage src={srcBroken} />
</Avatar.Image>
<Avatar.Fallback className={fallbackClass()}>
Expand Down Expand Up @@ -97,7 +102,12 @@ export const Chromatic = () => (

<h1>With image framework component</h1>
<Avatar.Root className={rootClass()}>
<Avatar.Image className={imageClass()} alt="John Smith" onLoadingStatusChange={console.log}>
<Avatar.Image
className={imageClass()}
alt="John Smith"
onLoadingStatusChange={console.log}
asChild
>
<FakeFrameworkImage src={otherSrc} />
</Avatar.Image>
<Avatar.Fallback className={fallbackClass()}>
Expand All @@ -107,7 +117,12 @@ export const Chromatic = () => (

<h1>With image framework component & with fallback (but broken src)</h1>
<Avatar.Root className={rootClass()}>
<Avatar.Image className={imageClass()} alt="John Smith" onLoadingStatusChange={console.log}>
<Avatar.Image
className={imageClass()}
alt="John Smith"
onLoadingStatusChange={console.log}
asChild
>
<FakeFrameworkImage src={srcBroken} />
</Avatar.Image>
<Avatar.Fallback className={fallbackClass()}>
Expand Down
2 changes: 1 addition & 1 deletion packages/react/avatar/src/Avatar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { axe } from 'jest-axe';
import type { RenderResult } from '@testing-library/react';
import { render } from '@testing-library/react';
import { render, waitFor } from '@testing-library/react';
import * as Avatar from '@radix-ui/react-avatar';

const ROOT_TEST_ID = 'avatar-root';
Expand Down
35 changes: 21 additions & 14 deletions packages/react/avatar/src/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as React from 'react';
import type { Scope } from '@radix-ui/react-context';
import { createContextScope } from '@radix-ui/react-context';
import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
import { useLayoutEffect } from '@radix-ui/react-use-layout-effect';
import { Primitive } from '@radix-ui/react-primitive';

import type { Scope } from '@radix-ui/react-context';

/* -------------------------------------------------------------------------------------------------
* Avatar
* -----------------------------------------------------------------------------------------------*/
Expand All @@ -25,7 +26,6 @@ const [AvatarProvider, useAvatarContext] = createAvatarContext<AvatarContextValu

type AvatarElement = React.ElementRef<typeof Primitive.span>;
type PrimitiveSpanProps = React.ComponentPropsWithoutRef<typeof Primitive.span>;

interface AvatarProps extends PrimitiveSpanProps {}

const Avatar = React.forwardRef<AvatarElement, AvatarProps>(
Expand Down Expand Up @@ -54,32 +54,36 @@ const IMAGE_NAME = 'AvatarImage';

type AvatarImageElement = React.ElementRef<typeof Primitive.img>;
type PrimitiveImageProps = React.ComponentPropsWithoutRef<typeof Primitive.img>;

interface AvatarImageProps extends PrimitiveImageProps {
onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
}

const AvatarImage = React.forwardRef<AvatarImageElement, AvatarImageProps>(
(props: ScopedProps<AvatarImageProps>, forwardedRef) => {
const { __scopeAvatar, src, onLoadingStatusChange = () => {}, ...imageProps } = props;

const context = useAvatarContext(IMAGE_NAME, __scopeAvatar);
const imageLoadingStatus = useImageLoadingStatus(src, props.asChild, imageProps.referrerPolicy);
const { loadingStatus: imageLoadingStatus, setLoadingStatus } = useImageLoadingStatus(
src,
props.asChild,
imageProps.referrerPolicy
);
const handleLoadingStatusChange = useCallbackRef((status: ImageLoadingStatus) => {
setLoadingStatus(status);
onLoadingStatusChange(status);
context.onImageLoadingStatusChange(status);
});

useLayoutEffect(() => {
if (!src) {
handleLoadingStatusChange('error');
return;
if (imageLoadingStatus !== 'idle') {
handleLoadingStatusChange(imageLoadingStatus);
}

handleLoadingStatusChange('loading');
}, [handleLoadingStatusChange, src]);
}, [imageLoadingStatus, handleLoadingStatusChange]);

if (props.asChild && props.children) {
if (imageLoadingStatus === 'error') {
return null;
}

// Ensure children is a valid React element
const child = React.Children.only(props.children) as React.ReactElement;

Expand Down Expand Up @@ -147,7 +151,11 @@ AvatarFallback.displayName = FALLBACK_NAME;

/* -----------------------------------------------------------------------------------------------*/

function useImageLoadingStatus(src?: string, bypass?: boolean, referrerPolicy?: React.HTMLAttributeReferrerPolicy) {
function useImageLoadingStatus(
src?: string,
bypass?: boolean,
referrerPolicy?: React.HTMLAttributeReferrerPolicy
) {
const [loadingStatus, setLoadingStatus] = React.useState<ImageLoadingStatus>('idle');

useLayoutEffect(() => {
Expand Down Expand Up @@ -181,9 +189,8 @@ function useImageLoadingStatus(src?: string, bypass?: boolean, referrerPolicy?:
};
}, [src, bypass, referrerPolicy]);

return loadingStatus;
return { loadingStatus, setLoadingStatus };
}

const Root = Avatar;
const Image = AvatarImage;
const Fallback = AvatarFallback;
Expand Down

0 comments on commit fed51e6

Please sign in to comment.