@linktr.ee/messaging-react 4.2.0-rc-1787325710 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/Card-BYJyA83X.js +701 -0
  2. package/dist/Card-BYJyA83X.js.map +1 -0
  3. package/dist/Card-Be81K6su.cjs +2 -0
  4. package/dist/Card-Be81K6su.cjs.map +1 -0
  5. package/dist/Card-BplJtZao.cjs +2 -0
  6. package/dist/Card-BplJtZao.cjs.map +1 -0
  7. package/dist/Card-BzvDr-pU.js +112 -0
  8. package/dist/Card-BzvDr-pU.js.map +1 -0
  9. package/dist/PurchaseStatusBadge-BmqAQEV9.cjs +2 -0
  10. package/dist/PurchaseStatusBadge-BmqAQEV9.cjs.map +1 -0
  11. package/dist/PurchaseStatusBadge-D1RRsubZ.js +59 -0
  12. package/dist/PurchaseStatusBadge-D1RRsubZ.js.map +1 -0
  13. package/dist/assets/index.css +1 -2
  14. package/dist/index-BUC9vqhJ.js +5916 -0
  15. package/dist/index-BUC9vqhJ.js.map +1 -0
  16. package/dist/index-BkqyKPJP.cjs +3 -0
  17. package/dist/index-BkqyKPJP.cjs.map +1 -0
  18. package/dist/index.cjs +2 -3
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +26 -0
  21. package/dist/index.js +41 -4071
  22. package/dist/index.js.map +1 -1
  23. package/dist/testing.cjs +2 -2
  24. package/dist/testing.cjs.map +1 -1
  25. package/dist/testing.js +118 -106
  26. package/dist/testing.js.map +1 -1
  27. package/package.json +5 -5
  28. package/src/components/ChannelView.stories.tsx +4 -0
  29. package/src/index.ts +3 -0
  30. package/src/logging/index.test.tsx +136 -0
  31. package/src/logging/index.tsx +48 -0
  32. package/src/providers/MessagingProvider.test.tsx +127 -1
  33. package/src/providers/MessagingProvider.tsx +26 -15
  34. package/src/types.ts +8 -0
  35. package/dist/AttachmentCard-C-X0jLZ2.cjs +0 -2
  36. package/dist/AttachmentCard-C-X0jLZ2.cjs.map +0 -1
  37. package/dist/AttachmentCard-D2IoHdcv.js +0 -410
  38. package/dist/AttachmentCard-D2IoHdcv.js.map +0 -1
  39. package/dist/Card-C7uLQbYz.js +0 -105
  40. package/dist/Card-C7uLQbYz.js.map +0 -1
  41. package/dist/Card-DENHpLIC.cjs +0 -2
  42. package/dist/Card-DENHpLIC.cjs.map +0 -1
  43. package/dist/Card-Ds1FMcUj.cjs +0 -2
  44. package/dist/Card-Ds1FMcUj.cjs.map +0 -1
  45. package/dist/Card-EKD9NkTa.js +0 -498
  46. package/dist/Card-EKD9NkTa.js.map +0 -1
  47. package/dist/LoadingDots-BSk_UeS0.cjs +0 -2
  48. package/dist/LoadingDots-BSk_UeS0.cjs.map +0 -1
  49. package/dist/LoadingDots-B_WiXBeQ.js +0 -73
  50. package/dist/LoadingDots-B_WiXBeQ.js.map +0 -1
  51. package/dist/rolldown-runtime-BocRIvOZ.cjs +0 -1
@@ -0,0 +1,48 @@
1
+ import type { Logger } from '@linktr.ee/messaging-core'
2
+ import React, { createContext, useContext, useMemo } from 'react'
3
+
4
+ /**
5
+ * Default sink: the console calls the package makes today. Wrappers, not
6
+ * `console.error.bind(console)`, so a later-installed spy is still observed.
7
+ */
8
+ export const consoleLogger: Required<Logger> = {
9
+ debug: (message, ...args) => console.log(message, ...args),
10
+ info: (message, ...args) => console.log(message, ...args),
11
+ warn: (message, ...args) => console.warn(message, ...args),
12
+ error: (message, ...args) => console.error(message, ...args),
13
+ }
14
+
15
+ /**
16
+ * Merge a partial sink over the console defaults, so a consumer can route only
17
+ * the levels it cares about (e.g. `error`) and leave the rest on console.
18
+ */
19
+ export const resolveLogger = (logger?: Logger): Required<Logger> => ({
20
+ debug: logger?.debug ?? consoleLogger.debug,
21
+ info: logger?.info ?? consoleLogger.info,
22
+ warn: logger?.warn ?? consoleLogger.warn,
23
+ error: logger?.error ?? consoleLogger.error,
24
+ })
25
+
26
+ const LoggerContext = createContext<Required<Logger>>(consoleLogger)
27
+
28
+ /**
29
+ * Provides the resolved logger sink to the messaging subtree. Rendered by
30
+ * `MessagingProvider`; usable standalone in tests or in a subtree that needs a
31
+ * different sink.
32
+ */
33
+ export const MessagingLoggerProvider: React.FC<{
34
+ logger?: Logger
35
+ children: React.ReactNode
36
+ }> = ({ logger, children }) => {
37
+ const value = useMemo(() => resolveLogger(logger), [logger])
38
+ return (
39
+ <LoggerContext.Provider value={value}>{children}</LoggerContext.Provider>
40
+ )
41
+ }
42
+
43
+ /**
44
+ * The logger sink for the current subtree. Falls back to console when no
45
+ * provider (or no `logger` prop) is present, so call sites never branch.
46
+ */
47
+ export const useMessagingLogger = (): Required<Logger> =>
48
+ useContext(LoggerContext)
@@ -1,10 +1,16 @@
1
1
  import { StreamChatService } from '@linktr.ee/messaging-core'
2
- import type { MessagingUser } from '@linktr.ee/messaging-core'
2
+ import type {
3
+ Logger,
4
+ MessagingUser,
5
+ StreamChatServiceConfig,
6
+ } from '@linktr.ee/messaging-core'
3
7
  import { render, waitFor, act } from '@testing-library/react'
4
8
  import React from 'react'
5
9
  import type { StreamChat } from 'stream-chat'
6
10
  import { describe, it, expect, vi, beforeEach } from 'vitest'
7
11
 
12
+ import { useMessagingLogger } from '../logging'
13
+
8
14
  import { MessagingProvider, useMessagingContext } from './MessagingProvider'
9
15
 
10
16
  // Stub stream-chat-react's <Chat /> so we don't need a real, fully-wired
@@ -488,4 +494,124 @@ describe('MessagingProvider', () => {
488
494
  expect(last.hasClient).toBe(false)
489
495
  expect(last.error).toBeNull()
490
496
  })
497
+
498
+ describe('logger seam', () => {
499
+ const ServiceProbe: React.FC<{
500
+ onService: (service: StreamChatService | null) => void
501
+ }> = ({ onService }) => {
502
+ const { service } = useMessagingContext()
503
+ React.useEffect(() => {
504
+ onService(service)
505
+ }, [service, onService])
506
+ return null
507
+ }
508
+
509
+ const LoggerProbe: React.FC<{ onLogger: (logger: Logger) => void }> = ({
510
+ onLogger,
511
+ }) => {
512
+ const logger = useMessagingLogger()
513
+ React.useEffect(() => {
514
+ onLogger(logger)
515
+ }, [logger, onLogger])
516
+ return null
517
+ }
518
+
519
+ // The service stores its resolved sink privately; read it to prove which
520
+ // logger the provider handed to the constructor.
521
+ const serviceLogger = (service: StreamChatService) =>
522
+ (service as unknown as { logger: Logger }).logger
523
+
524
+ const renderWithLogger = async (
525
+ props: Partial<React.ComponentProps<typeof MessagingProvider>> & {
526
+ serviceConfig: Omit<StreamChatServiceConfig, 'apiKey'>
527
+ }
528
+ ) => {
529
+ setupServiceMock()
530
+ const services: (StreamChatService | null)[] = []
531
+ const loggers: Logger[] = []
532
+
533
+ render(
534
+ <MessagingProvider
535
+ apiKey="mock-api-key"
536
+ user={
537
+ { type: 'guest', id: 'guest-1', name: 'Guest' } as MessagingUser
538
+ }
539
+ {...props}
540
+ >
541
+ <ServiceProbe onService={(s) => services.push(s)} />
542
+ <LoggerProbe onLogger={(l) => loggers.push(l)} />
543
+ </MessagingProvider>
544
+ )
545
+
546
+ await waitFor(() => expect(services.filter(Boolean).length).toBe(1))
547
+ return {
548
+ service: services.filter(Boolean).pop() as StreamChatService,
549
+ logger: loggers[loggers.length - 1],
550
+ }
551
+ }
552
+
553
+ const baseServiceConfig: Omit<StreamChatServiceConfig, 'apiKey'> = {
554
+ fetchToken: async () => 'token',
555
+ createChannel: async () => ({ channelId: 'ch-1' }),
556
+ }
557
+
558
+ it('forwards the logger prop into the service and to the subtree', async () => {
559
+ const sink: Logger = { error: vi.fn() }
560
+ const { service, logger } = await renderWithLogger({
561
+ serviceConfig: baseServiceConfig,
562
+ logger: sink,
563
+ })
564
+
565
+ expect(serviceLogger(service).error).toBe(sink.error)
566
+
567
+ const error = new Error('boom')
568
+ logger.error?.('[MessagingShell] Failed:', error)
569
+ expect(sink.error).toHaveBeenCalledWith('[MessagingShell] Failed:', error)
570
+ })
571
+
572
+ it('resolves a partial prop sink before handing it to the service', async () => {
573
+ const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {})
574
+ const sink: Logger = { error: vi.fn() }
575
+ const { service } = await renderWithLogger({
576
+ serviceConfig: baseServiceConfig,
577
+ logger: sink,
578
+ })
579
+
580
+ // The service swaps its own console defaults out wholesale, so an
581
+ // unresolved partial sink would silence the levels it omits.
582
+ serviceLogger(service).warn?.('[StreamChatService] Slow:', 1)
583
+ expect(consoleWarn).toHaveBeenCalledWith('[StreamChatService] Slow:', 1)
584
+ })
585
+
586
+ it('lets an explicit serviceConfig.logger win over the logger prop', async () => {
587
+ const configSink: Logger = { error: vi.fn() }
588
+ const propSink: Logger = { error: vi.fn() }
589
+ const { service } = await renderWithLogger({
590
+ serviceConfig: { ...baseServiceConfig, logger: configSink },
591
+ logger: propSink,
592
+ })
593
+
594
+ expect(serviceLogger(service)).toBe(configSink)
595
+ })
596
+
597
+ it('leaves the service on its console defaults when no logger is given', async () => {
598
+ const consoleError = vi
599
+ .spyOn(console, 'error')
600
+ .mockImplementation(() => {})
601
+ const { service, logger } = await renderWithLogger({
602
+ serviceConfig: baseServiceConfig,
603
+ })
604
+
605
+ // Not the react seam object: the service fell back to its own defaults.
606
+ expect(serviceLogger(service)).not.toBe(logger)
607
+
608
+ const error = new Error('boom')
609
+ logger.error?.('[MessagingShell] Failed:', error)
610
+ serviceLogger(service).error?.('[StreamChatService] Failed:', error)
611
+ expect(consoleError.mock.calls).toEqual([
612
+ ['[MessagingShell] Failed:', error],
613
+ ['[StreamChatService] Failed:', error],
614
+ ])
615
+ })
616
+ })
491
617
  })
@@ -13,6 +13,7 @@ import React, {
13
13
  import { StreamChat } from 'stream-chat'
14
14
  import { Chat } from 'stream-chat-react'
15
15
 
16
+ import { MessagingLoggerProvider, resolveLogger } from '../logging'
16
17
  import type { MessagingProviderProps, MessagingCapabilities } from '../types'
17
18
 
18
19
  /**
@@ -56,6 +57,7 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
56
57
  capabilities = {},
57
58
  debug = false,
58
59
  client: injectedClient,
60
+ logger,
59
61
  }) => {
60
62
  // Create debug logger that respects the debug prop
61
63
  const debugLog = useCallback(
@@ -94,6 +96,8 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
94
96
  serviceConfigRef.current = serviceConfig
95
97
  const debugRef = useRef(debug)
96
98
  debugRef.current = debug
99
+ const loggerRef = useRef(logger)
100
+ loggerRef.current = logger
97
101
 
98
102
  // Track renders and prop changes
99
103
  const prevPropsRef = useRef({
@@ -199,6 +203,11 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
199
203
  apiKey,
200
204
  debug: debugRef.current,
201
205
  client: chatClient,
206
+ // An explicit serviceConfig.logger wins — existing injectors keep working.
207
+ // Resolved, because the service swaps its console defaults out wholesale.
208
+ ...(currentConfig.logger || !loggerRef.current
209
+ ? {}
210
+ : { logger: resolveLogger(loggerRef.current) }),
202
211
  })
203
212
 
204
213
  serviceRef.current = newService
@@ -404,20 +413,22 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
404
413
  })
405
414
 
406
415
  return (
407
- <MessagingContext.Provider value={contextValue}>
408
- {chatClient ? (
409
- <Chat
410
- client={chatClient}
411
- customClasses={{
412
- channelList:
413
- 'str-chat__channel-list str-chat__channel-list-react bg-transparent lg:border-r-2 border-r-0 border-[#0000000A]',
414
- }}
415
- >
416
- {children}
417
- </Chat>
418
- ) : (
419
- children
420
- )}
421
- </MessagingContext.Provider>
416
+ <MessagingLoggerProvider logger={logger}>
417
+ <MessagingContext.Provider value={contextValue}>
418
+ {chatClient ? (
419
+ <Chat
420
+ client={chatClient}
421
+ customClasses={{
422
+ channelList:
423
+ 'str-chat__channel-list str-chat__channel-list-react bg-transparent lg:border-r-2 border-r-0 border-[#0000000A]',
424
+ }}
425
+ >
426
+ {children}
427
+ </Chat>
428
+ ) : (
429
+ children
430
+ )}
431
+ </MessagingContext.Provider>
432
+ </MessagingLoggerProvider>
422
433
  )
423
434
  }
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ Logger,
2
3
  MessagingUser,
3
4
  StreamChatServiceConfig,
4
5
  } from '@linktr.ee/messaging-core'
@@ -501,6 +502,13 @@ interface MessagingProviderBaseProps {
501
502
  user: MessagingUser | null
502
503
  capabilities?: MessagingCapabilities
503
504
  debug?: boolean
505
+ /**
506
+ * Optional sink for the package's own log output. Levels left undefined fall
507
+ * back to console, which is also the default when the prop is omitted. In
508
+ * apiKey mode it is forwarded to the underlying StreamChatService unless
509
+ * `serviceConfig.logger` is set, which wins.
510
+ */
511
+ logger?: Logger
504
512
  }
505
513
 
506
514
  /**
@@ -1,2 +0,0 @@
1
- const e=require("./rolldown-runtime-BocRIvOZ.cjs");let t=require("react");t=e.t(t,1);let n=require("react/jsx-runtime"),r=require("classnames");r=e.t(r,1);let i=require("@phosphor-icons/react");var a=[`linktr.ee`],o=`messaging-attachment-v1_0`;function s(e){try{return new URL(e).hostname.toLowerCase()}catch{return null}}function c(e){return a.some(t=>e===t||e.endsWith(`.${t}`))}function l(e){let t=e?.trim();if(!t)return;let n=s(t);if(!n||!c(n))return t;let r=new URL(t);return r.searchParams.get(`io`)===`true`&&r.searchParams.get(`size`)===o?t:(r.searchParams.set(`io`,`true`),r.searchParams.set(`size`,o),r.toString())}var u=[[/pdf/,`pdf`],[/wordprocessingml|msword|\.doc/,`doc`],[/spreadsheetml|ms-excel|\.xls/,`xls`],[/csv/,`csv`],[/presentationml|ms-powerpoint|\.ppt/,`ppt`],[/zip|x-rar|x-7z|x-tar|x-gzip/,`zip`],[/plain|rtf/,`text`],[/markdown/,`markdown`]];function d(e){return e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:e.startsWith(`image/`)?`image`:`document`}function f(e){let t=u.find(([t])=>t.test(e));return t?t[1]:`generic`}var p={video:i.VideoCameraIcon,audio:i.SpeakerHighIcon,image:i.ImageIcon,document:i.FileIcon},m={pdf:i.FilePdfIcon,doc:i.FileDocIcon,xls:i.FileXlsIcon,csv:i.FileCsvIcon,ppt:i.FilePptIcon,zip:i.FileZipIcon,text:i.FileTextIcon,markdown:i.FileMdIcon,generic:i.FileIcon};function h(e){let t=d(e);return t===`document`?m[f(e)]:p[t]}function g(e,n){return t.default.createElement(h(e),n)}function _(){return!1}var v=e=>`touches`in e?e.touches[0]?.clientX??e.changedTouches[0]?.clientX??0:e.clientX,y=({source:e,mimeType:r,poster:a,autoPlay:o=!1,playing:s,loop:c=!1,controls:l=!0,showProgress:u=!1,muted:f=!1,onContainerClick:p})=>{let m=d(r),h=(0,t.useRef)(null),y=(0,t.useRef)(null),b=(0,t.useRef)(null),x=(0,t.useRef)(s),[S,C]=(0,t.useState)(o),[w,T]=(0,t.useState)(0),[E,D]=(0,t.useState)(!1),[O,k]=(0,t.useState)(!1),[A,j]=(0,t.useState)(!1),[M,N]=(0,t.useState)(!1),[P,F]=(0,t.useState)(!0),[I,L]=(0,t.useState)(null),R=(0,t.useCallback)(()=>{j(!1),C(!0)},[]),z=(0,t.useCallback)(e=>{let t=y.current;if(!t)return 0;let n=t.getBoundingClientRect();return Math.max(0,Math.min(1,(v(e)-n.left)/n.width))},[]),B=(0,t.useCallback)(e=>{let t=h.current;t&&t.duration&&(t.currentTime=e*t.duration)},[]),V=e=>{e.stopPropagation(),D(!0);let t=z(e);T(t),B(t)},H=(0,t.useCallback)(e=>{e.key===`ArrowRight`&&B(Math.min(1,w+.05)),e.key===`ArrowLeft`&&B(Math.max(0,w-.05))},[B,w]);(0,t.useEffect)(()=>{s!==void 0&&s!==x.current&&(x.current=s,C(s))},[s]),(0,t.useEffect)(()=>{if(!S){b.current!==null&&(cancelAnimationFrame(b.current),b.current=null);return}let e=()=>{let t=h.current;t&&t.duration&&!E&&T(t.currentTime/t.duration),b.current=requestAnimationFrame(e)};return b.current=requestAnimationFrame(e),()=>{b.current!==null&&cancelAnimationFrame(b.current)}},[S,E]),(0,t.useEffect)(()=>{let e=h.current;e&&(S?e.play().catch(e=>{C(!1),j(!0),_()&&console.debug(`[MediaPlayer] play() failed`,e)}):e.pause())},[S]),(0,t.useEffect)(()=>{if(!E)return;let e=e=>T(z(e)),t=e=>{D(!1),B(z(e))};return window.addEventListener(`mousemove`,e),window.addEventListener(`mouseup`,t),window.addEventListener(`touchmove`,e,{passive:!0}),window.addEventListener(`touchend`,t),()=>{window.removeEventListener(`mousemove`,e),window.removeEventListener(`mouseup`,t),window.removeEventListener(`touchmove`,e),window.removeEventListener(`touchend`,t)}},[E,z,B]);let U=I?{aspectRatio:String(I)}:void 0,W=I?``:` aspect-video`,G=Math.round(w*100);return(0,n.jsxs)(`div`,{role:`button`,tabIndex:0,className:`relative cursor-pointer overflow-hidden bg-black ${W}`,style:U,onClick:e=>{if(p){p(e);return}A||l&&C(e=>!e)},onKeyDown:e=>{if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),p){p(e);return}A||l&&C(e=>!e)}},children:[a&&(m===`audio`||P)&&(0,n.jsx)(`img`,{src:a,alt:``,className:`absolute inset-0 h-full w-full object-cover`}),!a&&(m===`audio`||P)&&(0,n.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center`,children:g(r,{className:`size-12 text-black/20`,weight:`regular`})}),(0,n.jsx)(`div`,{className:`absolute inset-0`,children:m===`audio`?(0,n.jsx)(`audio`,{ref:h,src:e,loop:c,muted:f,style:{width:`100%`,height:`100%`},onLoadStart:()=>N(!0),onCanPlay:()=>{N(!1),F(!1)},onWaiting:()=>N(!0),onPlay:()=>j(!1),onEnded:()=>{c||(C(!1),T(0))},children:(0,n.jsx)(`track`,{kind:`captions`})}):(0,n.jsx)(`video`,{ref:h,src:e,loop:c,muted:f,playsInline:!0,style:{width:`100%`,height:`100%`},onLoadStart:()=>N(!0),onCanPlay:()=>{N(!1),F(!1)},onWaiting:()=>N(!0),onPlay:()=>j(!1),onLoadedMetadata:()=>{let e=h.current;e instanceof HTMLVideoElement&&e.videoWidth&&e.videoHeight&&L(e.videoWidth/e.videoHeight)},onEnded:()=>{c||(C(!1),T(0))},children:(0,n.jsx)(`track`,{kind:`captions`})})}),M&&!A&&(0,n.jsx)(`div`,{className:`absolute inset-0 z-10 flex items-center justify-center`,children:(0,n.jsx)(i.CircleNotchIcon,{className:`size-8 animate-spin text-white/80`,weight:`bold`})}),A&&!l&&(0,n.jsx)(`div`,{className:`absolute inset-0 z-30 flex cursor-pointer items-center justify-center bg-black/35`,role:`button`,tabIndex:0,"aria-label":`Play preview`,onClick:e=>{e.stopPropagation(),R()},onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),e.stopPropagation(),R())},children:(0,n.jsx)(`span`,{className:`flex size-16 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm`,children:(0,n.jsx)(i.PlayIcon,{className:`size-9 translate-x-0.5`,weight:`fill`})})}),u&&!l&&(0,n.jsx)(`div`,{className:`absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent`,children:(0,n.jsx)(`div`,{role:`slider`,"aria-label":`Playback position`,"aria-valuenow":G,"aria-valuemin":0,"aria-valuemax":100,tabIndex:0,ref:y,className:`relative flex h-4 w-full cursor-pointer items-center`,onMouseDown:V,onTouchStart:V,onClick:e=>e.stopPropagation(),onKeyDown:H,children:(0,n.jsx)(`div`,{className:`w-full overflow-hidden rounded-full bg-white/30 h-1`,children:(0,n.jsx)(`div`,{className:`h-full rounded-full bg-white`,style:{width:`${G}%`}})})})}),l&&(0,n.jsxs)(`div`,{className:`absolute inset-x-0 bottom-0 flex items-center gap-2 bg-gradient-to-t from-black/60 to-transparent px-3 pb-2.5 pt-6 transition-all duration-200`,children:[(0,n.jsx)(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),C(e=>!e)},className:`shrink-0 text-white`,"aria-label":S?`Pause`:`Play`,children:S?(0,n.jsx)(i.PauseIcon,{className:`size-5`,weight:`fill`}):(0,n.jsx)(i.PlayIcon,{className:`size-5 translate-x-px`,weight:`fill`})}),(0,n.jsxs)(`div`,{role:`slider`,"aria-label":`Playback position`,"aria-valuenow":G,"aria-valuemin":0,"aria-valuemax":100,tabIndex:0,ref:y,className:`relative flex h-4 w-full cursor-pointer items-center`,onMouseDown:V,onTouchStart:V,onClick:e=>e.stopPropagation(),onMouseEnter:()=>k(!0),onMouseLeave:()=>k(!1),onKeyDown:H,children:[(0,n.jsx)(`div`,{className:`w-full overflow-hidden rounded-full bg-white/30 transition-all duration-200 ${O||E?`h-1.5`:`h-1`}`,children:(0,n.jsx)(`div`,{className:`h-full rounded-full bg-white`,style:{width:`${G}%`}})}),(0,n.jsx)(`div`,{className:`absolute size-3 -translate-x-1/2 rounded-full bg-white shadow transition-[opacity,transform] duration-200 ${O||E?`scale-100 opacity-100`:`scale-0 opacity-0`}`,style:{left:`${G}%`}})]})]})]})},b=e=>e===`dark`?`size-12 text-white/20`:`size-12 text-black/20`,x=e=>e===`dark`?`aspect-video overflow-hidden bg-white/10`:`aspect-video overflow-hidden bg-black/5`,S=({mimeType:e,sourceUrl:r,thumbnailUrl:i,title:a,variant:o,mediaPlayerProps:s,containedImage:c=!1})=>{let u=d(e),[f,p]=(0,t.useState)(!1),m=l(r),h=l(i);return r&&(u===`video`||u===`audio`)?(0,n.jsx)(y,{source:r,mimeType:e,poster:h,controls:!0,...s}):r&&u===`image`?c?(0,n.jsx)(`div`,{className:`relative aspect-video overflow-hidden bg-black/5`,children:(0,n.jsx)(`img`,{src:m??r,alt:a??``,className:`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${f?`opacity-100`:`opacity-0`}`,draggable:!1,onLoad:()=>p(!0)})}):(0,n.jsx)(`img`,{src:m??r,alt:a??``,className:`block w-full`,draggable:!1}):r&&u===`document`?i?c?(0,n.jsx)(`div`,{className:`relative aspect-video overflow-hidden bg-black/5`,children:(0,n.jsx)(`img`,{src:h??i,alt:a??``,className:`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${f?`opacity-100`:`opacity-0`}`,draggable:!1,onLoad:()=>p(!0)})}):(0,n.jsx)(`img`,{src:h??i,alt:``,className:`block w-full`,draggable:!1}):(0,n.jsx)(`div`,{className:`flex aspect-video w-full items-center justify-center ${o===`dark`?`bg-white/10`:`bg-black/5`}`,children:g(e,{className:b(o),weight:`regular`})}):i?(0,n.jsx)(`div`,{className:`relative ${x(o)}`,children:(0,n.jsx)(`img`,{src:h??i,alt:a??``,draggable:!1,className:`absolute inset-0 h-full w-full object-cover`})}):(0,n.jsx)(`div`,{className:`flex aspect-video w-full items-center justify-center ${o===`dark`?`bg-white/10`:`bg-black/5`}`,children:g(e,{className:b(o),weight:`regular`})})},C=({variant:e,thumbnail:t,title:i,placeholderTitle:a=`Attachment title`,mimeType:o,detail:s,statusBadge:c,action:l,topLeft:u,topRight:d,rootRef:f,"data-testid":p})=>{let m=e===`dark`,h=m?i??a:i??``,_=m&&!i;return(0,n.jsxs)(`div`,{ref:f,"data-testid":p,className:(0,r.default)(`relative w-[280px] select-none overflow-hidden rounded-[24px] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]`,m?`bg-[#1e2330]`:`bg-white`),children:[u?(0,n.jsx)(`div`,{className:`pointer-events-auto absolute left-3 top-3 z-50`,children:u}):null,d?(0,n.jsx)(`div`,{className:`pointer-events-auto absolute right-3 top-3 z-50`,children:d}):null,t,(0,n.jsxs)(`div`,{className:`px-4 pb-3 pt-3`,children:[h.trim()!==``&&(0,n.jsx)(`p`,{className:(0,r.default)(`mb-0.5 truncate text-base font-medium`,{"text-black":!m,"text-white/30":m&&_,"text-white":m&&!_}),children:h}),(0,n.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1`,children:[g(o,{className:(0,r.default)(`size-5 shrink-0`,m?`text-white/55`:`text-black/55`),weight:`regular`}),s!=null&&s!==``&&(0,n.jsx)(`span`,{className:(0,r.default)(`text-xs font-medium`,m?`text-white/55`:`text-black/55`),children:s}),c]}),l]})]})};Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return C}});
2
- //# sourceMappingURL=AttachmentCard-C-X0jLZ2.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AttachmentCard-C-X0jLZ2.cjs","names":[],"sources":["../src/utils/cdnImageUrl.ts","../src/components/AttachmentCard/utils/mimeType.ts","../src/components/AttachmentCard/utils/icons.ts","../src/utils/isDevBuild.ts","../src/components/AttachmentCard/MediaPlayer.tsx","../src/components/AttachmentCard/Thumbnail.tsx","../src/components/AttachmentCard/index.tsx"],"sourcesContent":["const LINKTREE_CDN_HOSTS = ['linktr.ee']\n\nconst ATTACHMENT_IMAGE_SIZE_PRESET = 'messaging-attachment-v1_0'\n\nfunction hostnameFromUrl(url: string): string | null {\n try {\n return new URL(url).hostname.toLowerCase()\n } catch {\n return null\n }\n}\n\nfunction isLinktreeCdnHost(hostname: string): boolean {\n return LINKTREE_CDN_HOSTS.some(\n (host) => hostname === host || hostname.endsWith(`.${host}`)\n )\n}\n\nexport function optimizeMessagingAttachmentUrl(\n url: string | undefined | null\n): string | undefined {\n const trimmed = url?.trim()\n if (!trimmed) return undefined\n\n const hostname = hostnameFromUrl(trimmed)\n if (!hostname || !isLinktreeCdnHost(hostname)) return trimmed\n\n const parsedUrl = new URL(trimmed)\n const isAlreadyMessagingOptimized =\n parsedUrl.searchParams.get('io') === 'true' &&\n parsedUrl.searchParams.get('size') === ATTACHMENT_IMAGE_SIZE_PRESET\n\n if (isAlreadyMessagingOptimized) {\n return trimmed\n }\n\n parsedUrl.searchParams.set('io', 'true')\n parsedUrl.searchParams.set('size', ATTACHMENT_IMAGE_SIZE_PRESET)\n\n return parsedUrl.toString()\n}\n","export type AttachmentSourceType = 'image' | 'audio' | 'video' | 'document'\n\nexport type DocumentIconType =\n | 'pdf'\n | 'doc'\n | 'xls'\n | 'csv'\n | 'ppt'\n | 'zip'\n | 'text'\n | 'markdown'\n | 'generic'\n\nconst DOCUMENT_ICON_PATTERNS: Array<[RegExp, DocumentIconType]> = [\n [/pdf/, 'pdf'],\n [/wordprocessingml|msword|\\.doc/, 'doc'],\n [/spreadsheetml|ms-excel|\\.xls/, 'xls'],\n [/csv/, 'csv'],\n [/presentationml|ms-powerpoint|\\.ppt/, 'ppt'],\n [/zip|x-rar|x-7z|x-tar|x-gzip/, 'zip'],\n [/plain|rtf/, 'text'],\n [/markdown/, 'markdown'],\n]\n\nexport function getSourceType(mimeType: string): AttachmentSourceType {\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n if (mimeType.startsWith('image/')) return 'image'\n return 'document'\n}\n\nexport function getDocumentIconType(mimeType: string): DocumentIconType {\n const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>\n pattern.test(mimeType)\n )\n return match ? match[1] : 'generic'\n}\n","import {\n FileIcon,\n FileCsvIcon,\n FileDocIcon,\n FileMdIcon,\n FilePdfIcon,\n FilePptIcon,\n FileTextIcon,\n FileXlsIcon,\n FileZipIcon,\n ImageIcon,\n SpeakerHighIcon,\n VideoCameraIcon,\n IconProps,\n} from '@phosphor-icons/react'\nimport React from 'react'\n\nimport { getDocumentIconType, getSourceType } from './mimeType'\nimport type { AttachmentSourceType } from './mimeType'\n\nconst MEDIA_TYPE_ICON: Record<AttachmentSourceType, React.ElementType> = {\n video: VideoCameraIcon,\n audio: SpeakerHighIcon,\n image: ImageIcon,\n document: FileIcon,\n}\n\nconst DOCUMENT_ICON_COMPONENT = {\n pdf: FilePdfIcon,\n doc: FileDocIcon,\n xls: FileXlsIcon,\n csv: FileCsvIcon,\n ppt: FilePptIcon,\n zip: FileZipIcon,\n text: FileTextIcon,\n markdown: FileMdIcon,\n generic: FileIcon,\n} as const\n\nfunction getTypeIcon(mimeType: string): React.ElementType {\n const sourceType = getSourceType(mimeType)\n if (sourceType !== 'document') return MEDIA_TYPE_ICON[sourceType]\n return DOCUMENT_ICON_COMPONENT[getDocumentIconType(mimeType)]\n}\n\n/** Use instead of `<TypeIcon />` where TypeIcon = getTypeIcon(mime) to satisfy react-hooks/static-components. */\nexport function renderTypeIcon(\n mimeType: string,\n props: IconProps\n): React.ReactElement {\n return React.createElement(getTypeIcon(mimeType), props)\n}\n","/**\n * True in Vite dev builds (`import.meta.env.DEV`). Uses a type assertion so\n * `tsc` does not rely on ambient `ImportMeta` merging (vite/client).\n */\nexport function isDevBuild(): boolean {\n return (\n typeof import.meta !== 'undefined' &&\n (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV === true\n )\n}\n","import { CircleNotchIcon, PauseIcon, PlayIcon } from '@phosphor-icons/react'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { isDevBuild } from '../../utils/isDevBuild'\n\nimport { renderTypeIcon } from './utils/icons'\nimport { getSourceType } from './utils/mimeType'\n\ntype TouchEventUnion =\n MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent\n\nconst getClientXFromEvent = (e: TouchEventUnion): number => {\n if ('touches' in e) {\n return e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0\n }\n return e.clientX\n}\n\nexport interface MediaPlayerProps {\n source: string\n mimeType: string\n poster?: string\n autoPlay?: boolean\n /** Controlled playing state. When provided, syncs to internal play/pause. */\n playing?: boolean\n loop?: boolean\n controls?: boolean\n showProgress?: boolean\n /** When true, requests muted playback (helps autoplay policies on video). */\n muted?: boolean\n /**\n * When provided, overrides the default click-to-play-toggle behaviour on the\n * player container. The play/pause button (which calls stopPropagation) is\n * unaffected, so inline playback still works.\n */\n onContainerClick?: (e: React.MouseEvent) => void\n}\n\nconst MediaPlayer: React.FC<MediaPlayerProps> = ({\n source,\n mimeType,\n poster,\n autoPlay = false,\n playing: playingProp,\n loop = false,\n controls = true,\n showProgress = false,\n muted = false,\n onContainerClick,\n}) => {\n // --- Derived ---\n const sourceType = getSourceType(mimeType)\n\n // --- Refs ---\n const playerRef = useRef<HTMLMediaElement>(null)\n const trackRef = useRef<HTMLDivElement>(null)\n const rafRef = useRef<number | null>(null)\n const prevPlayingPropRef = useRef(playingProp)\n\n // --- State: playback ---\n const [playing, setPlaying] = useState(autoPlay)\n const [played, setPlayed] = useState(0)\n const [seeking, setSeeking] = useState(false)\n\n // --- State: UI ---\n const [scrubberHovered, setScrubberHovered] = useState(false)\n /** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */\n const [manualPlayRequired, setManualPlayRequired] = useState(false)\n\n // --- State: loading ---\n const [buffering, setBuffering] = useState(false)\n /** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */\n const [initialLoad, setInitialLoad] = useState(true)\n const [videoAspect, setVideoAspect] = useState<number | null>(null)\n\n // --- Callbacks ---\n const startPlaybackFromGesture = useCallback(() => {\n setManualPlayRequired(false)\n setPlaying(true)\n }, [])\n\n const getFraction = useCallback((e: TouchEventUnion) => {\n const track = trackRef.current\n if (!track) return 0\n const rect = track.getBoundingClientRect()\n return Math.max(\n 0,\n Math.min(1, (getClientXFromEvent(e) - rect.left) / rect.width)\n )\n }, [])\n\n const seekTo = useCallback((fraction: number) => {\n const el = playerRef.current\n if (el && el.duration) el.currentTime = fraction * el.duration\n }, [])\n\n const handleTrackPointerDown = (\n e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>\n ) => {\n e.stopPropagation()\n setSeeking(true)\n const fraction = getFraction(e)\n setPlayed(fraction)\n seekTo(fraction)\n }\n\n const handleScrubberKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n },\n [seekTo, played]\n )\n\n // --- Effects ---\n\n // Sync controlled playing prop to internal state\n useEffect(() => {\n if (\n playingProp !== undefined &&\n playingProp !== prevPlayingPropRef.current\n ) {\n prevPlayingPropRef.current = playingProp\n setPlaying(playingProp)\n }\n }, [playingProp])\n\n // RAF-driven progress updates\n useEffect(() => {\n if (!playing) {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = null\n }\n return\n }\n const tick = () => {\n const el = playerRef.current\n if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n }\n }, [playing, seeking])\n\n // ReactPlayer v3 uses native HTML media elements and does not support a\n // declarative `playing` prop — playback must be driven imperatively.\n useEffect(() => {\n const el = playerRef.current\n if (!el) return\n if (playing) {\n void el.play().catch((err) => {\n setPlaying(false)\n setManualPlayRequired(true)\n if (isDevBuild()) {\n console.debug('[MediaPlayer] play() failed', err)\n }\n })\n } else {\n el.pause()\n }\n }, [playing])\n\n // Global seeking listeners\n useEffect(() => {\n if (!seeking) return\n const onMove = (e: MouseEvent | TouchEvent) => setPlayed(getFraction(e))\n const onUp = (e: MouseEvent | TouchEvent) => {\n setSeeking(false)\n seekTo(getFraction(e))\n }\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', onUp)\n window.addEventListener('touchmove', onMove, { passive: true })\n window.addEventListener('touchend', onUp)\n return () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', onUp)\n window.removeEventListener('touchmove', onMove)\n window.removeEventListener('touchend', onUp)\n }\n }, [seeking, getFraction, seekTo])\n\n // --- Derived render values ---\n // Use natural aspect ratio once metadata loads, fall back to 16:9 before then.\n const aspectStyle = videoAspect\n ? { aspectRatio: String(videoAspect) }\n : undefined\n const aspectClass = !videoAspect ? ' aspect-video' : ''\n const scrubberPercent = Math.round(played * 100)\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={`relative cursor-pointer overflow-hidden bg-black ${aspectClass}`}\n style={aspectStyle}\n onClick={(e) => {\n if (onContainerClick) {\n onContainerClick(e)\n return\n }\n if (manualPlayRequired) return\n if (controls) setPlaying((p) => !p)\n }}\n onKeyDown={(e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return\n e.preventDefault()\n if (onContainerClick) {\n onContainerClick(e as unknown as React.MouseEvent)\n return\n }\n if (manualPlayRequired) return\n if (controls) setPlaying((p) => !p)\n }}\n >\n {/* For audio, poster persists as a visual background. For video, hide once loaded. */}\n {poster && (sourceType === 'audio' || initialLoad) && (\n <img\n src={poster}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n )}\n {!poster && (sourceType === 'audio' || initialLoad) && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n <div className=\"absolute inset-0\">\n {sourceType === 'audio' ? (\n <audio\n ref={playerRef as React.RefObject<HTMLAudioElement>}\n src={source}\n loop={loop}\n muted={muted}\n style={{ width: '100%', height: '100%' }}\n onLoadStart={() => setBuffering(true)}\n onCanPlay={() => {\n setBuffering(false)\n setInitialLoad(false)\n }}\n onWaiting={() => setBuffering(true)}\n onPlay={() => setManualPlayRequired(false)}\n onEnded={() => {\n if (!loop) {\n setPlaying(false)\n setPlayed(0)\n }\n }}\n >\n <track kind=\"captions\" />\n </audio>\n ) : (\n <video\n ref={playerRef as React.RefObject<HTMLVideoElement>}\n src={source}\n loop={loop}\n muted={muted}\n playsInline\n style={{ width: '100%', height: '100%' }}\n onLoadStart={() => setBuffering(true)}\n onCanPlay={() => {\n setBuffering(false)\n setInitialLoad(false)\n }}\n onWaiting={() => setBuffering(true)}\n onPlay={() => setManualPlayRequired(false)}\n onLoadedMetadata={() => {\n const el = playerRef.current\n if (\n el instanceof HTMLVideoElement &&\n el.videoWidth &&\n el.videoHeight\n ) {\n setVideoAspect(el.videoWidth / el.videoHeight)\n }\n }}\n onEnded={() => {\n if (!loop) {\n setPlaying(false)\n setPlayed(0)\n }\n }}\n >\n <track kind=\"captions\" />\n </video>\n )}\n </div>\n\n {buffering && !manualPlayRequired && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center\">\n <CircleNotchIcon\n className=\"size-8 animate-spin text-white/80\"\n weight=\"bold\"\n />\n </div>\n )}\n\n {manualPlayRequired && !controls && (\n <div\n className=\"absolute inset-0 z-30 flex cursor-pointer items-center justify-center bg-black/35\"\n role=\"button\"\n tabIndex={0}\n aria-label=\"Play preview\"\n onClick={(e) => {\n e.stopPropagation()\n startPlaybackFromGesture()\n }}\n onKeyDown={(e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return\n e.preventDefault()\n e.stopPropagation()\n startPlaybackFromGesture()\n }}\n >\n <span className=\"flex size-16 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm\">\n <PlayIcon className=\"size-9 translate-x-0.5\" weight=\"fill\" />\n </span>\n </div>\n )}\n\n {showProgress && !controls && (\n <div className=\"absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent\">\n <div\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={scrubberPercent}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n ref={trackRef}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={handleTrackPointerDown}\n onTouchStart={handleTrackPointerDown}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={handleScrubberKeyDown}\n >\n <div className=\"w-full overflow-hidden rounded-full bg-white/30 h-1\">\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${scrubberPercent}%` }}\n />\n </div>\n </div>\n </div>\n )}\n\n {controls && (\n <div className=\"absolute inset-x-0 bottom-0 flex items-center gap-2 bg-gradient-to-t from-black/60 to-transparent px-3 pb-2.5 pt-6 transition-all duration-200\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setPlaying((p) => !p)\n }}\n className=\"shrink-0 text-white\"\n aria-label={playing ? 'Pause' : 'Play'}\n >\n {playing ? (\n <PauseIcon className=\"size-5\" weight=\"fill\" />\n ) : (\n <PlayIcon className=\"size-5 translate-x-px\" weight=\"fill\" />\n )}\n </button>\n\n <div\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={scrubberPercent}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n ref={trackRef}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={handleTrackPointerDown}\n onTouchStart={handleTrackPointerDown}\n onClick={(e) => e.stopPropagation()}\n onMouseEnter={() => setScrubberHovered(true)}\n onMouseLeave={() => setScrubberHovered(false)}\n onKeyDown={handleScrubberKeyDown}\n >\n <div\n className={`w-full overflow-hidden rounded-full bg-white/30 transition-all duration-200 ${scrubberHovered || seeking ? 'h-1.5' : 'h-1'}`}\n >\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${scrubberPercent}%` }}\n />\n </div>\n <div\n className={`absolute size-3 -translate-x-1/2 rounded-full bg-white shadow transition-[opacity,transform] duration-200 ${scrubberHovered || seeking ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}\n style={{ left: `${scrubberPercent}%` }}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default MediaPlayer\n","import React, { useState } from 'react'\n\nimport { optimizeMessagingAttachmentUrl } from '../../utils/cdnImageUrl'\n\nimport MediaPlayer, { type MediaPlayerProps } from './MediaPlayer'\nimport { renderTypeIcon } from './utils/icons'\nimport { getSourceType } from './utils/mimeType'\n\nexport type AttachmentThumbnailVariant = 'light' | 'dark'\n\nexport interface AttachmentThumbnailProps {\n mimeType: string\n sourceUrl?: string\n thumbnailUrl?: string\n title?: string\n variant: AttachmentThumbnailVariant\n /** Extra props passed to MediaPlayer when source is video or audio. */\n mediaPlayerProps?: Partial<\n Pick<\n MediaPlayerProps,\n 'autoPlay' | 'loop' | 'muted' | 'controls' | 'onContainerClick'\n >\n >\n /**\n * When true (Visitor unlocked image/document), use aspect-video + object-contain fade-in.\n */\n containedImage?: boolean\n}\n\nconst placeholderIconClass = (variant: AttachmentThumbnailVariant) =>\n variant === 'dark' ? 'size-12 text-white/20' : 'size-12 text-black/20'\n\nconst posterShellClass = (variant: AttachmentThumbnailVariant) =>\n variant === 'dark'\n ? 'aspect-video overflow-hidden bg-white/10'\n : 'aspect-video overflow-hidden bg-black/5'\n\n/**\n * Renders the media preview area for attachment cards (LockedAttachment, MediaMessage).\n * Overlays (dim, lock, eye toggle) are composed by the parent.\n */\nconst AttachmentThumbnail: React.FC<AttachmentThumbnailProps> = ({\n mimeType,\n sourceUrl,\n thumbnailUrl,\n title,\n variant,\n mediaPlayerProps,\n containedImage = false,\n}) => {\n const sourceType = getSourceType(mimeType)\n const [sourceReady, setSourceReady] = useState(false)\n const optimizedSourceUrl = optimizeMessagingAttachmentUrl(sourceUrl)\n const optimizedThumbnailUrl = optimizeMessagingAttachmentUrl(thumbnailUrl)\n\n if (sourceUrl && (sourceType === 'video' || sourceType === 'audio')) {\n return (\n <MediaPlayer\n source={sourceUrl}\n mimeType={mimeType}\n poster={optimizedThumbnailUrl}\n controls\n {...mediaPlayerProps}\n />\n )\n }\n\n if (sourceUrl && sourceType === 'image') {\n if (containedImage) {\n return (\n <div className=\"relative aspect-video overflow-hidden bg-black/5\">\n <img\n src={optimizedSourceUrl ?? sourceUrl}\n alt={title ?? ''}\n className={`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}\n draggable={false}\n onLoad={() => setSourceReady(true)}\n />\n </div>\n )\n }\n return (\n <img\n src={optimizedSourceUrl ?? sourceUrl}\n alt={title ?? ''}\n className=\"block w-full\"\n draggable={false}\n />\n )\n }\n\n if (sourceUrl && sourceType === 'document') {\n if (thumbnailUrl) {\n if (containedImage) {\n return (\n <div className=\"relative aspect-video overflow-hidden bg-black/5\">\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt={title ?? ''}\n className={`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}\n draggable={false}\n onLoad={() => setSourceReady(true)}\n />\n </div>\n )\n }\n return (\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt=\"\"\n className=\"block w-full\"\n draggable={false}\n />\n )\n }\n return (\n <div\n className={`flex aspect-video w-full items-center justify-center ${variant === 'dark' ? 'bg-white/10' : 'bg-black/5'}`}\n >\n {renderTypeIcon(mimeType, {\n className: placeholderIconClass(variant),\n weight: 'regular',\n })}\n </div>\n )\n }\n\n // Poster-only or empty (no sourceUrl)\n if (thumbnailUrl) {\n return (\n <div className={`relative ${posterShellClass(variant)}`}>\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt={title ?? ''}\n draggable={false}\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n </div>\n )\n }\n\n return (\n <div\n className={`flex aspect-video w-full items-center justify-center ${variant === 'dark' ? 'bg-white/10' : 'bg-black/5'}`}\n >\n {renderTypeIcon(mimeType, {\n className: placeholderIconClass(variant),\n weight: 'regular',\n })}\n </div>\n )\n}\n\nexport default AttachmentThumbnail\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport { renderTypeIcon } from './utils/icons'\n\nexport { default as AttachmentThumbnail } from './Thumbnail'\nexport type {\n AttachmentThumbnailProps,\n AttachmentThumbnailVariant,\n} from './Thumbnail'\nexport type { MediaPlayerProps } from './MediaPlayer'\nexport { renderTypeIcon } from './utils/icons'\nexport {\n getSourceType,\n type AttachmentSourceType,\n type DocumentIconType,\n} from './utils/mimeType'\n\nexport interface AttachmentCardProps {\n variant: 'light' | 'dark'\n thumbnail: React.ReactNode\n title?: string\n placeholderTitle?: string\n mimeType: string\n detail?: string\n statusBadge?: React.ReactNode\n action?: React.ReactNode\n topLeft?: React.ReactNode\n topRight?: React.ReactNode\n rootRef?: React.Ref<HTMLDivElement>\n 'data-testid'?: string\n}\n\nconst AttachmentCard: React.FC<AttachmentCardProps> = ({\n variant,\n thumbnail,\n title,\n placeholderTitle = 'Attachment title',\n mimeType,\n detail,\n statusBadge,\n action,\n topLeft,\n topRight,\n rootRef,\n 'data-testid': dataTestId,\n}) => {\n const isDark = variant === 'dark'\n const displayTitle = isDark ? (title ?? placeholderTitle) : (title ?? '')\n const titleDimmed = isDark && !title\n\n return (\n <div\n ref={rootRef}\n data-testid={dataTestId}\n className={classNames(\n 'relative w-[280px] select-none overflow-hidden rounded-[24px] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]',\n isDark ? 'bg-[#1e2330]' : 'bg-white'\n )}\n >\n {topLeft ? (\n <div className=\"pointer-events-auto absolute left-3 top-3 z-50\">\n {topLeft}\n </div>\n ) : null}\n {topRight ? (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-50\">\n {topRight}\n </div>\n ) : null}\n\n {thumbnail}\n\n <div className=\"px-4 pb-3 pt-3\">\n {displayTitle.trim() !== '' && (\n <p\n className={classNames('mb-0.5 truncate text-base font-medium', {\n 'text-black': !isDark,\n 'text-white/30': isDark && titleDimmed,\n 'text-white': isDark && !titleDimmed,\n })}\n >\n {displayTitle}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-1\">\n {renderTypeIcon(mimeType, {\n className: classNames(\n 'size-5 shrink-0',\n isDark ? 'text-white/55' : 'text-black/55'\n ),\n weight: 'regular',\n })}\n\n {detail != null && detail !== '' && (\n <span\n className={classNames(\n 'text-xs font-medium',\n isDark ? 'text-white/55' : 'text-black/55'\n )}\n >\n {detail}\n </span>\n )}\n\n {statusBadge}\n </div>\n\n {action}\n </div>\n </div>\n )\n}\n\nexport default AttachmentCard\n"],"mappings":"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"}