From 5f769aff82473e06bcbdf708899447bf3afdaf6c Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:34:28 +0100 Subject: [PATCH 1/8] migrate Avatar.js to typescript --- src/components/{Avatar.js => Avatar.tsx} | 88 +++++++++++------------- 1 file changed, 40 insertions(+), 48 deletions(-) rename src/components/{Avatar.js => Avatar.tsx} (54%) diff --git a/src/components/Avatar.js b/src/components/Avatar.tsx similarity index 54% rename from src/components/Avatar.js rename to src/components/Avatar.tsx index 0052400bf51a..1729cb9fbe88 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.tsx @@ -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'; @@ -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[]; /** Additional styles to pass to Icon */ // eslint-disable-next-line react/forbid-prop-types - iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object), + iconAdditionalStyles?: StyleProp; /** Extra styles to pass to View wrapper */ - containerStyles: stylePropTypes, + containerStyles?: StyleProp; /** Set the size of Avatar */ - size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), + size?: ValueOf; /** * 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; /** 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, + fill = undefined, + fallbackIcon = Expensicons.FallbackAvatar, + type = CONST.ICON_TYPE_AVATAR, + name = '', +}: AvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); const [imageError, setImageError] = useState(false); @@ -70,46 +67,43 @@ 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 = - props.imageStyles && props.imageStyles.length - ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, styles.noBorderRadius] - : [StyleUtils.getAvatarStyle(props.size), styles.noBorderRadius]; + const imageStyle = imageStyles?.length ? [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius] : [StyleUtils.getAvatarStyle(size), styles.noBorderRadius]; - const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; + const iconStyle = imageStyles?.length ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, 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; + const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name).fill : fill ?? theme.icon; + const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; return ( - - {_.isFunction(props.source) || (imageError && _.isFunction(fallbackAvatar)) ? ( + + {typeof source === 'function' || imageError && typeof fallbackAvatar === 'function' ? ( ) : ( - + setImageError(true)} /> @@ -119,8 +113,6 @@ function Avatar(props) { ); } -Avatar.defaultProps = defaultProps; -Avatar.propTypes = propTypes; Avatar.displayName = 'Avatar'; export default Avatar; From 55c18972bee4606acbc0956eb012059942c0868f Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 17 Nov 2023 11:53:08 +0100 Subject: [PATCH 2/8] fix correct source typing --- src/components/Avatar.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 1729cb9fbe88..a9912d88daf8 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -83,12 +83,14 @@ function Avatar({ 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 source === 'function' || imageError && typeof fallbackAvatar === 'function' ? ( + {typeof avatarSource === 'function' ? ( setImageError(true)} /> From a50448b6fc87394ac6ef668c26492b00f0b776ee Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 17 Nov 2023 11:56:26 +0100 Subject: [PATCH 3/8] fix source attribute requirement --- src/components/Avatar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index a9912d88daf8..94b898c09b48 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -14,7 +14,7 @@ import Image from './Image'; type AvatarProps = { /** Source for the avatar. Can be a URL or an icon. */ - source: AvatarSource; + source?: AvatarSource; /** Extra styles to pass to Image */ // eslint-disable-next-line react/forbid-prop-types From 0c531d9376ced59639e729ae763251a80ccc585e Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 21 Nov 2023 11:51:20 +0100 Subject: [PATCH 4/8] remove eslint comments and swap ternary --- src/components/Avatar.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 94b898c09b48..124506b09bf5 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -17,11 +17,9 @@ type AvatarProps = { source?: AvatarSource; /** Extra styles to pass to Image */ - // eslint-disable-next-line react/forbid-prop-types imageStyles?: ViewStyle[]; /** Additional styles to pass to Icon */ - // eslint-disable-next-line react/forbid-prop-types iconAdditionalStyles?: StyleProp; /** Extra styles to pass to View wrapper */ @@ -96,8 +94,8 @@ function Avatar({ fill={imageError ? theme.offline : iconFillColor} additionalStyles={[ StyleUtils.getAvatarBorderStyle(size, type), - isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name) : {}, - imageError ? StyleUtils.getBackgroundColorStyle(theme.fallbackIconColor) : {}, + isWorkspace && StyleUtils.getDefaultWorkspaceAvatarColor(name), + imageError && StyleUtils.getBackgroundColorStyle(theme.fallbackIconColor), iconAdditionalStyles, ]} /> From 3a72e07a66e5127dc91cb128b80c76e798299ef2 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:55:54 +0100 Subject: [PATCH 5/8] remove fill default value --- src/components/Avatar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 124506b09bf5..1aecd1b3dfde 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -52,7 +52,7 @@ function Avatar({ iconAdditionalStyles = [], containerStyles = [], size = CONST.AVATAR_SIZE.DEFAULT, - fill = undefined, + fill, fallbackIcon = Expensicons.FallbackAvatar, type = CONST.ICON_TYPE_AVATAR, name = '', From 1a89fb8874c3c1820ab287692830af275f3fd569 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 23 Nov 2023 08:38:50 +0100 Subject: [PATCH 6/8] create separate type for AvatarType --- src/components/Avatar.tsx | 3 ++- src/components/SubscriptAvatar.tsx | 3 ++- src/types/onyx/OnyxCommon.ts | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 1aecd1b3dfde..f715ff9496aa 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -11,6 +11,7 @@ import CONST from '@src/CONST'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Image from './Image'; +import { AvatarType } from '@src/types/onyx/OnyxCommon'; type AvatarProps = { /** Source for the avatar. Can be a URL or an icon. */ @@ -40,7 +41,7 @@ type AvatarProps = { fallbackIcon?: 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/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index ab9f0dec8e57..c10dfec13d74 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -8,13 +8,14 @@ import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; +import { AvatarType } from '@src/types/onyx/OnyxCommon'; type SubAvatar = { /** 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; diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index ac69baed3ef1..e432a6c00c18 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}; From 2cee42f88b4d8d4b108647d3072e3a9a665d9ded Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 23 Nov 2023 08:39:54 +0100 Subject: [PATCH 7/8] fix prettier --- src/components/Avatar.tsx | 2 +- src/components/SubscriptAvatar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index f715ff9496aa..ffcacdbe2576 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -8,10 +8,10 @@ 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 Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Image from './Image'; -import { AvatarType } from '@src/types/onyx/OnyxCommon'; type AvatarProps = { /** Source for the avatar. Can be a URL or an icon. */ diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index c10dfec13d74..c6bba8168ee3 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -6,9 +6,9 @@ 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'; -import { AvatarType } from '@src/types/onyx/OnyxCommon'; type SubAvatar = { /** Avatar source to display */ From 8f981a0a75fa45538952bf7ec0fca143a26c2764 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:52:27 +0100 Subject: [PATCH 8/8] fix ts suggestions --- src/components/Avatar.tsx | 17 ++++++++--------- src/styles/StyleUtils.ts | 2 ++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index ffcacdbe2576..2e67dd0025e1 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,10 +1,10 @@ import React, {useEffect, useState} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; 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'; @@ -18,7 +18,7 @@ type AvatarProps = { source?: AvatarSource; /** Extra styles to pass to Image */ - imageStyles?: ViewStyle[]; + imageStyles?: StyleProp; /** Additional styles to pass to Icon */ iconAdditionalStyles?: StyleProp; @@ -27,7 +27,7 @@ type AvatarProps = { containerStyles?: StyleProp; /** Set the size of Avatar */ - size?: ValueOf; + size?: AvatarSizeName; /** * The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' @@ -49,9 +49,9 @@ type AvatarProps = { function Avatar({ source, - imageStyles = [], - iconAdditionalStyles = [], - containerStyles = [], + imageStyles, + iconAdditionalStyles, + containerStyles, size = CONST.AVATAR_SIZE.DEFAULT, fill, fallbackIcon = Expensicons.FallbackAvatar, @@ -75,9 +75,8 @@ function Avatar({ 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 iconStyle = imageStyles?.length ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined; + const imageStyle = [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius]; + const iconStyle = imageStyles ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined; const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name).fill : fill ?? theme.icon; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 4b998f940244..1443717d046e 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1390,6 +1390,8 @@ function getDotIndicatorTextStyles(isErrorText = true): TextStyle { return isErrorText ? {...styles.offlineFeedback.text, color: styles.formError.color} : {...styles.offlineFeedback.text}; } +export type {AvatarSizeName}; + export { combineStyles, displayIfTrue,