Contact: New design for shared contact (#4333)

This commit is contained in:
Alexander Zinchuk 2024-03-08 12:48:44 +01:00
parent 550e2b4373
commit 7bf1b2b878
4 changed files with 184 additions and 57 deletions

View File

@ -0,0 +1,35 @@
import React, { memo } from '../../lib/teact/teact';
import type { ApiPeer } from '../../api/types';
import buildClassName from '../../util/buildClassName';
import { getPeerColorClass } from './helpers/peerColor';
import EmojiIconBackground from './embedded/EmojiIconBackground';
type OwnProps = {
peer?: ApiPeer;
noUserColors?: boolean;
shoudReset?: boolean;
className?: string;
emojiIconClassName?: string;
children: React.ReactNode;
};
function PeerColorWrapper({
peer, noUserColors, shoudReset, className, emojiIconClassName, children,
}: OwnProps) {
return (
<div className={buildClassName(getPeerColorClass(peer, noUserColors, shoudReset), className)}>
{peer?.color?.backgroundEmojiId && (
<EmojiIconBackground
className={emojiIconClassName}
emojiDocumentId={peer.color.backgroundEmojiId}
/>
)}
{children}
</div>
);
}
export default memo(PeerColorWrapper);

View File

@ -0,0 +1,83 @@
.root {
position: relative;
margin: 0.25rem 0.25rem 0.875rem 0.25rem;
border-radius: 0.25rem;
overflow: hidden;
background-color: var(--accent-background-color);
color: var(--accent-color);
&::before {
content: "";
display: block;
position: absolute;
top: 0;
bottom: 0;
inset-inline-start: 0;
width: 0.1875rem;
background: var(--bar-gradient, var(--accent-color));
}
}
.info-container {
display: flex;
padding: 0.5rem 1.5rem 0.5rem 0.75rem;
cursor: var(--custom-cursor, pointer);
}
.info {
display: flex;
flex-direction: column;
align-self: center;
margin-left: 0.5rem;
}
.name {
font-size: 1rem;
margin-bottom: 0.25rem;
font-weight: 500;
max-width: 12.5rem;
}
.phone {
color: var(--color-text);
}
.name,
.phone {
line-height: 1rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.divider {
height: 0.0625rem;
margin: 0 0.5rem;
background: var(--accent-color);
filter: opacity(0.4)
}
.buttons {
display: flex;
.button {
width: auto;
flex: 1;
height: 2.25rem;
border-radius: 0;
font-weight: 500;
background-color: transparent;
color: var(--accent-color);
--ripple-color: var(--accent-background-active-color);
&:not(.disabled):not(:disabled):hover {
color: var(--accent-color);
background-color: transparent;
}
}
}
.emoji-icon-background {
margin-bottom: 2.25rem;
}

View File

@ -1,38 +0,0 @@
.Contact {
display: flex;
align-items: center;
padding: 0.25rem;
&.interactive {
cursor: var(--custom-cursor, pointer);
}
.Avatar {
margin-right: 0.8125rem;
}
.contact-info {
padding: 0.5rem;
padding-left: 0;
white-space: nowrap;
overflow: hidden;
.contact-name {
font-size: 1rem;
margin-bottom: 0.25rem;
font-weight: 500;
}
.contact-phone {
color: var(--secondary-color);
}
.contact-name,
.contact-phone {
line-height: 1rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}

View File

@ -4,15 +4,19 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiContact, ApiCountryCode, ApiUser } from '../../../api/types';
import { getCanAddContact, getUserFullName } from '../../../global/helpers';
import { selectUser } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { copyTextToClipboard } from '../../../util/clipboard';
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import Avatar from '../../common/Avatar';
import PeerColorWrapper from '../../common/PeerColorWrapper';
import Button from '../../ui/Button';
import './Contact.scss';
import styles from './Contact.module.scss';
type OwnProps = {
contact: ApiContact;
@ -28,38 +32,81 @@ const UNREGISTERED_CONTACT_ID = '0';
const Contact: FC<OwnProps & StateProps> = ({
contact, user, phoneCodeList,
}) => {
const { openChat } = getActions();
const lang = useLang();
const {
openChat, openAddContactDialog, showNotification, openChatWithInfo,
} = getActions();
const {
firstName,
lastName,
phoneNumber,
userId,
} = contact;
const isRegistered = userId !== UNREGISTERED_CONTACT_ID;
const canAddContact = isRegistered && user && getCanAddContact(user);
const handleClick = useLastCallback(() => {
const handleOpenChat = useLastCallback(() => {
openChat({ id: userId });
});
const handleAddContact = useLastCallback(() => {
openAddContactDialog({ userId: user?.id });
});
const handleClick = useLastCallback(() => {
if (user) {
openChatWithInfo({ id: userId });
} else {
copyTextToClipboard(phoneNumber);
showNotification({ message: lang('PhoneCopied') });
}
});
return (
<div
className={buildClassName('Contact', isRegistered && 'interactive')}
onClick={isRegistered ? handleClick : undefined}
>
<Avatar
size="large"
peer={user}
text={firstName || lastName}
/>
<div className="contact-info">
<div className="contact-name">{firstName} {lastName}</div>
<div className="contact-phone">{formatPhoneNumberWithCode(phoneCodeList, phoneNumber)}</div>
<PeerColorWrapper peer={user} emojiIconClassName={styles.emojiIconBackground} className={styles.root}>
<div className={styles.infoContainer} onClick={handleClick}>
<Avatar size="large" peer={user} text={getContactName(contact)} />
<div className={styles.info}>
<div className={styles.name}>
{user ? getUserFullName(user) : getContactName(contact)}
</div>
<div className={styles.phone}>{formatPhoneNumberWithCode(phoneCodeList, phoneNumber)}</div>
</div>
</div>
</div>
{isRegistered && (
<>
<div className={styles.divider} />
<div className={styles.buttons}>
<Button isText color="translucent" ripple onClick={handleOpenChat} className={styles.button}>
{lang('SharedContactMessage')}
</Button>
{canAddContact && (
<Button isText color="translucent" ripple onClick={handleAddContact} className={styles.button}>
{lang('SharedContactAdd')}
</Button>
)}
</div>
</>
)}
</PeerColorWrapper>
);
};
function getContactName(contact: ApiContact) {
if (contact.firstName && contact.lastName) {
return `${contact.firstName} ${contact.lastName}`;
}
if (contact.firstName) {
return contact.firstName;
}
if (contact.lastName) {
return contact.lastName;
}
return '';
}
export default withGlobal<OwnProps>(
(global, { contact }): StateProps => {
const { countryList: { phoneCodes: phoneCodeList } } = global;