From 56b4a8a3b05ea050c92d477ec344617db3b0b97b Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 6 Mar 2024 17:48:38 +0100 Subject: [PATCH 1/5] RoomHeaderAvatars migrated to ts --- src/ROUTES.ts | 2 +- src/components/Avatar.tsx | 44 +-------------- ...HeaderAvatars.js => RoomHeaderAvatars.tsx} | 54 +++++++++---------- src/components/types.ts | 44 +++++++++++++++ 4 files changed, 71 insertions(+), 73 deletions(-) rename src/components/{RoomHeaderAvatars.js => RoomHeaderAvatars.tsx} (72%) create mode 100644 src/components/types.ts diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cfc287ba2cdc..8c4a9c47f570 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -37,7 +37,7 @@ const ROUTES = { }, PROFILE_AVATAR: { route: 'a/:accountID/avatar', - getRoute: (accountID: string) => `a/${accountID}/avatar` as const, + getRoute: (accountID: string | number) => `a/${accountID}/avatar` as const, }, TRANSITION_BETWEEN_APPS: 'transition', diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 2b2d0a60f657..4ce50ecad0cc 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,55 +1,16 @@ import React, {useEffect, useState} from 'react'; -import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; +import type {ImageStyle, StyleProp} from 'react-native'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; -import type {AvatarSource} from '@libs/UserUtils'; -import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; -import type {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; - - /** Used to locate fallback icon in end-to-end tests. */ - fallbackIconTestID?: string; - - /** 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; -}; +import type AvatarProps from './types'; function Avatar({ source, @@ -124,4 +85,3 @@ function Avatar({ Avatar.displayName = 'Avatar'; export default Avatar; -export {type AvatarProps}; diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.tsx similarity index 72% rename from src/components/RoomHeaderAvatars.js rename to src/components/RoomHeaderAvatars.tsx index 64cc9ac7abf3..836e55e79567 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.tsx @@ -1,63 +1,58 @@ -import PropTypes from 'prop-types'; import React, {memo} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import Avatar from './Avatar'; -import avatarPropTypes from './avatarPropTypes'; import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; import Text from './Text'; +import type AvatarProps from './types'; -const propTypes = { - icons: PropTypes.arrayOf(avatarPropTypes), - reportID: PropTypes.string, +type RoomHeaderAvatarsProps = { + icons: AvatarProps[]; + reportID: string; }; -const defaultProps = { - icons: [], - reportID: '', -}; - -function RoomHeaderAvatars(props) { - const navigateToAvatarPage = (icon) => { +function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) { + const navigateToAvatarPage = (icon: AvatarProps) => { if (icon.type === CONST.ICON_TYPE_WORKSPACE) { - Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(props.reportID)); + Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); return; } - Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); + if (icon.id !== undefined) { + Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); + } }; const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - if (!props.icons.length) { + if (icons.length) { return null; } - if (props.icons.length === 1) { + if (icons.length === 1) { return ( navigateToAvatarPage(props.icons[0])} + onPress={() => navigateToAvatarPage(icons[0])} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={props.icons[0].name} + accessibilityLabel={icons[0].name ?? ''} > ); } - const iconsToDisplay = props.icons.slice(0, CONST.REPORT.MAX_PREVIEW_AVATARS); + const iconsToDisplay = icons.slice(0, CONST.REPORT.MAX_PREVIEW_AVATARS); const iconStyle = [ styles.roomHeaderAvatar, @@ -68,8 +63,9 @@ function RoomHeaderAvatars(props) { return ( - {_.map(iconsToDisplay, (icon, index) => ( + {iconsToDisplay.map((icon, index) => ( @@ -77,7 +73,7 @@ function RoomHeaderAvatars(props) { style={[styles.mln4, StyleUtils.getAvatarBorderRadius(CONST.AVATAR_SIZE.LARGE_BORDERED, icon.type)]} onPress={() => navigateToAvatarPage(icon)} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={icon.name} + accessibilityLabel={icon.name ?? ''} > - {index === CONST.REPORT.MAX_PREVIEW_AVATARS - 1 && props.icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS !== 0 && ( + {index === CONST.REPORT.MAX_PREVIEW_AVATARS - 1 && icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS !== 0 && ( <> - {`+${props.icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS}`} + {`+${icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS}`} )} @@ -110,8 +106,6 @@ function RoomHeaderAvatars(props) { ); } -RoomHeaderAvatars.defaultProps = defaultProps; -RoomHeaderAvatars.propTypes = propTypes; RoomHeaderAvatars.displayName = 'RoomHeaderAvatars'; export default memo(RoomHeaderAvatars); diff --git a/src/components/types.ts b/src/components/types.ts new file mode 100644 index 000000000000..a1cf9c2a0c4f --- /dev/null +++ b/src/components/types.ts @@ -0,0 +1,44 @@ +import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; +import type {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSizeName} from '@styles/utils'; +import type {AvatarType} from '@src/types/onyx/OnyxCommon'; + +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; + + /** Used to locate fallback icon in end-to-end tests. */ + fallbackIconTestID?: string; + + /** 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; + id?: string | number; +}; + +export default AvatarProps; From fa08a81639143936fe795593ecf386c9500e924d Mon Sep 17 00:00:00 2001 From: smelaa Date: Thu, 7 Mar 2024 11:37:29 +0100 Subject: [PATCH 2/5] Address reviewer's comments --- src/components/Avatar.tsx | 47 ++++++++++++++++++++++++++-- src/components/RoomHeaderAvatars.tsx | 6 ++-- src/components/types.ts | 44 -------------------------- 3 files changed, 48 insertions(+), 49 deletions(-) delete mode 100644 src/components/types.ts diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 4ce50ecad0cc..e5725b779110 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,16 +1,58 @@ import React, {useEffect, useState} from 'react'; -import type {ImageStyle, StyleProp} from 'react-native'; import {View} from 'react-native'; +import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; +import type {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; +import type {AvatarType} from '@src/types/onyx/OnyxCommon'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Image from './Image'; -import type AvatarProps from './types'; + +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; + + /** Used to locate fallback icon in end-to-end tests. */ + fallbackIconTestID?: string; + + /** 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; + // this prop is used in RoomHeaderAvatars + // eslint-disable-next-line react/no-unused-prop-types + id?: string | number; +}; function Avatar({ source, @@ -85,3 +127,4 @@ function Avatar({ Avatar.displayName = 'Avatar'; export default Avatar; +export {type AvatarProps}; diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 836e55e79567..dd986ce39e31 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -5,10 +5,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type {AvatarProps} from './Avatar'; import Avatar from './Avatar'; import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; import Text from './Text'; -import type AvatarProps from './types'; type RoomHeaderAvatarsProps = { icons: AvatarProps[]; @@ -21,14 +21,14 @@ function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); return; } - if (icon.id !== undefined) { + if (icon.id) { Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); } }; const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - if (icons.length) { + if (!icons.length) { return null; } diff --git a/src/components/types.ts b/src/components/types.ts deleted file mode 100644 index a1cf9c2a0c4f..000000000000 --- a/src/components/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; -import type {AvatarSource} from '@libs/UserUtils'; -import type {AvatarSizeName} from '@styles/utils'; -import type {AvatarType} from '@src/types/onyx/OnyxCommon'; - -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; - - /** Used to locate fallback icon in end-to-end tests. */ - fallbackIconTestID?: string; - - /** 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; - id?: string | number; -}; - -export default AvatarProps; From 69b5ce2c8b0be693c00b140d1c964bf1608a17f2 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 20 Mar 2024 09:46:39 +0100 Subject: [PATCH 3/5] Address review comments --- src/components/Avatar.tsx | 3 --- src/components/RoomHeaderAvatars.tsx | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index e5725b779110..da42bd86ecdb 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -49,9 +49,6 @@ type AvatarProps = { /** Owner of the avatar. If user, displayName. If workspace, policy name */ name?: string; - // this prop is used in RoomHeaderAvatars - // eslint-disable-next-line react/no-unused-prop-types - id?: string | number; }; function Avatar({ diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index dd986ce39e31..7a7348b52667 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -5,18 +5,18 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import type {AvatarProps} from './Avatar'; +import type {Icon} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; import Text from './Text'; type RoomHeaderAvatarsProps = { - icons: AvatarProps[]; + icons: Icon[]; reportID: string; }; function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) { - const navigateToAvatarPage = (icon: AvatarProps) => { + const navigateToAvatarPage = (icon: Icon) => { if (icon.type === CONST.ICON_TYPE_WORKSPACE) { Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); return; From 4490b99daad69fa168694f532761c252b4bb74e7 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 20 Mar 2024 10:12:04 +0100 Subject: [PATCH 4/5] Revert changes in Avatar.tsx --- 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 da42bd86ecdb..2b2d0a60f657 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; From aa91b5c2bb8016b21d84206ec543cc8dcc5894df Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 20 Mar 2024 10:48:57 +0100 Subject: [PATCH 5/5] Address review coments --- src/components/RoomHeaderAvatars.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 7a7348b52667..9298062aa6f9 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -15,12 +15,13 @@ type RoomHeaderAvatarsProps = { reportID: string; }; -function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) { +function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { const navigateToAvatarPage = (icon: Icon) => { if (icon.type === CONST.ICON_TYPE_WORKSPACE) { Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); return; } + if (icon.id) { Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); } @@ -28,6 +29,7 @@ function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + if (!icons.length) { return null; } @@ -35,14 +37,14 @@ function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) if (icons.length === 1) { return ( navigateToAvatarPage(icons[0])} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={icons[0].name ?? ''} >