signal-desktop/ts/components/conversation/Quote.stories.tsx

581 lines
14 KiB
TypeScript
Raw Normal View History

// Copyright 2020-2022 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2022-07-06 19:06:20 +00:00
import type { Meta, Story } from '@storybook/react';
2020-08-27 17:02:25 +00:00
import * as React from 'react';
import { action } from '@storybook/addon-actions';
2021-05-28 16:15:17 +00:00
import { ConversationColors } from '../../types/Colors';
2020-08-27 17:02:25 +00:00
import { pngUrl } from '../../storybook/Fixtures';
2022-11-04 13:22:07 +00:00
import type { Props as TimelineMessagesProps } from './TimelineMessage';
import { TimelineMessage } from './TimelineMessage';
import { TextDirection } from './Message';
import {
AUDIO_MP3,
IMAGE_PNG,
LONG_MESSAGE,
VIDEO_MP4,
2021-08-09 20:06:21 +00:00
stringToMIMEType,
} from '../../types/MIME';
import type { Props } from './Quote';
import { Quote } from './Quote';
import { ReadStatus } from '../../messages/MessageReadStatus';
2021-09-18 00:30:08 +00:00
import { setupI18n } from '../../util/setupI18n';
2020-08-27 17:02:25 +00:00
import enMessages from '../../../_locales/en/messages.json';
2021-05-07 22:21:10 +00:00
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { WidthBreakpoint } from '../_util';
import { ThemeType } from '../../types/Util';
2022-11-30 21:47:54 +00:00
import { PaymentEventKind } from '../../types/Payment';
2020-09-14 19:51:27 +00:00
2020-08-27 17:02:25 +00:00
const i18n = setupI18n('en', enMessages);
2022-06-07 00:48:02 +00:00
export default {
2022-07-06 19:06:20 +00:00
component: Quote,
2022-06-07 00:48:02 +00:00
title: 'Components/Conversation/Quote',
2022-07-06 19:06:20 +00:00
argTypes: {
authorTitle: {
defaultValue: 'Default Sender',
},
conversationColor: {
defaultValue: 'forest',
},
doubleCheckMissingQuoteReference: { action: true },
i18n: {
defaultValue: i18n,
},
isFromMe: {
control: { type: 'checkbox' },
defaultValue: false,
},
isGiftBadge: {
control: { type: 'checkbox' },
defaultValue: false,
},
isIncoming: {
control: { type: 'checkbox' },
defaultValue: false,
},
isViewOnce: {
control: { type: 'checkbox' },
defaultValue: false,
},
onClick: { action: true },
onClose: { action: true },
rawAttachment: {
defaultValue: undefined,
},
referencedMessageNotFound: {
control: { type: 'checkbox' },
defaultValue: false,
},
text: {
defaultValue: 'A sample message from a pal',
},
},
} as Meta;
2020-08-27 17:02:25 +00:00
2022-11-04 13:22:07 +00:00
const defaultMessageProps: TimelineMessagesProps = {
2021-05-07 22:21:10 +00:00
author: getDefaultConversation({
2021-04-27 19:55:21 +00:00
id: 'some-id',
title: 'Person X',
2021-05-07 22:21:10 +00:00
}),
canReact: true,
2020-08-27 17:02:25 +00:00
canReply: true,
canRetry: true,
canRetryDeleteForEveryone: true,
canDeleteForEveryone: true,
canDownload: true,
checkForAccount: action('checkForAccount'),
clearSelectedMessage: action('default--clearSelectedMessage'),
containerElementRef: React.createRef<HTMLElement>(),
containerWidthBreakpoint: WidthBreakpoint.Wide,
2021-05-28 16:15:17 +00:00
conversationColor: 'crimson',
2020-08-27 17:02:25 +00:00
conversationId: 'conversationId',
2022-05-11 20:59:58 +00:00
conversationTitle: 'Conversation Title',
2020-08-27 17:02:25 +00:00
conversationType: 'direct', // override
deleteMessage: action('default--deleteMessage'),
deleteMessageForEveryone: action('default--deleteMessageForEveryone'),
2020-08-27 17:02:25 +00:00
direction: 'incoming',
2022-12-10 02:02:22 +00:00
showLightboxForViewOnceMedia: action('default--showLightboxForViewOnceMedia'),
doubleCheckMissingQuoteReference: action(
'default--doubleCheckMissingQuoteReference'
),
2021-11-17 21:11:46 +00:00
getPreferredBadge: () => undefined,
2020-08-27 17:02:25 +00:00
i18n,
id: 'messageId',
2022-11-04 13:22:07 +00:00
// renderingContext: 'storybook',
2020-08-27 17:02:25 +00:00
interactionMode: 'keyboard',
isBlocked: false,
isMessageRequestAccepted: true,
kickOffAttachmentDownload: action('default--kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('default--markAttachmentAsCorrupted'),
markViewed: action('default--markViewed'),
messageExpanded: action('default--message-expanded'),
showConversation: action('default--showConversation'),
2022-05-11 20:59:58 +00:00
openGiftBadge: action('openGiftBadge'),
openLink: action('default--openLink'),
2020-08-27 17:02:25 +00:00
previews: [],
reactToMessage: action('default--reactToMessage'),
readStatus: ReadStatus.Read,
2020-08-27 17:02:25 +00:00
renderEmojiPicker: () => <div />,
renderReactionPicker: () => <div />,
renderAudioAttachment: () => <div>*AudioAttachment*</div>,
setQuoteByMessageId: action('default--setQuoteByMessageId'),
retrySend: action('default--retrySend'),
retryDeleteForEveryone: action('default--retryDeleteForEveryone'),
2022-12-14 18:12:04 +00:00
saveAttachment: action('saveAttachment'),
scrollToQuotedMessage: action('default--scrollToQuotedMessage'),
selectMessage: action('default--selectMessage'),
shouldCollapseAbove: false,
shouldCollapseBelow: false,
shouldHideMetadata: false,
pushPanelForConversation: action('default--pushPanelForConversation'),
showContactModal: action('default--showContactModal'),
showExpiredIncomingTapToViewToast: action(
'showExpiredIncomingTapToViewToast'
),
showExpiredOutgoingTapToViewToast: action(
'showExpiredOutgoingTapToViewToast'
),
toggleForwardMessageModal: action('default--toggleForwardMessageModal'),
showMessageDetail: action('default--showMessageDetail'),
2022-12-10 02:02:22 +00:00
showLightbox: action('default--showLightbox'),
startConversation: action('default--startConversation'),
2020-08-27 17:02:25 +00:00
status: 'sent',
text: 'This is really interesting.',
textDirection: TextDirection.Default,
theme: ThemeType.light,
2020-08-27 17:02:25 +00:00
timestamp: Date.now(),
2022-07-06 19:06:20 +00:00
viewStory: action('viewStory'),
2020-08-27 17:02:25 +00:00
};
const renderInMessage = ({
authorTitle,
2021-05-28 16:15:17 +00:00
conversationColor,
2020-08-27 17:02:25 +00:00
isFromMe,
rawAttachment,
isViewOnce,
2022-05-11 20:59:58 +00:00
isGiftBadge,
2020-08-27 17:02:25 +00:00
referencedMessageNotFound,
text: quoteText,
}: Props) => {
const messageProps = {
...defaultMessageProps,
2021-05-28 16:15:17 +00:00
conversationColor,
2020-08-27 17:02:25 +00:00
quote: {
authorId: 'an-author',
authorTitle,
2021-05-28 16:15:17 +00:00
conversationColor,
2022-11-30 21:47:54 +00:00
conversationTitle: getDefaultConversation().title,
2020-08-27 17:02:25 +00:00
isFromMe,
rawAttachment,
isViewOnce,
2022-05-11 20:59:58 +00:00
isGiftBadge,
2020-08-27 17:02:25 +00:00
referencedMessageNotFound,
sentAt: Date.now() - 30 * 1000,
text: quoteText,
},
};
return (
<div style={{ overflow: 'hidden' }}>
2022-11-04 13:22:07 +00:00
<TimelineMessage {...messageProps} />
2020-08-27 17:02:25 +00:00
<br />
2022-11-04 13:22:07 +00:00
<TimelineMessage {...messageProps} direction="outgoing" />
2020-08-27 17:02:25 +00:00
</div>
);
};
2022-11-18 00:45:19 +00:00
// eslint-disable-next-line react/function-component-definition
2022-07-06 19:06:20 +00:00
const Template: Story<Props> = args => <Quote {...args} />;
const TemplateInMessage: Story<Props> = args => renderInMessage(args);
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const OutgoingByAnotherAuthor = Template.bind({});
OutgoingByAnotherAuthor.args = {
authorTitle: getDefaultConversation().title,
2022-06-07 00:48:02 +00:00
};
OutgoingByAnotherAuthor.story = {
name: 'Outgoing by Another Author',
};
2022-07-06 19:06:20 +00:00
export const OutgoingByMe = Template.bind({});
OutgoingByMe.args = {
isFromMe: true,
2022-06-07 00:48:02 +00:00
};
OutgoingByMe.story = {
name: 'Outgoing by Me',
};
2022-07-06 19:06:20 +00:00
export const IncomingByAnotherAuthor = Template.bind({});
IncomingByAnotherAuthor.args = {
authorTitle: getDefaultConversation().title,
isIncoming: true,
2022-06-07 00:48:02 +00:00
};
IncomingByAnotherAuthor.story = {
name: 'Incoming by Another Author',
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const IncomingByMe = Template.bind({});
IncomingByMe.args = {
isFromMe: true,
isIncoming: true,
2022-06-07 00:48:02 +00:00
};
IncomingByMe.story = {
name: 'Incoming by Me',
};
2020-08-27 17:02:25 +00:00
2022-11-18 00:45:19 +00:00
export function IncomingOutgoingColors(args: Props): JSX.Element {
2020-08-27 17:02:25 +00:00
return (
<>
2021-05-28 16:15:17 +00:00
{ConversationColors.map(color =>
2022-07-06 19:06:20 +00:00
renderInMessage({ ...args, conversationColor: color })
2021-05-28 16:15:17 +00:00
)}
2020-08-27 17:02:25 +00:00
</>
);
2022-11-18 00:45:19 +00:00
}
2022-07-06 19:06:20 +00:00
IncomingOutgoingColors.args = {};
2022-06-07 00:48:02 +00:00
IncomingOutgoingColors.story = {
name: 'Incoming/Outgoing Colors',
};
2022-07-06 19:06:20 +00:00
export const ImageOnly = Template.bind({});
ImageOnly.args = {
text: '',
rawAttachment: {
contentType: IMAGE_PNG,
fileName: 'sax.png',
isVoiceMessage: false,
thumbnail: {
2020-08-27 17:02:25 +00:00
contentType: IMAGE_PNG,
2022-07-06 19:06:20 +00:00
height: 100,
width: 100,
path: pngUrl,
objectUrl: pngUrl,
2020-08-27 17:02:25 +00:00
},
2022-07-06 19:06:20 +00:00
},
2022-06-07 00:48:02 +00:00
};
2022-07-06 19:06:20 +00:00
export const ImageAttachment = Template.bind({});
ImageAttachment.args = {
rawAttachment: {
contentType: IMAGE_PNG,
fileName: 'sax.png',
isVoiceMessage: false,
thumbnail: {
2020-08-27 17:02:25 +00:00
contentType: IMAGE_PNG,
2022-07-06 19:06:20 +00:00
height: 100,
width: 100,
path: pngUrl,
objectUrl: pngUrl,
2020-08-27 17:02:25 +00:00
},
2022-07-06 19:06:20 +00:00
},
2022-06-07 00:48:02 +00:00
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const ImageAttachmentNoThumbnail = Template.bind({});
ImageAttachmentNoThumbnail.args = {
rawAttachment: {
contentType: IMAGE_PNG,
fileName: 'sax.png',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
2022-07-06 19:06:20 +00:00
ImageAttachmentNoThumbnail.story = {
2022-06-07 00:48:02 +00:00
name: 'Image Attachment w/o Thumbnail',
};
2022-07-06 19:06:20 +00:00
export const ImageTapToView = Template.bind({});
ImageTapToView.args = {
text: '',
isViewOnce: true,
rawAttachment: {
contentType: IMAGE_PNG,
fileName: 'sax.png',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
ImageTapToView.story = {
name: 'Image Tap-to-View',
};
2022-07-06 19:06:20 +00:00
export const VideoOnly = Template.bind({});
VideoOnly.args = {
rawAttachment: {
contentType: VIDEO_MP4,
fileName: 'great-video.mp4',
isVoiceMessage: false,
thumbnail: {
contentType: IMAGE_PNG,
height: 100,
width: 100,
path: pngUrl,
objectUrl: pngUrl,
2020-08-27 17:02:25 +00:00
},
2022-07-06 19:06:20 +00:00
},
text: undefined,
2022-06-07 00:48:02 +00:00
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const VideoAttachment = Template.bind({});
VideoAttachment.args = {
rawAttachment: {
contentType: VIDEO_MP4,
fileName: 'great-video.mp4',
isVoiceMessage: false,
thumbnail: {
contentType: IMAGE_PNG,
height: 100,
width: 100,
path: pngUrl,
objectUrl: pngUrl,
2020-08-27 17:02:25 +00:00
},
2022-07-06 19:06:20 +00:00
},
2022-06-07 00:48:02 +00:00
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const VideoAttachmentNoThumbnail = Template.bind({});
VideoAttachmentNoThumbnail.args = {
rawAttachment: {
contentType: VIDEO_MP4,
fileName: 'great-video.mp4',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
2022-07-06 19:06:20 +00:00
VideoAttachmentNoThumbnail.story = {
2022-06-07 00:48:02 +00:00
name: 'Video Attachment w/o Thumbnail',
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const VideoTapToView = Template.bind({});
VideoTapToView.args = {
text: '',
isViewOnce: true,
rawAttachment: {
contentType: VIDEO_MP4,
fileName: 'great-video.mp4',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
VideoTapToView.story = {
name: 'Video Tap-to-View',
};
2022-07-06 19:06:20 +00:00
export const GiftBadge = TemplateInMessage.bind({});
GiftBadge.args = {
text: "Some text which shouldn't be rendered",
isGiftBadge: true,
};
export const AudioOnly = Template.bind({});
AudioOnly.args = {
rawAttachment: {
contentType: AUDIO_MP3,
fileName: 'great-video.mp3',
isVoiceMessage: false,
},
text: undefined,
};
export const AudioAttachment = Template.bind({});
AudioAttachment.args = {
rawAttachment: {
contentType: AUDIO_MP3,
fileName: 'great-video.mp3',
isVoiceMessage: false,
},
};
export const VoiceMessageOnly = Template.bind({});
VoiceMessageOnly.args = {
rawAttachment: {
contentType: AUDIO_MP3,
fileName: 'great-video.mp3',
isVoiceMessage: true,
},
text: undefined,
};
export const VoiceMessageAttachment = Template.bind({});
VoiceMessageAttachment.args = {
rawAttachment: {
contentType: AUDIO_MP3,
fileName: 'great-video.mp3',
isVoiceMessage: true,
},
};
export const OtherFileOnly = Template.bind({});
OtherFileOnly.args = {
rawAttachment: {
contentType: stringToMIMEType('application/json'),
fileName: 'great-data.json',
isVoiceMessage: false,
},
text: undefined,
};
export const MediaTapToView = Template.bind({});
MediaTapToView.args = {
text: '',
isViewOnce: true,
rawAttachment: {
contentType: AUDIO_MP3,
fileName: 'great-video.mp3',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
MediaTapToView.story = {
name: 'Media Tap-to-View',
};
2022-07-06 19:06:20 +00:00
export const OtherFileAttachment = Template.bind({});
OtherFileAttachment.args = {
rawAttachment: {
contentType: stringToMIMEType('application/json'),
fileName: 'great-data.json',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const LongMessageAttachmentShouldBeHidden = Template.bind({});
LongMessageAttachmentShouldBeHidden.args = {
rawAttachment: {
contentType: LONG_MESSAGE,
fileName: 'signal-long-message-123.txt',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
LongMessageAttachmentShouldBeHidden.story = {
name: 'Long message attachment (should be hidden)',
};
2022-07-06 19:06:20 +00:00
export const NoCloseButton = Template.bind({});
NoCloseButton.args = {
onClose: undefined,
2022-06-07 00:48:02 +00:00
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const MessageNotFound = TemplateInMessage.bind({});
MessageNotFound.args = {
referencedMessageNotFound: true,
2022-06-07 00:48:02 +00:00
};
2020-08-27 17:02:25 +00:00
2022-07-06 19:06:20 +00:00
export const MissingTextAttachment = Template.bind({});
MissingTextAttachment.args = {
text: undefined,
2022-06-07 00:48:02 +00:00
};
MissingTextAttachment.story = {
name: 'Missing Text & Attachment',
};
2020-09-16 22:42:48 +00:00
2022-07-06 19:06:20 +00:00
export const MentionOutgoingAnotherAuthor = Template.bind({});
MentionOutgoingAnotherAuthor.args = {
authorTitle: 'Tony Stark',
text: '@Captain America Lunch later?',
2022-06-07 00:48:02 +00:00
};
MentionOutgoingAnotherAuthor.story = {
name: '@mention + outgoing + another author',
};
2022-07-06 19:06:20 +00:00
export const MentionOutgoingMe = Template.bind({});
MentionOutgoingMe.args = {
isFromMe: true,
text: '@Captain America Lunch later?',
2022-06-07 00:48:02 +00:00
};
MentionOutgoingMe.story = {
name: '@mention + outgoing + me',
};
2022-07-06 19:06:20 +00:00
export const MentionIncomingAnotherAuthor = Template.bind({});
MentionIncomingAnotherAuthor.args = {
authorTitle: 'Captain America',
isIncoming: true,
text: '@Tony Stark sure',
2022-06-07 00:48:02 +00:00
};
MentionIncomingAnotherAuthor.story = {
name: '@mention + incoming + another author',
};
2020-09-16 22:42:48 +00:00
2022-07-06 19:06:20 +00:00
export const MentionIncomingMe = Template.bind({});
MentionIncomingMe.args = {
isFromMe: true,
isIncoming: true,
text: '@Tony Stark sure',
2022-06-07 00:48:02 +00:00
};
MentionIncomingMe.story = {
name: '@mention + incoming + me',
};
2021-05-28 16:15:17 +00:00
2022-11-18 00:45:19 +00:00
export function CustomColor(args: Props): JSX.Element {
return (
<>
<Quote
{...args}
customColor={{
start: { hue: 82, saturation: 35 },
}}
/>
<Quote
{...args}
isIncoming={false}
text="A gradient"
customColor={{
deg: 192,
start: { hue: 304, saturation: 85 },
end: { hue: 231, saturation: 76 },
}}
/>
</>
);
}
2022-07-06 19:06:20 +00:00
CustomColor.args = {
isIncoming: true,
text: 'Solid + Gradient',
};
export const IsStoryReply = Template.bind({});
IsStoryReply.args = {
text: 'Wow!',
authorTitle: 'Amanda',
isStoryReply: true,
moduleClassName: 'StoryReplyQuote',
onClose: undefined,
rawAttachment: {
contentType: VIDEO_MP4,
fileName: 'great-video.mp4',
isVoiceMessage: false,
},
2022-06-07 00:48:02 +00:00
};
IsStoryReply.story = {
name: 'isStoryReply',
};
2022-07-06 19:06:20 +00:00
export const IsStoryReplyEmoji = Template.bind({});
IsStoryReplyEmoji.args = {
authorTitle: getDefaultConversation().firstName,
isStoryReply: true,
moduleClassName: 'StoryReplyQuote',
onClose: undefined,
rawAttachment: {
contentType: IMAGE_PNG,
fileName: 'sax.png',
isVoiceMessage: false,
thumbnail: {
contentType: IMAGE_PNG,
height: 100,
width: 100,
path: pngUrl,
objectUrl: pngUrl,
},
},
reactionEmoji: '🏋️',
2022-06-07 00:48:02 +00:00
};
IsStoryReplyEmoji.story = {
name: 'isStoryReply emoji',
};
2022-11-30 21:47:54 +00:00
export const Payment = Template.bind({});
Payment.args = {
text: '',
payment: {
kind: PaymentEventKind.Notification,
note: null,
},
};