Skip to content

Commit

Permalink
Simplify root and context
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Jan 31, 2025
1 parent cc939fc commit 8c0e720
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 69 deletions.
18 changes: 12 additions & 6 deletions packages/react/src/avatar/fallback/AvatarFallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { BaseUIComponentProps } from '../../utils/types';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useAvatarRootContext } from '../root/AvatarRootContext';
import type { AvatarRoot } from '../root/AvatarRoot';

/**
* Rendered when the image fails to load or when no image is provided.
Expand All @@ -15,7 +16,7 @@ const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallback.Props>(
function AvatarFallback(props: AvatarFallback.Props, forwardedRef) {
const { className, render, delay, ...otherProps } = props;

const context = useAvatarRootContext();
const { imageLoadingStatus } = useAvatarRootContext();
const [delayPassed, setDelayPassed] = React.useState(delay === undefined);

React.useEffect(() => {
Expand All @@ -30,29 +31,34 @@ const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallback.Props>(
};
}, [delay]);

const state: AvatarRoot.State = React.useMemo(
() => ({
imageLoadingStatus,
}),
[imageLoadingStatus],
);

const { renderElement } = useComponentRenderer({
render: render ?? 'span',
state: context.state,
state,
className,
ref: forwardedRef,
extraProps: otherProps,
});

const shouldRender = context.imageLoadingStatus !== 'loaded' && delayPassed;
const shouldRender = imageLoadingStatus !== 'loaded' && delayPassed;

return shouldRender ? renderElement() : null;
},
);

export namespace AvatarFallback {
export interface Props extends BaseUIComponentProps<'span', State> {
export interface Props extends BaseUIComponentProps<'span', AvatarRoot.State> {
/**
* How long to wait before showing the fallback. Specified in milliseconds.
*/
delay?: number;
}

export interface State {}
}

export { AvatarFallback };
Expand Down
32 changes: 14 additions & 18 deletions packages/react/src/avatar/image/AvatarImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useEventCallback } from '../../utils/useEventCallback';
import { useEnhancedEffect } from '../../utils/useEnhancedEffect';
import { useAvatarRootContext } from '../root/AvatarRootContext';
import type { AvatarRoot } from '../root/AvatarRoot';
import { useImageLoadingStatus, ImageLoadingStatus } from './useImageLoadingStatus';

/**
Expand All @@ -19,21 +20,14 @@ const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImage.Props>(functi
props: AvatarImage.Props,
forwardedRef,
) {
const {
className,
render,
src,
onLoadingStatusChange = NOOP,
referrerPolicy,
...otherProps
} = props;
const { className, render, onLoadingStatusChange = NOOP, referrerPolicy, ...otherProps } = props;

const context = useAvatarRootContext();
const imageLoadingStatus = useImageLoadingStatus(src, referrerPolicy);
const imageLoadingStatus = useImageLoadingStatus(props.src, referrerPolicy);

const handleLoadingStatusChange = useEventCallback((status: ImageLoadingStatus) => {
onLoadingStatusChange(status);
context.onImageLoadingStatusChange(status);
context.setImageLoadingStatus(status);
});

useEnhancedEffect(() => {
Expand All @@ -42,26 +36,28 @@ const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImage.Props>(functi
}
}, [imageLoadingStatus, handleLoadingStatusChange]);

const state: AvatarRoot.State = React.useMemo(
() => ({
imageLoadingStatus,
}),
[imageLoadingStatus],
);

const { renderElement } = useComponentRenderer({
render: render ?? 'img',
state: context.state,
state,
className,
ref: forwardedRef,
extraProps: {
...otherProps,
src,
},
extraProps: otherProps,
});

return imageLoadingStatus === 'loaded' ? renderElement() : null;
});

export namespace AvatarImage {
export interface Props extends BaseUIComponentProps<'img', State> {
export interface Props extends BaseUIComponentProps<'img', AvatarRoot.State> {
onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
}

export interface State {}
}

export { AvatarImage };
Expand Down
18 changes: 9 additions & 9 deletions packages/react/src/avatar/root/AvatarRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { BaseUIComponentProps } from '../../utils/types';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useAvatarRoot } from './useAvatarRoot';
import { AvatarRootContext } from './AvatarRootContext';

const rootStyleHookMapping = {
Expand All @@ -22,25 +21,24 @@ const AvatarRoot = React.forwardRef<HTMLSpanElement, AvatarRoot.Props>(function
) {
const { className, render, ...otherProps } = props;

const { getRootProps, ...avatar } = useAvatarRoot();
const [imageLoadingStatus, setImageLoadingStatus] = React.useState<ImageLoadingStatus>('idle');

const state: AvatarRoot.State = React.useMemo(
() => ({
imageLoadingStatus: avatar.imageLoadingStatus,
imageLoadingStatus,
}),
[avatar.imageLoadingStatus],
[imageLoadingStatus],
);

const contextValue = React.useMemo(
() => ({
...avatar,
state,
imageLoadingStatus,
setImageLoadingStatus,
}),
[avatar, state],
[imageLoadingStatus, setImageLoadingStatus],
);

const { renderElement } = useComponentRenderer({
propGetter: getRootProps,
render: render ?? 'span',
state,
className,
Expand All @@ -54,11 +52,13 @@ const AvatarRoot = React.forwardRef<HTMLSpanElement, AvatarRoot.Props>(function
);
});

export type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';

export namespace AvatarRoot {
export interface Props extends BaseUIComponentProps<'span', State> {}

export interface State {
imageLoadingStatus: 'idle' | 'loading' | 'loaded' | 'error';
imageLoadingStatus: ImageLoadingStatus;
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/avatar/root/AvatarRootContext.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client';
import * as React from 'react';
import type { AvatarRoot } from './AvatarRoot';
import type { useAvatarRoot } from './useAvatarRoot';
import type { ImageLoadingStatus } from './AvatarRoot';

export interface AvatarRootContext extends Omit<useAvatarRoot.ReturnValue, 'getRootProps'> {
state: AvatarRoot.State;
export interface AvatarRootContext {
imageLoadingStatus: ImageLoadingStatus;
setImageLoadingStatus: React.Dispatch<React.SetStateAction<ImageLoadingStatus>>;
}

export const AvatarRootContext = React.createContext<AvatarRootContext | undefined>(undefined);
Expand Down
32 changes: 0 additions & 32 deletions packages/react/src/avatar/root/useAvatarRoot.ts

This file was deleted.

0 comments on commit 8c0e720

Please sign in to comment.