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 { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { getFakeBadge } from '../test-both/helpers/getFakeBadge';
import { ThemeType } from '../types/Util';
const i18n = setupI18n('en', enMessages);
@ -64,7 +65,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
sharedGroupNames: [],
size: 80,
title: overrideProps.title || '',
theme: overrideProps.theme,
theme: overrideProps.theme || ThemeType.light,
});
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 = {
avatarPath?: string;
badge?: BadgeType;
blur?: AvatarBlur;
color?: AvatarColorType;
loading?: boolean;
@ -63,7 +62,6 @@ export type Props = {
profileName?: string;
sharedGroupNames: Array<string>;
size: AvatarSize;
theme?: ThemeType;
title: string;
unblurredAvatarPath?: string;
searchResult?: boolean;
@ -75,7 +73,11 @@ export type Props = {
innerRef?: React.Ref<HTMLDivElement>;
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>([
[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 (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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