@linktr.ee/messaging-react 2.1.0 → 2.2.0-rc-1778753733

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 (58) hide show
  1. package/dist/{Card-CsJvUF_b.js → Card-BdTueeyk.js} +2 -2
  2. package/dist/{Card-CsJvUF_b.js.map → Card-BdTueeyk.js.map} +1 -1
  3. package/dist/{Card-DlMSDSdm.js → Card-ChR37pLZ.js} +2 -2
  4. package/dist/{Card-DlMSDSdm.js.map → Card-ChR37pLZ.js.map} +1 -1
  5. package/dist/{Card-CFFNq49v.js → Card-EKxCn56j.js} +3 -3
  6. package/dist/{Card-CFFNq49v.js.map → Card-EKxCn56j.js.map} +1 -1
  7. package/dist/{LockedThumbnail-DpJx169C.js → LockedThumbnail-B16qP3eH.js} +2 -2
  8. package/dist/{LockedThumbnail-DpJx169C.js.map → LockedThumbnail-B16qP3eH.js.map} +1 -1
  9. package/dist/index-Dn7BC9xK.js +4748 -0
  10. package/dist/index-Dn7BC9xK.js.map +1 -0
  11. package/dist/index.d.ts +591 -25
  12. package/dist/index.js +24 -19
  13. package/package.json +1 -1
  14. package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +841 -0
  15. package/src/components/LinkAttachment/LinkAttachment.stories.tsx +7 -92
  16. package/src/components/LinkAttachment/LinkAttachment.test.tsx +69 -0
  17. package/src/components/LinkAttachment/components/Received/Card.tsx +10 -30
  18. package/src/components/LinkAttachment/components/_shared/CardShell.tsx +5 -1
  19. package/src/components/LinkAttachment/index.tsx +24 -50
  20. package/src/components/LinkAttachment/types.ts +12 -5
  21. package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +203 -0
  22. package/src/components/MessageAttachment/Audio/index.tsx +189 -0
  23. package/src/components/MessageAttachment/File/FileAttachment.stories.tsx +352 -0
  24. package/src/components/MessageAttachment/File/index.tsx +240 -0
  25. package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +288 -0
  26. package/src/components/MessageAttachment/Image/index.tsx +257 -0
  27. package/src/components/MessageAttachment/MessageAttachment.test.tsx +783 -0
  28. package/src/components/MessageAttachment/Pdf/PdfAttachment.stories.tsx +292 -0
  29. package/src/components/MessageAttachment/Pdf/index.tsx +228 -0
  30. package/src/components/MessageAttachment/Video/VideoAttachment.stories.tsx +272 -0
  31. package/src/components/MessageAttachment/Video/index.tsx +281 -0
  32. package/src/components/MessageAttachment/_shared/Bubble.tsx +173 -0
  33. package/src/components/MessageAttachment/_shared/CompactDocumentRow.tsx +152 -0
  34. package/src/components/MessageAttachment/_shared/DismissButton.tsx +39 -0
  35. package/src/components/MessageAttachment/_shared/DownloadAction.tsx +175 -0
  36. package/src/components/MessageAttachment/_shared/ImageViewer.tsx +314 -0
  37. package/src/components/MessageAttachment/_shared/MediaStackGrid.tsx +139 -0
  38. package/src/components/MessageAttachment/_shared/PdfViewer.tsx +100 -0
  39. package/src/components/MessageAttachment/_shared/VideoViewer.tsx +171 -0
  40. package/src/components/MessageAttachment/_shared/ViewerShell.tsx +159 -0
  41. package/src/components/MessageAttachment/_shared/fileMeta.test.ts +82 -0
  42. package/src/components/MessageAttachment/_shared/fileMeta.ts +95 -0
  43. package/src/components/MessageAttachment/_shared/triggerDownload.ts +54 -0
  44. package/src/components/MessageAttachment/_shared/useViewer.ts +53 -0
  45. package/src/components/MessageAttachment/index.tsx +149 -0
  46. package/src/components/MessageAttachment/stories/StoryTable.tsx +72 -0
  47. package/src/components/MessageAttachment/types.ts +178 -0
  48. package/src/index.ts +32 -0
  49. package/dist/Card-D32U6KfZ.js +0 -85
  50. package/dist/Card-D32U6KfZ.js.map +0 -1
  51. package/dist/Card-DlSSJPip.js +0 -60
  52. package/dist/Card-DlSSJPip.js.map +0 -1
  53. package/dist/Card-zGbhRBwv.js +0 -48
  54. package/dist/Card-zGbhRBwv.js.map +0 -1
  55. package/dist/CardThumbnail-DTBuRQHF.js +0 -239
  56. package/dist/CardThumbnail-DTBuRQHF.js.map +0 -1
  57. package/dist/index-DfcRe-Hj.js +0 -3103
  58. package/dist/index-DfcRe-Hj.js.map +0 -1
@@ -0,0 +1,149 @@
1
+ import AudioAttachment, {
2
+ type AudioComposerProps,
3
+ type AudioReceivedProps,
4
+ type AudioSentProps,
5
+ } from './Audio'
6
+ import FileAttachment, {
7
+ type FileComposerProps,
8
+ type FileReceivedProps,
9
+ type FileSentProps,
10
+ } from './File'
11
+ import ImageAttachment, {
12
+ type ImageComposerProps,
13
+ type ImageReceivedProps,
14
+ type ImageSentProps,
15
+ } from './Image'
16
+ import PdfAttachment, {
17
+ type PdfComposerProps,
18
+ type PdfReceivedProps,
19
+ type PdfSentProps,
20
+ } from './Pdf'
21
+ import VideoAttachment, {
22
+ type VideoComposerProps,
23
+ type VideoReceivedProps,
24
+ type VideoSentProps,
25
+ } from './Video'
26
+
27
+ /**
28
+ * Per-MIME chat attachment cards used inside the messaging stream.
29
+ *
30
+ * Each subnamespace (`Image`, `Video`, `Audio`, `Pdf`, `File`) exposes
31
+ * three states (`Composer`, `Sent`, `Received`) that map to the
32
+ * sender / recipient sides of a chat:
33
+ *
34
+ * - `Composer` — sender draft state with a dismiss `×` affordance.
35
+ * - `Sent` — sender-side state shown after posting.
36
+ * - `Received` — recipient-side state.
37
+ *
38
+ * Sent / Received variants accept an optional `text` prop that renders
39
+ * inside the same bubble as the attachment — mirroring the mobile chat
40
+ * where a sender can pair a caption with an attachment ('Here is the
41
+ * file', 'Here is the image'). Composer prop types intentionally omit
42
+ * `text` (and `items` / `groupPosition`) so the type system enforces
43
+ * the same contract: captions live in the message-input textarea, not
44
+ * inside the draft attachment preview.
45
+ *
46
+ * Stacked attachments — every type except `Composer` accepts an
47
+ * `items={[…]}` prop on Sent / Received that takes precedence over
48
+ * the singular `src`/`filename`/etc. inputs:
49
+ *
50
+ * - Image / Video render a 1 / 2 / 3 / 4-tile grid (5+ collapse
51
+ * into a `+N` overflow tile). Activating any tile opens the
52
+ * built-in `ImageViewer` / `VideoViewer` at that index, with
53
+ * arrow-key navigation between siblings.
54
+ * - Pdf / Audio / File render their compact rows / players in a
55
+ * vertical stack with an 8px gap between rows. Each row keeps
56
+ * its own activation target — Pdf opens the viewer at that
57
+ * index, Audio gets its own native player, and File downloads
58
+ * the asset for that row.
59
+ *
60
+ * The Composer surface accepts only a single attachment at a time,
61
+ * so its props omit `items` / `text`.
62
+ *
63
+ * Image / Video Composer renders bare — just the media with rounded
64
+ * corners and a dismiss overlay, no surrounding bubble — so the draft
65
+ * preview reads as an in-progress attachment instead of a sent
66
+ * message. Sent / Received wrap the media in the shared bubble chrome.
67
+ *
68
+ * Image / Video / Pdf are interactive in every state (Composer, Sent,
69
+ * Received) — clicks open the corresponding native viewer with built-in
70
+ * download. File rows download the asset directly. Audio renders a
71
+ * native `<audio controls>` player which exposes its own download in
72
+ * the kebab menu, so we don't render a separate Download affordance.
73
+ *
74
+ * # Lazy-loading defaults
75
+ *
76
+ * Media attachments bake in network-friendly defaults for long chat
77
+ * histories and expose explicit escape hatches when a caller knows
78
+ * better (e.g. a hero attachment that's already on-screen):
79
+ *
80
+ * - **Image** — every `<img>` rendered on the bubble surface
81
+ * (Composer thumbnail, single + stacked tiles, `+N` overflow)
82
+ * ships with `loading="lazy" decoding="async"`. Override via the
83
+ * attachment's `loading` prop (or `ImageItem.loading` for per-tile
84
+ * control). The opened `ImageViewer` always eager-loads the
85
+ * active image so it appears immediately.
86
+ * - **Video** — the `<video>` rendered inside the `VideoViewer`
87
+ * defaults to `preload="none"`, so no video bytes are fetched
88
+ * until the user opens the viewer. Poster `<img>` thumbnails on
89
+ * the bubble surface are `loading="lazy" decoding="async"`.
90
+ * Override via the attachment's `preload` prop (or
91
+ * `VideoItem.preload` per item). Once the viewer is open the
92
+ * active item bumps to `preload="metadata"` so duration / the
93
+ * first frame appear immediately.
94
+ * - **Audio** — a single audio bubble defaults to
95
+ * `preload="metadata"` so the native player surfaces duration
96
+ * immediately. A stacked audio bubble (`items.length > 1`)
97
+ * defaults to `preload="none"` for every player so a thread of
98
+ * voice memos doesn't fan out N parallel metadata requests on
99
+ * first paint. Override via the attachment's `preload` prop
100
+ * (or `AudioItem.preload` per track).
101
+ * - **Pdf** / **File** — no inline media element to throttle; the
102
+ * bubble renders compact rows that download / open lazily on
103
+ * activation. No `loading` / `preload` props on these surfaces.
104
+ */
105
+ const MessageAttachment = {
106
+ Image: ImageAttachment,
107
+ Video: VideoAttachment,
108
+ Audio: AudioAttachment,
109
+ Pdf: PdfAttachment,
110
+ File: FileAttachment,
111
+ }
112
+
113
+ export default MessageAttachment
114
+
115
+ export type {
116
+ ImageComposerProps,
117
+ ImageSentProps,
118
+ ImageReceivedProps,
119
+ VideoComposerProps,
120
+ VideoSentProps,
121
+ VideoReceivedProps,
122
+ AudioComposerProps,
123
+ AudioSentProps,
124
+ AudioReceivedProps,
125
+ PdfComposerProps,
126
+ PdfSentProps,
127
+ PdfReceivedProps,
128
+ FileComposerProps,
129
+ FileSentProps,
130
+ FileReceivedProps,
131
+ }
132
+ export type {
133
+ ImageItem,
134
+ ImageLoadingMode,
135
+ VideoItem,
136
+ PdfItem,
137
+ AudioItem,
138
+ FileItem,
139
+ BubbleGroupPosition,
140
+ MediaPreloadMode,
141
+ MessageAttachmentBaseProps,
142
+ MessageAttachmentState,
143
+ } from './types'
144
+ export { bubbleGroupPositionFromStream } from './types'
145
+ export {
146
+ formatFileSize,
147
+ getFileExtensionLabel,
148
+ buildCompactMetaLabel,
149
+ } from './_shared/fileMeta'
@@ -0,0 +1,72 @@
1
+ import React from 'react'
2
+
3
+ /**
4
+ * Shared layout used by every per-type `MessageAttachment` story —
5
+ * three columns (Composer / Sent / Received) on a tinted page so the
6
+ * dark sender bubbles read against a softer surface and the light
7
+ * received bubbles still pop. Mirrors the layout the existing
8
+ * `LinkAttachment` and `MediaMessage` stories use.
9
+ */
10
+ export const StoryPage: React.FC<{ children: React.ReactNode }> = ({
11
+ children,
12
+ }) => (
13
+ <div className="min-h-screen w-full bg-[#F9F7F4] p-12">{children}</div>
14
+ )
15
+
16
+ export const StoryHeading: React.FC<{
17
+ title: string
18
+ description?: string
19
+ }> = ({ title, description }) => (
20
+ <div className="mb-6">
21
+ <h2 className="text-base font-semibold text-black/80">{title}</h2>
22
+ {description ? (
23
+ <p className="mt-1 max-w-prose text-sm text-black/55">{description}</p>
24
+ ) : null}
25
+ </div>
26
+ )
27
+
28
+ export const ColumnHeader: React.FC<{ children: React.ReactNode }> = ({
29
+ children,
30
+ }) => (
31
+ <p className="mb-3 text-xs font-medium uppercase tracking-wide text-black/40">
32
+ {children}
33
+ </p>
34
+ )
35
+
36
+ export interface StoryRowProps {
37
+ label: string
38
+ /** Omit `composer` to leave the column blank — used by stacked
39
+ * Image/Video rows since the composer surface only ever shows a
40
+ * single attachment (no stacked previews in the composer). */
41
+ composer?: React.ReactNode
42
+ sent: React.ReactNode
43
+ received: React.ReactNode
44
+ }
45
+
46
+ export const StoryRow: React.FC<StoryRowProps> = ({
47
+ label,
48
+ composer,
49
+ sent,
50
+ received,
51
+ }) => (
52
+ <div className="grid grid-cols-[160px_minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)] items-start gap-6 py-4">
53
+ <p className="text-xs font-medium text-black/55">{label}</p>
54
+ <div>{composer ?? null}</div>
55
+ <div>{sent}</div>
56
+ <div>{received}</div>
57
+ </div>
58
+ )
59
+
60
+ export const StoryGrid: React.FC<{ children: React.ReactNode }> = ({
61
+ children,
62
+ }) => (
63
+ <div className="space-y-3 divide-y divide-black/5">
64
+ <div className="grid grid-cols-[160px_minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)] gap-6">
65
+ <span />
66
+ <ColumnHeader>Composer</ColumnHeader>
67
+ <ColumnHeader>Sent</ColumnHeader>
68
+ <ColumnHeader>Received</ColumnHeader>
69
+ </div>
70
+ {children}
71
+ </div>
72
+ )
@@ -0,0 +1,178 @@
1
+ import type React from 'react'
2
+
3
+ /**
4
+ * Three messaging states a `MessageAttachment.*` card can render in,
5
+ * mirroring the LinkAttachment / LockedAttachment families.
6
+ */
7
+ export type MessageAttachmentState = 'composer' | 'sent' | 'received'
8
+
9
+ /**
10
+ * Visual variant of the surrounding `Bubble` chrome. Sender-side bubbles
11
+ * (`composer`, `sent`) get the dark treatment; recipient-side bubbles
12
+ * (`received`) get the light treatment.
13
+ */
14
+ export type BubbleVariant = 'dark' | 'light'
15
+
16
+ /** Maps a state to the bubble chrome it should render with. */
17
+ export const bubbleVariantForState = (
18
+ state: MessageAttachmentState
19
+ ): BubbleVariant => (state === 'received' ? 'light' : 'dark')
20
+
21
+ /**
22
+ * Position of a bubble inside a same-author run, mirroring the grouping
23
+ * stream-chat-react applies to consecutive messages from the same user:
24
+ *
25
+ * - `'single'` — standalone message, every corner fully rounded.
26
+ * - `'first'` — first in a 2+ message run; the corner facing the
27
+ * next bubble in the run is flattened.
28
+ * - `'middle'` — both the corner facing the previous bubble and the
29
+ * corner facing the next bubble are flattened.
30
+ * - `'end'` — last in a 2+ message run; the corner facing the
31
+ * previous bubble is flattened (this is also the
32
+ * bubble the avatar attaches to on the receiver side).
33
+ *
34
+ * "Facing" is determined by the bubble's side: sender-aligned bubbles
35
+ * cluster against their right edge, receiver-aligned bubbles against
36
+ * their left edge.
37
+ */
38
+ export type BubbleGroupPosition = 'single' | 'first' | 'middle' | 'end'
39
+
40
+ /**
41
+ * Convenience adapter that derives the `BubbleGroupPosition` from the
42
+ * three booleans `stream-chat-react` forwards into the `Message` HOC.
43
+ * Drop-in for `useMessageContext()` consumers:
44
+ *
45
+ * ```tsx
46
+ * const { firstOfGroup, endOfGroup, groupedByUser } = useMessageContext()
47
+ * <MessageAttachment.Image.Sent
48
+ * {...props}
49
+ * groupPosition={bubbleGroupPositionFromStream({
50
+ * firstOfGroup, endOfGroup, groupedByUser,
51
+ * })}
52
+ * />
53
+ * ```
54
+ */
55
+ export const bubbleGroupPositionFromStream = ({
56
+ firstOfGroup,
57
+ endOfGroup,
58
+ groupedByUser,
59
+ }: {
60
+ firstOfGroup?: boolean
61
+ endOfGroup?: boolean
62
+ groupedByUser?: boolean
63
+ }): BubbleGroupPosition => {
64
+ if (!groupedByUser) return 'single'
65
+ if (firstOfGroup && endOfGroup) return 'single'
66
+ if (firstOfGroup) return 'first'
67
+ if (endOfGroup) return 'end'
68
+ return 'middle'
69
+ }
70
+
71
+ /** Loading hint forwarded to the underlying `<img>` element. */
72
+ export type ImageLoadingMode = 'lazy' | 'eager'
73
+
74
+ /** Preload hint forwarded to the underlying `<video>` / `<audio>` element. */
75
+ export type MediaPreloadMode = 'none' | 'metadata' | 'auto'
76
+
77
+ /** A single image inside an Image attachment (single or stacked). */
78
+ export interface ImageItem {
79
+ src: string
80
+ alt?: string
81
+ /** Optional width/height hint — preserved aspect on cover-fit. */
82
+ width?: number
83
+ height?: number
84
+ /**
85
+ * Per-tile native lazy-load override. Defaults to the parent
86
+ * attachment's `loading` value (which itself defaults to `'lazy'`).
87
+ * Pass `'eager'` for above-the-fold hero tiles.
88
+ */
89
+ loading?: ImageLoadingMode
90
+ }
91
+
92
+ /** A single video inside a Video attachment (single or stacked). */
93
+ export interface VideoItem {
94
+ src: string
95
+ /** Poster / thumbnail rendered before playback starts. */
96
+ poster?: string
97
+ /** Optional MIME type (e.g. `video/mp4`). */
98
+ mimeType?: string
99
+ /**
100
+ * Per-item override for the `<video preload>` hint. Defaults to the
101
+ * parent attachment's `preload` value (which itself defaults to
102
+ * `'none'` so the poster carries the visual weight and no video
103
+ * bytes are fetched until playback).
104
+ */
105
+ preload?: MediaPreloadMode
106
+ }
107
+
108
+ /** A single PDF inside a Pdf attachment (single or stacked). */
109
+ export interface PdfItem {
110
+ src: string
111
+ /** Filename — drives the title + the meta line + the viewer toolbar. */
112
+ filename?: string
113
+ /** File size in bytes — formatted into the `EXT · SIZE` meta line. */
114
+ fileSize?: number
115
+ /** Override displayed title. Defaults to `filename`. */
116
+ title?: string
117
+ }
118
+
119
+ /** A single audio file inside an Audio attachment (single or stacked). */
120
+ export interface AudioItem {
121
+ src: string
122
+ /** MIME type hint — typed onto the inline `<source>` element. */
123
+ mimeType?: string
124
+ /**
125
+ * Filename — used as the download default name (consumed by the
126
+ * native player's kebab menu).
127
+ */
128
+ filename?: string
129
+ /**
130
+ * Per-track override for the `<audio preload>` hint. Defaults to
131
+ * the parent attachment's `preload` value, which itself defaults
132
+ * to `'metadata'` for a single audio (so the native player can
133
+ * surface duration immediately) and `'none'` for a stacked thread
134
+ * (so a fan-out of metadata requests doesn't happen on first paint).
135
+ */
136
+ preload?: MediaPreloadMode
137
+ }
138
+
139
+ /** A single generic file inside a File attachment (single or stacked). */
140
+ export interface FileItem {
141
+ src: string
142
+ /** Filename — drives the title + the meta line + the download default name. */
143
+ filename?: string
144
+ /** File size in bytes — formatted into the `EXT · SIZE` meta line. */
145
+ fileSize?: number
146
+ /** MIME type — drives the type icon. Defaults to `application/octet-stream`. */
147
+ mimeType?: string
148
+ /** Override displayed title. Defaults to `filename`. */
149
+ title?: string
150
+ }
151
+
152
+ /** Shared props every `MessageAttachment.*.{Composer|Sent|Received}` accepts. */
153
+ export interface MessageAttachmentBaseProps {
154
+ /**
155
+ * Optional text rendered below the attachment inside the same bubble.
156
+ * Mirrors the chat behavior in the mobile screenshots — `'Here is the
157
+ * file'`, `'Here is the image'`, etc.
158
+ */
159
+ text?: React.ReactNode
160
+ /**
161
+ * Position of this attachment inside a same-author message run —
162
+ * mirrors the corner-flattening stream-chat-react applies to text
163
+ * bubbles in a clustered conversation. Defaults to `'single'`, so
164
+ * standalone usage keeps every corner rounded. Pass the value
165
+ * derived from `bubbleGroupPositionFromStream({ firstOfGroup,
166
+ * endOfGroup, groupedByUser })` when rendering inside a real
167
+ * `CustomMessage` thread to make the attachment visually merge
168
+ * with adjacent bubbles. Image / Video Composer ignore this — the
169
+ * draft preview renders bare without the surrounding bubble.
170
+ */
171
+ groupPosition?: BubbleGroupPosition
172
+ }
173
+
174
+ /** Shared props for the `Composer` state of every attachment type. */
175
+ export interface ComposerExtras {
176
+ /** Renders a dismiss `×` button overlaid on the attachment. */
177
+ onDismiss?: () => void
178
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export { ChannelView } from './components/ChannelView'
8
8
  export { default as ActionButton } from './components/ActionButton'
9
9
  export { default as LockedAttachment } from './components/LockedAttachment'
10
10
  export { default as LinkAttachment } from './components/LinkAttachment'
11
+ export { default as MessageAttachment } from './components/MessageAttachment'
11
12
  export { Avatar } from './components/Avatar'
12
13
  export { FaqList } from './components/FaqList'
13
14
  export { FaqListItem } from './components/FaqList/FaqListItem'
@@ -71,6 +72,37 @@ export type {
71
72
  LinkAttachmentCta,
72
73
  LinkAttachmentLayout,
73
74
  } from './components/LinkAttachment'
75
+ export type {
76
+ ImageComposerProps as MessageAttachmentImageComposerProps,
77
+ ImageSentProps as MessageAttachmentImageSentProps,
78
+ ImageReceivedProps as MessageAttachmentImageReceivedProps,
79
+ VideoComposerProps as MessageAttachmentVideoComposerProps,
80
+ VideoSentProps as MessageAttachmentVideoSentProps,
81
+ VideoReceivedProps as MessageAttachmentVideoReceivedProps,
82
+ AudioComposerProps as MessageAttachmentAudioComposerProps,
83
+ AudioSentProps as MessageAttachmentAudioSentProps,
84
+ AudioReceivedProps as MessageAttachmentAudioReceivedProps,
85
+ PdfComposerProps as MessageAttachmentPdfComposerProps,
86
+ PdfSentProps as MessageAttachmentPdfSentProps,
87
+ PdfReceivedProps as MessageAttachmentPdfReceivedProps,
88
+ FileComposerProps as MessageAttachmentFileComposerProps,
89
+ FileSentProps as MessageAttachmentFileSentProps,
90
+ FileReceivedProps as MessageAttachmentFileReceivedProps,
91
+ ImageItem as MessageAttachmentImageItem,
92
+ VideoItem as MessageAttachmentVideoItem,
93
+ PdfItem as MessageAttachmentPdfItem,
94
+ AudioItem as MessageAttachmentAudioItem,
95
+ FileItem as MessageAttachmentFileItem,
96
+ BubbleGroupPosition as MessageAttachmentGroupPosition,
97
+ MessageAttachmentBaseProps,
98
+ MessageAttachmentState,
99
+ } from './components/MessageAttachment'
100
+ export {
101
+ formatFileSize,
102
+ getFileExtensionLabel,
103
+ buildCompactMetaLabel,
104
+ bubbleGroupPositionFromStream as messageAttachmentGroupPositionFromStream,
105
+ } from './components/MessageAttachment'
74
106
  export type { CustomMessageRegistry } from './components/CustomMessage/context'
75
107
  export type { AttachmentSourceType } from './components/AttachmentCard/utils/mimeType'
76
108
  export type { Faq, FaqListProps } from './components/FaqList'
@@ -1,85 +0,0 @@
1
- import { jsx as a, jsxs as d } from "react/jsx-runtime";
2
- import { XIcon as x, PencilSimpleIcon as N } from "@phosphor-icons/react";
3
- import { i as k, C as o, A as w, a as c, b as A } from "./CardThumbnail-DTBuRQHF.js";
4
- const B = ({
5
- title: e,
6
- placeholderTitle: h,
7
- description: m,
8
- url: u,
9
- mimeType: i,
10
- thumbnailUrl: b,
11
- sourceUrl: s,
12
- layout: f = "featured",
13
- appIcon: v,
14
- cta: g,
15
- onDismiss: r,
16
- onEditClick: n
17
- }) => {
18
- const l = f === "classic", p = k(i, s), t = r ? /* @__PURE__ */ a(
19
- "button",
20
- {
21
- type: "button",
22
- onClick: r,
23
- "aria-label": "Dismiss attachment",
24
- className: "flex size-6 items-center justify-center rounded-full bg-[#121110] text-white",
25
- children: /* @__PURE__ */ a(x, { className: "size-3", weight: "bold" })
26
- }
27
- ) : void 0, C = n ? /* @__PURE__ */ a(
28
- "button",
29
- {
30
- type: "button",
31
- onClick: n,
32
- "aria-label": "Edit attachment",
33
- className: "flex size-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15",
34
- children: /* @__PURE__ */ a(N, { className: "size-5", weight: "regular" })
35
- }
36
- ) : void 0;
37
- return p ? /* @__PURE__ */ a(o, { variant: "dark", bgClassName: w, children: /* @__PURE__ */ d("div", { className: "flex items-center gap-2 pr-3", children: [
38
- /* @__PURE__ */ a("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ a(
39
- c,
40
- {
41
- variant: "dark",
42
- sourceUrl: s,
43
- title: e,
44
- mimeType: i
45
- }
46
- ) }),
47
- t && /* @__PURE__ */ a("div", { className: "shrink-0", children: t })
48
- ] }) }) : /* @__PURE__ */ d(
49
- o,
50
- {
51
- variant: "dark",
52
- topRight: l ? t : void 0,
53
- children: [
54
- !l && /* @__PURE__ */ a(
55
- c,
56
- {
57
- variant: "dark",
58
- thumbnailUrl: b,
59
- sourceUrl: s,
60
- title: e,
61
- mimeType: i,
62
- topRight: t
63
- }
64
- ),
65
- /* @__PURE__ */ a(
66
- A,
67
- {
68
- variant: "dark",
69
- title: e,
70
- placeholderTitle: h,
71
- description: m,
72
- url: u,
73
- appIcon: v,
74
- cta: g,
75
- trailingAction: C
76
- }
77
- )
78
- ]
79
- }
80
- );
81
- };
82
- export {
83
- B as default
84
- };
85
- //# sourceMappingURL=Card-D32U6KfZ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Card-D32U6KfZ.js","sources":["../src/components/LinkAttachment/components/Composer/Card.tsx"],"sourcesContent":["import { PencilSimpleIcon, XIcon } from '@phosphor-icons/react'\nimport React from 'react'\n\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n} from '../_shared/CardThumbnail'\n\nexport interface ComposerCardProps extends LinkAttachmentBaseProps {\n /**\n * When provided, renders a dismiss X in the thumbnail corner. Called when\n * the composer clicks it to remove the attachment.\n */\n onDismiss?: () => void\n /**\n * When provided, renders a pencil button to the right of the description\n * that the composer can use to edit the attachment metadata.\n */\n onEditClick?: () => void\n}\n\n/**\n * The card the composer sees while drafting a link attachment.\n * Matches the Composer column of the messaging design system in Figma.\n */\nconst ComposerCard: React.FC<ComposerCardProps> = ({\n title,\n placeholderTitle,\n description,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n onDismiss,\n onEditClick,\n}) => {\n const isClassic = layout === 'classic'\n const isAudio = isPlayableAudio(mimeType, sourceUrl)\n const dismissButton = onDismiss ? (\n <button\n type=\"button\"\n onClick={onDismiss}\n aria-label=\"Dismiss attachment\"\n className=\"flex size-6 items-center justify-center rounded-full bg-[#121110] text-white\"\n >\n <XIcon className=\"size-3\" weight=\"bold\" />\n </button>\n ) : undefined\n\n const editButton = onEditClick ? (\n <button\n type=\"button\"\n onClick={onEditClick}\n aria-label=\"Edit attachment\"\n className=\"flex size-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15\"\n >\n <PencilSimpleIcon className=\"size-5\" weight=\"regular\" />\n </button>\n ) : undefined\n\n // Audio cards collapse to just the native player — render the dismiss\n // button as an inline sibling so it always has its own space and never\n // overlaps the audio control's volume/menu buttons.\n if (isAudio) {\n return (\n <CardShell variant=\"dark\" bgClassName={AUDIO_BG_CLASS}>\n <div className=\"flex items-center gap-2 pr-3\">\n <div className=\"min-w-0 flex-1\">\n <CardThumbnail\n variant=\"dark\"\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n />\n </div>\n {dismissButton && <div className=\"shrink-0\">{dismissButton}</div>}\n </div>\n </CardShell>\n )\n }\n\n return (\n <CardShell\n variant=\"dark\"\n topRight={isClassic ? dismissButton : undefined}\n >\n {!isClassic && (\n <CardThumbnail\n variant=\"dark\"\n thumbnailUrl={thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n topRight={dismissButton}\n />\n )}\n <CardBody\n variant=\"dark\"\n title={title}\n placeholderTitle={placeholderTitle}\n description={description}\n url={url}\n appIcon={appIcon}\n cta={cta}\n trailingAction={editButton}\n />\n </CardShell>\n )\n}\n\nexport default ComposerCard\n"],"names":["ComposerCard","title","placeholderTitle","description","url","mimeType","thumbnailUrl","sourceUrl","layout","appIcon","cta","onDismiss","onEditClick","isClassic","isAudio","isPlayableAudio","dismissButton","jsx","XIcon","editButton","PencilSimpleIcon","CardShell","AUDIO_BG_CLASS","jsxs","CardThumbnail","CardBody"],"mappings":";;;AA4BA,MAAMA,IAA4C,CAAC;AAAA,EACjD,OAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,aAAAC;AAAA,EACA,KAAAC;AAAA,EACA,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,EACA,QAAAC,IAAS;AAAA,EACT,SAAAC;AAAA,EACA,KAAAC;AAAA,EACA,WAAAC;AAAA,EACA,aAAAC;AACF,MAAM;AACJ,QAAMC,IAAYL,MAAW,WACvBM,IAAUC,EAAgBV,GAAUE,CAAS,GAC7CS,IAAgBL,IACpB,gBAAAM;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,SAASN;AAAA,MACT,cAAW;AAAA,MACX,WAAU;AAAA,MAEV,UAAA,gBAAAM,EAACC,GAAA,EAAM,WAAU,UAAS,QAAO,OAAA,CAAO;AAAA,IAAA;AAAA,EAAA,IAExC,QAEEC,IAAaP,IACjB,gBAAAK;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,SAASL;AAAA,MACT,cAAW;AAAA,MACX,WAAU;AAAA,MAEV,UAAA,gBAAAK,EAACG,GAAA,EAAiB,WAAU,UAAS,QAAO,UAAA,CAAU;AAAA,IAAA;AAAA,EAAA,IAEtD;AAKJ,SAAIN,IAEA,gBAAAG,EAACI,KAAU,SAAQ,QAAO,aAAaC,GACrC,UAAA,gBAAAC,EAAC,OAAA,EAAI,WAAU,gCACb,UAAA;AAAA,IAAA,gBAAAN,EAAC,OAAA,EAAI,WAAU,kBACb,UAAA,gBAAAA;AAAA,MAACO;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,WAAAjB;AAAA,QACA,OAAAN;AAAA,QACA,UAAAI;AAAA,MAAA;AAAA,IAAA,GAEJ;AAAA,IACCW,KAAiB,gBAAAC,EAAC,OAAA,EAAI,WAAU,YAAY,UAAAD,EAAA,CAAc;AAAA,EAAA,EAAA,CAC7D,EAAA,CACF,IAKF,gBAAAO;AAAA,IAACF;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,UAAUR,IAAYG,IAAgB;AAAA,MAErC,UAAA;AAAA,QAAA,CAACH,KACA,gBAAAI;AAAA,UAACO;AAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,cAAAlB;AAAA,YACA,WAAAC;AAAA,YACA,OAAAN;AAAA,YACA,UAAAI;AAAA,YACA,UAAUW;AAAA,UAAA;AAAA,QAAA;AAAA,QAGd,gBAAAC;AAAA,UAACQ;AAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,OAAAxB;AAAA,YACA,kBAAAC;AAAA,YACA,aAAAC;AAAA,YACA,KAAAC;AAAA,YACA,SAAAK;AAAA,YACA,KAAAC;AAAA,YACA,gBAAgBS;AAAA,UAAA;AAAA,QAAA;AAAA,MAClB;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -1,60 +0,0 @@
1
- import { jsxs as p, jsx as t } from "react/jsx-runtime";
2
- import { c, n as x, i as A, A as k, C as B, a as O, b as P } from "./CardThumbnail-DTBuRQHF.js";
3
- const w = ({
4
- title: i,
5
- description: h,
6
- url: s,
7
- mimeType: l,
8
- thumbnailUrl: C,
9
- sourceUrl: n,
10
- layout: f = "featured",
11
- appIcon: v,
12
- cta: a,
13
- onClick: e
14
- }) => {
15
- const r = c(l, n), d = x(s), u = a == null && d != null && !r ? d : void 0, b = a == null && !r ? e : void 0, g = A(l, n) ? k : void 0, m = a && e ? {
16
- ...a,
17
- onClick: () => {
18
- var o;
19
- e(), (o = a.onClick) == null || o.call(a);
20
- }
21
- } : a;
22
- return /* @__PURE__ */ p(
23
- B,
24
- {
25
- variant: "light",
26
- href: u,
27
- onClick: b,
28
- ariaLabel: i ?? "Open attachment preview",
29
- bgClassName: g,
30
- "data-testid": "link-attachment",
31
- children: [
32
- f === "featured" && /* @__PURE__ */ t(
33
- O,
34
- {
35
- variant: "light",
36
- thumbnailUrl: C,
37
- sourceUrl: n,
38
- title: i,
39
- mimeType: l
40
- }
41
- ),
42
- /* @__PURE__ */ t(
43
- P,
44
- {
45
- variant: "light",
46
- title: i,
47
- description: h,
48
- url: s,
49
- appIcon: v,
50
- cta: m
51
- }
52
- )
53
- ]
54
- }
55
- );
56
- };
57
- export {
58
- w as default
59
- };
60
- //# sourceMappingURL=Card-DlSSJPip.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Card-DlSSJPip.js","sources":["../src/components/LinkAttachment/components/Received/Card.tsx"],"sourcesContent":["import React from 'react'\n\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n isPlayableMedia,\n} from '../_shared/CardThumbnail'\nimport { normalizeExternalHref } from '../_shared/normalizeExternalHref'\n\nexport interface ReceivedCardProps extends LinkAttachmentBaseProps {\n /**\n * Fired when the recipient activates the card. Behavior depends on how\n * the card is configured:\n * - **Link app with a CTA** (FAQ / Form): the CTA owns navigation;\n * `onClick` fires when the recipient taps the CTA itself, alongside\n * `cta.onClick` (use for analytics).\n * - **Link app with a URL** (Spotify / TikTok / generic link): the card\n * chrome is an `<a target=\"_blank\">` opening `url` — `onClick` fires\n * alongside the navigation (use for analytics).\n * - **Image / file / placeholder attachment**: the card has no URL, so\n * it renders as a button. `onClick` is the consumer's hook for\n * opening an image / file preview (lightbox).\n * - **Video / audio attachment**: the shell stays non-interactive so\n * the native media controls remain operable — `onClick` is ignored\n * in this configuration.\n */\n onClick?: () => void\n}\n\n/**\n * The card the recipient sees in chat for a link attachment. Matches the\n * Received column of the messaging design system in Figma.\n *\n * The chrome adapts to its content:\n * - Link previews / link apps render the light card body and either\n * navigate via `url` or surface a CTA button when `cta` is set.\n * - Image / file attachments render as media-only cards — the 180px\n * thumbnail (or type-icon placeholder) fills the card with no title /\n * description body. `onClick` fires when the recipient taps the card\n * so consumers can open an image preview / lightbox.\n */\nconst ReceivedCard: React.FC<ReceivedCardProps> = ({\n title,\n description,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n onClick,\n}) => {\n // The Received card is opened by either the CTA (FAQ / Form), the URL\n // (link previews / Spotify / TikTok), or — for media-only attachments —\n // by tapping the entire card chrome to open a preview. We hand the\n // anchor behavior off to CardShell only when there's no CTA so we don't\n // end up with nested anchors when a CTA is present.\n //\n // Video / audio attachments are an exception: wrapping the shell in\n // either a `<button>` or an `<a>` around `<video controls>` /\n // `<audio controls>` creates nested interactive elements, and clicks on\n // the native media controls can bubble up to fire the outer card\n // action (preview / link navigation). For those, we render a plain\n // non-interactive shell and let the media element own clicks — both\n // `shellHref` and `shellOnClick` are suppressed even when `url` is set.\n const isPlayingMedia = isPlayableMedia(mimeType, sourceUrl)\n // Normalize the URL so a bare hostname like `tr.ee/briemix` (used in\n // our own docs / stories) is treated as an external link instead of a\n // relative path. Returns `undefined` for empty / whitespace-only\n // values, so those fall through to the media-preview path instead of\n // producing an empty `href` on the shell anchor.\n const normalizedUrl = normalizeExternalHref(url)\n const shellHref =\n cta == null && normalizedUrl != null && !isPlayingMedia\n ? normalizedUrl\n : undefined\n const shellOnClick =\n cta == null && !isPlayingMedia ? onClick : undefined\n const audioBg = isPlayableAudio(mimeType, sourceUrl)\n ? AUDIO_BG_CLASS\n : undefined\n\n // When a CTA is set the shell isn't interactive — the CTA owns the\n // click target. Forward the card-level `onClick` to the CTA so\n // analytics / side-effect consumers still fire on activation while\n // preserving the CTA's own `onClick` callback.\n const wrappedCta =\n cta && onClick\n ? {\n ...cta,\n onClick: () => {\n onClick()\n cta.onClick?.()\n },\n }\n : cta\n\n return (\n <CardShell\n variant=\"light\"\n href={shellHref}\n onClick={shellOnClick}\n ariaLabel={title ?? 'Open attachment preview'}\n bgClassName={audioBg}\n data-testid=\"link-attachment\"\n >\n {layout === 'featured' && (\n <CardThumbnail\n variant=\"light\"\n thumbnailUrl={thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n />\n )}\n <CardBody\n variant=\"light\"\n title={title}\n description={description}\n url={url}\n appIcon={appIcon}\n cta={wrappedCta}\n />\n </CardShell>\n )\n}\n\nexport default ReceivedCard\n"],"names":["ReceivedCard","title","description","url","mimeType","thumbnailUrl","sourceUrl","layout","appIcon","cta","onClick","isPlayingMedia","isPlayableMedia","normalizedUrl","normalizeExternalHref","shellHref","shellOnClick","audioBg","isPlayableAudio","AUDIO_BG_CLASS","wrappedCta","_a","jsxs","CardShell","jsx","CardThumbnail","CardBody"],"mappings":";;AA4CA,MAAMA,IAA4C,CAAC;AAAA,EACjD,OAAAC;AAAA,EACA,aAAAC;AAAA,EACA,KAAAC;AAAA,EACA,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,EACA,QAAAC,IAAS;AAAA,EACT,SAAAC;AAAA,EACA,KAAAC;AAAA,EACA,SAAAC;AACF,MAAM;AAcJ,QAAMC,IAAiBC,EAAgBR,GAAUE,CAAS,GAMpDO,IAAgBC,EAAsBX,CAAG,GACzCY,IACJN,KAAO,QAAQI,KAAiB,QAAQ,CAACF,IACrCE,IACA,QACAG,IACJP,KAAO,QAAQ,CAACE,IAAiBD,IAAU,QACvCO,IAAUC,EAAgBd,GAAUE,CAAS,IAC/Ca,IACA,QAMEC,IACJX,KAAOC,IACH;AAAA,IACE,GAAGD;AAAA,IACH,SAAS,MAAM;;AACb,MAAAC,EAAA,IACAW,IAAAZ,EAAI,YAAJ,QAAAY,EAAA,KAAAZ;AAAA,IACF;AAAA,EAAA,IAEFA;AAEN,SACE,gBAAAa;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAMR;AAAA,MACN,SAASC;AAAA,MACT,WAAWf,KAAS;AAAA,MACpB,aAAagB;AAAA,MACb,eAAY;AAAA,MAEX,UAAA;AAAA,QAAAV,MAAW,cACV,gBAAAiB;AAAA,UAACC;AAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,cAAApB;AAAA,YACA,WAAAC;AAAA,YACA,OAAAL;AAAA,YACA,UAAAG;AAAA,UAAA;AAAA,QAAA;AAAA,QAGJ,gBAAAoB;AAAA,UAACE;AAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,OAAAzB;AAAA,YACA,aAAAC;AAAA,YACA,KAAAC;AAAA,YACA,SAAAK;AAAA,YACA,KAAKY;AAAA,UAAA;AAAA,QAAA;AAAA,MACP;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -1,48 +0,0 @@
1
- import { jsxs as f, jsx as s } from "react/jsx-runtime";
2
- import { C as b, A as m, a as u, b as v, i as A } from "./CardThumbnail-DTBuRQHF.js";
3
- const k = ({
4
- title: a,
5
- placeholderTitle: i,
6
- description: t,
7
- url: o,
8
- mimeType: r,
9
- thumbnailUrl: e,
10
- sourceUrl: d,
11
- layout: n = "featured",
12
- appIcon: C,
13
- cta: l
14
- }) => /* @__PURE__ */ f(
15
- b,
16
- {
17
- variant: "dark",
18
- bgClassName: A(r, d) ? m : void 0,
19
- children: [
20
- n === "featured" && /* @__PURE__ */ s(
21
- u,
22
- {
23
- variant: "dark",
24
- thumbnailUrl: e,
25
- sourceUrl: d,
26
- title: a,
27
- mimeType: r
28
- }
29
- ),
30
- /* @__PURE__ */ s(
31
- v,
32
- {
33
- variant: "dark",
34
- title: a,
35
- placeholderTitle: i,
36
- description: t,
37
- url: o,
38
- appIcon: C,
39
- cta: l
40
- }
41
- )
42
- ]
43
- }
44
- );
45
- export {
46
- k as default
47
- };
48
- //# sourceMappingURL=Card-zGbhRBwv.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Card-zGbhRBwv.js","sources":["../src/components/LinkAttachment/components/Sent/Card.tsx"],"sourcesContent":["import React from 'react'\n\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n} from '../_shared/CardThumbnail'\n\nexport interface SentCardProps extends LinkAttachmentBaseProps {}\n\n/**\n * The card the sender sees in chat after a link attachment has been posted.\n * Matches the Sent column of the messaging design system in Figma — same\n * dark chrome as the Composer card minus the dismiss / edit affordances.\n */\nconst SentCard: React.FC<SentCardProps> = ({\n title,\n placeholderTitle,\n description,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n}) => (\n <CardShell\n variant=\"dark\"\n bgClassName={\n isPlayableAudio(mimeType, sourceUrl) ? AUDIO_BG_CLASS : undefined\n }\n >\n {layout === 'featured' && (\n <CardThumbnail\n variant=\"dark\"\n thumbnailUrl={thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n />\n )}\n <CardBody\n variant=\"dark\"\n title={title}\n placeholderTitle={placeholderTitle}\n description={description}\n url={url}\n appIcon={appIcon}\n cta={cta}\n />\n </CardShell>\n)\n\nexport default SentCard\n"],"names":["SentCard","title","placeholderTitle","description","url","mimeType","thumbnailUrl","sourceUrl","layout","appIcon","cta","jsxs","CardShell","isPlayableAudio","AUDIO_BG_CLASS","jsx","CardThumbnail","CardBody"],"mappings":";;AAiBA,MAAMA,IAAoC,CAAC;AAAA,EACzC,OAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,aAAAC;AAAA,EACA,KAAAC;AAAA,EACA,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,EACA,QAAAC,IAAS;AAAA,EACT,SAAAC;AAAA,EACA,KAAAC;AACF,MACE,gBAAAC;AAAA,EAACC;AAAA,EAAA;AAAA,IACC,SAAQ;AAAA,IACR,aACEC,EAAgBR,GAAUE,CAAS,IAAIO,IAAiB;AAAA,IAGzD,UAAA;AAAA,MAAAN,MAAW,cACV,gBAAAO;AAAA,QAACC;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,cAAAV;AAAA,UACA,WAAAC;AAAA,UACA,OAAAN;AAAA,UACA,UAAAI;AAAA,QAAA;AAAA,MAAA;AAAA,MAGJ,gBAAAU;AAAA,QAACE;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,OAAAhB;AAAA,UACA,kBAAAC;AAAA,UACA,aAAAC;AAAA,UACA,KAAAC;AAAA,UACA,SAAAK;AAAA,UACA,KAAAC;AAAA,QAAA;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AACF;"}