@oxy.so/bloom 6.1.0 → 6.3.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 (230) hide show
  1. package/README.md +1 -1
  2. package/docs/adoption-matrix.mdx +4 -3
  3. package/docs/chat-composer.mdx +5 -1
  4. package/docs/cover-header.mdx +56 -0
  5. package/docs/message-media.mdx +1 -1
  6. package/lib/commonjs/ai-profile-card/AiProfileCard.js +186 -263
  7. package/lib/commonjs/ai-profile-card/AiProfileCard.js.map +1 -1
  8. package/lib/commonjs/chat-composer/SuggestionList.js +3 -1
  9. package/lib/commonjs/chat-composer/SuggestionList.js.map +1 -1
  10. package/lib/commonjs/chat-composer/SuggestionList.web.js +3 -1
  11. package/lib/commonjs/chat-composer/SuggestionList.web.js.map +1 -1
  12. package/lib/commonjs/chat-composer/create-suggestion-list.js +64 -10
  13. package/lib/commonjs/chat-composer/create-suggestion-list.js.map +1 -1
  14. package/lib/commonjs/chat-composer/messages.js +7 -0
  15. package/lib/commonjs/chat-composer/messages.js.map +1 -1
  16. package/lib/commonjs/cover-header/CoverHeader.js +83 -0
  17. package/lib/commonjs/cover-header/CoverHeader.js.map +1 -0
  18. package/lib/commonjs/cover-header/CoverImage.js +72 -0
  19. package/lib/commonjs/cover-header/CoverImage.js.map +1 -0
  20. package/lib/commonjs/cover-header/index.js +13 -0
  21. package/lib/commonjs/cover-header/index.js.map +1 -0
  22. package/lib/commonjs/cover-header/types.js +6 -0
  23. package/lib/commonjs/cover-header/types.js.map +1 -0
  24. package/lib/commonjs/index.js +951 -939
  25. package/lib/commonjs/index.js.map +1 -1
  26. package/lib/commonjs/index.web.js +951 -939
  27. package/lib/commonjs/index.web.js.map +1 -1
  28. package/lib/commonjs/locale/translations/ar.js +7 -0
  29. package/lib/commonjs/locale/translations/ar.js.map +1 -1
  30. package/lib/commonjs/locale/translations/bn.js +7 -0
  31. package/lib/commonjs/locale/translations/bn.js.map +1 -1
  32. package/lib/commonjs/locale/translations/ca.js +7 -0
  33. package/lib/commonjs/locale/translations/ca.js.map +1 -1
  34. package/lib/commonjs/locale/translations/de.js +7 -0
  35. package/lib/commonjs/locale/translations/de.js.map +1 -1
  36. package/lib/commonjs/locale/translations/es.js +7 -0
  37. package/lib/commonjs/locale/translations/es.js.map +1 -1
  38. package/lib/commonjs/locale/translations/fr.js +7 -0
  39. package/lib/commonjs/locale/translations/fr.js.map +1 -1
  40. package/lib/commonjs/locale/translations/hi.js +7 -0
  41. package/lib/commonjs/locale/translations/hi.js.map +1 -1
  42. package/lib/commonjs/locale/translations/id.js +7 -0
  43. package/lib/commonjs/locale/translations/id.js.map +1 -1
  44. package/lib/commonjs/locale/translations/it.js +7 -0
  45. package/lib/commonjs/locale/translations/it.js.map +1 -1
  46. package/lib/commonjs/locale/translations/ja.js +7 -0
  47. package/lib/commonjs/locale/translations/ja.js.map +1 -1
  48. package/lib/commonjs/locale/translations/pt.js +7 -0
  49. package/lib/commonjs/locale/translations/pt.js.map +1 -1
  50. package/lib/commonjs/locale/translations/ru.js +7 -0
  51. package/lib/commonjs/locale/translations/ru.js.map +1 -1
  52. package/lib/commonjs/locale/translations/tr.js +7 -0
  53. package/lib/commonjs/locale/translations/tr.js.map +1 -1
  54. package/lib/commonjs/locale/translations/zh.js +7 -0
  55. package/lib/commonjs/locale/translations/zh.js.map +1 -1
  56. package/lib/commonjs/message-bubble/MessageBubble.js +17 -11
  57. package/lib/commonjs/message-bubble/MessageBubble.js.map +1 -1
  58. package/lib/commonjs/message-media/StickerMessage.js +1 -1
  59. package/lib/commonjs/settings-modal/SettingsModal.js +19 -9
  60. package/lib/commonjs/settings-modal/SettingsModal.js.map +1 -1
  61. package/lib/module/ai-profile-card/AiProfileCard.js +185 -262
  62. package/lib/module/ai-profile-card/AiProfileCard.js.map +1 -1
  63. package/lib/module/chat-composer/SuggestionList.js +3 -1
  64. package/lib/module/chat-composer/SuggestionList.js.map +1 -1
  65. package/lib/module/chat-composer/SuggestionList.web.js +3 -1
  66. package/lib/module/chat-composer/SuggestionList.web.js.map +1 -1
  67. package/lib/module/chat-composer/create-suggestion-list.js +64 -10
  68. package/lib/module/chat-composer/create-suggestion-list.js.map +1 -1
  69. package/lib/module/chat-composer/messages.js +7 -0
  70. package/lib/module/chat-composer/messages.js.map +1 -1
  71. package/lib/module/cover-header/CoverHeader.js +78 -0
  72. package/lib/module/cover-header/CoverHeader.js.map +1 -0
  73. package/lib/module/cover-header/CoverImage.js +68 -0
  74. package/lib/module/cover-header/CoverImage.js.map +1 -0
  75. package/lib/module/cover-header/index.js +4 -0
  76. package/lib/module/cover-header/index.js.map +1 -0
  77. package/lib/module/cover-header/types.js +4 -0
  78. package/lib/module/cover-header/types.js.map +1 -0
  79. package/lib/module/index.js +1 -0
  80. package/lib/module/index.js.map +1 -1
  81. package/lib/module/index.web.js +1 -0
  82. package/lib/module/index.web.js.map +1 -1
  83. package/lib/module/locale/translations/ar.js +7 -0
  84. package/lib/module/locale/translations/ar.js.map +1 -1
  85. package/lib/module/locale/translations/bn.js +7 -0
  86. package/lib/module/locale/translations/bn.js.map +1 -1
  87. package/lib/module/locale/translations/ca.js +7 -0
  88. package/lib/module/locale/translations/ca.js.map +1 -1
  89. package/lib/module/locale/translations/de.js +7 -0
  90. package/lib/module/locale/translations/de.js.map +1 -1
  91. package/lib/module/locale/translations/es.js +7 -0
  92. package/lib/module/locale/translations/es.js.map +1 -1
  93. package/lib/module/locale/translations/fr.js +7 -0
  94. package/lib/module/locale/translations/fr.js.map +1 -1
  95. package/lib/module/locale/translations/hi.js +7 -0
  96. package/lib/module/locale/translations/hi.js.map +1 -1
  97. package/lib/module/locale/translations/id.js +7 -0
  98. package/lib/module/locale/translations/id.js.map +1 -1
  99. package/lib/module/locale/translations/it.js +7 -0
  100. package/lib/module/locale/translations/it.js.map +1 -1
  101. package/lib/module/locale/translations/ja.js +7 -0
  102. package/lib/module/locale/translations/ja.js.map +1 -1
  103. package/lib/module/locale/translations/pt.js +7 -0
  104. package/lib/module/locale/translations/pt.js.map +1 -1
  105. package/lib/module/locale/translations/ru.js +7 -0
  106. package/lib/module/locale/translations/ru.js.map +1 -1
  107. package/lib/module/locale/translations/tr.js +7 -0
  108. package/lib/module/locale/translations/tr.js.map +1 -1
  109. package/lib/module/locale/translations/zh.js +7 -0
  110. package/lib/module/locale/translations/zh.js.map +1 -1
  111. package/lib/module/message-bubble/MessageBubble.js +17 -11
  112. package/lib/module/message-bubble/MessageBubble.js.map +1 -1
  113. package/lib/module/message-media/StickerMessage.js +1 -1
  114. package/lib/module/settings-modal/SettingsModal.js +19 -9
  115. package/lib/module/settings-modal/SettingsModal.js.map +1 -1
  116. package/lib/typescript/commonjs/ai-profile-card/AiProfileCard.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/chat-composer/SuggestionList.d.ts +1 -1
  118. package/lib/typescript/commonjs/chat-composer/SuggestionList.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/chat-composer/SuggestionList.web.d.ts +1 -1
  120. package/lib/typescript/commonjs/chat-composer/SuggestionList.web.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/chat-composer/create-suggestion-list.d.ts +4 -2
  122. package/lib/typescript/commonjs/chat-composer/create-suggestion-list.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/chat-composer/messages.d.ts +6 -0
  124. package/lib/typescript/commonjs/chat-composer/messages.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/chat-composer/types.d.ts +17 -0
  126. package/lib/typescript/commonjs/chat-composer/types.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/cover-header/CoverHeader.d.ts +21 -0
  128. package/lib/typescript/commonjs/cover-header/CoverHeader.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/cover-header/CoverImage.d.ts +11 -0
  130. package/lib/typescript/commonjs/cover-header/CoverImage.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/cover-header/index.d.ts +3 -0
  132. package/lib/typescript/commonjs/cover-header/index.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/cover-header/types.d.ts +33 -0
  134. package/lib/typescript/commonjs/cover-header/types.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/index.d.ts +1 -0
  136. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/index.web.d.ts +1 -0
  138. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/locale/translations/ar.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/locale/translations/bn.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/locale/translations/ca.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/locale/translations/de.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/locale/translations/es.d.ts.map +1 -1
  144. package/lib/typescript/commonjs/locale/translations/fr.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/locale/translations/hi.d.ts.map +1 -1
  146. package/lib/typescript/commonjs/locale/translations/id.d.ts.map +1 -1
  147. package/lib/typescript/commonjs/locale/translations/it.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/locale/translations/ja.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/locale/translations/pt.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/locale/translations/ru.d.ts.map +1 -1
  151. package/lib/typescript/commonjs/locale/translations/tr.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/locale/translations/zh.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/message-bubble/MessageBubble.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/message-bubble/types.d.ts +13 -6
  155. package/lib/typescript/commonjs/message-bubble/types.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/message-media/StickerMessage.d.ts +1 -1
  157. package/lib/typescript/commonjs/settings-modal/SettingsModal.d.ts.map +1 -1
  158. package/lib/typescript/module/ai-profile-card/AiProfileCard.d.ts.map +1 -1
  159. package/lib/typescript/module/chat-composer/SuggestionList.d.ts +1 -1
  160. package/lib/typescript/module/chat-composer/SuggestionList.d.ts.map +1 -1
  161. package/lib/typescript/module/chat-composer/SuggestionList.web.d.ts +1 -1
  162. package/lib/typescript/module/chat-composer/SuggestionList.web.d.ts.map +1 -1
  163. package/lib/typescript/module/chat-composer/create-suggestion-list.d.ts +4 -2
  164. package/lib/typescript/module/chat-composer/create-suggestion-list.d.ts.map +1 -1
  165. package/lib/typescript/module/chat-composer/messages.d.ts +6 -0
  166. package/lib/typescript/module/chat-composer/messages.d.ts.map +1 -1
  167. package/lib/typescript/module/chat-composer/types.d.ts +17 -0
  168. package/lib/typescript/module/chat-composer/types.d.ts.map +1 -1
  169. package/lib/typescript/module/cover-header/CoverHeader.d.ts +21 -0
  170. package/lib/typescript/module/cover-header/CoverHeader.d.ts.map +1 -0
  171. package/lib/typescript/module/cover-header/CoverImage.d.ts +11 -0
  172. package/lib/typescript/module/cover-header/CoverImage.d.ts.map +1 -0
  173. package/lib/typescript/module/cover-header/index.d.ts +3 -0
  174. package/lib/typescript/module/cover-header/index.d.ts.map +1 -0
  175. package/lib/typescript/module/cover-header/types.d.ts +33 -0
  176. package/lib/typescript/module/cover-header/types.d.ts.map +1 -0
  177. package/lib/typescript/module/index.d.ts +1 -0
  178. package/lib/typescript/module/index.d.ts.map +1 -1
  179. package/lib/typescript/module/index.web.d.ts +1 -0
  180. package/lib/typescript/module/index.web.d.ts.map +1 -1
  181. package/lib/typescript/module/locale/translations/ar.d.ts.map +1 -1
  182. package/lib/typescript/module/locale/translations/bn.d.ts.map +1 -1
  183. package/lib/typescript/module/locale/translations/ca.d.ts.map +1 -1
  184. package/lib/typescript/module/locale/translations/de.d.ts.map +1 -1
  185. package/lib/typescript/module/locale/translations/es.d.ts.map +1 -1
  186. package/lib/typescript/module/locale/translations/fr.d.ts.map +1 -1
  187. package/lib/typescript/module/locale/translations/hi.d.ts.map +1 -1
  188. package/lib/typescript/module/locale/translations/id.d.ts.map +1 -1
  189. package/lib/typescript/module/locale/translations/it.d.ts.map +1 -1
  190. package/lib/typescript/module/locale/translations/ja.d.ts.map +1 -1
  191. package/lib/typescript/module/locale/translations/pt.d.ts.map +1 -1
  192. package/lib/typescript/module/locale/translations/ru.d.ts.map +1 -1
  193. package/lib/typescript/module/locale/translations/tr.d.ts.map +1 -1
  194. package/lib/typescript/module/locale/translations/zh.d.ts.map +1 -1
  195. package/lib/typescript/module/message-bubble/MessageBubble.d.ts.map +1 -1
  196. package/lib/typescript/module/message-bubble/types.d.ts +13 -6
  197. package/lib/typescript/module/message-bubble/types.d.ts.map +1 -1
  198. package/lib/typescript/module/message-media/StickerMessage.d.ts +1 -1
  199. package/lib/typescript/module/settings-modal/SettingsModal.d.ts.map +1 -1
  200. package/package.json +18 -1
  201. package/src/ai-profile-card/AiProfileCard.tsx +159 -241
  202. package/src/chat-composer/SuggestionList.tsx +2 -1
  203. package/src/chat-composer/SuggestionList.web.tsx +2 -1
  204. package/src/chat-composer/create-suggestion-list.tsx +54 -8
  205. package/src/chat-composer/messages.ts +9 -0
  206. package/src/chat-composer/types.ts +17 -0
  207. package/src/cover-header/CoverHeader.tsx +75 -0
  208. package/src/cover-header/CoverImage.tsx +60 -0
  209. package/src/cover-header/index.ts +3 -0
  210. package/src/cover-header/types.ts +30 -0
  211. package/src/index.ts +1 -0
  212. package/src/index.web.ts +1 -0
  213. package/src/locale/translations/ar.ts +3 -0
  214. package/src/locale/translations/bn.ts +3 -0
  215. package/src/locale/translations/ca.ts +3 -0
  216. package/src/locale/translations/de.ts +3 -0
  217. package/src/locale/translations/es.ts +3 -0
  218. package/src/locale/translations/fr.ts +3 -0
  219. package/src/locale/translations/hi.ts +3 -0
  220. package/src/locale/translations/id.ts +3 -0
  221. package/src/locale/translations/it.ts +3 -0
  222. package/src/locale/translations/ja.ts +3 -0
  223. package/src/locale/translations/pt.ts +3 -0
  224. package/src/locale/translations/ru.ts +3 -0
  225. package/src/locale/translations/tr.ts +3 -0
  226. package/src/locale/translations/zh.ts +3 -0
  227. package/src/message-bubble/MessageBubble.tsx +17 -11
  228. package/src/message-bubble/types.ts +13 -6
  229. package/src/message-media/StickerMessage.tsx +1 -1
  230. package/src/settings-modal/SettingsModal.tsx +19 -9
@@ -1,6 +1,7 @@
1
1
  import { useSurfaceFill } from '../styles/surface-levels';
2
2
  import { useCardFill } from '../card/use-card-fill';
3
3
  import type { Surface as SurfaceComponent } from '../surface';
4
+ import type { Loading as LoadingComponent } from '../loading';
4
5
  /**
5
6
  * The anchored list above the composer: `@mention`, `/command` and `:shortcode`
6
7
  * are ONE part with a `kind`, because the row geometry, the keyboard model and
@@ -14,6 +15,7 @@ import React from 'react';
14
15
  import { Image, Pressable, ScrollView, View } from 'react-native';
15
16
 
16
17
  import { RiHashtag } from '../icons/remix/RiHashtag';
18
+ import { RiVerifiedBadgeFill } from '../icons/remix/RiVerifiedBadgeFill';
17
19
  import { useImageResolver } from '../image-resolver/context';
18
20
  import { useMessages } from '../locale/messages';
19
21
  import { useTheme } from '../theme/use-theme';
@@ -30,8 +32,9 @@ import type { ChatComposerSuggestion, SuggestionKind, SuggestionListProps } from
30
32
  import { dataHook, IS_WEB } from './web-hooks';
31
33
 
32
34
  /** Platform dependencies are bound once; shared rendering adds no wrapper. */
33
- export function createSuggestionList({ Surface }: {
35
+ export function createSuggestionList({ Surface, Loading }: {
34
36
  Surface: typeof SurfaceComponent;
37
+ Loading: typeof LoadingComponent;
35
38
  }) {
36
39
 
37
40
  function isUrl(value: string): boolean {
@@ -111,11 +114,15 @@ export function createSuggestionList({ Surface }: {
111
114
  style,
112
115
  testID,
113
116
  accessibilityLabel,
117
+ loading = false,
118
+ showEmpty = false,
119
+ emptyLabel,
114
120
  }: SuggestionListProps) {
115
121
  const theme = useTheme();
116
122
  const palette = resolveChatComposerPalette(theme, useCardFill(style));
117
123
  const { messages } = useMessages(CHAT_COMPOSER_MESSAGES);
118
- if (suggestions.length === 0) return null;
124
+ const empty = !loading && suggestions.length === 0;
125
+ if (empty && !showEmpty) return null;
119
126
 
120
127
  // React Native's `Role` union has no `listbox`; react-native-web passes the
121
128
  // DOM `role` straight through, so it travels as a web-only prop.
@@ -144,6 +151,34 @@ export function createSuggestionList({ Surface }: {
144
151
  {header}
145
152
  </Text>
146
153
  ) : null}
154
+ {loading ? (
155
+ <View style={{ minHeight: SUGGESTION_ROW_HEIGHT, justifyContent: 'center', paddingLeft: 8, paddingRight: 8 }}>
156
+ <Loading
157
+ variant="inline"
158
+ size="sm"
159
+ color={palette.textSecondary}
160
+ text={messages.searchingSuggestions}
161
+ textStyle={{ color: palette.textSecondary }}
162
+ accessibilityLabel={messages.searchingSuggestions}
163
+ testID={testID ? `${testID}-loading` : undefined}
164
+ />
165
+ </View>
166
+ ) : empty ? (
167
+ <Text
168
+ variant="body-2-regular"
169
+ accessibilityRole="text"
170
+ style={{
171
+ color: palette.textSecondary,
172
+ minHeight: SUGGESTION_ROW_HEIGHT,
173
+ paddingLeft: 8,
174
+ paddingRight: 8,
175
+ paddingTop: 10,
176
+ paddingBottom: 10,
177
+ }}
178
+ testID={testID ? `${testID}-empty` : undefined}>
179
+ {emptyLabel ?? messages.noSuggestions[kind]}
180
+ </Text>
181
+ ) : (
147
182
  <ScrollView
148
183
  {...dataHook('bloomChatComposerScroll')}
149
184
  {...listRole}
@@ -167,9 +202,12 @@ export function createSuggestionList({ Surface }: {
167
202
  cursor: suggestion.disabled ? 'auto' : 'pointer',
168
203
  '--bloom-chat-composer-ring': palette.focusRing,
169
204
  };
170
- const name = suggestion.handle
171
- ? `${suggestion.label} ${suggestion.handle}`
172
- : suggestion.label;
205
+ const verified = kind === 'mention' && suggestion.verified === true;
206
+ const name = [
207
+ suggestion.label,
208
+ verified ? messages.suggestionVerified : undefined,
209
+ suggestion.handle,
210
+ ].filter(Boolean).join(verified ? ', ' : ' ');
173
211
  return (
174
212
  <Pressable
175
213
  key={suggestion.id}
@@ -186,9 +224,16 @@ export function createSuggestionList({ Surface }: {
186
224
  <Leading kind={kind} suggestion={suggestion} />
187
225
  <View style={{ flexShrink: 1, flexGrow: 1 }}>
188
226
  <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 6 }}>
189
- <Text variant="body-medium" numberOfLines={1} style={{ color: palette.text }}>
190
- {suggestion.label}
191
- </Text>
227
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3, flexShrink: 1 }}>
228
+ <Text variant="body-medium" numberOfLines={1} style={{ color: palette.text, flexShrink: 1 }}>
229
+ {suggestion.label}
230
+ </Text>
231
+ {verified ? (
232
+ <View testID={testID ? `${testID}-verified-${suggestion.id}` : undefined}>
233
+ <RiVerifiedBadgeFill width={14} height={14} fill={palette.accent} />
234
+ </View>
235
+ ) : null}
236
+ </View>
192
237
  {suggestion.handle ? (
193
238
  <Text
194
239
  variant="body-2-regular"
@@ -211,6 +256,7 @@ export function createSuggestionList({ Surface }: {
211
256
  );
212
257
  })}
213
258
  </ScrollView>
259
+ )}
214
260
  </Surface>
215
261
  );
216
262
  }
@@ -46,6 +46,12 @@ export interface ChatComposerMessages {
46
46
  removeAttachment: (name: string) => string;
47
47
  /** `SuggestionList`'s name per kind. */
48
48
  suggestions: Record<SuggestionKind, string>;
49
+ /** A suggestion row's verified marker, as announced. */
50
+ suggestionVerified: string;
51
+ /** `SuggestionList` while its caller is still searching. */
52
+ searchingSuggestions: string;
53
+ /** `SuggestionList` when a finished search found nothing, per kind. */
54
+ noSuggestions: Record<SuggestionKind, string>;
49
55
  /** `AttachmentMenu`'s default rows. */
50
56
  attachmentItems: Record<AttachmentMenuItemId, string>;
51
57
  }
@@ -77,5 +83,8 @@ export const CHAT_COMPOSER_MESSAGES: MessageCatalog<ChatComposerMessages> = defi
77
83
  attachments: 'Attachments',
78
84
  removeAttachment: (name) => `Remove ${name}`,
79
85
  suggestions: { mention: 'People', command: 'Commands', emoji: 'Emoji' },
86
+ suggestionVerified: 'Verified',
87
+ searchingSuggestions: 'Searching…',
88
+ noSuggestions: { mention: 'No people found', command: 'No commands found', emoji: 'No emoji found' },
80
89
  attachmentItems: { gallery: 'Gallery', camera: 'Camera', file: 'File', location: 'Location', contact: 'Contact', poll: 'Poll', music: 'Music' },
81
90
  });
@@ -66,6 +66,8 @@ export interface ChatComposerSuggestion {
66
66
  avatar?: string;
67
67
  /** The glyph itself, for `kind="emoji"`. */
68
68
  emoji?: string;
69
+ /** Draws the verified marker after the name, for `kind="mention"`. */
70
+ verified?: boolean;
69
71
  disabled?: boolean;
70
72
  }
71
73
 
@@ -91,6 +93,21 @@ export interface SuggestionListProps extends PartProps {
91
93
  maxHeight?: number;
92
94
  /** Accessible name of the listbox. Defaults to a name derived from `kind`. */
93
95
  accessibilityLabel?: string;
96
+ /**
97
+ * The caller is still searching. Draws a named, busy "Searching…" line in
98
+ * place of the rows, so a list that has nothing YET is not mistaken for one
99
+ * that found nothing. Default `false`.
100
+ */
101
+ loading?: boolean;
102
+ /**
103
+ * Draws a "No people found" line when a finished search is empty. Off by
104
+ * default, and an empty list draws nothing — right for a composer that shows
105
+ * the list only while it has rows; a picker the user opened on purpose wants
106
+ * the answer.
107
+ */
108
+ showEmpty?: boolean;
109
+ /** Replaces the localized empty line. */
110
+ emptyLabel?: string;
94
111
  }
95
112
 
96
113
  export interface ChatComposerLabels {
@@ -0,0 +1,75 @@
1
+ import React, { memo } from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import { useTheme } from '../theme/use-theme';
5
+ import { CoverImage } from './CoverImage';
6
+ import type { CoverHeaderProps } from './types';
7
+
8
+ const DEFAULT_POSITION = { x: 0.5, y: 0.5 };
9
+
10
+ /**
11
+ * A cover band with content rising into its bottom edge — a profile's banner
12
+ * and the avatar that overlaps it.
13
+ *
14
+ * cover absolute over the top `coverHeight`, clipped, background-tertiary
15
+ * under the photo; the FIRST child
16
+ * content in flow, `paddingTop: coverHeight - overlap`; the LAST child
17
+ *
18
+ * The overlap is made by layout, not by moving anything: the band is taken out
19
+ * of flow and the content starts `overlap` short of its bottom edge. No
20
+ * negative margin, no transform, no zIndex. The content is the later sibling,
21
+ * so it paints over the band on iOS, Android and web alike, and the measured
22
+ * height is the real one — a list header or a sticky section after it starts
23
+ * exactly where the content ends.
24
+ */
25
+ function CoverHeaderComponent({
26
+ coverSource,
27
+ coverPosition = DEFAULT_POSITION,
28
+ cover,
29
+ coverHeight = 170,
30
+ overlap = 45,
31
+ coverStyle,
32
+ contentStyle,
33
+ children,
34
+ style,
35
+ testID,
36
+ }: CoverHeaderProps) {
37
+ const theme = useTheme();
38
+ const rise = Math.min(Math.max(overlap, 0), coverHeight);
39
+
40
+ return (
41
+ <View testID={testID} style={[{ position: 'relative', width: '100%' }, style]}>
42
+ <View
43
+ testID={testID ? `${testID}-cover` : undefined}
44
+ style={[
45
+ {
46
+ position: 'absolute',
47
+ top: 0,
48
+ left: 0,
49
+ right: 0,
50
+ height: coverHeight,
51
+ overflow: 'hidden',
52
+ backgroundColor: theme.colors.backgroundTertiary,
53
+ },
54
+ coverStyle,
55
+ ]}>
56
+ {cover ??
57
+ (coverSource ? (
58
+ <CoverImage
59
+ source={coverSource}
60
+ position={coverPosition}
61
+ testID={testID ? `${testID}-cover-image` : undefined}
62
+ />
63
+ ) : null)}
64
+ </View>
65
+ <View
66
+ testID={testID ? `${testID}-content` : undefined}
67
+ style={[{ position: 'relative', width: '100%' }, contentStyle, { paddingTop: coverHeight - rise }]}>
68
+ {children}
69
+ </View>
70
+ </View>
71
+ );
72
+ }
73
+
74
+ export const CoverHeader = memo(CoverHeaderComponent);
75
+ CoverHeader.displayName = 'CoverHeader';
@@ -0,0 +1,60 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ Image,
4
+ StyleSheet,
5
+ View,
6
+ type ImageLoadEventData,
7
+ type ImageSourcePropType,
8
+ type LayoutChangeEvent,
9
+ type NativeSyntheticEvent,
10
+ } from 'react-native';
11
+
12
+ /** `object-fit: cover` with an `object-position`: the image sized to cover the box, offset by the fractions. */
13
+ export function CoverImage({
14
+ source,
15
+ position,
16
+ testID,
17
+ }: {
18
+ source: string | ImageSourcePropType;
19
+ position: { x: number; y: number };
20
+ testID?: string;
21
+ }) {
22
+ const [box, setBox] = useState<{ width: number; height: number } | null>(null);
23
+ const [natural, setNatural] = useState<{ width: number; height: number } | null>(null);
24
+ const resolved: ImageSourcePropType = typeof source === 'string' ? { uri: source } : source;
25
+
26
+ const onLayout = useCallback((event: LayoutChangeEvent) => {
27
+ const { width, height } = event.nativeEvent.layout;
28
+ setBox((prev) => (prev && prev.width === width && prev.height === height ? prev : { width, height }));
29
+ }, []);
30
+ const onLoad = useCallback((event: NativeSyntheticEvent<ImageLoadEventData>) => {
31
+ const s = event.nativeEvent?.source;
32
+ if (s && s.width > 0 && s.height > 0) setNatural({ width: s.width, height: s.height });
33
+ }, []);
34
+
35
+ const frame = useMemo(() => {
36
+ if (!box || !natural) return null;
37
+ const scale = Math.max(box.width / natural.width, box.height / natural.height);
38
+ const width = natural.width * scale;
39
+ const height = natural.height * scale;
40
+ return { width, height, left: (box.width - width) * position.x, top: (box.height - height) * position.y };
41
+ }, [box, natural, position.x, position.y]);
42
+
43
+ return (
44
+ <View style={StyleSheet.absoluteFill} onLayout={onLayout} testID={testID ? `${testID}-frame` : undefined}>
45
+ <Image
46
+ testID={testID}
47
+ source={resolved}
48
+ onLoad={onLoad}
49
+ accessibilityIgnoresInvertColors
50
+ aria-hidden
51
+ resizeMode="cover"
52
+ style={
53
+ frame
54
+ ? { position: 'absolute', left: frame.left, top: frame.top, width: frame.width, height: frame.height }
55
+ : StyleSheet.absoluteFill
56
+ }
57
+ />
58
+ </View>
59
+ );
60
+ }
@@ -0,0 +1,3 @@
1
+ export { CoverHeader } from './CoverHeader';
2
+
3
+ export type { CoverHeaderProps } from './types';
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ImageSourcePropType, StyleProp, ViewStyle } from 'react-native';
3
+
4
+ export interface CoverHeaderProps {
5
+ /** Photo across the cover band. Without one the band shows `background-tertiary`. */
6
+ coverSource?: string | ImageSourcePropType | null;
7
+ /**
8
+ * Where the photo's crop sits, as fractions (`object-position`). Default
9
+ * `{ x: 0.5, y: 0.5 }` — centred, as `resizeMode="cover"`.
10
+ */
11
+ coverPosition?: { x: number; y: number };
12
+ /** Drawn in the band instead of the photo (an editor, a gradient, a video poster). */
13
+ cover?: ReactNode;
14
+ /** Height of the cover band. Default `170`. */
15
+ coverHeight?: number;
16
+ /**
17
+ * How far the content rises into the cover band. Default `45` — half a 90px
18
+ * avatar. Clamped to `0..coverHeight`.
19
+ */
20
+ overlap?: number;
21
+ /** The band's own style: corner radii, a different fill. */
22
+ coverStyle?: StyleProp<ViewStyle>;
23
+ /** The content column's style. Its `paddingTop` is the component's and is ignored. */
24
+ contentStyle?: StyleProp<ViewStyle>;
25
+ /** The content: the avatar row first, then whatever follows it. */
26
+ children?: ReactNode;
27
+ style?: StyleProp<ViewStyle>;
28
+ /** Also names the parts: `-cover`, `-cover-image`, `-content`. */
29
+ testID?: string;
30
+ }
package/src/index.ts CHANGED
@@ -198,6 +198,7 @@ export * from './calendar';
198
198
  export * from './stat-cards';
199
199
  export * from './recent-hires-card';
200
200
  export * from './ai-profile-card';
201
+ export * from './cover-header';
201
202
  export * from './important-alerts-card';
202
203
  export * from './patient-info-card';
203
204
  export * from './sidebar';
package/src/index.web.ts CHANGED
@@ -203,6 +203,7 @@ export * from './calendar';
203
203
  export * from './stat-cards';
204
204
  export * from './recent-hires-card';
205
205
  export * from './ai-profile-card';
206
+ export * from './cover-header';
206
207
  export * from './important-alerts-card';
207
208
  export * from './patient-info-card';
208
209
  export * from './sidebar';
@@ -176,6 +176,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
176
176
  attachments: 'المرفقات',
177
177
  removeAttachment: (name) => `إزالة ${name}`,
178
178
  suggestions: { mention: 'الأشخاص', command: 'الأوامر', emoji: 'رموز تعبيرية' },
179
+ suggestionVerified: 'موثّق',
180
+ searchingSuggestions: 'جارٍ البحث…',
181
+ noSuggestions: { mention: 'لم يتم العثور على أشخاص', command: 'لم يتم العثور على أوامر', emoji: 'لم يتم العثور على رموز تعبيرية' },
179
182
  attachmentItems: { gallery: 'المعرض', camera: 'الكاميرا', file: 'ملف', location: 'الموقع', contact: 'جهة اتصال', poll: 'استطلاع', music: 'موسيقى' },
180
183
  };
181
184
 
@@ -158,6 +158,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
158
158
  attachments: 'সংযুক্তি',
159
159
  removeAttachment: (name) => `${name} সরান`,
160
160
  suggestions: { mention: 'লোকজন', command: 'কমান্ড', emoji: 'ইমোজি' },
161
+ suggestionVerified: 'যাচাইকৃত',
162
+ searchingSuggestions: 'খোঁজা হচ্ছে…',
163
+ noSuggestions: { mention: 'কাউকে পাওয়া যায়নি', command: 'কোনো কমান্ড পাওয়া যায়নি', emoji: 'কোনো ইমোজি পাওয়া যায়নি' },
161
164
  attachmentItems: { gallery: 'গ্যালারি', camera: 'ক্যামেরা', file: 'ফাইল', location: 'অবস্থান', contact: 'পরিচিতি', poll: 'পোল', music: 'সংগীত' },
162
165
  };
163
166
 
@@ -165,6 +165,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
165
165
  attachments: 'Fitxers adjunts',
166
166
  removeAttachment: (name) => `Treu ${name}`,
167
167
  suggestions: { mention: 'Persones', command: 'Ordres', emoji: 'Emoji' },
168
+ suggestionVerified: 'Verificat',
169
+ searchingSuggestions: 'Cercant…',
170
+ noSuggestions: { mention: "No s'ha trobat ningú", command: "No s'han trobat ordres", emoji: "No s'han trobat emojis" },
168
171
  attachmentItems: { gallery: 'Galeria', camera: 'Càmera', file: 'Fitxer', location: 'Ubicació', contact: 'Contacte', poll: 'Enquesta', music: 'Música' },
169
172
  };
170
173
 
@@ -160,6 +160,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
160
160
  attachments: 'Anhänge',
161
161
  removeAttachment: (name) => `${name} entfernen`,
162
162
  suggestions: { mention: 'Personen', command: 'Befehle', emoji: 'Emoji' },
163
+ suggestionVerified: 'Verifiziert',
164
+ searchingSuggestions: 'Suche läuft…',
165
+ noSuggestions: { mention: 'Keine Personen gefunden', command: 'Keine Befehle gefunden', emoji: 'Keine Emojis gefunden' },
163
166
  attachmentItems: { gallery: 'Galerie', camera: 'Kamera', file: 'Datei', location: 'Standort', contact: 'Kontakt', poll: 'Umfrage', music: 'Musik' },
164
167
  };
165
168
 
@@ -165,6 +165,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
165
165
  attachments: 'Archivos adjuntos',
166
166
  removeAttachment: (name) => `Quitar ${name}`,
167
167
  suggestions: { mention: 'Personas', command: 'Comandos', emoji: 'Emoji' },
168
+ suggestionVerified: 'Verificado',
169
+ searchingSuggestions: 'Buscando…',
170
+ noSuggestions: { mention: 'No se encontraron personas', command: 'No se encontraron comandos', emoji: 'No se encontraron emojis' },
168
171
  attachmentItems: { gallery: 'Galería', camera: 'Cámara', file: 'Archivo', location: 'Ubicación', contact: 'Contacto', poll: 'Encuesta', music: 'Música' },
169
172
  };
170
173
 
@@ -165,6 +165,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
165
165
  attachments: 'Pièces jointes',
166
166
  removeAttachment: (name) => `Retirer ${name}`,
167
167
  suggestions: { mention: 'Personnes', command: 'Commandes', emoji: 'Émoji' },
168
+ suggestionVerified: 'Vérifié',
169
+ searchingSuggestions: 'Recherche…',
170
+ noSuggestions: { mention: 'Aucune personne trouvée', command: 'Aucune commande trouvée', emoji: 'Aucun émoji trouvé' },
168
171
  attachmentItems: { gallery: 'Galerie', camera: 'Appareil photo', file: 'Fichier', location: 'Position', contact: 'Contact', poll: 'Sondage', music: 'Musique' },
169
172
  };
170
173
 
@@ -158,6 +158,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
158
158
  attachments: 'अटैचमेंट',
159
159
  removeAttachment: (name) => `${name} हटाएं`,
160
160
  suggestions: { mention: 'लोग', command: 'कमांड', emoji: 'इमोजी' },
161
+ suggestionVerified: 'सत्यापित',
162
+ searchingSuggestions: 'खोज रहे हैं…',
163
+ noSuggestions: { mention: 'कोई व्यक्ति नहीं मिला', command: 'कोई कमांड नहीं मिला', emoji: 'कोई इमोजी नहीं मिला' },
161
164
  attachmentItems: { gallery: 'गैलरी', camera: 'कैमरा', file: 'फ़ाइल', location: 'लोकेशन', contact: 'संपर्क', poll: 'पोल', music: 'संगीत' },
162
165
  };
163
166
 
@@ -158,6 +158,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
158
158
  attachments: 'Lampiran',
159
159
  removeAttachment: (name) => `Hapus ${name}`,
160
160
  suggestions: { mention: 'Orang', command: 'Perintah', emoji: 'Emoji' },
161
+ suggestionVerified: 'Terverifikasi',
162
+ searchingSuggestions: 'Mencari…',
163
+ noSuggestions: { mention: 'Orang tidak ditemukan', command: 'Perintah tidak ditemukan', emoji: 'Emoji tidak ditemukan' },
161
164
  attachmentItems: { gallery: 'Galeri', camera: 'Kamera', file: 'File', location: 'Lokasi', contact: 'Kontak', poll: 'Polling', music: 'Musik' },
162
165
  };
163
166
 
@@ -165,6 +165,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
165
165
  attachments: 'Allegati',
166
166
  removeAttachment: (name) => `Rimuovi ${name}`,
167
167
  suggestions: { mention: 'Persone', command: 'Comandi', emoji: 'Emoji' },
168
+ suggestionVerified: 'Verificato',
169
+ searchingSuggestions: 'Ricerca in corso…',
170
+ noSuggestions: { mention: 'Nessuna persona trovata', command: 'Nessun comando trovato', emoji: 'Nessuna emoji trovata' },
168
171
  attachmentItems: { gallery: 'Galleria', camera: 'Fotocamera', file: 'File', location: 'Posizione', contact: 'Contatto', poll: 'Sondaggio', music: 'Musica' },
169
172
  };
170
173
 
@@ -158,6 +158,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
158
158
  attachments: '添付ファイル',
159
159
  removeAttachment: (name) => `${name}を削除`,
160
160
  suggestions: { mention: 'ユーザー', command: 'コマンド', emoji: '絵文字' },
161
+ suggestionVerified: '認証済み',
162
+ searchingSuggestions: '検索中…',
163
+ noSuggestions: { mention: 'ユーザーが見つかりません', command: 'コマンドが見つかりません', emoji: '絵文字が見つかりません' },
161
164
  attachmentItems: { gallery: 'ギャラリー', camera: 'カメラ', file: 'ファイル', location: '位置情報', contact: '連絡先', poll: 'アンケート', music: '音楽' },
162
165
  };
163
166
 
@@ -165,6 +165,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
165
165
  attachments: 'Anexos',
166
166
  removeAttachment: (name) => `Remover ${name}`,
167
167
  suggestions: { mention: 'Pessoas', command: 'Comandos', emoji: 'Emoji' },
168
+ suggestionVerified: 'Verificado',
169
+ searchingSuggestions: 'Pesquisando…',
170
+ noSuggestions: { mention: 'Nenhuma pessoa encontrada', command: 'Nenhum comando encontrado', emoji: 'Nenhum emoji encontrado' },
168
171
  attachmentItems: { gallery: 'Galeria', camera: 'Câmera', file: 'Arquivo', location: 'Localização', contact: 'Contato', poll: 'Enquete', music: 'Música' },
169
172
  };
170
173
 
@@ -163,6 +163,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
163
163
  attachments: 'Вложения',
164
164
  removeAttachment: (name) => `Удалить ${name}`,
165
165
  suggestions: { mention: 'Люди', command: 'Команды', emoji: 'Эмодзи' },
166
+ suggestionVerified: 'Подтверждён',
167
+ searchingSuggestions: 'Поиск…',
168
+ noSuggestions: { mention: 'Никого не найдено', command: 'Команды не найдены', emoji: 'Эмодзи не найдены' },
166
169
  attachmentItems: { gallery: 'Галерея', camera: 'Камера', file: 'Файл', location: 'Геопозиция', contact: 'Контакт', poll: 'Опрос', music: 'Музыка' },
167
170
  };
168
171
 
@@ -159,6 +159,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
159
159
  attachments: 'Ekler',
160
160
  removeAttachment: (name) => `Kaldır: ${name}`,
161
161
  suggestions: { mention: 'Kişiler', command: 'Komutlar', emoji: 'Emoji' },
162
+ suggestionVerified: 'Doğrulanmış',
163
+ searchingSuggestions: 'Aranıyor…',
164
+ noSuggestions: { mention: 'Kişi bulunamadı', command: 'Komut bulunamadı', emoji: 'Emoji bulunamadı' },
162
165
  attachmentItems: { gallery: 'Galeri', camera: 'Kamera', file: 'Dosya', location: 'Konum', contact: 'Kişi', poll: 'Anket', music: 'Müzik' },
163
166
  };
164
167
 
@@ -158,6 +158,9 @@ const CHAT_COMPOSER_MESSAGES: Translations['CHAT_COMPOSER_MESSAGES'] = {
158
158
  attachments: '附件',
159
159
  removeAttachment: (name) => `移除 ${name}`,
160
160
  suggestions: { mention: '用户', command: '命令', emoji: '表情' },
161
+ suggestionVerified: '已认证',
162
+ searchingSuggestions: '正在搜索…',
163
+ noSuggestions: { mention: '未找到用户', command: '未找到命令', emoji: '未找到表情' },
161
164
  attachmentItems: { gallery: '相册', camera: '相机', file: '文件', location: '位置', contact: '联系人', poll: '投票', music: '音乐' },
162
165
  };
163
166
 
@@ -89,7 +89,8 @@ import type { MessageBubbleProps } from './types';
89
89
  *
90
90
  * THE SLOT HAS TWO FITS, and the bubble cannot tell them apart by looking.
91
91
  * `mediaFit="bleed"` (the default) cancels the bubble's padding so a photo runs
92
- * to the radius. `mediaFit="inset"` keeps it, for a block that carries no
92
+ * to the radius. `mediaFit="bare"` draws no bubble at all around media alone —
93
+ * a sticker. `mediaFit="inset"` keeps the padding, for a block that carries no
93
94
  * padding of its own — `PollMessage`, `LocationMessage`, `ContactMessage` are
94
95
  * all typography, and bled they clip a question against the top edge, cut an
95
96
  * address off at the bottom and put an avatar on the left one. The fit also
@@ -233,10 +234,13 @@ function MessageBubbleComponent({
233
234
 
234
235
  const radii = bubbleRadii(direction, position);
235
236
  const fill = failed ? mixColor(side.fill, paint.failed, theme.isDark ? 0.3 : 0.22) : side.fill;
236
- const showTail = tail && hasTail(position);
237
237
  const mediaOnly = media !== undefined && text === undefined && children === undefined && !deleted;
238
- const bleed = mediaFit === 'bleed';
239
- const overlayMeta = metaOverlay ?? (mediaOnly && bleed);
238
+ // `bare` only holds for media alone: with text beside it the text needs a
239
+ // surface, so the block is fitted as `bleed` instead.
240
+ const bare = mediaFit === 'bare' && mediaOnly;
241
+ const bleed = mediaFit === 'bleed' || (mediaFit === 'bare' && !mediaOnly);
242
+ const showTail = tail && hasTail(position) && !bare;
243
+ const overlayMeta = metaOverlay ?? (mediaOnly && (bleed || bare));
240
244
  const hasMeta =
241
245
  !deleted &&
242
246
  (time !== undefined ||
@@ -274,16 +278,18 @@ function MessageBubbleComponent({
274
278
  {
275
279
  borderRadius: 0,
276
280
  ...radii,
277
- backgroundColor: fill,
278
- borderWidth: side.border === undefined ? 0 : 1,
281
+ backgroundColor: bare ? 'transparent' : fill,
282
+ borderWidth: bare || side.border === undefined ? 0 : 1,
279
283
  borderColor: side.border,
280
- paddingTop: BUBBLE_PADDING_Y,
281
- paddingBottom: BUBBLE_PADDING_Y,
282
- paddingLeft: BUBBLE_PADDING_X,
283
- paddingRight: BUBBLE_PADDING_X,
284
+ paddingTop: bare ? 0 : BUBBLE_PADDING_Y,
285
+ paddingBottom: bare ? 0 : BUBBLE_PADDING_Y,
286
+ paddingLeft: bare ? 0 : BUBBLE_PADDING_X,
287
+ paddingRight: bare ? 0 : BUBBLE_PADDING_X,
284
288
  gap: 2,
285
289
  opacity: pending && !failed ? 0.65 : 1,
286
- overflow: 'hidden',
290
+ // A bare block keeps its own shape: clipping it to the bubble's radii
291
+ // would round a sticker's corners off.
292
+ overflow: bare ? 'visible' : 'hidden',
287
293
  },
288
294
  bubbleStyle,
289
295
  ]}
@@ -17,16 +17,22 @@ export type MessagePosition = 'single' | 'first' | 'middle' | 'last';
17
17
  * How the `media` slot is fitted inside the bubble.
18
18
  *
19
19
  * bleed the bubble's padding is CANCELLED and the block runs to the radius
20
- * — a photo, a video, an album, a sticker: something with its own
21
- * edge, which an inset would frame twice
20
+ * — a photo, a video, an album: something with its own edge, which
21
+ * an inset would frame twice
22
22
  * inset the block keeps the bubble's padding — a poll, a place, a contact
23
23
  * card: typography with no padding of its own, which bled means a
24
24
  * question clipped against the top edge and an avatar on the left one
25
+ * bare NO bubble at all — no fill, border, padding or tail — for a
26
+ * media-only message whose block is its own shape: a sticker. A
27
+ * sticker drawn inside a bubble is a picture of a sticker, and the
28
+ * fill turns its cut-out into a square. With text beside the media
29
+ * this falls back to `bleed`, because the text needs a surface.
25
30
  *
26
- * It also decides where the meta row lands by default: over a bled block on a
27
- * translucent pill, under an inset one where it cannot cover a line of text.
31
+ * It also decides where the meta row lands by default: over a bled or bare
32
+ * block on a translucent pill, under an inset one where it cannot cover a line
33
+ * of text.
28
34
  */
29
- export type MessageMediaFit = 'bleed' | 'inset';
35
+ export type MessageMediaFit = 'bleed' | 'inset' | 'bare';
30
36
 
31
37
  /** A text span the bubble hands back to the caller to render. */
32
38
  export type MessageEntityType = 'link' | 'mention' | 'hashtag';
@@ -102,7 +108,8 @@ export interface MessageBubbleProps {
102
108
  /**
103
109
  * How `media` is fitted. Default `'bleed'` — today's photo. Pass `'inset'`
104
110
  * for a block that draws no padding of its own (`PollMessage`,
105
- * `LocationMessage`, `ContactMessage`). See {@link MessageMediaFit}.
111
+ * `LocationMessage`, `ContactMessage`), and `'bare'` for a sticker, which
112
+ * draws no bubble at all. See {@link MessageMediaFit}.
106
113
  */
107
114
  mediaFit?: MessageMediaFit;
108
115
  /**
@@ -18,7 +18,7 @@ const TRANSPARENT = 'transparent';
18
18
  *
19
19
  * IT DRAWS NO SURFACE AT ALL — no background, no radius, no padding — because a
20
20
  * sticker with a bubble behind it is a picture of a sticker. The shell renders it
21
- * with `variant="bare"`, which is `message-bubble`'s no-chrome mode; a bubble
21
+ * with `mediaFit="bare"`, which is `message-bubble`'s no-chrome mode; a bubble
22
22
  * around one would also put a scrim behind the transparent parts and turn a
23
23
  * cut-out into a square.
24
24
  *