@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.
- package/README.md +1 -1
- package/docs/adoption-matrix.mdx +4 -3
- package/docs/chat-composer.mdx +5 -1
- package/docs/cover-header.mdx +56 -0
- package/docs/message-media.mdx +1 -1
- package/lib/commonjs/ai-profile-card/AiProfileCard.js +186 -263
- package/lib/commonjs/ai-profile-card/AiProfileCard.js.map +1 -1
- package/lib/commonjs/chat-composer/SuggestionList.js +3 -1
- package/lib/commonjs/chat-composer/SuggestionList.js.map +1 -1
- package/lib/commonjs/chat-composer/SuggestionList.web.js +3 -1
- package/lib/commonjs/chat-composer/SuggestionList.web.js.map +1 -1
- package/lib/commonjs/chat-composer/create-suggestion-list.js +64 -10
- package/lib/commonjs/chat-composer/create-suggestion-list.js.map +1 -1
- package/lib/commonjs/chat-composer/messages.js +7 -0
- package/lib/commonjs/chat-composer/messages.js.map +1 -1
- package/lib/commonjs/cover-header/CoverHeader.js +83 -0
- package/lib/commonjs/cover-header/CoverHeader.js.map +1 -0
- package/lib/commonjs/cover-header/CoverImage.js +72 -0
- package/lib/commonjs/cover-header/CoverImage.js.map +1 -0
- package/lib/commonjs/cover-header/index.js +13 -0
- package/lib/commonjs/cover-header/index.js.map +1 -0
- package/lib/commonjs/cover-header/types.js +6 -0
- package/lib/commonjs/cover-header/types.js.map +1 -0
- package/lib/commonjs/index.js +951 -939
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +951 -939
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/locale/translations/ar.js +7 -0
- package/lib/commonjs/locale/translations/ar.js.map +1 -1
- package/lib/commonjs/locale/translations/bn.js +7 -0
- package/lib/commonjs/locale/translations/bn.js.map +1 -1
- package/lib/commonjs/locale/translations/ca.js +7 -0
- package/lib/commonjs/locale/translations/ca.js.map +1 -1
- package/lib/commonjs/locale/translations/de.js +7 -0
- package/lib/commonjs/locale/translations/de.js.map +1 -1
- package/lib/commonjs/locale/translations/es.js +7 -0
- package/lib/commonjs/locale/translations/es.js.map +1 -1
- package/lib/commonjs/locale/translations/fr.js +7 -0
- package/lib/commonjs/locale/translations/fr.js.map +1 -1
- package/lib/commonjs/locale/translations/hi.js +7 -0
- package/lib/commonjs/locale/translations/hi.js.map +1 -1
- package/lib/commonjs/locale/translations/id.js +7 -0
- package/lib/commonjs/locale/translations/id.js.map +1 -1
- package/lib/commonjs/locale/translations/it.js +7 -0
- package/lib/commonjs/locale/translations/it.js.map +1 -1
- package/lib/commonjs/locale/translations/ja.js +7 -0
- package/lib/commonjs/locale/translations/ja.js.map +1 -1
- package/lib/commonjs/locale/translations/pt.js +7 -0
- package/lib/commonjs/locale/translations/pt.js.map +1 -1
- package/lib/commonjs/locale/translations/ru.js +7 -0
- package/lib/commonjs/locale/translations/ru.js.map +1 -1
- package/lib/commonjs/locale/translations/tr.js +7 -0
- package/lib/commonjs/locale/translations/tr.js.map +1 -1
- package/lib/commonjs/locale/translations/zh.js +7 -0
- package/lib/commonjs/locale/translations/zh.js.map +1 -1
- package/lib/commonjs/message-bubble/MessageBubble.js +17 -11
- package/lib/commonjs/message-bubble/MessageBubble.js.map +1 -1
- package/lib/commonjs/message-media/StickerMessage.js +1 -1
- package/lib/commonjs/settings-modal/SettingsModal.js +19 -9
- package/lib/commonjs/settings-modal/SettingsModal.js.map +1 -1
- package/lib/module/ai-profile-card/AiProfileCard.js +185 -262
- package/lib/module/ai-profile-card/AiProfileCard.js.map +1 -1
- package/lib/module/chat-composer/SuggestionList.js +3 -1
- package/lib/module/chat-composer/SuggestionList.js.map +1 -1
- package/lib/module/chat-composer/SuggestionList.web.js +3 -1
- package/lib/module/chat-composer/SuggestionList.web.js.map +1 -1
- package/lib/module/chat-composer/create-suggestion-list.js +64 -10
- package/lib/module/chat-composer/create-suggestion-list.js.map +1 -1
- package/lib/module/chat-composer/messages.js +7 -0
- package/lib/module/chat-composer/messages.js.map +1 -1
- package/lib/module/cover-header/CoverHeader.js +78 -0
- package/lib/module/cover-header/CoverHeader.js.map +1 -0
- package/lib/module/cover-header/CoverImage.js +68 -0
- package/lib/module/cover-header/CoverImage.js.map +1 -0
- package/lib/module/cover-header/index.js +4 -0
- package/lib/module/cover-header/index.js.map +1 -0
- package/lib/module/cover-header/types.js +4 -0
- package/lib/module/cover-header/types.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/locale/translations/ar.js +7 -0
- package/lib/module/locale/translations/ar.js.map +1 -1
- package/lib/module/locale/translations/bn.js +7 -0
- package/lib/module/locale/translations/bn.js.map +1 -1
- package/lib/module/locale/translations/ca.js +7 -0
- package/lib/module/locale/translations/ca.js.map +1 -1
- package/lib/module/locale/translations/de.js +7 -0
- package/lib/module/locale/translations/de.js.map +1 -1
- package/lib/module/locale/translations/es.js +7 -0
- package/lib/module/locale/translations/es.js.map +1 -1
- package/lib/module/locale/translations/fr.js +7 -0
- package/lib/module/locale/translations/fr.js.map +1 -1
- package/lib/module/locale/translations/hi.js +7 -0
- package/lib/module/locale/translations/hi.js.map +1 -1
- package/lib/module/locale/translations/id.js +7 -0
- package/lib/module/locale/translations/id.js.map +1 -1
- package/lib/module/locale/translations/it.js +7 -0
- package/lib/module/locale/translations/it.js.map +1 -1
- package/lib/module/locale/translations/ja.js +7 -0
- package/lib/module/locale/translations/ja.js.map +1 -1
- package/lib/module/locale/translations/pt.js +7 -0
- package/lib/module/locale/translations/pt.js.map +1 -1
- package/lib/module/locale/translations/ru.js +7 -0
- package/lib/module/locale/translations/ru.js.map +1 -1
- package/lib/module/locale/translations/tr.js +7 -0
- package/lib/module/locale/translations/tr.js.map +1 -1
- package/lib/module/locale/translations/zh.js +7 -0
- package/lib/module/locale/translations/zh.js.map +1 -1
- package/lib/module/message-bubble/MessageBubble.js +17 -11
- package/lib/module/message-bubble/MessageBubble.js.map +1 -1
- package/lib/module/message-media/StickerMessage.js +1 -1
- package/lib/module/settings-modal/SettingsModal.js +19 -9
- package/lib/module/settings-modal/SettingsModal.js.map +1 -1
- package/lib/typescript/commonjs/ai-profile-card/AiProfileCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/chat-composer/SuggestionList.d.ts +1 -1
- package/lib/typescript/commonjs/chat-composer/SuggestionList.d.ts.map +1 -1
- package/lib/typescript/commonjs/chat-composer/SuggestionList.web.d.ts +1 -1
- package/lib/typescript/commonjs/chat-composer/SuggestionList.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/chat-composer/create-suggestion-list.d.ts +4 -2
- package/lib/typescript/commonjs/chat-composer/create-suggestion-list.d.ts.map +1 -1
- package/lib/typescript/commonjs/chat-composer/messages.d.ts +6 -0
- package/lib/typescript/commonjs/chat-composer/messages.d.ts.map +1 -1
- package/lib/typescript/commonjs/chat-composer/types.d.ts +17 -0
- package/lib/typescript/commonjs/chat-composer/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/cover-header/CoverHeader.d.ts +21 -0
- package/lib/typescript/commonjs/cover-header/CoverHeader.d.ts.map +1 -0
- package/lib/typescript/commonjs/cover-header/CoverImage.d.ts +11 -0
- package/lib/typescript/commonjs/cover-header/CoverImage.d.ts.map +1 -0
- package/lib/typescript/commonjs/cover-header/index.d.ts +3 -0
- package/lib/typescript/commonjs/cover-header/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/cover-header/types.d.ts +33 -0
- package/lib/typescript/commonjs/cover-header/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +1 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/ar.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/bn.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/ca.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/de.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/es.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/fr.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/hi.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/id.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/it.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/ja.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/pt.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/ru.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/tr.d.ts.map +1 -1
- package/lib/typescript/commonjs/locale/translations/zh.d.ts.map +1 -1
- package/lib/typescript/commonjs/message-bubble/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/commonjs/message-bubble/types.d.ts +13 -6
- package/lib/typescript/commonjs/message-bubble/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/message-media/StickerMessage.d.ts +1 -1
- package/lib/typescript/commonjs/settings-modal/SettingsModal.d.ts.map +1 -1
- package/lib/typescript/module/ai-profile-card/AiProfileCard.d.ts.map +1 -1
- package/lib/typescript/module/chat-composer/SuggestionList.d.ts +1 -1
- package/lib/typescript/module/chat-composer/SuggestionList.d.ts.map +1 -1
- package/lib/typescript/module/chat-composer/SuggestionList.web.d.ts +1 -1
- package/lib/typescript/module/chat-composer/SuggestionList.web.d.ts.map +1 -1
- package/lib/typescript/module/chat-composer/create-suggestion-list.d.ts +4 -2
- package/lib/typescript/module/chat-composer/create-suggestion-list.d.ts.map +1 -1
- package/lib/typescript/module/chat-composer/messages.d.ts +6 -0
- package/lib/typescript/module/chat-composer/messages.d.ts.map +1 -1
- package/lib/typescript/module/chat-composer/types.d.ts +17 -0
- package/lib/typescript/module/chat-composer/types.d.ts.map +1 -1
- package/lib/typescript/module/cover-header/CoverHeader.d.ts +21 -0
- package/lib/typescript/module/cover-header/CoverHeader.d.ts.map +1 -0
- package/lib/typescript/module/cover-header/CoverImage.d.ts +11 -0
- package/lib/typescript/module/cover-header/CoverImage.d.ts.map +1 -0
- package/lib/typescript/module/cover-header/index.d.ts +3 -0
- package/lib/typescript/module/cover-header/index.d.ts.map +1 -0
- package/lib/typescript/module/cover-header/types.d.ts +33 -0
- package/lib/typescript/module/cover-header/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +1 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/ar.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/bn.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/ca.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/de.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/es.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/fr.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/hi.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/id.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/it.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/ja.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/pt.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/ru.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/tr.d.ts.map +1 -1
- package/lib/typescript/module/locale/translations/zh.d.ts.map +1 -1
- package/lib/typescript/module/message-bubble/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/module/message-bubble/types.d.ts +13 -6
- package/lib/typescript/module/message-bubble/types.d.ts.map +1 -1
- package/lib/typescript/module/message-media/StickerMessage.d.ts +1 -1
- package/lib/typescript/module/settings-modal/SettingsModal.d.ts.map +1 -1
- package/package.json +18 -1
- package/src/ai-profile-card/AiProfileCard.tsx +159 -241
- package/src/chat-composer/SuggestionList.tsx +2 -1
- package/src/chat-composer/SuggestionList.web.tsx +2 -1
- package/src/chat-composer/create-suggestion-list.tsx +54 -8
- package/src/chat-composer/messages.ts +9 -0
- package/src/chat-composer/types.ts +17 -0
- package/src/cover-header/CoverHeader.tsx +75 -0
- package/src/cover-header/CoverImage.tsx +60 -0
- package/src/cover-header/index.ts +3 -0
- package/src/cover-header/types.ts +30 -0
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- package/src/locale/translations/ar.ts +3 -0
- package/src/locale/translations/bn.ts +3 -0
- package/src/locale/translations/ca.ts +3 -0
- package/src/locale/translations/de.ts +3 -0
- package/src/locale/translations/es.ts +3 -0
- package/src/locale/translations/fr.ts +3 -0
- package/src/locale/translations/hi.ts +3 -0
- package/src/locale/translations/id.ts +3 -0
- package/src/locale/translations/it.ts +3 -0
- package/src/locale/translations/ja.ts +3 -0
- package/src/locale/translations/pt.ts +3 -0
- package/src/locale/translations/ru.ts +3 -0
- package/src/locale/translations/tr.ts +3 -0
- package/src/locale/translations/zh.ts +3 -0
- package/src/message-bubble/MessageBubble.tsx +17 -11
- package/src/message-bubble/types.ts +13 -6
- package/src/message-media/StickerMessage.tsx +1 -1
- 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
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
<
|
|
190
|
-
{
|
|
191
|
-
|
|
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,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="
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
|
21
|
-
*
|
|
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
|
|
27
|
-
* translucent pill, under an inset one where it cannot cover a line
|
|
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`)
|
|
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 `
|
|
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
|
*
|