diff --git a/ts/components/ConversationListItem.md b/ts/components/ConversationListItem.md deleted file mode 100644 index 6d5e9a393cd9..000000000000 --- a/ts/components/ConversationListItem.md +++ /dev/null @@ -1,615 +0,0 @@ -#### With name and profile - -```jsx - - console.log('onClick', result)} - i18n={util.i18n} - /> - -``` - -#### Profile, with name, no avatar - -```jsx - - console.log('onClick', result)} - i18n={util.i18n} - /> - -``` - -#### Conversation with yourself - -```jsx - - console.log('onClick', result)} - i18n={util.i18n} - /> - -``` - -#### All types of status - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### Is typing - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> -
-
- console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### Message Request - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> -
-
- console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### Selected - -#### With unread - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### Selected - -```jsx - - console.log('onClick', result)} - i18n={util.i18n} - /> - -``` - -#### With emoji/links in message, no status - -We don't want Jumbomoji or links. - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### Long content - -We only show one line. - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### More narrow - -On platforms that show scrollbars all the time, this is true all the time. - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### With various ages - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` - -#### Missing data - -```jsx - -
- console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> - console.log('onClick', result)} - i18n={util.i18n} - /> -
-
-``` diff --git a/ts/components/ConversationListItem.stories.tsx b/ts/components/ConversationListItem.stories.tsx new file mode 100644 index 000000000000..428f08993ec4 --- /dev/null +++ b/ts/components/ConversationListItem.stories.tsx @@ -0,0 +1,248 @@ +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; + +import { + ConversationListItem, + MessageStatuses, + Props, +} from './ConversationListItem'; + +// tslint:disable-next-line +import 'draft-js/dist/Draft.css'; + +// @ts-ignore +import { setup as setupI18n } from '../../js/modules/i18n'; + +// @ts-ignore +import enMessages from '../../_locales/en/messages.json'; +import { action } from '@storybook/addon-actions'; +import { boolean, date, select, text } from '@storybook/addon-knobs'; + +const i18n = setupI18n('en', enMessages); + +const story = storiesOf('Components/ConversationListItem', module); + +story.addDecorator(storyFn => ( +
{storyFn()}
+)); + +const createProps = (overrideProps: Partial = {}): Props => ({ + ...overrideProps, + i18n, + isAccepted: boolean( + 'isAccepted', + overrideProps.isAccepted !== undefined ? overrideProps.isAccepted : true + ), + isMe: boolean('isMe', overrideProps.isMe || false), + avatarPath: text('avatarPath', overrideProps.avatarPath || ''), + id: overrideProps.id || '', + isSelected: boolean('isSelected', overrideProps.isSelected || false), + title: text('title', overrideProps.title || 'Some Person'), + name: overrideProps.name || 'Some Person', + type: overrideProps.type || 'direct', + onClick: action('onClick'), + lastMessage: overrideProps.lastMessage || { + text: text('lastMessage.text', 'Hi there!'), + status: select( + 'status', + MessageStatuses.reduce((m, s) => ({ ...m, [s]: s }), {}), + 'read' + ), + }, + lastUpdated: date( + 'lastUpdated', + new Date(overrideProps.lastUpdated || Date.now() - 5 * 60 * 1000) + ), +}); + +story.add('Name', () => { + const props = createProps(); + + return ; +}); + +story.add('Name and Avatar', () => { + const props = createProps({ + avatarPath: '/fixtures/kitten-1-64-64.jpg', + }); + + return ; +}); + +story.add('Conversation with Yourself', () => { + const props = createProps({ + lastMessage: { + text: 'Just a second', + status: 'read', + }, + name: 'Myself', + title: 'Myself', + isMe: true, + }); + + return ; +}); + +story.add('Message Statuses', () => { + return MessageStatuses.map(status => { + const props = createProps({ + lastMessage: { + text: status, + status, + }, + }); + + return ; + }); +}); + +story.add('Typing Status', () => { + const props = createProps({ + typingContact: { + name: 'Someone Here', + }, + }); + + return ; +}); + +story.add('Message Request', () => { + const props = createProps({ + isAccepted: false, + lastMessage: { + text: 'A Message', + status: 'delivered', + }, + }); + + return ; +}); + +story.add('Unread', () => { + const counts = [4, 10, 250]; + + return counts.map(unreadCount => { + const props = createProps({ + lastMessage: { + text: 'Hey there!', + status: 'delivered', + }, + unreadCount, + }); + + return ; + }); +}); + +story.add('Selected', () => { + const props = createProps({ + lastMessage: { + text: 'Hey there!', + status: 'read', + }, + isSelected: true, + }); + + return ; +}); + +story.add('Emoji in Message', () => { + const props = createProps({ + lastMessage: { + text: '🔥', + status: 'read', + }, + }); + + return ; +}); + +story.add('Link in Message', () => { + const props = createProps({ + lastMessage: { + text: 'Download at http://signal.org', + status: 'read', + }, + }); + + return ; +}); + +story.add('Long Name', () => { + const name = + 'Long contact name. Esquire. The third. And stuff. And more! And more!'; + + const props = createProps({ + name, + title: name, + }); + + return ; +}); + +story.add('Long Message', () => { + const messages = [ + "Long line. This is a really really really long line. Really really long. Because that's just how it is", + `Many lines. This is a many-line message. +Line 2 is really exciting but it shouldn't be seen. +Line three is even better. +Line 4, well.`, + ]; + + return messages.map(message => { + const props = createProps({ + name, + lastMessage: { + text: message, + status: 'read', + }, + }); + + return ; + }); +}); + +story.add('Various Times', () => { + const times: Array<[number, string]> = [ + [Date.now() - 5 * 60 * 60 * 1000, 'Five hours ago'], + [Date.now() - 24 * 60 * 60 * 1000, 'One day ago'], + [Date.now() - 7 * 24 * 60 * 60 * 1000, 'One week ago'], + [Date.now() - 365 * 24 * 60 * 60 * 1000, 'One year ago'], + ]; + + return times.map(([lastUpdated, messageText]) => { + const props = createProps({ + name, + lastUpdated, + lastMessage: { + text: messageText, + status: 'read', + }, + }); + + return ; + }); +}); + +story.add('Missing Date', () => { + const props = createProps(); + + return ; +}); + +story.add('Missing Message', () => { + const props = createProps(); + + return ; +}); + +story.add('Missing Text', () => { + const props = createProps(); + + return ( + + ); +}); diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 4cad6b6a42af..fc5ff66ea330 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -12,6 +12,17 @@ import { cleanId } from './_util'; import { LocalizerType } from '../types/Util'; import { ColorType } from '../types/Colors'; +export const MessageStatuses = [ + 'sending', + 'sent', + 'delivered', + 'read', + 'error', + 'partial-sent', +] as const; + +export type MessageStatusType = typeof MessageStatuses[number]; + export type PropsData = { id: string; phoneNumber?: string; @@ -33,13 +44,7 @@ export type PropsData = { typingContact?: Object; lastMessage?: { - status: - | 'sending' - | 'sent' - | 'delivered' - | 'read' - | 'error' - | 'partial-sent'; + status: MessageStatusType; text: string; deletedForEveryone?: boolean; }; @@ -51,7 +56,7 @@ type PropsHousekeeping = { onClick?: (id: string) => void; }; -type Props = PropsData & PropsHousekeeping; +export type Props = PropsData & PropsHousekeeping; export class ConversationListItem extends React.PureComponent { public renderAvatar() {