Require badge props in <Avatar> to ensure no missing spots

This commit is contained in:
Evan Hahn 2021-12-01 11:24:00 -06:00 committed by GitHub
parent 7affe313f0
commit e030b3d18c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 94 additions and 46 deletions

View file

@ -16,6 +16,7 @@ import type { AvatarColorType } from '../types/Colors';
import { AvatarColors } from '../types/Colors'; import { AvatarColors } from '../types/Colors';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { getFakeBadge } from '../test-both/helpers/getFakeBadge'; import { getFakeBadge } from '../test-both/helpers/getFakeBadge';
import { ThemeType } from '../types/Util';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
@ -64,7 +65,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
sharedGroupNames: [], sharedGroupNames: [],
size: 80, size: 80,
title: overrideProps.title || '', title: overrideProps.title || '',
theme: overrideProps.theme, theme: overrideProps.theme || ThemeType.light,
}); });
const sizes: Array<Props['size']> = [112, 96, 80, 52, 32, 28]; const sizes: Array<Props['size']> = [112, 96, 80, 52, 32, 28];

View file

@ -49,7 +49,6 @@ type BadgePlacementType = { bottom: number; right: number };
export type Props = { export type Props = {
avatarPath?: string; avatarPath?: string;
badge?: BadgeType;
blur?: AvatarBlur; blur?: AvatarBlur;
color?: AvatarColorType; color?: AvatarColorType;
loading?: boolean; loading?: boolean;
@ -63,7 +62,6 @@ export type Props = {
profileName?: string; profileName?: string;
sharedGroupNames: Array<string>; sharedGroupNames: Array<string>;
size: AvatarSize; size: AvatarSize;
theme?: ThemeType;
title: string; title: string;
unblurredAvatarPath?: string; unblurredAvatarPath?: string;
searchResult?: boolean; searchResult?: boolean;
@ -75,7 +73,11 @@ export type Props = {
innerRef?: React.Ref<HTMLDivElement>; innerRef?: React.Ref<HTMLDivElement>;
i18n: LocalizerType; i18n: LocalizerType;
} & Pick<React.HTMLProps<HTMLDivElement>, 'className'>; } & (
| { badge: undefined; theme?: ThemeType }
| { badge: BadgeType; theme: ThemeType }
) &
Pick<React.HTMLProps<HTMLDivElement>, 'className'>;
const BADGE_PLACEMENT_BY_SIZE = new Map<number, BadgePlacementType>([ const BADGE_PLACEMENT_BY_SIZE = new Map<number, BadgePlacementType>([
[28, { bottom: -4, right: -2 }], [28, { bottom: -4, right: -2 }],
@ -290,8 +292,6 @@ export const Avatar: FunctionComponent<Props> = ({
); );
} }
} }
} else if (badge && !theme) {
log.error('<Avatar> requires a theme if a badge is provided');
} }
return ( return (

View file

@ -13,6 +13,8 @@ import type { AvatarColorType } from '../types/Colors';
import { AvatarColors } from '../types/Colors'; import { AvatarColors } from '../types/Colors';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { getFakeBadge } from '../test-both/helpers/getFakeBadge';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
@ -29,9 +31,10 @@ const conversationTypeMap: Record<string, Props['conversationType']> = {
group: 'group', group: 'group',
}; };
const createProps = (overrideProps: Partial<Props> = {}): Props => ({ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
acceptedMessageRequest: true, acceptedMessageRequest: true,
avatarPath: text('avatarPath', overrideProps.avatarPath || ''), avatarPath: text('avatarPath', overrideProps.avatarPath || ''),
badge: overrideProps.badge,
color: select('color', colorMap, overrideProps.color || AvatarColors[0]), color: select('color', colorMap, overrideProps.color || AvatarColors[0]),
conversationType: select( conversationType: select(
'conversationType', 'conversationType',
@ -52,19 +55,29 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
size: 80, size: 80,
startUpdate: action('startUpdate'), startUpdate: action('startUpdate'),
style: {}, style: {},
theme: React.useContext(StorybookThemeContext),
title: text('title', overrideProps.title || ''), title: text('title', overrideProps.title || ''),
}); });
const stories = storiesOf('Components/Avatar Popup', module); const stories = storiesOf('Components/Avatar Popup', module);
stories.add('Avatar Only', () => { stories.add('Avatar Only', () => {
const props = createProps(); const props = useProps();
return <AvatarPopup {...props} />;
});
stories.add('Has badge', () => {
const props = useProps({
badge: getFakeBadge(),
title: 'Janet Yellen',
});
return <AvatarPopup {...props} />; return <AvatarPopup {...props} />;
}); });
stories.add('Title', () => { stories.add('Title', () => {
const props = createProps({ const props = useProps({
title: 'My Great Title', title: 'My Great Title',
}); });
@ -72,7 +85,7 @@ stories.add('Title', () => {
}); });
stories.add('Profile Name', () => { stories.add('Profile Name', () => {
const props = createProps({ const props = useProps({
profileName: 'Sam Neill', profileName: 'Sam Neill',
}); });
@ -80,7 +93,7 @@ stories.add('Profile Name', () => {
}); });
stories.add('Phone Number', () => { stories.add('Phone Number', () => {
const props = createProps({ const props = useProps({
profileName: 'Sam Neill', profileName: 'Sam Neill',
phoneNumber: '(555) 867-5309', phoneNumber: '(555) 867-5309',
}); });
@ -89,7 +102,7 @@ stories.add('Phone Number', () => {
}); });
stories.add('Update Available', () => { stories.add('Update Available', () => {
const props = createProps({ const props = useProps({
hasPendingUpdate: true, hasPendingUpdate: true,
}); });

View file

@ -8,10 +8,11 @@ import type { Props as AvatarProps } from './Avatar';
import { Avatar } from './Avatar'; import { Avatar } from './Avatar';
import { useRestoreFocus } from '../hooks/useRestoreFocus'; import { useRestoreFocus } from '../hooks/useRestoreFocus';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType, ThemeType } from '../types/Util';
export type Props = { export type Props = {
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
readonly theme: ThemeType;
hasPendingUpdate: boolean; hasPendingUpdate: boolean;
startUpdate: () => unknown; startUpdate: () => unknown;

View file

@ -47,6 +47,7 @@ export const CallNeedPermissionScreen: React.FC<Props> = ({
<Avatar <Avatar
acceptedMessageRequest={conversation.acceptedMessageRequest} acceptedMessageRequest={conversation.acceptedMessageRequest}
avatarPath={conversation.avatarPath} avatarPath={conversation.avatarPath}
badge={undefined}
color={conversation.color || AvatarColors[0]} color={conversation.color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"

View file

@ -327,6 +327,7 @@ export const CallScreen: React.FC<PropsType> = ({
<Avatar <Avatar
acceptedMessageRequest acceptedMessageRequest
avatarPath={me.avatarPath} avatarPath={me.avatarPath}
badge={undefined}
color={me.color || AvatarColors[0]} color={me.color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"
@ -364,6 +365,7 @@ export const CallScreen: React.FC<PropsType> = ({
<Avatar <Avatar
acceptedMessageRequest acceptedMessageRequest
avatarPath={me.avatarPath} avatarPath={me.avatarPath}
badge={undefined}
color={me.color || AvatarColors[0]} color={me.color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"

View file

@ -99,6 +99,7 @@ export const CallingParticipantsList = React.memo(
participant.acceptedMessageRequest participant.acceptedMessageRequest
} }
avatarPath={participant.avatarPath} avatarPath={participant.avatarPath}
badge={undefined}
color={participant.color} color={participant.color}
conversationType="direct" conversationType="direct"
i18n={i18n} i18n={i18n}

View file

@ -51,6 +51,7 @@ const NoVideo = ({
<Avatar <Avatar
acceptedMessageRequest={acceptedMessageRequest} acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={undefined}
color={color || AvatarColors[0]} color={color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"

View file

@ -164,6 +164,7 @@ export const CallingPreCallInfo: FunctionComponent<PropsType> = ({
<div className="module-CallingPreCallInfo"> <div className="module-CallingPreCallInfo">
<Avatar <Avatar
avatarPath={conversation.avatarPath} avatarPath={conversation.avatarPath}
badge={undefined}
color={conversation.color} color={conversation.color}
acceptedMessageRequest={conversation.acceptedMessageRequest} acceptedMessageRequest={conversation.acceptedMessageRequest}
conversationType={conversation.type} conversationType={conversation.type}

View file

@ -52,6 +52,7 @@ export const ContactPill: FunctionComponent<PropsType> = ({
<Avatar <Avatar
acceptedMessageRequest={acceptedMessageRequest} acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={undefined}
color={color} color={color}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"

View file

@ -70,6 +70,7 @@ function renderAvatar(
<Avatar <Avatar
acceptedMessageRequest={acceptedMessageRequest} acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={undefined}
color={color || AvatarColors[0]} color={color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"

View file

@ -319,6 +319,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
<Avatar <Avatar
acceptedMessageRequest={acceptedMessageRequest} acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={undefined}
color={color || AvatarColors[0]} color={color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType="direct" conversationType="direct"

View file

@ -78,6 +78,7 @@ export const GroupV2JoinDialog = React.memo((props: PropsType) => {
<Avatar <Avatar
acceptedMessageRequest={false} acceptedMessageRequest={false}
avatarPath={avatar ? avatar.url : undefined} avatarPath={avatar ? avatar.url : undefined}
badge={undefined}
blur={AvatarBlur.NoBlur} blur={AvatarBlur.NoBlur}
loading={avatar && !avatar.url} loading={avatar && !avatar.url}
conversationType="group" conversationType="group"

View file

@ -230,6 +230,7 @@ export const IncomingCallBar = (props: PropsType): JSX.Element | null => {
<Avatar <Avatar
acceptedMessageRequest={acceptedMessageRequest} acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={undefined}
color={color || AvatarColors[0]} color={color || AvatarColors[0]}
noteToSelf={false} noteToSelf={false}
conversationType={conversationType} conversationType={conversationType}

View file

@ -56,6 +56,7 @@ export const LeftPaneSearchInput = forwardRef<HTMLInputElement, PropsType>(
<Avatar <Avatar
acceptedMessageRequest={searchConversation.acceptedMessageRequest} acceptedMessageRequest={searchConversation.acceptedMessageRequest}
avatarPath={searchConversation.avatarPath} avatarPath={searchConversation.avatarPath}
badge={undefined}
color={searchConversation.color} color={searchConversation.color}
conversationType={searchConversation.type} conversationType={searchConversation.type}
i18n={i18n} i18n={i18n}

View file

@ -673,6 +673,7 @@ function LightboxHeader({
<Avatar <Avatar
acceptedMessageRequest={conversation.acceptedMessageRequest} acceptedMessageRequest={conversation.acceptedMessageRequest}
avatarPath={conversation.avatarPath} avatarPath={conversation.avatarPath}
badge={undefined}
color={conversation.color} color={conversation.color}
conversationType={conversation.type} conversationType={conversation.type}
i18n={i18n} i18n={i18n}

View file

@ -37,6 +37,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}
@ -65,6 +66,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}
@ -92,6 +94,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}
@ -114,6 +117,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}
@ -136,6 +140,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}
@ -158,6 +163,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}
@ -180,6 +186,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', 'Cayce Bollard (profile)')} title={text('title', 'Cayce Bollard (profile)')}
@ -202,6 +209,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<Wrapper <Wrapper
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', '+1 (646) 327-2700')} title={text('title', '+1 (646) 327-2700')}
@ -226,6 +234,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
isMe={false} isMe={false}
title={text('title', 'Unknown contact')} title={text('title', 'Unknown contact')}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
name={text('name', '')} name={text('name', '')}
profileName={text('profileName', '')} profileName={text('profileName', '')}
@ -247,6 +256,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
isMe={false} isMe={false}
title={text('title', 'Unknown contact')} title={text('title', 'Unknown contact')}
acceptedMessageRequest={false} acceptedMessageRequest={false}
badge={undefined}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
name={text('name', '')} name={text('name', '')}
profileName={text('profileName', '')} profileName={text('profileName', '')}
@ -265,6 +275,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<Wrapper <Wrapper
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
@ -284,6 +295,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<Wrapper <Wrapper
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
@ -303,6 +315,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<Wrapper <Wrapper
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
@ -323,6 +336,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<Wrapper <Wrapper
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
@ -343,6 +357,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<Wrapper <Wrapper
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={text('title', 'Unknown group')} title={text('title', 'Unknown group')}
@ -362,6 +377,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<Wrapper <Wrapper
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe isMe
title={getTitle()} title={getTitle()}

View file

@ -423,6 +423,7 @@ const renderHeroRow = () => {
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest acceptedMessageRequest
badge={undefined}
i18n={i18n} i18n={i18n}
isMe={false} isMe={false}
title={getTitle()} title={getTitle()}

View file

@ -49,6 +49,7 @@ export function renderAvatar({
<Avatar <Avatar
acceptedMessageRequest={false} acceptedMessageRequest={false}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={undefined}
blur={AvatarBlur.NoBlur} blur={AvatarBlur.NoBlur}
color={AvatarColors[0]} color={AvatarColors[0]}
conversationType="direct" conversationType="direct"

View file

@ -67,10 +67,10 @@ type PropsType = {
); );
export const BaseConversationListItem: FunctionComponent<PropsType> = export const BaseConversationListItem: FunctionComponent<PropsType> =
React.memo(function BaseConversationListItem({ React.memo(function BaseConversationListItem(props) {
const {
acceptedMessageRequest, acceptedMessageRequest,
avatarPath, avatarPath,
badge,
checked, checked,
color, color,
conversationType, conversationType,
@ -93,11 +93,11 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
profileName, profileName,
sharedGroupNames, sharedGroupNames,
shouldShowSpinner, shouldShowSpinner,
theme,
title, title,
unblurredAvatarPath, unblurredAvatarPath,
unreadCount, unreadCount,
}) { } = props;
const identifier = id ? cleanId(id) : undefined; const identifier = id ? cleanId(id) : undefined;
const htmlId = useMemo(() => uuid(), []); const htmlId = useMemo(() => uuid(), []);
const isUnread = isConversationUnread({ markedUnread, unreadCount }); const isUnread = isConversationUnread({ markedUnread, unreadCount });
@ -145,7 +145,6 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
<Avatar <Avatar
acceptedMessageRequest={acceptedMessageRequest} acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath} avatarPath={avatarPath}
badge={badge}
color={color} color={color}
conversationType={conversationType} conversationType={conversationType}
noteToSelf={isAvatarNoteToSelf} noteToSelf={isAvatarNoteToSelf}
@ -155,11 +154,14 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
name={name} name={name}
phoneNumber={phoneNumber} phoneNumber={phoneNumber}
profileName={profileName} profileName={profileName}
theme={theme}
title={title} title={title}
sharedGroupNames={sharedGroupNames} sharedGroupNames={sharedGroupNames}
size={AvatarSize.FORTY_EIGHT} size={AvatarSize.FORTY_EIGHT}
unblurredAvatarPath={unblurredAvatarPath} unblurredAvatarPath={unblurredAvatarPath}
// This is here to appease the type checker.
{...(props.badge
? { badge: props.badge, theme: props.theme }
: { badge: undefined })}
/> />
<div <div
className={classNames( className={classNames(