import '../../stream-custom-data' import type { Meta, StoryFn } from '@storybook/react' import React, { useEffect } from 'react' import { Channel as ChannelType } from 'stream-chat' import { Channel, Chat, MessageList, MessageUIComponentProps, Window, WithComponents, } from 'stream-chat-react' import { Template, createMockChannel, type TemplateProps, } from '../../stories/customMessageStoryTemplate' import { currentUserArgType, StoryUser, storyUsers, } from '../../stories/decorators/storyUser' import { createMockStreamChatClient } from '../../testing/createMockStreamChatClient' import CustomTypingIndicator from '../CustomTypingIndicator' import { CustomMessageActions } from './CustomMessageActions' import { CustomMessage } from './index' const meta: Meta = { title: 'CustomMessage', component: CustomMessage, argTypes: currentUserArgType, args: { currentUser: storyUsers.creator }, parameters: { layout: 'fullscreen', }, } export default meta export const Default: StoryFn = Template.bind({}) Default.args = { messages: [ { id: 'msg-1', text: 'Hey, how are you?', user: storyUsers.visitor, }, { id: 'msg-2', text: "I'm doing great, thanks!", user: storyUsers.creator, }, { id: 'msg-3', text: 'Awesome! Have a good day.', user: storyUsers.visitor, }, ], } export const MentionContrast: StoryFn = Template.bind({}) MentionContrast.args = { messages: [ { id: 'msg-1', text: 'Hi @Creator, could you take a look at this?', user: storyUsers.visitor, mentioned_users: [storyUsers.creator], }, { id: 'msg-2', text: 'Sure thing, @Visitor — I will check it now.', user: storyUsers.creator, mentioned_users: [storyUsers.visitor], }, ], } // MES-1036: "✓✓ Delivered" appears below only the most recent *sent* // message. Every mock message carries status: 'received', and stream's // MessageList derives `lastOwnMessage`, so the indicator lands on the last // creator message ("Talk soon!") and not on the earlier sent bubble. export const DeliveredStatus: StoryFn = Template.bind({}) DeliveredStatus.args = { messages: [ { id: 'msg-1', text: 'Hey, did you get my last message?', user: storyUsers.visitor, }, { id: 'msg-2', text: 'Yes! Just replying now.', user: storyUsers.creator, }, { id: 'msg-3', text: 'Sounds good.', user: storyUsers.visitor, }, { id: 'msg-4', text: 'Let me know if you need anything else.', user: storyUsers.creator, }, { id: 'msg-5', text: 'Talk soon!', user: storyUsers.creator, }, ], } export const OutboundStatus: StoryFn = Template.bind({}) OutboundStatus.args = { messages: [ { id: 'msg-1', text: 'July class details', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_OUTBOUND', outbound_id: 'outbound-1', }, }, ], resolveOutbound: () => ({ id: 'automation-1', name: 'July 9th Class attendees', }), } export const OutboundStatusNonLatest: StoryFn = Template.bind({}) OutboundStatusNonLatest.args = { messages: [ { id: 'msg-1', text: 'July class details', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_OUTBOUND', outbound_id: 'outbound-1', }, }, { id: 'msg-2', text: 'Let me know if you have any questions.', user: storyUsers.creator, }, ], resolveOutbound: () => ({ id: 'automation-1', name: 'July 9th Class attendees', }), } export const OutboundStatusResolved: StoryFn = Template.bind({}) OutboundStatusResolved.args = { ...OutboundStatus.args, resolveOutbound: () => ({ id: 'automation-1', name: 'July 9th Class absentee', }), } export const ThreeConsecutiveSentMessages: StoryFn = Template.bind({}) ThreeConsecutiveSentMessages.args = { messages: [ { id: 'msg-1', text: 'why is it that the sun', user: storyUsers.visitor, }, { id: 'msg-2', text: 'has rays', user: storyUsers.creator, }, { id: 'msg-3', text: 'how how how', user: storyUsers.creator, }, { id: 'msg-4', text: 'thanks for the note', user: storyUsers.visitor, }, ], } export const MixedTags: StoryFn = Template.bind({}) MixedTags.args = { messages: [ { id: 'msg-1', text: 'Regular message from a fan', user: storyUsers.visitor, }, { id: 'msg-2', text: 'I wanted to tip you for your amazing work!', user: storyUsers.visitor, metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' }, }, { id: 'msg-3', text: 'Thank you!', user: storyUsers.creator, }, { id: 'msg-4', text: '', user: storyUsers.visitor, metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$25.00' }, }, { id: 'msg-5', text: 'Wow, a tip with no message! Thanks!', user: storyUsers.creator, }, { id: 'msg-6', text: 'This is a paid message!', user: storyUsers.visitor, metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10.00' }, }, { id: 'msg-7', text: 'Got it, thanks!', user: storyUsers.creator, }, { id: 'msg-8', text: 'This is from a chatbot!', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_CHATBOT' }, }, { id: 'msg-9', text: 'Thanks for letting me know.', user: storyUsers.creator, }, ], } export const ChatbotVariants: StoryFn = Template.bind({}) ChatbotVariants.args = { messages: [ { id: 'msg-1', text: 'Would you like to share some details that I can relay to Rupi Kaur?', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_CHATBOT' }, }, { id: 'msg-2', text: "I'm sorry to hear that. Are you referring to the course you've recently purchased?", user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_CHATBOT' }, }, { id: 'msg-3', text: '', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_CHATBOT' }, attachments: [ { type: 'card', title: 'The Perfect Yoga Course', title_link: 'https://linktr.ee/rupikaur/learntoyoga', text: 'linktr.ee/rupikaur/learntoyoga', image_url: 'https://images.unsplash.com/photo-1571019613914-85f342c1d4b5?auto=format&fit=crop&w=1024&q=80', }, ], }, ], } export const ChatbotLinkPreviews: StoryFn = Template.bind({}) ChatbotLinkPreviews.args = { messages: [ { id: 'msg-1', text: 'Check the FAQ pages: pizzeriamozza.com/faq or osteriamozza.com/faq.', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_CHATBOT' }, attachments: [ { type: 'link', og_scrape_url: 'https://www.pizzeriamozza.com/faq', title: 'Pizzeria Mozza | Pizza Restaurant in CA', text: 'FAQ | Pizzeria Mozza', image_url: 'https://picsum.photos/seed/mozza1/560/315', }, { type: 'link', og_scrape_url: 'https://www.osteriamozza.com/faq', title: 'Osteria Mozza | Italian Restaurant in LA', text: 'FAQ | Osteria Mozza', image_url: 'https://picsum.photos/seed/mozza2/560/315', }, ], }, ], } export const WithTypingIndicatorComparison: StoryFn = Template.bind({}) WithTypingIndicatorComparison.args = { currentUser: storyUsers.creator, typingUser: storyUsers.visitor, messages: [ { id: 'msg-1', text: 'Hi, is this still available?', user: storyUsers.visitor, }, { id: 'msg-2', text: 'Yes, absolutely.', user: storyUsers.creator, }, { id: 'msg-3', text: 'Great! I have another question…', user: storyUsers.visitor, }, ], } WithTypingIndicatorComparison.parameters = { docs: { description: { story: 'Renders a standard incoming message and the custom typing indicator in the same list for visual comparison.', }, }, // The typing indicator is animation-dominant, so a snapshot mostly captures // whichever frame the animation happened to be on — it flags a visual change // on nearly every build. Matches CustomTypingIndicator.stories.tsx; the story // stays browsable in Storybook for design review. chromatic: { disableSnapshot: true }, } // ─── Message Actions ────────────────────────────────────────────────────────── /** * Same as TemplateInner but wraps with WithComponents so CustomMessageActions * is wired in — matching what ChannelView does in production. */ const WithActionsTemplateInner: React.FC<{ currentUser: StoryUser messages: TemplateProps['messages'] }> = ({ currentUser, messages }) => { const [client] = React.useState(() => createMockStreamChatClient(currentUser)) const [channel, setChannel] = React.useState(null) useEffect(() => { createMockChannel(client, messages).then(setChannel) }, [client, messages]) const MessageComponent = React.useMemo(() => { return function CustomMessageComponent(props: MessageUIComponentProps) { return } }, []) if (!channel) { return
Loading...
} return (
) } const WithActionsTemplate: StoryFn = ({ currentUser = storyUsers.creator, messages, }) => ( ) export const ConversationWithPaidAttachments: StoryFn = WithActionsTemplate.bind({}) ConversationWithPaidAttachments.args = { currentUser: storyUsers.creator, messages: [ { id: 'msg-1', text: 'Can I have your workout plan?', user: storyUsers.visitor, }, { id: 'msg-2', text: 'Yes, of course!', user: storyUsers.creator, }, { id: 'msg-3', text: 'Let me know if you have any questions!', user: storyUsers.creator, metadata: { custom_type: 'MESSAGE_ATTACHMENT', payment_status: 'paid', amount_text: 'AU$9.99', attachment_title: "Alicia's Workout Plan", attachment_mime_type: 'video/mp4', attachment_thumbnail: '/video-thumbnail-blurred.jpg', attachment_detail: '1:20', }, }, { id: 'msg-4', text: 'Looks amazing, unlocking now!', user: storyUsers.visitor, }, { id: 'msg-5', text: 'Here is another resource for you.', user: storyUsers.visitor, metadata: { custom_type: 'MESSAGE_ATTACHMENT', amount_text: 'AU$4.99', attachment_title: 'Nutrition Guide', attachment_mime_type: 'application/pdf', attachment_thumbnail: '/video-thumbnail-blurred.jpg', attachment_detail: '12 pages', }, }, ], } ConversationWithPaidAttachments.parameters = { docs: { description: { story: 'Report and Delete actions only appear on paid attachment messages — regular messages show no actions. Hovering msg-3 (a purchased attachment) shows the delete confirmation with buyer warning.', }, }, }