@repzo/widget-react-native 1.0.1 → 1.0.5
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/lib/commonjs/api/client.js +3 -1
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/api/realtime.js +2 -0
- package/lib/commonjs/api/realtime.js.map +1 -1
- package/lib/commonjs/i18n.js +26 -0
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/locales.js +5 -0
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +129 -10
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +97 -66
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/Gradient.js +18 -2
- package/lib/commonjs/ui/components/Gradient.js.map +1 -1
- package/lib/commonjs/ui/components/TeamAvatars.js +63 -0
- package/lib/commonjs/ui/components/TeamAvatars.js.map +1 -0
- package/lib/commonjs/ui/components/icons.js +50 -0
- package/lib/commonjs/ui/components/icons.js.map +1 -1
- package/lib/commonjs/ui/hooks/use-top-inset.js +22 -0
- package/lib/commonjs/ui/hooks/use-top-inset.js.map +1 -0
- package/lib/commonjs/ui/screens/chat/ChatView.js +113 -7
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +51 -0
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/commonjs/ui/screens/home/HomeView.js +42 -9
- package/lib/commonjs/ui/screens/home/HomeView.js.map +1 -1
- package/lib/commonjs/ui/screens/home/HomeView.styles.js +5 -3
- package/lib/commonjs/ui/screens/home/HomeView.styles.js.map +1 -1
- package/lib/module/api/client.js +3 -1
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/api/realtime.js +2 -0
- package/lib/module/api/realtime.js.map +1 -1
- package/lib/module/i18n.js +25 -0
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/locales.js +5 -0
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/widget-store.js +129 -10
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +101 -70
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/Gradient.js +18 -2
- package/lib/module/ui/components/Gradient.js.map +1 -1
- package/lib/module/ui/components/TeamAvatars.js +59 -0
- package/lib/module/ui/components/TeamAvatars.js.map +1 -0
- package/lib/module/ui/components/icons.js +49 -0
- package/lib/module/ui/components/icons.js.map +1 -1
- package/lib/module/ui/hooks/use-top-inset.js +17 -0
- package/lib/module/ui/hooks/use-top-inset.js.map +1 -0
- package/lib/module/ui/screens/chat/ChatView.js +115 -9
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +51 -0
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/ui/screens/home/HomeView.js +44 -12
- package/lib/module/ui/screens/home/HomeView.js.map +1 -1
- package/lib/module/ui/screens/home/HomeView.styles.js +4 -2
- package/lib/module/ui/screens/home/HomeView.styles.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/api/realtime.d.ts.map +1 -1
- package/lib/typescript/commonjs/i18n.d.ts +7 -0
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +24 -0
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +14 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Gradient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/use-top-inset.d.ts +11 -0
- package/lib/typescript/commonjs/ui/hooks/use-top-inset.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +51 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +13 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/home/HomeView.d.ts +7 -0
- package/lib/typescript/commonjs/ui/screens/home/HomeView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/home/HomeView.styles.d.ts +3 -1
- package/lib/typescript/commonjs/ui/screens/home/HomeView.styles.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/api/realtime.d.ts.map +1 -1
- package/lib/typescript/module/i18n.d.ts +7 -0
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +24 -0
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +14 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Gradient.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/icons.d.ts +2 -0
- package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/use-top-inset.d.ts +11 -0
- package/lib/typescript/module/ui/hooks/use-top-inset.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +51 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +13 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/home/HomeView.d.ts +7 -0
- package/lib/typescript/module/ui/screens/home/HomeView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/home/HomeView.styles.d.ts +3 -1
- package/lib/typescript/module/ui/screens/home/HomeView.styles.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/client.ts +3 -1
- package/src/api/realtime.ts +2 -0
- package/src/i18n.ts +28 -0
- package/src/locales.ts +5 -0
- package/src/state/widget-store.ts +124 -8
- package/src/types.ts +16 -0
- package/src/ui/WidgetProvider.tsx +66 -7
- package/src/ui/components/Gradient.tsx +19 -2
- package/src/ui/components/TeamAvatars.tsx +58 -0
- package/src/ui/components/icons.tsx +37 -0
- package/src/ui/hooks/use-top-inset.ts +16 -0
- package/src/ui/screens/chat/ChatView.styles.ts +51 -0
- package/src/ui/screens/chat/ChatView.tsx +120 -22
- package/src/ui/screens/chat/ChatView.types.ts +13 -0
- package/src/ui/screens/home/HomeView.styles.ts +4 -2
- package/src/ui/screens/home/HomeView.tsx +36 -8
|
@@ -47,11 +47,13 @@ import { Avatar } from '../../components/Avatar';
|
|
|
47
47
|
import { ImageViewer } from '../../components/ImageViewer';
|
|
48
48
|
import { WidgetErrorBoundary } from '../../components/ErrorBoundary';
|
|
49
49
|
import { GradientBackground } from '../../components/Gradient';
|
|
50
|
-
import { ArrowUpIcon, ChatOutlineIcon, ChevronIcon, PaperclipIcon } from '../../components/icons';
|
|
50
|
+
import { ArrowUpIcon, ChatOutlineIcon, ChevronIcon, ClockIcon, PaperclipIcon } from '../../components/icons';
|
|
51
|
+
import { TeamAvatars } from '../../components/TeamAvatars';
|
|
51
52
|
import { Markdown } from '../../components/Markdown';
|
|
52
53
|
import { useReduceMotion } from '../../hooks/use-reduce-motion';
|
|
54
|
+
import { useTopInset } from '../../hooks/use-top-inset';
|
|
53
55
|
import { globalStyles } from '../../styles/global';
|
|
54
|
-
import { contrastingTextColor, mixHex, palette, withAlpha } from '../../styles/theme';
|
|
56
|
+
import { contrastingTextColor, mixHex, palette, spacing, withAlpha } from '../../styles/theme';
|
|
55
57
|
import { formatClockTime, formatDayLabel, isSameDay } from '../../utils/conversations';
|
|
56
58
|
import { stripHtml } from '../../utils/html';
|
|
57
59
|
import { isHttpUrl, openExternalUrl } from '../../utils/safe-url';
|
|
@@ -67,7 +69,11 @@ export function ChatView({
|
|
|
67
69
|
color,
|
|
68
70
|
agentName,
|
|
69
71
|
agentAvatar,
|
|
72
|
+
agentOnline,
|
|
70
73
|
isOnline,
|
|
74
|
+
teamName,
|
|
75
|
+
teamAvatars = [],
|
|
76
|
+
replyTimeLabel,
|
|
71
77
|
locale,
|
|
72
78
|
prefillText,
|
|
73
79
|
offlineMessage,
|
|
@@ -309,8 +315,20 @@ export function ChatView({
|
|
|
309
315
|
const headerTextColor = contrastingTextColor(color);
|
|
310
316
|
const onDarkHeader = headerTextColor === palette.white;
|
|
311
317
|
const isEmptyThread = !loading && messages.length === 0;
|
|
318
|
+
const agentIsActive = agentOnline ?? isOnline;
|
|
319
|
+
// "Waiting for a teammate" until someone replies: no teammate in the header
|
|
320
|
+
// and the visitor spoke last (bot replies before that still count as waiting).
|
|
321
|
+
const lastChatMessage = [...messages]
|
|
322
|
+
.reverse()
|
|
323
|
+
.find((message) => (message.messageType ?? 'message') === 'message' && !message.deletedAt);
|
|
324
|
+
const awaitingTeammate = !agentName && !agentTyping && lastChatMessage?.direction === 'inbound';
|
|
325
|
+
// "Sent" / "Seen" goes under the visitor's latest message only.
|
|
326
|
+
const lastVisitorMessageId = [...messages]
|
|
327
|
+
.reverse()
|
|
328
|
+
.find((message) => message.direction === 'inbound' && (message.messageType ?? 'message') === 'message')?.id;
|
|
312
329
|
// Two stops off the brand color: flat fill looked like an unstyled block.
|
|
313
330
|
// A non-hex brand color falls through mixHex unchanged and renders solid.
|
|
331
|
+
const topInset = useTopInset();
|
|
314
332
|
const headerGradient = [color, mixHex(color, '#000000', 0.16)];
|
|
315
333
|
const headerControl = { backgroundColor: withAlpha(headerTextColor, 0.16) };
|
|
316
334
|
|
|
@@ -320,7 +338,12 @@ export function ChatView({
|
|
|
320
338
|
was empty whenever no agent was assigned yet. */}
|
|
321
339
|
<GradientBackground
|
|
322
340
|
colors={headerGradient}
|
|
323
|
-
style={[
|
|
341
|
+
style={[
|
|
342
|
+
styles.header,
|
|
343
|
+
{ borderBottomColor: mixHex(color, '#000000', 0.26) },
|
|
344
|
+
// Reaches under the status bar so the brand colour runs to the top.
|
|
345
|
+
topInset > 0 ? { paddingTop: spacing.md + topInset, minHeight: 72 + topInset } : null,
|
|
346
|
+
]}>
|
|
324
347
|
<Pressable
|
|
325
348
|
onPress={onBack}
|
|
326
349
|
accessibilityRole="button"
|
|
@@ -334,28 +357,57 @@ export function ChatView({
|
|
|
334
357
|
<ChevronIcon size={18} direction={backDirection()} color={headerTextColor} />
|
|
335
358
|
</Pressable>
|
|
336
359
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
360
|
+
{agentName ? (
|
|
361
|
+
// A teammate joined: their photo, name and own presence.
|
|
362
|
+
<View style={styles.headerIdentity}>
|
|
363
|
+
<View style={styles.avatarWrapper}>
|
|
364
|
+
<Avatar
|
|
365
|
+
avatar={agentAvatar}
|
|
366
|
+
name={agentName}
|
|
367
|
+
size={36}
|
|
368
|
+
translucent={onDarkHeader}
|
|
369
|
+
color={palette.ink}
|
|
370
|
+
/>
|
|
371
|
+
{agentIsActive ? <View style={styles.onlineDot} /> : null}
|
|
372
|
+
</View>
|
|
373
|
+
<View style={styles.headerText}>
|
|
374
|
+
<Text style={[styles.headerName, { color: headerTextColor }]} numberOfLines={1}>
|
|
375
|
+
{agentName}
|
|
376
|
+
</Text>
|
|
377
|
+
<Text
|
|
378
|
+
style={[styles.headerStatus, { color: withAlpha(headerTextColor, 0.75) }]}
|
|
379
|
+
numberOfLines={1}>
|
|
380
|
+
{agentIsActive ? labels.statusOnline : labels.statusAway}
|
|
381
|
+
</Text>
|
|
382
|
+
</View>
|
|
383
|
+
</View>
|
|
384
|
+
) : (
|
|
385
|
+
// No teammate yet, or the conversation closed: the team, Intercom-style.
|
|
386
|
+
<View style={styles.headerIdentity}>
|
|
387
|
+
<TeamAvatars
|
|
388
|
+
avatars={teamAvatars}
|
|
389
|
+
size={30}
|
|
344
390
|
color={palette.ink}
|
|
391
|
+
ringColor={color}
|
|
392
|
+
translucent={onDarkHeader}
|
|
345
393
|
/>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
394
|
+
<View style={styles.headerText}>
|
|
395
|
+
<Text style={[styles.headerName, { color: headerTextColor }]} numberOfLines={1}>
|
|
396
|
+
{teamName || labels.team}
|
|
397
|
+
</Text>
|
|
398
|
+
{replyTimeLabel ? (
|
|
399
|
+
<View style={styles.headerReplyTime}>
|
|
400
|
+
<ClockIcon size={12} color={withAlpha(headerTextColor, 0.75)} />
|
|
401
|
+
<Text
|
|
402
|
+
style={[styles.headerStatus, styles.headerReplyTimeText, { color: withAlpha(headerTextColor, 0.75) }]}
|
|
403
|
+
numberOfLines={1}>
|
|
404
|
+
{replyTimeLabel}
|
|
405
|
+
</Text>
|
|
406
|
+
</View>
|
|
407
|
+
) : null}
|
|
408
|
+
</View>
|
|
357
409
|
</View>
|
|
358
|
-
|
|
410
|
+
)}
|
|
359
411
|
|
|
360
412
|
<Pressable
|
|
361
413
|
onPress={onClose}
|
|
@@ -443,6 +495,7 @@ export function ChatView({
|
|
|
443
495
|
: null
|
|
444
496
|
}
|
|
445
497
|
timeLabel={formatClockTime(message.createdAt, locale)}
|
|
498
|
+
receiptLabel={message.id === lastVisitorMessageId ? receiptLabel(message, labels) : undefined}
|
|
446
499
|
onSubmitProcedureForm={
|
|
447
500
|
shouldShowProcedureForm(messages, message)
|
|
448
501
|
? (body) => void onSendMessage(body).catch(() => {})
|
|
@@ -457,6 +510,13 @@ export function ChatView({
|
|
|
457
510
|
);
|
|
458
511
|
})}
|
|
459
512
|
|
|
513
|
+
{awaitingTeammate ? (
|
|
514
|
+
<View style={styles.waitingRow}>
|
|
515
|
+
<TeamAvatars avatars={teamAvatars} size={20} color={color} />
|
|
516
|
+
<Text style={styles.waitingText}>{labels.waitingForTeammate}</Text>
|
|
517
|
+
</View>
|
|
518
|
+
) : null}
|
|
519
|
+
|
|
460
520
|
{agentTyping ? (
|
|
461
521
|
<View style={styles.typingRow}>
|
|
462
522
|
<View style={styles.typingBubble}>
|
|
@@ -576,6 +636,7 @@ function MessageBubble({
|
|
|
576
636
|
labels,
|
|
577
637
|
senderLabel,
|
|
578
638
|
timeLabel,
|
|
639
|
+
receiptLabel,
|
|
579
640
|
onSubmitProcedureForm,
|
|
580
641
|
onAgentUiAction,
|
|
581
642
|
onOpenImage,
|
|
@@ -585,10 +646,28 @@ function MessageBubble({
|
|
|
585
646
|
labels: ChatLabels;
|
|
586
647
|
senderLabel?: string | null;
|
|
587
648
|
timeLabel: string;
|
|
649
|
+
receiptLabel?: string;
|
|
588
650
|
onSubmitProcedureForm?: (body: string) => void;
|
|
589
651
|
onAgentUiAction?: (action: AgentUiAction) => void | Promise<void>;
|
|
590
652
|
onOpenImage: (image: { url: string; name: string }) => void;
|
|
591
653
|
}) {
|
|
654
|
+
const joinedName = message.metadata?.event === 'agent_joined' ? message.metadata?.agentName : null;
|
|
655
|
+
if (message.messageType === 'system' && typeof joinedName === 'string' && joinedName) {
|
|
656
|
+
return (
|
|
657
|
+
<View style={[styles.systemRow, styles.joinedRow]}>
|
|
658
|
+
<Avatar
|
|
659
|
+
avatar={typeof message.metadata?.agentAvatar === 'string' ? message.metadata.agentAvatar : null}
|
|
660
|
+
name={joinedName}
|
|
661
|
+
size={20}
|
|
662
|
+
color={palette.ink}
|
|
663
|
+
/>
|
|
664
|
+
<Text style={styles.systemText}>
|
|
665
|
+
<Text style={styles.joinedName}>{joinedName}</Text> {labels.joinedConversation}
|
|
666
|
+
</Text>
|
|
667
|
+
</View>
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
592
671
|
if (message.messageType === 'system') {
|
|
593
672
|
const text = message.bodyPlain || stripHtml(message.body || '') || labels.conversationUpdated;
|
|
594
673
|
return (
|
|
@@ -599,6 +678,17 @@ function MessageBubble({
|
|
|
599
678
|
}
|
|
600
679
|
|
|
601
680
|
const isVisitor = message.direction === 'inbound';
|
|
681
|
+
|
|
682
|
+
if (message.deletedAt) {
|
|
683
|
+
return (
|
|
684
|
+
<View style={[styles.messageRow, isVisitor ? styles.messageRowRight : styles.messageRowLeft]}>
|
|
685
|
+
<View style={styles.deletedBubble}>
|
|
686
|
+
<Text style={styles.deletedText}>{labels.messageDeleted}</Text>
|
|
687
|
+
</View>
|
|
688
|
+
</View>
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
602
692
|
const rtl = isRTL();
|
|
603
693
|
const messageText = message.bodyPlain || stripHtml(message.body || '');
|
|
604
694
|
const hasBody = !!messageText;
|
|
@@ -658,6 +748,8 @@ function MessageBubble({
|
|
|
658
748
|
) : null}
|
|
659
749
|
</View>
|
|
660
750
|
|
|
751
|
+
{receiptLabel ? <Text style={styles.receiptText}>{receiptLabel}</Text> : null}
|
|
752
|
+
|
|
661
753
|
{agentUiBlocks.length > 0 && onAgentUiAction ? (
|
|
662
754
|
<AgentUiBlocks
|
|
663
755
|
blocks={agentUiBlocks}
|
|
@@ -679,6 +771,12 @@ function MessageBubble({
|
|
|
679
771
|
);
|
|
680
772
|
}
|
|
681
773
|
|
|
774
|
+
/** Sent once the server has it, Seen once a teammate opened the conversation. */
|
|
775
|
+
function receiptLabel(message: Message, labels: ChatLabels): string | undefined {
|
|
776
|
+
if (message.deletedAt || message.status === 'pending' || message.status === 'failed') return undefined;
|
|
777
|
+
return message.status === 'read' ? labels.messageSeen : labels.messageSent;
|
|
778
|
+
}
|
|
779
|
+
|
|
682
780
|
function AttachmentPreview({
|
|
683
781
|
attachment,
|
|
684
782
|
isVisitor,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TeamAvatar } from '../../../types';
|
|
1
2
|
/** Props and label bundle for the chat thread. */
|
|
2
3
|
|
|
3
4
|
import type { DraftAttachment, PickResult } from '../../../attachments/pickers';
|
|
@@ -25,6 +26,11 @@ export interface ChatLabels {
|
|
|
25
26
|
today: string;
|
|
26
27
|
yesterday: string;
|
|
27
28
|
conversationUpdated: string;
|
|
29
|
+
messageDeleted: string;
|
|
30
|
+
messageSent: string;
|
|
31
|
+
messageSeen: string;
|
|
32
|
+
waitingForTeammate: string;
|
|
33
|
+
joinedConversation: string;
|
|
28
34
|
procedureSend: string;
|
|
29
35
|
procedureSent: string;
|
|
30
36
|
blocks: AgentUiBlockLabels;
|
|
@@ -56,7 +62,14 @@ export interface ChatViewProps {
|
|
|
56
62
|
/** Whoever is handling this thread, once the backend assigns someone. */
|
|
57
63
|
agentName?: string | null;
|
|
58
64
|
agentAvatar?: string | null;
|
|
65
|
+
/** The assigned teammate's own presence; falls back to `isOnline` when absent. */
|
|
66
|
+
agentOnline?: boolean | null;
|
|
59
67
|
isOnline: boolean;
|
|
68
|
+
/** Header title with no teammate yet (or after close); falls back to the Team label. */
|
|
69
|
+
teamName?: string | null;
|
|
70
|
+
teamAvatars?: TeamAvatar[];
|
|
71
|
+
/** e.g. "We usually reply within a few minutes", under the team name. */
|
|
72
|
+
replyTimeLabel?: string;
|
|
60
73
|
prefillText?: string;
|
|
61
74
|
offlineMessage?: string;
|
|
62
75
|
locale: string;
|
|
@@ -23,6 +23,9 @@ export const START_CARD_LIFT = 28;
|
|
|
23
23
|
export const START_CARD_MIN_HEIGHT = 76;
|
|
24
24
|
export const HEADER_FADE_HEIGHT = 90;
|
|
25
25
|
export const CLOSE_BUTTON_SIZE = 28;
|
|
26
|
+
/** The logo keeps its own shape at this height, up to the web widget's max width. */
|
|
27
|
+
export const LOGO_HEIGHT = 40;
|
|
28
|
+
export const LOGO_MAX_WIDTH = 152;
|
|
26
29
|
export const CLOSE_ICON_SIZE = 14;
|
|
27
30
|
|
|
28
31
|
export const styles = StyleSheet.create({
|
|
@@ -58,8 +61,7 @@ export const styles = StyleSheet.create({
|
|
|
58
61
|
minHeight: 40,
|
|
59
62
|
},
|
|
60
63
|
logo: {
|
|
61
|
-
|
|
62
|
-
height: 40,
|
|
64
|
+
height: LOGO_HEIGHT,
|
|
63
65
|
},
|
|
64
66
|
closeButton: {
|
|
65
67
|
marginStart: 'auto',
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* send today.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { Fragment, useEffect, useRef, type ReactNode } from 'react';
|
|
16
|
+
import { Fragment, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
17
17
|
import {
|
|
18
18
|
Animated,
|
|
19
19
|
Easing,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
CloseIcon,
|
|
38
38
|
} from '../../components/icons';
|
|
39
39
|
import { useReduceMotion } from '../../hooks/use-reduce-motion';
|
|
40
|
+
import { useTopInset } from '../../hooks/use-top-inset';
|
|
40
41
|
import { globalStyles } from '../../styles/global';
|
|
41
42
|
import { contrastingTextColor, palette, spacing, withAlpha } from '../../styles/theme';
|
|
42
43
|
import { flipForRTL } from '../../utils/rtl';
|
|
@@ -45,6 +46,9 @@ import {
|
|
|
45
46
|
CLOSE_ICON_SIZE,
|
|
46
47
|
HEADER_BOTTOM_PADDING,
|
|
47
48
|
HEADER_FADE_HEIGHT,
|
|
49
|
+
HEADER_MIN_HEIGHT,
|
|
50
|
+
LOGO_HEIGHT,
|
|
51
|
+
LOGO_MAX_WIDTH,
|
|
48
52
|
START_CARD_LIFT,
|
|
49
53
|
styles,
|
|
50
54
|
} from './HomeView.styles';
|
|
@@ -55,7 +59,7 @@ import type { HomeViewProps } from './HomeView.types';
|
|
|
55
59
|
* it, and defaulting to dark text over a dark brand header is unreadable, so
|
|
56
60
|
* derive something legible from the background instead.
|
|
57
61
|
*/
|
|
58
|
-
function resolveHeaderTextColor(config: WidgetConfig | null, fallbackColor: string): string {
|
|
62
|
+
export function resolveHeaderTextColor(config: WidgetConfig | null, fallbackColor: string): string {
|
|
59
63
|
if (config?.homeHeaderTextColor === 'white') return palette.white;
|
|
60
64
|
if (config?.homeHeaderTextColor === 'black') return palette.ink;
|
|
61
65
|
if (config?.homeBackgroundType === 'image') return palette.white;
|
|
@@ -116,8 +120,14 @@ export function HomeView({
|
|
|
116
120
|
|
|
117
121
|
// The start card is pulled up over the seam by a fixed amount, so the header
|
|
118
122
|
// pads its bottom by that same amount and the greeting never slides under it.
|
|
123
|
+
// The header reaches up under the status bar (no white strip above it), so
|
|
124
|
+
// it grows by that inset and keeps its content where it was.
|
|
125
|
+
const topInset = useTopInset();
|
|
119
126
|
const headerStyle = [
|
|
120
127
|
styles.header,
|
|
128
|
+
topInset > 0
|
|
129
|
+
? { paddingTop: spacing.xl + topInset, minHeight: HEADER_MIN_HEIGHT + topInset }
|
|
130
|
+
: null,
|
|
121
131
|
allowNewConversations ? { paddingBottom: HEADER_BOTTOM_PADDING + START_CARD_LIFT } : null,
|
|
122
132
|
];
|
|
123
133
|
|
|
@@ -125,12 +135,7 @@ export function HomeView({
|
|
|
125
135
|
<>
|
|
126
136
|
<View style={styles.headerTop}>
|
|
127
137
|
{config?.homeLogoUrl ? (
|
|
128
|
-
<
|
|
129
|
-
source={{ uri: config.homeLogoUrl }}
|
|
130
|
-
style={styles.logo}
|
|
131
|
-
resizeMode="contain"
|
|
132
|
-
accessibilityIgnoresInvertColors
|
|
133
|
-
/>
|
|
138
|
+
<HomeLogo key={config.homeLogoUrl} uri={config.homeLogoUrl} />
|
|
134
139
|
) : (
|
|
135
140
|
<View />
|
|
136
141
|
)}
|
|
@@ -296,6 +301,29 @@ export function HomeView({
|
|
|
296
301
|
);
|
|
297
302
|
}
|
|
298
303
|
|
|
304
|
+
/**
|
|
305
|
+
* The workspace logo at its own shape, flush with the greeting's left edge.
|
|
306
|
+
* A fixed-width box with `contain` centred a square logo inside it, so it
|
|
307
|
+
* floated in from the edge; the web widget sizes it to the image instead.
|
|
308
|
+
* Square until the image reports its size, then as wide as its ratio needs,
|
|
309
|
+
* capped like the web widget.
|
|
310
|
+
*/
|
|
311
|
+
function HomeLogo({ uri }: { uri: string }) {
|
|
312
|
+
const [aspectRatio, setAspectRatio] = useState(1);
|
|
313
|
+
return (
|
|
314
|
+
<Image
|
|
315
|
+
source={{ uri }}
|
|
316
|
+
style={[styles.logo, { width: Math.min(LOGO_MAX_WIDTH, LOGO_HEIGHT * aspectRatio) }]}
|
|
317
|
+
resizeMode="contain"
|
|
318
|
+
accessibilityIgnoresInvertColors
|
|
319
|
+
onLoad={(event) => {
|
|
320
|
+
const { width, height } = event.nativeEvent.source ?? {};
|
|
321
|
+
if (width && height) setAspectRatio(width / height);
|
|
322
|
+
}}
|
|
323
|
+
/>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
299
327
|
/**
|
|
300
328
|
* Staggered entrance for the body sections. Opacity and translate only, so the
|
|
301
329
|
* whole thing runs on the native driver and never touches layout.
|