Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'Avatar.js' component to TypeScript #31482

Merged
merged 10 commits into from
Dec 4, 2023
90 changes: 42 additions & 48 deletions src/components/Avatar.js → src/components/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import {StyleProp, View, ViewStyle} from 'react-native';
import {ValueOf} from 'type-fest';
import useNetwork from '@hooks/useNetwork';
import * as ReportUtils from '@libs/ReportUtils';
import stylePropTypes from '@styles/stylePropTypes';
import {AvatarSource} from '@libs/UserUtils';
import * as StyleUtils from '@styles/StyleUtils';
import useTheme from '@styles/themes/useTheme';
import useThemeStyles from '@styles/useThemeStyles';
Expand All @@ -13,55 +12,53 @@ import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Image from './Image';

const propTypes = {
type AvatarProps = {
/** Source for the avatar. Can be a URL or an icon. */
source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
source?: AvatarSource;

/** Extra styles to pass to Image */
// eslint-disable-next-line react/forbid-prop-types
imageStyles: PropTypes.arrayOf(PropTypes.object),
imageStyles?: ViewStyle[];

cdOut marked this conversation as resolved.
Show resolved Hide resolved
cdOut marked this conversation as resolved.
Show resolved Hide resolved
/** Additional styles to pass to Icon */
// eslint-disable-next-line react/forbid-prop-types
iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object),
cdOut marked this conversation as resolved.
Show resolved Hide resolved
iconAdditionalStyles?: StyleProp<ViewStyle>;

/** Extra styles to pass to View wrapper */
containerStyles: stylePropTypes,
containerStyles?: StyleProp<ViewStyle>;

/** Set the size of Avatar */
size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)),
size?: ValueOf<typeof CONST.AVATAR_SIZE>;

cdOut marked this conversation as resolved.
Show resolved Hide resolved
/**
* The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue'
* If the avatar is type === workspace, this fill color will be ignored and decided based on the name prop.
*/
fill: PropTypes.string,
fill?: string;

/** A fallback avatar icon to display when there is an error on loading avatar from remote URL.
* If the avatar is type === workspace, this fallback icon will be ignored and decided based on the name prop.
*/
fallbackIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
fallbackIcon?: AvatarSource;

/** Denotes whether it is an avatar or a workspace avatar */
type: PropTypes.oneOf([CONST.ICON_TYPE_AVATAR, CONST.ICON_TYPE_WORKSPACE]),
type?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE;

cdOut marked this conversation as resolved.
Show resolved Hide resolved
/** Owner of the avatar. If user, displayName. If workspace, policy name */
name: PropTypes.string,
name?: string;
};

const defaultProps = {
source: null,
imageStyles: [],
iconAdditionalStyles: [],
containerStyles: [],
size: CONST.AVATAR_SIZE.DEFAULT,
fill: undefined,
fallbackIcon: Expensicons.FallbackAvatar,
type: CONST.ICON_TYPE_AVATAR,
name: '',
};

function Avatar(props) {
function Avatar({
source,
imageStyles = [],
iconAdditionalStyles = [],
containerStyles = [],
size = CONST.AVATAR_SIZE.DEFAULT,
cdOut marked this conversation as resolved.
Show resolved Hide resolved
fill = undefined,
fallbackIcon = Expensicons.FallbackAvatar,
cdOut marked this conversation as resolved.
Show resolved Hide resolved
type = CONST.ICON_TYPE_AVATAR,
name = '',
}: AvatarProps) {
const theme = useTheme();
const styles = useThemeStyles();
const [imageError, setImageError] = useState(false);
Expand All @@ -70,46 +67,45 @@ function Avatar(props) {

useEffect(() => {
setImageError(false);
}, [props.source]);
}, [source]);

if (!props.source) {
if (!source) {
return null;
}

const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE;
const iconSize = StyleUtils.getAvatarSize(props.size);
const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE;
const iconSize = StyleUtils.getAvatarSize(size);

const imageStyle = imageStyles?.length ? [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius] : [StyleUtils.getAvatarStyle(size), styles.noBorderRadius];

const imageStyle =
props.imageStyles && props.imageStyles.length
? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, styles.noBorderRadius]
: [StyleUtils.getAvatarStyle(props.size), styles.noBorderRadius];
const iconStyle = imageStyles?.length ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined;

const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined;
const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name).fill : fill ?? theme.icon;
const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar;
cdOut marked this conversation as resolved.
Show resolved Hide resolved

const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill || theme.icon;
const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon || Expensicons.FallbackAvatar;
const avatarSource = imageError ? fallbackAvatar : source;

return (
<View style={[props.containerStyles, styles.pointerEventsNone]}>
{_.isFunction(props.source) || (imageError && _.isFunction(fallbackAvatar)) ? (
<View style={[containerStyles, styles.pointerEventsNone]}>
{typeof avatarSource === 'function' ? (
<View style={iconStyle}>
<Icon
src={imageError ? fallbackAvatar : props.source}
src={avatarSource}
height={iconSize}
width={iconSize}
fill={imageError ? theme.offline : iconFillColor}
additionalStyles={[
StyleUtils.getAvatarBorderStyle(props.size, props.type),
isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name) : {},
StyleUtils.getAvatarBorderStyle(size, type),
isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name) : {},
imageError ? StyleUtils.getBackgroundColorStyle(theme.fallbackIconColor) : {},
...props.iconAdditionalStyles,
cdOut marked this conversation as resolved.
Show resolved Hide resolved
iconAdditionalStyles,
]}
/>
</View>
) : (
<View style={[iconStyle, StyleUtils.getAvatarBorderStyle(props.size, props.type), ...props.iconAdditionalStyles]}>
<View style={[iconStyle, StyleUtils.getAvatarBorderStyle(size, type), iconAdditionalStyles]}>
<Image
source={{uri: imageError ? fallbackAvatar : props.source}}
source={{uri: avatarSource}}
style={imageStyle}
onError={() => setImageError(true)}
/>
Expand All @@ -119,8 +115,6 @@ function Avatar(props) {
);
}

Avatar.defaultProps = defaultProps;
Avatar.propTypes = propTypes;
Avatar.displayName = 'Avatar';

export default Avatar;
Loading