Include sender in group update notifications
This commit is contained in:
parent
d88c21e5b6
commit
71436d18e2
28 changed files with 1016 additions and 472 deletions
|
@ -2,11 +2,12 @@ import React from 'react';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import { getInitials } from '../util/getInitials';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { ColorType, LocalizerType } from '../types/Util';
|
||||
|
||||
export interface Props {
|
||||
avatarPath?: string;
|
||||
color?: string;
|
||||
color?: ColorType;
|
||||
|
||||
conversationType: 'group' | 'direct';
|
||||
noteToSelf?: boolean;
|
||||
name?: string;
|
||||
|
|
|
@ -4,13 +4,13 @@ import classNames from 'classnames';
|
|||
import { Avatar } from './Avatar';
|
||||
import { Emojify } from './conversation/Emojify';
|
||||
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { ColorType, LocalizerType } from '../types/Util';
|
||||
|
||||
interface Props {
|
||||
phoneNumber: string;
|
||||
isMe?: boolean;
|
||||
name?: string;
|
||||
color: string;
|
||||
color: ColorType;
|
||||
verified: boolean;
|
||||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
|
|
|
@ -8,12 +8,12 @@ import { ContactName } from './conversation/ContactName';
|
|||
import { TypingAnimation } from './conversation/TypingAnimation';
|
||||
import { cleanId } from './_util';
|
||||
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { ColorType, LocalizerType } from '../types/Util';
|
||||
|
||||
export type PropsData = {
|
||||
id: string;
|
||||
phoneNumber: string;
|
||||
color?: string;
|
||||
color?: ColorType;
|
||||
profileName?: string;
|
||||
name?: string;
|
||||
type: 'group' | 'direct';
|
||||
|
|
|
@ -2,13 +2,13 @@ import React from 'react';
|
|||
|
||||
import { LocalizerType, RenderTextCallbackType } from '../types/Util';
|
||||
|
||||
type FullJSX = Array<JSX.Element | string> | JSX.Element | string;
|
||||
export type FullJSXType = Array<JSX.Element | string> | JSX.Element | string;
|
||||
|
||||
interface Props {
|
||||
/** The translation string id */
|
||||
id: string;
|
||||
i18n: LocalizerType;
|
||||
components?: Array<FullJSX>;
|
||||
components?: Array<FullJSXType>;
|
||||
renderText?: RenderTextCallbackType;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ export class Intl extends React.Component<Props> {
|
|||
),
|
||||
};
|
||||
|
||||
public getComponent(index: number, key: number): FullJSX | undefined {
|
||||
public getComponent(index: number, key: number): FullJSXType | undefined {
|
||||
const { id, components } = this.props;
|
||||
|
||||
if (!components || !components.length || components.length <= index) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { createPortal } from 'react-dom';
|
|||
import { showSettings } from '../shims/Whisper';
|
||||
import { Avatar } from './Avatar';
|
||||
import { AvatarPopup } from './AvatarPopup';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { ColorType, LocalizerType } from '../types/Util';
|
||||
|
||||
export interface PropsType {
|
||||
searchTerm: string;
|
||||
|
@ -25,7 +25,7 @@ export interface PropsType {
|
|||
phoneNumber: string;
|
||||
isMe: boolean;
|
||||
name?: string;
|
||||
color: string;
|
||||
color: ColorType;
|
||||
verified: boolean;
|
||||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { MessageBodyHighlight } from './MessageBodyHighlight';
|
|||
import { Timestamp } from './conversation/Timestamp';
|
||||
import { ContactName } from './conversation/ContactName';
|
||||
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { ColorType, LocalizerType } from '../types/Util';
|
||||
|
||||
export type PropsDataType = {
|
||||
isSelected?: boolean;
|
||||
|
@ -22,7 +22,7 @@ export type PropsDataType = {
|
|||
phoneNumber: string;
|
||||
isMe?: boolean;
|
||||
name?: string;
|
||||
color?: string;
|
||||
color?: ColorType;
|
||||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ export const NetworkStatus = ({
|
|||
|
||||
const [isConnecting, setIsConnecting] = React.useState<boolean>(false);
|
||||
React.useEffect(() => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
let timeout: any;
|
||||
|
||||
if (isConnecting) {
|
||||
timeout = setTimeout(() => {
|
||||
|
|
|
@ -14,32 +14,15 @@ import { storiesOf } from '@storybook/react';
|
|||
//import { boolean, select } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
// @ts-ignore
|
||||
import gif from '../../fixtures/giphy-GVNvOUpeYmI7e.gif';
|
||||
// @ts-ignore
|
||||
import png from '../../fixtures/freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png';
|
||||
// @ts-ignore
|
||||
import landscapeGreen from '../../fixtures/1000x50-green.jpeg';
|
||||
// @ts-ignore
|
||||
import landscapePurple from '../../fixtures/200x50-purple.png';
|
||||
import {
|
||||
gifObjectUrl,
|
||||
landscapeGreenObjectUrl,
|
||||
landscapePurpleObjectUrl,
|
||||
pngObjectUrl,
|
||||
} from '../storybook/Fixtures';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
function makeObjectUrl(data: ArrayBuffer, contentType: string): string {
|
||||
const blob = new Blob([data], {
|
||||
type: contentType,
|
||||
});
|
||||
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
// 320x240
|
||||
const gifObjectUrl = makeObjectUrl(gif, 'image/gif');
|
||||
// 800×1200
|
||||
const pngObjectUrl = makeObjectUrl(png, 'image/png');
|
||||
const landscapeGreenObjectUrl = makeObjectUrl(landscapeGreen, 'image/jpeg');
|
||||
const landscapePurpleObjectUrl = makeObjectUrl(landscapePurple, 'image/png');
|
||||
|
||||
const messageLookup: Map<string, MessageSearchResultPropsType> = new Map();
|
||||
|
||||
const CONTACT = 'contact' as 'contact';
|
||||
|
@ -64,6 +47,7 @@ messageLookup.set('1-guid-guid-guid-guid-guid', {
|
|||
from: {
|
||||
phoneNumber: '(202) 555-0020',
|
||||
isMe: true,
|
||||
color: 'blue',
|
||||
avatarPath: gifObjectUrl,
|
||||
},
|
||||
to: {
|
||||
|
@ -116,6 +100,7 @@ messageLookup.set('4-guid-guid-guid-guid-guid', {
|
|||
from: {
|
||||
phoneNumber: '(202) 555-0020',
|
||||
isMe: true,
|
||||
color: 'light_green',
|
||||
avatarPath: gifObjectUrl,
|
||||
},
|
||||
to: {
|
||||
|
@ -160,6 +145,7 @@ const conversations = [
|
|||
phoneNumber: '(202) 555-0011',
|
||||
name: 'Everyone 🌆',
|
||||
type: GROUP,
|
||||
color: 'signal-blue' as 'signal-blue',
|
||||
avatarPath: landscapeGreenObjectUrl,
|
||||
isMe: false,
|
||||
lastUpdated: Date.now() - 5 * 60 * 1000,
|
||||
|
@ -177,6 +163,7 @@ const conversations = [
|
|||
id: '+12025550012',
|
||||
phoneNumber: '(202) 555-0012',
|
||||
name: 'Everyone Else 🔥',
|
||||
color: 'pink' as 'pink',
|
||||
type: DIRECT,
|
||||
avatarPath: landscapePurpleObjectUrl,
|
||||
isMe: false,
|
||||
|
@ -198,6 +185,7 @@ const contacts = [
|
|||
id: '+12025550013',
|
||||
phoneNumber: '(202) 555-0013',
|
||||
name: 'The one Everyone',
|
||||
color: 'blue' as 'blue',
|
||||
type: DIRECT,
|
||||
avatarPath: gifObjectUrl,
|
||||
isMe: false,
|
||||
|
@ -213,7 +201,7 @@ const contacts = [
|
|||
phoneNumber: '(202) 555-0014',
|
||||
name: 'No likey everyone',
|
||||
type: DIRECT,
|
||||
color: 'red',
|
||||
color: 'red' as 'red',
|
||||
isMe: false,
|
||||
lastUpdated: Date.now() - 11 * 60 * 1000,
|
||||
unreadCount: 0,
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
### Name variations, 1:1 conversation
|
||||
|
||||
Note the five items in menu, and the second-level menu with disappearing messages options. Disappearing message set to 'off'.
|
||||
|
||||
#### With name and profile, verified
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
i18n={util.i18n}
|
||||
color="red"
|
||||
isVerified={true}
|
||||
avatarPath={util.gifObjectUrl}
|
||||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0001"
|
||||
id="1"
|
||||
profileName="🔥Flames🔥"
|
||||
onSetDisappearingMessages={seconds =>
|
||||
console.log('onSetDisappearingMessages', seconds)
|
||||
}
|
||||
onDeleteMessages={() => console.log('onDeleteMessages')}
|
||||
onResetSession={() => console.log('onResetSession')}
|
||||
onShowSafetyNumber={() => console.log('onShowSafetyNumber')}
|
||||
onShowAllMedia={() => console.log('onShowAllMedia')}
|
||||
onShowGroupMembers={() => console.log('onShowGroupMembers')}
|
||||
onGoBack={() => console.log('onGoBack')}
|
||||
onSearchInConversation={() => console.log('onSearchInConversation')}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
#### With name, not verified, no avatar
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
i18n={util.i18n}
|
||||
color="blue"
|
||||
isVerified={false}
|
||||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0002"
|
||||
id="2"
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
#### Profile, no name
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
i18n={util.i18n}
|
||||
color="teal"
|
||||
isVerified={false}
|
||||
phoneNumber="(202) 555-0003"
|
||||
id="3"
|
||||
profileName="🔥Flames🔥"
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
#### No name, no profile, no color
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader i18n={util.i18n} phoneNumber="(202) 555-0011" id="11" />
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### With back button
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
showBackButton={true}
|
||||
color="deep_orange"
|
||||
i18n={util.i18n}
|
||||
phoneNumber="(202) 555-0004"
|
||||
id="4"
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### Disappearing messages set
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
color="indigo"
|
||||
i18n={util.i18n}
|
||||
phoneNumber="(202) 555-0005"
|
||||
id="5"
|
||||
expirationSettingName="10 seconds"
|
||||
timerOptions={[
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
]}
|
||||
onSetDisappearingMessages={seconds =>
|
||||
console.log('onSetDisappearingMessages', seconds)
|
||||
}
|
||||
onDeleteMessages={() => console.log('onDeleteMessages')}
|
||||
onResetSession={() => console.log('onResetSession')}
|
||||
onShowSafetyNumber={() => console.log('onShowSafetyNumber')}
|
||||
onShowAllMedia={() => console.log('onShowAllMedia')}
|
||||
onShowGroupMembers={() => console.log('onShowGroupMembers')}
|
||||
onGoBack={() => console.log('onGoBack')}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### In a group
|
||||
|
||||
Note that the menu should includes 'Show Members' instead of 'Show Safety Number'
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
i18n={util.i18n}
|
||||
color="green"
|
||||
phoneNumber="(202) 555-0006"
|
||||
id="6"
|
||||
isGroup={true}
|
||||
onSetDisappearingMessages={seconds =>
|
||||
console.log('onSetDisappearingMessages', seconds)
|
||||
}
|
||||
onDeleteMessages={() => console.log('onDeleteMessages')}
|
||||
onResetSession={() => console.log('onResetSession')}
|
||||
onShowSafetyNumber={() => console.log('onShowSafetyNumber')}
|
||||
onShowAllMedia={() => console.log('onShowAllMedia')}
|
||||
onShowGroupMembers={() => console.log('onShowGroupMembers')}
|
||||
onGoBack={() => console.log('onGoBack')}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### In chat with yourself
|
||||
|
||||
This is the 'Note to self' conversation. Note that the menu should not have a 'Show Safety Number' entry.
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<ConversationHeader
|
||||
color="cyan"
|
||||
i18n={util.i18n}
|
||||
phoneNumber="(202) 555-0007"
|
||||
id="7"
|
||||
isMe={true}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
227
ts/components/conversation/ConversationHeader.stories.tsx
Normal file
227
ts/components/conversation/ConversationHeader.stories.tsx
Normal file
|
@ -0,0 +1,227 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
// @ts-ignore
|
||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||
// @ts-ignore
|
||||
import enMessages from '../../../\_locales/en/messages.json';
|
||||
|
||||
import {
|
||||
ConversationHeader,
|
||||
Props,
|
||||
PropsActions,
|
||||
PropsHousekeeping,
|
||||
} from './ConversationHeader';
|
||||
|
||||
import { gifObjectUrl } from '../../storybook/Fixtures';
|
||||
|
||||
const book = storiesOf('Components/Conversation/ConversationHeader', module);
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
type ConversationHeaderStory = {
|
||||
title: string;
|
||||
description: string;
|
||||
items: Array<{
|
||||
title: string;
|
||||
props: Props;
|
||||
}>;
|
||||
};
|
||||
|
||||
const actionProps: PropsActions = {
|
||||
onSetDisappearingMessages: action('onSetDisappearingMessages'),
|
||||
onDeleteMessages: action('onDeleteMessages'),
|
||||
onResetSession: action('onResetSession'),
|
||||
onSearchInConversation: action('onSearchInConversation'),
|
||||
|
||||
onShowSafetyNumber: action('onShowSafetyNumber'),
|
||||
onShowAllMedia: action('onShowAllMedia'),
|
||||
onShowGroupMembers: action('onShowGroupMembers'),
|
||||
onGoBack: action('onGoBack'),
|
||||
|
||||
onArchive: action('onArchive'),
|
||||
onMoveToInbox: action('onMoveToInbox'),
|
||||
};
|
||||
|
||||
const housekeepingProps: PropsHousekeeping = {
|
||||
i18n,
|
||||
};
|
||||
|
||||
const stories: Array<ConversationHeaderStory> = [
|
||||
{
|
||||
title: '1:1 conversation',
|
||||
description:
|
||||
"Note the five items in menu, and the second-level menu with disappearing messages options. Disappearing message set to 'off'.",
|
||||
items: [
|
||||
{
|
||||
title: 'With name and profile, verified',
|
||||
props: {
|
||||
color: 'red',
|
||||
isVerified: true,
|
||||
avatarPath: gifObjectUrl,
|
||||
name: 'Someone 🔥 Somewhere',
|
||||
phoneNumber: '(202) 555-0001',
|
||||
id: '1',
|
||||
profileName: '🔥Flames🔥',
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With name, not verified, no avatar',
|
||||
props: {
|
||||
color: 'blue',
|
||||
isVerified: false,
|
||||
name: 'Someone 🔥 Somewhere',
|
||||
phoneNumber: '(202) 555-0002',
|
||||
id: '2',
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Profile, no name',
|
||||
props: {
|
||||
color: 'teal',
|
||||
isVerified: false,
|
||||
phoneNumber: '(202) 555-0003',
|
||||
id: '3',
|
||||
profileName: '🔥Flames🔥',
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'No name, no profile, no color',
|
||||
props: {
|
||||
phoneNumber: '(202) 555-0011',
|
||||
id: '11',
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With back button',
|
||||
props: {
|
||||
showBackButton: true,
|
||||
color: 'deep_orange',
|
||||
phoneNumber: '(202) 555-0004',
|
||||
id: '4',
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Disappearing messages set',
|
||||
props: {
|
||||
color: 'indigo',
|
||||
phoneNumber: '(202) 555-0005',
|
||||
id: '5',
|
||||
expirationSettingName: '10 seconds',
|
||||
timerOptions: [
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'In a group',
|
||||
description:
|
||||
"Note that the menu should includes 'Show Members' instead of 'Show Safety Number'",
|
||||
items: [
|
||||
{
|
||||
title: 'Basic',
|
||||
props: {
|
||||
color: 'signal-blue',
|
||||
name: 'Typescript support group',
|
||||
phoneNumber: '',
|
||||
id: '1',
|
||||
isGroup: true,
|
||||
expirationSettingName: '10 seconds',
|
||||
timerOptions: [
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'In a group you left - no disappearing messages',
|
||||
props: {
|
||||
color: 'signal-blue',
|
||||
name: 'Typescript support group',
|
||||
phoneNumber: '',
|
||||
id: '2',
|
||||
isGroup: true,
|
||||
leftGroup: true,
|
||||
expirationSettingName: '10 seconds',
|
||||
timerOptions: [
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Note to Self',
|
||||
description: 'No safety number entry.',
|
||||
items: [
|
||||
{
|
||||
title: 'In chat with yourself',
|
||||
props: {
|
||||
color: 'blue',
|
||||
phoneNumber: '(202) 555-0007',
|
||||
id: '7',
|
||||
isMe: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
stories.forEach(({ title, description, items }) =>
|
||||
book.add(
|
||||
title,
|
||||
() =>
|
||||
items.map(({ title: subtitle, props }, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
{subtitle ? <h3>{subtitle}</h3> : null}
|
||||
<ConversationHeader {...props} />
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
{
|
||||
docs: description,
|
||||
}
|
||||
)
|
||||
);
|
|
@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|||
|
||||
import { Emojify } from './Emojify';
|
||||
import { Avatar } from '../Avatar';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { ColorType, LocalizerType } from '../../types/Util';
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
|
@ -16,24 +16,27 @@ interface TimerOption {
|
|||
value: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
export interface PropsData {
|
||||
id: string;
|
||||
name?: string;
|
||||
|
||||
phoneNumber: string;
|
||||
profileName?: string;
|
||||
color: string;
|
||||
color?: ColorType;
|
||||
avatarPath?: string;
|
||||
|
||||
isVerified: boolean;
|
||||
isMe: boolean;
|
||||
isGroup: boolean;
|
||||
isArchived: boolean;
|
||||
isVerified?: boolean;
|
||||
isMe?: boolean;
|
||||
isGroup?: boolean;
|
||||
isArchived?: boolean;
|
||||
leftGroup?: boolean;
|
||||
|
||||
expirationSettingName?: string;
|
||||
showBackButton: boolean;
|
||||
timerOptions: Array<TimerOption>;
|
||||
showBackButton?: boolean;
|
||||
timerOptions?: Array<TimerOption>;
|
||||
}
|
||||
|
||||
export interface PropsActions {
|
||||
onSetDisappearingMessages: (seconds: number) => void;
|
||||
onDeleteMessages: () => void;
|
||||
onResetSession: () => void;
|
||||
|
@ -46,10 +49,14 @@ interface Props {
|
|||
|
||||
onArchive: () => void;
|
||||
onMoveToInbox: () => void;
|
||||
}
|
||||
|
||||
export interface PropsHousekeeping {
|
||||
i18n: LocalizerType;
|
||||
}
|
||||
|
||||
export type Props = PropsData & PropsActions & PropsHousekeeping;
|
||||
|
||||
export class ConversationHeader extends React.Component<Props> {
|
||||
public showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
public menuTriggerRef: React.RefObject<any>;
|
||||
|
@ -218,6 +225,7 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
isMe,
|
||||
isGroup,
|
||||
isArchived,
|
||||
leftGroup,
|
||||
onDeleteMessages,
|
||||
onResetSession,
|
||||
onSetDisappearingMessages,
|
||||
|
@ -233,18 +241,20 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
|
||||
return (
|
||||
<ContextMenu id={triggerId}>
|
||||
<SubMenu title={disappearingTitle}>
|
||||
{(timerOptions || []).map(item => (
|
||||
<MenuItem
|
||||
key={item.value}
|
||||
onClick={() => {
|
||||
onSetDisappearingMessages(item.value);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</SubMenu>
|
||||
{leftGroup ? null : (
|
||||
<SubMenu title={disappearingTitle}>
|
||||
{(timerOptions || []).map(item => (
|
||||
<MenuItem
|
||||
key={item.value}
|
||||
onClick={() => {
|
||||
onSetDisappearingMessages(item.value);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</SubMenu>
|
||||
)}
|
||||
<MenuItem onClick={onShowAllMedia}>{i18n('viewAllMedia')}</MenuItem>
|
||||
{isGroup ? (
|
||||
<MenuItem onClick={onShowGroupMembers}>
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
### Three changes, all types
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'name',
|
||||
newName: 'New Group Name',
|
||||
},
|
||||
{
|
||||
type: 'remove',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### Joined group
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### Left group
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'remove',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'remove',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'remove',
|
||||
isMe: true,
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### Title changed
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'name',
|
||||
newName: 'New Group Name',
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### Generic group update
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<GroupNotification
|
||||
changes={[
|
||||
{
|
||||
type: 'general',
|
||||
},
|
||||
]}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
353
ts/components/conversation/GroupNotification.stories.tsx
Normal file
353
ts/components/conversation/GroupNotification.stories.tsx
Normal file
|
@ -0,0 +1,353 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
// @ts-ignore
|
||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||
// @ts-ignore
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
|
||||
import { GroupNotification, Props } from './GroupNotification';
|
||||
|
||||
const book = storiesOf('Components/Conversation', module);
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
type GroupNotificationStory = [string, Array<Props>];
|
||||
|
||||
const stories: Array<GroupNotificationStory> = [
|
||||
[
|
||||
'Combo',
|
||||
[
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'name', newName: 'Fishing Stories' },
|
||||
{ type: 'avatar' },
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'name', newName: 'Fishing Stories' },
|
||||
{ type: 'avatar' },
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'Joined group',
|
||||
[
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'add',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
isMe: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'Left group',
|
||||
[
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'remove',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1001',
|
||||
profileName: 'Mrs. Ice',
|
||||
},
|
||||
{
|
||||
phoneNumber: '(202) 555-1002',
|
||||
name: 'Ms. Earth',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'remove',
|
||||
contacts: [
|
||||
{
|
||||
phoneNumber: '(202) 555-1000',
|
||||
profileName: 'Mr. Fire',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'remove',
|
||||
contacts: [
|
||||
{
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'Title changed',
|
||||
[
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'name',
|
||||
newName: 'New Group Name',
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'name',
|
||||
newName: 'New Group Name',
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'Avatar changed',
|
||||
[
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'avatar',
|
||||
newName: 'New Group Name',
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
isMe: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'avatar',
|
||||
newName: 'New Group Name',
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'Generic group update',
|
||||
[
|
||||
{
|
||||
from: {
|
||||
name: 'Alice',
|
||||
phoneNumber: '(202) 555-1000',
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'general',
|
||||
},
|
||||
],
|
||||
i18n,
|
||||
},
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
book.add('GroupNotification', () =>
|
||||
stories.map(([title, propsArray]) => (
|
||||
<>
|
||||
<h3>{title}</h3>
|
||||
{propsArray.map((props, i) => {
|
||||
return (
|
||||
<>
|
||||
<div key={i} className="module-message-container">
|
||||
<div className="module-inline-notification-wrapper">
|
||||
<GroupNotification {...props} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
))
|
||||
);
|
|
@ -1,10 +1,8 @@
|
|||
import React from 'react';
|
||||
// import classNames from 'classnames';
|
||||
import { compact, flatten } from 'lodash';
|
||||
|
||||
import { ContactName } from './ContactName';
|
||||
import { Emojify } from './Emojify';
|
||||
import { Intl } from '../Intl';
|
||||
import { FullJSXType, Intl } from '../Intl';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
|
@ -13,16 +11,17 @@ interface Contact {
|
|||
phoneNumber: string;
|
||||
profileName?: string;
|
||||
name?: string;
|
||||
isMe?: boolean;
|
||||
}
|
||||
|
||||
interface Change {
|
||||
type: 'add' | 'remove' | 'name' | 'general';
|
||||
isMe: boolean;
|
||||
type: 'add' | 'remove' | 'name' | 'avatar' | 'general';
|
||||
newName?: string;
|
||||
contacts?: Array<Contact>;
|
||||
}
|
||||
|
||||
export type PropsData = {
|
||||
from: Contact;
|
||||
changes: Array<Change>;
|
||||
};
|
||||
|
||||
|
@ -30,48 +29,82 @@ type PropsHousekeeping = {
|
|||
i18n: LocalizerType;
|
||||
};
|
||||
|
||||
type Props = PropsData & PropsHousekeeping;
|
||||
export type Props = PropsData & PropsHousekeeping;
|
||||
|
||||
export class GroupNotification extends React.Component<Props> {
|
||||
public renderChange(change: Change) {
|
||||
const { isMe, contacts, type, newName } = change;
|
||||
public renderChange(change: Change, from: Contact) {
|
||||
const { contacts, type, newName } = change;
|
||||
const { i18n } = this.props;
|
||||
|
||||
const people = compact(
|
||||
flatten(
|
||||
(contacts || []).map((contact, index) => {
|
||||
const element = (
|
||||
<span
|
||||
key={`external-${contact.phoneNumber}`}
|
||||
className="module-group-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
phoneNumber={contact.phoneNumber}
|
||||
profileName={contact.profileName}
|
||||
name={contact.name}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
const otherPeople = compact(
|
||||
(contacts || []).map(contact => {
|
||||
if (contact.isMe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [index > 0 ? ', ' : null, element];
|
||||
})
|
||||
return (
|
||||
<span
|
||||
key={`external-${contact.phoneNumber}`}
|
||||
className="module-group-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
phoneNumber={contact.phoneNumber}
|
||||
profileName={contact.profileName}
|
||||
name={contact.name}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
);
|
||||
const otherPeopleWithCommas: FullJSXType = compact(
|
||||
flatten(
|
||||
otherPeople.map((person, index) => [index > 0 ? ', ' : null, person])
|
||||
)
|
||||
);
|
||||
const contactsIncludesMe = (contacts || []).length !== otherPeople.length;
|
||||
|
||||
switch (type) {
|
||||
case 'name':
|
||||
return <Emojify text={i18n('titleIsNow', [newName || ''])} />;
|
||||
return (
|
||||
<Intl i18n={i18n} id="titleIsNow" components={[newName || '']} />
|
||||
);
|
||||
case 'avatar':
|
||||
return <Intl i18n={i18n} id="updatedGroupAvatar" />;
|
||||
case 'add':
|
||||
if (!contacts || !contacts.length) {
|
||||
throw new Error('Group update is missing contacts');
|
||||
}
|
||||
|
||||
const joinKey =
|
||||
contacts.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup';
|
||||
if (contacts.length === 1) {
|
||||
if (contactsIncludesMe) {
|
||||
return <Intl i18n={i18n} id="youJoinedTheGroup" />;
|
||||
} else {
|
||||
return (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="joinedTheGroup"
|
||||
components={[otherPeopleWithCommas]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <Intl i18n={i18n} id={joinKey} components={[people]} />;
|
||||
return (
|
||||
<>
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="multipleJoinedTheGroup"
|
||||
components={[otherPeopleWithCommas]}
|
||||
/>
|
||||
{contactsIncludesMe ? (
|
||||
<div className="module-group-notification__change">
|
||||
<Intl i18n={i18n} id="youJoinedTheGroup" />
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
case 'remove':
|
||||
if (isMe) {
|
||||
if (from && from.isMe) {
|
||||
return i18n('youLeftTheGroup');
|
||||
}
|
||||
|
||||
|
@ -82,22 +115,49 @@ export class GroupNotification extends React.Component<Props> {
|
|||
const leftKey =
|
||||
contacts.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup';
|
||||
|
||||
return <Intl i18n={i18n} id={leftKey} components={[people]} />;
|
||||
return (
|
||||
<Intl i18n={i18n} id={leftKey} components={[otherPeopleWithCommas]} />
|
||||
);
|
||||
case 'general':
|
||||
return i18n('updatedTheGroup');
|
||||
return;
|
||||
default:
|
||||
throw missingCaseError(type);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { changes } = this.props;
|
||||
const { changes, i18n, from } = this.props;
|
||||
|
||||
// Leave messages are always from the person leaving, so we omit the fromLabel if
|
||||
// the change is a 'leave.'
|
||||
const isLeftOnly =
|
||||
changes && changes.length === 1 && changes[0].type === 'remove';
|
||||
|
||||
const fromContact = (
|
||||
<ContactName
|
||||
phoneNumber={from.phoneNumber}
|
||||
profileName={from.profileName}
|
||||
name={from.name}
|
||||
/>
|
||||
);
|
||||
|
||||
const fromLabel = from.isMe ? (
|
||||
<Intl i18n={i18n} id="youUpdatedTheGroup" />
|
||||
) : (
|
||||
<Intl i18n={i18n} id="updatedTheGroup" components={[fromContact]} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="module-group-notification">
|
||||
{isLeftOnly ? null : (
|
||||
<>
|
||||
{fromLabel}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{(changes || []).map((change, index) => (
|
||||
<div key={index} className="module-group-notification__change">
|
||||
{this.renderChange(change)}
|
||||
{this.renderChange(change, from)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -106,7 +106,7 @@ const stories: Array<MessageStory> = [
|
|||
makeDataProps: () => ({
|
||||
...baseDataProps,
|
||||
direction: 'incoming',
|
||||
authorColor: 'gray',
|
||||
authorColor: 'grey',
|
||||
text:
|
||||
'Hello there from the new world! And this is multiple lines of text. Lines and lines and lines.',
|
||||
}),
|
||||
|
|
|
@ -5,7 +5,7 @@ import moment from 'moment';
|
|||
import { Avatar } from '../Avatar';
|
||||
import { ContactName } from './ContactName';
|
||||
import { Message, Props as MessageProps } from './Message';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { ColorType, LocalizerType } from '../../types/Util';
|
||||
|
||||
interface Contact {
|
||||
status: string;
|
||||
|
@ -13,7 +13,7 @@ interface Contact {
|
|||
name?: string;
|
||||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
color: string;
|
||||
color: ColorType;
|
||||
isOutgoingKeyError: boolean;
|
||||
isUnidentifiedDelivery: boolean;
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@ import classNames from 'classnames';
|
|||
import { TypingAnimation } from './TypingAnimation';
|
||||
import { Avatar } from '../Avatar';
|
||||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { ColorType, LocalizerType } from '../../types/Util';
|
||||
|
||||
interface Props {
|
||||
avatarPath?: string;
|
||||
color: string;
|
||||
color: ColorType;
|
||||
name?: string;
|
||||
phoneNumber: string;
|
||||
profileName?: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue