diff --git a/src/components/Avatar.js b/src/components/Avatar.js deleted file mode 100644 index 0e173f856593..000000000000 --- a/src/components/Avatar.js +++ /dev/null @@ -1,126 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import useNetwork from '@hooks/useNetwork'; -import * as ReportUtils from '@libs/ReportUtils'; -import stylePropTypes from '@styles/stylePropTypes'; -import * as StyleUtils from '@styles/StyleUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; -import CONST from '@src/CONST'; -import Icon from './Icon'; -import * as Expensicons from './Icon/Expensicons'; -import Image from './Image'; - -const propTypes = { - /** Source for the avatar. Can be a URL or an icon. */ - source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - - /** Extra styles to pass to Image */ - // eslint-disable-next-line react/forbid-prop-types - imageStyles: PropTypes.arrayOf(PropTypes.object), - - /** Additional styles to pass to Icon */ - // eslint-disable-next-line react/forbid-prop-types - iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object), - - /** Extra styles to pass to View wrapper */ - containerStyles: stylePropTypes, - - /** Set the size of Avatar */ - size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), - - /** - * 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, - - /** 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]), - - /** Denotes whether it is an avatar or a workspace avatar */ - type: PropTypes.oneOf([CONST.ICON_TYPE_AVATAR, CONST.ICON_TYPE_WORKSPACE]), - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name: PropTypes.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) { - const theme = useTheme(); - const styles = useThemeStyles(); - const [imageError, setImageError] = useState(false); - - useNetwork({onReconnect: () => setImageError(false)}); - - useEffect(() => { - setImageError(false); - }, [props.source]); - - if (!props.source) { - return null; - } - - const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; - const iconSize = StyleUtils.getAvatarSize(props.size); - - const imageStyle = - props.imageStyles && props.imageStyles.length - ? [StyleUtils.getAvatarStyle(theme, props.size), ...props.imageStyles, styles.noBorderRadius] - : [StyleUtils.getAvatarStyle(theme, props.size), styles.noBorderRadius]; - - const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(theme, props.size), styles.bgTransparent, ...props.imageStyles] : undefined; - - const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill || theme.icon; - const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon || Expensicons.FallbackAvatar; - - return ( - - {_.isFunction(props.source) || (imageError && _.isFunction(fallbackAvatar)) ? ( - - - - ) : ( - - setImageError(true)} - /> - - )} - - ); -} - -Avatar.defaultProps = defaultProps; -Avatar.propTypes = propTypes; -Avatar.displayName = 'Avatar'; - -export default Avatar; diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx new file mode 100644 index 000000000000..d394a84bd207 --- /dev/null +++ b/src/components/Avatar.tsx @@ -0,0 +1,118 @@ +import React, {useEffect, useState} from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import useNetwork from '@hooks/useNetwork'; +import * as ReportUtils from '@libs/ReportUtils'; +import {AvatarSource} from '@libs/UserUtils'; +import * as StyleUtils from '@styles/StyleUtils'; +import type {AvatarSizeName} from '@styles/StyleUtils'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; +import {AvatarType} from '@src/types/onyx/OnyxCommon'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import Image from './Image'; + +type AvatarProps = { + /** Source for the avatar. Can be a URL or an icon. */ + source?: AvatarSource; + + /** Extra styles to pass to Image */ + imageStyles?: StyleProp; + + /** Additional styles to pass to Icon */ + iconAdditionalStyles?: StyleProp; + + /** Extra styles to pass to View wrapper */ + containerStyles?: StyleProp; + + /** Set the size of Avatar */ + size?: AvatarSizeName; + + /** + * 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?: 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?: AvatarSource; + + /** Denotes whether it is an avatar or a workspace avatar */ + type?: AvatarType; + + /** Owner of the avatar. If user, displayName. If workspace, policy name */ + name?: string; +}; + +function Avatar({ + source, + imageStyles, + iconAdditionalStyles, + containerStyles, + size = CONST.AVATAR_SIZE.DEFAULT, + fill, + fallbackIcon = Expensicons.FallbackAvatar, + type = CONST.ICON_TYPE_AVATAR, + name = '', +}: AvatarProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const [imageError, setImageError] = useState(false); + + useNetwork({onReconnect: () => setImageError(false)}); + + useEffect(() => { + setImageError(false); + }, [source]); + + if (!source) { + return null; + } + + const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE; + const iconSize = StyleUtils.getAvatarSize(size); + + const imageStyle = [StyleUtils.getAvatarStyle(theme, size), imageStyles, styles.noBorderRadius]; + const iconStyle = imageStyles ? [StyleUtils.getAvatarStyle(theme, size), styles.bgTransparent, imageStyles] : undefined; + + const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name).fill : fill ?? theme.icon; + const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; + + const avatarSource = imageError ? fallbackAvatar : source; + + return ( + + {typeof avatarSource === 'function' ? ( + + + + ) : ( + + setImageError(true)} + /> + + )} + + ); +} + +Avatar.displayName = 'Avatar'; + +export default Avatar; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 0a244d4392e4..52ecf6633d9b 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -6,6 +6,7 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {AvatarType} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; @@ -14,7 +15,7 @@ type SubAvatar = { source?: AvatarSource; /** Denotes whether it is an avatar or a workspace avatar */ - type?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; + type?: AvatarType; /** Owner of the avatar. If user, displayName. If workspace, policy name */ name?: string; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 9a131dad48cd..4cb62b930632 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1377,6 +1377,8 @@ function getDotIndicatorTextStyles(styles: ThemeStyles, isErrorText = true): Tex return isErrorText ? {...styles.offlineFeedback.text, color: styles.formError.color} : {...styles.offlineFeedback.text}; } +export type {AvatarSizeName}; + export { combineStyles, displayIfTrue, diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 49d3428be532..956e9ff36b24 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -10,12 +10,14 @@ type ErrorFields = Record; +type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; + type Icon = { /** Avatar source to display */ source: AvatarSource; /** Denotes whether it is an avatar or a workspace avatar */ - type: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; + type: AvatarType; /** Owner of the avatar. If user, displayName. If workspace, policy name */ name: string; @@ -27,4 +29,4 @@ type Icon = { fallbackIcon?: AvatarSource; }; -export type {Icon, PendingAction, PendingFields, ErrorFields, Errors}; +export type {Icon, PendingAction, PendingFields, ErrorFields, Errors, AvatarType};