import type { Meta, StoryFn } from '@storybook/react' import React, { useState } from 'react' import { MessageText } from 'stream-chat-react' import type { LockedAttachmentGalleryItem } from './types' import LockedAttachment from '.' const meta: Meta = { title: 'LockedAttachment', parameters: { layout: 'fullscreen' }, } export default meta const MatrixTable: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
Visitor Creator
) const MatrixTableRow: React.FC<{ label: React.ReactNode received: React.ReactNode sent: React.ReactNode }> = ({ label, received, sent }) => ( {label} {received} {sent} ) /** * Deliberately renders WITHOUT any `.str-chat` ancestor or * `str-chat__message*` structure — the real Composer/preview surfaces this * card renders in outside a message thread. If a card looks wrong here, it's * leaning on ambient stream-chat-react CSS instead of its own explicit * styling. */ const MatrixTableCell: React.FC<{ isMine: boolean children: React.ReactNode }> = ({ children }) =>
{children}
export const Text: StoryFn = () => { const [isUnlocking, setUnlocking] = useState(false) // Matches what `CustomMessage` passes `MessageText` for a revealed paid // text attachment (`displayMessage`) — just enough of a `LocalMessage` // shape to render the real `str-chat__message-text` markup/CSS. const purchasedMessage = { text: "Here's the address, see you Thursday at 7!", type: 'regular', } as Parameters[0]['message'] return ( alert('Removed')} /> } /> } /> { setUnlocking(true) setTimeout(() => setUnlocking(false), 1500) }} /> } sent={ } /> } sent={null} /> } /> } sent={ } /> } /> ) } const VIDEO_THUMBNAIL_BLURRED = '/video-thumbnail-blurred.jpg' const VIDEO_THUMBNAIL = '/video-thumbnail.jpg' const VIDEO_SOURCE = '/video-source.mp4' const AUDIO_THUMBNAIL_BLURRED = '/audio-thumbnail-blurred.jpg' const AUDIO_THUMBNAIL = '/audio-thumbnail.jpg' const AUDIO_SOURCE = '/audio-source.mp3' const IMAGE_THUMBNAIL_BLURRED = '/image-thumbnail-blurred.jpg' const IMAGE_THUMBNAIL = '/image-thumbnail.jpg' const IMAGE_SOURCE = '/image-source.jpg' const DOCUMENT_THUMBNAIL_BLURRED = '/document-thumbnail-blurred.jpg' const DOCUMENT_THUMBNAIL = '/document-thumbnail.jpg' const DOCUMENT_SOURCE = '/document-source.pdf' const VARIANTS = [ { label: 'Video', title: "Alicia's Workout Plan", mimeType: 'video/mp4', detail: '1:20', thumbnailUrl: VIDEO_THUMBNAIL_BLURRED, thumbnailUnlockedUrl: VIDEO_THUMBNAIL, sourceUrl: VIDEO_SOURCE, }, { label: 'Audio', title: 'Morning Meditation', mimeType: 'audio/mpeg', detail: '4:35', thumbnailUrl: AUDIO_THUMBNAIL_BLURRED, thumbnailUnlockedUrl: AUDIO_THUMBNAIL, sourceUrl: AUDIO_SOURCE, }, { label: 'Image', title: 'Picture of my cat', mimeType: 'image/jpeg', detail: '3.2 MB', thumbnailUrl: IMAGE_THUMBNAIL_BLURRED, thumbnailUnlockedUrl: IMAGE_THUMBNAIL, sourceUrl: IMAGE_SOURCE, }, { label: 'Document', title: 'Strength Training Guide', mimeType: 'application/zip', detail: '14 files', thumbnailUrl: DOCUMENT_THUMBNAIL_BLURRED, thumbnailUnlockedUrl: DOCUMENT_THUMBNAIL, sourceUrl: DOCUMENT_SOURCE, }, { label: 'Unknown', title: 'Unknown Attachment', mimeType: 'application/octet-stream', detail: '2.4 MB', thumbnailUrl: undefined, thumbnailUnlockedUrl: undefined, sourceUrl: DOCUMENT_SOURCE, }, ] /** * A mixed-media collection (a video + 3 photos) used by the Gallery stories * below. `LockedAttachment` accepts a `gallery` prop and renders a carousel * of these items when 2+ items are provided. */ const GALLERY_ITEMS: LockedAttachmentGalleryItem[] = [ { mimeType: 'video/mp4', thumbnailUrl: VIDEO_THUMBNAIL, sourceUrl: VIDEO_SOURCE, }, { mimeType: 'image/jpeg', thumbnailUrl: IMAGE_THUMBNAIL, sourceUrl: IMAGE_SOURCE, }, { mimeType: 'application/pdf', thumbnailUrl: DOCUMENT_THUMBNAIL, sourceUrl: DOCUMENT_SOURCE, }, { mimeType: 'audio/mpeg', thumbnailUrl: AUDIO_THUMBNAIL, sourceUrl: AUDIO_SOURCE, }, ] const GALLERY_DETAIL = '4 items' const GALLERY_TITLE = 'Workout Guide' const MediaMatrix: React.FC<{ variant: (typeof VARIANTS)[number] }> = ({ variant, }) => { const [isPaid, setPaid] = useState(false) const [isUnlocking, setUnlocking] = useState(false) const { title, mimeType, detail, thumbnailUrl, thumbnailUnlockedUrl, sourceUrl, } = variant const fetchSource = async () => ({ sourceUrl, thumbnailUrl: thumbnailUnlockedUrl, }) return ( alert('Dismissed')} /> } /> } /> { setUnlocking(true) setTimeout(() => { setUnlocking(false) setPaid(true) }, 1500) }} onDownloadClick={() => alert('Download clicked')} onFetchSource={fetchSource} /> } sent={ } /> alert('Download clicked')} onFetchSource={fetchSource} /> } // Sender never unlocks their own message — no distinct state to show. sent={null} /> alert('Unlock clicked')} onDownloadClick={() => alert('Download clicked')} onFetchSource={fetchSource} /> } sent={ } /> ) } const variantByLabel = (label: string) => VARIANTS.find((v) => v.label === label)! export const Video: StoryFn = () => ( ) // LockedAttachment (contentType="media", !isMine) autoplays the video preview once paid; even with // our SMIL/CSS animation freeze the