Update to new design for avatars: individual/group icons/colors
And two initials.
This commit is contained in:
parent
cf16ced91c
commit
8f3e3b7aaf
21 changed files with 1210 additions and 1017 deletions
299
ts/components/Avatar.md
Normal file
299
ts/components/Avatar.md
Normal file
|
@ -0,0 +1,299 @@
|
|||
### With avatar
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
color="pink"
|
||||
name="John Smith"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="pink"
|
||||
name="Puppies"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
conversationType="group"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### With only name
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
color="blue"
|
||||
name="John"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="green"
|
||||
name="John Smith"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="red"
|
||||
name="Puppies"
|
||||
conversationType="group"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### Just phone number
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
color="pink"
|
||||
phoneNumber="(555) 353-3433"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### All colors
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
color="red"
|
||||
name="Red"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="deep_orange"
|
||||
name="Deep Orange"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="brown"
|
||||
name="Broen"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="pink"
|
||||
name="Pink"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="purple"
|
||||
name="Purple"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="indigo"
|
||||
name="Indigo"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="blue"
|
||||
name="Blue"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="teal"
|
||||
name="Teal"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="green"
|
||||
name="Green"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="light_green"
|
||||
name="Light Green"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={28}
|
||||
color="blue_grey"
|
||||
name="Blue Grey"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### 36px
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={36}
|
||||
color="teal"
|
||||
name="John Smith"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={36}
|
||||
color="teal"
|
||||
name="John"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={36}
|
||||
color="teal"
|
||||
name="John Smith"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={36}
|
||||
color="teal"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={36}
|
||||
color="teal"
|
||||
name="Pupplies"
|
||||
conversationType="group"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### 48px
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={48}
|
||||
color="teal"
|
||||
name="John Smith"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={48}
|
||||
color="teal"
|
||||
name="John"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={48}
|
||||
color="teal"
|
||||
name="John Smith"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={48}
|
||||
color="teal"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={48}
|
||||
color="teal"
|
||||
name="Pupplies"
|
||||
conversationType="group"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### 80px
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={80}
|
||||
color="teal"
|
||||
name="John Smith"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={80}
|
||||
color="teal"
|
||||
name="John"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={80}
|
||||
color="teal"
|
||||
name="John Smith"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={80}
|
||||
color="teal"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<Avatar
|
||||
size={80}
|
||||
color="teal"
|
||||
name="Pupplies"
|
||||
conversationType="group"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### Broken color
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
color="fake"
|
||||
name="F"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### Broken image
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
color="pink"
|
||||
name="John Smith"
|
||||
avatarPath="nonexistent"
|
||||
conversationType="direct"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
### Broken image for group
|
||||
|
||||
```jsx
|
||||
<Avatar
|
||||
size={28}
|
||||
avatarPath="nonexistent"
|
||||
color="pink"
|
||||
name="Puppies"
|
||||
avatarPath="nonexistent"
|
||||
conversationType="group"
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
118
ts/components/Avatar.tsx
Normal file
118
ts/components/Avatar.tsx
Normal file
|
@ -0,0 +1,118 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { getInitials } from '../util/getInitials';
|
||||
import { Localizer } from '../types/Util';
|
||||
|
||||
interface Props {
|
||||
avatarPath?: string;
|
||||
color?: string;
|
||||
conversationType: 'group' | 'direct';
|
||||
i18n: Localizer;
|
||||
name?: string;
|
||||
phoneNumber?: string;
|
||||
profileName?: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
imageBroken: boolean;
|
||||
}
|
||||
|
||||
export class Avatar extends React.Component<Props, State> {
|
||||
public handleImageErrorBound: () => void;
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.handleImageErrorBound = this.handleImageError.bind(this);
|
||||
|
||||
this.state = {
|
||||
imageBroken: false,
|
||||
};
|
||||
}
|
||||
|
||||
public handleImageError() {
|
||||
// tslint:disable-next-line no-console
|
||||
console.log('Avatar: Image failed to load; failing over to placeholder');
|
||||
this.setState({
|
||||
imageBroken: true,
|
||||
});
|
||||
}
|
||||
|
||||
public renderImage() {
|
||||
const { avatarPath, i18n, name, phoneNumber, profileName } = this.props;
|
||||
const { imageBroken } = this.state;
|
||||
const hasImage = avatarPath && !imageBroken;
|
||||
|
||||
if (!hasImage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = `${name || phoneNumber}${
|
||||
!name && profileName ? ` ~${profileName}` : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<img
|
||||
onError={this.handleImageErrorBound}
|
||||
alt={i18n('contactAvatarAlt', [title])}
|
||||
src={avatarPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderNoImage() {
|
||||
const { conversationType, name, size } = this.props;
|
||||
|
||||
const initials = getInitials(name);
|
||||
const isGroup = conversationType === 'group';
|
||||
|
||||
if (!isGroup && initials) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-avatar__label',
|
||||
`module-avatar__label--${size}`
|
||||
)}
|
||||
>
|
||||
{initials}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-avatar__icon',
|
||||
`module-avatar__icon--${conversationType}`,
|
||||
`module-avatar__icon--${size}`
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { avatarPath, color, size } = this.props;
|
||||
const { imageBroken } = this.state;
|
||||
|
||||
const hasImage = avatarPath && !imageBroken;
|
||||
|
||||
if (size !== 28 && size !== 36 && size !== 48 && size !== 80) {
|
||||
throw new Error(`Size ${size} is not supported!`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-avatar',
|
||||
`module-avatar--${size}`,
|
||||
hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image',
|
||||
!hasImage ? `module-avatar--${color}` : null
|
||||
)}
|
||||
>
|
||||
{hasImage ? this.renderImage() : this.renderNoImage()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Avatar } from './Avatar';
|
||||
import { Emojify } from './conversation/Emojify';
|
||||
|
||||
import { Localizer } from '../types/Util';
|
||||
|
@ -17,35 +18,28 @@ interface Props {
|
|||
onClick?: () => void;
|
||||
}
|
||||
|
||||
function getInitial(name: string): string {
|
||||
return name.trim()[0] || '#';
|
||||
}
|
||||
|
||||
export class ContactListItem extends React.Component<Props> {
|
||||
public renderAvatar({ displayName }: { displayName: string }) {
|
||||
const { avatarPath, i18n, color, name } = this.props;
|
||||
|
||||
if (avatarPath) {
|
||||
return (
|
||||
<div className="module-contact-list-item__avatar">
|
||||
<img alt={i18n('contactAvatarAlt', [displayName])} src={avatarPath} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const title = name ? getInitial(name) : '#';
|
||||
public renderAvatar() {
|
||||
const {
|
||||
avatarPath,
|
||||
i18n,
|
||||
color,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-contact-list-item__avatar-default',
|
||||
`module-contact-list-item__avatar-default--${color}`
|
||||
)}
|
||||
>
|
||||
<div className="module-contact-list-item__avatar-default__label">
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
color={color}
|
||||
conversationType="direct"
|
||||
i18n={i18n}
|
||||
name={name}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
size={48}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -82,7 +76,7 @@ export class ContactListItem extends React.Component<Props> {
|
|||
onClick ? 'module-contact-list-item--with-click-handler' : null
|
||||
)}
|
||||
>
|
||||
{this.renderAvatar({ displayName })}
|
||||
{this.renderAvatar()}
|
||||
<div className="module-contact-list-item__text">
|
||||
<div className="module-contact-list-item__text__name">
|
||||
<Emojify text={displayName} i18n={i18n} /> {profileElement}
|
||||
|
|
|
@ -1,154 +1,175 @@
|
|||
#### With name and profile
|
||||
|
||||
```jsx
|
||||
<ConversationListItem
|
||||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0011"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: "What's going on?",
|
||||
status: 'sent',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Profile, with name, no avatar
|
||||
|
||||
```jsx
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Just a second',
|
||||
status: 'read',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
```
|
||||
|
||||
#### All types of status
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<ConversationListItem
|
||||
name="Someone 🔥 Somewhere"
|
||||
conversationType={'direct'}
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
avatarPath={util.gifObjectUrl}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Sending',
|
||||
status: 'sending',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Sent',
|
||||
text: "What's going on?",
|
||||
status: 'sent',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### Profile, with name, no avatar
|
||||
|
||||
```jsx
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Delivered',
|
||||
status: 'delivered',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Read',
|
||||
text: 'Just a second',
|
||||
status: 'read',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Error',
|
||||
status: 'error',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### All types of status
|
||||
|
||||
```jsx
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Sending',
|
||||
status: 'sending',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Sent',
|
||||
status: 'sent',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Delivered',
|
||||
status: 'delivered',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Read',
|
||||
status: 'read',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Error',
|
||||
status: 'error',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### With unread
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
unreadCount={4}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
unreadCount={10}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
unreadCount={250}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
unreadCount={4}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
unreadCount={10}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
unreadCount={250}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### Selected
|
||||
|
||||
```jsx
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
isSelected={true}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
isSelected={true}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### With emoji/links in message, no status
|
||||
|
@ -156,26 +177,30 @@
|
|||
We don't want Jumbomoji or links.
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Download at http://signal.org',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: '🔥',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Download at http://signal.org',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: '🔥',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### Long content
|
||||
|
@ -183,72 +208,80 @@ We don't want Jumbomoji or links.
|
|||
We only show one line.
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Normal message',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
status: 'read',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Normal message',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
status: 'read',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
unreadCount={8}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Many lines. This is a many-line message.\nLine 2 is really exciting but it shouldn't be seen.\nLine three is even better.\nLine 4, well.",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Many lines. This is a many-line message.\nLine 2 is really exciting but it shouldn't be seen.\nLine three is even better.\nLine 4, well.",
|
||||
status: 'delivered',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
unreadCount={8}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Many lines. This is a many-line message.\nLine 2 is really exciting but it shouldn't be seen.\nLine three is even better.\nLine 4, well.",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Many lines. This is a many-line message.\nLine 2 is really exciting but it shouldn't be seen.\nLine three is even better.\nLine 4, well.",
|
||||
status: 'delivered',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### More narrow
|
||||
|
@ -256,104 +289,119 @@ We only show one line.
|
|||
On platforms that show scrollbars all the time, this is true all the time.
|
||||
|
||||
```jsx
|
||||
<div style={{ width: '280px' }}>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Normal message',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div style={{ width: '280px' }}>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Normal message',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### With various ages
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Five hours ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 24 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'One day ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 7 * 24 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'One week ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 365 * 24 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'One year ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Five hours ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 24 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'One day ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 7 * 24 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'One week ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 365 * 24 * 60 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'One year ago',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
||||
#### Missing data
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<ConversationListItem
|
||||
name="John"
|
||||
lastUpdated={null}
|
||||
lastMessage={{
|
||||
text: 'Missing last updated',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
name="Missing message"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: null,
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: null,
|
||||
status: 'sent',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
<util.LeftPaneContext theme={util.theme}>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
name="John"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={null}
|
||||
lastMessage={{
|
||||
text: 'Missing last updated',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
name="Missing message"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: null,
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
conversationType={'direct'}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: null,
|
||||
status: 'sent',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
</util.LeftPaneContext>
|
||||
```
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Avatar } from './Avatar';
|
||||
import { MessageBody } from './conversation/MessageBody';
|
||||
import { Timestamp } from './conversation/Timestamp';
|
||||
import { ContactName } from './conversation/ContactName';
|
||||
|
@ -11,6 +12,7 @@ interface Props {
|
|||
profileName?: string;
|
||||
name?: string;
|
||||
color?: string;
|
||||
conversationType: 'group' | 'direct';
|
||||
avatarPath?: string;
|
||||
|
||||
lastUpdated: number;
|
||||
|
@ -26,50 +28,29 @@ interface Props {
|
|||
onClick?: () => void;
|
||||
}
|
||||
|
||||
function getInitial(name: string): string {
|
||||
return name.trim()[0] || '#';
|
||||
}
|
||||
|
||||
export class ConversationListItem extends React.Component<Props> {
|
||||
public renderAvatar() {
|
||||
const {
|
||||
avatarPath,
|
||||
color,
|
||||
conversationType,
|
||||
i18n,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
} = this.props;
|
||||
|
||||
if (!avatarPath) {
|
||||
const initial = getInitial(name || '');
|
||||
|
||||
return (
|
||||
<div className="module-conversation-list-item__avatar-container">
|
||||
<div
|
||||
className={classNames(
|
||||
'module-conversation-list-item__avatar',
|
||||
'module-conversation-list-item__default-avatar',
|
||||
`module-conversation-list-item__default-avatar--${color}`
|
||||
)}
|
||||
>
|
||||
{initial}
|
||||
</div>
|
||||
{this.renderUnread()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const title = `${name || phoneNumber}${
|
||||
!name && profileName ? ` ~${profileName}` : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className="module-conversation-list-item__avatar-container">
|
||||
<img
|
||||
className="module-conversation-list-item__avatar"
|
||||
alt={i18n('contactAvatarAlt', [title])}
|
||||
src={avatarPath}
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
color={color}
|
||||
conversationType={conversationType}
|
||||
i18n={i18n}
|
||||
name={name}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
size={48}
|
||||
/>
|
||||
{this.renderUnread()}
|
||||
</div>
|
||||
|
|
|
@ -207,7 +207,9 @@ export class ContactDetail extends React.Component<Props> {
|
|||
|
||||
return (
|
||||
<div className="module-contact-detail">
|
||||
{renderAvatar({ contact, i18n, module })}
|
||||
<div className="module-contact-detail__avatar">
|
||||
{renderAvatar({ contact, i18n, size: 80 })}
|
||||
</div>
|
||||
{renderName({ contact, isIncoming, module })}
|
||||
{renderContactShorthand({ contact, isIncoming, module })}
|
||||
{this.renderSendMessage({ hasSignalAccount, i18n, onSendMessage })}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Emojify } from './Emojify';
|
||||
import { Avatar } from '../Avatar';
|
||||
import { Localizer } from '../../types/Util';
|
||||
import {
|
||||
ContextMenu,
|
||||
|
@ -45,10 +45,6 @@ interface Props {
|
|||
onGoBack: () => void;
|
||||
}
|
||||
|
||||
function getInitial(name: string): string {
|
||||
return name.trim()[0] || '#';
|
||||
}
|
||||
|
||||
export class ConversationHeader extends React.Component<Props> {
|
||||
public captureMenuTriggerBound: (trigger: any) => void;
|
||||
public showMenuBound: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
|
@ -116,37 +112,25 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
avatarPath,
|
||||
color,
|
||||
i18n,
|
||||
isGroup,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
} = this.props;
|
||||
|
||||
if (!avatarPath) {
|
||||
const initial = getInitial(name || '');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-conversation-header___avatar',
|
||||
'module-conversation-header___default-avatar',
|
||||
`module-conversation-header___default-avatar--${color}`
|
||||
)}
|
||||
>
|
||||
{initial}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const title = `${name || phoneNumber}${
|
||||
!name && profileName ? ` ~${profileName}` : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<img
|
||||
className="module-conversation-header___avatar"
|
||||
alt={i18n('contactAvatarAlt', [title])}
|
||||
src={avatarPath}
|
||||
/>
|
||||
<span className="module-conversation-header__avatar">
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
color={color}
|
||||
conversationType={isGroup ? 'group' : 'direct'}
|
||||
i18n={i18n}
|
||||
name={name}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
size={28}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Avatar } from '../Avatar';
|
||||
import { Contact, getName } from '../../types/Contact';
|
||||
|
||||
import { Localizer } from '../../types/Util';
|
||||
|
@ -41,7 +42,7 @@ export class EmbeddedContact extends React.Component<Props> {
|
|||
role="button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{renderAvatar({ contact, i18n, module })}
|
||||
{renderAvatar({ contact, i18n, size: 48 })}
|
||||
<div className="module-embedded-contact__text-container">
|
||||
{renderName({ contact, isIncoming, module })}
|
||||
{renderContactShorthand({ contact, isIncoming, module })}
|
||||
|
@ -53,40 +54,29 @@ export class EmbeddedContact extends React.Component<Props> {
|
|||
|
||||
// Note: putting these below the main component so style guide picks up EmbeddedContact
|
||||
|
||||
function getInitial(name: string): string {
|
||||
return name.trim()[0] || '#';
|
||||
}
|
||||
|
||||
export function renderAvatar({
|
||||
contact,
|
||||
i18n,
|
||||
module,
|
||||
size,
|
||||
}: {
|
||||
contact: Contact;
|
||||
i18n: Localizer;
|
||||
module: string;
|
||||
size: number;
|
||||
}) {
|
||||
const { avatar } = contact;
|
||||
|
||||
const path = avatar && avatar.avatar && avatar.avatar.path;
|
||||
const avatarPath = avatar && avatar.avatar && avatar.avatar.path;
|
||||
const name = getName(contact) || '';
|
||||
|
||||
if (!path) {
|
||||
const initials = getInitial(name);
|
||||
|
||||
return (
|
||||
<div className={`module-${module}__image-container`}>
|
||||
<div className={`module-${module}__image-container__default-avatar`}>
|
||||
{initials}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`module-${module}__image-container`}>
|
||||
<img src={path} alt={i18n('contactAvatarAlt', [name])} />
|
||||
</div>
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
color="grey"
|
||||
conversationType="direct"
|
||||
i18n={i18n}
|
||||
name={name}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
isVideoTypeSupported,
|
||||
} from '../../util/GoogleChrome';
|
||||
|
||||
import { Avatar } from '../Avatar';
|
||||
import { MessageBody } from './MessageBody';
|
||||
import { ExpireTimer, getIncrement } from './ExpireTimer';
|
||||
import { Timestamp } from './Timestamp';
|
||||
|
@ -133,10 +134,6 @@ function canDisplayImage(attachment?: Attachment) {
|
|||
return height > 0 && height <= 4096 && width > 0 && width <= 4096;
|
||||
}
|
||||
|
||||
function getInitial(name: string): string {
|
||||
return name.trim()[0] || '#';
|
||||
}
|
||||
|
||||
function getExtension({
|
||||
fileName,
|
||||
contentType,
|
||||
|
@ -633,21 +630,17 @@ export class Message extends React.Component<Props, State> {
|
|||
|
||||
public renderAvatar() {
|
||||
const {
|
||||
authorAvatarPath,
|
||||
authorName,
|
||||
authorPhoneNumber,
|
||||
authorProfileName,
|
||||
authorAvatarPath,
|
||||
conversationColor,
|
||||
collapseMetadata,
|
||||
conversationColor,
|
||||
conversationType,
|
||||
direction,
|
||||
i18n,
|
||||
} = this.props;
|
||||
|
||||
const title = `${authorName || authorPhoneNumber}${
|
||||
!authorName && authorProfileName ? ` ~${authorProfileName}` : ''
|
||||
}`;
|
||||
|
||||
if (
|
||||
collapseMetadata ||
|
||||
conversationType !== 'group' ||
|
||||
|
@ -656,26 +649,18 @@ export class Message extends React.Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!authorAvatarPath) {
|
||||
const label = authorName ? getInitial(authorName) : '#';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message__author-default-avatar',
|
||||
`module-message__author-default-avatar--${conversationColor}`
|
||||
)}
|
||||
>
|
||||
<div className="module-message__author-default-avatar__label">
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-message__author-avatar">
|
||||
<img alt={i18n('contactAvatarAlt', [title])} src={authorAvatarPath} />
|
||||
<Avatar
|
||||
avatarPath={authorAvatarPath}
|
||||
color={conversationColor}
|
||||
conversationType="direct"
|
||||
i18n={i18n}
|
||||
name={authorName}
|
||||
phoneNumber={authorPhoneNumber}
|
||||
profileName={authorProfileName}
|
||||
size={36}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import classNames from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
import { Avatar } from '../Avatar';
|
||||
import { ContactName } from './ContactName';
|
||||
import { Message, Props as MessageProps } from './Message';
|
||||
import { Localizer } from '../../types/Util';
|
||||
|
@ -31,40 +32,21 @@ interface Props {
|
|||
i18n: Localizer;
|
||||
}
|
||||
|
||||
function getInitial(name: string): string {
|
||||
return name.trim()[0] || '#';
|
||||
}
|
||||
|
||||
export class MessageDetail extends React.Component<Props> {
|
||||
public renderAvatar(contact: Contact) {
|
||||
const { i18n } = this.props;
|
||||
const { avatarPath, color, phoneNumber, name, profileName } = contact;
|
||||
|
||||
if (!avatarPath) {
|
||||
const initial = getInitial(name || '');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message-detail__contact__avatar',
|
||||
'module-message-detail__contact__default-avatar',
|
||||
`module-message-detail__contact__default-avatar--${color}`
|
||||
)}
|
||||
>
|
||||
{initial}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const title = `${name || phoneNumber}${
|
||||
!name && profileName ? ` ~${profileName}` : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<img
|
||||
className="module-message-detail__contact__avatar"
|
||||
alt={i18n('contactAvatarAlt', [title])}
|
||||
src={avatarPath}
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
color={color}
|
||||
conversationType="direct"
|
||||
i18n={i18n}
|
||||
name={name}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
size={48}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
25
ts/styleguide/LeftPaneContext.tsx
Normal file
25
ts/styleguide/LeftPaneContext.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Corresponds to the theme setting in the app, and the class added to the root element.
|
||||
*/
|
||||
theme: 'light-theme' | 'dark-theme';
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the parent elements necessary to allow the main Signal Desktop stylesheet to
|
||||
* apply (with no changes) to messages in the Style Guide.
|
||||
*/
|
||||
export class LeftPaneContext extends React.Component<Props> {
|
||||
public render() {
|
||||
const { theme } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames(theme || 'light-theme')}>
|
||||
<div className="gutter">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
|||
|
||||
import { default as _ } from 'lodash';
|
||||
export { ConversationContext } from './ConversationContext';
|
||||
export { LeftPaneContext } from './LeftPaneContext';
|
||||
|
||||
export { _, classNames };
|
||||
|
||||
|
|
21
ts/util/getInitials.ts
Normal file
21
ts/util/getInitials.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
const BAD_CHARACTERS = /[^A-Za-z\s]+/g;
|
||||
const WHITESPACE = /\s+/g;
|
||||
|
||||
function removeNonInitials(name: string) {
|
||||
return name.replace(BAD_CHARACTERS, '').replace(WHITESPACE, ' ');
|
||||
}
|
||||
|
||||
export function getInitials(name?: string): string | null {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cleaned = removeNonInitials(name);
|
||||
const parts = cleaned.split(' ');
|
||||
const initials = parts.map(part => part.trim()[0]);
|
||||
if (!initials.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return initials.slice(0, 2).join('');
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue